test_http.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. from __future__ import unicode_literals
  4. # Allow direct execution
  5. import os
  6. import sys
  7. import unittest
  8. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  9. import contextlib
  10. import gzip
  11. import io
  12. import ssl
  13. import tempfile
  14. import threading
  15. import zlib
  16. # avoid deprecated alias assertRaisesRegexp
  17. if hasattr(unittest.TestCase, 'assertRaisesRegex'):
  18. unittest.TestCase.assertRaisesRegexp = unittest.TestCase.assertRaisesRegex
  19. try:
  20. import brotli
  21. except ImportError:
  22. brotli = None
  23. try:
  24. from urllib.request import pathname2url
  25. except ImportError:
  26. from urllib import pathname2url
  27. from youtube_dl.compat import (
  28. compat_http_cookiejar_Cookie,
  29. compat_http_server,
  30. compat_str as str,
  31. compat_urllib_error,
  32. compat_urllib_HTTPError,
  33. compat_urllib_parse,
  34. compat_urllib_request,
  35. )
  36. from youtube_dl.utils import (
  37. sanitized_Request,
  38. urlencode_postdata,
  39. )
  40. from test.helper import (
  41. expectedFailureIf,
  42. FakeYDL,
  43. FakeLogger,
  44. http_server_port,
  45. )
  46. from youtube_dl import YoutubeDL
  47. TEST_DIR = os.path.dirname(os.path.abspath(__file__))
  48. class HTTPTestRequestHandler(compat_http_server.BaseHTTPRequestHandler):
  49. protocol_version = 'HTTP/1.1'
  50. # work-around old/new -style class inheritance
  51. def super(self, meth_name, *args, **kwargs):
  52. from types import MethodType
  53. try:
  54. super()
  55. fn = lambda s, m, *a, **k: getattr(super(), m)(*a, **k)
  56. except TypeError:
  57. fn = lambda s, m, *a, **k: getattr(compat_http_server.BaseHTTPRequestHandler, m)(s, *a, **k)
  58. self.super = MethodType(fn, self)
  59. return self.super(meth_name, *args, **kwargs)
  60. def log_message(self, format, *args):
  61. pass
  62. def _headers(self):
  63. payload = str(self.headers).encode('utf-8')
  64. self.send_response(200)
  65. self.send_header('Content-Type', 'application/json')
  66. self.send_header('Content-Length', str(len(payload)))
  67. self.end_headers()
  68. self.wfile.write(payload)
  69. def _redirect(self):
  70. self.send_response(int(self.path[len('/redirect_'):]))
  71. self.send_header('Location', '/method')
  72. self.send_header('Content-Length', '0')
  73. self.end_headers()
  74. def _method(self, method, payload=None):
  75. self.send_response(200)
  76. self.send_header('Content-Length', str(len(payload or '')))
  77. self.send_header('Method', method)
  78. self.end_headers()
  79. if payload:
  80. self.wfile.write(payload)
  81. def _status(self, status):
  82. payload = '<html>{0} NOT FOUND</html>'.format(status).encode('utf-8')
  83. self.send_response(int(status))
  84. self.send_header('Content-Type', 'text/html; charset=utf-8')
  85. self.send_header('Content-Length', str(len(payload)))
  86. self.end_headers()
  87. self.wfile.write(payload)
  88. def _read_data(self):
  89. if 'Content-Length' in self.headers:
  90. return self.rfile.read(int(self.headers['Content-Length']))
  91. def _test_url(self, path, host='127.0.0.1', scheme='http', port=None):
  92. return '{0}://{1}:{2}/{3}'.format(
  93. scheme, host,
  94. port if port is not None
  95. else http_server_port(self.server), path)
  96. def do_POST(self):
  97. data = self._read_data()
  98. if self.path.startswith('/redirect_'):
  99. self._redirect()
  100. elif self.path.startswith('/method'):
  101. self._method('POST', data)
  102. elif self.path.startswith('/headers'):
  103. self._headers()
  104. else:
  105. self._status(404)
  106. def do_HEAD(self):
  107. if self.path.startswith('/redirect_'):
  108. self._redirect()
  109. elif self.path.startswith('/method'):
  110. self._method('HEAD')
  111. else:
  112. self._status(404)
  113. def do_PUT(self):
  114. data = self._read_data()
  115. if self.path.startswith('/redirect_'):
  116. self._redirect()
  117. elif self.path.startswith('/method'):
  118. self._method('PUT', data)
  119. else:
  120. self._status(404)
  121. def do_GET(self):
  122. def respond(payload=b'<html><video src="/vid.mp4" /></html>',
  123. payload_type='text/html; charset=utf-8',
  124. payload_encoding=None,
  125. resp_code=200):
  126. self.send_response(resp_code)
  127. self.send_header('Content-Type', payload_type)
  128. if payload_encoding:
  129. self.send_header('Content-Encoding', payload_encoding)
  130. self.send_header('Content-Length', str(len(payload))) # required for persistent connections
  131. self.end_headers()
  132. self.wfile.write(payload)
  133. def gzip_compress(p):
  134. buf = io.BytesIO()
  135. with contextlib.closing(gzip.GzipFile(fileobj=buf, mode='wb')) as f:
  136. f.write(p)
  137. return buf.getvalue()
  138. if self.path == '/video.html':
  139. respond()
  140. elif self.path == '/vid.mp4':
  141. respond(b'\x00\x00\x00\x00\x20\x66\x74[video]', 'video/mp4')
  142. elif self.path == '/302':
  143. if sys.version_info[0] == 3:
  144. # XXX: Python 3 http server does not allow non-ASCII header values
  145. self.send_response(404)
  146. self.end_headers()
  147. return
  148. new_url = self._test_url('中文.html')
  149. self.send_response(302)
  150. self.send_header(b'Location', new_url.encode('utf-8'))
  151. self.end_headers()
  152. elif self.path == '/%E4%B8%AD%E6%96%87.html':
  153. respond()
  154. elif self.path == '/%c7%9f':
  155. respond()
  156. elif self.path.startswith('/redirect_'):
  157. self._redirect()
  158. elif self.path.startswith('/method'):
  159. self._method('GET')
  160. elif self.path.startswith('/headers'):
  161. self._headers()
  162. elif self.path.startswith('/308-to-headers'):
  163. self.send_response(308)
  164. self.send_header('Location', '/headers')
  165. self.send_header('Content-Length', '0')
  166. self.end_headers()
  167. elif self.path == '/trailing_garbage':
  168. payload = b'<html><video src="/vid.mp4" /></html>'
  169. compressed = gzip_compress(payload) + b'trailing garbage'
  170. respond(compressed, payload_encoding='gzip')
  171. elif self.path == '/302-non-ascii-redirect':
  172. new_url = self._test_url('中文.html')
  173. # actually respond with permanent redirect
  174. self.send_response(301)
  175. self.send_header('Location', new_url)
  176. self.send_header('Content-Length', '0')
  177. self.end_headers()
  178. elif self.path == '/content-encoding':
  179. encodings = self.headers.get('ytdl-encoding', '')
  180. payload = b'<html><video src="/vid.mp4" /></html>'
  181. for encoding in filter(None, (e.strip() for e in encodings.split(','))):
  182. if encoding == 'br' and brotli:
  183. payload = brotli.compress(payload)
  184. elif encoding == 'gzip':
  185. payload = gzip_compress(payload)
  186. elif encoding == 'deflate':
  187. payload = zlib.compress(payload)
  188. elif encoding == 'unsupported':
  189. payload = b'raw'
  190. break
  191. else:
  192. self._status(415)
  193. return
  194. respond(payload, payload_encoding=encodings)
  195. else:
  196. self._status(404)
  197. def send_header(self, keyword, value):
  198. """
  199. Forcibly allow HTTP server to send non percent-encoded non-ASCII characters in headers.
  200. This is against what is defined in RFC 3986: but we need to test that we support this
  201. since some sites incorrectly do this.
  202. """
  203. if keyword.lower() == 'connection':
  204. return self.super('send_header', keyword, value)
  205. if not hasattr(self, '_headers_buffer'):
  206. self._headers_buffer = []
  207. self._headers_buffer.append('{0}: {1}\r\n'.format(keyword, value).encode('utf-8'))
  208. def end_headers(self):
  209. if hasattr(self, '_headers_buffer'):
  210. self.wfile.write(b''.join(self._headers_buffer))
  211. self._headers_buffer = []
  212. self.super('end_headers')
  213. class TestHTTP(unittest.TestCase):
  214. # when does it make sense to check the SSL certificate?
  215. _check_cert = (
  216. sys.version_info >= (3, 2)
  217. or (sys.version_info[0] == 2 and sys.version_info[1:] >= (7, 19)))
  218. def setUp(self):
  219. # HTTP server
  220. self.http_httpd = compat_http_server.HTTPServer(
  221. ('127.0.0.1', 0), HTTPTestRequestHandler)
  222. self.http_port = http_server_port(self.http_httpd)
  223. self.http_server_thread = threading.Thread(target=self.http_httpd.serve_forever)
  224. self.http_server_thread.daemon = True
  225. self.http_server_thread.start()
  226. try:
  227. from http.server import ThreadingHTTPServer
  228. except ImportError:
  229. try:
  230. from socketserver import ThreadingMixIn
  231. except ImportError:
  232. from SocketServer import ThreadingMixIn
  233. class ThreadingHTTPServer(ThreadingMixIn, compat_http_server.HTTPServer):
  234. pass
  235. # HTTPS server
  236. certfn = os.path.join(TEST_DIR, 'testcert.pem')
  237. self.https_httpd = ThreadingHTTPServer(
  238. ('127.0.0.1', 0), HTTPTestRequestHandler)
  239. try:
  240. sslctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
  241. sslctx.verify_mode = ssl.CERT_NONE
  242. sslctx.check_hostname = False
  243. sslctx.load_cert_chain(certfn, None)
  244. self.https_httpd.socket = sslctx.wrap_socket(
  245. self.https_httpd.socket, server_side=True)
  246. except AttributeError:
  247. self.https_httpd.socket = ssl.wrap_socket(
  248. self.https_httpd.socket, certfile=certfn, server_side=True)
  249. self.https_port = http_server_port(self.https_httpd)
  250. self.https_server_thread = threading.Thread(target=self.https_httpd.serve_forever)
  251. self.https_server_thread.daemon = True
  252. self.https_server_thread.start()
  253. def tearDown(self):
  254. def closer(svr):
  255. def _closer():
  256. svr.shutdown()
  257. svr.server_close()
  258. return _closer
  259. shutdown_thread = threading.Thread(target=closer(self.http_httpd))
  260. shutdown_thread.start()
  261. self.http_server_thread.join(2.0)
  262. shutdown_thread = threading.Thread(target=closer(self.https_httpd))
  263. shutdown_thread.start()
  264. self.https_server_thread.join(2.0)
  265. def _test_url(self, path, host='127.0.0.1', scheme='http', port=None):
  266. return '{0}://{1}:{2}/{3}'.format(
  267. scheme, host,
  268. port if port is not None
  269. else self.https_port if scheme == 'https'
  270. else self.http_port, path)
  271. @unittest.skipUnless(_check_cert, 'No support for certificate check in SSL')
  272. def test_nocheckcertificate(self):
  273. with FakeYDL({'logger': FakeLogger()}) as ydl:
  274. with self.assertRaises(compat_urllib_error.URLError):
  275. ydl.urlopen(sanitized_Request(self._test_url('headers', scheme='https')))
  276. with FakeYDL({'logger': FakeLogger(), 'nocheckcertificate': True}) as ydl:
  277. r = ydl.urlopen(sanitized_Request(self._test_url('headers', scheme='https')))
  278. self.assertEqual(r.getcode(), 200)
  279. r.close()
  280. def test_percent_encode(self):
  281. with FakeYDL() as ydl:
  282. # Unicode characters should be encoded with uppercase percent-encoding
  283. res = ydl.urlopen(sanitized_Request(self._test_url('中文.html')))
  284. self.assertEqual(res.getcode(), 200)
  285. res.close()
  286. # don't normalize existing percent encodings
  287. res = ydl.urlopen(sanitized_Request(self._test_url('%c7%9f')))
  288. self.assertEqual(res.getcode(), 200)
  289. res.close()
  290. def test_unicode_path_redirection(self):
  291. with FakeYDL() as ydl:
  292. r = ydl.urlopen(sanitized_Request(self._test_url('302-non-ascii-redirect')))
  293. self.assertEqual(r.url, self._test_url('%E4%B8%AD%E6%96%87.html'))
  294. r.close()
  295. def test_redirect(self):
  296. with FakeYDL() as ydl:
  297. def do_req(redirect_status, method, check_no_content=False):
  298. data = b'testdata' if method in ('POST', 'PUT') else None
  299. res = ydl.urlopen(sanitized_Request(
  300. self._test_url('redirect_{0}'.format(redirect_status)),
  301. method=method, data=data))
  302. if check_no_content:
  303. self.assertNotIn('Content-Type', res.headers)
  304. return res.read().decode('utf-8'), res.headers.get('method', '')
  305. # A 303 must either use GET or HEAD for subsequent request
  306. self.assertEqual(do_req(303, 'POST'), ('', 'GET'))
  307. self.assertEqual(do_req(303, 'HEAD'), ('', 'HEAD'))
  308. self.assertEqual(do_req(303, 'PUT'), ('', 'GET'))
  309. # 301 and 302 turn POST only into a GET, with no Content-Type
  310. self.assertEqual(do_req(301, 'POST', True), ('', 'GET'))
  311. self.assertEqual(do_req(301, 'HEAD'), ('', 'HEAD'))
  312. self.assertEqual(do_req(302, 'POST', True), ('', 'GET'))
  313. self.assertEqual(do_req(302, 'HEAD'), ('', 'HEAD'))
  314. self.assertEqual(do_req(301, 'PUT'), ('testdata', 'PUT'))
  315. self.assertEqual(do_req(302, 'PUT'), ('testdata', 'PUT'))
  316. # 307 and 308 should not change method
  317. for m in ('POST', 'PUT'):
  318. self.assertEqual(do_req(307, m), ('testdata', m))
  319. self.assertEqual(do_req(308, m), ('testdata', m))
  320. self.assertEqual(do_req(307, 'HEAD'), ('', 'HEAD'))
  321. self.assertEqual(do_req(308, 'HEAD'), ('', 'HEAD'))
  322. # These should not redirect and instead raise an HTTPError
  323. for code in (300, 304, 305, 306):
  324. with self.assertRaises(compat_urllib_HTTPError):
  325. do_req(code, 'GET')
  326. # Jython 2.7.1 times out for some reason
  327. @expectedFailureIf(sys.platform.startswith('java') and sys.version_info < (2, 7, 2))
  328. def test_content_type(self):
  329. # https://github.com/yt-dlp/yt-dlp/commit/379a4f161d4ad3e40932dcf5aca6e6fb9715ab28
  330. with FakeYDL({'nocheckcertificate': True}) as ydl:
  331. # method should be auto-detected as POST
  332. r = sanitized_Request(self._test_url('headers', scheme='https'), data=urlencode_postdata({'test': 'test'}))
  333. headers = ydl.urlopen(r).read().decode('utf-8')
  334. self.assertIn('Content-Type: application/x-www-form-urlencoded', headers)
  335. # test http
  336. r = sanitized_Request(self._test_url('headers'), data=urlencode_postdata({'test': 'test'}))
  337. headers = ydl.urlopen(r).read().decode('utf-8')
  338. self.assertIn('Content-Type: application/x-www-form-urlencoded', headers)
  339. def test_cookiejar(self):
  340. with FakeYDL() as ydl:
  341. ydl.cookiejar.set_cookie(compat_http_cookiejar_Cookie(
  342. 0, 'test', 'ytdl', None, False, '127.0.0.1', True,
  343. False, '/headers', True, False, None, False, None, None, {}))
  344. data = ydl.urlopen(sanitized_Request(
  345. self._test_url('headers'))).read().decode('utf-8')
  346. self.assertIn('Cookie: test=ytdl', data)
  347. def test_passed_cookie_header(self):
  348. # We should accept a Cookie header being passed as in normal headers and handle it appropriately.
  349. with FakeYDL() as ydl:
  350. # Specified Cookie header should be used
  351. res = ydl.urlopen(sanitized_Request(
  352. self._test_url('headers'), headers={'Cookie': 'test=test'})).read().decode('utf-8')
  353. self.assertIn('Cookie: test=test', res)
  354. # Specified Cookie header should be removed on any redirect
  355. res = ydl.urlopen(sanitized_Request(
  356. self._test_url('308-to-headers'), headers={'Cookie': 'test=test'})).read().decode('utf-8')
  357. self.assertNotIn('Cookie: test=test', res)
  358. # Specified Cookie header should override global cookiejar for that request
  359. ydl.cookiejar.set_cookie(compat_http_cookiejar_Cookie(
  360. 0, 'test', 'ytdlp', None, False, '127.0.0.1', True,
  361. False, '/headers', True, False, None, False, None, None, {}))
  362. data = ydl.urlopen(sanitized_Request(
  363. self._test_url('headers'), headers={'Cookie': 'test=test'})).read().decode('utf-8')
  364. self.assertNotIn('Cookie: test=ytdlp', data)
  365. self.assertIn('Cookie: test=test', data)
  366. def test_no_compression_compat_header(self):
  367. with FakeYDL() as ydl:
  368. data = ydl.urlopen(
  369. sanitized_Request(
  370. self._test_url('headers'),
  371. headers={'Youtubedl-no-compression': True})).read()
  372. self.assertIn(b'Accept-Encoding: identity', data)
  373. self.assertNotIn(b'youtubedl-no-compression', data.lower())
  374. def test_gzip_trailing_garbage(self):
  375. # https://github.com/ytdl-org/youtube-dl/commit/aa3e950764337ef9800c936f4de89b31c00dfcf5
  376. # https://github.com/ytdl-org/youtube-dl/commit/6f2ec15cee79d35dba065677cad9da7491ec6e6f
  377. with FakeYDL() as ydl:
  378. data = ydl.urlopen(sanitized_Request(self._test_url('trailing_garbage'))).read().decode('utf-8')
  379. self.assertEqual(data, '<html><video src="/vid.mp4" /></html>')
  380. def __test_compression(self, encoding):
  381. with FakeYDL() as ydl:
  382. res = ydl.urlopen(
  383. sanitized_Request(
  384. self._test_url('content-encoding'),
  385. headers={'ytdl-encoding': encoding}))
  386. self.assertEqual(res.headers.get('Content-Encoding'), encoding)
  387. self.assertEqual(res.read(), b'<html><video src="/vid.mp4" /></html>')
  388. @unittest.skipUnless(brotli, 'brotli support is not installed')
  389. @unittest.expectedFailure
  390. def test_brotli(self):
  391. self.__test_compression('br')
  392. @unittest.expectedFailure
  393. def test_deflate(self):
  394. self.__test_compression('deflate')
  395. @unittest.expectedFailure
  396. def test_gzip(self):
  397. self.__test_compression('gzip')
  398. @unittest.expectedFailure # not yet implemented
  399. def test_multiple_encodings(self):
  400. # https://www.rfc-editor.org/rfc/rfc9110.html#section-8.4
  401. with FakeYDL() as ydl:
  402. for pair in ('gzip,deflate', 'deflate, gzip', 'gzip, gzip', 'deflate, deflate'):
  403. res = ydl.urlopen(
  404. sanitized_Request(
  405. self._test_url('content-encoding'),
  406. headers={'ytdl-encoding': pair}))
  407. self.assertEqual(res.headers.get('Content-Encoding'), pair)
  408. self.assertEqual(res.read(), b'<html><video src="/vid.mp4" /></html>')
  409. def test_unsupported_encoding(self):
  410. # it should return the raw content
  411. with FakeYDL() as ydl:
  412. res = ydl.urlopen(
  413. sanitized_Request(
  414. self._test_url('content-encoding'),
  415. headers={'ytdl-encoding': 'unsupported'}))
  416. self.assertEqual(res.headers.get('Content-Encoding'), 'unsupported')
  417. self.assertEqual(res.read(), b'raw')
  418. def _build_proxy_handler(name):
  419. class HTTPTestRequestHandler(compat_http_server.BaseHTTPRequestHandler):
  420. proxy_name = name
  421. def log_message(self, format, *args):
  422. pass
  423. def do_GET(self):
  424. self.send_response(200)
  425. self.send_header('Content-Type', 'text/plain; charset=utf-8')
  426. self.end_headers()
  427. self.wfile.write('{0}: {1}'.format(self.proxy_name, self.path).encode('utf-8'))
  428. return HTTPTestRequestHandler
  429. class TestProxy(unittest.TestCase):
  430. def setUp(self):
  431. self.proxy = compat_http_server.HTTPServer(
  432. ('127.0.0.1', 0), _build_proxy_handler('normal'))
  433. self.port = http_server_port(self.proxy)
  434. self.proxy_thread = threading.Thread(target=self.proxy.serve_forever)
  435. self.proxy_thread.daemon = True
  436. self.proxy_thread.start()
  437. self.geo_proxy = compat_http_server.HTTPServer(
  438. ('127.0.0.1', 0), _build_proxy_handler('geo'))
  439. self.geo_port = http_server_port(self.geo_proxy)
  440. self.geo_proxy_thread = threading.Thread(target=self.geo_proxy.serve_forever)
  441. self.geo_proxy_thread.daemon = True
  442. self.geo_proxy_thread.start()
  443. def tearDown(self):
  444. def closer(svr):
  445. def _closer():
  446. svr.shutdown()
  447. svr.server_close()
  448. return _closer
  449. shutdown_thread = threading.Thread(target=closer(self.proxy))
  450. shutdown_thread.start()
  451. self.proxy_thread.join(2.0)
  452. shutdown_thread = threading.Thread(target=closer(self.geo_proxy))
  453. shutdown_thread.start()
  454. self.geo_proxy_thread.join(2.0)
  455. def _test_proxy(self, host='127.0.0.1', port=None):
  456. return '{0}:{1}'.format(
  457. host, port if port is not None else self.port)
  458. def test_proxy(self):
  459. geo_proxy = self._test_proxy(port=self.geo_port)
  460. ydl = YoutubeDL({
  461. 'proxy': self._test_proxy(),
  462. 'geo_verification_proxy': geo_proxy,
  463. })
  464. url = 'http://foo.com/bar'
  465. response = ydl.urlopen(url).read().decode('utf-8')
  466. self.assertEqual(response, 'normal: {0}'.format(url))
  467. req = compat_urllib_request.Request(url)
  468. req.add_header('Ytdl-request-proxy', geo_proxy)
  469. response = ydl.urlopen(req).read().decode('utf-8')
  470. self.assertEqual(response, 'geo: {0}'.format(url))
  471. def test_proxy_with_idn(self):
  472. ydl = YoutubeDL({
  473. 'proxy': self._test_proxy(),
  474. })
  475. url = 'http://中文.tw/'
  476. response = ydl.urlopen(url).read().decode('utf-8')
  477. # b'xn--fiq228c' is '中文'.encode('idna')
  478. self.assertEqual(response, 'normal: http://xn--fiq228c.tw/')
  479. class TestFileURL(unittest.TestCase):
  480. # See https://github.com/ytdl-org/youtube-dl/issues/8227
  481. def test_file_urls(self):
  482. tf = tempfile.NamedTemporaryFile(delete=False)
  483. tf.write(b'foobar')
  484. tf.close()
  485. url = compat_urllib_parse.urljoin('file://', pathname2url(tf.name))
  486. with FakeYDL() as ydl:
  487. self.assertRaisesRegexp(
  488. compat_urllib_error.URLError, 'file:// scheme is explicitly disabled in youtube-dl for security reasons', ydl.urlopen, url)
  489. # not yet implemented
  490. """
  491. with FakeYDL({'enable_file_urls': True}) as ydl:
  492. res = ydl.urlopen(url)
  493. self.assertEqual(res.read(), b'foobar')
  494. res.close()
  495. """
  496. os.unlink(tf.name)
  497. if __name__ == '__main__':
  498. unittest.main()