mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-31 14:45:14 +00:00 
			
		
		
		
	[networking] Add keep_header_casing extension (#11652)
				
					
				
			Authored by: coletdjnz, Grub4K Co-authored-by: coletdjnz <coletdjnz@protonmail.com>
This commit is contained in:
		| @@ -720,6 +720,15 @@ class TestHTTPRequestHandler(TestRequestHandlerBase): | ||||
|                     rh, Request( | ||||
|                         f'http://127.0.0.1:{self.http_port}/headers', proxies={'all': 'http://10.255.255.255'})).close() | ||||
| 
 | ||||
|     @pytest.mark.skip_handlers_if(lambda _, handler: handler not in ['Urllib', 'CurlCFFI'], 'handler does not support keep_header_casing') | ||||
|     def test_keep_header_casing(self, handler): | ||||
|         with handler() as rh: | ||||
|             res = validate_and_send( | ||||
|                 rh, Request( | ||||
|                     f'http://127.0.0.1:{self.http_port}/headers', headers={'X-test-heaDer': 'test'}, extensions={'keep_header_casing': True})).read().decode() | ||||
| 
 | ||||
|             assert 'X-test-heaDer: test' in res | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.parametrize('handler', ['Urllib', 'Requests', 'CurlCFFI'], indirect=True) | ||||
| class TestClientCertificate: | ||||
| @@ -1289,6 +1298,7 @@ class TestRequestHandlerValidation: | ||||
|             ({'legacy_ssl': False}, False), | ||||
|             ({'legacy_ssl': True}, False), | ||||
|             ({'legacy_ssl': 'notabool'}, AssertionError), | ||||
|             ({'keep_header_casing': True}, UnsupportedRequest), | ||||
|         ]), | ||||
|         ('Requests', 'http', [ | ||||
|             ({'cookiejar': 'notacookiejar'}, AssertionError), | ||||
| @@ -1299,6 +1309,9 @@ class TestRequestHandlerValidation: | ||||
|             ({'legacy_ssl': False}, False), | ||||
|             ({'legacy_ssl': True}, False), | ||||
|             ({'legacy_ssl': 'notabool'}, AssertionError), | ||||
|             ({'keep_header_casing': False}, False), | ||||
|             ({'keep_header_casing': True}, False), | ||||
|             ({'keep_header_casing': 'notabool'}, AssertionError), | ||||
|         ]), | ||||
|         ('CurlCFFI', 'http', [ | ||||
|             ({'cookiejar': 'notacookiejar'}, AssertionError), | ||||
|   | ||||
| @@ -3,19 +3,20 @@ | ||||
| # Allow direct execution | ||||
| import os | ||||
| import sys | ||||
| import unittest | ||||
| import unittest.mock | ||||
| import warnings | ||||
| import datetime as dt | ||||
| 
 | ||||
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||
| 
 | ||||
| 
 | ||||
| import contextlib | ||||
| import datetime as dt | ||||
| import io | ||||
| import itertools | ||||
| import json | ||||
| import pickle | ||||
| import subprocess | ||||
| import unittest | ||||
| import unittest.mock | ||||
| import warnings | ||||
| import xml.etree.ElementTree | ||||
| 
 | ||||
| from yt_dlp.compat import ( | ||||
| @@ -2087,21 +2088,26 @@ Line 1 | ||||
|         headers = HTTPHeaderDict() | ||||
|         headers['ytdl-test'] = b'0' | ||||
|         self.assertEqual(list(headers.items()), [('Ytdl-Test', '0')]) | ||||
|         self.assertEqual(list(headers.sensitive().items()), [('ytdl-test', '0')]) | ||||
|         headers['ytdl-test'] = 1 | ||||
|         self.assertEqual(list(headers.items()), [('Ytdl-Test', '1')]) | ||||
|         self.assertEqual(list(headers.sensitive().items()), [('ytdl-test', '1')]) | ||||
|         headers['Ytdl-test'] = '2' | ||||
|         self.assertEqual(list(headers.items()), [('Ytdl-Test', '2')]) | ||||
|         self.assertEqual(list(headers.sensitive().items()), [('Ytdl-test', '2')]) | ||||
|         self.assertTrue('ytDl-Test' in headers) | ||||
|         self.assertEqual(str(headers), str(dict(headers))) | ||||
|         self.assertEqual(repr(headers), str(dict(headers))) | ||||
| 
 | ||||
|         headers.update({'X-dlp': 'data'}) | ||||
|         self.assertEqual(set(headers.items()), {('Ytdl-Test', '2'), ('X-Dlp', 'data')}) | ||||
|         self.assertEqual(set(headers.sensitive().items()), {('Ytdl-test', '2'), ('X-dlp', 'data')}) | ||||
|         self.assertEqual(dict(headers), {'Ytdl-Test': '2', 'X-Dlp': 'data'}) | ||||
|         self.assertEqual(len(headers), 2) | ||||
|         self.assertEqual(headers.copy(), headers) | ||||
|         headers2 = HTTPHeaderDict({'X-dlp': 'data3'}, **headers, **{'X-dlp': 'data2'}) | ||||
|         headers2 = HTTPHeaderDict({'X-dlp': 'data3'}, headers, **{'X-dlP': 'data2'}) | ||||
|         self.assertEqual(set(headers2.items()), {('Ytdl-Test', '2'), ('X-Dlp', 'data2')}) | ||||
|         self.assertEqual(set(headers2.sensitive().items()), {('Ytdl-test', '2'), ('X-dlP', 'data2')}) | ||||
|         self.assertEqual(len(headers2), 2) | ||||
|         headers2.clear() | ||||
|         self.assertEqual(len(headers2), 0) | ||||
| @@ -2109,16 +2115,23 @@ Line 1 | ||||
|         # ensure we prefer latter headers | ||||
|         headers3 = HTTPHeaderDict({'Ytdl-TeSt': 1}, {'Ytdl-test': 2}) | ||||
|         self.assertEqual(set(headers3.items()), {('Ytdl-Test', '2')}) | ||||
|         self.assertEqual(set(headers3.sensitive().items()), {('Ytdl-test', '2')}) | ||||
|         del headers3['ytdl-tesT'] | ||||
|         self.assertEqual(dict(headers3), {}) | ||||
| 
 | ||||
|         headers4 = HTTPHeaderDict({'ytdl-test': 'data;'}) | ||||
|         self.assertEqual(set(headers4.items()), {('Ytdl-Test', 'data;')}) | ||||
|         self.assertEqual(set(headers4.sensitive().items()), {('ytdl-test', 'data;')}) | ||||
| 
 | ||||
|         # common mistake: strip whitespace from values | ||||
|         # https://github.com/yt-dlp/yt-dlp/issues/8729 | ||||
|         headers5 = HTTPHeaderDict({'ytdl-test': ' data; '}) | ||||
|         self.assertEqual(set(headers5.items()), {('Ytdl-Test', 'data;')}) | ||||
|         self.assertEqual(set(headers5.sensitive().items()), {('ytdl-test', 'data;')}) | ||||
| 
 | ||||
|         # test if picklable | ||||
|         headers6 = HTTPHeaderDict(a=1, b=2) | ||||
|         self.assertEqual(pickle.loads(pickle.dumps(headers6)), headers6) | ||||
| 
 | ||||
|     def test_extract_basic_auth(self): | ||||
|         assert extract_basic_auth('http://:foo.bar') == ('http://:foo.bar', None) | ||||
|   | ||||
| @@ -44,7 +44,7 @@ def websocket_handler(websocket): | ||||
|                 return websocket.send('2') | ||||
|         elif isinstance(message, str): | ||||
|             if message == 'headers': | ||||
|                 return websocket.send(json.dumps(dict(websocket.request.headers))) | ||||
|                 return websocket.send(json.dumps(dict(websocket.request.headers.raw_items()))) | ||||
|             elif message == 'path': | ||||
|                 return websocket.send(websocket.request.path) | ||||
|             elif message == 'source_address': | ||||
| @@ -266,18 +266,18 @@ class TestWebsSocketRequestHandlerConformance: | ||||
|         with handler(cookiejar=cookiejar) as rh: | ||||
|             ws = ws_validate_and_send(rh, Request(self.ws_base_url)) | ||||
|             ws.send('headers') | ||||
|             assert json.loads(ws.recv())['cookie'] == 'test=ytdlp' | ||||
|             assert HTTPHeaderDict(json.loads(ws.recv()))['cookie'] == 'test=ytdlp' | ||||
|             ws.close() | ||||
| 
 | ||||
|         with handler() as rh: | ||||
|             ws = ws_validate_and_send(rh, Request(self.ws_base_url)) | ||||
|             ws.send('headers') | ||||
|             assert 'cookie' not in json.loads(ws.recv()) | ||||
|             assert 'cookie' not in HTTPHeaderDict(json.loads(ws.recv())) | ||||
|             ws.close() | ||||
| 
 | ||||
|             ws = ws_validate_and_send(rh, Request(self.ws_base_url, extensions={'cookiejar': cookiejar})) | ||||
|             ws.send('headers') | ||||
|             assert json.loads(ws.recv())['cookie'] == 'test=ytdlp' | ||||
|             assert HTTPHeaderDict(json.loads(ws.recv()))['cookie'] == 'test=ytdlp' | ||||
|             ws.close() | ||||
| 
 | ||||
|     @pytest.mark.skip_handler('Websockets', 'Set-Cookie not supported by websockets') | ||||
| @@ -287,7 +287,7 @@ class TestWebsSocketRequestHandlerConformance: | ||||
|             ws_validate_and_send(rh, Request(f'{self.ws_base_url}/get_cookie', extensions={'cookiejar': YoutubeDLCookieJar()})) | ||||
|             ws = ws_validate_and_send(rh, Request(self.ws_base_url, extensions={'cookiejar': YoutubeDLCookieJar()})) | ||||
|             ws.send('headers') | ||||
|             assert 'cookie' not in json.loads(ws.recv()) | ||||
|             assert 'cookie' not in HTTPHeaderDict(json.loads(ws.recv())) | ||||
|             ws.close() | ||||
| 
 | ||||
|     @pytest.mark.skip_handler('Websockets', 'Set-Cookie not supported by websockets') | ||||
| @@ -298,12 +298,12 @@ class TestWebsSocketRequestHandlerConformance: | ||||
|             ws_validate_and_send(rh, Request(f'{self.ws_base_url}/get_cookie')) | ||||
|             ws = ws_validate_and_send(rh, Request(self.ws_base_url)) | ||||
|             ws.send('headers') | ||||
|             assert json.loads(ws.recv())['cookie'] == 'test=ytdlp' | ||||
|             assert HTTPHeaderDict(json.loads(ws.recv()))['cookie'] == 'test=ytdlp' | ||||
|             ws.close() | ||||
|             cookiejar.clear_session_cookies() | ||||
|             ws = ws_validate_and_send(rh, Request(self.ws_base_url)) | ||||
|             ws.send('headers') | ||||
|             assert 'cookie' not in json.loads(ws.recv()) | ||||
|             assert 'cookie' not in HTTPHeaderDict(json.loads(ws.recv())) | ||||
|             ws.close() | ||||
| 
 | ||||
|     def test_source_address(self, handler): | ||||
| @@ -341,6 +341,14 @@ class TestWebsSocketRequestHandlerConformance: | ||||
|             assert headers['test3'] == 'test3' | ||||
|             ws.close() | ||||
| 
 | ||||
|     def test_keep_header_casing(self, handler): | ||||
|         with handler(headers=HTTPHeaderDict({'x-TeSt1': 'test'})) as rh: | ||||
|             ws = ws_validate_and_send(rh, Request(self.ws_base_url, headers={'x-TeSt2': 'test'}, extensions={'keep_header_casing': True})) | ||||
|             ws.send('headers') | ||||
|             headers = json.loads(ws.recv()) | ||||
|             assert 'x-TeSt1' in headers | ||||
|             assert 'x-TeSt2' in headers | ||||
| 
 | ||||
|     @pytest.mark.parametrize('client_cert', ( | ||||
|         {'client_certificate': os.path.join(MTLS_CERT_DIR, 'clientwithkey.crt')}, | ||||
|         { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Simon Sawicki
					Simon Sawicki