wreq-rb 0.5.0 → 0.5.1

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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +1922 -397
  3. data/LICENSE +203 -0
  4. data/README.md +19 -15
  5. data/ext/wreq_rb/Cargo.toml +4 -6
  6. data/ext/wreq_rb/src/client.rs +41 -48
  7. data/lib/wreq-rb/version.rb +1 -1
  8. data/patches/0001-add-transfer-size-tracking.patch +76 -67
  9. data/vendor/wreq/Cargo.toml +119 -71
  10. data/vendor/wreq/README.md +25 -20
  11. data/vendor/wreq/bench/http1.rs +25 -0
  12. data/vendor/wreq/bench/http1_over_tls.rs +25 -0
  13. data/vendor/wreq/bench/http2.rs +25 -0
  14. data/vendor/wreq/bench/http2_over_tls.rs +25 -0
  15. data/vendor/wreq/bench/support/bench.rs +91 -0
  16. data/vendor/wreq/bench/support/client.rs +217 -0
  17. data/vendor/wreq/bench/support/server.rs +188 -0
  18. data/vendor/wreq/bench/support.rs +56 -0
  19. data/vendor/wreq/examples/cert_store.rs +4 -4
  20. data/vendor/wreq/examples/{emulation.rs → emulate.rs} +2 -2
  21. data/vendor/wreq/examples/http2_websocket.rs +2 -2
  22. data/vendor/wreq/examples/keylog.rs +3 -3
  23. data/vendor/wreq/examples/{request_with_emulation.rs → request_with_emulate.rs} +2 -2
  24. data/vendor/wreq/examples/rt.rs +23 -0
  25. data/vendor/wreq/src/client/body.rs +23 -61
  26. data/vendor/wreq/src/client/emulate.rs +119 -0
  27. data/vendor/wreq/src/client/{http/future.rs → future.rs} +11 -32
  28. data/vendor/wreq/src/client/{http → layer}/client/pool.rs +66 -61
  29. data/vendor/wreq/src/client/{http → layer}/client.rs +416 -270
  30. data/vendor/wreq/src/client/layer/config.rs +27 -6
  31. data/vendor/wreq/src/client/layer/decoder.rs +9 -4
  32. data/vendor/wreq/src/client/layer/redirect/future.rs +6 -3
  33. data/vendor/wreq/src/client/layer/redirect.rs +4 -5
  34. data/vendor/wreq/src/client/layer/retry.rs +8 -5
  35. data/vendor/wreq/src/client/layer/timeout/body.rs +15 -6
  36. data/vendor/wreq/src/client/layer/timeout/future.rs +23 -18
  37. data/vendor/wreq/src/client/layer/timeout.rs +24 -74
  38. data/vendor/wreq/src/client/layer.rs +1 -2
  39. data/vendor/wreq/src/client/multipart.rs +137 -154
  40. data/vendor/wreq/src/client/request.rs +202 -118
  41. data/vendor/wreq/src/client/response.rs +46 -45
  42. data/vendor/wreq/src/client/upgrade.rs +15 -0
  43. data/vendor/wreq/src/client/ws.rs +73 -25
  44. data/vendor/wreq/src/client.rs +1655 -17
  45. data/vendor/wreq/src/config.rs +11 -11
  46. data/vendor/wreq/src/{client/conn → conn}/connector.rs +139 -137
  47. data/vendor/wreq/src/conn/descriptor.rs +143 -0
  48. data/vendor/wreq/src/conn/http.rs +484 -0
  49. data/vendor/wreq/src/conn/net/io.rs +75 -0
  50. data/vendor/wreq/src/conn/net/tcp/compio.rs +71 -0
  51. data/vendor/wreq/src/conn/net/tcp/tokio.rs +57 -0
  52. data/vendor/wreq/src/conn/net/tcp.rs +561 -0
  53. data/vendor/wreq/src/conn/net/uds/compio.rs +60 -0
  54. data/vendor/wreq/src/{client/conn/uds.rs → conn/net/uds/tokio.rs} +18 -12
  55. data/vendor/wreq/src/conn/net/uds.rs +11 -0
  56. data/vendor/wreq/src/conn/net.rs +130 -0
  57. data/vendor/wreq/src/{client/conn → conn}/proxy/socks.rs +2 -9
  58. data/vendor/wreq/src/{client/conn → conn}/proxy/tunnel.rs +21 -56
  59. data/vendor/wreq/src/conn/tls_info.rs +47 -0
  60. data/vendor/wreq/src/{client/conn.rs → conn.rs} +202 -54
  61. data/vendor/wreq/src/cookie.rs +302 -142
  62. data/vendor/wreq/src/dns/gai/compio.rs +77 -0
  63. data/vendor/wreq/src/dns/gai/tokio.rs +90 -0
  64. data/vendor/wreq/src/dns/gai.rs +14 -164
  65. data/vendor/wreq/src/dns/hickory.rs +16 -23
  66. data/vendor/wreq/src/dns/resolve.rs +7 -41
  67. data/vendor/wreq/src/dns.rs +90 -7
  68. data/vendor/wreq/src/error.rs +57 -31
  69. data/vendor/wreq/src/ext.rs +25 -0
  70. data/vendor/wreq/src/group.rs +211 -0
  71. data/vendor/wreq/src/header.rs +100 -112
  72. data/vendor/wreq/src/lib.rs +124 -73
  73. data/vendor/wreq/src/proxy.rs +6 -20
  74. data/vendor/wreq/src/redirect.rs +1 -1
  75. data/vendor/wreq/src/rt.rs +208 -0
  76. data/vendor/wreq/src/sync.rs +97 -98
  77. data/vendor/wreq/src/tls/compress.rs +124 -0
  78. data/vendor/wreq/src/tls/conn/ext.rs +54 -45
  79. data/vendor/wreq/src/tls/conn/service.rs +14 -18
  80. data/vendor/wreq/src/tls/conn.rs +169 -241
  81. data/vendor/wreq/src/tls/keylog.rs +68 -5
  82. data/vendor/wreq/src/tls/session.rs +205 -0
  83. data/vendor/wreq/src/tls/{x509 → trust}/identity.rs +4 -21
  84. data/vendor/wreq/src/tls/{x509/parser.rs → trust/parse.rs} +1 -1
  85. data/vendor/wreq/src/tls/{x509 → trust}/store.rs +42 -81
  86. data/vendor/wreq/src/tls/{x509.rs → trust.rs} +8 -2
  87. data/vendor/wreq/src/tls.rs +489 -25
  88. data/vendor/wreq/src/trace.rs +0 -12
  89. data/vendor/wreq/src/util.rs +1 -1
  90. data/vendor/wreq/tests/badssl.rs +10 -10
  91. data/vendor/wreq/tests/client.rs +3 -9
  92. data/vendor/wreq/tests/cookie.rs +6 -8
  93. data/vendor/wreq/tests/{emulation.rs → emulate.rs} +130 -22
  94. data/vendor/wreq/tests/multipart.rs +43 -1
  95. data/vendor/wreq/tests/proxy.rs +1 -1
  96. data/vendor/wreq/tests/support/layer.rs +1 -0
  97. metadata +49 -71
  98. data/patches/0002-add-cancel-connections.patch +0 -181
  99. data/vendor/wreq/src/client/conn/conn.rs +0 -231
  100. data/vendor/wreq/src/client/conn/http.rs +0 -1023
  101. data/vendor/wreq/src/client/conn/tls_info.rs +0 -98
  102. data/vendor/wreq/src/client/core/body/incoming.rs +0 -485
  103. data/vendor/wreq/src/client/core/body/length.rs +0 -118
  104. data/vendor/wreq/src/client/core/body.rs +0 -34
  105. data/vendor/wreq/src/client/core/common/buf.rs +0 -149
  106. data/vendor/wreq/src/client/core/common/rewind.rs +0 -141
  107. data/vendor/wreq/src/client/core/common/watch.rs +0 -76
  108. data/vendor/wreq/src/client/core/common.rs +0 -3
  109. data/vendor/wreq/src/client/core/conn/http1.rs +0 -342
  110. data/vendor/wreq/src/client/core/conn/http2.rs +0 -307
  111. data/vendor/wreq/src/client/core/conn.rs +0 -11
  112. data/vendor/wreq/src/client/core/dispatch.rs +0 -299
  113. data/vendor/wreq/src/client/core/error.rs +0 -435
  114. data/vendor/wreq/src/client/core/ext.rs +0 -201
  115. data/vendor/wreq/src/client/core/http1.rs +0 -178
  116. data/vendor/wreq/src/client/core/http2.rs +0 -483
  117. data/vendor/wreq/src/client/core/proto/h1/conn.rs +0 -988
  118. data/vendor/wreq/src/client/core/proto/h1/decode.rs +0 -1170
  119. data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +0 -684
  120. data/vendor/wreq/src/client/core/proto/h1/encode.rs +0 -580
  121. data/vendor/wreq/src/client/core/proto/h1/io.rs +0 -879
  122. data/vendor/wreq/src/client/core/proto/h1/role.rs +0 -694
  123. data/vendor/wreq/src/client/core/proto/h1.rs +0 -104
  124. data/vendor/wreq/src/client/core/proto/h2/client.rs +0 -650
  125. data/vendor/wreq/src/client/core/proto/h2/ping.rs +0 -539
  126. data/vendor/wreq/src/client/core/proto/h2.rs +0 -379
  127. data/vendor/wreq/src/client/core/proto/headers.rs +0 -138
  128. data/vendor/wreq/src/client/core/proto.rs +0 -58
  129. data/vendor/wreq/src/client/core/rt/bounds.rs +0 -57
  130. data/vendor/wreq/src/client/core/rt/timer.rs +0 -150
  131. data/vendor/wreq/src/client/core/rt/tokio.rs +0 -99
  132. data/vendor/wreq/src/client/core/rt.rs +0 -25
  133. data/vendor/wreq/src/client/core/upgrade.rs +0 -267
  134. data/vendor/wreq/src/client/core.rs +0 -16
  135. data/vendor/wreq/src/client/emulation.rs +0 -161
  136. data/vendor/wreq/src/client/http/client/error.rs +0 -142
  137. data/vendor/wreq/src/client/http/client/exec.rs +0 -29
  138. data/vendor/wreq/src/client/http/client/extra.rs +0 -77
  139. data/vendor/wreq/src/client/http/client/util.rs +0 -104
  140. data/vendor/wreq/src/client/http.rs +0 -1629
  141. data/vendor/wreq/src/client/layer/config/options.rs +0 -156
  142. data/vendor/wreq/src/client/layer/cookie.rs +0 -161
  143. data/vendor/wreq/src/hash.rs +0 -143
  144. data/vendor/wreq/src/tls/conn/cache.rs +0 -123
  145. data/vendor/wreq/src/tls/conn/cert_compression.rs +0 -125
  146. data/vendor/wreq/src/tls/keylog/handle.rs +0 -64
  147. data/vendor/wreq/src/tls/options.rs +0 -464
  148. /data/vendor/wreq/src/client/{http → layer}/client/lazy.rs +0 -0
  149. /data/vendor/wreq/src/{client/conn → conn}/proxy.rs +0 -0
  150. /data/vendor/wreq/src/{client/conn → conn}/verbose.rs +0 -0
