Skip to content

Commit 772195c

Browse files
authored
Fix header fragmentation parsing (Fix #21)
2 parents 48f38d6 + 58dc648 commit 772195c

File tree

2 files changed

+109
-2
lines changed

2 files changed

+109
-2
lines changed

httptools/parser/parser.pyx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ cdef class HttpParser:
9898
self._last_error = None
9999

100100
cdef _maybe_call_on_header(self):
101-
if self._current_header_name is not None:
101+
if self._current_header_value is not None:
102102
current_header_name = self._current_header_name
103103
current_header_value = self._current_header_value
104104

@@ -110,7 +110,10 @@ cdef class HttpParser:
110110

111111
cdef _on_header_field(self, bytes field):
112112
self._maybe_call_on_header()
113-
self._current_header_name = field
113+
if self._current_header_name is None:
114+
self._current_header_name = field
115+
else:
116+
self._current_header_name += field
114117

115118
cdef _on_header_value(self, bytes val):
116119
if self._current_header_value is None:

tests/test_parser.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,110 @@ def test_parser_request_4(self):
419419
with self.assertRaisesRegex(TypeError, 'a bytes-like object'):
420420
p.feed_data('POST HTTP/1.2')
421421

422+
def test_parser_request_fragmented(self):
423+
m = mock.Mock()
424+
headers = {}
425+
m.on_header.side_effect = headers.__setitem__
426+
p = httptools.HttpRequestParser(m)
427+
428+
REQUEST = (
429+
b'PUT / HTTP/1.1\r\nHost: localhost:1234\r\nContent-Type: text/pl',
430+
b'ain; charset=utf-8\r\nX-Empty-Header: \r\nConnection: close\r\n',
431+
b'Content-Length: 10\r\n\r\n1234567890',
432+
)
433+
434+
p.feed_data(REQUEST[0])
435+
436+
m.on_message_begin.assert_called_once_with()
437+
m.on_url.assert_called_once_with(b'/')
438+
self.assertEqual(headers, {b'Host': b'localhost:1234'})
439+
440+
p.feed_data(REQUEST[1])
441+
self.assertEqual(
442+
headers,
443+
{b'Host': b'localhost:1234',
444+
b'Content-Type': b'text/plain; charset=utf-8',
445+
b'X-Empty-Header': b''})
446+
447+
p.feed_data(REQUEST[2])
448+
self.assertEqual(
449+
headers,
450+
{b'Host': b'localhost:1234',
451+
b'Content-Type': b'text/plain; charset=utf-8',
452+
b'X-Empty-Header': b'',
453+
b'Connection': b'close',
454+
b'Content-Length': b'10'})
455+
m.on_message_complete.assert_called_once_with()
456+
457+
def test_parser_request_fragmented_header(self):
458+
m = mock.Mock()
459+
headers = {}
460+
m.on_header.side_effect = headers.__setitem__
461+
p = httptools.HttpRequestParser(m)
462+
463+
REQUEST = (
464+
b'PUT / HTTP/1.1\r\nHost: localhost:1234\r\nContent-',
465+
b'Type: text/plain; charset=utf-8\r\n\r\n',
466+
)
467+
468+
p.feed_data(REQUEST[0])
469+
470+
m.on_message_begin.assert_called_once_with()
471+
m.on_url.assert_called_once_with(b'/')
472+
self.assertEqual(headers, {b'Host': b'localhost:1234'})
473+
474+
p.feed_data(REQUEST[1])
475+
self.assertEqual(
476+
headers,
477+
{b'Host': b'localhost:1234',
478+
b'Content-Type': b'text/plain; charset=utf-8'})
479+
480+
def test_parser_request_fragmented_value(self):
481+
m = mock.Mock()
482+
headers = {}
483+
m.on_header.side_effect = headers.__setitem__
484+
p = httptools.HttpRequestParser(m)
485+
486+
REQUEST = (
487+
b'PUT / HTTP/1.1\r\nHost: localhost:1234\r\nContent-Type:',
488+
b' text/pla',
489+
b'in; chars',
490+
b'et=utf-8\r\n\r\n',
491+
)
492+
493+
p.feed_data(REQUEST[0])
494+
495+
m.on_message_begin.assert_called_once_with()
496+
m.on_url.assert_called_once_with(b'/')
497+
self.assertEqual(headers, {b'Host': b'localhost:1234'})
498+
499+
p.feed_data(REQUEST[1])
500+
p.feed_data(REQUEST[2])
501+
p.feed_data(REQUEST[3])
502+
self.assertEqual(
503+
headers,
504+
{b'Host': b'localhost:1234',
505+
b'Content-Type': b'text/plain; charset=utf-8'})
506+
507+
def test_parser_request_fragmented_bytes(self):
508+
m = mock.Mock()
509+
headers = {}
510+
m.on_header.side_effect = headers.__setitem__
511+
p = httptools.HttpRequestParser(m)
512+
513+
REQUEST = \
514+
b'PUT / HTTP/1.1\r\nHost: localhost:1234\r\nContent-' \
515+
b'Type: text/plain; charset=utf-8\r\n\r\n'
516+
517+
step = 1
518+
for i in range(0, len(REQUEST), step):
519+
p.feed_data(REQUEST[i:i+step])
520+
521+
self.assertEqual(
522+
headers,
523+
{b'Host': b'localhost:1234',
524+
b'Content-Type': b'text/plain; charset=utf-8'})
525+
422526

423527
class TestUrlParser(unittest.TestCase):
424528

0 commit comments

Comments
 (0)