summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hellkamp <marc@gsites.de>2016-12-10 14:10:26 +0100
committerMarcel Hellkamp <marc@gsites.de>2016-12-10 14:19:29 +0100
commit9405bbc5dc34c8b83699411ef6cb017f8b6ff11a (patch)
treee78a23785f372bdba173e8177888309e2dbccce4
parentbd8e503f7493c876a8bdbcf8482ee1bdce0ea042 (diff)
downloadbottle-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.py22
-rwxr-xr-xtest/test_environ.py14
2 files changed, 28 insertions, 8 deletions
diff --git a/bottle.py b/bottle.py
index 88fdaed..2dcf86f 100644
--- a/bottle.py
+++ b/bottle.py
@@ -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()