@@ -1,19 +1,12 @@
1
1
  //! HTTP Cookies
2
2
 
3
- use std::{convert::TryInto, fmt, sync::Arc, time::SystemTime};
3
+ use std::{collections::HashMap, convert::TryInto, fmt, sync::Arc, time::SystemTime};
4
4
 
5
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
- };
6
+ use cookie::{Cookie as RawCookie, CookieJar, Expiration, SameSite, time::Duration};
7
+ use http::{Uri, Version};
8
+
9
+ use crate::{IntoUri, error::Error, ext::UriExt, header::HeaderValue, sync::RwLock};
17
10
 
18
11
  /// Cookie header values in two forms.
19
12
  #[derive(Debug, Clone)]
@@ -34,23 +27,28 @@ pub trait CookieStore: Send + Sync {
34
27
  /// Store a set of Set-Cookie header values received from `uri`
35
28
  fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, uri: &Uri);
36
29
 
37
- /// Get any [`Cookies`] in the store for `uri`
38
- fn cookies(&self, uri: &Uri) -> Cookies;
30
+ /// Returns cookies for the given URI and HTTP version.
31
+ ///
32
+ /// Following [RFC 9112 §5.6.3], HTTP/1.1 combines all cookies into a single header.
33
+ /// For [HTTP/2] and above, cookies are sent as separate header fields
34
+ /// as per [RFC 9113 §8.1.2.5].
35
+ ///
36
+ /// [RFC 9112 §5.6.3]: https://www.rfc-editor.org/rfc/rfc9112#section-5.6.3
37
+ /// [RFC 9113 §8.1.2.5]: https://www.rfc-editor.org/rfc/rfc9113#section-8.1.2.5
38
+ /// [HTTP/2]: https://datatracker.ietf.org/doc/html/rfc9113
39
+ fn cookies(&self, uri: &Uri, version: Version) -> Cookies;
39
40
  }
