| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- import re
- from fqdn._compat import cached_property
- class FQDN:
- """
- From https://tools.ietf.org/html/rfc1035#page-9, RFC 1035 3.1. Name space
- definitions:
- Domain names in messages are expressed in terms of a sequence of
- labels. Each label is represented as a one octet length field followed
- by that number of octets. Since every domain name ends with the null
- label of the root, a domain name is terminated by a length byte of
- zero. The high order two bits of every length octet must be zero, and
- the remaining six bits of the length field limit the label to 63 octets
- or less.
- To simplify implementations, the total length of a domain name (i.e.,
- label octets and label length octets) is restricted to 255 octets or
- less.
- Therefore the max length of a domain name is actually 253 ASCII bytes
- without the trailing null byte or the leading length byte, and the max
- length of a label is 63 bytes without the leading length byte.
- """
- PREFERRED_NAME_SYNTAX_REGEXSTR = (
- r"^((?![-])[-A-Z\d]{1,63}(?<!-)[.])*(?!-)[-A-Z\d]{1,63}(?<!-)[.]?$"
- )
- ALLOW_UNDERSCORES_REGEXSTR = (
- r"^((?![-])[-_A-Z\d]{1,63}(?<!-)[.])*(?!-)[-_A-Z\d]{1,63}(?<!-)[.]?$"
- )
- def __init__(self, fqdn, *nothing, **kwargs):
- if nothing:
- raise ValueError("got extra positional parameter, try kwargs")
- unknown_kwargs = set(kwargs.keys()) - {"allow_underscores", "min_labels"}
- if unknown_kwargs:
- raise ValueError("got extra kwargs: {}".format(unknown_kwargs))
- if not (fqdn and isinstance(fqdn, str)):
- raise ValueError("fqdn must be str")
- self._fqdn = fqdn.lower()
- self._allow_underscores = kwargs.get("allow_underscores", False)
- self._min_labels = kwargs.get("min_labels", 2)
- def __str__(self):
- """
- The FQDN as a string in absolute form
- """
- return self.absolute
- @property
- def _regex(self):
- regexstr = (
- FQDN.PREFERRED_NAME_SYNTAX_REGEXSTR
- if not self._allow_underscores
- else FQDN.ALLOW_UNDERSCORES_REGEXSTR
- )
- return re.compile(regexstr, re.IGNORECASE)
- @cached_property
- def is_valid(self):
- """
- True for a validated fully-qualified domain nam (FQDN), in full
- compliance with RFC 1035, and the "preferred form" specified in RFC
- 3686 s. 2, whether relative or absolute.
- https://tools.ietf.org/html/rfc3696#section-2
- https://tools.ietf.org/html/rfc1035
- If and only if the FQDN ends with a dot (in place of the RFC1035
- trailing null byte), it may have a total length of 254 bytes, still it
- must be less than 253 bytes.
- """
- length = len(self._fqdn)
- if self._fqdn.endswith("."):
- length -= 1
- if length > 253:
- return False
- regex_pass = self._regex.match(self._fqdn)
- if not regex_pass:
- return False
- return self.labels_count >= self._min_labels
- @property
- def labels_count(self):
- has_terminal_dot = self._fqdn[-1] == "."
- count = self._fqdn.count(".") + (0 if has_terminal_dot else 1)
- return count
- @cached_property
- def is_valid_absolute(self):
- """
- True for a fully-qualified domain name (FQDN) that is RFC
- preferred-form compliant and ends with a `.`.
- With relative FQDNS in DNS lookups, the current hosts domain name or
- search domains may be appended.
- """
- return self._fqdn.endswith(".") and self.is_valid
- @cached_property
- def is_valid_relative(self):
- """
- True for a validated fully-qualified domain name that compiles with the
- RFC preferred-form and does not ends with a `.`.
- """
- return not self._fqdn.endswith(".") and self.is_valid
- @cached_property
- def absolute(self):
- """
- The FQDN as a string in absolute form
- """
- if not self.is_valid:
- raise ValueError("invalid FQDN `{0}`".format(self._fqdn))
- if self.is_valid_absolute:
- return self._fqdn
- return "{0}.".format(self._fqdn)
- @cached_property
- def relative(self):
- """
- The FQDN as a string in relative form
- """
- if not self.is_valid:
- raise ValueError("invalid FQDN `{0}`".format(self._fqdn))
- if self.is_valid_absolute:
- return self._fqdn[:-1]
- return self._fqdn
- def __eq__(self, other):
- if isinstance(other, FQDN):
- return self.absolute == other.absolute
- def __hash__(self):
- return hash(self.absolute) + hash("fqdn")
|