1# coding: utf-8
2
3import asyncio
4import base64
5import ctypes
6import getpass
7import html
8import html.parser
9import http
10import http.client
11import http.cookiejar
12import http.cookies
13import http.server
14import itertools
15import optparse
16import os
17import re
18import shlex
19import shutil
20import socket
21import struct
22import subprocess
23import sys
24import tokenize
25import urllib
26import xml.etree.ElementTree as etree
27from subprocess import DEVNULL
28
29
30# HTMLParseError has been deprecated in Python 3.3 and removed in
31# Python 3.5. Introducing dummy exception for Python >3.5 for compatible
32# and uniform cross-version exception handling
33class compat_HTMLParseError(Exception):
34    pass
35
36
37# compat_ctypes_WINFUNCTYPE = ctypes.WINFUNCTYPE
38# will not work since ctypes.WINFUNCTYPE does not exist in UNIX machines
39def compat_ctypes_WINFUNCTYPE(*args, **kwargs):
40    return ctypes.WINFUNCTYPE(*args, **kwargs)
41
42
43class _TreeBuilder(etree.TreeBuilder):
44    def doctype(self, name, pubid, system):
45        pass
46
47
48def compat_etree_fromstring(text):
49    return etree.XML(text, parser=etree.XMLParser(target=_TreeBuilder()))
50
51
52compat_os_name = os._name if os.name == 'java' else os.name
53
54
55if compat_os_name == 'nt':
56    def compat_shlex_quote(s):
57        return s if re.match(r'^[-_\w./]+$', s) else '"%s"' % s.replace('"', '\\"')
58else:
59    from shlex import quote as compat_shlex_quote
60
61
62def compat_ord(c):
63    if type(c) is int:
64        return c
65    else:
66        return ord(c)
67
68
69def compat_setenv(key, value, env=os.environ):
70    env[key] = value
71
72
73if compat_os_name == 'nt' and sys.version_info < (3, 8):
74    # os.path.realpath on Windows does not follow symbolic links
75    # prior to Python 3.8 (see https://bugs.python.org/issue9949)
76    def compat_realpath(path):
77        while os.path.islink(path):
78            path = os.path.abspath(os.readlink(path))
79        return path
80else:
81    compat_realpath = os.path.realpath
82
83
84def compat_print(s):
85    assert isinstance(s, compat_str)
86    print(s)
87
88
89# Fix https://github.com/ytdl-org/youtube-dl/issues/4223
90# See http://bugs.python.org/issue9161 for what is broken
91def workaround_optparse_bug9161():
92    op = optparse.OptionParser()
93    og = optparse.OptionGroup(op, 'foo')
94    try:
95        og.add_option('-t')
96    except TypeError:
97        real_add_option = optparse.OptionGroup.add_option
98
99        def _compat_add_option(self, *args, **kwargs):
100            enc = lambda v: (
101                v.encode('ascii', 'replace') if isinstance(v, compat_str)
102                else v)
103            bargs = [enc(a) for a in args]
104            bkwargs = dict(
105                (k, enc(v)) for k, v in kwargs.items())
106            return real_add_option(self, *bargs, **bkwargs)
107        optparse.OptionGroup.add_option = _compat_add_option
108
109
110try:
111    compat_Pattern = re.Pattern
112except AttributeError:
113    compat_Pattern = type(re.compile(''))
114
115
116try:
117    compat_Match = re.Match
118except AttributeError:
119    compat_Match = type(re.compile('').match(''))
120
121
122try:
123    compat_asyncio_run = asyncio.run  # >= 3.7
124except AttributeError:
125    def compat_asyncio_run(coro):
126        try:
127            loop = asyncio.get_event_loop()
128        except RuntimeError:
129            loop = asyncio.new_event_loop()
130            asyncio.set_event_loop(loop)
131        loop.run_until_complete(coro)
132
133    asyncio.run = compat_asyncio_run
134
135
136# Python 3.8+ does not honor %HOME% on windows, but this breaks compatibility with youtube-dl
137# See https://github.com/yt-dlp/yt-dlp/issues/792
138# https://docs.python.org/3/library/os.path.html#os.path.expanduser
139if compat_os_name in ('nt', 'ce') and 'HOME' in os.environ:
140    _userhome = os.environ['HOME']
141
142    def compat_expanduser(path):
143        if not path.startswith('~'):
144            return path
145        i = path.replace('\\', '/', 1).find('/')  # ~user
146        if i < 0:
147            i = len(path)
148        userhome = os.path.join(os.path.dirname(_userhome), path[1:i]) if i > 1 else _userhome
149        return userhome + path[i:]
150else:
151    compat_expanduser = os.path.expanduser
152
153
154try:
155    from Cryptodome.Cipher import AES as compat_pycrypto_AES
156except ImportError:
157    try:
158        from Crypto.Cipher import AES as compat_pycrypto_AES
159    except ImportError:
160        compat_pycrypto_AES = None
161
162
163WINDOWS_VT_MODE = False if compat_os_name == 'nt' else None
164
165
166def windows_enable_vt_mode():  # TODO: Do this the proper way https://bugs.python.org/issue30075
167    if compat_os_name != 'nt':
168        return
169    global WINDOWS_VT_MODE
170    startupinfo = subprocess.STARTUPINFO()
171    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
172    try:
173        subprocess.Popen('', shell=True, startupinfo=startupinfo)
174        WINDOWS_VT_MODE = True
175    except Exception:
176        pass
177
178
179#  Deprecated
180
181compat_basestring = str
182compat_chr = chr
183compat_input = input
184compat_integer_types = (int, )
185compat_kwargs = lambda kwargs: kwargs
186compat_numeric_types = (int, float, complex)
187compat_str = str
188compat_xpath = lambda xpath: xpath
189compat_zip = zip
190
191compat_HTMLParser = html.parser.HTMLParser
192compat_HTTPError = urllib.error.HTTPError
193compat_Struct = struct.Struct
194compat_b64decode = base64.b64decode
195compat_cookiejar = http.cookiejar
196compat_cookiejar_Cookie = compat_cookiejar.Cookie
197compat_cookies = http.cookies
198compat_cookies_SimpleCookie = compat_cookies.SimpleCookie
199compat_etree_Element = etree.Element
200compat_etree_register_namespace = etree.register_namespace
201compat_get_terminal_size = shutil.get_terminal_size
202compat_getenv = os.getenv
203compat_getpass = getpass.getpass
204compat_html_entities = html.entities
205compat_html_entities_html5 = compat_html_entities.html5
206compat_http_client = http.client
207compat_http_server = http.server
208compat_itertools_count = itertools.count
209compat_parse_qs = urllib.parse.parse_qs
210compat_shlex_split = shlex.split
211compat_socket_create_connection = socket.create_connection
212compat_struct_pack = struct.pack
213compat_struct_unpack = struct.unpack
214compat_subprocess_get_DEVNULL = lambda: DEVNULL
215compat_tokenize_tokenize = tokenize.tokenize
216compat_urllib_error = urllib.error
217compat_urllib_parse = urllib.parse
218compat_urllib_parse_quote = urllib.parse.quote
219compat_urllib_parse_quote_plus = urllib.parse.quote_plus
220compat_urllib_parse_unquote = urllib.parse.unquote
221compat_urllib_parse_unquote_plus = urllib.parse.unquote_plus
222compat_urllib_parse_unquote_to_bytes = urllib.parse.unquote_to_bytes
223compat_urllib_parse_urlencode = urllib.parse.urlencode
224compat_urllib_parse_urlparse = urllib.parse.urlparse
225compat_urllib_parse_urlunparse = urllib.parse.urlunparse
226compat_urllib_request = urllib.request
227compat_urllib_request_DataHandler = urllib.request.DataHandler
228compat_urllib_response = urllib.response
229compat_urlparse = urllib.parse
230compat_urlretrieve = urllib.request.urlretrieve
231compat_xml_parse_error = etree.ParseError
232
233
234# Set public objects
235
236__all__ = [
237    'WINDOWS_VT_MODE',
238    'compat_HTMLParseError',
239    'compat_HTMLParser',
240    'compat_HTTPError',
241    'compat_Match',
242    'compat_Pattern',
243    'compat_Struct',
244    'compat_asyncio_run',
245    'compat_b64decode',
246    'compat_basestring',
247    'compat_chr',
248    'compat_cookiejar',
249    'compat_cookiejar_Cookie',
250    'compat_cookies',
251    'compat_cookies_SimpleCookie',
252    'compat_ctypes_WINFUNCTYPE',
253    'compat_etree_Element',
254    'compat_etree_fromstring',
255    'compat_etree_register_namespace',
256    'compat_expanduser',
257    'compat_get_terminal_size',
258    'compat_getenv',
259    'compat_getpass',
260    'compat_html_entities',
261    'compat_html_entities_html5',
262    'compat_http_client',
263    'compat_http_server',
264    'compat_input',
265    'compat_integer_types',
266    'compat_itertools_count',
267    'compat_kwargs',
268    'compat_numeric_types',
269    'compat_ord',
270    'compat_os_name',
271    'compat_parse_qs',
272    'compat_print',
273    'compat_pycrypto_AES',
274    'compat_realpath',
275    'compat_setenv',
276    'compat_shlex_quote',
277    'compat_shlex_split',
278    'compat_socket_create_connection',
279    'compat_str',
280    'compat_struct_pack',
281    'compat_struct_unpack',
282    'compat_subprocess_get_DEVNULL',
283    'compat_tokenize_tokenize',
284    'compat_urllib_error',
285    'compat_urllib_parse',
286    'compat_urllib_parse_quote',
287    'compat_urllib_parse_quote_plus',
288    'compat_urllib_parse_unquote',
289    'compat_urllib_parse_unquote_plus',
290    'compat_urllib_parse_unquote_to_bytes',
291    'compat_urllib_parse_urlencode',
292    'compat_urllib_parse_urlparse',
293    'compat_urllib_parse_urlunparse',
294    'compat_urllib_request',
295    'compat_urllib_request_DataHandler',
296    'compat_urllib_response',
297    'compat_urlparse',
298    'compat_urlretrieve',
299    'compat_xml_parse_error',
300    'compat_xpath',
301    'compat_zip',
302    'windows_enable_vt_mode',
303    'workaround_optparse_bug9161',
304]
305