curb 0.8.4 → 1.3.5

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +522 -0
  3. data/Rakefile +79 -26
  4. data/ext/banned.h +32 -0
  5. data/ext/curb.c +549 -230
  6. data/ext/curb.h +19 -10
  7. data/ext/curb_easy.c +1935 -366
  8. data/ext/curb_easy.h +16 -0
  9. data/ext/curb_errors.c +115 -18
  10. data/ext/curb_errors.h +8 -5
  11. data/ext/curb_macros.h +33 -21
  12. data/ext/curb_multi.c +1858 -272
  13. data/ext/curb_multi.h +10 -3
  14. data/ext/curb_postfield.c +149 -77
  15. data/ext/curb_postfield.h +1 -0
  16. data/ext/curb_upload.c +38 -11
  17. data/ext/curb_upload.h +2 -0
  18. data/ext/extconf.rb +353 -35
  19. data/lib/curb.rb +1 -0
  20. data/lib/curl/easy.rb +314 -78
  21. data/lib/curl/multi.rb +134 -28
  22. data/lib/curl.rb +217 -11
  23. data/tests/bug_crash_on_debug.rb +14 -28
  24. data/tests/bug_crash_on_progress.rb +32 -16
  25. data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
  26. data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
  27. data/tests/bug_follow_redirect_288.rb +83 -0
  28. data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
  29. data/tests/bug_issue102.rb +1 -1
  30. data/tests/bug_issue_noproxy.rb +56 -0
  31. data/tests/bug_issue_post_redirect.rb +93 -0
  32. data/tests/bug_issue_spnego.rb +41 -0
  33. data/tests/bug_multi_segfault.rb +1 -0
  34. data/tests/bug_raise_on_callback.rb +30 -0
  35. data/tests/helper.rb +400 -44
  36. data/tests/leak_trace.rb +237 -0
  37. data/tests/mem_check.rb +3 -0
  38. data/tests/tc_curl.rb +31 -1
  39. data/tests/tc_curl_download.rb +12 -7
  40. data/tests/tc_curl_easy.rb +911 -63
  41. data/tests/tc_curl_easy_cookielist.rb +277 -0
  42. data/tests/tc_curl_easy_request_target.rb +41 -0
  43. data/tests/tc_curl_easy_resolve.rb +48 -0
  44. data/tests/tc_curl_maxfilesize.rb +12 -0
  45. data/tests/tc_curl_multi.rb +855 -53
  46. data/tests/tc_curl_native_coverage.rb +145 -0
  47. data/tests/tc_curl_postfield.rb +207 -30
  48. data/tests/tc_curl_protocols.rb +37 -0
  49. data/tests/tc_fiber_scheduler.rb +543 -0
  50. data/tests/tc_ftp_options.rb +39 -0
  51. data/tests/tc_gc_compact.rb +223 -0
  52. data/tests/tc_test_server_methods.rb +110 -0
  53. data/tests/timeout.rb +30 -6
  54. metadata +59 -36
  55. data/README +0 -194
data/lib/curl/easy.rb CHANGED
@@ -1,34 +1,146 @@
1
+ # frozen_string_literal: true
1
2
  module Curl
2
3
  class Easy
