check and test for headers containing return characters or leading whitespace

This commit is contained in:
Nate Prewitt
2016-06-29 13:46:40 -04:00
parent 92fe51c0af
commit 2669ab797c
5 changed files with 75 additions and 7 deletions
+1
View File
@@ -166,3 +166,4 @@ Patches and Suggestions
- Dmitry Dygalo (`@Stranger6667 <https://github.com/Stranger6667>`_)
- piotrjurkiewicz
- Jesse Shapiro <jesse@jesseshapiro.net> (`@haikuginger <https://github.com/haikuginger>`_)
- Nate Prewitt <nate.prewitt@gmail.com> (`@nateprewitt <https://github.com/nateprewitt>`_)
+5 -1
View File
@@ -80,7 +80,11 @@ class InvalidSchema(RequestException, ValueError):
class InvalidURL(RequestException, ValueError):
""" The URL provided was somehow invalid. """
"""The URL provided was somehow invalid."""
class InvalidHeader(RequestException, ValueError):
"""The header value provided was somehow invalid."""
class ChunkedEncodingError(RequestException):
+8 -4
View File
@@ -27,7 +27,8 @@ from .exceptions import (
from .utils import (
guess_filename, get_auth_from_url, requote_uri,
stream_decode_response_unicode, to_key_val_list, parse_header_links,
iter_slices, guess_json_utf, super_len, to_native_string)
iter_slices, guess_json_utf, super_len, to_native_string,
check_header_validity)
from .compat import (
cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
is_py2, chardet, builtin_str, basestring)
@@ -403,10 +404,13 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
def prepare_headers(self, headers):
"""Prepares the given HTTP headers."""
self.headers = CaseInsensitiveDict()
if headers:
self.headers = CaseInsensitiveDict((to_native_string(name), value) for name, value in headers.items())
else:
self.headers = CaseInsensitiveDict()
for header in headers.items():
# Raise exception on invalid header value.
check_header_validity(header)
name, value = header
self.headers[to_native_string(name)] = value
def prepare_body(self, data, files, json=None):
"""Prepares the given HTTP body data."""
+19 -1
View File
@@ -27,7 +27,7 @@ from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2,
basestring)
from .cookies import RequestsCookieJar, cookiejar_from_dict
from .structures import CaseInsensitiveDict
from .exceptions import InvalidURL, FileModeWarning
from .exceptions import InvalidURL, InvalidHeader, FileModeWarning
_hush_pyflakes = (RequestsCookieJar,)
@@ -732,6 +732,24 @@ def to_native_string(string, encoding='ascii'):
return out
# Moved outside of function to avoid recompile every call
_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$')
_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$')
def check_header_validity(header):
"""Verifies that header value doesn't contain leading whitespace or
return characters. This prevents unintended header injection.
:param header: tuple, in the format (name, value).
"""
name, value = header
if isinstance(value, bytes):
pat = _CLEAN_HEADER_REGEX_BYTE
else:
pat = _CLEAN_HEADER_REGEX_STR
if not pat.match(value):
raise InvalidHeader("Invalid return character or leading space in header: %s" % name)
def urldefragauth(url):
"""
+42 -1
View File
@@ -23,7 +23,7 @@ from requests.cookies import cookiejar_from_dict, morsel_to_cookie
from requests.exceptions import (
ConnectionError, ConnectTimeout, InvalidSchema, InvalidURL,
MissingSchema, ReadTimeout, Timeout, RetryError, TooManyRedirects,
ProxyError)
ProxyError, InvalidHeader)
from requests.models import PreparedRequest
from requests.structures import CaseInsensitiveDict
from requests.sessions import SessionRedirectMixin
@@ -1128,6 +1128,47 @@ class TestRequests:
assert 'unicode' in p.headers.keys()
assert 'byte' in p.headers.keys()
def test_header_validation(self,httpbin):
"""Ensure prepare_headers regex isn't flagging valid header contents."""
headers_ok = {'foo': 'bar baz qux',
'bar': '1',
'baz': '',
'qux': str.encode(u'fbbq')}
r = requests.get(httpbin('get'), headers=headers_ok)
assert r.request.headers['foo'] == headers_ok['foo']
def test_header_no_return_chars(self, httpbin):
"""Ensure that a header containing return character sequences raise an
exception. Otherwise, multiple headers are created from single string.
"""
headers_ret = {'foo': 'bar\r\nbaz: qux'}
headers_lf = {'foo': 'bar\nbaz: qux'}
headers_cr = {'foo': 'bar\rbaz: qux'}
# Test for newline
with pytest.raises(InvalidHeader):
r = requests.get(httpbin('get'), headers=headers_ret)
# Test for line feed
with pytest.raises(InvalidHeader):
r = requests.get(httpbin('get'), headers=headers_lf)
# Test for carriage return
with pytest.raises(InvalidHeader):
r = requests.get(httpbin('get'), headers=headers_cr)
def test_header_no_leading_space(self, httpbin):
"""Ensure headers containing leading whitespace raise
InvalidHeader Error before sending.
"""
headers_space = {'foo': ' bar'}
headers_tab = {'foo': ' bar'}
# Test for whitespace
with pytest.raises(InvalidHeader):
r = requests.get(httpbin('get'), headers=headers_space)
# Test for tab
with pytest.raises(InvalidHeader):
r = requests.get(httpbin('get'), headers=headers_tab)
@pytest.mark.parametrize('files', ('foo', b'foo', bytearray(b'foo')))
def test_can_send_objects_with_files(self, httpbin, files):
data = {'a': 'this is a string'}