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:19:29 +0100 |
commit | 9405bbc5dc34c8b83699411ef6cb017f8b6ff11a (patch) | |
tree | e78a23785f372bdba173e8177888309e2dbccce4 | |
parent | bd8e503f7493c876a8bdbcf8482ee1bdce0ea042 (diff) | |
download | bottle-9405bbc5dc34c8b83699411ef6cb017f8b6ff11a.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.
-rw-r--r-- | bottle.py | 22 | ||||
-rwxr-xr-x | test/test_environ.py | 14 |
2 files changed, 28 insertions, 8 deletions
@@ -1398,10 +1398,18 @@ class BaseRequest(object): self.environ['bottle.request.ext.%s'%name] = value +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 _hkey(s): - return s.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): @@ -1921,7 +1929,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. """ @@ -1933,15 +1940,14 @@ class HeaderDict(MultiDict): def __contains__(self, key): return _hkey(key) in self.dict def __delitem__(self, key): del self.dict[_hkey(key)] def __getitem__(self, key): return self.dict[_hkey(key)][-1] - def __setitem__(self, key, value): self.dict[_hkey(key)] = [str(value)] - def append(self, key, value): - self.dict.setdefault(_hkey(key), []).append(str(value)) - def replace(self, key, value): self.dict[_hkey(key)] = [str(value)] + def __setitem__(self, key, value): self.dict[_hkey(key)] = [_hval(value)] + def append(self, key, value): self.dict.setdefault(_hkey(key), []).append(_hval(value)) + def replace(self, key, value): self.dict[_hkey(key)] = [_hval(value)] def getall(self, key): return self.dict.get(_hkey(key)) or [] def get(self, key, default=None, index=-1): 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 2b8079b..7f441fc 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, _e, HTTPError, parse_date import tools @@ -655,6 +658,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() |