4
+ class << self
5
+ def deferred_multi_close_mutex
6
+ @deferred_multi_close_mutex ||= Mutex.new
7
+ end
8
+
9
+ def deferred_multi_closes
10
+ deferred_multi_close_mutex.synchronize do
11
+ (@deferred_multi_closes ||= []).dup
12
+ end
13
+ end
14
+
15
+ def release_deferred_multi_close(multi, easy)
16
+ if easy && multi.requests[easy.object_id]
17
+ begin
18
+ multi.remove(easy)
19
+ rescue StandardError
20
+ # Deferred cleanup only applies to implicit single-easy multis, so
21
+ # clear any stale Ruby bookkeeping and continue closing the handle.
22
+ multi.instance_variable_set(:@requests, {})
23
+ end
24
+ else
25
+ multi.instance_variable_set(:@requests, {})
26
+ end
27
+
28
+ multi.instance_variable_set(:@deferred_close, false)
29
+ multi._close
30
+ true
31
+ rescue StandardError
32
+ false
33
+ end
34
+
35
+ def defer_multi_close(multi, easy, owner: Thread.current)
36
+ deferred_multi_close_mutex.synchronize do
37
+ @deferred_multi_closes ||= []
38
+ return if @deferred_multi_closes.any? { |entry| entry[:multi].equal?(multi) }
39
+
40
+ multi.instance_variable_set(:@deferred_close, true)
41
+ @deferred_multi_closes << { multi: multi, easy: easy, owner: owner }
42
+ end
43
+ end
44
+
45
+ def flush_deferred_multi_closes(all_threads: false)
46
+ pending = deferred_multi_close_mutex.synchronize do
47
+ @deferred_multi_closes ||= []
48
+
49
+ if all_threads
50
+ @deferred_multi_closes.shift(@deferred_multi_closes.length)
51
+ else
52
+ owner = Thread.current
53
+ remaining = []
54
+ current = []
55
+
56
+ @deferred_multi_closes.each do |entry|
57
+ if entry[:owner].equal?(owner)
58
+ current << entry
59
+ else
60
+ remaining << entry
61
+ end
62
+ end
63
+
64
+ @deferred_multi_closes = remaining
65
+ current
66
+ end
67
+ end
68
+
69
+ pending.each do |entry|
70
+ multi = entry[:multi]
71
+ easy = entry[:easy]
72
+
73
+ unless release_deferred_multi_close(multi, easy)
74
+ defer_multi_close(multi, easy, owner: entry[:owner])
75
+ end
76
+ end
77
+ end
78
+ end
3
79
 
80
+ at_exit do
81
+ flush_deferred_multi_closes(all_threads: true)
82
+ end
83
+
84
+ alias_method :_curb_native_close, :close
85
+ alias_method :_curb_native_multi_set, :multi=
86
+
87
+ def close
88
+ previous_multi = self.multi
89
+ result = _curb_native_close
90
+ previous_multi.__send__(:__unregister_idle_easy_reference, self) if previous_multi
91
+ result
92
+ end
93
+
94
+ def multi=(multi)
95
+ previous_multi = self.multi
96
+ return multi if previous_multi.equal?(multi)
97
+
98
+ result = _curb_native_multi_set(multi)
99
+ previous_multi.__send__(:__unregister_idle_easy_reference, self) if previous_multi
100
+ multi.__send__(:__register_idle_easy_reference, self) if multi
101
+ result
102
+ end
103
+
4
104
  alias post http_post
5
105
  alias put http_put
106
+ alias body body_str
107
+ alias head header_str
108
+
109
+ class Error < StandardError
110
+ attr_accessor :message, :code
111
+ def initialize(code, msg)
112
+ self.message = msg
113
+ self.code = code
114
+ end
115
+ end
6
116
 
7
117
  #
8
118
  # call-seq:
9
119
  # easy.status => String
10
120
  #
11
121
  def status
12
- parts = self.header_str.split(/\s/)
13
- status = []
14
- parts.shift
15
- while parts.size > 0 && parts.first != ''
16
- status << parts.shift
17
- end
18
- status.join(' ')
122
+ # Matches the last HTTP Status - following the HTTP protocol specification 'Status-Line = HTTP-Version SP Status-Code SP (Opt:)Reason-Phrase CRLF'
123
+ statuses = self.header_str.to_s.scan(/HTTP\/\d(\.\d)?\s(\d+\s.*)\r\n/).map {|match| match[1] }
124
+ statuses.last.strip if statuses.length > 0
19
125
  end
20
126
 
21
127
  #
22
128
  # call-seq:
23
129
  # easy.set :sym|Fixnum, value
24
- #
130
+ #
25
131
  # set options on the curl easy handle see http://curl.haxx.se/libcurl/c/curl_easy_setopt.html
26
132
  #
27
133
  def set(opt,val)
28
134
  if opt.is_a?(Symbol)
29
- setopt(sym2curl(opt), val)
135
+ option = sym2curl(opt)
30
136
  else
31
- setopt(opt.to_i, val)
137
+ option = opt.to_i
138
+ end
139
+
140
+ begin
141
+ setopt(option, val)
142
+ rescue TypeError
143
+ raise TypeError, "Curb doesn't support setting #{opt} [##{option}] option"
32
144
  end
33
145
  end
34
146
 
@@ -51,13 +163,57 @@ module Curl
51
163
  # the configured HTTP Verb.
52
164
  #
53
165
  def perform
