curb 0.8.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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +579 -0
  3. data/Rakefile +85 -27
  4. data/doc.rb +48 -8
  5. data/ext/banned.h +32 -0
  6. data/ext/curb.c +563 -233
  7. data/ext/curb.h +19 -10
  8. data/ext/curb_easy.c +3229 -368
  9. data/ext/curb_easy.h +42 -0
  10. data/ext/curb_errors.c +117 -18
  11. data/ext/curb_errors.h +9 -5
  12. data/ext/curb_macros.h +33 -21
  13. data/ext/curb_multi.c +1904 -272
  14. data/ext/curb_multi.h +11 -3
  15. data/ext/curb_postfield.c +149 -77
  16. data/ext/curb_postfield.h +1 -0
  17. data/ext/curb_upload.c +38 -11
  18. data/ext/curb_upload.h +2 -0
  19. data/ext/extconf.rb +355 -35
  20. data/lib/curb.rb +1 -0
  21. data/lib/curl/download.rb +160 -0
  22. data/lib/curl/easy.rb +422 -88
  23. data/lib/curl/multi.rb +295 -56
  24. data/lib/curl.rb +676 -11
  25. data/tests/bug_crash_on_debug.rb +14 -28
  26. data/tests/bug_crash_on_progress.rb +32 -16
  27. data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
  28. data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
  29. data/tests/bug_follow_redirect_288.rb +83 -0
  30. data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
  31. data/tests/bug_issue102.rb +17 -0
  32. data/tests/bug_issue_noproxy.rb +56 -0
  33. data/tests/bug_issue_post_redirect.rb +93 -0
  34. data/tests/bug_issue_spnego.rb +41 -0
  35. data/tests/bug_multi_segfault.rb +1 -0
  36. data/tests/bug_poison.rb +29 -0
  37. data/tests/bug_raise_on_callback.rb +30 -0
  38. data/tests/helper.rb +400 -44
  39. data/tests/leak_trace.rb +237 -0
  40. data/tests/mem_check.rb +3 -0
  41. data/tests/tc_curl.rb +31 -1
  42. data/tests/tc_curl_download.rb +98 -7
  43. data/tests/tc_curl_easy.rb +985 -66
  44. data/tests/tc_curl_easy_cookielist.rb +277 -0
  45. data/tests/tc_curl_easy_request_target.rb +41 -0
  46. data/tests/tc_curl_easy_resolve.rb +48 -0
  47. data/tests/tc_curl_maxfilesize.rb +212 -0
  48. data/tests/tc_curl_multi.rb +1111 -51
  49. data/tests/tc_curl_native_coverage.rb +145 -0
  50. data/tests/tc_curl_network_policy.rb +1475 -0
  51. data/tests/tc_curl_postfield.rb +207 -30
  52. data/tests/tc_curl_protocols.rb +388 -0
  53. data/tests/tc_fiber_scheduler.rb +584 -0
  54. data/tests/tc_ftp_options.rb +39 -0
  55. data/tests/tc_gc_compact.rb +223 -0
  56. data/tests/tc_test_server_methods.rb +110 -0
  57. data/tests/timeout.rb +30 -6
  58. metadata +66 -31
  59. data/README +0 -194
data/lib/curl/easy.rb CHANGED
@@ -1,36 +1,159 @@
1
+ # frozen_string_literal: true
2
+ require 'curl/download'
1
3
  module Curl
2
4
  class Easy
5
+ class << self
6
+ def deferred_multi_close_mutex
7
+ @deferred_multi_close_mutex ||= Mutex.new
8
+ end
9
+
10
+ def deferred_multi_closes
11
+ deferred_multi_close_mutex.synchronize do
12
+ (@deferred_multi_closes ||= []).dup
13
+ end
14
+ end
15
+
16
+ def release_deferred_multi_close(multi, easy)
17
+ if easy && multi.requests[easy.object_id]
18
+ begin
19
+ multi.remove(easy)
20
+ rescue StandardError
21
+ # Deferred cleanup only applies to implicit single-easy multis, so
22
+ # clear any stale Ruby bookkeeping and continue closing the handle.
23
+ multi.instance_variable_set(:@requests, {})
24
+ end
25
+ else
26
+ multi.instance_variable_set(:@requests, {})
27
+ end
28
+
29
+ multi.instance_variable_set(:@deferred_close, false)
30
+ multi._close
31
+ true
32
+ rescue StandardError
33
+ false
34
+ end
3
35
 
