Completed
Push — master ( 96792e...fac29e )
by Chris
01:34
created

is_hostname()   A

Complexity

Conditions 2

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 20
rs 9.4285
1
import socket
2
3
from netaddr import iter_iprange
4
from netaddr.core import AddrFormatError
5
6
7
def is_ip(addr):
8
    """Determine if a string is really an ip, or a hostname instead.
9
10
    Args:
11
        addr (str): The ip address string to check
12
13
    Returns:
14
        bool: Whether or not `addr` is a valid ip.
15
    """
16
    if '.' not in addr:
17
        return False
18
    parts = addr.split('.')
19
    for part in parts:
20
        try:
21
            int(part)
22
        except ValueError:
23
            return False
24
    return True
25
26
27
def is_hostname(addr):
28
    """Determine if a string is a hostname.
29
30
    Based on https://en.wikipedia.org/wiki
31
        /Hostname#Restrictions_on_valid_hostnames
32
33
    Args:
34
        addr (str): The address string to check
35
36
    Returns:
37
        bool: Whether or not `addr` is a valid hostname.
38
    """
39
    if any([
40
        '_' in addr,
41
        '.' not in addr,
42
        addr.endswith('-'),
43
        is_ip(addr),
44
    ]):
45
        return False
46
    return True
47
48
49
def valid_hosts(formcls, field):
50
    """Validate a list of IPs (Ipv4) or hostnames using python stdlib.
51
52
    This is more robust than the WTForm version as it also considers hostnames.
53
54
    Comma separated values:
55
    e.g. '10.7.223.101,10.7.12.0'
56
    Space separated values:
57
    e.g. '10.223.101 10.7.223.102'
58
    Ranges:
59
    e.g. '10.7.223.200-10.7.224.10'
60
    Hostnames:
61
    e.g. foo.x.y.com, baz.bar.z.com
62
63
    :param formcls (object): The form class.
64
    :param field (str): The list of ips.
65
    """
66
    data = field.data
67
    if ',' in data:
68
        ips = [ip for ip in data.split(',') if ip]
69
    elif ' ' in data:
70
        ips = [ip for ip in data.split(' ') if ip]
71
    elif '-' in data:
72
        try:
73
            start, end = data.split('-')
74
            ips = iter_iprange(start, end)
75
            ips = [str(ip) for ip in list(ips)]
76
        except ValueError:
77
            raise ValueError(
78
                'Invalid range specified. Format should be: '
79
                'XXX.XXX.XXX.XXX-XXX.XXX.XXX.XXX '
80
                '(e.g. 10.7.223.200-10.7.224.10)')
81
        except AddrFormatError as e:
82
            raise ValueError(e)
83
    else:
84
        # Just use the single ip
85
        ips = [data]
86
    # If any fails conversion, it is invalid.
87
    for ip in ips:
88
        # Skip hostnames
89
        if not is_ip(ip):
90
            if not is_hostname(ip):
91
                raise ValueError('Invalid hostname: "{}"'.format(ip))
92
            else:
93
                continue
94
        try:
95
            socket.inet_aton(ip)
96
        except socket.error:
97
            raise ValueError('Invalid IP: {}'.format(ip))
98