ruyihttp 0.8.1__py3-none-win_amd64.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.
- ruyihttp/__init__.py +98 -0
- ruyihttp/_connect.py +109 -0
- ruyihttp/_firefox.py +267 -0
- ruyihttp/_http2.py +548 -0
- ruyihttp/_http3.py +503 -0
- ruyihttp/_nss.py +378 -0
- ruyihttp/_retry.py +39 -0
- ruyihttp/_socks.py +57 -0
- ruyihttp/_transport.py +1234 -0
- ruyihttp/adapters.py +9 -0
- ruyihttp/api.py +40 -0
- ruyihttp/firefox-profile.json +49 -0
- ruyihttp/firefox-root-constraints.json +3 -0
- ruyihttp/firefox-roots.pem +2991 -0
- ruyihttp/firefox-suffixes.dat +16501 -0
- ruyihttp/freebl3.dll +0 -0
- ruyihttp/licenses/README.md +29 -0
- ruyihttp/licenses/firefox-license.html +6470 -0
- ruyihttp/licenses/firefox-notice.txt +9 -0
- ruyihttp/licenses/mozilla-runtime-data.txt +21 -0
- ruyihttp/licenses/rust-aho-corasick-1.1.0-COPYING.txt +3 -0
- ruyihttp/licenses/rust-aho-corasick-1.1.0-LICENSE-MIT.txt +21 -0
- ruyihttp/licenses/rust-aho-corasick-1.1.0-UNLICENSE.txt +24 -0
- ruyihttp/licenses/rust-base64-0.22.1-LICENSE-APACHE.txt +201 -0
- ruyihttp/licenses/rust-base64-0.22.1-LICENSE-MIT.txt +21 -0
- ruyihttp/licenses/rust-bindgen-0.73.1-LICENSE.txt +29 -0
- ruyihttp/licenses/rust-bitflags-2.10.0-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-block-buffer-0.10.3-LICENSE-APACHE.txt +201 -0
- ruyihttp/licenses/rust-block-buffer-0.10.3-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-bytes-1.11.1-LICENSE.txt +25 -0
- ruyihttp/licenses/rust-cexpr-0.6.0-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-cfg-if-1.0.0-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-cfg_aliases-0.2.2-LICENSE.txt +9 -0
- ruyihttp/licenses/rust-clang-sys-1.7.0-LICENSE.txt +202 -0
- ruyihttp/licenses/rust-cpufeatures-0.2.8-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-crypto-common-0.1.6-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-darling-0.20.10-LICENSE.txt +21 -0
- ruyihttp/licenses/rust-dependencies.json +2052 -0
- ruyihttp/licenses/rust-digest-0.10.7-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-Apache-2.0.txt +73 -0
- ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-CC0-1.0.txt +121 -0
- ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-MIT.txt +9 -0
- ruyihttp/licenses/rust-enumset-1.1.2-LICENSE-MIT.txt +26 -0
- ruyihttp/licenses/rust-env_logger-0.10.0-LICENSE-MIT.txt +23 -0
- ruyihttp/licenses/rust-equivalent-1.0.1-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-fnv-1.0.7-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-generic-array-0.14.6-LICENSE.txt +21 -0
- ruyihttp/licenses/rust-hashbrown-0.17.1-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-heck-0.5.0-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-hex-0.4.3-LICENSE-APACHE.txt +202 -0
- ruyihttp/licenses/rust-hex-0.4.3-LICENSE-MIT.txt +20 -0
- ruyihttp/licenses/rust-http-1.4.0-LICENSE-APACHE.txt +201 -0
- ruyihttp/licenses/rust-http-1.4.0-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-ident_case-1.0.1-LICENSE.txt +19 -0
- ruyihttp/licenses/rust-indexmap-2.11.4-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-itoa-1.0.15-LICENSE-APACHE.txt +176 -0
- ruyihttp/licenses/rust-libc-0.2.186-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-libloading-0.8.6-LICENSE.txt +12 -0
- ruyihttp/licenses/rust-minimal-lexical-0.2.1-LICENSE-APACHE.txt +201 -0
- ruyihttp/licenses/rust-minimal-lexical-0.2.1-LICENSE.md.txt +37 -0
- ruyihttp/licenses/rust-mtu-0.4.1-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-nom-7.1.3-LICENSE.txt +20 -0
- ruyihttp/licenses/rust-pkcs11-bindings-0.1.8-LICENSE.txt +21 -0
- ruyihttp/licenses/rust-qlog-0.16.0-COPYING.txt +23 -0
- ruyihttp/licenses/rust-quinn-udp-0.6.0-LICENSE-APACHE.txt +201 -0
- ruyihttp/licenses/rust-quinn-udp-0.6.0-LICENSE-MIT.txt +7 -0
- ruyihttp/licenses/rust-regex-syntax-0.8.5-src-unicode_tables-LICENSE-UNICODE.txt +57 -0
- ruyihttp/licenses/rust-rustc-hash-2.1.1-LICENSE-APACHE.txt +176 -0
- ruyihttp/licenses/rust-rustc-hash-2.1.1-LICENSE-MIT.txt +23 -0
- ruyihttp/licenses/rust-serde_spanned-1.0.3-LICENSE-MIT.txt +19 -0
- ruyihttp/licenses/rust-serde_with-3.12.0-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-sfv-0.14.0-LICENSE-MIT.txt +27 -0
- ruyihttp/licenses/rust-sha1-0.10.5-LICENSE-MIT.txt +27 -0
- ruyihttp/licenses/rust-shlex-2.0.1-LICENSE-APACHE.txt +13 -0
- ruyihttp/licenses/rust-shlex-2.0.1-LICENSE-MIT.txt +21 -0
- ruyihttp/licenses/rust-smallvec-1.15.2-LICENSE-MIT.txt +25 -0
- ruyihttp/licenses/rust-static_assertions-1.1.0-LICENSE-MIT.txt +21 -0
- ruyihttp/licenses/rust-strsim-0.11.1-LICENSE.txt +23 -0
- ruyihttp/licenses/rust-strum-0.27.1-LICENSE.txt +21 -0
- ruyihttp/licenses/rust-typenum-1.16.0-LICENSE-APACHE.txt +201 -0
- ruyihttp/licenses/rust-typenum-1.16.0-LICENSE-MIT.txt +21 -0
- ruyihttp/licenses/rust-typenum-1.16.0-LICENSE.txt +1 -0
- ruyihttp/licenses/rust-unicode-ident-1.0.6-LICENSE-UNICODE.txt +46 -0
- ruyihttp/licenses/rust-version_check-0.9.4-LICENSE-MIT.txt +19 -0
- ruyihttp/licenses/rust-windows-0.62.2-license-apache-2.0.txt +201 -0
- ruyihttp/licenses/rust-windows-0.62.2-license-mit.txt +21 -0
- ruyihttp/licenses/rust-winnow-0.7.13-LICENSE-MIT.txt +18 -0
- ruyihttp/licenses/rust-zeroize-1.8.1-LICENSE-MIT.txt +21 -0
- ruyihttp/licenses/rust-zeroize_derive-1.4.2-LICENSE-MIT.txt +21 -0
- ruyihttp/mozglue.dll +0 -0
- ruyihttp/necko_core.dll +0 -0
- ruyihttp/necko_quic.dll +0 -0
- ruyihttp/nss3.dll +0 -0
- ruyihttp/runtime-manifest.json +16 -0
- ruyihttp/sessions.py +83 -0
- ruyihttp/softokn3.dll +0 -0
- ruyihttp-0.8.1.dist-info/METADATA +273 -0
- ruyihttp-0.8.1.dist-info/RECORD +175 -0
- ruyihttp-0.8.1.dist-info/WHEEL +5 -0
- ruyihttp-0.8.1.dist-info/licenses/LICENSE +373 -0
- ruyihttp-0.8.1.dist-info/licenses/THIRD_PARTY_NOTICES.md +36 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/README.md +29 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/firefox-license.html +6470 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/firefox-notice.txt +9 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/mozilla-runtime-data.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-aho-corasick-1.1.0-COPYING.txt +3 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-aho-corasick-1.1.0-LICENSE-MIT.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-aho-corasick-1.1.0-UNLICENSE.txt +24 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-base64-0.22.1-LICENSE-APACHE.txt +201 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-base64-0.22.1-LICENSE-MIT.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-bindgen-0.73.1-LICENSE.txt +29 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-bitflags-2.10.0-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-block-buffer-0.10.3-LICENSE-APACHE.txt +201 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-block-buffer-0.10.3-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-bytes-1.11.1-LICENSE.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-cexpr-0.6.0-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-cfg-if-1.0.0-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-cfg_aliases-0.2.2-LICENSE.txt +9 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-clang-sys-1.7.0-LICENSE.txt +202 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-cpufeatures-0.2.8-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-crypto-common-0.1.6-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-darling-0.20.10-LICENSE.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-dependencies.json +2052 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-digest-0.10.7-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-Apache-2.0.txt +73 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-CC0-1.0.txt +121 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-MIT.txt +9 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-enumset-1.1.2-LICENSE-MIT.txt +26 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-env_logger-0.10.0-LICENSE-MIT.txt +23 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-equivalent-1.0.1-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-fnv-1.0.7-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-generic-array-0.14.6-LICENSE.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-hashbrown-0.17.1-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-heck-0.5.0-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-hex-0.4.3-LICENSE-APACHE.txt +202 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-hex-0.4.3-LICENSE-MIT.txt +20 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-http-1.4.0-LICENSE-APACHE.txt +201 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-http-1.4.0-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-ident_case-1.0.1-LICENSE.txt +19 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-indexmap-2.11.4-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-itoa-1.0.15-LICENSE-APACHE.txt +176 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-libc-0.2.186-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-libloading-0.8.6-LICENSE.txt +12 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-minimal-lexical-0.2.1-LICENSE-APACHE.txt +201 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-minimal-lexical-0.2.1-LICENSE.md.txt +37 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-mtu-0.4.1-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-nom-7.1.3-LICENSE.txt +20 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-pkcs11-bindings-0.1.8-LICENSE.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-qlog-0.16.0-COPYING.txt +23 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-quinn-udp-0.6.0-LICENSE-APACHE.txt +201 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-quinn-udp-0.6.0-LICENSE-MIT.txt +7 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-regex-syntax-0.8.5-src-unicode_tables-LICENSE-UNICODE.txt +57 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-rustc-hash-2.1.1-LICENSE-APACHE.txt +176 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-rustc-hash-2.1.1-LICENSE-MIT.txt +23 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-serde_spanned-1.0.3-LICENSE-MIT.txt +19 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-serde_with-3.12.0-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-sfv-0.14.0-LICENSE-MIT.txt +27 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-sha1-0.10.5-LICENSE-MIT.txt +27 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-shlex-2.0.1-LICENSE-APACHE.txt +13 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-shlex-2.0.1-LICENSE-MIT.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-smallvec-1.15.2-LICENSE-MIT.txt +25 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-static_assertions-1.1.0-LICENSE-MIT.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-strsim-0.11.1-LICENSE.txt +23 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-strum-0.27.1-LICENSE.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-typenum-1.16.0-LICENSE-APACHE.txt +201 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-typenum-1.16.0-LICENSE-MIT.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-typenum-1.16.0-LICENSE.txt +1 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-unicode-ident-1.0.6-LICENSE-UNICODE.txt +46 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-version_check-0.9.4-LICENSE-MIT.txt +19 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-windows-0.62.2-license-apache-2.0.txt +201 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-windows-0.62.2-license-mit.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-winnow-0.7.13-LICENSE-MIT.txt +18 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-zeroize-1.8.1-LICENSE-MIT.txt +21 -0
- ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-zeroize_derive-1.4.2-LICENSE-MIT.txt +21 -0
- ruyihttp-0.8.1.dist-info/top_level.txt +1 -0
ruyihttp/_http2.py
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
import queue
|
|
6
|
+
import ssl
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from email.message import Message
|
|
10
|
+
from urllib.parse import urlsplit
|
|
11
|
+
|
|
12
|
+
from h2.config import H2Configuration
|
|
13
|
+
from h2.connection import H2Connection
|
|
14
|
+
from h2.events import (
|
|
15
|
+
ConnectionTerminated,
|
|
16
|
+
DataReceived,
|
|
17
|
+
InformationalResponseReceived,
|
|
18
|
+
RemoteSettingsChanged,
|
|
19
|
+
ResponseReceived,
|
|
20
|
+
StreamEnded,
|
|
21
|
+
StreamReset,
|
|
22
|
+
TrailersReceived,
|
|
23
|
+
UnknownFrameReceived,
|
|
24
|
+
)
|
|
25
|
+
from h2.exceptions import H2Error
|
|
26
|
+
from h2.settings import SettingCodes, Settings
|
|
27
|
+
from hpack import Encoder
|
|
28
|
+
from hpack.hpack import INDEX_INCREMENTAL, INDEX_NEVER
|
|
29
|
+
from urllib3._collections import HTTPHeaderDict
|
|
30
|
+
from urllib3.exceptions import ProtocolError
|
|
31
|
+
|
|
32
|
+
from ._http3 import _Call
|
|
33
|
+
from ._retry import Http1Required, Rejected, Reset
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FirefoxEncoder(Encoder):
|
|
37
|
+
@Encoder.header_table_size.setter
|
|
38
|
+
def header_table_size(self, value):
|
|
39
|
+
self.header_table.maxsize = value
|
|
40
|
+
self.header_table.resized = True
|
|
41
|
+
minimum = (
|
|
42
|
+
min(value, *self.table_size_changes) if self.table_size_changes else value
|
|
43
|
+
)
|
|
44
|
+
self.table_size_changes = [minimum, value] if minimum < value else [value]
|
|
45
|
+
|
|
46
|
+
def add(self, to_add, sensitive, huffman=False):
|
|
47
|
+
name, value = to_add
|
|
48
|
+
never = name == b"authorization" or name == b"cookie" and len(value) < 20
|
|
49
|
+
plain = (
|
|
50
|
+
name == b":path"
|
|
51
|
+
or len(name) + len(value) + 32 > self.header_table_size // 2
|
|
52
|
+
or self.header_table_size < 128
|
|
53
|
+
)
|
|
54
|
+
entries = (*self.header_table.STATIC_TABLE, *self.header_table.dynamic_entries)
|
|
55
|
+
reference = 0
|
|
56
|
+
matched = None
|
|
57
|
+
for index, (entry_name, entry_value) in enumerate(entries, 1):
|
|
58
|
+
if entry_name == name:
|
|
59
|
+
reference = index
|
|
60
|
+
if entry_value == value:
|
|
61
|
+
matched = index
|
|
62
|
+
break
|
|
63
|
+
if matched and not never and not plain:
|
|
64
|
+
return self._encode_indexed(matched)
|
|
65
|
+
mode = INDEX_NEVER if never else b"\x00" if plain else INDEX_INCREMENTAL
|
|
66
|
+
encoded = (
|
|
67
|
+
self._encode_indexed_literal(reference, value, mode, True)
|
|
68
|
+
if reference
|
|
69
|
+
else self._encode_literal(name, value, mode, True)
|
|
70
|
+
)
|
|
71
|
+
if not never and not plain:
|
|
72
|
+
self.header_table.add(name, value)
|
|
73
|
+
return encoded
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class _Protocol(H2Connection):
|
|
77
|
+
def _receive_goaway_frame(self, frame):
|
|
78
|
+
event = ConnectionTerminated()
|
|
79
|
+
event.error_code = frame.error_code
|
|
80
|
+
event.last_stream_id = frame.last_stream_id
|
|
81
|
+
event.additional_data = frame.additional_data
|
|
82
|
+
return [], [event]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Connection:
|
|
86
|
+
def __init__(self, transport):
|
|
87
|
+
self.transport = transport
|
|
88
|
+
self.transport.enable_nonblocking()
|
|
89
|
+
self._output = bytearray()
|
|
90
|
+
self._write_buffer = b""
|
|
91
|
+
self._tls_pending = False
|
|
92
|
+
self.protocol = _Protocol(
|
|
93
|
+
H2Configuration(client_side=True, header_encoding=None)
|
|
94
|
+
)
|
|
95
|
+
self.protocol.encoder = FirefoxEncoder()
|
|
96
|
+
self.protocol.local_settings = Settings(
|
|
97
|
+
client=True,
|
|
98
|
+
initial_values={
|
|
99
|
+
SettingCodes.HEADER_TABLE_SIZE: 65536,
|
|
100
|
+
SettingCodes.ENABLE_PUSH: 0,
|
|
101
|
+
SettingCodes.INITIAL_WINDOW_SIZE: 131072,
|
|
102
|
+
SettingCodes.MAX_FRAME_SIZE: 16384,
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
# hyper-h2 adds ENABLE_CONNECT_PROTOCOL by default; Firefox omits it.
|
|
106
|
+
del self.protocol.local_settings[SettingCodes.ENABLE_CONNECT_PROTOCOL]
|
|
107
|
+
self.protocol.decoder.max_allowed_table_size = 65536
|
|
108
|
+
self.protocol.initiate_connection()
|
|
109
|
+
self.protocol.increment_flow_control_window(12517377)
|
|
110
|
+
self.streams = {}
|
|
111
|
+
self.closed = False
|
|
112
|
+
self.goaway = False
|
|
113
|
+
self.goaway_error = 0
|
|
114
|
+
self.error = None
|
|
115
|
+
self.received_settings = False
|
|
116
|
+
self.origins = None
|
|
117
|
+
self.commands = queue.Queue()
|
|
118
|
+
self._command_lock = threading.Lock()
|
|
119
|
+
self.pending = []
|
|
120
|
+
self.wake = threading.Event()
|
|
121
|
+
self._stopping = threading.Event()
|
|
122
|
+
self._thread = threading.Thread(
|
|
123
|
+
target=self._run, name="necko-http2", daemon=True
|
|
124
|
+
)
|
|
125
|
+
self._thread.start()
|
|
126
|
+
|
|
127
|
+
def submit(self, operation, exchange, **arguments):
|
|
128
|
+
call = _Call(operation, exchange, exchange.timeout, **arguments)
|
|
129
|
+
with self._command_lock:
|
|
130
|
+
closed = self.closed
|
|
131
|
+
if not closed:
|
|
132
|
+
self.commands.put(call)
|
|
133
|
+
if closed:
|
|
134
|
+
if exchange.ended and not exchange.error:
|
|
135
|
+
if operation == "read":
|
|
136
|
+
size = arguments["size"]
|
|
137
|
+
data = bytes(exchange.buffer[:size])
|
|
138
|
+
del exchange.buffer[:size]
|
|
139
|
+
return data
|
|
140
|
+
if operation in ("headers", "finish", "cancel"):
|
|
141
|
+
return None
|
|
142
|
+
raise (
|
|
143
|
+
exchange.error
|
|
144
|
+
or self.error
|
|
145
|
+
or ProtocolError("HTTP/2 connection closed")
|
|
146
|
+
)
|
|
147
|
+
self.wake.set()
|
|
148
|
+
if not call.done.wait(exchange.timeout):
|
|
149
|
+
call.cancelled = True
|
|
150
|
+
self.cancel(exchange)
|
|
151
|
+
raise TimeoutError("HTTP/2 stream timed out")
|
|
152
|
+
if call.error:
|
|
153
|
+
raise call.error
|
|
154
|
+
return call.result
|
|
155
|
+
|
|
156
|
+
def cancel(self, exchange):
|
|
157
|
+
with self._command_lock:
|
|
158
|
+
if exchange.closed:
|
|
159
|
+
return
|
|
160
|
+
exchange.closed = True
|
|
161
|
+
if not self.closed:
|
|
162
|
+
self.commands.put(_Call("cancel", exchange, None))
|
|
163
|
+
self.wake.set()
|
|
164
|
+
|
|
165
|
+
def flush(self):
|
|
166
|
+
self._output.extend(self.protocol.data_to_send())
|
|
167
|
+
for _ in range(32):
|
|
168
|
+
if not self._write_buffer:
|
|
169
|
+
if not self._output:
|
|
170
|
+
try:
|
|
171
|
+
self.transport.write_some(b"")
|
|
172
|
+
self._tls_pending = False
|
|
173
|
+
except BlockingIOError:
|
|
174
|
+
self._tls_pending = True
|
|
175
|
+
return
|
|
176
|
+
self._write_buffer = bytes(self._output[:16384])
|
|
177
|
+
del self._output[:16384]
|
|
178
|
+
try:
|
|
179
|
+
count = self.transport.write_some(self._write_buffer)
|
|
180
|
+
except BlockingIOError:
|
|
181
|
+
self._tls_pending = True
|
|
182
|
+
return
|
|
183
|
+
if not count:
|
|
184
|
+
return
|
|
185
|
+
self._write_buffer = self._write_buffer[count:]
|
|
186
|
+
|
|
187
|
+
def receive(self):
|
|
188
|
+
data = self.transport.read(65536)
|
|
189
|
+
if not data:
|
|
190
|
+
self.closed = True
|
|
191
|
+
raise Reset("HTTP/2 connection ended before stream completion")
|
|
192
|
+
try:
|
|
193
|
+
events = self.protocol.receive_data(data)
|
|
194
|
+
for event in events:
|
|
195
|
+
if isinstance(event, ConnectionTerminated):
|
|
196
|
+
self.goaway = True
|
|
197
|
+
self.goaway_error = event.error_code
|
|
198
|
+
for stream, exchange in self.streams.items():
|
|
199
|
+
if stream > event.last_stream_id and not exchange.ended:
|
|
200
|
+
error = (
|
|
201
|
+
Http1Required if event.error_code == 13 else Rejected
|
|
202
|
+
)
|
|
203
|
+
exchange.fail(error("HTTP/2 GOAWAY rejected this request"))
|
|
204
|
+
elif isinstance(event, StreamReset):
|
|
205
|
+
exchange = self.streams.get(event.stream_id)
|
|
206
|
+
if exchange is not None:
|
|
207
|
+
error = (
|
|
208
|
+
Rejected
|
|
209
|
+
if event.error_code == 7
|
|
210
|
+
else Http1Required
|
|
211
|
+
if event.error_code == 13
|
|
212
|
+
else ProtocolError
|
|
213
|
+
if event.error_code in (0, 8)
|
|
214
|
+
else Reset
|
|
215
|
+
)
|
|
216
|
+
exchange.fail(error(f"HTTP/2 stream reset: {event.error_code}"))
|
|
217
|
+
if event.error_code == 9:
|
|
218
|
+
self.goaway = True
|
|
219
|
+
elif isinstance(event, RemoteSettingsChanged):
|
|
220
|
+
self.received_settings = True
|
|
221
|
+
elif (
|
|
222
|
+
isinstance(event, UnknownFrameReceived) and event.frame.type == 0xC
|
|
223
|
+
):
|
|
224
|
+
self.receive_origins(event.frame)
|
|
225
|
+
elif isinstance(
|
|
226
|
+
event,
|
|
227
|
+
(
|
|
228
|
+
ResponseReceived,
|
|
229
|
+
InformationalResponseReceived,
|
|
230
|
+
DataReceived,
|
|
231
|
+
StreamEnded,
|
|
232
|
+
TrailersReceived,
|
|
233
|
+
),
|
|
234
|
+
):
|
|
235
|
+
exchange = self.streams.get(event.stream_id)
|
|
236
|
+
if exchange is None or exchange.closed:
|
|
237
|
+
if isinstance(event, DataReceived):
|
|
238
|
+
self.protocol.acknowledge_received_data(
|
|
239
|
+
event.flow_controlled_length, event.stream_id
|
|
240
|
+
)
|
|
241
|
+
continue
|
|
242
|
+
exchange.received_data = True
|
|
243
|
+
if isinstance(event, ResponseReceived):
|
|
244
|
+
exchange.response_headers(event.headers)
|
|
245
|
+
elif isinstance(event, DataReceived):
|
|
246
|
+
exchange.buffer.extend(event.data)
|
|
247
|
+
# Credit is returned only after the caller consumes body data.
|
|
248
|
+
exchange.credit += event.flow_controlled_length
|
|
249
|
+
elif isinstance(event, StreamEnded):
|
|
250
|
+
exchange.ended = True
|
|
251
|
+
self.flush()
|
|
252
|
+
except H2Error as error:
|
|
253
|
+
self.closed = True
|
|
254
|
+
raise ProtocolError(str(error)) from error
|
|
255
|
+
|
|
256
|
+
def receive_origins(self, frame):
|
|
257
|
+
if (
|
|
258
|
+
frame.stream_id
|
|
259
|
+
or frame.flag_byte & 0x0F
|
|
260
|
+
or getattr(self.transport, "using_proxy", False)
|
|
261
|
+
):
|
|
262
|
+
return
|
|
263
|
+
origins = set(self.origins or ())
|
|
264
|
+
data = frame.body
|
|
265
|
+
while len(data) >= 2:
|
|
266
|
+
size = int.from_bytes(data[:2], "big")
|
|
267
|
+
data = data[2:]
|
|
268
|
+
if size > len(data):
|
|
269
|
+
break
|
|
270
|
+
value, data = data[:size], data[size:]
|
|
271
|
+
try:
|
|
272
|
+
origin = urlsplit(value.decode("ascii"))
|
|
273
|
+
if origin.scheme != "https" or not origin.hostname:
|
|
274
|
+
continue
|
|
275
|
+
host = origin.hostname.encode("idna").decode("ascii")
|
|
276
|
+
if any(character in host for character in "*\\\x00\r\n\t "):
|
|
277
|
+
continue
|
|
278
|
+
origins.add((host, origin.port or 443))
|
|
279
|
+
except (ValueError, UnicodeError):
|
|
280
|
+
continue
|
|
281
|
+
self.origins = frozenset(origins)
|
|
282
|
+
|
|
283
|
+
def idle(self):
|
|
284
|
+
return not self.closed and not self.goaway
|
|
285
|
+
|
|
286
|
+
def _execute(self, call):
|
|
287
|
+
exchange = call.exchange
|
|
288
|
+
operation = call.operation
|
|
289
|
+
if call.cancelled:
|
|
290
|
+
raise TimeoutError("HTTP/2 request operation cancelled")
|
|
291
|
+
if operation != "cancel" and exchange.closed:
|
|
292
|
+
raise exchange.error or ProtocolError("HTTP/2 response closed")
|
|
293
|
+
if operation != "cancel" and exchange.error:
|
|
294
|
+
raise exchange.error
|
|
295
|
+
if call.deadline is not None and time.monotonic() >= call.deadline:
|
|
296
|
+
raise TimeoutError("HTTP/2 stream timed out")
|
|
297
|
+
if operation in ("fetch", "write", "finish") and (
|
|
298
|
+
len(self._output) + len(self._write_buffer) >= 65536
|
|
299
|
+
):
|
|
300
|
+
return False
|
|
301
|
+
if operation == "fetch":
|
|
302
|
+
if self.goaway:
|
|
303
|
+
if self.goaway_error == 13:
|
|
304
|
+
raise Http1Required("HTTP/2 peer requires HTTP/1.1")
|
|
305
|
+
raise Rejected("HTTP/2 connection is draining")
|
|
306
|
+
maximum = min(100, self.protocol.remote_settings.max_concurrent_streams)
|
|
307
|
+
if self.protocol.open_outbound_streams >= maximum:
|
|
308
|
+
return False
|
|
309
|
+
exchange.stream_id = max(3, self.protocol.get_next_available_stream_id())
|
|
310
|
+
exchange.sent_data = True
|
|
311
|
+
self.protocol.send_headers(
|
|
312
|
+
exchange.stream_id,
|
|
313
|
+
call.arguments["headers"],
|
|
314
|
+
end_stream=exchange.sent_end,
|
|
315
|
+
priority_weight=call.arguments["weight"],
|
|
316
|
+
priority_depends_on=0,
|
|
317
|
+
priority_exclusive=False,
|
|
318
|
+
)
|
|
319
|
+
self.protocol.increment_flow_control_window(
|
|
320
|
+
12582912 - 131072, exchange.stream_id
|
|
321
|
+
)
|
|
322
|
+
self.streams[exchange.stream_id] = exchange
|
|
323
|
+
elif operation == "write":
|
|
324
|
+
window = self.protocol.local_flow_control_window(exchange.stream_id)
|
|
325
|
+
if window <= 0:
|
|
326
|
+
return False
|
|
327
|
+
data = call.arguments["data"]
|
|
328
|
+
count = min(len(data), window, self.protocol.max_outbound_frame_size, 16000)
|
|
329
|
+
self.protocol.send_data(exchange.stream_id, data[:count])
|
|
330
|
+
call.result = count
|
|
331
|
+
elif operation == "finish":
|
|
332
|
+
if not exchange.sent_end:
|
|
333
|
+
self.protocol.end_stream(exchange.stream_id)
|
|
334
|
+
exchange.sent_end = True
|
|
335
|
+
elif operation == "headers":
|
|
336
|
+
if exchange.status is None:
|
|
337
|
+
return False
|
|
338
|
+
elif operation == "read":
|
|
339
|
+
if not exchange.buffer and not exchange.ended:
|
|
340
|
+
return False
|
|
341
|
+
size = call.arguments["size"]
|
|
342
|
+
call.result = bytes(exchange.buffer[:size])
|
|
343
|
+
del exchange.buffer[:size]
|
|
344
|
+
credit = (
|
|
345
|
+
min(len(call.result), exchange.credit)
|
|
346
|
+
if exchange.buffer
|
|
347
|
+
else exchange.credit
|
|
348
|
+
)
|
|
349
|
+
if credit:
|
|
350
|
+
self.protocol.acknowledge_received_data(credit, exchange.stream_id)
|
|
351
|
+
exchange.credit -= credit
|
|
352
|
+
elif operation == "cancel":
|
|
353
|
+
self.streams.pop(exchange.stream_id, None)
|
|
354
|
+
exchange.buffer.clear()
|
|
355
|
+
if exchange.stream_id is not None:
|
|
356
|
+
if exchange.credit:
|
|
357
|
+
self.protocol.acknowledge_received_data(
|
|
358
|
+
exchange.credit, exchange.stream_id
|
|
359
|
+
)
|
|
360
|
+
exchange.credit = 0
|
|
361
|
+
if not exchange.ended:
|
|
362
|
+
try:
|
|
363
|
+
self.protocol.reset_stream(exchange.stream_id, error_code=8)
|
|
364
|
+
except H2Error:
|
|
365
|
+
pass
|
|
366
|
+
exchange.fail(ProtocolError("HTTP/2 response closed"))
|
|
367
|
+
return True
|
|
368
|
+
|
|
369
|
+
def _run(self):
|
|
370
|
+
try:
|
|
371
|
+
while not self._stopping.is_set():
|
|
372
|
+
self.wake.clear()
|
|
373
|
+
while True:
|
|
374
|
+
try:
|
|
375
|
+
self.pending.append(self.commands.get_nowait())
|
|
376
|
+
except queue.Empty:
|
|
377
|
+
break
|
|
378
|
+
waiting = []
|
|
379
|
+
for call in self.pending:
|
|
380
|
+
try:
|
|
381
|
+
if not self._execute(call):
|
|
382
|
+
waiting.append(call)
|
|
383
|
+
continue
|
|
384
|
+
except Exception as error:
|
|
385
|
+
call.error = error
|
|
386
|
+
call.done.set()
|
|
387
|
+
self.flush()
|
|
388
|
+
self.pending = waiting
|
|
389
|
+
self.flush()
|
|
390
|
+
for _ in range(32):
|
|
391
|
+
pending = self.transport.library.necko_tls_pending(
|
|
392
|
+
self.transport.handle
|
|
393
|
+
)
|
|
394
|
+
if pending < 0:
|
|
395
|
+
raise Reset("HTTP/2 transport closed")
|
|
396
|
+
if not pending:
|
|
397
|
+
break
|
|
398
|
+
try:
|
|
399
|
+
self.receive()
|
|
400
|
+
except BlockingIOError:
|
|
401
|
+
break
|
|
402
|
+
else:
|
|
403
|
+
continue
|
|
404
|
+
if (
|
|
405
|
+
self.pending
|
|
406
|
+
or self._output
|
|
407
|
+
or self._write_buffer
|
|
408
|
+
or self._tls_pending
|
|
409
|
+
):
|
|
410
|
+
self.wake.wait(0.001)
|
|
411
|
+
else:
|
|
412
|
+
self.wake.wait(0.01)
|
|
413
|
+
if self._stopping.is_set() and not self.closed:
|
|
414
|
+
self.protocol.close_connection(error_code=0)
|
|
415
|
+
self.flush()
|
|
416
|
+
except Exception as error:
|
|
417
|
+
if isinstance(error, OSError) and not isinstance(
|
|
418
|
+
error, (TimeoutError, ssl.SSLError)
|
|
419
|
+
):
|
|
420
|
+
error = Reset(str(error))
|
|
421
|
+
self.error = error
|
|
422
|
+
finally:
|
|
423
|
+
with self._command_lock:
|
|
424
|
+
self.closed = True
|
|
425
|
+
error = self.error or ProtocolError("HTTP/2 connection closed")
|
|
426
|
+
for exchange in self.streams.values():
|
|
427
|
+
if not exchange.ended:
|
|
428
|
+
exchange.fail(error)
|
|
429
|
+
while True:
|
|
430
|
+
try:
|
|
431
|
+
self.pending.append(self.commands.get_nowait())
|
|
432
|
+
except queue.Empty:
|
|
433
|
+
break
|
|
434
|
+
for call in self.pending:
|
|
435
|
+
call.error = call.exchange.error or error
|
|
436
|
+
call.done.set()
|
|
437
|
+
self.transport._close_native()
|
|
438
|
+
|
|
439
|
+
def close(self):
|
|
440
|
+
self._stopping.set()
|
|
441
|
+
self.wake.set()
|
|
442
|
+
if threading.current_thread() is not self._thread:
|
|
443
|
+
self._thread.join(timeout=1)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class Exchange:
|
|
447
|
+
multiplexed = True
|
|
448
|
+
|
|
449
|
+
def __init__(self, transport, method, url, headers, has_body, timeout=None):
|
|
450
|
+
self.transport = transport
|
|
451
|
+
if not getattr(transport, "h2", None):
|
|
452
|
+
transport.h2 = Connection(transport)
|
|
453
|
+
self.connection = transport.h2
|
|
454
|
+
self.stream_id = None
|
|
455
|
+
self.timeout = timeout
|
|
456
|
+
self.error = None
|
|
457
|
+
self.received_data = False
|
|
458
|
+
self.sent_data = False
|
|
459
|
+
self.closed = False
|
|
460
|
+
self.buffer = bytearray()
|
|
461
|
+
self.credit = 0
|
|
462
|
+
self.ended = False
|
|
463
|
+
self.sent_end = not has_body
|
|
464
|
+
self.status = None
|
|
465
|
+
self.version = 20
|
|
466
|
+
self.reason = ""
|
|
467
|
+
self.headers = HTTPHeaderDict()
|
|
468
|
+
self.msg = Message()
|
|
469
|
+
target = urlsplit(url)
|
|
470
|
+
path = target.path or "/"
|
|
471
|
+
if target.query:
|
|
472
|
+
path += "?" + target.query
|
|
473
|
+
fields = [
|
|
474
|
+
(b":method", method.encode("ascii")),
|
|
475
|
+
(b":path", path.encode("ascii")),
|
|
476
|
+
(b":authority", headers["Host"].encode("ascii")),
|
|
477
|
+
(b":scheme", target.scheme.encode("ascii")),
|
|
478
|
+
]
|
|
479
|
+
excluded = {
|
|
480
|
+
"host",
|
|
481
|
+
"connection",
|
|
482
|
+
"proxy-connection",
|
|
483
|
+
"keep-alive",
|
|
484
|
+
"upgrade",
|
|
485
|
+
"transfer-encoding",
|
|
486
|
+
}
|
|
487
|
+
excluded.update(
|
|
488
|
+
item.strip().lower() for item in headers.get("Connection", "").split(",")
|
|
489
|
+
)
|
|
490
|
+
for raw_name, raw_value in headers.items():
|
|
491
|
+
name = raw_name.decode("ascii") if isinstance(raw_name, bytes) else raw_name
|
|
492
|
+
value = (
|
|
493
|
+
raw_value
|
|
494
|
+
if isinstance(raw_value, bytes)
|
|
495
|
+
else raw_value.encode("latin1")
|
|
496
|
+
)
|
|
497
|
+
if name.lower() not in excluded:
|
|
498
|
+
if name.lower() == "cookie":
|
|
499
|
+
fields.extend((b"cookie", part) for part in value.split(b"; "))
|
|
500
|
+
else:
|
|
501
|
+
fields.append((name.lower().encode("ascii"), value))
|
|
502
|
+
try:
|
|
503
|
+
self.connection.submit(
|
|
504
|
+
"fetch",
|
|
505
|
+
self,
|
|
506
|
+
headers=fields,
|
|
507
|
+
weight=42 if headers.get("Sec-Fetch-Dest") == "document" else 22,
|
|
508
|
+
)
|
|
509
|
+
except Exception:
|
|
510
|
+
self.close()
|
|
511
|
+
raise
|
|
512
|
+
|
|
513
|
+
def fail(self, error):
|
|
514
|
+
if self.error is None:
|
|
515
|
+
self.error = error
|
|
516
|
+
|
|
517
|
+
def response_headers(self, fields):
|
|
518
|
+
for name, value in fields:
|
|
519
|
+
if name == b":status":
|
|
520
|
+
self.status = int(value)
|
|
521
|
+
elif not name.startswith(b":"):
|
|
522
|
+
decoded_name, decoded_value = (
|
|
523
|
+
name.decode("ascii"),
|
|
524
|
+
value.decode("latin1"),
|
|
525
|
+
)
|
|
526
|
+
self.headers.add(decoded_name, decoded_value)
|
|
527
|
+
self.msg[decoded_name] = decoded_value
|
|
528
|
+
|
|
529
|
+
def write(self, data):
|
|
530
|
+
if isinstance(data, str):
|
|
531
|
+
data = data.encode("utf-8")
|
|
532
|
+
view = memoryview(data)
|
|
533
|
+
while view:
|
|
534
|
+
size = self.connection.submit("write", self, data=bytes(view[:16000]))
|
|
535
|
+
view = view[size:]
|
|
536
|
+
|
|
537
|
+
def receive(self):
|
|
538
|
+
self.connection.submit("finish", self)
|
|
539
|
+
self.connection.submit("headers", self)
|
|
540
|
+
|
|
541
|
+
def read(self, size):
|
|
542
|
+
return self.connection.submit("read", self, size=size)
|
|
543
|
+
|
|
544
|
+
def reusable(self):
|
|
545
|
+
return self.ended and not self.buffer
|
|
546
|
+
|
|
547
|
+
def close(self):
|
|
548
|
+
self.connection.cancel(self)
|