Permalink
Browse files

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.
  • Loading branch information...
defnull committed Dec 10, 2016
1 parent 58882c7 commit 6d7e13da0f998820800ecb3fe9ccee4189aefb54
Showing with 28 additions and 11 deletions.
  1. +14 −11 bottle.py
  2. +14 −0 test/test_environ.py
@@ -1573,9 +1573,16 @@ def __delattr__(self, name, value):
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 @@ def __getattr__(self, name, default=unicode()):
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 @@ def __getitem__(self, key):
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 @@ 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]

@@ -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 @@ def test_non_string_header(self):
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()

0 comments on commit 6d7e13d

Please sign in to comment.