wreq 1.2.4 → 1.2.6

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +22 -19
  3. data/Cargo.toml +15 -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 +372 -0
  22. data/crates/wreq-util/src/emulate/profile/chrome/header.rs +74 -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 +37 -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 +29 -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 +153 -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 +72 -45
  58. data/lib/wreq_ruby/body.rb +30 -9
  59. data/lib/wreq_ruby/client.rb +88 -55
  60. data/lib/wreq_ruby/cookie.rb +81 -21
  61. data/lib/wreq_ruby/emulate.rb +77 -7
  62. data/lib/wreq_ruby/error.rb +5 -7
  63. data/lib/wreq_ruby/header.rb +205 -114
  64. data/lib/wreq_ruby/http.rb +74 -0
  65. data/lib/wreq_ruby/response.rb +31 -3
  66. data/script/build_windows_gnu.ps1 +6 -0
  67. data/src/arch.rs +22 -0
  68. data/src/client/body/form.rs +2 -0
  69. data/src/client/body/json.rs +47 -14
  70. data/src/client/body/stream.rs +147 -43
  71. data/src/client/body.rs +11 -15
  72. data/src/client/param.rs +7 -7
  73. data/src/client/req.rs +87 -47
  74. data/src/client/resp.rs +23 -24
  75. data/src/client.rs +310 -230
  76. data/src/cookie.rs +280 -87
  77. data/src/emulate.rs +86 -50
  78. data/src/error.rs +198 -45
  79. data/src/extractor.rs +16 -76
  80. data/src/header.rs +318 -130
  81. data/src/http.rs +62 -33
  82. data/src/lib.rs +26 -20
  83. data/src/macros.rs +71 -46
  84. data/src/options.rs +284 -0
  85. data/src/rt.rs +22 -11
  86. data/src/serde/de/array_deserializer.rs +48 -0
  87. data/src/serde/de/array_enumerator.rs +59 -0
  88. data/src/serde/de/deserializer.rs +307 -0
  89. data/src/serde/de/enum_deserializer.rs +47 -0
  90. data/src/serde/de/hash_deserializer.rs +89 -0
  91. data/src/serde/de/number_deserializer.rs +51 -0
  92. data/src/serde/de/variant_deserializer.rs +101 -0
  93. data/src/serde/de.rs +33 -0
  94. data/src/serde/error.rs +146 -0
  95. data/src/serde/ser/enums.rs +14 -0
  96. data/src/serde/ser/map_serializer.rs +56 -0
  97. data/src/serde/ser/seq_serializer.rs +71 -0
  98. data/src/serde/ser/serializer.rs +194 -0
  99. data/src/serde/ser/struct_serializer.rs +106 -0
  100. data/src/serde/ser/struct_variant_serializer.rs +44 -0
  101. data/src/serde/ser/tuple_variant_serializer.rs +41 -0
  102. data/src/serde/ser.rs +18 -0
  103. data/src/serde/tests.rs +237 -0
  104. data/src/serde.rs +202 -0
  105. data/test/body_sender_test.rb +246 -0
  106. data/test/cookie_test.rb +211 -10
  107. data/test/header_test.rb +228 -192
  108. data/test/json_precision_test.rb +209 -0
  109. data/test/option_validation_test.rb +311 -0
  110. data/test/stream_test.rb +36 -0
  111. data/test/value_semantics_test.rb +238 -0
  112. metadata +77 -1
data/test/cookie_test.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "test_helper"
4
+ require "bigdecimal"
5
+ require "open3"
6
+ require "rbconfig"
4
7
 
5
8
  class CookieTest < Minitest::Test
6
9
  def setup
@@ -41,6 +44,14 @@ class CookieTest < Minitest::Test
41
44
  assert_equal true, (c.secure || c.secure?)
42
45
  end
43
46
 
47
+ def test_add_rejects_invalid_cookie_type
48
+ error = assert_raises(TypeError) do
49
+ @jar.add(Object.new, @base_url)
50
+ end
51
+
52
+ assert_equal "cookie must be a Wreq::Cookie or String", error.message
53
+ end
54
+
44
55
  def test_add_multiple_and_remove
45
56
  @jar.add("a=1; Path=/", @base_url)
46
57
  @jar.add("b=2; Path=/", @base_url)
@@ -84,7 +95,9 @@ class CookieTest < Minitest::Test
84
95
  @jar.add("exp=1; Expires=#{t.gmtime.strftime("%a, %d %b %Y %H:%M:%S GMT")}; Path=/", @base_url)
85
96
  c2 = @jar.get_all.find { |c| c.name == "exp" }
86
97
  assert c2
87
- # expires returns Float (unix seconds) or nil
98
+ # expires_at returns Time. expires remains available for numeric callers.
99
+ assert_kind_of Time, c2.expires_at
100
+ assert_predicate c2.expires_at, :utc?
88
101
  if (e = c2.expires)
89
102
  assert_kind_of Float, e
90
103
  assert_operator e, :>, Time.now.to_f - 1_000_000 # sanity bound
@@ -104,15 +117,18 @@ class CookieTest < Minitest::Test
104
117
  assert_nil c.domain