40
41
 
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>`].
42
+ impl_into_shared!(
43
+ /// Trait for converting types into a shared cookie store ([`Arc<dyn CookieStore>`]).
48
44
  ///
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
- }
45
+ /// Implemented for any [`CookieStore`] type, [`Arc<T>`] where `T: CookieStore`, and [`Arc<dyn
46
+ /// CookieStore>`]. Enables ergonomic conversion to a trait object for use in APIs without manual
47
+ /// boxing.
48
+ pub trait IntoCookieStore => CookieStore
49
+ );
50
+
51
+ impl_request_config_value!(Arc<dyn CookieStore>);
54
52
 
55
53
  /// Trait for converting types into an owned cookie ([`Cookie<'static>`]).
56
54
  pub trait IntoCookie {
@@ -67,43 +65,8 @@ pub struct Cookie<'a>(RawCookie<'a>);
67
65
  /// This is the implementation used when simply calling `cookie_store(true)`.
68
66
  /// This type is exposed to allow creating one and filling it with some
69
67
  /// 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
- }
68
+ #[derive(Debug, Default)]
69
+ pub struct Jar(RwLock<HashMap<String, HashMap<String, CookieJar>>>);
107
70
 
108
71
  // ===== impl IntoCookie =====
109
72
 
@@ -243,30 +206,6 @@ macro_rules! into_uri {
243
206
  }
