Example #1
0
 def setUp(self):
     from waitress.parser import HTTPRequestParser
     from waitress.adjustments import Adjustments
     my_adj = Adjustments()
     self.parser = HTTPRequestParser(my_adj)
 def setUp(self):
     my_adj = Adjustments()
     self.parser = HTTPRequestParser(my_adj)
Example #3
0
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")
Example #5
0
 def setUp(self):
     from waitress.parser import HTTPRequestParser
     from waitress.adjustments import Adjustments
     my_adj = Adjustments()
     self.parser = HTTPRequestParser(my_adj)
Example #6
0
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
Example #7
0
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',
            })
Example #8
0
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")