wreq-rb 0.6.0 → 0.6.2

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +257 -102
  3. data/ext/wreq_rb/Cargo.toml +7 -4
  4. data/ext/wreq_rb/src/client.rs +105 -15
  5. data/ext/wreq_rb/src/response.rs +28 -3
  6. data/lib/wreq-rb/version.rb +1 -1
  7. data/patches/0001-add-transfer-size-tracking.patch +11 -15
  8. data/vendor/wreq/Cargo.toml +9 -8
  9. data/vendor/wreq/README.md +5 -5
  10. data/vendor/wreq/bench/support/bench.rs +6 -2
  11. data/vendor/wreq/bench/support/client.rs +88 -1
  12. data/vendor/wreq/bench/support/exec.rs +0 -0
  13. data/vendor/wreq/bench/support/rt.rs +34 -0
  14. data/vendor/wreq/bench/support/server.rs +1 -1
  15. data/vendor/wreq/bench/support.rs +1 -15
  16. data/vendor/wreq/examples/cert_store.rs +13 -13
  17. data/vendor/wreq/examples/request_with_emulate.rs +1 -1
  18. data/vendor/wreq/examples/tcp_linger.rs +22 -0
  19. data/vendor/wreq/src/client/layer/client/pool.rs +17 -17
  20. data/vendor/wreq/src/client/layer/client.rs +2 -0
  21. data/vendor/wreq/src/client/layer/decoder.rs +71 -17
  22. data/vendor/wreq/src/client/layer/redirect/future.rs +49 -63
  23. data/vendor/wreq/src/client/layer/redirect/policy.rs +2 -26
  24. data/vendor/wreq/src/client/layer/redirect.rs +48 -60
  25. data/vendor/wreq/src/client/layer/retry.rs +12 -15
  26. data/vendor/wreq/src/client/layer/timeout/body.rs +27 -21
  27. data/vendor/wreq/src/client/layer/timeout/future.rs +33 -58
  28. data/vendor/wreq/src/client/layer/timeout.rs +8 -14
  29. data/vendor/wreq/src/client/request.rs +4 -0
  30. data/vendor/wreq/src/client.rs +53 -31
  31. data/vendor/wreq/src/conn/connector.rs +99 -129
  32. data/vendor/wreq/src/conn/http.rs +25 -18
  33. data/vendor/wreq/src/conn/net/tcp.rs +601 -107
  34. data/vendor/wreq/src/conn/proxy/socks.rs +6 -6
  35. data/vendor/wreq/src/conn/timeout.rs +166 -0
  36. data/vendor/wreq/src/conn.rs +5 -4
  37. data/vendor/wreq/src/cookie/jar.rs +1225 -0
  38. data/vendor/wreq/src/cookie/store.rs +321 -0
  39. data/vendor/wreq/src/cookie.rs +108 -612
  40. data/vendor/wreq/src/dns/resolve.rs +8 -2
  41. data/vendor/wreq/src/dns.rs +4 -4
  42. data/vendor/wreq/src/error.rs +53 -20
  43. data/vendor/wreq/src/lib.rs +1 -0
  44. data/vendor/wreq/src/proxy/matcher.rs +26 -12
  45. data/vendor/wreq/src/proxy/win.rs +39 -9
  46. data/vendor/wreq/src/redirect.rs +515 -100
  47. data/vendor/wreq/src/tls/conn.rs +3 -11
  48. data/vendor/wreq/src/tls/session.rs +7 -8
  49. data/vendor/wreq/src/tls/trust/store.rs +4 -4
  50. data/vendor/wreq/src/util.rs +23 -0
  51. data/vendor/wreq/tests/badssl.rs +72 -7
  52. data/vendor/wreq/tests/brotli.rs +1 -1
  53. data/vendor/wreq/tests/client.rs +24 -0
  54. data/vendor/wreq/tests/connector_layers.rs +8 -4
  55. data/vendor/wreq/tests/cookie.rs +59 -0
  56. data/vendor/wreq/tests/deflate.rs +1 -1
  57. data/vendor/wreq/tests/gzip.rs +53 -1
  58. data/vendor/wreq/tests/layers.rs +8 -4
  59. data/vendor/wreq/tests/redirect.rs +180 -97
  60. data/vendor/wreq/tests/timeouts.rs +47 -12
  61. data/vendor/wreq/tests/zstd.rs +1 -1
  62. metadata +8 -2
