class TestHTTPRequestParser(unittest.TestCase): def setUp(self): from waitress.parser import HTTPRequestParser from waitress.adjustments import Adjustments my_adj = Adjustments() self.parser = HTTPRequestParser(my_adj) def test_get_body_stream_None(self): self.parser.body_recv = None result = self.parser.get_body_stream() self.assertEqual(result.getvalue(), b'') def test_get_body_stream_nonNone(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv result = self.parser.get_body_stream() self.assertEqual(result, body_rcv) def test_received_nonsense_with_double_cr(self): data = b"""\ HTTP/1.0 GET /foobar """ result = self.parser.received(data) self.assertEqual(result, 22) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_nonsense_nothing(self): data = b"""\ """ result = self.parser.received(data) self.assertEqual(result, 2) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_no_doublecr(self): data = b"""\ GET /foobar HTTP/8.4 """ result = self.parser.received(data) self.assertEqual(result, 21) self.assertFalse(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_already_completed(self): self.parser.completed = True result = self.parser.received(b'a') self.assertEqual(result, 0) def test_received_cl_too_large(self): from waitress.utilities import RequestEntityTooLarge self.parser.adj.max_request_body_size = 2 data = b"""\ GET /foobar HTTP/8.4 Content-Length: 10 """ result = self.parser.received(data) self.assertEqual(result, 41) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_headers_too_large(self): from waitress.utilities import RequestHeaderFieldsTooLarge self.parser.adj.max_request_header_size = 2 data = b"""\ GET /foobar HTTP/8.4 X-Foo: 1 """ result = self.parser.received(data) self.assertEqual(result, 30) self.assertTrue(self.parser.completed) self.assertTrue( isinstance(self.parser.error, RequestHeaderFieldsTooLarge)) def test_received_body_too_large(self): from waitress.utilities import RequestEntityTooLarge self.parser.adj.max_request_body_size = 2 data = b"""\ GET /foobar HTTP/1.1 Transfer-Encoding: chunked X-Foo: 1 20;\r This string has 32 characters\r 0\r\n\r\n""" result = self.parser.received(data) self.assertEqual(result, 58) self.parser.received(data[result:]) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_error_from_parser(self): from waitress.utilities import BadRequest data = b"""\ GET /foobar HTTP/1.1 Transfer-Encoding: chunked X-Foo: 1 garbage """ # header result = self.parser.received(data) # body result = self.parser.received(data[result:]) self.assertEqual(result, 8) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, BadRequest)) def test_parse_header_gardenpath(self): data = b"""\ GET /foobar HTTP/8.4 foo: bar""" self.parser.parse_header(data) self.assertEqual(self.parser.first_line, b'GET /foobar HTTP/8.4') self.assertEqual(self.parser.headers['FOO'], 'bar') def test_parse_header_no_cr_in_headerplus(self): data = b"GET /foobar HTTP/8.4" self.parser.parse_header(data) self.assertEqual(self.parser.first_line, data) def test_parse_header_bad_content_length(self): data = b"GET /foobar HTTP/8.4\ncontent-length: abc" self.parser.parse_header(data) self.assertEqual(self.parser.body_rcv, None) def test_parse_header_11_te_chunked(self): data = b"GET /foobar HTTP/1.1\ntransfer-encoding: chunked" self.parser.parse_header(data) self.assertEqual(self.parser.body_rcv.__class__.__name__, 'ChunkedReceiver') def test_parse_header_11_expect_continue(self): data = b"GET /foobar HTTP/1.1\nexpect: 100-continue" self.parser.parse_header(data) self.assertEqual(self.parser.expect_continue, True) def test_parse_header_connection_close(self): data = b"GET /foobar HTTP/1.1\nConnection: close\n\n" self.parser.parse_header(data) self.assertEqual(self.parser.connection_close, True) def test__close_with_body_rcv(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv self.parser._close() self.assertTrue(body_rcv.closed) def test__close_with_no_body_rcv(self): self.parser.body_rcv = None self.parser._close() # doesn't raise
class TestHTTPRequestParser(unittest.TestCase): def setUp(self): my_adj = Adjustments() self.parser = HTTPRequestParser(my_adj) def test_get_body_stream_None(self): self.parser.body_recv = None result = self.parser.get_body_stream() self.assertEqual(result.getvalue(), b"") def test_get_body_stream_nonNone(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv result = self.parser.get_body_stream() self.assertEqual(result, body_rcv) def test_received_get_no_headers(self): data = b"HTTP/1.0 GET /foobar\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 24) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_bad_host_header(self): data = b"HTTP/1.0 GET /foobar\r\n Host: foo\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 36) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.error.__class__, BadRequest) def test_received_bad_transfer_encoding(self): data = (b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: foo\r\n" b"\r\n" b"1d;\r\n" b"This string has 29 characters\r\n" b"0\r\n\r\n") result = self.parser.received(data) self.assertEqual(result, 48) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.error.__class__, ServerNotImplemented) def test_received_nonsense_nothing(self): data = b"\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 4) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_no_doublecr(self): data = b"GET /foobar HTTP/8.4\r\n" result = self.parser.received(data) self.assertEqual(result, 22) self.assertFalse(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_already_completed(self): self.parser.completed = True result = self.parser.received(b"a") self.assertEqual(result, 0) def test_received_cl_too_large(self): self.parser.adj.max_request_body_size = 2 data = b"GET /foobar HTTP/8.4\r\nContent-Length: 10\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 44) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_headers_too_large(self): self.parser.adj.max_request_header_size = 2 data = b"GET /foobar HTTP/8.4\r\nX-Foo: 1\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 34) self.assertTrue(self.parser.completed) self.assertTrue( isinstance(self.parser.error, RequestHeaderFieldsTooLarge)) def test_received_body_too_large(self): self.parser.adj.max_request_body_size = 2 data = (b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: chunked\r\n" b"X-Foo: 1\r\n" b"\r\n" b"1d;\r\n" b"This string has 29 characters\r\n" b"0\r\n\r\n") result = self.parser.received(data) self.assertEqual(result, 62) self.parser.received(data[result:]) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_error_from_parser(self): data = (b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: chunked\r\n" b"X-Foo: 1\r\n" b"\r\n" b"garbage\r\n") # header result = self.parser.received(data) # body result = self.parser.received(data[result:]) self.assertEqual(result, 9) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, BadRequest)) def test_received_chunked_completed_sets_content_length(self): data = (b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: chunked\r\n" b"X-Foo: 1\r\n" b"\r\n" b"1d;\r\n" b"This string has 29 characters\r\n" b"0\r\n\r\n") result = self.parser.received(data) self.assertEqual(result, 62) data = data[result:] result = self.parser.received(data) self.assertTrue(self.parser.completed) self.assertTrue(self.parser.error is None) self.assertEqual(self.parser.headers["CONTENT_LENGTH"], "29") def test_parse_header_gardenpath(self): data = b"GET /foobar HTTP/8.4\r\nfoo: bar\r\n" self.parser.parse_header(data) self.assertEqual(self.parser.first_line, b"GET /foobar HTTP/8.4") self.assertEqual(self.parser.headers["FOO"], "bar") def test_parse_header_no_cr_in_headerplus(self): data = b"GET /foobar HTTP/8.4" try: self.parser.parse_header(data) except ParsingError: pass else: # pragma: nocover self.assertTrue(False) def test_parse_header_bad_content_length(self): data = b"GET /foobar HTTP/8.4\r\ncontent-length: abc\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Content-Length is invalid", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_multiple_content_length(self): data = b"GET /foobar HTTP/8.4\r\ncontent-length: 10\r\ncontent-length: 20\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Content-Length is invalid", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_11_te_chunked(self): # NB: test that capitalization of header value is unimportant data = b"GET /foobar HTTP/1.1\r\ntransfer-encoding: ChUnKed\r\n" self.parser.parse_header(data) self.assertEqual(self.parser.body_rcv.__class__.__name__, "ChunkedReceiver") def test_parse_header_transfer_encoding_invalid(self): data = b"GET /foobar HTTP/1.1\r\ntransfer-encoding: gzip\r\n" try: self.parser.parse_header(data) except TransferEncodingNotImplemented as e: self.assertIn("Transfer-Encoding requested is not supported.", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_transfer_encoding_invalid_multiple(self): data = b"GET /foobar HTTP/1.1\r\ntransfer-encoding: gzip\r\ntransfer-encoding: chunked\r\n" try: self.parser.parse_header(data) except TransferEncodingNotImplemented as e: self.assertIn("Transfer-Encoding requested is not supported.", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_transfer_encoding_invalid_whitespace(self): data = b"GET /foobar HTTP/1.1\r\nTransfer-Encoding:\x85chunked\r\n" try: self.parser.parse_header(data) except TransferEncodingNotImplemented as e: self.assertIn("Transfer-Encoding requested is not supported.", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_transfer_encoding_invalid_unicode(self): # This is the binary encoding for the UTF-8 character # https://www.compart.com/en/unicode/U+212A "unicode character "K"" # which if waitress were to accidentally do the wrong thing get # lowercased to just the ascii "k" due to unicode collisions during # transformation data = b"GET /foobar HTTP/1.1\r\nTransfer-Encoding: chun\xe2\x84\xaaed\r\n" try: self.parser.parse_header(data) except TransferEncodingNotImplemented as e: self.assertIn("Transfer-Encoding requested is not supported.", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_11_expect_continue(self): data = b"GET /foobar HTTP/1.1\r\nexpect: 100-continue\r\n" self.parser.parse_header(data) self.assertEqual(self.parser.expect_continue, True) def test_parse_header_connection_close(self): data = b"GET /foobar HTTP/1.1\r\nConnection: close\r\n" self.parser.parse_header(data) self.assertEqual(self.parser.connection_close, True) def test_close_with_body_rcv(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv self.parser.close() self.assertTrue(body_rcv.closed) def test_close_with_no_body_rcv(self): self.parser.body_rcv = None self.parser.close() # doesn't raise def test_parse_header_lf_only(self): data = b"GET /foobar HTTP/8.4\nfoo: bar" try: self.parser.parse_header(data) except ParsingError: pass else: # pragma: nocover self.assertTrue(False) def test_parse_header_cr_only(self): data = b"GET /foobar HTTP/8.4\rfoo: bar" try: self.parser.parse_header(data) except ParsingError: pass else: # pragma: nocover self.assertTrue(False) def test_parse_header_extra_lf_in_header(self): data = b"GET /foobar HTTP/8.4\r\nfoo: \nbar\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Bare CR or LF found in header line", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_extra_lf_in_first_line(self): data = b"GET /foobar\n HTTP/8.4\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Bare CR or LF found in HTTP message", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_whitespace(self): data = b"GET /foobar HTTP/8.4\r\nfoo : bar\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_whitespace_vtab(self): data = b"GET /foobar HTTP/1.1\r\nfoo:\x0bbar\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_no_colon(self): data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nnotvalid\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_folding_spacing(self): data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\n\t\x0bbaz\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_chars(self): data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nfoo: \x0bbaz\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_empty(self): data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nempty:\r\n" self.parser.parse_header(data) self.assertIn("EMPTY", self.parser.headers) self.assertIn("FOO", self.parser.headers) self.assertEqual(self.parser.headers["EMPTY"], "") self.assertEqual(self.parser.headers["FOO"], "bar") def test_parse_header_multiple_values(self): data = b"GET /foobar HTTP/1.1\r\nfoo: bar, whatever, more, please, yes\r\n" self.parser.parse_header(data) self.assertIn("FOO", self.parser.headers) self.assertEqual(self.parser.headers["FOO"], "bar, whatever, more, please, yes") def test_parse_header_multiple_values_header_folded(self): data = b"GET /foobar HTTP/1.1\r\nfoo: bar, whatever,\r\n more, please, yes\r\n" self.parser.parse_header(data) self.assertIn("FOO", self.parser.headers) self.assertEqual(self.parser.headers["FOO"], "bar, whatever, more, please, yes") def test_parse_header_multiple_values_header_folded_multiple(self): data = b"GET /foobar HTTP/1.1\r\nfoo: bar, whatever,\r\n more\r\nfoo: please, yes\r\n" self.parser.parse_header(data) self.assertIn("FOO", self.parser.headers) self.assertEqual(self.parser.headers["FOO"], "bar, whatever, more, please, yes") def test_parse_header_multiple_values_extra_space(self): # Tests errata from: https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189 data = b"GET /foobar HTTP/1.1\r\nfoo: abrowser/0.001 (C O M M E N T)\r\n" self.parser.parse_header(data) self.assertIn("FOO", self.parser.headers) self.assertEqual(self.parser.headers["FOO"], "abrowser/0.001 (C O M M E N T)") def test_parse_header_invalid_backtrack_bad(self): data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nfoo: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x10\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_short_values(self): data = b"GET /foobar HTTP/1.1\r\none: 1\r\ntwo: 22\r\n" self.parser.parse_header(data) self.assertIn("ONE", self.parser.headers) self.assertIn("TWO", self.parser.headers) self.assertEqual(self.parser.headers["ONE"], "1") self.assertEqual(self.parser.headers["TWO"], "22")
class TestHTTPRequestParser(unittest.TestCase): def setUp(self): from waitress.parser import HTTPRequestParser from waitress.adjustments import Adjustments my_adj = Adjustments() self.parser = HTTPRequestParser(my_adj) def test_get_body_stream_None(self): self.parser.body_recv = None result = self.parser.get_body_stream() self.assertEqual(result.getvalue(), b'') def test_get_body_stream_nonNone(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv result = self.parser.get_body_stream() self.assertEqual(result, body_rcv) def test_received_nonsense_with_double_cr(self): data = b"""\ HTTP/1.0 GET /foobar """ result = self.parser.received(data) self.assertEqual(result, 22) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_bad_host_header(self): from waitress.utilities import BadRequest data = b"""\ HTTP/1.0 GET /foobar Host: foo """ result = self.parser.received(data) self.assertEqual(result, 33) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.error.__class__, BadRequest) def test_received_nonsense_nothing(self): data = b"""\ """ result = self.parser.received(data) self.assertEqual(result, 2) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_no_doublecr(self): data = b"""\ GET /foobar HTTP/8.4 """ result = self.parser.received(data) self.assertEqual(result, 21) self.assertFalse(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_already_completed(self): self.parser.completed = True result = self.parser.received(b'a') self.assertEqual(result, 0) def test_received_cl_too_large(self): from waitress.utilities import RequestEntityTooLarge self.parser.adj.max_request_body_size = 2 data = b"""\ GET /foobar HTTP/8.4 Content-Length: 10 """ result = self.parser.received(data) self.assertEqual(result, 41) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_headers_too_large(self): from waitress.utilities import RequestHeaderFieldsTooLarge self.parser.adj.max_request_header_size = 2 data = b"""\ GET /foobar HTTP/8.4 X-Foo: 1 """ result = self.parser.received(data) self.assertEqual(result, 30) self.assertTrue(self.parser.completed) self.assertTrue( isinstance(self.parser.error, RequestHeaderFieldsTooLarge)) def test_received_body_too_large(self): from waitress.utilities import RequestEntityTooLarge self.parser.adj.max_request_body_size = 2 data = b"""\ GET /foobar HTTP/1.1 Transfer-Encoding: chunked X-Foo: 1 20;\r\n This string has 32 characters\r\n 0\r\n\r\n""" result = self.parser.received(data) self.assertEqual(result, 58) self.parser.received(data[result:]) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_error_from_parser(self): from waitress.utilities import BadRequest data = b"""\ GET /foobar HTTP/1.1 Transfer-Encoding: chunked X-Foo: 1 garbage """ # header result = self.parser.received(data) # body result = self.parser.received(data[result:]) self.assertEqual(result, 8) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, BadRequest)) def test_received_chunked_completed_sets_content_length(self): data = b"""\ GET /foobar HTTP/1.1 Transfer-Encoding: chunked X-Foo: 1 20;\r\n This string has 32 characters\r\n 0\r\n\r\n""" result = self.parser.received(data) self.assertEqual(result, 58) data = data[result:] result = self.parser.received(data) self.assertTrue(self.parser.completed) self.assertTrue(self.parser.error is None) self.assertEqual(self.parser.headers['CONTENT_LENGTH'], '32') def test_parse_header_gardenpath(self): data = b"""\ GET /foobar HTTP/8.4 foo: bar""" self.parser.parse_header(data) self.assertEqual(self.parser.first_line, b'GET /foobar HTTP/8.4') self.assertEqual(self.parser.headers['FOO'], 'bar') def test_parse_header_no_cr_in_headerplus(self): data = b"GET /foobar HTTP/8.4" self.parser.parse_header(data) self.assertEqual(self.parser.first_line, data) def test_parse_header_bad_content_length(self): data = b"GET /foobar HTTP/8.4\ncontent-length: abc" self.parser.parse_header(data) self.assertEqual(self.parser.body_rcv, None) def test_parse_header_11_te_chunked(self): # NB: test that capitalization of header value is unimportant data = b"GET /foobar HTTP/1.1\ntransfer-encoding: ChUnKed" self.parser.parse_header(data) self.assertEqual(self.parser.body_rcv.__class__.__name__, 'ChunkedReceiver') def test_parse_header_11_expect_continue(self): data = b"GET /foobar HTTP/1.1\nexpect: 100-continue" self.parser.parse_header(data) self.assertEqual(self.parser.expect_continue, True) def test_parse_header_connection_close(self): data = b"GET /foobar HTTP/1.1\nConnection: close\n\n" self.parser.parse_header(data) self.assertEqual(self.parser.connection_close, True) def test_close_with_body_rcv(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv self.parser.close() self.assertTrue(body_rcv.closed) def test_close_with_no_body_rcv(self): self.parser.body_rcv = None self.parser.close() # doesn't raise def test_header_continuations(self): # Ensure that lines starting with a space or tab character # are appended with a space to the preceding line # as per https://www.ietf.org/rfc/rfc2616.txt data = b"""\ GET /foobar HTTP/1.1 Transfer-Encoding: chunked X-Forwarded-For: \t10.11.12.13, \tunknown,127.0.0.1, 255.255.255.255 """ self.feed(data) self.assertTrue(self.parser.completed) self.assertEqual( self.parser.headers, { 'TRANSFER_ENCODING': 'chunked', 'X_FORWARDED_FOR': '10.11.12.13, unknown,127.0.0.1, 255.255.255.255', })
class TestHTTPRequestParser(unittest.TestCase): def setUp(self): from waitress.parser import HTTPRequestParser from waitress.adjustments import Adjustments my_adj = Adjustments() self.parser = HTTPRequestParser(my_adj) def test_get_body_stream_None(self): self.parser.body_recv = None result = self.parser.get_body_stream() self.assertEqual(result.getvalue(), b'') def test_get_body_stream_nonNone(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv result = self.parser.get_body_stream() self.assertEqual(result, body_rcv) def test_received_nonsense_with_double_cr(self): data = b"""\ HTTP/1.0 GET /foobar """ result = self.parser.received(data) self.assertEqual(result, 22) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_bad_host_header(self): from waitress.utilities import BadRequest data = b"""\ HTTP/1.0 GET /foobar Host: foo """ result = self.parser.received(data) self.assertEqual(result, 33) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.error.__class__, BadRequest) def test_received_nonsense_nothing(self): data = b"""\ """ result = self.parser.received(data) self.assertEqual(result, 2) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_no_doublecr(self): data = b"""\ GET /foobar HTTP/8.4 """ result = self.parser.received(data) self.assertEqual(result, 21) self.assertFalse(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_already_completed(self): self.parser.completed = True result = self.parser.received(b'a') self.assertEqual(result, 0) def test_received_cl_too_large(self): from waitress.utilities import RequestEntityTooLarge self.parser.adj.max_request_body_size = 2 data = b"""\ GET /foobar HTTP/8.4 Content-Length: 10 """ result = self.parser.received(data) self.assertEqual(result, 41) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_headers_too_large(self): from waitress.utilities import RequestHeaderFieldsTooLarge self.parser.adj.max_request_header_size = 2 data = b"""\ GET /foobar HTTP/8.4 X-Foo: 1 """ result = self.parser.received(data) self.assertEqual(result, 30) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestHeaderFieldsTooLarge)) def test_received_body_too_large(self): from waitress.utilities import RequestEntityTooLarge self.parser.adj.max_request_body_size = 2 data = b"""\ GET /foobar HTTP/1.1 Transfer-Encoding: chunked X-Foo: 1 20;\r\n This string has 32 characters\r\n 0\r\n\r\n""" result = self.parser.received(data) self.assertEqual(result, 58) self.parser.received(data[result:]) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_error_from_parser(self): from waitress.utilities import BadRequest data = b"""\ GET /foobar HTTP/1.1 Transfer-Encoding: chunked X-Foo: 1 garbage """ # header result = self.parser.received(data) # body result = self.parser.received(data[result:]) self.assertEqual(result, 8) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, BadRequest)) def test_received_chunked_completed_sets_content_length(self): data = b"""\ GET /foobar HTTP/1.1 Transfer-Encoding: chunked X-Foo: 1 20;\r\n This string has 32 characters\r\n 0\r\n\r\n""" result = self.parser.received(data) self.assertEqual(result, 58) data = data[result:] result = self.parser.received(data) self.assertTrue(self.parser.completed) self.assertTrue(self.parser.error is None) self.assertEqual(self.parser.headers['CONTENT_LENGTH'], '32') def test_parse_header_gardenpath(self): data = b"""\ GET /foobar HTTP/8.4 foo: bar""" self.parser.parse_header(data) self.assertEqual(self.parser.first_line, b'GET /foobar HTTP/8.4') self.assertEqual(self.parser.headers['FOO'], 'bar') def test_parse_header_no_cr_in_headerplus(self): data = b"GET /foobar HTTP/8.4" self.parser.parse_header(data) self.assertEqual(self.parser.first_line, data) def test_parse_header_bad_content_length(self): data = b"GET /foobar HTTP/8.4\ncontent-length: abc" self.parser.parse_header(data) self.assertEqual(self.parser.body_rcv, None) def test_parse_header_11_te_chunked(self): # NB: test that capitalization of header value is unimportant data = b"GET /foobar HTTP/1.1\ntransfer-encoding: ChUnKed" self.parser.parse_header(data) self.assertEqual(self.parser.body_rcv.__class__.__name__, 'ChunkedReceiver') def test_parse_header_11_expect_continue(self): data = b"GET /foobar HTTP/1.1\nexpect: 100-continue" self.parser.parse_header(data) self.assertEqual(self.parser.expect_continue, True) def test_parse_header_connection_close(self): data = b"GET /foobar HTTP/1.1\nConnection: close\n\n" self.parser.parse_header(data) self.assertEqual(self.parser.connection_close, True) def test_close_with_body_rcv(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv self.parser.close() self.assertTrue(body_rcv.closed) def test_close_with_no_body_rcv(self): self.parser.body_rcv = None self.parser.close() # doesn't raise
class TestHTTPRequestParser(unittest.TestCase): def setUp(self): from waitress.parser import HTTPRequestParser from waitress.adjustments import Adjustments my_adj = Adjustments() self.parser = HTTPRequestParser(my_adj) def test_get_body_stream_None(self): self.parser.body_recv = None result = self.parser.get_body_stream() self.assertEqual(result.getvalue(), b"") def test_get_body_stream_nonNone(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv result = self.parser.get_body_stream() self.assertEqual(result, body_rcv) def test_received_get_no_headers(self): data = b"HTTP/1.0 GET /foobar\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 24) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_bad_host_header(self): from waitress.utilities import BadRequest data = b"HTTP/1.0 GET /foobar\r\n Host: foo\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 36) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.error.__class__, BadRequest) def test_received_bad_transfer_encoding(self): from waitress.utilities import ServerNotImplemented data = (b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: foo\r\n" b"\r\n" b"1d;\r\n" b"This string has 29 characters\r\n" b"0\r\n\r\n") result = self.parser.received(data) self.assertEqual(result, 48) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.error.__class__, ServerNotImplemented) def test_received_nonsense_nothing(self): data = b"\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 4) self.assertTrue(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_no_doublecr(self): data = b"GET /foobar HTTP/8.4\r\n" result = self.parser.received(data) self.assertEqual(result, 22) self.assertFalse(self.parser.completed) self.assertEqual(self.parser.headers, {}) def test_received_already_completed(self): self.parser.completed = True result = self.parser.received(b"a") self.assertEqual(result, 0) def test_received_cl_too_large(self): from waitress.utilities import RequestEntityTooLarge self.parser.adj.max_request_body_size = 2 data = b"GET /foobar HTTP/8.4\r\nContent-Length: 10\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 44) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_headers_too_large(self): from waitress.utilities import RequestHeaderFieldsTooLarge self.parser.adj.max_request_header_size = 2 data = b"GET /foobar HTTP/8.4\r\nX-Foo: 1\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 34) self.assertTrue(self.parser.completed) self.assertTrue( isinstance(self.parser.error, RequestHeaderFieldsTooLarge)) def test_received_body_too_large(self): from waitress.utilities import RequestEntityTooLarge self.parser.adj.max_request_body_size = 2 data = (b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: chunked\r\n" b"X-Foo: 1\r\n" b"\r\n" b"1d;\r\n" b"This string has 29 characters\r\n" b"0\r\n\r\n") result = self.parser.received(data) self.assertEqual(result, 62) self.parser.received(data[result:]) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_error_from_parser(self): from waitress.utilities import BadRequest data = (b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: chunked\r\n" b"X-Foo: 1\r\n" b"\r\n" b"garbage\r\n") # header result = self.parser.received(data) # body result = self.parser.received(data[result:]) self.assertEqual(result, 9) self.assertTrue(self.parser.completed) self.assertTrue(isinstance(self.parser.error, BadRequest)) def test_received_chunked_completed_sets_content_length(self): data = (b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: chunked\r\n" b"X-Foo: 1\r\n" b"\r\n" b"1d;\r\n" b"This string has 29 characters\r\n" b"0\r\n\r\n") result = self.parser.received(data) self.assertEqual(result, 62) data = data[result:] result = self.parser.received(data) self.assertTrue(self.parser.completed) self.assertTrue(self.parser.error is None) self.assertEqual(self.parser.headers["CONTENT_LENGTH"], "29") def test_parse_header_gardenpath(self): data = b"GET /foobar HTTP/8.4\r\nfoo: bar\r\n" self.parser.parse_header(data) self.assertEqual(self.parser.first_line, b"GET /foobar HTTP/8.4") self.assertEqual(self.parser.headers["FOO"], "bar") def test_parse_header_no_cr_in_headerplus(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/8.4" try: self.parser.parse_header(data) except ParsingError: pass else: # pragma: nocover self.assertTrue(False) def test_parse_header_bad_content_length(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/8.4\r\ncontent-length: abc\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Content-Length is invalid", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_multiple_content_length(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/8.4\r\ncontent-length: 10\r\ncontent-length: 20\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Content-Length is invalid", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_11_te_chunked(self): # NB: test that capitalization of header value is unimportant data = b"GET /foobar HTTP/1.1\r\ntransfer-encoding: ChUnKed\r\n" self.parser.parse_header(data) self.assertEqual(self.parser.body_rcv.__class__.__name__, "ChunkedReceiver") def test_parse_header_transfer_encoding_invalid(self): from waitress.parser import TransferEncodingNotImplemented data = b"GET /foobar HTTP/1.1\r\ntransfer-encoding: gzip\r\n" try: self.parser.parse_header(data) except TransferEncodingNotImplemented as e: self.assertIn("Transfer-Encoding requested is not supported.", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_transfer_encoding_invalid_multiple(self): from waitress.parser import TransferEncodingNotImplemented data = b"GET /foobar HTTP/1.1\r\ntransfer-encoding: gzip\r\ntransfer-encoding: chunked\r\n" try: self.parser.parse_header(data) except TransferEncodingNotImplemented as e: self.assertIn("Transfer-Encoding requested is not supported.", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_11_expect_continue(self): data = b"GET /foobar HTTP/1.1\r\nexpect: 100-continue\r\n" self.parser.parse_header(data) self.assertEqual(self.parser.expect_continue, True) def test_parse_header_connection_close(self): data = b"GET /foobar HTTP/1.1\r\nConnection: close\r\n" self.parser.parse_header(data) self.assertEqual(self.parser.connection_close, True) def test_close_with_body_rcv(self): body_rcv = DummyBodyStream() self.parser.body_rcv = body_rcv self.parser.close() self.assertTrue(body_rcv.closed) def test_close_with_no_body_rcv(self): self.parser.body_rcv = None self.parser.close() # doesn't raise def test_parse_header_lf_only(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/8.4\nfoo: bar" try: self.parser.parse_header(data) except ParsingError: pass else: # pragma: nocover self.assertTrue(False) def test_parse_header_cr_only(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/8.4\rfoo: bar" try: self.parser.parse_header(data) except ParsingError: pass else: # pragma: nocover self.assertTrue(False) def test_parse_header_extra_lf_in_header(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/8.4\r\nfoo: \nbar\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Bare CR or LF found in header line", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_extra_lf_in_first_line(self): from waitress.parser import ParsingError data = b"GET /foobar\n HTTP/8.4\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Bare CR or LF found in HTTP message", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_whitespace(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/8.4\r\nfoo : bar\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_whitespace_vtab(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/1.1\r\nfoo:\x0bbar\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_no_colon(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nnotvalid\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_folding_spacing(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\n\t\x0bbaz\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_invalid_chars(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\n\foo: \x0bbaz\r\n" try: self.parser.parse_header(data) except ParsingError as e: self.assertIn("Invalid header", e.args[0]) else: # pragma: nocover self.assertTrue(False) def test_parse_header_empty(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nempty:\r\n" self.parser.parse_header(data) self.assertIn("EMPTY", self.parser.headers) self.assertIn("FOO", self.parser.headers) self.assertEqual(self.parser.headers["EMPTY"], "") self.assertEqual(self.parser.headers["FOO"], "bar") def test_parse_header_multiple_values(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/1.1\r\nfoo: bar, whatever, more, please, yes\r\n" self.parser.parse_header(data) self.assertIn("FOO", self.parser.headers) self.assertEqual(self.parser.headers["FOO"], "bar, whatever, more, please, yes") def test_parse_header_multiple_values_header_folded(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/1.1\r\nfoo: bar, whatever,\r\n more, please, yes\r\n" self.parser.parse_header(data) self.assertIn("FOO", self.parser.headers) self.assertEqual(self.parser.headers["FOO"], "bar, whatever, more, please, yes") def test_parse_header_multiple_values_header_folded_multiple(self): from waitress.parser import ParsingError data = b"GET /foobar HTTP/1.1\r\nfoo: bar, whatever,\r\n more\r\nfoo: please, yes\r\n" self.parser.parse_header(data) self.assertIn("FOO", self.parser.headers) self.assertEqual(self.parser.headers["FOO"], "bar, whatever, more, please, yes")