curb 1.4.0 → 1.4.1
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/ChangeLog.md +432 -0
- data/README.md +25 -5
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +274 -161
- data/ext/curb_easy.h +5 -0
- data/ext/curb_macros.h +16 -0
- data/ext/curb_multi.c +51 -8
- data/ext/curb_multi.h +4 -0
- data/ext/curb_postfield.c +103 -114
- data/ext/curb_postfield.h +10 -1
- data/lib/curl/easy.rb +10 -3
- data/lib/curl/multi.rb +1 -1
- data/lib/curl.rb +9 -7
- data/tests/tc_curl_easy.rb +116 -0
- data/tests/tc_curl_easy_cookielist.rb +10 -8
- data/tests/tc_curl_maxfilesize.rb +109 -5
- data/tests/tc_curl_native_coverage.rb +57 -0
- data/tests/tc_curl_network_policy.rb +108 -1
- data/tests/tc_curl_postfield.rb +137 -0
- data/tests/tc_curl_protocols.rb +19 -7
- data/tests/tc_curl_transfer_guard.rb +112 -0
- data/tests/tc_ractor.rb +48 -1
- metadata +5 -2
data/tests/tc_curl_protocols.rb
CHANGED
|
@@ -104,11 +104,10 @@ class TestCurbCurlProtocols < Test::Unit::TestCase
|
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
assert_match(/GET/, easy.body_str)
|
|
107
|
+
def test_safe_get_blocks_local_http_destination
|
|
108
|
+
assert_raise(Curl::Err::UnsafeDestinationError) do
|
|
109
|
+
Curl.safe_get(TestServlet.url)
|
|
110
|
+
end
|
|
112
111
|
end
|
|
113
112
|
|
|
114
113
|
def test_safe_bang_denies_file_protocol_for_easy_perform
|
|
@@ -159,8 +158,8 @@ class TestCurbCurlProtocols < Test::Unit::TestCase
|
|
|
159
158
|
end
|
|
160
159
|
end
|
|
161
160
|
|
|
162
|
-
def
|
|
163
|
-
Curl.safe!
|
|
161
|
+
def test_safe_bang_explicit_none_allows_trusted_http_destination
|
|
162
|
+
Curl.safe! { |config| config.network_policy = :none }
|
|
164
163
|
easy = Curl.get(TestServlet.url)
|
|
165
164
|
|
|
166
165
|
assert_equal 200, easy.response_code
|
|
@@ -184,6 +183,7 @@ class TestCurbCurlProtocols < Test::Unit::TestCase
|
|
|
184
183
|
with_file_redirect_server do |url|
|
|
185
184
|
easy = Curl::Easy.new(url)
|
|
186
185
|
easy.follow_location = true
|
|
186
|
+
stub_public_network_policy_for_local_fixture(easy)
|
|
187
187
|
easy.safe_http!
|
|
188
188
|
|
|
189
189
|
assert_raises Curl::Err::UnsupportedProtocolError do
|
|
@@ -198,6 +198,7 @@ class TestCurbCurlProtocols < Test::Unit::TestCase
|
|
|
198
198
|
|
|
199
199
|
easy = Curl::Easy.new(url)
|
|
200
200
|
easy.follow_location = true
|
|
201
|
+
stub_public_network_policy_for_local_fixture(easy)
|
|
201
202
|
|
|
202
203
|
assert_raises Curl::Err::UnsupportedProtocolError do
|
|
203
204
|
easy.perform
|
|
@@ -211,6 +212,7 @@ class TestCurbCurlProtocols < Test::Unit::TestCase
|
|
|
211
212
|
easy.follow_location = true
|
|
212
213
|
easy.ssl_verify_peer = false
|
|
213
214
|
easy.ssl_verify_host = false
|
|
215
|
+
stub_public_network_policy_for_local_fixture(easy)
|
|
214
216
|
easy.safe_http!
|
|
215
217
|
|
|
216
218
|
easy.perform
|
|
@@ -226,6 +228,7 @@ class TestCurbCurlProtocols < Test::Unit::TestCase
|
|
|
226
228
|
easy.follow_location = true
|
|
227
229
|
easy.ssl_verify_peer = false
|
|
228
230
|
easy.ssl_verify_host = false
|
|
231
|
+
stub_public_network_policy_for_local_fixture(easy)
|
|
229
232
|
easy.safe_http!
|
|
230
233
|
|
|
231
234
|
easy.perform
|
|
@@ -240,6 +243,7 @@ class TestCurbCurlProtocols < Test::Unit::TestCase
|
|
|
240
243
|
easy = Curl::Easy.new(url)
|
|
241
244
|
easy.follow_location = true
|
|
242
245
|
easy.max_redirects = 2
|
|
246
|
+
stub_public_network_policy_for_local_fixture(easy)
|
|
243
247
|
easy.safe_http!
|
|
244
248
|
|
|
245
249
|
assert_raises Curl::Err::TooManyRedirectsError do
|
|
@@ -252,6 +256,14 @@ class TestCurbCurlProtocols < Test::Unit::TestCase
|
|
|
252
256
|
|
|
253
257
|
private
|
|
254
258
|
|
|
259
|
+
# Isolate protocol/redirect behavior using local fixtures. Destination blocking
|
|
260
|
+
# is exercised without this stub in tc_curl_network_policy.rb.
|
|
261
|
+
def stub_public_network_policy_for_local_fixture(easy)
|
|
262
|
+
def easy.network_policy=(policy)
|
|
263
|
+
raise "unexpected policy: #{policy}" unless policy == :public
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
255
267
|
def with_file_redirect_server
|
|
256
268
|
port_socket = TCPServer.new('127.0.0.1', 0)
|
|
257
269
|
port = port_socket.addr[1]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
+
|
|
3
|
+
class TestCurbTransferGuard < Test::Unit::TestCase
|
|
4
|
+
class InterruptedTransfer < StandardError; end
|
|
5
|
+
|
|
6
|
+
[:perform, :_socket_perform].each do |drive|
|
|
7
|
+
define_method("test_#{drive}_rejects_cross_thread_handle_access") do
|
|
8
|
+
with_waiting_transfer(drive) do |easy, multi, worker, release|
|
|
9
|
+
operations = {
|
|
10
|
+
close: -> { easy.close },
|
|
11
|
+
reset: -> { easy.reset },
|
|
12
|
+
initialize: -> { easy.__send__(:initialize) },
|
|
13
|
+
clone: -> { easy.clone },
|
|
14
|
+
dup: -> { easy.dup },
|
|
15
|
+
setopt: -> { easy.setopt(Curl::CURLOPT_CUSTOMREQUEST, 'DELETE') },
|
|
16
|
+
post_body: -> { easy.post_body = 'replacement' },
|
|
17
|
+
url: -> { easy.url = 'http://127.0.0.1/' },
|
|
18
|
+
timeout: -> { easy.timeout = 1 },
|
|
19
|
+
multi_assignment: -> { easy.multi = nil },
|
|
20
|
+
multi_remove: -> { multi.remove(easy) },
|
|
21
|
+
multi_close: -> { multi.close },
|
|
22
|
+
multi_initialize: -> { multi.__send__(:initialize) },
|
|
23
|
+
multi_add: -> { multi.add(Curl::Easy.new('http://127.0.0.1/')) },
|
|
24
|
+
multi_option: -> { multi.max_connects = 1 }
|
|
25
|
+
}
|
|
26
|
+
operations.each do |name, operation|
|
|
27
|
+
error = assert_raise(RuntimeError, name.to_s, &operation)
|
|
28
|
+
assert_match(/another thread|active Curl::Multi/, error.message, name.to_s)
|
|
29
|
+
end
|
|
30
|
+
assert_equal [easy], multi.requests.values
|
|
31
|
+
|
|
32
|
+
release << true
|
|
33
|
+
assert worker.join(5), 'perform did not finish'
|
|
34
|
+
worker.value
|
|
35
|
+
assert_equal 'ok', easy.body_str
|
|
36
|
+
assert_nothing_raised { easy.clone.close }
|
|
37
|
+
assert_nothing_raised { easy.reset }
|
|
38
|
+
assert_nothing_raised { easy.close }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
define_method("test_#{drive}_releases_guard_after_interruption") do
|
|
43
|
+
with_waiting_transfer(drive) do |easy, multi, worker, release|
|
|
44
|
+
worker.raise(InterruptedTransfer, 'stop')
|
|
45
|
+
assert worker.join(5), 'interrupted perform did not finish'
|
|
46
|
+
assert_kind_of InterruptedTransfer, worker[:transfer_error]
|
|
47
|
+
assert_nothing_raised { multi.remove(easy) }
|
|
48
|
+
assert_nothing_raised { easy.reset }
|
|
49
|
+
assert_nothing_raised { easy.close }
|
|
50
|
+
release << true
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def with_waiting_transfer(drive)
|
|
56
|
+
if drive == :_socket_perform && !Curl::Multi.private_method_defined?(drive)
|
|
57
|
+
omit('socket-action perform path is not available')
|
|
58
|
+
end
|
|
59
|
+
server = TCPServer.new('127.0.0.1', 0)
|
|
60
|
+
started = Queue.new
|
|
61
|
+
release = Queue.new
|
|
62
|
+
server_thread = Thread.new do
|
|
63
|
+
socket = server.accept
|
|
64
|
+
begin
|
|
65
|
+
request = +''
|
|
66
|
+
request << socket.readpartial(4096) until request.include?("\r\n\r\n")
|
|
67
|
+
started << true
|
|
68
|
+
release.pop
|
|
69
|
+
socket.write("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok")
|
|
70
|
+
rescue Errno::EPIPE, Errno::ECONNRESET
|
|
71
|
+
# The interruption test intentionally closes the connection early.
|
|
72
|
+
ensure
|
|
73
|
+
socket.close
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
easy = Curl::Easy.new("http://127.0.0.1:#{server.addr[1]}/")
|
|
77
|
+
multi = Curl::Multi.new
|
|
78
|
+
# Exercise reuse after autoclose as well as the active-transfer checks.
|
|
79
|
+
previous_autoclose = Curl::Multi.autoclose
|
|
80
|
+
begin
|
|
81
|
+
Curl::Multi.autoclose = true
|
|
82
|
+
multi.add(Curl::Easy.new($TEST_URL))
|
|
83
|
+
multi.perform
|
|
84
|
+
ensure
|
|
85
|
+
Curl::Multi.autoclose = previous_autoclose
|
|
86
|
+
end
|
|
87
|
+
multi.add(easy)
|
|
88
|
+
worker = Thread.new do
|
|
89
|
+
Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception=)
|
|
90
|
+
begin
|
|
91
|
+
multi.__send__(drive)
|
|
92
|
+
rescue InterruptedTransfer => error
|
|
93
|
+
# Keep join non-raising so the test can inspect the exception explicitly.
|
|
94
|
+
Thread.current[:transfer_error] = error
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
Timeout.timeout(5) { started.pop }
|
|
98
|
+
yield easy, multi, worker, release
|
|
99
|
+
ensure
|
|
100
|
+
release << true if release
|
|
101
|
+
if worker && worker.alive?
|
|
102
|
+
worker.kill
|
|
103
|
+
worker.join(5)
|
|
104
|
+
end
|
|
105
|
+
multi.close if multi
|
|
106
|
+
server.close if server && !server.closed?
|
|
107
|
+
if server_thread
|
|
108
|
+
server_thread.join(5)
|
|
109
|
+
server_thread.kill if server_thread.alive?
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/tests/tc_ractor.rb
CHANGED
|
@@ -30,7 +30,10 @@ class TestCurbRactor < Test::Unit::TestCase
|
|
|
30
30
|
|
|
31
31
|
workers = RACTOR_WORKERS.times.map do |worker_id|
|
|
32
32
|
Ractor.new(url, worker_id, REQUESTS_PER_WORKER) do |target, id, request_count|
|
|
33
|
-
Curl.safe!
|
|
33
|
+
Curl.safe! do |config|
|
|
34
|
+
config.protocols = [:http]
|
|
35
|
+
config.network_policy = :none # This concurrency fixture uses localhost.
|
|
36
|
+
end
|
|
34
37
|
Curl::Multi.default_timeout = 25 + id
|
|
35
38
|
Curl::Multi.autoclose = true
|
|
36
39
|
|
|
@@ -76,6 +79,50 @@ class TestCurbRactor < Test::Unit::TestCase
|
|
|
76
79
|
Curl::Multi.autoclose = original_autoclose if defined?(original_autoclose)
|
|
77
80
|
end
|
|
78
81
|
|
|
82
|
+
def test_multipart_snapshots_are_private_to_parallel_ractors
|
|
83
|
+
omit_unless_curb_ractor_safe
|
|
84
|
+
worker_count = Integer(ENV.fetch('CURB_MULTIPART_RACTORS', '4'))
|
|
85
|
+
request_count = Integer(ENV.fetch('CURB_MULTIPART_REQUESTS', '5'))
|
|
86
|
+
workers = worker_count.times.map do |worker_id|
|
|
87
|
+
Ractor.new(TestServlet.url, worker_id, request_count) do |url, id, count|
|
|
88
|
+
easy = Curl::Easy.new(url)
|
|
89
|
+
easy.multipart_form_post = true
|
|
90
|
+
count.times do |iteration|
|
|
91
|
+
name = +"field_#{id}_#{iteration}"
|
|
92
|
+
payload = "payload_#{id}_#{iteration}\0" + ('x' * 1024)
|
|
93
|
+
expected_name = name.dup
|
|
94
|
+
expected_payload = payload.dup
|
|
95
|
+
direct = Curl::PostField.content(name, payload, 'application/octet-stream')
|
|
96
|
+
file = Curl::PostField.file('upload', 'payload.bin') { payload }
|
|
97
|
+
later = Curl::PostField.content('later') do
|
|
98
|
+
name.replace('changed' * 1000)
|
|
99
|
+
payload.replace('changed' * 1000)
|
|
100
|
+
# Ruby #21537: explicit GC in a Windows Ractor can deadlock
|
|
101
|
+
# the VM barrier. Keep parallel mutation coverage on Windows;
|
|
102
|
+
# single-Ractor compaction is covered by the PostField tests.
|
|
103
|
+
if iteration % 10 == 0 && !WINDOWS
|
|
104
|
+
GC.start
|
|
105
|
+
GC.compact if GC.respond_to?(:compact)
|
|
106
|
+
end
|
|
107
|
+
'done'
|
|
108
|
+
end
|
|
109
|
+
easy.http_post(direct, file, later)
|
|
110
|
+
body = easy.body_str
|
|
111
|
+
unless easy.response_code == 200 &&
|
|
112
|
+
body.include?("name=\"#{expected_name}\"") &&
|
|
113
|
+
body.include?('filename="payload.bin"') &&
|
|
114
|
+
body.scan(expected_payload).length == 2 &&
|
|
115
|
+
!body.include?('changed')
|
|
116
|
+
raise "Multipart snapshot changed in Ractor #{id}, request #{iteration}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
easy.close
|
|
120
|
+
count
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
assert_equal Array.new(worker_count, request_count), workers.map { |worker| ractor_value(worker) }
|
|
124
|
+
end
|
|
125
|
+
|
|
79
126
|
private
|
|
80
127
|
|
|
81
128
|
def omit_unless_curb_ractor_safe
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: curb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ross Bamford
|
|
8
8
|
- Todd A. Fisher
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Curb (probably CUrl-RuBy or something) provides Ruby-language bindings
|
|
14
14
|
for the libcurl(3), a fully-featured client-side URL transfer library. cURL and
|
|
@@ -21,6 +21,7 @@ extra_rdoc_files:
|
|
|
21
21
|
- LICENSE
|
|
22
22
|
- README.md
|
|
23
23
|
files:
|
|
24
|
+
- ChangeLog.md
|
|
24
25
|
- LICENSE
|
|
25
26
|
- README.md
|
|
26
27
|
- Rakefile
|
|
@@ -84,6 +85,7 @@ files:
|
|
|
84
85
|
- tests/tc_curl_network_policy.rb
|
|
85
86
|
- tests/tc_curl_postfield.rb
|
|
86
87
|
- tests/tc_curl_protocols.rb
|
|
88
|
+
- tests/tc_curl_transfer_guard.rb
|
|
87
89
|
- tests/tc_fiber_scheduler.rb
|
|
88
90
|
- tests/tc_ftp_options.rb
|
|
89
91
|
- tests/tc_gc_compact.rb
|
|
@@ -157,6 +159,7 @@ test_files:
|
|
|
157
159
|
- tests/tc_curl_network_policy.rb
|
|
158
160
|
- tests/tc_curl_postfield.rb
|
|
159
161
|
- tests/tc_curl_protocols.rb
|
|
162
|
+
- tests/tc_curl_transfer_guard.rb
|
|
160
163
|
- tests/tc_fiber_scheduler.rb
|
|
161
164
|
- tests/tc_ftp_options.rb
|
|
162
165
|
- tests/tc_gc_compact.rb
|