1 from __future__ import unicode_literals
11 import urllib.request as compat_urllib_request
12 except ImportError: # Python 2
13 import urllib2 as compat_urllib_request
16 import urllib.error as compat_urllib_error
17 except ImportError: # Python 2
18 import urllib2 as compat_urllib_error
21 import urllib.parse as compat_urllib_parse
22 except ImportError: # Python 2
23 import urllib as compat_urllib_parse
26 from urllib.parse import urlparse as compat_urllib_parse_urlparse
27 except ImportError: # Python 2
28 from urlparse import urlparse as compat_urllib_parse_urlparse
31 import urllib.parse as compat_urlparse
32 except ImportError: # Python 2
33 import urlparse as compat_urlparse
36 import http.cookiejar as compat_cookiejar
37 except ImportError: # Python 2
38 import cookielib as compat_cookiejar
41 import html.entities as compat_html_entities
42 except ImportError: # Python 2
43 import htmlentitydefs as compat_html_entities
46 import html.parser as compat_html_parser
47 except ImportError: # Python 2
48 import HTMLParser as compat_html_parser
51 import http.client as compat_http_client
52 except ImportError: # Python 2
53 import httplib as compat_http_client
56 from urllib.error import HTTPError as compat_HTTPError
57 except ImportError: # Python 2
58 from urllib2 import HTTPError as compat_HTTPError
61 from urllib.request import urlretrieve as compat_urlretrieve
62 except ImportError: # Python 2
63 from urllib import urlretrieve as compat_urlretrieve
67 from subprocess import DEVNULL
68 compat_subprocess_get_DEVNULL = lambda: DEVNULL
70 compat_subprocess_get_DEVNULL = lambda: open(os.path.devnull, 'w')
73 from urllib.parse import unquote as compat_urllib_parse_unquote
75 def compat_urllib_parse_unquote(string, encoding='utf-8', errors='replace'):
78 res = string.split('%')
85 # pct_sequence: contiguous sequence of percent-encoded bytes, decoded
92 pct_sequence += item[:2].decode('hex')
95 # This segment was just a single percent-encoded character.
96 # May be part of a sequence of code units, so delay decoding.
97 # (Stored in pct_sequence).
101 # Encountered non-percent-encoded characters. Flush the current
103 string += pct_sequence.decode(encoding, errors) + rest
106 # Flush the final pct_sequence
107 string += pct_sequence.decode(encoding, errors)
112 from urllib.parse import parse_qs as compat_parse_qs
113 except ImportError: # Python 2
114 # HACK: The following is the correct parse_qs implementation from cpython 3's stdlib.
115 # Python 2's version is apparently totally broken
117 def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
118 encoding='utf-8', errors='replace'):
119 qs, _coerce_result = qs, unicode
120 pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
122 for name_value in pairs:
123 if not name_value and not strict_parsing:
125 nv = name_value.split('=', 1)
128 raise ValueError("bad query field: %r" % (name_value,))
129 # Handle case of a control-name with no equal sign
130 if keep_blank_values:
134 if len(nv[1]) or keep_blank_values:
135 name = nv[0].replace('+', ' ')
136 name = compat_urllib_parse_unquote(
137 name, encoding=encoding, errors=errors)
138 name = _coerce_result(name)
139 value = nv[1].replace('+', ' ')
140 value = compat_urllib_parse_unquote(
141 value, encoding=encoding, errors=errors)
142 value = _coerce_result(value)
143 r.append((name, value))
146 def compat_parse_qs(qs, keep_blank_values=False, strict_parsing=False,
147 encoding='utf-8', errors='replace'):
149 pairs = _parse_qsl(qs, keep_blank_values, strict_parsing,
150 encoding=encoding, errors=errors)
151 for name, value in pairs:
152 if name in parsed_result:
153 parsed_result[name].append(value)
155 parsed_result[name] = [value]
159 compat_str = unicode # Python 2
164 compat_chr = unichr # Python 2
169 from xml.etree.ElementTree import ParseError as compat_xml_parse_error
170 except ImportError: # Python 2.6
171 from xml.parsers.expat import ExpatError as compat_xml_parse_error
174 from shlex import quote as shlex_quote
175 except ImportError: # Python < 3.3
177 return "'" + s.replace("'", "'\"'\"'") + "'"
181 if type(c) is int: return c
185 if sys.version_info >= (3, 0):
186 compat_getenv = os.getenv
187 compat_expanduser = os.path.expanduser
189 # Environment variables should be decoded with filesystem encoding.
190 # Otherwise it will fail if any non-ASCII characters present (see #3854 #3217 #2918)
192 def compat_getenv(key, default=None):
193 from .utils import get_filesystem_encoding
194 env = os.getenv(key, default)
196 env = env.decode(get_filesystem_encoding())
199 # HACK: The default implementations of os.path.expanduser from cpython do not decode
200 # environment variables with filesystem encoding. We will work around this by
201 # providing adjusted implementations.
202 # The following are os.path.expanduser implementations from cpython 2.7.8 stdlib
203 # for different platforms with correct environment variables decoding.
205 if os.name == 'posix':
206 def compat_expanduser(path):
207 """Expand ~ and ~user constructions. If user or $HOME is unknown,
209 if not path.startswith('~'):
211 i = path.find('/', 1)
215 if 'HOME' not in os.environ:
217 userhome = pwd.getpwuid(os.getuid()).pw_dir
219 userhome = compat_getenv('HOME')
223 pwent = pwd.getpwnam(path[1:i])
226 userhome = pwent.pw_dir
227 userhome = userhome.rstrip('/')
228 return (userhome + path[i:]) or '/'
229 elif os.name == 'nt' or os.name == 'ce':
230 def compat_expanduser(path):
231 """Expand ~ and ~user constructs.
233 If user or $HOME is unknown, do nothing."""
237 while i < n and path[i] not in '/\\':
240 if 'HOME' in os.environ:
241 userhome = compat_getenv('HOME')
242 elif 'USERPROFILE' in os.environ:
243 userhome = compat_getenv('USERPROFILE')
244 elif not 'HOMEPATH' in os.environ:
248 drive = compat_getenv('HOMEDRIVE')
251 userhome = os.path.join(drive, compat_getenv('HOMEPATH'))
254 userhome = os.path.join(os.path.dirname(userhome), path[1:i])
256 return userhome + path[i:]
258 compat_expanduser = os.path.expanduser
261 if sys.version_info < (3, 0):
263 from .utils import preferredencoding
264 print(s.encode(preferredencoding(), 'xmlcharrefreplace'))
267 assert type(s) == type(u'')
272 subprocess_check_output = subprocess.check_output
273 except AttributeError:
274 def subprocess_check_output(*args, **kwargs):
275 assert 'input' not in kwargs
276 p = subprocess.Popen(*args, stdout=subprocess.PIPE, **kwargs)
277 output, _ = p.communicate()
280 raise subprocess.CalledProcessError(ret, p.args, output=output)
283 if sys.version_info < (3, 0) and sys.platform == 'win32':
284 def compat_getpass(prompt, *args, **kwargs):
285 if isinstance(prompt, compat_str):
286 from .utils import preferredencoding
287 prompt = prompt.encode(preferredencoding())
288 return getpass.getpass(prompt, *args, **kwargs)
290 compat_getpass = getpass.getpass
292 # Old 2.6 and 2.7 releases require kwargs to be bytes
294 (lambda x: x)(**{'x': 0})
296 def compat_kwargs(kwargs):
297 return dict((bytes(k), v) for k, v in kwargs.items())
299 compat_kwargs = lambda kwargs: kwargs
302 # Fix https://github.com/rg3/youtube-dl/issues/4223
303 # See http://bugs.python.org/issue9161 for what is broken
304 def workaround_optparse_bug9161():
306 optparse.OptionGroup('foo').add_option('-t')
308 real_add_option = optparse.OptionGroup.add_option
310 def _compat_add_option(self, *args, **kwargs):
312 v.encode('ascii', 'replace') if isinstance(v, compat_str)
314 bargs = [enc(a) for a in args]
316 (k, enc(v)) for k, v in kwargs.items())
317 return real_add_option(self, *bargs, **bkwargs)
318 optparse.OptionGroup.add_option = _compat_add_option
328 'compat_html_entities',
329 'compat_html_parser',
330 'compat_http_client',
336 'compat_subprocess_get_DEVNULL',
337 'compat_urllib_error',
338 'compat_urllib_parse',
339 'compat_urllib_parse_unquote',
340 'compat_urllib_parse_urlparse',
341 'compat_urllib_request',
343 'compat_urlretrieve',
344 'compat_xml_parse_error',
346 'subprocess_check_output',
347 'workaround_optparse_bug9161',