_legacy.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. """No longer used and new code should not use. Exists only for API compat."""
  2. import asyncio
  3. import atexit
  4. import platform
  5. import struct
  6. import sys
  7. import urllib.error
  8. import urllib.parse
  9. import urllib.request
  10. import zlib
  11. from ._utils import Popen, decode_base_n, preferredencoding
  12. from .traversal import traverse_obj
  13. from ..dependencies import certifi, websockets
  14. from ..networking._helper import make_ssl_context
  15. from ..networking._urllib import HTTPHandler
  16. # isort: split
  17. from .networking import escape_rfc3986 # noqa: F401
  18. from .networking import normalize_url as escape_url
  19. from .networking import random_user_agent, std_headers # noqa: F401
  20. from ..cookies import YoutubeDLCookieJar # noqa: F401
  21. from ..networking._urllib import PUTRequest # noqa: F401
  22. from ..networking._urllib import SUPPORTED_ENCODINGS, HEADRequest # noqa: F401
  23. from ..networking._urllib import ProxyHandler as PerRequestProxyHandler # noqa: F401
  24. from ..networking._urllib import RedirectHandler as YoutubeDLRedirectHandler # noqa: F401
  25. from ..networking._urllib import ( # noqa: F401
  26. make_socks_conn_class,
  27. update_Request,
  28. )
  29. from ..networking.exceptions import HTTPError, network_exceptions # noqa: F401
  30. has_certifi = bool(certifi)
  31. has_websockets = bool(websockets)
  32. class WebSocketsWrapper:
  33. """Wraps websockets module to use in non-async scopes"""
  34. pool = None
  35. def __init__(self, url, headers=None, connect=True, **ws_kwargs):
  36. self.loop = asyncio.new_event_loop()
  37. # XXX: "loop" is deprecated
  38. self.conn = websockets.connect(
  39. url, extra_headers=headers, ping_interval=None,
  40. close_timeout=float('inf'), loop=self.loop, ping_timeout=float('inf'), **ws_kwargs)
  41. if connect:
  42. self.__enter__()
  43. atexit.register(self.__exit__, None, None, None)
  44. def __enter__(self):
  45. if not self.pool:
  46. self.pool = self.run_with_loop(self.conn.__aenter__(), self.loop)
  47. return self
  48. def send(self, *args):
  49. self.run_with_loop(self.pool.send(*args), self.loop)
  50. def recv(self, *args):
  51. return self.run_with_loop(self.pool.recv(*args), self.loop)
  52. def __exit__(self, type, value, traceback):
  53. try:
  54. return self.run_with_loop(self.conn.__aexit__(type, value, traceback), self.loop)
  55. finally:
  56. self.loop.close()
  57. self._cancel_all_tasks(self.loop)
  58. # taken from https://github.com/python/cpython/blob/3.9/Lib/asyncio/runners.py with modifications
  59. # for contributors: If there's any new library using asyncio needs to be run in non-async, move these function out of this class
  60. @staticmethod
  61. def run_with_loop(main, loop):
  62. if not asyncio.iscoroutine(main):
  63. raise ValueError(f'a coroutine was expected, got {main!r}')
  64. try:
  65. return loop.run_until_complete(main)
  66. finally:
  67. loop.run_until_complete(loop.shutdown_asyncgens())
  68. if hasattr(loop, 'shutdown_default_executor'):
  69. loop.run_until_complete(loop.shutdown_default_executor())
  70. @staticmethod
  71. def _cancel_all_tasks(loop):
  72. to_cancel = asyncio.all_tasks(loop)
  73. if not to_cancel:
  74. return
  75. for task in to_cancel:
  76. task.cancel()
  77. # XXX: "loop" is removed in Python 3.10+
  78. loop.run_until_complete(
  79. asyncio.gather(*to_cancel, loop=loop, return_exceptions=True))
  80. for task in to_cancel:
  81. if task.cancelled():
  82. continue
  83. if task.exception() is not None:
  84. loop.call_exception_handler({
  85. 'message': 'unhandled exception during asyncio.run() shutdown',
  86. 'exception': task.exception(),
  87. 'task': task,
  88. })
  89. def load_plugins(name, suffix, namespace):
  90. from ..plugins import load_plugins
  91. ret = load_plugins(name, suffix)
  92. namespace.update(ret)
  93. return ret
  94. def traverse_dict(dictn, keys, casesense=True):
  95. return traverse_obj(dictn, keys, casesense=casesense, is_user_input=True, traverse_string=True)
  96. def decode_base(value, digits):
  97. return decode_base_n(value, table=digits)
  98. def platform_name():
  99. """ Returns the platform name as a str """
  100. return platform.platform()
  101. def get_subprocess_encoding():
  102. if sys.platform == 'win32' and sys.getwindowsversion()[0] >= 5:
  103. # For subprocess calls, encode with locale encoding
  104. # Refer to http://stackoverflow.com/a/9951851/35070
  105. encoding = preferredencoding()
  106. else:
  107. encoding = sys.getfilesystemencoding()
  108. if encoding is None:
  109. encoding = 'utf-8'
  110. return encoding
  111. # UNUSED
  112. # Based on png2str() written by @gdkchan and improved by @yokrysty
  113. # Originally posted at https://github.com/ytdl-org/youtube-dl/issues/9706
  114. def decode_png(png_data):
  115. # Reference: https://www.w3.org/TR/PNG/
  116. header = png_data[8:]
  117. if png_data[:8] != b'\x89PNG\x0d\x0a\x1a\x0a' or header[4:8] != b'IHDR':
  118. raise OSError('Not a valid PNG file.')
  119. int_map = {1: '>B', 2: '>H', 4: '>I'}
  120. unpack_integer = lambda x: struct.unpack(int_map[len(x)], x)[0]
  121. chunks = []
  122. while header:
  123. length = unpack_integer(header[:4])
  124. header = header[4:]
  125. chunk_type = header[:4]
  126. header = header[4:]
  127. chunk_data = header[:length]
  128. header = header[length:]
  129. header = header[4:] # Skip CRC
  130. chunks.append({
  131. 'type': chunk_type,
  132. 'length': length,
  133. 'data': chunk_data,
  134. })
  135. ihdr = chunks[0]['data']
  136. width = unpack_integer(ihdr[:4])
  137. height = unpack_integer(ihdr[4:8])
  138. idat = b''
  139. for chunk in chunks:
  140. if chunk['type'] == b'IDAT':
  141. idat += chunk['data']
  142. if not idat:
  143. raise OSError('Unable to read PNG data.')
  144. decompressed_data = bytearray(zlib.decompress(idat))
  145. stride = width * 3
  146. pixels = []
  147. def _get_pixel(idx):
  148. x = idx % stride
  149. y = idx // stride
  150. return pixels[y][x]
  151. for y in range(height):
  152. base_pos = y * (1 + stride)
  153. filter_type = decompressed_data[base_pos]
  154. current_row = []
  155. pixels.append(current_row)
  156. for x in range(stride):
  157. color = decompressed_data[1 + base_pos + x]
  158. basex = y * stride + x
  159. left = 0
  160. up = 0
  161. if x > 2:
  162. left = _get_pixel(basex - 3)
  163. if y > 0:
  164. up = _get_pixel(basex - stride)
  165. if filter_type == 1: # Sub
  166. color = (color + left) & 0xff
  167. elif filter_type == 2: # Up
  168. color = (color + up) & 0xff
  169. elif filter_type == 3: # Average
  170. color = (color + ((left + up) >> 1)) & 0xff
  171. elif filter_type == 4: # Paeth
  172. a = left
  173. b = up
  174. c = 0
  175. if x > 2 and y > 0:
  176. c = _get_pixel(basex - stride - 3)
  177. p = a + b - c
  178. pa = abs(p - a)
  179. pb = abs(p - b)
  180. pc = abs(p - c)
  181. if pa <= pb and pa <= pc:
  182. color = (color + a) & 0xff
  183. elif pb <= pc:
  184. color = (color + b) & 0xff
  185. else:
  186. color = (color + c) & 0xff
  187. current_row.append(color)
  188. return width, height, pixels
  189. def register_socks_protocols():
  190. # "Register" SOCKS protocols
  191. # In Python < 2.6.5, urlsplit() suffers from bug https://bugs.python.org/issue7904
  192. # URLs with protocols not in urlparse.uses_netloc are not handled correctly
  193. for scheme in ('socks', 'socks4', 'socks4a', 'socks5'):
  194. if scheme not in urllib.parse.uses_netloc:
  195. urllib.parse.uses_netloc.append(scheme)
  196. def handle_youtubedl_headers(headers):
  197. filtered_headers = headers
  198. if 'Youtubedl-no-compression' in filtered_headers:
  199. filtered_headers = {k: v for k, v in filtered_headers.items() if k.lower() != 'accept-encoding'}
  200. del filtered_headers['Youtubedl-no-compression']
  201. return filtered_headers
  202. def request_to_url(req):
  203. if isinstance(req, urllib.request.Request):
  204. return req.get_full_url()
  205. else:
  206. return req
  207. def sanitized_Request(url, *args, **kwargs):
  208. from ..utils import extract_basic_auth, sanitize_url
  209. url, auth_header = extract_basic_auth(escape_url(sanitize_url(url)))
  210. if auth_header is not None:
  211. headers = args[1] if len(args) >= 2 else kwargs.setdefault('headers', {})
  212. headers['Authorization'] = auth_header
  213. return urllib.request.Request(url, *args, **kwargs)
  214. class YoutubeDLHandler(HTTPHandler):
  215. def __init__(self, params, *args, **kwargs):
  216. self._params = params
  217. super().__init__(*args, **kwargs)
  218. YoutubeDLHTTPSHandler = YoutubeDLHandler
  219. class YoutubeDLCookieProcessor(urllib.request.HTTPCookieProcessor):
  220. def __init__(self, cookiejar=None):
  221. urllib.request.HTTPCookieProcessor.__init__(self, cookiejar)
  222. def http_response(self, request, response):
  223. return urllib.request.HTTPCookieProcessor.http_response(self, request, response)
  224. https_request = urllib.request.HTTPCookieProcessor.http_request
  225. https_response = http_response
  226. def make_HTTPS_handler(params, **kwargs):
  227. return YoutubeDLHTTPSHandler(params, context=make_ssl_context(
  228. verify=not params.get('nocheckcertificate'),
  229. client_certificate=params.get('client_certificate'),
  230. client_certificate_key=params.get('client_certificate_key'),
  231. client_certificate_password=params.get('client_certificate_password'),
  232. legacy_support=params.get('legacyserverconnect'),
  233. use_certifi='no-certifi' not in params.get('compat_opts', []),
  234. ), **kwargs)
  235. def process_communicate_or_kill(p, *args, **kwargs):
  236. return Popen.communicate_or_kill(p, *args, **kwargs)