Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

feat: Limit the size of allowed HTTP bodies & headers #602

Merged
merged 1 commit into from
Aug 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions autopush/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
)
from autopush.web.simplepush import SimplePushHandler
from autopush.web.webpush import WebPushHandler
from autopush.web.limitedhttpconnection import LimitedHTTPConnection


shared_config_files = [
Expand Down Expand Up @@ -553,6 +554,8 @@ def endpoint_main(sysargs=None, use_files=True):
default_host=settings.hostname, debug=args.debug,
log_function=skip_request_logging
)
site.protocol = LimitedHTTPConnection
site.protocol.maxData = settings.max_data
mount_health_handlers(site, settings)

settings.metrics.start()
Expand Down
51 changes: 51 additions & 0 deletions autopush/tests/test_limitedhttpconnection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from io import BytesIO

from mock import Mock
from twisted.trial import unittest
from nose.tools import eq_

from autopush.web.limitedhttpconnection import (
LimitedHTTPConnection,
)


class TestLimitedHttpConnection(unittest.TestCase):
def test_lineRecieved(self):
mock_transport = Mock()
conn = LimitedHTTPConnection()
conn.factory = Mock()
conn.factory.settings = {}
conn.makeConnection(mock_transport)
conn._on_headers = Mock()

conn.maxHeaders = 2
conn.lineReceived("line 1")
eq_(conn._headersbuffer, ["line 1\r\n"])
conn.lineReceived("line 2")
conn.lineReceived("line 3")
mock_transport.loseConnection.assert_called()
conn.lineReceived("")
eq_(conn._headersbuffer, [])
conn._on_headers.assert_called()
eq_(conn._on_headers.call_args[0][0],
"line 1\r\nline 2\r\n")

def test_rawDataReceived(self):
mock_transport = Mock()
conn = LimitedHTTPConnection()
conn.factory = Mock()
conn.factory.settings = {}
conn.makeConnection(mock_transport)
conn._on_headers = Mock()
conn._on_request_body = Mock()
conn._contentbuffer = BytesIO()

conn.maxData = 10
conn.rawDataReceived("12345")
conn._contentbuffer = BytesIO()
conn.content_length = 3
conn.rawDataReceived("12345")
eq_(False, mock_transport.loseConnection.called)
conn._on_request_body.assert_called()
conn.rawDataReceived("12345678901")
mock_transport.loseConnection.assert_called()
54 changes: 54 additions & 0 deletions autopush/web/limitedhttpconnection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from cyclone import httpserver
from twisted.logger import Logger


class LimitedHTTPConnection(httpserver.HTTPConnection):
"""
Limit the amount of data being sent to a reasonable amount.

twisted already limits TCP streamed chunk reads to 65K, with
~16k per header line. By default, we'll limit the number of
header lines to 100, and the maximum amount of data for the body
to be 4K.

"""
maxHeaders = 100
maxData = 1024*4

def lineReceived(self, line):
"""Process a header line of data, ensuring we have not exceeded the
max number of allowable headers.

:param line: raw header line
"""
if line:
if len(self._headersbuffer) == self.maxHeaders:
Logger().warn("Too many headers sent, terminating connection")
return self.lineLengthExceeded(line)
self._headersbuffer.append(line + self.delimiter)
else:
buff = "".join(self._headersbuffer)
self._headersbuffer = []
self._on_headers(buff)

def rawDataReceived(self, data):
"""Process a raw chunk of data, ensuring we have not exceeded the
max size of a data block

:param data: raw data block
"""
if len(data) > self.maxData:
Logger().warn("Too much data sent, terminating connection")
return self.lineLengthExceeded(data)
if self.content_length is not None:
data, rest = data[:self.content_length], data[self.content_length:]
self.content_length -= len(data)
else:
rest = ''

self._contentbuffer.write(data)
if self.content_length <= 0:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice bugfix (I noticed this is == 0 in cyclone) =]

self._contentbuffer.seek(0, 0)
self._on_request_body(self._contentbuffer.read())
self._content_length = self._contentbuffer = None
self.setLineMode(rest)