Skip to content
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
7 changes: 5 additions & 2 deletions httptools/parser/parser.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ cdef class HttpParser:
self._last_error = None

cdef _maybe_call_on_header(self):
if self._current_header_name is not None:
if self._current_header_value is not None:
current_header_name = self._current_header_name
current_header_value = self._current_header_value

Expand All @@ -107,7 +107,10 @@ cdef class HttpParser:

cdef _on_header_field(self, bytes field):
self._maybe_call_on_header()
self._current_header_name = field
if self._current_header_name is None:
self._current_header_name = field
else:
self._current_header_name += field

cdef _on_header_value(self, bytes val):
if self._current_header_value is None:
Expand Down
104 changes: 104 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,110 @@ def test_parser_request_4(self):
with self.assertRaisesRegex(TypeError, 'a bytes-like object'):
p.feed_data('POST HTTP/1.2')

def test_parser_request_fragmented(self):
m = mock.Mock()
headers = {}
m.on_header.side_effect = headers.__setitem__
p = httptools.HttpRequestParser(m)

REQUEST = (
b'PUT / HTTP/1.1\r\nHost: localhost:1234\r\nContent-Type: text/pl',
b'ain; charset=utf-8\r\nX-Empty-Header: \r\nConnection: close\r\n',
b'Content-Length: 10\r\n\r\n1234567890',
)

p.feed_data(REQUEST[0])

m.on_message_begin.assert_called_once_with()
m.on_url.assert_called_once_with(b'/')
self.assertEqual(headers, {b'Host': b'localhost:1234'})

p.feed_data(REQUEST[1])
self.assertEqual(
headers,
{b'Host': b'localhost:1234',
b'Content-Type': b'text/plain; charset=utf-8',
b'X-Empty-Header': b''})

p.feed_data(REQUEST[2])
self.assertEqual(
headers,
{b'Host': b'localhost:1234',
b'Content-Type': b'text/plain; charset=utf-8',
b'X-Empty-Header': b'',
b'Connection': b'close',
b'Content-Length': b'10'})
m.on_message_complete.assert_called_once_with()

def test_parser_request_fragmented_header(self):
m = mock.Mock()
headers = {}
m.on_header.side_effect = headers.__setitem__
p = httptools.HttpRequestParser(m)

REQUEST = (
b'PUT / HTTP/1.1\r\nHost: localhost:1234\r\nContent-',
b'Type: text/plain; charset=utf-8\r\n\r\n',
)

p.feed_data(REQUEST[0])

m.on_message_begin.assert_called_once_with()
m.on_url.assert_called_once_with(b'/')
self.assertEqual(headers, {b'Host': b'localhost:1234'})

p.feed_data(REQUEST[1])
self.assertEqual(
headers,
{b'Host': b'localhost:1234',
b'Content-Type': b'text/plain; charset=utf-8'})

def test_parser_request_fragmented_value(self):
m = mock.Mock()
headers = {}
m.on_header.side_effect = headers.__setitem__
p = httptools.HttpRequestParser(m)

REQUEST = (
b'PUT / HTTP/1.1\r\nHost: localhost:1234\r\nContent-Type:',
b' text/pla',
b'in; chars',
b'et=utf-8\r\n\r\n',
)

p.feed_data(REQUEST[0])

m.on_message_begin.assert_called_once_with()
m.on_url.assert_called_once_with(b'/')
self.assertEqual(headers, {b'Host': b'localhost:1234'})

p.feed_data(REQUEST[1])
p.feed_data(REQUEST[2])
p.feed_data(REQUEST[3])
self.assertEqual(
headers,
{b'Host': b'localhost:1234',
b'Content-Type': b'text/plain; charset=utf-8'})

def test_parser_request_fragmented_bytes(self):
m = mock.Mock()
headers = {}
m.on_header.side_effect = headers.__setitem__
p = httptools.HttpRequestParser(m)

REQUEST = \
b'PUT / HTTP/1.1\r\nHost: localhost:1234\r\nContent-' \
b'Type: text/plain; charset=utf-8\r\n\r\n'

step = 1
for i in range(0, len(REQUEST), step):
p.feed_data(REQUEST[i:i+step])

self.assertEqual(
headers,
{b'Host': b'localhost:1234',
b'Content-Type': b'text/plain; charset=utf-8'})


class TestUrlParser(unittest.TestCase):

Expand Down