curb 1.3.6 → 1.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11348a158e7da3d3406acd021be1a15f881ce2c607ff04e60b522a3bd92cd828
4
- data.tar.gz: 0c524ce914df77b109a2aad85289386b2bf54028733fcf4ec5657a84abf511b8
3
+ metadata.gz: 9f3088ba29db67ea56c9587898267648a704c8acbcb9e95cb58318c3464deb12
4
+ data.tar.gz: 40134cd41f0704c2aaa3b65ca6507987f6e8bc755c4530c9361ccd25f4831c17
5
5
  SHA512:
6
- metadata.gz: b4ebd6b04943bfc099a6593ede1c70298555ed1a6613958e8628cde43c30b8c7f8b86990d91c7a350481589495fe7d5dc4ad1ac3c313823bb7fb21e483e8910c
7
- data.tar.gz: 0a78bc41496259eae9989d25e4a47c57a9a9aed6f79b00e77efae691b487a730e71e6cdf8081ae35d33893a84b033219ae24c40cf47effd8e3d1b0ca3591f222
6
+ metadata.gz: 29ffbf5bb4301db5729aff433a938251622c8483a29f5fa0f5344b52573048964c9863d00d254b42d01ff172291a1b469ea10f1fa9757b58fe4743a66c7078a5
7
+ data.tar.gz: d2e21e48891f398af31a7b2dd8d99a2465f82bb97d9caecda8ac4cb69fb599e894c3172bc56a6106a28d7761b03743fc78ee80701b3051ac673b7b6f1ed27ea9
data/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # Curb - Libcurl bindings for Ruby
2
2
 
