wreq-rb 0.3.0
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.
- checksums.yaml +7 -0
- data/Cargo.lock +2688 -0
- data/Cargo.toml +6 -0
- data/README.md +179 -0
- data/ext/wreq_rb/Cargo.toml +39 -0
- data/ext/wreq_rb/extconf.rb +22 -0
- data/ext/wreq_rb/src/client.rs +565 -0
- data/ext/wreq_rb/src/error.rs +25 -0
- data/ext/wreq_rb/src/lib.rs +20 -0
- data/ext/wreq_rb/src/response.rs +132 -0
- data/lib/wreq-rb/version.rb +5 -0
- data/lib/wreq-rb.rb +17 -0
- data/patches/0001-add-transfer-size-tracking.patch +292 -0
- data/vendor/wreq/Cargo.toml +306 -0
- data/vendor/wreq/LICENSE +202 -0
- data/vendor/wreq/README.md +122 -0
- data/vendor/wreq/examples/cert_store.rs +77 -0
- data/vendor/wreq/examples/connect_via_lower_priority_tokio_runtime.rs +258 -0
- data/vendor/wreq/examples/emulation.rs +118 -0
- data/vendor/wreq/examples/form.rs +14 -0
- data/vendor/wreq/examples/http1_websocket.rs +37 -0
- data/vendor/wreq/examples/http2_websocket.rs +45 -0
- data/vendor/wreq/examples/json_dynamic.rs +41 -0
- data/vendor/wreq/examples/json_typed.rs +47 -0
- data/vendor/wreq/examples/keylog.rs +16 -0
- data/vendor/wreq/examples/request_with_emulation.rs +115 -0
- data/vendor/wreq/examples/request_with_interface.rs +37 -0
- data/vendor/wreq/examples/request_with_local_address.rs +16 -0
- data/vendor/wreq/examples/request_with_proxy.rs +13 -0
- data/vendor/wreq/examples/request_with_redirect.rs +22 -0
- data/vendor/wreq/examples/request_with_version.rs +15 -0
- data/vendor/wreq/examples/tor_socks.rs +24 -0
- data/vendor/wreq/examples/unix_socket.rs +33 -0
- data/vendor/wreq/src/client/body.rs +304 -0
- data/vendor/wreq/src/client/conn/conn.rs +231 -0
- data/vendor/wreq/src/client/conn/connector.rs +549 -0
- data/vendor/wreq/src/client/conn/http.rs +1023 -0
- data/vendor/wreq/src/client/conn/proxy/socks.rs +233 -0
- data/vendor/wreq/src/client/conn/proxy/tunnel.rs +260 -0
- data/vendor/wreq/src/client/conn/proxy.rs +39 -0
- data/vendor/wreq/src/client/conn/tls_info.rs +98 -0
- data/vendor/wreq/src/client/conn/uds.rs +44 -0
- data/vendor/wreq/src/client/conn/verbose.rs +149 -0
- data/vendor/wreq/src/client/conn.rs +323 -0
- data/vendor/wreq/src/client/core/body/incoming.rs +485 -0
- data/vendor/wreq/src/client/core/body/length.rs +118 -0
- data/vendor/wreq/src/client/core/body.rs +34 -0
- data/vendor/wreq/src/client/core/common/buf.rs +149 -0
- data/vendor/wreq/src/client/core/common/rewind.rs +141 -0
- data/vendor/wreq/src/client/core/common/watch.rs +76 -0
- data/vendor/wreq/src/client/core/common.rs +3 -0
- data/vendor/wreq/src/client/core/conn/http1.rs +342 -0
- data/vendor/wreq/src/client/core/conn/http2.rs +307 -0
- data/vendor/wreq/src/client/core/conn.rs +11 -0
- data/vendor/wreq/src/client/core/dispatch.rs +299 -0
- data/vendor/wreq/src/client/core/error.rs +435 -0
- data/vendor/wreq/src/client/core/ext.rs +201 -0
- data/vendor/wreq/src/client/core/http1.rs +178 -0
- data/vendor/wreq/src/client/core/http2.rs +483 -0
- data/vendor/wreq/src/client/core/proto/h1/conn.rs +988 -0
- data/vendor/wreq/src/client/core/proto/h1/decode.rs +1170 -0
- data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +684 -0
- data/vendor/wreq/src/client/core/proto/h1/encode.rs +580 -0
- data/vendor/wreq/src/client/core/proto/h1/io.rs +879 -0
- data/vendor/wreq/src/client/core/proto/h1/role.rs +694 -0
- data/vendor/wreq/src/client/core/proto/h1.rs +104 -0
- data/vendor/wreq/src/client/core/proto/h2/client.rs +650 -0
- data/vendor/wreq/src/client/core/proto/h2/ping.rs +539 -0
- data/vendor/wreq/src/client/core/proto/h2.rs +379 -0
- data/vendor/wreq/src/client/core/proto/headers.rs +138 -0
- data/vendor/wreq/src/client/core/proto.rs +58 -0
- data/vendor/wreq/src/client/core/rt/bounds.rs +57 -0
- data/vendor/wreq/src/client/core/rt/timer.rs +150 -0
- data/vendor/wreq/src/client/core/rt/tokio.rs +99 -0
- data/vendor/wreq/src/client/core/rt.rs +25 -0
- data/vendor/wreq/src/client/core/upgrade.rs +267 -0
- data/vendor/wreq/src/client/core.rs +16 -0
- data/vendor/wreq/src/client/emulation.rs +161 -0
- data/vendor/wreq/src/client/http/client/error.rs +142 -0
- data/vendor/wreq/src/client/http/client/exec.rs +29 -0
- data/vendor/wreq/src/client/http/client/extra.rs +77 -0
- data/vendor/wreq/src/client/http/client/lazy.rs +79 -0
- data/vendor/wreq/src/client/http/client/pool.rs +1105 -0
- data/vendor/wreq/src/client/http/client/util.rs +104 -0
- data/vendor/wreq/src/client/http/client.rs +1003 -0
- data/vendor/wreq/src/client/http/future.rs +99 -0
- data/vendor/wreq/src/client/http.rs +1629 -0
- data/vendor/wreq/src/client/layer/config/options.rs +156 -0
- data/vendor/wreq/src/client/layer/config.rs +116 -0
- data/vendor/wreq/src/client/layer/cookie.rs +161 -0
- data/vendor/wreq/src/client/layer/decoder.rs +139 -0
- data/vendor/wreq/src/client/layer/redirect/future.rs +270 -0
- data/vendor/wreq/src/client/layer/redirect/policy.rs +63 -0
- data/vendor/wreq/src/client/layer/redirect.rs +145 -0
- data/vendor/wreq/src/client/layer/retry/classify.rs +105 -0
- data/vendor/wreq/src/client/layer/retry/scope.rs +51 -0
- data/vendor/wreq/src/client/layer/retry.rs +151 -0
- data/vendor/wreq/src/client/layer/timeout/body.rs +233 -0
- data/vendor/wreq/src/client/layer/timeout/future.rs +90 -0
- data/vendor/wreq/src/client/layer/timeout.rs +177 -0
- data/vendor/wreq/src/client/layer.rs +15 -0
- data/vendor/wreq/src/client/multipart.rs +717 -0
- data/vendor/wreq/src/client/request.rs +818 -0
- data/vendor/wreq/src/client/response.rs +534 -0
- data/vendor/wreq/src/client/ws/json.rs +99 -0
- data/vendor/wreq/src/client/ws/message.rs +453 -0
- data/vendor/wreq/src/client/ws.rs +714 -0
- data/vendor/wreq/src/client.rs +27 -0
- data/vendor/wreq/src/config.rs +140 -0
- data/vendor/wreq/src/cookie.rs +579 -0
- data/vendor/wreq/src/dns/gai.rs +249 -0
- data/vendor/wreq/src/dns/hickory.rs +78 -0
- data/vendor/wreq/src/dns/resolve.rs +180 -0
- data/vendor/wreq/src/dns.rs +69 -0
- data/vendor/wreq/src/error.rs +502 -0
- data/vendor/wreq/src/ext.rs +398 -0
- data/vendor/wreq/src/hash.rs +143 -0
- data/vendor/wreq/src/header.rs +506 -0
- data/vendor/wreq/src/into_uri.rs +187 -0
- data/vendor/wreq/src/lib.rs +586 -0
- data/vendor/wreq/src/proxy/mac.rs +82 -0
- data/vendor/wreq/src/proxy/matcher.rs +806 -0
- data/vendor/wreq/src/proxy/uds.rs +66 -0
- data/vendor/wreq/src/proxy/win.rs +31 -0
- data/vendor/wreq/src/proxy.rs +569 -0
- data/vendor/wreq/src/redirect.rs +575 -0
- data/vendor/wreq/src/retry.rs +198 -0
- data/vendor/wreq/src/sync.rs +129 -0
- data/vendor/wreq/src/tls/conn/cache.rs +123 -0
- data/vendor/wreq/src/tls/conn/cert_compression.rs +125 -0
- data/vendor/wreq/src/tls/conn/ext.rs +82 -0
- data/vendor/wreq/src/tls/conn/macros.rs +34 -0
- data/vendor/wreq/src/tls/conn/service.rs +138 -0
- data/vendor/wreq/src/tls/conn.rs +681 -0
- data/vendor/wreq/src/tls/keylog/handle.rs +64 -0
- data/vendor/wreq/src/tls/keylog.rs +99 -0
- data/vendor/wreq/src/tls/options.rs +464 -0
- data/vendor/wreq/src/tls/x509/identity.rs +122 -0
- data/vendor/wreq/src/tls/x509/parser.rs +71 -0
- data/vendor/wreq/src/tls/x509/store.rs +228 -0
- data/vendor/wreq/src/tls/x509.rs +68 -0
- data/vendor/wreq/src/tls.rs +154 -0
- data/vendor/wreq/src/trace.rs +55 -0
- data/vendor/wreq/src/util.rs +122 -0
- data/vendor/wreq/tests/badssl.rs +228 -0
- data/vendor/wreq/tests/brotli.rs +350 -0
- data/vendor/wreq/tests/client.rs +1098 -0
- data/vendor/wreq/tests/connector_layers.rs +227 -0
- data/vendor/wreq/tests/cookie.rs +306 -0
- data/vendor/wreq/tests/deflate.rs +347 -0
- data/vendor/wreq/tests/emulation.rs +260 -0
- data/vendor/wreq/tests/gzip.rs +347 -0
- data/vendor/wreq/tests/layers.rs +261 -0
- data/vendor/wreq/tests/multipart.rs +165 -0
- data/vendor/wreq/tests/proxy.rs +438 -0
- data/vendor/wreq/tests/redirect.rs +629 -0
- data/vendor/wreq/tests/retry.rs +135 -0
- data/vendor/wreq/tests/support/delay_server.rs +117 -0
- data/vendor/wreq/tests/support/error.rs +16 -0
- data/vendor/wreq/tests/support/layer.rs +183 -0
- data/vendor/wreq/tests/support/mod.rs +9 -0
- data/vendor/wreq/tests/support/server.rs +232 -0
- data/vendor/wreq/tests/timeouts.rs +281 -0
- data/vendor/wreq/tests/unix_socket.rs +135 -0
- data/vendor/wreq/tests/upgrade.rs +98 -0
- data/vendor/wreq/tests/zstd.rs +559 -0
- metadata +225 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
//! HTTP header types
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides [`HeaderName`], [`HeaderMap`], [`OrigHeaderMap`], [`OrigHeaderName`], and a
|
|
4
|
+
//! number of types used for interacting with `HeaderMap`. These types allow representing both
|
|
5
|
+
//! HTTP/1 and HTTP/2 headers.
|
|
6
|
+
|
|
7
|
+
pub use http::header::*;
|
|
8
|
+
pub use name::OrigHeaderName;
|
|
9
|
+
|
|
10
|
+
/// Trait for types that can be converted into an [`OrigHeaderName`] (case-preserved header).
|
|
11
|
+
///
|
|
12
|
+
/// This trait is sealed, so only known types can implement it.
|
|
13
|
+
/// Supported types:
|
|
14
|
+
/// - `&'static str`
|
|
15
|
+
/// - `String`
|
|
16
|
+
/// - `Bytes`
|
|
17
|
+
/// - `HeaderName`
|
|
18
|
+
/// - `&HeaderName`
|
|
19
|
+
/// - `OrigHeaderName`
|
|
20
|
+
/// - `&OrigHeaderName`
|
|
21
|
+
pub trait IntoOrigHeaderName: sealed::Sealed {
|
|
22
|
+
/// Converts the type into an [`OrigHeaderName`].
|
|
23
|
+
fn into_orig_header_name(self) -> OrigHeaderName;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// A map from header names to their original casing as received in an HTTP message.
|
|
27
|
+
///
|
|
28
|
+
/// [`OrigHeaderMap`] not only preserves the original case of each header name as it appeared
|
|
29
|
+
/// in the request or response, but also maintains the insertion order of headers. This makes
|
|
30
|
+
/// it suitable for use cases where the order of headers matters, such as HTTP/1.x message
|
|
31
|
+
/// serialization, proxying, or reproducing requests/responses exactly as received.
|
|
32
|
+
#[derive(Debug, Clone, Default)]
|
|
33
|
+
pub struct OrigHeaderMap(HeaderMap<OrigHeaderName>);
|
|
34
|
+
|
|
35
|
+
// ===== impl OrigHeaderMap =====
|
|
36
|
+
|
|
37
|
+
impl OrigHeaderMap {
|
|
38
|
+
/// Creates a new, empty [`OrigHeaderMap`].
|
|
39
|
+
#[inline]
|
|
40
|
+
pub fn new() -> Self {
|
|
41
|
+
Self(HeaderMap::default())
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Creates an empty [`OrigHeaderMap`] with the specified capacity.
|
|
45
|
+
#[inline]
|
|
46
|
+
pub fn with_capacity(size: usize) -> Self {
|
|
47
|
+
Self(HeaderMap::with_capacity(size))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Insert a new header name into the collection.
|
|
51
|
+
///
|
|
52
|
+
/// If the map did not previously have this key present, then `false` is
|
|
53
|
+
/// returned.
|
|
54
|
+
///
|
|
55
|
+
/// If the map did have this key present, the new value is pushed to the end
|
|
56
|
+
/// of the list of values currently associated with the key. The key is not
|
|
57
|
+
/// updated, though; this matters for types that can be `==` without being
|
|
58
|
+
/// identical.
|
|
59
|
+
#[inline]
|
|
60
|
+
pub fn insert<N>(&mut self, orig: N) -> bool
|
|
61
|
+
where
|
|
62
|
+
N: IntoOrigHeaderName,
|
|
63
|
+
{
|
|
64
|
+
let orig_header_name = orig.into_orig_header_name();
|
|
65
|
+
match &orig_header_name.kind {
|
|
66
|
+
name::Kind::Cased(bytes) => HeaderName::from_bytes(bytes)
|
|
67
|
+
.map(|name| self.0.append(name, orig_header_name))
|
|
68
|
+
.unwrap_or(false),
|
|
69
|
+
name::Kind::Standard(header_name) => {
|
|
70
|
+
self.0.append(header_name.clone(), orig_header_name)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Extends the map with all entries from another [`OrigHeaderMap`], preserving order.
|
|
76
|
+
#[inline]
|
|
77
|
+
pub fn extend(&mut self, iter: OrigHeaderMap) {
|
|
78
|
+
self.0.extend(iter.0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Returns the number of headers stored in the map.
|
|
82
|
+
///
|
|
83
|
+
/// This number represents the total number of **values** stored in the map.
|
|
84
|
+
/// This number can be greater than or equal to the number of **keys**
|
|
85
|
+
/// stored given that a single key may have more than one associated value.
|
|
86
|
+
#[inline]
|
|
87
|
+
pub fn len(&self) -> usize {
|
|
88
|
+
self.0.len()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Returns true if the map contains no elements.
|
|
92
|
+
#[inline]
|
|
93
|
+
pub fn is_empty(&self) -> bool {
|
|
94
|
+
self.0.is_empty()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Returns an iterator over all header names and their original spellings, in insertion order.
|
|
98
|
+
#[inline]
|
|
99
|
+
pub fn iter(&self) -> impl Iterator<Item = (&HeaderName, &OrigHeaderName)> {
|
|
100
|
+
self.0.iter()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl OrigHeaderMap {
|
|
105
|
+
/// Sorts headers by this map, preserving original casing.
|
|
106
|
+
/// Headers in the map come first, others follow.
|
|
107
|
+
pub(crate) fn sort_headers(&self, headers: &mut HeaderMap) {
|
|
108
|
+
if headers.len() <= 1 || self.0.is_empty() {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Create a new header map to store the sorted headers
|
|
113
|
+
let mut sorted_headers = HeaderMap::with_capacity(headers.keys_len());
|
|
114
|
+
|
|
115
|
+
// First insert headers in the specified order
|
|
116
|
+
for name in self.0.keys() {
|
|
117
|
+
for value in headers.get_all(name) {
|
|
118
|
+
sorted_headers.append(name.clone(), value.clone());
|
|
119
|
+
}
|
|
120
|
+
headers.remove(name);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Then insert any remaining headers that were not ordered
|
|
124
|
+
let mut prev_name: Option<HeaderName> = None;
|
|
125
|
+
for (name, value) in headers.drain() {
|
|
126
|
+
match (name, &prev_name) {
|
|
127
|
+
(Some(name), _) => {
|
|
128
|
+
prev_name.replace(name.clone());
|
|
129
|
+
sorted_headers.insert(name, value);
|
|
130
|
+
}
|
|
131
|
+
(None, Some(prev_name)) => {
|
|
132
|
+
sorted_headers.append(prev_name, value);
|
|
133
|
+
}
|
|
134
|
+
_ => {}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
std::mem::swap(headers, &mut sorted_headers);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Calls the given function for each header in this map's order, preserving original casing.
|
|
142
|
+
/// Headers in the map are processed first, others follow.
|
|
143
|
+
pub(crate) fn sort_headers_for_each<F>(&self, headers: &mut HeaderMap, mut dst: F)
|
|
144
|
+
where
|
|
145
|
+
F: FnMut(&[u8], &HeaderValue),
|
|
146
|
+
{
|
|
147
|
+
// First, sort headers according to the order defined in this map
|
|
148
|
+
for (name, orig_name) in self.iter() {
|
|
149
|
+
for value in headers.get_all(name) {
|
|
150
|
+
dst(orig_name.as_ref(), value);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
headers.remove(name);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// After processing all ordered headers, append any remaining headers
|
|
157
|
+
let mut prev_name: Option<OrigHeaderName> = None;
|
|
158
|
+
for (name, value) in headers.drain() {
|
|
159
|
+
match (name, &prev_name) {
|
|
160
|
+
(Some(name), _) => {
|
|
161
|
+
dst(name.as_ref(), &value);
|
|
162
|
+
prev_name.replace(name.into_orig_header_name());
|
|
163
|
+
}
|
|
164
|
+
(None, Some(prev_name)) => {
|
|
165
|
+
dst(prev_name.as_ref(), &value);
|
|
166
|
+
}
|
|
167
|
+
_ => (),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
impl<'a> IntoIterator for &'a OrigHeaderMap {
|
|
174
|
+
type Item = (&'a HeaderName, &'a OrigHeaderName);
|
|
175
|
+
type IntoIter = <&'a HeaderMap<OrigHeaderName> as IntoIterator>::IntoIter;
|
|
176
|
+
|
|
177
|
+
#[inline]
|
|
178
|
+
fn into_iter(self) -> Self::IntoIter {
|
|
179
|
+
self.0.iter()
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
impl IntoIterator for OrigHeaderMap {
|
|
184
|
+
type Item = (Option<HeaderName>, OrigHeaderName);
|
|
185
|
+
type IntoIter = <HeaderMap<OrigHeaderName> as IntoIterator>::IntoIter;
|
|
186
|
+
|
|
187
|
+
#[inline]
|
|
188
|
+
fn into_iter(self) -> Self::IntoIter {
|
|
189
|
+
self.0.into_iter()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
impl_request_config_value!(OrigHeaderMap);
|
|
194
|
+
|
|
195
|
+
mod name {
|
|
196
|
+
use bytes::Bytes;
|
|
197
|
+
use http::HeaderName;
|
|
198
|
+
|
|
199
|
+
use super::IntoOrigHeaderName;
|
|
200
|
+
|
|
201
|
+
/// An HTTP header name with both normalized and original casing.
|
|
202
|
+
///
|
|
203
|
+
/// While HTTP headers are case-insensitive, this type stores both
|
|
204
|
+
/// the canonical `HeaderName` and the original casing as received,
|
|
205
|
+
/// useful for preserving header order and formatting in proxies,
|
|
206
|
+
/// debugging, or exact HTTP message reproduction.
|
|
207
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
208
|
+
pub struct OrigHeaderName {
|
|
209
|
+
pub(super) kind: Kind,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
213
|
+
pub(super) enum Kind {
|
|
214
|
+
/// The original casing of the header name as received.
|
|
215
|
+
Cased(Bytes),
|
|
216
|
+
/// The canonical (normalized, lowercased) header name.
|
|
217
|
+
Standard(HeaderName),
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
impl AsRef<[u8]> for OrigHeaderName {
|
|
221
|
+
#[inline]
|
|
222
|
+
fn as_ref(&self) -> &[u8] {
|
|
223
|
+
match &self.kind {
|
|
224
|
+
Kind::Standard(name) => name.as_ref(),
|
|
225
|
+
Kind::Cased(orig) => orig.as_ref(),
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
impl IntoOrigHeaderName for &'static str {
|
|
231
|
+
#[inline]
|
|
232
|
+
fn into_orig_header_name(self) -> OrigHeaderName {
|
|
233
|
+
Bytes::from_static(self.as_bytes()).into_orig_header_name()
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
impl IntoOrigHeaderName for String {
|
|
238
|
+
#[inline]
|
|
239
|
+
fn into_orig_header_name(self) -> OrigHeaderName {
|
|
240
|
+
Bytes::from(self).into_orig_header_name()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
impl IntoOrigHeaderName for Bytes {
|
|
245
|
+
#[inline]
|
|
246
|
+
fn into_orig_header_name(self) -> OrigHeaderName {
|
|
247
|
+
OrigHeaderName {
|
|
248
|
+
kind: Kind::Cased(self),
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
impl IntoOrigHeaderName for &HeaderName {
|
|
254
|
+
#[inline]
|
|
255
|
+
fn into_orig_header_name(self) -> OrigHeaderName {
|
|
256
|
+
OrigHeaderName {
|
|
257
|
+
kind: Kind::Standard(self.clone()),
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
impl IntoOrigHeaderName for HeaderName {
|
|
263
|
+
#[inline]
|
|
264
|
+
fn into_orig_header_name(self) -> OrigHeaderName {
|
|
265
|
+
OrigHeaderName {
|
|
266
|
+
kind: Kind::Standard(self),
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
impl IntoOrigHeaderName for OrigHeaderName {
|
|
272
|
+
#[inline]
|
|
273
|
+
fn into_orig_header_name(self) -> OrigHeaderName {
|
|
274
|
+
self
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
impl IntoOrigHeaderName for &OrigHeaderName {
|
|
279
|
+
#[inline]
|
|
280
|
+
fn into_orig_header_name(self) -> OrigHeaderName {
|
|
281
|
+
self.clone()
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
mod sealed {
|
|
287
|
+
|
|
288
|
+
use bytes::Bytes;
|
|
289
|
+
use http::HeaderName;
|
|
290
|
+
|
|
291
|
+
use crate::header::OrigHeaderName;
|
|
292
|
+
|
|
293
|
+
pub trait Sealed {}
|
|
294
|
+
|
|
295
|
+
impl Sealed for &'static str {}
|
|
296
|
+
impl Sealed for String {}
|
|
297
|
+
impl Sealed for Bytes {}
|
|
298
|
+
impl Sealed for &HeaderName {}
|
|
299
|
+
impl Sealed for HeaderName {}
|
|
300
|
+
impl Sealed for &OrigHeaderName {}
|
|
301
|
+
impl Sealed for OrigHeaderName {}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#[cfg(test)]
|
|
305
|
+
mod test {
|
|
306
|
+
use http::{HeaderMap, HeaderName, HeaderValue};
|
|
307
|
+
|
|
308
|
+
use super::OrigHeaderMap;
|
|
309
|
+
|
|
310
|
+
/// Returns a view of all spellings associated with that header name,
|
|
311
|
+
/// in the order they were found.
|
|
312
|
+
#[inline]
|
|
313
|
+
pub(crate) fn get_all<'a>(
|
|
314
|
+
orig_headers: &'a OrigHeaderMap,
|
|
315
|
+
name: &HeaderName,
|
|
316
|
+
) -> impl Iterator<Item = impl AsRef<[u8]> + 'a> + 'a {
|
|
317
|
+
orig_headers.0.get_all(name).into_iter()
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
#[test]
|
|
321
|
+
fn test_header_order() {
|
|
322
|
+
let mut headers = OrigHeaderMap::new();
|
|
323
|
+
|
|
324
|
+
// Insert headers with different cases and order
|
|
325
|
+
headers.insert("X-Test");
|
|
326
|
+
headers.insert("X-Another");
|
|
327
|
+
headers.insert("x-test2");
|
|
328
|
+
|
|
329
|
+
// Check order and case
|
|
330
|
+
let mut iter = headers.iter();
|
|
331
|
+
assert_eq!(iter.next().unwrap().1.as_ref(), b"X-Test");
|
|
332
|
+
assert_eq!(iter.next().unwrap().1.as_ref(), b"X-Another");
|
|
333
|
+
assert_eq!(iter.next().unwrap().1.as_ref(), b"x-test2");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
#[test]
|
|
337
|
+
fn test_extend_preserves_order() {
|
|
338
|
+
use super::OrigHeaderMap;
|
|
339
|
+
|
|
340
|
+
let mut map1 = OrigHeaderMap::new();
|
|
341
|
+
map1.insert("A-Header");
|
|
342
|
+
map1.insert("B-Header");
|
|
343
|
+
|
|
344
|
+
let mut map2 = OrigHeaderMap::new();
|
|
345
|
+
map2.insert("C-Header");
|
|
346
|
+
map2.insert("D-Header");
|
|
347
|
+
|
|
348
|
+
map1.extend(map2);
|
|
349
|
+
|
|
350
|
+
let names: Vec<_> = map1.iter().map(|(_, orig)| orig.as_ref()).collect();
|
|
351
|
+
assert_eq!(
|
|
352
|
+
names,
|
|
353
|
+
vec![b"A-Header", b"B-Header", b"C-Header", b"D-Header"]
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#[test]
|
|
358
|
+
fn test_header_case() {
|
|
359
|
+
let mut headers = OrigHeaderMap::new();
|
|
360
|
+
|
|
361
|
+
// Insert headers with different cases
|
|
362
|
+
headers.insert("X-Test");
|
|
363
|
+
headers.insert("x-test");
|
|
364
|
+
|
|
365
|
+
// Check that both headers are stored
|
|
366
|
+
let all_x_test: Vec<_> = get_all(&headers, &"X-Test".parse().unwrap()).collect();
|
|
367
|
+
assert_eq!(all_x_test.len(), 2);
|
|
368
|
+
assert!(all_x_test.iter().any(|v| v.as_ref() == b"X-Test"));
|
|
369
|
+
assert!(all_x_test.iter().any(|v| v.as_ref() == b"x-test"));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
#[test]
|
|
373
|
+
fn test_header_multiple_cases() {
|
|
374
|
+
let mut headers = OrigHeaderMap::new();
|
|
375
|
+
|
|
376
|
+
// Insert multiple headers with the same name but different cases
|
|
377
|
+
headers.insert("X-test");
|
|
378
|
+
headers.insert("x-test");
|
|
379
|
+
headers.insert("X-test");
|
|
380
|
+
|
|
381
|
+
// Check that all variations are stored
|
|
382
|
+
let all_x_test: Vec<_> = get_all(&headers, &"x-test".parse().unwrap()).collect();
|
|
383
|
+
assert_eq!(all_x_test.len(), 3);
|
|
384
|
+
assert!(all_x_test.iter().any(|v| v.as_ref() == b"X-test"));
|
|
385
|
+
assert!(all_x_test.iter().any(|v| v.as_ref() == b"x-test"));
|
|
386
|
+
assert!(all_x_test.iter().any(|v| v.as_ref() == b"X-test"));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
#[test]
|
|
390
|
+
fn test_sort_headers_preserves_multiple_cookie_values() {
|
|
391
|
+
// Create original header map for ordering
|
|
392
|
+
let mut orig_headers = OrigHeaderMap::new();
|
|
393
|
+
orig_headers.insert("Cookie");
|
|
394
|
+
orig_headers.insert("User-Agent");
|
|
395
|
+
orig_headers.insert("Accept");
|
|
396
|
+
|
|
397
|
+
// Create headers with multiple Cookie values
|
|
398
|
+
let mut headers = HeaderMap::new();
|
|
399
|
+
|
|
400
|
+
// Add multiple Cookie headers (this simulates how cookies are often sent)
|
|
401
|
+
headers.append("cookie", HeaderValue::from_static("session=abc123"));
|
|
402
|
+
headers.append("cookie", HeaderValue::from_static("theme=dark"));
|
|
403
|
+
headers.append("cookie", HeaderValue::from_static("lang=en"));
|
|
404
|
+
|
|
405
|
+
// Add other headers
|
|
406
|
+
headers.insert("user-agent", HeaderValue::from_static("Mozilla/5.0"));
|
|
407
|
+
headers.insert("accept", HeaderValue::from_static("text/html"));
|
|
408
|
+
headers.insert("host", HeaderValue::from_static("example.com"));
|
|
409
|
+
|
|
410
|
+
// Record original cookie values for comparison
|
|
411
|
+
let original_cookies: Vec<_> = headers
|
|
412
|
+
.get_all("cookie")
|
|
413
|
+
.iter()
|
|
414
|
+
.map(|v| v.to_str().unwrap().to_string())
|
|
415
|
+
.collect();
|
|
416
|
+
|
|
417
|
+
// Sort headers according to orig_headers order
|
|
418
|
+
orig_headers.sort_headers(&mut headers);
|
|
419
|
+
|
|
420
|
+
// Verify all cookie values are preserved
|
|
421
|
+
let sorted_cookies: Vec<_> = headers
|
|
422
|
+
.get_all("cookie")
|
|
423
|
+
.iter()
|
|
424
|
+
.map(|v| v.to_str().unwrap().to_string())
|
|
425
|
+
.collect();
|
|
426
|
+
|
|
427
|
+
assert_eq!(
|
|
428
|
+
original_cookies.len(),
|
|
429
|
+
sorted_cookies.len(),
|
|
430
|
+
"Cookie count should be preserved"
|
|
431
|
+
);
|
|
432
|
+
assert_eq!(original_cookies.len(), 3, "Should have 3 cookie values");
|
|
433
|
+
|
|
434
|
+
// Verify all original cookies are still present (order might change but content preserved)
|
|
435
|
+
for original_cookie in &original_cookies {
|
|
436
|
+
assert!(
|
|
437
|
+
sorted_cookies.contains(original_cookie),
|
|
438
|
+
"Cookie '{original_cookie}' should be preserved"
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Verify header ordering - Cookie should come first
|
|
443
|
+
let header_names: Vec<_> = headers.keys().collect();
|
|
444
|
+
assert_eq!(
|
|
445
|
+
header_names[0].as_str(),
|
|
446
|
+
"cookie",
|
|
447
|
+
"Cookie should be first header"
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
// Verify all headers are preserved
|
|
451
|
+
assert_eq!(
|
|
452
|
+
headers.len(),
|
|
453
|
+
6,
|
|
454
|
+
"Should have 6 total header values (3 cookies + 3 others)"
|
|
455
|
+
);
|
|
456
|
+
assert!(headers.contains_key("user-agent"));
|
|
457
|
+
assert!(headers.contains_key("accept"));
|
|
458
|
+
assert!(headers.contains_key("host"));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
#[test]
|
|
462
|
+
fn test_sort_headers_multiple_values_different_headers() {
|
|
463
|
+
let mut orig_headers = OrigHeaderMap::new();
|
|
464
|
+
orig_headers.insert("Accept");
|
|
465
|
+
orig_headers.insert("Cookie");
|
|
466
|
+
|
|
467
|
+
let mut headers = HeaderMap::new();
|
|
468
|
+
|
|
469
|
+
// Multiple Accept headers
|
|
470
|
+
headers.append("accept", HeaderValue::from_static("text/html"));
|
|
471
|
+
headers.append("accept", HeaderValue::from_static("application/json"));
|
|
472
|
+
|
|
473
|
+
// Multiple Cookie headers
|
|
474
|
+
headers.append("cookie", HeaderValue::from_static("a=1"));
|
|
475
|
+
headers.append("cookie", HeaderValue::from_static("b=2"));
|
|
476
|
+
|
|
477
|
+
// Single header
|
|
478
|
+
headers.insert("host", HeaderValue::from_static("example.com"));
|
|
479
|
+
|
|
480
|
+
let total_before = headers.len();
|
|
481
|
+
|
|
482
|
+
orig_headers.sort_headers(&mut headers);
|
|
483
|
+
|
|
484
|
+
// Verify all values preserved
|
|
485
|
+
assert_eq!(
|
|
486
|
+
headers.len(),
|
|
487
|
+
total_before,
|
|
488
|
+
"Total header count should be preserved"
|
|
489
|
+
);
|
|
490
|
+
assert_eq!(
|
|
491
|
+
headers.get_all("accept").iter().count(),
|
|
492
|
+
2,
|
|
493
|
+
"Accept headers should be preserved"
|
|
494
|
+
);
|
|
495
|
+
assert_eq!(
|
|
496
|
+
headers.get_all("cookie").iter().count(),
|
|
497
|
+
2,
|
|
498
|
+
"Cookie headers should be preserved"
|
|
499
|
+
);
|
|
500
|
+
assert_eq!(
|
|
501
|
+
headers.get_all("host").iter().count(),
|
|
502
|
+
1,
|
|
503
|
+
"Host header should be preserved"
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
//! URI conversion utilities.
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides the [`IntoUri`] trait, allowing various types
|
|
4
|
+
//! (such as `&str`, `String`, `Vec<u8>`, etc.) to be fallibly converted into an [`http::Uri`].
|
|
5
|
+
//! The conversion is based on `TryFrom<T> for Uri` and ensures the resulting URI is valid and
|
|
6
|
+
//! contains a host.
|
|
7
|
+
//!
|
|
8
|
+
//! Internally, the trait is sealed to prevent
|
|
9
|
+
|
|
10
|
+
use bytes::Bytes;
|
|
11
|
+
use http::Uri;
|
|
12
|
+
|
|
13
|
+
use crate::{Error, Result};
|
|
14
|
+
|
|
15
|
+
/// Converts a value into a [`Uri`] with error handling.
|
|
16
|
+
///
|
|
17
|
+
/// This trait is implemented for common types such as [`Uri`], [`String`], [`&str`], and byte
|
|
18
|
+
/// slices, as well as any type that can be fallibly converted into a [`Uri`] via [`TryFrom`].
|
|
19
|
+
pub trait IntoUri: IntoUriSealed {}
|
|
20
|
+
|
|
21
|
+
impl IntoUri for Uri {}
|
|
22
|
+
impl IntoUri for &Uri {}
|
|
23
|
+
impl IntoUri for &str {}
|
|
24
|
+
impl IntoUri for String {}
|
|
25
|
+
impl IntoUri for &String {}
|
|
26
|
+
impl IntoUri for Vec<u8> {}
|
|
27
|
+
impl IntoUri for &[u8] {}
|
|
28
|
+
|
|
29
|
+
pub trait IntoUriSealed {
|
|
30
|
+
// Besides parsing as a valid `Uri`.
|
|
31
|
+
fn into_uri(self) -> Result<Uri>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl IntoUriSealed for &[u8] {
|
|
35
|
+
fn into_uri(self) -> Result<Uri> {
|
|
36
|
+
Uri::try_from(self)
|
|
37
|
+
.or_else(|_| internal::parse(internal::Kind::Bytes(self)))
|
|
38
|
+
.and_then(IntoUriSealed::into_uri)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl IntoUriSealed for Vec<u8> {
|
|
43
|
+
fn into_uri(self) -> Result<Uri> {
|
|
44
|
+
let bytes = Bytes::from(self);
|
|
45
|
+
Uri::from_maybe_shared(bytes.clone())
|
|
46
|
+
.or_else(|_| internal::parse(internal::Kind::Bytes(&bytes)))
|
|
47
|
+
.and_then(IntoUriSealed::into_uri)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl IntoUriSealed for &str {
|
|
52
|
+
fn into_uri(self) -> Result<Uri> {
|
|
53
|
+
Uri::try_from(self)
|
|
54
|
+
.or_else(|_| internal::parse(internal::Kind::Str(self)))
|
|
55
|
+
.and_then(IntoUriSealed::into_uri)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
impl IntoUriSealed for String {
|
|
60
|
+
#[inline]
|
|
61
|
+
fn into_uri(self) -> Result<Uri> {
|
|
62
|
+
self.into_bytes().into_uri()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
impl IntoUriSealed for &String {
|
|
67
|
+
#[inline]
|
|
68
|
+
fn into_uri(self) -> Result<Uri> {
|
|
69
|
+
IntoUriSealed::into_uri(self.as_str())
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
impl IntoUriSealed for Uri {
|
|
74
|
+
fn into_uri(self) -> Result<Uri> {
|
|
75
|
+
match (self.scheme(), self.authority()) {
|
|
76
|
+
(Some(_), Some(_)) => Ok(self),
|
|
77
|
+
_ => Err(Error::uri_bad_scheme(self)),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
impl IntoUriSealed for &Uri {
|
|
83
|
+
fn into_uri(self) -> Result<Uri> {
|
|
84
|
+
match (self.scheme(), self.authority()) {
|
|
85
|
+
(Some(_), Some(_)) => Ok(self.clone()),
|
|
86
|
+
_ => Err(Error::uri_bad_scheme(self.clone())),
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
mod internal {
|
|
92
|
+
use http::Uri;
|
|
93
|
+
use url::Url;
|
|
94
|
+
|
|
95
|
+
use crate::{Error, Result};
|
|
96
|
+
|
|
97
|
+
pub(super) enum Kind<'a> {
|
|
98
|
+
Bytes(&'a [u8]),
|
|
99
|
+
Str(&'a str),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pub(super) fn parse(s: Kind) -> Result<Uri> {
|
|
103
|
+
let s = match s {
|
|
104
|
+
Kind::Bytes(bytes) => std::str::from_utf8(bytes).map_err(Error::decode),
|
|
105
|
+
Kind::Str(s) => Ok(s),
|
|
106
|
+
}?;
|
|
107
|
+
|
|
108
|
+
Url::parse(s)
|
|
109
|
+
.map(String::from)
|
|
110
|
+
.map_err(Error::builder)
|
|
111
|
+
.and_then(|s| Uri::try_from(s).map_err(Error::builder))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#[cfg(test)]
|
|
116
|
+
mod tests {
|
|
117
|
+
use super::IntoUriSealed;
|
|
118
|
+
|
|
119
|
+
#[test]
|
|
120
|
+
fn into_uri_bad_scheme() {
|
|
121
|
+
let err = "/hello/world".into_uri().unwrap_err();
|
|
122
|
+
assert_eq!(
|
|
123
|
+
err.to_string(),
|
|
124
|
+
"builder error for uri (/hello/world): URI scheme is not allowed"
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
let err = "127.0.0.1".into_uri().unwrap_err();
|
|
128
|
+
assert_eq!(
|
|
129
|
+
err.to_string(),
|
|
130
|
+
"builder error for uri (127.0.0.1): URI scheme is not allowed"
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#[test]
|
|
135
|
+
fn into_uri_with_space_in_path() {
|
|
136
|
+
let uri = "http://example.com/hello world".into_uri().unwrap();
|
|
137
|
+
assert_eq!(uri, "http://example.com/hello%20world");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[test]
|
|
141
|
+
fn into_uri_with_unicode_in_path() {
|
|
142
|
+
let uri = "http://example.com/文件/测试".into_uri().unwrap();
|
|
143
|
+
assert_eq!(uri, "http://example.com/文件/测试");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#[test]
|
|
147
|
+
fn into_uri_with_special_chars_in_path() {
|
|
148
|
+
let uri = "http://example.com/path<>{}".into_uri().unwrap();
|
|
149
|
+
assert_eq!(uri, "http://example.com/path%3C%3E%7B%7D");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[test]
|
|
153
|
+
fn into_uri_with_query_preserved() {
|
|
154
|
+
let uri = "http://example.com/path?key=value&foo=bar"
|
|
155
|
+
.into_uri()
|
|
156
|
+
.unwrap();
|
|
157
|
+
assert_eq!(uri, "http://example.com/path?key=value&foo=bar");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#[test]
|
|
161
|
+
fn into_uri_bytes_with_encoding() {
|
|
162
|
+
let bytes = b"http://example.com/hello world";
|
|
163
|
+
let uri = bytes.into_uri().unwrap();
|
|
164
|
+
assert_eq!(uri, "http://example.com/hello%20world");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[test]
|
|
168
|
+
fn test_bytes_with_query() {
|
|
169
|
+
let bytes = b"http://example.com/path?key=hello%20world";
|
|
170
|
+
let uri = bytes.into_uri().unwrap();
|
|
171
|
+
assert_eq!(uri.to_string(), "http://example.com/path?key=hello%20world");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[test]
|
|
175
|
+
fn test_bytes_with_unicode() {
|
|
176
|
+
let bytes = b"http://example.com/\xE6\xB5\x8B\xE8\xAF\x95";
|
|
177
|
+
let uri = bytes.into_uri().unwrap();
|
|
178
|
+
assert_eq!(uri, "http://example.com/测试");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#[test]
|
|
182
|
+
fn test_bytes_minimal() {
|
|
183
|
+
let bytes = b"http://example.com";
|
|
184
|
+
let uri = bytes.into_uri().unwrap();
|
|
185
|
+
assert_eq!(uri, "http://example.com");
|
|
186
|
+
}
|
|
187
|
+
}
|