Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/utils/encoding.py: 56%

95 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import codecs 

2import datetime 

3import locale 

4from decimal import Decimal 

5from urllib.parse import quote 

6 

7from django.utils.functional import Promise 

8 

9 

10class DjangoUnicodeDecodeError(UnicodeDecodeError): 

11 def __init__(self, obj, *args): 

12 self.obj = obj 

13 super().__init__(*args) 

14 

15 def __str__(self): 

16 return "%s. You passed in %r (%s)" % ( 

17 super().__str__(), 

18 self.obj, 

19 type(self.obj), 

20 ) 

21 

22 

23def smart_str(s, encoding="utf-8", strings_only=False, errors="strict"): 

24 """ 

25 Return a string representing 's'. Treat bytestrings using the 'encoding' 

26 codec. 

27 

28 If strings_only is True, don't convert (some) non-string-like objects. 

29 """ 

30 if isinstance(s, Promise): 30 ↛ 32line 30 didn't jump to line 32, because the condition on line 30 was never true

31 # The input is the result of a gettext_lazy() call. 

32 return s 

33 return force_str(s, encoding, strings_only, errors) 

34 

35 

36_PROTECTED_TYPES = ( 

37 type(None), 

38 int, 

39 float, 

40 Decimal, 

41 datetime.datetime, 

42 datetime.date, 

43 datetime.time, 

44) 

45 

46 

47def is_protected_type(obj): 

48 """Determine if the object instance is of a protected type. 

49 

50 Objects of protected types are preserved as-is when passed to 

51 force_str(strings_only=True). 

52 """ 

53 return isinstance(obj, _PROTECTED_TYPES) 

54 

55 

56def force_str(s, encoding="utf-8", strings_only=False, errors="strict"): 

57 """ 

58 Similar to smart_str(), except that lazy instances are resolved to 

59 strings, rather than kept as lazy objects. 

60 

61 If strings_only is True, don't convert (some) non-string-like objects. 

62 """ 

63 # Handle the common case first for performance reasons. 

64 if issubclass(type(s), str): 

65 return s 

66 if strings_only and is_protected_type(s): 66 ↛ 67line 66 didn't jump to line 67, because the condition on line 66 was never true

67 return s 

68 try: 

69 if isinstance(s, bytes): 

70 s = str(s, encoding, errors) 

71 else: 

72 s = str(s) 

73 except UnicodeDecodeError as e: 

74 raise DjangoUnicodeDecodeError(s, *e.args) 

75 return s 

76 

77 

78def smart_bytes(s, encoding="utf-8", strings_only=False, errors="strict"): 

79 """ 

80 Return a bytestring version of 's', encoded as specified in 'encoding'. 

81 

82 If strings_only is True, don't convert (some) non-string-like objects. 

83 """ 

84 if isinstance(s, Promise): 

85 # The input is the result of a gettext_lazy() call. 

86 return s 

87 return force_bytes(s, encoding, strings_only, errors) 

88 

89 

90def force_bytes(s, encoding="utf-8", strings_only=False, errors="strict"): 

91 """ 

92 Similar to smart_bytes, except that lazy instances are resolved to 

93 strings, rather than kept as lazy objects. 

94 

95 If strings_only is True, don't convert (some) non-string-like objects. 

96 """ 

97 # Handle the common case first for performance reasons. 

98 if isinstance(s, bytes): 

99 if encoding == "utf-8": 99 ↛ 102line 99 didn't jump to line 102, because the condition on line 99 was never false

100 return s 

101 else: 

102 return s.decode("utf-8", errors).encode(encoding, errors) 

103 if strings_only and is_protected_type(s): 103 ↛ 104line 103 didn't jump to line 104, because the condition on line 103 was never true

104 return s 

105 if isinstance(s, memoryview): 105 ↛ 106line 105 didn't jump to line 106, because the condition on line 105 was never true

106 return bytes(s) 

107 return str(s).encode(encoding, errors) 

108 

109 

110def iri_to_uri(iri): 

111 """ 

112 Convert an Internationalized Resource Identifier (IRI) portion to a URI 

113 portion that is suitable for inclusion in a URL. 

114 

115 This is the algorithm from section 3.1 of RFC 3987, slightly simplified 

116 since the input is assumed to be a string rather than an arbitrary byte 

117 stream. 

118 

119 Take an IRI (string or UTF-8 bytes, e.g. '/I ♥ Django/' or 

120 b'/I \xe2\x99\xa5 Django/') and return a string containing the encoded 

121 result with ASCII chars only (e.g. '/I%20%E2%99%A5%20Django/'). 

122 """ 

123 # The list of safe characters here is constructed from the "reserved" and 

124 # "unreserved" characters specified in sections 2.2 and 2.3 of RFC 3986: 

125 # reserved = gen-delims / sub-delims 

126 # gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" 

127 # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 

128 # / "*" / "+" / "," / ";" / "=" 

129 # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 

130 # Of the unreserved characters, urllib.parse.quote() already considers all 

131 # but the ~ safe. 

132 # The % character is also added to the list of safe characters here, as the 

133 # end of section 3.1 of RFC 3987 specifically mentions that % must not be 

134 # converted. 

135 if iri is None: 135 ↛ 136line 135 didn't jump to line 136, because the condition on line 135 was never true

