diff options
author | Marcel Hellkamp <marc@gsites.de> | 2016-12-10 14:10:26 +0100 |
---|---|---|
committer | Marcel Hellkamp <marc@gsites.de> | 2016-12-10 14:10:26 +0100 |
commit | 6d7e13da0f998820800ecb3fe9ccee4189aefb54 (patch) | |
tree | d5a13f84106e33933d20f9ec825533e4b4d55ba5 | |
parent | 58882c718889533e2f848b70d9733c1474f8ed44 (diff) | |
download | bottle-6d7e13da0f998820800ecb3fe9ccee4189aefb54.tar.gz |
fix #913: Harden bottle against malformed headers.
Bottle now checks against certain control characters (\n, \r and \0) in header names or values and raises a ValueError if the application tries to set an invalid header.
-rwxr-xr-x | bottle.py | 25 | ||||
-rwxr-xr-x | test/test_environ.py | 14 |
2 files changed, 28 insertions, 11 deletions
@@ -1573,9 +1573,16 @@ class BaseRequest(object): raise AttributeError("Attribute not defined: %s" % name) -def _hkey(s): - return s.title().replace('_', '-') - +def _hkey(key): + if '\n' in key or '\r' in key or '\0' in key: + raise ValueError("Header names must not contain control characters: %r" % key) + return key.title().replace('_', '-') + +def _hval(value): + value = value if isinstance(value, unicode) else str(value) + if '\n' in value or '\r' in value or '\0' in value: + raise ValueError("Header value must not contain control characters: %r" % value) + return value class HeaderProperty(object): def __init__(self, name, reader=None, writer=str, default=''): @@ -2170,7 +2177,6 @@ class FormsDict(MultiDict): return super(FormsDict, self).__getattr__(name) return self.getunicode(name, default=default) - class HeaderDict(MultiDict): """ A case-insensitive version of :class:`MultiDict` that defaults to replace the old value instead of appending it. """ @@ -2189,16 +2195,13 @@ class HeaderDict(MultiDict): return self.dict[_hkey(key)][-1] def __setitem__(self, key, value): - self.dict[_hkey(key)] = [value if isinstance(value, unicode) else - str(value)] + self.dict[_hkey(key)] = [_hval(value)] def append(self, key, value): - self.dict.setdefault(_hkey(key), []).append( - value if isinstance(value, unicode) else str(value)) + self.dict.setdefault(_hkey(key), []).append(_hval(value)) def replace(self, key, value): - self.dict[_hkey(key)] = [value if isinstance(value, unicode) else - str(value)] + self.dict[_hkey(key)] = [_hval(value)] def getall(self, key): return self.dict.get(_hkey(key)) or [] @@ -2207,7 +2210,7 @@ class HeaderDict(MultiDict): return MultiDict.get(self, _hkey(key), default, index) def filter(self, names): - for name in [_hkey(n) for n in names]: + for name in (_hkey(n) for n in names): if name in self.dict: del self.dict[name] diff --git a/test/test_environ.py b/test/test_environ.py index 2312ce4..4ee1111 100755 --- a/test/test_environ.py +++ b/test/test_environ.py @@ -3,6 +3,9 @@ import unittest import sys + +import itertools + import bottle from bottle import request, tob, touni, tonat, json_dumps, HTTPError, parse_date from test import tools @@ -695,6 +698,17 @@ class TestResponse(unittest.TestCase): response['x-test'] = None self.assertEqual('None', response['x-test']) + def test_prevent_control_characters_in_headers(self): + apis = 'append', 'replace', '__setitem__', 'setdefault' + masks = '{}test', 'test{}', 'te{}st' + tests = '\n', '\r', '\n\r', '\0' + for api, mask, test in itertools.product(apis, masks, tests): + hd = bottle.HeaderDict() + func = getattr(hd, api) + value = mask.replace("{}", test) + self.assertRaises(ValueError, func, value, "test-value") + self.assertRaises(ValueError, func, "test-name", value) + def test_expires_header(self): import datetime response = BaseResponse() |