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,579 @@
|
|
|
1
|
+
//! HTTP Cookies
|
|
2
|
+
|
|
3
|
+
use std::{convert::TryInto, fmt, sync::Arc, time::SystemTime};
|
|
4
|
+
|
|
5
|
+
use bytes::Bytes;
|
|
6
|
+
use cookie::{Cookie as RawCookie, CookieJar, Expiration, SameSite};
|
|
7
|
+
use http::Uri;
|
|
8
|
+
|
|
9
|
+
use crate::{
|
|
10
|
+
IntoUri,
|
|
11
|
+
error::Error,
|
|
12
|
+
ext::UriExt,
|
|
13
|
+
hash::{HASHER, HashMap},
|
|
14
|
+
header::HeaderValue,
|
|
15
|
+
sync::RwLock,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/// Cookie header values in two forms.
|
|
19
|
+
#[derive(Debug, Clone)]
|
|
20
|
+
#[non_exhaustive]
|
|
21
|
+
pub enum Cookies {
|
|
22
|
+
/// All cookies combined into one header (compressed).
|
|
23
|
+
Compressed(HeaderValue),
|
|
24
|
+
|
|
25
|
+
/// Each cookie sent as its own header (uncompressed).
|
|
26
|
+
Uncompressed(Vec<HeaderValue>),
|
|
27
|
+
|
|
28
|
+
/// No cookies.
|
|
29
|
+
Empty,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Actions for a persistent cookie store providing session support.
|
|
33
|
+
pub trait CookieStore: Send + Sync {
|
|
34
|
+
/// Store a set of Set-Cookie header values received from `uri`
|
|
35
|
+
fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, uri: &Uri);
|
|
36
|
+
|
|
37
|
+
/// Get any [`Cookies`] in the store for `uri`
|
|
38
|
+
fn cookies(&self, uri: &Uri) -> Cookies;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Trait for converting types into a shared cookie store ([`Arc<dyn CookieStore>`]).
|
|
42
|
+
///
|
|
43
|
+
/// Implemented for any [`CookieStore`] type, [`Arc<T>`] where `T: CookieStore`, and [`Arc<dyn
|
|
44
|
+
/// CookieStore>`]. Enables ergonomic conversion to a trait object for use in APIs without manual
|
|
45
|
+
/// boxing.
|
|
46
|
+
pub trait IntoCookieStore {
|
|
47
|
+
/// Converts the implementor into an [`Arc<dyn CookieStore>`].
|
|
48
|
+
///
|
|
49
|
+
/// This method allows ergonomic conversion of concrete cookie stores, [`Arc<T>`], or
|
|
50
|
+
/// existing [`Arc<dyn CookieStore>`] into a trait object suitable for APIs that expect
|
|
51
|
+
/// a shared cookie store.
|
|
52
|
+
fn into_cookie_store(self) -> Arc<dyn CookieStore>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Trait for converting types into an owned cookie ([`Cookie<'static>`]).
|
|
56
|
+
pub trait IntoCookie {
|
|
57
|
+
/// Converts the implementor into a optional owned [`Cookie<'static>`].
|
|
58
|
+
fn into_cookie(self) -> Option<Cookie<'static>>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// A single HTTP cookie.
|
|
62
|
+
#[derive(Debug, Clone)]
|
|
63
|
+
pub struct Cookie<'a>(RawCookie<'a>);
|
|
64
|
+
|
|
65
|
+
/// A good default `CookieStore` implementation.
|
|
66
|
+
///
|
|
67
|
+
/// This is the implementation used when simply calling `cookie_store(true)`.
|
|
68
|
+
/// This type is exposed to allow creating one and filling it with some
|
|
69
|
+
/// existing cookies more easily, before creating a [`crate::Client`].
|
|
70
|
+
pub struct Jar {
|
|
71
|
+
compression: bool,
|
|
72
|
+
store: Arc<RwLock<HashMap<String, HashMap<String, CookieJar>>>>,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ===== impl CookieStore =====
|
|
76
|
+
|
|
77
|
+
impl_request_config_value!(Arc<dyn CookieStore>);
|
|
78
|
+
|
|
79
|
+
// ===== impl IntoCookieStore =====
|
|
80
|
+
|
|
81
|
+
impl IntoCookieStore for Arc<dyn CookieStore> {
|
|
82
|
+
#[inline]
|
|
83
|
+
fn into_cookie_store(self) -> Arc<dyn CookieStore> {
|
|
84
|
+
self
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
impl<R> IntoCookieStore for Arc<R>
|
|
89
|
+
where
|
|
90
|
+
R: CookieStore + 'static,
|
|
91
|
+
{
|
|
92
|
+
#[inline]
|
|
93
|
+
fn into_cookie_store(self) -> Arc<dyn CookieStore> {
|
|
94
|
+
self
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
impl<R> IntoCookieStore for R
|
|
99
|
+
where
|
|
100
|
+
R: CookieStore + 'static,
|
|
101
|
+
{
|
|
102
|
+
#[inline]
|
|
103
|
+
fn into_cookie_store(self) -> Arc<dyn CookieStore> {
|
|
104
|
+
Arc::new(self)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ===== impl IntoCookie =====
|
|
109
|
+
|
|
110
|
+
impl IntoCookie for Cookie<'_> {
|
|
111
|
+
#[inline]
|
|
112
|
+
fn into_cookie(self) -> Option<Cookie<'static>> {
|
|
113
|
+
Some(self.into_owned())
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
impl IntoCookie for RawCookie<'_> {
|
|
118
|
+
#[inline]
|
|
119
|
+
fn into_cookie(self) -> Option<Cookie<'static>> {
|
|
120
|
+
Some(Cookie(self.into_owned()))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
impl IntoCookie for &str {
|
|
125
|
+
#[inline]
|
|
126
|
+
fn into_cookie(self) -> Option<Cookie<'static>> {
|
|
127
|
+
RawCookie::parse(self).map(|c| Cookie(c.into_owned())).ok()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ===== impl Cookie =====
|
|
132
|
+
|
|
133
|
+
impl<'a> Cookie<'a> {
|
|
134
|
+
pub(crate) fn parse(value: &'a HeaderValue) -> crate::Result<Cookie<'a>> {
|
|
135
|
+
std::str::from_utf8(value.as_bytes())
|
|
136
|
+
.map_err(cookie::ParseError::from)
|
|
137
|
+
.and_then(cookie::Cookie::parse)
|
|
138
|
+
.map_err(Error::decode)
|
|
139
|
+
.map(Cookie)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// The name of the cookie.
|
|
143
|
+
#[inline]
|
|
144
|
+
pub fn name(&self) -> &str {
|
|
145
|
+
self.0.name()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// The value of the cookie.
|
|
149
|
+
#[inline]
|
|
150
|
+
pub fn value(&self) -> &str {
|
|
151
|
+
self.0.value()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Returns true if the 'HttpOnly' directive is enabled.
|
|
155
|
+
#[inline]
|
|
156
|
+
pub fn http_only(&self) -> bool {
|
|
157
|
+
self.0.http_only().unwrap_or(false)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Returns true if the 'Secure' directive is enabled.
|
|
161
|
+
#[inline]
|
|
162
|
+
pub fn secure(&self) -> bool {
|
|
163
|
+
self.0.secure().unwrap_or(false)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Returns true if 'SameSite' directive is 'Lax'.
|
|
167
|
+
#[inline]
|
|
168
|
+
pub fn same_site_lax(&self) -> bool {
|
|
169
|
+
self.0.same_site() == Some(SameSite::Lax)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// Returns true if 'SameSite' directive is 'Strict'.
|
|
173
|
+
#[inline]
|
|
174
|
+
pub fn same_site_strict(&self) -> bool {
|
|
175
|
+
self.0.same_site() == Some(SameSite::Strict)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// Returns the path directive of the cookie, if set.
|
|
179
|
+
#[inline]
|
|
180
|
+
pub fn path(&self) -> Option<&str> {
|
|
181
|
+
self.0.path()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// Returns the domain directive of the cookie, if set.
|
|
185
|
+
#[inline]
|
|
186
|
+
pub fn domain(&self) -> Option<&str> {
|
|
187
|
+
self.0.domain()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// Get the Max-Age information.
|
|
191
|
+
#[inline]
|
|
192
|
+
pub fn max_age(&self) -> Option<std::time::Duration> {
|
|
193
|
+
self.0.max_age().and_then(|d| d.try_into().ok())
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// The cookie expiration time.
|
|
197
|
+
#[inline]
|
|
198
|
+
pub fn expires(&self) -> Option<SystemTime> {
|
|
199
|
+
match self.0.expires() {
|
|
200
|
+
Some(Expiration::DateTime(offset)) => Some(SystemTime::from(offset)),
|
|
201
|
+
None | Some(Expiration::Session) => None,
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// Converts `self` into a `Cookie` with a static lifetime with as few
|
|
206
|
+
/// allocations as possible.
|
|
207
|
+
#[inline]
|
|
208
|
+
pub fn into_owned(self) -> Cookie<'static> {
|
|
209
|
+
Cookie(self.0.into_owned())
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
impl fmt::Display for Cookie<'_> {
|
|
214
|
+
#[inline]
|
|
215
|
+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
216
|
+
self.0.fmt(f)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
impl<'c> From<RawCookie<'c>> for Cookie<'c> {
|
|
221
|
+
#[inline]
|
|
222
|
+
fn from(cookie: RawCookie<'c>) -> Cookie<'c> {
|
|
223
|
+
Cookie(cookie)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
impl<'c> From<Cookie<'c>> for RawCookie<'c> {
|
|
228
|
+
#[inline]
|
|
229
|
+
fn from(cookie: Cookie<'c>) -> RawCookie<'c> {
|
|
230
|
+
cookie.0
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ===== impl Jar =====
|
|
235
|
+
|
|
236
|
+
macro_rules! into_uri {
|
|
237
|
+
($expr:expr) => {
|
|
238
|
+
match $expr.into_uri() {
|
|
239
|
+
Ok(u) => u,
|
|
240
|
+
Err(_) => return,
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
impl Jar {
|
|
246
|
+
/// Creates a new [`Jar`] with the specified compression setting.
|
|
247
|
+
pub fn new(compression: bool) -> Self {
|
|
248
|
+
Self {
|
|
249
|
+
compression,
|
|
250
|
+
store: Arc::new(RwLock::new(HashMap::with_hasher(HASHER))),
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/// Clone this [`Jar`], sharing storage but enabling compression.
|
|
255
|
+
pub fn compressed(self: &Arc<Self>) -> Arc<Self> {
|
|
256
|
+
Arc::new(Jar {
|
|
257
|
+
compression: true,
|
|
258
|
+
store: self.store.clone(),
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/// Clone this [`Jar`], sharing storage but disabling compression.
|
|
263
|
+
pub fn uncompressed(self: &Arc<Self>) -> Arc<Self> {
|
|
264
|
+
Arc::new(Jar {
|
|
265
|
+
compression: false,
|
|
266
|
+
store: self.store.clone(),
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Get a cookie by name for a given Uri.
|
|
271
|
+
///
|
|
272
|
+
/// Returns the cookie with the specified name for the domain and path
|
|
273
|
+
/// derived from the given Uri, if it exists.
|
|
274
|
+
///
|
|
275
|
+
/// # Example
|
|
276
|
+
/// ```
|
|
277
|
+
/// use wreq::cookie::Jar;
|
|
278
|
+
/// let jar = Jar::default();
|
|
279
|
+
/// jar.add_cookie_str("foo=bar; Path=/foo; Domain=example.com", "http://example.com/foo");
|
|
280
|
+
/// let cookie = jar.get("foo", "http://example.com/foo").unwrap();
|
|
281
|
+
/// assert_eq!(cookie.value(), "bar");
|
|
282
|
+
/// ```
|
|
283
|
+
pub fn get<U: IntoUri>(&self, name: &str, uri: U) -> Option<Cookie<'static>> {
|
|
284
|
+
let uri = uri.into_uri().ok()?;
|
|
285
|
+
let cookie = self
|
|
286
|
+
.store
|
|
287
|
+
.read()
|
|
288
|
+
.get(uri.host()?)?
|
|
289
|
+
.get(uri.path())?
|
|
290
|
+
.get(name)?
|
|
291
|
+
.clone()
|
|
292
|
+
.into_owned();
|
|
293
|
+
Some(Cookie(cookie))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// Get all cookies in this jar.
|
|
297
|
+
///
|
|
298
|
+
/// Returns an iterator over all cookies currently stored in the jar,
|
|
299
|
+
/// regardless of domain or path.
|
|
300
|
+
///
|
|
301
|
+
/// # Example
|
|
302
|
+
/// ```
|
|
303
|
+
/// use wreq::cookie::Jar;
|
|
304
|
+
/// let jar = Jar::default();
|
|
305
|
+
/// jar.add_cookie_str("foo=bar; Domain=example.com", "http://example.com");
|
|
306
|
+
/// for cookie in jar.get_all() {
|
|
307
|
+
/// println!("{}={}", cookie.name(), cookie.value());
|
|
308
|
+
/// }
|
|
309
|
+
/// ```
|
|
310
|
+
pub fn get_all(&self) -> impl Iterator<Item = Cookie<'static>> {
|
|
311
|
+
self.store
|
|
312
|
+
.read()
|
|
313
|
+
.iter()
|
|
314
|
+
.flat_map(|(_, path_map)| {
|
|
315
|
+
path_map.iter().flat_map(|(_, name_map)| {
|
|
316
|
+
name_map
|
|
317
|
+
.iter()
|
|
318
|
+
.map(|cookie| Cookie(cookie.clone().into_owned()))
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
.collect::<Vec<_>>()
|
|
322
|
+
.into_iter()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/// Add a cookie to this jar.
|
|
326
|
+
///
|
|
327
|
+
/// # Example
|
|
328
|
+
///
|
|
329
|
+
/// ```
|
|
330
|
+
/// use wreq::cookie::Jar;
|
|
331
|
+
/// use cookie::CookieBuilder;
|
|
332
|
+
/// let jar = Jar::default();
|
|
333
|
+
/// let cookie = CookieBuilder::new("foo", "bar")
|
|
334
|
+
/// .domain("example.com")
|
|
335
|
+
/// .path("/")
|
|
336
|
+
/// .build();
|
|
337
|
+
/// jar.add(cookie, "http://example.com");
|
|
338
|
+
///
|
|
339
|
+
/// let cookie = CookieBuilder::new("foo", "bar")
|
|
340
|
+
/// .domain("example.com")
|
|
341
|
+
/// .path("/")
|
|
342
|
+
/// .build();
|
|
343
|
+
/// jar.add(cookie, "http://example.com");
|
|
344
|
+
/// ```
|
|
345
|
+
pub fn add<C, U>(&self, cookie: C, uri: U)
|
|
346
|
+
where
|
|
347
|
+
C: IntoCookie,
|
|
348
|
+
U: IntoUri,
|
|
349
|
+
{
|
|
350
|
+
if let Some(cookie) = cookie.into_cookie() {
|
|
351
|
+
let cookie: RawCookie<'static> = cookie.into();
|
|
352
|
+
let uri = into_uri!(uri);
|
|
353
|
+
let domain = cookie
|
|
354
|
+
.domain()
|
|
355
|
+
.map(normalize_domain)
|
|
356
|
+
.or_else(|| uri.host())
|
|
357
|
+
.unwrap_or_default();
|
|
358
|
+
let path = cookie.path().unwrap_or_else(|| normalize_path(&uri));
|
|
359
|
+
|
|
360
|
+
let mut inner = self.store.write();
|
|
361
|
+
let name_map = inner
|
|
362
|
+
.entry(domain.to_owned())
|
|
363
|
+
.or_insert_with(|| HashMap::with_hasher(HASHER))
|
|
364
|
+
.entry(path.to_owned())
|
|
365
|
+
.or_default();
|
|
366
|
+
|
|
367
|
+
// RFC 6265: If Max-Age=0 or Expires in the past, remove the cookie
|
|
368
|
+
let expired = cookie
|
|
369
|
+
.expires_datetime()
|
|
370
|
+
.is_some_and(|dt| dt <= SystemTime::now())
|
|
371
|
+
|| cookie.max_age().is_some_and(|age| age.is_zero());
|
|
372
|
+
|
|
373
|
+
if expired {
|
|
374
|
+
name_map.remove(cookie);
|
|
375
|
+
} else {
|
|
376
|
+
name_map.add(cookie);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// Remove a cookie by name for a given Uri.
|
|
382
|
+
///
|
|
383
|
+
/// Removes the cookie with the specified name for the domain and path
|
|
384
|
+
/// derived from the given Uri, if it exists.
|
|
385
|
+
///
|
|
386
|
+
/// # Example
|
|
387
|
+
/// ```
|
|
388
|
+
/// use wreq::cookie::Jar;
|
|
389
|
+
/// let jar = Jar::default();
|
|
390
|
+
/// jar.add_cookie_str("foo=bar; Path=/foo; Domain=example.com", "http://example.com/foo");
|
|
391
|
+
/// assert!(jar.get("foo", "http://example.com/foo").is_some());
|
|
392
|
+
/// jar.remove("foo", "http://example.com/foo");
|
|
393
|
+
/// assert!(jar.get("foo", "http://example.com/foo").is_none());
|
|
394
|
+
/// ```
|
|
395
|
+
pub fn remove<C, U>(&self, cookie: C, uri: U)
|
|
396
|
+
where
|
|
397
|
+
C: Into<RawCookie<'static>>,
|
|
398
|
+
U: IntoUri,
|
|
399
|
+
{
|
|
400
|
+
let uri = into_uri!(uri);
|
|
401
|
+
if let Some(host) = uri.host() {
|
|
402
|
+
let mut inner = self.store.write();
|
|
403
|
+
if let Some(path_map) = inner.get_mut(host) {
|
|
404
|
+
if let Some(name_map) = path_map.get_mut(uri.path()) {
|
|
405
|
+
name_map.remove(cookie.into());
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/// Clear all cookies from this jar.
|
|
412
|
+
///
|
|
413
|
+
/// Removes all cookies from the jar, leaving it empty.
|
|
414
|
+
///
|
|
415
|
+
/// # Example
|
|
416
|
+
/// ```
|
|
417
|
+
/// use wreq::cookie::Jar;
|
|
418
|
+
/// let jar = Jar::default();
|
|
419
|
+
/// jar.add_cookie_str("foo=bar; Domain=example.com", "http://example.com");
|
|
420
|
+
/// assert_eq!(jar.get_all().count(), 1);
|
|
421
|
+
/// jar.clear();
|
|
422
|
+
/// assert_eq!(jar.get_all().count(), 0);
|
|
423
|
+
/// ```
|
|
424
|
+
pub fn clear(&self) {
|
|
425
|
+
self.store.write().clear();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
impl CookieStore for Jar {
|
|
430
|
+
fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, uri: &Uri) {
|
|
431
|
+
let cookies = cookie_headers
|
|
432
|
+
.map(Cookie::parse)
|
|
433
|
+
.filter_map(Result::ok)
|
|
434
|
+
.map(|cookie| cookie.0.into_owned());
|
|
435
|
+
|
|
436
|
+
for cookie in cookies {
|
|
437
|
+
self.add(cookie, uri);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
fn cookies(&self, uri: &Uri) -> Cookies {
|
|
442
|
+
let host = match uri.host() {
|
|
443
|
+
Some(h) => h,
|
|
444
|
+
None => return Cookies::Empty,
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
let store = self.store.read();
|
|
448
|
+
let iter = store
|
|
449
|
+
.iter()
|
|
450
|
+
.filter(|(domain, _)| domain_match(host, domain))
|
|
451
|
+
.flat_map(|(_, path_map)| {
|
|
452
|
+
path_map
|
|
453
|
+
.iter()
|
|
454
|
+
.filter(|(path, _)| path_match(uri.path(), path))
|
|
455
|
+
.flat_map(|(_, name_map)| {
|
|
456
|
+
name_map.iter().filter(|cookie| {
|
|
457
|
+
if cookie.secure() == Some(true) && uri.is_http() {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if cookie
|
|
462
|
+
.expires_datetime()
|
|
463
|
+
.is_some_and(|dt| dt <= SystemTime::now())
|
|
464
|
+
{
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
true
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
if self.compression {
|
|
474
|
+
let cookies = iter.fold(String::new(), |mut cookies, cookie| {
|
|
475
|
+
if !cookies.is_empty() {
|
|
476
|
+
cookies.push_str("; ");
|
|
477
|
+
}
|
|
478
|
+
cookies.push_str(cookie.name());
|
|
479
|
+
cookies.push('=');
|
|
480
|
+
cookies.push_str(cookie.value());
|
|
481
|
+
cookies
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
if cookies.is_empty() {
|
|
485
|
+
return Cookies::Empty;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
HeaderValue::from_maybe_shared(Bytes::from(cookies))
|
|
489
|
+
.map(Cookies::Compressed)
|
|
490
|
+
.unwrap_or(Cookies::Empty)
|
|
491
|
+
} else {
|
|
492
|
+
let cookies = iter
|
|
493
|
+
.map(|cookie| {
|
|
494
|
+
let name = cookie.name();
|
|
495
|
+
let value = cookie.value();
|
|
496
|
+
|
|
497
|
+
let mut cookie_str = String::with_capacity(name.len() + 1 + value.len());
|
|
498
|
+
cookie_str.push_str(name);
|
|
499
|
+
cookie_str.push('=');
|
|
500
|
+
cookie_str.push_str(value);
|
|
501
|
+
|
|
502
|
+
HeaderValue::from_maybe_shared(Bytes::from(cookie_str))
|
|
503
|
+
})
|
|
504
|
+
.filter_map(Result::ok)
|
|
505
|
+
.collect();
|
|
506
|
+
|
|
507
|
+
Cookies::Uncompressed(cookies)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
impl Default for Jar {
|
|
513
|
+
fn default() -> Self {
|
|
514
|
+
Self::new(true)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const DEFAULT_PATH: &str = "/";
|
|
519
|
+
|
|
520
|
+
/// Determines if the given `host` matches the cookie `domain` according to
|
|
521
|
+
/// [RFC 6265 section 5.1.3](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3).
|
|
522
|
+
///
|
|
523
|
+
/// - Returns true if the host and domain are identical.
|
|
524
|
+
/// - Returns true if the host is a subdomain of the domain (host ends with ".domain").
|
|
525
|
+
/// - Returns false otherwise.
|
|
526
|
+
fn domain_match(host: &str, domain: &str) -> bool {
|
|
527
|
+
if domain.is_empty() {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
if host == domain {
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
host.len() > domain.len()
|
|
534
|
+
&& host.as_bytes()[host.len() - domain.len() - 1] == b'.'
|
|
535
|
+
&& host.ends_with(domain)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/// Determines if the request path matches the cookie path according to
|
|
539
|
+
/// [RFC 6265 section 5.1.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4).
|
|
540
|
+
///
|
|
541
|
+
/// - Returns true if the request path and cookie path are identical.
|
|
542
|
+
/// - Returns true if the request path starts with the cookie path, and
|
|
543
|
+
/// - the cookie path ends with '/', or
|
|
544
|
+
/// - the next character in the request path after the cookie path is '/'.
|
|
545
|
+
/// - Returns false otherwise.
|
|
546
|
+
fn path_match(req_path: &str, cookie_path: &str) -> bool {
|
|
547
|
+
req_path == cookie_path
|
|
548
|
+
|| req_path.starts_with(cookie_path)
|
|
549
|
+
&& (cookie_path.ends_with(DEFAULT_PATH)
|
|
550
|
+
|| req_path[cookie_path.len()..].starts_with(DEFAULT_PATH))
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/// Normalizes a domain by stripping any port information.
|
|
554
|
+
///
|
|
555
|
+
/// According to [RFC 6265 section 5.2.3](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3),
|
|
556
|
+
/// the domain attribute of a cookie must not include a port. If a port is present (non-standard),
|
|
557
|
+
/// it will be ignored for domain matching purposes.
|
|
558
|
+
fn normalize_domain(domain: &str) -> &str {
|
|
559
|
+
domain.split(':').next().unwrap_or(domain)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/// Computes the normalized default path for a cookie as specified in
|
|
563
|
+
/// [RFC 6265 section 5.1.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4).
|
|
564
|
+
///
|
|
565
|
+
/// This function normalizes the path for a cookie, ensuring it matches
|
|
566
|
+
/// browser and server expectations for default cookie scope.
|
|
567
|
+
fn normalize_path(uri: &Uri) -> &str {
|
|
568
|
+
let path = uri.path();
|
|
569
|
+
if !path.starts_with(DEFAULT_PATH) {
|
|
570
|
+
return DEFAULT_PATH;
|
|
571
|
+
}
|
|
572
|
+
if let Some(pos) = path.rfind(DEFAULT_PATH) {
|
|
573
|
+
if pos == 0 {
|
|
574
|
+
return DEFAULT_PATH;
|
|
575
|
+
}
|
|
576
|
+
return &path[..pos];
|
|
577
|
+
}
|
|
578
|
+
DEFAULT_PATH
|
|
579
|
+
}
|