Skip to content

Commit 0431983

Browse files
sethmlarsonillia-v
andcommitted
[3.11] pythongh-146211: Reject CR/LF in HTTP tunnel request headers (pythonGH-146212)
(cherry picked from commit 05ed7ce) Co-authored-by: Seth Larson <seth@python.org> Co-authored-by: Illia Volochii <illia.volochii@gmail.com>
1 parent 642865d commit 0431983

File tree

3 files changed

+57
-1
lines changed

3 files changed

+57
-1
lines changed

Lib/http/client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,12 +941,21 @@ def _wrap_ipv6(self, ip):
941941
return ip
942942

943943
def _tunnel(self):
944+
if _contains_disallowed_url_pchar_re.search(self._tunnel_host):
945+
raise ValueError('Tunnel host can\'t contain control characters %r'
946+
% (self._tunnel_host,))
944947
connect = b"CONNECT %s:%d HTTP/1.0\r\n" % (
945948
self._wrap_ipv6(self._tunnel_host.encode("ascii")),
946949
self._tunnel_port)
947950
headers = [connect]
948951
for header, value in self._tunnel_headers.items():
949-
headers.append(f"{header}: {value}\r\n".encode("latin-1"))
952+
header_bytes = header.encode("latin-1")
953+
value_bytes = value.encode("latin-1")
954+
if not _is_legal_header_name(header_bytes):
955+
raise ValueError('Invalid header name %r' % (header_bytes,))
956+
if _is_illegal_header_value(value_bytes):
957+
raise ValueError('Invalid header value %r' % (value_bytes,))
958+
headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes))
950959
headers.append(b"\r\n")
951960
# Making a single send() call instead of one per line encourages
952961
# the host OS to use a more optimal packet size instead of

Lib/test/test_httplib.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,51 @@ def test_invalid_headers(self):
371371
with self.assertRaisesRegex(ValueError, 'Invalid header'):
372372
conn.putheader(name, value)
373373

374+
def test_invalid_tunnel_headers(self):
375+
cases = (
376+
('Invalid\r\nName', 'ValidValue'),
377+
('Invalid\rName', 'ValidValue'),
378+
('Invalid\nName', 'ValidValue'),
379+
('\r\nInvalidName', 'ValidValue'),
380+
('\rInvalidName', 'ValidValue'),
381+
('\nInvalidName', 'ValidValue'),
382+
(' InvalidName', 'ValidValue'),
383+
('\tInvalidName', 'ValidValue'),
384+
('Invalid:Name', 'ValidValue'),
385+
(':InvalidName', 'ValidValue'),
386+
('ValidName', 'Invalid\r\nValue'),
387+
('ValidName', 'Invalid\rValue'),
388+
('ValidName', 'Invalid\nValue'),
389+
('ValidName', 'InvalidValue\r\n'),
390+
('ValidName', 'InvalidValue\r'),
391+
('ValidName', 'InvalidValue\n'),
392+
)
393+
for name, value in cases:
394+
with self.subTest((name, value)):
395+
conn = client.HTTPConnection('example.com')
396+
conn.set_tunnel('tunnel', headers={
397+
name: value
398+
})
399+
conn.sock = FakeSocket('')
400+
with self.assertRaisesRegex(ValueError, 'Invalid header'):
401+
conn._tunnel() # Called in .connect()
402+
403+
def test_invalid_tunnel_host(self):
404+
cases = (
405+
'invalid\r.host',
406+
'\ninvalid.host',
407+
'invalid.host\r\n',
408+
'invalid.host\x00',
409+
'invalid host',
410+
)
411+
for tunnel_host in cases:
412+
with self.subTest(tunnel_host):
413+
conn = client.HTTPConnection('example.com')
414+
conn.set_tunnel(tunnel_host)
415+
conn.sock = FakeSocket('')
416+
with self.assertRaisesRegex(ValueError, 'Tunnel host can\'t contain control characters'):
417+
conn._tunnel() # Called in .connect()
418+
374419
def test_headers_debuglevel(self):
375420
body = (
376421
b'HTTP/1.1 200 OK\r\n'
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Reject CR/LF characters in tunnel request headers for the
2+
HTTPConnection.set_tunnel() method.

0 commit comments

Comments
 (0)