curb 0.8.5 → 1.3.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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +579 -0
  3. data/Rakefile +85 -27
  4. data/doc.rb +48 -8
  5. data/ext/banned.h +32 -0
  6. data/ext/curb.c +563 -233
  7. data/ext/curb.h +19 -10
  8. data/ext/curb_easy.c +3229 -368
  9. data/ext/curb_easy.h +42 -0
  10. data/ext/curb_errors.c +117 -18
  11. data/ext/curb_errors.h +9 -5
  12. data/ext/curb_macros.h +33 -21
  13. data/ext/curb_multi.c +1904 -272
  14. data/ext/curb_multi.h +11 -3
  15. data/ext/curb_postfield.c +149 -77
  16. data/ext/curb_postfield.h +1 -0
  17. data/ext/curb_upload.c +38 -11
  18. data/ext/curb_upload.h +2 -0
  19. data/ext/extconf.rb +355 -35
  20. data/lib/curb.rb +1 -0
  21. data/lib/curl/download.rb +160 -0
  22. data/lib/curl/easy.rb +422 -88
  23. data/lib/curl/multi.rb +295 -56
  24. data/lib/curl.rb +676 -11
  25. data/tests/bug_crash_on_debug.rb +14 -28
  26. data/tests/bug_crash_on_progress.rb +32 -16
  27. data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
  28. data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
  29. data/tests/bug_follow_redirect_288.rb +83 -0
  30. data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
  31. data/tests/bug_issue102.rb +17 -0
  32. data/tests/bug_issue_noproxy.rb +56 -0
  33. data/tests/bug_issue_post_redirect.rb +93 -0
  34. data/tests/bug_issue_spnego.rb +41 -0
  35. data/tests/bug_multi_segfault.rb +1 -0
  36. data/tests/bug_poison.rb +29 -0
  37. data/tests/bug_raise_on_callback.rb +30 -0
  38. data/tests/helper.rb +400 -44
  39. data/tests/leak_trace.rb +237 -0
  40. data/tests/mem_check.rb +3 -0
  41. data/tests/tc_curl.rb +31 -1
  42. data/tests/tc_curl_download.rb +98 -7
  43. data/tests/tc_curl_easy.rb +985 -66
  44. data/tests/tc_curl_easy_cookielist.rb +277 -0
  45. data/tests/tc_curl_easy_request_target.rb +41 -0
  46. data/tests/tc_curl_easy_resolve.rb +48 -0
  47. data/tests/tc_curl_maxfilesize.rb +212 -0
  48. data/tests/tc_curl_multi.rb +1111 -51
  49. data/tests/tc_curl_native_coverage.rb +145 -0
  50. data/tests/tc_curl_network_policy.rb +1475 -0
  51. data/tests/tc_curl_postfield.rb +207 -30
  52. data/tests/tc_curl_protocols.rb +388 -0
  53. data/tests/tc_fiber_scheduler.rb +584 -0
  54. data/tests/tc_ftp_options.rb +39 -0
  55. data/tests/tc_gc_compact.rb +223 -0
  56. data/tests/tc_test_server_methods.rb +110 -0
  57. data/tests/timeout.rb +30 -6
  58. metadata +66 -31
  59. data/README +0 -194
@@ -4,6 +4,456 @@ class FooNoToS
4
4
  end
5
5
 
6
6
  class TestCurbCurlEasy < Test::Unit::TestCase
