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.
Files changed (175) hide show
  1. ruyihttp/__init__.py +98 -0
  2. ruyihttp/_connect.py +109 -0
  3. ruyihttp/_firefox.py +267 -0
  4. ruyihttp/_http2.py +548 -0
  5. ruyihttp/_http3.py +503 -0
  6. ruyihttp/_nss.py +378 -0
  7. ruyihttp/_retry.py +39 -0
  8. ruyihttp/_socks.py +57 -0
  9. ruyihttp/_transport.py +1234 -0
  10. ruyihttp/adapters.py +9 -0
  11. ruyihttp/api.py +40 -0
  12. ruyihttp/firefox-profile.json +49 -0
  13. ruyihttp/firefox-root-constraints.json +3 -0
  14. ruyihttp/firefox-roots.pem +2991 -0
  15. ruyihttp/firefox-suffixes.dat +16501 -0
  16. ruyihttp/freebl3.dll +0 -0
  17. ruyihttp/licenses/README.md +29 -0
  18. ruyihttp/licenses/firefox-license.html +6470 -0
  19. ruyihttp/licenses/firefox-notice.txt +9 -0
  20. ruyihttp/licenses/mozilla-runtime-data.txt +21 -0
  21. ruyihttp/licenses/rust-aho-corasick-1.1.0-COPYING.txt +3 -0
  22. ruyihttp/licenses/rust-aho-corasick-1.1.0-LICENSE-MIT.txt +21 -0
  23. ruyihttp/licenses/rust-aho-corasick-1.1.0-UNLICENSE.txt +24 -0
  24. ruyihttp/licenses/rust-base64-0.22.1-LICENSE-APACHE.txt +201 -0
  25. ruyihttp/licenses/rust-base64-0.22.1-LICENSE-MIT.txt +21 -0
  26. ruyihttp/licenses/rust-bindgen-0.73.1-LICENSE.txt +29 -0
  27. ruyihttp/licenses/rust-bitflags-2.10.0-LICENSE-MIT.txt +25 -0
  28. ruyihttp/licenses/rust-block-buffer-0.10.3-LICENSE-APACHE.txt +201 -0
  29. ruyihttp/licenses/rust-block-buffer-0.10.3-LICENSE-MIT.txt +25 -0
  30. ruyihttp/licenses/rust-bytes-1.11.1-LICENSE.txt +25 -0
  31. ruyihttp/licenses/rust-cexpr-0.6.0-LICENSE-MIT.txt +25 -0
  32. ruyihttp/licenses/rust-cfg-if-1.0.0-LICENSE-MIT.txt +25 -0
  33. ruyihttp/licenses/rust-cfg_aliases-0.2.2-LICENSE.txt +9 -0
  34. ruyihttp/licenses/rust-clang-sys-1.7.0-LICENSE.txt +202 -0
  35. ruyihttp/licenses/rust-cpufeatures-0.2.8-LICENSE-MIT.txt +25 -0
  36. ruyihttp/licenses/rust-crypto-common-0.1.6-LICENSE-MIT.txt +25 -0
  37. ruyihttp/licenses/rust-darling-0.20.10-LICENSE.txt +21 -0
  38. ruyihttp/licenses/rust-dependencies.json +2052 -0
  39. ruyihttp/licenses/rust-digest-0.10.7-LICENSE-MIT.txt +25 -0
  40. ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-Apache-2.0.txt +73 -0
  41. ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-CC0-1.0.txt +121 -0
  42. ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-MIT.txt +9 -0
  43. ruyihttp/licenses/rust-enumset-1.1.2-LICENSE-MIT.txt +26 -0
  44. ruyihttp/licenses/rust-env_logger-0.10.0-LICENSE-MIT.txt +23 -0
  45. ruyihttp/licenses/rust-equivalent-1.0.1-LICENSE-MIT.txt +25 -0
  46. ruyihttp/licenses/rust-fnv-1.0.7-LICENSE-MIT.txt +25 -0
  47. ruyihttp/licenses/rust-generic-array-0.14.6-LICENSE.txt +21 -0
  48. ruyihttp/licenses/rust-hashbrown-0.17.1-LICENSE-MIT.txt +25 -0
  49. ruyihttp/licenses/rust-heck-0.5.0-LICENSE-MIT.txt +25 -0
  50. ruyihttp/licenses/rust-hex-0.4.3-LICENSE-APACHE.txt +202 -0
  51. ruyihttp/licenses/rust-hex-0.4.3-LICENSE-MIT.txt +20 -0
  52. ruyihttp/licenses/rust-http-1.4.0-LICENSE-APACHE.txt +201 -0
  53. ruyihttp/licenses/rust-http-1.4.0-LICENSE-MIT.txt +25 -0
  54. ruyihttp/licenses/rust-ident_case-1.0.1-LICENSE.txt +19 -0
  55. ruyihttp/licenses/rust-indexmap-2.11.4-LICENSE-MIT.txt +25 -0
  56. ruyihttp/licenses/rust-itoa-1.0.15-LICENSE-APACHE.txt +176 -0
  57. ruyihttp/licenses/rust-libc-0.2.186-LICENSE-MIT.txt +25 -0
  58. ruyihttp/licenses/rust-libloading-0.8.6-LICENSE.txt +12 -0
  59. ruyihttp/licenses/rust-minimal-lexical-0.2.1-LICENSE-APACHE.txt +201 -0
  60. ruyihttp/licenses/rust-minimal-lexical-0.2.1-LICENSE.md.txt +37 -0
  61. ruyihttp/licenses/rust-mtu-0.4.1-LICENSE-MIT.txt +25 -0
  62. ruyihttp/licenses/rust-nom-7.1.3-LICENSE.txt +20 -0
  63. ruyihttp/licenses/rust-pkcs11-bindings-0.1.8-LICENSE.txt +21 -0
  64. ruyihttp/licenses/rust-qlog-0.16.0-COPYING.txt +23 -0
  65. ruyihttp/licenses/rust-quinn-udp-0.6.0-LICENSE-APACHE.txt +201 -0
  66. ruyihttp/licenses/rust-quinn-udp-0.6.0-LICENSE-MIT.txt +7 -0
  67. ruyihttp/licenses/rust-regex-syntax-0.8.5-src-unicode_tables-LICENSE-UNICODE.txt +57 -0
  68. ruyihttp/licenses/rust-rustc-hash-2.1.1-LICENSE-APACHE.txt +176 -0
  69. ruyihttp/licenses/rust-rustc-hash-2.1.1-LICENSE-MIT.txt +23 -0
  70. ruyihttp/licenses/rust-serde_spanned-1.0.3-LICENSE-MIT.txt +19 -0
  71. ruyihttp/licenses/rust-serde_with-3.12.0-LICENSE-MIT.txt +25 -0
  72. ruyihttp/licenses/rust-sfv-0.14.0-LICENSE-MIT.txt +27 -0
  73. ruyihttp/licenses/rust-sha1-0.10.5-LICENSE-MIT.txt +27 -0
  74. ruyihttp/licenses/rust-shlex-2.0.1-LICENSE-APACHE.txt +13 -0
  75. ruyihttp/licenses/rust-shlex-2.0.1-LICENSE-MIT.txt +21 -0
  76. ruyihttp/licenses/rust-smallvec-1.15.2-LICENSE-MIT.txt +25 -0
  77. ruyihttp/licenses/rust-static_assertions-1.1.0-LICENSE-MIT.txt +21 -0
  78. ruyihttp/licenses/rust-strsim-0.11.1-LICENSE.txt +23 -0
  79. ruyihttp/licenses/rust-strum-0.27.1-LICENSE.txt +21 -0
  80. ruyihttp/licenses/rust-typenum-1.16.0-LICENSE-APACHE.txt +201 -0
  81. ruyihttp/licenses/rust-typenum-1.16.0-LICENSE-MIT.txt +21 -0
  82. ruyihttp/licenses/rust-typenum-1.16.0-LICENSE.txt +1 -0
  83. ruyihttp/licenses/rust-unicode-ident-1.0.6-LICENSE-UNICODE.txt +46 -0
  84. ruyihttp/licenses/rust-version_check-0.9.4-LICENSE-MIT.txt +19 -0
  85. ruyihttp/licenses/rust-windows-0.62.2-license-apache-2.0.txt +201 -0
  86. ruyihttp/licenses/rust-windows-0.62.2-license-mit.txt +21 -0
  87. ruyihttp/licenses/rust-winnow-0.7.13-LICENSE-MIT.txt +18 -0
  88. ruyihttp/licenses/rust-zeroize-1.8.1-LICENSE-MIT.txt +21 -0
  89. ruyihttp/licenses/rust-zeroize_derive-1.4.2-LICENSE-MIT.txt +21 -0
  90. ruyihttp/mozglue.dll +0 -0
  91. ruyihttp/necko_core.dll +0 -0
  92. ruyihttp/necko_quic.dll +0 -0
  93. ruyihttp/nss3.dll +0 -0
  94. ruyihttp/runtime-manifest.json +16 -0
  95. ruyihttp/sessions.py +83 -0
  96. ruyihttp/softokn3.dll +0 -0
  97. ruyihttp-0.8.1.dist-info/METADATA +273 -0
  98. ruyihttp-0.8.1.dist-info/RECORD +175 -0
  99. ruyihttp-0.8.1.dist-info/WHEEL +5 -0
  100. ruyihttp-0.8.1.dist-info/licenses/LICENSE +373 -0
  101. ruyihttp-0.8.1.dist-info/licenses/THIRD_PARTY_NOTICES.md +36 -0
  102. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/README.md +29 -0
  103. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/firefox-license.html +6470 -0
  104. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/firefox-notice.txt +9 -0
  105. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/mozilla-runtime-data.txt +21 -0
  106. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-aho-corasick-1.1.0-COPYING.txt +3 -0
  107. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-aho-corasick-1.1.0-LICENSE-MIT.txt +21 -0
  108. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-aho-corasick-1.1.0-UNLICENSE.txt +24 -0
  109. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-base64-0.22.1-LICENSE-APACHE.txt +201 -0
  110. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-base64-0.22.1-LICENSE-MIT.txt +21 -0
  111. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-bindgen-0.73.1-LICENSE.txt +29 -0
  112. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-bitflags-2.10.0-LICENSE-MIT.txt +25 -0
  113. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-block-buffer-0.10.3-LICENSE-APACHE.txt +201 -0
  114. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-block-buffer-0.10.3-LICENSE-MIT.txt +25 -0
  115. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-bytes-1.11.1-LICENSE.txt +25 -0
  116. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-cexpr-0.6.0-LICENSE-MIT.txt +25 -0
  117. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-cfg-if-1.0.0-LICENSE-MIT.txt +25 -0
  118. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-cfg_aliases-0.2.2-LICENSE.txt +9 -0
  119. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-clang-sys-1.7.0-LICENSE.txt +202 -0
  120. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-cpufeatures-0.2.8-LICENSE-MIT.txt +25 -0
  121. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-crypto-common-0.1.6-LICENSE-MIT.txt +25 -0
  122. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-darling-0.20.10-LICENSE.txt +21 -0
  123. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-dependencies.json +2052 -0
  124. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-digest-0.10.7-LICENSE-MIT.txt +25 -0
  125. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-Apache-2.0.txt +73 -0
  126. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-CC0-1.0.txt +121 -0
  127. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-enum-map-2.7.3-LICENSES-MIT.txt +9 -0
  128. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-enumset-1.1.2-LICENSE-MIT.txt +26 -0
  129. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-env_logger-0.10.0-LICENSE-MIT.txt +23 -0
  130. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-equivalent-1.0.1-LICENSE-MIT.txt +25 -0
  131. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-fnv-1.0.7-LICENSE-MIT.txt +25 -0
  132. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-generic-array-0.14.6-LICENSE.txt +21 -0
  133. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-hashbrown-0.17.1-LICENSE-MIT.txt +25 -0
  134. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-heck-0.5.0-LICENSE-MIT.txt +25 -0
  135. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-hex-0.4.3-LICENSE-APACHE.txt +202 -0
  136. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-hex-0.4.3-LICENSE-MIT.txt +20 -0
  137. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-http-1.4.0-LICENSE-APACHE.txt +201 -0
  138. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-http-1.4.0-LICENSE-MIT.txt +25 -0
  139. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-ident_case-1.0.1-LICENSE.txt +19 -0
  140. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-indexmap-2.11.4-LICENSE-MIT.txt +25 -0
  141. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-itoa-1.0.15-LICENSE-APACHE.txt +176 -0
  142. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-libc-0.2.186-LICENSE-MIT.txt +25 -0
  143. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-libloading-0.8.6-LICENSE.txt +12 -0
  144. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-minimal-lexical-0.2.1-LICENSE-APACHE.txt +201 -0
  145. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-minimal-lexical-0.2.1-LICENSE.md.txt +37 -0
  146. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-mtu-0.4.1-LICENSE-MIT.txt +25 -0
  147. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-nom-7.1.3-LICENSE.txt +20 -0
  148. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-pkcs11-bindings-0.1.8-LICENSE.txt +21 -0
  149. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-qlog-0.16.0-COPYING.txt +23 -0
  150. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-quinn-udp-0.6.0-LICENSE-APACHE.txt +201 -0
  151. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-quinn-udp-0.6.0-LICENSE-MIT.txt +7 -0
  152. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-regex-syntax-0.8.5-src-unicode_tables-LICENSE-UNICODE.txt +57 -0
  153. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-rustc-hash-2.1.1-LICENSE-APACHE.txt +176 -0
  154. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-rustc-hash-2.1.1-LICENSE-MIT.txt +23 -0
  155. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-serde_spanned-1.0.3-LICENSE-MIT.txt +19 -0
  156. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-serde_with-3.12.0-LICENSE-MIT.txt +25 -0
  157. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-sfv-0.14.0-LICENSE-MIT.txt +27 -0
  158. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-sha1-0.10.5-LICENSE-MIT.txt +27 -0
  159. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-shlex-2.0.1-LICENSE-APACHE.txt +13 -0
  160. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-shlex-2.0.1-LICENSE-MIT.txt +21 -0
  161. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-smallvec-1.15.2-LICENSE-MIT.txt +25 -0
  162. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-static_assertions-1.1.0-LICENSE-MIT.txt +21 -0
  163. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-strsim-0.11.1-LICENSE.txt +23 -0
  164. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-strum-0.27.1-LICENSE.txt +21 -0
  165. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-typenum-1.16.0-LICENSE-APACHE.txt +201 -0
  166. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-typenum-1.16.0-LICENSE-MIT.txt +21 -0
  167. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-typenum-1.16.0-LICENSE.txt +1 -0
  168. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-unicode-ident-1.0.6-LICENSE-UNICODE.txt +46 -0
  169. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-version_check-0.9.4-LICENSE-MIT.txt +19 -0
  170. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-windows-0.62.2-license-apache-2.0.txt +201 -0
  171. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-windows-0.62.2-license-mit.txt +21 -0
  172. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-winnow-0.7.13-LICENSE-MIT.txt +18 -0
  173. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-zeroize-1.8.1-LICENSE-MIT.txt +21 -0
  174. ruyihttp-0.8.1.dist-info/licenses/ruyihttp/licenses/rust-zeroize_derive-1.4.2-LICENSE-MIT.txt +21 -0
  175. 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)