1 from http import HTTPStatus 2 3 import pytest 4 5 from .. import _events 6 from .._events import ( 7 ConnectionClosed, 8 Data, 9 EndOfMessage, 10 Event, 11 InformationalResponse, 12 Request, 13 Response, 14 ) 15 from .._util import LocalProtocolError 16 17 18 def test_events() -> None: 19 with pytest.raises(LocalProtocolError): 20 # Missing Host: 21 req = Request( 22 method="GET", target="/", headers=[("a", "b")], http_version="1.1" 23 ) 24 # But this is okay (HTTP/1.0) 25 req = Request(method="GET", target="/", headers=[("a", "b")], http_version="1.0") 26 # fields are normalized 27 assert req.method == b"GET" 28 assert req.target == b"/" 29 assert req.headers == [(b"a", b"b")] 30 assert req.http_version == b"1.0" 31 32 # This is also okay -- has a Host (with weird capitalization, which is ok) 33 req = Request( 34 method="GET", 35 target="/", 36 headers=[("a", "b"), ("hOSt", "example.com")], 37 http_version="1.1", 38 ) 39 # we normalize header capitalization 40 assert req.headers == [(b"a", b"b"), (b"host", b"example.com")] 41 42 # Multiple host is bad too 43 with pytest.raises(LocalProtocolError): 44 req = Request( 45 method="GET", 46 target="/", 47 headers=[("Host", "a"), ("Host", "a")], 48 http_version="1.1", 49 ) 50 # Even for HTTP/1.0 51 with pytest.raises(LocalProtocolError): 52 req = Request( 53 method="GET", 54 target="/", 55 headers=[("Host", "a"), ("Host", "a")], 56 http_version="1.0", 57 ) 58 59 # Header values are validated 60 for bad_char in "\x00\r\n\f\v": 61 with pytest.raises(LocalProtocolError): 62 req = Request( 63 method="GET", 64 target="/", 65 headers=[("Host", "a"), ("Foo", "asd" + bad_char)], 66 http_version="1.0", 67 ) 68 69 # But for compatibility we allow non-whitespace control characters, even 70 # though they're forbidden by the spec. 71 Request( 72 method="GET", 73 target="/", 74 headers=[("Host", "a"), ("Foo", "asd\x01\x02\x7f")], 75 http_version="1.0", 76 ) 77 78 # Request target is validated 79 for bad_byte in b"\x00\x20\x7f\xee": 80 target = bytearray(b"/") 81 target.append(bad_byte) 82 with pytest.raises(LocalProtocolError): 83 Request( 84 method="GET", target=target, headers=[("Host", "a")], http_version="1.1" 85 ) 86 87 # Request method is validated 88 with pytest.raises(LocalProtocolError): 89 Request( 90 method="GET / HTTP/1.1", 91 target=target, 92 headers=[("Host", "a")], 93 http_version="1.1", 94 ) 95 96 ir = InformationalResponse(status_code=100, headers=[("Host", "a")]) 97 assert ir.status_code == 100 98 assert ir.headers == [(b"host", b"a")] 99 assert ir.http_version == b"1.1" 100 101 with pytest.raises(LocalProtocolError): 102 InformationalResponse(status_code=200, headers=[("Host", "a")]) 103 104 resp = Response(status_code=204, headers=[], http_version="1.0") # type: ignore[arg-type] 105 assert resp.status_code == 204 106 assert resp.headers == [] 107 assert resp.http_version == b"1.0" 108 109 with pytest.raises(LocalProtocolError): 110 resp = Response(status_code=100, headers=[], http_version="1.0") # type: ignore[arg-type] 111 112 with pytest.raises(LocalProtocolError): 113 Response(status_code="100", headers=[], http_version="1.0") # type: ignore[arg-type] 114 115 with pytest.raises(LocalProtocolError): 116 InformationalResponse(status_code=b"100", headers=[], http_version="1.0") # type: ignore[arg-type] 117 118 d = Data(data=b"asdf") 119 assert d.data == b"asdf" 120 121 eom = EndOfMessage() 122 assert eom.headers == [] 123 124 cc = ConnectionClosed() 125 assert repr(cc) == "ConnectionClosed()" 126 127 128 def test_intenum_status_code() -> None: 129 # https://github.com/python-hyper/h11/issues/72 130 131 r = Response(status_code=HTTPStatus.OK, headers=[], http_version="1.0") # type: ignore[arg-type] 132 assert r.status_code == HTTPStatus.OK 133 assert type(r.status_code) is not type(HTTPStatus.OK) 134 assert type(r.status_code) is int 135 136 137 def test_header_casing() -> None: 138 r = Request( 139 method="GET", 140 target="/", 141 headers=[("Host", "example.org"), ("Connection", "keep-alive")], 142 http_version="1.1", 143 ) 144 assert len(r.headers) == 2 145 assert r.headers[0] == (b"host", b"example.org") 146 assert r.headers == [(b"host", b"example.org"), (b"connection", b"keep-alive")] 147 assert r.headers.raw_items() == [ 148 (b"Host", b"example.org"), 149 (b"Connection", b"keep-alive"), 150 ] 151