Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions Lib/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,17 +547,22 @@ def send_response_only(self, code, message=None):
message = ''
if not hasattr(self, '_headers_buffer'):
self._headers_buffer = []
self._headers_buffer.append(("%s %d %s\r\n" %
(self.protocol_version, code, message)).encode(
'latin-1', 'strict'))
line = "%s %d %s\r\n" % (self.protocol_version, code, message)
if '\r' in line[:-2] or '\n' in line[:-2]:
raise ValueError("CR and LF characters are not allowed "
"in the response reason phrase")
self._headers_buffer.append(line.encode('latin-1', 'strict'))

def send_header(self, keyword, value, *, _is_extra=False):
"""Send a MIME header to the headers buffer."""
if self.request_version != 'HTTP/0.9':
if not hasattr(self, '_headers_buffer'):
self._headers_buffer = []
self._headers_buffer.append(
("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))
line = "%s: %s\r\n" % (keyword, value)
if '\r' in line[:-2] or '\n' in line[:-2]:
raise ValueError("CR and LF characters are not allowed "
"in HTTP header names or values")
self._headers_buffer.append(line.encode('latin-1', 'strict'))
if not hasattr(self, '_default_response_headers'):
self._default_response_headers = []
if not _is_extra:
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_httpservers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,29 @@ def test_header_buffering_of_send_header(self):
self.assertEqual(output.getData(), b'Foo: foo\r\nbar: bar\r\n\r\n')
self.assertEqual(output.numWrites, 1)

def test_send_header_rejects_crlf(self):
handler = SocketlessRequestHandler()
handler.wfile = BytesIO()
handler.request_version = 'HTTP/1.1'

for keyword, value in (
('Foo', 'bar\r\nSet-Cookie: injected=1'),
('Foo', 'bar\nSet-Cookie: injected=1'),
('Foo', 'bar\rSet-Cookie: injected=1'),
('Foo\r\nEvil', 'bar'),
):
with self.subTest(keyword=keyword, value=value):
with self.assertRaises(ValueError):
handler.send_header(keyword, value)

def test_send_response_only_rejects_crlf(self):
handler = SocketlessRequestHandler()
handler.wfile = BytesIO()
handler.request_version = 'HTTP/1.1'

with self.assertRaises(ValueError):
handler.send_response_only(200, 'OK\r\nX-Injected: yes')

def test_header_unbuffered_when_continue(self):

def _readAndReseek(f):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:meth:`~http.server.BaseHTTPRequestHandler.send_header` and
:meth:`~http.server.BaseHTTPRequestHandler.send_response_only` now raise
:exc:`ValueError` when a header name, header value or the response reason
phrase contains a carriage return or line feed, preventing response
splitting and header injection.
Loading