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:10:26 +0100
commit6d7e13da0f998820800ecb3fe9ccee4189aefb54 (patch)
treed5a13f84106e33933d20f9ec825533e4b4d55ba5
parent58882c718889533e2f848b70d9733c1474f8ed44 (diff)
downloadbottle-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-xbottle.py25
-rwxr-xr-xtest/test_environ.py14
2 files changed, 28 insertions, 11 deletions
diff --git a/bottle.py b/bottle.py
index 5f28dda..62c00f4 100755
--- a/bottle.py
+++ b/bottle.py
@@ -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()