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

63 statements  

1import re 

2 

3from fqdn._compat import cached_property 

4 

5 

6class FQDN: 

7 """ 

8 From https://tools.ietf.org/html/rfc1035#page-9, RFC 1035 3.1. Name space 

9 definitions: 

10 

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. 

18 

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. 

22 

23 

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 """ 

28 

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 ) 

35 

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)) 

42 

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) 

48 

49 def __str__(self): 

50 """ 

51 The FQDN as a string in absolute form 

52 """ 

53 return self.absolute 

54 

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) 

63 

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. 

70 

71 https://tools.ietf.org/html/rfc3696#section-2 

72 https://tools.ietf.org/html/rfc1035 

73 

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 

86 

87 return self.labels_count >= self._min_labels 

88 

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 

94 

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 `.`. 

100 

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 

105 

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 

113 

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)) 

121 

122 if self.is_valid_absolute: 

123 return self._fqdn 

124 

125 return "{0}.".format(self._fqdn) 

126 

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)) 

134 

135 if self.is_valid_absolute: 

136 return self._fqdn[:-1] 

137 

138 return self._fqdn 

139 

140 def __eq__(self, other): 

141 if isinstance(other, FQDN): 

142 return self.absolute == other.absolute 

143 

144 def __hash__(self): 

145 return hash(self.absolute) + hash("fqdn")