7
+ def test_global_reset
8
+ Curl.get($TEST_URL)
9
+ # in a Timeout block you should reset the thread current handle
10
+ Curl.reset
11
+ end
12
+
13
+ def test_nested_easy_methods
14
+ easy = Curl.get(TestServlet.url) {|http|
15
+ res = Curl.get(TestServlet.url + '/not_here')
16
+ assert_equal 404, res.code
17
+ }
18
+ assert_equal 200, easy.code
19
+ end
20
+
21
+ def test_perform_with_implicit_multi
22
+ easy = Curl::Easy.new($TEST_URL)
23
+
24
+ assert_nothing_raised { easy.perform }
25
+ assert_match(/^# DO NOT REMOVE THIS COMMENT/, easy.body_str)
26
+ end
27
+
28
+ def test_curlopt_resolve
29
+ require 'resolv'
30
+ uri = URI.parse(TestServlet.url)
31
+ resolved_ip = Resolv.getaddress(uri.host) # perform DNS lookup once
32
+ mapping = "#{uri.host}:#{uri.port}:#{resolved_ip}"
33
+
34
+ http = Curl::Easy.new(TestServlet.url)
35
+ http.setopt(Curl::CURLOPT_RESOLVE, [mapping])
36
+
37
+ http.get
38
+
39
+ assert_match(/GET/, http.body)
40
+ end
41
+
42
+ def test_resolve_clear_keeps_handle_usable
43
+ host = "curb.invalid"
44
+ url = "http://#{host}:#{TestServlet.port}#{TestServlet.path}"
45
+ mapping = "#{host}:#{TestServlet.port}:127.0.0.1"
46
+
47
+ http = Curl::Easy.new(url)
48
+ http.dns_cache_timeout = 0
49
+ http.resolve = [mapping]
50
+ http.get
51
+ assert_match(/GET/, http.body)
52
+
53
+ http.resolve = nil
54
+ http.url = TestServlet.url
55
+ http.get
56
+ assert_match(/GET/, http.body)
57
+ end
58
+
59
+ def test_curlopt_stderr_with_file
60
+ # does not work with Tempfile directly
61
+ path = Tempfile.new('curb_test_curlopt_stderr').path
62
+ File.open(path, 'w') do |file|
63
+ easy = Curl::Easy.new(TestServlet.url)
64
+ easy.verbose = true
65
+ easy.setopt(Curl::CURLOPT_STDERR, file)
66
+ easy.perform
67
+ end
68
+ output = File.read(path)
69
+
70
+ assert_match(/HTTP\/1\.1\ 200\ OK(?:\ )?/, output)
71
+ assert_match('Host: 127.0.0.1:9129', output)
72
+ end
73
+
74
+ def test_curlopt_stderr_with_io
75
+ tmp = Tempfile.new('curb_test_curlopt_stderr')
76
+ path = tmp.path
77
+ fd = IO.sysopen(path, 'w')
78
+ io = IO.new(fd, 'w')
79
+ io.sync = true
80
+
81
+ easy = Curl::Easy.new(TestServlet.url)
82
+ easy.verbose = true
83
+ easy.setopt(Curl::CURLOPT_STDERR, io)
84
+ easy.perform
85
+
86
+ io.flush
87
+ io.close
88
+ output = File.read(path)
89
+
90
+ assert_match(/HTTP\/1\.1\ 200\ OK(?:\ )?/, output)
91
+ assert_match('Host: 127.0.0.1:9129', output)
92
+ ensure
93
+ tmp.close! if defined?(tmp) && tmp
94
+ end
95
+
96
+ def test_curlopt_stderr_gc_safe
97
+ tmp = Tempfile.new('curb_test_curlopt_stderr_gc')
98
+ path = tmp.path
99
+ fd = IO.sysopen(path, 'w')
100
+ io = IO.new(fd, 'w')
101
+
102
+ easy = Curl::Easy.new(TestServlet.url)
103
+ easy.verbose = true
104
+ easy.setopt(Curl::CURLOPT_STDERR, io)
105
+
106
+ # Drop our Ruby reference and force GC; curb should retain it internally
107
+ io = nil
108
+ GC.start
109
+
110
+ easy.perform
111
+
112
+ output = File.read(path)
113
+ assert_match(/HTTP\/1\.1\ 200\ OK(?:\ )?/, output)
114
+ assert_match('Host: 127.0.0.1:9129', output)
115
+ ensure
116
+ tmp.close! if defined?(tmp) && tmp
117
+ end
118
+
119
+ def test_head_request_no_body_and_no_timeout
120
+ # Ensure a HEAD request completes and does not attempt to read a body
121
+ # even when the server advertises a Content-Length.
122
+ easy = nil
123
+ assert_nothing_raised do
124
+ easy = Curl.http(:HEAD, TestServlet.url)
125
+ end
126
+ assert_not_nil easy
127
+ # Header string should contain the HTTP status line.
128
+ assert_match(/HTTP\/1\.1\s\d+\s/, easy.header_str.to_s)
129
+ # Body should be empty for HEAD requests (libcurl won't call the write callback).
130
+ assert_equal "", easy.body_str.to_s
131
+ end
132
+
133
+ def test_head_request_restores_easy_state_after_callback_exception
134
+ easy = Curl::Easy.new(TestServlet.url)
135
+ easy.on_complete { raise "head complete blew up" }
136
+
137
+ error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.http("HEAD") }
138
+ assert_equal "head complete blew up", error.message
139
+
140
+ easy.on_complete { |_curl| }
141
+ easy.perform
142
+
143
+ assert_equal "GET", easy.body_str
144
+ end
145
+
146
+ def test_patch_request_does_not_leak_custom_method_to_next_perform
147
+ easy = Curl::Easy.new(TestServlet.url)
148
+
149
+ easy.http_patch("a=b")
150
+ assert_equal "PATCH\na=b", easy.body_str
151
+
152
+ easy.perform
153
+ assert_equal "GET", easy.body_str
154
+ end
155
+
156
+ def test_put_request_does_not_leak_custom_method_to_next_perform
157
+ easy = Curl::Easy.new(TestServlet.url)
158
+
159
+ easy.http_put("payload")
160
+ assert_equal "PUT\npayload", easy.body_str
161
+
162
+ easy.perform
163
+ assert_equal "GET", easy.body_str
164
+ end
165
+
166
+ def test_failed_put_data_setup_does_not_leave_easy_in_upload_mode
167
+ easy = Curl::Easy.new(TestServlet.url)
168
+ easy.headers = ["X-Test: true"]
169
+
170
+ assert_raise(RuntimeError) { easy.put_data = "payload" }
171
+
172
+ easy.headers = {}
173
+ easy.perform
174
+ assert_equal "GET", easy.body_str
175
+ end
176
+
177
+ def test_perform_releases_failed_implicit_multi_without_follow_up_easy_perform
178
+ Curl::Easy.flush_deferred_multi_closes
179
+ assert_equal 0, Curl::Easy.deferred_multi_closes.length
180
+
181
+ easy, implicit_multi = capture_failed_implicit_multi_cleanup_state
182
+
183
+ assert_nil easy.multi
184
+ assert_equal({}, implicit_multi.requests)
185
+ assert_equal false, implicit_multi.instance_variable_get(:@deferred_close)
186
+ assert_equal 0, Curl::Easy.deferred_multi_closes.length
187
+ ensure
188
+ easy.multi = nil if defined?(easy) && easy
189
+ implicit_multi.close if defined?(implicit_multi) && implicit_multi
190
+ Curl::Easy.flush_deferred_multi_closes
191
+ end
192
+
193
+ def test_deferred_multi_closes_stay_with_their_owner_thread_across_retry
194
+ Curl::Easy.flush_deferred_multi_closes(all_threads: true)
195
+ multi = deferred_close_stub_multi(failures: 1)
196
+
197
+ Curl::Easy.defer_multi_close(multi, nil)
198
+
199
+ Curl::Easy.flush_deferred_multi_closes
200
+ assert_equal 1, multi.close_attempts
201
+ assert_equal true, multi.instance_variable_get(:@deferred_close)
202
+ assert_equal 1, Curl::Easy.deferred_multi_closes.length
203
+
204
+ Thread.new { Curl::Easy.flush_deferred_multi_closes }.join
205
+ assert_equal 1, multi.close_attempts
206
+ assert_equal true, multi.instance_variable_get(:@deferred_close)
207
+ assert_equal 1, Curl::Easy.deferred_multi_closes.length
208
+
209
+ Curl::Easy.flush_deferred_multi_closes
210
+ assert_equal 2, multi.close_attempts
211
+ assert_equal true, multi.closed?
212
+ assert_equal false, multi.instance_variable_get(:@deferred_close)
213
+ assert_equal 0, Curl::Easy.deferred_multi_closes.length
214
+ ensure
215
+ Curl::Easy.flush_deferred_multi_closes(all_threads: true)
216
+ end
217
+
218
+ def capture_created_multis
219
+ created_multis = []
220
+ multi_singleton = class << Curl::Multi; self; end
221
+
222
+ multi_singleton.__send__(:alias_method, :__curb_test_original_new, :new)
223
+ multi_singleton.__send__(:define_method, :new) do |*args, &block|
224
+ multi = __curb_test_original_new(*args, &block)
225
+ created_multis << multi
226
+ multi
227
+ end
228
+
229
+ yield created_multis
230
+ ensure
231
+ if defined?(multi_singleton) && multi_singleton.method_defined?(:__curb_test_original_new)
232
+ multi_singleton.__send__(:remove_method, :new)
233
+ multi_singleton.__send__(:alias_method, :new, :__curb_test_original_new)
234
+ multi_singleton.__send__(:remove_method, :__curb_test_original_new)
235
+ end
236
+ end
237
+
238
+ def capture_failed_implicit_multi_cleanup_state
239
+ created_multis = nil
240
+ easy = nil
241
+
242
+ capture_created_multis do |captured_multis|
243
+ created_multis = captured_multis
244
+ easy = Curl::Easy.new(TestServlet.url)
245
+ easy.on_complete { raise "complete blew up" }
246
+
247
+ error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.perform }
248
+ assert_equal "complete blew up", error.message
249
+ end
250
+
251
+ assert_equal 1, created_multis.length, "test should observe one implicit multi created by Easy#perform"
252
+
253
+ [easy, created_multis.first]
254
+ end
255
+
256
+ def deferred_close_stub_multi(failures:)
257
+ Object.new.tap do |multi|
258
+ requests = {}
259
+ remaining_failures = failures
260
+ close_attempts = 0
261
+ closed = false
262
+
263
+ multi.define_singleton_method(:requests) { requests }
264
+ multi.define_singleton_method(:_close) do
265
+ close_attempts += 1
266
+ if remaining_failures.positive?
267
+ remaining_failures -= 1
268
+ raise "close failed"
269
+ end
270
+
271
+ closed = true
272
+ end
273
+ multi.define_singleton_method(:close_attempts) { close_attempts }
274
+ multi.define_singleton_method(:closed?) { closed }
275
+ end
276
+ end
277
+
278
+ def test_perform_restores_explicit_multi_after_callback_error
279
+ multi = Curl::Multi.new
280
+ easy = Curl::Easy.new(TestServlet.url)
281
+ easy.multi = multi
282
+ easy.on_complete { raise "complete blew up" }
283
+
284
+ error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.perform }
285
+ assert_equal "complete blew up", error.message
286
+ assert_same multi, easy.multi
287
+ assert_equal({}, multi.requests)
288
+
289
+ easy.on_complete { |_curl| }
290
+ easy.perform
291
+
292
+ assert_same multi, easy.multi
293
+ assert_equal "GET", easy.body_str
294
+ ensure
295
+ easy.multi = nil if defined?(easy) && easy && easy.multi.equal?(multi)
296
+ multi.close if defined?(multi) && multi
297
+ end
298
+
299
+ def test_perform_restores_cached_implicit_multi_after_callback_error
300
+ Curl::Multi.autoclose = false
301
+
302
+ easy = Curl::Easy.new(TestServlet.url)
303
+ easy.perform
304
+
305
+ cached_multi = easy.multi
306
+ easy.on_complete { raise "complete blew up" }
307
+
308
+ error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.perform }
309
+ assert_equal "complete blew up", error.message
310
+ assert_same cached_multi, easy.multi
311
+ assert_equal({}, cached_multi.requests)
312
+
313
+ easy.on_complete { |_curl| }
314
+ easy.perform
315
+
316
+ assert_same cached_multi, easy.multi
317
+ assert_equal "GET", easy.body_str
318
+ ensure
319
+ easy.multi = nil if defined?(easy) && easy
320
+ cached_multi.close if defined?(cached_multi) && cached_multi
321
+ Curl::Multi.autoclose = false
322
+ end
323
+
324
+ def test_close_clears_explicit_idle_easy_multi_reference
325
+ easy = Curl::Easy.new($TEST_URL)
326
+ multi = Curl::Multi.new
327
+ easy.multi = multi
328
+
329
+ assert_same multi, easy.multi
330
+ assert_equal({}, multi.requests)
331
+
332
+ multi.close
333
+
334
+ assert_nil easy.multi
335
+ end
336
+
337
+ def test_close_clears_cached_idle_easy_multi_reference
338
+ Curl::Multi.autoclose = false
339
+
340
+ easy = Curl::Easy.new($TEST_URL)
341
+ easy.perform
342
+
343
+ cached_multi = easy.multi
344
+ assert_not_nil cached_multi
345
+ assert_equal({}, cached_multi.requests)
346
+
347
+ cached_multi.close
348
+
349
+ assert_nil easy.multi
350
+ ensure
351
+ easy.multi = nil if defined?(easy) && easy
352
+ Curl::Multi.autoclose = false
353
+ end
354
+
355
+ def test_multi_assignment_rejects_non_multi_objects
356
+ easy = Curl::Easy.new($TEST_URL)
357
+
358
+ error = assert_raise(TypeError) { easy.multi = Object.new }
359
+
360
+ assert_match(/Curl::Multi/, error.message)
361
+ assert_nil easy.multi
362
+ end
363
+
364
+ def test_perform_can_reuse_explicit_multi_when_autoclose_is_enabled
365
+ Curl::Multi.autoclose = true
366
+
367
+ easy = Curl::Easy.new(TestServlet.url)
368
+ multi = Curl::Multi.new
369
+
370
+ easy.multi = multi
371
+ easy.perform
372
+
373
+ assert_nil easy.multi
374
+ assert_equal({}, multi.requests)
375
+
376
+ easy.multi = multi
377
+ easy.perform
378
+
379
+ assert_nil easy.multi
380
+ assert_equal({}, multi.requests)
381
+ assert_equal "GET", easy.body_str
382
+ ensure
383
+ easy.multi = nil if defined?(easy) && easy
384
+ multi.close if defined?(multi) && multi
385
+ Curl::Multi.autoclose = false
386
+ end
387
+
388
+ def test_perform_reraises_on_body_exception_for_frozen_easy
389
+ easy = Curl::Easy.new($TEST_URL)
390
+ callback_error = Class.new(RuntimeError)
391
+ easy.on_body { raise callback_error, "body blew up" }
392
+ easy.freeze
393
+
394
+ error = assert_raise(callback_error) { easy.perform }
395
+
396
+ assert_equal "body blew up", error.message
397
+ end
398
+
399
+ def test_perform_preserves_implicit_multi_when_autoclose_is_disabled
400
+ Curl::Multi.autoclose = false
401
+
402
+ easy = Curl::Easy.new(TestServlet.url)
403
+ easy.perform
404
+
405
+ first_multi = easy.multi
406
+ assert_not_nil first_multi
407
+ assert_equal({}, first_multi.requests)
408
+
409
+ easy.perform
410
+
411
+ assert_same first_multi, easy.multi
412
+ assert_equal "GET", easy.body_str
413
+ ensure
414
+ easy.multi = nil if defined?(easy) && easy
415
+ first_multi.close if defined?(first_multi) && first_multi
416
+ Curl::Multi.autoclose = false
417
+ end
418
+
419
+ def test_curlopt_stderr_fails_with_tempdir
420
+ Tempfile.open('curb_test_curlopt_stderr') do |tempfile|
421
+ easy = Curl::Easy.new(TestServlet.url)
422
+
423
+ assert_raise(TypeError) do
424
+ easy.setopt(Curl::CURLOPT_STDERR, tempfile)
425
+ end
426
+ end
427
+ end
428
+
429
+ def test_curlopt_stderr_fails_with_stringio
430
+ stringio = StringIO.new
431
+ easy = Curl::Easy.new(TestServlet.url)
432
+
433
+ assert_raise(TypeError) do
434
+ easy.setopt(Curl::CURLOPT_STDERR, stringio)
435
+ end
436
+ end
437
+
438
+ def test_curlopt_stderr_fails_with_string
439
+ string = String.new
440
+ easy = Curl::Easy.new(TestServlet.url)
441
+
442
+ assert_raise(TypeError) do
443
+ easy.setopt(Curl::CURLOPT_STDERR, string)
444
+ end
445
+ end
446
+
447
+ def test_exception
448
+ begin
449
+ Curl.get('NOT_FOUND_URL')
450
+ rescue
451
+ assert true
452
+ rescue Exception
453
+ assert false, "We should raise StandardError"
454
+ end
455
+ end
456
+
7
457
  def test_threads
