Skip to content

Commit

Permalink
fix #913: redirect() doesn't filter "\r\n" leads to CRLF attack
Browse files Browse the repository at this point in the history
The previous fix (6d7e13d) was incomplete.
  • Loading branch information
defnull committed Dec 17, 2016
1 parent 860d586 commit 78f67d5
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 8 deletions.
14 changes: 7 additions & 7 deletions bottle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1413,21 +1413,21 @@ def _hval(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, cls):
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):
Expand Down Expand Up @@ -1534,7 +1534,7 @@ def headers(self):
def __contains__(self, name): return _hkey(name) in self._headers
def __delitem__(self, name): del self._headers[_hkey(name)]
def __getitem__(self, name): return self._headers[_hkey(name)][-1]
def __setitem__(self, name, value): self._headers[_hkey(name)] = [str(value)]
def __setitem__(self, name, 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
Expand All @@ -1544,11 +1544,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)] = [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(str(value))
self._headers.setdefault(_hkey(name), []).append(_hval(value))

def iter_headers(self):
''' Yield (header, value) tuples, skipping headers that are not
Expand Down
13 changes: 12 additions & 1 deletion test/test_environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,16 +669,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 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 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()
Expand Down

0 comments on commit 78f67d5

Please sign in to comment.