54
- self.multi = Curl::Multi.new if self.multi.nil?
55
- self.multi.add self
56
- ret = self.multi.perform
166
+ self.class.flush_deferred_multi_closes
167
+
168
+ if Curl.scheduler_active? && self.multi.nil?
169
+ ret = Curl.perform_with_scheduler(self)
170
+ else
171
+ multi = self.multi
172
+ created_multi = multi.nil?
173
+ raised = false
174
+
175
+ if created_multi
176
+ multi = Curl::Multi.new
177
+ self.multi = multi
178
+ end
179
+
180
+ begin
181
+ multi.add(self)
182
+ ret = multi.perform
183
+ multi.remove(self) if self.multi == multi
184
+ rescue Exception
185
+ raised = true
186
+ raise
187
+ ensure
188
+ if created_multi
189
+ if raised
190
+ unless self.class.release_deferred_multi_close(multi, self)
191
+ self.class.defer_multi_close(multi, self)
192
+ end
193
+ self.multi = nil if self.multi == multi
194
+ elsif Curl::Multi.autoclose
195
+ multi.__send__(:_autoclose)
196
+ self.multi = nil if self.multi == multi
197
+ else
198
+ self.multi = multi
199
+ end
200
+ elsif Curl::Multi.autoclose
201
+ multi.__send__(:_autoclose)
202
+ self.multi = nil if self.multi == multi
203
+ else
204
+ self.multi = multi
205
+ end
206
+ end
207
+ end
208
+
209
+ if (callback_error = _take_callback_error)
210
+ raise callback_error
211
+ end
57
212
 
58
213
  if self.last_result != 0 && self.on_failure.nil?
59
- error = Curl::Easy.error(self.last_result)
60
- raise error.first
214
+ err_class, err_summary = Curl::Easy.error(self.last_result)
215
+ err_detail = self.last_error
216
+ raise err_class.new([err_summary, err_detail].compact.join(": "))
61
217
  end
62
218
 
63
219
  ret
@@ -65,6 +221,7 @@ module Curl
65
221
 
66
222
  #
67
223
  # call-seq:
224
+
68
225
  #
69
226
  # easy = Curl::Easy.new
70
227
  # easy.nosignal = true
@@ -81,54 +238,59 @@ module Curl
81
238
  # easy.perform
82
239
  #
83
240
  def delete=(onoff)
84
- set :customrequest, onoff ? 'delete' : nil
241
+ set :customrequest, onoff ? 'DELETE' : nil
85
242
  onoff
86
243
  end
87
- #
244
+ #
88
245
  # call-seq:
89
- #
246
+ #
90
247
  # easy = Curl::Easy.new("url")
91
- # easy.version = Curl::HTTP_1_1
92
- # easy.version = Curl::HTTP_1_0
93
- # easy.version = Curl::HTTP_NONE
94
- #
248
+ # easy.version = Curl::HTTP_2_0
249
+ # easy.http_version = Curl::HTTP_1_1
250
+ # easy.http_version = Curl::HTTP_1_0
251
+ # easy.http_version = Curl::HTTP_NONE
252
+ #
95
253
  def version=(http_version)
96
- set :http_version, http_version
254
+ self.http_version = http_version
255
+ end
256
+
257
+ def version
258
+ http_version
97
259
  end
98
260
 
99
- #
261
+ #
100
262
  # call-seq:
101
263
  # easy.url = "http://some.url/" => "http://some.url/"
102
- #
264
+ #
103
265
  # Set the URL for subsequent calls to +perform+. It is acceptable
104
266
  # (and even recommended) to reuse Curl::Easy instances by reassigning
105
267
  # the URL between calls to +perform+.
106
- #
268
+ #
107
269
  def url=(u)
108
270
  set :url, u
109
271
  end
110
272
 
111
- #
273
+ #
112
274
  # call-seq:
113
275
  # easy.proxy_url = string => string
114
- #
276
+ #
115
277
  # Set the URL of the HTTP proxy to use for subsequent calls to +perform+.
116
278
  # The URL should specify the the host name or dotted IP address. To specify
117
279
  # port number in this string, append :[port] to the end of the host name.
118
280
  # The proxy string may be prefixed with [protocol]:// since any such prefix
119
281
  # will be ignored. The proxy's port number may optionally be specified with
120
282
  # the separate option proxy_port .
