BryanW commited on
Commit
ec3500b
·
verified ·
1 Parent(s): 27d7c98

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/__init__.cpython-312.pyc +0 -0
  2. URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/override.cpython-312.pyc +0 -0
  3. URSA/.venv_ursa/lib/python3.12/site-packages/einops-0.8.1.dist-info/licenses/LICENSE +21 -0
  4. URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_api.cpython-312.pyc +0 -0
  5. URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_error.cpython-312.pyc +0 -0
  6. URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_soft.cpython-312.pyc +0 -0
  7. URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_windows.cpython-312.pyc +0 -0
  8. URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/asyncio.cpython-312.pyc +0 -0
  9. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/__init__.cpython-312.pyc +0 -0
  10. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_abnf.cpython-312.pyc +0 -0
  11. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_connection.cpython-312.pyc +0 -0
  12. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_events.cpython-312.pyc +0 -0
  13. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_headers.cpython-312.pyc +0 -0
  14. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_readers.cpython-312.pyc +0 -0
  15. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc +0 -0
  16. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_state.cpython-312.pyc +0 -0
  17. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_util.cpython-312.pyc +0 -0
  18. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_version.cpython-312.pyc +0 -0
  19. URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_writers.cpython-312.pyc +0 -0
  20. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__init__.py +0 -0
  21. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  22. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/helpers.cpython-312.pyc +0 -0
  23. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_against_stdlib_http.cpython-312.pyc +0 -0
  24. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_connection.cpython-312.pyc +0 -0
  25. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_events.cpython-312.pyc +0 -0
  26. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_headers.cpython-312.pyc +0 -0
  27. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_helpers.cpython-312.pyc +0 -0
  28. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_io.cpython-312.pyc +0 -0
  29. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_receivebuffer.cpython-312.pyc +0 -0
  30. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_state.cpython-312.pyc +0 -0
  31. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_util.cpython-312.pyc +0 -0
  32. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/data/test-file +1 -0
  33. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/helpers.py +101 -0
  34. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_against_stdlib_http.py +115 -0
  35. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_connection.py +1122 -0
  36. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_events.py +150 -0
  37. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_headers.py +157 -0
  38. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_helpers.py +32 -0
  39. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_io.py +572 -0
  40. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_receivebuffer.py +135 -0
  41. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_state.py +271 -0
  42. URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_util.py +112 -0
  43. URSA/.venv_ursa/lib/python3.12/site-packages/imageio-2.37.2.dist-info/licenses/LICENSE +24 -0
  44. URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc +0 -0
  45. URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc +0 -0
  46. URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc +0 -0
  47. URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc +0 -0
  48. URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc +0 -0
  49. URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/debug.cpython-312.pyc +0 -0
  50. URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc +0 -0
URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (9.87 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/override.cpython-312.pyc ADDED
Binary file (308 Bytes). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/einops-0.8.1.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Alex Rogozhnikov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_api.cpython-312.pyc ADDED
Binary file (16.6 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_error.cpython-312.pyc ADDED
Binary file (1.78 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_soft.cpython-312.pyc ADDED
Binary file (2.48 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_windows.cpython-312.pyc ADDED
Binary file (3.29 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/asyncio.cpython-312.pyc ADDED
Binary file (15.6 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (1.09 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_abnf.cpython-312.pyc ADDED
Binary file (1.79 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_connection.cpython-312.pyc ADDED
Binary file (22.6 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_events.cpython-312.pyc ADDED
Binary file (13.3 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_headers.cpython-312.pyc ADDED
Binary file (7.88 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_readers.cpython-312.pyc ADDED
Binary file (9.43 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc ADDED
Binary file (4.72 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_state.cpython-312.pyc ADDED
Binary file (8.55 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_util.cpython-312.pyc ADDED
Binary file (4.73 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_version.cpython-312.pyc ADDED
Binary file (224 Bytes). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_writers.cpython-312.pyc ADDED
Binary file (6.31 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__init__.py ADDED
File without changes
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (201 Bytes). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/helpers.cpython-312.pyc ADDED
Binary file (4.4 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_against_stdlib_http.cpython-312.pyc ADDED
Binary file (6.97 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_connection.cpython-312.pyc ADDED
Binary file (58.4 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_events.cpython-312.pyc ADDED
Binary file (5.56 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_headers.cpython-312.pyc ADDED
Binary file (7.14 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_helpers.cpython-312.pyc ADDED
Binary file (1.21 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_io.cpython-312.pyc ADDED
Binary file (20 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_receivebuffer.cpython-312.pyc ADDED
Binary file (3.99 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_state.cpython-312.pyc ADDED
Binary file (12.6 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_util.cpython-312.pyc ADDED
Binary file (6.19 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/data/test-file ADDED
@@ -0,0 +1 @@
 
 
1
+ 92b12bc045050b55b848d37167a1a63947c364579889ce1d39788e45e9fac9e5
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/helpers.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import cast, List, Type, Union, ValuesView
2
+
3
+ from .._connection import Connection, NEED_DATA, PAUSED
4
+ from .._events import (
5
+ ConnectionClosed,
6
+ Data,
7
+ EndOfMessage,
8
+ Event,
9
+ InformationalResponse,
10
+ Request,
11
+ Response,
12
+ )
13
+ from .._state import CLIENT, CLOSED, DONE, MUST_CLOSE, SERVER
14
+ from .._util import Sentinel
15
+
16
+ try:
17
+ from typing import Literal
18
+ except ImportError:
19
+ from typing_extensions import Literal # type: ignore
20
+
21
+
22
+ def get_all_events(conn: Connection) -> List[Event]:
23
+ got_events = []
24
+ while True:
25
+ event = conn.next_event()
26
+ if event in (NEED_DATA, PAUSED):
27
+ break
28
+ event = cast(Event, event)
29
+ got_events.append(event)
30
+ if type(event) is ConnectionClosed:
31
+ break
32
+ return got_events
33
+
34
+
35
+ def receive_and_get(conn: Connection, data: bytes) -> List[Event]:
36
+ conn.receive_data(data)
37
+ return get_all_events(conn)
38
+
39
+
40
+ # Merges adjacent Data events, converts payloads to bytestrings, and removes
41
+ # chunk boundaries.
42
+ def normalize_data_events(in_events: List[Event]) -> List[Event]:
43
+ out_events: List[Event] = []
44
+ for event in in_events:
45
+ if type(event) is Data:
46
+ event = Data(data=bytes(event.data), chunk_start=False, chunk_end=False)
47
+ if out_events and type(out_events[-1]) is type(event) is Data:
48
+ out_events[-1] = Data(
49
+ data=out_events[-1].data + event.data,
50
+ chunk_start=out_events[-1].chunk_start,
51
+ chunk_end=out_events[-1].chunk_end,
52
+ )
53
+ else:
54
+ out_events.append(event)
55
+ return out_events
56
+
57
+
58
+ # Given that we want to write tests that push some events through a Connection
59
+ # and check that its state updates appropriately... we might as make a habit
60
+ # of pushing them through two Connections with a fake network link in
61
+ # between.
62
+ class ConnectionPair:
63
+ def __init__(self) -> None:
64
+ self.conn = {CLIENT: Connection(CLIENT), SERVER: Connection(SERVER)}
65
+ self.other = {CLIENT: SERVER, SERVER: CLIENT}
66
+
67
+ @property
68
+ def conns(self) -> ValuesView[Connection]:
69
+ return self.conn.values()
70
+
71
+ # expect="match" if expect=send_events; expect=[...] to say what expected
72
+ def send(
73
+ self,
74
+ role: Type[Sentinel],
75
+ send_events: Union[List[Event], Event],
76
+ expect: Union[List[Event], Event, Literal["match"]] = "match",
77
+ ) -> bytes:
78
+ if not isinstance(send_events, list):
79
+ send_events = [send_events]
80
+ data = b""
81
+ closed = False
82
+ for send_event in send_events:
83
+ new_data = self.conn[role].send(send_event)
84
+ if new_data is None:
85
+ closed = True
86
+ else:
87
+ data += new_data
88
+ # send uses b"" to mean b"", and None to mean closed
89
+ # receive uses b"" to mean closed, and None to mean "try again"
90
+ # so we have to translate between the two conventions
91
+ if data:
92
+ self.conn[self.other[role]].receive_data(data)
93
+ if closed:
94
+ self.conn[self.other[role]].receive_data(b"")
95
+ got_events = get_all_events(self.conn[self.other[role]])
96
+ if expect == "match":
97
+ expect = send_events
98
+ if not isinstance(expect, list):
99
+ expect = [expect]
100
+ assert got_events == expect
101
+ return data
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_against_stdlib_http.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os.path
3
+ import socket
4
+ import socketserver
5
+ import threading
6
+ from contextlib import closing, contextmanager
7
+ from http.server import SimpleHTTPRequestHandler
8
+ from typing import Callable, Generator
9
+ from urllib.request import urlopen
10
+
11
+ import h11
12
+
13
+
14
+ @contextmanager
15
+ def socket_server(
16
+ handler: Callable[..., socketserver.BaseRequestHandler]
17
+ ) -> Generator[socketserver.TCPServer, None, None]:
18
+ httpd = socketserver.TCPServer(("127.0.0.1", 0), handler)
19
+ thread = threading.Thread(
20
+ target=httpd.serve_forever, kwargs={"poll_interval": 0.01}
21
+ )
22
+ thread.daemon = True
23
+ try:
24
+ thread.start()
25
+ yield httpd
26
+ finally:
27
+ httpd.shutdown()
28
+
29
+
30
+ test_file_path = os.path.join(os.path.dirname(__file__), "data/test-file")
31
+ with open(test_file_path, "rb") as f:
32
+ test_file_data = f.read()
33
+
34
+
35
+ class SingleMindedRequestHandler(SimpleHTTPRequestHandler):
36
+ def translate_path(self, path: str) -> str:
37
+ return test_file_path
38
+
39
+
40
+ def test_h11_as_client() -> None:
41
+ with socket_server(SingleMindedRequestHandler) as httpd:
42
+ with closing(socket.create_connection(httpd.server_address)) as s:
43
+ c = h11.Connection(h11.CLIENT)
44
+
45
+ s.sendall(
46
+ c.send( # type: ignore[arg-type]
47
+ h11.Request(
48
+ method="GET", target="/foo", headers=[("Host", "localhost")]
49
+ )
50
+ )
51
+ )
52
+ s.sendall(c.send(h11.EndOfMessage())) # type: ignore[arg-type]
53
+
54
+ data = bytearray()
55
+ while True:
56
+ event = c.next_event()
57
+ print(event)
58
+ if event is h11.NEED_DATA:
59
+ # Use a small read buffer to make things more challenging
60
+ # and exercise more paths :-)
61
+ c.receive_data(s.recv(10))
62
+ continue
63
+ if type(event) is h11.Response:
64
+ assert event.status_code == 200
65
+ if type(event) is h11.Data:
66
+ data += event.data
67
+ if type(event) is h11.EndOfMessage:
68
+ break
69
+ assert bytes(data) == test_file_data
70
+
71
+
72
+ class H11RequestHandler(socketserver.BaseRequestHandler):
73
+ def handle(self) -> None:
74
+ with closing(self.request) as s:
75
+ c = h11.Connection(h11.SERVER)
76
+ request = None
77
+ while True:
78
+ event = c.next_event()
79
+ if event is h11.NEED_DATA:
80
+ # Use a small read buffer to make things more challenging
81
+ # and exercise more paths :-)
82
+ c.receive_data(s.recv(10))
83
+ continue
84
+ if type(event) is h11.Request:
85
+ request = event
86
+ if type(event) is h11.EndOfMessage:
87
+ break
88
+ assert request is not None
89
+ info = json.dumps(
90
+ {
91
+ "method": request.method.decode("ascii"),
92
+ "target": request.target.decode("ascii"),
93
+ "headers": {
94
+ name.decode("ascii"): value.decode("ascii")
95
+ for (name, value) in request.headers
96
+ },
97
+ }
98
+ )
99
+ s.sendall(c.send(h11.Response(status_code=200, headers=[]))) # type: ignore[arg-type]
100
+ s.sendall(c.send(h11.Data(data=info.encode("ascii"))))
101
+ s.sendall(c.send(h11.EndOfMessage()))
102
+
103
+
104
+ def test_h11_as_server() -> None:
105
+ with socket_server(H11RequestHandler) as httpd:
106
+ host, port = httpd.server_address
107
+ url = "http://{}:{}/some-path".format(host, port)
108
+ with closing(urlopen(url)) as f:
109
+ assert f.getcode() == 200
110
+ data = f.read()
111
+ info = json.loads(data.decode("ascii"))
112
+ print(info)
113
+ assert info["method"] == "GET"
114
+ assert info["target"] == "/some-path"
115
+ assert "urllib" in info["headers"]["user-agent"]
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_connection.py ADDED
@@ -0,0 +1,1122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, cast, Dict, List, Optional, Tuple, Type
2
+
3
+ import pytest
4
+
5
+ from .._connection import _body_framing, _keep_alive, Connection, NEED_DATA, PAUSED
6
+ from .._events import (
7
+ ConnectionClosed,
8
+ Data,
9
+ EndOfMessage,
10
+ Event,
11
+ InformationalResponse,
12
+ Request,
13
+ Response,
14
+ )
15
+ from .._state import (
16
+ CLIENT,
17
+ CLOSED,
18
+ DONE,
19
+ ERROR,
20
+ IDLE,
21
+ MIGHT_SWITCH_PROTOCOL,
22
+ MUST_CLOSE,
23
+ SEND_BODY,
24
+ SEND_RESPONSE,
25
+ SERVER,
26
+ SWITCHED_PROTOCOL,
27
+ )
28
+ from .._util import LocalProtocolError, RemoteProtocolError, Sentinel
29
+ from .helpers import ConnectionPair, get_all_events, receive_and_get
30
+
31
+
32
+ def test__keep_alive() -> None:
33
+ assert _keep_alive(
34
+ Request(method="GET", target="/", headers=[("Host", "Example.com")])
35
+ )
36
+ assert not _keep_alive(
37
+ Request(
38
+ method="GET",
39
+ target="/",
40
+ headers=[("Host", "Example.com"), ("Connection", "close")],
41
+ )
42
+ )
43
+ assert not _keep_alive(
44
+ Request(
45
+ method="GET",
46
+ target="/",
47
+ headers=[("Host", "Example.com"), ("Connection", "a, b, cLOse, foo")],
48
+ )
49
+ )
50
+ assert not _keep_alive(
51
+ Request(method="GET", target="/", headers=[], http_version="1.0") # type: ignore[arg-type]
52
+ )
53
+
54
+ assert _keep_alive(Response(status_code=200, headers=[])) # type: ignore[arg-type]
55
+ assert not _keep_alive(Response(status_code=200, headers=[("Connection", "close")]))
56
+ assert not _keep_alive(
57
+ Response(status_code=200, headers=[("Connection", "a, b, cLOse, foo")])
58
+ )
59
+ assert not _keep_alive(Response(status_code=200, headers=[], http_version="1.0")) # type: ignore[arg-type]
60
+
61
+
62
+ def test__body_framing() -> None:
63
+ def headers(cl: Optional[int], te: bool) -> List[Tuple[str, str]]:
64
+ headers = []
65
+ if cl is not None:
66
+ headers.append(("Content-Length", str(cl)))
67
+ if te:
68
+ headers.append(("Transfer-Encoding", "chunked"))
69
+ return headers
70
+
71
+ def resp(
72
+ status_code: int = 200, cl: Optional[int] = None, te: bool = False
73
+ ) -> Response:
74
+ return Response(status_code=status_code, headers=headers(cl, te))
75
+
76
+ def req(cl: Optional[int] = None, te: bool = False) -> Request:
77
+ h = headers(cl, te)
78
+ h += [("Host", "example.com")]
79
+ return Request(method="GET", target="/", headers=h)
80
+
81
+ # Special cases where the headers are ignored:
82
+ for kwargs in [{}, {"cl": 100}, {"te": True}, {"cl": 100, "te": True}]:
83
+ kwargs = cast(Dict[str, Any], kwargs)
84
+ for meth, r in [
85
+ (b"HEAD", resp(**kwargs)),
86
+ (b"GET", resp(status_code=204, **kwargs)),
87
+ (b"GET", resp(status_code=304, **kwargs)),
88
+ ]:
89
+ assert _body_framing(meth, r) == ("content-length", (0,))
90
+
91
+ # Transfer-encoding
92
+ for kwargs in [{"te": True}, {"cl": 100, "te": True}]:
93
+ kwargs = cast(Dict[str, Any], kwargs)
94
+ for meth, r in [(None, req(**kwargs)), (b"GET", resp(**kwargs))]: # type: ignore
95
+ assert _body_framing(meth, r) == ("chunked", ())
96
+
97
+ # Content-Length
98
+ for meth, r in [(None, req(cl=100)), (b"GET", resp(cl=100))]: # type: ignore
99
+ assert _body_framing(meth, r) == ("content-length", (100,))
100
+
101
+ # No headers
102
+ assert _body_framing(None, req()) == ("content-length", (0,)) # type: ignore
103
+ assert _body_framing(b"GET", resp()) == ("http/1.0", ())
104
+
105
+
106
+ def test_Connection_basics_and_content_length() -> None:
107
+ with pytest.raises(ValueError):
108
+ Connection("CLIENT") # type: ignore
109
+
110
+ p = ConnectionPair()
111
+ assert p.conn[CLIENT].our_role is CLIENT
112
+ assert p.conn[CLIENT].their_role is SERVER
113
+ assert p.conn[SERVER].our_role is SERVER
114
+ assert p.conn[SERVER].their_role is CLIENT
115
+
116
+ data = p.send(
117
+ CLIENT,
118
+ Request(
119
+ method="GET",
120
+ target="/",
121
+ headers=[("Host", "example.com"), ("Content-Length", "10")],
122
+ ),
123
+ )
124
+ assert data == (
125
+ b"GET / HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 10\r\n\r\n"
126
+ )
127
+
128
+ for conn in p.conns:
129
+ assert conn.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
130
+ assert p.conn[CLIENT].our_state is SEND_BODY
131
+ assert p.conn[CLIENT].their_state is SEND_RESPONSE
132
+ assert p.conn[SERVER].our_state is SEND_RESPONSE
133
+ assert p.conn[SERVER].their_state is SEND_BODY
134
+
135
+ assert p.conn[CLIENT].their_http_version is None
136
+ assert p.conn[SERVER].their_http_version == b"1.1"
137
+
138
+ data = p.send(SERVER, InformationalResponse(status_code=100, headers=[])) # type: ignore[arg-type]
139
+ assert data == b"HTTP/1.1 100 \r\n\r\n"
140
+
141
+ data = p.send(SERVER, Response(status_code=200, headers=[("Content-Length", "11")]))
142
+ assert data == b"HTTP/1.1 200 \r\nContent-Length: 11\r\n\r\n"
143
+
144
+ for conn in p.conns:
145
+ assert conn.states == {CLIENT: SEND_BODY, SERVER: SEND_BODY}
146
+
147
+ assert p.conn[CLIENT].their_http_version == b"1.1"
148
+ assert p.conn[SERVER].their_http_version == b"1.1"
149
+
150
+ data = p.send(CLIENT, Data(data=b"12345"))
151
+ assert data == b"12345"
152
+ data = p.send(
153
+ CLIENT, Data(data=b"67890"), expect=[Data(data=b"67890"), EndOfMessage()]
154
+ )
155
+ assert data == b"67890"
156
+ data = p.send(CLIENT, EndOfMessage(), expect=[])
157
+ assert data == b""
158
+
159
+ for conn in p.conns:
160
+ assert conn.states == {CLIENT: DONE, SERVER: SEND_BODY}
161
+
162
+ data = p.send(SERVER, Data(data=b"1234567890"))
163
+ assert data == b"1234567890"
164
+ data = p.send(SERVER, Data(data=b"1"), expect=[Data(data=b"1"), EndOfMessage()])
165
+ assert data == b"1"
166
+ data = p.send(SERVER, EndOfMessage(), expect=[])
167
+ assert data == b""
168
+
169
+ for conn in p.conns:
170
+ assert conn.states == {CLIENT: DONE, SERVER: DONE}
171
+
172
+
173
+ def test_chunked() -> None:
174
+ p = ConnectionPair()
175
+
176
+ p.send(
177
+ CLIENT,
178
+ Request(
179
+ method="GET",
180
+ target="/",
181
+ headers=[("Host", "example.com"), ("Transfer-Encoding", "chunked")],
182
+ ),
183
+ )
184
+ data = p.send(CLIENT, Data(data=b"1234567890", chunk_start=True, chunk_end=True))
185
+ assert data == b"a\r\n1234567890\r\n"
186
+ data = p.send(CLIENT, Data(data=b"abcde", chunk_start=True, chunk_end=True))
187
+ assert data == b"5\r\nabcde\r\n"
188
+ data = p.send(CLIENT, Data(data=b""), expect=[])
189
+ assert data == b""
190
+ data = p.send(CLIENT, EndOfMessage(headers=[("hello", "there")]))
191
+ assert data == b"0\r\nhello: there\r\n\r\n"
192
+
193
+ p.send(
194
+ SERVER, Response(status_code=200, headers=[("Transfer-Encoding", "chunked")])
195
+ )
196
+ p.send(SERVER, Data(data=b"54321", chunk_start=True, chunk_end=True))
197
+ p.send(SERVER, Data(data=b"12345", chunk_start=True, chunk_end=True))
198
+ p.send(SERVER, EndOfMessage())
199
+
200
+ for conn in p.conns:
201
+ assert conn.states == {CLIENT: DONE, SERVER: DONE}
202
+
203
+
204
+ def test_chunk_boundaries() -> None:
205
+ conn = Connection(our_role=SERVER)
206
+
207
+ request = (
208
+ b"POST / HTTP/1.1\r\n"
209
+ b"Host: example.com\r\n"
210
+ b"Transfer-Encoding: chunked\r\n"
211
+ b"\r\n"
212
+ )
213
+ conn.receive_data(request)
214
+ assert conn.next_event() == Request(
215
+ method="POST",
216
+ target="/",
217
+ headers=[("Host", "example.com"), ("Transfer-Encoding", "chunked")],
218
+ )
219
+ assert conn.next_event() is NEED_DATA
220
+
221
+ conn.receive_data(b"5\r\nhello\r\n")
222
+ assert conn.next_event() == Data(data=b"hello", chunk_start=True, chunk_end=True)
223
+
224
+ conn.receive_data(b"5\r\nhel")
225
+ assert conn.next_event() == Data(data=b"hel", chunk_start=True, chunk_end=False)
226
+
227
+ conn.receive_data(b"l")
228
+ assert conn.next_event() == Data(data=b"l", chunk_start=False, chunk_end=False)
229
+
230
+ conn.receive_data(b"o\r\n")
231
+ assert conn.next_event() == Data(data=b"o", chunk_start=False, chunk_end=True)
232
+
233
+ conn.receive_data(b"5\r\nhello")
234
+ assert conn.next_event() == Data(data=b"hello", chunk_start=True, chunk_end=True)
235
+
236
+ conn.receive_data(b"\r\n")
237
+ assert conn.next_event() == NEED_DATA
238
+
239
+ conn.receive_data(b"0\r\n\r\n")
240
+ assert conn.next_event() == EndOfMessage()
241
+
242
+
243
+ def test_client_talking_to_http10_server() -> None:
244
+ c = Connection(CLIENT)
245
+ c.send(Request(method="GET", target="/", headers=[("Host", "example.com")]))
246
+ c.send(EndOfMessage())
247
+ assert c.our_state is DONE
248
+ # No content-length, so Http10 framing for body
249
+ assert receive_and_get(c, b"HTTP/1.0 200 OK\r\n\r\n") == [
250
+ Response(status_code=200, headers=[], http_version="1.0", reason=b"OK") # type: ignore[arg-type]
251
+ ]
252
+ assert c.our_state is MUST_CLOSE
253
+ assert receive_and_get(c, b"12345") == [Data(data=b"12345")]
254
+ assert receive_and_get(c, b"67890") == [Data(data=b"67890")]
255
+ assert receive_and_get(c, b"") == [EndOfMessage(), ConnectionClosed()]
256
+ assert c.their_state is CLOSED
257
+
258
+
259
+ def test_server_talking_to_http10_client() -> None:
260
+ c = Connection(SERVER)
261
+ # No content-length, so no body
262
+ # NB: no host header
263
+ assert receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n") == [
264
+ Request(method="GET", target="/", headers=[], http_version="1.0"), # type: ignore[arg-type]
265
+ EndOfMessage(),
266
+ ]
267
+ assert c.their_state is MUST_CLOSE
268
+
269
+ # We automatically Connection: close back at them
270
+ assert (
271
+ c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
272
+ == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n"
273
+ )
274
+
275
+ assert c.send(Data(data=b"12345")) == b"12345"
276
+ assert c.send(EndOfMessage()) == b""
277
+ assert c.our_state is MUST_CLOSE
278
+
279
+ # Check that it works if they do send Content-Length
280
+ c = Connection(SERVER)
281
+ # NB: no host header
282
+ assert receive_and_get(c, b"POST / HTTP/1.0\r\nContent-Length: 10\r\n\r\n1") == [
283
+ Request(
284
+ method="POST",
285
+ target="/",
286
+ headers=[("Content-Length", "10")],
287
+ http_version="1.0",
288
+ ),
289
+ Data(data=b"1"),
290
+ ]
291
+ assert receive_and_get(c, b"234567890") == [Data(data=b"234567890"), EndOfMessage()]
292
+ assert c.their_state is MUST_CLOSE
293
+ assert receive_and_get(c, b"") == [ConnectionClosed()]
294
+
295
+
296
+ def test_automatic_transfer_encoding_in_response() -> None:
297
+ # Check that in responses, the user can specify either Transfer-Encoding:
298
+ # chunked or no framing at all, and in both cases we automatically select
299
+ # the right option depending on whether the peer speaks HTTP/1.0 or
300
+ # HTTP/1.1
301
+ for user_headers in [
302
+ [("Transfer-Encoding", "chunked")],
303
+ [],
304
+ # In fact, this even works if Content-Length is set,
305
+ # because if both are set then Transfer-Encoding wins
306
+ [("Transfer-Encoding", "chunked"), ("Content-Length", "100")],
307
+ ]:
308
+ user_headers = cast(List[Tuple[str, str]], user_headers)
309
+ p = ConnectionPair()
310
+ p.send(
311
+ CLIENT,
312
+ [
313
+ Request(method="GET", target="/", headers=[("Host", "example.com")]),
314
+ EndOfMessage(),
315
+ ],
316
+ )
317
+ # When speaking to HTTP/1.1 client, all of the above cases get
318
+ # normalized to Transfer-Encoding: chunked
319
+ p.send(
320
+ SERVER,
321
+ Response(status_code=200, headers=user_headers),
322
+ expect=Response(
323
+ status_code=200, headers=[("Transfer-Encoding", "chunked")]
324
+ ),
325
+ )
326
+
327
+ # When speaking to HTTP/1.0 client, all of the above cases get
328
+ # normalized to no-framing-headers
329
+ c = Connection(SERVER)
330
+ receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n")
331
+ assert (
332
+ c.send(Response(status_code=200, headers=user_headers))
333
+ == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n"
334
+ )
335
+ assert c.send(Data(data=b"12345")) == b"12345"
336
+
337
+
338
+ def test_automagic_connection_close_handling() -> None:
339
+ p = ConnectionPair()
340
+ # If the user explicitly sets Connection: close, then we notice and
341
+ # respect it
342
+ p.send(
343
+ CLIENT,
344
+ [
345
+ Request(
346
+ method="GET",
347
+ target="/",
348
+ headers=[("Host", "example.com"), ("Connection", "close")],
349
+ ),
350
+ EndOfMessage(),
351
+ ],
352
+ )
353
+ for conn in p.conns:
354
+ assert conn.states[CLIENT] is MUST_CLOSE
355
+ # And if the client sets it, the server automatically echoes it back
356
+ p.send(
357
+ SERVER,
358
+ # no header here...
359
+ [Response(status_code=204, headers=[]), EndOfMessage()], # type: ignore[arg-type]
360
+ # ...but oh look, it arrived anyway
361
+ expect=[
362
+ Response(status_code=204, headers=[("connection", "close")]),
363
+ EndOfMessage(),
364
+ ],
365
+ )
366
+ for conn in p.conns:
367
+ assert conn.states == {CLIENT: MUST_CLOSE, SERVER: MUST_CLOSE}
368
+
369
+
370
+ def test_100_continue() -> None:
371
+ def setup() -> ConnectionPair:
372
+ p = ConnectionPair()
373
+ p.send(
374
+ CLIENT,
375
+ Request(
376
+ method="GET",
377
+ target="/",
378
+ headers=[
379
+ ("Host", "example.com"),
380
+ ("Content-Length", "100"),
381
+ ("Expect", "100-continue"),
382
+ ],
383
+ ),
384
+ )
385
+ for conn in p.conns:
386
+ assert conn.client_is_waiting_for_100_continue
387
+ assert not p.conn[CLIENT].they_are_waiting_for_100_continue
388
+ assert p.conn[SERVER].they_are_waiting_for_100_continue
389
+ return p
390
+
391
+ # Disabled by 100 Continue
392
+ p = setup()
393
+ p.send(SERVER, InformationalResponse(status_code=100, headers=[])) # type: ignore[arg-type]
394
+ for conn in p.conns:
395
+ assert not conn.client_is_waiting_for_100_continue
396
+ assert not conn.they_are_waiting_for_100_continue
397
+
398
+ # Disabled by a real response
399
+ p = setup()
400
+ p.send(
401
+ SERVER, Response(status_code=200, headers=[("Transfer-Encoding", "chunked")])
402
+ )
403
+ for conn in p.conns:
404
+ assert not conn.client_is_waiting_for_100_continue
405
+ assert not conn.they_are_waiting_for_100_continue
406
+
407
+ # Disabled by the client going ahead and sending stuff anyway
408
+ p = setup()
409
+ p.send(CLIENT, Data(data=b"12345"))
410
+ for conn in p.conns:
411
+ assert not conn.client_is_waiting_for_100_continue
412
+ assert not conn.they_are_waiting_for_100_continue
413
+
414
+
415
+ def test_max_incomplete_event_size_countermeasure() -> None:
416
+ # Infinitely long headers are definitely not okay
417
+ c = Connection(SERVER)
418
+ c.receive_data(b"GET / HTTP/1.0\r\nEndless: ")
419
+ assert c.next_event() is NEED_DATA
420
+ with pytest.raises(RemoteProtocolError):
421
+ while True:
422
+ c.receive_data(b"a" * 1024)
423
+ c.next_event()
424
+
425
+ # Checking that the same header is accepted / rejected depending on the
426
+ # max_incomplete_event_size setting:
427
+ c = Connection(SERVER, max_incomplete_event_size=5000)
428
+ c.receive_data(b"GET / HTTP/1.0\r\nBig: ")
429
+ c.receive_data(b"a" * 4000)
430
+ c.receive_data(b"\r\n\r\n")
431
+ assert get_all_events(c) == [
432
+ Request(
433
+ method="GET", target="/", http_version="1.0", headers=[("big", "a" * 4000)]
434
+ ),
435
+ EndOfMessage(),
436
+ ]
437
+
438
+ c = Connection(SERVER, max_incomplete_event_size=4000)
439
+ c.receive_data(b"GET / HTTP/1.0\r\nBig: ")
440
+ c.receive_data(b"a" * 4000)
441
+ with pytest.raises(RemoteProtocolError):
442
+ c.next_event()
443
+
444
+ # Temporarily exceeding the size limit is fine, as long as its done with
445
+ # complete events:
446
+ c = Connection(SERVER, max_incomplete_event_size=5000)
447
+ c.receive_data(b"GET / HTTP/1.0\r\nContent-Length: 10000")
448
+ c.receive_data(b"\r\n\r\n" + b"a" * 10000)
449
+ assert get_all_events(c) == [
450
+ Request(
451
+ method="GET",
452
+ target="/",
453
+ http_version="1.0",
454
+ headers=[("Content-Length", "10000")],
455
+ ),
456
+ Data(data=b"a" * 10000),
457
+ EndOfMessage(),
458
+ ]
459
+
460
+ c = Connection(SERVER, max_incomplete_event_size=100)
461
+ # Two pipelined requests to create a way-too-big receive buffer... but
462
+ # it's fine because we're not checking
463
+ c.receive_data(
464
+ b"GET /1 HTTP/1.1\r\nHost: a\r\n\r\n"
465
+ b"GET /2 HTTP/1.1\r\nHost: b\r\n\r\n" + b"X" * 1000
466
+ )
467
+ assert get_all_events(c) == [
468
+ Request(method="GET", target="/1", headers=[("host", "a")]),
469
+ EndOfMessage(),
470
+ ]
471
+ # Even more data comes in, still no problem
472
+ c.receive_data(b"X" * 1000)
473
+ # We can respond and reuse to get the second pipelined request
474
+ c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
475
+ c.send(EndOfMessage())
476
+ c.start_next_cycle()
477
+ assert get_all_events(c) == [
478
+ Request(method="GET", target="/2", headers=[("host", "b")]),
479
+ EndOfMessage(),
480
+ ]
481
+ # But once we unpause and try to read the next message, and find that it's
482
+ # incomplete and the buffer is *still* way too large, then *that's* a
483
+ # problem:
484
+ c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
485
+ c.send(EndOfMessage())
486
+ c.start_next_cycle()
487
+ with pytest.raises(RemoteProtocolError):
488
+ c.next_event()
489
+
490
+
491
+ def test_reuse_simple() -> None:
492
+ p = ConnectionPair()
493
+ p.send(
494
+ CLIENT,
495
+ [Request(method="GET", target="/", headers=[("Host", "a")]), EndOfMessage()],
496
+ )
497
+ p.send(
498
+ SERVER,
499
+ [
500
+ Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
501
+ EndOfMessage(),
502
+ ],
503
+ )
504
+ for conn in p.conns:
505
+ assert conn.states == {CLIENT: DONE, SERVER: DONE}
506
+ conn.start_next_cycle()
507
+
508
+ p.send(
509
+ CLIENT,
510
+ [
511
+ Request(method="DELETE", target="/foo", headers=[("Host", "a")]),
512
+ EndOfMessage(),
513
+ ],
514
+ )
515
+ p.send(
516
+ SERVER,
517
+ [
518
+ Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]),
519
+ EndOfMessage(),
520
+ ],
521
+ )
522
+
523
+
524
+ def test_pipelining() -> None:
525
+ # Client doesn't support pipelining, so we have to do this by hand
526
+ c = Connection(SERVER)
527
+ assert c.next_event() is NEED_DATA
528
+ # 3 requests all bunched up
529
+ c.receive_data(
530
+ b"GET /1 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n"
531
+ b"12345"
532
+ b"GET /2 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n"
533
+ b"67890"
534
+ b"GET /3 HTTP/1.1\r\nHost: a.com\r\n\r\n"
535
+ )
536
+ assert get_all_events(c) == [
537
+ Request(
538
+ method="GET",
539
+ target="/1",
540
+ headers=[("Host", "a.com"), ("Content-Length", "5")],
541
+ ),
542
+ Data(data=b"12345"),
543
+ EndOfMessage(),
544
+ ]
545
+ assert c.their_state is DONE
546
+ assert c.our_state is SEND_RESPONSE
547
+
548
+ assert c.next_event() is PAUSED
549
+
550
+ c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
551
+ c.send(EndOfMessage())
552
+ assert c.their_state is DONE
553
+ assert c.our_state is DONE
554
+
555
+ c.start_next_cycle()
556
+
557
+ assert get_all_events(c) == [
558
+ Request(
559
+ method="GET",
560
+ target="/2",
561
+ headers=[("Host", "a.com"), ("Content-Length", "5")],
562
+ ),
563
+ Data(data=b"67890"),
564
+ EndOfMessage(),
565
+ ]
566
+ assert c.next_event() is PAUSED
567
+ c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
568
+ c.send(EndOfMessage())
569
+ c.start_next_cycle()
570
+
571
+ assert get_all_events(c) == [
572
+ Request(method="GET", target="/3", headers=[("Host", "a.com")]),
573
+ EndOfMessage(),
574
+ ]
575
+ # Doesn't pause this time, no trailing data
576
+ assert c.next_event() is NEED_DATA
577
+ c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
578
+ c.send(EndOfMessage())
579
+
580
+ # Arrival of more data triggers pause
581
+ assert c.next_event() is NEED_DATA
582
+ c.receive_data(b"SADF")
583
+ assert c.next_event() is PAUSED
584
+ assert c.trailing_data == (b"SADF", False)
585
+ # If EOF arrives while paused, we don't see that either:
586
+ c.receive_data(b"")
587
+ assert c.trailing_data == (b"SADF", True)
588
+ assert c.next_event() is PAUSED
589
+ c.receive_data(b"")
590
+ assert c.next_event() is PAUSED
591
+ # Can't call receive_data with non-empty buf after closing it
592
+ with pytest.raises(RuntimeError):
593
+ c.receive_data(b"FDSA")
594
+
595
+
596
+ def test_protocol_switch() -> None:
597
+ for (req, deny, accept) in [
598
+ (
599
+ Request(
600
+ method="CONNECT",
601
+ target="example.com:443",
602
+ headers=[("Host", "foo"), ("Content-Length", "1")],
603
+ ),
604
+ Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]),
605
+ Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
606
+ ),
607
+ (
608
+ Request(
609
+ method="GET",
610
+ target="/",
611
+ headers=[("Host", "foo"), ("Content-Length", "1"), ("Upgrade", "a, b")],
612
+ ),
613
+ Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
614
+ InformationalResponse(status_code=101, headers=[("Upgrade", "a")]),
615
+ ),
616
+ (
617
+ Request(
618
+ method="CONNECT",
619
+ target="example.com:443",
620
+ headers=[("Host", "foo"), ("Content-Length", "1"), ("Upgrade", "a, b")],
621
+ ),
622
+ Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]),
623
+ # Accept CONNECT, not upgrade
624
+ Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
625
+ ),
626
+ (
627
+ Request(
628
+ method="CONNECT",
629
+ target="example.com:443",
630
+ headers=[("Host", "foo"), ("Content-Length", "1"), ("Upgrade", "a, b")],
631
+ ),
632
+ Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]),
633
+ # Accept Upgrade, not CONNECT
634
+ InformationalResponse(status_code=101, headers=[("Upgrade", "b")]),
635
+ ),
636
+ ]:
637
+
638
+ def setup() -> ConnectionPair:
639
+ p = ConnectionPair()
640
+ p.send(CLIENT, req)
641
+ # No switch-related state change stuff yet; the client has to
642
+ # finish the request before that kicks in
643
+ for conn in p.conns:
644
+ assert conn.states[CLIENT] is SEND_BODY
645
+ p.send(CLIENT, [Data(data=b"1"), EndOfMessage()])
646
+ for conn in p.conns:
647
+ assert conn.states[CLIENT] is MIGHT_SWITCH_PROTOCOL
648
+ assert p.conn[SERVER].next_event() is PAUSED
649
+ return p
650
+
651
+ # Test deny case
652
+ p = setup()
653
+ p.send(SERVER, deny)
654
+ for conn in p.conns:
655
+ assert conn.states == {CLIENT: DONE, SERVER: SEND_BODY}
656
+ p.send(SERVER, EndOfMessage())
657
+ # Check that re-use is still allowed after a denial
658
+ for conn in p.conns:
659
+ conn.start_next_cycle()
660
+
661
+ # Test accept case
662
+ p = setup()
663
+ p.send(SERVER, accept)
664
+ for conn in p.conns:
665
+ assert conn.states == {CLIENT: SWITCHED_PROTOCOL, SERVER: SWITCHED_PROTOCOL}
666
+ conn.receive_data(b"123")
667
+ assert conn.next_event() is PAUSED
668
+ conn.receive_data(b"456")
669
+ assert conn.next_event() is PAUSED
670
+ assert conn.trailing_data == (b"123456", False)
671
+
672
+ # Pausing in might-switch, then recovery
673
+ # (weird artificial case where the trailing data actually is valid
674
+ # HTTP for some reason, because this makes it easier to test the state
675
+ # logic)
676
+ p = setup()
677
+ sc = p.conn[SERVER]
678
+ sc.receive_data(b"GET / HTTP/1.0\r\n\r\n")
679
+ assert sc.next_event() is PAUSED
680
+ assert sc.trailing_data == (b"GET / HTTP/1.0\r\n\r\n", False)
681
+ sc.send(deny)
682
+ assert sc.next_event() is PAUSED
683
+ sc.send(EndOfMessage())
684
+ sc.start_next_cycle()
685
+ assert get_all_events(sc) == [
686
+ Request(method="GET", target="/", headers=[], http_version="1.0"), # type: ignore[arg-type]
687
+ EndOfMessage(),
688
+ ]
689
+
690
+ # When we're DONE, have no trailing data, and the connection gets
691
+ # closed, we report ConnectionClosed(). When we're in might-switch or
692
+ # switched, we don't.
693
+ p = setup()
694
+ sc = p.conn[SERVER]
695
+ sc.receive_data(b"")
696
+ assert sc.next_event() is PAUSED
697
+ assert sc.trailing_data == (b"", True)
698
+ p.send(SERVER, accept)
699
+ assert sc.next_event() is PAUSED
700
+
701
+ p = setup()
702
+ sc = p.conn[SERVER]
703
+ sc.receive_data(b"")
704
+ assert sc.next_event() is PAUSED
705
+ sc.send(deny)
706
+ assert sc.next_event() == ConnectionClosed()
707
+
708
+ # You can't send after switching protocols, or while waiting for a
709
+ # protocol switch
710
+ p = setup()
711
+ with pytest.raises(LocalProtocolError):
712
+ p.conn[CLIENT].send(
713
+ Request(method="GET", target="/", headers=[("Host", "a")])
714
+ )
715
+ p = setup()
716
+ p.send(SERVER, accept)
717
+ with pytest.raises(LocalProtocolError):
718
+ p.conn[SERVER].send(Data(data=b"123"))
719
+
720
+
721
+ def test_close_simple() -> None:
722
+ # Just immediately closing a new connection without anything having
723
+ # happened yet.
724
+ for (who_shot_first, who_shot_second) in [(CLIENT, SERVER), (SERVER, CLIENT)]:
725
+
726
+ def setup() -> ConnectionPair:
727
+ p = ConnectionPair()
728
+ p.send(who_shot_first, ConnectionClosed())
729
+ for conn in p.conns:
730
+ assert conn.states == {
731
+ who_shot_first: CLOSED,
732
+ who_shot_second: MUST_CLOSE,
733
+ }
734
+ return p
735
+
736
+ # You can keep putting b"" into a closed connection, and you keep
737
+ # getting ConnectionClosed() out:
738
+ p = setup()
739
+ assert p.conn[who_shot_second].next_event() == ConnectionClosed()
740
+ assert p.conn[who_shot_second].next_event() == ConnectionClosed()
741
+ p.conn[who_shot_second].receive_data(b"")
742
+ assert p.conn[who_shot_second].next_event() == ConnectionClosed()
743
+ # Second party can close...
744
+ p = setup()
745
+ p.send(who_shot_second, ConnectionClosed())
746
+ for conn in p.conns:
747
+ assert conn.our_state is CLOSED
748
+ assert conn.their_state is CLOSED
749
+ # But trying to receive new data on a closed connection is a
750
+ # RuntimeError (not ProtocolError, because the problem here isn't
751
+ # violation of HTTP, it's violation of physics)
752
+ p = setup()
753
+ with pytest.raises(RuntimeError):
754
+ p.conn[who_shot_second].receive_data(b"123")
755
+ # And receiving new data on a MUST_CLOSE connection is a ProtocolError
756
+ p = setup()
757
+ p.conn[who_shot_first].receive_data(b"GET")
758
+ with pytest.raises(RemoteProtocolError):
759
+ p.conn[who_shot_first].next_event()
760
+
761
+
762
+ def test_close_different_states() -> None:
763
+ req = [
764
+ Request(method="GET", target="/foo", headers=[("Host", "a")]),
765
+ EndOfMessage(),
766
+ ]
767
+ resp = [
768
+ Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
769
+ EndOfMessage(),
770
+ ]
771
+
772
+ # Client before request
773
+ p = ConnectionPair()
774
+ p.send(CLIENT, ConnectionClosed())
775
+ for conn in p.conns:
776
+ assert conn.states == {CLIENT: CLOSED, SERVER: MUST_CLOSE}
777
+
778
+ # Client after request
779
+ p = ConnectionPair()
780
+ p.send(CLIENT, req)
781
+ p.send(CLIENT, ConnectionClosed())
782
+ for conn in p.conns:
783
+ assert conn.states == {CLIENT: CLOSED, SERVER: SEND_RESPONSE}
784
+
785
+ # Server after request -> not allowed
786
+ p = ConnectionPair()
787
+ p.send(CLIENT, req)
788
+ with pytest.raises(LocalProtocolError):
789
+ p.conn[SERVER].send(ConnectionClosed())
790
+ p.conn[CLIENT].receive_data(b"")
791
+ with pytest.raises(RemoteProtocolError):
792
+ p.conn[CLIENT].next_event()
793
+
794
+ # Server after response
795
+ p = ConnectionPair()
796
+ p.send(CLIENT, req)
797
+ p.send(SERVER, resp)
798
+ p.send(SERVER, ConnectionClosed())
799
+ for conn in p.conns:
800
+ assert conn.states == {CLIENT: MUST_CLOSE, SERVER: CLOSED}
801
+
802
+ # Both after closing (ConnectionClosed() is idempotent)
803
+ p = ConnectionPair()
804
+ p.send(CLIENT, req)
805
+ p.send(SERVER, resp)
806
+ p.send(CLIENT, ConnectionClosed())
807
+ p.send(SERVER, ConnectionClosed())
808
+ p.send(CLIENT, ConnectionClosed())
809
+ p.send(SERVER, ConnectionClosed())
810
+
811
+ # In the middle of sending -> not allowed
812
+ p = ConnectionPair()
813
+ p.send(
814
+ CLIENT,
815
+ Request(
816
+ method="GET", target="/", headers=[("Host", "a"), ("Content-Length", "10")]
817
+ ),
818
+ )
819
+ with pytest.raises(LocalProtocolError):
820
+ p.conn[CLIENT].send(ConnectionClosed())
821
+ p.conn[SERVER].receive_data(b"")
822
+ with pytest.raises(RemoteProtocolError):
823
+ p.conn[SERVER].next_event()
824
+
825
+
826
+ # Receive several requests and then client shuts down their side of the
827
+ # connection; we can respond to each
828
+ def test_pipelined_close() -> None:
829
+ c = Connection(SERVER)
830
+ # 2 requests then a close
831
+ c.receive_data(
832
+ b"GET /1 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n"
833
+ b"12345"
834
+ b"GET /2 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n"
835
+ b"67890"
836
+ )
837
+ c.receive_data(b"")
838
+ assert get_all_events(c) == [
839
+ Request(
840
+ method="GET",
841
+ target="/1",
842
+ headers=[("host", "a.com"), ("content-length", "5")],
843
+ ),
844
+ Data(data=b"12345"),
845
+ EndOfMessage(),
846
+ ]
847
+ assert c.states[CLIENT] is DONE
848
+ c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
849
+ c.send(EndOfMessage())
850
+ assert c.states[SERVER] is DONE
851
+ c.start_next_cycle()
852
+ assert get_all_events(c) == [
853
+ Request(
854
+ method="GET",
855
+ target="/2",
856
+ headers=[("host", "a.com"), ("content-length", "5")],
857
+ ),
858
+ Data(data=b"67890"),
859
+ EndOfMessage(),
860
+ ConnectionClosed(),
861
+ ]
862
+ assert c.states == {CLIENT: CLOSED, SERVER: SEND_RESPONSE}
863
+ c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
864
+ c.send(EndOfMessage())
865
+ assert c.states == {CLIENT: CLOSED, SERVER: MUST_CLOSE}
866
+ c.send(ConnectionClosed())
867
+ assert c.states == {CLIENT: CLOSED, SERVER: CLOSED}
868
+
869
+
870
+ def test_sendfile() -> None:
871
+ class SendfilePlaceholder:
872
+ def __len__(self) -> int:
873
+ return 10
874
+
875
+ placeholder = SendfilePlaceholder()
876
+
877
+ def setup(
878
+ header: Tuple[str, str], http_version: str
879
+ ) -> Tuple[Connection, Optional[List[bytes]]]:
880
+ c = Connection(SERVER)
881
+ receive_and_get(
882
+ c, "GET / HTTP/{}\r\nHost: a\r\n\r\n".format(http_version).encode("ascii")
883
+ )
884
+ headers = []
885
+ if header:
886
+ headers.append(header)
887
+ c.send(Response(status_code=200, headers=headers))
888
+ return c, c.send_with_data_passthrough(Data(data=placeholder)) # type: ignore
889
+
890
+ c, data = setup(("Content-Length", "10"), "1.1")
891
+ assert data == [placeholder] # type: ignore
892
+ # Raises an error if the connection object doesn't think we've sent
893
+ # exactly 10 bytes
894
+ c.send(EndOfMessage())
895
+
896
+ _, data = setup(("Transfer-Encoding", "chunked"), "1.1")
897
+ assert placeholder in data # type: ignore
898
+ data[data.index(placeholder)] = b"x" * 10 # type: ignore
899
+ assert b"".join(data) == b"a\r\nxxxxxxxxxx\r\n" # type: ignore
900
+
901
+ c, data = setup(None, "1.0") # type: ignore
902
+ assert data == [placeholder] # type: ignore
903
+ assert c.our_state is SEND_BODY
904
+
905
+
906
+ def test_errors() -> None:
907
+ # After a receive error, you can't receive
908
+ for role in [CLIENT, SERVER]:
909
+ c = Connection(our_role=role)
910
+ c.receive_data(b"gibberish\r\n\r\n")
911
+ with pytest.raises(RemoteProtocolError):
912
+ c.next_event()
913
+ # Now any attempt to receive continues to raise
914
+ assert c.their_state is ERROR
915
+ assert c.our_state is not ERROR
916
+ print(c._cstate.states)
917
+ with pytest.raises(RemoteProtocolError):
918
+ c.next_event()
919
+ # But we can still yell at the client for sending us gibberish
920
+ if role is SERVER:
921
+ assert (
922
+ c.send(Response(status_code=400, headers=[])) # type: ignore[arg-type]
923
+ == b"HTTP/1.1 400 \r\nConnection: close\r\n\r\n"
924
+ )
925
+
926
+ # After an error sending, you can no longer send
927
+ # (This is especially important for things like content-length errors,
928
+ # where there's complex internal state being modified)
929
+ def conn(role: Type[Sentinel]) -> Connection:
930
+ c = Connection(our_role=role)
931
+ if role is SERVER:
932
+ # Put it into the state where it *could* send a response...
933
+ receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n")
934
+ assert c.our_state is SEND_RESPONSE
935
+ return c
936
+
937
+ for role in [CLIENT, SERVER]:
938
+ if role is CLIENT:
939
+ # This HTTP/1.0 request won't be detected as bad until after we go
940
+ # through the state machine and hit the writing code
941
+ good = Request(method="GET", target="/", headers=[("Host", "example.com")])
942
+ bad = Request(
943
+ method="GET",
944
+ target="/",
945
+ headers=[("Host", "example.com")],
946
+ http_version="1.0",
947
+ )
948
+ elif role is SERVER:
949
+ good = Response(status_code=200, headers=[]) # type: ignore[arg-type,assignment]
950
+ bad = Response(status_code=200, headers=[], http_version="1.0") # type: ignore[arg-type,assignment]
951
+ # Make sure 'good' actually is good
952
+ c = conn(role)
953
+ c.send(good)
954
+ assert c.our_state is not ERROR
955
+ # Do that again, but this time sending 'bad' first
956
+ c = conn(role)
957
+ with pytest.raises(LocalProtocolError):
958
+ c.send(bad)
959
+ assert c.our_state is ERROR
960
+ assert c.their_state is not ERROR
961
+ # Now 'good' is not so good
962
+ with pytest.raises(LocalProtocolError):
963
+ c.send(good)
964
+
965
+ # And check send_failed() too
966
+ c = conn(role)
967
+ c.send_failed()
968
+ assert c.our_state is ERROR
969
+ assert c.their_state is not ERROR
970
+ # This is idempotent
971
+ c.send_failed()
972
+ assert c.our_state is ERROR
973
+ assert c.their_state is not ERROR
974
+
975
+
976
+ def test_idle_receive_nothing() -> None:
977
+ # At one point this incorrectly raised an error
978
+ for role in [CLIENT, SERVER]:
979
+ c = Connection(role)
980
+ assert c.next_event() is NEED_DATA
981
+
982
+
983
+ def test_connection_drop() -> None:
984
+ c = Connection(SERVER)
985
+ c.receive_data(b"GET /")
986
+ assert c.next_event() is NEED_DATA
987
+ c.receive_data(b"")
988
+ with pytest.raises(RemoteProtocolError):
989
+ c.next_event()
990
+
991
+
992
+ def test_408_request_timeout() -> None:
993
+ # Should be able to send this spontaneously as a server without seeing
994
+ # anything from client
995
+ p = ConnectionPair()
996
+ p.send(SERVER, Response(status_code=408, headers=[(b"connection", b"close")]))
997
+
998
+
999
+ # This used to raise IndexError
1000
+ def test_empty_request() -> None:
1001
+ c = Connection(SERVER)
1002
+ c.receive_data(b"\r\n")
1003
+ with pytest.raises(RemoteProtocolError):
1004
+ c.next_event()
1005
+
1006
+
1007
+ # This used to raise IndexError
1008
+ def test_empty_response() -> None:
1009
+ c = Connection(CLIENT)
1010
+ c.send(Request(method="GET", target="/", headers=[("Host", "a")]))
1011
+ c.receive_data(b"\r\n")
1012
+ with pytest.raises(RemoteProtocolError):
1013
+ c.next_event()
1014
+
1015
+
1016
+ @pytest.mark.parametrize(
1017
+ "data",
1018
+ [
1019
+ b"\x00",
1020
+ b"\x20",
1021
+ b"\x16\x03\x01\x00\xa5", # Typical start of a TLS Client Hello
1022
+ ],
1023
+ )
1024
+ def test_early_detection_of_invalid_request(data: bytes) -> None:
1025
+ c = Connection(SERVER)
1026
+ # Early detection should occur before even receiving a `\r\n`
1027
+ c.receive_data(data)
1028
+ with pytest.raises(RemoteProtocolError):
1029
+ c.next_event()
1030
+
1031
+
1032
+ @pytest.mark.parametrize(
1033
+ "data",
1034
+ [
1035
+ b"\x00",
1036
+ b"\x20",
1037
+ b"\x16\x03\x03\x00\x31", # Typical start of a TLS Server Hello
1038
+ ],
1039
+ )
1040
+ def test_early_detection_of_invalid_response(data: bytes) -> None:
1041
+ c = Connection(CLIENT)
1042
+ # Early detection should occur before even receiving a `\r\n`
1043
+ c.receive_data(data)
1044
+ with pytest.raises(RemoteProtocolError):
1045
+ c.next_event()
1046
+
1047
+
1048
+ # This used to give different headers for HEAD and GET.
1049
+ # The correct way to handle HEAD is to put whatever headers we *would* have
1050
+ # put if it were a GET -- even though we know that for HEAD, those headers
1051
+ # will be ignored.
1052
+ def test_HEAD_framing_headers() -> None:
1053
+ def setup(method: bytes, http_version: bytes) -> Connection:
1054
+ c = Connection(SERVER)
1055
+ c.receive_data(
1056
+ method + b" / HTTP/" + http_version + b"\r\n" + b"Host: example.com\r\n\r\n"
1057
+ )
1058
+ assert type(c.next_event()) is Request
1059
+ assert type(c.next_event()) is EndOfMessage
1060
+ return c
1061
+
1062
+ for method in [b"GET", b"HEAD"]:
1063
+ # No Content-Length, HTTP/1.1 peer, should use chunked
1064
+ c = setup(method, b"1.1")
1065
+ assert (
1066
+ c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" # type: ignore[arg-type]
1067
+ b"Transfer-Encoding: chunked\r\n\r\n"
1068
+ )
1069
+
1070
+ # No Content-Length, HTTP/1.0 peer, frame with connection: close
1071
+ c = setup(method, b"1.0")
1072
+ assert (
1073
+ c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" # type: ignore[arg-type]
1074
+ b"Connection: close\r\n\r\n"
1075
+ )
1076
+
1077
+ # Content-Length + Transfer-Encoding, TE wins
1078
+ c = setup(method, b"1.1")
1079
+ assert (
1080
+ c.send(
1081
+ Response(
1082
+ status_code=200,
1083
+ headers=[
1084
+ ("Content-Length", "100"),
1085
+ ("Transfer-Encoding", "chunked"),
1086
+ ],
1087
+ )
1088
+ )
1089
+ == b"HTTP/1.1 200 \r\n"
1090
+ b"Transfer-Encoding: chunked\r\n\r\n"
1091
+ )
1092
+
1093
+
1094
+ def test_special_exceptions_for_lost_connection_in_message_body() -> None:
1095
+ c = Connection(SERVER)
1096
+ c.receive_data(
1097
+ b"POST / HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 100\r\n\r\n"
1098
+ )
1099
+ assert type(c.next_event()) is Request
1100
+ assert c.next_event() is NEED_DATA
1101
+ c.receive_data(b"12345")
1102
+ assert c.next_event() == Data(data=b"12345")
1103
+ c.receive_data(b"")
1104
+ with pytest.raises(RemoteProtocolError) as excinfo:
1105
+ c.next_event()
1106
+ assert "received 5 bytes" in str(excinfo.value)
1107
+ assert "expected 100" in str(excinfo.value)
1108
+
1109
+ c = Connection(SERVER)
1110
+ c.receive_data(
1111
+ b"POST / HTTP/1.1\r\n"
1112
+ b"Host: example.com\r\n"
1113
+ b"Transfer-Encoding: chunked\r\n\r\n"
1114
+ )
1115
+ assert type(c.next_event()) is Request
1116
+ assert c.next_event() is NEED_DATA
1117
+ c.receive_data(b"8\r\n012345")
1118
+ assert c.next_event().data == b"012345" # type: ignore
1119
+ c.receive_data(b"")
1120
+ with pytest.raises(RemoteProtocolError) as excinfo:
1121
+ c.next_event()
1122
+ assert "incomplete chunked read" in str(excinfo.value)
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_events.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ]
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_headers.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ )
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_helpers.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .._events import (
2
+ ConnectionClosed,
3
+ Data,
4
+ EndOfMessage,
5
+ Event,
6
+ InformationalResponse,
7
+ Request,
8
+ Response,
9
+ )
10
+ from .helpers import normalize_data_events
11
+
12
+
13
+ def test_normalize_data_events() -> None:
14
+ assert normalize_data_events(
15
+ [
16
+ Data(data=bytearray(b"1")),
17
+ Data(data=b"2"),
18
+ Response(status_code=200, headers=[]), # type: ignore[arg-type]
19
+ Data(data=b"3"),
20
+ Data(data=b"4"),
21
+ EndOfMessage(),
22
+ Data(data=b"5"),
23
+ Data(data=b"6"),
24
+ Data(data=b"7"),
25
+ ]
26
+ ) == [
27
+ Data(data=b"12"),
28
+ Response(status_code=200, headers=[]), # type: ignore[arg-type]
29
+ Data(data=b"34"),
30
+ EndOfMessage(),
31
+ Data(data=b"567"),
32
+ ]
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_io.py ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Callable, Generator, List
2
+
3
+ import pytest
4
+
5
+ from .._events import (
6
+ ConnectionClosed,
7
+ Data,
8
+ EndOfMessage,
9
+ Event,
10
+ InformationalResponse,
11
+ Request,
12
+ Response,
13
+ )
14
+ from .._headers import Headers, normalize_and_validate
15
+ from .._readers import (
16
+ _obsolete_line_fold,
17
+ ChunkedReader,
18
+ ContentLengthReader,
19
+ Http10Reader,
20
+ READERS,
21
+ )
22
+ from .._receivebuffer import ReceiveBuffer
23
+ from .._state import (
24
+ CLIENT,
25
+ CLOSED,
26
+ DONE,
27
+ IDLE,
28
+ MIGHT_SWITCH_PROTOCOL,
29
+ MUST_CLOSE,
30
+ SEND_BODY,
31
+ SEND_RESPONSE,
32
+ SERVER,
33
+ SWITCHED_PROTOCOL,
34
+ )
35
+ from .._util import LocalProtocolError
36
+ from .._writers import (
37
+ ChunkedWriter,
38
+ ContentLengthWriter,
39
+ Http10Writer,
40
+ write_any_response,
41
+ write_headers,
42
+ write_request,
43
+ WRITERS,
44
+ )
45
+ from .helpers import normalize_data_events
46
+
47
+ SIMPLE_CASES = [
48
+ (
49
+ (CLIENT, IDLE),
50
+ Request(
51
+ method="GET",
52
+ target="/a",
53
+ headers=[("Host", "foo"), ("Connection", "close")],
54
+ ),
55
+ b"GET /a HTTP/1.1\r\nHost: foo\r\nConnection: close\r\n\r\n",
56
+ ),
57
+ (
58
+ (SERVER, SEND_RESPONSE),
59
+ Response(status_code=200, headers=[("Connection", "close")], reason=b"OK"),
60
+ b"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n",
61
+ ),
62
+ (
63
+ (SERVER, SEND_RESPONSE),
64
+ Response(status_code=200, headers=[], reason=b"OK"), # type: ignore[arg-type]
65
+ b"HTTP/1.1 200 OK\r\n\r\n",
66
+ ),
67
+ (
68
+ (SERVER, SEND_RESPONSE),
69
+ InformationalResponse(
70
+ status_code=101, headers=[("Upgrade", "websocket")], reason=b"Upgrade"
71
+ ),
72
+ b"HTTP/1.1 101 Upgrade\r\nUpgrade: websocket\r\n\r\n",
73
+ ),
74
+ (
75
+ (SERVER, SEND_RESPONSE),
76
+ InformationalResponse(status_code=101, headers=[], reason=b"Upgrade"), # type: ignore[arg-type]
77
+ b"HTTP/1.1 101 Upgrade\r\n\r\n",
78
+ ),
79
+ ]
80
+
81
+
82
+ def dowrite(writer: Callable[..., None], obj: Any) -> bytes:
83
+ got_list: List[bytes] = []
84
+ writer(obj, got_list.append)
85
+ return b"".join(got_list)
86
+
87
+
88
+ def tw(writer: Any, obj: Any, expected: Any) -> None:
89
+ got = dowrite(writer, obj)
90
+ assert got == expected
91
+
92
+
93
+ def makebuf(data: bytes) -> ReceiveBuffer:
94
+ buf = ReceiveBuffer()
95
+ buf += data
96
+ return buf
97
+
98
+
99
+ def tr(reader: Any, data: bytes, expected: Any) -> None:
100
+ def check(got: Any) -> None:
101
+ assert got == expected
102
+ # Headers should always be returned as bytes, not e.g. bytearray
103
+ # https://github.com/python-hyper/wsproto/pull/54#issuecomment-377709478
104
+ for name, value in getattr(got, "headers", []):
105
+ assert type(name) is bytes
106
+ assert type(value) is bytes
107
+
108
+ # Simple: consume whole thing
109
+ buf = makebuf(data)
110
+ check(reader(buf))
111
+ assert not buf
112
+
113
+ # Incrementally growing buffer
114
+ buf = ReceiveBuffer()
115
+ for i in range(len(data)):
116
+ assert reader(buf) is None
117
+ buf += data[i : i + 1]
118
+ check(reader(buf))
119
+
120
+ # Trailing data
121
+ buf = makebuf(data)
122
+ buf += b"trailing"
123
+ check(reader(buf))
124
+ assert bytes(buf) == b"trailing"
125
+
126
+
127
+ def test_writers_simple() -> None:
128
+ for ((role, state), event, binary) in SIMPLE_CASES:
129
+ tw(WRITERS[role, state], event, binary)
130
+
131
+
132
+ def test_readers_simple() -> None:
133
+ for ((role, state), event, binary) in SIMPLE_CASES:
134
+ tr(READERS[role, state], binary, event)
135
+
136
+
137
+ def test_writers_unusual() -> None:
138
+ # Simple test of the write_headers utility routine
139
+ tw(
140
+ write_headers,
141
+ normalize_and_validate([("foo", "bar"), ("baz", "quux")]),
142
+ b"foo: bar\r\nbaz: quux\r\n\r\n",
143
+ )
144
+ tw(write_headers, Headers([]), b"\r\n")
145
+
146
+ # We understand HTTP/1.0, but we don't speak it
147
+ with pytest.raises(LocalProtocolError):
148
+ tw(
149
+ write_request,
150
+ Request(
151
+ method="GET",
152
+ target="/",
153
+ headers=[("Host", "foo"), ("Connection", "close")],
154
+ http_version="1.0",
155
+ ),
156
+ None,
157
+ )
158
+ with pytest.raises(LocalProtocolError):
159
+ tw(
160
+ write_any_response,
161
+ Response(
162
+ status_code=200, headers=[("Connection", "close")], http_version="1.0"
163
+ ),
164
+ None,
165
+ )
166
+
167
+
168
+ def test_readers_unusual() -> None:
169
+ # Reading HTTP/1.0
170
+ tr(
171
+ READERS[CLIENT, IDLE],
172
+ b"HEAD /foo HTTP/1.0\r\nSome: header\r\n\r\n",
173
+ Request(
174
+ method="HEAD",
175
+ target="/foo",
176
+ headers=[("Some", "header")],
177
+ http_version="1.0",
178
+ ),
179
+ )
180
+
181
+ # check no-headers, since it's only legal with HTTP/1.0
182
+ tr(
183
+ READERS[CLIENT, IDLE],
184
+ b"HEAD /foo HTTP/1.0\r\n\r\n",
185
+ Request(method="HEAD", target="/foo", headers=[], http_version="1.0"), # type: ignore[arg-type]
186
+ )
187
+
188
+ tr(
189
+ READERS[SERVER, SEND_RESPONSE],
190
+ b"HTTP/1.0 200 OK\r\nSome: header\r\n\r\n",
191
+ Response(
192
+ status_code=200,
193
+ headers=[("Some", "header")],
194
+ http_version="1.0",
195
+ reason=b"OK",
196
+ ),
197
+ )
198
+
199
+ # single-character header values (actually disallowed by the ABNF in RFC
200
+ # 7230 -- this is a bug in the standard that we originally copied...)
201
+ tr(
202
+ READERS[SERVER, SEND_RESPONSE],
203
+ b"HTTP/1.0 200 OK\r\n" b"Foo: a a a a a \r\n\r\n",
204
+ Response(
205
+ status_code=200,
206
+ headers=[("Foo", "a a a a a")],
207
+ http_version="1.0",
208
+ reason=b"OK",
209
+ ),
210
+ )
211
+
212
+ # Empty headers -- also legal
213
+ tr(
214
+ READERS[SERVER, SEND_RESPONSE],
215
+ b"HTTP/1.0 200 OK\r\n" b"Foo:\r\n\r\n",
216
+ Response(
217
+ status_code=200, headers=[("Foo", "")], http_version="1.0", reason=b"OK"
218
+ ),
219
+ )
220
+
221
+ tr(
222
+ READERS[SERVER, SEND_RESPONSE],
223
+ b"HTTP/1.0 200 OK\r\n" b"Foo: \t \t \r\n\r\n",
224
+ Response(
225
+ status_code=200, headers=[("Foo", "")], http_version="1.0", reason=b"OK"
226
+ ),
227
+ )
228
+
229
+ # Tolerate broken servers that leave off the response code
230
+ tr(
231
+ READERS[SERVER, SEND_RESPONSE],
232
+ b"HTTP/1.0 200\r\n" b"Foo: bar\r\n\r\n",
233
+ Response(
234
+ status_code=200, headers=[("Foo", "bar")], http_version="1.0", reason=b""
235
+ ),
236
+ )
237
+
238
+ # Tolerate headers line endings (\r\n and \n)
239
+ # \n\r\b between headers and body
240
+ tr(
241
+ READERS[SERVER, SEND_RESPONSE],
242
+ b"HTTP/1.1 200 OK\r\nSomeHeader: val\n\r\n",
243
+ Response(
244
+ status_code=200,
245
+ headers=[("SomeHeader", "val")],
246
+ http_version="1.1",
247
+ reason="OK",
248
+ ),
249
+ )
250
+
251
+ # delimited only with \n
252
+ tr(
253
+ READERS[SERVER, SEND_RESPONSE],
254
+ b"HTTP/1.1 200 OK\nSomeHeader1: val1\nSomeHeader2: val2\n\n",
255
+ Response(
256
+ status_code=200,
257
+ headers=[("SomeHeader1", "val1"), ("SomeHeader2", "val2")],
258
+ http_version="1.1",
259
+ reason="OK",
260
+ ),
261
+ )
262
+
263
+ # mixed \r\n and \n
264
+ tr(
265
+ READERS[SERVER, SEND_RESPONSE],
266
+ b"HTTP/1.1 200 OK\r\nSomeHeader1: val1\nSomeHeader2: val2\n\r\n",
267
+ Response(
268
+ status_code=200,
269
+ headers=[("SomeHeader1", "val1"), ("SomeHeader2", "val2")],
270
+ http_version="1.1",
271
+ reason="OK",
272
+ ),
273
+ )
274
+
275
+ # obsolete line folding
276
+ tr(
277
+ READERS[CLIENT, IDLE],
278
+ b"HEAD /foo HTTP/1.1\r\n"
279
+ b"Host: example.com\r\n"
280
+ b"Some: multi-line\r\n"
281
+ b" header\r\n"
282
+ b"\tnonsense\r\n"
283
+ b" \t \t\tI guess\r\n"
284
+ b"Connection: close\r\n"
285
+ b"More-nonsense: in the\r\n"
286
+ b" last header \r\n\r\n",
287
+ Request(
288
+ method="HEAD",
289
+ target="/foo",
290
+ headers=[
291
+ ("Host", "example.com"),
292
+ ("Some", "multi-line header nonsense I guess"),
293
+ ("Connection", "close"),
294
+ ("More-nonsense", "in the last header"),
295
+ ],
296
+ ),
297
+ )
298
+
299
+ with pytest.raises(LocalProtocolError):
300
+ tr(
301
+ READERS[CLIENT, IDLE],
302
+ b"HEAD /foo HTTP/1.1\r\n" b" folded: line\r\n\r\n",
303
+ None,
304
+ )
305
+
306
+ with pytest.raises(LocalProtocolError):
307
+ tr(
308
+ READERS[CLIENT, IDLE],
309
+ b"HEAD /foo HTTP/1.1\r\n" b"foo : line\r\n\r\n",
310
+ None,
311
+ )
312
+ with pytest.raises(LocalProtocolError):
313
+ tr(
314
+ READERS[CLIENT, IDLE],
315
+ b"HEAD /foo HTTP/1.1\r\n" b"foo\t: line\r\n\r\n",
316
+ None,
317
+ )
318
+ with pytest.raises(LocalProtocolError):
319
+ tr(
320
+ READERS[CLIENT, IDLE],
321
+ b"HEAD /foo HTTP/1.1\r\n" b"foo\t: line\r\n\r\n",
322
+ None,
323
+ )
324
+ with pytest.raises(LocalProtocolError):
325
+ tr(READERS[CLIENT, IDLE], b"HEAD /foo HTTP/1.1\r\n" b": line\r\n\r\n", None)
326
+
327
+
328
+ def test__obsolete_line_fold_bytes() -> None:
329
+ # _obsolete_line_fold has a defensive cast to bytearray, which is
330
+ # necessary to protect against O(n^2) behavior in case anyone ever passes
331
+ # in regular bytestrings... but right now we never pass in regular
332
+ # bytestrings. so this test just exists to get some coverage on that
333
+ # defensive cast.
334
+ assert list(_obsolete_line_fold([b"aaa", b"bbb", b" ccc", b"ddd"])) == [
335
+ b"aaa",
336
+ bytearray(b"bbb ccc"),
337
+ b"ddd",
338
+ ]
339
+
340
+
341
+ def _run_reader_iter(
342
+ reader: Any, buf: bytes, do_eof: bool
343
+ ) -> Generator[Any, None, None]:
344
+ while True:
345
+ event = reader(buf)
346
+ if event is None:
347
+ break
348
+ yield event
349
+ # body readers have undefined behavior after returning EndOfMessage,
350
+ # because this changes the state so they don't get called again
351
+ if type(event) is EndOfMessage:
352
+ break
353
+ if do_eof:
354
+ assert not buf
355
+ yield reader.read_eof()
356
+
357
+
358
+ def _run_reader(*args: Any) -> List[Event]:
359
+ events = list(_run_reader_iter(*args))
360
+ return normalize_data_events(events)
361
+
362
+
363
+ def t_body_reader(thunk: Any, data: bytes, expected: Any, do_eof: bool = False) -> None:
364
+ # Simple: consume whole thing
365
+ print("Test 1")
366
+ buf = makebuf(data)
367
+ assert _run_reader(thunk(), buf, do_eof) == expected
368
+
369
+ # Incrementally growing buffer
370
+ print("Test 2")
371
+ reader = thunk()
372
+ buf = ReceiveBuffer()
373
+ events = []
374
+ for i in range(len(data)):
375
+ events += _run_reader(reader, buf, False)
376
+ buf += data[i : i + 1]
377
+ events += _run_reader(reader, buf, do_eof)
378
+ assert normalize_data_events(events) == expected
379
+
380
+ is_complete = any(type(event) is EndOfMessage for event in expected)
381
+ if is_complete and not do_eof:
382
+ buf = makebuf(data + b"trailing")
383
+ assert _run_reader(thunk(), buf, False) == expected
384
+
385
+
386
+ def test_ContentLengthReader() -> None:
387
+ t_body_reader(lambda: ContentLengthReader(0), b"", [EndOfMessage()])
388
+
389
+ t_body_reader(
390
+ lambda: ContentLengthReader(10),
391
+ b"0123456789",
392
+ [Data(data=b"0123456789"), EndOfMessage()],
393
+ )
394
+
395
+
396
+ def test_Http10Reader() -> None:
397
+ t_body_reader(Http10Reader, b"", [EndOfMessage()], do_eof=True)
398
+ t_body_reader(Http10Reader, b"asdf", [Data(data=b"asdf")], do_eof=False)
399
+ t_body_reader(
400
+ Http10Reader, b"asdf", [Data(data=b"asdf"), EndOfMessage()], do_eof=True
401
+ )
402
+
403
+
404
+ def test_ChunkedReader() -> None:
405
+ t_body_reader(ChunkedReader, b"0\r\n\r\n", [EndOfMessage()])
406
+
407
+ t_body_reader(
408
+ ChunkedReader,
409
+ b"0\r\nSome: header\r\n\r\n",
410
+ [EndOfMessage(headers=[("Some", "header")])],
411
+ )
412
+
413
+ t_body_reader(
414
+ ChunkedReader,
415
+ b"5\r\n01234\r\n"
416
+ + b"10\r\n0123456789abcdef\r\n"
417
+ + b"0\r\n"
418
+ + b"Some: header\r\n\r\n",
419
+ [
420
+ Data(data=b"012340123456789abcdef"),
421
+ EndOfMessage(headers=[("Some", "header")]),
422
+ ],
423
+ )
424
+
425
+ t_body_reader(
426
+ ChunkedReader,
427
+ b"5\r\n01234\r\n" + b"10\r\n0123456789abcdef\r\n" + b"0\r\n\r\n",
428
+ [Data(data=b"012340123456789abcdef"), EndOfMessage()],
429
+ )
430
+
431
+ # handles upper and lowercase hex
432
+ t_body_reader(
433
+ ChunkedReader,
434
+ b"aA\r\n" + b"x" * 0xAA + b"\r\n" + b"0\r\n\r\n",
435
+ [Data(data=b"x" * 0xAA), EndOfMessage()],
436
+ )
437
+
438
+ # refuses arbitrarily long chunk integers
439
+ with pytest.raises(LocalProtocolError):
440
+ # Technically this is legal HTTP/1.1, but we refuse to process chunk
441
+ # sizes that don't fit into 20 characters of hex
442
+ t_body_reader(ChunkedReader, b"9" * 100 + b"\r\nxxx", [Data(data=b"xxx")])
443
+
444
+ # refuses garbage in the chunk count
445
+ with pytest.raises(LocalProtocolError):
446
+ t_body_reader(ChunkedReader, b"10\x00\r\nxxx", None)
447
+
448
+ # handles (and discards) "chunk extensions" omg wtf
449
+ t_body_reader(
450
+ ChunkedReader,
451
+ b"5; hello=there\r\n"
452
+ + b"xxxxx"
453
+ + b"\r\n"
454
+ + b'0; random="junk"; some=more; canbe=lonnnnngg\r\n\r\n',
455
+ [Data(data=b"xxxxx"), EndOfMessage()],
456
+ )
457
+
458
+ t_body_reader(
459
+ ChunkedReader,
460
+ b"5 \r\n01234\r\n" + b"0\r\n\r\n",
461
+ [Data(data=b"01234"), EndOfMessage()],
462
+ )
463
+
464
+
465
+ def test_ContentLengthWriter() -> None:
466
+ w = ContentLengthWriter(5)
467
+ assert dowrite(w, Data(data=b"123")) == b"123"
468
+ assert dowrite(w, Data(data=b"45")) == b"45"
469
+ assert dowrite(w, EndOfMessage()) == b""
470
+
471
+ w = ContentLengthWriter(5)
472
+ with pytest.raises(LocalProtocolError):
473
+ dowrite(w, Data(data=b"123456"))
474
+
475
+ w = ContentLengthWriter(5)
476
+ dowrite(w, Data(data=b"123"))
477
+ with pytest.raises(LocalProtocolError):
478
+ dowrite(w, Data(data=b"456"))
479
+
480
+ w = ContentLengthWriter(5)
481
+ dowrite(w, Data(data=b"123"))
482
+ with pytest.raises(LocalProtocolError):
483
+ dowrite(w, EndOfMessage())
484
+
485
+ w = ContentLengthWriter(5)
486
+ dowrite(w, Data(data=b"123")) == b"123"
487
+ dowrite(w, Data(data=b"45")) == b"45"
488
+ with pytest.raises(LocalProtocolError):
489
+ dowrite(w, EndOfMessage(headers=[("Etag", "asdf")]))
490
+
491
+
492
+ def test_ChunkedWriter() -> None:
493
+ w = ChunkedWriter()
494
+ assert dowrite(w, Data(data=b"aaa")) == b"3\r\naaa\r\n"
495
+ assert dowrite(w, Data(data=b"a" * 20)) == b"14\r\n" + b"a" * 20 + b"\r\n"
496
+
497
+ assert dowrite(w, Data(data=b"")) == b""
498
+
499
+ assert dowrite(w, EndOfMessage()) == b"0\r\n\r\n"
500
+
501
+ assert (
502
+ dowrite(w, EndOfMessage(headers=[("Etag", "asdf"), ("a", "b")]))
503
+ == b"0\r\nEtag: asdf\r\na: b\r\n\r\n"
504
+ )
505
+
506
+
507
+ def test_Http10Writer() -> None:
508
+ w = Http10Writer()
509
+ assert dowrite(w, Data(data=b"1234")) == b"1234"
510
+ assert dowrite(w, EndOfMessage()) == b""
511
+
512
+ with pytest.raises(LocalProtocolError):
513
+ dowrite(w, EndOfMessage(headers=[("Etag", "asdf")]))
514
+
515
+
516
+ def test_reject_garbage_after_request_line() -> None:
517
+ with pytest.raises(LocalProtocolError):
518
+ tr(READERS[SERVER, SEND_RESPONSE], b"HTTP/1.0 200 OK\x00xxxx\r\n\r\n", None)
519
+
520
+
521
+ def test_reject_garbage_after_response_line() -> None:
522
+ with pytest.raises(LocalProtocolError):
523
+ tr(
524
+ READERS[CLIENT, IDLE],
525
+ b"HEAD /foo HTTP/1.1 xxxxxx\r\n" b"Host: a\r\n\r\n",
526
+ None,
527
+ )
528
+
529
+
530
+ def test_reject_garbage_in_header_line() -> None:
531
+ with pytest.raises(LocalProtocolError):
532
+ tr(
533
+ READERS[CLIENT, IDLE],
534
+ b"HEAD /foo HTTP/1.1\r\n" b"Host: foo\x00bar\r\n\r\n",
535
+ None,
536
+ )
537
+
538
+
539
+ def test_reject_non_vchar_in_path() -> None:
540
+ for bad_char in b"\x00\x20\x7f\xee":
541
+ message = bytearray(b"HEAD /")
542
+ message.append(bad_char)
543
+ message.extend(b" HTTP/1.1\r\nHost: foobar\r\n\r\n")
544
+ with pytest.raises(LocalProtocolError):
545
+ tr(READERS[CLIENT, IDLE], message, None)
546
+
547
+
548
+ # https://github.com/python-hyper/h11/issues/57
549
+ def test_allow_some_garbage_in_cookies() -> None:
550
+ tr(
551
+ READERS[CLIENT, IDLE],
552
+ b"HEAD /foo HTTP/1.1\r\n"
553
+ b"Host: foo\r\n"
554
+ b"Set-Cookie: ___utmvafIumyLc=kUd\x01UpAt; path=/; Max-Age=900\r\n"
555
+ b"\r\n",
556
+ Request(
557
+ method="HEAD",
558
+ target="/foo",
559
+ headers=[
560
+ ("Host", "foo"),
561
+ ("Set-Cookie", "___utmvafIumyLc=kUd\x01UpAt; path=/; Max-Age=900"),
562
+ ],
563
+ ),
564
+ )
565
+
566
+
567
+ def test_host_comes_first() -> None:
568
+ tw(
569
+ write_headers,
570
+ normalize_and_validate([("foo", "bar"), ("Host", "example.com")]),
571
+ b"Host: example.com\r\nfoo: bar\r\n\r\n",
572
+ )
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_receivebuffer.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from typing import Tuple
3
+
4
+ import pytest
5
+
6
+ from .._receivebuffer import ReceiveBuffer
7
+
8
+
9
+ def test_receivebuffer() -> None:
10
+ b = ReceiveBuffer()
11
+ assert not b
12
+ assert len(b) == 0
13
+ assert bytes(b) == b""
14
+
15
+ b += b"123"
16
+ assert b
17
+ assert len(b) == 3
18
+ assert bytes(b) == b"123"
19
+
20
+ assert bytes(b) == b"123"
21
+
22
+ assert b.maybe_extract_at_most(2) == b"12"
23
+ assert b
24
+ assert len(b) == 1
25
+ assert bytes(b) == b"3"
26
+
27
+ assert bytes(b) == b"3"
28
+
29
+ assert b.maybe_extract_at_most(10) == b"3"
30
+ assert bytes(b) == b""
31
+
32
+ assert b.maybe_extract_at_most(10) is None
33
+ assert not b
34
+
35
+ ################################################################
36
+ # maybe_extract_until_next
37
+ ################################################################
38
+
39
+ b += b"123\n456\r\n789\r\n"
40
+
41
+ assert b.maybe_extract_next_line() == b"123\n456\r\n"
42
+ assert bytes(b) == b"789\r\n"
43
+
44
+ assert b.maybe_extract_next_line() == b"789\r\n"
45
+ assert bytes(b) == b""
46
+
47
+ b += b"12\r"
48
+ assert b.maybe_extract_next_line() is None
49
+ assert bytes(b) == b"12\r"
50
+
51
+ b += b"345\n\r"
52
+ assert b.maybe_extract_next_line() is None
53
+ assert bytes(b) == b"12\r345\n\r"
54
+
55
+ # here we stopped at the middle of b"\r\n" delimiter
56
+
57
+ b += b"\n6789aaa123\r\n"
58
+ assert b.maybe_extract_next_line() == b"12\r345\n\r\n"
59
+ assert b.maybe_extract_next_line() == b"6789aaa123\r\n"
60
+ assert b.maybe_extract_next_line() is None
61
+ assert bytes(b) == b""
62
+
63
+ ################################################################
64
+ # maybe_extract_lines
65
+ ################################################################
66
+
67
+ b += b"123\r\na: b\r\nfoo:bar\r\n\r\ntrailing"
68
+ lines = b.maybe_extract_lines()
69
+ assert lines == [b"123", b"a: b", b"foo:bar"]
70
+ assert bytes(b) == b"trailing"
71
+
72
+ assert b.maybe_extract_lines() is None
73
+
74
+ b += b"\r\n\r"
75
+ assert b.maybe_extract_lines() is None
76
+
77
+ assert b.maybe_extract_at_most(100) == b"trailing\r\n\r"
78
+ assert not b
79
+
80
+ # Empty body case (as happens at the end of chunked encoding if there are
81
+ # no trailing headers, e.g.)
82
+ b += b"\r\ntrailing"
83
+ assert b.maybe_extract_lines() == []
84
+ assert bytes(b) == b"trailing"
85
+
86
+
87
+ @pytest.mark.parametrize(
88
+ "data",
89
+ [
90
+ pytest.param(
91
+ (
92
+ b"HTTP/1.1 200 OK\r\n",
93
+ b"Content-type: text/plain\r\n",
94
+ b"Connection: close\r\n",
95
+ b"\r\n",
96
+ b"Some body",
97
+ ),
98
+ id="with_crlf_delimiter",
99
+ ),
100
+ pytest.param(
101
+ (
102
+ b"HTTP/1.1 200 OK\n",
103
+ b"Content-type: text/plain\n",
104
+ b"Connection: close\n",
105
+ b"\n",
106
+ b"Some body",
107
+ ),
108
+ id="with_lf_only_delimiter",
109
+ ),
110
+ pytest.param(
111
+ (
112
+ b"HTTP/1.1 200 OK\n",
113
+ b"Content-type: text/plain\r\n",
114
+ b"Connection: close\n",
115
+ b"\n",
116
+ b"Some body",
117
+ ),
118
+ id="with_mixed_crlf_and_lf",
119
+ ),
120
+ ],
121
+ )
122
+ def test_receivebuffer_for_invalid_delimiter(data: Tuple[bytes]) -> None:
123
+ b = ReceiveBuffer()
124
+
125
+ for line in data:
126
+ b += line
127
+
128
+ lines = b.maybe_extract_lines()
129
+
130
+ assert lines == [
131
+ b"HTTP/1.1 200 OK",
132
+ b"Content-type: text/plain",
133
+ b"Connection: close",
134
+ ]
135
+ assert bytes(b) == b"Some body"
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_state.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ from .._events import (
4
+ ConnectionClosed,
5
+ Data,
6
+ EndOfMessage,
7
+ Event,
8
+ InformationalResponse,
9
+ Request,
10
+ Response,
11
+ )
12
+ from .._state import (
13
+ _SWITCH_CONNECT,
14
+ _SWITCH_UPGRADE,
15
+ CLIENT,
16
+ CLOSED,
17
+ ConnectionState,
18
+ DONE,
19
+ IDLE,
20
+ MIGHT_SWITCH_PROTOCOL,
21
+ MUST_CLOSE,
22
+ SEND_BODY,
23
+ SEND_RESPONSE,
24
+ SERVER,
25
+ SWITCHED_PROTOCOL,
26
+ )
27
+ from .._util import LocalProtocolError
28
+
29
+
30
+ def test_ConnectionState() -> None:
31
+ cs = ConnectionState()
32
+
33
+ # Basic event-triggered transitions
34
+
35
+ assert cs.states == {CLIENT: IDLE, SERVER: IDLE}
36
+
37
+ cs.process_event(CLIENT, Request)
38
+ # The SERVER-Request special case:
39
+ assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
40
+
41
+ # Illegal transitions raise an error and nothing happens
42
+ with pytest.raises(LocalProtocolError):
43
+ cs.process_event(CLIENT, Request)
44
+ assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
45
+
46
+ cs.process_event(SERVER, InformationalResponse)
47
+ assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
48
+
49
+ cs.process_event(SERVER, Response)
50
+ assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_BODY}
51
+
52
+ cs.process_event(CLIENT, EndOfMessage)
53
+ cs.process_event(SERVER, EndOfMessage)
54
+ assert cs.states == {CLIENT: DONE, SERVER: DONE}
55
+
56
+ # State-triggered transition
57
+
58
+ cs.process_event(SERVER, ConnectionClosed)
59
+ assert cs.states == {CLIENT: MUST_CLOSE, SERVER: CLOSED}
60
+
61
+
62
+ def test_ConnectionState_keep_alive() -> None:
63
+ # keep_alive = False
64
+ cs = ConnectionState()
65
+ cs.process_event(CLIENT, Request)
66
+ cs.process_keep_alive_disabled()
67
+ cs.process_event(CLIENT, EndOfMessage)
68
+ assert cs.states == {CLIENT: MUST_CLOSE, SERVER: SEND_RESPONSE}
69
+
70
+ cs.process_event(SERVER, Response)
71
+ cs.process_event(SERVER, EndOfMessage)
72
+ assert cs.states == {CLIENT: MUST_CLOSE, SERVER: MUST_CLOSE}
73
+
74
+
75
+ def test_ConnectionState_keep_alive_in_DONE() -> None:
76
+ # Check that if keep_alive is disabled when the CLIENT is already in DONE,
77
+ # then this is sufficient to immediately trigger the DONE -> MUST_CLOSE
78
+ # transition
79
+ cs = ConnectionState()
80
+ cs.process_event(CLIENT, Request)
81
+ cs.process_event(CLIENT, EndOfMessage)
82
+ assert cs.states[CLIENT] is DONE
83
+ cs.process_keep_alive_disabled()
84
+ assert cs.states[CLIENT] is MUST_CLOSE
85
+
86
+
87
+ def test_ConnectionState_switch_denied() -> None:
88
+ for switch_type in (_SWITCH_CONNECT, _SWITCH_UPGRADE):
89
+ for deny_early in (True, False):
90
+ cs = ConnectionState()
91
+ cs.process_client_switch_proposal(switch_type)
92
+ cs.process_event(CLIENT, Request)
93
+ cs.process_event(CLIENT, Data)
94
+ assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
95
+
96
+ assert switch_type in cs.pending_switch_proposals
97
+
98
+ if deny_early:
99
+ # before client reaches DONE
100
+ cs.process_event(SERVER, Response)
101
+ assert not cs.pending_switch_proposals
102
+
103
+ cs.process_event(CLIENT, EndOfMessage)
104
+
105
+ if deny_early:
106
+ assert cs.states == {CLIENT: DONE, SERVER: SEND_BODY}
107
+ else:
108
+ assert cs.states == {
109
+ CLIENT: MIGHT_SWITCH_PROTOCOL,
110
+ SERVER: SEND_RESPONSE,
111
+ }
112
+
113
+ cs.process_event(SERVER, InformationalResponse)
114
+ assert cs.states == {
115
+ CLIENT: MIGHT_SWITCH_PROTOCOL,
116
+ SERVER: SEND_RESPONSE,
117
+ }
118
+
119
+ cs.process_event(SERVER, Response)
120
+ assert cs.states == {CLIENT: DONE, SERVER: SEND_BODY}
121
+ assert not cs.pending_switch_proposals
122
+
123
+
124
+ _response_type_for_switch = {
125
+ _SWITCH_UPGRADE: InformationalResponse,
126
+ _SWITCH_CONNECT: Response,
127
+ None: Response,
128
+ }
129
+
130
+
131
+ def test_ConnectionState_protocol_switch_accepted() -> None:
132
+ for switch_event in [_SWITCH_UPGRADE, _SWITCH_CONNECT]:
133
+ cs = ConnectionState()
134
+ cs.process_client_switch_proposal(switch_event)
135
+ cs.process_event(CLIENT, Request)
136
+ cs.process_event(CLIENT, Data)
137
+ assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
138
+
139
+ cs.process_event(CLIENT, EndOfMessage)
140
+ assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE}
141
+
142
+ cs.process_event(SERVER, InformationalResponse)
143
+ assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE}
144
+
145
+ cs.process_event(SERVER, _response_type_for_switch[switch_event], switch_event)
146
+ assert cs.states == {CLIENT: SWITCHED_PROTOCOL, SERVER: SWITCHED_PROTOCOL}
147
+
148
+
149
+ def test_ConnectionState_double_protocol_switch() -> None:
150
+ # CONNECT + Upgrade is legal! Very silly, but legal. So we support
151
+ # it. Because sometimes doing the silly thing is easier than not.
152
+ for server_switch in [None, _SWITCH_UPGRADE, _SWITCH_CONNECT]:
153
+ cs = ConnectionState()
154
+ cs.process_client_switch_proposal(_SWITCH_UPGRADE)
155
+ cs.process_client_switch_proposal(_SWITCH_CONNECT)
156
+ cs.process_event(CLIENT, Request)
157
+ cs.process_event(CLIENT, EndOfMessage)
158
+ assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE}
159
+ cs.process_event(
160
+ SERVER, _response_type_for_switch[server_switch], server_switch
161
+ )
162
+ if server_switch is None:
163
+ assert cs.states == {CLIENT: DONE, SERVER: SEND_BODY}
164
+ else:
165
+ assert cs.states == {CLIENT: SWITCHED_PROTOCOL, SERVER: SWITCHED_PROTOCOL}
166
+
167
+
168
+ def test_ConnectionState_inconsistent_protocol_switch() -> None:
169
+ for client_switches, server_switch in [
170
+ ([], _SWITCH_CONNECT),
171
+ ([], _SWITCH_UPGRADE),
172
+ ([_SWITCH_UPGRADE], _SWITCH_CONNECT),
173
+ ([_SWITCH_CONNECT], _SWITCH_UPGRADE),
174
+ ]:
175
+ cs = ConnectionState()
176
+ for client_switch in client_switches: # type: ignore[attr-defined]
177
+ cs.process_client_switch_proposal(client_switch)
178
+ cs.process_event(CLIENT, Request)
179
+ with pytest.raises(LocalProtocolError):
180
+ cs.process_event(SERVER, Response, server_switch)
181
+
182
+
183
+ def test_ConnectionState_keepalive_protocol_switch_interaction() -> None:
184
+ # keep_alive=False + pending_switch_proposals
185
+ cs = ConnectionState()
186
+ cs.process_client_switch_proposal(_SWITCH_UPGRADE)
187
+ cs.process_event(CLIENT, Request)
188
+ cs.process_keep_alive_disabled()
189
+ cs.process_event(CLIENT, Data)
190
+ assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
191
+
192
+ # the protocol switch "wins"
193
+ cs.process_event(CLIENT, EndOfMessage)
194
+ assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE}
195
+
196
+ # but when the server denies the request, keep_alive comes back into play
197
+ cs.process_event(SERVER, Response)
198
+ assert cs.states == {CLIENT: MUST_CLOSE, SERVER: SEND_BODY}
199
+
200
+
201
+ def test_ConnectionState_reuse() -> None:
202
+ cs = ConnectionState()
203
+
204
+ with pytest.raises(LocalProtocolError):
205
+ cs.start_next_cycle()
206
+
207
+ cs.process_event(CLIENT, Request)
208
+ cs.process_event(CLIENT, EndOfMessage)
209
+
210
+ with pytest.raises(LocalProtocolError):
211
+ cs.start_next_cycle()
212
+
213
+ cs.process_event(SERVER, Response)
214
+ cs.process_event(SERVER, EndOfMessage)
215
+
216
+ cs.start_next_cycle()
217
+ assert cs.states == {CLIENT: IDLE, SERVER: IDLE}
218
+
219
+ # No keepalive
220
+
221
+ cs.process_event(CLIENT, Request)
222
+ cs.process_keep_alive_disabled()
223
+ cs.process_event(CLIENT, EndOfMessage)
224
+ cs.process_event(SERVER, Response)
225
+ cs.process_event(SERVER, EndOfMessage)
226
+
227
+ with pytest.raises(LocalProtocolError):
228
+ cs.start_next_cycle()
229
+
230
+ # One side closed
231
+
232
+ cs = ConnectionState()
233
+ cs.process_event(CLIENT, Request)
234
+ cs.process_event(CLIENT, EndOfMessage)
235
+ cs.process_event(CLIENT, ConnectionClosed)
236
+ cs.process_event(SERVER, Response)
237
+ cs.process_event(SERVER, EndOfMessage)
238
+
239
+ with pytest.raises(LocalProtocolError):
240
+ cs.start_next_cycle()
241
+
242
+ # Succesful protocol switch
243
+
244
+ cs = ConnectionState()
245
+ cs.process_client_switch_proposal(_SWITCH_UPGRADE)
246
+ cs.process_event(CLIENT, Request)
247
+ cs.process_event(CLIENT, EndOfMessage)
248
+ cs.process_event(SERVER, InformationalResponse, _SWITCH_UPGRADE)
249
+
250
+ with pytest.raises(LocalProtocolError):
251
+ cs.start_next_cycle()
252
+
253
+ # Failed protocol switch
254
+
255
+ cs = ConnectionState()
256
+ cs.process_client_switch_proposal(_SWITCH_UPGRADE)
257
+ cs.process_event(CLIENT, Request)
258
+ cs.process_event(CLIENT, EndOfMessage)
259
+ cs.process_event(SERVER, Response)
260
+ cs.process_event(SERVER, EndOfMessage)
261
+
262
+ cs.start_next_cycle()
263
+ assert cs.states == {CLIENT: IDLE, SERVER: IDLE}
264
+
265
+
266
+ def test_server_request_is_illegal() -> None:
267
+ # There used to be a bug in how we handled the Request special case that
268
+ # made this allowed...
269
+ cs = ConnectionState()
270
+ with pytest.raises(LocalProtocolError):
271
+ cs.process_event(SERVER, Request)
URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_util.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import sys
3
+ import traceback
4
+ from typing import NoReturn
5
+
6
+ import pytest
7
+
8
+ from .._util import (
9
+ bytesify,
10
+ LocalProtocolError,
11
+ ProtocolError,
12
+ RemoteProtocolError,
13
+ Sentinel,
14
+ validate,
15
+ )
16
+
17
+
18
+ def test_ProtocolError() -> None:
19
+ with pytest.raises(TypeError):
20
+ ProtocolError("abstract base class")
21
+
22
+
23
+ def test_LocalProtocolError() -> None:
24
+ try:
25
+ raise LocalProtocolError("foo")
26
+ except LocalProtocolError as e:
27
+ assert str(e) == "foo"
28
+ assert e.error_status_hint == 400
29
+
30
+ try:
31
+ raise LocalProtocolError("foo", error_status_hint=418)
32
+ except LocalProtocolError as e:
33
+ assert str(e) == "foo"
34
+ assert e.error_status_hint == 418
35
+
36
+ def thunk() -> NoReturn:
37
+ raise LocalProtocolError("a", error_status_hint=420)
38
+
39
+ try:
40
+ try:
41
+ thunk()
42
+ except LocalProtocolError as exc1:
43
+ orig_traceback = "".join(traceback.format_tb(sys.exc_info()[2]))
44
+ exc1._reraise_as_remote_protocol_error()
45
+ except RemoteProtocolError as exc2:
46
+ assert type(exc2) is RemoteProtocolError
47
+ assert exc2.args == ("a",)
48
+ assert exc2.error_status_hint == 420
49
+ new_traceback = "".join(traceback.format_tb(sys.exc_info()[2]))
50
+ assert new_traceback.endswith(orig_traceback)
51
+
52
+
53
+ def test_validate() -> None:
54
+ my_re = re.compile(rb"(?P<group1>[0-9]+)\.(?P<group2>[0-9]+)")
55
+ with pytest.raises(LocalProtocolError):
56
+ validate(my_re, b"0.")
57
+
58
+ groups = validate(my_re, b"0.1")
59
+ assert groups == {"group1": b"0", "group2": b"1"}
60
+
61
+ # successful partial matches are an error - must match whole string
62
+ with pytest.raises(LocalProtocolError):
63
+ validate(my_re, b"0.1xx")
64
+ with pytest.raises(LocalProtocolError):
65
+ validate(my_re, b"0.1\n")
66
+
67
+
68
+ def test_validate_formatting() -> None:
69
+ my_re = re.compile(rb"foo")
70
+
71
+ with pytest.raises(LocalProtocolError) as excinfo:
72
+ validate(my_re, b"", "oops")
73
+ assert "oops" in str(excinfo.value)
74
+
75
+ with pytest.raises(LocalProtocolError) as excinfo:
76
+ validate(my_re, b"", "oops {}")
77
+ assert "oops {}" in str(excinfo.value)
78
+
79
+ with pytest.raises(LocalProtocolError) as excinfo:
80
+ validate(my_re, b"", "oops {} xx", 10)
81
+ assert "oops 10 xx" in str(excinfo.value)
82
+
83
+
84
+ def test_make_sentinel() -> None:
85
+ class S(Sentinel, metaclass=Sentinel):
86
+ pass
87
+
88
+ assert repr(S) == "S"
89
+ assert S == S
90
+ assert type(S).__name__ == "S"
91
+ assert S in {S}
92
+ assert type(S) is S
93
+
94
+ class S2(Sentinel, metaclass=Sentinel):
95
+ pass
96
+
97
+ assert repr(S2) == "S2"
98
+ assert S != S2
99
+ assert S not in {S2}
100
+ assert type(S) is not type(S2)
101
+
102
+
103
+ def test_bytesify() -> None:
104
+ assert bytesify(b"123") == b"123"
105
+ assert bytesify(bytearray(b"123")) == b"123"
106
+ assert bytesify("123") == b"123"
107
+
108
+ with pytest.raises(UnicodeEncodeError):
109
+ bytesify("\u1234")
110
+
111
+ with pytest.raises(TypeError):
112
+ bytesify(10)
URSA/.venv_ursa/lib/python3.12/site-packages/imageio-2.37.2.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2014-2025, imageio developers
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
+
URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (1.67 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc ADDED
Binary file (2.15 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc ADDED
Binary file (4.99 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc ADDED
Binary file (19.4 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc ADDED
Binary file (1.57 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/debug.cpython-312.pyc ADDED
Binary file (6.6 kB). View file
 
URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc ADDED
Binary file (1.62 kB). View file