You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

encoding.py 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import codecs
  2. import datetime
  3. import locale
  4. from decimal import Decimal
  5. from urllib.parse import quote
  6. from django.utils import six
  7. from django.utils.functional import Promise
  8. class DjangoUnicodeDecodeError(UnicodeDecodeError):
  9. def __init__(self, obj, *args):
  10. self.obj = obj
  11. super().__init__(*args)
  12. def __str__(self):
  13. return '%s. You passed in %r (%s)' % (super().__str__(), self.obj, type(self.obj))
  14. # For backwards compatibility. (originally in Django, then added to six 1.9)
  15. python_2_unicode_compatible = six.python_2_unicode_compatible
  16. def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'):
  17. """
  18. Return a string representing 's'. Treat bytestrings using the 'encoding'
  19. codec.
  20. If strings_only is True, don't convert (some) non-string-like objects.
  21. """
  22. if isinstance(s, Promise):
  23. # The input is the result of a gettext_lazy() call.
  24. return s
  25. return force_text(s, encoding, strings_only, errors)
  26. _PROTECTED_TYPES = (
  27. type(None), int, float, Decimal, datetime.datetime, datetime.date, datetime.time,
  28. )
  29. def is_protected_type(obj):
  30. """Determine if the object instance is of a protected type.
  31. Objects of protected types are preserved as-is when passed to
  32. force_text(strings_only=True).
  33. """
  34. return isinstance(obj, _PROTECTED_TYPES)
  35. def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
  36. """
  37. Similar to smart_text, except that lazy instances are resolved to
  38. strings, rather than kept as lazy objects.
  39. If strings_only is True, don't convert (some) non-string-like objects.
  40. """
  41. # Handle the common case first for performance reasons.
  42. if issubclass(type(s), str):
  43. return s
  44. if strings_only and is_protected_type(s):
  45. return s
  46. try:
  47. if isinstance(s, bytes):
  48. s = str(s, encoding, errors)
  49. else:
  50. s = str(s)
  51. except UnicodeDecodeError as e:
  52. raise DjangoUnicodeDecodeError(s, *e.args)
  53. return s
  54. def smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
  55. """
  56. Return a bytestring version of 's', encoded as specified in 'encoding'.
  57. If strings_only is True, don't convert (some) non-string-like objects.
  58. """
  59. if isinstance(s, Promise):
  60. # The input is the result of a gettext_lazy() call.
  61. return s
  62. return force_bytes(s, encoding, strings_only, errors)
  63. def force_bytes(s, encoding='utf-8', strings_only=False, errors='strict'):
  64. """
  65. Similar to smart_bytes, except that lazy instances are resolved to
  66. strings, rather than kept as lazy objects.
  67. If strings_only is True, don't convert (some) non-string-like objects.
  68. """
  69. # Handle the common case first for performance reasons.
  70. if isinstance(s, bytes):
  71. if encoding == 'utf-8':
  72. return s
  73. else:
  74. return s.decode('utf-8', errors).encode(encoding, errors)
  75. if strings_only and is_protected_type(s):
  76. return s
  77. if isinstance(s, memoryview):
  78. return bytes(s)
  79. return str(s).encode(encoding, errors)
  80. smart_str = smart_text
  81. force_str = force_text
  82. smart_str.__doc__ = """
  83. Apply smart_text in Python 3 and smart_bytes in Python 2.
  84. This is suitable for writing to sys.stdout (for instance).
  85. """
  86. force_str.__doc__ = """
  87. Apply force_text in Python 3 and force_bytes in Python 2.
  88. """
  89. def iri_to_uri(iri):
  90. """
  91. Convert an Internationalized Resource Identifier (IRI) portion to a URI
  92. portion that is suitable for inclusion in a URL.
  93. This is the algorithm from section 3.1 of RFC 3987, slightly simplified
  94. since the input is assumed to be a string rather than an arbitrary byte
  95. stream.
  96. Take an IRI (string or UTF-8 bytes, e.g. '/I ♥ Django/' or
  97. b'/I \xe2\x99\xa5 Django/') and return a string containing the encoded
  98. result with ASCII chars only (e.g. '/I%20%E2%99%A5%20Django/').
  99. """
  100. # The list of safe characters here is constructed from the "reserved" and
  101. # "unreserved" characters specified in sections 2.2 and 2.3 of RFC 3986:
  102. # reserved = gen-delims / sub-delims
  103. # gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
  104. # sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
  105. # / "*" / "+" / "," / ";" / "="
  106. # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  107. # Of the unreserved characters, urllib.parse.quote() already considers all
  108. # but the ~ safe.
  109. # The % character is also added to the list of safe characters here, as the
  110. # end of section 3.1 of RFC 3987 specifically mentions that % must not be
  111. # converted.
  112. if iri is None:
  113. return iri
  114. elif isinstance(iri, Promise):
  115. iri = str(iri)
  116. return quote(iri, safe="/#%[]=:;$&()+,!?*@'~")
  117. # List of byte values that uri_to_iri() decodes from percent encoding.
  118. # First, the unreserved characters from RFC 3986:
  119. _ascii_ranges = [[45, 46, 95, 126], range(65, 91), range(97, 123)]
  120. _hextobyte = {
  121. (fmt % char).encode(): bytes((char,))
  122. for ascii_range in _ascii_ranges
  123. for char in ascii_range
  124. for fmt in ['%02x', '%02X']
  125. }
  126. # And then everything above 128, because bytes ≥ 128 are part of multibyte
  127. # unicode characters.
  128. _hexdig = '0123456789ABCDEFabcdef'
  129. _hextobyte.update({
  130. (a + b).encode(): bytes.fromhex(a + b)
  131. for a in _hexdig[8:] for b in _hexdig
  132. })
  133. def uri_to_iri(uri):
  134. """
  135. Convert a Uniform Resource Identifier(URI) into an Internationalized
  136. Resource Identifier(IRI).
  137. This is the algorithm from section 3.2 of RFC 3987, excluding step 4.
  138. Take an URI in ASCII bytes (e.g. '/I%20%E2%99%A5%20Django/') and return
  139. a string containing the encoded result (e.g. '/I%20♥%20Django/').
  140. """
  141. if uri is None:
  142. return uri
  143. uri = force_bytes(uri)
  144. # Fast selective unqote: First, split on '%' and then starting with the
  145. # second block, decode the first 2 bytes if they represent a hex code to
  146. # decode. The rest of the block is the part after '%AB', not containing
  147. # any '%'. Add that to the output without further processing.
  148. bits = uri.split(b'%')
  149. if len(bits) == 1:
  150. iri = uri
  151. else:
  152. parts = [bits[0]]
  153. append = parts.append
  154. hextobyte = _hextobyte
  155. for item in bits[1:]:
  156. hex = item[:2]
  157. if hex in hextobyte:
  158. append(hextobyte[item[:2]])
  159. append(item[2:])
  160. else:
  161. append(b'%')
  162. append(item)
  163. iri = b''.join(parts)
  164. return repercent_broken_unicode(iri).decode()
  165. def escape_uri_path(path):
  166. """
  167. Escape the unsafe characters from the path portion of a Uniform Resource
  168. Identifier (URI).
  169. """
  170. # These are the "reserved" and "unreserved" characters specified in
  171. # sections 2.2 and 2.3 of RFC 2396:
  172. # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
  173. # unreserved = alphanum | mark
  174. # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
  175. # The list of safe characters here is constructed subtracting ";", "=",
  176. # and "?" according to section 3.3 of RFC 2396.
  177. # The reason for not subtracting and escaping "/" is that we are escaping
  178. # the entire path, not a path segment.
  179. return quote(path, safe="/:@&+$,-_.!~*'()")
  180. def repercent_broken_unicode(path):
  181. """
  182. As per section 3.2 of RFC 3987, step three of converting a URI into an IRI,
  183. repercent-encode any octet produced that is not part of a strictly legal
  184. UTF-8 octet sequence.
  185. """
  186. while True:
  187. try:
  188. path.decode()
  189. except UnicodeDecodeError as e:
  190. # CVE-2019-14235: A recursion shouldn't be used since the exception
  191. # handling uses massive amounts of memory
  192. repercent = quote(path[e.start:e.end], safe=b"/#%[]=:;$&()+,!?*@'~")
  193. path = path[:e.start] + force_bytes(repercent) + path[e.end:]
  194. else:
  195. return path
  196. def filepath_to_uri(path):
  197. """Convert a file system path to a URI portion that is suitable for
  198. inclusion in a URL.
  199. Encode certain chars that would normally be recognized as special chars
  200. for URIs. Do not encode the ' character, as it is a valid character
  201. within URIs. See the encodeURIComponent() JavaScript function for details.
  202. """
  203. if path is None:
  204. return path
  205. # I know about `os.sep` and `os.altsep` but I want to leave
  206. # some flexibility for hardcoding separators.
  207. return quote(path.replace("\\", "/"), safe="/~!*()'")
  208. def get_system_encoding():
  209. """
  210. The encoding of the default system locale. Fallback to 'ascii' if the
  211. #encoding is unsupported by Python or could not be determined. See tickets
  212. #10335 and #5846.
  213. """
  214. try:
  215. encoding = locale.getdefaultlocale()[1] or 'ascii'
  216. codecs.lookup(encoding)
  217. except Exception:
  218. encoding = 'ascii'
  219. return encoding
  220. DEFAULT_LOCALE_ENCODING = get_system_encoding()