http-fields 0.1.1__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.
- http_fields/__init__.py +193 -0
- http_fields/accept.py +37 -0
- http_fields/acceptcharset.py +48 -0
- http_fields/acceptencoding.py +42 -0
- http_fields/acceptlanguage.py +47 -0
- http_fields/acceptranges.py +35 -0
- http_fields/accesscontrol.py +197 -0
- http_fields/age.py +34 -0
- http_fields/allow.py +46 -0
- http_fields/altsvc.py +70 -0
- http_fields/authbases.py +79 -0
- http_fields/authenticationinfo.py +19 -0
- http_fields/authorization.py +23 -0
- http_fields/cachecontrol.py +119 -0
- http_fields/cachestatus.py +12 -0
- http_fields/connection.py +35 -0
- http_fields/contentdigest.py +19 -0
- http_fields/contentdisposition.py +66 -0
- http_fields/contentencoding.py +45 -0
- http_fields/contentlanguage.py +38 -0
- http_fields/contentlength.py +34 -0
- http_fields/contentlocation.py +24 -0
- http_fields/contentrange.py +74 -0
- http_fields/contenttype.py +104 -0
- http_fields/cookie.py +38 -0
- http_fields/date.py +17 -0
- http_fields/dateheader.py +32 -0
- http_fields/entitytaglist.py +39 -0
- http_fields/etag.py +46 -0
- http_fields/expect.py +52 -0
- http_fields/expires.py +17 -0
- http_fields/forwarded.py +36 -0
- http_fields/fromheader.py +42 -0
- http_fields/header.py +250 -0
- http_fields/host.py +44 -0
- http_fields/ifmatch.py +24 -0
- http_fields/ifmodifiedsince.py +17 -0
- http_fields/ifnonematch.py +24 -0
- http_fields/ifrange.py +49 -0
- http_fields/ifunmodifiedsince.py +17 -0
- http_fields/lastmodified.py +17 -0
- http_fields/link.py +35 -0
- http_fields/location.py +24 -0
- http_fields/maxforwards.py +32 -0
- http_fields/origin.py +42 -0
- http_fields/parsedobjs.py +62 -0
- http_fields/prefer.py +64 -0
- http_fields/priority.py +55 -0
- http_fields/productlistheader.py +33 -0
- http_fields/proxyauthenticate.py +17 -0
- http_fields/proxyauthenticationinfo.py +17 -0
- http_fields/proxyauthorization.py +17 -0
- http_fields/proxystatus.py +12 -0
- http_fields/py.typed +0 -0
- http_fields/range.py +37 -0
- http_fields/referer.py +24 -0
- http_fields/retryafter.py +45 -0
- http_fields/server.py +18 -0
- http_fields/setcookie.py +419 -0
- http_fields/stricttransportsecurity.py +96 -0
- http_fields/structuredfields.py +356 -0
- http_fields/structuredheaders.py +90 -0
- http_fields/te.py +36 -0
- http_fields/trailer.py +44 -0
- http_fields/upgrade.py +35 -0
- http_fields/uriheader.py +36 -0
- http_fields/useragent.py +18 -0
- http_fields/vary.py +40 -0
- http_fields/via.py +35 -0
- http_fields/visitors/__init__.py +1 -0
- http_fields/visitors/rfc3986.py +195 -0
- http_fields/visitors/rfc6265.py +62 -0
- http_fields/visitors/rfc6266.py +234 -0
- http_fields/visitors/rfc7230.py +80 -0
- http_fields/visitors/rfc7239.py +43 -0
- http_fields/visitors/rfc7240.py +77 -0
- http_fields/visitors/rfc7838.py +77 -0
- http_fields/visitors/rfc8288.py +56 -0
- http_fields/visitors/rfc9110/__init__.py +117 -0
- http_fields/visitors/rfc9110/_base.py +71 -0
- http_fields/visitors/rfc9110/accept.py +135 -0
- http_fields/visitors/rfc9110/acceptcharset.py +26 -0
- http_fields/visitors/rfc9110/acceptencoding.py +76 -0
- http_fields/visitors/rfc9110/acceptlanguage.py +55 -0
- http_fields/visitors/rfc9110/acceptranges.py +13 -0
- http_fields/visitors/rfc9110/allow.py +13 -0
- http_fields/visitors/rfc9110/authenticationinfo.py +19 -0
- http_fields/visitors/rfc9110/authorization.py +77 -0
- http_fields/visitors/rfc9110/authparam.py +40 -0
- http_fields/visitors/rfc9110/authscheme.py +17 -0
- http_fields/visitors/rfc9110/challenge.py +57 -0
- http_fields/visitors/rfc9110/comment.py +99 -0
- http_fields/visitors/rfc9110/connection.py +13 -0
- http_fields/visitors/rfc9110/contentlanguage.py +21 -0
- http_fields/visitors/rfc9110/contentlength.py +9 -0
- http_fields/visitors/rfc9110/contentrange.py +70 -0
- http_fields/visitors/rfc9110/contenttype.py +52 -0
- http_fields/visitors/rfc9110/date.py +12 -0
- http_fields/visitors/rfc9110/entitytag.py +66 -0
- http_fields/visitors/rfc9110/etag.py +12 -0
- http_fields/visitors/rfc9110/field.py +15 -0
- http_fields/visitors/rfc9110/host.py +32 -0
- http_fields/visitors/rfc9110/httpdate.py +167 -0
- http_fields/visitors/rfc9110/ifmatch.py +24 -0
- http_fields/visitors/rfc9110/ifmodifiedsince.py +12 -0
- http_fields/visitors/rfc9110/ifnonematch.py +26 -0
- http_fields/visitors/rfc9110/ifunmodifiedsince.py +12 -0
- http_fields/visitors/rfc9110/lastmodified.py +14 -0
- http_fields/visitors/rfc9110/location.py +9 -0
- http_fields/visitors/rfc9110/parameters.py +124 -0
- http_fields/visitors/rfc9110/product.py +39 -0
- http_fields/visitors/rfc9110/quotedstring.py +66 -0
- http_fields/visitors/rfc9110/range.py +100 -0
- http_fields/visitors/rfc9110/rangeunit.py +17 -0
- http_fields/visitors/rfc9110/retryafter.py +20 -0
- http_fields/visitors/rfc9110/server.py +16 -0
- http_fields/visitors/rfc9110/te.py +83 -0
- http_fields/visitors/rfc9110/token.py +18 -0
- http_fields/visitors/rfc9110/token68.py +18 -0
- http_fields/visitors/rfc9110/type.py +17 -0
- http_fields/visitors/rfc9110/upgrade.py +57 -0
- http_fields/visitors/rfc9110/useragent.py +16 -0
- http_fields/visitors/rfc9110/vary.py +10 -0
- http_fields/visitors/rfc9110/via.py +86 -0
- http_fields/visitors/rfc9110/weight.py +40 -0
- http_fields/visitors/rfc9110/wwwauthenticate.py +21 -0
- http_fields/visitors/rfc9111.py +92 -0
- http_fields/wwwauthenticate.py +24 -0
- http_fields-0.1.1.dist-info/METADATA +88 -0
- http_fields-0.1.1.dist-info/RECORD +132 -0
- http_fields-0.1.1.dist-info/WHEEL +4 -0
- http_fields-0.1.1.dist-info/licenses/LICENSE.txt +16 -0
http_fields/altsvc.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Alt-Svc and Alt-Used header classes."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
from abnf import Rule
|
|
7
|
+
from abnf.grammars import rfc7838
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from http_fields.header import Header
|
|
11
|
+
from http_fields.parsedobjs import ParsedStr
|
|
12
|
+
from http_fields.visitors.rfc7838 import AltSvcVisitor, AltValue
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AltUsedAuthority(ParsedStr):
|
|
16
|
+
"""An Alt-Used authority (the alternative ``host[:port]`` actually used). Self-validating."""
|
|
17
|
+
|
|
18
|
+
parser = rfc7838.Rule("Alt-Used")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class AltSvc(Header):
|
|
23
|
+
"""Alt-Svc header, as defined by RFC 7838. The special value ``clear`` is represented by
|
|
24
|
+
``clear=True`` with no ``values``."""
|
|
25
|
+
|
|
26
|
+
name: ClassVar[str] = "Alt-Svc"
|
|
27
|
+
rule: ClassVar[Rule] = rfc7838.Rule("Alt-Svc")
|
|
28
|
+
visitor: ClassVar[AltSvcVisitor] = AltSvcVisitor()
|
|
29
|
+
|
|
30
|
+
values: tuple[AltValue, ...] = ()
|
|
31
|
+
clear: bool = False
|
|
32
|
+
|
|
33
|
+
def __init__(self, *values: AltValue, clear: bool = False) -> None:
|
|
34
|
+
# Each AltValue self-validates (ProtocolId/AltAuthority/Param), so the field-type
|
|
35
|
+
# check fully guarantees a safe serialized value -- no re-parse.
|
|
36
|
+
object.__setattr__(self, "values", tuple(values))
|
|
37
|
+
object.__setattr__(self, "clear", clear)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def parse(cls, value: str) -> Self:
|
|
41
|
+
if value.strip().lower() == "clear":
|
|
42
|
+
cls._node(value) # validate
|
|
43
|
+
return cls(clear=True)
|
|
44
|
+
return cls(*cls.visitor.visit(cls._node(value)))
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def value(self) -> str:
|
|
48
|
+
return "clear" if self.clear else ", ".join(str(v) for v in self.values)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class AltUsed(Header):
|
|
53
|
+
"""Alt-Used header, as defined by RFC 7838: the alternative authority actually used."""
|
|
54
|
+
|
|
55
|
+
name: ClassVar[str] = "Alt-Used"
|
|
56
|
+
rule: ClassVar[Rule] = rfc7838.Rule("Alt-Used")
|
|
57
|
+
|
|
58
|
+
authority: AltUsedAuthority
|
|
59
|
+
|
|
60
|
+
def __init__(self, authority: str) -> None:
|
|
61
|
+
# The authority self-validates as a leaf; build from a string via AltUsed(...).
|
|
62
|
+
object.__setattr__(self, "authority", AltUsedAuthority(authority))
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def parse(cls, value: str) -> Self:
|
|
66
|
+
return cls(value)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def value(self) -> str:
|
|
70
|
+
return self.authority
|
http_fields/authbases.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Base classes for the authentication / authorization header families.
|
|
2
|
+
|
|
3
|
+
These pair a request/response header with its proxy counterpart:
|
|
4
|
+
``ChallengeListHeader`` (WWW-Authenticate / Proxy-Authenticate), ``CredentialsHeader``
|
|
5
|
+
(Authorization / Proxy-Authorization), and ``AuthParamsHeader`` (Authentication-Info /
|
|
6
|
+
Proxy-Authentication-Info). Concrete subclasses supply ``name``/``rule``/``visitor``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
|
|
12
|
+
from abnf import NodeVisitor
|
|
13
|
+
from typing_extensions import Self
|
|
14
|
+
|
|
15
|
+
from http_fields.header import Header
|
|
16
|
+
from http_fields.visitors.rfc9110 import (
|
|
17
|
+
AuthParam,
|
|
18
|
+
AuthParamChallenge,
|
|
19
|
+
AuthParamCredentials,
|
|
20
|
+
TokenChallenge,
|
|
21
|
+
TokenCredentials,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class ChallengeListHeader(Header):
|
|
27
|
+
"""Base for headers that are a list of authentication challenges."""
|
|
28
|
+
|
|
29
|
+
visitor: ClassVar[NodeVisitor]
|
|
30
|
+
|
|
31
|
+
challenges: tuple[TokenChallenge | AuthParamChallenge, ...]
|
|
32
|
+
|
|
33
|
+
def __init__(self, *challenges: TokenChallenge | AuthParamChallenge) -> None:
|
|
34
|
+
object.__setattr__(self, "challenges", tuple(challenges))
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def parse(cls, value: str) -> Self:
|
|
38
|
+
return cls(*cls.visitor.visit(cls._node(value)))
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def value(self) -> str:
|
|
42
|
+
return ", ".join(str(challenge) for challenge in self.challenges)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class CredentialsHeader(Header):
|
|
47
|
+
"""Base for headers carrying a single credentials value."""
|
|
48
|
+
|
|
49
|
+
visitor: ClassVar[NodeVisitor]
|
|
50
|
+
|
|
51
|
+
credentials: TokenCredentials | AuthParamCredentials
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def parse(cls, value: str) -> Self:
|
|
55
|
+
return cls(cls.visitor.visit(cls._node(value)))
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def value(self) -> str:
|
|
59
|
+
return str(self.credentials)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class AuthParamsHeader(Header):
|
|
64
|
+
"""Base for headers that are a list of auth-params."""
|
|
65
|
+
|
|
66
|
+
visitor: ClassVar[NodeVisitor]
|
|
67
|
+
|
|
68
|
+
auth_params: tuple[AuthParam, ...] = ()
|
|
69
|
+
|
|
70
|
+
def __init__(self, *auth_params: AuthParam) -> None:
|
|
71
|
+
object.__setattr__(self, "auth_params", tuple(auth_params))
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def parse(cls, value: str) -> Self:
|
|
75
|
+
return cls(*cls.visitor.visit(cls._node(value)))
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def value(self) -> str:
|
|
79
|
+
return ",".join(str(p) for p in self.auth_params)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Authentication-Info header class."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from abnf import Rule
|
|
6
|
+
from abnf.grammars import rfc9110
|
|
7
|
+
|
|
8
|
+
from http_fields.authbases import AuthParamsHeader
|
|
9
|
+
from http_fields.visitors.rfc9110 import AuthenticationInfoVisitor, AuthParam
|
|
10
|
+
|
|
11
|
+
__all__ = ["AuthenticationInfo", "AuthParam"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthenticationInfo(AuthParamsHeader):
|
|
15
|
+
"""Authentication-Info header, as defined by RFC 9110."""
|
|
16
|
+
|
|
17
|
+
name: ClassVar[str] = "authentication-info"
|
|
18
|
+
rule: ClassVar[Rule] = rfc9110.Rule("Authentication-Info")
|
|
19
|
+
visitor = AuthenticationInfoVisitor()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Authorization header class."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from abnf import Rule
|
|
6
|
+
from abnf.grammars import rfc9110
|
|
7
|
+
|
|
8
|
+
from http_fields.authbases import CredentialsHeader
|
|
9
|
+
from http_fields.visitors.rfc9110 import (
|
|
10
|
+
AuthorizationVisitor,
|
|
11
|
+
AuthParamCredentials,
|
|
12
|
+
TokenCredentials,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = ["AuthParamCredentials", "Authorization", "TokenCredentials"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Authorization(CredentialsHeader):
|
|
19
|
+
"""Authorization header, as defined by RFC 9110."""
|
|
20
|
+
|
|
21
|
+
name: ClassVar[str] = "Authorization"
|
|
22
|
+
rule: ClassVar[Rule] = rfc9110.Rule("Authorization")
|
|
23
|
+
visitor = AuthorizationVisitor()
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Cache-Control header class."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, ClassVar
|
|
5
|
+
|
|
6
|
+
from abnf import Rule
|
|
7
|
+
from abnf.grammars import rfc9111
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from http_fields.header import Header
|
|
11
|
+
from http_fields.parsedobjs import NonNegativeInt
|
|
12
|
+
from http_fields.visitors.rfc9111 import CacheControlVisitor, CacheDirective
|
|
13
|
+
|
|
14
|
+
# directive-name -> field-name maps, shared by parse() and value.
|
|
15
|
+
_BOOL = {
|
|
16
|
+
"immutable": "immutable",
|
|
17
|
+
"public": "public",
|
|
18
|
+
"no-store": "no_store",
|
|
19
|
+
"no-transform": "no_transform",
|
|
20
|
+
"must-revalidate": "must_revalidate",
|
|
21
|
+
"proxy-revalidate": "proxy_revalidate",
|
|
22
|
+
"only-if-cached": "only_if_cached",
|
|
23
|
+
}
|
|
24
|
+
_INT = {
|
|
25
|
+
"max-age": "max_age",
|
|
26
|
+
"max-stale": "max_stale",
|
|
27
|
+
"min-fresh": "min_fresh",
|
|
28
|
+
"s-maxage": "s_maxage",
|
|
29
|
+
}
|
|
30
|
+
_BOOL_OR_STR = {"no-cache": "no_cache", "private": "private"}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, kw_only=True)
|
|
34
|
+
class CacheControl(Header):
|
|
35
|
+
"""Cache-Control header, as defined by RFC 9111.
|
|
36
|
+
|
|
37
|
+
The ``no_cache`` and ``private`` directives may be ``True`` or a string of field names.
|
|
38
|
+
Non-standard directives are carried in ``cache_extension``.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: ClassVar[str] = "Cache-Control"
|
|
42
|
+
rule: ClassVar[Rule] = rfc9111.Rule("Cache-Control")
|
|
43
|
+
visitor: ClassVar[CacheControlVisitor] = CacheControlVisitor()
|
|
44
|
+
|
|
45
|
+
immutable: bool = False
|
|
46
|
+
public: bool = False
|
|
47
|
+
private: bool | str = False
|
|
48
|
+
no_cache: bool | str = False
|
|
49
|
+
no_store: bool = False
|
|
50
|
+
no_transform: bool = False
|
|
51
|
+
must_revalidate: bool = False
|
|
52
|
+
proxy_revalidate: bool = False
|
|
53
|
+
max_age: int | None = None
|
|
54
|
+
# max_stale is None when absent, True when valueless ("stale of any age",
|
|
55
|
+
# RFC 9111 section 5.2.1.2), or an int delta-seconds bound.
|
|
56
|
+
max_stale: int | bool | None = None
|
|
57
|
+
min_fresh: int | None = None
|
|
58
|
+
only_if_cached: bool = False
|
|
59
|
+
s_maxage: int | None = None
|
|
60
|
+
cache_extension: tuple[CacheDirective, ...] = ()
|
|
61
|
+
|
|
62
|
+
def __post_init__(self) -> None:
|
|
63
|
+
# fail-fast coercion of the int-valued directives (frozen -> setattr).
|
|
64
|
+
for attr in ("max_age", "min_fresh", "s_maxage"):
|
|
65
|
+
v = getattr(self, attr)
|
|
66
|
+
if v is not None:
|
|
67
|
+
object.__setattr__(self, attr, NonNegativeInt(v))
|
|
68
|
+
# max-stale may be valueless (True); only coerce a delta-seconds bound.
|
|
69
|
+
if self.max_stale is not None and self.max_stale is not True:
|
|
70
|
+
object.__setattr__(self, "max_stale", NonNegativeInt(self.max_stale))
|
|
71
|
+
# Enforce the declared field types (e.g. no-cache/private must be bool | str, not a
|
|
72
|
+
# list that CacheDirective would silently swallow as a valueless directive).
|
|
73
|
+
super().__post_init__()
|
|
74
|
+
# Serializing builds a self-validating CacheDirective for each field (name -> Token,
|
|
75
|
+
# no-cache/private string -> QuotedString, ints -> NonNegativeInt), rejecting invalid
|
|
76
|
+
# content, and caches the result; a valid serialization is grammar-valid by
|
|
77
|
+
# construction, so no re-parse is needed.
|
|
78
|
+
_ = self.value
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def parse(cls, value: str) -> Self:
|
|
82
|
+
kw: dict[str, Any] = {}
|
|
83
|
+
extension: list[CacheDirective] = []
|
|
84
|
+
for directive in cls.visitor.visit(cls._node(value)):
|
|
85
|
+
n = str(directive.name)
|
|
86
|
+
if n in _BOOL:
|
|
87
|
+
kw[_BOOL[n]] = True
|
|
88
|
+
elif n in _INT:
|
|
89
|
+
kw[_INT[n]] = directive.value
|
|
90
|
+
elif n in _BOOL_OR_STR:
|
|
91
|
+
kw[_BOOL_OR_STR[n]] = (
|
|
92
|
+
directive.value if isinstance(directive.value, str) else True
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
extension.append(directive)
|
|
96
|
+
return cls(**kw, cache_extension=tuple(extension))
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def value(self) -> str:
|
|
100
|
+
out: list[CacheDirective] = []
|
|
101
|
+
for n, attr in _BOOL.items():
|
|
102
|
+
if getattr(self, attr):
|
|
103
|
+
out.append(CacheDirective(n))
|
|
104
|
+
for n, attr in _INT.items():
|
|
105
|
+
v = getattr(self, attr)
|
|
106
|
+
if v is None:
|
|
107
|
+
continue
|
|
108
|
+
if attr == "max_stale" and v is True: # valueless max-stale
|
|
109
|
+
out.append(CacheDirective(n))
|
|
110
|
+
else: # keep 0, unlike a truthiness check
|
|
111
|
+
out.append(CacheDirective(n, v))
|
|
112
|
+
for n, attr in _BOOL_OR_STR.items():
|
|
113
|
+
v = getattr(self, attr)
|
|
114
|
+
if isinstance(v, str):
|
|
115
|
+
out.append(CacheDirective(n, v))
|
|
116
|
+
elif v:
|
|
117
|
+
out.append(CacheDirective(n))
|
|
118
|
+
out.extend(self.cache_extension)
|
|
119
|
+
return ",".join(str(directive) for directive in out)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Cache-Status header class."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from http_fields.structuredheaders import StructuredListHeader
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CacheStatus(StructuredListHeader):
|
|
9
|
+
"""Cache-Status header, as defined by RFC 9211. Each ``members`` item identifies a cache
|
|
10
|
+
(its bare value) and carries parameters such as ``hit``, ``fwd``, ``ttl``, and ``detail``."""
|
|
11
|
+
|
|
12
|
+
name: ClassVar[str] = "cache-status"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Connection header class."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
from abnf import Rule
|
|
7
|
+
from abnf.grammars import rfc9110
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from http_fields.header import Header
|
|
11
|
+
from http_fields.visitors.rfc9110 import ConnectionVisitor, Token
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class Connection(Header):
|
|
16
|
+
"""Connection header, as defined by RFC 9110."""
|
|
17
|
+
|
|
18
|
+
name: ClassVar[str] = "connection"
|
|
19
|
+
rule: ClassVar[Rule] = rfc9110.Rule("Connection")
|
|
20
|
+
visitor: ClassVar[ConnectionVisitor] = ConnectionVisitor()
|
|
21
|
+
|
|
22
|
+
directives: tuple[Token, ...]
|
|
23
|
+
|
|
24
|
+
def __init__(self, *directives: Token) -> None:
|
|
25
|
+
object.__setattr__(self, "directives", tuple(directives))
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def parse(cls, value: str) -> Self:
|
|
29
|
+
return cls(
|
|
30
|
+
*(Token(d, parse=False) for d in cls.visitor.visit(cls._node(value)))
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def value(self) -> str:
|
|
35
|
+
return ",".join(self.directives)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Content-Digest and Repr-Digest header classes."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from http_fields.structuredheaders import DigestHeader
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ContentDigest(DigestHeader):
|
|
9
|
+
"""Content-Digest header, as defined by RFC 9530: a Dictionary of algorithm name to the
|
|
10
|
+
byte-sequence digest of the message content."""
|
|
11
|
+
|
|
12
|
+
name: ClassVar[str] = "content-digest"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ReprDigest(DigestHeader):
|
|
16
|
+
"""Repr-Digest header, as defined by RFC 9530: like Content-Digest, but over the selected
|
|
17
|
+
representation."""
|
|
18
|
+
|
|
19
|
+
name: ClassVar[str] = "repr-digest"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Content-Disposition header class."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
from abnf import ParseError, Rule
|
|
7
|
+
from abnf.grammars import rfc6266
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from http_fields.header import Header
|
|
11
|
+
from http_fields.visitors.rfc6266 import (
|
|
12
|
+
ContentDispositionNodeVisitor,
|
|
13
|
+
DispExtParm,
|
|
14
|
+
DispositionType,
|
|
15
|
+
ExtValue,
|
|
16
|
+
FilenameParm,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class ContentDisposition(Header):
|
|
22
|
+
"""Content-Disposition header, as defined by RFC 6266."""
|
|
23
|
+
|
|
24
|
+
name: ClassVar[str] = "Content-Disposition"
|
|
25
|
+
rule: ClassVar[Rule] = rfc6266.Rule("content-disposition")
|
|
26
|
+
visitor: ClassVar[ContentDispositionNodeVisitor] = ContentDispositionNodeVisitor()
|
|
27
|
+
|
|
28
|
+
disposition_type: DispositionType
|
|
29
|
+
disposition_parms: tuple[FilenameParm | DispExtParm, ...] = ()
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def build(
|
|
33
|
+
cls,
|
|
34
|
+
disposition_type: str,
|
|
35
|
+
disposition_parms: dict[str, str | ExtValue] | None = None,
|
|
36
|
+
) -> Self:
|
|
37
|
+
"""Build a Content-Disposition from a disposition type and a parameter mapping.
|
|
38
|
+
|
|
39
|
+
A parameter named ``filename`` or ``filename*`` (case-insensitive) becomes a
|
|
40
|
+
FilenameParm; anything else becomes a DispExtParm.
|
|
41
|
+
"""
|
|
42
|
+
parms = tuple(
|
|
43
|
+
FilenameParm(pname, pvalue)
|
|
44
|
+
if pname.lower() in {"filename", "filename*"}
|
|
45
|
+
else DispExtParm(pname, pvalue)
|
|
46
|
+
for pname, pvalue in (disposition_parms or {}).items()
|
|
47
|
+
)
|
|
48
|
+
return cls(DispositionType(disposition_type), parms)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def parse(cls, value: str) -> Self:
|
|
52
|
+
# unlike most header rules, 'content-disposition' matches the whole header line, so
|
|
53
|
+
# we prepend the field name before parsing.
|
|
54
|
+
cls._check_length(value)
|
|
55
|
+
try:
|
|
56
|
+
node = cls.rule.parse_all(f"content-disposition: {value}")
|
|
57
|
+
except ParseError as exc:
|
|
58
|
+
raise ValueError(f'Invalid {cls.__name__} value "{value}".') from exc
|
|
59
|
+
disposition_type, parms = cls.visitor.visit(node)
|
|
60
|
+
return cls(disposition_type, tuple(parms))
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def value(self) -> str:
|
|
64
|
+
return str(self.disposition_type) + "".join(
|
|
65
|
+
f";{parm}" for parm in self.disposition_parms
|
|
66
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""ContentEncoding header class."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
from abnf import Rule
|
|
7
|
+
from abnf.grammars import rfc9110
|
|
8
|
+
from abnf.parser import Node, NodeVisitor
|
|
9
|
+
from typing_extensions import Self
|
|
10
|
+
|
|
11
|
+
from http_fields.header import Header
|
|
12
|
+
from http_fields.visitors.rfc9110 import Token
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ContentEncodingVisitor(NodeVisitor):
|
|
16
|
+
def visit_content_encoding(self, node: Node) -> list[str]:
|
|
17
|
+
return list(filter(None, map(self.visit, node.children)))
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def visit_content_coding(node: Node) -> str:
|
|
21
|
+
return node.value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class ContentEncoding(Header):
|
|
26
|
+
"""Content-Encoding header, as defined by RFC 9110."""
|
|
27
|
+
|
|
28
|
+
name: ClassVar[str] = "content-encoding"
|
|
29
|
+
rule: ClassVar[Rule] = rfc9110.Rule("Content-Encoding")
|
|
30
|
+
visitor: ClassVar[ContentEncodingVisitor] = ContentEncodingVisitor()
|
|
31
|
+
|
|
32
|
+
codings: tuple[Token, ...]
|
|
33
|
+
|
|
34
|
+
def __init__(self, *codings: Token) -> None:
|
|
35
|
+
object.__setattr__(self, "codings", tuple(codings))
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def parse(cls, value: str) -> Self:
|
|
39
|
+
return cls(
|
|
40
|
+
*(Token(c, parse=False) for c in cls.visitor.visit(cls._node(value)))
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def value(self) -> str:
|
|
45
|
+
return ", ".join(self.codings)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Content-Language header class."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
from abnf import Rule
|
|
7
|
+
from abnf.grammars import rfc9110
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from http_fields.header import Header
|
|
11
|
+
from http_fields.visitors.rfc9110.contentlanguage import (
|
|
12
|
+
ContentLanguageVisitor,
|
|
13
|
+
LanguageTag,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class ContentLanguage(Header):
|
|
19
|
+
"""Content-Language header, as defined by RFC 9110."""
|
|
20
|
+
|
|
21
|
+
name: ClassVar[str] = "content-language"
|
|
22
|
+
rule: ClassVar[Rule] = rfc9110.Rule("Content-Language")
|
|
23
|
+
visitor: ClassVar[ContentLanguageVisitor] = ContentLanguageVisitor()
|
|
24
|
+
|
|
25
|
+
languages: tuple[LanguageTag, ...]
|
|
26
|
+
|
|
27
|
+
def __init__(self, *languages: LanguageTag) -> None:
|
|
28
|
+
object.__setattr__(self, "languages", tuple(languages))
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def parse(cls, value: str) -> Self:
|
|
32
|
+
return cls(
|
|
33
|
+
*(LanguageTag(x, parse=False) for x in cls.visitor.visit(cls._node(value)))
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def value(self) -> str:
|
|
38
|
+
return ", ".join(self.languages)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""ContentLength header class."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
from abnf import Rule
|
|
7
|
+
from abnf.grammars import rfc9110
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from http_fields.header import Header
|
|
11
|
+
from http_fields.parsedobjs import NonNegativeInt
|
|
12
|
+
from http_fields.visitors.rfc9110 import ContentLengthVisitor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class ContentLength(Header):
|
|
17
|
+
"""Content-Length header."""
|
|
18
|
+
|
|
19
|
+
name: ClassVar[str] = "content-length"
|
|
20
|
+
rule: ClassVar[Rule] = rfc9110.Rule("Content-Length")
|
|
21
|
+
visitor: ClassVar[ContentLengthVisitor] = ContentLengthVisitor()
|
|
22
|
+
|
|
23
|
+
length: NonNegativeInt
|
|
24
|
+
|
|
25
|
+
def __init__(self, length: int) -> None:
|
|
26
|
+
object.__setattr__(self, "length", NonNegativeInt(length))
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def parse(cls, value: str) -> Self:
|
|
30
|
+
return cls(cls.visitor.visit(cls._node(value)))
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def value(self) -> str:
|
|
34
|
+
return str(self.length)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Content-Location header class."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from abnf import Rule
|
|
6
|
+
from abnf.grammars import rfc9110
|
|
7
|
+
|
|
8
|
+
from http_fields.parsedobjs import ParsedStr
|
|
9
|
+
from http_fields.uriheader import UriHeader
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ContentLocationUri(ParsedStr):
|
|
13
|
+
"""A Content-Location URI reference (RFC 9110). Self-validating."""
|
|
14
|
+
|
|
15
|
+
parser = rfc9110.Rule("Content-Location")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ContentLocation(UriHeader):
|
|
19
|
+
"""Content-Location header, as defined by RFC 9110."""
|
|
20
|
+
|
|
21
|
+
name: ClassVar[str] = "content-location"
|
|
22
|
+
rule: ClassVar[Rule] = rfc9110.Rule("Content-Location")
|
|
23
|
+
uri_type: ClassVar[type[ParsedStr]] = ContentLocationUri
|
|
24
|
+
uri: ContentLocationUri
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Content-Range header class."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
from abnf import Rule
|
|
7
|
+
from abnf.grammars import rfc9110
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from http_fields.header import Header
|
|
11
|
+
from http_fields.parsedobjs import NonNegativeInt
|
|
12
|
+
from http_fields.visitors.rfc9110 import ContentRangeVisitor, RangeUnit
|
|
13
|
+
from http_fields.visitors.rfc9110.contentrange import RangeResp
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class ContentRange(Header):
|
|
18
|
+
"""Content-Range header, as defined by RFC 9110."""
|
|
19
|
+
|
|
20
|
+
name: ClassVar[str] = "content-range"
|
|
21
|
+
rule: ClassVar[Rule] = rfc9110.Rule("Content-Range")
|
|
22
|
+
visitor: ClassVar[ContentRangeVisitor] = ContentRangeVisitor()
|
|
23
|
+
|
|
24
|
+
range_unit: RangeUnit
|
|
25
|
+
first_pos: NonNegativeInt | None
|
|
26
|
+
last_pos: NonNegativeInt | None
|
|
27
|
+
complete_length: NonNegativeInt | None
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
range_unit: str,
|
|
32
|
+
*,
|
|
33
|
+
first_pos: int | None = None,
|
|
34
|
+
last_pos: int | None = None,
|
|
35
|
+
complete_length: int | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
if (first_pos is None) != (last_pos is None):
|
|
38
|
+
raise ValueError(
|
|
39
|
+
"Both first_pos and last_pos must be given, or both omitted."
|
|
40
|
+
)
|
|
41
|
+
if first_pos is None and complete_length is None:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"complete_length is required when first_pos and last_pos are omitted."
|
|
44
|
+
)
|
|
45
|
+
object.__setattr__(self, "range_unit", RangeUnit(range_unit))
|
|
46
|
+
object.__setattr__(
|
|
47
|
+
self, "first_pos", None if first_pos is None else NonNegativeInt(first_pos)
|
|
48
|
+
)
|
|
49
|
+
object.__setattr__(
|
|
50
|
+
self, "last_pos", None if last_pos is None else NonNegativeInt(last_pos)
|
|
51
|
+
)
|
|
52
|
+
object.__setattr__(
|
|
53
|
+
self,
|
|
54
|
+
"complete_length",
|
|
55
|
+
None if complete_length is None else NonNegativeInt(complete_length),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def parse(cls, value: str) -> Self:
|
|
60
|
+
range_unit, rng = cls.visitor.visit(cls._node(value))
|
|
61
|
+
satisfied = isinstance(rng, RangeResp)
|
|
62
|
+
return cls(
|
|
63
|
+
range_unit,
|
|
64
|
+
first_pos=rng.first_pos if satisfied else None,
|
|
65
|
+
last_pos=rng.last_pos if satisfied else None,
|
|
66
|
+
complete_length=rng.complete_length,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def value(self) -> str:
|
|
71
|
+
if self.first_pos is not None and self.last_pos is not None:
|
|
72
|
+
length = self.complete_length if self.complete_length is not None else "*"
|
|
73
|
+
return f"{self.range_unit} {self.first_pos}-{self.last_pos}/{length}"
|
|
74
|
+
return f"{self.range_unit} */{self.complete_length}"
|