3
- [![CI](https://github.com/taf2/curb/actions/workflows/ci.yml/badge.svg)](https://github.com/taf2/curb/actions/workflows/ci.yml)
4
- [![codecov](https://codecov.io/gh/taf2/curb/branch/master/graph/badge.svg)](https://codecov.io/gh/taf2/curb)
3
+ [![CI](https://github.com/taf2/curb/actions/workflows/CI.yml/badge.svg)](https://github.com/taf2/curb/actions/workflows/CI.yml)
5
4
  [![Gem Version](https://badge.fury.io/rb/curb.svg)](https://badge.fury.io/rb/curb)
6
5
 
7
- * [CI Build Status](https://github.com/taf2/curb/actions/workflows/ci.yml)
6
+ * [CI Build Status](https://github.com/taf2/curb/actions/workflows/CI.yml)
8
7
  * [rubydoc rdoc](http://www.rubydoc.info/github/taf2/curb/)
9
8
  * [github project](http://github.com/taf2/curb/tree/master)
10
9
 
@@ -23,8 +22,8 @@ Ruby license. See the LICENSE file for the gory details.
23
22
 
24
23
  ## Easy mode
25
24
 
26
- GET request
27
- ```
25
+ ### GET request
26
+ ```ruby
28
27
  res = Curl.get("https://www.google.com/") {|http|
29
28
  http.timeout = 10 # raise exception if request/response not handled within 10 seconds
30
29
  }
@@ -33,8 +32,8 @@ GET request
33
32
  puts res.body
34
33
  ```
35
34
 
36
- POST request
37
- ```
35
+ ### POST request
36
+ ```ruby
38
37
  res = Curl.post("https://your-server.com/endpoint", {post: "this"}.to_json) {|http|
39
38
  http.headers["Content-Type"] = "application/json"
40
39
  }
@@ -45,7 +44,9 @@ POST request
45
44
 
46
45
  ## FTP Support
47
46
 
47
+ ```ruby
48
48
  require 'curb'
49
+ ```
49
50
 
50
51
  ### Basic FTP Download
51
52
  ```ruby
@@ -139,7 +140,7 @@ puts list.body
139
140
  ```
140
141
 
141
142
  ### Advanced FTP Usage with Various Options
142
- ```
143
+ ```ruby
143
144
  puts "\n=== Advanced FTP Example ==="
144
145
  advanced = Curl::Easy.new do |curl|
145
146
  curl.url = 'ftp://ftp.example.com/remote/file.txt'
@@ -175,7 +176,7 @@ advanced.perform
175
176
  ```
176
177
 
177
178
  ### Parallel FTP Downloads
178
- ```
179
+ ```ruby
179
180
  puts "\n=== Parallel FTP Downloads Example ==="
180
181
  urls = [
181
182
  'ftp://ftp.example.com/file1.txt',
@@ -185,7 +186,7 @@ urls = [
185
186
  ```
186
187
 
187
188
  ### Common options for all connections
188
- ```
189
+ ```ruby
189
190
  options = {
190
191
  :username => 'user',
191
192
  :password => 'password',
@@ -204,7 +205,7 @@ end
204
205
  `curb` is a libcurl binding and intentionally supports protocols beyond HTTP.
205
206
  Do not pass untrusted URLs to `Curl.get`, `Curl::Easy.new`, or related raw
206
207
  helpers without application-level validation. For user-supplied URLs, enable the
207
- process-wide safety policy before making requests:
208
+ safety policy for the current Ractor before making requests:
208
209
 
209
210
  ```ruby
210
211
  Curl.safe! do |config|
@@ -256,11 +257,47 @@ configured. For untrusted or large responses, use `on_body`, `download`, and/or
256
257
  `max_body_bytes` is enforced for downloads as well as buffered responses and
257
258
  custom body callbacks.
258
259
 
260
+ ## Ractor support
261
+
262
+ On Ruby 3.0+, curb can perform requests from multiple Ractors when it is built
263
+ against a libcurl that reports the `CURL_VERSION_THREADSAFE` capability
264
+ (libcurl 7.84.0 or newer). Check `Curl::RACTOR_SAFE` at runtime. Builds that do
265
+ not meet those requirements continue to work in the main Ractor but do not
266
+ advertise native Ractor safety.
267
+
268
+ Create `Curl::Easy` and `Curl::Multi` handles inside the Ractor that uses them;
269
+ do not operate on the same native handle concurrently from different Ractors.
270
+ Safety configuration, `Curl::Multi.default_timeout`, `Curl::Multi.autoclose`,
271
+ and deferred cleanup queues are isolated per Ractor. Call `Curl.safe!` in each
272
+ Ractor that needs the safety policy:
273
+
274
+ ```ruby
275
+ raise "this curb build is not Ractor-safe" unless Curl::RACTOR_SAFE
276
+
277
+ workers = 4.times.map do
278
+ Ractor.new do
279
+ Curl.safe! { |config| config.protocols = [:http, :https] }
280
+ easy = Curl::Easy.new("https://example.com/")
281
+ easy.perform
282
+ easy.response_code
283
+ end
284
+ end
285
+
286
+ # Ruby 4.0 uses Ractor#value; earlier Ractor releases use Ractor#take.
287
+ statuses = workers.map do |worker|
288
+ worker.respond_to?(:value) ? worker.value : worker.take
289
+ end
290
+ ```
291
+
292
+ Curb enables `CURLOPT_NOSIGNAL` on newly initialized and reset Easy handles by
293
+ default so separate handles can be used safely from parallel Ruby execution.
294
+ Applications can still override the option explicitly when required.
295
+
259
296
  ## You will need
260
297
 
261
298
  * A working Ruby installation (`2.0.0+` will work but `2.1+` preferred) (it's possible it still works with 1.8.7 but you'd have to tell me if not...)
262
299
  * A working libcurl development installation
263
- (Ideally one of the versions listed in the compatibility chart below that maps to your `curb` version)
300
+ (Ideally one of the versions listed in the compatibility chart below that maps to your `curb` version)
264
301
  * A sane build environment (e.g. gcc, make)
265
302
 
266
303
  ## Version Compatibility chart
@@ -269,8 +306,28 @@ A **non-exhaustive** set of compatibility versions of the libcurl library
269
306
  with this gem are as follows. (Note that these are only the ones that have been
270
307
  tested and reported to work across a variety of platforms / rubies)
271
308
 
309
+ The upper bounds for recent releases reflect the compatibility work documented
310
+ in the changelog, including the libcurl 8.16.0 regression workaround in 1.2.2
311
+ and the libcurl 8.20.0 scheduler fixes in 1.3.5. The 1.3.7 upper bound also
312
+ includes expected compatibility with libcurl 8.21.0; this version is not yet
313
+ an explicit CI target.
314
+
272
315
  | Gem Version | Release Date | libcurl versions |
273
316
  | ----------- | -------------- | ----------------- |
317
+ | 1.4.0 | Sep 27, 2026 | 7.58 – 8.21.0 |
318
+ | 1.3.7 | Jul 10, 2026 | 7.58 – 8.21.0 |
319
+ | 1.3.6 | Jun 17, 2026 | 7.58 – 8.20.0 |
320
+ | 1.3.5 | May 14, 2026 | 7.58 – 8.20.0 |
321
+ | 1.3.4 | May 12, 2026 | 7.58 – 8.16.0 |
322
+ | 1.3.3 | May 11, 2026 | 7.58 – 8.16.0 |
323
+ | 1.3.2 | Apr 23, 2026 | 7.58 – 8.16.0 |
324
+ | 1.3.1 | Apr 05, 2026 | 7.58 – 8.16.0 |
325
+ | 1.3.0 | Apr 02, 2026 | 7.58 – 8.16.0 |
326
+ | 1.2.2 | Sep 18, 2025 | 7.58 – 8.16.0 |
327
+ | 1.2.1 | Sep 17, 2025 | 7.58 – 8.12.1 |
328
+ | 1.2.0 | Aug 31, 2025 | 7.58 – 8.12.1 |
329
+ | 1.1.0 | Jul 24, 2025 | 7.58 – 8.12.1 |
330
+ | 1.0.9 | Feb 15, 2025 | 7.58 – 8.12.1 |
274
331
  | 1.0.8 | Feb 10, 2025 | 7.58 – 8.12.1 |
275
332
  | 1.0.7 | Feb 09, 2025 | 7.58 – 8.12.1 |
276
333
  | 1.0.6 | Aug 23, 2024 | 7.58 – 8.12.1 |
@@ -287,7 +344,7 @@ tested and reported to work across a variety of platforms / rubies)
287
344
  | 0.9.4 | Aug 2017 | 7.41 – 7.58 |
288
345
  | 0.9.3 | Apr 2016 | 7.26 – 7.58 |
289
346
 
290
- ```*avoid using these version are known to have issues with segmentation faults```
347
+ *Avoid using these versions; they are known to have issues with segmentation faults.*
291
348
 
292
349
  ## Installation...
293
350
 
@@ -457,6 +514,7 @@ c = Curl::Easy.http_post("http://my.rails.box/thing/create",
457
514
  c = Curl::Easy.new("http://my.rails.box/files/upload")
458
515
  c.multipart_form_post = true
459
516
  c.http_post(Curl::PostField.file('thing[file]', 'myfile.rb'))
517
+ ```
460
518
 
461
519
  ### Custom request target
462
520
 
@@ -470,7 +528,6 @@ c.perform
470
528
  ```
471
529
 
472
530
  For HTTPS, prefer `easy.resolve = ["host:443:IP"]` to keep Host/SNI/certificates aligned.
473
- ```
474
531
 
475
532
  ### Using HTTP/2
476
533
 
data/Rakefile CHANGED
@@ -126,7 +126,18 @@ end
126
126
 
127
127
  RubyMemcheck.config(**ruby_memcheck_config)
128
128
  namespace :test do
129
- RubyMemcheck::TestTask.new(valgrind: :compile) do|t|
129
+ # Ruby pools fiber stacks next to each other, about 1MB apart. Valgrind
130
+ # treats a stack-pointer jump smaller than --max-stackframe (2MB) as an
131
+ # ordinary call or return rather than a switch to another stack, and marks
132
+ # the memory in between, which can hold another fiber's stack, as
133
+ # inaccessible. Later reads of that fiber's stack (turning a block into a
134
+ # Proc, GC marking suspended fibers) are then reported as invalid. Spacing
135
+ # fiber stacks wider than that lets valgrind recognize every fiber switch.
136
+ task :valgrind_env do
137
+ ENV['RUBY_FIBER_MACHINE_STACK_SIZE'] ||= (4 * 1024 * 1024).to_s
138
+ end
139
+
140
+ RubyMemcheck::TestTask.new(valgrind: [:compile, :valgrind_env]) do|t|
130
141
  t.test_files = FileList['tests/tc_*.rb']
131
142
  t.verbose = false
132
143
  end
data/ext/curb.c CHANGED
@@ -243,25 +243,38 @@ static void finalize_curb_core(VALUE data) {
243
243
 
244
244
  void Init_curb_core() {
245
245
  curl_version_info_data *ver;
246
- VALUE curlver, curllongver, curlvernum;
246
+ VALUE curbver, curlver, curllongver, curlvernum;
247
+ int ractor_safe = 0;
247
248
 
248
249
  curl_global_init(CURL_GLOBAL_ALL);
249
250
  rb_set_end_proc(finalize_curb_core, Qnil);
250
251
  ver = curl_version_info(CURLVERSION_NOW);
251
252
 
253
+ #if defined(HAVE_RB_EXT_RACTOR_SAFE) && defined(HAVE_CURL_VERSION_THREADSAFE)
254
+ /* Ractors execute native methods in parallel. Only opt in when both Ruby
255
+ * provides the safety marker and libcurl advertises thread-safe global
256
+ * initialization. Individual Curl handles remain Ractor-owned. */
257
+ if (ver && (ver->features & CURL_VERSION_THREADSAFE)) {
258
+ rb_ext_ractor_safe(1);
259
+ ractor_safe = 1;
260
+ }
261
+ #endif
262
+
252
263
  mCurl = rb_define_module("Curl");
253
264
 
254
- curlver = rb_str_new2(ver->version);
255
- curllongver = rb_str_new2(curl_version());
265
+ curbver = rb_obj_freeze(rb_str_new2(CURB_VERSION));
266
+ curlver = rb_obj_freeze(rb_str_new2(ver->version));
267
+ curllongver = rb_obj_freeze(rb_str_new2(curl_version()));
256
268
  curlvernum = LONG2NUM(LIBCURL_VERSION_NUM);
257
269
 
258
- rb_define_const(mCurl, "CURB_VERSION", rb_str_new2(CURB_VERSION));
270
+ rb_define_const(mCurl, "CURB_VERSION", curbver);
259
271
  rb_define_const(mCurl, "VERSION", curlver);
260
272
  rb_define_const(mCurl, "CURL_VERSION", curlver);
261
273
  rb_define_const(mCurl, "VERNUM", curlvernum);
262
274
  rb_define_const(mCurl, "CURL_VERNUM", curlvernum);
263
275
  rb_define_const(mCurl, "LONG_VERSION", curllongver);
264
276
  rb_define_const(mCurl, "CURL_LONG_VERSION", curllongver);
277
+ rb_define_const(mCurl, "RACTOR_SAFE", ractor_safe ? Qtrue : Qfalse);
265
278
 
266
279
  /* Passed to on_debug handler to indicate that the data is informational text. */
267
280
  rb_define_const(mCurl, "CURLINFO_TEXT", LONG2NUM(CURLINFO_TEXT));
@@ -748,6 +761,15 @@ void Init_curb_core() {
748
761
  #ifdef HAVE_CURLOPT_FTP_CREATE_MISSING_DIRS
749
762
  CURB_DEFINE(CURLOPT_FTP_CREATE_MISSING_DIRS);
750
763
  #endif
764
+ #ifdef HAVE_CURLFTP_CREATE_DIR_NONE
765
+ CURB_DEFINE(CURLFTP_CREATE_DIR_NONE);
766
+ #endif
767
+ #ifdef HAVE_CURLFTP_CREATE_DIR
768
+ CURB_DEFINE(CURLFTP_CREATE_DIR);
769
+ #endif
770
+ #ifdef HAVE_CURLFTP_CREATE_DIR_RETRY
771
+ CURB_DEFINE(CURLFTP_CREATE_DIR_RETRY);
772
+ #endif
751
773
  #ifdef HAVE_CURLOPT_FTP_RESPONSE_TIMEOUT
752
774
  CURB_DEFINE(CURLOPT_FTP_RESPONSE_TIMEOUT);
753
775
  #endif
data/ext/curb.h CHANGED
@@ -28,11 +28,11 @@
28
28
  #include "curb_macros.h"
29
29
 
30
30
  // These should be managed from the Rake 'release' task.
31
- #define CURB_VERSION "1.3.6"
32
- #define CURB_VER_NUM 1036
31
+ #define CURB_VERSION "1.4.0"
32
+ #define CURB_VER_NUM 1040
33
33
  #define CURB_VER_MAJ 1
34
- #define CURB_VER_MIN 3
35
- #define CURB_VER_MIC 6
34
+ #define CURB_VER_MIN 4
35
+ #define CURB_VER_MIC 0
36
36
  #define CURB_VER_PATCH 0
37
37
 
38
38
 
data/ext/curb_easy.c CHANGED
@@ -66,6 +66,17 @@ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list);
66
66
  static size_t proc_data_handler(char *stream, size_t size, size_t nmemb, VALUE proc);
67
67
  static int curb_array_includes_string(VALUE list, VALUE value);
68
68
 
69
+ static void curb_easy_apply_thread_defaults(ruby_curl_easy *rbce) {
70
+ #ifdef HAVE_CURLOPT_NOSIGNAL
71
+ CURLcode ecode = curl_easy_setopt(rbce->curl, CURLOPT_NOSIGNAL, 1L);
72
+ if (ecode != CURLE_OK) {
73
+ raise_curl_easy_error_exception(ecode);
74
+ }
75
+ #else
76
+ (void)rbce;
77
+ #endif
78
+ }
79
+
69
80
  /* ================== CURL HANDLER FUNCS ==============*/
70
81
 
71
82
  static int curb_ipv4_is_unsafe_destination(const unsigned char *ip) {
@@ -1187,6 +1198,7 @@ static int proc_debug_handler(CURL *curl,
1187
1198
  static void curl_easy_mark(void *ptr) {
1188
1199
  ruby_curl_easy *rbce = (ruby_curl_easy *)ptr;
1189
1200
  if (rbce) {
1201
+ if (!NIL_P(rbce->self)) { rb_gc_mark(rbce->self); }
1190
1202
  if (!NIL_P(rbce->opts)) { rb_gc_mark(rbce->opts); }
1191
1203
  if (!NIL_P(rbce->multi)) { rb_gc_mark(rbce->multi); }
1192
1204
  if (!NIL_P(rbce->callback_error)) { rb_gc_mark(rbce->callback_error); }
@@ -1500,6 +1512,7 @@ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
1500
1512
  ruby_curl_easy_zero(rbce);
1501
1513
  rbce->self = self;
1502
1514
 
1515
+ curb_easy_apply_thread_defaults(rbce);
1503
1516
  curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
1504
1517
 
1505
1518
  rb_easy_set("url", url);
@@ -1658,6 +1671,7 @@ static VALUE ruby_curl_easy_close(VALUE self) {
1658
1671
  ruby_curl_easy_zero(rbce);
1659
1672
  rbce->self = self;
1660
1673
 
1674
+ curb_easy_apply_thread_defaults(rbce);
1661
1675
  curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, rbce->err_buf);
1662
1676
 
1663
1677
  /* give the new curl handle a reference back to the ruby object */
@@ -1702,6 +1716,7 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
1702
1716
  ruby_curl_easy_zero(rbce);
1703
1717
  rbce->self = self;
1704
1718
 
1719
+ curb_easy_apply_thread_defaults(rbce);
1705
1720
  curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
1706
1721
 
1707
1722
  /* reset clobbers the private setting, so reset it to self */
@@ -2551,6 +2566,41 @@ static VALUE ruby_curl_easy_proxy_type_get(VALUE self) {
2551
2566
  (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : \
2552
2567
  (!strncmp("any",node,3)) ? CURLAUTH_ANY : 0
2553
2568
  #endif
2569
+ /*
2570
+ * Convert the arguments to http_auth_types= / proxy_auth_types= into a
2571
+ * CURLAUTH bitmask. Accepts nil, an Integer mask, or one or more auth type
2572
+ * names (symbols/strings), either as separate args or as a single Array.
2573
+ */
2574
+ static long ruby_curl_easy_auth_types_to_mask(VALUE args_ary) {
2575
+ long i, len;
2576
+ char* node = NULL;
2577
+ long mask = 0;
2578
+ VALUE first;
2579
+
2580
+ /* easy.http_auth_types = [:basic, :digest] arrives as a single Array arg */
2581
+ if (RARRAY_LEN(args_ary) == 1 && RB_TYPE_P(rb_ary_entry(args_ary,0), T_ARRAY)) {
2582
+ args_ary = rb_ary_entry(args_ary,0);
2583
+ }
2584
+
2585
+ len = RARRAY_LEN(args_ary);
2586
+ first = rb_ary_entry(args_ary,0);
2587
+
2588
+ if (len == 1 && (first == Qnil || TYPE(first) == T_FIXNUM || TYPE(first) == T_BIGNUM)) {
2589
+ return first == Qnil ? 0 : NUM2LONG(first);
2590
+ }
2591
+
2592
+ // we could have multiple values, but they should be symbols
2593
+ // rb_obj_as_string always yields a String, even if to_s misbehaves;
2594
+ // RSTRING_PTR on a raw rb_funcall result would crash if #to_s returned
2595
+ // a non-String (e.g. a Fixnum, whose VALUE isn't a real pointer).
2596
+ for( i = 0; i < len; ++i ) {
2597
+ VALUE node_str = rb_obj_as_string(rb_ary_entry(args_ary,i));
2598
+ node = StringValuePtr(node_str);
2599
+ mask |= CURL_HTTPAUTH_STR_TO_NUM(node);
2600
+ }
2601
+ return mask;
2602
+ }
2603
+
2554
2604
  /*
2555
2605
  * call-seq:
2556
2606
  * easy.http_auth_types = fixnum or nil => fixnum or nil
@@ -2560,37 +2610,14 @@ static VALUE ruby_curl_easy_proxy_type_get(VALUE self) {
2560
2610
  * +perform+ calls. This is a bitmap made by ORing together the
2561
2611
  * Curl::CURLAUTH constants.
2562
2612
  */
2563
- static VALUE ruby_curl_easy_http_auth_types_set(int argc, VALUE *argv, VALUE self) {//VALUE self, VALUE http_auth_types) {
2613
+ static VALUE ruby_curl_easy_http_auth_types_set(int argc, VALUE *argv, VALUE self) {
2564
2614
  ruby_curl_easy *rbce;
2565
2615
  VALUE args_ary;
2566
- long i, len;
2567
- char* node = NULL;
2568
- long mask = 0;
2569
2616
 
2570
2617
  rb_scan_args(argc, argv, "*", &args_ary);
2571
2618
  TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2572
2619
 
2573
- len = RARRAY_LEN(args_ary);
2574
-
2575
- if (len == 1 && (rb_ary_entry(args_ary,0) == Qnil || TYPE(rb_ary_entry(args_ary,0)) == T_FIXNUM ||
2576
- TYPE(rb_ary_entry(args_ary,0)) == T_BIGNUM)) {
2577
- if (rb_ary_entry(args_ary,0) == Qnil) {
2578
- rbce->http_auth_types = 0;
2579
- }
2580
- else {
2581
- rbce->http_auth_types = NUM2LONG(rb_ary_entry(args_ary,0));
2582
- }
2583
- }
2584
- else {
2585
- // we could have multiple values, but they should be symbols
2586
- node = RSTRING_PTR(rb_funcall(rb_ary_entry(args_ary,0),rb_intern("to_s"),0));
2587
- mask = CURL_HTTPAUTH_STR_TO_NUM(node);
2588
- for( i = 1; i < len; ++i ) {
2589
- node = RSTRING_PTR(rb_funcall(rb_ary_entry(args_ary,i),rb_intern("to_s"),0));
2590
- mask |= CURL_HTTPAUTH_STR_TO_NUM(node);
2591
- }
2592
- rbce->http_auth_types = mask;
2593
- }
2620
+ rbce->http_auth_types = ruby_curl_easy_auth_types_to_mask(args_ary);
2594
2621
  return LONG2NUM(rbce->http_auth_types);
2595
2622
  }
2596
2623
 
@@ -2608,13 +2635,21 @@ static VALUE ruby_curl_easy_http_auth_types_get(VALUE self) {
2608
2635
  /*
2609
2636
  * call-seq:
2610
2637
  * easy.proxy_auth_types = fixnum or nil => fixnum or nil
2638
+ * easy.proxy_auth_types = [:basic,:digest,:digest_ie,:gssnegotiate, :ntlm, :any, :anysafe]
2611
2639
  *
2612
2640
  * Set the proxy authentication types that may be used for the following
2613
2641
  * +perform+ calls. This is a bitmap made by ORing together the
2614
2642
  * Curl::CURLAUTH constants.
2615
2643
  */
2616
- static VALUE ruby_curl_easy_proxy_auth_types_set(VALUE self, VALUE proxy_auth_types) {
2617
- CURB_IMMED_SETTER(ruby_curl_easy, proxy_auth_types, 0);
2644
+ static VALUE ruby_curl_easy_proxy_auth_types_set(int argc, VALUE *argv, VALUE self) {
2645
+ ruby_curl_easy *rbce;
2646
+ VALUE args_ary;
2647
+
2648
+ rb_scan_args(argc, argv, "*", &args_ary);
2649
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2650
+
2651
+ rbce->proxy_auth_types = ruby_curl_easy_auth_types_to_mask(args_ary);
2652
+ return LONG2NUM(rbce->proxy_auth_types);
2618
2653
  }
2619
2654
 
2620
2655
  /*
@@ -5581,6 +5616,17 @@ static VALUE ruby_curl_easy_last_error(VALUE self) {
5581
5616
  * @note Some options - like url or cookie - aren't set directly throught +curl_easy_setopt+, but stored in the Ruby object state.
5582
5617
  * @note When +curl_easy_setopt+ is called, return value is not checked here.
5583
5618
  */
5619
+ static long ruby_curl_easy_option_to_long(VALUE value) {
5620
+ if (value == Qtrue) {
5621
+ return 1L;
5622
+ }
5623
+ if (value == Qfalse) {
5624
+ return 0L;
5625
+ }
5626
+
5627
+ return NUM2LONG(rb_funcall(value, rb_intern("to_i"), 0));
5628
+ }
5629
+
5584
5630
  static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
5585
5631
  ruby_curl_easy *rbce;
5586
5632
  long option = NUM2LONG(opt);
@@ -5696,59 +5742,76 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
5696
5742
  /* FTP-specific toggles */
5697
5743
  #ifdef HAVE_CURLOPT_DIRLISTONLY
5698
5744
  case CURLOPT_DIRLISTONLY: {
5699
- int type = rb_type(val);
5700
- VALUE value;
5701
- if (type == T_TRUE) {
5702
- value = rb_int_new(1);
5703
- } else if (type == T_FALSE) {
5704
- value = rb_int_new(0);
5705
- } else {
5706
- value = rb_funcall(val, rb_intern("to_i"), 0);
5707
- }
5708
- curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, NUM2LONG(value));
5745
+ curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, ruby_curl_easy_option_to_long(val));
5709
5746
  } break;
5710
5747
  #endif
5748
+ #ifdef HAVE_CURLOPT_FTPPORT
5749
+ case CURLOPT_FTPPORT:
5750
+ curl_easy_setopt(rbce->curl, CURLOPT_FTPPORT, NIL_P(val) ? NULL : StringValueCStr(val));
5751
+ break;
5752
+ #endif
5753
+ #ifdef HAVE_CURLOPT_APPEND
5754
+ case CURLOPT_APPEND:
5755
+ curl_easy_setopt(rbce->curl, CURLOPT_APPEND, ruby_curl_easy_option_to_long(val));
5756
+ break;
5757
+ #endif
5711
5758
  #ifdef HAVE_CURLOPT_FTP_USE_EPSV
5712
- case CURLOPT_FTP_USE_EPSV: {
5713
- int type = rb_type(val);
5714
- VALUE value;
5715
- if (type == T_TRUE) {
5716
- value = rb_int_new(1);
5717
- } else if (type == T_FALSE) {
5718
- value = rb_int_new(0);
5719
- } else {
5720
- value = rb_funcall(val, rb_intern("to_i"), 0);
5721
- }
5722
- curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, NUM2LONG(value));
5723
- } break;
5759
+ case CURLOPT_FTP_USE_EPSV:
5760
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, ruby_curl_easy_option_to_long(val));
5761
+ break;
5724
5762
  #endif
5725
5763
  #ifdef HAVE_CURLOPT_FTP_USE_EPRT
5726
- case CURLOPT_FTP_USE_EPRT: {
5727
- int type = rb_type(val);
5728
- VALUE value;
5729
- if (type == T_TRUE) {
5730
- value = rb_int_new(1);
5731
- } else if (type == T_FALSE) {
5732
- value = rb_int_new(0);
5733
- } else {
5734
- value = rb_funcall(val, rb_intern("to_i"), 0);
5735
- }
5736
- curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, NUM2LONG(value));
5737
- } break;
5764
+ case CURLOPT_FTP_USE_EPRT:
5765
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, ruby_curl_easy_option_to_long(val));
5766
+ break;
5767
+ #endif
5768
+ #ifdef HAVE_CURLOPT_FTP_USE_PRET
5769
+ case CURLOPT_FTP_USE_PRET:
5770
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_PRET, ruby_curl_easy_option_to_long(val));
5771
+ break;
5772
+ #endif
5773
+ #ifdef HAVE_CURLOPT_FTP_CREATE_MISSING_DIRS
5774
+ case CURLOPT_FTP_CREATE_MISSING_DIRS:
5775
+ rb_easy_set("ftp_create_missing_dirs", val);
5776
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_CREATE_MISSING_DIRS, ruby_curl_easy_option_to_long(val));
5777
+ break;
5778
+ #endif
5779
+ #ifdef HAVE_CURLOPT_FTP_RESPONSE_TIMEOUT
5780
+ case CURLOPT_FTP_RESPONSE_TIMEOUT:
5781
+ rbce->ftp_response_timeout = ruby_curl_easy_option_to_long(val);
5782
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_RESPONSE_TIMEOUT, rbce->ftp_response_timeout);
5783
+ break;
5784
+ #endif
5785
+ #ifdef HAVE_CURLOPT_FTP_ALTERNATIVE_TO_USER
5786
+ case CURLOPT_FTP_ALTERNATIVE_TO_USER:
5787
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_ALTERNATIVE_TO_USER, NIL_P(val) ? NULL : StringValueCStr(val));
5788
+ break;
5738
5789
  #endif
5739
5790
  #ifdef HAVE_CURLOPT_FTP_SKIP_PASV_IP
5740
- case CURLOPT_FTP_SKIP_PASV_IP: {
5741
- int type = rb_type(val);
5742
- VALUE value;
5743
- if (type == T_TRUE) {
5744
- value = rb_int_new(1);
5745
- } else if (type == T_FALSE) {
5746
- value = rb_int_new(0);
5747
- } else {
5748
- value = rb_funcall(val, rb_intern("to_i"), 0);
5749
- }
5750
- curl_easy_setopt(rbce->curl, CURLOPT_FTP_SKIP_PASV_IP, NUM2LONG(value));
5751
- } break;
5791
+ case CURLOPT_FTP_SKIP_PASV_IP:
5792
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_SKIP_PASV_IP, ruby_curl_easy_option_to_long(val));
5793
+ break;
5794
+ #endif
5795
+ #ifdef HAVE_CURLOPT_FTPSSLAUTH
5796
+ case CURLOPT_FTPSSLAUTH:
5797
+ curl_easy_setopt(rbce->curl, CURLOPT_FTPSSLAUTH, ruby_curl_easy_option_to_long(val));
5798
+ break;
5799
+ #endif
5800
+ #ifdef HAVE_CURLOPT_FTP_SSL_CCC
5801
+ case CURLOPT_FTP_SSL_CCC:
5802
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_SSL_CCC, ruby_curl_easy_option_to_long(val));
5803
+ break;
5804
+ #endif
5805
+ #ifdef HAVE_CURLOPT_FTP_ACCOUNT
5806
+ case CURLOPT_FTP_ACCOUNT:
5807
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_ACCOUNT, NIL_P(val) ? NULL : StringValueCStr(val));
5808
+ break;
5809
+ #endif
5810
+ #ifdef HAVE_CURLOPT_FTP_FILEMETHOD
5811
+ case CURLOPT_FTP_FILEMETHOD:
5812
+ rbce->ftp_filemethod = ruby_curl_easy_option_to_long(val);
5813
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_FILEMETHOD, rbce->ftp_filemethod);
5814
+ break;
5752
5815
  #endif
5753
5816
  case CURLOPT_RANGE: {
5754
5817
  curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val));
@@ -5955,6 +6018,34 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
5955
6018
  return val;
5956
6019
  }
5957
6020
 
6021
+ #ifdef HAVE_CURLOPT_FTP_CREATE_MISSING_DIRS
6022
+ /*
6023
+ * call-seq:
6024
+ * easy.ftp_create_missing_dirs = boolean_or_mode => boolean_or_mode
6025
+ *
6026
+ * Configure whether libcurl creates missing directories for an FTP or SFTP
6027
+ * transfer. In addition to true and false, this accepts the
6028
+ * Curl::CURLFTP_CREATE_DIR_* mode constants when provided by libcurl.
6029
+ */
6030
+ static VALUE ruby_curl_easy_ftp_create_missing_dirs_set(VALUE self, VALUE value) {
6031
+ return ruby_curl_easy_set_opt(
6032
+ self,
6033
+ LONG2NUM(CURLOPT_FTP_CREATE_MISSING_DIRS),
6034
+ value
6035
+ );
6036
+ }
6037
+
6038
+ /*
6039
+ * call-seq:
6040
+ * easy.ftp_create_missing_dirs => boolean, number, or nil
6041
+ *
6042
+ * Return the configured missing-directory creation mode.
6043
+ */
6044
+ static VALUE ruby_curl_easy_ftp_create_missing_dirs_get(VALUE self) {
6045
+ CURB_OBJECT_HGETTER(ruby_curl_easy, ftp_create_missing_dirs);
6046
+ }
6047
+ #endif
6048
+
5958
6049
  /*
5959
6050
  * call-seq:
5960
6051
  * easy.getinfo(opt) => nil
@@ -6010,8 +6101,9 @@ static VALUE ruby_curl_easy_escape(VALUE self, VALUE svalue) {
6010
6101
 
6011
6102
  TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
6012
6103
 
6013
- /* NOTE: make sure the value is a string, if not call to_s */
6014
- if( rb_type(str) != T_STRING ) { str = rb_funcall(str,rb_intern("to_s"),0); }
6104
+ /* NOTE: make sure the value is a string; rb_obj_as_string always yields a
6105
+ * String, even if the object's to_s misbehaves and returns a non-String. */
6106
+ if( rb_type(str) != T_STRING ) { str = rb_obj_as_string(str); }
6015
6107
 
6016
6108
  #if (LIBCURL_VERSION_NUM >= 0x070f04)
6017
6109
  result = (char*)curl_easy_escape(rbce->curl, StringValuePtr(str), (int)RSTRING_LEN(str));
@@ -6041,6 +6133,11 @@ static VALUE ruby_curl_easy_unescape(VALUE self, VALUE str) {
6041
6133
 
6042
6134
  TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
6043
6135
 
6136
+ /* Coerce as its own statement before use below - StringValuePtr and
6137
+ * RSTRING_LEN are sibling args to the same call and C does not guarantee
6138
+ * left-to-right evaluation order between them. */
6139
+ StringValue(str);
6140
+
6044
6141
  #if (LIBCURL_VERSION_NUM >= 0x070f04)
6045
6142
  result = (char*)curl_easy_unescape(rbce->curl, StringValuePtr(str), (int)RSTRING_LEN(str), &rlen);
6046
6143
  #else
@@ -6075,7 +6172,7 @@ void init_curb_easy() {
6075
6172
  idNetworkPolicyNone = rb_intern("none");
6076
6173
  idNetworkPolicyPublic = rb_intern("public");
6077
6174
 
6078
- rbstrAmp = rb_str_new2("&");
6175
+ rbstrAmp = rb_obj_freeze(rb_str_new2("&"));
6079
6176
  rb_global_variable(&rbstrAmp);
6080
6177
 
6081
6178
  cCurlEasy = rb_define_class_under(mCurl, "Easy", rb_cObject);
@@ -6146,7 +6243,7 @@ void init_curb_easy() {
6146
6243
  rb_define_method(cCurlEasy, "proxy_type", ruby_curl_easy_proxy_type_get, 0);
6147
6244
  rb_define_method(cCurlEasy, "http_auth_types=", ruby_curl_easy_http_auth_types_set, -1);
6148
6245
  rb_define_method(cCurlEasy, "http_auth_types", ruby_curl_easy_http_auth_types_get, 0);
6149
- rb_define_method(cCurlEasy, "proxy_auth_types=", ruby_curl_easy_proxy_auth_types_set, 1);
6246
+ rb_define_method(cCurlEasy, "proxy_auth_types=", ruby_curl_easy_proxy_auth_types_set, -1);
6150
6247
  rb_define_method(cCurlEasy, "proxy_auth_types", ruby_curl_easy_proxy_auth_types_get, 0);
6151
6248
  rb_define_method(cCurlEasy, "max_redirects=", ruby_curl_easy_max_redirects_set, 1);
6152
6249
  rb_define_method(cCurlEasy, "max_redirects", ruby_curl_easy_max_redirects_get, 0);
@@ -6162,6 +6259,10 @@ void init_curb_easy() {
6162
6259
  rb_define_method(cCurlEasy, "dns_cache_timeout", ruby_curl_easy_dns_cache_timeout_get, 0);
6163
6260
  rb_define_method(cCurlEasy, "ftp_response_timeout=", ruby_curl_easy_ftp_response_timeout_set, 1);
6164
6261
  rb_define_method(cCurlEasy, "ftp_response_timeout", ruby_curl_easy_ftp_response_timeout_get, 0);
6262
+ #ifdef HAVE_CURLOPT_FTP_CREATE_MISSING_DIRS
6263
+ rb_define_method(cCurlEasy, "ftp_create_missing_dirs=", ruby_curl_easy_ftp_create_missing_dirs_set, 1);
6264
+ rb_define_method(cCurlEasy, "ftp_create_missing_dirs", ruby_curl_easy_ftp_create_missing_dirs_get, 0);
6265
+ #endif
6165
6266
  rb_define_method(cCurlEasy, "low_speed_limit=", ruby_curl_easy_low_speed_limit_set, 1);
6166
6267
  rb_define_method(cCurlEasy, "low_speed_limit", ruby_curl_easy_low_speed_limit_get, 0);
6167
6268
  rb_define_method(cCurlEasy, "low_speed_time=", ruby_curl_easy_low_speed_time_set, 1);
data/ext/curb_easy.h CHANGED
@@ -112,6 +112,7 @@ typedef struct {
112
112
  curb_cidr_rule *network_allowed_cidr_rules;
113
113
  char **network_allowed_hosts;
114
114
 
115
+ /* attachment_sequence of the multi at the time this easy was last added. */
115
116
  unsigned long multi_attachment_generation;
116
117
  curl_off_t downloaded_body_bytes;
117
118
  size_t network_allowed_cidr_rule_count;