1 import pytest 2 3 from .._events import Request 4 from .._headers import ( 5 get_comma_header, 6 has_expect_100_continue, 7 Headers, 8 normalize_and_validate, 9 set_comma_header, 10 ) 11 from .._util import LocalProtocolError 12 13 14 def test_normalize_and_validate() -> None: 15 assert normalize_and_validate([("foo", "bar")]) == [(b"foo", b"bar")] 16 assert normalize_and_validate([(b"foo", b"bar")]) == [(b"foo", b"bar")] 17 18 # no leading/trailing whitespace in names 19 with pytest.raises(LocalProtocolError): 20 normalize_and_validate([(b"foo ", "bar")]) 21 with pytest.raises(LocalProtocolError): 22 normalize_and_validate([(b" foo", "bar")]) 23 24 # no weird characters in names 25 with pytest.raises(LocalProtocolError) as excinfo: 26 normalize_and_validate([(b"foo bar", b"baz")]) 27 assert "foo bar" in str(excinfo.value) 28 with pytest.raises(LocalProtocolError): 29 normalize_and_validate([(b"foo\x00bar", b"baz")]) 30 # Not even 8-bit characters: 31 with pytest.raises(LocalProtocolError): 32 normalize_and_validate([(b"foo\xffbar", b"baz")]) 33 # And not even the control characters we allow in values: 34 with pytest.raises(LocalProtocolError): 35 normalize_and_validate([(b"foo\x01bar", b"baz")]) 36 37 # no return or NUL characters in values 38 with pytest.raises(LocalProtocolError) as excinfo: 39 normalize_and_validate([("foo", "bar\rbaz")]) 40 assert "bar\\rbaz" in str(excinfo.value) 41 with pytest.raises(LocalProtocolError): 42 normalize_and_validate([("foo", "bar\nbaz")]) 43 with pytest.raises(LocalProtocolError): 44 normalize_and_validate([("foo", "bar\x00baz")]) 45 # no leading/trailing whitespace 46 with pytest.raises(LocalProtocolError): 47 normalize_and_validate([("foo", "barbaz ")]) 48 with pytest.raises(LocalProtocolError): 49 normalize_and_validate([("foo", " barbaz")]) 50 with pytest.raises(LocalProtocolError): 51 normalize_and_validate([("foo", "barbaz\t")]) 52 with pytest.raises(LocalProtocolError): 53 normalize_and_validate([("foo", "\tbarbaz")]) 54 55 # content-length 56 assert normalize_and_validate([("Content-Length", "1")]) == [ 57 (b"content-length", b"1") 58 ] 59 with pytest.raises(LocalProtocolError): 60 normalize_and_validate([("Content-Length", "asdf")]) 61 with pytest.raises(LocalProtocolError): 62 normalize_and_validate([("Content-Length", "1x")]) 63 with pytest.raises(LocalProtocolError): 64 normalize_and_validate([("Content-Length", "1"), ("Content-Length", "2")]) 65 assert normalize_and_validate( 66 [("Content-Length", "0"), ("Content-Length", "0")] 67 ) == [(b"content-length", b"0")] 68 assert normalize_and_validate([("Content-Length", "0 , 0")]) == [ 69 (b"content-length", b"0") 70 ] 71 with pytest.raises(LocalProtocolError): 72 normalize_and_validate( 73 [("Content-Length", "1"), ("Content-Length", "1"), ("Content-Length", "2")] 74 ) 75 with pytest.raises(LocalProtocolError): 76 normalize_and_validate([("Content-Length", "1 , 1,2")]) 77 78 # transfer-encoding 79 assert normalize_and_validate([("Transfer-Encoding", "chunked")]) == [ 80 (b"transfer-encoding", b"chunked") 81 ] 82 assert normalize_and_validate([("Transfer-Encoding", "cHuNkEd")]) == [ 83 (b"transfer-encoding", b"chunked") 84 ] 85 with pytest.raises(LocalProtocolError) as excinfo: 86 normalize_and_validate([("Transfer-Encoding", "gzip")]) 87 assert excinfo.value.error_status_hint == 501 # Not Implemented 88 with pytest.raises(LocalProtocolError) as excinfo: 89 normalize_and_validate( 90 [("Transfer-Encoding", "chunked"), ("Transfer-Encoding", "gzip")] 91 ) 92 assert excinfo.value.error_status_hint == 501 # Not Implemented 93 94 95 def test_get_set_comma_header() -> None: 96 headers = normalize_and_validate( 97 [ 98 ("Connection", "close"), 99 ("whatever", "something"), 100 ("connectiON", "fOo,, , BAR"), 101 ] 102 ) 103 104 assert get_comma_header(headers, b"connection") == [b"close", b"foo", b"bar"] 105 106 headers = set_comma_header(headers, b"newthing", ["a", "b"]) # type: ignore 107 108 with pytest.raises(LocalProtocolError): 109 set_comma_header(headers, b"newthing", [" a", "b"]) # type: ignore 110 111 assert headers == [ 112 (b"connection", b"close"), 113 (b"whatever", b"something"), 114 (b"connection", b"fOo,, , BAR"), 115 (b"newthing", b"a"), 116 (b"newthing", b"b"), 117 ] 118 119 headers = set_comma_header(headers, b"whatever", ["different thing"]) # type: ignore 120 121 assert headers == [ 122 (b"connection", b"close"), 123 (b"connection", b"fOo,, , BAR"), 124 (b"newthing", b"a"), 125 (b"newthing", b"b"), 126 (b"whatever", b"different thing"), 127 ] 128 129 130 def test_has_100_continue() -> None: 131 assert has_expect_100_continue( 132 Request( 133 method="GET", 134 target="/", 135 headers=[("Host", "example.com"), ("Expect", "100-continue")], 136 ) 137 ) 138 assert not has_expect_100_continue( 139 Request(method="GET", target="/", headers=[("Host", "example.com")]) 140 ) 141 # Case insensitive 142 assert has_expect_100_continue( 143 Request( 144 method="GET", 145 target="/", 146 headers=[("Host", "example.com"), ("Expect", "100-Continue")], 147 ) 148 ) 149 # Doesn't work in HTTP/1.0 150 assert not has_expect_100_continue( 151 Request( 152 method="GET", 153 target="/", 154 headers=[("Host", "example.com"), ("Expect", "100-continue")], 155 http_version="1.0", 156 ) 157 ) 158