wreq 1.2.3 → 1.2.5

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +9 -13
  3. data/Cargo.toml +7 -2
  4. data/crates/wreq-util/.github/FUNDING.yml +15 -0
  5. data/crates/wreq-util/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  6. data/crates/wreq-util/.github/ISSUE_TEMPLATE/custom.md +10 -0
  7. data/crates/wreq-util/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  8. data/crates/wreq-util/.github/dependabot.yml +22 -0
  9. data/crates/wreq-util/.github/workflows/ci.yml +82 -0
  10. data/crates/wreq-util/.github/workflows/release-plz.yml +52 -0
  11. data/crates/wreq-util/.gitignore +24 -0
  12. data/crates/wreq-util/CHANGELOG.md +74 -0
  13. data/crates/wreq-util/Cargo.toml +94 -0
  14. data/crates/wreq-util/LICENSE +201 -0
  15. data/crates/wreq-util/README.md +62 -0
  16. data/crates/wreq-util/examples/emulate.rs +39 -0
  17. data/crates/wreq-util/examples/tower_delay.rs +191 -0
  18. data/crates/wreq-util/release-plz.toml +2 -0
  19. data/crates/wreq-util/rustfmt.toml +5 -0
  20. data/crates/wreq-util/src/emulate/compress.rs +85 -0
  21. data/crates/wreq-util/src/emulate/macros.rs +370 -0
  22. data/crates/wreq-util/src/emulate/profile/chrome/header.rs +59 -0
  23. data/crates/wreq-util/src/emulate/profile/chrome/http2.rs +65 -0
  24. data/crates/wreq-util/src/emulate/profile/chrome/tls.rs +153 -0
  25. data/crates/wreq-util/src/emulate/profile/chrome.rs +2028 -0
  26. data/crates/wreq-util/src/emulate/profile/firefox/header.rs +21 -0
  27. data/crates/wreq-util/src/emulate/profile/firefox/http2.rs +113 -0
  28. data/crates/wreq-util/src/emulate/profile/firefox/tls.rs +253 -0
  29. data/crates/wreq-util/src/emulate/profile/firefox.rs +518 -0
  30. data/crates/wreq-util/src/emulate/profile/okhttp.rs +241 -0
  31. data/crates/wreq-util/src/emulate/profile/opera/header.rs +30 -0
  32. data/crates/wreq-util/src/emulate/profile/opera/http2.rs +50 -0
  33. data/crates/wreq-util/src/emulate/profile/opera/tls.rs +97 -0
  34. data/crates/wreq-util/src/emulate/profile/opera.rs +299 -0
  35. data/crates/wreq-util/src/emulate/profile/safari/header.rs +59 -0
  36. data/crates/wreq-util/src/emulate/profile/safari/http2.rs +116 -0
  37. data/crates/wreq-util/src/emulate/profile/safari/tls.rs +177 -0
  38. data/crates/wreq-util/src/emulate/profile/safari.rs +222 -0
  39. data/crates/wreq-util/src/emulate/profile.rs +47 -0
  40. data/crates/wreq-util/src/emulate.rs +369 -0
  41. data/crates/wreq-util/src/lib.rs +14 -0
  42. data/crates/wreq-util/src/rand.rs +24 -0
  43. data/crates/wreq-util/src/tower/delay/future.rs +43 -0
  44. data/crates/wreq-util/src/tower/delay/layer.rs +201 -0
  45. data/crates/wreq-util/src/tower/delay/service.rs +190 -0
  46. data/crates/wreq-util/src/tower/delay.rs +98 -0
  47. data/crates/wreq-util/src/tower.rs +7 -0
  48. data/crates/wreq-util/tests/client.rs +51 -0
  49. data/crates/wreq-util/tests/emulate_chrome.rs +270 -0
  50. data/crates/wreq-util/tests/emulate_firefox.rs +92 -0
  51. data/crates/wreq-util/tests/emulate_okhttp.rs +46 -0
  52. data/crates/wreq-util/tests/emulate_opera.rs +113 -0
  53. data/crates/wreq-util/tests/emulate_safari.rs +151 -0
  54. data/crates/wreq-util/tests/support/mod.rs +55 -0
  55. data/crates/wreq-util/tests/support/server.rs +232 -0
  56. data/examples/emulate_request.rb +1 -1
  57. data/lib/wreq.rb +20 -18
  58. data/lib/wreq_ruby/body.rb +3 -0
  59. data/lib/wreq_ruby/client.rb +18 -18
  60. data/lib/wreq_ruby/cookie.rb +2 -2
  61. data/lib/wreq_ruby/emulate.rb +3 -1
  62. data/lib/wreq_ruby/header.rb +205 -114
  63. data/lib/wreq_ruby/response.rb +28 -3
  64. data/script/build_windows_gnu.ps1 +7 -0
  65. data/src/cookie.rs +1 -3
  66. data/src/emulate.rs +3 -142
  67. data/src/error.rs +2 -6
  68. data/src/header/helper.rs +112 -0
  69. data/src/header.rs +198 -133
  70. data/src/http.rs +2 -13
  71. data/src/macros.rs +10 -0
  72. data/test/header_test.rb +228 -192
  73. data/test/stream_test.rb +36 -0
  74. metadata +54 -1