36
+ def defer_multi_close(multi, easy, owner: Thread.current)
37
+ deferred_multi_close_mutex.synchronize do
38
+ @deferred_multi_closes ||= []
39
+ return if @deferred_multi_closes.any? { |entry| entry[:multi].equal?(multi) }
40
+
41
+ multi.instance_variable_set(:@deferred_close, true)
42
+ @deferred_multi_closes << { multi: multi, easy: easy, owner: owner }
43
+ end
44
+ end
45
+
46
+ def flush_deferred_multi_closes(all_threads: false)
47
+ pending = deferred_multi_close_mutex.synchronize do
48
+ @deferred_multi_closes ||= []
49
+
50
+ if all_threads
51
+ @deferred_multi_closes.shift(@deferred_multi_closes.length)
52
+ else
53
+ owner = Thread.current
54
+ remaining = []
55
+ current = []
56
+
57
+ @deferred_multi_closes.each do |entry|
58
+ if entry[:owner].equal?(owner)
59
+ current << entry
60
+ else
61
+ remaining << entry
62
+ end
63
+ end
64
+
65
+ @deferred_multi_closes = remaining
66
+ current
67
+ end
68
+ end
69
+
70
+ pending.each do |entry|
71
+ multi = entry[:multi]
72
+ easy = entry[:easy]
73
+
74
+ unless release_deferred_multi_close(multi, easy)
75
+ defer_multi_close(multi, easy, owner: entry[:owner])
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ at_exit do
82
+ flush_deferred_multi_closes(all_threads: true)
83
+ end
84
+
85
+ alias_method :_curb_native_close, :close
86
+ alias_method :_curb_native_multi_set, :multi=
87
+ alias_method :_curb_native_reset, :reset
88
+
89
+ def close
90
+ previous_multi = self.multi
91
+ result = _curb_native_close
92
+ __curb_clear_safety_override!
93
+ previous_multi.__send__(:__unregister_idle_easy_reference, self) if previous_multi
94
+ result
95
+ end
96
+
97
+ def multi=(multi)
98
+ previous_multi = self.multi
99
+ return multi if previous_multi.equal?(multi)
100
+
101
+ if previous_multi && previous_multi.requests[self.object_id]
102
+ previous_multi.remove(self)
103
+ end
104
+
105
+ result = _curb_native_multi_set(multi)
106
+ previous_multi.__send__(:__unregister_idle_easy_reference, self) if previous_multi
107
+ multi.__send__(:__register_idle_easy_reference, self) if multi
108
+ result
109
+ end
110
+
111
+ def reset
112
+ result = _curb_native_reset
113
+ __curb_clear_safety_override!
114
+ result
115
+ end
116
+
4
117
  alias post http_post
5
118
  alias put http_put
6
119
  alias body body_str
7
120
  alias head header_str
8
121
 
122
+ class Error < StandardError
123
+ attr_accessor :message, :code
124
+ def initialize(code, msg)
125
+ self.message = msg
126
+ self.code = code
127
+ end
128
+ end
129
+
9
130
  #
10
131
  # call-seq:
11
132
  # easy.status => String
12
133
  #
13
134
  def status
14
- parts = self.header_str.split(/\s/)
15
- status = []
16
- parts.shift
17
- while parts.size > 0 && parts.first != ''
18
- status << parts.shift
19
- end
20
- status.join(' ')
135
+ # Matches the last HTTP Status - following the HTTP protocol specification 'Status-Line = HTTP-Version SP Status-Code SP (Opt:)Reason-Phrase CRLF'
136
+ statuses = self.header_str.to_s.scan(/HTTP\/\d(\.\d)?\s(\d+\s.*)\r\n/).map {|match| match[1] }
137
+ statuses.last.strip if statuses.length > 0
21
138
  end