121
- #
283
+ #
122
284
  # When you tell the library to use an HTTP proxy, libcurl will transparently
123
285
  # convert operations to HTTP even if you specify an FTP URL etc. This may have
124
286
  # an impact on what other features of the library you can use, such as
125
287
  # FTP specifics that don't work unless you tunnel through the HTTP proxy. Such
126
288
  # tunneling is activated with proxy_tunnel = true.
127
- #
289
+ #
128
290
  # libcurl respects the environment variables *http_proxy*, *ftp_proxy*,
129
291
  # *all_proxy* etc, if any of those is set. The proxy_url option does however
130
292
  # override any possibly set environment variables.
131
- #
293
+ #
132
294
  # Starting with libcurl 7.14.1, the proxy host string given in environment
133
295
  # variables can be specified the exact same way as the proxy can be set with
134
296
  # proxy_url, including protocol prefix (http://) and embedded user + password.
@@ -137,99 +299,150 @@ module Curl
137
299
  set :proxy, url
138
300
  end
139
301
 
302
+ #
303
+ # call-seq:
304
+ # easy.request_target = string => string
305
+ #
306
+ # Set the request-target used in the HTTP request line (libcurl CURLOPT_REQUEST_TARGET).
307
+ # Useful for absolute-form request targets (e.g., when speaking to proxies) or
308
+ # special targets like "*" (OPTIONS *). Requires libcurl with CURLOPT_REQUEST_TARGET support.
309
+ #
310
+ def request_target=(value)
311
+ if Curl.const_defined?(:CURLOPT_REQUEST_TARGET)
312
+ set :request_target, value
313
+ else
314
+ raise NotImplementedError, "CURLOPT_REQUEST_TARGET is not supported by this libcurl"
315
+ end
316
+ end
317
+
140
318
  def ssl_verify_host=(value)
141
319
  value = 1 if value.class == TrueClass
142
320
  value = 0 if value.class == FalseClass
143
321
  self.ssl_verify_host_integer=value
144
322
  end
145
323
 
146
- #
324
+ #
147
325
  # call-seq:
148
326
  # easy.ssl_verify_host? => boolean
149
- #
327
+ #
150
328
  # Deprecated: call easy.ssl_verify_host instead
151
329
  # can be one of [0,1,2]
152
- #
330
+ #
153
331
  # Determine whether this Curl instance will verify that the server cert
154
332
  # is for the server it is known as.
155
- #
333
+ #
156
334
  def ssl_verify_host?
157
335
  ssl_verify_host.nil? ? false : (ssl_verify_host > 0)
158
336
  end
159
337
 
160
- #
338
+ #
161
339
  # call-seq:
162
340
  # easy.interface = string => string
163
- #
341
+ #
164
342
  # Set the interface name to use as the outgoing network interface.
165
343
  # The name can be an interface name, an IP address or a host name.
166
- #
344
+ #
167
345
  def interface=(value)
168
346
  set :interface, value
169
347
  end
170
348
 
171
- #
349
+ #
172
350
  # call-seq:
173
351
  # easy.userpwd = string => string
174
- #
352
+ #
175
353
  # Set the username/password string to use for subsequent calls to +perform+.
176
354
  # The supplied string should have the form "username:password"
177
- #
355
+ #
178
356
  def userpwd=(value)
179
357
  set :userpwd, value
180
358
  end
181
359
 
182
- #
360
+ #
183
361
  # call-seq:
184
362
  # easy.proxypwd = string => string
185
- #
363
+ #
186
364
  # Set the username/password string to use for proxy connection during
187
365
  # subsequent calls to +perform+. The supplied string should have the
188
366
  # form "username:password"
189
- #
367
+ #
190
368
  def proxypwd=(value)
191
369
  set :proxyuserpwd, value
192
370
  end
193
371
 
194
- #
372
+ #
195
373
  # call-seq:
196
374
  # easy.cookies = "name1=content1; name2=content2;" => string
