Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/fqdn/__init__.py: 37%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import re
3from fqdn._compat import cached_property
6class FQDN:
7 """
8 From https://tools.ietf.org/html/rfc1035#page-9, RFC 1035 3.1. Name space
9 definitions:
11 Domain names in messages are expressed in terms of a sequence of
12 labels. Each label is represented as a one octet length field followed
13 by that number of octets. Since every domain name ends with the null
14 label of the root, a domain name is terminated by a length byte of
15 zero. The high order two bits of every length octet must be zero, and
16 the remaining six bits of the length field limit the label to 63 octets
17 or less.
19 To simplify implementations, the total length of a domain name (i.e.,
20 label octets and label length octets) is restricted to 255 octets or
21 less.
24 Therefore the max length of a domain name is actually 253 ASCII bytes
25 without the trailing null byte or the leading length byte, and the max
26 length of a label is 63 bytes without the leading length byte.
27 """
29 PREFERRED_NAME_SYNTAX_REGEXSTR = (
30 r"^((?![-])[-A-Z\d]{1,63}(?<!-)[.])*(?!-)[-A-Z\d]{1,63}(?<!-)[.]?$"
31 )
32 ALLOW_UNDERSCORES_REGEXSTR = (
33 r"^((?![-])[-_A-Z\d]{1,63}(?<!-)[.])*(?!-)[-_A-Z\d]{1,63}(?<!-)[.]?$"
34 )
36 def __init__(self, fqdn, *nothing, **kwargs):
37 if nothing:
38 raise ValueError("got extra positional parameter, try kwargs")
39 unknown_kwargs = set(kwargs.keys()) - {"allow_underscores", "min_labels"}
40 if unknown_kwargs:
41 raise ValueError("got extra kwargs: {}".format(unknown_kwargs))
43 if not (fqdn and isinstance(fqdn, str)):
44 raise ValueError("fqdn must be str")
45 self._fqdn = fqdn.lower()
46 self._allow_underscores = kwargs.get("allow_underscores", False)
47 self._min_labels = kwargs.get("min_labels", 2)
49 def __str__(self):
50 """
51 The FQDN as a string in absolute form
52 """
53 return self.absolute
55 @property
56 def _regex(self):
57 regexstr = (
58 FQDN.PREFERRED_NAME_SYNTAX_REGEXSTR
59 if not self._allow_underscores
60 else FQDN.ALLOW_UNDERSCORES_REGEXSTR
61 )
62 return re.compile(regexstr, re.IGNORECASE)
64 @cached_property
65 def is_valid(self):
66 """
67 True for a validated fully-qualified domain nam (FQDN), in full
68 compliance with RFC 1035, and the "preferred form" specified in RFC
69 3686 s. 2, whether relative or absolute.
71 https://tools.ietf.org/html/rfc3696#section-2
72 https://tools.ietf.org/html/rfc1035
74 If and only if the FQDN ends with a dot (in place of the RFC1035
75 trailing null byte), it may have a total length of 254 bytes, still it
76 must be less than 253 bytes.
77 """
78 length = len(self._fqdn)
79 if self._fqdn.endswith("."):
80 length -= 1
81 if length > 253:
82 return False
83 regex_pass = self._regex.match(self._fqdn)
84 if not regex_pass:
85 return False
87 return self.labels_count >= self._min_labels
89 @property
90 def labels_count(self):
91 has_terminal_dot = self._fqdn[-1] == "."
92 count = self._fqdn.count(".") + (0 if has_terminal_dot else 1)
93 return count
95 @cached_property
96 def is_valid_absolute(self):
97 """
98 True for a fully-qualified domain name (FQDN) that is RFC
99 preferred-form compliant and ends with a `.`.
101 With relative FQDNS in DNS lookups, the current hosts domain name or
102 search domains may be appended.
103 """
104 return self._fqdn.endswith(".") and self.is_valid
106 @cached_property
107 def is_valid_relative(self):
108 """
109 True for a validated fully-qualified domain name that compiles with the
110 RFC preferred-form and does not ends with a `.`.
111 """
112 return not self._fqdn.endswith(".") and self.is_valid
114 @cached_property
115 def absolute(self):
116 """
117 The FQDN as a string in absolute form
118 """
119 if not self.is_valid:
120 raise ValueError("invalid FQDN `{0}`".format(self._fqdn))
122 if self.is_valid_absolute:
123 return self._fqdn
125 return "{0}.".format(self._fqdn)
127 @cached_property
128 def relative(self):
129 """
130 The FQDN as a string in relative form
131 """
132 if not self.is_valid:
133 raise ValueError("invalid FQDN `{0}`".format(self._fqdn))
135 if self.is_valid_absolute:
136 return self._fqdn[:-1]
138 return self._fqdn
140 def __eq__(self, other):
141 if isinstance(other, FQDN):
142 return self.absolute == other.absolute
144 def __hash__(self):
145 return hash(self.absolute) + hash("fqdn")