]> jfr.im git - yt-dlp.git/blame - youtube_dl/compat.py
[tf1] add support for more related web sites
[yt-dlp.git] / youtube_dl / compat.py
CommitLineData
451948b2
PH
1from __future__ import unicode_literals
2
0a67a363 3import binascii
003c69a8 4import collections
0a67a363 5import email
8c25f81b 6import getpass
0a67a363 7import io
e07e9313 8import optparse
8c25f81b 9import os
7d4111ed 10import re
51f579b6 11import shlex
003c69a8 12import shutil
be4a824d 13import socket
dab0daee 14import struct
8c25f81b
PH
15import subprocess
16import sys
a0e060ac 17import itertools
36e6f62c 18import xml.etree.ElementTree
8c25f81b
PH
19
20
21try:
22 import urllib.request as compat_urllib_request
5f6a1245 23except ImportError: # Python 2
8c25f81b
PH
24 import urllib2 as compat_urllib_request
25
26try:
27 import urllib.error as compat_urllib_error
5f6a1245 28except ImportError: # Python 2
8c25f81b
PH
29 import urllib2 as compat_urllib_error
30
31try:
32 import urllib.parse as compat_urllib_parse
5f6a1245 33except ImportError: # Python 2
8c25f81b
PH
34 import urllib as compat_urllib_parse
35
36try:
37 from urllib.parse import urlparse as compat_urllib_parse_urlparse
5f6a1245 38except ImportError: # Python 2
8c25f81b
PH
39 from urlparse import urlparse as compat_urllib_parse_urlparse
40
41try:
42 import urllib.parse as compat_urlparse
5f6a1245 43except ImportError: # Python 2
8c25f81b
PH
44 import urlparse as compat_urlparse
45
0a67a363
YCH
46try:
47 import urllib.response as compat_urllib_response
48except ImportError: # Python 2
49 import urllib as compat_urllib_response
50
8c25f81b
PH
51try:
52 import http.cookiejar as compat_cookiejar
5f6a1245 53except ImportError: # Python 2
8c25f81b
PH
54 import cookielib as compat_cookiejar
55
799207e8 56try:
57 import http.cookies as compat_cookies
58except ImportError: # Python 2
59 import Cookie as compat_cookies
60
8c25f81b
PH
61try:
62 import html.entities as compat_html_entities
5f6a1245 63except ImportError: # Python 2
8c25f81b
PH
64 import htmlentitydefs as compat_html_entities
65
8c25f81b
PH
66try:
67 import http.client as compat_http_client
5f6a1245 68except ImportError: # Python 2
8c25f81b
PH
69 import httplib as compat_http_client
70
71try:
72 from urllib.error import HTTPError as compat_HTTPError
73except ImportError: # Python 2
74 from urllib2 import HTTPError as compat_HTTPError
75
76try:
77 from urllib.request import urlretrieve as compat_urlretrieve
78except ImportError: # Python 2
79 from urllib import urlretrieve as compat_urlretrieve
80
8bb56eee
BF
81try:
82 from html.parser import HTMLParser as compat_HTMLParser
83except ImportError: # Python 2
84 from HTMLParser import HTMLParser as compat_HTMLParser
85
8c25f81b
PH
86
87try:
88 from subprocess import DEVNULL
89 compat_subprocess_get_DEVNULL = lambda: DEVNULL
90except ImportError:
91 compat_subprocess_get_DEVNULL = lambda: open(os.path.devnull, 'w')
92
83fda3c0
PH
93try:
94 import http.server as compat_http_server
95except ImportError:
96 import BaseHTTPServer as compat_http_server
97
953fed28
PH
98try:
99 compat_str = unicode # Python 2
100except NameError:
101 compat_str = str
102
8c25f81b 103try:
55139679 104 from urllib.parse import unquote_to_bytes as compat_urllib_parse_unquote_to_bytes
8c25f81b 105 from urllib.parse import unquote as compat_urllib_parse_unquote
aa99aa4e 106 from urllib.parse import unquote_plus as compat_urllib_parse_unquote_plus
55139679 107except ImportError: # Python 2
22603348
S
108 _asciire = (compat_urllib_parse._asciire if hasattr(compat_urllib_parse, '_asciire')
109 else re.compile('([\x00-\x7f]+)'))
3cc8b4c3 110
4d08161a 111 # HACK: The following are the correct unquote_to_bytes, unquote and unquote_plus
55139679
S
112 # implementations from cpython 3.4.3's stdlib. Python 2's version
113 # is apparently broken (see https://github.com/rg3/youtube-dl/pull/6244)
114
c9c854ce 115 def compat_urllib_parse_unquote_to_bytes(string):
116 """unquote_to_bytes('abc%20def') -> b'abc def'."""
117 # Note: strings are encoded as UTF-8. This is only an issue if it contains
118 # unescaped non-ASCII characters, which URIs should not.
119 if not string:
120 # Is it a string-like object?
121 string.split
122 return b''
953fed28 123 if isinstance(string, compat_str):
c9c854ce 124 string = string.encode('utf-8')
55139679 125 bits = string.split(b'%')
c9c854ce 126 if len(bits) == 1:
127 return string
128 res = [bits[0]]
129 append = res.append
c9c854ce 130 for item in bits[1:]:
131 try:
55139679 132 append(compat_urllib_parse._hextochr[item[:2]])
c9c854ce 133 append(item[2:])
55139679 134 except KeyError:
c9c854ce 135 append(b'%')
136 append(item)
137 return b''.join(res)
138
a0f28f90 139 def compat_urllib_parse_unquote(string, encoding='utf-8', errors='replace'):
c9c854ce 140 """Replace %xx escapes by their single-character equivalent. The optional
141 encoding and errors parameters specify how to decode percent-encoded
142 sequences into Unicode characters, as accepted by the bytes.decode()
143 method.
144 By default, percent-encoded sequences are decoded with UTF-8, and invalid
145 sequences are replaced by a placeholder character.
146
147 unquote('abc%20def') -> 'abc def'.
148 """
c9c854ce 149 if '%' not in string:
150 string.split
151 return string
152 if encoding is None:
153 encoding = 'utf-8'
154 if errors is None:
155 errors = 'replace'
3cc8b4c3 156 bits = _asciire.split(string)
c9c854ce 157 res = [bits[0]]
158 append = res.append
159 for i in range(1, len(bits), 2):
55139679
S
160 append(compat_urllib_parse_unquote_to_bytes(bits[i]).decode(encoding, errors))
161 append(bits[i + 1])
c9c854ce 162 return ''.join(res)
163
aa99aa4e
S
164 def compat_urllib_parse_unquote_plus(string, encoding='utf-8', errors='replace'):
165 """Like unquote(), but also replace plus signs by spaces, as required for
166 unquoting HTML form values.
167
168 unquote_plus('%7e/abc+def') -> '~/abc def'
169 """
170 string = string.replace('+', ' ')
171 return compat_urllib_parse_unquote(string, encoding, errors)
172
15707c7e
S
173try:
174 from urllib.parse import urlencode as compat_urllib_parse_urlencode
175except ImportError: # Python 2
176 # Python 2 will choke in urlencode on mixture of byte and unicode strings.
177 # Possible solutions are to either port it from python 3 with all
178 # the friends or manually ensure input query contains only byte strings.
179 # We will stick with latter thus recursively encoding the whole query.
180 def compat_urllib_parse_urlencode(query, doseq=0, encoding='utf-8'):
181 def encode_elem(e):
182 if isinstance(e, dict):
183 e = encode_dict(e)
184 elif isinstance(e, (list, tuple,)):
92d5477d
YCH
185 list_e = encode_list(e)
186 e = tuple(list_e) if isinstance(e, tuple) else list_e
15707c7e
S
187 elif isinstance(e, compat_str):
188 e = e.encode(encoding)
189 return e
190
191 def encode_dict(d):
192 return dict((encode_elem(k), encode_elem(v)) for k, v in d.items())
193
194 def encode_list(l):
195 return [encode_elem(e) for e in l]
196
197 return compat_urllib_parse.urlencode(encode_elem(query), doseq=doseq)
198
0a67a363
YCH
199try:
200 from urllib.request import DataHandler as compat_urllib_request_DataHandler
201except ImportError: # Python < 3.4
202 # Ported from CPython 98774:1733b3bd46db, Lib/urllib/request.py
203 class compat_urllib_request_DataHandler(compat_urllib_request.BaseHandler):
204 def data_open(self, req):
205 # data URLs as specified in RFC 2397.
206 #
207 # ignores POSTed data
208 #
209 # syntax:
210 # dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
211 # mediatype := [ type "/" subtype ] *( ";" parameter )
212 # data := *urlchar
213 # parameter := attribute "=" value
214 url = req.get_full_url()
215
611c1dd9
S
216 scheme, data = url.split(':', 1)
217 mediatype, data = data.split(',', 1)
0a67a363
YCH
218
219 # even base64 encoded data URLs might be quoted so unquote in any case:
220 data = compat_urllib_parse_unquote_to_bytes(data)
611c1dd9 221 if mediatype.endswith(';base64'):
0a67a363
YCH
222 data = binascii.a2b_base64(data)
223 mediatype = mediatype[:-7]
224
225 if not mediatype:
611c1dd9 226 mediatype = 'text/plain;charset=US-ASCII'
0a67a363
YCH
227
228 headers = email.message_from_string(
611c1dd9 229 'Content-type: %s\nContent-length: %d\n' % (mediatype, len(data)))
0a67a363
YCH
230
231 return compat_urllib_response.addinfourl(io.BytesIO(data), headers, url)
232
8f9312c3 233try:
0196149c 234 compat_basestring = basestring # Python 2
8f9312c3 235except NameError:
0196149c 236 compat_basestring = str
8f9312c3
PH
237
238try:
239 compat_chr = unichr # Python 2
240except NameError:
241 compat_chr = chr
242
243try:
244 from xml.etree.ElementTree import ParseError as compat_xml_parse_error
245except ImportError: # Python 2.6
246 from xml.parsers.expat import ExpatError as compat_xml_parse_error
247
36e6f62c
JMF
248if sys.version_info[0] >= 3:
249 compat_etree_fromstring = xml.etree.ElementTree.fromstring
250else:
ae37338e
JMF
251 # python 2.x tries to encode unicode strings with ascii (see the
252 # XMLParser._fixtext method)
36e6f62c
JMF
253 etree = xml.etree.ElementTree
254
f7854627
JMF
255 try:
256 _etree_iter = etree.Element.iter
257 except AttributeError: # Python <=2.6
258 def _etree_iter(root):
259 for el in root.findall('*'):
260 yield el
261 for sub in _etree_iter(el):
262 yield sub
263
36e6f62c
JMF
264 # on 2.6 XML doesn't have a parser argument, function copied from CPython
265 # 2.7 source
266 def _XML(text, parser=None):
267 if not parser:
268 parser = etree.XMLParser(target=etree.TreeBuilder())
269 parser.feed(text)
270 return parser.close()
271
272 def _element_factory(*args, **kwargs):
273 el = etree.Element(*args, **kwargs)
274 for k, v in el.items():
387db16a
JMF
275 if isinstance(v, bytes):
276 el.set(k, v.decode('utf-8'))
36e6f62c
JMF
277 return el
278
279 def compat_etree_fromstring(text):
f7854627
JMF
280 doc = _XML(text, parser=etree.XMLParser(target=etree.TreeBuilder(element_factory=_element_factory)))
281 for el in _etree_iter(doc):
282 if el.text is not None and isinstance(el.text, bytes):
283 el.text = el.text.decode('utf-8')
284 return doc
8c25f81b 285
57f7e3c6
S
286if sys.version_info < (2, 7):
287 # Here comes the crazy part: In 2.6, if the xpath is a unicode,
288 # .//node does not match if a node is a direct child of . !
289 def compat_xpath(xpath):
290 if isinstance(xpath, compat_str):
291 xpath = xpath.encode('ascii')
292 return xpath
293else:
294 compat_xpath = lambda xpath: xpath
295
8c25f81b
PH
296try:
297 from urllib.parse import parse_qs as compat_parse_qs
5f6a1245 298except ImportError: # Python 2
8c25f81b
PH
299 # HACK: The following is the correct parse_qs implementation from cpython 3's stdlib.
300 # Python 2's version is apparently totally broken
301
302 def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
9e1a5b84 303 encoding='utf-8', errors='replace'):
8f9312c3 304 qs, _coerce_result = qs, compat_str
8c25f81b
PH
305 pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
306 r = []
307 for name_value in pairs:
308 if not name_value and not strict_parsing:
309 continue
310 nv = name_value.split('=', 1)
311 if len(nv) != 2:
312 if strict_parsing:
611c1dd9 313 raise ValueError('bad query field: %r' % (name_value,))
8c25f81b
PH
314 # Handle case of a control-name with no equal sign
315 if keep_blank_values:
316 nv.append('')
317 else:
318 continue
319 if len(nv[1]) or keep_blank_values:
320 name = nv[0].replace('+', ' ')
321 name = compat_urllib_parse_unquote(
322 name, encoding=encoding, errors=errors)
323 name = _coerce_result(name)
324 value = nv[1].replace('+', ' ')
325 value = compat_urllib_parse_unquote(
326 value, encoding=encoding, errors=errors)
327 value = _coerce_result(value)
328 r.append((name, value))
329 return r
330
331 def compat_parse_qs(qs, keep_blank_values=False, strict_parsing=False,
9e1a5b84 332 encoding='utf-8', errors='replace'):
8c25f81b
PH
333 parsed_result = {}
334 pairs = _parse_qsl(qs, keep_blank_values, strict_parsing,
9e1a5b84 335 encoding=encoding, errors=errors)
8c25f81b
PH
336 for name, value in pairs:
337 if name in parsed_result:
338 parsed_result[name].append(value)
339 else:
340 parsed_result[name] = [value]
341 return parsed_result
342
8c25f81b 343try:
702ccf2d 344 from shlex import quote as compat_shlex_quote
8c25f81b 345except ImportError: # Python < 3.3
702ccf2d 346 def compat_shlex_quote(s):
7d4111ed
PH
347 if re.match(r'^[-_\w./]+$', s):
348 return s
349 else:
350 return "'" + s.replace("'", "'\"'\"'") + "'"
8c25f81b
PH
351
352
8df5ae15 353if sys.version_info >= (2, 7, 3):
51f579b6
S
354 compat_shlex_split = shlex.split
355else:
356 # Working around shlex issue with unicode strings on some python 2
357 # versions (see http://bugs.python.org/issue1548891)
358 def compat_shlex_split(s, comments=False, posix=True):
953fed28 359 if isinstance(s, compat_str):
51f579b6
S
360 s = s.encode('utf-8')
361 return shlex.split(s, comments, posix)
362
363
8c25f81b 364def compat_ord(c):
5f6a1245
JW
365 if type(c) is int:
366 return c
367 else:
368 return ord(c)
8c25f81b
PH
369
370
e9c0cdd3
YCH
371compat_os_name = os._name if os.name == 'java' else os.name
372
373
8c25f81b
PH
374if sys.version_info >= (3, 0):
375 compat_getenv = os.getenv
376 compat_expanduser = os.path.expanduser
fe40f9ee
S
377
378 def compat_setenv(key, value, env=os.environ):
379 env[key] = value
8c25f81b
PH
380else:
381 # Environment variables should be decoded with filesystem encoding.
382 # Otherwise it will fail if any non-ASCII characters present (see #3854 #3217 #2918)
383
384 def compat_getenv(key, default=None):
385 from .utils import get_filesystem_encoding
386 env = os.getenv(key, default)
387 if env:
388 env = env.decode(get_filesystem_encoding())
389 return env
390
fe40f9ee
S
391 def compat_setenv(key, value, env=os.environ):
392 def encode(v):
393 from .utils import get_filesystem_encoding
394 return v.encode(get_filesystem_encoding()) if isinstance(v, compat_str) else v
395 env[encode(key)] = encode(value)
396
8c25f81b
PH
397 # HACK: The default implementations of os.path.expanduser from cpython do not decode
398 # environment variables with filesystem encoding. We will work around this by
399 # providing adjusted implementations.
400 # The following are os.path.expanduser implementations from cpython 2.7.8 stdlib
401 # for different platforms with correct environment variables decoding.
402
e9c0cdd3 403 if compat_os_name == 'posix':
8c25f81b
PH
404 def compat_expanduser(path):
405 """Expand ~ and ~user constructions. If user or $HOME is unknown,
406 do nothing."""
407 if not path.startswith('~'):
408 return path
409 i = path.find('/', 1)
410 if i < 0:
411 i = len(path)
412 if i == 1:
413 if 'HOME' not in os.environ:
414 import pwd
415 userhome = pwd.getpwuid(os.getuid()).pw_dir
416 else:
417 userhome = compat_getenv('HOME')
418 else:
419 import pwd
420 try:
421 pwent = pwd.getpwnam(path[1:i])
422 except KeyError:
423 return path
424 userhome = pwent.pw_dir
425 userhome = userhome.rstrip('/')
426 return (userhome + path[i:]) or '/'
e9c0cdd3 427 elif compat_os_name == 'nt' or compat_os_name == 'ce':
8c25f81b
PH
428 def compat_expanduser(path):
429 """Expand ~ and ~user constructs.
430
431 If user or $HOME is unknown, do nothing."""
432 if path[:1] != '~':
433 return path
434 i, n = 1, len(path)
435 while i < n and path[i] not in '/\\':
436 i = i + 1
437
438 if 'HOME' in os.environ:
439 userhome = compat_getenv('HOME')
440 elif 'USERPROFILE' in os.environ:
441 userhome = compat_getenv('USERPROFILE')
83e865a3 442 elif 'HOMEPATH' not in os.environ:
8c25f81b
PH
443 return path
444 else:
445 try:
446 drive = compat_getenv('HOMEDRIVE')
447 except KeyError:
448 drive = ''
449 userhome = os.path.join(drive, compat_getenv('HOMEPATH'))
450
5f6a1245 451 if i != 1: # ~user
8c25f81b
PH
452 userhome = os.path.join(os.path.dirname(userhome), path[1:i])
453
454 return userhome + path[i:]
455 else:
456 compat_expanduser = os.path.expanduser
457
458
459if sys.version_info < (3, 0):
460 def compat_print(s):
461 from .utils import preferredencoding
462 print(s.encode(preferredencoding(), 'xmlcharrefreplace'))
463else:
464 def compat_print(s):
b061ea6e 465 assert isinstance(s, compat_str)
8c25f81b
PH
466 print(s)
467
468
8c25f81b
PH
469if sys.version_info < (3, 0) and sys.platform == 'win32':
470 def compat_getpass(prompt, *args, **kwargs):
471 if isinstance(prompt, compat_str):
baa70803 472 from .utils import preferredencoding
8c25f81b
PH
473 prompt = prompt.encode(preferredencoding())
474 return getpass.getpass(prompt, *args, **kwargs)
475else:
476 compat_getpass = getpass.getpass
477
614db89a 478# Python < 2.6.5 require kwargs to be bytes
c7b0add8 479try:
c6973bd4
PH
480 def _testfunc(x):
481 pass
482 _testfunc(**{'x': 0})
c7b0add8
PH
483except TypeError:
484 def compat_kwargs(kwargs):
485 return dict((bytes(k), v) for k, v in kwargs.items())
486else:
487 compat_kwargs = lambda kwargs: kwargs
8c25f81b 488
e07e9313 489
be4a824d
PH
490if sys.version_info < (2, 7):
491 def compat_socket_create_connection(address, timeout, source_address=None):
492 host, port = address
493 err = None
494 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
495 af, socktype, proto, canonname, sa = res
496 sock = None
497 try:
498 sock = socket.socket(af, socktype, proto)
499 sock.settimeout(timeout)
500 if source_address:
501 sock.bind(source_address)
502 sock.connect(sa)
503 return sock
504 except socket.error as _:
505 err = _
506 if sock is not None:
507 sock.close()
508 if err is not None:
509 raise err
510 else:
611c1dd9 511 raise socket.error('getaddrinfo returns an empty list')
be4a824d
PH
512else:
513 compat_socket_create_connection = socket.create_connection
514
515
e07e9313
PH
516# Fix https://github.com/rg3/youtube-dl/issues/4223
517# See http://bugs.python.org/issue9161 for what is broken
518def workaround_optparse_bug9161():
07e378fa
PH
519 op = optparse.OptionParser()
520 og = optparse.OptionGroup(op, 'foo')
e07e9313 521 try:
07e378fa 522 og.add_option('-t')
b244b5c3 523 except TypeError:
e07e9313
PH
524 real_add_option = optparse.OptionGroup.add_option
525
526 def _compat_add_option(self, *args, **kwargs):
527 enc = lambda v: (
528 v.encode('ascii', 'replace') if isinstance(v, compat_str)
529 else v)
530 bargs = [enc(a) for a in args]
531 bkwargs = dict(
532 (k, enc(v)) for k, v in kwargs.items())
533 return real_add_option(self, *bargs, **bkwargs)
534 optparse.OptionGroup.add_option = _compat_add_option
535
003c69a8
JMF
536if hasattr(shutil, 'get_terminal_size'): # Python >= 3.3
537 compat_get_terminal_size = shutil.get_terminal_size
538else:
539 _terminal_size = collections.namedtuple('terminal_size', ['columns', 'lines'])
540
13118a50 541 def compat_get_terminal_size(fallback=(80, 24)):
4810c48d 542 columns = compat_getenv('COLUMNS')
003c69a8
JMF
543 if columns:
544 columns = int(columns)
545 else:
546 columns = None
4810c48d 547 lines = compat_getenv('LINES')
003c69a8
JMF
548 if lines:
549 lines = int(lines)
550 else:
551 lines = None
552
4810c48d 553 if columns is None or lines is None or columns <= 0 or lines <= 0:
13118a50
YCH
554 try:
555 sp = subprocess.Popen(
556 ['stty', 'size'],
557 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
558 out, err = sp.communicate()
f2dbc540 559 _lines, _columns = map(int, out.split())
13118a50
YCH
560 except Exception:
561 _columns, _lines = _terminal_size(*fallback)
562
4810c48d 563 if columns is None or columns <= 0:
13118a50 564 columns = _columns
4810c48d 565 if lines is None or lines <= 0:
13118a50 566 lines = _lines
003c69a8
JMF
567 return _terminal_size(columns, lines)
568
a0e060ac
YCH
569try:
570 itertools.count(start=0, step=1)
571 compat_itertools_count = itertools.count
572except TypeError: # Python 2.6
573 def compat_itertools_count(start=0, step=1):
574 n = start
575 while True:
576 yield n
577 n += step
e07e9313 578
67134eab
JMF
579if sys.version_info >= (3, 0):
580 from tokenize import tokenize as compat_tokenize_tokenize
581else:
582 from tokenize import generate_tokens as compat_tokenize_tokenize
e07e9313 583
dab0daee
YCH
584
585try:
586 struct.pack('!I', 0)
587except TypeError:
588 # In Python 2.6 and 2.7.x < 2.7.7, struct requires a bytes argument
589 # See https://bugs.python.org/issue19099
edaa23f8 590 def compat_struct_pack(spec, *args):
dab0daee
YCH
591 if isinstance(spec, compat_str):
592 spec = spec.encode('ascii')
593 return struct.pack(spec, *args)
594
edaa23f8 595 def compat_struct_unpack(spec, *args):
dab0daee
YCH
596 if isinstance(spec, compat_str):
597 spec = spec.encode('ascii')
598 return struct.unpack(spec, *args)
599else:
edaa23f8
YCH
600 compat_struct_pack = struct.pack
601 compat_struct_unpack = struct.unpack
dab0daee
YCH
602
603
8c25f81b 604__all__ = [
8bb56eee 605 'compat_HTMLParser',
8c25f81b 606 'compat_HTTPError',
0196149c 607 'compat_basestring',
8c25f81b
PH
608 'compat_chr',
609 'compat_cookiejar',
799207e8 610 'compat_cookies',
36e6f62c 611 'compat_etree_fromstring',
8c25f81b 612 'compat_expanduser',
003c69a8 613 'compat_get_terminal_size',
8c25f81b
PH
614 'compat_getenv',
615 'compat_getpass',
616 'compat_html_entities',
8c25f81b 617 'compat_http_client',
83fda3c0 618 'compat_http_server',
a0e060ac 619 'compat_itertools_count',
c7b0add8 620 'compat_kwargs',
8c25f81b 621 'compat_ord',
e9c0cdd3 622 'compat_os_name',
8c25f81b
PH
623 'compat_parse_qs',
624 'compat_print',
fe40f9ee 625 'compat_setenv',
702ccf2d 626 'compat_shlex_quote',
51f579b6 627 'compat_shlex_split',
be4a824d 628 'compat_socket_create_connection',
987493ae 629 'compat_str',
edaa23f8
YCH
630 'compat_struct_pack',
631 'compat_struct_unpack',
8c25f81b 632 'compat_subprocess_get_DEVNULL',
67134eab 633 'compat_tokenize_tokenize',
8c25f81b
PH
634 'compat_urllib_error',
635 'compat_urllib_parse',
636 'compat_urllib_parse_unquote',
aa99aa4e 637 'compat_urllib_parse_unquote_plus',
9fefc886 638 'compat_urllib_parse_unquote_to_bytes',
15707c7e 639 'compat_urllib_parse_urlencode',
8c25f81b
PH
640 'compat_urllib_parse_urlparse',
641 'compat_urllib_request',
0a67a363
YCH
642 'compat_urllib_request_DataHandler',
643 'compat_urllib_response',
8c25f81b
PH
644 'compat_urlparse',
645 'compat_urlretrieve',
646 'compat_xml_parse_error',
57f7e3c6 647 'compat_xpath',
e07e9313 648 'workaround_optparse_bug9161',
8c25f81b 649]