provablyfine 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- provablyfine/__init__.py +0 -0
- provablyfine/anet/__init__.py +5 -0
- provablyfine/anet/base.py +52 -0
- provablyfine/anet/conftest.py +137 -0
- provablyfine/anet/exceptions.py +2 -0
- provablyfine/anet/http.py +130 -0
- provablyfine/anet/mux.py +458 -0
- provablyfine/anet/socket.py +88 -0
- provablyfine/anet/sockets.py +57 -0
- provablyfine/anet/ssl.py +182 -0
- provablyfine/anet/stream.py +48 -0
- provablyfine/anet/test_http.py +232 -0
- provablyfine/anet/test_mux.py +403 -0
- provablyfine/anet/test_socket.py +152 -0
- provablyfine/anet/test_ssl.py +192 -0
- provablyfine/anet/test_stream.py +238 -0
- provablyfine/api/__init__.py +0 -0
- provablyfine/api/app.py +189 -0
- provablyfine/api/app_db.py +372 -0
- provablyfine/api/config.py +64 -0
- provablyfine/api/context.py +109 -0
- provablyfine/api/converters.py +586 -0
- provablyfine/api/crypto_policy.py +9 -0
- provablyfine/api/db.py +225 -0
- provablyfine/api/dependencies.py +36 -0
- provablyfine/api/endpoints/__init__.py +37 -0
- provablyfine/api/endpoints/audit_log.py +23 -0
- provablyfine/api/endpoints/auth.py +92 -0
- provablyfine/api/endpoints/auth_endpoint.py +158 -0
- provablyfine/api/endpoints/auth_http_sig.py +92 -0
- provablyfine/api/endpoints/auth_oauth2.py +258 -0
- provablyfine/api/endpoints/auth_oidc.py +180 -0
- provablyfine/api/endpoints/bastion.py +88 -0
- provablyfine/api/endpoints/boundary.py +151 -0
- provablyfine/api/endpoints/debug.py +21 -0
- provablyfine/api/endpoints/directory.py +29 -0
- provablyfine/api/endpoints/identity.py +321 -0
- provablyfine/api/endpoints/initialize.py +182 -0
- provablyfine/api/endpoints/public.py +30 -0
- provablyfine/api/endpoints/role.py +135 -0
- provablyfine/api/endpoints/ssh.py +265 -0
- provablyfine/api/endpoints/tag.py +65 -0
- provablyfine/api/endpoints/tenant.py +152 -0
- provablyfine/api/grant.py +503 -0
- provablyfine/api/middleware.py +43 -0
- provablyfine/api/model/__init__.py +27 -0
- provablyfine/api/model/audit_log.py +42 -0
- provablyfine/api/model/auth_config.py +77 -0
- provablyfine/api/model/bastion.py +146 -0
- provablyfine/api/model/boundary.py +110 -0
- provablyfine/api/model/denylist.py +21 -0
- provablyfine/api/model/grant.py +229 -0
- provablyfine/api/model/identity.py +131 -0
- provablyfine/api/model/identity_invitation_key.py +74 -0
- provablyfine/api/model/oidc_key.py +47 -0
- provablyfine/api/model/role.py +109 -0
- provablyfine/api/model/signing_key.py +38 -0
- provablyfine/api/model/utils.py +13 -0
- provablyfine/api/oauth2_providers.py +8 -0
- provablyfine/api/registry_db.py +63 -0
- provablyfine/api/responses.py +34 -0
- provablyfine/api/rotate.py +80 -0
- provablyfine/api/schemas/__init__.py +17 -0
- provablyfine/api/schemas/audit.py +20 -0
- provablyfine/api/schemas/auth.py +112 -0
- provablyfine/api/schemas/base.py +7 -0
- provablyfine/api/schemas/bastion.py +42 -0
- provablyfine/api/schemas/boundary.py +44 -0
- provablyfine/api/schemas/directory.py +33 -0
- provablyfine/api/schemas/grant.py +196 -0
- provablyfine/api/schemas/identity.py +99 -0
- provablyfine/api/schemas/jwk.py +34 -0
- provablyfine/api/schemas/problem.py +10 -0
- provablyfine/api/schemas/role.py +45 -0
- provablyfine/api/schemas/ssh.py +43 -0
- provablyfine/api/schemas/tag.py +30 -0
- provablyfine/api/schemas/tenant.py +28 -0
- provablyfine/api/server.py +65 -0
- provablyfine/api/signature.py +264 -0
- provablyfine/api/test_grant.py +579 -0
- provablyfine/base64url.py +20 -0
- provablyfine/bastion/__init__.py +3 -0
- provablyfine/bastion/app.py +179 -0
- provablyfine/bastion/atomic.py +28 -0
- provablyfine/bastion/control_app.py +68 -0
- provablyfine/bastion/exceptions.py +2 -0
- provablyfine/bastion/fdstore.py +82 -0
- provablyfine/bastion/http.py +204 -0
- provablyfine/bastion/relay.py +232 -0
- provablyfine/bastion/server.py +135 -0
- provablyfine/bastion/systemd.py +72 -0
- provablyfine/bastion/test_atomic.py +97 -0
- provablyfine/bastion/trusted_key.py +55 -0
- provablyfine/cli/__init__.py +0 -0
- provablyfine/cli/grant.py +38 -0
- provablyfine/cli/login.py +213 -0
- provablyfine/cli/pf/__init__.py +0 -0
- provablyfine/cli/pf/bastion_cli.py +274 -0
- provablyfine/cli/pf/dev_bastion_cli.py +55 -0
- provablyfine/cli/pf/main.py +147 -0
- provablyfine/cli/pf/openssh_cli.py +93 -0
- provablyfine/cli/pf/openssh_host_init.py +249 -0
- provablyfine/cli/pf/ssh_cli.py +200 -0
- provablyfine/cli/pfa/__init__.py +0 -0
- provablyfine/cli/pfa/audit_log_cli.py +56 -0
- provablyfine/cli/pfa/auth_cli.py +173 -0
- provablyfine/cli/pfa/bastion_cli.py +115 -0
- provablyfine/cli/pfa/boundary_cli.py +186 -0
- provablyfine/cli/pfa/grant_cli.py +278 -0
- provablyfine/cli/pfa/identity_cli.py +220 -0
- provablyfine/cli/pfa/main.py +149 -0
- provablyfine/cli/pfa/role_cli.py +200 -0
- provablyfine/cli/pfa/tag_cli.py +90 -0
- provablyfine/cli/pfa/tenant_cli.py +103 -0
- provablyfine/cli/yaml_utils.py +18 -0
- provablyfine/client/__init__.py +5 -0
- provablyfine/client/aio.py +311 -0
- provablyfine/client/configuration.py +31 -0
- provablyfine/client/exceptions.py +6 -0
- provablyfine/client/http_client.py +365 -0
- provablyfine/client/schemas.py +629 -0
- provablyfine/client/ssh_utils.py +30 -0
- provablyfine/client/sync.py +688 -0
- provablyfine/jwk.py +405 -0
- provablyfine/log.py +139 -0
- provablyfine/ssh/__init__.py +3 -0
- provablyfine/ssh/agent.py +143 -0
- provablyfine/ssh/buffer.py +94 -0
- provablyfine/ssh/cert.py +213 -0
- provablyfine/ssh/constants.py +7 -0
- provablyfine/ssh/exceptions.py +6 -0
- provablyfine/ssh/serde.py +143 -0
- provablyfine/ssh/test_buffer.py +47 -0
- provablyfine/ssh/test_serde.py +170 -0
- provablyfine/ssh/test_ssh_keygen.py +314 -0
- provablyfine/test_jwk.py +253 -0
- provablyfine/tui/__init__.py +0 -0
- provablyfine/tui/_utils.py +4 -0
- provablyfine/tui/app.py +74 -0
- provablyfine/tui/async_client.py +66 -0
- provablyfine/tui/audit_log_list.py +43 -0
- provablyfine/tui/auth_list.py +219 -0
- provablyfine/tui/auth_view.py +116 -0
- provablyfine/tui/auto_complete.py +75 -0
- provablyfine/tui/base.py +39 -0
- provablyfine/tui/bastion_list.py +134 -0
- provablyfine/tui/bastion_view.py +174 -0
- provablyfine/tui/boundary_list.py +118 -0
- provablyfine/tui/boundary_view.py +198 -0
- provablyfine/tui/checkbox_input.py +107 -0
- provablyfine/tui/clipboard.py +44 -0
- provablyfine/tui/grant_edit/__init__.py +24 -0
- provablyfine/tui/grant_edit/base.py +242 -0
- provablyfine/tui/grant_edit/boundary.py +77 -0
- provablyfine/tui/grant_edit/identity.py +128 -0
- provablyfine/tui/grant_edit/role.py +77 -0
- provablyfine/tui/grant_edit/screens.py +89 -0
- provablyfine/tui/grant_edit/ssh_command.py +53 -0
- provablyfine/tui/grant_edit/ssh_port_forward.py +43 -0
- provablyfine/tui/grant_edit/ssh_shell.py +57 -0
- provablyfine/tui/grant_edit/tag.py +66 -0
- provablyfine/tui/grant_edit/tenant.py +73 -0
- provablyfine/tui/grant_list.py +155 -0
- provablyfine/tui/header.py +72 -0
- provablyfine/tui/home.py +70 -0
- provablyfine/tui/identity_list.py +220 -0
- provablyfine/tui/identity_view.py +174 -0
- provablyfine/tui/member_list.py +49 -0
- provablyfine/tui/relogin.py +256 -0
- provablyfine/tui/role_list.py +117 -0
- provablyfine/tui/role_view.py +194 -0
- provablyfine/tui/setup.py +380 -0
- provablyfine/tui/tag_list.py +94 -0
- provablyfine/tui/tenant_list.py +101 -0
- provablyfine/tui/test_clipboard.py +30 -0
- provablyfine/tui/test_utils.py +17 -0
- provablyfine-0.1.0.dist-info/METADATA +133 -0
- provablyfine-0.1.0.dist-info/RECORD +180 -0
- provablyfine-0.1.0.dist-info/WHEEL +4 -0
- provablyfine-0.1.0.dist-info/entry_points.txt +5 -0
provablyfine/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import enum
|
|
3
|
+
import socket as _socket
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Shut(enum.IntEnum):
|
|
8
|
+
RD = _socket.SHUT_RD
|
|
9
|
+
WR = _socket.SHUT_WR
|
|
10
|
+
RDWR = _socket.SHUT_RDWR
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Socket(abc.ABC):
|
|
14
|
+
@abc.abstractmethod
|
|
15
|
+
def fileno(self) -> int:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@abc.abstractmethod
|
|
19
|
+
def detach(self) -> int:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abc.abstractmethod
|
|
23
|
+
async def send(self, data: bytes) -> int:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abc.abstractmethod
|
|
27
|
+
async def recv(self, n: int) -> bytes:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abc.abstractmethod
|
|
31
|
+
async def shutdown(self, flag: Shut) -> None:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abc.abstractmethod
|
|
35
|
+
def close(self) -> None:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@abc.abstractmethod
|
|
39
|
+
def getsockname(self) -> typing.Any:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
@abc.abstractmethod
|
|
43
|
+
async def bind(self, address: typing.Any) -> None:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abc.abstractmethod
|
|
47
|
+
async def listen(self, n: int) -> None:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@abc.abstractmethod
|
|
51
|
+
async def accept(self) -> tuple["Socket", typing.Any]:
|
|
52
|
+
pass
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Shared fixtures and helpers for anet tests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import collections.abc
|
|
7
|
+
import datetime
|
|
8
|
+
import ipaddress
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
import cryptography
|
|
12
|
+
import cryptography.hazmat.primitives.asymmetric.rsa as crypto_rsa
|
|
13
|
+
import cryptography.hazmat.primitives.hashes as crypto_hashes
|
|
14
|
+
import cryptography.hazmat.primitives.serialization as crypto_serialization
|
|
15
|
+
import cryptography.x509
|
|
16
|
+
import cryptography.x509.oid as crypto_oid
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from . import socket as anet_socket
|
|
20
|
+
from . import ssl as anet_ssl
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _create_server_context(
|
|
24
|
+
certfile: str | os.PathLike[str],
|
|
25
|
+
keyfile: str | os.PathLike[str],
|
|
26
|
+
) -> anet_ssl.SSLContext:
|
|
27
|
+
"""Create server-side SSL context from certificate and key files."""
|
|
28
|
+
ctx = anet_ssl.SSLContext(anet_ssl.ContextProtocol.SERVER)
|
|
29
|
+
ctx.load_cert_chain(str(certfile), str(keyfile))
|
|
30
|
+
return ctx
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _create_client_context(
|
|
34
|
+
cafile: str | os.PathLike[str],
|
|
35
|
+
check_hostname: bool = True,
|
|
36
|
+
) -> anet_ssl.SSLContext:
|
|
37
|
+
"""Create client-side SSL context with CA verification."""
|
|
38
|
+
ctx = anet_ssl.SSLContext(anet_ssl.ContextProtocol.CLIENT)
|
|
39
|
+
ctx.load_verify_locations(str(cafile))
|
|
40
|
+
ctx.check_hostname = check_hostname
|
|
41
|
+
ctx.verify_mode = anet_ssl.VerifyMode.REQUIRED
|
|
42
|
+
return ctx
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture(scope="session")
|
|
46
|
+
def ssl_contexts(tmp_path_factory: pytest.TempPathFactory) -> tuple[anet_ssl.SSLContext, anet_ssl.SSLContext]:
|
|
47
|
+
"""Generate self-signed CA and server certificate; return (server_ctx, client_ctx)."""
|
|
48
|
+
tmpdir = tmp_path_factory.mktemp("ssl")
|
|
49
|
+
|
|
50
|
+
# Generate CA key and cert
|
|
51
|
+
ca_key = crypto_rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
52
|
+
ca_subject = cryptography.x509.Name([cryptography.x509.NameAttribute(crypto_oid.NameOID.COMMON_NAME, "Test CA")])
|
|
53
|
+
now = datetime.datetime.now(datetime.UTC)
|
|
54
|
+
ca_cert = (
|
|
55
|
+
cryptography.x509.CertificateBuilder()
|
|
56
|
+
.subject_name(ca_subject)
|
|
57
|
+
.issuer_name(ca_subject)
|
|
58
|
+
.public_key(ca_key.public_key())
|
|
59
|
+
.serial_number(1)
|
|
60
|
+
.not_valid_before(now)
|
|
61
|
+
.not_valid_after(now + datetime.timedelta(days=365))
|
|
62
|
+
.add_extension(
|
|
63
|
+
cryptography.x509.BasicConstraints(ca=True, path_length=None),
|
|
64
|
+
critical=True,
|
|
65
|
+
)
|
|
66
|
+
.sign(ca_key, crypto_hashes.SHA256())
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Generate server key and cert
|
|
70
|
+
server_key = crypto_rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
71
|
+
server_subject = cryptography.x509.Name(
|
|
72
|
+
[cryptography.x509.NameAttribute(crypto_oid.NameOID.COMMON_NAME, "127.0.0.1")]
|
|
73
|
+
)
|
|
74
|
+
server_cert = (
|
|
75
|
+
cryptography.x509.CertificateBuilder()
|
|
76
|
+
.subject_name(server_subject)
|
|
77
|
+
.issuer_name(ca_subject)
|
|
78
|
+
.public_key(server_key.public_key())
|
|
79
|
+
.serial_number(2)
|
|
80
|
+
.not_valid_before(now)
|
|
81
|
+
.not_valid_after(now + datetime.timedelta(days=365))
|
|
82
|
+
.add_extension(
|
|
83
|
+
cryptography.x509.SubjectAlternativeName([cryptography.x509.IPAddress(ipaddress.IPv4Address("127.0.0.1"))]),
|
|
84
|
+
critical=False,
|
|
85
|
+
)
|
|
86
|
+
.sign(ca_key, crypto_hashes.SHA256())
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Write to temp files
|
|
90
|
+
ca_cert_file = tmpdir / "ca.pem"
|
|
91
|
+
server_cert_file = tmpdir / "server.pem"
|
|
92
|
+
server_key_file = tmpdir / "server.key"
|
|
93
|
+
|
|
94
|
+
ca_cert_file.write_bytes(ca_cert.public_bytes(crypto_serialization.Encoding.PEM))
|
|
95
|
+
server_cert_file.write_bytes(server_cert.public_bytes(crypto_serialization.Encoding.PEM))
|
|
96
|
+
server_key_file.write_bytes(
|
|
97
|
+
server_key.private_bytes(
|
|
98
|
+
encoding=crypto_serialization.Encoding.PEM,
|
|
99
|
+
format=crypto_serialization.PrivateFormat.PKCS8,
|
|
100
|
+
encryption_algorithm=crypto_serialization.NoEncryption(),
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Create SSL contexts
|
|
105
|
+
server_ctx = _create_server_context(server_cert_file, server_key_file)
|
|
106
|
+
client_ctx = _create_client_context(ca_cert_file, check_hostname=False)
|
|
107
|
+
|
|
108
|
+
return server_ctx, client_ctx
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.fixture
|
|
112
|
+
async def anet_socketpair() -> collections.abc.AsyncGenerator[tuple[anet_socket.Socket, anet_socket.Socket], None]:
|
|
113
|
+
"""Create a connected AF_UNIX socketpair wrapped in anet.socket.Socket."""
|
|
114
|
+
a, b = anet_socket.socketpair(anet_socket.Family.UNIX, anet_socket.Type.STREAM)
|
|
115
|
+
yield a, b
|
|
116
|
+
a.close()
|
|
117
|
+
b.close()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.fixture
|
|
121
|
+
async def tls_socketpair(
|
|
122
|
+
anet_socketpair: tuple[anet_socket.Socket, anet_socket.Socket],
|
|
123
|
+
ssl_contexts: tuple[anet_ssl.SSLContext, anet_ssl.SSLContext],
|
|
124
|
+
) -> collections.abc.AsyncGenerator[tuple[anet_ssl.Socket, anet_ssl.Socket], None]:
|
|
125
|
+
"""Create a TLS-wrapped socketpair with completed handshake."""
|
|
126
|
+
raw_client, raw_server = anet_socketpair
|
|
127
|
+
server_ctx, client_ctx = ssl_contexts
|
|
128
|
+
|
|
129
|
+
ssl_server = await server_ctx.wrap_socket(raw_server, server_side=True)
|
|
130
|
+
ssl_client = await client_ctx.wrap_socket(raw_client, server_side=False, server_hostname=None)
|
|
131
|
+
|
|
132
|
+
# Handshake must be concurrent
|
|
133
|
+
await asyncio.gather(ssl_server.handshake(), ssl_client.handshake())
|
|
134
|
+
|
|
135
|
+
yield ssl_client, ssl_server
|
|
136
|
+
ssl_client.close()
|
|
137
|
+
ssl_server.close()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from . import base, exceptions, stream
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclasses.dataclass
|
|
12
|
+
class Message:
|
|
13
|
+
start_line: str
|
|
14
|
+
headers: dict[str, str]
|
|
15
|
+
body: bytes
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
async def deserialize(cls, sock: base.Socket) -> Message:
|
|
19
|
+
reader = stream.Reader(sock)
|
|
20
|
+
try:
|
|
21
|
+
start_line = (await reader.read_until(b"\r\n"))[:-2].decode("ascii")
|
|
22
|
+
except EOFError:
|
|
23
|
+
raise exceptions.Error("Unable to read start line before connection was closed")
|
|
24
|
+
headers: dict[str, str] = {}
|
|
25
|
+
while True:
|
|
26
|
+
try:
|
|
27
|
+
line = (await reader.read_until(b"\r\n"))[:-2].decode("ascii")
|
|
28
|
+
except EOFError:
|
|
29
|
+
raise exceptions.Error("Unable to last line before connection was closed")
|
|
30
|
+
if line == "":
|
|
31
|
+
break
|
|
32
|
+
colon = line.find(":")
|
|
33
|
+
if colon == -1:
|
|
34
|
+
logger.warning(f"Invalid header: {line}")
|
|
35
|
+
raise exceptions.Error("Invalid header")
|
|
36
|
+
key = line[:colon].lower()
|
|
37
|
+
value = line[colon + 1 :].lstrip()
|
|
38
|
+
headers[key] = value
|
|
39
|
+
if "content-length" in headers:
|
|
40
|
+
length = headers["content-length"].strip()
|
|
41
|
+
if not length.isdigit():
|
|
42
|
+
logger.warning(f"Invalid Content-Length: {length}")
|
|
43
|
+
raise exceptions.Error("Invalid Content-Length")
|
|
44
|
+
body = await reader.read(int(length))
|
|
45
|
+
else:
|
|
46
|
+
# We do not support chunk encoding
|
|
47
|
+
body = b""
|
|
48
|
+
return Message(start_line=start_line, headers=headers, body=body)
|
|
49
|
+
|
|
50
|
+
async def serialize(self, sock: base.Socket) -> None:
|
|
51
|
+
lines = [self.start_line]
|
|
52
|
+
for name, value in self.headers.items():
|
|
53
|
+
lines.append(f"{name}: {value}")
|
|
54
|
+
lines.extend(["", ""])
|
|
55
|
+
data = b"\r\n".join(line.encode("ascii") for line in lines) + self.body
|
|
56
|
+
await sock.send(data)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclasses.dataclass
|
|
60
|
+
class Request:
|
|
61
|
+
method: str
|
|
62
|
+
resource_target: str
|
|
63
|
+
version: str
|
|
64
|
+
headers: dict[str, str]
|
|
65
|
+
body: bytes
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
async def deserialize(cls, sock: base.Socket) -> Request:
|
|
69
|
+
message = await Message.deserialize(sock)
|
|
70
|
+
start_line = message.start_line
|
|
71
|
+
space = start_line.find(" ")
|
|
72
|
+
if space == -1:
|
|
73
|
+
raise exceptions.Error(f"Invalid request start line: {message.start_line}")
|
|
74
|
+
method = start_line[:space]
|
|
75
|
+
start_line = start_line[space + 1 :]
|
|
76
|
+
space = start_line.find(" ")
|
|
77
|
+
if space == -1:
|
|
78
|
+
raise exceptions.Error(f"Invalid request start line: {message.start_line}")
|
|
79
|
+
resource_target = start_line[:space]
|
|
80
|
+
version = start_line[space + 1 :]
|
|
81
|
+
return Request(
|
|
82
|
+
method=method,
|
|
83
|
+
resource_target=resource_target,
|
|
84
|
+
version=version,
|
|
85
|
+
headers=message.headers,
|
|
86
|
+
body=message.body,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
async def serialize(self, sock: base.Socket) -> None:
|
|
90
|
+
start = f"{self.method} {self.resource_target} {self.version}"
|
|
91
|
+
message = Message(start_line=start, headers=self.headers, body=self.body)
|
|
92
|
+
await message.serialize(sock)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclasses.dataclass
|
|
96
|
+
class Response:
|
|
97
|
+
version: str
|
|
98
|
+
status_code: int
|
|
99
|
+
reason: str
|
|
100
|
+
headers: dict[str, str]
|
|
101
|
+
body: bytes
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
async def deserialize(cls, sock: base.Socket) -> Response:
|
|
105
|
+
message = await Message.deserialize(sock)
|
|
106
|
+
status_line = message.start_line
|
|
107
|
+
space1 = status_line.find(" ")
|
|
108
|
+
if space1 == -1:
|
|
109
|
+
raise exceptions.Error(f"Invalid status_line={status_line}")
|
|
110
|
+
version = status_line[:space1]
|
|
111
|
+
remainder = status_line[space1 + 1 :]
|
|
112
|
+
space2 = remainder.find(" ")
|
|
113
|
+
if space2 == -1:
|
|
114
|
+
raise exceptions.Error(f"Invalid status_line={status_line}")
|
|
115
|
+
status_code = remainder[:space2]
|
|
116
|
+
reason = remainder[space2 + 1 :]
|
|
117
|
+
if not status_code.isdigit():
|
|
118
|
+
raise exceptions.Error(f"Invalid status_code={status_code}")
|
|
119
|
+
return Response(
|
|
120
|
+
version=version,
|
|
121
|
+
status_code=int(status_code),
|
|
122
|
+
reason=reason,
|
|
123
|
+
headers=message.headers,
|
|
124
|
+
body=message.body,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
async def serialize(self, sock: base.Socket) -> None:
|
|
128
|
+
start = f"{self.version} {self.status_code} {self.reason}"
|
|
129
|
+
message = Message(start_line=start, headers=self.headers, body=self.body)
|
|
130
|
+
await message.serialize(sock)
|