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.

317 lines
10 KiB

  1. from __future__ import unicode_literals
  2. import getpass
  3. import os
  4. import subprocess
  5. import sys
  6. try:
  7. import urllib.request as compat_urllib_request
  8. except ImportError: # Python 2
  9. import urllib2 as compat_urllib_request
  10. try:
  11. import urllib.error as compat_urllib_error
  12. except ImportError: # Python 2
  13. import urllib2 as compat_urllib_error
  14. try:
  15. import urllib.parse as compat_urllib_parse
  16. except ImportError: # Python 2
  17. import urllib as compat_urllib_parse
  18. try:
  19. from urllib.parse import urlparse as compat_urllib_parse_urlparse
  20. except ImportError: # Python 2
  21. from urlparse import urlparse as compat_urllib_parse_urlparse
  22. try:
  23. import urllib.parse as compat_urlparse
  24. except ImportError: # Python 2
  25. import urlparse as compat_urlparse
  26. try:
  27. import http.cookiejar as compat_cookiejar
  28. except ImportError: # Python 2
  29. import cookielib as compat_cookiejar
  30. try:
  31. import html.entities as compat_html_entities
  32. except ImportError: # Python 2
  33. import htmlentitydefs as compat_html_entities
  34. try:
  35. import html.parser as compat_html_parser
  36. except ImportError: # Python 2
  37. import HTMLParser as compat_html_parser
  38. try:
  39. import http.client as compat_http_client
  40. except ImportError: # Python 2
  41. import httplib as compat_http_client
  42. try:
  43. from urllib.error import HTTPError as compat_HTTPError
  44. except ImportError: # Python 2
  45. from urllib2 import HTTPError as compat_HTTPError
  46. try:
  47. from urllib.request import urlretrieve as compat_urlretrieve
  48. except ImportError: # Python 2
  49. from urllib import urlretrieve as compat_urlretrieve
  50. try:
  51. from subprocess import DEVNULL
  52. compat_subprocess_get_DEVNULL = lambda: DEVNULL
  53. except ImportError:
  54. compat_subprocess_get_DEVNULL = lambda: open(os.path.devnull, 'w')
  55. try:
  56. from urllib.parse import unquote as compat_urllib_parse_unquote
  57. except ImportError:
  58. def compat_urllib_parse_unquote(string, encoding='utf-8', errors='replace'):
  59. if string == '':
  60. return string
  61. res = string.split('%')
  62. if len(res) == 1:
  63. return string
  64. if encoding is None:
  65. encoding = 'utf-8'
  66. if errors is None:
  67. errors = 'replace'
  68. # pct_sequence: contiguous sequence of percent-encoded bytes, decoded
  69. pct_sequence = b''
  70. string = res[0]
  71. for item in res[1:]:
  72. try:
  73. if not item:
  74. raise ValueError
  75. pct_sequence += item[:2].decode('hex')
  76. rest = item[2:]
  77. if not rest:
  78. # This segment was just a single percent-encoded character.
  79. # May be part of a sequence of code units, so delay decoding.
  80. # (Stored in pct_sequence).
  81. continue
  82. except ValueError:
  83. rest = '%' + item
  84. # Encountered non-percent-encoded characters. Flush the current
  85. # pct_sequence.
  86. string += pct_sequence.decode(encoding, errors) + rest
  87. pct_sequence = b''
  88. if pct_sequence:
  89. # Flush the final pct_sequence
  90. string += pct_sequence.decode(encoding, errors)
  91. return string
  92. try:
  93. from urllib.parse import parse_qs as compat_parse_qs
  94. except ImportError: # Python 2
  95. # HACK: The following is the correct parse_qs implementation from cpython 3's stdlib.
  96. # Python 2's version is apparently totally broken
  97. def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
  98. encoding='utf-8', errors='replace'):
  99. qs, _coerce_result = qs, unicode
  100. pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
  101. r = []
  102. for name_value in pairs:
  103. if not name_value and not strict_parsing:
  104. continue
  105. nv = name_value.split('=', 1)
  106. if len(nv) != 2:
  107. if strict_parsing:
  108. raise ValueError("bad query field: %r" % (name_value,))
  109. # Handle case of a control-name with no equal sign
  110. if keep_blank_values:
  111. nv.append('')
  112. else:
  113. continue
  114. if len(nv[1]) or keep_blank_values:
  115. name = nv[0].replace('+', ' ')
  116. name = compat_urllib_parse_unquote(
  117. name, encoding=encoding, errors=errors)
  118. name = _coerce_result(name)
  119. value = nv[1].replace('+', ' ')
  120. value = compat_urllib_parse_unquote(
  121. value, encoding=encoding, errors=errors)
  122. value = _coerce_result(value)
  123. r.append((name, value))
  124. return r
  125. def compat_parse_qs(qs, keep_blank_values=False, strict_parsing=False,
  126. encoding='utf-8', errors='replace'):
  127. parsed_result = {}
  128. pairs = _parse_qsl(qs, keep_blank_values, strict_parsing,
  129. encoding=encoding, errors=errors)
  130. for name, value in pairs:
  131. if name in parsed_result:
  132. parsed_result[name].append(value)
  133. else:
  134. parsed_result[name] = [value]
  135. return parsed_result
  136. try:
  137. compat_str = unicode # Python 2
  138. except NameError:
  139. compat_str = str
  140. try:
  141. compat_chr = unichr # Python 2
  142. except NameError:
  143. compat_chr = chr
  144. try:
  145. from xml.etree.ElementTree import ParseError as compat_xml_parse_error
  146. except ImportError: # Python 2.6
  147. from xml.parsers.expat import ExpatError as compat_xml_parse_error
  148. try:
  149. from shlex import quote as shlex_quote
  150. except ImportError: # Python < 3.3
  151. def shlex_quote(s):
  152. return "'" + s.replace("'", "'\"'\"'") + "'"
  153. def compat_ord(c):
  154. if type(c) is int: return c
  155. else: return ord(c)
  156. if sys.version_info >= (3, 0):
  157. compat_getenv = os.getenv
  158. compat_expanduser = os.path.expanduser
  159. else:
  160. # Environment variables should be decoded with filesystem encoding.
  161. # Otherwise it will fail if any non-ASCII characters present (see #3854 #3217 #2918)
  162. def compat_getenv(key, default=None):
  163. from .utils import get_filesystem_encoding
  164. env = os.getenv(key, default)
  165. if env:
  166. env = env.decode(get_filesystem_encoding())
  167. return env
  168. # HACK: The default implementations of os.path.expanduser from cpython do not decode
  169. # environment variables with filesystem encoding. We will work around this by
  170. # providing adjusted implementations.
  171. # The following are os.path.expanduser implementations from cpython 2.7.8 stdlib
  172. # for different platforms with correct environment variables decoding.
  173. if os.name == 'posix':
  174. def compat_expanduser(path):
  175. """Expand ~ and ~user constructions. If user or $HOME is unknown,
  176. do nothing."""
  177. if not path.startswith('~'):
  178. return path
  179. i = path.find('/', 1)
  180. if i < 0:
  181. i = len(path)
  182. if i == 1:
  183. if 'HOME' not in os.environ:
  184. import pwd
  185. userhome = pwd.getpwuid(os.getuid()).pw_dir
  186. else:
  187. userhome = compat_getenv('HOME')
  188. else:
  189. import pwd
  190. try:
  191. pwent = pwd.getpwnam(path[1:i])
  192. except KeyError:
  193. return path
  194. userhome = pwent.pw_dir
  195. userhome = userhome.rstrip('/')
  196. return (userhome + path[i:]) or '/'
  197. elif os.name == 'nt' or os.name == 'ce':
  198. def compat_expanduser(path):
  199. """Expand ~ and ~user constructs.
  200. If user or $HOME is unknown, do nothing."""
  201. if path[:1] != '~':
  202. return path
  203. i, n = 1, len(path)
  204. while i < n and path[i] not in '/\\':
  205. i = i + 1
  206. if 'HOME' in os.environ:
  207. userhome = compat_getenv('HOME')
  208. elif 'USERPROFILE' in os.environ:
  209. userhome = compat_getenv('USERPROFILE')
  210. elif not 'HOMEPATH' in os.environ:
  211. return path
  212. else:
  213. try:
  214. drive = compat_getenv('HOMEDRIVE')
  215. except KeyError:
  216. drive = ''
  217. userhome = os.path.join(drive, compat_getenv('HOMEPATH'))
  218. if i != 1: #~user
  219. userhome = os.path.join(os.path.dirname(userhome), path[1:i])
  220. return userhome + path[i:]
  221. else:
  222. compat_expanduser = os.path.expanduser
  223. if sys.version_info < (3, 0):
  224. def compat_print(s):
  225. from .utils import preferredencoding
  226. print(s.encode(preferredencoding(), 'xmlcharrefreplace'))
  227. else:
  228. def compat_print(s):
  229. assert type(s) == type(u'')
  230. print(s)
  231. try:
  232. subprocess_check_output = subprocess.check_output
  233. except AttributeError:
  234. def subprocess_check_output(*args, **kwargs):
  235. assert 'input' not in kwargs
  236. p = subprocess.Popen(*args, stdout=subprocess.PIPE, **kwargs)
  237. output, _ = p.communicate()
  238. ret = p.poll()
  239. if ret:
  240. raise subprocess.CalledProcessError(ret, p.args, output=output)
  241. return output
  242. if sys.version_info < (3, 0) and sys.platform == 'win32':
  243. def compat_getpass(prompt, *args, **kwargs):
  244. if isinstance(prompt, compat_str):
  245. from .utils import preferredencoding
  246. prompt = prompt.encode(preferredencoding())
  247. return getpass.getpass(prompt, *args, **kwargs)
  248. else:
  249. compat_getpass = getpass.getpass
  250. __all__ = [
  251. 'compat_HTTPError',
  252. 'compat_chr',
  253. 'compat_cookiejar',
  254. 'compat_expanduser',
  255. 'compat_getenv',
  256. 'compat_getpass',
  257. 'compat_html_entities',
  258. 'compat_html_parser',
  259. 'compat_http_client',
  260. 'compat_ord',
  261. 'compat_parse_qs',
  262. 'compat_print',
  263. 'compat_str',
  264. 'compat_subprocess_get_DEVNULL',
  265. 'compat_urllib_error',
  266. 'compat_urllib_parse',
  267. 'compat_urllib_parse_unquote',
  268. 'compat_urllib_parse_urlparse',
  269. 'compat_urllib_request',
  270. 'compat_urlparse',
  271. 'compat_urlretrieve',
  272. 'compat_xml_parse_error',
  273. 'shlex_quote',
  274. 'subprocess_check_output',
  275. ]