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.

314 lines
9.9 KiB

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