8
458
  t = []
9
459
  5.times do
@@ -12,33 +462,29 @@ class TestCurbCurlEasy < Test::Unit::TestCase
12
462
  c = Curl.get($TEST_URL)
13
463
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
14
464
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body)
15
- assert_equal "", c.header_str
16
- assert_equal "", c.head
17
465
  end
18
466
  end
19
467
  end
20
468
 
21
- t.each {|t| t.join }
469
+ t.each {|x| x.join }
22
470
  end
23
471
 
24
472
  def test_class_perform_01
25
473
  assert_instance_of Curl::Easy, c = Curl::Easy.perform($TEST_URL)
26
474
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
27
475
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body)
28
- assert_equal "", c.header_str
29
476
  end
30
477
 
31
478
  def test_class_perform_02
32
- data = ""
479
+ data = String.new
33
480
  assert_instance_of Curl::Easy, c = Curl::Easy.perform($TEST_URL) { |curl| curl.on_body { |d| data << d; d.length } }
34
481
 
35
482
  assert_nil c.body_str
36
- assert_equal "", c.header_str
37
483
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, data)
38
484
  end
39
485
 
40
486
  def test_class_perform_03
41
- assert_raise(Curl::Err::CouldntReadError) { c = Curl::Easy.perform($TEST_URL + "nonexistent") }
487
+ assert_raise(Curl::Err::CouldntReadError) { Curl::Easy.perform($TEST_URL + "nonexistent") }
42
488
  end