197
- #
198
- # Set cookies to be sent by this Curl::Easy instance. The format of the string should
199
- # be NAME=CONTENTS, where NAME is the cookie name and CONTENTS is what the cookie should contain.
200
- # Set multiple cookies in one string like this: "name1=content1; name2=content2;" etc.
201
- #
375
+ #
376
+ # Set the manual Cookie request header for this Curl::Easy instance.
377
+ # The format of the string should be NAME=CONTENTS, where NAME is the cookie name and
378
+ # CONTENTS is what the cookie should contain. Set multiple cookies in one string like this:
379
+ # "name1=content1; name2=content2;".
380
+ #
381
+ # Notes:
382
+ # - This only affects the outgoing Cookie header (libcurl CURLOPT_COOKIE) and does NOT
383
+ # alter the internal libcurl cookie engine (which stores cookies from Set-Cookie).
384
+ # - To change cookies stored in the engine, use {#cookielist} / {#cookielist=} or
385
+ # {#set} with :cookielist.
386
+ # - To clear a previously set manual Cookie header, assign an empty string ('').
387
+ # Assigning +nil+ has no effect in current curb versions.
388
+ #
202
389
  def cookies=(value)
203
390
  set :cookie, value
204
391
  end
205
392
 
206
- #
393
+ #
207
394
  # call-seq:
208
395
  # easy.cookiefile = string => string
209
- #
396
+ #
210
397
  # Set a file that contains cookies to be sent in subsequent requests by this Curl::Easy instance.
211
- #
398
+ #
212
399
  # *Note* that you must set enable_cookies true to enable the cookie
213
400
  # engine, or this option will be ignored.
214
- #
401
+ #
402
+ # Note: assigning +nil+ has no effect; pass a path string to use a cookie file.
403
+ #
215
404
  def cookiefile=(value)
216
405
  set :cookiefile, value
217
406
  end
218
407
 
219
- #
408
+ #
220
409
  # call-seq:
221
410
  # easy.cookiejar = string => string
222
- #
223
- # Set a cookiejar file to use for this Curl::Easy instance.
224
- # Cookies from the response will be written into this file.
225
- #
411
+ #
412
+ # Set a cookiejar file to use for this Curl::Easy instance. Cookies from the response
413
+ # will be written into this file.
414
+ #
226
415
  # *Note* that you must set enable_cookies true to enable the cookie
227
416
  # engine, or this option will be ignored.
228
- #
417
+ #
418
+ # Note: assigning +nil+ has no effect; pass a path string to persist cookies to a file.
419
+ #
229
420
  def cookiejar=(value)
230
421
  set :cookiejar, value
231
422
  end
232
423
 
424
+ #
425
+ # call-seq:
426
+ # easy.cookielist = string => string
427
+ #
428
+ # Modify cookies in libcurl's internal cookie engine (CURLOPT_COOKIELIST).
429
+ # Accepts a Set-Cookie style string, one or more lines in Netscape cookie file format,
430
+ # or one of the special commands: "ALL" (clear), "SESS" (remove session cookies),
431
+ # "FLUSH" (write to jar), "RELOAD" (reload from file).
432
+ #
433
+ # Examples:
434
+ # easy.cookielist = "Set-Cookie: session=42; Domain=example.com; Path=/;"
435
+ # easy.cookielist = [
436
+ # ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c1', 'v1'].join("\t"),
437
+ # ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c2', 'v2'].join("\t"),
438
+ # ''
439
+ # ].join("\n")
440
+ # easy.cookielist = 'ALL' # clear all cookies in the engine
441
+ #
442
+ def cookielist=(value)
443
+ set :cookielist, value
444
+ end
445
+
233
446
  #
234
447
  # call-seq:
235
448
  # easy = Curl::Easy.new("url") do|c|
@@ -300,7 +513,7 @@ module Curl
300
513
 
301
514
  #
302
515
  # call-seq:
303
- # Curl::Easy.perform(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
516
+ # Curl::Easy.perform(url) { |easy| ... } => #<Curl::Easy...>
304
517
  #
305
518
  # Convenience method that creates a new Curl::Easy instance with
306
519
  # the specified URL and calls the general +perform+ method, before returning
@@ -318,7 +531,7 @@ module Curl
318
531
 
319
532
  #
320
533
  # call-seq:
321
- # Curl::Easy.http_get(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
534
+ # Curl::Easy.http_get(url) { |easy| ... } => #<Curl::Easy...>
322
535
  #
323
536
  # Convenience method that creates a new Curl::Easy instance with
324
537
  # the specified URL and calls +http_get+, before returning the new instance.
@@ -335,7 +548,7 @@ module Curl
335
548
 
336
549
  #
337
550
  # call-seq:
