curb 1.3.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.
- checksums.yaml +4 -4
- data/README.md +57 -0
- data/Rakefile +8 -3
- data/doc.rb +48 -8
- data/ext/curb.c +24 -0
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +1378 -55
- 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 +48 -2
- data/ext/curb_multi.h +1 -0
- data/ext/extconf.rb +8 -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/tc_curl_download.rb +86 -0
- data/tests/tc_curl_easy.rb +76 -0
- data/tests/tc_curl_maxfilesize.rb +201 -1
- data/tests/tc_curl_multi.rb +258 -0
- data/tests/tc_curl_network_policy.rb +1475 -0
- data/tests/tc_curl_protocols.rb +351 -0
- data/tests/tc_fiber_scheduler.rb +41 -0
- metadata +7 -2
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)
|
|
@@ -1629,6 +1636,36 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1629
1636
|
easy.http_get
|
|
1630
1637
|
end
|
|
1631
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
|
+
|
|
1632
1669
|
def test_last_result_initialization
|
|
1633
1670
|
# Test for issue #463 - ensure last_result is properly initialized to 0
|
|
1634
1671
|
easy = Curl::Easy.new
|
|
@@ -1780,6 +1817,35 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1780
1817
|
end
|
|
1781
1818
|
end
|
|
1782
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
|
+
|
|
1783
1849
|
def test_close_in_on_progress_is_blocked
|
|
1784
1850
|
curl = Curl::Easy.new(TestServlet.url)
|
|
1785
1851
|
did_raise = false
|
|
@@ -1865,6 +1931,16 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1865
1931
|
end
|
|
1866
1932
|
end
|
|
1867
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
|
+
|
|
1868
1944
|
include TestServerMethods
|
|
1869
1945
|
|
|
1870
1946
|
def setup
|
|
@@ -5,8 +5,208 @@ class TestCurbCurlMaxFileSize < Test::Unit::TestCase
|
|
|
5
5
|
@easy = Curl::Easy.new
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def teardown
|
|
9
|
+
Curl.__send__(:clear_safe!) if Curl.respond_to?(:clear_safe!, true)
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_maxfilesize_setopt
|
|
9
14
|
@easy.set(Curl::CURLOPT_MAXFILESIZE, 5000000)
|
|
15
|
+
@easy.set(Curl::CURLOPT_MAXFILESIZE_LARGE, 5000000) if Curl.const_defined?(:CURLOPT_MAXFILESIZE_LARGE)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_max_body_bytes_exact_limit_succeeds
|
|
19
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\n12345678") do |url|
|
|
20
|
+
@easy.url = url
|
|
21
|
+
@easy.max_body_bytes = 8
|
|
22
|
+
@easy.perform
|
|
23
|
+
|
|
24
|
+
assert_equal '12345678', @easy.body_str
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_max_body_bytes_rejects_large_buffered_response
|
|
29
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 64\r\n\r\n#{'x' * 64}") do |url|
|
|
30
|
+
@easy.url = url
|
|
31
|
+
@easy.max_body_bytes = 10
|
|
32
|
+
|
|
33
|
+
assert_body_limit_error do
|
|
34
|
+
@easy.perform
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
assert_operator @easy.body_str.to_s.bytesize, :<=, 10
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_safe_get_accepts_max_body_bytes_as_second_argument
|
|
42
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 64\r\n\r\n#{'x' * 64}") do |url|
|
|
43
|
+
assert_body_limit_error do
|
|
44
|
+
Curl.safe_get(url, :max_body_bytes => 10)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_safe_get_reapplies_max_body_bytes_after_user_block
|
|
50
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 64\r\n\r\n#{'x' * 64}") do |url|
|
|
51
|
+
assert_body_limit_error do
|
|
52
|
+
Curl.safe_get(url, {}, :max_body_bytes => 10) do |easy|
|
|
53
|
+
easy.max_body_bytes = nil
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_safe_bang_max_body_bytes_applies_to_easy_perform
|
|
60
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 64\r\n\r\n#{'x' * 64}") do |url|
|
|
61
|
+
Curl.safe! do |config|
|
|
62
|
+
config.max_body_bytes = 10
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
@easy.url = url
|
|
66
|
+
|
|
67
|
+
assert_body_limit_error do
|
|
68
|
+
@easy.perform
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_safe_bang_reapplies_max_body_bytes_after_shortcut_block
|
|
74
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 64\r\n\r\n#{'x' * 64}") do |url|
|
|
75
|
+
Curl.safe! do |config|
|
|
76
|
+
config.max_body_bytes = 10
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
assert_body_limit_error do
|
|
80
|
+
Curl.get(url) do |easy|
|
|
81
|
+
easy.max_body_bytes = nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_safe_get_max_body_bytes_tightens_global_cap
|
|
88
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 64\r\n\r\n#{'x' * 64}") do |url|
|
|
89
|
+
Curl.safe! do |config|
|
|
90
|
+
config.max_body_bytes = 1_000
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
assert_body_limit_error do
|
|
94
|
+
Curl.safe_get(url, :max_body_bytes => 10)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def test_safe_bang_rejects_negative_max_body_bytes
|
|
100
|
+
assert_raise(ArgumentError) do
|
|
101
|
+
Curl.safe! do |config|
|
|
102
|
+
config.max_body_bytes = -1
|
|
103
|
+
end
|
|
104
|
+
end
|
|
10
105
|
end
|
|
11
106
|
|
|
107
|
+
def test_safe_head_does_not_apply_body_cap_to_content_length
|
|
108
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 65536\r\n\r\n") do |url|
|
|
109
|
+
easy = Curl.safe_head(url, :max_body_bytes => 1)
|
|
110
|
+
|
|
111
|
+
assert_equal 200, easy.response_code
|
|
112
|
+
assert_equal '', easy.body_str
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_max_body_bytes_rejects_chunked_response_without_content_length
|
|
117
|
+
chunks = ["1234", "5678", "90"]
|
|
118
|
+
response = +"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
|
|
119
|
+
chunks.each { |chunk| response << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" }
|
|
120
|
+
response << "0\r\n\r\n"
|
|
121
|
+
|
|
122
|
+
with_raw_http_response(response) do |url|
|
|
123
|
+
@easy.url = url
|
|
124
|
+
@easy.max_body_bytes = 8
|
|
125
|
+
|
|
126
|
+
assert_body_limit_error do
|
|
127
|
+
@easy.perform
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
assert_operator @easy.body_str.to_s.bytesize, :<=, 8
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_max_body_bytes_limits_on_body_callback
|
|
135
|
+
chunks = ["1234", "5678", "90"]
|
|
136
|
+
response = +"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
|
|
137
|
+
chunks.each { |chunk| response << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" }
|
|
138
|
+
response << "0\r\n\r\n"
|
|
139
|
+
seen = +''
|
|
140
|
+
|
|
141
|
+
with_raw_http_response(response) do |url|
|
|
142
|
+
@easy.url = url
|
|
143
|
+
@easy.max_body_bytes = 8
|
|
144
|
+
@easy.on_body { |data| seen << data; data.bytesize }
|
|
145
|
+
|
|
146
|
+
assert_body_limit_error do
|
|
147
|
+
@easy.perform
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
assert_equal '12345678', seen
|
|
151
|
+
assert_nil @easy.body_str
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_max_body_bytes_zero_clears_limit
|
|
156
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\n123456789012") do |url|
|
|
157
|
+
@easy.url = url
|
|
158
|
+
@easy.max_body_bytes = 1
|
|
159
|
+
@easy.max_body_bytes = 0
|
|
160
|
+
@easy.perform
|
|
161
|
+
|
|
162
|
+
assert_nil @easy.max_body_bytes
|
|
163
|
+
assert_equal '123456789012', @easy.body_str
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def test_max_body_bytes_counter_resets_between_reused_transfers
|
|
168
|
+
@easy.max_body_bytes = 8
|
|
169
|
+
|
|
170
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\n12345678") do |url|
|
|
171
|
+
@easy.url = url
|
|
172
|
+
@easy.perform
|
|
173
|
+
assert_equal '12345678', @easy.body_str
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nabcdefgh") do |url|
|
|
177
|
+
@easy.url = url
|
|
178
|
+
@easy.perform
|
|
179
|
+
assert_equal 'abcdefgh', @easy.body_str
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
def assert_body_limit_error
|
|
186
|
+
assert_raise(Curl::Err::FileSizeExceededError) do
|
|
187
|
+
yield
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def with_raw_http_response(response)
|
|
192
|
+
server = TCPServer.new('127.0.0.1', 0)
|
|
193
|
+
port = server.addr[1]
|
|
194
|
+
thread = Thread.new do
|
|
195
|
+
socket = server.accept
|
|
196
|
+
begin
|
|
197
|
+
socket.readpartial(4096)
|
|
198
|
+
rescue EOFError
|
|
199
|
+
end
|
|
200
|
+
socket.write(response)
|
|
201
|
+
socket.close
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
yield "http://127.0.0.1:#{port}/"
|
|
205
|
+
ensure
|
|
206
|
+
server.close if server && !server.closed?
|
|
207
|
+
if thread
|
|
208
|
+
thread.join(5)
|
|
209
|
+
thread.kill if thread.alive?
|
|
210
|
+
end
|
|
211
|
+
end
|
|
12
212
|
end
|
data/tests/tc_curl_multi.rb
CHANGED
|
@@ -189,6 +189,23 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
189
189
|
second_multi.close if defined?(second_multi) && second_multi
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
+
def test_easy_multi_assignment_detaches_from_old_multi
|
|
193
|
+
multi = Curl::Multi.new
|
|
194
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
195
|
+
|
|
196
|
+
multi.add(easy)
|
|
197
|
+
easy.multi = nil
|
|
198
|
+
|
|
199
|
+
assert_nil easy.multi
|
|
200
|
+
assert_equal({}, multi.requests)
|
|
201
|
+
assert_nothing_raised { easy.close }
|
|
202
|
+
assert_nothing_raised { multi.close }
|
|
203
|
+
|
|
204
|
+
multi = nil
|
|
205
|
+
ensure
|
|
206
|
+
multi.close if defined?(multi) && multi
|
|
207
|
+
end
|
|
208
|
+
|
|
192
209
|
def test_close_makes_multi_unusable
|
|
193
210
|
multi = Curl::Multi.new
|
|
194
211
|
multi.close
|
|
@@ -752,6 +769,23 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
752
769
|
m.close if m
|
|
753
770
|
end
|
|
754
771
|
|
|
772
|
+
def test_native_safety_signatures_are_pruned_after_perform
|
|
773
|
+
m = Curl::Multi.new
|
|
774
|
+
easy = Curl::Easy.new($TEST_URL)
|
|
775
|
+
|
|
776
|
+
m.add(easy)
|
|
777
|
+
signatures = m.__send__(:__curb_native_safety_signatures)
|
|
778
|
+
assert_equal easy, m.requests[easy.object_id]
|
|
779
|
+
assert signatures.key?(easy.object_id)
|
|
780
|
+
|
|
781
|
+
m.perform
|
|
782
|
+
|
|
783
|
+
assert_equal 0, m.requests.length
|
|
784
|
+
assert_empty signatures
|
|
785
|
+
ensure
|
|
786
|
+
m.close if m
|
|
787
|
+
end
|
|
788
|
+
|
|
755
789
|
def test_easy_multi_is_cleared_after_perform
|
|
756
790
|
m = Curl::Multi.new
|
|
757
791
|
c = Curl::Easy.new($TEST_URL)
|
|
@@ -766,6 +800,21 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
766
800
|
m.close if m
|
|
767
801
|
end
|
|
768
802
|
|
|
803
|
+
def test_frozen_idle_easy_multi_reference_is_cleared_on_close
|
|
804
|
+
multi = Curl::Multi.new
|
|
805
|
+
easy = Curl::Easy.new($TEST_URL)
|
|
806
|
+
easy.freeze
|
|
807
|
+
|
|
808
|
+
easy.multi = multi
|
|
809
|
+
|
|
810
|
+
assert_same multi, easy.multi
|
|
811
|
+
assert_nothing_raised { multi.close }
|
|
812
|
+
assert_nil easy.multi
|
|
813
|
+
multi = nil
|
|
814
|
+
ensure
|
|
815
|
+
multi.close if multi
|
|
816
|
+
end
|
|
817
|
+
|
|
769
818
|
def test_easy_multi_is_available_during_completion_callbacks
|
|
770
819
|
multi = Curl::Multi.new
|
|
771
820
|
easy = Curl::Easy.new($TEST_URL)
|
|
@@ -991,6 +1040,193 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
991
1040
|
end
|
|
992
1041
|
end
|
|
993
1042
|
|
|
1043
|
+
def test_multi_download_refuses_existing_explicit_download_path
|
|
1044
|
+
root_uri = 'http://127.0.0.1:9129/ext/'
|
|
1045
|
+
|
|
1046
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1047
|
+
download_path = File.join(download_dir, 'curb_easy.c')
|
|
1048
|
+
File.write(download_path, 'sentinel')
|
|
1049
|
+
|
|
1050
|
+
assert_raise(Curl::DownloadTargetExistsError) do
|
|
1051
|
+
Curl::Multi.download([root_uri + 'curb_easy.c'], {}, {}, [download_path])
|
|
1052
|
+
end
|
|
1053
|
+
|
|
1054
|
+
assert_equal 'sentinel', File.read(download_path)
|
|
1055
|
+
end
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
def test_multi_download_failed_transfer_does_not_publish_file_without_block
|
|
1059
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1060
|
+
download_path = File.join(download_dir, 'missing')
|
|
1061
|
+
|
|
1062
|
+
error = assert_raise(Curl::Multi::DownloadError) do
|
|
1063
|
+
Curl::Multi.download(['file:///curb/missing'], {}, {}, [download_path])
|
|
1064
|
+
end
|
|
1065
|
+
|
|
1066
|
+
assert !error.errors.empty?
|
|
1067
|
+
assert !File.exist?(download_path)
|
|
1068
|
+
end
|
|
1069
|
+
end
|
|
1070
|
+
|
|
1071
|
+
def test_multi_download_failed_transfer_does_not_publish_file_or_call_block
|
|
1072
|
+
called = false
|
|
1073
|
+
|
|
1074
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1075
|
+
download_path = File.join(download_dir, 'missing')
|
|
1076
|
+
|
|
1077
|
+
error = assert_raise(Curl::Multi::DownloadError) do
|
|
1078
|
+
Curl::Multi.download(['file:///curb/missing'], {}, {}, [download_path]) do |_curl, _path|
|
|
1079
|
+
called = true
|
|
1080
|
+
end
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
assert !error.errors.empty?
|
|
1084
|
+
assert_equal false, called
|
|
1085
|
+
assert !File.exist?(download_path)
|
|
1086
|
+
end
|
|
1087
|
+
end
|
|
1088
|
+
|
|
1089
|
+
def test_multi_download_failed_transfer_closes_io_destination
|
|
1090
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1091
|
+
download_path = File.join(download_dir, 'missing')
|
|
1092
|
+
io = File.open(download_path, 'wb')
|
|
1093
|
+
|
|
1094
|
+
error = assert_raise(Curl::Multi::DownloadError) do
|
|
1095
|
+
Curl::Multi.download(['file:///curb/missing'], {}, {}, [io])
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
assert !error.errors.any? { |e| e.is_a?(ArgumentError) }
|
|
1099
|
+
assert io.closed?
|
|
1100
|
+
assert_equal '', File.read(download_path)
|
|
1101
|
+
end
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
def test_multi_download_preserves_body_limit_callback_error
|
|
1105
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 64\r\n\r\n#{'x' * 64}") do |url|
|
|
1106
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1107
|
+
download_path = File.join(download_dir, 'too-large')
|
|
1108
|
+
|
|
1109
|
+
error = assert_raise(Curl::Err::FileSizeExceededError) do
|
|
1110
|
+
Curl::Multi.download([url], { :max_body_bytes => 10 }, {}, [download_path])
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
assert_match(/Maximum body size exceeded/, error.message)
|
|
1114
|
+
assert !File.exist?(download_path)
|
|
1115
|
+
end
|
|
1116
|
+
end
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
def test_multi_download_preserves_output_write_callback_error
|
|
1120
|
+
writer_error = Class.new(StandardError)
|
|
1121
|
+
|
|
1122
|
+
with_raw_http_response("HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata") do |url|
|
|
1123
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1124
|
+
writer = File.open(File.join(download_dir, 'writer'), 'wb')
|
|
1125
|
+
writer.define_singleton_method(:write) do |_data|
|
|
1126
|
+
raise writer_error, 'writer failed'
|
|
1127
|
+
end
|
|
1128
|
+
|
|
1129
|
+
error = assert_raise(writer_error) do
|
|
1130
|
+
Curl::Multi.download([url], {}, {}, [writer])
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
assert_equal 'writer failed', error.message
|
|
1134
|
+
assert writer.closed?
|
|
1135
|
+
end
|
|
1136
|
+
end
|
|
1137
|
+
end
|
|
1138
|
+
|
|
1139
|
+
def test_multi_download_safe_directory_rejects_unsafe_names
|
|
1140
|
+
root_uri = 'http://127.0.0.1:9129/ext/'
|
|
1141
|
+
|
|
1142
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1143
|
+
['../escape.c', '.env', 'nested/file', '/tmp/escape.c', "bad\0name"].each do |name|
|
|
1144
|
+
assert_raise(ArgumentError, "expected #{name.inspect} to be rejected") do
|
|
1145
|
+
Curl::Multi.download([root_uri + 'curb_easy.c'], {}, {}, [name], :download_dir => download_dir)
|
|
1146
|
+
end
|
|
1147
|
+
end
|
|
1148
|
+
end
|
|
1149
|
+
end
|
|
1150
|
+
|
|
1151
|
+
def test_multi_download_rejects_duplicate_destinations
|
|
1152
|
+
root_uri = 'http://127.0.0.1:9129/ext/'
|
|
1153
|
+
|
|
1154
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1155
|
+
assert_raise(ArgumentError) do
|
|
1156
|
+
Curl::Multi.download(
|
|
1157
|
+
[root_uri + 'curb_easy.c?one=1', root_uri + 'curb_easy.c?two=2'],
|
|
1158
|
+
{},
|
|
1159
|
+
{},
|
|
1160
|
+
nil,
|
|
1161
|
+
:download_dir => download_dir
|
|
1162
|
+
)
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
assert !File.exist?(File.join(download_dir, 'curb_easy.c'))
|
|
1166
|
+
assert_equal [], Dir[File.join(download_dir, '.curb-download-*')]
|
|
1167
|
+
end
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
def test_multi_download_safe_directory_strips_query_from_url_basename
|
|
1171
|
+
root_uri = 'http://127.0.0.1:9129/ext/'
|
|
1172
|
+
|
|
1173
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1174
|
+
Curl::Multi.download([root_uri + 'curb_easy.c?cache=1'], {}, {}, nil, :download_dir => download_dir)
|
|
1175
|
+
|
|
1176
|
+
download_path = File.join(download_dir, 'curb_easy.c')
|
|
1177
|
+
assert File.exist?(download_path)
|
|
1178
|
+
assert !File.exist?(File.join(download_dir, 'curb_easy.c?cache=1'))
|
|
1179
|
+
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(download_path)
|
|
1180
|
+
end
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
def test_multi_download_hash_url_config_uses_hash_url
|
|
1184
|
+
root_uri = 'http://127.0.0.1:9129/ext/'
|
|
1185
|
+
|
|
1186
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1187
|
+
download_path = File.join(download_dir, 'curb_easy.c')
|
|
1188
|
+
|
|
1189
|
+
Curl::Multi.download([{ :url => root_uri + 'curb_easy.c', :method => :get }], {}, {}, [download_path]) do |curl, path|
|
|
1190
|
+
assert_equal 200, curl.response_code
|
|
1191
|
+
assert_equal download_path, path
|
|
1192
|
+
end
|
|
1193
|
+
|
|
1194
|
+
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(download_path)
|
|
1195
|
+
end
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
def test_multi_download_duplicate_urls_use_distinct_explicit_paths
|
|
1199
|
+
url = "file://#{File.expand_path(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c'))}"
|
|
1200
|
+
|
|
1201
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1202
|
+
paths = [
|
|
1203
|
+
File.join(download_dir, 'a.c'),
|
|
1204
|
+
File.join(download_dir, 'b.c')
|
|
1205
|
+
]
|
|
1206
|
+
completed_paths = []
|
|
1207
|
+
|
|
1208
|
+
Curl::Multi.download([url, url], {}, {}, paths) do |_curl, path|
|
|
1209
|
+
completed_paths << path
|
|
1210
|
+
end
|
|
1211
|
+
|
|
1212
|
+
assert_equal paths.sort, completed_paths.sort
|
|
1213
|
+
assert_equal File.read(paths[0]), File.read(paths[1])
|
|
1214
|
+
end
|
|
1215
|
+
end
|
|
1216
|
+
|
|
1217
|
+
def test_multi_download_accepts_indexed_hash_download_paths
|
|
1218
|
+
root_uri = 'http://127.0.0.1:9129/ext/'
|
|
1219
|
+
|
|
1220
|
+
Dir.mktmpdir('curb-download-') do |download_dir|
|
|
1221
|
+
download_path = File.join(download_dir, 'curb_easy.c')
|
|
1222
|
+
|
|
1223
|
+
Curl::Multi.download([root_uri + 'curb_easy.c'], {}, {}, {0 => download_path})
|
|
1224
|
+
|
|
1225
|
+
assert File.exist?(download_path)
|
|
1226
|
+
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(download_path)
|
|
1227
|
+
end
|
|
1228
|
+
end
|
|
1229
|
+
|
|
994
1230
|
def test_multi_easy_post_01
|
|
995
1231
|
urls = [
|
|
996
1232
|
{ :url => TestServlet.url + '?q=1', :post_fields => {'field1' => 'value1', 'k' => 'j'}},
|
|
@@ -1279,6 +1515,28 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
|
1279
1515
|
server_thread.kill if defined?(server_thread) && server_thread&.alive?
|
|
1280
1516
|
end
|
|
1281
1517
|
|
|
1518
|
+
def with_raw_http_response(response)
|
|
1519
|
+
server = TCPServer.new('127.0.0.1', 0)
|
|
1520
|
+
port = server.addr[1]
|
|
1521
|
+
thread = Thread.new do
|
|
1522
|
+
socket = server.accept
|
|
1523
|
+
begin
|
|
1524
|
+
socket.readpartial(4096)
|
|
1525
|
+
rescue EOFError
|
|
1526
|
+
end
|
|
1527
|
+
socket.write(response)
|
|
1528
|
+
socket.close
|
|
1529
|
+
end
|
|
1530
|
+
|
|
1531
|
+
yield "http://127.0.0.1:#{port}/"
|
|
1532
|
+
ensure
|
|
1533
|
+
server.close if defined?(server) && server && !server.closed?
|
|
1534
|
+
if defined?(thread) && thread
|
|
1535
|
+
thread.join(5)
|
|
1536
|
+
thread.kill if thread.alive?
|
|
1537
|
+
end
|
|
1538
|
+
end
|
|
1539
|
+
|
|
1282
1540
|
include TestServerMethods
|
|
1283
1541
|
|
|
1284
1542
|
def setup
|