22
139
 
23
140
  #
24
141
  # call-seq:
25
142
  # easy.set :sym|Fixnum, value
26
- #
143
+ #
27
144
  # set options on the curl easy handle see http://curl.haxx.se/libcurl/c/curl_easy_setopt.html
28
145
  #
29
146
  def set(opt,val)
30
147
  if opt.is_a?(Symbol)
31
- setopt(sym2curl(opt), val)
148
+ option = sym2curl(opt)
32
149
  else
33
- setopt(opt.to_i, val)
150
+ option = opt.to_i
151
+ end
152
+
153
+ begin
154
+ setopt(option, val)
155
+ rescue TypeError
156
+ raise TypeError, "Curb doesn't support setting #{opt} [##{option}] option"
34
157
  end
35
158
  end
36
159
 
@@ -44,6 +167,84 @@ module Curl
44
167
  Curl.const_get("CURLOPT_#{opt.to_s.upcase}")
45
168
  end
46
169
 
170
+ def allowed_protocols=(protocols)
171
+ set_protocol_allowlist('CURLOPT_PROTOCOLS_STR', 'CURLOPT_PROTOCOLS', protocols)
172
+ end
173
+
174
+ def allowed_redirect_protocols=(protocols)
175
+ set_protocol_allowlist('CURLOPT_REDIR_PROTOCOLS_STR', 'CURLOPT_REDIR_PROTOCOLS', protocols)
176
+ end
177
+
178
+ def safe_http!
179
+ __curb_set_safety_override!(
180
+ :protocols => [:http, :https],
181
+ :redirect_protocols => [:http, :https]
182
+ )
183
+ end
184
+
185
+ private
186
+
187
+ def __curb_set_safety_override!(options)
188
+ @__curb_safety_override = (defined?(@__curb_safety_override) && @__curb_safety_override) ? @__curb_safety_override.dup : {}
189
+ @__curb_safety_override[:protocols] = Array(options[:protocols]).map { |protocol| protocol.to_s.downcase.to_sym } if options.key?(:protocols)
190
+ @__curb_safety_override[:redirect_protocols] = Array(options[:redirect_protocols]).map { |protocol| protocol.to_s.downcase.to_sym } if options.key?(:redirect_protocols)
191
+ @__curb_safety_override[:max_body_bytes] = options[:max_body_bytes] if options.key?(:max_body_bytes) && options[:max_body_bytes]
192
+ @__curb_safety_override_generation = __curb_safety_override_generation + 1
193
+ Curl.__send__(:apply_safety!, self) if Curl.respond_to?(:apply_safety!, true)
194
+ self
195
+ end
196
+
197
+ def __curb_clear_safety_override!
198
+ had_override = defined?(@__curb_safety_override) && @__curb_safety_override
199
+ return unless had_override
200
+ return if frozen?
201
+
202
+ @__curb_safety_override = nil
203
+ @__curb_safety_override_generation = __curb_safety_override_generation + 1
204
+ end
205
+
206
+ def __curb_safety_override
207
+ defined?(@__curb_safety_override) ? @__curb_safety_override : nil
208
+ end
209
+
210
+ def __curb_safety_override_generation
211
+ defined?(@__curb_safety_override_generation) ? @__curb_safety_override_generation : 0
212
+ end
213
+
214
+ def set_protocol_allowlist(string_option, bitmask_option, protocols)
215
+ protocol_names = Array(protocols).map { |protocol| protocol.to_s.downcase }
216
+ raise ArgumentError, "at least one protocol is required" if protocol_names.empty?
217
+
218
+ valid_names = %w[
219
+ dict file ftp ftps gopher gophers http https imap imaps ldap ldaps
220
+ mqtt pop3 pop3s rtmp rtmpe rtmps rtmpt rtmpte rtmpts rtsp scp sftp
221
+ smb smbs smtp smtps telnet tftp ws wss
222
+ ]
223
+
224
+ if Curl.const_defined?(string_option)
225
+ protocol_names.each do |name|
226
+ raise ArgumentError, "unsupported protocol: #{name.inspect}" unless valid_names.include?(name)
227
+ end
228
+
229
+ setopt(Curl.const_get(string_option), protocol_names.join(','))
230
+ elsif Curl.const_defined?(bitmask_option)
231
+ protocol_pairs = protocol_names.map do |name|
232
+ const_name = "CURLPROTO_#{name.upcase}"
233
+ raise ArgumentError, "unsupported protocol: #{name.inspect}" unless Curl.const_defined?(const_name)
234
+
235
+ [name, Curl.const_get(const_name)]
236
+ end
237
+
238
+ setopt(Curl.const_get(bitmask_option), protocol_pairs.inject(0) { |mask, pair| mask | pair.last })
239
+ else
240
+ raise NotImplementedError, "protocol allowlists are not supported by this libcurl"
241
+ end
242
+
243
+ protocols
244
+ end
245
+
246
+ public
247
+
47
248
  #
