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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from abnf import Node, NodeVisitor
|
|
4
|
+
from abnf.grammars import rfc7838
|
|
5
|
+
|
|
6
|
+
from http_fields.parsedobjs import ParsedStr
|
|
7
|
+
from http_fields.visitors.rfc9110.parameters import (
|
|
8
|
+
Param,
|
|
9
|
+
ParamsInput,
|
|
10
|
+
as_params,
|
|
11
|
+
param_from_node,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = ["AltAuthority", "AltSvcVisitor", "AltValue", "ProtocolId"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ProtocolId(ParsedStr):
|
|
18
|
+
"""An RFC 7838 protocol-id (a percent-encoded ALPN protocol name). Self-validating."""
|
|
19
|
+
|
|
20
|
+
parser = rfc7838.Rule("protocol-id")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AltAuthority(ParsedStr):
|
|
24
|
+
"""An RFC 7838 alt-authority: a quoted ``host:port`` (the enclosing quotes are part of
|
|
25
|
+
the value). Self-validating."""
|
|
26
|
+
|
|
27
|
+
parser = rfc7838.Rule("alt-authority")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class AltValue:
|
|
32
|
+
"""One RFC 7838 alt-value: a protocol-id, an alt-authority, and parameters."""
|
|
33
|
+
|
|
34
|
+
protocol_id: ProtocolId
|
|
35
|
+
authority: AltAuthority
|
|
36
|
+
params: tuple[Param, ...] = ()
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
protocol_id: str,
|
|
41
|
+
authority: str,
|
|
42
|
+
params: ParamsInput = (),
|
|
43
|
+
) -> None:
|
|
44
|
+
# Each part self-validates; an existing leaf/Param passes through unchanged.
|
|
45
|
+
object.__setattr__(self, "protocol_id", ProtocolId(protocol_id))
|
|
46
|
+
object.__setattr__(self, "authority", AltAuthority(authority))
|
|
47
|
+
object.__setattr__(self, "params", as_params(params))
|
|
48
|
+
|
|
49
|
+
def __str__(self) -> str:
|
|
50
|
+
out = f"{self.protocol_id}={self.authority}"
|
|
51
|
+
for p in self.params:
|
|
52
|
+
out += f"; {p}"
|
|
53
|
+
return out
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AltSvcVisitor(NodeVisitor):
|
|
57
|
+
@staticmethod
|
|
58
|
+
def visit_alternative(node: Node) -> tuple[str, str]:
|
|
59
|
+
# protocol-id "=" alt-authority
|
|
60
|
+
return (node.children[0].value, node.children[-1].value)
|
|
61
|
+
|
|
62
|
+
def visit_alt_value(self, node: Node) -> AltValue:
|
|
63
|
+
protocol_id = authority = ""
|
|
64
|
+
params: list[Param] = []
|
|
65
|
+
for child in node.children:
|
|
66
|
+
if child.name == "alternative":
|
|
67
|
+
protocol_id, authority = self.visit_alternative(child)
|
|
68
|
+
elif child.name == "parameter":
|
|
69
|
+
params.append(param_from_node(child))
|
|
70
|
+
return AltValue(
|
|
71
|
+
ProtocolId(protocol_id, parse=False),
|
|
72
|
+
AltAuthority(authority, parse=False),
|
|
73
|
+
params,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def visit_alt_svc(self, node: Node) -> list[AltValue]:
|
|
77
|
+
return [self.visit_alt_value(c) for c in node.children if c.name == "alt-value"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from abnf import Node, NodeVisitor
|
|
4
|
+
from abnf.grammars import rfc8288
|
|
5
|
+
|
|
6
|
+
from http_fields.parsedobjs import ParsedStr
|
|
7
|
+
from http_fields.visitors.rfc9110.parameters import (
|
|
8
|
+
Param,
|
|
9
|
+
ParamsInput,
|
|
10
|
+
as_params,
|
|
11
|
+
param_from_node,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = ["LinkValue", "LinkVisitor", "URIReference"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class URIReference(ParsedStr):
|
|
18
|
+
"""An RFC 3986 URI-Reference, as used by RFC 8288's link target. Self-validating."""
|
|
19
|
+
|
|
20
|
+
parser = rfc8288.Rule("URI-Reference")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class LinkValue:
|
|
25
|
+
"""One RFC 8288 link-value: a target URI reference plus link-params."""
|
|
26
|
+
|
|
27
|
+
target: URIReference
|
|
28
|
+
params: tuple[Param, ...] = ()
|
|
29
|
+
|
|
30
|
+
def __init__(self, target: str, params: ParamsInput = ()) -> None:
|
|
31
|
+
# target and each Param self-validate; an existing leaf/Param passes through.
|
|
32
|
+
object.__setattr__(self, "target", URIReference(target))
|
|
33
|
+
object.__setattr__(self, "params", as_params(params))
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
out = f"<{self.target}>"
|
|
37
|
+
for p in self.params:
|
|
38
|
+
out += f"; {p}"
|
|
39
|
+
return out
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class LinkVisitor(NodeVisitor):
|
|
43
|
+
def visit_link_value(self, node: Node) -> LinkValue:
|
|
44
|
+
target = ""
|
|
45
|
+
params: list[Param] = []
|
|
46
|
+
for child in node.children:
|
|
47
|
+
if child.name == "URI-Reference":
|
|
48
|
+
target = child.value
|
|
49
|
+
elif child.name == "link-param":
|
|
50
|
+
params.append(param_from_node(child))
|
|
51
|
+
return LinkValue(URIReference(target, parse=False), params)
|
|
52
|
+
|
|
53
|
+
def visit_link(self, node: Node) -> list[LinkValue]:
|
|
54
|
+
return [
|
|
55
|
+
self.visit_link_value(c) for c in node.children if c.name == "link-value"
|
|
56
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Visitors and dataclasses from RFC9110."""
|
|
2
|
+
|
|
3
|
+
# There are many classes and functions, so manageabilty has led to
|
|
4
|
+
# many modules,
|
|
5
|
+
|
|
6
|
+
from http_fields.visitors.rfc9110._base import imf_fixdate
|
|
7
|
+
from http_fields.visitors.rfc9110.accept import AcceptType, AcceptVisitor
|
|
8
|
+
from http_fields.visitors.rfc9110.acceptcharset import AcceptCharsetVisitor
|
|
9
|
+
from http_fields.visitors.rfc9110.acceptencoding import (
|
|
10
|
+
AcceptEncodingVisitor,
|
|
11
|
+
WeightedCoding,
|
|
12
|
+
)
|
|
13
|
+
from http_fields.visitors.rfc9110.acceptlanguage import (
|
|
14
|
+
AcceptLanguageVisitor,
|
|
15
|
+
WeightedLanguageRange,
|
|
16
|
+
)
|
|
17
|
+
from http_fields.visitors.rfc9110.acceptranges import AcceptRangesVisitor
|
|
18
|
+
from http_fields.visitors.rfc9110.allow import AllowVisitor
|
|
19
|
+
from http_fields.visitors.rfc9110.authenticationinfo import (
|
|
20
|
+
AuthenticationInfoVisitor,
|
|
21
|
+
ProxyAuthenticationInfoVisitor,
|
|
22
|
+
)
|
|
23
|
+
from http_fields.visitors.rfc9110.authorization import (
|
|
24
|
+
AuthorizationVisitor,
|
|
25
|
+
AuthParamCredentials,
|
|
26
|
+
ProxyAuthorizationVisitor,
|
|
27
|
+
TokenCredentials,
|
|
28
|
+
)
|
|
29
|
+
from http_fields.visitors.rfc9110.authparam import AuthParam
|
|
30
|
+
from http_fields.visitors.rfc9110.challenge import AuthParamChallenge, TokenChallenge
|
|
31
|
+
from http_fields.visitors.rfc9110.comment import Comment
|
|
32
|
+
from http_fields.visitors.rfc9110.connection import ConnectionVisitor
|
|
33
|
+
from http_fields.visitors.rfc9110.contentlength import ContentLengthVisitor
|
|
34
|
+
from http_fields.visitors.rfc9110.contentrange import ContentRangeVisitor
|
|
35
|
+
from http_fields.visitors.rfc9110.contenttype import ContentTypeVisitor, MediaType
|
|
36
|
+
from http_fields.visitors.rfc9110.date import DateVisitor
|
|
37
|
+
from http_fields.visitors.rfc9110.entitytag import EntityTag
|
|
38
|
+
from http_fields.visitors.rfc9110.etag import ETagVisitor
|
|
39
|
+
from http_fields.visitors.rfc9110.field import FieldName, FieldValue
|
|
40
|
+
from http_fields.visitors.rfc9110.host import Hostname, HostVisitor
|
|
41
|
+
from http_fields.visitors.rfc9110.httpdate import HttpDateVisitor
|
|
42
|
+
from http_fields.visitors.rfc9110.ifmatch import IfMatchVisitor
|
|
43
|
+
from http_fields.visitors.rfc9110.ifmodifiedsince import IfModifiedSinceVisitor
|
|
44
|
+
from http_fields.visitors.rfc9110.ifnonematch import IfNoneMatchVisitor
|
|
45
|
+
from http_fields.visitors.rfc9110.ifunmodifiedsince import IfUnmodifiedSinceVisitor
|
|
46
|
+
from http_fields.visitors.rfc9110.lastmodified import LastModifiedVisitor
|
|
47
|
+
from http_fields.visitors.rfc9110.location import LocationVisitor
|
|
48
|
+
from http_fields.visitors.rfc9110.parameters import Parameter
|
|
49
|
+
from http_fields.visitors.rfc9110.product import Product
|
|
50
|
+
from http_fields.visitors.rfc9110.quotedstring import QuotedString, QuotedStringVisitor
|
|
51
|
+
from http_fields.visitors.rfc9110.rangeunit import RangeUnit
|
|
52
|
+
from http_fields.visitors.rfc9110.retryafter import RetryAfterVisitor
|
|
53
|
+
from http_fields.visitors.rfc9110.server import ServerVisitor
|
|
54
|
+
from http_fields.visitors.rfc9110.token import Token, TokenVisitor
|
|
55
|
+
from http_fields.visitors.rfc9110.useragent import UserAgentVisitor
|
|
56
|
+
from http_fields.visitors.rfc9110.vary import VaryVisitor
|
|
57
|
+
from http_fields.visitors.rfc9110.weight import Weight
|
|
58
|
+
from http_fields.visitors.rfc9110.wwwauthenticate import (
|
|
59
|
+
ProxyAuthenticateVisitor,
|
|
60
|
+
WWWAuthenticateVisitor,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"imf_fixdate",
|
|
65
|
+
"AcceptCharsetVisitor",
|
|
66
|
+
"AcceptEncodingVisitor",
|
|
67
|
+
"AcceptLanguageVisitor",
|
|
68
|
+
"AcceptRangesVisitor",
|
|
69
|
+
"AcceptType",
|
|
70
|
+
"AcceptVisitor",
|
|
71
|
+
"AllowVisitor",
|
|
72
|
+
"AuthenticationInfoVisitor",
|
|
73
|
+
"AuthorizationVisitor",
|
|
74
|
+
"AuthParam",
|
|
75
|
+
"AuthParamCredentials",
|
|
76
|
+
"AuthParamChallenge",
|
|
77
|
+
"Comment",
|
|
78
|
+
"ConnectionVisitor",
|
|
79
|
+
"ContentLengthVisitor",
|
|
80
|
+
"ContentRangeVisitor",
|
|
81
|
+
"ContentTypeVisitor",
|
|
82
|
+
"DateVisitor",
|
|
83
|
+
"EntityTag",
|
|
84
|
+
"ETagVisitor",
|
|
85
|
+
"FieldName",
|
|
86
|
+
"FieldValue",
|
|
87
|
+
"HostVisitor",
|
|
88
|
+
"Hostname",
|
|
89
|
+
"HttpDateVisitor",
|
|
90
|
+
"IfMatchVisitor",
|
|
91
|
+
"IfModifiedSinceVisitor",
|
|
92
|
+
"IfNoneMatchVisitor",
|
|
93
|
+
"IfUnmodifiedSinceVisitor",
|
|
94
|
+
"LastModifiedVisitor",
|
|
95
|
+
"LocationVisitor",
|
|
96
|
+
"MediaType",
|
|
97
|
+
"Parameter",
|
|
98
|
+
"Product",
|
|
99
|
+
"ProxyAuthenticateVisitor",
|
|
100
|
+
"ProxyAuthenticationInfoVisitor",
|
|
101
|
+
"ProxyAuthorizationVisitor",
|
|
102
|
+
"QuotedString",
|
|
103
|
+
"QuotedStringVisitor",
|
|
104
|
+
"RangeUnit",
|
|
105
|
+
"RetryAfterVisitor",
|
|
106
|
+
"ServerVisitor",
|
|
107
|
+
"Token",
|
|
108
|
+
"TokenChallenge",
|
|
109
|
+
"TokenCredentials",
|
|
110
|
+
"TokenVisitor",
|
|
111
|
+
"UserAgentVisitor",
|
|
112
|
+
"VaryVisitor",
|
|
113
|
+
"Weight",
|
|
114
|
+
"WeightedCoding",
|
|
115
|
+
"WeightedLanguageRange",
|
|
116
|
+
"WWWAuthenticateVisitor",
|
|
117
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""A few functions used here and there in this package."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import TypeVar, cast
|
|
6
|
+
|
|
7
|
+
S = TypeVar("S")
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def transform(
|
|
12
|
+
items: Iterator[S | T], type_s: type[S], type_t: type[T]
|
|
13
|
+
) -> Iterator[tuple[S, T | None]]:
|
|
14
|
+
"""Pair each ``type_s`` item with the optional ``type_t`` item that follows it.
|
|
15
|
+
|
|
16
|
+
Iterative (not recursive) so an arbitrarily long stream — e.g. a very long
|
|
17
|
+
``Accept`` header — cannot exhaust the call stack.
|
|
18
|
+
"""
|
|
19
|
+
pending: S | None = None
|
|
20
|
+
for item in items:
|
|
21
|
+
if isinstance(item, type_t):
|
|
22
|
+
if pending is None: # pragma: no cover - a type_t with no preceding type_s
|
|
23
|
+
raise AssertionError()
|
|
24
|
+
yield (pending, cast(T, item))
|
|
25
|
+
pending = None
|
|
26
|
+
elif isinstance(item, type_s):
|
|
27
|
+
if pending is not None:
|
|
28
|
+
yield (pending, None)
|
|
29
|
+
pending = item
|
|
30
|
+
else: # pragma: no cover
|
|
31
|
+
raise AssertionError()
|
|
32
|
+
if pending is not None:
|
|
33
|
+
yield (pending, None)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def imf_fixdate(pydate: datetime):
|
|
37
|
+
"""
|
|
38
|
+
Converts a datetime object to a fixdate string.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# HTTP dates are expressed in GMT/UTC. Convert tz-aware datetimes to UTC so the
|
|
42
|
+
# printed wall-clock fields match the "GMT" label; naive datetimes are assumed
|
|
43
|
+
# to already be UTC.
|
|
44
|
+
if pydate.tzinfo is not None:
|
|
45
|
+
pydate = pydate.astimezone(timezone.utc)
|
|
46
|
+
|
|
47
|
+
day_name = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
|
48
|
+
month_name = [
|
|
49
|
+
"Jan",
|
|
50
|
+
"Feb",
|
|
51
|
+
"Mar",
|
|
52
|
+
"Apr",
|
|
53
|
+
"May",
|
|
54
|
+
"Jun",
|
|
55
|
+
"Jul",
|
|
56
|
+
"Aug",
|
|
57
|
+
"Sep",
|
|
58
|
+
"Oct",
|
|
59
|
+
"Nov",
|
|
60
|
+
"Dec",
|
|
61
|
+
]
|
|
62
|
+
fixdate_fmt = "%s, %02d %s %04d %02d:%02d:%02d GMT"
|
|
63
|
+
return fixdate_fmt % (
|
|
64
|
+
day_name[pydate.weekday()],
|
|
65
|
+
pydate.day,
|
|
66
|
+
month_name[pydate.month - 1],
|
|
67
|
+
pydate.year,
|
|
68
|
+
pydate.hour,
|
|
69
|
+
pydate.minute,
|
|
70
|
+
pydate.second,
|
|
71
|
+
)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from abnf import Node, NodeVisitor
|
|
6
|
+
|
|
7
|
+
import http_fields.visitors.rfc9110._base as base
|
|
8
|
+
import http_fields.visitors.rfc9110.parameters as parameters
|
|
9
|
+
import http_fields.visitors.rfc9110.token as token
|
|
10
|
+
import http_fields.visitors.rfc9110.type as type_grammar
|
|
11
|
+
import http_fields.visitors.rfc9110.weight as weight
|
|
12
|
+
|
|
13
|
+
# NB: do not alias the "type" grammar module as `type` -- it would shadow the
|
|
14
|
+
# builtin, which a frozen dataclass's generated __setattr__ (`type(self)`) needs.
|
|
15
|
+
Parameter = parameters.Parameter
|
|
16
|
+
ParametersVisitor = parameters.ParametersVisitor
|
|
17
|
+
SubtypeVisitor = type_grammar.SubtypeVisitor
|
|
18
|
+
Token = token.Token
|
|
19
|
+
TypeVisitor = type_grammar.TypeVisitor
|
|
20
|
+
Weight = weight.Weight
|
|
21
|
+
WeightVisitor = weight.WeightVisitor
|
|
22
|
+
as_qvalue = weight.as_qvalue
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class MediaRange:
|
|
27
|
+
type: Token
|
|
28
|
+
subtype: Token
|
|
29
|
+
params: tuple[Parameter, ...]
|
|
30
|
+
|
|
31
|
+
def __init__(self, type: str, subtype: str, params: list[Parameter] | None = None):
|
|
32
|
+
object.__setattr__(self, "type", Token(type))
|
|
33
|
+
object.__setattr__(self, "subtype", Token(subtype))
|
|
34
|
+
object.__setattr__(self, "params", tuple(params) if params else ())
|
|
35
|
+
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
return f"{self.type}/{self.subtype}" + (
|
|
38
|
+
(";" + ";".join(str(p) for p in self.params)) if self.params else ""
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MediaRangeVisitor(NodeVisitor):
|
|
43
|
+
visit_type = TypeVisitor()
|
|
44
|
+
visit_subtype = SubtypeVisitor()
|
|
45
|
+
visit_parameters = ParametersVisitor()
|
|
46
|
+
|
|
47
|
+
def visit_media_range(self, node: Node) -> MediaRange:
|
|
48
|
+
items = filter(None, map(self.visit, node.children))
|
|
49
|
+
item = next(items)
|
|
50
|
+
if item == "*/*":
|
|
51
|
+
type, subtype = tuple([Token(x) for x in item.split("/")])
|
|
52
|
+
# and anything else must be a param.
|
|
53
|
+
else:
|
|
54
|
+
type = item
|
|
55
|
+
item = next(items)
|
|
56
|
+
subtype = Token("*") if item == "/*" else item
|
|
57
|
+
params: list[Parameter] = next(items, [])
|
|
58
|
+
return MediaRange(type, subtype, params)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def visit_literal(node: Node):
|
|
62
|
+
return node.value if node.value in ["*/*", "/*"] else None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class AcceptType:
|
|
67
|
+
type: Token
|
|
68
|
+
subtype: Token
|
|
69
|
+
params: tuple[Parameter, ...] = ()
|
|
70
|
+
weight: Weight | None = None
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
type: str,
|
|
75
|
+
subtype: str,
|
|
76
|
+
*,
|
|
77
|
+
params: list[tuple[str, str] | Parameter] | None = None,
|
|
78
|
+
weight: float | Weight | None = None,
|
|
79
|
+
) -> None:
|
|
80
|
+
_params = (
|
|
81
|
+
[Parameter(*p) if isinstance(p, tuple) else p for p in params]
|
|
82
|
+
if params
|
|
83
|
+
else []
|
|
84
|
+
)
|
|
85
|
+
resolved_weight: Weight | None
|
|
86
|
+
if isinstance(weight, (float, Weight)):
|
|
87
|
+
resolved_weight = (
|
|
88
|
+
Weight(qvalue=weight) if isinstance(weight, float) else weight
|
|
89
|
+
)
|
|
90
|
+
resolved_params = tuple(_params)
|
|
91
|
+
elif _params:
|
|
92
|
+
# sometimes, weight was captured by parser as a parameter because abnf
|
|
93
|
+
# backtracking is somewhat arbitrary. A trailing "q" parameter is a weight
|
|
94
|
+
# only if its value is an unquoted, in-range qvalue (0..1); a quoted or
|
|
95
|
+
# out-of-range "q" is an ordinary parameter.
|
|
96
|
+
w = _params[-1]
|
|
97
|
+
qvalue = as_qvalue(w.value) if w.name == "q" else None
|
|
98
|
+
if qvalue is not None:
|
|
99
|
+
resolved_weight = Weight(qvalue=qvalue)
|
|
100
|
+
resolved_params = tuple(_params[:-1])
|
|
101
|
+
else:
|
|
102
|
+
resolved_weight = None
|
|
103
|
+
resolved_params = tuple(_params)
|
|
104
|
+
else:
|
|
105
|
+
resolved_weight = None
|
|
106
|
+
resolved_params = tuple(_params)
|
|
107
|
+
object.__setattr__(self, "type", Token(type))
|
|
108
|
+
object.__setattr__(self, "subtype", Token(subtype))
|
|
109
|
+
object.__setattr__(self, "params", resolved_params)
|
|
110
|
+
object.__setattr__(self, "weight", resolved_weight)
|
|
111
|
+
|
|
112
|
+
def __str__(self):
|
|
113
|
+
return (
|
|
114
|
+
f"{self.type}/{self.subtype}"
|
|
115
|
+
+ ((";" + ";".join(str(p) for p in self.params)) if self.params else "")
|
|
116
|
+
+ (";" + str(self.weight) if self.weight else "")
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AcceptVisitor(NodeVisitor):
|
|
121
|
+
visit_media_range = MediaRangeVisitor()
|
|
122
|
+
visit_weight = WeightVisitor()
|
|
123
|
+
|
|
124
|
+
def visit_accept(self, node: Node):
|
|
125
|
+
items = filter(None, map(self.visit, node.children))
|
|
126
|
+
accept_types = [
|
|
127
|
+
AcceptType(
|
|
128
|
+
media_range.type,
|
|
129
|
+
media_range.subtype,
|
|
130
|
+
params=media_range.params,
|
|
131
|
+
weight=weight,
|
|
132
|
+
)
|
|
133
|
+
for media_range, weight in base.transform(items, MediaRange, Weight)
|
|
134
|
+
]
|
|
135
|
+
return accept_types
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from abnf import Node, NodeVisitor
|
|
2
|
+
|
|
3
|
+
import http_fields.visitors.rfc9110._base as base
|
|
4
|
+
import http_fields.visitors.rfc9110.token as token
|
|
5
|
+
import http_fields.visitors.rfc9110.weight as weight
|
|
6
|
+
|
|
7
|
+
Token = token.Token
|
|
8
|
+
TokenVisitor = token.TokenVisitor
|
|
9
|
+
Weight = weight.Weight
|
|
10
|
+
WeightVisitor = weight.WeightVisitor
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AcceptCharsetVisitor(NodeVisitor):
|
|
14
|
+
visit_token = TokenVisitor()
|
|
15
|
+
visit_weight = WeightVisitor()
|
|
16
|
+
|
|
17
|
+
def visit_accept_charset(self, node: Node):
|
|
18
|
+
items = filter(None, map(self.visit, node.children))
|
|
19
|
+
charsets = list(base.transform(items, Token, Weight))
|
|
20
|
+
return charsets
|
|
21
|
+
|
|
22
|
+
# the accept-charset value "*" is matched by the RFC 9110 rule 'token', so
|
|
23
|
+
# the parser will usually recognize it as a token in the alternation 'token / "*".
|
|
24
|
+
# we have this method just in case, but skip testing it.
|
|
25
|
+
def visit_literal(self, node: Node): # pragma: no cover
|
|
26
|
+
return Token("*") if node.value == "*" else None
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from abnf import Node
|
|
6
|
+
from abnf import NodeVisitor as _NodeVisitor
|
|
7
|
+
from abnf.grammars import rfc9110
|
|
8
|
+
|
|
9
|
+
import http_fields.visitors.rfc9110._base as base
|
|
10
|
+
import http_fields.visitors.rfc9110.token as token
|
|
11
|
+
import http_fields.visitors.rfc9110.weight as weight
|
|
12
|
+
from http_fields.parsedobjs import ParsedStr
|
|
13
|
+
|
|
14
|
+
Token = token.Token
|
|
15
|
+
TokenVisitor = token.TokenVisitor
|
|
16
|
+
Weight = weight.Weight
|
|
17
|
+
WeightVisitor = weight.WeightVisitor
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class NodeVisitor(_NodeVisitor):
|
|
21
|
+
def visit_children(self, node: Node, *, f: Callable[[Any], bool] | None = None):
|
|
22
|
+
for child in node.children:
|
|
23
|
+
result = self.visit(child)
|
|
24
|
+
if (f and f(result)) or result is not None:
|
|
25
|
+
yield result
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Coding(ParsedStr):
|
|
29
|
+
"""Represents an RFC 9110 codings thing."""
|
|
30
|
+
|
|
31
|
+
parser = rfc9110.Rule("codings")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class WeightedCoding:
|
|
36
|
+
coding: Coding
|
|
37
|
+
weight: Weight | None
|
|
38
|
+
|
|
39
|
+
def __init__(self, coding: str, weight: float | (Weight | None) = None):
|
|
40
|
+
object.__setattr__(self, "coding", Coding(coding))
|
|
41
|
+
if isinstance(weight, Weight):
|
|
42
|
+
object.__setattr__(self, "weight", weight)
|
|
43
|
+
elif isinstance(weight, (int, float)) and not isinstance(weight, bool):
|
|
44
|
+
# Accept any real number, including 0 ("not acceptable"); a bare
|
|
45
|
+
# None means no weight was specified.
|
|
46
|
+
object.__setattr__(self, "weight", Weight(float(weight)))
|
|
47
|
+
else:
|
|
48
|
+
object.__setattr__(self, "weight", None)
|
|
49
|
+
|
|
50
|
+
def __str__(self):
|
|
51
|
+
return str(self.coding) + (f";{str(self.weight)}" if self.weight else "")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AcceptEncodingVisitor(NodeVisitor):
|
|
55
|
+
visit_weight = WeightVisitor()
|
|
56
|
+
visit_token = TokenVisitor()
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def visit_literal(node: Node):
|
|
60
|
+
return node.value if node.value in {"identity", "*"} else None
|
|
61
|
+
|
|
62
|
+
def visit_content_coding(self, node: Node):
|
|
63
|
+
item: Token = next(filter(None, map(self.visit, node.children)))
|
|
64
|
+
return Coding(item)
|
|
65
|
+
|
|
66
|
+
def visit_codings(self, node: Node):
|
|
67
|
+
item: Coding = next(filter(None, map(self.visit, node.children)))
|
|
68
|
+
return item
|
|
69
|
+
|
|
70
|
+
def visit_accept_encoding(self, node: Node):
|
|
71
|
+
# items = filter(None, map(self.visit, node.children))
|
|
72
|
+
items = self.visit_children(node)
|
|
73
|
+
return [
|
|
74
|
+
WeightedCoding(enc, weight)
|
|
75
|
+
for enc, weight in base.transform(items, Coding, Weight)
|
|
76
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from abnf import Node, NodeVisitor
|
|
4
|
+
from abnf.grammars import rfc9110
|
|
5
|
+
|
|
6
|
+
import http_fields.visitors.rfc9110._base as base
|
|
7
|
+
import http_fields.visitors.rfc9110.token as token
|
|
8
|
+
import http_fields.visitors.rfc9110.weight as weight
|
|
9
|
+
from http_fields.parsedobjs import ParsedStr
|
|
10
|
+
|
|
11
|
+
__all__ = ["WeightedLanguageRange", "AcceptLanguageVisitor"]
|
|
12
|
+
|
|
13
|
+
Token = token.Token
|
|
14
|
+
Weight = weight.Weight
|
|
15
|
+
WeightVisitor = weight.WeightVisitor
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LanguageRange(ParsedStr):
|
|
19
|
+
"""Represents an RFC 9110 language-range."""
|
|
20
|
+
|
|
21
|
+
parser = rfc9110.Rule("language-range")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class WeightedLanguageRange:
|
|
26
|
+
language_range: LanguageRange
|
|
27
|
+
weight: Weight | None
|
|
28
|
+
|
|
29
|
+
def __init__(self, language_range: str, weight: float | Weight | None = None):
|
|
30
|
+
object.__setattr__(self, "language_range", LanguageRange(language_range))
|
|
31
|
+
if isinstance(weight, Weight):
|
|
32
|
+
object.__setattr__(self, "weight", weight)
|
|
33
|
+
elif isinstance(weight, (int, float)) and not isinstance(weight, bool):
|
|
34
|
+
# a numeric 0 is a valid weight ("not acceptable"); only None means unset.
|
|
35
|
+
object.__setattr__(self, "weight", Weight(float(weight)))
|
|
36
|
+
else:
|
|
37
|
+
object.__setattr__(self, "weight", None)
|
|
38
|
+
|
|
39
|
+
def __str__(self) -> str:
|
|
40
|
+
return str(self.language_range) + (f";{self.weight}" if self.weight else "")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AcceptLanguageVisitor(NodeVisitor):
|
|
44
|
+
visit_weight = WeightVisitor()
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def visit_language_range(node: Node):
|
|
48
|
+
return LanguageRange(node.value)
|
|
49
|
+
|
|
50
|
+
def visit_accept_language(self, node: Node):
|
|
51
|
+
items = filter(None, map(self.visit, node.children))
|
|
52
|
+
return [
|
|
53
|
+
WeightedLanguageRange(enc, weight)
|
|
54
|
+
for enc, weight in base.transform(items, LanguageRange, weight.Weight)
|
|
55
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from abnf import Node, NodeVisitor
|
|
2
|
+
|
|
3
|
+
import http_fields.visitors.rfc9110.rangeunit as rangeunit
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AcceptRangesVisitor(NodeVisitor):
|
|
7
|
+
visit_range_unit = rangeunit.RangeUnitVisitor()
|
|
8
|
+
|
|
9
|
+
def visit_acceptable_ranges(self, node: Node) -> list[rangeunit.RangeUnit]:
|
|
10
|
+
return list(filter(None, map(self.visit, node.children)))
|
|
11
|
+
|
|
12
|
+
def visit_accept_ranges(self, node: Node) -> list[rangeunit.RangeUnit]:
|
|
13
|
+
return next(filter(None, map(self.visit, node.children)))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from abnf import Node, NodeVisitor
|
|
2
|
+
|
|
3
|
+
import http_fields.visitors.rfc9110.token as token
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AllowVisitor(NodeVisitor):
|
|
7
|
+
visit_token = token.TokenVisitor()
|
|
8
|
+
|
|
9
|
+
def visit_allow(self, node: Node) -> list[token.Token]:
|
|
10
|
+
return list(filter(None, map(self.visit, node.children)))
|
|
11
|
+
|
|
12
|
+
def visit_method(self, node: Node) -> token.Token:
|
|
13
|
+
return next(filter(None, map(self.visit, node.children)))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from abnf import Node, NodeVisitor
|
|
2
|
+
|
|
3
|
+
from http_fields.visitors.rfc9110.authparam import AuthParam, AuthParamVisitor
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AuthenticationInfoVisitor(NodeVisitor):
|
|
7
|
+
visit_auth_param = AuthParamVisitor()
|
|
8
|
+
|
|
9
|
+
def visit_authentication_info(self, node: Node) -> list[AuthParam]:
|
|
10
|
+
items = filter(None, map(self.visit, node.children))
|
|
11
|
+
return list(items)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProxyAuthenticationInfoVisitor(NodeVisitor):
|
|
15
|
+
visit_auth_param = AuthParamVisitor()
|
|
16
|
+
|
|
17
|
+
def visit_proxy_authentication_info(self, node: Node) -> list[AuthParam]:
|
|
18
|
+
items = filter(None, map(self.visit, node.children))
|
|
19
|
+
return list(items)
|