105
118
  assert_nil c.max_age
106
119
  assert_nil c.expires
120
+ assert_nil c.expires_at
107
121
 
108
122
  assert_equal false, (c.http_only || c.http_only?)
109
123
  assert_equal false, (c.secure || c.secure?)
110
124
  assert_equal false, c.same_site_lax?
111
125
  assert_equal false, c.same_site_strict?
126
+ assert_nil c.same_site
127
+ assert_equal "sid=abc", c.to_s
112
128
  end
113
129
 
114
130
  def test_cookie_new_full_attributes
115
- exp = Time.now.to_f + 7200.0
131
+ exp = Time.utc(2030, 1, 1, 0, 0, Rational(123_456_789, 1_000_000_000))
116
132
  c = Wreq::Cookie.new("sess", "v",
117
133
  domain: "example.com",
118
134
  path: "/",
@@ -130,16 +146,199 @@ class CookieTest < Minitest::Test
130
146
  # Max-Age returns seconds as Integer
131
147
  assert_equal 3600, c.max_age
132
148
 
133
- # Expires returns Float seconds-since-epoch (with small tolerance)
134
- assert c.expires
135
- assert_kind_of Float, c.expires
136
- assert_in_delta exp, c.expires, 2.0
149
+ assert_equal exp, c.expires_at
150
+ assert_predicate c.expires_at, :utc?
151
+ assert_in_delta exp.to_f, c.expires, 1e-6
137
152
 
138
153
  assert_equal true, (c.http_only || c.http_only?)
139
154
  assert_equal true, (c.secure || c.secure?)
140
- # constructor currently sets SameSite to none
141
155
  assert_equal true, c.same_site_lax?
142
156
  assert_equal false, c.same_site_strict?
157
+ assert_equal Wreq::SameSite::Lax, c.same_site
158
+ end
159
+
160
+ def test_same_site_reader_distinguishes_none_from_unset
161
+ cookie = Wreq::Cookie.new("same-site", "value", same_site: Wreq::SameSite::None)
162
+
163
+ assert_equal Wreq::SameSite::None, cookie.same_site
164
+ refute cookie.same_site_lax?
165
+ refute cookie.same_site_strict?
166
+ end
167
+
168
+ def test_cookie_new_uses_shared_keyword_validation
169
+ cookie = Wreq::Cookie.new("sid", "abc", **{"path" => "/"})
170
+ assert_equal "/", cookie.path
171
+
172
+ secret = "must-not-appear"
173
+ unknown_error = assert_raises(ArgumentError) do
174
+ Wreq::Cookie.new("sid", "abc", domian: secret)
175
+ end
176
+ assert_includes unknown_error.message, ":domian"
177
+ refute_includes unknown_error.message, secret
178
+
179
+ duplicate_options = {path: "/one"}
180
+ duplicate_options["path"] = "/two"
181
+ duplicate_error = assert_raises(ArgumentError) do
182
+ Wreq::Cookie.new("sid", "abc", **duplicate_options)
183
+ end
184
+ assert_includes duplicate_error.message, "duplicate option: :path"
185
+ end
186
+
187
+ def test_expires_accepts_past_time
188
+ expiration = Time.at(Rational(-5, 4)).utc
189
+ cookie = Wreq::Cookie.new("past", "value", expires: expiration)
190
+
191
+ assert_equal expiration, cookie.expires_at
192
+ assert_in_delta(-1.25, cookie.expires, 1e-9)
193
+ end
194
+
195
+ def test_expires_accepts_integer_and_fractional_timestamps
196
+ [
197
+ 1_893_456_000,
198
+ 1_893_456_000.125,
199
+ Rational(-5, 4),
200
+ BigDecimal("1893456000.125")
201
+ ].each do |timestamp|
202
+ cookie = Wreq::Cookie.new("timestamp", "value", expires: timestamp)
203
+
204
+ assert_kind_of Time, cookie.expires_at
205
+ assert_predicate cookie.expires_at, :utc?
206
+ assert_in_delta timestamp.to_f, cookie.expires_at.to_f, 1e-6
207
+ assert_in_delta timestamp.to_f, cookie.expires, 1e-6
208
+ end
209
+ end
210
+
211
+ def test_expires_normalizes_time_to_utc_without_losing_precision
212
+ expiration = Time.new(
213
+ 2030,
214
+ 1,
215
+ 1,
216
+ 12,
217
+ 34,
218
+ Rational(123_456_789, 1_000_000_000),
219
+ "+09:00"
220
+ )
221
+ actual = Wreq::Cookie.new("offset", "value", expires: expiration).expires_at
222
+
223
+ assert_predicate actual, :utc?
224
+ assert_equal expiration.to_r, actual.to_r
225
+ end
226
+
227
+ def test_expires_rejects_non_finite_timestamps
228
+ [Float::NAN, Float::INFINITY, -Float::INFINITY].each do |timestamp|
229
+ error = assert_raises(ArgumentError) do
230
+ Wreq::Cookie.new("invalid", "value", expires: timestamp)
231
+ end
232
+
233
+ assert_match(/expires.*finite/, error.message)
234
+ end
235
+ end
236
+
237
+ def test_expires_rejects_unrepresentable_times
238
+ expirations = [
239
+ -(2**63),
240
+ 2**63 - 1,
241
+ -Float::MAX,
242
+ Float::MAX,
243
+ Time.utc(10_000, 1, 1)
244
+ ]
245
+
246
+ expirations.each do |expiration|
247
+ error = assert_raises(RangeError) do
248
+ Wreq::Cookie.new("invalid", "value", expires: expiration)
249
+ end
250
+
251
+ assert_match(/expires.*supported range/, error.message)
252
+ end
253
+ end
254
+
255
+ def test_max_age_accepts_signed_boundaries_without_wrapping
256
+ [-(2**63), -1, 0, 2**63 - 1].each do |max_age|
257
+ cookie = Wreq::Cookie.new("max-age", "value", max_age: max_age)
258
+
259
+ assert_equal max_age, cookie.max_age
260
+ end
261
+
262
+ [-(2**63) - 1, 2**63].each do |max_age|
263
+ assert_raises(RangeError) do
264
+ Wreq::Cookie.new("max-age", "value", max_age: max_age)
265
+ end
266
+ end
267
+ end
268
+
269
+ def test_non_positive_max_age_removes_cookie_from_jar
270
+ [-1, 0].each do |max_age|
271
+ jar = Wreq::Jar.new
272
+ jar.add("session=old; Path=/", @base_url)
273
+ deletion = Wreq::Cookie.new("session", "gone", path: "/", max_age: max_age)
274
+
275
+ jar.add(deletion, @base_url)
276
+
277
+ assert_equal max_age, deletion.max_age
278
+ assert_empty jar.get_all
279
+ end
280
+ end
281
+
282
+ def test_past_expiration_removes_cookie_from_jar
283
+ @jar.add("session=old; Path=/", @base_url)
284
+ deletion = Wreq::Cookie.new(
285
+ "session",
286
+ "gone",
287
+ path: "/",
288
+ expires: Time.at(-1).utc
289
+ )
290
+
291
+ @jar.add(deletion, @base_url)
292
+
293
+ assert_empty @jar.get_all
294
+ end
295
+
296
+ def test_expiration_regressions_exit_subprocess_normally
297
+ lib_dir = File.expand_path("../lib", __dir__)
298
+ script = <<~RUBY
299
+ require "wreq"
300
+
301
+ past = Wreq::Cookie.new("past", "value", expires: -1.0)
302
+ abort "past timestamp was not retained" unless past.expires_at == Time.at(-1).utc
303
+
304
+ [Float::NAN, Float::INFINITY, -Float::INFINITY].each do |timestamp|
305
+ begin
306
+ Wreq::Cookie.new("invalid", "value", expires: timestamp)
307
+ rescue ArgumentError
308
+ next
309
+ end
310
+
311
+ abort "non-finite timestamp did not raise ArgumentError"
312
+ end
313
+
314
+ [Float::MAX, -Float::MAX].each do |timestamp|
315
+ begin
316
+ Wreq::Cookie.new("invalid", "value", expires: timestamp)
317
+ rescue RangeError
318
+ next
319
+ end
320
+
321
+ abort "unrepresentable finite timestamp did not raise RangeError"
322
+ end
323
+
324
+ [-(2**63) - 1, 2**63].each do |max_age|
325
+ begin
326
+ Wreq::Cookie.new("invalid", "value", max_age: max_age)
327
+ rescue RangeError
328
+ next
329
+ end
330
+
331
+ abort "out-of-range Max-Age did not raise RangeError"
332
+ end
333
+
334
+ puts "ok"
335
+ RUBY
336
+
337
+ stdout, stderr, status = Open3.capture3(RbConfig.ruby, "-I", lib_dir, "-e", script)
338
+
339
+ assert status.success?, "subprocess failed with #{status.inspect}: #{stderr}"
340
+ assert_equal "ok\n", stdout
341
+ refute_match(/panicked|fatal|access violation|cannot convert float seconds to Duration/i, stderr)
143
342
  end
144
343
 
145
344
  def test_same_site_flags_from_parsed_header
@@ -148,10 +347,12 @@ class CookieTest < Minitest::Test
148
347
  @jar.add("s2=1; Path=/; SameSite=Lax", @base_url)
149
348
 
150
349
  cookies = @jar.get_all
151
- h = cookies.to_h { |ck| [ck.name, [ck.same_site_strict?, ck.same_site_lax?]] }
350
+ h = cookies.to_h do |cookie|
351
+ [cookie.name, [cookie.same_site, cookie.same_site_strict?, cookie.same_site_lax?]]
352
+ end
152
353
 
153
- assert_equal [true, false], h["s1"]
154
- assert_equal [false, true], h["s2"]
354
+ assert_equal [Wreq::SameSite::Strict, true, false], h["s1"]
355
+ assert_equal [Wreq::SameSite::Lax, false, true], h["s2"]
155
356
  end
156
357
 
157
358
  def test_request_uncompressed_cookies