48
249
  # call-seq:
49
250
  # easy.perform => true
@@ -53,13 +254,62 @@ module Curl
53
254
  # the configured HTTP Verb.
54
255
  #
55
256
  def perform
56
- self.multi = Curl::Multi.new if self.multi.nil?
57
- self.multi.add self
58
- ret = self.multi.perform
257
+ Curl.__send__(:apply_safety!, self) if Curl.respond_to?(:apply_safety!, true)
258
+ self.class.flush_deferred_multi_closes
259
+
260
+ if Curl.scheduler_active? && self.multi.nil?
261
+ ret = Curl.perform_with_scheduler(self)
262
+ else
263
+ multi = self.multi
264
+ created_multi = multi.nil?
265
+ raised = false
266
+
267
+ if created_multi
268
+ multi = Curl::Multi.new
269
+ self.multi = multi
270
+ end
271
+
272
+ begin
273
+ multi.add(self)
274
+ ret = multi.perform
275
+ multi.remove(self) if self.multi == multi
276
+ rescue Exception
277
+ raised = true
278
+ raise
279
+ ensure
280
+ if created_multi
281
+ if raised
282
+ unless self.class.release_deferred_multi_close(multi, self)
283
+ self.class.defer_multi_close(multi, self)
284
+ end
285
+ self.multi = nil if self.multi == multi
286
+ elsif Curl::Multi.autoclose
287
+ multi.__send__(:_autoclose)
288
+ self.multi = nil if self.multi == multi
289
+ else
290
+ self.multi = multi
291
+ end
292
+ elsif Curl::Multi.autoclose
293
+ multi.__send__(:_autoclose)
294
+ self.multi = nil if self.multi == multi
295
+ else
296
+ self.multi = multi
297
+ end
298
+ end
299
+ end
300
+
301
+ if (callback_error = _take_callback_error)
302
+ raise callback_error
303
+ end
304
+
305
+ if respond_to?(:unsafe_destination_error) && (unsafe_destination_error = self.unsafe_destination_error)
306
+ raise Curl::Err::UnsafeDestinationError, unsafe_destination_error
307
+ end
59
308
 
60
309
  if self.last_result != 0 && self.on_failure.nil?
61
- error = Curl::Easy.error(self.last_result)
62
- raise error.first
310
+ err_class, err_summary = Curl::Easy.error(self.last_result)
311
+ err_detail = self.last_error
312
+ raise err_class.new([err_summary, err_detail].compact.join(": "))
63
313
  end
64
314
 
65
315
  ret
@@ -67,6 +317,7 @@ module Curl
67
317
 
68
318
  #
69
319
  # call-seq:
320
+
70
321
  #
71
322
  # easy = Curl::Easy.new
72
323
  # easy.nosignal = true