244
207
 
245
208
  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
209
  /// Get a cookie by name for a given Uri.
271
210
  ///
272
211
  /// Returns the cookie with the specified name for the domain and path
@@ -276,16 +215,17 @@ impl Jar {
276
215
  /// ```
277
216
  /// use wreq::cookie::Jar;
278
217
  /// let jar = Jar::default();
279
- /// jar.add_cookie_str("foo=bar; Path=/foo; Domain=example.com", "http://example.com/foo");
218
+ /// jar.add("foo=bar; Path=/foo; Domain=example.com", "http://example.com/foo");
280
219
  /// let cookie = jar.get("foo", "http://example.com/foo").unwrap();
281
220
  /// assert_eq!(cookie.value(), "bar");
282
221
  /// ```
283
222
  pub fn get<U: IntoUri>(&self, name: &str, uri: U) -> Option<Cookie<'static>> {
284
223
  let uri = uri.into_uri().ok()?;
224
+ let host = normalize_domain(uri.host()?);
285
225
  let cookie = self
286
- .store
226
+ .0
287
227
  .read()
288
- .get(uri.host()?)?
228
+ .get(host)?
289
229
  .get(uri.path())?
290
230
  .get(name)?
291
231
  .clone()
@@ -302,20 +242,30 @@ impl Jar {
302
242
  /// ```
303
243
  /// use wreq::cookie::Jar;
304
244
  /// let jar = Jar::default();
305
- /// jar.add_cookie_str("foo=bar; Domain=example.com", "http://example.com");
245
+ /// jar.add("foo=bar; Domain=example.com", "http://example.com");
306
246
  /// for cookie in jar.get_all() {
307
247
  /// println!("{}={}", cookie.name(), cookie.value());
308
248
  /// }
309
249
  /// ```
310
250
  pub fn get_all(&self) -> impl Iterator<Item = Cookie<'static>> {
311
- self.store
251
+ self.0
312
252
  .read()
313
253
  .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()))
254
+ .flat_map(|(domain, path_map)| {
255
+ path_map.iter().flat_map(|(path, name_map)| {
256
+ name_map.iter().map(|cookie| {
257
+ let mut cookie = cookie.clone().into_owned();
258
+
259
+ if cookie.domain().is_none() {
260
+ cookie.set_domain(domain.to_owned());
261
+ }
262
+
263
+ if cookie.path().is_none() {
264
+ cookie.set_path(path.to_owned());
265
+ }
266
+
267
+ Cookie(cookie)
268
+ })
319
269
  })
320
270
  })
321
271
  .collect::<Vec<_>>()
