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/lib/curl/multi.rb
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require 'curl/download'
|
|
2
3
|
module Curl
|
|
3
4
|
class Multi
|
|
4
5
|
class DownloadError < RuntimeError
|
|
5
6
|
attr_accessor :errors
|
|
6
7
|
end
|
|
8
|
+
|
|
9
|
+
IDLE_EASY_REFERENCES_USE_WEAK_MAP = begin
|
|
10
|
+
probe = ObjectSpace::WeakMap.new
|
|
11
|
+
probe[Object.new.freeze] = true
|
|
12
|
+
true
|
|
13
|
+
rescue ArgumentError, FrozenError, NameError
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
|
|
7
17
|
class << self
|
|
8
18
|
# call-seq:
|
|
9
19
|
# Curl::Multi.get(['url1','url2','url3','url4','url5'], :follow_location => true) do|easy|
|
|
@@ -102,6 +112,7 @@ module Curl
|
|
|
102
112
|
url = c.delete(:url)
|
|
103
113
|
method = c.delete(:method)
|
|
104
114
|
headers = c.delete(:headers)
|
|
115
|
+
internal_info = c.delete(:__curb_internal_info)
|
|
105
116
|
|
|
106
117
|
easy = Curl::Easy.new if easy.nil?
|
|
107
118
|
|
|
@@ -140,7 +151,13 @@ module Curl
|
|
|
140
151
|
|
|
141
152
|
easy.on_complete {|curl|
|
|
142
153
|
free_handles << curl
|
|
143
|
-
|
|
154
|
+
if blk
|
|
155
|
+
if internal_info
|
|
156
|
+
blk.call(curl,curl.response_code,method,internal_info)
|
|
157
|
+
else
|
|
158
|
+
blk.call(curl,curl.response_code,method)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
144
161
|
}
|
|
145
162
|
m.add(easy)
|
|
146
163
|
end
|
|
@@ -182,73 +199,122 @@ module Curl
|
|
|
182
199
|
#
|
|
183
200
|
# Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|}
|
|
184
201
|
#
|
|
185
|
-
# will create 2 new files file1.txt and file2.txt
|
|
202
|
+
# will create 2 new files file1.txt and file2.txt, unless either file
|
|
203
|
+
# already exists. Auto-derived filenames are safely derived from the last
|
|
204
|
+
# URL path component. Pass <tt>:download_dir</tt> as the fifth argument to
|
|
205
|
+
# treat download_paths as basenames inside a trusted directory and reject
|
|
206
|
+
# absolute, parent-directory, dotfile, and nested names.
|
|
186
207
|
#
|
|
187
208
|
# 2 files will be opened, and remain open until the call completes
|
|
188
209
|
#
|
|
189
210
|
# when using the :post or :put method, urls should be a hash, including the individual post fields per post
|
|
190
211
|
#
|
|
191
|
-
def download(urls,easy_options={},multi_options={},download_paths=nil,&blk)
|
|
212
|
+
def download(urls,easy_options={},multi_options={},download_paths=nil,download_options={},&blk)
|
|
192
213
|
errors = []
|
|
193
214
|
procs = []
|
|
194
215
|
files = []
|
|
195
216
|
urls_with_config = []
|
|
196
|
-
|
|
217
|
+
seen_download_paths = {}
|
|
218
|
+
download_infos = []
|
|
219
|
+
|
|
220
|
+
if Curl.download_options_hash?(download_paths) && download_options.empty?
|
|
221
|
+
download_options = download_paths
|
|
222
|
+
download_paths = nil
|
|
223
|
+
end
|
|
197
224
|
|
|
198
225
|
urls.each_with_index do|urlcfg,i|
|
|
199
226
|
if urlcfg.is_a?(Hash)
|
|
200
|
-
url =
|
|
227
|
+
url = urlcfg[:url]
|
|
201
228
|
else
|
|
202
229
|
url = urlcfg
|
|
203
230
|
end
|
|
204
231
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
232
|
+
download_path_arg = download_paths && download_paths[i]
|
|
233
|
+
download_path, file, safe_output, overwrite = Curl.resolve_download_output(url, download_path_arg, download_options)
|
|
234
|
+
|
|
235
|
+
if safe_output
|
|
236
|
+
expanded_path = File.expand_path(download_path)
|
|
237
|
+
raise ArgumentError, "duplicate download destination: #{download_path}" if seen_download_paths[expanded_path]
|
|
238
|
+
|
|
239
|
+
seen_download_paths[expanded_path] = true
|
|
209
240
|
end
|
|
210
241
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
file
|
|
216
|
-
|
|
242
|
+
download_infos << {
|
|
243
|
+
:url => url,
|
|
244
|
+
:urlcfg => urlcfg,
|
|
245
|
+
:path => download_path,
|
|
246
|
+
:file => file,
|
|
247
|
+
:safe_output => safe_output,
|
|
248
|
+
:overwrite => overwrite
|
|
249
|
+
}
|
|
250
|
+
end
|
|
217
251
|
|
|
218
|
-
|
|
219
|
-
|
|
252
|
+
download_infos.each do |info|
|
|
253
|
+
info[:file] ||= Curl.open_safe_download_output(info[:path], :overwrite => info[:overwrite])
|
|
254
|
+
file = info[:file]
|
|
255
|
+
procs << (lambda {|data| file.write data; data.size })
|
|
256
|
+
files << file
|
|
257
|
+
|
|
258
|
+
if info[:urlcfg].is_a?(Hash)
|
|
259
|
+
urls_with_config << info[:urlcfg].merge({:on_body => procs.last, :__curb_internal_info => info}.merge(easy_options))
|
|
220
260
|
else
|
|
221
|
-
urls_with_config << {:url => url, :on_body => procs.last, :method => :get}.merge(easy_options)
|
|
261
|
+
urls_with_config << {:url => info[:url], :on_body => procs.last, :method => :get, :__curb_internal_info => info}.merge(easy_options)
|
|
222
262
|
end
|
|
223
|
-
url_to_download_paths[url] = {:path => download_path, :file => file} # store for later
|
|
224
263
|
end
|
|
225
264
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
finalize_download = lambda do |curl, info|
|
|
266
|
+
file = info[:file]
|
|
267
|
+
files.reject!{|f| f == file }
|
|
268
|
+
|
|
269
|
+
if curl.last_result != 0
|
|
230
270
|
begin
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
271
|
+
if info[:safe_output]
|
|
272
|
+
file.close(false)
|
|
273
|
+
else
|
|
274
|
+
file.close
|
|
275
|
+
end
|
|
234
276
|
rescue => e
|
|
235
277
|
errors << e
|
|
236
278
|
end
|
|
279
|
+
err_class, err_summary = Curl::Easy.error(curl.last_result)
|
|
280
|
+
err_detail = curl.last_error
|
|
281
|
+
errors << err_class.new([err_summary, err_detail].compact.join(": "))
|
|
282
|
+
false
|
|
283
|
+
else
|
|
284
|
+
begin
|
|
285
|
+
if info[:safe_output]
|
|
286
|
+
file.close(true)
|
|
287
|
+
else
|
|
288
|
+
file.close
|
|
289
|
+
end
|
|
290
|
+
true
|
|
291
|
+
rescue => e
|
|
292
|
+
errors << e
|
|
293
|
+
false
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
Curl::Multi.http(urls_with_config, multi_options) do |c,code,method,info|
|
|
299
|
+
if finalize_download.call(c, info) && blk
|
|
237
300
|
blk.call(c,info[:path])
|
|
238
301
|
end
|
|
239
|
-
else
|
|
240
|
-
Curl::Multi.http(urls_with_config, multi_options)
|
|
241
302
|
end
|
|
242
303
|
|
|
243
304
|
ensure
|
|
305
|
+
pending_exception = $!
|
|
244
306
|
files.each {|f|
|
|
245
307
|
begin
|
|
246
|
-
f.
|
|
308
|
+
if f.is_a?(Curl::SafeDownloadOutput)
|
|
309
|
+
f.close(false)
|
|
310
|
+
else
|
|
311
|
+
f.close
|
|
312
|
+
end
|
|
247
313
|
rescue => e
|
|
248
314
|
errors << e
|
|
249
315
|
end
|
|
250
316
|
}
|
|
251
|
-
if errors.any?
|
|
317
|
+
if errors.any? && !pending_exception
|
|
252
318
|
de = Curl::Multi::DownloadError.new
|
|
253
319
|
de.errors = errors
|
|
254
320
|
raise de
|
|
@@ -270,19 +336,46 @@ module Curl
|
|
|
270
336
|
@requests ||= {}
|
|
271
337
|
end
|
|
272
338
|
|
|
339
|
+
def __curb_native_safety_signatures
|
|
340
|
+
@__curb_native_safety_signatures ||= {}
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def __curb_safety_signature_for(easy)
|
|
344
|
+
safety_signature = if Curl.respond_to?(:safety_signature_for, true)
|
|
345
|
+
Curl.__send__(:safety_signature_for, easy)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
[
|
|
349
|
+
safety_signature,
|
|
350
|
+
easy.respond_to?(:network_policy) ? easy.network_policy : nil,
|
|
351
|
+
easy.respond_to?(:allowed_hosts) ? easy.allowed_hosts : nil,
|
|
352
|
+
easy.respond_to?(:allowed_cidrs) ? easy.allowed_cidrs : nil
|
|
353
|
+
]
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def __record_native_safety_signature(easy)
|
|
357
|
+
__curb_native_safety_signatures[easy.object_id] = __curb_safety_signature_for(easy)
|
|
358
|
+
end
|
|
359
|
+
|
|
273
360
|
def __idle_easy_references
|
|
274
|
-
@__curb_idle_easy_references ||=
|
|
361
|
+
@__curb_idle_easy_references ||= __new_idle_easy_references
|
|
275
362
|
end
|
|
276
363
|
|
|
277
364
|
def __register_idle_easy_reference(easy)
|
|
278
|
-
|
|
365
|
+
if IDLE_EASY_REFERENCES_USE_WEAK_MAP
|
|
366
|
+
__idle_easy_references[easy] = true
|
|
367
|
+
else
|
|
368
|
+
__idle_easy_references[easy.object_id] = true
|
|
369
|
+
end
|
|
279
370
|
self
|
|
280
371
|
end
|
|
281
372
|
|
|
282
373
|
def __unregister_idle_easy_reference(easy)
|
|
283
374
|
return self unless instance_variable_defined?(:@__curb_idle_easy_references)
|
|
284
375
|
|
|
285
|
-
if
|
|
376
|
+
if !IDLE_EASY_REFERENCES_USE_WEAK_MAP
|
|
377
|
+
@__curb_idle_easy_references.delete(easy.object_id)
|
|
378
|
+
elsif @__curb_idle_easy_references.respond_to?(:delete)
|
|
286
379
|
@__curb_idle_easy_references.delete(easy)
|
|
287
380
|
else
|
|
288
381
|
retained_references = ObjectSpace::WeakMap.new
|
|
@@ -299,30 +392,70 @@ module Curl
|
|
|
299
392
|
|
|
300
393
|
def __clear_idle_easy_references
|
|
301
394
|
return unless instance_variable_defined?(:@__curb_idle_easy_references)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
395
|
+
|
|
396
|
+
if IDLE_EASY_REFERENCES_USE_WEAK_MAP
|
|
397
|
+
@__curb_idle_easy_references.keys.each do |easy|
|
|
398
|
+
easy.multi = nil if easy.multi.equal?(self)
|
|
399
|
+
end
|
|
400
|
+
else
|
|
401
|
+
@__curb_idle_easy_references.keys.each do |easy_object_id|
|
|
402
|
+
begin
|
|
403
|
+
easy = ObjectSpace._id2ref(easy_object_id)
|
|
404
|
+
rescue RangeError
|
|
405
|
+
next
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
next unless easy.is_a?(Curl::Easy)
|
|
409
|
+
|
|
410
|
+
easy.multi = nil if easy.multi.equal?(self)
|
|
411
|
+
end
|
|
305
412
|
end
|
|
306
|
-
@__curb_idle_easy_references =
|
|
413
|
+
@__curb_idle_easy_references = __new_idle_easy_references
|
|
307
414
|
end
|
|
308
415
|
|
|
309
|
-
|
|
310
|
-
|
|
416
|
+
def __new_idle_easy_references
|
|
417
|
+
IDLE_EASY_REFERENCES_USE_WEAK_MAP ? ObjectSpace::WeakMap.new : {}
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
private :__idle_easy_references, :__curb_native_safety_signatures,
|
|
421
|
+
:__curb_safety_signature_for, :__record_native_safety_signature,
|
|
422
|
+
:__register_idle_easy_reference,
|
|
423
|
+
:__unregister_idle_easy_reference, :__clear_idle_easy_references,
|
|
424
|
+
:__new_idle_easy_references
|
|
425
|
+
|
|
426
|
+
alias_method :_curb_native_perform, :perform
|
|
427
|
+
|
|
428
|
+
def perform(*args, &block)
|
|
429
|
+
requests.each_value do |easy|
|
|
430
|
+
Curl.__send__(:apply_safety!, easy) if Curl.respond_to?(:apply_safety!, true)
|
|
431
|
+
signature = __curb_safety_signature_for(easy)
|
|
432
|
+
if __curb_native_safety_signatures[easy.object_id] != signature &&
|
|
433
|
+
easy.respond_to?(:__curb_native_setup!, true)
|
|
434
|
+
easy.__send__(:__curb_native_setup!)
|
|
435
|
+
__curb_native_safety_signatures[easy.object_id] = signature
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
_curb_native_perform(*args, &block)
|
|
440
|
+
end
|
|
311
441
|
|
|
312
442
|
def add(easy)
|
|
313
443
|
return self if requests[easy.object_id]
|
|
314
444
|
# Once a deferred callback exception is pending, Multi#perform is
|
|
315
445
|
# draining existing transfers only and must not start replacement work.
|
|
316
446
|
return self if instance_variable_defined?(:@__curb_deferred_exception)
|
|
447
|
+
Curl.__send__(:apply_safety!, easy) if Curl.respond_to?(:apply_safety!, true)
|
|
317
448
|
_add(easy)
|
|
318
449
|
__unregister_idle_easy_reference(easy)
|
|
319
450
|
requests[easy.object_id] = easy
|
|
451
|
+
__record_native_safety_signature(easy)
|
|
320
452
|
self
|
|
321
453
|
end
|
|
322
454
|
|
|
323
455
|
def remove(easy)
|
|
324
456
|
return self if !requests[easy.object_id]
|
|
325
457
|
requests.delete(easy.object_id)
|
|
458
|
+
__curb_native_safety_signatures.delete(easy.object_id)
|
|
326
459
|
_remove(easy)
|
|
327
460
|
self
|
|
328
461
|
end
|