@@ -83,54 +334,59 @@ module Curl
83
334
  # easy.perform
84
335
  #
85
336
  def delete=(onoff)
86
- set :customrequest, onoff ? 'delete' : nil
337
+ set :customrequest, onoff ? 'DELETE' : nil
87
338
  onoff
88
339
  end
89
- #
340
+ #
90
341
  # call-seq:
91
- #
342
+ #
92
343
  # easy = Curl::Easy.new("url")
93
- # easy.version = Curl::HTTP_1_1
94
- # easy.version = Curl::HTTP_1_0
95
- # easy.version = Curl::HTTP_NONE
96
- #
344
+ # easy.version = Curl::HTTP_2_0
345
+ # easy.http_version = Curl::HTTP_1_1
346
+ # easy.http_version = Curl::HTTP_1_0
347
+ # easy.http_version = Curl::HTTP_NONE
348
+ #
97
349
  def version=(http_version)
98
- set :http_version, http_version
350
+ self.http_version = http_version
99
351
  end
100
352
 
101
- #
353
+ def version
354
+ http_version
355
+ end
356
+
357
+ #
102
358
  # call-seq:
103
359
  # easy.url = "http://some.url/" => "http://some.url/"
104
- #
360
+ #
105
361
  # Set the URL for subsequent calls to +perform+. It is acceptable
106
362
  # (and even recommended) to reuse Curl::Easy instances by reassigning
107
363
  # the URL between calls to +perform+.
108
- #
364
+ #
109
365
  def url=(u)
110
366
  set :url, u
111
367
  end
112
368
 
113
- #
369
+ #
114
370
  # call-seq:
115
371
  # easy.proxy_url = string => string
116
- #
372
+ #
117
373
  # Set the URL of the HTTP proxy to use for subsequent calls to +perform+.
118
374
  # The URL should specify the the host name or dotted IP address. To specify
119
375
  # port number in this string, append :[port] to the end of the host name.
120
376
  # The proxy string may be prefixed with [protocol]:// since any such prefix
121
377
  # will be ignored. The proxy's port number may optionally be specified with
122
378
  # the separate option proxy_port .
123
- #
379
+ #
124
380
  # When you tell the library to use an HTTP proxy, libcurl will transparently
125
381
  # convert operations to HTTP even if you specify an FTP URL etc. This may have
126
382
  # an impact on what other features of the library you can use, such as
127
383
  # FTP specifics that don't work unless you tunnel through the HTTP proxy. Such
128
384
  # tunneling is activated with proxy_tunnel = true.
129
- #
385
+ #
130
386
  # libcurl respects the environment variables *http_proxy*, *ftp_proxy*,
131
387
  # *all_proxy* etc, if any of those is set. The proxy_url option does however
132
388
  # override any possibly set environment variables.
133
- #
389
+ #
134
390
  # Starting with libcurl 7.14.1, the proxy host string given in environment
135
391
  # variables can be specified the exact same way as the proxy can be set with
136
392
  # proxy_url, including protocol prefix (http://) and embedded user + password.
@@ -139,99 +395,150 @@ module Curl
139
395
  set :proxy, url
140
396
  end
141
397
 
398
+ #
399
+ # call-seq:
400
+ # easy.request_target = string => string
401
+ #
402
+ # Set the request-target used in the HTTP request line (libcurl CURLOPT_REQUEST_TARGET).
403
+ # Useful for absolute-form request targets (e.g., when speaking to proxies) or
404
+ # special targets like "*" (OPTIONS *). Requires libcurl with CURLOPT_REQUEST_TARGET support.
405
+ #
406
+ def request_target=(value)
407
+ if Curl.const_defined?(:CURLOPT_REQUEST_TARGET)
408
+ set :request_target, value
409
+ else
410
+ raise NotImplementedError, "CURLOPT_REQUEST_TARGET is not supported by this libcurl"
411
+ end
412
+ end
413
+
142
414
  def ssl_verify_host=(value)