43
489
 
44
490
  def test_new_01
@@ -128,11 +574,10 @@ class TestCurbCurlEasy < Test::Unit::TestCase
128
574
  c = Curl::Easy.new($TEST_URL)
129
575
  assert_equal true, c.http_get
130
576
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
131
- assert_equal "", c.header_str
132
577
  end
133
578
 
134
579
  def test_get_02
135
- data = ""
580
+ data = String.new
136
581
  c = Curl::Easy.new($TEST_URL) do |curl|
137
582
  curl.on_body { |d| data << d; d.length }
138
583
  end
@@ -140,7 +585,6 @@ class TestCurbCurlEasy < Test::Unit::TestCase
140
585
  assert_equal true, c.http_get
141
586
 
142
587
  assert_nil c.body_str
143
- assert_equal "", c.header_str
144
588
  assert_match(/^# DO NOT REMOVE THIS COMMENT/, data)
145
589
  end
146
590
 
@@ -153,6 +597,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
153
597
 
154
598
 
155
599
  def test_last_effective_url_01
600
+ omit('Windows file URL semantics differ') if WINDOWS
156
601
  c = Curl::Easy.new($TEST_URL)
157
602
 
158
603
  assert_equal $TEST_URL, c.url
@@ -323,19 +768,71 @@ class TestCurbCurlEasy < Test::Unit::TestCase
323
768
  c.max_redirects = nil
324
769
  assert_nil c.max_redirects
325
770
  end
326
-
771
+
772
+ def test_timeout_with_floats
773
+ c = Curl::Easy.new($TEST_URL)
774
+
775
+ c.timeout = 1.5
776
+ assert_equal 1500, c.timeout_ms
777
+ assert_equal 1.5, c.timeout
778
+ end
779
+
780
+ def test_timeout_with_negative
781
+ c = Curl::Easy.new($TEST_URL)
782
+
783
+ c.timeout = -1.5
784
+ assert_equal 0, c.timeout
785
+ assert_equal 0, c.timeout_ms
786
+
787
+ c.timeout = -4.8
788
+ assert_equal 0, c.timeout
789
+ assert_equal 0, c.timeout_ms
790
+ end
791
+
327
792
  def test_timeout_01
328
793
  c = Curl::Easy.new($TEST_URL)
329
-
330
- assert_nil c.timeout
331
-
794
+
795
+ assert_equal 0, c.timeout
796
+
332
797
  c.timeout = 3
333
798
  assert_equal 3, c.timeout
334
-
335
- c.timeout = nil
336
- assert_nil c.timeout
799
+
800
+ c.timeout = 0
801
+ assert_equal 0, c.timeout
337
802
  end
338
-
803
+
804
+ def test_timeout_ms_01
805
+ c = Curl::Easy.new($TEST_URL)
806
+
807
+ assert_equal 0, c.timeout_ms
808
+
809
+ c.timeout_ms = 100
810
+ assert_equal 100, c.timeout_ms
811
+
812
+ c.timeout_ms = nil
813
+ assert_equal 0, c.timeout_ms
814
+ end
815
+
816
+ def test_timeout_ms_with_floats
817
+ c = Curl::Easy.new($TEST_URL)
818
+
819
+ c.timeout_ms = 55.5
820
+ assert_equal 55, c.timeout_ms
821
+ assert_equal 0.055, c.timeout
822
+ end
823
+
824
+ def test_timeout_ms_with_negative
825
+ c = Curl::Easy.new($TEST_URL)
826
+
827
+ c.timeout_ms = -1.5
828
+ assert_equal 0, c.timeout
829
+ assert_equal 0, c.timeout_ms
830
+
831
+ c.timeout_ms = -4.8
832
+ assert_equal 0, c.timeout
833
+ assert_equal 0, c.timeout_ms
834
+ end
835
+
339
836
  def test_connect_timeout_01
340
837
  c = Curl::Easy.new($TEST_URL)
341
838
 
@@ -347,7 +844,19 @@ class TestCurbCurlEasy < Test::Unit::TestCase
347
844
  c.connect_timeout = nil
348
845
  assert_nil c.connect_timeout
349
846
  end
350
-
847
+
848
+ def test_connect_timeout_ms_01
849
+ c = Curl::Easy.new($TEST_URL)
850
+
851
+ assert_nil c.connect_timeout_ms
852
+
853
+ c.connect_timeout_ms = 100
854
+ assert_equal 100, c.connect_timeout_ms
855
+
856
+ c.connect_timeout_ms = nil
857
+ assert_nil c.connect_timeout_ms
858
+ end
859
+
351
860
  def test_ftp_response_timeout_01
352
861
  c = Curl::Easy.new($TEST_URL)
353
862
 
@@ -537,6 +1046,27 @@ class TestCurbCurlEasy < Test::Unit::TestCase
537
1046
  assert_raises(ArgumentError) { c.resolve_mode = :bad }
538
1047
  end
539
1048
 
1049
+ def test_http_version_accessors
1050
+ c = Curl::Easy.new
1051
+ assert_equal Curl::HTTP_NONE, c.http_version
1052
+
1053
+ c.http_version = Curl::HTTP_1_1
1054
+ assert_equal Curl::HTTP_1_1, c.http_version
1055
+
1056
+ if Curl.const_defined?(:HTTP_2TLS)
1057
+ c.http_version = Curl::HTTP_2TLS
1058
+ assert_equal Curl::HTTP_2TLS, c.http_version
1059
+ end
1060
+
1061
+ if Curl.const_defined?(:HTTP_2_PRIOR_KNOWLEDGE)
1062
+ c.http_version = Curl::HTTP_2_PRIOR_KNOWLEDGE
1063
+ assert_equal Curl::HTTP_2_PRIOR_KNOWLEDGE, c.http_version
1064
+ end
1065
+
1066
+ c.http_version = nil
1067
+ assert_equal Curl::HTTP_NONE, c.http_version
1068
+ end
1069
+
540
1070
  def test_enable_cookies
541
1071
  c = Curl::Easy.new
542
1072
  assert !c.enable_cookies?
@@ -565,14 +1095,23 @@ class TestCurbCurlEasy < Test::Unit::TestCase
565
1095
  assert_equal "some.file", c.cookiejar
566
1096
  end
567
1097
 
1098
+ def test_cookielist
1099
+ c = Curl::Easy.new TestServlet.url
1100
+ c.enable_cookies = true
1101
+ c.post_body = URI.encode_www_form('c' => 'somename=somevalue')
1102
+ assert_nil c.cookielist
1103
+ c.perform
1104
+ assert_match(/somevalue/, c.cookielist.join(''))
1105
+ end
1106
+
568
1107
  def test_on_success
569
1108
  curl = Curl::Easy.new($TEST_URL)
570
1109
  on_success_called = false
571
1110
  curl.on_success {|c|
572
1111
  on_success_called = true
573
- assert_not_nil c.body_str
574
- assert_equal "", c.header_str
575
- assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
1112
+ assert_not_nil c.body
1113
+ assert_match(/Content-Length: /, c.head)
1114
+ assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body)
576
1115
  }
577
1116
  curl.perform
578
1117
  assert on_success_called, "Success handler not called"
@@ -614,20 +1153,27 @@ class TestCurbCurlEasy < Test::Unit::TestCase
614
1153
  assert_equal 'GET', curl.body_str
615
1154
  end
616
1155
 
617
- def test_post_remote
1156
+ def test_remote
618
1157
  curl = Curl::Easy.new(TestServlet.url)
619
1158
  curl.http_post([Curl::PostField.content('document_id', 5)])
620
1159
  assert_equal "POST\ndocument_id=5", curl.unescape(curl.body_str)
1160
+
1161
+ curl.http_put([Curl::PostField.content('document_id', 5)])
1162
+ assert_equal "PUT\ndocument_id=5", curl.unescape(curl.body_str)
1163
+
1164
+ curl.http_patch([Curl::PostField.content('document_id', 5)])
1165
+ assert_equal "PATCH\ndocument_id=5", curl.unescape(curl.body_str)
621
1166
  end
622
1167
 
623
- def test_post_remote_is_easy_handle
1168
+ def test_remote_is_easy_handle
624
1169
  # see: http://pastie.org/560852 and
625
1170
  # http://groups.google.com/group/curb---ruby-libcurl-bindings/browse_thread/thread/216bb2d9b037f347?hl=en
626
- [:post, :get, :head, :delete].each do |method|
1171
+ [:post, :patch, :put, :get, :head, :delete].each do |method|
627
1172
  retries = 0
628
1173
  begin
629
1174
  count = 0
630
- curl = Curl::Easy.send("http_#{method}", TestServlet.url) do|c|
1175
+ m = "http_#{method}".to_sym
1176
+ Curl::Easy.send(m, TestServlet.url) do|c|
631
1177
  count += 1
632
1178
  assert_equal Curl::Easy, c.class
633
1179
  end
@@ -636,24 +1182,40 @@ class TestCurbCurlEasy < Test::Unit::TestCase
636
1182
  retries+=1
637
1183
  retry if retries < 3
638
1184
  raise e
1185
+ rescue ArgumentError => e
1186
+ assert false, "#{m} - #{e.message}"
639
1187
  end
640
1188
  end
641
1189
  end
642
1190
 
643
1191
  # see: https://github.com/rvanlieshout/curb/commit/8bcdefddc0162484681ebd1a92d52a642666a445
644
- def test_post_multipart_array_remote
1192
+ def test_multipart_array_remote
645
1193
  curl = Curl::Easy.new(TestServlet.url)
646
1194
  curl.multipart_form_post = true
647
1195
  fields = [
648
- Curl::PostField.file('foo', File.expand_path(File.join(File.dirname(__FILE__),'..','README'))),
649
- Curl::PostField.file('bar', File.expand_path(File.join(File.dirname(__FILE__),'..','README')))
1196
+ Curl::PostField.file('foo', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))),
1197
+ Curl::PostField.file('bar', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md')))
650
1198
  ]
651
1199
  curl.http_post(fields)
652
- assert_match /HTTP POST file upload/, curl.body_str
653
- assert_match /Content-Disposition: form-data/, curl.body_str
1200
+ assert_match(/HTTP POST file upload/, curl.body_str)
1201
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
1202
+
1203
+ curl = Curl::Easy.new(TestServlet.url)
1204
+ curl.multipart_form_post = true
1205
+ fields = [
1206
+ Curl::PostField.file('foo', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))),
1207
+ Curl::PostField.file('bar', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md')))
1208
+ ]
1209
+ curl.http_put(fields)
1210
+ assert_match(/HTTP POST file upload/, curl.body_str)
1211
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
1212
+
1213
+ curl.http_patch(fields)
1214
+ assert_match(/HTTP POST file upload/, curl.body_str)
1215
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
654
1216
  end
655
1217
 
656
- def test_post_with_body_remote
1218
+ def test_with_body_remote
657
1219
  curl = Curl::Easy.new(TestServlet.url)
658
1220
  curl.post_body = 'foo=bar&encoded%20string=val'
659
1221
 
@@ -661,23 +1223,151 @@ class TestCurbCurlEasy < Test::Unit::TestCase
661
1223
 
662
1224
  assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
663
1225
  assert_equal 'foo=bar&encoded%20string=val', curl.post_body
1226
+
1227
+ curl = Curl::Easy.new(TestServlet.url)
1228
+ curl.put_data = 'foo=bar&encoded%20string=val'
1229
+
1230
+ curl.perform
1231
+
1232
+ assert_equal "PUT\nfoo=bar&encoded%20string=val", curl.body_str
664
1233
  end
665
-
666
- def test_form_post_body_remote
1234
+
1235
+ def test_post_body_from_to_s_keeps_string_buffer_alive
1236
+ post_body = Object.new
1237
+ def post_body.to_s
1238
+ 'foo=bar&encoded%20string=val'
1239
+ end
1240
+
1241
+ curl = Curl::Easy.new(TestServlet.url)
1242
+ curl.post_body = post_body
1243
+
1244
+ assert_equal 'foo=bar&encoded%20string=val', curl.post_body
1245
+
1246
+ GC.start
1247
+ GC.compact if GC.respond_to?(:compact)
1248
+
1249
+ curl.perform
1250
+ assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
1251
+ end
1252
+
1253
+ def test_post_body_preserves_assigned_snapshot_when_original_string_is_mutated
1254
+ omit('CURLOPT_COPYPOSTFIELDS is not available in this build') unless Curl.const_defined?(:CURLOPT_COPYPOSTFIELDS)
1255
+
1256
+ curl = Curl::Easy.new(TestServlet.url)
1257
+ post_body = String.new('alpha=1')
1258
+ curl.post_body = post_body
1259
+
1260
+ post_body.replace('beta=2')
1261
+
1262
+ assert_equal 'beta=2', post_body
1263
+ assert_equal 'alpha=1', curl.post_body
1264
+
1265
+ curl.perform
1266
+
1267
+ assert_equal "POST\nalpha=1", curl.body_str
1268
+ assert_equal 'alpha=1', curl.post_body
1269
+ end
1270
+
1271
+ def test_setopt_postfields_keeps_string_buffer_alive
1272
+ curl = Curl::Easy.new(TestServlet.url)
1273
+ curl.set(Curl::CURLOPT_POSTFIELDS, 'foo=bar&encoded%20string=val')
1274
+
1275
+ assert_equal 'foo=bar&encoded%20string=val', curl.post_body
1276
+
1277
+ GC.start
1278
+ GC.compact if GC.respond_to?(:compact)
1279
+
1280
+ curl.perform
1281
+ assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
1282
+ end
1283
+
1284
+ def test_setopt_postfields_preserves_assigned_snapshot_when_original_string_is_mutated
1285
+ omit('CURLOPT_COPYPOSTFIELDS is not available in this build') unless Curl.const_defined?(:CURLOPT_COPYPOSTFIELDS)
1286
+
1287
+ curl = Curl::Easy.new(TestServlet.url)
1288
+ post_body = String.new('alpha=1')
1289
+ curl.set(Curl::CURLOPT_POSTFIELDS, post_body)
1290
+
1291
+ post_body.replace('beta=2')
1292
+
1293
+ assert_equal 'beta=2', post_body
1294
+ assert_equal 'alpha=1', curl.post_body
1295
+
1296
+ curl.perform
1297
+
1298
+ assert_equal "POST\nalpha=1", curl.body_str
1299
+ assert_equal 'alpha=1', curl.post_body
1300
+ end
1301
+
1302
+ def test_setopt_postfields_nil_preserves_post_request
1303
+ curl = Curl::Easy.new(TestServlet.url)
1304
+ curl.set(Curl::CURLOPT_POST, true)
1305
+ curl.set(Curl::CURLOPT_POSTFIELDS, nil)
1306
+
1307
+ curl.perform
1308
+
1309
+ assert_nil curl.post_body
1310
+ assert_equal "POST\n", curl.body_str
1311
+ end
1312
+
1313
+ def test_form_body_remote
667
1314
  curl = Curl::Easy.new(TestServlet.url)
668
1315
  curl.http_post('foo=bar', 'encoded%20string=val')
669
1316
 
670
1317
  assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
671
1318
  assert_equal 'foo=bar&encoded%20string=val', curl.post_body
1319
+
1320
+ curl = Curl::Easy.new(TestServlet.url)
1321
+ curl.http_put('foo=bar', 'encoded%20string=val')
1322
+
1323
+ assert_equal "PUT\nfoo=bar&encoded%20string=val", curl.body_str
1324
+
1325
+ curl = Curl::Easy.new(TestServlet.url)
1326
+ curl.http_patch('foo=bar', 'encoded%20string=val')
1327
+
1328
+ assert_equal "PATCH\nfoo=bar&encoded%20string=val", curl.body_str
1329
+ end
1330
+
1331
+ def test_multipart_file_remote
1332
+ [:put, :post, :patch].each {|method|
1333
+ curl = Curl::Easy.new(TestServlet.url)
1334
+ curl.multipart_form_post = true
1335
+ pf = Curl::PostField.file('readme', File.expand_path(File.join(File.dirname(__FILE__),'..','README.md')))
1336
+ curl.send("http_#{method}", pf)
1337
+ assert_match(/HTTP POST file upload/, curl.body_str)
1338
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
1339
+ }
1340
+ end
1341
+
1342
+ def test_multipart_form_reuse_clears_previous_form
1343
+ curl = Curl::Easy.new(TestServlet.url)
1344
+ curl.multipart_form_post = true
1345
+ curl.http_post(Curl::PostField.content('document_id', '5'))
1346
+ assert_match(/document_id/, curl.body_str)
1347
+
1348
+ curl.multipart_form_post = false
1349
+ curl.post_body = 'foo=bar'
1350
+ curl.http_post
1351
+
1352
+ assert_equal "POST\nfoo=bar", curl.body_str
672
1353
  end
673
1354
 
674
- def test_post_multipart_file_remote
1355
+ def test_multipart_patch_build_failure_restores_easy_request_state
675
1356
  curl = Curl::Easy.new(TestServlet.url)
676
1357
  curl.multipart_form_post = true
677
- pf = Curl::PostField.file('readme', File.expand_path(File.join(File.dirname(__FILE__),'..','README')))
678
- curl.http_post(pf)
679
- assert_match /HTTP POST file upload/, curl.body_str
680
- assert_match /Content-Disposition: form-data/, curl.body_str
1358
+ field = Curl::PostField.content('document_id', '5')
1359
+
1360
+ field.name = nil
1361
+ error = assert_raise(Curl::Err::InvalidPostFieldError) do
1362
+ # The first field allocates native multipart form state before the second field raises.
1363
+ curl.http_patch(Curl::PostField.content('keep', 'allocated'), field)
1364
+ end
1365
+ assert_match(/Cannot post unnamed field/, error.message)
1366
+
1367
+ curl.multipart_form_post = false
1368
+ curl.perform
1369
+
1370
+ assert_equal 'GET', curl.body_str
681
1371
  end
682
1372
 
683
1373
  def test_delete_remote
@@ -699,7 +1389,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
699
1389
  redirect = curl.header_str.match(/Location: (.*)/)
700
1390
 
701
1391
  assert_equal '', curl.body_str
702
- assert_match '/nonexistent', redirect[1]
1392
+ assert_match('/nonexistent', redirect[1])
703
1393
  end
704
1394
 
705
1395
  def test_head_accessor
@@ -710,7 +1400,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
710
1400
  redirect = curl.header_str.match(/Location: (.*)/)
711
1401
 
712
1402
  assert_equal '', curl.body_str
713
- assert_match '/nonexistent', redirect[1]
1403
+ assert_match('/nonexistent', redirect[1])
714
1404
  curl.head = false
715
1405
  curl.perform
716
1406
  assert_equal 'GET', curl.body_str
@@ -720,11 +1410,11 @@ class TestCurbCurlEasy < Test::Unit::TestCase
720
1410
  curl = Curl::Easy.new(TestServlet.url)
721
1411
  curl.headers['Content-Type'] = 'application/json'
722
1412
  assert curl.http_put("message")
723
- assert_match /^PUT/, curl.body_str
724
- assert_match /message$/, curl.body_str
725
- assert_match /message$/, curl.body
726
- assert_match /application\/json/, curl.header_str
727
- assert_match /application\/json/, curl.head
1413
+ assert_match(/^PUT/, curl.body_str)
1414
+ assert_match(/message$/, curl.body_str)
1415
+ assert_match(/message$/, curl.body)
1416
+ assert_match(/application\/json/, curl.header_str)
1417
+ assert_match(/application\/json/, curl.head)
728
1418
  end
729
1419
 
730
1420
  def test_put_data
@@ -733,8 +1423,46 @@ class TestCurbCurlEasy < Test::Unit::TestCase
733
1423
 
734
1424
  curl.perform
735
1425
 
736
- assert_match /^PUT/, curl.body_str
737
- assert_match /message$/, curl.body_str
1426
+ assert_match(/^PUT/, curl.body_str)
1427
+ assert_match(/message$/, curl.body_str)
1428
+ end
1429
+
1430
+ def test_put_data_from_to_s_object
1431
+ curl = Curl::Easy.new(TestServlet.url)
1432
+ curl.put_data = :message
1433
+
1434
+ curl.perform
1435
+
1436
+ assert_equal "PUT\nmessage", curl.body_str
1437
+ end
1438
+
1439
+ def test_put_data_read_exception_sets_result_and_releases_callback_state
1440
+ error_class = Class.new(StandardError)
1441
+ reader_class = Class.new do
1442
+ define_method(:read) do |_len|
1443
+ raise error_class, 'upload read failed'
1444
+ end
1445
+
1446
+ def seek(_offset, _whence = SEEK_SET)
1447
+ 0
1448
+ end
1449
+
1450
+ def stat
1451
+ Struct.new(:size).new(1)
1452
+ end
1453
+ end
1454
+
1455
+ curl = Curl::Easy.new(TestServlet.url)
1456
+ curl.put_data = reader_class.new
1457
+
1458
+ error = assert_raise(error_class) { curl.perform }
1459
+ assert_equal 'upload read failed', error.message
1460
+ assert_not_equal 0, curl.last_result
1461
+
1462
+ curl.url = TestServlet.url
1463
+ curl.http_get
1464
+
1465
+ assert_equal 'GET', curl.body_str
738
1466
  end
