You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			146 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			146 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Python
		
	
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")
 |