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.
Files changed (167) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +2688 -0
  3. data/Cargo.toml +6 -0
  4. data/README.md +179 -0
  5. data/ext/wreq_rb/Cargo.toml +39 -0
  6. data/ext/wreq_rb/extconf.rb +22 -0
  7. data/ext/wreq_rb/src/client.rs +565 -0
  8. data/ext/wreq_rb/src/error.rs +25 -0
  9. data/ext/wreq_rb/src/lib.rs +20 -0
  10. data/ext/wreq_rb/src/response.rs +132 -0
  11. data/lib/wreq-rb/version.rb +5 -0
  12. data/lib/wreq-rb.rb +17 -0
  13. data/patches/0001-add-transfer-size-tracking.patch +292 -0
  14. data/vendor/wreq/Cargo.toml +306 -0
  15. data/vendor/wreq/LICENSE +202 -0
  16. data/vendor/wreq/README.md +122 -0
  17. data/vendor/wreq/examples/cert_store.rs +77 -0
  18. data/vendor/wreq/examples/connect_via_lower_priority_tokio_runtime.rs +258 -0
  19. data/vendor/wreq/examples/emulation.rs +118 -0
  20. data/vendor/wreq/examples/form.rs +14 -0
  21. data/vendor/wreq/examples/http1_websocket.rs +37 -0
  22. data/vendor/wreq/examples/http2_websocket.rs +45 -0
  23. data/vendor/wreq/examples/json_dynamic.rs +41 -0
  24. data/vendor/wreq/examples/json_typed.rs +47 -0
  25. data/vendor/wreq/examples/keylog.rs +16 -0
  26. data/vendor/wreq/examples/request_with_emulation.rs +115 -0
  27. data/vendor/wreq/examples/request_with_interface.rs +37 -0
  28. data/vendor/wreq/examples/request_with_local_address.rs +16 -0
  29. data/vendor/wreq/examples/request_with_proxy.rs +13 -0
  30. data/vendor/wreq/examples/request_with_redirect.rs +22 -0
  31. data/vendor/wreq/examples/request_with_version.rs +15 -0
  32. data/vendor/wreq/examples/tor_socks.rs +24 -0
  33. data/vendor/wreq/examples/unix_socket.rs +33 -0
  34. data/vendor/wreq/src/client/body.rs +304 -0
  35. data/vendor/wreq/src/client/conn/conn.rs +231 -0
  36. data/vendor/wreq/src/client/conn/connector.rs +549 -0
  37. data/vendor/wreq/src/client/conn/http.rs +1023 -0
  38. data/vendor/wreq/src/client/conn/proxy/socks.rs +233 -0
  39. data/vendor/wreq/src/client/conn/proxy/tunnel.rs +260 -0
  40. data/vendor/wreq/src/client/conn/proxy.rs +39 -0
  41. data/vendor/wreq/src/client/conn/tls_info.rs +98 -0
  42. data/vendor/wreq/src/client/conn/uds.rs +44 -0
  43. data/vendor/wreq/src/client/conn/verbose.rs +149 -0
  44. data/vendor/wreq/src/client/conn.rs +323 -0
  45. data/vendor/wreq/src/client/core/body/incoming.rs +485 -0
  46. data/vendor/wreq/src/client/core/body/length.rs +118 -0
  47. data/vendor/wreq/src/client/core/body.rs +34 -0
  48. data/vendor/wreq/src/client/core/common/buf.rs +149 -0
  49. data/vendor/wreq/src/client/core/common/rewind.rs +141 -0
  50. data/vendor/wreq/src/client/core/common/watch.rs +76 -0
  51. data/vendor/wreq/src/client/core/common.rs +3 -0
  52. data/vendor/wreq/src/client/core/conn/http1.rs +342 -0
  53. data/vendor/wreq/src/client/core/conn/http2.rs +307 -0
  54. data/vendor/wreq/src/client/core/conn.rs +11 -0
  55. data/vendor/wreq/src/client/core/dispatch.rs +299 -0
  56. data/vendor/wreq/src/client/core/error.rs +435 -0
  57. data/vendor/wreq/src/client/core/ext.rs +201 -0
  58. data/vendor/wreq/src/client/core/http1.rs +178 -0
  59. data/vendor/wreq/src/client/core/http2.rs +483 -0
  60. data/vendor/wreq/src/client/core/proto/h1/conn.rs +988 -0
  61. data/vendor/wreq/src/client/core/proto/h1/decode.rs +1170 -0
  62. data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +684 -0
  63. data/vendor/wreq/src/client/core/proto/h1/encode.rs +580 -0
  64. data/vendor/wreq/src/client/core/proto/h1/io.rs +879 -0
  65. data/vendor/wreq/src/client/core/proto/h1/role.rs +694 -0
  66. data/vendor/wreq/src/client/core/proto/h1.rs +104 -0
  67. data/vendor/wreq/src/client/core/proto/h2/client.rs +650 -0
  68. data/vendor/wreq/src/client/core/proto/h2/ping.rs +539 -0
  69. data/vendor/wreq/src/client/core/proto/h2.rs +379 -0
  70. data/vendor/wreq/src/client/core/proto/headers.rs +138 -0
  71. data/vendor/wreq/src/client/core/proto.rs +58 -0
  72. data/vendor/wreq/src/client/core/rt/bounds.rs +57 -0
  73. data/vendor/wreq/src/client/core/rt/timer.rs +150 -0
  74. data/vendor/wreq/src/client/core/rt/tokio.rs +99 -0
  75. data/vendor/wreq/src/client/core/rt.rs +25 -0
  76. data/vendor/wreq/src/client/core/upgrade.rs +267 -0
  77. data/vendor/wreq/src/client/core.rs +16 -0
  78. data/vendor/wreq/src/client/emulation.rs +161 -0
  79. data/vendor/wreq/src/client/http/client/error.rs +142 -0
  80. data/vendor/wreq/src/client/http/client/exec.rs +29 -0
  81. data/vendor/wreq/src/client/http/client/extra.rs +77 -0
  82. data/vendor/wreq/src/client/http/client/lazy.rs +79 -0
  83. data/vendor/wreq/src/client/http/client/pool.rs +1105 -0
  84. data/vendor/wreq/src/client/http/client/util.rs +104 -0
  85. data/vendor/wreq/src/client/http/client.rs +1003 -0
  86. data/vendor/wreq/src/client/http/future.rs +99 -0
  87. data/vendor/wreq/src/client/http.rs +1629 -0
  88. data/vendor/wreq/src/client/layer/config/options.rs +156 -0
  89. data/vendor/wreq/src/client/layer/config.rs +116 -0
  90. data/vendor/wreq/src/client/layer/cookie.rs +161 -0
  91. data/vendor/wreq/src/client/layer/decoder.rs +139 -0
  92. data/vendor/wreq/src/client/layer/redirect/future.rs +270 -0
  93. data/vendor/wreq/src/client/layer/redirect/policy.rs +63 -0
  94. data/vendor/wreq/src/client/layer/redirect.rs +145 -0
  95. data/vendor/wreq/src/client/layer/retry/classify.rs +105 -0
  96. data/vendor/wreq/src/client/layer/retry/scope.rs +51 -0
  97. data/vendor/wreq/src/client/layer/retry.rs +151 -0
  98. data/vendor/wreq/src/client/layer/timeout/body.rs +233 -0
  99. data/vendor/wreq/src/client/layer/timeout/future.rs +90 -0
  100. data/vendor/wreq/src/client/layer/timeout.rs +177 -0
  101. data/vendor/wreq/src/client/layer.rs +15 -0
  102. data/vendor/wreq/src/client/multipart.rs +717 -0
  103. data/vendor/wreq/src/client/request.rs +818 -0
  104. data/vendor/wreq/src/client/response.rs +534 -0
  105. data/vendor/wreq/src/client/ws/json.rs +99 -0
  106. data/vendor/wreq/src/client/ws/message.rs +453 -0
  107. data/vendor/wreq/src/client/ws.rs +714 -0
  108. data/vendor/wreq/src/client.rs +27 -0
  109. data/vendor/wreq/src/config.rs +140 -0
  110. data/vendor/wreq/src/cookie.rs +579 -0
  111. data/vendor/wreq/src/dns/gai.rs +249 -0
  112. data/vendor/wreq/src/dns/hickory.rs +78 -0
  113. data/vendor/wreq/src/dns/resolve.rs +180 -0
  114. data/vendor/wreq/src/dns.rs +69 -0
  115. data/vendor/wreq/src/error.rs +502 -0
  116. data/vendor/wreq/src/ext.rs +398 -0
  117. data/vendor/wreq/src/hash.rs +143 -0
  118. data/vendor/wreq/src/header.rs +506 -0
  119. data/vendor/wreq/src/into_uri.rs +187 -0
  120. data/vendor/wreq/src/lib.rs +586 -0
  121. data/vendor/wreq/src/proxy/mac.rs +82 -0
  122. data/vendor/wreq/src/proxy/matcher.rs +806 -0
  123. data/vendor/wreq/src/proxy/uds.rs +66 -0
  124. data/vendor/wreq/src/proxy/win.rs +31 -0
  125. data/vendor/wreq/src/proxy.rs +569 -0
  126. data/vendor/wreq/src/redirect.rs +575 -0
  127. data/vendor/wreq/src/retry.rs +198 -0
  128. data/vendor/wreq/src/sync.rs +129 -0
  129. data/vendor/wreq/src/tls/conn/cache.rs +123 -0
  130. data/vendor/wreq/src/tls/conn/cert_compression.rs +125 -0
  131. data/vendor/wreq/src/tls/conn/ext.rs +82 -0
  132. data/vendor/wreq/src/tls/conn/macros.rs +34 -0
  133. data/vendor/wreq/src/tls/conn/service.rs +138 -0
  134. data/vendor/wreq/src/tls/conn.rs +681 -0
  135. data/vendor/wreq/src/tls/keylog/handle.rs +64 -0
  136. data/vendor/wreq/src/tls/keylog.rs +99 -0
  137. data/vendor/wreq/src/tls/options.rs +464 -0
  138. data/vendor/wreq/src/tls/x509/identity.rs +122 -0
  139. data/vendor/wreq/src/tls/x509/parser.rs +71 -0
  140. data/vendor/wreq/src/tls/x509/store.rs +228 -0
  141. data/vendor/wreq/src/tls/x509.rs +68 -0
  142. data/vendor/wreq/src/tls.rs +154 -0
  143. data/vendor/wreq/src/trace.rs +55 -0
  144. data/vendor/wreq/src/util.rs +122 -0
  145. data/vendor/wreq/tests/badssl.rs +228 -0
  146. data/vendor/wreq/tests/brotli.rs +350 -0
  147. data/vendor/wreq/tests/client.rs +1098 -0
  148. data/vendor/wreq/tests/connector_layers.rs +227 -0
  149. data/vendor/wreq/tests/cookie.rs +306 -0
  150. data/vendor/wreq/tests/deflate.rs +347 -0
  151. data/vendor/wreq/tests/emulation.rs +260 -0
  152. data/vendor/wreq/tests/gzip.rs +347 -0
  153. data/vendor/wreq/tests/layers.rs +261 -0
  154. data/vendor/wreq/tests/multipart.rs +165 -0
  155. data/vendor/wreq/tests/proxy.rs +438 -0
  156. data/vendor/wreq/tests/redirect.rs +629 -0
  157. data/vendor/wreq/tests/retry.rs +135 -0
  158. data/vendor/wreq/tests/support/delay_server.rs +117 -0
  159. data/vendor/wreq/tests/support/error.rs +16 -0
  160. data/vendor/wreq/tests/support/layer.rs +183 -0
  161. data/vendor/wreq/tests/support/mod.rs +9 -0
  162. data/vendor/wreq/tests/support/server.rs +232 -0
  163. data/vendor/wreq/tests/timeouts.rs +281 -0
  164. data/vendor/wreq/tests/unix_socket.rs +135 -0
  165. data/vendor/wreq/tests/upgrade.rs +98 -0
  166. data/vendor/wreq/tests/zstd.rs +559 -0
  167. 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
+ }