136 return iri 

137 elif isinstance(iri, Promise): 137 ↛ 138line 137 didn't jump to line 138, because the condition on line 137 was never true

138 iri = str(iri) 

139 return quote(iri, safe="/#%[]=:;$&()+,!?*@'~") 

140 

141 

142# List of byte values that uri_to_iri() decodes from percent encoding. 

143# First, the unreserved characters from RFC 3986: 

144_ascii_ranges = [[45, 46, 95, 126], range(65, 91), range(97, 123)] 

145_hextobyte = { 

146 (fmt % char).encode(): bytes((char,)) 

147 for ascii_range in _ascii_ranges 

148 for char in ascii_range 

149 for fmt in ["%02x", "%02X"] 

150} 

151# And then everything above 128, because bytes ≥ 128 are part of multibyte 

152# Unicode characters. 

153_hexdig = "0123456789ABCDEFabcdef" 

154_hextobyte.update( 

155 {(a + b).encode(): bytes.fromhex(a + b) for a in _hexdig[8:] for b in _hexdig} 

156) 

157 

158 

159def uri_to_iri(uri): 

160 """ 

161 Convert a Uniform Resource Identifier(URI) into an Internationalized 

162 Resource Identifier(IRI). 

163 

164 This is the algorithm from section 3.2 of RFC 3987, excluding step 4. 

165 

166 Take an URI in ASCII bytes (e.g. '/I%20%E2%99%A5%20Django/') and return 

167 a string containing the encoded result (e.g. '/I%20♥%20Django/'). 

168 """ 

169 if uri is None: 

170 return uri 

171 uri = force_bytes(uri) 

172 # Fast selective unquote: First, split on '%' and then starting with the 

173 # second block, decode the first 2 bytes if they represent a hex code to 

174 # decode. The rest of the block is the part after '%AB', not containing 

175 # any '%'. Add that to the output without further processing. 

176 bits = uri.split(b"%") 

177 if len(bits) == 1: 

178 iri = uri 

179 else: 

180 parts = [bits[0]] 

181 append = parts.append 

182 hextobyte = _hextobyte 

183 for item in bits[1:]: 

184 hex = item[:2] 

185 if hex in hextobyte: 

186 append(hextobyte[item[:2]]) 

187 append(item[2:]) 

188 else: 

189 append(b"%") 

190 append(item) 

191 iri = b"".join(parts) 

192 return repercent_broken_unicode(iri).decode() 

193 

194 

195def escape_uri_path(path): 

196 """ 

197 Escape the unsafe characters from the path portion of a Uniform Resource 

198 Identifier (URI). 

199 """ 

200 # These are the "reserved" and "unreserved" characters specified in 

201 # sections 2.2 and 2.3 of RFC 2396: 

202 # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," 

203 # unreserved = alphanum | mark 

204 # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" 

205 # The list of safe characters here is constructed subtracting ";", "=", 

206 # and "?" according to section 3.3 of RFC 2396. 

207 # The reason for not subtracting and escaping "/" is that we are escaping 

208 # the entire path, not a path segment. 

209 return quote(path, safe="/:@&+$,-_.!~*'()") 

210 

211 

212def punycode(domain): 

213 """Return the Punycode of the given domain if it's non-ASCII.""" 

214 return domain.encode("idna").decode("ascii") 

215 

216 

217def repercent_broken_unicode(path): 

218 """ 

219 As per section 3.2 of RFC 3987, step three of converting a URI into an IRI, 

220 repercent-encode any octet produced that is not part of a strictly legal 

221 UTF-8 octet sequence. 

222 """ 

223 while True: 

224 try: 

225 path.decode() 

226 except UnicodeDecodeError as e: 

227 # CVE-2019-14235: A recursion shouldn't be used since the exception 

228 # handling uses massive amounts of memory 

229 repercent = quote(path[e.start : e.end], safe=b"/#%[]=:;$&()+,!?*@'~") 

230 path = path[: e.start] + repercent.encode() + path[e.end :] 

231 else: 

232 return path 

233 

234 

235def filepath_to_uri(path): 

236 """Convert a file system path to a URI portion that is suitable for 

237 inclusion in a URL. 

238 

239 Encode certain chars that would normally be recognized as special chars 

240 for URIs. Do not encode the ' character, as it is a valid character 

241 within URIs. See the encodeURIComponent() JavaScript function for details. 

242 """ 

243 if path is None: 243 ↛ 244line 243 didn't jump to line 244, because the condition on line 243 was never true

244 return path 

245 # I know about `os.sep` and `os.altsep` but I want to leave 

246 # some flexibility for hardcoding separators. 

247 return quote(str(path).replace("\\", "/"), safe="/~!*()'") 

248 

249 

250def get_system_encoding(): 

251 """ 

252 The encoding of the default system locale. Fallback to 'ascii' if the 

253 #encoding is unsupported by Python or could not be determined. See tickets 

254 #10335 and #5846. 

255 """ 

256 try: 

257 encoding = locale.getdefaultlocale()[1] or "ascii" 

258 codecs.lookup(encoding) 

259 except Exception: 

260 encoding = "ascii" 

261 return encoding 

262 

263 

264DEFAULT_LOCALE_ENCODING = get_system_encoding()