338
- # Curl::Easy.http_head(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
551
+ # Curl::Easy.http_head(url) { |easy| ... } => #<Curl::Easy...>
339
552
  #
340
553
  # Convenience method that creates a new Curl::Easy instance with
341
554
  # the specified URL and calls +http_head+, before returning the new instance.
@@ -353,13 +566,36 @@ module Curl
353
566
  #
354
567
  # call-seq:
355
568
  # Curl::Easy.http_put(url, data) {|c| ... }
569
+ # Curl::Easy.http_put(url, "some=urlencoded%20form%20data&and=so%20on") => true
570
+ # Curl::Easy.http_put(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true
571
+ # Curl::Easy.http_put(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
572
+ # Curl::Easy.http_put(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true
356
573
  #
357
574
  # see easy.http_put
358
575
  #
359
- def http_put(url, data)
360
- c = Curl::Easy.new url
576
+ def http_put(*args)
577
+ url = args.shift
578
+ c = Curl::Easy.new(url)
361
579
  yield c if block_given?
362
- c.http_put data
580
+ c.http_put(*args)
581
+ c
582
+ end
583
+
584
+ #
585
+ # call-seq:
586
+ # Curl::Easy.http_patch(url, data) {|c| ... }
587
+ # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data&and=so%20on") => true
588
+ # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true
589
+ # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
590
+ # Curl::Easy.http_patch(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true
591
+ #
592
+ # see easy.http_patch
593
+ #
594
+ def http_patch(*args)
595
+ url = args.shift
596
+ c = Curl::Easy.new(url)
597
+ yield c if block_given?
598
+ c.http_patch(*args)
363
599
  c
364
600
  end
365
601
 
@@ -389,7 +625,7 @@ module Curl
389
625
 
390
626
  #
391
627
  # call-seq:
392
- # Curl::Easy.http_delete(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
628
+ # Curl::Easy.http_delete(url) { |easy| ... } => #<Curl::Easy...>
393
629
  #
394
630
  # Convenience method that creates a new Curl::Easy instance with
395
631
  # the specified URL and calls +http_delete+, before returning the new instance.
@@ -403,35 +639,35 @@ module Curl
403
639
  c.http_delete
404
640
  c
405
641
  end
406
-
642
+
407
643
  # call-seq:
408
644
  # Curl::Easy.download(url, filename = url.split(/\?/).first.split(/\//).last) { |curl| ... }
409
- #
645
+ #
410
646
  # Stream the specified url (via perform) and save the data directly to the
411
- # supplied filename (defaults to the last component of the URL path, which will
647
+ # supplied filename (defaults to the last component of the URL path, which will
412
648
  # usually be the filename most simple urls).
413
- #
649
+ #
414
650
  # If a block is supplied, it will be passed the curl instance prior to the
415
651
  # perform call.
416
- #
652
+ #
417
653
  # *Note* that the semantics of the on_body handler are subtly changed when using
418
- # download, to account for the automatic routing of data to the specified file: The
654
+ # download, to account for the automatic routing of data to the specified file: The
419
655
  # data string is passed to the handler *before* it is written
420
- # to the file, allowing the handler to perform mutative operations where
656
+ # to the file, allowing the handler to perform mutative operations where
421
657
  # necessary. As usual, the transfer will be aborted if the on_body handler
422
- # returns a size that differs from the data chunk size - in this case, the
658
+ # returns a size that differs from the data chunk size - in this case, the
423
659
  # offending chunk will *not* be written to the file, the file will be closed,
424
660
  # and a Curl::Err::AbortedByCallbackError will be raised.
425
661
  def download(url, filename = url.split(/\?/).first.split(/\//).last, &blk)
426
662
  curl = Curl::Easy.new(url, &blk)
427
-
663
+
428
664
  output = if filename.is_a? IO
429
665
  filename.binmode if filename.respond_to?(:binmode)
430
666
  filename
431
667
  else
432
668
  File.open(filename, 'wb')
433
669
  end
434
-
670
+
435
671
  begin
436
672
  old_on_body = curl.on_body do |data|
437
673
  result = old_on_body ? old_on_body.call(data) : data.length
@@ -442,7 +678,7 @@ module Curl
442
678
  ensure
443
679
  output.close rescue IOError
444
680
  end
445
-
681
+
446
682
  return curl
447
683
  end
448
684
  end