@@ -348,19 +298,51 @@ impl Jar {
348
298
  U: IntoUri,
349
299
  {
350
300
  if let Some(cookie) = cookie.into_cookie() {
351
- let cookie: RawCookie<'static> = cookie.into();
352
301
  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();
302
+ let mut cookie: RawCookie<'static> = cookie.into();
303
+
304
+ // If the request-uri contains no host component:
305
+ let Some(host) = uri.host() else {
306
+ return;
307
+ };
308
+
309
+ // If the canonicalized request-host does not domain-match the
310
+ // domain-attribute:
311
+ // Ignore the cookie entirely and abort these steps.
312
+ //
313
+ // RFC 6265 §5.3 + §5.1.3:
314
+ // https://datatracker.ietf.org/doc/html/rfc6265#section-5.3
315
+ // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3
316
+ let domain = if let Some(domain) = cookie.domain() {
317
+ let domain = normalize_domain(domain);
318
+ if domain.is_empty() || !domain_match(normalize_domain(host), domain) {
319
+ return;
320
+ }
321
+ domain
322
+ } else {
323
+ normalize_domain(host)
324
+ };
325
+
326
+ // If the request-uri contains no path component or if the first character of the
327
+ // path component of the request-uri is not a %x2F ("/") OR if the cookie's path-
328
+ // attribute is missing or does not start with a %x2F ("/"):
329
+ // Let cookie-path be the default-path of the request-uri.
330
+ // Otherwise:
331
+ // Let cookie-path be the substring of the request-uri's path from the first
332
+ // character up to, not including, the right-most %x2F ("/").
333
+ //
334
+ // RFC 6265 §5.2.4 + §5.1.4:
335
+ // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4
336
+ // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
337
+ let path = cookie
338
+ .path()
339
+ .filter(|path| path.starts_with(DEFAULT_PATH))
340
+ .unwrap_or_else(|| normalize_path(uri.path()));
341
+
342
+ let mut inner = self.0.write();
361
343
  let name_map = inner
362
344
  .entry(domain.to_owned())
363
- .or_insert_with(|| HashMap::with_hasher(HASHER))
345
+ .or_default()
364
346
  .entry(path.to_owned())
365
347
  .or_default();
366
348
 
@@ -368,11 +350,12 @@ impl Jar {
368
350
  let expired = cookie
369
351
  .expires_datetime()
370
352
  .is_some_and(|dt| dt <= SystemTime::now())
371
- || cookie.max_age().is_some_and(|age| age.is_zero());
353
+ || cookie.max_age().is_some_and(Duration::is_zero);
372
354
 
373
355
  if expired {
374
356
  name_map.remove(cookie);
375
357
  } else {
358
+ cookie.set_path(path.to_owned());
376
359
  name_map.add(cookie);
377
360
  }
378
361
  }
@@ -387,7 +370,7 @@ impl Jar {
387
370
  /// ```
388
371
  /// use wreq::cookie::Jar;
389
372
  /// let jar = Jar::default();
390
- /// jar.add_cookie_str("foo=bar; Path=/foo; Domain=example.com", "http://example.com/foo");
373
+ /// jar.add("foo=bar; Path=/foo; Domain=example.com", "http://example.com/foo");
391
374
  /// assert!(jar.get("foo", "http://example.com/foo").is_some());
392
375
  /// jar.remove("foo", "http://example.com/foo");
393
376
  /// assert!(jar.get("foo", "http://example.com/foo").is_none());
@@ -399,7 +382,8 @@ impl Jar {
399
382
  {
400
383
  let uri = into_uri!(uri);
401
384
  if let Some(host) = uri.host() {
402
- let mut inner = self.store.write();
385
+ let host = normalize_domain(host);
386
+ let mut inner = self.0.write();
403
387
  if let Some(path_map) = inner.get_mut(host) {
404
388
  if let Some(name_map) = path_map.get_mut(uri.path()) {
405
389
  name_map.remove(cookie.into());
@@ -416,13 +400,13 @@ impl Jar {
416
400
  /// ```
417
401
  /// use wreq::cookie::Jar;
418
402
  /// let jar = Jar::default();
419
- /// jar.add_cookie_str("foo=bar; Domain=example.com", "http://example.com");
403
+ /// jar.add("foo=bar; Domain=example.com", "http://example.com");
420
404
  /// assert_eq!(jar.get_all().count(), 1);
421
405
  /// jar.clear();
422
406
  /// assert_eq!(jar.get_all().count(), 0);
423
407
  /// ```
424
408
  pub fn clear(&self) {
425
- self.store.write().clear();
409
+ self.0.write().clear();
426
410
  }
427
411
  }
428
412
 
@@ -438,13 +422,13 @@ impl CookieStore for Jar {
438
422
  }
439
423
  }
440
424
 
441
- fn cookies(&self, uri: &Uri) -> Cookies {
425
+ fn cookies(&self, uri: &Uri, version: Version) -> Cookies {
442
426
  let host = match uri.host() {
443
- Some(h) => h,
427
+ Some(h) => normalize_domain(h),
444
428
  None => return Cookies::Empty,
445
429
  };
446
430
 
447
- let store = self.store.read();
431
+ let store = self.0.read();
448
432
  let iter = store
449
433
  .iter()
450
434
  .filter(|(domain, _)| domain_match(host, domain))
@@ -470,7 +454,24 @@ impl CookieStore for Jar {
470
454
  })
471
455
  });
472
456
 
473
- if self.compression {
457
+ if matches!(version, Version::HTTP_2 | Version::HTTP_3) {
458
+ let cookies = iter
459
+ .map(|cookie| {
460
+ let name = cookie.name();
461
+ let value = cookie.value();
462
+
463
+ let mut cookie_str = String::with_capacity(name.len() + 1 + value.len());
464
+ cookie_str.push_str(name);
465
+ cookie_str.push('=');
466
+ cookie_str.push_str(value);
467
+
468
+ HeaderValue::from_maybe_shared(Bytes::from(cookie_str))
469
+ })
470
+ .filter_map(Result::ok)
471
+ .collect();
472
+
473
+ Cookies::Uncompressed(cookies)
474
+ } else {
474
475
  let cookies = iter.fold(String::new(), |mut cookies, cookie| {
475
476
  if !cookies.is_empty() {
476
477
  cookies.push_str("; ");
@@ -488,33 +489,10 @@ impl CookieStore for Jar {
488
489
  HeaderValue::from_maybe_shared(Bytes::from(cookies))
489
490
  .map(Cookies::Compressed)
490
491
  .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
492
  }
509
493
  }
510
494
  }
511
495
 
512
- impl Default for Jar {
513
- fn default() -> Self {
514
- Self::new(true)
515
- }
516
- }
517
-
518
496
  const DEFAULT_PATH: &str = "/";
519
497
 
520
498
  /// Determines if the given `host` matches the cookie `domain` according to
@@ -556,7 +534,11 @@ fn path_match(req_path: &str, cookie_path: &str) -> bool {
556
534
  /// the domain attribute of a cookie must not include a port. If a port is present (non-standard),
557
535
  /// it will be ignored for domain matching purposes.
558
536
  fn normalize_domain(domain: &str) -> &str {
559
- domain.split(':').next().unwrap_or(domain)
537
+ let host_without_port = domain.split(':').next().unwrap_or(domain);
538
+ let without_leading = host_without_port
539
+ .strip_prefix(".")
540
+ .unwrap_or(host_without_port);
541
+ without_leading.strip_suffix(".").unwrap_or(without_leading)
560
542
  }
561
543
 
562
544
  /// Computes the normalized default path for a cookie as specified in
@@ -564,8 +546,7 @@ fn normalize_domain(domain: &str) -> &str {
564
546
  ///
565
547
  /// This function normalizes the path for a cookie, ensuring it matches
566
548
  /// browser and server expectations for default cookie scope.
567
- fn normalize_path(uri: &Uri) -> &str {
568
- let path = uri.path();
549
+ fn normalize_path(path: &str) -> &str {
569
550
  if !path.starts_with(DEFAULT_PATH) {
570
551
  return DEFAULT_PATH;
571
552
  }
@@ -577,3 +558,182 @@ fn normalize_path(uri: &Uri) -> &str {
577
558
  }
578
559
  DEFAULT_PATH
579
560
  }
561
+
562
+ #[cfg(test)]
563
+ mod tests {
564
+ use http::{Uri, Version};
565
+
566
+ use super::{CookieStore, Cookies, Jar};
567
+
568
+ #[test]
569
+ fn jar_get_all_backfills_domain_and_path() {
570
+ let jar = Jar::default();
571
+ jar.add("session=abc", "http://example.com/foo/bar");
572
+
573
+ let cookies = jar.get_all().collect::<Vec<_>>();
574
+ assert_eq!(cookies.len(), 1);
575
+
576
+ let cookie = &cookies[0];
577
+ assert_eq!(cookie.name(), "session");
578
+ assert_eq!(cookie.value(), "abc");
579
+ assert_eq!(cookie.domain(), Some("example.com"));
580
+ assert_eq!(cookie.path(), Some("/foo"));
581
+ }
582
+
583
+ #[test]
584
+ fn jar_get_all_keeps_existing_domain_and_path() {
585
+ let jar = Jar::default();
586
+ jar.add(
587
+ "session=abc; Domain=example.com; Path=/custom",
588
+ "http://example.com/foo/bar",
589
+ );
590
+
591
+ let cookies = jar.get_all().collect::<Vec<_>>();
592
+ assert_eq!(cookies.len(), 1);
593
+
594
+ let cookie = &cookies[0];
595
+ assert_eq!(cookie.name(), "session");
596
+ assert_eq!(cookie.value(), "abc");
597
+ assert_eq!(cookie.domain(), Some("example.com"));
598
+ assert_eq!(cookie.path(), Some("/custom"));
599
+ }
600
+
601
+ #[test]
602
+ fn jar_get_all_backfills_only_missing_field() {
603
+ let jar = Jar::default();
604
+ jar.add("a=1; Domain=example.com", "http://example.com/foo/bar");
605
+ jar.add("b=2; Path=/fixed", "http://example.com/foo/bar");
606
+
607
+ let mut cookies = jar.get_all().collect::<Vec<_>>();
608
+ cookies.sort_by(|left, right| left.name().cmp(right.name()));
609
+
610
+ let a = &cookies[0];
611
+ assert_eq!(a.name(), "a");
612
+ assert_eq!(a.domain(), Some("example.com"));
613
+ assert_eq!(a.path(), Some("/foo"));
614
+
615
+ let b = &cookies[1];
616
+ assert_eq!(b.name(), "b");
617
+ assert_eq!(b.domain(), Some("example.com"));
618
+ assert_eq!(b.path(), Some("/fixed"));
619
+ }
620
+
621
+ #[test]
622
+ fn jar_add_rejects_mismatched_domain() {
623
+ let jar = Jar::default();
624
+ jar.add("session=abc; Domain=other.com", "http://example.com/foo");
625
+
626
+ assert_eq!(jar.get_all().count(), 0);
627
+ }
628
+
629
+ #[test]
630
+ fn jar_add_accepts_matching_parent_domain() {
631
+ let jar = Jar::default();
632
+ jar.add(
633
+ "session=abc; Domain=example.com",
634
+ "http://api.example.com/foo",
635
+ );
636
+
637
+ let cookies = jar.get_all().collect::<Vec<_>>();
638
+ assert_eq!(cookies.len(), 1);
639
+ assert_eq!(cookies[0].domain(), Some("example.com"));
640
+ }
641
+
642
+ #[test]
643
+ fn jar_get_all_export_import_keeps_effective_path() {
644
+ let source = Jar::default();
645
+ source.add("session=abc", "http://example.com/foo/bar");
646
+
647
+ let exported = source.get_all().collect::<Vec<_>>();
648
+ assert_eq!(exported.len(), 1);
649
+ assert_eq!(exported[0].path(), Some("/foo"));
650
+
651
+ let target = Jar::default();
652
+ for cookie in exported {
653
+ target.add(cookie, "http://example.com/another/deeper");
654
+ }
655
+
656
+ let imported = target.get_all().collect::<Vec<_>>();
657
+ assert_eq!(imported.len(), 1);
658
+ assert_eq!(imported[0].path(), Some("/foo"));
659
+ }
660
+
661
+ #[test]
662
+ fn cookie_store_invalid_explicit_path_falls_back_to_default_path() {
663
+ let jar = Jar::default();
664
+ jar.add("key=val; Path=noslash", "http://example.com/foo/bar");
665
+
666
+ assert!(jar.get("key", "http://example.com/foo").is_some());
667
+ assert!(jar.get("key", "http://example.com/noslash").is_none());
668
+
669
+ let cookies = jar.get_all().collect::<Vec<_>>();
670
+ assert_eq!(cookies.len(), 1);
671
+ assert_eq!(cookies[0].path(), Some("/foo"));
672
+ }
673
+
674
+ #[test]
675
+ fn jar_sends_parent_domain_cookie_to_subdomain() {
676
+ let jar = Jar::default();
677
+ jar.add(
678
+ "session=abc; Domain=example.com; Path=/",
679
+ "http://example.com/login",
680
+ );
681
+
682
+ let should_receive = [
683
+ "http://example.com/dashboard",
684
+ "http://api.example.com/dashboard",
685
+ "http://sub.api.example.com/dashboard",
686
+ ];
687
+ for uri_str in &should_receive {
688
+ let uri = Uri::from_static(uri_str);
689
+ match jar.cookies(&uri, Version::HTTP_11) {
690
+ Cookies::Compressed(v) => assert_eq!(
691
+ v.to_str().unwrap(),
692
+ "session=abc",
693
+ "expected cookie to be sent to {uri_str}"
694
+ ),
695
+ other => panic!("expected Compressed cookie for {uri_str}, got {other:?}"),
696
+ }
697
+ }
698
+
699
+ let should_not_receive = [
700
+ "http://notexample.com/dashboard",
701
+ "http://fakeexample.com/dashboard",
702
+ ];
703
+ for uri_str in &should_not_receive {
704
+ let uri = Uri::from_static(uri_str);
705
+ assert!(
706
+ matches!(jar.cookies(&uri, Version::HTTP_11), Cookies::Empty),
707
+ "cookie must NOT be sent to {uri_str}"
708
+ );
709
+ }
710
+ }
711
+
712
+ #[test]
713
+ fn jar_subdomain_cookie_does_not_leak_to_parent_or_sibling() {
714
+ let jar = Jar::default();
715
+ jar.add(
716
+ "token=xyz; Domain=api.example.com; Path=/",
717
+ "http://api.example.com/",
718
+ );
719
+
720
+ let uri = Uri::from_static("http://api.example.com/");
721
+ assert!(
722
+ matches!(jar.cookies(&uri, Version::HTTP_11), Cookies::Compressed(_)),
723
+ "cookie must be sent to api.example.com"
724
+ );
725
+
726
+ let must_not_receive = [
727
+ "http://example.com/",
728
+ "http://other.example.com/",
729
+ "http://notapi.example.com/",
730
+ ];
731
+ for uri_str in &must_not_receive {
732
+ let uri = Uri::from_static(uri_str);
733
+ assert!(
734
+ matches!(jar.cookies(&uri, Version::HTTP_11), Cookies::Empty),
735
+ "cookie must NOT leak to {uri_str}"
736
+ );
737
+ }
738
+ }
739
+ }
@@ -0,0 +1,77 @@
1
+ // This module contains the `GaiResolver` implementation for the `compio` runtime.
2
+
3
+ use std::{
4
+ future::Future,
5
+ io,
6
+ net::ToSocketAddrs,
7
+ pin::Pin,
8
+ task::{self, Poll},
9
+ };
10
+
11
+ use tower::Service;
12
+
13
+ use super::{GaiAddrs, GaiResolver};
14
+ use crate::dns::{Addrs, Name, Resolve, Resolving, SocketAddrs};
15
+
16
+ /// A future to resolve a name returned by `GaiResolver`.
17
+ pub struct GaiFuture {
18
+ inner: compio::runtime::JoinHandle<Result<SocketAddrs, io::Error>>,
19
+ }
20
+
21
+ // ==== impl GaiResolver ====
22
+
23
+ impl GaiResolver {
24
+ /// Creates a new [`GaiResolver`].
25
+ pub fn new() -> Self {
26
+ GaiResolver { _priv: () }
27
+ }
28
+ }
29
+
30
+ impl Service<Name> for GaiResolver {
31
+ type Response = GaiAddrs;
32
+ type Error = io::Error;
33
+ type Future = GaiFuture;
34
+
35
+ #[inline]
36
+ fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> {
37
+ Poll::Ready(Ok(()))
38
+ }
39
+
40
+ fn call(&mut self, name: Name) -> Self::Future {
41
+ let blocking = compio::runtime::spawn_blocking(move || {
42
+ debug!("resolving {}", name);
43
+ (name.as_str(), 0)
44
+ .to_socket_addrs()
45
+ .map(|i| SocketAddrs::new(i.collect()))
46
+ });
47
+ GaiFuture { inner: blocking }
48
+ }
49
+ }
50
+
51
+ impl Resolve for GaiResolver {
52
+ fn resolve(&self, name: Name) -> Resolving {
53
+ let mut this = self.clone();
54
+ Box::pin(async move {
55
+ this.call(name)
56
+ .await
57
+ .map(|addrs| Box::new(addrs) as Addrs)
58
+ .map_err(Into::into)
59
+ })
60
+ }
61
+ }
62
+
63
+ // ==== impl GaiFuture ====
64
+
65
+ impl Future for GaiFuture {
66
+ type Output = Result<GaiAddrs, io::Error>;
67
+
68
+ fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
69
+ Pin::new(&mut self.inner).poll(cx).map(|res| match res {
70
+ Ok(Ok(addrs)) => Ok(GaiAddrs { inner: addrs }),
71
+ Ok(Err(err)) => Err(err),
72
+ Err(join_err) => Err(io::Error::other(format!(
73
+ "DNS resolution blocked task panicked: {join_err}"
74
+ ))),
75
+ })
76
+ }
77
+ }