@@ -0,0 +1,321 @@
1
+ use std::collections::HashMap;
2
+
3
+ use cookie::{
4
+ Cookie as RawCookie,
5
+ time::{Duration, OffsetDateTime},
6
+ };
7
+ use http::Uri;
8
+ use url::Host;
9
+
10
+ use crate::ext::UriExt;
11
+
12
+ pub const DEFAULT_PATH: &str = "/";
13
+
14
+ /// Canonical immutable host used as a cookie domain key.
15
+ type CanonicalHost = Host<Box<str>>;
16
+ type NameMap = HashMap<Box<str>, CookieEntry>;
17
+ type PathMap = HashMap<Box<str>, CookieScopeMap>;
18
+ type DomainMap = HashMap<CanonicalHost, PathMap>;
19
+
20
+ /// A stored cookie and its sequence number for request ordering.
21
+ #[derive(Debug)]
22
+ pub struct CookieEntry {
23
+ pub cookie: RawCookie<'static>,
24
+ pub creation_index: u64,
25
+ }
26
+
27
+ /// Keeps host-only and `Domain` cookies separate because the host-only flag is part of a cookie's
28
+ /// identity under the RFC 6265bis storage model.
29
+ ///
30
+ /// https://httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html#section-5.7
31
+ #[derive(Debug, Default)]
32
+ pub struct CookieScopeMap {
33
+ host_only: NameMap,
34
+ domain: NameMap,
35
+ }
36
+
37
+ impl CookieScopeMap {
38
+ /// Returns a mutable cookie from the selected host-only or `Domain` scope.
39
+ pub fn get_mut(&mut self, name: &str, host_only: bool) -> Option<&mut CookieEntry> {
40
+ if host_only {
41
+ self.host_only.get_mut(name)
42
+ } else {
43
+ self.domain.get_mut(name)
44
+ }
45
+ }
46
+
47
+ /// Returns cookies with the requested name from both storage scopes.
48
+ pub fn entries(&self, name: &str) -> impl Iterator<Item = &CookieEntry> {
49
+ self.host_only
50
+ .get(name)
51
+ .into_iter()
52
+ .chain(self.domain.get(name))
53
+ }
54
+
55
+ /// Inserts a cookie into its host-only or `Domain` scope.
56
+ pub fn insert(&mut self, name: Box<str>, host_only: bool, entry: CookieEntry) {
57
+ if host_only {
58
+ self.host_only.insert(name, entry);
59
+ } else {
60
+ self.domain.insert(name, entry);
61
+ }
62
+ }
63
+
64
+ /// Removes a cookie from the selected host-only or `Domain` scope.
65
+ pub fn remove(&mut self, name: &str, host_only: bool) {
66
+ if host_only {
67
+ self.host_only.remove(name);
68
+ } else {
69
+ self.domain.remove(name);
70
+ }
71
+ }
72
+
73
+ /// Removes both host-only and `Domain` cookies with the requested name.
74
+ pub fn remove_all(&mut self, name: &str) {
75
+ self.host_only.remove(name);
76
+ self.domain.remove(name);
77
+ }
78
+
79
+ /// Returns every cookie in this domain and path scope.
80
+ pub fn values(&self) -> impl Iterator<Item = &CookieEntry> {
81
+ self.host_only.values().chain(self.domain.values())
82
+ }
83
+
84
+ /// Returns `true` when the host-only and `Domain` scopes are empty.
85
+ pub fn is_empty(&self) -> bool {
86
+ self.host_only.is_empty() && self.domain.is_empty()
87
+ }
88
+ }
89
+
90
+ /// Stores cookies by domain, path, host-only scope, and name.
91
+ #[derive(Debug, Default)]
92
+ pub struct Store {
93
+ pub cookies: DomainMap,
94
+ next_creation_index: u64,
95
+ }
96
+
97
+ impl Store {
98
+ /// Inserts or replaces a cookie in its domain and path scope.
99
+ pub fn insert_stored_cookie(
100
+ &mut self,
101
+ domain: CanonicalHost,
102
+ path: String,
103
+ cookie: RawCookie<'static>,
104
+ ) {
105
+ let host_only = cookie.domain().is_none();
106
+
107
+ // Chromium inherits the creation time only when the replacement keeps the same value. A
108
+ // value change receives a new creation time and therefore moves later among equal-length
109
+ // paths.
110
+ // https://chromium.googlesource.com/chromium/src/+/main/net/cookies/cookie_monster.cc
111
+ if let Some(entry) = self
112
+ .cookies
113
+ .get_mut(&domain)
114
+ .and_then(|path_map| path_map.get_mut(path.as_str()))
115
+ .and_then(|cookie_map| cookie_map.get_mut(cookie.name(), host_only))
116
+ {
117
+ if entry.cookie.value() != cookie.value() {
118
+ entry.creation_index = self.next_creation_index;
119
+ self.next_creation_index = self.next_creation_index.saturating_add(1);
120
+ }
121
+ entry.cookie = cookie;
122
+ return;
123
+ }
124
+
125
+ let creation_index = self.next_creation_index;
126
+ self.next_creation_index = self.next_creation_index.saturating_add(1);
127
+ let name = Box::from(cookie.name());
128
+
129
+ self.cookies
130
+ .entry(domain)
131
+ .or_default()
132
+ .entry(path.into_boxed_str())
133
+ .or_default()
134
+ .insert(
135
+ name,
136
+ host_only,
137
+ CookieEntry {
138
+ cookie,
139
+ creation_index,
140
+ },
141
+ );
142
+ }
143
+
144
+ /// Removes a cookie matching the domain, path, name, and selected storage scope.
145
+ pub fn remove_stored_cookie(
146
+ &mut self,
147
+ domain: &CanonicalHost,
148
+ path: &str,
149
+ name: &str,
150
+ host_only: bool,
151
+ ) {
152
+ self.remove_stored_cookie_inner(domain, path, name, Some(host_only));
153
+ }
154
+
155
+ /// Removes both host-only and `Domain` cookies matching the domain, path, and name.
156
+ pub fn remove_stored_cookies(&mut self, domain: &CanonicalHost, path: &str, name: &str) {
157
+ self.remove_stored_cookie_inner(domain, path, name, None);
158
+ }
159
+
160
+ fn remove_stored_cookie_inner(
161
+ &mut self,
162
+ domain: &CanonicalHost,
163
+ path: &str,
164
+ name: &str,
165
+ host_only: Option<bool>,
166
+ ) {
167
+ let remove_domain = if let Some(path_map) = self.cookies.get_mut(domain) {
168
+ let remove_path = if let Some(cookie_map) = path_map.get_mut(path) {
169
+ if let Some(host_only) = host_only {
170
+ cookie_map.remove(name, host_only);
171
+ } else {
172
+ cookie_map.remove_all(name);
173
+ }
174
+ cookie_map.is_empty()
175
+ } else {
176
+ false
177
+ };
178
+
179
+ if remove_path {
180
+ path_map.remove(path);
181
+ }
182
+
183
+ path_map.is_empty()
184
+ } else {
185
+ false
186
+ };
187
+
188
+ if remove_domain {
189
+ self.cookies.remove(domain);
190
+ }
191
+ }
192
+
193
+ /// Returns the unexpired cookies that apply to a request URI.
194
+ pub fn matching_cookies<'a>(
195
+ &'a self,
196
+ uri: &'a Uri,
197
+ request_host: &'a CanonicalHost,
198
+ now: OffsetDateTime,
199
+ ) -> impl Iterator<Item = (&'a CanonicalHost, &'a str, &'a CookieEntry)> + 'a {
200
+ self.cookies.iter().flat_map(move |(domain, path_map)| {
201
+ path_map.iter().flat_map(move |(path, cookie_map)| {
202
+ cookie_map.values().filter_map(move |entry| {
203
+ request_matches_cookie(uri, request_host, domain, path, &entry.cookie, now)
204
+ .then_some((domain, path.as_ref(), entry))
205
+ })
206
+ })
207
+ })
208
+ }
209
+
210
+ /// Returns whether an insecure cookie would overlay an unexpired `Secure` cookie.
211
+ ///
212
+ /// RFC 6265bis section 5.7:
213
+ /// https://httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html#section-5.7
214
+ pub fn would_overlay_secure_cookie(
215
+ &self,
216
+ name: &str,
217
+ domain: &CanonicalHost,
218
+ path: &str,
219
+ now: OffsetDateTime,
220
+ ) -> bool {
221
+ self.cookies.iter().any(|(stored_domain, path_map)| {
222
+ (domain_match(stored_domain, domain) || domain_match(domain, stored_domain))
223
+ && path_map.iter().any(|(stored_path, cookie_map)| {
224
+ path_match(path, stored_path)
225
+ && cookie_map.entries(name).any(|entry| {
226
+ entry.cookie.secure() == Some(true)
227
+ && !cookie_is_expired(&entry.cookie, now)
228
+ })
229
+ })
230
+ })
231
+ }
232
+ }
233
+
234
+ /// Applies the RFC 6265 request selection rules supported by this HTTP client.
235
+ fn request_matches_cookie(
236
+ uri: &Uri,
237
+ request_host: &CanonicalHost,
238
+ cookie_domain: &CanonicalHost,
239
+ cookie_path: &str,
240
+ cookie: &RawCookie<'_>,
241
+ now: OffsetDateTime,
242
+ ) -> bool {
243
+ // Host-only cookies require an exact host match. A Domain attribute enables suffix matching.
244
+ // RFC 6265 section 5.4: https://www.rfc-editor.org/rfc/rfc6265.html#section-5.4
245
+ if !(uri.is_http() || uri.is_https())
246
+ || !domain_match(request_host, cookie_domain)
247
+ || cookie.domain().is_none() && request_host != cookie_domain
248
+ || !path_match(uri.path(), cookie_path)
249
+ || cookie.secure() == Some(true) && uri.is_http()
250
+ {
251
+ return false;
252
+ }
253
+
254
+ !cookie_is_expired(cookie, now)
255
+ }
256
+
257
+ /// Returns whether a stored cookie has reached its effective expiration deadline.
258
+ pub fn cookie_is_expired(cookie: &RawCookie<'_>, now: OffsetDateTime) -> bool {
259
+ cookie
260
+ .max_age()
261
+ .is_some_and(|max_age| max_age <= Duration::ZERO)
262
+ || cookie
263
+ .expires_datetime()
264
+ .is_some_and(|deadline| deadline <= now)
265
+ }
266
+
267
+ /// Determines whether `host` domain-matches `domain` as defined by RFC 6265 section 5.1.3.
268
+ ///
269
+ /// https://www.rfc-editor.org/rfc/rfc6265.html#section-5.1.3
270
+ pub fn domain_match(host: &CanonicalHost, domain: &CanonicalHost) -> bool {
271
+ if host == domain {
272
+ return true;
273
+ }
274
+
275
+ let (Host::Domain(host), Host::Domain(domain)) = (host, domain) else {
276
+ return false;
277
+ };
278
+
279
+ host.len() > domain.len()
280
+ && host.as_bytes()[host.len() - domain.len() - 1] == b'.'
281
+ && host.ends_with(domain.as_ref())
282
+ }
283
+
284
+ /// Determines whether `request_path` path-matches `cookie_path` under RFC 6265 section 5.1.4.
285
+ ///
286
+ /// https://www.rfc-editor.org/rfc/rfc6265.html#section-5.1.4
287
+ fn path_match(request_path: &str, cookie_path: &str) -> bool {
288
+ request_path == cookie_path
289
+ || request_path.starts_with(cookie_path)
290
+ && (cookie_path.ends_with(DEFAULT_PATH)
291
+ || request_path[cookie_path.len()..].starts_with(DEFAULT_PATH))
292
+ }
293
+
294
+ /// Canonicalizes a DNS name or IP literal for cookie domain matching.
295
+ pub fn canonical_host(host: &str) -> Option<CanonicalHost> {
296
+ // RFC 6265 section 5.2.3 requires a leading dot in Domain to be ignored.
297
+ // https://www.rfc-editor.org/rfc/rfc6265.html#section-5.2.3
298
+ let host = host.strip_prefix('.').unwrap_or(host);
299
+
300
+ match Host::parse(host).ok()? {
301
+ Host::Domain(domain) => Some(Host::Domain(domain.into_boxed_str())),
302
+ Host::Ipv4(address) => Some(Host::Ipv4(address)),
303
+ Host::Ipv6(address) => Some(Host::Ipv6(address)),
304
+ }
305
+ }
306
+
307
+ /// Computes the default cookie path from a request path under RFC 6265 section 5.1.4.
308
+ ///
309
+ /// https://www.rfc-editor.org/rfc/rfc6265.html#section-5.1.4
310
+ pub fn normalize_path(path: &str) -> &str {
311
+ if !path.starts_with(DEFAULT_PATH) {
312
+ return DEFAULT_PATH;
313
+ }
314
+ if let Some(pos) = path.rfind(DEFAULT_PATH) {
315
+ if pos == 0 {
316
+ return DEFAULT_PATH;
317
+ }
318
+ return &path[..pos];
319
+ }
320
+ DEFAULT_PATH
321
+ }