143
415
  value = 1 if value.class == TrueClass
144
416
  value = 0 if value.class == FalseClass
145
417
  self.ssl_verify_host_integer=value
146
418
  end
147
419
 
148
- #
420
+ #
149
421
  # call-seq:
150
422
  # easy.ssl_verify_host? => boolean
151
- #
423
+ #
152
424
  # Deprecated: call easy.ssl_verify_host instead
153
425
  # can be one of [0,1,2]
154
- #
426
+ #
155
427
  # Determine whether this Curl instance will verify that the server cert
156
428
  # is for the server it is known as.
157
- #
429
+ #
158
430
  def ssl_verify_host?
159
431
  ssl_verify_host.nil? ? false : (ssl_verify_host > 0)
160
432
  end
161
433
 
162
- #
434
+ #
163
435
  # call-seq:
164
436
  # easy.interface = string => string
165
- #
437
+ #
166
438
  # Set the interface name to use as the outgoing network interface.
167
439
  # The name can be an interface name, an IP address or a host name.
168
- #
440
+ #
169
441
  def interface=(value)
170
442
  set :interface, value
171
443
  end
172
444
 
173
- #
445
+ #
174
446
  # call-seq:
175
447
  # easy.userpwd = string => string
176
- #
448
+ #
177
449
  # Set the username/password string to use for subsequent calls to +perform+.
178
450
  # The supplied string should have the form "username:password"
179
- #
451
+ #
180
452
  def userpwd=(value)
181
453
  set :userpwd, value
182
454
  end
183
455
 
184
- #
456
+ #
185
457
  # call-seq:
186
458
  # easy.proxypwd = string => string
187
- #
459
+ #
188
460
  # Set the username/password string to use for proxy connection during
189
461
  # subsequent calls to +perform+. The supplied string should have the
190
462
  # form "username:password"
191
- #
463
+ #
192
464
  def proxypwd=(value)
193
465
  set :proxyuserpwd, value
194
466
  end
195
467
 
196
- #
468
+ #
197
469
  # call-seq:
198
470
  # easy.cookies = "name1=content1; name2=content2;" => string
199
- #
200
- # Set cookies to be sent by this Curl::Easy instance. The format of the string should
201
- # be NAME=CONTENTS, where NAME is the cookie name and CONTENTS is what the cookie should contain.
202
- # Set multiple cookies in one string like this: "name1=content1; name2=content2;" etc.
203
- #
471
+ #
472
+ # Set the manual Cookie request header for this Curl::Easy instance.
473
+ # The format of the string should be NAME=CONTENTS, where NAME is the cookie name and
474
+ # CONTENTS is what the cookie should contain. Set multiple cookies in one string like this:
475
+ # "name1=content1; name2=content2;".
476
+ #
477
+ # Notes:
478
+ # - This only affects the outgoing Cookie header (libcurl CURLOPT_COOKIE) and does NOT
479
+ # alter the internal libcurl cookie engine (which stores cookies from Set-Cookie).
480
+ # - To change cookies stored in the engine, use {#cookielist} / {#cookielist=} or
481
+ # {#set} with :cookielist.
482
+ # - To clear a previously set manual Cookie header, assign an empty string ('').
483
+ # Assigning +nil+ has no effect in current curb versions.
484
+ #
204
485
  def cookies=(value)
205
486
  set :cookie, value
206
487
  end
207
488
 
208
- #
489
+ #
209
490
  # call-seq:
210
491
  # easy.cookiefile = string => string
211
- #
492
+ #
212
493
  # Set a file that contains cookies to be sent in subsequent requests by this Curl::Easy instance.
213
- #
494
+ #
214
495
  # *Note* that you must set enable_cookies true to enable the cookie
215
496
  # engine, or this option will be ignored.
216
- #
497
+ #
498
+ # Note: assigning +nil+ has no effect; pass a path string to use a cookie file.
499
+ #
217
500
  def cookiefile=(value)
218
501
  set :cookiefile, value
219
502
  end
220
503
 
