Commit 798736e3 authored by Serhiy Storchaka's avatar Serhiy Storchaka

Issue #22928: Disabled HTTP header injections in httplib.

Original patch by Demian Brecht.
parent 8f7d987f
...@@ -68,6 +68,7 @@ Req-sent-unread-response _CS_REQ_SENT <response_class> ...@@ -68,6 +68,7 @@ Req-sent-unread-response _CS_REQ_SENT <response_class>
from array import array from array import array
import os import os
import re
import socket import socket
from sys import py3kwarning from sys import py3kwarning
from urlparse import urlsplit from urlparse import urlsplit
...@@ -218,6 +219,34 @@ _MAXLINE = 65536 ...@@ -218,6 +219,34 @@ _MAXLINE = 65536
# maximum amount of headers accepted # maximum amount of headers accepted
_MAXHEADERS = 100 _MAXHEADERS = 100
# Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2)
#
# VCHAR = %x21-7E
# obs-text = %x80-FF
# header-field = field-name ":" OWS field-value OWS
# field-name = token
# field-value = *( field-content / obs-fold )
# field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
# field-vchar = VCHAR / obs-text
#
# obs-fold = CRLF 1*( SP / HTAB )
# ; obsolete line folding
# ; see Section 3.2.4
# token = 1*tchar
#
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
# / DIGIT / ALPHA
# ; any VCHAR, except delimiters
#
# VCHAR defined in http://tools.ietf.org/html/rfc5234#appendix-B.1
# the patterns for both name and value are more leniant than RFC
# definitions to allow for backwards compatibility
_is_legal_header_name = re.compile(r'\A[^:\s][^:\r\n]*\Z').match
_is_illegal_header_value = re.compile(r'\n(?![ \t])|\r(?![ \t\n])').search
class HTTPMessage(mimetools.Message): class HTTPMessage(mimetools.Message):
...@@ -983,7 +1012,16 @@ class HTTPConnection: ...@@ -983,7 +1012,16 @@ class HTTPConnection:
if self.__state != _CS_REQ_STARTED: if self.__state != _CS_REQ_STARTED:
raise CannotSendHeader() raise CannotSendHeader()
hdr = '%s: %s' % (header, '\r\n\t'.join([str(v) for v in values])) header = '%s' % header
if not _is_legal_header_name(header):
raise ValueError('Invalid header name %r' % (header,))
values = [str(v) for v in values]
for one_value in values:
if _is_illegal_header_value(one_value):
raise ValueError('Invalid header value %r' % (one_value,))
hdr = '%s: %s' % (header, '\r\n\t'.join(values))
self._output(hdr) self._output(hdr)
def endheaders(self, message_body=None): def endheaders(self, message_body=None):
......
...@@ -145,6 +145,33 @@ class HeaderTests(TestCase): ...@@ -145,6 +145,33 @@ class HeaderTests(TestCase):
conn.putheader('Content-length',42) conn.putheader('Content-length',42)
self.assertIn('Content-length: 42', conn._buffer) self.assertIn('Content-length: 42', conn._buffer)
conn.putheader('Foo', ' bar ')
self.assertIn(b'Foo: bar ', conn._buffer)
conn.putheader('Bar', '\tbaz\t')
self.assertIn(b'Bar: \tbaz\t', conn._buffer)
conn.putheader('Authorization', 'Bearer mytoken')
self.assertIn(b'Authorization: Bearer mytoken', conn._buffer)
conn.putheader('IterHeader', 'IterA', 'IterB')
self.assertIn(b'IterHeader: IterA\r\n\tIterB', conn._buffer)
conn.putheader('LatinHeader', b'\xFF')
self.assertIn(b'LatinHeader: \xFF', conn._buffer)
conn.putheader('Utf8Header', b'\xc3\x80')
self.assertIn(b'Utf8Header: \xc3\x80', conn._buffer)
conn.putheader('C1-Control', b'next\x85line')
self.assertIn(b'C1-Control: next\x85line', conn._buffer)
conn.putheader('Embedded-Fold-Space', 'is\r\n allowed')
self.assertIn(b'Embedded-Fold-Space: is\r\n allowed', conn._buffer)
conn.putheader('Embedded-Fold-Tab', 'is\r\n\tallowed')
self.assertIn(b'Embedded-Fold-Tab: is\r\n\tallowed', conn._buffer)
conn.putheader('Key Space', 'value')
self.assertIn(b'Key Space: value', conn._buffer)
conn.putheader('KeySpace ', 'value')
self.assertIn(b'KeySpace : value', conn._buffer)
conn.putheader(b'Nonbreak\xa0Space', 'value')
self.assertIn(b'Nonbreak\xa0Space: value', conn._buffer)
conn.putheader(b'\xa0NonbreakSpace', 'value')
self.assertIn(b'\xa0NonbreakSpace: value', conn._buffer)
def test_ipv6host_header(self): def test_ipv6host_header(self):
# Default host header on IPv6 transaction should wrapped by [] if # Default host header on IPv6 transaction should wrapped by [] if
# its actual IPv6 address # its actual IPv6 address
...@@ -174,6 +201,35 @@ class HeaderTests(TestCase): ...@@ -174,6 +201,35 @@ class HeaderTests(TestCase):
self.assertEqual(resp.getheader('First'), 'val') self.assertEqual(resp.getheader('First'), 'val')
self.assertEqual(resp.getheader('Second'), 'val') self.assertEqual(resp.getheader('Second'), 'val')
def test_invalid_headers(self):
conn = httplib.HTTPConnection('example.com')
conn.sock = FakeSocket('')
conn.putrequest('GET', '/')
# http://tools.ietf.org/html/rfc7230#section-3.2.4, whitespace is no
# longer allowed in header names
cases = (
(b'Invalid\r\nName', b'ValidValue'),
(b'Invalid\rName', b'ValidValue'),
(b'Invalid\nName', b'ValidValue'),
(b'\r\nInvalidName', b'ValidValue'),
(b'\rInvalidName', b'ValidValue'),
(b'\nInvalidName', b'ValidValue'),
(b' InvalidName', b'ValidValue'),
(b'\tInvalidName', b'ValidValue'),
(b'Invalid:Name', b'ValidValue'),
(b':InvalidName', b'ValidValue'),
(b'ValidName', b'Invalid\r\nValue'),
(b'ValidName', b'Invalid\rValue'),
(b'ValidName', b'Invalid\nValue'),
(b'ValidName', b'InvalidValue\r\n'),
(b'ValidName', b'InvalidValue\r'),
(b'ValidName', b'InvalidValue\n'),
)
for name, value in cases:
with self.assertRaisesRegexp(ValueError, 'Invalid header'):
conn.putheader(name, value)
class BasicTest(TestCase): class BasicTest(TestCase):
def test_status_lines(self): def test_status_lines(self):
......
...@@ -21,6 +21,9 @@ Core and Builtins ...@@ -21,6 +21,9 @@ Core and Builtins
Library Library
------- -------
- Issue #22928: Disabled HTTP header injections in httplib.
Original patch by Demian Brecht.
- Issue #23615: Module tarfile is now can be reloaded with imp.reload(). - Issue #23615: Module tarfile is now can be reloaded with imp.reload().
- Issue #22853: Fixed a deadlock when use multiprocessing.Queue at import time. - Issue #22853: Fixed a deadlock when use multiprocessing.Queue at import time.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment