curb 1.3.5 → 1.3.7
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.
- checksums.yaml +4 -4
- data/README.md +90 -13
- data/Rakefile +8 -3
- data/doc.rb +48 -8
- data/ext/curb.c +33 -0
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +1471 -87
- data/ext/curb_easy.h +26 -0
- data/ext/curb_errors.c +2 -0
- data/ext/curb_errors.h +1 -0
- data/ext/curb_multi.c +257 -130
- data/ext/curb_multi.h +1 -0
- data/ext/extconf.rb +12 -0
- data/lib/curl/download.rb +160 -0
- data/lib/curl/easy.rb +113 -13
- data/lib/curl/multi.rb +172 -39
- data/lib/curl.rb +471 -11
- data/tests/bug_poison.rb +29 -0
- data/tests/helper.rb +178 -1
- data/tests/io_select_less_scheduler_probe.rb +105 -0
- data/tests/tc_curl_download.rb +86 -0
- data/tests/tc_curl_easy.rb +77 -0
- data/tests/tc_curl_easy_resolve.rb +2 -0
- data/tests/tc_curl_maxfilesize.rb +201 -1
- data/tests/tc_curl_multi.rb +258 -0
- data/tests/tc_curl_native_coverage.rb +1 -0
- data/tests/tc_curl_network_policy.rb +1475 -0
- data/tests/tc_curl_protocols.rb +351 -0
- data/tests/tc_fiber_scheduler.rb +373 -4
- data/tests/tc_ftp_options.rb +58 -0
- data/tests/tc_gc_compact.rb +53 -0
- data/tests/tc_test_server_methods.rb +15 -0
- metadata +9 -2
data/tests/helper.rb
CHANGED
|
@@ -238,6 +238,85 @@ module BugTestServerSetupTeardown
|
|
|
238
238
|
end
|
|
239
239
|
end
|
|
240
240
|
|
|
241
|
+
# Test::Unit creates a new test instance for every test method. Keep the
|
|
242
|
+
# common WEBrick fixture process-scoped so those instances can share it while
|
|
243
|
+
# still allowing an explicit stop_test_server call to exercise lifecycle code.
|
|
244
|
+
module CurbTestServerRegistry
|
|
245
|
+
Entry = Struct.new(:key, :port, :server, :thread, :lock_file, :pid)
|
|
246
|
+
|
|
247
|
+
@mutex = Mutex.new
|
|
248
|
+
@entries = {}
|
|
249
|
+
|
|
250
|
+
class << self
|
|
251
|
+
def acquire(port, servlet)
|
|
252
|
+
key = [port, servlet]
|
|
253
|
+
|
|
254
|
+
@mutex.synchronize do
|
|
255
|
+
entry = @entries[key]
|
|
256
|
+
return entry if active?(entry)
|
|
257
|
+
|
|
258
|
+
@entries.delete(key)
|
|
259
|
+
entry = yield
|
|
260
|
+
@entries[key] = entry if entry
|
|
261
|
+
entry
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def stop(entry, timeout)
|
|
266
|
+
return false unless remove(entry)
|
|
267
|
+
|
|
268
|
+
shutdown_entry(entry, timeout)
|
|
269
|
+
true
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def register_shutdown_hook(entry)
|
|
273
|
+
# Test::Unit can run a directly executed test file from its own at_exit
|
|
274
|
+
# callback. Register after startup so this hook runs after that callback.
|
|
275
|
+
at_exit { stop(entry, 5) }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
def active?(entry)
|
|
281
|
+
entry && entry.server && entry.thread && entry.thread.alive? && entry.server.status == :Running
|
|
282
|
+
rescue StandardError
|
|
283
|
+
false
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def remove(entry)
|
|
287
|
+
@mutex.synchronize do
|
|
288
|
+
return false unless @entries[entry.key].equal?(entry)
|
|
289
|
+
|
|
290
|
+
@entries.delete(entry.key)
|
|
291
|
+
true
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def shutdown_entry(entry, timeout)
|
|
296
|
+
return unless Process.pid == entry.pid
|
|
297
|
+
|
|
298
|
+
begin
|
|
299
|
+
entry.server.shutdown if entry.server
|
|
300
|
+
rescue StandardError
|
|
301
|
+
nil
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
thread = entry.thread
|
|
305
|
+
if thread
|
|
306
|
+
thread.join(timeout)
|
|
307
|
+
if thread.alive?
|
|
308
|
+
thread.kill
|
|
309
|
+
thread.join(timeout)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
File.unlink(entry.lock_file) if entry.lock_file && File.exist?(entry.lock_file)
|
|
314
|
+
rescue Errno::ENOENT
|
|
315
|
+
nil
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
241
320
|
module TestServerMethods
|
|
242
321
|
def server_responding?(port)
|
|
243
322
|
socket = TCPSocket.new('127.0.0.1', port)
|
|
@@ -267,6 +346,22 @@ module TestServerMethods
|
|
|
267
346
|
end
|
|
268
347
|
end
|
|
269
348
|
|
|
349
|
+
# Waiting on WEBrick's state avoids opening a probe connection for every
|
|
350
|
+
# startup. On Windows those short-lived connections make server teardown
|
|
351
|
+
# disproportionately expensive.
|
|
352
|
+
def wait_for_server_started(server, port, thread: nil)
|
|
353
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + server_startup_timeout
|
|
354
|
+
|
|
355
|
+
loop do
|
|
356
|
+
return true if server.status == :Running
|
|
357
|
+
return false if thread && !thread.alive?
|
|
358
|
+
|
|
359
|
+
raise "Failed to startup test server on port #{port}" if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
360
|
+
|
|
361
|
+
sleep 0.01
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
270
365
|
def server_shutdown_timeout
|
|
271
366
|
[server_startup_timeout, 0.25].max
|
|
272
367
|
end
|
|
@@ -337,6 +432,13 @@ module TestServerMethods
|
|
|
337
432
|
end
|
|
338
433
|
|
|
339
434
|
def stop_test_server
|
|
435
|
+
shared_entry = instance_variable_defined?(:@__shared_test_server_entry) ? @__shared_test_server_entry : nil
|
|
436
|
+
if shared_entry
|
|
437
|
+
CurbTestServerRegistry.stop(shared_entry, server_shutdown_timeout)
|
|
438
|
+
release_shared_test_server
|
|
439
|
+
return
|
|
440
|
+
end
|
|
441
|
+
|
|
340
442
|
server = instance_variable_defined?(:@server) ? @server : nil
|
|
341
443
|
pid = instance_variable_defined?(:@__pid) ? @__pid : nil
|
|
342
444
|
return unless server || pid
|
|
@@ -382,11 +484,28 @@ module TestServerMethods
|
|
|
382
484
|
def teardown
|
|
383
485
|
super
|
|
384
486
|
ensure
|
|
385
|
-
|
|
487
|
+
if instance_variable_defined?(:@__shared_test_server_entry) && @__shared_test_server_entry
|
|
488
|
+
release_shared_test_server
|
|
489
|
+
else
|
|
490
|
+
stop_test_server
|
|
491
|
+
end
|
|
386
492
|
end
|
|
387
493
|
|
|
388
494
|
def server_setup(port=9129,servlet=TestServlet)
|
|
389
495
|
@__port = port
|
|
496
|
+
|
|
497
|
+
unless TEST_SINGLE_THREADED
|
|
498
|
+
entry = CurbTestServerRegistry.acquire(port, servlet) do
|
|
499
|
+
start_shared_test_server(port, servlet)
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
if entry
|
|
503
|
+
attach_shared_test_server(entry)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
return
|
|
507
|
+
end
|
|
508
|
+
|
|
390
509
|
@server = nil unless instance_variable_defined?(:@server)
|
|
391
510
|
@__pid = nil unless instance_variable_defined?(:@__pid)
|
|
392
511
|
@test_thread = nil unless instance_variable_defined?(:@test_thread)
|
|
@@ -474,6 +593,64 @@ module TestServerMethods
|
|
|
474
593
|
end
|
|
475
594
|
rescue Errno::EADDRINUSE
|
|
476
595
|
end
|
|
596
|
+
|
|
597
|
+
def attach_shared_test_server(entry)
|
|
598
|
+
@server = entry.server
|
|
599
|
+
@test_thread = entry.thread
|
|
600
|
+
@__pid = nil
|
|
601
|
+
@__shared_test_server_entry = entry
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def release_shared_test_server
|
|
605
|
+
@server = nil
|
|
606
|
+
@test_thread = nil
|
|
607
|
+
@__pid = nil
|
|
608
|
+
@__shared_test_server_entry = nil
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def start_shared_test_server(port, servlet)
|
|
612
|
+
clear_stale_server_lock(port)
|
|
613
|
+
|
|
614
|
+
if File.exist?(locked_file)
|
|
615
|
+
begin
|
|
616
|
+
wait_for_server_ready(port)
|
|
617
|
+
return nil
|
|
618
|
+
rescue RuntimeError
|
|
619
|
+
clear_stale_server_lock(port)
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
write_server_lock
|
|
624
|
+
server = WEBrick::HTTPServer.new(:Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__)), :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
625
|
+
|
|
626
|
+
server.mount(servlet.path, servlet)
|
|
627
|
+
server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext'))
|
|
628
|
+
|
|
629
|
+
thread = Thread.new(server) { |srv| srv.start }
|
|
630
|
+
unless wait_for_server_started(server, port, thread: thread)
|
|
631
|
+
server.shutdown
|
|
632
|
+
thread.join(server_shutdown_timeout)
|
|
633
|
+
raise "Failed to startup test server on port #{port}"
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
entry = CurbTestServerRegistry::Entry.new([port, servlet], port, server, thread, locked_file, Process.pid)
|
|
637
|
+
CurbTestServerRegistry.register_shutdown_hook(entry)
|
|
638
|
+
entry
|
|
639
|
+
rescue Errno::EADDRINUSE
|
|
640
|
+
File.unlink(locked_file) if File.exist?(locked_file)
|
|
641
|
+
return nil if server_responding?(port)
|
|
642
|
+
|
|
643
|
+
raise
|
|
644
|
+
rescue StandardError
|
|
645
|
+
begin
|
|
646
|
+
server.shutdown if defined?(server) && server
|
|
647
|
+
thread.join(server_shutdown_timeout) if defined?(thread) && thread
|
|
648
|
+
rescue StandardError
|
|
649
|
+
nil
|
|
650
|
+
end
|
|
651
|
+
File.unlink(locked_file) if File.exist?(locked_file)
|
|
652
|
+
raise
|
|
653
|
+
end
|
|
477
654
|
end
|
|
478
655
|
|
|
479
656
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Reproduces the FIBER_TODO.md [Critical] finding: a Fiber scheduler that
|
|
2
|
+
# implements io_wait/kernel_sleep/block/unblock but NOT the optional io_select
|
|
3
|
+
# hook. rb_fiber_scheduler_io_select returns Qundef (without waiting) for such
|
|
4
|
+
# schedulers; if the multi-fd wait path of Curl::Multi#_socket_perform treats
|
|
5
|
+
# that as a completed wait, the drive loop spins at 100% CPU with no interrupt
|
|
6
|
+
# checkpoint and this script never exits — it cannot even service SIGTERM.
|
|
7
|
+
#
|
|
8
|
+
# Run by tc_fiber_scheduler.rb in a subprocess guarded by a SIGKILL fuse.
|
|
9
|
+
# Usage: ruby io_select_less_scheduler_probe.rb <port>
|
|
10
|
+
# <port> must serve HTTP GET /test (see BugTestServerSetupTeardown).
|
|
11
|
+
|
|
12
|
+
require 'fiber'
|
|
13
|
+
require 'curb'
|
|
14
|
+
|
|
15
|
+
port = Integer(ARGV[0])
|
|
16
|
+
url = "http://127.0.0.1:#{port}/test"
|
|
17
|
+
|
|
18
|
+
class NoIoSelectScheduler
|
|
19
|
+
def fiber(&block)
|
|
20
|
+
Fiber.new(blocking: false, &block)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def io_wait(io, events, timeout = nil)
|
|
24
|
+
readers = (events & IO::READABLE) != 0 ? [io] : nil
|
|
25
|
+
writers = (events & IO::WRITABLE) != 0 ? [io] : nil
|
|
26
|
+
deadline = monotonic_now + timeout if timeout
|
|
27
|
+
|
|
28
|
+
loop do
|
|
29
|
+
readable, writable = IO.select(readers, writers, nil, 0)
|
|
30
|
+
ready = 0
|
|
31
|
+
ready |= IO::READABLE if readable && !readable.empty?
|
|
32
|
+
ready |= IO::WRITABLE if writable && !writable.empty?
|
|
33
|
+
return ready unless ready.zero?
|
|
34
|
+
return false if deadline && monotonic_now >= deadline
|
|
35
|
+
|
|
36
|
+
Fiber.yield
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def kernel_sleep(duration = nil)
|
|
41
|
+
deadline = monotonic_now + (duration || 0)
|
|
42
|
+
Fiber.yield while monotonic_now < deadline
|
|
43
|
+
duration
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def block(_blocker, timeout = nil)
|
|
47
|
+
kernel_sleep(timeout)
|
|
48
|
+
false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def unblock(*); end
|
|
52
|
+
|
|
53
|
+
def close; end
|
|
54
|
+
|
|
55
|
+
def fiber_interrupt(*); end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def monotonic_now
|
|
60
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
results = []
|
|
65
|
+
ticks = 0
|
|
66
|
+
done = false
|
|
67
|
+
|
|
68
|
+
Fiber.set_scheduler(NoIoSelectScheduler.new)
|
|
69
|
+
fiber = Fiber.schedule do
|
|
70
|
+
multi = Curl::Multi.new
|
|
71
|
+
begin
|
|
72
|
+
# Two easies so the drive loop tracks more than one socket and takes the
|
|
73
|
+
# multi-fd (io_select) wait branch.
|
|
74
|
+
2.times do
|
|
75
|
+
easy = Curl::Easy.new(url)
|
|
76
|
+
easy.on_complete { results << easy.response_code }
|
|
77
|
+
multi.add(easy)
|
|
78
|
+
end
|
|
79
|
+
multi.send(:_socket_perform)
|
|
80
|
+
ensure
|
|
81
|
+
multi.close
|
|
82
|
+
done = true
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
ticker = Fiber.schedule do
|
|
86
|
+
until done
|
|
87
|
+
sleep 0.005
|
|
88
|
+
ticks += 1 unless done
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
while fiber.alive? || ticker.alive?
|
|
93
|
+
fiber.resume if fiber.alive?
|
|
94
|
+
ticker.resume if ticker.alive?
|
|
95
|
+
sleep 0.001
|
|
96
|
+
end
|
|
97
|
+
Fiber.set_scheduler(nil)
|
|
98
|
+
|
|
99
|
+
if results == [200, 200]
|
|
100
|
+
puts "OK ticks=#{ticks}"
|
|
101
|
+
exit 0
|
|
102
|
+
else
|
|
103
|
+
puts "FAILED results=#{results.inspect}"
|
|
104
|
+
exit 1
|
|
105
|
+
end
|
data/tests/tc_curl_download.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
+
require 'tmpdir'
|
|
2
3
|
|
|
3
4
|
class TestCurbCurlDownload < Test::Unit::TestCase
|
|
4
5
|
include TestServerMethods
|
|
@@ -18,6 +19,91 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
18
19
|
File.unlink(dl_path) if File.exist?(dl_path)
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
def test_download_default_path_does_not_overwrite_existing_file
|
|
23
|
+
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
24
|
+
|
|
25
|
+
Dir.mktmpdir('curb-download-') do |dir|
|
|
26
|
+
Dir.chdir(dir) do
|
|
27
|
+
File.write('curb_easy.c', 'sentinel')
|
|
28
|
+
|
|
29
|
+
assert_raise(Curl::DownloadTargetExistsError) do
|
|
30
|
+
Curl::Easy.download(dl_url)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
assert_equal 'sentinel', File.read('curb_easy.c')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_download_default_path_strips_query_from_url_basename
|
|
39
|
+
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c?cache=1"
|
|
40
|
+
source_path = File.expand_path(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c'))
|
|
41
|
+
|
|
42
|
+
Dir.mktmpdir('curb-download-') do |dir|
|
|
43
|
+
Dir.chdir(dir) do
|
|
44
|
+
Curl::Easy.download(dl_url)
|
|
45
|
+
|
|
46
|
+
assert File.exist?('curb_easy.c')
|
|
47
|
+
assert !File.exist?('curb_easy.c?cache=1')
|
|
48
|
+
assert_equal File.read(source_path), File.read('curb_easy.c')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_download_safe_directory_rejects_unsafe_names
|
|
54
|
+
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
55
|
+
|
|
56
|
+
Dir.mktmpdir('curb-download-') do |dir|
|
|
57
|
+
['../escape.c', '.env', 'nested/file', '/tmp/escape.c', "bad\0name"].each do |name|
|
|
58
|
+
assert_raise(ArgumentError, "expected #{name.inspect} to be rejected") do
|
|
59
|
+
Curl::Easy.download(dl_url, name, :download_dir => dir)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_download_safe_directory_rejects_dotfile_from_url
|
|
66
|
+
dl_url = "http://127.0.0.1:9129/ext/.env"
|
|
67
|
+
|
|
68
|
+
Dir.mktmpdir('curb-download-') do |dir|
|
|
69
|
+
assert_raise(ArgumentError) do
|
|
70
|
+
Curl::Easy.download(dl_url, :download_dir => dir)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
assert !File.exist?(File.join(dir, '.env'))
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_download_overwrite_true_replaces_existing_file
|
|
78
|
+
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
79
|
+
|
|
80
|
+
Dir.mktmpdir('curb-download-') do |dir|
|
|
81
|
+
dl_path = File.join(dir, 'curb_easy.c')
|
|
82
|
+
File.write(dl_path, 'sentinel')
|
|
83
|
+
|
|
84
|
+
Curl::Easy.download(dl_url, dl_path, :overwrite => true)
|
|
85
|
+
|
|
86
|
+
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_download_callback_abort_does_not_replace_existing_file
|
|
91
|
+
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
92
|
+
|
|
93
|
+
Dir.mktmpdir('curb-download-') do |dir|
|
|
94
|
+
dl_path = File.join(dir, 'curb_easy.c')
|
|
95
|
+
File.write(dl_path, 'sentinel')
|
|
96
|
+
|
|
97
|
+
assert_raise(Curl::Err::WriteError, Curl::Err::RecvError) do
|
|
98
|
+
Curl::Easy.download(dl_url, dl_path, :overwrite => true) do |curl|
|
|
99
|
+
curl.on_body { |_data| 0 }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
assert_equal 'sentinel', File.read(dl_path)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
21
107
|
def test_download_url_to_file_via_file_io
|
|
22
108
|
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
23
109
|
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
data/tests/tc_curl_easy.rb
CHANGED
|
@@ -18,6 +18,13 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
18
18
|
assert_equal 200, easy.code
|
|
19
19
|
end
|
|
20
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
|
+
|
|
21
28
|
def test_curlopt_resolve
|
|
22
29
|
require 'resolv'
|
|
23
30
|
uri = URI.parse(TestServlet.url)
|
|
@@ -39,6 +46,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
39
46
|
|
|
40
47
|
http = Curl::Easy.new(url)
|
|
41
48
|
http.dns_cache_timeout = 0
|
|
49
|
+
http.proxy_url = ""
|
|
42
50
|
http.resolve = [mapping]
|
|
43
51
|
http.get
|
|
44
52
|
assert_match(/GET/, http.body)
|
|
@@ -1629,6 +1637,36 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1629
1637
|
easy.http_get
|
|
1630
1638
|
end
|
|
1631
1639
|
|
|
1640
|
+
def test_easy_reset_clears_network_policy_allowlists
|
|
1641
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
1642
|
+
easy.allowed_hosts = ['127.0.0.1']
|
|
1643
|
+
easy.network_policy = :public
|
|
1644
|
+
easy.allowed_cidrs = ['1.1.1.0/24']
|
|
1645
|
+
|
|
1646
|
+
settings = easy.reset
|
|
1647
|
+
|
|
1648
|
+
assert settings.key?(:allowed_hosts)
|
|
1649
|
+
assert settings.key?(:allowed_cidrs)
|
|
1650
|
+
assert_nil easy.allowed_hosts
|
|
1651
|
+
assert_nil easy.allowed_cidrs
|
|
1652
|
+
assert_equal :none, easy.network_policy
|
|
1653
|
+
rescue NotImplementedError
|
|
1654
|
+
omit('network_policy=:public requires CURLOPT_OPENSOCKETFUNCTION support')
|
|
1655
|
+
end
|
|
1656
|
+
|
|
1657
|
+
def test_frozen_easy_close_and_reset_skip_safety_override_cleanup
|
|
1658
|
+
closed_easy = Curl::Easy.new
|
|
1659
|
+
closed_easy.freeze
|
|
1660
|
+
|
|
1661
|
+
assert_nothing_raised { closed_easy.close }
|
|
1662
|
+
|
|
1663
|
+
reset_easy = Curl::Easy.new
|
|
1664
|
+
reset_easy.safe_http!
|
|
1665
|
+
reset_easy.freeze
|
|
1666
|
+
|
|
1667
|
+
assert_nothing_raised { reset_easy.reset }
|
|
1668
|
+
end
|
|
1669
|
+
|
|
1632
1670
|
def test_last_result_initialization
|
|
1633
1671
|
# Test for issue #463 - ensure last_result is properly initialized to 0
|
|
1634
1672
|
easy = Curl::Easy.new
|
|
@@ -1780,6 +1818,35 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1780
1818
|
end
|
|
1781
1819
|
end
|
|
1782
1820
|
|
|
1821
|
+
def test_close_during_http_post_argument_coercion_is_blocked
|
|
1822
|
+
curl = Curl::Easy.new(TestServlet.url)
|
|
1823
|
+
field = Object.new
|
|
1824
|
+
field.define_singleton_method(:to_s) do
|
|
1825
|
+
curl.close
|
|
1826
|
+
"a=b"
|
|
1827
|
+
end
|
|
1828
|
+
|
|
1829
|
+
error = assert_raise(RuntimeError) { curl.http_post(field) }
|
|
1830
|
+
assert_match(/Cannot close an active curl handle/, error.message)
|
|
1831
|
+
|
|
1832
|
+
assert_nothing_raised { curl.close }
|
|
1833
|
+
end
|
|
1834
|
+
|
|
1835
|
+
def test_close_during_url_coercion_is_blocked
|
|
1836
|
+
curl = Curl::Easy.new
|
|
1837
|
+
url = Object.new
|
|
1838
|
+
url.define_singleton_method(:to_str) do
|
|
1839
|
+
curl.close
|
|
1840
|
+
TestServlet.url
|
|
1841
|
+
end
|
|
1842
|
+
|
|
1843
|
+
curl.url = url
|
|
1844
|
+
error = assert_raise(RuntimeError) { curl.perform }
|
|
1845
|
+
assert_match(/Cannot close an active curl handle/, error.message)
|
|
1846
|
+
|
|
1847
|
+
assert_nothing_raised { curl.close }
|
|
1848
|
+
end
|
|
1849
|
+
|
|
1783
1850
|
def test_close_in_on_progress_is_blocked
|
|
1784
1851
|
curl = Curl::Easy.new(TestServlet.url)
|
|
1785
1852
|
did_raise = false
|
|
@@ -1865,6 +1932,16 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1865
1932
|
end
|
|
1866
1933
|
end
|
|
1867
1934
|
|
|
1935
|
+
def test_poison
|
|
1936
|
+
res_a = Curl.get("#{TestServlet.url}?a_body")
|
|
1937
|
+
first_a_body = Digest::MD5.hexdigest(res_a.body)
|
|
1938
|
+
res_b = Curl.get("#{TestServlet.url}?b_body")
|
|
1939
|
+
|
|
1940
|
+
b_body = Digest::MD5.hexdigest(res_b.body)
|
|
1941
|
+
a_body = Digest::MD5.hexdigest(res_a.body)
|
|
1942
|
+
assert_not_equal a_body, b_body, "we expected #{a_body} to be #{first_a_body}"
|
|
1943
|
+
end
|
|
1944
|
+
|
|
1868
1945
|
include TestServerMethods
|
|
1869
1946
|
|
|
1870
1947
|
def setup
|
|
@@ -22,6 +22,7 @@ class TestCurbCurlEasyResolve < Test::Unit::TestCase
|
|
|
22
22
|
mapping = "#{host}:#{TestServlet.port}:127.0.0.1"
|
|
23
23
|
|
|
24
24
|
@easy.url = "http://#{host}:#{TestServlet.port}#{TestServlet.path}"
|
|
25
|
+
@easy.proxy_url = ""
|
|
25
26
|
@easy.dns_cache_timeout = 0
|
|
26
27
|
@easy.set(:resolve, [mapping])
|
|
27
28
|
|
|
@@ -39,6 +40,7 @@ class TestCurbCurlEasyResolve < Test::Unit::TestCase
|
|
|
39
40
|
entry.define_singleton_method(:to_s) { mapping }
|
|
40
41
|
|
|
41
42
|
@easy.url = "http://#{host}:#{TestServlet.port}#{TestServlet.path}"
|
|
43
|
+
@easy.proxy_url = ""
|
|
42
44
|
@easy.dns_cache_timeout = 0
|
|
43
45
|
@easy.resolve = [entry]
|
|
44
46
|
|