221
- #
504
+ #
222
505
  # call-seq:
223
506
  # easy.cookiejar = string => string
224
- #
225
- # Set a cookiejar file to use for this Curl::Easy instance.
226
- # Cookies from the response will be written into this file.
227
- #
507
+ #
508
+ # Set a cookiejar file to use for this Curl::Easy instance. Cookies from the response
509
+ # will be written into this file.
510
+ #
228
511
  # *Note* that you must set enable_cookies true to enable the cookie
229
512
  # engine, or this option will be ignored.
230
- #
513
+ #
514
+ # Note: assigning +nil+ has no effect; pass a path string to persist cookies to a file.
515
+ #
231
516
  def cookiejar=(value)
232
517
  set :cookiejar, value
233
518
  end
234
519
 
520
+ #
521
+ # call-seq:
522
+ # easy.cookielist = string => string
523
+ #
524
+ # Modify cookies in libcurl's internal cookie engine (CURLOPT_COOKIELIST).
525
+ # Accepts a Set-Cookie style string, one or more lines in Netscape cookie file format,
526
+ # or one of the special commands: "ALL" (clear), "SESS" (remove session cookies),
527
+ # "FLUSH" (write to jar), "RELOAD" (reload from file).
528
+ #
529
+ # Examples:
530
+ # easy.cookielist = "Set-Cookie: session=42; Domain=example.com; Path=/;"
531
+ # easy.cookielist = [
532
+ # ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c1', 'v1'].join("\t"),
533
+ # ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c2', 'v2'].join("\t"),
534
+ # ''
535
+ # ].join("\n")
536
+ # easy.cookielist = 'ALL' # clear all cookies in the engine
537
+ #
538
+ def cookielist=(value)
539
+ set :cookielist, value
540
+ end
541
+
235
542
  #
236
543
  # call-seq:
237
544
  # easy = Curl::Easy.new("url") do|c|
@@ -302,7 +609,7 @@ module Curl
302
609
 
303
610
  #
304
611
  # call-seq:
305
- # Curl::Easy.perform(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
612
+ # Curl::Easy.perform(url) { |easy| ... } => #<Curl::Easy...>
306
613
  #
307
614
  # Convenience method that creates a new Curl::Easy instance with
308
615
  # the specified URL and calls the general +perform+ method, before returning
@@ -320,7 +627,7 @@ module Curl
320
627
 
321
628
  #
322
629
  # call-seq:
323
- # Curl::Easy.http_get(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
630
+ # Curl::Easy.http_get(url) { |easy| ... } => #<Curl::Easy...>
324
631
  #
325
632
  # Convenience method that creates a new Curl::Easy instance with
326
633
  # the specified URL and calls +http_get+, before returning the new instance.
@@ -337,7 +644,7 @@ module Curl
337
644
 
338
645
  #
339
646
  # call-seq:
340
- # Curl::Easy.http_head(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
647
+ # Curl::Easy.http_head(url) { |easy| ... } => #<Curl::Easy...>
341
648
  #
342
649
  # Convenience method that creates a new Curl::Easy instance with
343
650
  # the specified URL and calls +http_head+, before returning the new instance.
@@ -355,13 +662,36 @@ module Curl
355
662
  #
356
663
  # call-seq:
357
664
  # Curl::Easy.http_put(url, data) {|c| ... }
665
+ # Curl::Easy.http_put(url, "some=urlencoded%20form%20data&and=so%20on") => true
666
+ # Curl::Easy.http_put(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true
667
+ # Curl::Easy.http_put(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
668
+ # Curl::Easy.http_put(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true
358
669
  #
359
670
  # see easy.http_put
360
671
  #
