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.

354 lines
11 KiB

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