739
1467
 
740
1468
  # https://github.com/taf2/curb/issues/101
@@ -744,8 +1472,8 @@ class TestCurbCurlEasy < Test::Unit::TestCase
744
1472
 
745
1473
  curl.perform
746
1474
 
747
- assert_match /^PUT/, curl.body_str
748
- assert_match "a\0b", curl.body_str
1475
+ assert_match(/^PUT/, curl.body_str)
1476
+ assert_match("a\0b", curl.body_str)
749
1477
  end
750
1478
 
751
1479
  def test_put_nil_data_no_crash
@@ -760,7 +1488,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
760
1488
  File.open(__FILE__,'rb') do|f|
761
1489
  assert curl.http_put(f)
762
1490
  end
763
- assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str
1491
+ assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str.tr("\r", '')
764
1492
  end
765
1493
 
766
1494
  def test_put_class_method
@@ -770,7 +1498,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
770
1498
  assert_equal Curl::Easy, c.class
771
1499
  end
772
1500
  assert_equal 1, count
773
- assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str
1501
+ assert_equal "PUT\n#{File.read(__FILE__)}", curl.body_str.tr("\r", '')
774
1502
  end
775
1503
 
776
1504
  # Generate a self-signed cert with
@@ -779,7 +1507,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
779
1507
  def test_cert
780
1508
  curl = Curl::Easy.new(TestServlet.url)
781
1509
  curl.cert= File.join(File.dirname(__FILE__),"cert.pem")
782
- assert_match /cert.pem$/,curl.cert
1510
+ assert_match(/cert.pem$/,curl.cert)
783
1511
  end
784
1512
 
785
1513
  def test_cert_with_password
@@ -787,7 +1515,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
787
1515
  path = File.join(File.dirname(__FILE__),"cert.pem")
788
1516
  curl.certpassword = 'password'
789
1517
  curl.cert = path
790
- assert_match /cert.pem$/,curl.cert
1518
+ assert_match(/cert.pem$/,curl.cert)
791
1519
  end
792
1520
 
793
1521
  def test_cert_type
@@ -808,7 +1536,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
808
1536
  def test_ca_cert
809
1537
  curl = Curl::Easy.new(TestServlet.url)
810
1538
  curl.cacert= File.join(File.dirname(__FILE__),"cacert.pem")
811
- assert_match /cacert.pem$/, curl.cacert
1539
+ assert_match(/cacert.pem$/, curl.cacert)
812
1540
  end
813
1541
 
814
1542
  def test_user_agent
@@ -834,7 +1562,15 @@ class TestCurbCurlEasy < Test::Unit::TestCase
834
1562
  $auth_header = nil
835
1563
  # curl checks the auth type supported by the server, so we have to create a
836
1564
  # new easy handle if we're going to change the auth type...
837
-
1565
+ if WINDOWS
1566
+ # On Windows, libcurl often uses SSPI for NTLM which yields a different
1567
+ # header value and encoding; skip the NTLM-specific assertion.
1568
+ return
1569
+ end
1570
+ # curl 8.20.0 disables NTLM by default and plans to remove it in
1571
+ # September 2026; libcurl silently downgrades to Basic when NTLM is
1572
+ # unavailable, so skip the NTLM-specific assertion in that case.
1573
+ return unless Curl.ntlm?
838
1574
  curl = Curl::Easy.new(TestServlet.url)
839
1575
  curl.username = "foo"
840
1576
  curl.password = "bar"
@@ -855,7 +1591,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
855
1591
  end
856
1592
 
857
1593
  def test_post_streaming
858
- readme = File.expand_path(File.join(File.dirname(__FILE__),'..','README'))
1594
+ readme = File.expand_path(File.join(File.dirname(__FILE__),'..','README.md'))
859
1595
 
860
1596
  pf = Curl::PostField.file("filename", readme)
861
1597
 
@@ -865,11 +1601,10 @@ class TestCurbCurlEasy < Test::Unit::TestCase
865
1601
  easy.multipart_form_post = true
866
1602
  easy.http_post(pf)
867
1603
 
868
- assert_not_equal(0,easy.body_str.size)
869
- assert_equal(easy.body_str,File.read(readme))
1604
+ assert_not_equal(0,easy.body.size)
1605
+ assert_equal(Digest::MD5.hexdigest(easy.body), Digest::MD5.hexdigest(File.binread(readme)))
870
1606
  end
871
1607
 
872
-
873
1608
  def test_easy_close
874
1609
  easy = Curl::Easy.new
875
1610
  easy.close
@@ -877,6 +1612,18 @@ class TestCurbCurlEasy < Test::Unit::TestCase
877
1612
  easy.http_get
878
1613
  end
879
1614
 
1615
+ def test_easy_close_restores_error_buffer
1616
+ easy = Curl::Easy.new('http://127.0.0.1:1')
1617
+ assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
1618
+ assert_not_nil easy.last_error
1619
+
1620
+ easy.close
1621
+ easy.url = 'http://127.0.0.1:1'
1622
+ assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
1623
+
1624
+ assert_not_nil easy.last_error
1625
+ end
1626
+
880
1627
  def test_easy_reset
881
1628
  easy = Curl::Easy.new
882
1629
  easy.url = TestServlet.url + "?query=foo"
@@ -889,6 +1636,48 @@ class TestCurbCurlEasy < Test::Unit::TestCase
889
1636
  easy.http_get
890
1637
  end
891
1638
 
1639
+ def test_easy_reset_clears_network_policy_allowlists
1640
+ easy = Curl::Easy.new(TestServlet.url)
1641
+ easy.allowed_hosts = ['127.0.0.1']
1642
+ easy.network_policy = :public
1643
+ easy.allowed_cidrs = ['1.1.1.0/24']
1644
+
1645
+ settings = easy.reset
1646
+
1647
+ assert settings.key?(:allowed_hosts)
1648
+ assert settings.key?(:allowed_cidrs)
1649
+ assert_nil easy.allowed_hosts
1650
+ assert_nil easy.allowed_cidrs
1651
+ assert_equal :none, easy.network_policy
1652
+ rescue NotImplementedError
1653
+ omit('network_policy=:public requires CURLOPT_OPENSOCKETFUNCTION support')
1654
+ end
1655
+
1656
+ def test_frozen_easy_close_and_reset_skip_safety_override_cleanup
1657
+ closed_easy = Curl::Easy.new
1658
+ closed_easy.freeze
1659
+
1660
+ assert_nothing_raised { closed_easy.close }
1661
+
1662
+ reset_easy = Curl::Easy.new
1663
+ reset_easy.safe_http!
1664
+ reset_easy.freeze
1665
+
1666
+ assert_nothing_raised { reset_easy.reset }
1667
+ end
1668
+
1669
+ def test_last_result_initialization
1670
+ # Test for issue #463 - ensure last_result is properly initialized to 0
1671
+ easy = Curl::Easy.new
1672
+ assert_equal 0, easy.last_result, "last_result should be initialized to 0 for new instance"
1673
+
1674
+ # Test that reset also sets last_result to 0
1675
+ easy.url = TestServlet.url
1676
+ easy.http_get
1677
+ easy.reset
1678
+ assert_equal 0, easy.last_result, "last_result should be reset to 0 after calling reset"
1679
+ end
1680
+
892
1681
  def test_easy_use_http_versions
893
1682
  easy = Curl::Easy.new
894
1683
  easy.url = TestServlet.url + "?query=foo"
@@ -914,16 +1703,22 @@ class TestCurbCurlEasy < Test::Unit::TestCase
914
1703
  assert_equal "PUT\nhello", curl.body_str
915
1704
  curl.http('COPY')
916
1705
  assert_equal 'COPY', curl.body_str
1706
+
1707
+ curl.http_patch
1708
+ assert_equal "PATCH\n", curl.body_str
1709
+
1710
+ curl.http_put
1711
+ assert_equal "PUT\n", curl.body_str
917
1712
  end
918
1713
 
919
1714
  def test_easy_http_verbs_must_respond_to_str
920
- # issue http://github.com/taf2/curb/issues#issue/45
1715
+ # issue http://github.com/taf2/curb/issues/45
921
1716
  assert_nothing_raised do
922
- c = Curl::Easy.new ; c.url = 'http://example.com' ; c.http(:get)
1717
+ c = Curl::Easy.new ; c.url = TestServlet.url ; c.http(:get)
923
1718
  end
924
1719
 
925
1720
  assert_raise RuntimeError do
926
- c = Curl::Easy.new ; c.url = 'http://example.com' ; c.http(FooNoToS.new)
1721
+ c = Curl::Easy.new ; c.url = TestServlet.url ; c.http(FooNoToS.new)
927
1722
  end
928
1723
 
929
1724
  end
@@ -962,8 +1757,8 @@ class TestCurbCurlEasy < Test::Unit::TestCase
962
1757
  curl.headers['Accept'] = '*/*'
963
1758
  curl.headers['Authorization'] = 'Foo Bar Biz Baz'
964
1759
  curl.http_put(rd)
965
- assert_match /^PUT/, curl.body_str
966
- assert_match /hello$/, curl.body_str
1760
+ assert_match(/^PUT/, curl.body_str)
1761
+ assert_match(/hello$/, curl.body_str)
967
1762
  curl.header_str
968
1763
  curl.body_str
969
1764
  end
@@ -996,7 +1791,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
996
1791
  c = Curl::Easy.new($TEST_URL)
997
1792
  c.on_success {|x| raise "error" }
998
1793
  c.perform
999
- rescue => e
1794
+ rescue Curl::Err::AbortedByCallbackError => e
1000
1795
  assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s
1001
1796
  c.close
1002
1797
  end
@@ -1022,6 +1817,130 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1022
1817
  end
1023
1818
  end
1024
1819
 
1820
+ def test_close_during_http_post_argument_coercion_is_blocked
1821
+ curl = Curl::Easy.new(TestServlet.url)
1822
+ field = Object.new
1823
+ field.define_singleton_method(:to_s) do
1824
+ curl.close
1825
+ "a=b"
1826
+ end
1827
+
1828
+ error = assert_raise(RuntimeError) { curl.http_post(field) }
1829
+ assert_match(/Cannot close an active curl handle/, error.message)
1830
+
1831
+ assert_nothing_raised { curl.close }
1832
+ end
1833
+
1834
+ def test_close_during_url_coercion_is_blocked
1835
+ curl = Curl::Easy.new
1836
+ url = Object.new
1837
+ url.define_singleton_method(:to_str) do
1838
+ curl.close
1839
+ TestServlet.url
1840
+ end
1841
+
1842
+ curl.url = url
1843
+ error = assert_raise(RuntimeError) { curl.perform }
1844
+ assert_match(/Cannot close an active curl handle/, error.message)
1845
+
1846
+ assert_nothing_raised { curl.close }
1847
+ end
1848
+
1849
+ def test_close_in_on_progress_is_blocked
1850
+ curl = Curl::Easy.new(TestServlet.url)
1851
+ did_raise = false
1852
+
1853
+ curl.on_progress do |_dltotal, _dlnow, _ultotal, _ulnow|
1854
+ unless did_raise
1855
+ begin
1856
+ curl.close
1857
+ rescue RuntimeError
1858
+ did_raise = true
1859
+ end
1860
+ end
1861
+ true
1862
+ end
1863
+
1864
+ curl.perform
1865
+ assert did_raise
1866
+ end
1867
+
1868
+ def test_close_in_on_debug_is_blocked
1869
+ curl = Curl::Easy.new(TestServlet.url)
1870
+ did_raise = false
1871
+
1872
+ curl.on_debug do |_type, _data|
1873
+ unless did_raise
1874
+ begin
1875
+ curl.close
1876
+ rescue RuntimeError
1877
+ did_raise = true
1878
+ end
1879
+ end
1880
+ end
1881
+
1882
+ curl.perform
1883
+ assert did_raise
1884
+ end
1885
+
1886
+ def test_close_in_upload_read_is_blocked
1887
+ curl = Curl::Easy.new(TestServlet.url)
1888
+
1889
+ reader_class = Class.new do
1890
+ attr_reader :close_blocked
1891
+
1892
+ def initialize(curl)
1893
+ @curl = curl
1894
+ @done = false
1895
+ @close_blocked = false
1896
+ end
1897
+
1898
+ def read(_len)
1899
+ return nil if @done
1900
+
1901
+ @done = true
1902
+ begin
1903
+ @curl.close
1904
+ rescue RuntimeError
1905
+ @close_blocked = true
1906
+ end
1907
+
1908
+ 'hello'
1909
+ end
1910
+
1911
+ def seek(_offset, _whence = SEEK_SET)
1912
+ 0
1913
+ end
1914
+
1915
+ def stat
1916
+ Struct.new(:size).new(5)
1917
+ end
1918
+ end
1919
+
1920
+ reader = reader_class.new(curl)
1921
+ curl.http_put(reader)
1922
+
1923
+ assert reader.close_blocked
1924
+ assert_equal "PUT\nhello", curl.body_str
1925
+ end
1926
+
1927
+ def test_set_unsupported_options
1928
+ curl = Curl::Easy.new
1929
+ assert_raises TypeError do
1930
+ curl.set(99999, 1)
1931
+ end
1932
+ end
1933
+
1934
+ def test_poison
1935
+ res_a = Curl.get("#{TestServlet.url}?a_body")
1936
+ first_a_body = Digest::MD5.hexdigest(res_a.body)
1937
+ res_b = Curl.get("#{TestServlet.url}?b_body")
1938
+
1939
+ b_body = Digest::MD5.hexdigest(res_b.body)
1940
+ a_body = Digest::MD5.hexdigest(res_a.body)
1941
+ assert_not_equal a_body, b_body, "we expected #{a_body} to be #{first_a_body}"
1942
+ end
1943
+
1025
1944
  include TestServerMethods
1026
1945
 
1027
1946
  def setup