Permalink
Browse files

fix #913: redirect() doesn't filter "\r\n" leads to CRLF attack

The previous fix (6d7e13d) was incomplete.
  • Loading branch information...
defnull committed Dec 17, 2016
1 parent f936dfa commit 3f838db73f7488a108dd8eea308fcc1188303371
Showing with 19 additions and 11 deletions.
  1. +7 −10 bottle.py
  2. +12 −1 test/test_environ.py
@@ -1592,21 +1592,21 @@ def _hval(value):
return value

class HeaderProperty(object):
def __init__(self, name, reader=None, writer=str, default=''):
def __init__(self, name, reader=None, writer=None, default=''):
self.name, self.default = name, default
self.reader, self.writer = reader, writer
self.__doc__ = 'Current value of the %r header.' % name.title()

def __get__(self, obj, _):
if obj is None: return self
value = obj.headers.get(self.name, self.default)
value = obj.get_header(self.name, self.default)
return self.reader(value) if self.reader else value

def __set__(self, obj, value):
obj.headers[self.name] = self.writer(value)
obj[self.name] = self.writer(value) if self.writer else value

def __delete__(self, obj):
del obj.headers[self.name]
del obj[self.name]


class BaseResponse(object):
@@ -1723,8 +1723,7 @@ def __getitem__(self, name):
return self._headers[_hkey(name)][-1]

def __setitem__(self, name, value):
self._headers[_hkey(name)] = [value if isinstance(value, unicode) else
str(value)]
self._headers[_hkey(name)] = [_hval(value)]

def get_header(self, name, default=None):
""" Return the value of a previously defined header. If there is no
@@ -1734,13 +1733,11 @@ def get_header(self, name, default=None):
def set_header(self, name, value):
""" Create a new response header, replacing any previously defined
headers with the same name. """
self._headers[_hkey(name)] = [value if isinstance(value, unicode)
else str(value)]
self._headers[_hkey(name)] = [_hval(value)]

def add_header(self, name, value):
""" Add an additional response header, not removing duplicates. """
self._headers.setdefault(_hkey(name), []).append(
value if isinstance(value, unicode) else str(value))
self._headers.setdefault(_hkey(name), []).append(_hval(value))

def iter_headers(self):
""" Yield (header, value) tuples, skipping headers that are not
@@ -699,16 +699,27 @@ def test_non_string_header(self):
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'

# Test HeaderDict
apis = 'append', 'replace', '__setitem__', 'setdefault'
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)

# Test functions on BaseResponse
apis = 'add_header', 'set_header', '__setitem__'
for api, mask, test in itertools.product(apis, masks, tests):
rs = bottle.BaseResponse()
func = getattr(rs, 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()

0 comments on commit 3f838db

Please sign in to comment.