data/src/emulate.rs CHANGED
@@ -50,6 +50,7 @@ define_ruby_enum!(
50
50
  Chrome147,
51
51
  Chrome148,
52
52
  Chrome149,
53
+ Chrome150,
53
54
 
54
55
  Edge101,
55
56
  Edge122,
@@ -222,152 +223,12 @@ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
222
223
  // Profile enum binding
223
224
  let profile = gem_module.define_class("Profile", ruby.class_object())?;
224
225
  profile.define_method("to_s", method!(Profile::to_s, 0))?;
225
- profile.const_set("Chrome100", Profile::Chrome100)?;
226
- profile.const_set("Chrome101", Profile::Chrome101)?;
227
- profile.const_set("Chrome104", Profile::Chrome104)?;
228
- profile.const_set("Chrome105", Profile::Chrome105)?;
229
- profile.const_set("Chrome106", Profile::Chrome106)?;
230
- profile.const_set("Chrome107", Profile::Chrome107)?;
231
- profile.const_set("Chrome108", Profile::Chrome108)?;
232
- profile.const_set("Chrome109", Profile::Chrome109)?;
233
- profile.const_set("Chrome110", Profile::Chrome110)?;
234
- profile.const_set("Chrome114", Profile::Chrome114)?;
235
- profile.const_set("Chrome116", Profile::Chrome116)?;
236
- profile.const_set("Chrome117", Profile::Chrome117)?;
237
- profile.const_set("Chrome118", Profile::Chrome118)?;
238
- profile.const_set("Chrome119", Profile::Chrome119)?;
239
- profile.const_set("Chrome120", Profile::Chrome120)?;
240
- profile.const_set("Chrome123", Profile::Chrome123)?;
241
- profile.const_set("Chrome124", Profile::Chrome124)?;
242
- profile.const_set("Chrome126", Profile::Chrome126)?;
243
- profile.const_set("Chrome127", Profile::Chrome127)?;
244
- profile.const_set("Chrome128", Profile::Chrome128)?;
245
- profile.const_set("Chrome129", Profile::Chrome129)?;
246
- profile.const_set("Chrome130", Profile::Chrome130)?;
247
- profile.const_set("Chrome131", Profile::Chrome131)?;
248
- profile.const_set("Chrome132", Profile::Chrome132)?;
249
- profile.const_set("Chrome133", Profile::Chrome133)?;
250
- profile.const_set("Chrome134", Profile::Chrome134)?;
251
- profile.const_set("Chrome135", Profile::Chrome135)?;
252
- profile.const_set("Chrome136", Profile::Chrome136)?;
253
- profile.const_set("Chrome137", Profile::Chrome137)?;
254
- profile.const_set("Chrome138", Profile::Chrome138)?;
255
- profile.const_set("Chrome139", Profile::Chrome139)?;
256
- profile.const_set("Chrome140", Profile::Chrome140)?;
257
- profile.const_set("Chrome141", Profile::Chrome141)?;
258
- profile.const_set("Chrome142", Profile::Chrome142)?;
259
- profile.const_set("Chrome143", Profile::Chrome143)?;
260
- profile.const_set("Chrome144", Profile::Chrome144)?;
261
- profile.const_set("Chrome145", Profile::Chrome145)?;
262
- profile.const_set("Chrome146", Profile::Chrome146)?;
263
- profile.const_set("Chrome147", Profile::Chrome147)?;
264
- profile.const_set("Chrome148", Profile::Chrome148)?;
265
-
266
- profile.const_set("Edge101", Profile::Edge101)?;
267
- profile.const_set("Edge122", Profile::Edge122)?;
268
- profile.const_set("Edge127", Profile::Edge127)?;
269
- profile.const_set("Edge131", Profile::Edge131)?;
270
- profile.const_set("Edge134", Profile::Edge134)?;
271
- profile.const_set("Edge135", Profile::Edge135)?;
272
- profile.const_set("Edge136", Profile::Edge136)?;
273
- profile.const_set("Edge137", Profile::Edge137)?;
274
- profile.const_set("Edge138", Profile::Edge138)?;
275
- profile.const_set("Edge139", Profile::Edge139)?;
276
- profile.const_set("Edge140", Profile::Edge140)?;
277
- profile.const_set("Edge141", Profile::Edge141)?;
278
- profile.const_set("Edge142", Profile::Edge142)?;
279
- profile.const_set("Edge143", Profile::Edge143)?;
280
- profile.const_set("Edge144", Profile::Edge144)?;
281
- profile.const_set("Edge145", Profile::Edge145)?;
282
- profile.const_set("Edge146", Profile::Edge146)?;
283
- profile.const_set("Edge147", Profile::Edge147)?;
284
- profile.const_set("Edge148", Profile::Edge148)?;
285
-
286
- profile.const_set("Firefox109", Profile::Firefox109)?;
287
- profile.const_set("Firefox117", Profile::Firefox117)?;
288
- profile.const_set("Firefox128", Profile::Firefox128)?;
289
- profile.const_set("Firefox133", Profile::Firefox133)?;
290
- profile.const_set("Firefox135", Profile::Firefox135)?;
291
- profile.const_set("FirefoxPrivate135", Profile::FirefoxPrivate135)?;
292
- profile.const_set("FirefoxAndroid135", Profile::FirefoxAndroid135)?;
293
- profile.const_set("Firefox136", Profile::Firefox136)?;
294
- profile.const_set("FirefoxPrivate136", Profile::FirefoxPrivate136)?;
295
- profile.const_set("Firefox139", Profile::Firefox139)?;
296
- profile.const_set("Firefox142", Profile::Firefox142)?;
297
- profile.const_set("Firefox143", Profile::Firefox143)?;
298
- profile.const_set("Firefox144", Profile::Firefox144)?;
299
- profile.const_set("Firefox145", Profile::Firefox145)?;
300
- profile.const_set("Firefox146", Profile::Firefox146)?;
301
- profile.const_set("Firefox147", Profile::Firefox147)?;
302
- profile.const_set("Firefox148", Profile::Firefox148)?;
303
- profile.const_set("Firefox149", Profile::Firefox149)?;
304
- profile.const_set("Firefox150", Profile::Firefox150)?;
305
- profile.const_set("Firefox151", Profile::Firefox151)?;
306
-
307
- profile.const_set("SafariIos17_2", Profile::SafariIos17_2)?;
308
- profile.const_set("SafariIos17_4_1", Profile::SafariIos17_4_1)?;
309
- profile.const_set("SafariIos16_5", Profile::SafariIos16_5)?;
310
- profile.const_set("Safari15_3", Profile::Safari15_3)?;
311
- profile.const_set("Safari15_5", Profile::Safari15_5)?;
312
- profile.const_set("Safari15_6_1", Profile::Safari15_6_1)?;
313
- profile.const_set("Safari16", Profile::Safari16)?;
314
- profile.const_set("Safari16_5", Profile::Safari16_5)?;
315
- profile.const_set("Safari17_0", Profile::Safari17_0)?;
316
- profile.const_set("Safari17_2_1", Profile::Safari17_2_1)?;
317
- profile.const_set("Safari17_4_1", Profile::Safari17_4_1)?;
318
- profile.const_set("Safari17_5", Profile::Safari17_5)?;
319
- profile.const_set("Safari17_6", Profile::Safari17_6)?;
320
- profile.const_set("Safari18", Profile::Safari18)?;
321
- profile.const_set("SafariIPad18", Profile::SafariIPad18)?;
322
- profile.const_set("Safari18_2", Profile::Safari18_2)?;
323
- profile.const_set("Safari18_3", Profile::Safari18_3)?;
324
- profile.const_set("Safari18_3_1", Profile::Safari18_3_1)?;
325
- profile.const_set("SafariIos18_1_1", Profile::SafariIos18_1_1)?;
326
- profile.const_set("Safari18_5", Profile::Safari18_5)?;
327
- profile.const_set("Safari26", Profile::Safari26)?;
328
- profile.const_set("Safari26_1", Profile::Safari26_1)?;
329
- profile.const_set("Safari26_2", Profile::Safari26_2)?;
330
- profile.const_set("Safari26_3", Profile::Safari26_3)?;
331
- profile.const_set("Safari26_4", Profile::Safari26_4)?;
332
- profile.const_set("SafariIos26", Profile::SafariIos26)?;
333
- profile.const_set("SafariIos26_2", Profile::SafariIos26_2)?;
334
- profile.const_set("SafariIPad26", Profile::SafariIPad26)?;
335
- profile.const_set("SafariIpad26_2", Profile::SafariIpad26_2)?;
336
-
337
- profile.const_set("OkHttp3_9", Profile::OkHttp3_9)?;
338
- profile.const_set("OkHttp3_11", Profile::OkHttp3_11)?;
339
- profile.const_set("OkHttp3_13", Profile::OkHttp3_13)?;
340
- profile.const_set("OkHttp3_14", Profile::OkHttp3_14)?;
341
- profile.const_set("OkHttp4_9", Profile::OkHttp4_9)?;
342
- profile.const_set("OkHttp4_10", Profile::OkHttp4_10)?;
343
- profile.const_set("OkHttp4_12", Profile::OkHttp4_12)?;
344
- profile.const_set("OkHttp5", Profile::OkHttp5)?;
345
-
346
- profile.const_set("Opera116", Profile::Opera116)?;
347
- profile.const_set("Opera117", Profile::Opera117)?;
348
- profile.const_set("Opera118", Profile::Opera118)?;
349
- profile.const_set("Opera119", Profile::Opera119)?;
350
- profile.const_set("Opera120", Profile::Opera120)?;
351
- profile.const_set("Opera121", Profile::Opera121)?;
352
- profile.const_set("Opera122", Profile::Opera122)?;
353
- profile.const_set("Opera123", Profile::Opera123)?;
354
- profile.const_set("Opera124", Profile::Opera124)?;
355
- profile.const_set("Opera125", Profile::Opera125)?;
356
- profile.const_set("Opera126", Profile::Opera126)?;
357
- profile.const_set("Opera127", Profile::Opera127)?;
358
- profile.const_set("Opera128", Profile::Opera128)?;
359
- profile.const_set("Opera129", Profile::Opera129)?;
360
- profile.const_set("Opera130", Profile::Opera130)?;
361
- profile.const_set("Opera131", Profile::Opera131)?;
226
+ Profile::define_constants(profile)?;
362
227
 
363
228
  // Platform enum binding
364
229
  let platform = gem_module.define_class("Platform", ruby.class_object())?;
365
230
  platform.define_method("to_s", method!(Platform::to_s, 0))?;
366
- platform.const_set("Windows", Platform::Windows)?;
367
- platform.const_set("MacOS", Platform::MacOS)?;
368
- platform.const_set("Linux", Platform::Linux)?;
369
- platform.const_set("Android", Platform::Android)?;
370
- platform.const_set("IOS", Platform::IOS)?;
231
+ Platform::define_constants(platform)?;
371
232
 
372
233
  // Emulation class binding
373
234
  let emulation = gem_module.define_class("Emulation", ruby.class_object())?;
data/src/error.rs CHANGED
@@ -72,17 +72,14 @@ define_exception!(DECODING_ERROR, "DecodingError", exception_runtime_error);
72
72
  // Configuration and builder errors
73
73
  define_exception!(BUILDER_ERROR, "BuilderError", exception_runtime_error);
74
74
 
75
- // Thread interruption error
76
- define_exception!(INTERRUPT_ERROR, "InterruptError", exception_interrupt);
77
-
78
75
  /// Memory error constant
79
76
  pub fn memory_error() -> MagnusError {
80
77
  MagnusError::new(ruby!().get_inner(&MEMORY), RACE_CONDITION_ERROR_MSG)
81
78
  }
82
79
 
83
- /// Thread interruption error (raised when Thread.kill cancels a request)
80
+ /// Create a Ruby thread interruption error.
84
81
  pub fn interrupt_error() -> MagnusError {
85
- MagnusError::new(ruby!().get_inner(&INTERRUPT_ERROR), "request interrupted")
82
+ MagnusError::new(ruby!().exception_interrupt(), "request interrupted")
86
83
  }
87
84
 
88
85
  /// LocalJumpError for methods that require a Ruby block.
@@ -159,5 +156,4 @@ pub fn include(ruby: &Ruby) {
159
156
  Lazy::force(&BODY_ERROR, ruby);
160
157
  Lazy::force(&DECODING_ERROR, ruby);
161
158
  Lazy::force(&BUILDER_ERROR, ruby);
162
- Lazy::force(&INTERRUPT_ERROR, ruby);
163
159
  }
@@ -0,0 +1,112 @@
1
+ //! Ruby value conversion helpers for `Wreq::Headers`.
2
+
3
+ use bytes::Bytes;
4
+ use http::{HeaderName, HeaderValue};
5
+ use magnus::{Error, RArray, RString, Symbol, TryConvert, Value, prelude::*, typed_data::Obj};
6
+
7
+ use crate::error::{
8
+ header_name_error_to_magnus, header_value_error_to_magnus, type_value_error_to_magnus,
9
+ };
10
+
11
+ use super::Headers;
12
+
13
+ /// Maximum number of field-value occurrences supported by `HeaderMap`.
14
+ const MAX_HEADER_ENTRIES: usize = 1 << 15;
15
+
16
+ /// Build a header collection from a Ruby source object.
17
+ ///
18
+ /// Accepts another `Wreq::Headers`, a Hash, or any object whose `to_a` result
19
+ /// contains two-element name-value pairs. Array values are delegated to
20
+ /// [`Headers::append`] so each value remains a separate occurrence.
21
+ pub(super) fn from_source(source: Value) -> Result<Headers, Error> {
22
+ if let Ok(headers) = Obj::<Headers>::try_convert(source) {
23
+ return Ok((*headers).clone());
24
+ }
25
+ if !source.respond_to("to_a", false)? {
26
+ return Err(type_value_error_to_magnus(
27
+ "Expected Headers, a Hash, or an enumerable of pairs",
28
+ ));
29
+ }
30
+
31
+ let pairs: RArray = source.funcall_public("to_a", ())?;
32
+ let headers = Headers::default();
33
+ for pair in pairs {
34
+ let pair = RArray::try_convert(pair)
35
+ .map_err(|_| type_value_error_to_magnus("Expected each header entry to be a pair"))?;
36
+ if pair.len() != 2 {
37
+ return Err(type_value_error_to_magnus(
38
+ "Expected each header entry to contain a name and value",
39
+ ));
40
+ }
41
+
42
+ headers.append(pair.entry(0)?, pair.entry(1)?)?;
43
+ }
44
+ Ok(headers)
45
+ }
46
+
47
+ /// Convert a Ruby String or Symbol into a normalized HTTP header name.
48
+ ///
49
+ /// Symbol underscores are changed to hyphens before [`HeaderName`] validates
50
+ /// and normalizes the name. Other Ruby types produce `Wreq::BuilderError`.
51
+ pub(super) fn parse_header_name(value: Value) -> Result<HeaderName, Error> {
52
+ let name = match (RString::from_value(value), Symbol::from_value(value)) {
53
+ (Some(name), _) => name.to_bytes(),
54
+ (None, Some(name)) => Bytes::from(name.name()?.replace('_', "-")),
55
+ (None, None) => {
56
+ return Err(type_value_error_to_magnus(
57
+ "Expected a String or Symbol header name",
58
+ ));
59
+ }
60
+ };
61
+ HeaderName::from_bytes(name.as_ref()).map_err(header_name_error_to_magnus)
62
+ }
63
+
64
+ /// Convert a Ruby String or Array of Strings into validated header values.
65
+ ///
66
+ /// Each Array element becomes one [`HeaderValue`]. An empty Array therefore
67
+ /// produces no values, allowing `set` to remove a header and `append` to do
68
+ /// nothing.
69
+ pub(super) fn parse_header_values(value: Value) -> Result<Vec<HeaderValue>, Error> {
70
+ if let Some(values) = RArray::from_value(value) {
71
+ values.into_iter().map(parse_header_value).collect()
72
+ } else {
73
+ Ok(vec![parse_header_value(value)?])
74
+ }
75
+ }
76
+
77
+ /// Convert one Ruby String into a validated HTTP header value.
78
+ ///
79
+ /// Invalid Ruby types and bytes rejected by [`HeaderValue`] are mapped to
80
+ /// `Wreq::BuilderError`.
81
+ fn parse_header_value(value: Value) -> Result<HeaderValue, Error> {
82
+ let value = RString::try_convert(value)
83
+ .map_err(|_| type_value_error_to_magnus("Expected a String header value"))?;
84
+ HeaderValue::from_maybe_shared(value.to_bytes()).map_err(header_value_error_to_magnus)
85
+ }
86
+
87
+ /// Validate the resulting number of header occurrences before a mutation.
88
+ ///
89
+ /// `current` is the collection length, `replaced` is the number of existing
90
+ /// occurrences removed by `set`, and `added` is the incoming value count.
91
+ /// Checked arithmetic prevents overflow; an invalid calculation or a result
92
+ /// above the native [`HeaderMap`](http::HeaderMap) limit returns
93
+ /// `Wreq::BuilderError` without mutating the collection.
94
+ pub(super) fn ensure_header_count(
95
+ current: usize,
96
+ replaced: usize,
97
+ added: usize,
98
+ ) -> Result<(), Error> {
99
+ let count = current
100
+ .checked_sub(replaced)
101
+ .and_then(|count| count.checked_add(added));
102
+ if count.is_some_and(|count| count <= MAX_HEADER_ENTRIES) {
103
+ Ok(())
104
+ } else {
105
+ Err(header_count_error())
106
+ }
107
+ }
108
+
109
+ /// Build the error returned when the native header map reaches its entry limit.
110
+ pub(super) fn header_count_error() -> Error {
111
+ type_value_error_to_magnus("Header collection exceeds 32,768 entries")
112
+ }