361
- def http_put(url, data)
362
- c = Curl::Easy.new url
672
+ def http_put(*args)
673
+ url = args.shift
674
+ c = Curl::Easy.new(url)
675
+ yield c if block_given?
676
+ c.http_put(*args)
677
+ c
678
+ end
679
+
680
+ #
681
+ # call-seq:
682
+ # Curl::Easy.http_patch(url, data) {|c| ... }
683
+ # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data&and=so%20on") => true
684
+ # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true
685
+ # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
686
+ # Curl::Easy.http_patch(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true
687
+ #
688
+ # see easy.http_patch
689
+ #
690
+ def http_patch(*args)
691
+ url = args.shift
692
+ c = Curl::Easy.new(url)
363
693
  yield c if block_given?
364
- c.http_put data
694
+ c.http_patch(*args)
365
695
  c
366
696
  end
367
697
 
@@ -391,7 +721,7 @@ module Curl
391
721
 
392
722
  #
393
723
  # call-seq:
394
- # Curl::Easy.http_delete(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
724
+ # Curl::Easy.http_delete(url) { |easy| ... } => #<Curl::Easy...>
395
725
  #
396
726
  # Convenience method that creates a new Curl::Easy instance with
397
727
  # the specified URL and calls +http_delete+, before returning the new instance.
@@ -405,35 +735,34 @@ module Curl
405
735
  c.http_delete
406
736
  c
407
737
  end
408
-
738
+
409
739
  # call-seq:
410
- # Curl::Easy.download(url, filename = url.split(/\?/).first.split(/\//).last) { |curl| ... }
411
- #
740
+ # Curl::Easy.download(url, filename = nil, options = {}) { |curl| ... }
741
+ #
412
742
  # Stream the specified url (via perform) and save the data directly to the
413
- # supplied filename (defaults to the last component of the URL path, which will
414
- # usually be the filename most simple urls).
415
- #
743
+ # supplied filename. The destination is written through a temporary file and
744
+ # existing files are not overwritten unless <tt>:overwrite => true</tt> is
745
+ # passed. When filename is omitted, the destination is safely derived from the
746
+ # last URL path component in the current directory. Pass
747
+ # <tt>:download_dir</tt> to treat the filename as a basename inside a trusted
748
+ # directory and reject absolute, parent-directory, dotfile, and nested names.
749
+ #
416
750
  # If a block is supplied, it will be passed the curl instance prior to the
417
751
  # perform call.
418
- #
752
+ #
419
753
  # *Note* that the semantics of the on_body handler are subtly changed when using
420
- # download, to account for the automatic routing of data to the specified file: The
754
+ # download, to account for the automatic routing of data to the specified file: The
421
755
  # data string is passed to the handler *before* it is written
422
- # to the file, allowing the handler to perform mutative operations where
756
+ # to the file, allowing the handler to perform mutative operations where
423
757
  # necessary. As usual, the transfer will be aborted if the on_body handler
424
- # returns a size that differs from the data chunk size - in this case, the
758
+ # returns a size that differs from the data chunk size - in this case, the
425
759
  # offending chunk will *not* be written to the file, the file will be closed,
426
760
  # and a Curl::Err::AbortedByCallbackError will be raised.
427
- def download(url, filename = url.split(/\?/).first.split(/\//).last, &blk)
761
+ def download(url, filename = nil, download_options = {}, &blk)
428
762
  curl = Curl::Easy.new(url, &blk)
429
-
430
- output = if filename.is_a? IO
431
- filename.binmode if filename.respond_to?(:binmode)
432
- filename
433
- else
434
- File.open(filename, 'wb')
435
- end
436
-
763
+ _download_path, output, safe_output = Curl.prepare_download_output(url, filename, download_options)
764
+
765
+ performed = false
437
766
  begin
438
767
  old_on_body = curl.on_body do |data|
439
768
  result = old_on_body ? old_on_body.call(data) : data.length
@@ -441,10 +770,15 @@ module Curl
441
770
  result
442
771
  end
443
772
  curl.perform
773
+ performed = true
444
774
  ensure
445
- output.close rescue IOError
775
+ if safe_output
776
+ output.close(performed)
777
+ else
778
+ output.close rescue IOError
779
+ end
446
780
  end
447
-
781
+
448
782
  return curl
449
783
  end
450
784
  end