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/ext/curb_easy.c CHANGED
@@ -11,13 +11,36 @@
11
11
  #include "curb_multi.h"
12
12
 
13
13
  #include <errno.h>
14
+ #include <stdlib.h>
14
15
  #include <string.h>
16
+ #ifndef _WIN32
17
+ #include <sys/types.h>
18
+ #include <sys/socket.h>
19
+ #include <netinet/in.h>
20
+ #include <arpa/inet.h>
21
+ #else
22
+ #include <winsock2.h>
23
+ #include <ws2tcpip.h>
24
+ #endif
25
+ #ifndef _WIN32
26
+ #include <strings.h>
27
+ #endif
28
+
29
+ #if defined(HAVE_CURLOPT_OPENSOCKETFUNCTION) && defined(HAVE_CURLOPT_OPENSOCKETDATA)
30
+ #define CURB_HAVE_OPENSOCKET_NETWORK_POLICY 1
31
+ #endif
32
+
33
+ #if defined(HAVE_CURLOPT_PREREQFUNCTION) && defined(HAVE_CURLOPT_PREREQDATA)
34
+ #define CURB_HAVE_PREREQ_HOST_POLICY 1
35
+ #endif
15
36
 
16
37
  extern VALUE mCurl;
17
38
 
18
39
  static VALUE idCall;
19
40
  static VALUE idJoin;
20
41
  static VALUE rbstrAmp;
42
+ static ID idNetworkPolicyNone;
43
+ static ID idNetworkPolicyPublic;
21
44
 
22
45
  #ifdef RDOC_NEVER_DEFINED
23
46
  mCurl = rb_define_module("Curl");
@@ -25,20 +48,859 @@ static VALUE rbstrAmp;
25
48
 
26
49
  VALUE cCurlEasy;
27
50
 
51
+ /* Internal wrapper type for passing pointers through rb_iterate callbacks.
52
+ * No mark/free needed - these are temporary wrappers that don't own memory. */
53
+ static const rb_data_type_t curl_slist_ptr_type = {
54
+ "curl_slist_ptr_wrapper",
55
+ { NULL, NULL, NULL },
56
+ NULL, NULL, 0
57
+ };
58
+
59
+ // for Ruby 1.8
60
+ #ifndef HAVE_RB_IO_STDIO_FILE
61
+ static FILE * rb_io_stdio_file(rb_io_t *fptr) {
62
+ return fptr->f;
63
+ }
64
+ #endif
65
+ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list);
66
+ static size_t proc_data_handler(char *stream, size_t size, size_t nmemb, VALUE proc);
67
+ static int curb_array_includes_string(VALUE list, VALUE value);
28
68
 
29
69
  /* ================== CURL HANDLER FUNCS ==============*/
30
70
 
31
- static VALUE callback_exception(VALUE unused) {
71
+ static int curb_ipv4_is_unsafe_destination(const unsigned char *ip) {
72
+ if (ip[0] == 0) return 1; /* 0.0.0.0/8 */
73
+ if (ip[0] == 10) return 1; /* RFC1918 */
74
+ if (ip[0] == 100 && (ip[1] & 0xc0) == 0x40) return 1; /* 100.64.0.0/10 */
75
+ if (ip[0] == 100 && ip[1] == 100 && ip[2] == 100 && ip[3] == 200) return 1;
76
+ if (ip[0] == 127) return 1; /* loopback */
77
+ if (ip[0] == 169 && ip[1] == 254) return 1; /* link-local / metadata */
78
+ if (ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31) return 1;/* RFC1918 */
79
+ if (ip[0] == 192 && ip[1] == 168) return 1; /* RFC1918 */
80
+ if (ip[0] == 192 && ip[1] == 0 && ip[2] == 0) return 1; /* IETF protocol assignments */
81
+ if (ip[0] == 192 && ip[1] == 0 && ip[2] == 2) return 1; /* TEST-NET-1 */
82
+ if (ip[0] == 198 && (ip[1] == 18 || ip[1] == 19)) return 1;
83
+ if (ip[0] == 198 && ip[1] == 51 && ip[2] == 100) return 1;
84
+ if (ip[0] == 203 && ip[1] == 0 && ip[2] == 113) return 1;
85
+ if (ip[0] >= 224) return 1; /* multicast/reserved */
86
+
87
+ return 0;
88
+ }
89
+
90
+ static int curb_ip_prefix_matches(const unsigned char *ip, const unsigned char *prefix, int bits) {
91
+ int full_bytes = bits / 8;
92
+ int remaining_bits = bits % 8;
93
+ int i;
94
+
95
+ for (i = 0; i < full_bytes; i++) {
96
+ if (ip[i] != prefix[i]) return 0;
97
+ }
98
+
99
+ if (remaining_bits) {
100
+ unsigned char mask = (unsigned char)(0xff << (8 - remaining_bits));
101
+ if ((ip[full_bytes] & mask) != (prefix[full_bytes] & mask)) return 0;
102
+ }
103
+
104
+ return 1;
105
+ }
106
+
107
+ static int curb_ipv6_prefix_matches(const unsigned char *ip, const unsigned char *prefix, int bits) {
108
+ return curb_ip_prefix_matches(ip, prefix, bits);
109
+ }
110
+
111
+ static int curb_ipv6_is_all_zero(const unsigned char *ip) {
112
+ int i;
113
+
114
+ for (i = 0; i < 16; i++) {
115
+ if (ip[i] != 0) return 0;
116
+ }
117
+
118
+ return 1;
119
+ }
120
+
121
+ static int curb_ipv6_is_ipv4_mapped(const unsigned char *ip) {
122
+ static const unsigned char prefix[12] = {
123
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff
124
+ };
125
+
126
+ return curb_ipv6_prefix_matches(ip, prefix, 96);
127
+ }
128
+
129
+ static int curb_ipv6_is_ipv4_compatible(const unsigned char *ip) {
130
+ static const unsigned char prefix[12] = {
131
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
132
+ };
133
+
134
+ return curb_ipv6_prefix_matches(ip, prefix, 96);
135
+ }
136
+
137
+ static int curb_ipv6_is_nat64_well_known(const unsigned char *ip) {
138
+ static const unsigned char prefix[12] = {
139
+ 0x00, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0
140
+ };
141
+
142
+ return curb_ipv6_prefix_matches(ip, prefix, 96);
143
+ }
144
+
145
+ static int curb_ipv6_is_nat64_local_use(const unsigned char *ip) {
146
+ static const unsigned char prefix[6] = {
147
+ 0x00, 0x64, 0xff, 0x9b, 0x00, 0x01
148
+ };
149
+
150
+ return curb_ipv6_prefix_matches(ip, prefix, 48);
151
+ }
152
+
153
+ static int curb_ipv6_is_unsafe_destination(const unsigned char *ip) {
154
+ static const unsigned char documentation_prefix[4] = { 0x20, 0x01, 0x0d, 0xb8 };
155
+ static const unsigned char benchmarking_prefix[6] = { 0x20, 0x01, 0x00, 0x02, 0, 0 };
156
+
157
+ if (curb_ipv6_is_all_zero(ip)) return 1; /* ::/128 */
158
+ if (curb_ipv6_is_ipv4_mapped(ip)) return curb_ipv4_is_unsafe_destination(ip + 12);
159
+ if (curb_ipv6_is_nat64_well_known(ip)) return curb_ipv4_is_unsafe_destination(ip + 12);
160
+ if (curb_ipv6_is_nat64_local_use(ip)) return 1;
161
+ if (curb_ipv6_is_ipv4_compatible(ip)) return 1; /* deprecated non-public space */
162
+ if (ip[0] == 0 && ip[1] == 0 && ip[14] == 0 && ip[15] == 1) return 1;
163
+ if ((ip[0] & 0xfe) == 0xfc) return 1; /* fc00::/7 */
164
+ if (ip[0] == 0xfe && (ip[1] & 0xc0) == 0x80) return 1; /* fe80::/10 */
165
+ if (ip[0] == 0xfe && (ip[1] & 0xc0) == 0xc0) return 1; /* fec0::/10 */
166
+ if (ip[0] == 0xff) return 1; /* multicast */
167
+ if (curb_ipv6_prefix_matches(ip, documentation_prefix, 32)) return 1;
168
+ if (curb_ipv6_prefix_matches(ip, benchmarking_prefix, 48)) return 1;
169
+ if (ip[0] == 0x20 && ip[1] == 0x02) return 1; /* 2002::/16 */
170
+
171
+ return 0;
172
+ }
173
+
174
+ static void curb_clear_network_allowed_cidr_rules(ruby_curl_easy *rbce) {
175
+ if (!rbce || !rbce->network_allowed_cidr_rules) return;
176
+
177
+ xfree(rbce->network_allowed_cidr_rules);
178
+ rbce->network_allowed_cidr_rules = NULL;
179
+ rbce->network_allowed_cidr_rule_count = 0;
180
+ }
181
+
182
+ static void curb_raise_invalid_cidr(char *tmp, const char *cidr) {
183
+ if (tmp) xfree(tmp);
184
+ rb_raise(rb_eArgError, "invalid CIDR range: %s", cidr);
185
+ }
186
+
187
+ static void curb_parse_cidr_rule(const char *cidr, curb_cidr_rule *rule) {
188
+ size_t len;
189
+ char *tmp;
190
+ char *address;
191
+ char *prefix_str = NULL;
192
+ char *slash;
193
+ long prefix = -1;
194
+ int max_prefix = 0;
195
+ unsigned char parsed[16];
196
+
197
+ if (!cidr || !rule) {
198
+ rb_raise(rb_eArgError, "invalid CIDR range");
199
+ }
200
+
201
+ len = strlen(cidr);
202
+ if (len == 0) {
203
+ rb_raise(rb_eArgError, "invalid CIDR range: %s", cidr);
204
+ }
205
+
206
+ tmp = ALLOC_N(char, len + 1);
207
+ memcpy(tmp, cidr, len + 1);
208
+ address = tmp;
209
+
210
+ if (address[0] == '[') {
211
+ char *closing = strchr(address, ']');
212
+ if (!closing) curb_raise_invalid_cidr(tmp, cidr);
213
+
214
+ *closing = '\0';
215
+ address++;
216
+
217
+ if (closing[1] == '/') {
218
+ prefix_str = closing + 2;
219
+ } else if (closing[1] != '\0') {
220
+ curb_raise_invalid_cidr(tmp, cidr);
221
+ }
222
+ } else {
223
+ slash = strchr(address, '/');
224
+ if (slash) {
225
+ *slash = '\0';
226
+ prefix_str = slash + 1;
227
+ }
228
+ }
229
+
230
+ memset(rule, 0, sizeof(curb_cidr_rule));
231
+
232
+ if (inet_pton(AF_INET, address, parsed) == 1) {
233
+ rule->family = CURB_CIDR_FAMILY_IPV4;
234
+ max_prefix = 32;
235
+ memcpy(rule->address, parsed, 4);
236
+ } else if (inet_pton(AF_INET6, address, parsed) == 1) {
237
+ rule->family = CURB_CIDR_FAMILY_IPV6;
238
+ max_prefix = 128;
239
+ memcpy(rule->address, parsed, 16);
240
+ } else {
241
+ curb_raise_invalid_cidr(tmp, cidr);
242
+ }
243
+
244
+ if (prefix_str) {
245
+ char *endptr = NULL;
246
+
247
+ if (prefix_str[0] == '\0') curb_raise_invalid_cidr(tmp, cidr);
248
+
249
+ errno = 0;
250
+ prefix = strtol(prefix_str, &endptr, 10);
251
+ if (errno != 0 || !endptr || *endptr != '\0' || prefix < 0 || prefix > max_prefix) {
252
+ curb_raise_invalid_cidr(tmp, cidr);
253
+ }
254
+ } else {
255
+ prefix = max_prefix;
256
+ }
257
+
258
+ rule->prefix_bits = (unsigned char)prefix;
259
+ xfree(tmp);
260
+ }
261
+
262
+ static VALUE curb_normalize_cidr_list(VALUE cidrs) {
263
+ VALUE list;
264
+ VALUE normalized;
265
+ long i;
266
+
267
+ if (NIL_P(cidrs)) return Qnil;
268
+
269
+ list = rb_check_array_type(cidrs);
270
+ if (NIL_P(list)) {
271
+ list = rb_ary_new_from_args(1, cidrs);
272
+ }
273
+
274
+ normalized = rb_ary_new_capa(RARRAY_LEN(list));
275
+ for (i = 0; i < RARRAY_LEN(list); i++) {
276
+ VALUE item = rb_ary_entry(list, i);
277
+ VALUE cidr = rb_obj_as_string(item);
278
+ curb_cidr_rule unused_rule;
279
+
280
+ curb_parse_cidr_rule(StringValueCStr(cidr), &unused_rule);
281
+ if (!curb_array_includes_string(normalized, cidr)) {
282
+ rb_ary_push(normalized, rb_str_dup(cidr));
283
+ }
284
+ }
285
+
286
+ return normalized;
287
+ }
288
+
289
+ static VALUE curb_dup_string_array(VALUE list) {
290
+ VALUE copy;
291
+ long i;
292
+
293
+ if (NIL_P(list)) return Qnil;
294
+
295
+ copy = rb_ary_new_capa(RARRAY_LEN(list));
296
+ for (i = 0; i < RARRAY_LEN(list); i++) {
297
+ rb_ary_push(copy, rb_str_dup(rb_ary_entry(list, i)));
298
+ }
299
+
300
+ return copy;
301
+ }
302
+
303
+ static int curb_array_includes_string(VALUE list, VALUE value) {
304
+ long i;
305
+
306
+ for (i = 0; i < RARRAY_LEN(list); i++) {
307
+ if (rb_str_cmp(rb_ary_entry(list, i), value) == 0) return 1;
308
+ }
309
+
310
+ return 0;
311
+ }
312
+
313
+ static char curb_ascii_downcase(char c) {
314
+ if (c >= 'A' && c <= 'Z') return (char)(c - 'A' + 'a');
315
+ return c;
316
+ }
317
+
318
+ static char *curb_normalized_host_from_range(const char *start, const char *end, int raise_errors) {
319
+ char *host;
320
+ size_t len;
321
+ size_t i;
322
+
323
+ while (start < end && (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n')) start++;
324
+ while (end > start && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\r' || end[-1] == '\n')) end--;
325
+ while (end > start && end[-1] == '.') end--;
326
+
327
+ if (end <= start) {
328
+ if (!raise_errors) return NULL;
329
+ rb_raise(rb_eArgError, "allowed_hosts cannot include blank entries");
330
+ }
331
+
332
+ len = (size_t)(end - start);
333
+ host = ALLOC_N(char, len + 1);
334
+ for (i = 0; i < len; i++) {
335
+ host[i] = curb_ascii_downcase(start[i]);
336
+ }
337
+ host[len] = '\0';
338
+
339
+ return host;
340
+ }
341
+
342
+ static char *curb_normalize_host_value_impl(const char *value, int raise_errors) {
343
+ const char *start;
344
+ const char *end;
345
+ const char *scheme;
346
+ const char *authority_end;
347
+ const char *scan;
348
+ const char *last_at = NULL;
349
+ int colon_count = 0;
350
+
351
+ if (!value) {
352
+ if (!raise_errors) return NULL;
353
+ rb_raise(rb_eArgError, "allowed_hosts cannot include blank entries");
354
+ }
355
+
356
+ start = value;
357
+ end = value + strlen(value);
358
+ while (start < end && (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n')) start++;
359
+ while (end > start && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\r' || end[-1] == '\n')) end--;
360
+ if (end <= start) {
361
+ if (!raise_errors) return NULL;
362
+ rb_raise(rb_eArgError, "allowed_hosts cannot include blank entries");
363
+ }
364
+
365
+ scheme = strstr(start, "://");
366
+ if (scheme && scheme < end) {
367
+ start = scheme + 3;
368
+ }
369
+
370
+ authority_end = end;
371
+ for (scan = start; scan < end; scan++) {
372
+ if (*scan == '/' || *scan == '?' || *scan == '#') {
373
+ authority_end = scan;
374
+ break;
375
+ }
376
+ }
377
+
378
+ for (scan = start; scan < authority_end; scan++) {
379
+ if (*scan == '@') last_at = scan;
380
+ }
381
+ if (last_at) start = last_at + 1;
382
+
383
+ if (start < authority_end && *start == '[') {
384
+ const char *closing = start + 1;
385
+ while (closing < authority_end && *closing != ']') closing++;
386
+ if (closing >= authority_end) {
387
+ if (!raise_errors) return NULL;
388
+ rb_raise(rb_eArgError, "invalid allowed host: %s", value);
389
+ }
390
+ return curb_normalized_host_from_range(start + 1, closing, raise_errors);
391
+ }
392
+
393
+ for (scan = start; scan < authority_end; scan++) {
394
+ if (*scan == ':') colon_count++;
395
+ }
396
+
397
+ if (colon_count <= 1) {
398
+ for (scan = start; scan < authority_end; scan++) {
399
+ if (*scan == ':') {
400
+ authority_end = scan;
401
+ break;
402
+ }
403
+ }
404
+ }
405
+
406
+ return curb_normalized_host_from_range(start, authority_end, raise_errors);
407
+ }
408
+
409
+ static char *curb_normalize_host_value(const char *value) {
410
+ return curb_normalize_host_value_impl(value, 1);
411
+ }
412
+
413
+ static char *curb_try_normalize_host_value(const char *value) {
414
+ return curb_normalize_host_value_impl(value, 0);
415
+ }
416
+
417
+ static VALUE curb_normalize_host_list(VALUE hosts) {
418
+ VALUE list;
419
+ VALUE normalized;
420
+ long i;
421
+
422
+ if (NIL_P(hosts)) return Qnil;
423
+
424
+ list = rb_check_array_type(hosts);
425
+ if (NIL_P(list)) {
426
+ list = rb_ary_new_from_args(1, hosts);
427
+ }
428
+
429
+ normalized = rb_ary_new_capa(RARRAY_LEN(list));
430
+ for (i = 0; i < RARRAY_LEN(list); i++) {
431
+ VALUE item = rb_ary_entry(list, i);
432
+ VALUE host_value = rb_obj_as_string(item);
433
+ char *host = curb_normalize_host_value(StringValueCStr(host_value));
434
+ VALUE normalized_host = rb_str_new_cstr(host);
435
+
436
+ if (!curb_array_includes_string(normalized, normalized_host)) {
437
+ rb_ary_push(normalized, normalized_host);
438
+ }
439
+ xfree(host);
440
+ }
441
+
442
+ return normalized;
443
+ }
444
+
445
+ static void curb_clear_network_allowed_hosts(ruby_curl_easy *rbce) {
446
+ size_t i;
447
+
448
+ if (!rbce || !rbce->network_allowed_hosts) return;
449
+
450
+ for (i = 0; i < rbce->network_allowed_host_count; i++) {
451
+ if (rbce->network_allowed_hosts[i]) xfree(rbce->network_allowed_hosts[i]);
452
+ }
453
+ xfree(rbce->network_allowed_hosts);
454
+ rbce->network_allowed_hosts = NULL;
455
+ rbce->network_allowed_host_count = 0;
456
+ }
457
+
458
+ static char *curb_strdup_cstr(const char *value) {
459
+ size_t len = strlen(value);
460
+ char *copy = ALLOC_N(char, len + 1);
461
+ memcpy(copy, value, len + 1);
462
+ return copy;
463
+ }
464
+
465
+ static void curb_prepare_network_allowed_hosts(ruby_curl_easy *rbce) {
466
+ VALUE hosts;
467
+ long count;
468
+ long i;
469
+ char **rules;
470
+
471
+ if (!rbce) return;
472
+
473
+ curb_clear_network_allowed_hosts(rbce);
474
+
475
+ hosts = rb_hash_aref(rbce->opts, rb_easy_hkey("allowed_hosts"));
476
+ if (NIL_P(hosts)) return;
477
+
478
+ count = RARRAY_LEN(hosts);
479
+ if (count <= 0) return;
480
+
481
+ rules = ALLOC_N(char *, count);
482
+ for (i = 0; i < count; i++) {
483
+ VALUE host = rb_ary_entry(hosts, i);
484
+ rules[i] = curb_strdup_cstr(StringValueCStr(host));
485
+ }
486
+
487
+ rbce->network_allowed_hosts = rules;
488
+ rbce->network_allowed_host_count = (size_t)count;
489
+ }
490
+
491
+ static int curb_host_rules_match(const ruby_curl_easy *rbce, const char *host) {
492
+ size_t i;
493
+
494
+ if (!rbce || rbce->network_allowed_host_count == 0) return 1;
495
+
496
+ for (i = 0; i < rbce->network_allowed_host_count; i++) {
497
+ if (strcmp(host, rbce->network_allowed_hosts[i]) == 0) return 1;
498
+ }
499
+
500
+ return 0;
501
+ }
502
+
503
+ static void curb_prepare_network_allowed_cidr_rules(ruby_curl_easy *rbce) {
504
+ VALUE cidrs;
505
+ long count;
506
+ long i;
507
+ curb_cidr_rule *rules;
508
+
509
+ if (!rbce) return;
510
+
511
+ curb_clear_network_allowed_cidr_rules(rbce);
512
+
513
+ cidrs = rb_hash_aref(rbce->opts, rb_easy_hkey("allowed_cidrs"));
514
+ if (NIL_P(cidrs)) return;
515
+
516
+ count = RARRAY_LEN(cidrs);
517
+ if (count <= 0) return;
518
+
519
+ rules = ALLOC_N(curb_cidr_rule, count);
520
+ for (i = 0; i < count; i++) {
521
+ VALUE cidr = rb_ary_entry(cidrs, i);
522
+ curb_parse_cidr_rule(StringValueCStr(cidr), &rules[i]);
523
+ }
524
+
525
+ rbce->network_allowed_cidr_rules = rules;
526
+ rbce->network_allowed_cidr_rule_count = (size_t)count;
527
+ }
528
+
529
+ static int curb_cidr_rules_match(const ruby_curl_easy *rbce, unsigned char family, const unsigned char *ip) {
530
+ size_t i;
531
+
532
+ if (!rbce || rbce->network_allowed_cidr_rule_count == 0) return 1;
533
+
534
+ for (i = 0; i < rbce->network_allowed_cidr_rule_count; i++) {
535
+ const curb_cidr_rule *rule = &rbce->network_allowed_cidr_rules[i];
536
+ if (rule->family != family) continue;
537
+ if (curb_ip_prefix_matches(ip, rule->address, rule->prefix_bits)) return 1;
538
+ }
539
+
540
+ return 0;
541
+ }
542
+
543
+ static void curb_format_ipv4(char *buf, size_t len, const unsigned char *ip) {
544
+ snprintf(buf, len, "%u.%u.%u.%u",
545
+ (unsigned int)ip[0], (unsigned int)ip[1],
546
+ (unsigned int)ip[2], (unsigned int)ip[3]);
547
+ }
548
+
549
+ static void curb_format_ipv6(char *buf, size_t len, const unsigned char *ip) {
550
+ #ifdef AF_INET6
551
+ #ifdef _WIN32
552
+ if (inet_ntop(AF_INET6, (void *)ip, buf, len)) return;
553
+ #else
554
+ if (inet_ntop(AF_INET6, (const void *)ip, buf, (socklen_t)len)) return;
555
+ #endif
556
+ #endif
557
+
558
+ snprintf(buf, len,
559
+ "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
560
+ (unsigned int)ip[0], (unsigned int)ip[1],
561
+ (unsigned int)ip[2], (unsigned int)ip[3],
562
+ (unsigned int)ip[4], (unsigned int)ip[5],
563
+ (unsigned int)ip[6], (unsigned int)ip[7],
564
+ (unsigned int)ip[8], (unsigned int)ip[9],
565
+ (unsigned int)ip[10], (unsigned int)ip[11],
566
+ (unsigned int)ip[12], (unsigned int)ip[13],
567
+ (unsigned int)ip[14], (unsigned int)ip[15]);
568
+ }
569
+
570
+ static void curb_store_destination_error(ruby_curl_easy *rbce, const char *address, const char *reason) {
571
+ if (!rbce) return;
572
+
573
+ rbce->unsafe_destination_blocked = 1;
574
+
575
+ if (reason && reason[0]) {
576
+ snprintf(rbce->unsafe_destination_error, CURL_ERROR_SIZE,
577
+ "blocked destination address %s %s by public network policy", address, reason);
578
+ } else {
579
+ snprintf(rbce->unsafe_destination_error, CURL_ERROR_SIZE,
580
+ "blocked unsafe destination address %s by public network policy", address);
581
+ }
582
+
583
+ snprintf(rbce->err_buf, CURL_ERROR_SIZE, "%s", rbce->unsafe_destination_error);
584
+ }
585
+
586
+ static void curb_store_unsafe_destination_error(ruby_curl_easy *rbce, const char *address) {
587
+ curb_store_destination_error(rbce, address, NULL);
588
+ }
589
+
590
+ static void curb_store_host_allowlist_error(ruby_curl_easy *rbce, const char *host) {
591
+ if (!rbce) return;
592
+
593
+ rbce->unsafe_destination_blocked = 1;
594
+ snprintf(rbce->unsafe_destination_error, CURL_ERROR_SIZE,
595
+ "blocked URL host %s by safe mode host allowlist", host ? host : "unknown");
596
+ snprintf(rbce->err_buf, CURL_ERROR_SIZE, "%s", rbce->unsafe_destination_error);
597
+ }
598
+
599
+ #ifdef CURB_HAVE_PREREQ_HOST_POLICY
600
+ static int curb_host_allowlist_prereq(void *clientp,
601
+ char *conn_primary_ip,
602
+ char *conn_local_ip,
603
+ int conn_primary_port,
604
+ int conn_local_port) {
605
+ ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
606
+ char *effective_url = NULL;
607
+ char *host = NULL;
608
+ CURLcode rc;
609
+
610
+ (void)conn_primary_ip;
611
+ (void)conn_local_ip;
612
+ (void)conn_primary_port;
613
+ (void)conn_local_port;
614
+
615
+ if (!rbce || rbce->network_allowed_host_count == 0) {
616
+ return CURL_PREREQFUNC_OK;
617
+ }
618
+
619
+ rc = curl_easy_getinfo(rbce->curl, CURLINFO_EFFECTIVE_URL, &effective_url);
620
+ if (rc != CURLE_OK || !effective_url) {
621
+ curb_store_host_allowlist_error(rbce, "unknown");
622
+ return CURL_PREREQFUNC_ABORT;
623
+ }
624
+
625
+ host = curb_try_normalize_host_value(effective_url);
626
+ if (!host) {
627
+ curb_store_host_allowlist_error(rbce, "unknown");
628
+ return CURL_PREREQFUNC_ABORT;
629
+ }
630
+
631
+ if (!curb_host_rules_match(rbce, host)) {
632
+ curb_store_host_allowlist_error(rbce, host);
633
+ xfree(host);
634
+ return CURL_PREREQFUNC_ABORT;
635
+ }
636
+
637
+ xfree(host);
638
+ return CURL_PREREQFUNC_OK;
639
+ }
640
+ #endif
641
+
642
+ #ifdef CURB_HAVE_OPENSOCKET_NETWORK_POLICY
643
+ static curl_socket_t curb_public_network_opensocket(void *clientp, curlsocktype purpose, struct curl_sockaddr *address) {
644
+ ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
645
+ curl_socket_t sockfd;
646
+ char address_string[80];
647
+ int checked_destination_address = 0;
648
+
649
+ (void)purpose;
650
+
651
+ if (!address) {
652
+ curb_store_unsafe_destination_error(rbce, "unknown");
653
+ return CURL_SOCKET_BAD;
654
+ }
655
+
656
+ #ifdef AF_INET
657
+ if (address->family == AF_INET && address->addrlen >= sizeof(struct sockaddr_in)) {
658
+ const struct sockaddr_in *sin = (const struct sockaddr_in *)&address->addr;
659
+ const unsigned char *ip = (const unsigned char *)&sin->sin_addr;
660
+ checked_destination_address = 1;
661
+
662
+ if (curb_ipv4_is_unsafe_destination(ip)) {
663
+ curb_format_ipv4(address_string, sizeof(address_string), ip);
664
+ curb_store_unsafe_destination_error(rbce, address_string);
665
+ return CURL_SOCKET_BAD;
666
+ }
667
+
668
+ if (!curb_cidr_rules_match(rbce, CURB_CIDR_FAMILY_IPV4, ip)) {
669
+ curb_format_ipv4(address_string, sizeof(address_string), ip);
670
+ curb_store_destination_error(rbce, address_string, "outside allowed CIDR ranges");
671
+ return CURL_SOCKET_BAD;
672
+ }
673
+ }
674
+ #endif
675
+
676
+ #ifdef AF_INET6
677
+ if (address->family == AF_INET6 && address->addrlen >= sizeof(struct sockaddr_in6)) {
678
+ const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)&address->addr;
679
+ const unsigned char *ip = (const unsigned char *)&sin6->sin6_addr;
680
+ checked_destination_address = 1;
681
+
682
+ if (curb_ipv6_is_unsafe_destination(ip)) {
683
+ curb_format_ipv6(address_string, sizeof(address_string), ip);
684
+ curb_store_unsafe_destination_error(rbce, address_string);
685
+ return CURL_SOCKET_BAD;
686
+ }
687
+
688
+ if (!curb_cidr_rules_match(rbce, CURB_CIDR_FAMILY_IPV6, ip)) {
689
+ curb_format_ipv6(address_string, sizeof(address_string), ip);
690
+ curb_store_destination_error(rbce, address_string, "outside allowed CIDR ranges");
691
+ return CURL_SOCKET_BAD;
692
+ }
693
+ }
694
+ #endif
695
+
696
+ if (!checked_destination_address && rbce && rbce->network_allowed_cidr_rule_count > 0) {
697
+ curb_store_destination_error(rbce, "unknown", "outside allowed CIDR ranges");
698
+ return CURL_SOCKET_BAD;
699
+ }
700
+
701
+ sockfd = socket(address->family, address->socktype, address->protocol);
702
+ if (sockfd == CURL_SOCKET_BAD) {
703
+ return CURL_SOCKET_BAD;
704
+ }
705
+
706
+ return sockfd;
707
+ }
708
+ #endif
709
+
710
+ static VALUE callback_exception(VALUE unused, VALUE exception) {
32
711
  return Qfalse;
33
- }
712
+ }
34
713
 
35
- /* These handle both body and header data */
36
- static size_t default_data_handler(char *stream,
714
+ static VALUE callback_exception_store_on_easy(VALUE arg, VALUE exception) {
715
+ ruby_curl_easy *rbce = (ruby_curl_easy *)arg;
716
+
717
+ if (rbce && NIL_P(rbce->callback_error)) {
718
+ rbce->callback_error = exception;
719
+ }
720
+
721
+ return Qfalse;
722
+ }
723
+
724
+ VALUE rb_curl_easy_take_callback_error(ruby_curl_easy *rbce) {
725
+ VALUE exception = Qnil;
726
+
727
+ if (!rbce) {
728
+ return Qnil;
729
+ }
730
+
731
+ exception = rbce->callback_error;
732
+ rbce->callback_error = Qnil;
733
+ return exception;
734
+ }
735
+
736
+ static VALUE ruby_curl_easy_take_callback_error(VALUE self) {
737
+ ruby_curl_easy *rbce = NULL;
738
+
739
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
740
+ return rb_curl_easy_take_callback_error(rbce);
741
+ }
742
+
743
+ static VALUE ensure_clear_easy_callback_active(VALUE arg) {
744
+ ruby_curl_easy *rbce = (ruby_curl_easy *)arg;
745
+ if (rbce) {
746
+ rbce->callback_active = 0;
747
+ }
748
+ return Qnil;
749
+ }
750
+
751
+ static VALUE with_easy_callback_active(ruby_curl_easy *rbce, VALUE (*func)(VALUE), VALUE arg) {
752
+ rbce->callback_active = 1;
753
+ return rb_ensure(func, arg, ensure_clear_easy_callback_active, (VALUE)rbce);
754
+ }
755
+
756
+ static void ruby_curl_easy_enter_native(ruby_curl_easy *rbce) {
757
+ if (rbce) {
758
+ rbce->native_active++;
759
+ }
760
+ }
761
+
762
+ static VALUE ruby_curl_easy_leave_native(VALUE arg) {
763
+ ruby_curl_easy *rbce = (ruby_curl_easy *)arg;
764
+ if (rbce && rbce->native_active > 0) {
765
+ rbce->native_active--;
766
+ }
767
+ return Qnil;
768
+ }
769
+
770
+ struct stream_read_call_args {
771
+ VALUE stream;
772
+ size_t read_bytes;
773
+ };
774
+
775
+ static VALUE call_stream_read(VALUE argp) {
776
+ struct stream_read_call_args *args = (struct stream_read_call_args *)argp;
777
+ return rb_funcall(args->stream, rb_intern("read"), 1, ULONG2NUM((unsigned long)args->read_bytes));
778
+ }
779
+
780
+ static VALUE call_stream_to_s(VALUE stream) {
781
+ return rb_funcall(stream, rb_intern("to_s"), 0);
782
+ }
783
+
784
+ static VALUE call_string_value(VALUE str) {
785
+ StringValue(str);
786
+ return str;
787
+ }
788
+
789
+ struct stream_seek_call_args {
790
+ VALUE stream;
791
+ curl_off_t offset;
792
+ int origin;
793
+ };
794
+
795
+ static VALUE call_stream_seek(VALUE argp) {
796
+ struct stream_seek_call_args *args = (struct stream_seek_call_args *)argp;
797
+ return rb_funcall(args->stream, rb_intern("seek"), 2, LL2NUM(args->offset), INT2NUM(args->origin));
798
+ }
799
+
800
+ struct proc_data_call_args {
801
+ char *stream;
802
+ size_t size;
803
+ size_t nmemb;
804
+ VALUE proc;
805
+ };
806
+
807
+ static VALUE call_proc_data_handler_wrapped(VALUE argp) {
808
+ struct proc_data_call_args *args = (struct proc_data_call_args *)argp;
809
+ return ULONG2NUM((unsigned long)proc_data_handler(args->stream, args->size, args->nmemb, args->proc));
810
+ }
811
+
812
+ struct easy_callback_dispatch_args {
813
+ ruby_curl_easy *rbce;
814
+ VALUE (*func)(VALUE);
815
+ VALUE arg;
816
+ };
817
+
818
+ static VALUE call_with_easy_callback_active(VALUE argp) {
819
+ struct easy_callback_dispatch_args *args = (struct easy_callback_dispatch_args *)argp;
820
+ return with_easy_callback_active(args->rbce, args->func, args->arg);
821
+ }
822
+
823
+ static VALUE rescue_easy_callback(ruby_curl_easy *rbce, VALUE (*func)(VALUE), VALUE arg) {
824
+ struct easy_callback_dispatch_args dispatch_args;
825
+ dispatch_args.rbce = rbce;
826
+ dispatch_args.func = func;
827
+ dispatch_args.arg = arg;
828
+ return rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception_store_on_easy, (VALUE)rbce);
829
+ }
830
+
831
+ static size_t curl_read_abort_result(void) {
832
+ #ifdef CURL_READFUNC_ABORT
833
+ return CURL_READFUNC_ABORT;
834
+ #else
835
+ return 0;
836
+ #endif
837
+ }
838
+
839
+ static int curl_seek_fail_result(void) {
840
+ #ifdef CURL_SEEKFUNC_FAIL
841
+ return CURL_SEEKFUNC_FAIL;
842
+ #else
843
+ return 1;
844
+ #endif
845
+ }
846
+
847
+ static int ruby_curl_easy_body_limit_exceeded(ruby_curl_easy *rbce, size_t total) {
848
+ VALUE max_body_bytes = rb_easy_get("max_body_bytes");
849
+ curl_off_t limit;
850
+
851
+ if (NIL_P(max_body_bytes)) {
852
+ return 0;
853
+ }
854
+
855
+ limit = (curl_off_t)NUM2LL(max_body_bytes);
856
+ if (limit <= 0) {
857
+ return 0;
858
+ }
859
+
860
+ if ((curl_off_t)total > limit - rbce->downloaded_body_bytes) {
861
+ if (NIL_P(rbce->callback_error)) {
862
+ rbce->callback_error = rb_exc_new_cstr(eCurlErrFileSizeExceeded, "Maximum body size exceeded");
863
+ }
864
+ return 1;
865
+ }
866
+
867
+ rbce->downloaded_body_bytes += (curl_off_t)total;
868
+ return 0;
869
+ }
870
+
871
+ /* Default body handler appends to easy.body_data buffer */
872
+ static size_t default_body_handler(char *stream,
37
873
  size_t size,
38
874
  size_t nmemb,
39
- VALUE out) {
40
- rb_str_buf_cat(out, stream, size * nmemb);
41
- return size * nmemb;
875
+ void *userdata) {
876
+ ruby_curl_easy *rbce = (ruby_curl_easy *)userdata;
877
+ size_t total = size * nmemb;
878
+ VALUE out = rb_easy_get("body_data");
879
+
880
+ if (ruby_curl_easy_body_limit_exceeded(rbce, total)) {
881
+ return 0;
882
+ }
883
+
884
+ if (NIL_P(out)) {
885
+ out = rb_easy_set("body_data", rb_str_buf_new(32768));
886
+ }
887
+ rb_str_buf_cat(out, stream, total);
888
+ return total;
889
+ }
890
+
891
+ /* Default header handler appends to easy.header_data buffer */
892
+ static size_t default_header_handler(char *stream,
893
+ size_t size,
894
+ size_t nmemb,
895
+ void *userdata) {
896
+ ruby_curl_easy *rbce = (ruby_curl_easy *)userdata;
897
+ size_t total = size * nmemb;
898
+ VALUE out = rb_easy_get("header_data");
899
+ if (NIL_P(out)) {
900
+ out = rb_easy_set("header_data", rb_str_buf_new(16384));
901
+ }
902
+ rb_str_buf_cat(out, stream, total);
903
+ return total;
42
904
  }
43
905
 
44
906
  // size_t function( void *ptr, size_t size, size_t nmemb, void *stream);
@@ -48,14 +910,36 @@ static size_t read_data_handler(void *ptr,
48
910
  ruby_curl_easy *rbce) {
49
911
  VALUE upload = rb_easy_get("upload");
50
912
  size_t read_bytes = (size*nmemb);
51
- VALUE stream = ruby_curl_upload_stream_get(upload);
913
+ VALUE stream;
914
+
915
+ if (NIL_P(upload)) {
916
+ return curl_read_abort_result();
917
+ }
918
+
919
+ stream = ruby_curl_upload_stream_get(upload);
52
920
 
53
921
  if (rb_respond_to(stream, rb_intern("read"))) {//if (rb_respond_to(stream, rb_intern("to_s"))) {
54
922
  /* copy read_bytes from stream into ptr */
55
- VALUE str = rb_funcall(stream, rb_intern("read"), 1, rb_int_new(read_bytes) );
923
+ struct stream_read_call_args args;
924
+ args.stream = stream;
925
+ args.read_bytes = read_bytes;
926
+ VALUE str = rescue_easy_callback(rbce, call_stream_read, (VALUE)&args);
56
927
  if( str != Qnil ) {
57
- memcpy(ptr, RSTRING_PTR(str), RSTRING_LEN(str));
58
- return RSTRING_LEN(str);
928
+ size_t str_len;
929
+
930
+ str = rescue_easy_callback(rbce, call_string_value, str);
931
+ if (str == Qfalse || str == Qnil) {
932
+ return curl_read_abort_result();
933
+ }
934
+
935
+ str_len = (size_t)RSTRING_LEN(str);
936
+ if (str_len > read_bytes) {
937
+ snprintf(rbce->err_buf, CURL_ERROR_SIZE, "read callback returned more data than requested");
938
+ return curl_read_abort_result();
939
+ }
940
+
941
+ memcpy(ptr, RSTRING_PTR(str), str_len);
942
+ return str_len;
59
943
  }
60
944
  else {
61
945
  return 0;
@@ -67,13 +951,22 @@ static size_t read_data_handler(void *ptr,
67
951
  size_t len;
68
952
  size_t remaining;
69
953
  char *str_ptr;
70
- Data_Get_Struct(upload, ruby_curl_upload, rbcu);
71
- str = rb_funcall(stream, rb_intern("to_s"), 0);
954
+ TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
955
+ str = rescue_easy_callback(rbce, call_stream_to_s, stream);
956
+ str = rescue_easy_callback(rbce, call_string_value, str);
957
+ if (str == Qfalse || str == Qnil) {
958
+ return curl_read_abort_result();
959
+ }
960
+
72
961
  len = RSTRING_LEN(str);
962
+ if (rbcu->offset >= len) {
963
+ return 0;
964
+ }
965
+
73
966
  remaining = len - rbcu->offset;
74
967
  str_ptr = RSTRING_PTR(str);
75
968
 
76
- if( remaining < read_bytes ) {
969
+ if( remaining <= read_bytes ) {
77
970
  if( remaining > 0 ) {
78
971
  memcpy(ptr, str_ptr+rbcu->offset, remaining);
79
972
  read_bytes = remaining;
@@ -81,14 +974,10 @@ static size_t read_data_handler(void *ptr,
81
974
  }
82
975
  return remaining;
83
976
  }
84
- else if( remaining > read_bytes ) { // read_bytes <= remaining - send what we can fit in the buffer(ptr)
977
+ else { // read_bytes < remaining - send what we can fit in the buffer(ptr)
85
978
  memcpy(ptr, str_ptr+rbcu->offset, read_bytes);
86
979
  rbcu->offset += read_bytes;
87
980
  }
88
- else { // they're equal
89
- memcpy(ptr, str_ptr+rbcu->offset, --read_bytes);
90
- rbcu->offset += read_bytes;
91
- }
92
981
  return read_bytes;
93
982
  }
94
983
  else {
@@ -96,6 +985,38 @@ static size_t read_data_handler(void *ptr,
96
985
  }
97
986
  }
98
987
 
988
+ int seek_data_handler(ruby_curl_easy *rbce,
989
+ curl_off_t offset,
990
+ int origin) {
991
+
992
+ VALUE upload = rb_easy_get("upload");
993
+ VALUE stream;
994
+
995
+ if (NIL_P(upload)) {
996
+ return curl_seek_fail_result();
997
+ }
998
+
999
+ stream = ruby_curl_upload_stream_get(upload);
1000
+
1001
+ if (rb_respond_to(stream, rb_intern("seek"))) {
1002
+ struct stream_seek_call_args args;
1003
+ args.stream = stream;
1004
+ args.offset = offset;
1005
+ args.origin = origin;
1006
+ rescue_easy_callback(rbce, call_stream_seek, (VALUE)&args);
1007
+ if (!NIL_P(rbce->callback_error)) {
1008
+ return curl_seek_fail_result();
1009
+ }
1010
+ } else {
1011
+ ruby_curl_upload *rbcu;
1012
+ TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
1013
+ // This OK because curl only uses SEEK_SET as per the documentation
1014
+ rbcu->offset = offset;
1015
+ }
1016
+
1017
+ return 0;
1018
+ }
1019
+
99
1020
  static size_t proc_data_handler(char *stream,
100
1021
  size_t size,
101
1022
  size_t nmemb,
@@ -120,22 +1041,44 @@ static size_t proc_data_handler_body(char *stream,
120
1041
  size_t nmemb,
121
1042
  ruby_curl_easy *rbce)
122
1043
  {
123
- size_t ret;
124
- rbce->callback_active = 1;
125
- ret = proc_data_handler(stream, size, nmemb, rb_easy_get("body_proc"));
126
- rbce->callback_active = 0;
127
- return ret;
1044
+ struct proc_data_call_args args;
1045
+ struct easy_callback_dispatch_args dispatch_args;
1046
+ VALUE procret;
1047
+ args.stream = stream;
1048
+ args.size = size;
1049
+ args.nmemb = nmemb;
1050
+ args.proc = rb_easy_get("body_proc");
1051
+
1052
+ if (ruby_curl_easy_body_limit_exceeded(rbce, size * nmemb)) {
1053
+ return 0;
1054
+ }
1055
+
1056
+ dispatch_args.rbce = rbce;
1057
+ dispatch_args.func = call_proc_data_handler_wrapped;
1058
+ dispatch_args.arg = (VALUE)&args;
1059
+ procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception_store_on_easy, (VALUE)rbce);
1060
+
1061
+ return ((procret == Qfalse) || (procret == Qnil)) ? 0 : NUM2ULONG(procret);
128
1062
  }
129
1063
  static size_t proc_data_handler_header(char *stream,
130
1064
  size_t size,
131
1065
  size_t nmemb,
132
1066
  ruby_curl_easy *rbce)
133
1067
  {
134
- size_t ret;
135
- rbce->callback_active = 1;
136
- ret = proc_data_handler(stream, size, nmemb, rb_easy_get("header_proc"));
137
- rbce->callback_active = 0;
138
- return ret;
1068
+ struct proc_data_call_args args;
1069
+ struct easy_callback_dispatch_args dispatch_args;
1070
+ VALUE procret;
1071
+ args.stream = stream;
1072
+ args.size = size;
1073
+ args.nmemb = nmemb;
1074
+ args.proc = rb_easy_get("header_proc");
1075
+
1076
+ dispatch_args.rbce = rbce;
1077
+ dispatch_args.func = call_proc_data_handler_wrapped;
1078
+ dispatch_args.arg = (VALUE)&args;
1079
+ procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception_store_on_easy, (VALUE)rbce);
1080
+
1081
+ return ((procret == Qfalse) || (procret == Qnil)) ? 0 : NUM2ULONG(procret);
139
1082
  }
140
1083
 
141
1084
 
@@ -147,11 +1090,18 @@ static VALUE call_progress_handler(VALUE ary) {
147
1090
  rb_ary_entry(ary, 4)); // rb_float_new(ulnow));
148
1091
  }
149
1092
 
150
- static int proc_progress_handler(VALUE proc,
1093
+ /* CURLOPT_PROGRESSFUNCTION callback (deprecated since 7.32.0) */
1094
+ #ifndef HAVE_CURLOPT_XFERINFOFUNCTION
1095
+ static int proc_progress_handler(void *clientp,
151
1096
  double dltotal,
152
1097
  double dlnow,
153
1098
  double ultotal,
154
1099
  double ulnow) {
1100
+ ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
1101
+ VALUE proc = rb_easy_get("progress_proc");
1102
+ if (proc == Qnil) {
1103
+ return 0;
1104
+ }
155
1105
  VALUE procret;
156
1106
  VALUE callargs = rb_ary_new2(5);
157
1107
 
@@ -161,71 +1111,297 @@ static int proc_progress_handler(VALUE proc,
161
1111
  rb_ary_store(callargs, 3, rb_float_new(ultotal));
162
1112
  rb_ary_store(callargs, 4, rb_float_new(ulnow));
163
1113
 
164
- //v = rb_rescue(range_check, (VALUE)args, range_failed, 0);
165
- //procret = rb_funcall(proc, idCall, 4, rb_float_new(dltotal),
166
- // rb_float_new(dlnow),
167
- // rb_float_new(ultotal),
168
- // rb_float_new(ulnow));
169
- procret = rb_rescue(call_progress_handler, callargs, callback_exception, Qnil);
1114
+ struct easy_callback_dispatch_args dispatch_args;
1115
+ dispatch_args.rbce = rbce;
1116
+ dispatch_args.func = call_progress_handler;
1117
+ dispatch_args.arg = callargs;
1118
+ procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception, Qnil);
1119
+
1120
+ return(((procret == Qfalse) || (procret == Qnil)) ? -1 : 0);
1121
+ }
1122
+ #endif
1123
+
1124
+ /* CURLOPT_XFERINFOFUNCTION callback (since 7.32.0, replaces PROGRESSFUNCTION) */
1125
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
1126
+ static int proc_xferinfo_handler(void *clientp,
1127
+ curl_off_t dltotal,
1128
+ curl_off_t dlnow,
1129
+ curl_off_t ultotal,
1130
+ curl_off_t ulnow) {
1131
+ ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
1132
+ VALUE proc = rb_easy_get("progress_proc");
1133
+ if (proc == Qnil) {
1134
+ return 0;
1135
+ }
1136
+ VALUE procret;
1137
+ VALUE callargs = rb_ary_new2(5);
1138
+
1139
+ rb_ary_store(callargs, 0, proc);
1140
+ rb_ary_store(callargs, 1, LL2NUM(dltotal));
1141
+ rb_ary_store(callargs, 2, LL2NUM(dlnow));
1142
+ rb_ary_store(callargs, 3, LL2NUM(ultotal));
1143
+ rb_ary_store(callargs, 4, LL2NUM(ulnow));
1144
+
1145
+ struct easy_callback_dispatch_args dispatch_args;
1146
+ dispatch_args.rbce = rbce;
1147
+ dispatch_args.func = call_progress_handler;
1148
+ dispatch_args.arg = callargs;
1149
+ procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception, Qnil);
170
1150
 
171
1151
  return(((procret == Qfalse) || (procret == Qnil)) ? -1 : 0);
172
1152
  }
1153
+ #endif
173
1154
 
174
1155
  static VALUE call_debug_handler(VALUE ary) {
175
1156
  return rb_funcall(rb_ary_entry(ary, 0), idCall, 2,
176
- rb_ary_entry(ary, 1), // INT2FIX(type),
1157
+ rb_ary_entry(ary, 1), // INT2NUM(type),
177
1158
  rb_ary_entry(ary, 2)); // rb_str_new(data, data_len)
178
1159
  }
179
1160
  static int proc_debug_handler(CURL *curl,
180
1161
  curl_infotype type,
181
1162
  char *data,
182
1163
  size_t data_len,
183
- VALUE proc) {
1164
+ void *clientp) {
1165
+ ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
1166
+ VALUE proc = rb_easy_get("debug_proc");
1167
+ if (proc == Qnil) {
1168
+ return 0;
1169
+ }
184
1170
  VALUE callargs = rb_ary_new2(3);
185
1171
  rb_ary_store(callargs, 0, proc);
186
- rb_ary_store(callargs, 1, INT2FIX(type));
1172
+ rb_ary_store(callargs, 1, INT2NUM(type));
187
1173
  rb_ary_store(callargs, 2, rb_str_new(data, data_len));
188
- rb_rescue(call_debug_handler, callargs, callback_exception, Qnil);
1174
+ struct easy_callback_dispatch_args dispatch_args;
1175
+ dispatch_args.rbce = rbce;
1176
+ dispatch_args.func = call_debug_handler;
1177
+ dispatch_args.arg = callargs;
1178
+ rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception, Qnil);
189
1179
  /* no way to indicate to libcurl that we should break out given an exception in the on_debug handler...
190
1180
  * this means exceptions will be swallowed
191
1181
  */
192
- //rb_funcall(proc, idCall, 2, INT2FIX(type), rb_str_new(data, data_len));
1182
+ //rb_funcall(proc, idCall, 2, INT2NUM(type), rb_str_new(data, data_len));
193
1183
  return 0;
194
1184
  }
195
1185
 
196
- /* ================== MARK/FREE FUNC ==================*/
197
- void curl_easy_mark(ruby_curl_easy *rbce) {
198
- if (!NIL_P(rbce->opts)) { rb_gc_mark(rbce->opts); }
199
- if (!NIL_P(rbce->multi)) { rb_gc_mark(rbce->multi); }
1186
+ /* ================== MARK/FREE/SIZE FUNCS ==================*/
1187
+ static void curl_easy_mark(void *ptr) {
1188
+ ruby_curl_easy *rbce = (ruby_curl_easy *)ptr;
1189
+ if (rbce) {
1190
+ if (!NIL_P(rbce->opts)) { rb_gc_mark(rbce->opts); }
1191
+ if (!NIL_P(rbce->multi)) { rb_gc_mark(rbce->multi); }
1192
+ if (!NIL_P(rbce->callback_error)) { rb_gc_mark(rbce->callback_error); }
1193
+ }
1194
+ }
1195
+
1196
+ static void ruby_curl_easy_clear_headers_list(ruby_curl_easy *rbce) {
1197
+ if (!rbce || !rbce->curl_headers) {
1198
+ return;
1199
+ }
1200
+ if (rbce->curl) {
1201
+ curl_easy_setopt(rbce->curl, CURLOPT_HTTPHEADER, NULL);
1202
+ }
1203
+ curl_slist_free_all(rbce->curl_headers);
1204
+ rbce->curl_headers = NULL;
1205
+ }
1206
+
1207
+ static void ruby_curl_easy_clear_proxy_headers_list(ruby_curl_easy *rbce) {
1208
+ if (!rbce || !rbce->curl_proxy_headers) {
1209
+ return;
1210
+ }
1211
+ #ifdef HAVE_CURLOPT_PROXYHEADER
1212
+ if (rbce->curl) {
1213
+ curl_easy_setopt(rbce->curl, CURLOPT_PROXYHEADER, NULL);
1214
+ }
1215
+ #endif
1216
+ curl_slist_free_all(rbce->curl_proxy_headers);
1217
+ rbce->curl_proxy_headers = NULL;
1218
+ }
1219
+
1220
+ static void ruby_curl_easy_clear_ftp_commands_list(ruby_curl_easy *rbce) {
1221
+ if (!rbce || !rbce->curl_ftp_commands) {
1222
+ return;
1223
+ }
1224
+ if (rbce->curl) {
1225
+ curl_easy_setopt(rbce->curl, CURLOPT_QUOTE, NULL);
1226
+ }
1227
+ curl_slist_free_all(rbce->curl_ftp_commands);
1228
+ rbce->curl_ftp_commands = NULL;
1229
+ }
1230
+
1231
+ static void ruby_curl_easy_clear_resolve_list(ruby_curl_easy *rbce) {
1232
+ if (!rbce || !rbce->curl_resolve) {
1233
+ return;
1234
+ }
1235
+ #ifdef HAVE_CURLOPT_RESOLVE
1236
+ if (rbce->curl) {
1237
+ curl_easy_setopt(rbce->curl, CURLOPT_RESOLVE, NULL);
1238
+ }
1239
+ #endif
1240
+ curl_slist_free_all(rbce->curl_resolve);
1241
+ rbce->curl_resolve = NULL;
1242
+ }
1243
+
1244
+ static void ruby_curl_easy_clear_connect_to_list(ruby_curl_easy *rbce) {
1245
+ if (!rbce || !rbce->curl_connect_to) {
1246
+ return;
1247
+ }
1248
+ #ifdef HAVE_CURLOPT_CONNECT_TO
1249
+ if (rbce->curl) {
1250
+ curl_easy_setopt(rbce->curl, CURLOPT_CONNECT_TO, NULL);
1251
+ }
1252
+ #endif
1253
+ curl_slist_free_all(rbce->curl_connect_to);
1254
+ rbce->curl_connect_to = NULL;
1255
+ }
1256
+
1257
+ static void ruby_curl_easy_clear_setup_lists(ruby_curl_easy *rbce) {
1258
+ ruby_curl_easy_clear_headers_list(rbce);
1259
+ ruby_curl_easy_clear_proxy_headers_list(rbce);
1260
+ ruby_curl_easy_clear_ftp_commands_list(rbce);
1261
+ ruby_curl_easy_clear_resolve_list(rbce);
1262
+ ruby_curl_easy_clear_connect_to_list(rbce);
1263
+ }
1264
+
1265
+ /* Legacy wrapper for external callers */
1266
+ void ruby_curl_easy_mark(ruby_curl_easy *rbce) {
1267
+ curl_easy_mark((void *)rbce);
1268
+ }
1269
+
1270
+ static ruby_curl_multi *ruby_curl_multi_pointer_if_compatible(VALUE multi_val) {
1271
+ if (NIL_P(multi_val) || !RB_TYPE_P(multi_val, T_DATA)) {
1272
+ return NULL;
1273
+ }
1274
+
1275
+ #if defined(RTYPEDDATA_P) && defined(RTYPEDDATA_TYPE) && defined(RTYPEDDATA_DATA)
1276
+ if (!RTYPEDDATA_P(multi_val)) {
1277
+ return NULL;
1278
+ }
1279
+
1280
+ if (RTYPEDDATA_TYPE(multi_val) != &ruby_curl_multi_data_type) {
1281
+ return NULL;
1282
+ }
1283
+
1284
+ return (ruby_curl_multi *)RTYPEDDATA_DATA(multi_val);
1285
+ #else
1286
+ if (!rb_typeddata_is_kind_of(multi_val, &ruby_curl_multi_data_type)) {
1287
+ return NULL;
1288
+ }
1289
+
1290
+ return DATA_PTR(multi_val);
1291
+ #endif
200
1292
  }
201
1293
 
202
1294
  static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
203
- if (rbce->curl_headers) {
204
- curl_slist_free_all(rbce->curl_headers);
1295
+ if (!rbce) {
1296
+ return;
205
1297
  }
206
1298
 
207
- if (rbce->curl_ftp_commands) {
208
- curl_slist_free_all(rbce->curl_ftp_commands);
1299
+ if (!NIL_P(rbce->multi)) {
1300
+ VALUE multi_val = rbce->multi;
1301
+ ruby_curl_multi *rbcm = ruby_curl_multi_pointer_if_compatible(multi_val);
1302
+
1303
+ rbce->multi = Qnil;
1304
+
1305
+ if (rbcm) {
1306
+ /* Best-effort: ensure the handle is detached from the multi to avoid
1307
+ * libcurl retaining a dangling pointer to a soon-to-be cleaned-up easy
1308
+ * handle. This path runs during GC, so it must not invoke Ruby APIs that
1309
+ * can allocate or raise. */
1310
+ if (rbcm->handle && rbce->curl) {
1311
+ curl_multi_remove_handle(rbcm->handle, rbce->curl);
1312
+ }
1313
+ rb_curl_multi_forget_easy(rbcm, rbce);
1314
+ }
209
1315
  }
210
1316
 
1317
+ ruby_curl_easy_clear_headers_list(rbce);
1318
+ ruby_curl_easy_clear_proxy_headers_list(rbce);
1319
+ ruby_curl_easy_clear_ftp_commands_list(rbce);
1320
+ ruby_curl_easy_clear_resolve_list(rbce);
1321
+ ruby_curl_easy_clear_connect_to_list(rbce);
1322
+ curb_clear_network_allowed_cidr_rules(rbce);
1323
+ curb_clear_network_allowed_hosts(rbce);
1324
+
211
1325
  if (rbce->curl) {
1326
+ /* disable any progress or debug events */
1327
+ curl_easy_setopt(rbce->curl, CURLOPT_WRITEFUNCTION, NULL);
1328
+ curl_easy_setopt(rbce->curl, CURLOPT_WRITEDATA, NULL);
1329
+ curl_easy_setopt(rbce->curl, CURLOPT_HEADERFUNCTION, NULL);
1330
+ curl_easy_setopt(rbce->curl, CURLOPT_HEADERDATA, NULL);
1331
+ curl_easy_setopt(rbce->curl, CURLOPT_DEBUGFUNCTION, NULL);
1332
+ curl_easy_setopt(rbce->curl, CURLOPT_DEBUGDATA, NULL);
1333
+ curl_easy_setopt(rbce->curl, CURLOPT_VERBOSE, 0);
1334
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
1335
+ curl_easy_setopt(rbce->curl, CURLOPT_XFERINFOFUNCTION, NULL);
1336
+ #else
1337
+ curl_easy_setopt(rbce->curl, CURLOPT_PROGRESSFUNCTION, NULL);
1338
+ #endif
1339
+ curl_easy_setopt(rbce->curl, CURLOPT_NOPROGRESS, 1);
212
1340
  curl_easy_cleanup(rbce->curl);
1341
+ rbce->curl = NULL;
213
1342
  }
1343
+
1344
+ rbce->self = Qnil;
214
1345
  }
215
1346
 
216
- void curl_easy_free(ruby_curl_easy *rbce) {
217
- ruby_curl_easy_free(rbce);
218
- free(rbce);
1347
+ /* TypedData-compatible free function */
1348
+ static void curl_easy_free(void *ptr) {
1349
+ ruby_curl_easy *rbce = (ruby_curl_easy *)ptr;
1350
+ if (rbce) {
1351
+ ruby_curl_easy_free(rbce);
1352
+ free(rbce);
1353
+ }
1354
+ }
1355
+
1356
+ /* Legacy wrapper for external callers (e.g., curb_multi) */
1357
+ void ruby_curl_easy_free_wrapper(ruby_curl_easy *rbce) {
1358
+ curl_easy_free((void *)rbce);
219
1359
  }
220
1360
 
1361
+ static size_t curl_easy_memsize(const void *ptr) {
1362
+ const ruby_curl_easy *rbce = (const ruby_curl_easy *)ptr;
1363
+ size_t size = sizeof(ruby_curl_easy);
1364
+ /* Note: We don't count curl_slist or CURL handle memory as they're
1365
+ * managed by libcurl and would require complex introspection */
1366
+ (void)rbce; /* silence unused warning */
1367
+ return size;
1368
+ }
1369
+
1370
+ const rb_data_type_t ruby_curl_easy_data_type = {
1371
+ "Curl::Easy",
1372
+ {
1373
+ curl_easy_mark,
1374
+ curl_easy_free,
1375
+ curl_easy_memsize,
1376
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
1377
+ NULL, /* compact - not needed */
1378
+ #endif
1379
+ },
1380
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
1381
+ NULL, NULL, /* parent, data */
1382
+ RUBY_TYPED_FREE_IMMEDIATELY
1383
+ #endif
1384
+ };
1385
+
221
1386
 
222
1387
  /* ================= ALLOC METHODS ====================*/
223
1388
 
224
1389
  static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
225
1390
  rbce->opts = rb_hash_new();
226
1391
 
1392
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
1393
+ memset(rbce->unsafe_destination_error, 0, CURL_ERROR_SIZE);
1394
+
1395
+ rbce->self = Qnil;
227
1396
  rbce->curl_headers = NULL;
1397
+ rbce->curl_proxy_headers = NULL;
228
1398
  rbce->curl_ftp_commands = NULL;
1399
+ rbce->curl_resolve = NULL;
1400
+ rbce->curl_connect_to = NULL;
1401
+ rbce->network_allowed_cidr_rules = NULL;
1402
+ rbce->network_allowed_hosts = NULL;
1403
+ rbce->network_allowed_cidr_rule_count = 0;
1404
+ rbce->network_allowed_host_count = 0;
229
1405
 
230
1406
  /* various-typed opts */
231
1407
  rbce->local_port = 0;
@@ -236,15 +1412,21 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
236
1412
  rbce->proxy_auth_types = 0;
237
1413
  rbce->max_redirs = -1;
238
1414
  rbce->timeout = 0;
1415
+ rbce->timeout_ms = 0;
239
1416
  rbce->connect_timeout = 0;
1417
+ rbce->connect_timeout_ms = 0;
240
1418
  rbce->dns_cache_timeout = 60;
241
1419
  rbce->ftp_response_timeout = 0;
242
1420
  rbce->low_speed_limit = 0;
243
1421
  rbce->low_speed_time = 0;
1422
+ rbce->max_send_speed_large = 0;
1423
+ rbce->max_recv_speed_large = 0;
244
1424
  rbce->ssl_version = -1;
245
1425
  rbce->use_ssl = -1;
246
1426
  rbce->ftp_filemethod = -1;
1427
+ rbce->http_version = CURL_HTTP_VERSION_NONE;
247
1428
  rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER;
1429
+ rbce->network_policy = CURB_NETWORK_POLICY_NONE;
248
1430
 
249
1431
  /* bool opts */
250
1432
  rbce->proxy_tunnel = 0;
@@ -258,29 +1440,53 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
258
1440
  rbce->verbose = 0;
259
1441
  rbce->multipart_form_post = 0;
260
1442
  rbce->enable_cookies = 0;
1443
+ rbce->cookielist_engine_enabled = 0;
261
1444
  rbce->ignore_content_length = 0;
262
1445
  rbce->callback_active = 0;
1446
+ rbce->unsafe_destination_blocked = 0;
1447
+ rbce->allow_proxy = 0;
1448
+ rbce->allow_unix_socket = 0;
1449
+ rbce->forbid_reuse_set = 0;
1450
+ rbce->native_active = 0;
1451
+ rbce->forbid_reuse = 0;
1452
+ rbce->callback_error = Qnil;
1453
+ rbce->last_result = 0;
1454
+ }
1455
+
1456
+ /*
1457
+ * Allocate space for a Curl::Easy instance.
1458
+ */
1459
+ static VALUE ruby_curl_easy_allocate(VALUE klass) {
1460
+ ruby_curl_easy *rbce;
1461
+ rbce = ALLOC(ruby_curl_easy);
1462
+ if (!rbce) {
1463
+ rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Easy");
1464
+ }
1465
+ rbce->curl = NULL;
1466
+ rbce->opts = Qnil;
1467
+ rbce->multi = Qnil;
1468
+ ruby_curl_easy_zero(rbce);
1469
+ return TypedData_Wrap_Struct(klass, &ruby_curl_easy_data_type, rbce);
263
1470
  }
264
1471
 
265
1472
  /*
266
1473
  * call-seq:
267
- * Curl::Easy.new => #&lt;Curl::Easy...&gt;
268
- * Curl::Easy.new(url = nil) => #&lt;Curl::Easy...&gt;
269
- * Curl::Easy.new(url = nil) { |self| ... } => #&lt;Curl::Easy...&gt;
1474
+ * Curl::Easy.new => #<Curl::Easy...>
1475
+ * Curl::Easy.new(url = nil) => #<Curl::Easy...>
1476
+ * Curl::Easy.new(url = nil) { |self| ... } => #<Curl::Easy...>
270
1477
  *
271
- * Create a new Curl::Easy instance, optionally supplying the URL.
1478
+ * Initialize a new Curl::Easy instance, optionally supplying the URL.
272
1479
  * The block form allows further configuration to be supplied before
273
1480
  * the instance is returned.
274
1481
  */
275
- static VALUE ruby_curl_easy_new(int argc, VALUE *argv, VALUE klass) {
1482
+ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
276
1483
  CURLcode ecode;
277
1484
  VALUE url, blk;
278
- VALUE new_curl;
279
1485
  ruby_curl_easy *rbce;
280
1486
 
281
1487
  rb_scan_args(argc, argv, "01&", &url, &blk);
282
1488
 
283
- rbce = ALLOC(ruby_curl_easy);
1489
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
284
1490
 
285
1491
  /* handler */
286
1492
  rbce->curl = curl_easy_init();
@@ -288,32 +1494,67 @@ static VALUE ruby_curl_easy_new(int argc, VALUE *argv, VALUE klass) {
288
1494
  rb_raise(eCurlErrFailedInit, "Failed to initialize easy handle");
289
1495
  }
290
1496
 
291
- new_curl = Data_Wrap_Struct(klass, curl_easy_mark, curl_easy_free, rbce);
292
-
293
1497
  rbce->multi = Qnil;
294
1498
  rbce->opts = Qnil;
295
1499
 
296
1500
  ruby_curl_easy_zero(rbce);
1501
+ rbce->self = self;
1502
+
1503
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
297
1504
 
298
1505
  rb_easy_set("url", url);
299
1506
 
300
- /* set the new_curl pointer to the curl handle */
301
- ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)new_curl);
1507
+ /* set the pointer to the curl handle */
1508
+ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
302
1509
  if (ecode != CURLE_OK) {
303
1510
  raise_curl_easy_error_exception(ecode);
304
1511
  }
305
1512
 
306
1513
  if (blk != Qnil) {
307
- rb_funcall(blk, idCall, 1, new_curl);
1514
+ rb_funcall(blk, idCall, 1, self);
308
1515
  }
309
1516
 
310
- return new_curl;
1517
+ return self;
1518
+ }
1519
+
1520
+ /* Helper to duplicate a curl_slist */
1521
+ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list) {
1522
+ struct curl_slist *dup = NULL;
1523
+ struct curl_slist *tmp;
1524
+ struct curl_slist *new_list;
1525
+ for (tmp = list; tmp; tmp = tmp->next) {
1526
+ new_list = curl_slist_append(dup, tmp->data);
1527
+ if (!new_list) {
1528
+ if (dup) { curl_slist_free_all(dup); }
1529
+ rb_raise(rb_eNoMemError, "Failed to duplicate curl_slist");
1530
+ }
1531
+ dup = new_list;
1532
+ }
1533
+ return dup;
1534
+ }
1535
+
1536
+ static VALUE duplicate_upload(VALUE upload) {
1537
+ ruby_curl_upload *rbcu, *newrbcu;
1538
+ VALUE new_upload;
1539
+
1540
+ if (NIL_P(upload)) {
1541
+ return Qnil;
1542
+ }
1543
+
1544
+ TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
1545
+
1546
+ new_upload = ruby_curl_upload_new(cCurlUpload);
1547
+ TypedData_Get_Struct(new_upload, ruby_curl_upload, &ruby_curl_upload_data_type, newrbcu);
1548
+ newrbcu->stream = rbcu->stream;
1549
+ newrbcu->offset = rbcu->offset;
1550
+
1551
+ return new_upload;
311
1552
  }
312
1553
 
313
1554
  /*
314
1555
  * call-seq:
315
- * easy.clone => #&lt;easy clone&gt;
316
- * easy.dup => #&lt;easy clone&gt;
1556
+ * easy.clone => <easy clone>
1557
+ * easy.dup => <easy clone>
317
1558
  *
318
1559
  * Clone this Curl::Easy instance, creating a new instance.
319
1560
  * This method duplicates the underlying CURL* handle.
@@ -321,15 +1562,65 @@ static VALUE ruby_curl_easy_new(int argc, VALUE *argv, VALUE klass) {
321
1562
  static VALUE ruby_curl_easy_clone(VALUE self) {
322
1563
  ruby_curl_easy *rbce, *newrbce;
323
1564
 
324
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1565
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
325
1566
 
326
1567
  newrbce = ALLOC(ruby_curl_easy);
1568
+ if (!newrbce) {
1569
+ rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Easy clone");
1570
+ }
1571
+ /* shallow copy */
327
1572
  memcpy(newrbce, rbce, sizeof(ruby_curl_easy));
1573
+
1574
+ /* now deep copy */
328
1575
  newrbce->curl = curl_easy_duphandle(rbce->curl);
329
- newrbce->curl_headers = NULL;
330
- newrbce->curl_ftp_commands = NULL;
1576
+ if (!newrbce->curl) {
1577
+ free(newrbce);
1578
+ rb_raise(rb_eNoMemError, "Failed to duplicate Curl::Easy handle");
1579
+ }
1580
+ newrbce->curl_headers = (rbce->curl_headers) ? duplicate_curl_slist(rbce->curl_headers) : NULL;
1581
+ newrbce->curl_proxy_headers = (rbce->curl_proxy_headers) ? duplicate_curl_slist(rbce->curl_proxy_headers) : NULL;
1582
+ newrbce->curl_ftp_commands = (rbce->curl_ftp_commands) ? duplicate_curl_slist(rbce->curl_ftp_commands) : NULL;
1583
+ newrbce->curl_resolve = (rbce->curl_resolve) ? duplicate_curl_slist(rbce->curl_resolve) : NULL;
1584
+ newrbce->curl_connect_to = (rbce->curl_connect_to) ? duplicate_curl_slist(rbce->curl_connect_to) : NULL;
1585
+ newrbce->network_allowed_cidr_rules = NULL;
1586
+ newrbce->network_allowed_cidr_rule_count = 0;
1587
+ newrbce->network_allowed_hosts = NULL;
1588
+ newrbce->network_allowed_host_count = 0;
1589
+
1590
+ /* A cloned easy should not retain ownership reference to the original multi. */
1591
+ newrbce->multi = Qnil;
1592
+ newrbce->callback_error = Qnil;
1593
+ newrbce->unsafe_destination_blocked = 0;
1594
+ memset(newrbce->unsafe_destination_error, 0, CURL_ERROR_SIZE);
1595
+ newrbce->native_active = 0;
1596
+
1597
+ if (rbce->opts != Qnil) {
1598
+ newrbce->opts = rb_funcall(rbce->opts, rb_intern("dup"), 0);
1599
+ }
331
1600
 
332
- return Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, newrbce);
1601
+ /* Set the error buffer on the new curl handle using the new err_buf */
1602
+ curl_easy_setopt(newrbce->curl, CURLOPT_ERRORBUFFER, newrbce->err_buf);
1603
+
1604
+ if (newrbce->opts != Qnil) {
1605
+ VALUE upload = rb_hash_aref(newrbce->opts, rb_easy_hkey("upload"));
1606
+ if (!NIL_P(upload)) {
1607
+ rb_hash_aset(newrbce->opts, rb_easy_hkey("upload"), duplicate_upload(upload));
1608
+ curl_easy_setopt(newrbce->curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler);
1609
+ curl_easy_setopt(newrbce->curl, CURLOPT_READDATA, newrbce);
1610
+ #ifdef HAVE_CURLOPT_SEEKFUNCTION
1611
+ curl_easy_setopt(newrbce->curl, CURLOPT_SEEKFUNCTION, (curl_seek_callback)seek_data_handler);
1612
+ #endif
1613
+ #ifdef HAVE_CURLOPT_SEEKDATA
1614
+ curl_easy_setopt(newrbce->curl, CURLOPT_SEEKDATA, newrbce);
1615
+ #endif
1616
+ }
1617
+ }
1618
+
1619
+ VALUE clone = TypedData_Wrap_Struct(cCurlEasy, &ruby_curl_easy_data_type, newrbce);
1620
+ newrbce->self = clone;
1621
+ curl_easy_setopt(newrbce->curl, CURLOPT_PRIVATE, (void*)newrbce);
1622
+
1623
+ return clone;
333
1624
  }
334
1625
 
335
1626
  /*
@@ -337,19 +1628,23 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
337
1628
  * easy.close => nil
338
1629
  *
339
1630
  * Close the Curl::Easy instance. Any open connections are closed
340
- * The easy handle is reinitialized. If a previous multi handle was
1631
+ * The easy handle is reinitialized. If a previous multi handle was
341
1632
  * open it is set to nil and will be cleared after a GC.
342
1633
  */
343
1634
  static VALUE ruby_curl_easy_close(VALUE self) {
344
1635
  CURLcode ecode;
345
1636
  ruby_curl_easy *rbce;
346
1637
 
347
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1638
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
348
1639
 
349
1640
  if (rbce->callback_active) {
350
1641
  rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback");
351
1642
  }
352
1643
 
1644
+ if (rbce->native_active) {
1645
+ rb_raise(rb_eRuntimeError, "Cannot close an active curl handle during native operation");
1646
+ }
1647
+
353
1648
  ruby_curl_easy_free(rbce);
354
1649
 
355
1650
  /* reinit the handle */
@@ -361,9 +1656,12 @@ static VALUE ruby_curl_easy_close(VALUE self) {
361
1656
  rbce->multi = Qnil;
362
1657
 
363
1658
  ruby_curl_easy_zero(rbce);
1659
+ rbce->self = self;
1660
+
1661
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, rbce->err_buf);
364
1662
 
365
1663
  /* give the new curl handle a reference back to the ruby object */
366
- ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)self);
1664
+ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
367
1665
  if (ecode != CURLE_OK) {
368
1666
  raise_curl_easy_error_exception(ecode);
369
1667
  }
@@ -387,29 +1685,31 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
387
1685
  CURLcode ecode;
388
1686
  ruby_curl_easy *rbce;
389
1687
  VALUE opts_dup;
390
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1688
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
391
1689
 
392
1690
  if (rbce->callback_active) {
393
1691
  rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback");
394
1692
  }
395
1693
 
1694
+ if (rbce->native_active) {
1695
+ rb_raise(rb_eRuntimeError, "Cannot reset an active curl handle during native operation");
1696
+ }
1697
+
396
1698
  opts_dup = rb_funcall(rbce->opts, rb_intern("dup"), 0);
397
1699
 
1700
+ ruby_curl_easy_cleanup(self, rbce);
398
1701
  curl_easy_reset(rbce->curl);
399
1702
  ruby_curl_easy_zero(rbce);
1703
+ rbce->self = self;
400
1704
 
401
- /* rest clobbers the private setting, so reset it to self */
402
- ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)self);
1705
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
1706
+
1707
+ /* reset clobbers the private setting, so reset it to self */
1708
+ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
403
1709
  if (ecode != CURLE_OK) {
404
1710
  raise_curl_easy_error_exception(ecode);
405
1711
  }
406
1712
 
407
- /* Free everything up */
408
- if (rbce->curl_headers) {
409
- curl_slist_free_all(rbce->curl_headers);
410
- rbce->curl_headers = NULL;
411
- }
412
-
413
1713
  return opts_dup;
414
1714
  }
415
1715
 
@@ -462,6 +1762,10 @@ static VALUE ruby_curl_easy_headers_set(VALUE self, VALUE headers) {
462
1762
  CURB_OBJECT_HSETTER(ruby_curl_easy, headers);
463
1763
  }
464
1764
 
1765
+ static VALUE ruby_curl_easy_proxy_headers_set(VALUE self, VALUE proxy_headers) {
1766
+ CURB_OBJECT_HSETTER(ruby_curl_easy, proxy_headers);
1767
+ }
1768
+
465
1769
  /*
466
1770
  * call-seq:
467
1771
  * easy.headers => Hash, Array or Str
@@ -471,12 +1775,47 @@ static VALUE ruby_curl_easy_headers_set(VALUE self, VALUE headers) {
471
1775
  static VALUE ruby_curl_easy_headers_get(VALUE self) {
472
1776
  ruby_curl_easy *rbce;
473
1777
  VALUE headers;
474
- Data_Get_Struct(self, ruby_curl_easy, rbce);
475
- headers = rb_easy_get("headers");//rb_hash_aref(rbce->opts, rb_intern("headers"));
1778
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1779
+ headers = rb_easy_get("headers");//rb_hash_aref(rbce->opts, rb_intern("headers"));
476
1780
  if (headers == Qnil) { headers = rb_easy_set("headers", rb_hash_new()); }
477
1781
  return headers;
478
1782
  }
479
1783
 
1784
+ /*
1785
+ * call-seq:
1786
+ * easy.proxy_headers = "Header: val" => "Header: val"
1787
+ * easy.proxy_headers = {"Header" => "val" ..., "Header" => "val"} => {"Header: val", ...}
1788
+ * easy.proxy_headers = ["Header: val" ..., "Header: val"] => ["Header: val", ...]
1789
+ *
1790
+ *
1791
+ * For example to set a standard or custom header:
1792
+ *
1793
+ * easy.proxy_headers["MyHeader"] = "myval"
1794
+ *
1795
+ * To remove a standard header (this is useful when removing libcurls default
1796
+ * 'Expect: 100-Continue' header when using HTTP form posts):
1797
+ *
1798
+ * easy.proxy_headers["Expect"] = ''
1799
+ *
1800
+ * Anything passed to libcurl as a header will be converted to a string during
1801
+ * the perform step.
1802
+ */
1803
+
1804
+ /*
1805
+ * call-seq:
1806
+ * easy.proxy_headers => Hash, Array or Str
1807
+ *
1808
+ * Obtain the custom HTTP proxy_headers for following requests.
1809
+ */
1810
+ static VALUE ruby_curl_easy_proxy_headers_get(VALUE self) {
1811
+ ruby_curl_easy *rbce;
1812
+ VALUE proxy_headers;
1813
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1814
+ proxy_headers = rb_easy_get("proxy_headers");//rb_hash_aref(rbce->opts, rb_intern("proxy_headers"));
1815
+ if (proxy_headers == Qnil) { proxy_headers = rb_easy_set("proxy_headers", rb_hash_new()); }
1816
+ return proxy_headers;
1817
+ }
1818
+
480
1819
  /*
481
1820
  * call-seq:
482
1821
  * easy.interface => string
@@ -515,7 +1854,16 @@ static VALUE ruby_curl_easy_proxypwd_get(VALUE self) {
515
1854
  * call-seq:
516
1855
  * easy.cookies => "name1=content1; name2=content2;"
517
1856
  *
518
- * Obtain the cookies for this Curl::Easy instance.
1857
+ * Obtain the manually set Cookie header string for this Curl::Easy instance.
1858
+ *
1859
+ * Notes:
1860
+ * - This corresponds to libcurl's CURLOPT_COOKIE and only affects the outgoing
1861
+ * Cookie request header. It does NOT modify the internal libcurl cookie engine
1862
+ * that stores cookies received via Set-Cookie.
1863
+ * - To inspect or modify cookies stored in the cookie engine, use
1864
+ * +easy.cookielist+ (getter) and +easy.cookielist=+ or +easy.set(:cookielist, ...)+ (setter).
1865
+ * - To clear a previously set manual Cookie header, assign an empty string.
1866
+ * Assigning +nil+ currently has no effect.
519
1867
  */
520
1868
  static VALUE ruby_curl_easy_cookies_get(VALUE self) {
521
1869
  CURB_OBJECT_HGETTER(ruby_curl_easy, cookies);
@@ -525,7 +1873,8 @@ static VALUE ruby_curl_easy_cookies_get(VALUE self) {
525
1873
  * call-seq:
526
1874
  * easy.cookiefile => string
527
1875
  *
528
- * Obtain the cookiefile file for this Curl::Easy instance.
1876
+ * Obtain the cookiefile path for this Curl::Easy instance (used to load cookies when the
1877
+ * cookie engine is enabled).
529
1878
  */
530
1879
  static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
531
1880
  CURB_OBJECT_HGETTER(ruby_curl_easy, cookiefile);
@@ -535,7 +1884,8 @@ static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
535
1884
  * call-seq:
536
1885
  * easy.cookiejar => string
537
1886
  *
538
- * Obtain the cookiejar file to use for this Curl::Easy instance.
1887
+ * Obtain the cookiejar path for this Curl::Easy instance (used to persist cookies when the
1888
+ * cookie engine is enabled).
539
1889
  */
540
1890
  static VALUE ruby_curl_easy_cookiejar_get(VALUE self) {
541
1891
  CURB_OBJECT_HGETTER(ruby_curl_easy, cookiejar);
@@ -684,57 +2034,102 @@ static VALUE ruby_curl_easy_useragent_get(VALUE self) {
684
2034
  /*
685
2035
  * call-seq:
686
2036
  * easy.post_body = "some=form%20data&to=send" => string or nil
687
- *
2037
+ *
688
2038
  * Sets the POST body of this Curl::Easy instance. This is expected to be
689
2039
  * URL encoded; no additional processing or encoding is done on the string.
690
2040
  * The content-type header will be set to application/x-www-form-urlencoded.
691
- *
2041
+ *
692
2042
  * This is handy if you want to perform a POST against a Curl::Multi instance.
693
2043
  */
694
- static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
2044
+ struct post_body_set_args {
2045
+ VALUE self;
2046
+ VALUE post_body;
2047
+ int force_http_get_on_nil;
2048
+ };
2049
+
2050
+ static VALUE ruby_curl_easy_post_body_set_with_mode_body(VALUE argp) {
2051
+ struct post_body_set_args *args = (struct post_body_set_args *)argp;
2052
+ VALUE self = args->self;
2053
+ VALUE post_body = args->post_body;
2054
+ int force_http_get_on_nil = args->force_http_get_on_nil;
695
2055
  ruby_curl_easy *rbce;
696
2056
  CURL *curl;
697
-
2057
+
2058
+ VALUE body_str;
2059
+ VALUE retained_body_str;
698
2060
  char *data;
699
2061
  long len;
700
2062
 
701
- Data_Get_Struct(self, ruby_curl_easy, rbce);
702
-
2063
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2064
+
703
2065
  curl = rbce->curl;
704
-
2066
+
705
2067
  if ( post_body == Qnil ) {
706
2068
  rb_easy_del("postdata_buffer");
707
- curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
708
-
709
- } else {
2069
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
2070
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0);
2071
+ if (force_http_get_on_nil) {
2072
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
2073
+ }
2074
+
2075
+ } else {
710
2076
  if (rb_type(post_body) == T_STRING) {
711
- data = StringValuePtr(post_body);
712
- len = RSTRING_LEN(post_body);
2077
+ body_str = post_body;
713
2078
  }
714
2079
  else if (rb_respond_to(post_body, rb_intern("to_s"))) {
715
- VALUE str_body = rb_funcall(post_body, rb_intern("to_s"), 0);
716
- data = StringValuePtr(str_body);
717
- len = RSTRING_LEN(post_body);
2080
+ body_str = rb_funcall(post_body, rb_intern("to_s"), 0);
718
2081
  }
719
2082
  else {
720
2083
  rb_raise(rb_eRuntimeError, "post data must respond_to .to_s");
721
2084
  }
722
-
723
- // Store the string, since it has to hang around for the duration of the
2085
+
2086
+ StringValue(body_str);
2087
+
2088
+ // Store the string, since it has to hang around for the duration of the
724
2089
  // request. See CURLOPT_POSTFIELDS in the libcurl docs.
725
- //rbce->postdata_buffer = post_body;
726
- rb_easy_set("postdata_buffer", post_body);
727
-
2090
+ #ifdef HAVE_CURLOPT_COPYPOSTFIELDS
2091
+ /*
2092
+ * libcurl copies the bytes immediately for COPYPOSTFIELDS, so retain a
2093
+ * matching Ruby snapshot for post_body instead of the caller's mutable
2094
+ * source string.
2095
+ */
2096
+ retained_body_str = rb_str_dup(body_str);
2097
+ #else
2098
+ retained_body_str = body_str;
2099
+ #endif
2100
+ data = StringValuePtr(retained_body_str);
2101
+ len = RSTRING_LEN(retained_body_str);
2102
+ rb_easy_set("postdata_buffer", retained_body_str);
2103
+
728
2104
  curl_easy_setopt(curl, CURLOPT_POST, 1);
729
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
730
2105
  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
731
-
2106
+ #ifdef HAVE_CURLOPT_COPYPOSTFIELDS
2107
+ curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, data);
2108
+ #else
2109
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
2110
+ #endif
2111
+
732
2112
  return post_body;
733
2113
  }
734
-
2114
+
735
2115
  return Qnil;
736
2116
  }
737
2117
 
2118
+ static VALUE ruby_curl_easy_post_body_set_with_mode(VALUE self, VALUE post_body, int force_http_get_on_nil) {
2119
+ ruby_curl_easy *rbce;
2120
+ struct post_body_set_args args = { self, post_body, force_http_get_on_nil };
2121
+
2122
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2123
+ ruby_curl_easy_enter_native(rbce);
2124
+
2125
+ return rb_ensure(ruby_curl_easy_post_body_set_with_mode_body, (VALUE)&args,
2126
+ ruby_curl_easy_leave_native, (VALUE)rbce);
2127
+ }
2128
+
2129
+ static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
2130
+ return ruby_curl_easy_post_body_set_with_mode(self, post_body, 1);
2131
+ }
2132
+
738
2133
  /*
739
2134
  * call-seq:
740
2135
  * easy.post_body => string or nil
@@ -748,7 +2143,7 @@ static VALUE ruby_curl_easy_post_body_get(VALUE self) {
748
2143
  /*
749
2144
  * call-seq:
750
2145
  * easy.put_data = data => ""
751
- *
2146
+ *
752
2147
  * Points this Curl::Easy instance to data to be uploaded via PUT. This
753
2148
  * sets the request to a PUT type request - useful if you want to PUT via
754
2149
  * a multi handle.
@@ -757,26 +2152,15 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
757
2152
  ruby_curl_easy *rbce;
758
2153
  CURL *curl;
759
2154
  VALUE upload;
2155
+ VALUE upload_stream = data;
760
2156
  VALUE headers;
2157
+ VALUE infile_size = Qnil;
761
2158
 
762
- Data_Get_Struct(self, ruby_curl_easy, rbce);
763
-
764
- upload = ruby_curl_upload_new(cCurlUpload);
765
- ruby_curl_upload_stream_set(upload,data);
766
-
767
- curl = rbce->curl;
768
- rb_easy_set("upload", upload); /* keep the upload object alive as long as
769
- the easy handle is active or until the upload
770
- is complete or terminated... */
771
-
772
- curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
773
- curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
774
- curl_easy_setopt(curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler);
775
- curl_easy_setopt(curl, CURLOPT_READDATA, rbce);
2159
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
776
2160
 
777
- /*
778
- * we need to set specific headers for the PUT to work... so
779
- * convert the internal headers structure to a HASH if one is set
2161
+ /*
2162
+ * Validate and prepare Ruby-visible state before mutating the CURL handle.
2163
+ * Several branches below can raise (header type, stat, size, to_s).
780
2164
  */
781
2165
  if (!rb_easy_nil("headers")) {
782
2166
  if (rb_easy_type_check("headers", T_ARRAY) || rb_easy_type_check("headers", T_STRING)) {
@@ -784,43 +2168,80 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
784
2168
  }
785
2169
  }
786
2170
 
787
- // exit fast if the payload is empty
788
- if (NIL_P(data)) { return data; }
2171
+ if (!NIL_P(data) && !rb_respond_to(data, rb_intern("read"))) {
2172
+ if (rb_respond_to(data, rb_intern("to_s"))) {
2173
+ upload_stream = rb_obj_as_string(data);
2174
+ } else {
2175
+ rb_raise(rb_eRuntimeError, "PUT data must respond to read or to_s");
2176
+ }
2177
+ }
789
2178
 
790
2179
  headers = rb_easy_get("headers");
791
- if( headers == Qnil ) {
2180
+ if( headers == Qnil ) {
792
2181
  headers = rb_hash_new();
793
2182
  }
794
2183
 
795
- if (rb_respond_to(data, rb_intern("read"))) {
796
- VALUE stat = rb_funcall(data, rb_intern("stat"), 0);
797
- if( stat && rb_hash_aref(headers, rb_str_new2("Content-Length")) == Qnil) {
2184
+ if (!NIL_P(data) && rb_respond_to(data, rb_intern("read"))) {
2185
+ VALUE stat = Qnil;
2186
+ if (rb_respond_to(data, rb_intern("stat"))) {
2187
+ stat = rb_funcall(data, rb_intern("stat"), 0);
2188
+ }
2189
+ if(!NIL_P(stat) && stat != Qfalse && rb_hash_aref(headers, rb_str_new2("Content-Length")) == Qnil) {
798
2190
  VALUE size;
799
2191
  if( rb_hash_aref(headers, rb_str_new2("Expect")) == Qnil ) {
800
2192
  rb_hash_aset(headers, rb_str_new2("Expect"), rb_str_new2(""));
801
2193
  }
802
2194
  size = rb_funcall(stat, rb_intern("size"), 0);
803
- curl_easy_setopt(curl, CURLOPT_INFILESIZE, FIX2LONG(size));
2195
+ infile_size = size;
804
2196
  }
805
2197
  else if( rb_hash_aref(headers, rb_str_new2("Content-Length")) == Qnil && rb_hash_aref(headers, rb_str_new2("Transfer-Encoding")) == Qnil ) {
806
2198
  rb_hash_aset(headers, rb_str_new2("Transfer-Encoding"), rb_str_new2("chunked"));
807
2199
  }
808
2200
  else if( rb_hash_aref(headers, rb_str_new2("Content-Length")) ) {
809
2201
  VALUE size = rb_funcall(rb_hash_aref(headers, rb_str_new2("Content-Length")), rb_intern("to_i"), 0);
810
- curl_easy_setopt(curl, CURLOPT_INFILESIZE, FIX2LONG(size));
2202
+ infile_size = size;
811
2203
  }
812
2204
  }
813
- else if (rb_respond_to(data, rb_intern("to_s"))) {
814
- curl_easy_setopt(curl, CURLOPT_INFILESIZE, RSTRING_LEN(data));
2205
+ else if (!NIL_P(data) && rb_respond_to(data, rb_intern("to_s"))) {
2206
+ infile_size = LONG2NUM(RSTRING_LEN(upload_stream));
815
2207
  if( rb_hash_aref(headers, rb_str_new2("Expect")) == Qnil ) {
816
2208
  rb_hash_aset(headers, rb_str_new2("Expect"), rb_str_new2(""));
817
2209
  }
818
2210
  }
2211
+ else if (NIL_P(data)) {
2212
+ /* Preserve legacy nil handling: configure an upload with no payload. */
2213
+ }
819
2214
  else {
820
2215
  rb_raise(rb_eRuntimeError, "PUT data must respond to read or to_s");
821
2216
  }
822
2217
  rb_easy_set("headers",headers);
823
2218
 
2219
+ upload = ruby_curl_upload_new(cCurlUpload);
2220
+ ruby_curl_upload_stream_set(upload, upload_stream);
2221
+
2222
+ curl = rbce->curl;
2223
+ rb_easy_set("upload", upload); /* keep the upload object alive as long as
2224
+ the easy handle is active or until the upload
2225
+ is complete or terminated... */
2226
+
2227
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
2228
+ curl_easy_setopt(curl, CURLOPT_POST, 0);
2229
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
2230
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0);
2231
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
2232
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler);
2233
+ #ifdef HAVE_CURLOPT_SEEKFUNCTION
2234
+ curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, (curl_seek_callback)seek_data_handler);
2235
+ #endif
2236
+ curl_easy_setopt(curl, CURLOPT_READDATA, rbce);
2237
+ #ifdef HAVE_CURLOPT_SEEKDATA
2238
+ curl_easy_setopt(curl, CURLOPT_SEEKDATA, rbce);
2239
+ #endif
2240
+
2241
+ if (!NIL_P(infile_size)) {
2242
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, NUM2LONG(infile_size));
2243
+ }
2244
+
824
2245
  // if we made it this far, all should be well.
825
2246
  return data;
826
2247
  }
@@ -829,20 +2250,188 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
829
2250
  * call-seq:
830
2251
  * easy.ftp_commands = ["CWD /", "MKD directory"] => ["CWD /", ...]
831
2252
  *
832
- * Explicitly sets the list of commands to execute on the FTP server when calling perform
2253
+ * Explicitly sets the list of commands to execute on the FTP server when calling perform.
2254
+ *
2255
+ * NOTE:
2256
+ * - This maps to libcurl CURLOPT_QUOTE; it sends commands on the control connection.
2257
+ * - Do not include data-transfer commands like LIST/NLST/RETR/STOR here. libcurl does not
2258
+ * parse PASV/EPSV replies from QUOTE commands and will not establish the required data
2259
+ * connection. For directory listings, set CURLOPT_DIRLISTONLY (via `easy.set(:dirlistonly, true)`)
2260
+ * and request an FTP directory URL (e.g. "ftp://host/path/") so libcurl manages PASV/EPSV
2261
+ * and the data connection for you.
833
2262
  */
834
2263
  static VALUE ruby_curl_easy_ftp_commands_set(VALUE self, VALUE ftp_commands) {
835
2264
  CURB_OBJECT_HSETTER(ruby_curl_easy, ftp_commands);
836
2265
  }
837
2266
 
838
2267
  /*
839
- * call-seq
2268
+ * call-seq:
840
2269
  * easy.ftp_commands => array or nil
841
2270
  */
842
2271
  static VALUE ruby_curl_easy_ftp_commands_get(VALUE self) {
843
2272
  CURB_OBJECT_HGETTER(ruby_curl_easy, ftp_commands);
844
2273
  }
845
2274
 
2275
+ /*
2276
+ * call-seq:
2277
+ * easy.resolve = [ "example.com:80:127.0.0.1" ] => [ "example.com:80:127.0.0.1" ]
2278
+ *
2279
+ * Set the resolve list to statically resolve hostnames to IP addresses,
2280
+ * bypassing DNS for matching hostname/port combinations.
2281
+ */
2282
+ static VALUE ruby_curl_easy_resolve_set(VALUE self, VALUE resolve) {
2283
+ CURB_OBJECT_HSETTER(ruby_curl_easy, resolve);
2284
+ }
2285
+
2286
+ /*
2287
+ * call-seq:
2288
+ * easy.resolve => array or nil
2289
+ */
2290
+ static VALUE ruby_curl_easy_resolve_get(VALUE self) {
2291
+ CURB_OBJECT_HGETTER(ruby_curl_easy, resolve);
2292
+ }
2293
+
2294
+ /*
2295
+ * call-seq:
2296
+ * easy.connect_to = [ "example.com:80:127.0.0.1:80" ] => [ "example.com:80:127.0.0.1:80" ]
2297
+ *
2298
+ * Set the connect-to list to redirect matching request host/port pairs to
2299
+ * alternate connection host/port pairs.
2300
+ */
2301
+ static VALUE ruby_curl_easy_connect_to_set(VALUE self, VALUE connect_to) {
2302
+ CURB_OBJECT_HSETTER(ruby_curl_easy, connect_to);
2303
+ }
2304
+
2305
+ /*
2306
+ * call-seq:
2307
+ * easy.connect_to => array or nil
2308
+ */
2309
+ static VALUE ruby_curl_easy_connect_to_get(VALUE self) {
2310
+ CURB_OBJECT_HGETTER(ruby_curl_easy, connect_to);
2311
+ }
2312
+
2313
+ /*
2314
+ * call-seq:
2315
+ * easy.doh_url = "https://dns.example/dns-query" => "https://dns.example/dns-query"
2316
+ *
2317
+ * Set the DNS-over-HTTPS URL to use for resolving hostnames.
2318
+ */
2319
+ static VALUE ruby_curl_easy_doh_url_set(VALUE self, VALUE doh_url) {
2320
+ CURB_OBJECT_HSETTER(ruby_curl_easy, doh_url);
2321
+ }
2322
+
2323
+ /*
2324
+ * call-seq:
2325
+ * easy.doh_url => string or nil
2326
+ */
2327
+ static VALUE ruby_curl_easy_doh_url_get(VALUE self) {
2328
+ CURB_OBJECT_HGETTER(ruby_curl_easy, doh_url);
2329
+ }
2330
+
2331
+ #ifdef HAVE_CURLOPT_DNS_SERVERS
2332
+ /*
2333
+ * call-seq:
2334
+ * easy.dns_servers => string or nil
2335
+ */
2336
+ static VALUE ruby_curl_easy_dns_servers_get(VALUE self) {
2337
+ CURB_OBJECT_HGETTER(ruby_curl_easy, dns_servers);
2338
+ }
2339
+ #endif
2340
+
2341
+ static VALUE ruby_curl_easy_allow_unix_socket_set(VALUE self, VALUE allow) {
2342
+ ruby_curl_easy *rbce;
2343
+
2344
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2345
+ rbce->allow_unix_socket = RTEST(allow) ? 1 : 0;
2346
+
2347
+ return allow;
2348
+ }
2349
+
2350
+ static VALUE ruby_curl_easy_allow_proxy_set(VALUE self, VALUE allow) {
2351
+ ruby_curl_easy *rbce;
2352
+
2353
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2354
+ rbce->allow_proxy = RTEST(allow) ? 1 : 0;
2355
+
2356
+ return allow;
2357
+ }
2358
+
2359
+ /*
2360
+ * call-seq:
2361
+ * easy.allowed_cidrs = ["203.0.113.0/24"] => ["203.0.113.0/24"]
2362
+ *
2363
+ * Set resolved-peer CIDR ranges that are allowed when network_policy is :public.
2364
+ * Private/local unsafe ranges are still blocked before this allowlist is
2365
+ * evaluated.
2366
+ */
2367
+ static VALUE ruby_curl_easy_allowed_cidrs_set(VALUE self, VALUE cidrs) {
2368
+ ruby_curl_easy *rbce;
2369
+ VALUE normalized;
2370
+ VALUE stored;
2371
+
2372
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2373
+ normalized = curb_normalize_cidr_list(cidrs);
2374
+ stored = curb_dup_string_array(normalized);
2375
+ rb_hash_aset(rbce->opts, rb_easy_hkey("allowed_cidrs"), stored);
2376
+ if (rbce->network_policy == CURB_NETWORK_POLICY_PUBLIC) {
2377
+ curb_prepare_network_allowed_cidr_rules(rbce);
2378
+ } else {
2379
+ curb_clear_network_allowed_cidr_rules(rbce);
2380
+ }
2381
+
2382
+ return curb_dup_string_array(stored);
2383
+ }
2384
+
2385
+ /*
2386
+ * call-seq:
2387
+ * easy.allowed_cidrs => array or nil
2388
+ */
2389
+ static VALUE ruby_curl_easy_allowed_cidrs_get(VALUE self) {
2390
+ ruby_curl_easy *rbce;
2391
+ VALUE cidrs;
2392
+
2393
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2394
+ cidrs = rb_hash_aref(rbce->opts, rb_easy_hkey("allowed_cidrs"));
2395
+
2396
+ return curb_dup_string_array(cidrs);
2397
+ }
2398
+
2399
+ /*
2400
+ * call-seq:
2401
+ * easy.allowed_hosts = ["api.example.com"] => ["api.example.com"]
2402
+ *
2403
+ * Set URL hosts allowed for this handle. When libcurl supports
2404
+ * CURLOPT_PREREQFUNCTION, this is checked before each request, including
2405
+ * followed redirects.
2406
+ */
2407
+ static VALUE ruby_curl_easy_allowed_hosts_set(VALUE self, VALUE hosts) {
2408
+ ruby_curl_easy *rbce;
2409
+ VALUE normalized;
2410
+ VALUE stored;
2411
+
2412
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2413
+ normalized = curb_normalize_host_list(hosts);
2414
+ stored = curb_dup_string_array(normalized);
2415
+ rb_hash_aset(rbce->opts, rb_easy_hkey("allowed_hosts"), stored);
2416
+ curb_prepare_network_allowed_hosts(rbce);
2417
+
2418
+ return curb_dup_string_array(stored);
2419
+ }
2420
+
2421
+ /*
2422
+ * call-seq:
2423
+ * easy.allowed_hosts => array or nil
2424
+ */
2425
+ static VALUE ruby_curl_easy_allowed_hosts_get(VALUE self) {
2426
+ ruby_curl_easy *rbce;
2427
+ VALUE hosts;
2428
+
2429
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2430
+ hosts = rb_hash_aref(rbce->opts, rb_easy_hkey("allowed_hosts"));
2431
+
2432
+ return curb_dup_string_array(hosts);
2433
+ }
2434
+
846
2435
  /* ================== IMMED ATTRS ==================*/
847
2436
 
848
2437
  /*
@@ -951,16 +2540,16 @@ static VALUE ruby_curl_easy_proxy_type_get(VALUE self) {
951
2540
  (!strncmp("digest",node,6)) ? CURLAUTH_DIGEST : \
952
2541
  (!strncmp("gssnegotiate",node,12)) ? CURLAUTH_GSSNEGOTIATE : \
953
2542
  (!strncmp("ntlm",node,4)) ? CURLAUTH_NTLM : \
954
- (!strncmp("any",node,3)) ? CURLAUTH_ANY : \
955
- (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : 0
956
- #else
2543
+ (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : \
2544
+ (!strncmp("any",node,3)) ? CURLAUTH_ANY : 0
2545
+ #else
957
2546
  #define CURL_HTTPAUTH_STR_TO_NUM(node) \
958
2547
  (!strncmp("basic",node,5)) ? CURLAUTH_BASIC : \
959
2548
  (!strncmp("digest",node,6)) ? CURLAUTH_DIGEST : \
960
2549
  (!strncmp("gssnegotiate",node,12)) ? CURLAUTH_GSSNEGOTIATE : \
961
2550
  (!strncmp("ntlm",node,4)) ? CURLAUTH_NTLM : \
962
- (!strncmp("any",node,3)) ? CURLAUTH_ANY : \
963
- (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : 0
2551
+ (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : \
2552
+ (!strncmp("any",node,3)) ? CURLAUTH_ANY : 0
964
2553
  #endif
965
2554
  /*
966
2555
  * call-seq:
@@ -974,21 +2563,22 @@ static VALUE ruby_curl_easy_proxy_type_get(VALUE self) {
974
2563
  static VALUE ruby_curl_easy_http_auth_types_set(int argc, VALUE *argv, VALUE self) {//VALUE self, VALUE http_auth_types) {
975
2564
  ruby_curl_easy *rbce;
976
2565
  VALUE args_ary;
977
- int i, len;
2566
+ long i, len;
978
2567
  char* node = NULL;
979
- long mask = 0x000000;
2568
+ long mask = 0;
980
2569
 
981
2570
  rb_scan_args(argc, argv, "*", &args_ary);
982
- Data_Get_Struct(self, ruby_curl_easy, rbce);
2571
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
983
2572
 
984
- len = (int)RARRAY_LEN(args_ary);
2573
+ len = RARRAY_LEN(args_ary);
985
2574
 
986
- if (len == 1 && (TYPE(rb_ary_entry(args_ary,0)) == T_FIXNUM || rb_ary_entry(args_ary,0) == Qnil)) {
987
- if (rb_ary_entry(args_ary,0) == Qnil) {
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) {
988
2578
  rbce->http_auth_types = 0;
989
2579
  }
990
2580
  else {
991
- rbce->http_auth_types = NUM2INT(rb_ary_entry(args_ary,0));
2581
+ rbce->http_auth_types = NUM2LONG(rb_ary_entry(args_ary,0));
992
2582
  }
993
2583
  }
994
2584
  else {
@@ -1001,7 +2591,7 @@ static VALUE ruby_curl_easy_http_auth_types_set(int argc, VALUE *argv, VALUE sel
1001
2591
  }
1002
2592
  rbce->http_auth_types = mask;
1003
2593
  }
1004
- return INT2NUM(rbce->http_auth_types);
2594
+ return LONG2NUM(rbce->http_auth_types);
1005
2595
  }
1006
2596
 
1007
2597
  /*
@@ -1066,7 +2656,7 @@ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) {
1066
2656
 
1067
2657
  /*
1068
2658
  * call-seq:
1069
- * easy.timeout = fixnum or nil => fixnum or nil
2659
+ * easy.timeout = float, fixnum or nil => numeric
1070
2660
  *
1071
2661
  * Set the maximum time in seconds that you allow the libcurl transfer
1072
2662
  * operation to take. Normally, name lookups can take a considerable time
@@ -1075,20 +2665,77 @@ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) {
1075
2665
  *
1076
2666
  * Set to nil (or zero) to disable timeout (it will then only timeout
1077
2667
  * on the system's internal timeouts).
2668
+ *
2669
+ * Uses timeout_ms internally instead of timeout because it allows for
2670
+ * better precision and libcurl will use the last set value when both
2671
+ * timeout and timeout_ms are set.
2672
+ *
1078
2673
  */
1079
- static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout) {
1080
- CURB_IMMED_SETTER(ruby_curl_easy, timeout, 0);
2674
+ static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout_s) {
2675
+ ruby_curl_easy *rbce;
2676
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2677
+
2678
+ if (Qnil == timeout_s || NUM2DBL(timeout_s) <= 0.0) {
2679
+ rbce->timeout_ms = 0;
2680
+ } else {
2681
+ rbce->timeout_ms = (unsigned long)(NUM2DBL(timeout_s) * 1000);
2682
+ }
2683
+
2684
+ return DBL2NUM(rbce->timeout_ms / 1000.0);
1081
2685
  }
1082
2686
 
1083
2687
  /*
1084
2688
  * call-seq:
1085
- * easy.timeout => fixnum or nil
2689
+ * easy.timeout => numeric
1086
2690
  *
1087
2691
  * Obtain the maximum time in seconds that you allow the libcurl transfer
1088
2692
  * operation to take.
2693
+ *
2694
+ * Uses timeout_ms internally instead of timeout.
2695
+ *
2696
+ */
2697
+ static VALUE ruby_curl_easy_timeout_get(VALUE self) {
2698
+ ruby_curl_easy *rbce;
2699
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2700
+ return DBL2NUM(rbce->timeout_ms / 1000.0);
2701
+ }
2702
+
2703
+ /*
2704
+ * call-seq:
2705
+ * easy.timeout_ms = fixnum or nil => fixnum or nil
2706
+ *
2707
+ * Set the maximum time in milliseconds that you allow the libcurl transfer
2708
+ * operation to take. Normally, name lookups can take a considerable time
2709
+ * and limiting operations to less than a few minutes risk aborting
2710
+ * perfectly normal operations.
2711
+ *
2712
+ * Set to nil (or zero) to disable timeout (it will then only timeout
2713
+ * on the system's internal timeouts).
2714
+ */
2715
+ static VALUE ruby_curl_easy_timeout_ms_set(VALUE self, VALUE timeout_ms) {
2716
+ ruby_curl_easy *rbce;
2717
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2718
+
2719
+ if (Qnil == timeout_ms || NUM2DBL(timeout_ms) <= 0.0) {
2720
+ rbce->timeout_ms = 0;
2721
+ } else {
2722
+ rbce->timeout_ms = NUM2ULONG(timeout_ms);
2723
+ }
2724
+
2725
+ return ULONG2NUM(rbce->timeout_ms);
2726
+ }
2727
+
2728
+ /*
2729
+ * call-seq:
2730
+ * easy.timeout_ms => fixnum or nil
2731
+ *
2732
+ * Obtain the maximum time in milliseconds that you allow the libcurl transfer
2733
+ * operation to take.
1089
2734
  */
1090
- static VALUE ruby_curl_easy_timeout_get(VALUE self, VALUE timeout) {
1091
- CURB_IMMED_GETTER(ruby_curl_easy, timeout, 0);
2735
+ static VALUE ruby_curl_easy_timeout_ms_get(VALUE self) {
2736
+ ruby_curl_easy *rbce;
2737
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2738
+ return LONG2NUM(rbce->timeout_ms);
1092
2739
  }
1093
2740
 
1094
2741
  /*
@@ -1113,10 +2760,36 @@ static VALUE ruby_curl_easy_connect_timeout_set(VALUE self, VALUE connect_timeou
1113
2760
  * Obtain the maximum time in seconds that you allow the connection to the
1114
2761
  * server to take.
1115
2762
  */
1116
- static VALUE ruby_curl_easy_connect_timeout_get(VALUE self, VALUE connect_timeout) {
2763
+ static VALUE ruby_curl_easy_connect_timeout_get(VALUE self) {
1117
2764
  CURB_IMMED_GETTER(ruby_curl_easy, connect_timeout, 0);
1118
2765
  }
1119
2766
 
2767
+ /*
2768
+ * call-seq:
2769
+ * easy.connect_timeout_ms = fixnum or nil => fixnum or nil
2770
+ *
2771
+ * Set the maximum time in milliseconds that you allow the connection to the
2772
+ * server to take. This only limits the connection phase, once it has
2773
+ * connected, this option is of no more use.
2774
+ *
2775
+ * Set to nil (or zero) to disable connection timeout (it will then only
2776
+ * timeout on the system's internal timeouts).
2777
+ */
2778
+ static VALUE ruby_curl_easy_connect_timeout_ms_set(VALUE self, VALUE connect_timeout_ms) {
2779
+ CURB_IMMED_SETTER(ruby_curl_easy, connect_timeout_ms, 0);
2780
+ }
2781
+
2782
+ /*
2783
+ * call-seq:
2784
+ * easy.connect_timeout_ms => fixnum or nil
2785
+ *
2786
+ * Obtain the maximum time in milliseconds that you allow the connection to the
2787
+ * server to take.
2788
+ */
2789
+ static VALUE ruby_curl_easy_connect_timeout_ms_get(VALUE self) {
2790
+ CURB_IMMED_GETTER(ruby_curl_easy, connect_timeout_ms, 0);
2791
+ }
2792
+
1120
2793
  /*
1121
2794
  * call-seq:
1122
2795
  * easy.dns_cache_timeout = fixnum or nil => fixnum or nil
@@ -1136,7 +2809,7 @@ static VALUE ruby_curl_easy_dns_cache_timeout_set(VALUE self, VALUE dns_cache_ti
1136
2809
  *
1137
2810
  * Obtain the dns cache timeout in seconds.
1138
2811
  */
1139
- static VALUE ruby_curl_easy_dns_cache_timeout_get(VALUE self, VALUE dns_cache_timeout) {
2812
+ static VALUE ruby_curl_easy_dns_cache_timeout_get(VALUE self) {
1140
2813
  CURB_IMMED_GETTER(ruby_curl_easy, dns_cache_timeout, -1);
1141
2814
  }
1142
2815
 
@@ -1163,7 +2836,7 @@ static VALUE ruby_curl_easy_ftp_response_timeout_set(VALUE self, VALUE ftp_respo
1163
2836
  *
1164
2837
  * Obtain the maximum time that libcurl will wait for FTP command responses.
1165
2838
  */
1166
- static VALUE ruby_curl_easy_ftp_response_timeout_get(VALUE self, VALUE ftp_response_timeout) {
2839
+ static VALUE ruby_curl_easy_ftp_response_timeout_get(VALUE self) {
1167
2840
  CURB_IMMED_GETTER(ruby_curl_easy, ftp_response_timeout, 0);
1168
2841
  }
1169
2842
 
@@ -1186,7 +2859,7 @@ static VALUE ruby_curl_easy_low_speed_limit_set(VALUE self, VALUE low_speed_limi
1186
2859
  * Obtain the minimum transfer speed over +low_speed+time+ below which the
1187
2860
  * transfer will be aborted.
1188
2861
  */
1189
- static VALUE ruby_curl_easy_low_speed_limit_get(VALUE self, VALUE low_speed_limit) {
2862
+ static VALUE ruby_curl_easy_low_speed_limit_get(VALUE self) {
1190
2863
  CURB_IMMED_GETTER(ruby_curl_easy, low_speed_limit, 0);
1191
2864
  }
1192
2865
 
@@ -1208,10 +2881,50 @@ static VALUE ruby_curl_easy_low_speed_time_set(VALUE self, VALUE low_speed_time)
1208
2881
  * Obtain the time that the transfer should be below +low_speed_limit+ for
1209
2882
  * the library to abort it.
1210
2883
  */
1211
- static VALUE ruby_curl_easy_low_speed_time_get(VALUE self, VALUE low_speed_time) {
2884
+ static VALUE ruby_curl_easy_low_speed_time_get(VALUE self) {
1212
2885
  CURB_IMMED_GETTER(ruby_curl_easy, low_speed_time, 0);
1213
2886
  }
1214
2887
 
2888
+ /*
2889
+ * call-seq:
2890
+ * easy.max_send_speed_large = fixnum or nil => fixnum or nil
2891
+ *
2892
+ * Set the maximal sending transfer speed (in bytes per second)
2893
+ */
2894
+ static VALUE ruby_curl_easy_max_send_speed_large_set(VALUE self, VALUE max_send_speed_large) {
2895
+ CURB_IMMED_SETTER(ruby_curl_easy, max_send_speed_large, 0);
2896
+ }
2897
+
2898
+ /*
2899
+ * call-seq:
2900
+ * easy.max_send_speed_large = fixnum or nil => fixnum or nil
2901
+ *
2902
+ * Get the maximal sending transfer speed (in bytes per second)
2903
+ */
2904
+ static VALUE ruby_curl_easy_max_send_speed_large_get(VALUE self) {
2905
+ CURB_IMMED_GETTER(ruby_curl_easy, max_send_speed_large, 0);
2906
+ }
2907
+
2908
+ /*
2909
+ * call-seq:
2910
+ * easy.max_recv_speed_large = fixnum or nil => fixnum or nil
2911
+ *
2912
+ * Set the maximal receiving transfer speed (in bytes per second)
2913
+ */
2914
+ static VALUE ruby_curl_easy_max_recv_speed_large_set(VALUE self, VALUE max_recv_speed_large) {
2915
+ CURB_IMMED_SETTER(ruby_curl_easy, max_recv_speed_large, 0);
2916
+ }
2917
+
2918
+ /*
2919
+ * call-seq:
2920
+ * easy.max_recv_speed_large = fixnum or nil => fixnum or nil
2921
+ *
2922
+ * Get the maximal receiving transfer speed (in bytes per second)
2923
+ */
2924
+ static VALUE ruby_curl_easy_max_recv_speed_large_get(VALUE self) {
2925
+ CURB_IMMED_GETTER(ruby_curl_easy, max_recv_speed_large, 0);
2926
+ }
2927
+
1215
2928
  /*
1216
2929
  * call-seq:
1217
2930
  * easy.username = string => string
@@ -1219,7 +2932,7 @@ static VALUE ruby_curl_easy_low_speed_time_get(VALUE self, VALUE low_speed_time)
1219
2932
  * Set the HTTP Authentication username.
1220
2933
  */
1221
2934
  static VALUE ruby_curl_easy_username_set(VALUE self, VALUE username) {
1222
- #if HAVE_CURLOPT_USERNAME
2935
+ #ifdef HAVE_CURLOPT_USERNAME
1223
2936
  CURB_OBJECT_HSETTER(ruby_curl_easy, username);
1224
2937
  #else
1225
2938
  return Qnil;
@@ -1229,11 +2942,11 @@ static VALUE ruby_curl_easy_username_set(VALUE self, VALUE username) {
1229
2942
  /*
1230
2943
  * call-seq:
1231
2944
  * easy.username => string
1232
- *
2945
+ *
1233
2946
  * Get the current username
1234
2947
  */
1235
- static VALUE ruby_curl_easy_username_get(VALUE self, VALUE username) {
1236
- #if HAVE_CURLOPT_USERNAME
2948
+ static VALUE ruby_curl_easy_username_get(VALUE self) {
2949
+ #ifdef HAVE_CURLOPT_USERNAME
1237
2950
  CURB_OBJECT_HGETTER(ruby_curl_easy, username);
1238
2951
  #else
1239
2952
  return Qnil;
@@ -1247,7 +2960,7 @@ static VALUE ruby_curl_easy_username_get(VALUE self, VALUE username) {
1247
2960
  * Set the HTTP Authentication password.
1248
2961
  */
1249
2962
  static VALUE ruby_curl_easy_password_set(VALUE self, VALUE password) {
1250
- #if HAVE_CURLOPT_PASSWORD
2963
+ #ifdef HAVE_CURLOPT_PASSWORD
1251
2964
  CURB_OBJECT_HSETTER(ruby_curl_easy, password);
1252
2965
  #else
1253
2966
  return Qnil;
@@ -1257,11 +2970,11 @@ static VALUE ruby_curl_easy_password_set(VALUE self, VALUE password) {
1257
2970
  /*
1258
2971
  * call-seq:
1259
2972
  * easy.password => string
1260
- *
2973
+ *
1261
2974
  * Get the current password
1262
2975
  */
1263
- static VALUE ruby_curl_easy_password_get(VALUE self, VALUE password) {
1264
- #if HAVE_CURLOPT_PASSWORD
2976
+ static VALUE ruby_curl_easy_password_get(VALUE self) {
2977
+ #ifdef HAVE_CURLOPT_PASSWORD
1265
2978
  CURB_OBJECT_HGETTER(ruby_curl_easy, password);
1266
2979
  #else
1267
2980
  return Qnil;
@@ -1273,8 +2986,16 @@ static VALUE ruby_curl_easy_password_get(VALUE self, VALUE password) {
1273
2986
  * easy.ssl_version = value => fixnum or nil
1274
2987
  *
1275
2988
  * Sets the version of SSL/TLS that libcurl will attempt to use. Valid
1276
- * options are Curl::CURL_SSLVERSION_TLSv1, Curl::CURL_SSLVERSION::SSLv2,
1277
- * Curl::CURL_SSLVERSION_SSLv3 and Curl::CURL_SSLVERSION_DEFAULT
2989
+ * options are:
2990
+ *
2991
+ * Curl::CURL_SSLVERSION_DEFAULT
2992
+ * Curl::CURL_SSLVERSION_TLSv1 (TLS 1.x)
2993
+ * Curl::CURL_SSLVERSION_SSLv2
2994
+ * Curl::CURL_SSLVERSION_SSLv3
2995
+ * Curl::CURL_SSLVERSION_TLSv1_0
2996
+ * Curl::CURL_SSLVERSION_TLSv1_1
2997
+ * Curl::CURL_SSLVERSION_TLSv1_2
2998
+ * Curl::CURL_SSLVERSION_TLSv1_3
1278
2999
  */
1279
3000
  static VALUE ruby_curl_easy_ssl_version_set(VALUE self, VALUE ssl_version) {
1280
3001
  CURB_IMMED_SETTER(ruby_curl_easy, ssl_version, -1);
@@ -1286,14 +3007,14 @@ static VALUE ruby_curl_easy_ssl_version_set(VALUE self, VALUE ssl_version) {
1286
3007
  *
1287
3008
  * Get the version of SSL/TLS that libcurl will attempt to use.
1288
3009
  */
1289
- static VALUE ruby_curl_easy_ssl_version_get(VALUE self, VALUE ssl_version) {
3010
+ static VALUE ruby_curl_easy_ssl_version_get(VALUE self) {
1290
3011
  CURB_IMMED_GETTER(ruby_curl_easy, ssl_version, -1);
1291
3012
  }
1292
3013
 
1293
3014
  /*
1294
3015
  * call-seq:
1295
3016
  * easy.use_ssl = value => fixnum or nil
1296
- *
3017
+ *
1297
3018
  * Ensure libcurl uses SSL for FTP connections. Valid options are Curl::CURL_USESSL_NONE,
1298
3019
  * Curl::CURL_USESSL_TRY, Curl::CURL_USESSL_CONTROL, and Curl::CURL_USESSL_ALL.
1299
3020
  */
@@ -1307,7 +3028,7 @@ static VALUE ruby_curl_easy_use_ssl_set(VALUE self, VALUE use_ssl) {
1307
3028
  *
1308
3029
  * Get the desired level for using SSL on FTP connections.
1309
3030
  */
1310
- static VALUE ruby_curl_easy_use_ssl_get(VALUE self, VALUE use_ssl) {
3031
+ static VALUE ruby_curl_easy_use_ssl_get(VALUE self) {
1311
3032
  CURB_IMMED_GETTER(ruby_curl_easy, use_ssl, -1);
1312
3033
  }
1313
3034
 
@@ -1323,12 +3044,12 @@ static VALUE ruby_curl_easy_ftp_filemethod_set(VALUE self, VALUE ftp_filemethod)
1323
3044
  }
1324
3045
 
1325
3046
  /*
1326
- * call-seq
3047
+ * call-seq:
1327
3048
  * easy.ftp_filemethod => fixnum
1328
3049
  *
1329
3050
  * Get the configuration for how libcurl will reach files on the server.
1330
3051
  */
1331
- static VALUE ruby_curl_easy_ftp_filemethod_get(VALUE self, VALUE ftp_filemethod) {
3052
+ static VALUE ruby_curl_easy_ftp_filemethod_get(VALUE self) {
1332
3053
  CURB_IMMED_GETTER(ruby_curl_easy, ftp_filemethod, -1);
1333
3054
  }
1334
3055
 
@@ -1486,7 +3207,7 @@ static VALUE ruby_curl_easy_use_netrc_q(VALUE self) {
1486
3207
  */
1487
3208
  static VALUE ruby_curl_easy_autoreferer_set(VALUE self, VALUE autoreferer) {
1488
3209
  ruby_curl_easy *rbce;
1489
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3210
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1490
3211
 
1491
3212
  if (Qtrue == autoreferer) {
1492
3213
  curl_easy_setopt(rbce->curl, CURLOPT_AUTOREFERER, 1);
@@ -1587,7 +3308,12 @@ static VALUE ruby_curl_easy_multipart_form_post_q(VALUE self) {
1587
3308
  * easy.enable_cookies = boolean => boolean
1588
3309
  *
1589
3310
  * Configure whether the libcurl cookie engine is enabled for this Curl::Easy
1590
- * instance.
3311
+ * instance. When enabled, cookies received via Set-Cookie are stored by libcurl
3312
+ * and automatically sent on subsequent matching requests. Use +easy.cookiefile+
3313
+ * to load cookies and +easy.cookiejar+ to persist them.
3314
+ *
3315
+ * This setting is independent from the manual Cookie header set via +easy.cookies+.
3316
+ * The manual header is additive and can be cleared by assigning an empty string.
1591
3317
  */
1592
3318
  static VALUE ruby_curl_easy_enable_cookies_set(VALUE self, VALUE enable_cookies)
1593
3319
  {
@@ -1637,9 +3363,10 @@ static VALUE ruby_curl_easy_ignore_content_length_q(VALUE self) {
1637
3363
  */
1638
3364
  static VALUE ruby_curl_easy_resolve_mode(VALUE self) {
1639
3365
  ruby_curl_easy *rbce;
1640
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3366
+ unsigned short rm;
3367
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1641
3368
 
1642
- unsigned short rm = rbce->resolve_mode;
3369
+ rm = rbce->resolve_mode;
1643
3370
 
1644
3371
  switch(rm) {
1645
3372
  case CURL_IPRESOLVE_V4:
@@ -1668,9 +3395,10 @@ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
1668
3395
  return Qnil;
1669
3396
  } else {
1670
3397
  ruby_curl_easy *rbce;
1671
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3398
+ ID resolve_mode_id;
3399
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1672
3400
 
1673
- ID resolve_mode_id = rb_to_id(resolve_mode);
3401
+ resolve_mode_id = rb_to_id(resolve_mode);
1674
3402
 
1675
3403
  if (resolve_mode_id == rb_intern("auto")) {
1676
3404
  rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER;
@@ -1688,12 +3416,147 @@ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
1688
3416
  }
1689
3417
  }
1690
3418
 
3419
+ /*
3420
+ * call-seq:
3421
+ * easy.network_policy => symbol
3422
+ *
3423
+ * Determine which native network policy will be enforced when libcurl opens
3424
+ * sockets for this handle.
3425
+ */
3426
+ static VALUE ruby_curl_easy_network_policy_get(VALUE self) {
3427
+ ruby_curl_easy *rbce;
3428
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3429
+
3430
+ switch (rbce->network_policy) {
3431
+ case CURB_NETWORK_POLICY_PUBLIC:
3432
+ return ID2SYM(idNetworkPolicyPublic);
3433
+ case CURB_NETWORK_POLICY_NONE:
3434
+ default:
3435
+ return ID2SYM(idNetworkPolicyNone);
3436
+ }
3437
+ }
3438
+
3439
+ /*
3440
+ * call-seq:
3441
+ * easy.network_policy = symbol => symbol
3442
+ *
3443
+ * Supported values:
3444
+ * [:none] disables native destination checks.
3445
+ * [:public] blocks loopback, private, link-local, multicast,
3446
+ * unspecified, metadata, and other non-public addresses.
3447
+ */
3448
+ static VALUE ruby_curl_easy_network_policy_set(VALUE self, VALUE network_policy) {
3449
+ ruby_curl_easy *rbce;
3450
+ ID network_policy_id;
3451
+
3452
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3453
+
3454
+ if (NIL_P(network_policy)) {
3455
+ rbce->network_policy = CURB_NETWORK_POLICY_NONE;
3456
+ return ID2SYM(idNetworkPolicyNone);
3457
+ }
3458
+
3459
+ if (TYPE(network_policy) != T_SYMBOL) {
3460
+ rb_raise(rb_eTypeError, "network_policy must be a Symbol");
3461
+ }
3462
+
3463
+ network_policy_id = rb_to_id(network_policy);
3464
+
3465
+ if (network_policy_id == idNetworkPolicyNone) {
3466
+ rbce->network_policy = CURB_NETWORK_POLICY_NONE;
3467
+ rbce->allow_proxy = 0;
3468
+ return network_policy;
3469
+ } else if (network_policy_id == idNetworkPolicyPublic) {
3470
+ #ifndef CURB_HAVE_OPENSOCKET_NETWORK_POLICY
3471
+ rb_raise(rb_eNotImpError, "network_policy=:public requires CURLOPT_OPENSOCKETFUNCTION support");
3472
+ #else
3473
+ rbce->network_policy = CURB_NETWORK_POLICY_PUBLIC;
3474
+ rbce->allow_proxy = 0;
3475
+ return network_policy;
3476
+ #endif
3477
+ }
3478
+
3479
+ rb_raise(rb_eArgError, "network_policy must be one of :none, :public");
3480
+ }
3481
+
3482
+ /*
3483
+ * call-seq:
3484
+ * easy.unsafe_destination_error => string or nil
3485
+ *
3486
+ * Return the native network policy block reason from the most recent transfer.
3487
+ */
3488
+ static VALUE ruby_curl_easy_unsafe_destination_error_get(VALUE self) {
3489
+ ruby_curl_easy *rbce;
3490
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3491
+
3492
+ if (rbce->unsafe_destination_blocked && rbce->unsafe_destination_error[0]) {
3493
+ return rb_str_new2(rbce->unsafe_destination_error);
3494
+ }
3495
+
3496
+ return Qnil;
3497
+ }
3498
+
3499
+ #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
3500
+ /*
3501
+ * call-seq:
3502
+ * easy.unix_socket_path => string or nil
3503
+ *
3504
+ * Return the configured Unix socket path, if set through CURLOPT_UNIX_SOCKET_PATH.
3505
+ */
3506
+ static VALUE ruby_curl_easy_unix_socket_path_get(VALUE self) {
3507
+ ruby_curl_easy *rbce;
3508
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3509
+
3510
+ return rb_easy_get("unix_socket_path");
3511
+ }
3512
+ #endif
3513
+
3514
+ /*
3515
+ * call-seq:
3516
+ * easy.http_version = Curl::HTTP_1_1 => Curl::HTTP_1_1
3517
+ *
3518
+ * Force libcurl to use a specific HTTP protocol version. By default libcurl
3519
+ * negotiates the highest version supported by both peers. Supported constants
3520
+ * include Curl::HTTP_NONE, Curl::HTTP_1_0, Curl::HTTP_1_1, Curl::HTTP_2_0,
3521
+ * Curl::HTTP_2TLS, and Curl::HTTP_2_PRIOR_KNOWLEDGE (when provided by libcurl).
3522
+ */
3523
+ static VALUE ruby_curl_easy_http_version_set(VALUE self, VALUE version) {
3524
+ ruby_curl_easy *rbce;
3525
+ long http_version;
3526
+
3527
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3528
+
3529
+ if (NIL_P(version)) {
3530
+ http_version = CURL_HTTP_VERSION_NONE;
3531
+ } else {
3532
+ http_version = NUM2LONG(version);
3533
+ }
3534
+
3535
+ rbce->http_version = http_version;
3536
+
3537
+ return version;
3538
+ }
3539
+
3540
+ /*
3541
+ * call-seq:
3542
+ * easy.http_version => integer
3543
+ *
3544
+ * Returns the HTTP protocol version currently configured.
3545
+ */
3546
+ static VALUE ruby_curl_easy_http_version_get(VALUE self) {
3547
+ ruby_curl_easy *rbce;
3548
+
3549
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3550
+
3551
+ return LONG2NUM(rbce->http_version);
3552
+ }
3553
+
1691
3554
 
1692
3555
  /* ================= EVENT PROCS ================== */
1693
3556
 
1694
3557
  /*
1695
3558
  * call-seq:
1696
- * easy.on_body { |body_data| ... } => &lt;old handler&gt;
3559
+ * easy.on_body { |body_data| ... } => <old handler>
1697
3560
  *
1698
3561
  * Assign or remove the +on_body+ handler for this Curl::Easy instance.
1699
3562
  * To remove a previously-supplied handler, call this method with no
@@ -1712,7 +3575,7 @@ static VALUE ruby_curl_easy_on_body_set(int argc, VALUE *argv, VALUE self) {
1712
3575
 
1713
3576
  /*
1714
3577
  * call-seq:
1715
- * easy.on_success { |easy| ... } => &lt;old handler&gt;
3578
+ * easy.on_success { |easy| ... } => <old handler>
1716
3579
  *
1717
3580
  * Assign or remove the +on_success+ handler for this Curl::Easy instance.
1718
3581
  * To remove a previously-supplied handler, call this method with no
@@ -1727,7 +3590,7 @@ static VALUE ruby_curl_easy_on_success_set(int argc, VALUE *argv, VALUE self) {
1727
3590
 
1728
3591
  /*
1729
3592
  * call-seq:
1730
- * easy.on_failure {|easy,code| ... } => &lt;old handler&gt;
3593
+ * easy.on_failure {|easy,code| ... } => <old handler>
1731
3594
  *
1732
3595
  * Assign or remove the +on_failure+ handler for this Curl::Easy instance.
1733
3596
  * To remove a previously-supplied handler, call this method with no
@@ -1742,13 +3605,13 @@ static VALUE ruby_curl_easy_on_failure_set(int argc, VALUE *argv, VALUE self) {
1742
3605
 
1743
3606
  /*
1744
3607
  * call-seq:
1745
- * easy.on_missing {|easy,code| ... } => &lt;old handler;&gt;
3608
+ * easy.on_missing {|easy,code| ... } => <old handler;>
1746
3609
  *
1747
3610
  * Assign or remove the on_missing handler for this Curl::Easy instance.
1748
3611
  * To remove a previously-supplied handler, call this method with no attached
1749
3612
  * block.
1750
3613
  *
1751
- * The +on_missing+ handler is called when request is finished with a
3614
+ * The +on_missing+ handler is called when request is finished with a
1752
3615
  * status of 40x
1753
3616
  */
1754
3617
  static VALUE ruby_curl_easy_on_missing_set(int argc, VALUE *argv, VALUE self) {
@@ -1757,13 +3620,13 @@ static VALUE ruby_curl_easy_on_missing_set(int argc, VALUE *argv, VALUE self) {
1757
3620
 
1758
3621
  /*
1759
3622
  * call-seq:
1760
- * easy.on_redirect {|easy,code| ... } => &lt;old handler;&gt;
3623
+ * easy.on_redirect {|easy,code| ... } => <old handler;>
1761
3624
  *
1762
3625
  * Assign or remove the on_redirect handler for this Curl::Easy instance.
1763
3626
  * To remove a previously-supplied handler, call this method with no attached
1764
3627
  * block.
1765
3628
  *
1766
- * The +on_redirect+ handler is called when request is finished with a
3629
+ * The +on_redirect+ handler is called when request is finished with a
1767
3630
  * status of 30x
1768
3631
  */
1769
3632
  static VALUE ruby_curl_easy_on_redirect_set(int argc, VALUE *argv, VALUE self) {
@@ -1772,7 +3635,7 @@ static VALUE ruby_curl_easy_on_redirect_set(int argc, VALUE *argv, VALUE self) {
1772
3635
 
1773
3636
  /*
1774
3637
  * call-seq:
1775
- * easy.on_complete {|easy| ... } => &lt;old handler&gt;
3638
+ * easy.on_complete {|easy| ... } => <old handler>
1776
3639
  *
1777
3640
  * Assign or remove the +on_complete+ handler for this Curl::Easy instance.
1778
3641
  * To remove a previously-supplied handler, call this method with no
@@ -1786,7 +3649,7 @@ static VALUE ruby_curl_easy_on_complete_set(int argc, VALUE *argv, VALUE self) {
1786
3649
 
1787
3650
  /*
1788
3651
  * call-seq:
1789
- * easy.on_header { |header_data| ... } => &lt;old handler&gt;
3652
+ * easy.on_header { |header_data| ... } => <old handler>
1790
3653
  *
1791
3654
  * Assign or remove the +on_header+ handler for this Curl::Easy instance.
1792
3655
  * To remove a previously-supplied handler, call this method with no
@@ -1802,7 +3665,7 @@ static VALUE ruby_curl_easy_on_header_set(int argc, VALUE *argv, VALUE self) {
1802
3665
 
1803
3666
  /*
1804
3667
  * call-seq:
1805
- * easy.on_progress { |dl_total, dl_now, ul_total, ul_now| ... } => &lt;old handler&gt;
3668
+ * easy.on_progress { |dl_total, dl_now, ul_total, ul_now| ... } => <old handler>
1806
3669
  *
1807
3670
  * Assign or remove the +on_progress+ handler for this Curl::Easy instance.
1808
3671
  * To remove a previously-supplied handler, call this method with no
@@ -1823,7 +3686,7 @@ static VALUE ruby_curl_easy_on_progress_set(int argc, VALUE *argv, VALUE self) {
1823
3686
 
1824
3687
  /*
1825
3688
  * call-seq:
1826
- * easy.on_debug { |type, data| ... } => &lt;old handler&gt;
3689
+ * easy.on_debug { |type, data| ... } => <old handler>
1827
3690
  *
1828
3691
  * Assign or remove the +on_debug+ handler for this Curl::Easy instance.
1829
3692
  * To remove a previously-supplied handler, call this method with no
@@ -1848,12 +3711,12 @@ static VALUE ruby_curl_easy_on_debug_set(int argc, VALUE *argv, VALUE self) {
1848
3711
  /***********************************************
1849
3712
  * This is an rb_iterate callback used to set up http headers.
1850
3713
  */
1851
- static VALUE cb_each_http_header(VALUE header, VALUE wrap) {
3714
+ static VALUE cb_each_http_header(VALUE header, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
1852
3715
  struct curl_slist **list;
1853
- Data_Get_Struct(wrap, struct curl_slist *, list);
1854
-
1855
3716
  VALUE header_str = Qnil;
1856
3717
 
3718
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
3719
+
1857
3720
  //rb_p(header);
1858
3721
 
1859
3722
  if (rb_type(header) == T_ARRAY) {
@@ -1862,57 +3725,203 @@ static VALUE cb_each_http_header(VALUE header, VALUE wrap) {
1862
3725
 
1863
3726
  name = rb_obj_as_string(rb_ary_entry(header, 0));
1864
3727
  value = rb_obj_as_string(rb_ary_entry(header, 1));
3728
+ if (rb_str_strlen(value) == 0) { // removing the header e.g. Accept: with nothing trailing should remove it see: https://curl.se/libcurl/c/CURLOPT_HTTPHEADER.html
3729
+ header_str = rb_str_plus(name, rb_str_new2(":"));
3730
+ } else {
3731
+ // This is a bit inefficient, but we don't want to be modifying
3732
+ // the actual values in the original hash.
3733
+ header_str = rb_str_plus(name, rb_str_new2(": "));
3734
+ header_str = rb_str_plus(header_str, value);
3735
+ }
3736
+ } else {
3737
+ header_str = rb_obj_as_string(header);
3738
+ }
3739
+
3740
+ //rb_p(header_str);
3741
+
3742
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(header_str));
3743
+ if (!new_list) {
3744
+ rb_raise(rb_eNoMemError, "Failed to append to header list");
3745
+ }
3746
+ *list = new_list;
3747
+ return header_str;
3748
+ }
3749
+
3750
+ /***********************************************
3751
+ * This is an rb_iterate callback used to set up http proxy headers.
3752
+ */
3753
+ static VALUE cb_each_http_proxy_header(VALUE proxy_header, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
3754
+ struct curl_slist **list;
3755
+ VALUE proxy_header_str = Qnil;
3756
+
3757
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
3758
+
3759
+ //rb_p(proxy_header);
3760
+
3761
+ if (rb_type(proxy_header) == T_ARRAY) {
3762
+ // we're processing a hash, proxy header is [name, val]
3763
+ VALUE name, value;
3764
+
3765
+ name = rb_obj_as_string(rb_ary_entry(proxy_header, 0));
3766
+ value = rb_obj_as_string(rb_ary_entry(proxy_header, 1));
1865
3767
 
1866
3768
  // This is a bit inefficient, but we don't want to be modifying
1867
3769
  // the actual values in the original hash.
1868
- header_str = rb_str_plus(name, rb_str_new2(": "));
1869
- header_str = rb_str_plus(header_str, value);
3770
+ proxy_header_str = rb_str_plus(name, rb_str_new2(": "));
3771
+ proxy_header_str = rb_str_plus(proxy_header_str, value);
1870
3772
  } else {
1871
- header_str = rb_obj_as_string(header);
3773
+ proxy_header_str = rb_obj_as_string(proxy_header);
1872
3774
  }
1873
3775
 
1874
3776
  //rb_p(header_str);
1875
3777
 
1876
- *list = curl_slist_append(*list, StringValuePtr(header_str));
1877
- return header_str;
3778
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(proxy_header_str));
3779
+ if (!new_list) {
3780
+ rb_raise(rb_eNoMemError, "Failed to append to proxy header list");
3781
+ }
3782
+ *list = new_list;
3783
+ return proxy_header_str;
1878
3784
  }
1879
3785
 
1880
3786
  /***********************************************
1881
3787
  * This is an rb_iterate callback used to set up ftp commands.
1882
3788
  */
1883
- static VALUE cb_each_ftp_command(VALUE ftp_command, VALUE wrap) {
3789
+ static VALUE cb_each_ftp_command(VALUE ftp_command, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
1884
3790
  struct curl_slist **list;
1885
- Data_Get_Struct(wrap, struct curl_slist *, list);
3791
+ VALUE ftp_command_string;
3792
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
1886
3793
 
1887
- VALUE ftp_command_string = rb_obj_as_string(ftp_command);
1888
- *list = curl_slist_append(*list, StringValuePtr(ftp_command));
3794
+ ftp_command_string = rb_obj_as_string(ftp_command);
3795
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(ftp_command_string));
3796
+ if (!new_list) {
3797
+ rb_raise(rb_eNoMemError, "Failed to append to FTP command list");
3798
+ }
3799
+ *list = new_list;
1889
3800
 
1890
3801
  return ftp_command_string;
1891
3802
  }
1892
3803
 
3804
+ /***********************************************
3805
+ * This is an rb_iterate callback used to set up the resolve list.
3806
+ */
3807
+ static VALUE cb_each_resolve(VALUE resolve, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
3808
+ struct curl_slist **list;
3809
+ VALUE resolve_string;
3810
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
3811
+
3812
+ resolve_string = rb_obj_as_string(resolve);
3813
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(resolve_string));
3814
+ if (!new_list) {
3815
+ rb_raise(rb_eNoMemError, "Failed to append to resolve list");
3816
+ }
3817
+ *list = new_list;
3818
+
3819
+ return resolve_string;
3820
+ }
3821
+
3822
+ /***********************************************
3823
+ * This is an rb_iterate callback used to set up the connect-to list.
3824
+ */
3825
+ static VALUE cb_each_connect_to(VALUE connect_to, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
3826
+ struct curl_slist **list;
3827
+ VALUE connect_to_string;
3828
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
3829
+
3830
+ connect_to_string = rb_obj_as_string(connect_to);
3831
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(connect_to_string));
3832
+ if (!new_list) {
3833
+ rb_raise(rb_eNoMemError, "Failed to append to connect-to list");
3834
+ }
3835
+ *list = new_list;
3836
+
3837
+ return connect_to_string;
3838
+ }
3839
+
1893
3840
  /***********************************************
1894
3841
  *
1895
3842
  * Setup a connection
1896
3843
  *
1897
3844
  * Always returns Qtrue, rb_raise on error.
1898
3845
  */
1899
- VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
3846
+ static VALUE ruby_curl_easy_setup_body(VALUE arg) {
3847
+ ruby_curl_easy *rbce = (ruby_curl_easy *)arg;
1900
3848
  // TODO this could do with a bit of refactoring...
1901
3849
  CURL *curl;
1902
3850
  VALUE url, _url = rb_easy_get("url");
1903
3851
  struct curl_slist **hdrs = &(rbce->curl_headers);
3852
+ struct curl_slist **phdrs = &(rbce->curl_proxy_headers);
1904
3853
  struct curl_slist **cmds = &(rbce->curl_ftp_commands);
3854
+ struct curl_slist **rslv = &(rbce->curl_resolve);
3855
+ struct curl_slist **cnto = &(rbce->curl_connect_to);
3856
+ int public_policy_disables_proxy;
1905
3857
 
1906
3858
  curl = rbce->curl;
3859
+ public_policy_disables_proxy = rbce->network_policy == CURB_NETWORK_POLICY_PUBLIC && !rbce->allow_proxy;
3860
+ rbce->callback_error = Qnil;
3861
+ rbce->unsafe_destination_blocked = 0;
3862
+ memset(rbce->unsafe_destination_error, 0, CURL_ERROR_SIZE);
1907
3863
 
1908
3864
  if (_url == Qnil) {
1909
3865
  rb_raise(eCurlErrError, "No URL supplied");
1910
3866
  }
1911
3867
 
1912
3868
  url = rb_check_string_type(_url);
1913
-
1914
3869
  curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
1915
3870
 
3871
+ #ifdef HAVE_CURLOPT_DOH_URL
3872
+ curl_easy_setopt(curl, CURLOPT_DOH_URL, rb_easy_nil("doh_url") ? NULL : rb_easy_get_str("doh_url"));
3873
+ #endif
3874
+
3875
+ #ifdef HAVE_CURLOPT_DNS_SERVERS
3876
+ if (rbce->network_policy == CURB_NETWORK_POLICY_PUBLIC && !rb_easy_nil("dns_servers")) {
3877
+ rb_raise(eCurlErrUnsafeDestination, "DNS server overrides are disabled by public network policy");
3878
+ }
3879
+ curl_easy_setopt(curl, CURLOPT_DNS_SERVERS, rb_easy_nil("dns_servers") ? NULL : rb_easy_get_str("dns_servers"));
3880
+ #endif
3881
+
3882
+ #ifdef CURB_HAVE_PREREQ_HOST_POLICY
3883
+ if (!rb_easy_nil("allowed_hosts")) {
3884
+ curb_prepare_network_allowed_hosts(rbce);
3885
+ curl_easy_setopt(curl, CURLOPT_PREREQFUNCTION, curb_host_allowlist_prereq);
3886
+ curl_easy_setopt(curl, CURLOPT_PREREQDATA, rbce);
3887
+ } else {
3888
+ curb_clear_network_allowed_hosts(rbce);
3889
+ curl_easy_setopt(curl, CURLOPT_PREREQFUNCTION, NULL);
3890
+ curl_easy_setopt(curl, CURLOPT_PREREQDATA, NULL);
3891
+ }
3892
+ #else
3893
+ curb_clear_network_allowed_hosts(rbce);
3894
+ #endif
3895
+
3896
+ #ifdef CURB_HAVE_OPENSOCKET_NETWORK_POLICY
3897
+ if (rbce->network_policy == CURB_NETWORK_POLICY_PUBLIC) {
3898
+ curb_prepare_network_allowed_cidr_rules(rbce);
3899
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, curb_public_network_opensocket);
3900
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, rbce);
3901
+ #ifdef HAVE_CURLOPT_FRESH_CONNECT
3902
+ curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1L);
3903
+ #endif
3904
+ #ifdef HAVE_CURLOPT_FORBID_REUSE
3905
+ curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1L);
3906
+ #endif
3907
+ #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
3908
+ if (!rbce->allow_unix_socket) {
3909
+ curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, NULL);
3910
+ }
3911
+ #endif
3912
+ } else {
3913
+ curb_clear_network_allowed_cidr_rules(rbce);
3914
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, NULL);
3915
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, NULL);
3916
+ #ifdef HAVE_CURLOPT_FRESH_CONNECT
3917
+ curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 0L);
3918
+ #endif
3919
+ #ifdef HAVE_CURLOPT_FORBID_REUSE
3920
+ curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, rbce->forbid_reuse_set ? rbce->forbid_reuse : 0L);
3921
+ #endif
3922
+ }
3923
+ #endif
3924
+
1916
3925
  // network stuff and auth
1917
3926
  if (!rb_easy_nil("interface_hm")) {
1918
3927
  curl_easy_setopt(curl, CURLOPT_INTERFACE, rb_easy_get_str("interface_hm"));
@@ -1920,7 +3929,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1920
3929
  curl_easy_setopt(curl, CURLOPT_INTERFACE, NULL);
1921
3930
  }
1922
3931
 
1923
- #if HAVE_CURLOPT_USERNAME == 1 && HAVE_CURLOPT_PASSWORD == 1
3932
+ #if defined(HAVE_CURLOPT_USERNAME) && defined(HAVE_CURLOPT_PASSWORD)
1924
3933
  if (!rb_easy_nil("username")) {
1925
3934
  curl_easy_setopt(curl, CURLOPT_USERNAME, rb_easy_get_str("username"));
1926
3935
  } else {
@@ -1936,7 +3945,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1936
3945
 
1937
3946
  if (!rb_easy_nil("userpwd")) {
1938
3947
  curl_easy_setopt(curl, CURLOPT_USERPWD, rb_easy_get_str("userpwd"));
1939
- #if HAVE_CURLOPT_USERNAME == 1
3948
+ #if defined(HAVE_CURLOPT_USERNAME) && defined(HAVE_CURLOPT_PASSWORD)
1940
3949
  } else if (rb_easy_nil("username") && rb_easy_nil("password")) { /* don't set this even to NULL if we have set username and password */
1941
3950
  #else
1942
3951
  } else {
@@ -1944,28 +3953,42 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1944
3953
  curl_easy_setopt(curl, CURLOPT_USERPWD, NULL);
1945
3954
  }
1946
3955
 
1947
- if (rb_easy_nil("proxy_url")) {
3956
+ if (public_policy_disables_proxy) {
3957
+ curl_easy_setopt(curl, CURLOPT_PROXY, "");
3958
+ } else if (rb_easy_nil("proxy_url")) {
1948
3959
  curl_easy_setopt(curl, CURLOPT_PROXY, NULL);
1949
3960
  } else {
1950
3961
  curl_easy_setopt(curl, CURLOPT_PROXY, rb_easy_get_str("proxy_url"));
1951
3962
  }
1952
3963
 
1953
- if (rb_easy_nil("proxypwd")) {
3964
+ if (public_policy_disables_proxy) {
3965
+ curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, NULL);
3966
+ } else if (rb_easy_nil("proxypwd")) {
1954
3967
  curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, NULL);
1955
3968
  } else {
1956
3969
  curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, rb_easy_get_str("proxypwd"));
1957
3970
  }
1958
3971
 
3972
+ #ifdef HAVE_CURLOPT_NOPROXY
3973
+ if (rb_easy_nil("noproxy")) {
3974
+ curl_easy_setopt(curl, CURLOPT_NOPROXY, NULL);
3975
+ } else {
3976
+ curl_easy_setopt(curl, CURLOPT_NOPROXY, rb_easy_get_str("noproxy"));
3977
+ }
3978
+ #endif
3979
+
1959
3980
  // body/header procs
3981
+ rbce->downloaded_body_bytes = 0;
3982
+
1960
3983
  if (!rb_easy_nil("body_proc")) {
1961
3984
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&proc_data_handler_body);
1962
3985
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, rbce);
1963
3986
  /* clear out the body_data if it was set */
1964
3987
  rb_easy_del("body_data");
1965
3988
  } else {
1966
- VALUE body_buffer = rb_easy_set("body_data", rb_str_buf_new(32768));
1967
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&default_data_handler);
1968
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
3989
+ rb_easy_set("body_data", rb_str_buf_new(32768));
3990
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&default_body_handler);
3991
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, rbce);
1969
3992
  }
1970
3993
 
1971
3994
  if (!rb_easy_nil("header_proc")) {
@@ -1974,9 +3997,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1974
3997
  /* clear out the header_data if it was set */
1975
3998
  rb_easy_del("header_data");
1976
3999
  } else {
1977
- VALUE header_buffer = rb_easy_set("header_data", rb_str_buf_new(16384));
1978
- curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&default_data_handler);
1979
- curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
4000
+ rb_easy_set("header_data", rb_str_buf_new(16384));
4001
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&default_header_handler);
4002
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, rbce);
1980
4003
  }
1981
4004
 
1982
4005
  /* encoding */
@@ -1986,21 +4009,31 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1986
4009
 
1987
4010
  // progress and debug procs
1988
4011
  if (!rb_easy_nil("progress_proc")) {
4012
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
4013
+ curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, &proc_xferinfo_handler);
4014
+ curl_easy_setopt(curl, CURLOPT_XFERINFODATA, rbce);
4015
+ #else
1989
4016
  curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, (curl_progress_callback)&proc_progress_handler);
1990
- curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rb_easy_get("progress_proc"));
4017
+ curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
4018
+ #endif
1991
4019
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
1992
4020
  } else {
1993
4021
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
4022
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
4023
+ curl_easy_setopt(curl, CURLOPT_XFERINFODATA, rbce);
4024
+ #else
4025
+ curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
4026
+ #endif
1994
4027
  }
1995
4028
 
1996
4029
  if (!rb_easy_nil("debug_proc")) {
1997
4030
  curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, (curl_debug_callback)&proc_debug_handler);
1998
- curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rb_easy_get("debug_proc"));
4031
+ curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce);
1999
4032
  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2000
4033
  } else {
2001
4034
  // have to remove handler to re-enable standard verbosity
2002
4035
  curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL);
2003
- curl_easy_setopt(curl, CURLOPT_DEBUGDATA, NULL);
4036
+ curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce);
2004
4037
  curl_easy_setopt(curl, CURLOPT_VERBOSE, rbce->verbose);
2005
4038
  }
2006
4039
 
@@ -2008,9 +4041,17 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2008
4041
 
2009
4042
  curl_easy_setopt(curl, CURLOPT_HEADER, rbce->header_in_body);
2010
4043
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, rbce->follow_location);
4044
+ #if LIBCURL_VERSION_NUM == 0x081000
4045
+ /* Work around 8.16.0 regression that clamps -1 (infinite) to zero */
4046
+ if (rbce->max_redirs >= 0) {
4047
+ curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs);
4048
+ }
4049
+ #else
2011
4050
  curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs);
4051
+ #endif
2012
4052
 
2013
- curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, rbce->proxy_tunnel);
4053
+ curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL,
4054
+ public_policy_disables_proxy ? 0L : rbce->proxy_tunnel);
2014
4055
  curl_easy_setopt(curl, CURLOPT_FILETIME, rbce->fetch_file_time);
2015
4056
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, rbce->ssl_verify_peer);
2016
4057
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, rbce->ssl_verify_host);
@@ -2023,13 +4064,22 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2023
4064
 
2024
4065
  curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, rbce->unrestricted_auth);
2025
4066
 
2026
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, rbce->timeout);
4067
+ #ifdef HAVE_CURLOPT_TIMEOUT_MS
4068
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, rbce->timeout_ms);
4069
+ #endif
4070
+
2027
4071
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, rbce->connect_timeout);
4072
+ #ifdef HAVE_CURLOPT_CONNECTTIMEOUT_MS
4073
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, rbce->connect_timeout_ms);
4074
+ #endif
2028
4075
  curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, rbce->dns_cache_timeout);
2029
4076
 
2030
4077
  curl_easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, rbce->ignore_content_length);
2031
4078
 
2032
4079
  curl_easy_setopt(curl, CURLOPT_IPRESOLVE, rbce->resolve_mode);
4080
+ #if HAVE_CURLOPT_HTTP_VERSION
4081
+ curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, rbce->http_version);
4082
+ #endif
2033
4083
 
2034
4084
 
2035
4085
  #if LIBCURL_VERSION_NUM >= 0x070a08
@@ -2043,6 +4093,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2043
4093
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, rbce->low_speed_limit);
2044
4094
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, rbce->low_speed_time);
2045
4095
 
4096
+ curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, rbce->max_recv_speed_large);
4097
+ curl_easy_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, rbce->max_send_speed_large);
4098
+
2046
4099
  // Set up localport / proxy port
2047
4100
  // FIXME these won't get returned to default if they're unset Ruby
2048
4101
  if (rbce->proxy_port > 0) {
@@ -2075,29 +4128,27 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2075
4128
  #endif
2076
4129
  }
2077
4130
 
2078
- if (rbce->http_auth_types > 0) {
4131
+ /*
4132
+ * NOTE: we used to set CURLAUTH_ANY but see: http://curl.haxx.se/mail/lib-2015-06/0033.html
4133
+ */
4134
+ if (rbce->http_auth_types != 0) {
2079
4135
  #if LIBCURL_VERSION_NUM >= 0x070a06
2080
4136
  curl_easy_setopt(curl, CURLOPT_HTTPAUTH, rbce->http_auth_types);
2081
- } else {
2082
- curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
2083
4137
  #else
2084
4138
  rb_warn("Installed libcurl is too old to support http_auth_types");
2085
4139
  #endif
2086
4140
  }
2087
4141
 
2088
- if (rbce->proxy_auth_types > 0) {
4142
+ if (rbce->proxy_auth_types != 0) {
2089
4143
  #if LIBCURL_VERSION_NUM >= 0x070a07
2090
4144
  curl_easy_setopt(curl, CURLOPT_PROXYAUTH, rbce->proxy_auth_types);
2091
- } else {
2092
- curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
2093
4145
  #else
2094
4146
  rb_warn("Installed libcurl is too old to support proxy_auth_types");
2095
4147
  #endif
2096
4148
  }
2097
4149
 
2098
- /* Set up HTTP cookie handling if necessary
2099
- FIXME this may not get disabled if it's enabled, the disabled again from ruby.
2100
- */
4150
+ /* Set up HTTP cookie handling if necessary */
4151
+ /* Enable/attach cookie engine if requested, or implicitly via COOKIELIST usage */
2101
4152
  if (rbce->enable_cookies) {
2102
4153
  if (!rb_easy_nil("cookiejar")) {
2103
4154
  curl_easy_setopt(curl, CURLOPT_COOKIEJAR, rb_easy_get_str("cookiejar"));
@@ -2108,6 +4159,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2108
4159
  } else {
2109
4160
  curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* "" = magic to just enable */
2110
4161
  }
4162
+ } else if (rbce->cookielist_engine_enabled) {
4163
+ /* Ensure cookie engine is enabled even if enable_cookies? is false. */
4164
+ curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
2111
4165
  }
2112
4166
 
2113
4167
  if (!rb_easy_nil("cookies")) {
@@ -2150,7 +4204,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2150
4204
  rb_warn("libcurl is not configured with SSL support");
2151
4205
  }
2152
4206
  #endif
2153
-
4207
+
2154
4208
  if (rbce->ftp_filemethod > 0) {
2155
4209
  curl_easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, rbce->ftp_filemethod);
2156
4210
  }
@@ -2160,16 +4214,25 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2160
4214
  curl_easy_setopt(curl, CURLOPT_USERAGENT, rb_easy_get_str("useragent"));
2161
4215
  }
2162
4216
 
4217
+ /* Setup can be rerun for safety-policy changes while a handle is attached.
4218
+ * Rebuild slist-backed options from Ruby opts instead of appending to stale
4219
+ * native lists. */
4220
+ ruby_curl_easy_clear_setup_lists(rbce);
4221
+
2163
4222
  /* Setup HTTP headers if necessary */
2164
4223
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL); // XXX: maybe we shouldn't be clearing this?
2165
4224
 
2166
4225
  if (!rb_easy_nil("headers")) {
2167
4226
  if (rb_easy_type_check("headers", T_ARRAY) || rb_easy_type_check("headers", T_HASH)) {
2168
- VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, hdrs);
2169
- rb_iterate(rb_each, rb_easy_get("headers"), cb_each_http_header, wrap);
4227
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, hdrs);
4228
+ rb_block_call(rb_easy_get("headers"), rb_intern("each"), 0, NULL, cb_each_http_header, wrap);
2170
4229
  } else {
2171
4230
  VALUE headers_str = rb_obj_as_string(rb_easy_get("headers"));
2172
- *hdrs = curl_slist_append(*hdrs, StringValuePtr(headers_str));
4231
+ struct curl_slist *new_list = curl_slist_append(*hdrs, StringValuePtr(headers_str));
4232
+ if (!new_list) {
4233
+ rb_raise(rb_eNoMemError, "Failed to append to headers list");
4234
+ }
4235
+ *hdrs = new_list;
2173
4236
  }
2174
4237
 
2175
4238
  if (*hdrs) {
@@ -2177,11 +4240,34 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2177
4240
  }
2178
4241
  }
2179
4242
 
4243
+ #ifdef HAVE_CURLOPT_PROXYHEADER
4244
+ /* Setup HTTP proxy headers if necessary */
4245
+ curl_easy_setopt(curl, CURLOPT_PROXYHEADER, NULL); // XXX: maybe we shouldn't be clearing this?
4246
+
4247
+ if (!rb_easy_nil("proxy_headers")) {
4248
+ if (rb_easy_type_check("proxy_headers", T_ARRAY) || rb_easy_type_check("proxy_headers", T_HASH)) {
4249
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, phdrs);
4250
+ rb_block_call(rb_easy_get("proxy_headers"), rb_intern("each"), 0, NULL, cb_each_http_proxy_header, wrap);
4251
+ } else {
4252
+ VALUE proxy_headers_str = rb_obj_as_string(rb_easy_get("proxy_headers"));
4253
+ struct curl_slist *new_list = curl_slist_append(*phdrs, StringValuePtr(proxy_headers_str));
4254
+ if (!new_list) {
4255
+ rb_raise(rb_eNoMemError, "Failed to append to proxy headers list");
4256
+ }
4257
+ *phdrs = new_list;
4258
+ }
4259
+
4260
+ if (*phdrs) {
4261
+ curl_easy_setopt(curl, CURLOPT_PROXYHEADER, *phdrs);
4262
+ }
4263
+ }
4264
+ #endif
4265
+
2180
4266
  /* Setup FTP commands if necessary */
2181
4267
  if (!rb_easy_nil("ftp_commands")) {
2182
4268
  if (rb_easy_type_check("ftp_commands", T_ARRAY)) {
2183
- VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, cmds);
2184
- rb_iterate(rb_each, rb_easy_get("ftp_commands"), cb_each_ftp_command, wrap);
4269
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, cmds);
4270
+ rb_block_call(rb_easy_get("ftp_commands"), rb_intern("each"), 0, NULL, cb_each_ftp_command, wrap);
2185
4271
  }
2186
4272
 
2187
4273
  if (*cmds) {
@@ -2189,40 +4275,155 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2189
4275
  }
2190
4276
  }
2191
4277
 
4278
+ #ifdef HAVE_CURLOPT_RESOLVE
4279
+ /* Setup resolve list if necessary */
4280
+ if (!rb_easy_nil("resolve")) {
4281
+ if (rb_easy_type_check("resolve", T_ARRAY)) {
4282
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, rslv);
4283
+ rb_block_call(rb_easy_get("resolve"), rb_intern("each"), 0, NULL, cb_each_resolve, wrap);
4284
+ } else {
4285
+ VALUE resolve_str = rb_obj_as_string(rb_easy_get("resolve"));
4286
+ struct curl_slist *new_list = curl_slist_append(*rslv, StringValuePtr(resolve_str));
4287
+ if (!new_list) {
4288
+ rb_raise(rb_eNoMemError, "Failed to append to resolve list");
4289
+ }
4290
+ *rslv = new_list;
4291
+ }
4292
+
4293
+ if (*rslv) {
4294
+ curl_easy_setopt(curl, CURLOPT_RESOLVE, *rslv);
4295
+ }
4296
+ }
4297
+ #endif
4298
+
4299
+ #ifdef HAVE_CURLOPT_CONNECT_TO
4300
+ /* Setup connect-to list if necessary */
4301
+ if (!rb_easy_nil("connect_to")) {
4302
+ if (rb_easy_type_check("connect_to", T_ARRAY)) {
4303
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, cnto);
4304
+ rb_block_call(rb_easy_get("connect_to"), rb_intern("each"), 0, NULL, cb_each_connect_to, wrap);
4305
+ } else {
4306
+ VALUE connect_to_str = rb_obj_as_string(rb_easy_get("connect_to"));
4307
+ struct curl_slist *new_list = curl_slist_append(*cnto, StringValuePtr(connect_to_str));
4308
+ if (!new_list) {
4309
+ rb_raise(rb_eNoMemError, "Failed to append to connect-to list");
4310
+ }
4311
+ *cnto = new_list;
4312
+ }
4313
+
4314
+ if (*cnto) {
4315
+ curl_easy_setopt(curl, CURLOPT_CONNECT_TO, *cnto);
4316
+ }
4317
+ }
4318
+ #endif
4319
+
2192
4320
  return Qnil;
2193
4321
  }
4322
+
4323
+ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
4324
+ ruby_curl_easy_enter_native(rbce);
4325
+ return rb_ensure(ruby_curl_easy_setup_body, (VALUE)rbce,
4326
+ ruby_curl_easy_leave_native, (VALUE)rbce);
4327
+ }
4328
+
4329
+ static VALUE ruby_curl_easy_setup_self(VALUE self) {
4330
+ ruby_curl_easy *rbce;
4331
+
4332
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4333
+ ruby_curl_easy_setup(rbce);
4334
+
4335
+ return self;
4336
+ }
2194
4337
  /***********************************************
2195
4338
  *
2196
4339
  * Clean up a connection
2197
4340
  *
2198
- * Always returns Qtrue.
4341
+ * Always returns Qnil.
2199
4342
  */
2200
4343
  VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2201
4344
 
2202
4345
  CURL *curl = rbce->curl;
2203
4346
  struct curl_slist *ftp_commands;
4347
+ struct curl_slist *resolve;
4348
+ struct curl_slist *connect_to;
2204
4349
 
2205
- /* Free everything up */
2206
- if (rbce->curl_headers) {
2207
- curl_slist_free_all(rbce->curl_headers);
2208
- rbce->curl_headers = NULL;
2209
- }
4350
+ ruby_curl_easy_clear_headers_list(rbce);
4351
+ ruby_curl_easy_clear_proxy_headers_list(rbce);
2210
4352
 
2211
4353
  ftp_commands = rbce->curl_ftp_commands;
2212
4354
  if (ftp_commands) {
2213
- curl_slist_free_all(ftp_commands);
2214
- rbce->curl_ftp_commands = NULL;
4355
+ ruby_curl_easy_clear_ftp_commands_list(rbce);
2215
4356
  }
2216
4357
 
4358
+ resolve = rbce->curl_resolve;
4359
+ if (resolve) {
4360
+ ruby_curl_easy_clear_resolve_list(rbce);
4361
+ }
4362
+
4363
+ connect_to = rbce->curl_connect_to;
4364
+ if (connect_to) {
4365
+ ruby_curl_easy_clear_connect_to_list(rbce);
4366
+ }
4367
+
4368
+ curb_clear_network_allowed_cidr_rules(rbce);
4369
+ curb_clear_network_allowed_hosts(rbce);
4370
+
2217
4371
  /* clean up a PUT request's curl options. */
2218
4372
  if (!rb_easy_nil("upload")) {
2219
4373
  rb_easy_del("upload"); // set the upload object to Qnil to let the GC clean up
2220
4374
  curl_easy_setopt(curl, CURLOPT_UPLOAD, 0);
2221
4375
  curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
2222
4376
  curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
4377
+ #ifdef HAVE_CURLOPT_SEEKFUNCTION
4378
+ curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, NULL);
4379
+ #endif
4380
+ #ifdef HAVE_CURLOPT_SEEKDATA
4381
+ curl_easy_setopt(curl, CURLOPT_SEEKDATA, NULL);
4382
+ #endif
2223
4383
  curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0);
2224
4384
  }
2225
4385
 
4386
+ // set values on cleanup to nil
4387
+ //rb_easy_del("multi");
4388
+
4389
+ return Qnil;
4390
+ }
4391
+
4392
+ struct easy_perform_request_restore_args {
4393
+ VALUE self;
4394
+ CURL *curl;
4395
+ ruby_curl_easy *rbce;
4396
+ int clear_customrequest;
4397
+ int clear_nobody;
4398
+ int clear_postfields;
4399
+ };
4400
+
4401
+ static VALUE perform_with_request_restore_body(VALUE argp) {
4402
+ struct easy_perform_request_restore_args *args = (struct easy_perform_request_restore_args *)argp;
4403
+ return rb_funcall(args->self, rb_intern("perform"), 0);
4404
+ }
4405
+
4406
+ static VALUE perform_with_request_restore_ensure(VALUE argp) {
4407
+ struct easy_perform_request_restore_args *args = (struct easy_perform_request_restore_args *)argp;
4408
+
4409
+ if (args->curl) {
4410
+ if (args->clear_nobody) {
4411
+ curl_easy_setopt(args->curl, CURLOPT_NOBODY, 0L);
4412
+ }
4413
+ if (args->clear_customrequest) {
4414
+ curl_easy_setopt(args->curl, CURLOPT_CUSTOMREQUEST, NULL);
4415
+ }
4416
+ if (args->clear_postfields) {
4417
+ curl_easy_setopt(args->curl, CURLOPT_POST, 0L);
4418
+ curl_easy_setopt(args->curl, CURLOPT_POSTFIELDS, NULL);
4419
+ curl_easy_setopt(args->curl, CURLOPT_POSTFIELDSIZE, 0L);
4420
+ curl_easy_setopt(args->curl, CURLOPT_HTTPGET, 1L);
4421
+ if (args->rbce && !NIL_P(args->rbce->opts)) {
4422
+ rb_hash_delete(args->rbce->opts, rb_easy_hkey("postdata_buffer"));
4423
+ }
4424
+ }
4425
+ }
4426
+
2226
4427
  return Qnil;
2227
4428
  }
2228
4429
 
@@ -2234,14 +4435,33 @@ static VALUE ruby_curl_easy_perform_verb_str(VALUE self, const char *verb) {
2234
4435
  CURL *curl;
2235
4436
  VALUE retval;
2236
4437
 
2237
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4438
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2238
4439
  curl = rbce->curl;
2239
4440
 
4441
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
4442
+
4443
+ /* Use method override and adjust related options for special verbs. */
2240
4444
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb);
2241
4445
 
2242
- retval = rb_funcall(self, rb_intern("perform"), 0);
4446
+ /* For HEAD, ensure no body is requested/downloaded, as some servers
4447
+ * include a Content-Length header which should not cause libcurl to
4448
+ * wait for a body that will never arrive. */
4449
+ int is_head = (verb && (
4450
+ #ifdef _WIN32
4451
+ _stricmp(verb, "HEAD") == 0
4452
+ #else
4453
+ strcasecmp(verb, "HEAD") == 0
4454
+ #endif
4455
+ ));
4456
+ if (is_head) {
4457
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
4458
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 0L);
4459
+ curl_easy_setopt(curl, CURLOPT_POST, 0L);
4460
+ }
2243
4461
 
2244
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
4462
+ struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, is_head, 0 };
4463
+ retval = rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args,
4464
+ perform_with_request_restore_ensure, (VALUE)&restore_args);
2245
4465
 
2246
4466
  return retval;
2247
4467
  }
@@ -2262,9 +4482,96 @@ static VALUE ruby_curl_easy_perform_verb(VALUE self, VALUE verb) {
2262
4482
  str_verb = rb_funcall(verb, rb_intern("to_s"), 0);
2263
4483
  return ruby_curl_easy_perform_verb_str(self, StringValueCStr(str_verb));
2264
4484
  }
2265
- else {
2266
- rb_raise(rb_eRuntimeError, "Invalid HTTP VERB, must response to 'to_s'");
4485
+ else {
4486
+ rb_raise(rb_eRuntimeError, "Invalid HTTP VERB, must response to 'to_s'");
4487
+ }
4488
+ }
4489
+
4490
+ static VALUE call_easy_perform(VALUE self) {
4491
+ return rb_funcall(self, rb_intern("perform"), 0);
4492
+ }
4493
+
4494
+ struct easy_form_perform_args {
4495
+ VALUE self;
4496
+ CURL *curl;
4497
+ int argc;
4498
+ VALUE *argv;
4499
+ struct curl_httppost *first;
4500
+ struct curl_httppost *last;
4501
+ int clear_customrequest;
4502
+ int form_set_on_curl;
4503
+ };
4504
+
4505
+ struct easy_join_args {
4506
+ VALUE args_ary;
4507
+ };
4508
+
4509
+ static VALUE join_easy_arguments_body(VALUE argp) {
4510
+ struct easy_join_args *args = (struct easy_join_args *)argp;
4511
+ return rb_funcall(args->args_ary, idJoin, 1, rbstrAmp);
4512
+ }
4513
+
4514
+ static VALUE join_easy_arguments(ruby_curl_easy *rbce, VALUE args_ary) {
4515
+ struct easy_join_args args = { args_ary };
4516
+
4517
+ ruby_curl_easy_enter_native(rbce);
4518
+ return rb_ensure(join_easy_arguments_body, (VALUE)&args,
4519
+ ruby_curl_easy_leave_native, (VALUE)rbce);
4520
+ }
4521
+
4522
+ static void append_multipart_form_argument(VALUE arg,
4523
+ struct curl_httppost **first,
4524
+ struct curl_httppost **last) {
4525
+ if (rb_obj_is_instance_of(arg, cCurlPostField)) {
4526
+ append_to_form(arg, first, last);
4527
+ } else if (rb_type(arg) == T_ARRAY) {
4528
+ long j, argv_len = RARRAY_LEN(arg);
4529
+ for (j = 0; j < argv_len; ++j) {
4530
+ VALUE field = rb_ary_entry(arg, j);
4531
+ if (rb_obj_is_instance_of(field, cCurlPostField)) {
4532
+ append_to_form(field, first, last);
4533
+ } else {
4534
+ rb_raise(eCurlErrInvalidPostField,
4535
+ "You must use PostFields only with multipart form posts");
4536
+ }
4537
+ }
4538
+ } else {
4539
+ rb_raise(eCurlErrInvalidPostField,
4540
+ "You must use PostFields only with multipart form posts");
4541
+ }
4542
+ }
4543
+
4544
+ static VALUE build_and_perform_multipart_form(VALUE argp) {
4545
+ struct easy_form_perform_args *args = (struct easy_form_perform_args *)argp;
4546
+ int i;
4547
+
4548
+ for (i = 0; i < args->argc; i++) {
4549
+ append_multipart_form_argument(args->argv[i], &args->first, &args->last);
4550
+ }
4551
+
4552
+ curl_easy_setopt(args->curl, CURLOPT_POST, 0);
4553
+ curl_easy_setopt(args->curl, CURLOPT_HTTPPOST, args->first);
4554
+ args->form_set_on_curl = 1;
4555
+
4556
+ return call_easy_perform(args->self);
4557
+ }
4558
+
4559
+ static VALUE ensure_free_form_post(VALUE argp) {
4560
+ struct easy_form_perform_args *args = (struct easy_form_perform_args *)argp;
4561
+ if (args->curl) {
4562
+ if (args->form_set_on_curl) {
4563
+ curl_easy_setopt(args->curl, CURLOPT_HTTPPOST, NULL);
4564
+ }
4565
+ if (args->clear_customrequest) {
4566
+ curl_easy_setopt(args->curl, CURLOPT_CUSTOMREQUEST, NULL);
4567
+ }
4568
+ }
4569
+ if (args->first) {
4570
+ curl_formfree(args->first);
4571
+ args->first = NULL;
2267
4572
  }
4573
+ args->last = NULL;
4574
+ return Qnil;
2268
4575
  }
2269
4576
 
2270
4577
  /*
@@ -2294,51 +4601,27 @@ static VALUE ruby_curl_easy_perform_verb(VALUE self, VALUE verb) {
2294
4601
  static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2295
4602
  ruby_curl_easy *rbce;
2296
4603
  CURL *curl;
2297
- int i;
2298
4604
  VALUE args_ary;
2299
4605
 
2300
4606
  rb_scan_args(argc, argv, "*", &args_ary);
2301
4607
 
2302
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4608
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2303
4609
  curl = rbce->curl;
2304
4610
 
4611
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
4612
+
2305
4613
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2306
4614
 
2307
4615
  if (rbce->multipart_form_post) {
2308
4616
  VALUE ret;
2309
- struct curl_httppost *first = NULL, *last = NULL;
2310
-
2311
- // Make the multipart form
2312
- for (i = 0; i < argc; i++) {
2313
- if (rb_obj_is_instance_of(argv[i], cCurlPostField)) {
2314
- append_to_form(argv[i], &first, &last);
2315
- } else if (rb_type(argv[i]) == T_ARRAY) {
2316
- // see: https://github.com/rvanlieshout/curb/commit/8bcdefddc0162484681ebd1a92d52a642666a445
2317
- int c = 0, argv_len = (int)RARRAY_LEN(argv[i]);
2318
- for (; c < argv_len; ++c) {
2319
- if (rb_obj_is_instance_of(rb_ary_entry(argv[i],c), cCurlPostField)) {
2320
- append_to_form(rb_ary_entry(argv[i],c), &first, &last);
2321
- } else {
2322
- rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts");
2323
- return Qnil;
2324
- }
2325
- }
2326
- } else {
2327
- rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts");
2328
- return Qnil;
2329
- }
2330
- }
2331
-
2332
- curl_easy_setopt(curl, CURLOPT_POST, 0);
2333
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
2334
- ret = rb_funcall(self, rb_intern("perform"), 0);
2335
- curl_formfree(first);
4617
+ struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 0, 0 };
4618
+ ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args);
2336
4619
 
2337
4620
  return ret;
2338
4621
  } else {
2339
4622
  VALUE post_body = Qnil;
2340
4623
  /* TODO: check for PostField.file and raise error before to_s fails */
2341
- if ((post_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp)) == Qnil) {
4624
+ if ((post_body = join_easy_arguments(rbce, args_ary)) == Qnil) {
2342
4625
  rb_raise(eCurlErrError, "Failed to join arguments");
2343
4626
  return Qnil;
2344
4627
  } else {
@@ -2353,7 +4636,65 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2353
4636
  ruby_curl_easy_post_body_set(self, post_body);
2354
4637
  }
2355
4638
 
2356
- return rb_funcall(self, rb_intern("perform"), 0);
4639
+ struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, 0, 0 };
4640
+ return rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args,
4641
+ perform_with_request_restore_ensure, (VALUE)&restore_args);
4642
+ }
4643
+ }
4644
+ }
4645
+
4646
+ /*
4647
+ * call-seq:
4648
+ * easy.http_patch("url=encoded%20form%20data;and=so%20on") => true
4649
+ * easy.http_patch("url=encoded%20form%20data", "and=so%20on", ...) => true
4650
+ * easy.http_patch("url=encoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
4651
+ * easy.http_patch(Curl::PostField, Curl::PostField ..., Curl::PostField) => true
4652
+ *
4653
+ * PATCH the specified formdata to the currently configured URL using
4654
+ * the current options set for this Curl::Easy instance. This method
4655
+ * always returns true, or raises an exception (defined under
4656
+ * Curl::Err) on error.
4657
+ *
4658
+ * When multipart_form_post is true the multipart logic is used; otherwise,
4659
+ * the arguments are joined into a raw PATCH body.
4660
+ */
4661
+ static VALUE ruby_curl_easy_perform_patch(int argc, VALUE *argv, VALUE self) {
4662
+ ruby_curl_easy *rbce;
4663
+ CURL *curl;
4664
+ VALUE args_ary;
4665
+
4666
+ rb_scan_args(argc, argv, "*", &args_ary);
4667
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4668
+ curl = rbce->curl;
4669
+
4670
+ /* Clear the error buffer */
4671
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
4672
+
4673
+ /* Set the custom HTTP method to PATCH */
4674
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
4675
+
4676
+ if (rbce->multipart_form_post) {
4677
+ VALUE ret;
4678
+ struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 1, 0 };
4679
+ ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args);
4680
+ return ret;
4681
+ } else {
4682
+ /* Join arguments into a raw PATCH body */
4683
+ VALUE patch_body = join_easy_arguments(rbce, args_ary);
4684
+ if (patch_body == Qnil) {
4685
+ rb_raise(eCurlErrError, "Failed to join arguments");
4686
+ return Qnil;
4687
+ } else {
4688
+ if (rb_type(patch_body) == T_STRING && RSTRING_LEN(patch_body) > 0) {
4689
+ ruby_curl_easy_post_body_set(self, patch_body);
4690
+ }
4691
+ /* If postdata_buffer is still nil, set it so that the PATCH header is enabled */
4692
+ if (rb_easy_nil("postdata_buffer")) {
4693
+ ruby_curl_easy_post_body_set(self, patch_body);
4694
+ }
4695
+ struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, 0, 1 };
4696
+ return rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args,
4697
+ perform_with_request_restore_ensure, (VALUE)&restore_args);
2357
4698
  }
2358
4699
  }
2359
4700
  }
@@ -2366,17 +4707,47 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2366
4707
  * current options set for this Curl::Easy instance. This method always
2367
4708
  * returns true, or raises an exception (defined under Curl::Err) on error.
2368
4709
  */
2369
- static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
4710
+ static VALUE ruby_curl_easy_perform_put(int argc, VALUE *argv, VALUE self) {
2370
4711
  ruby_curl_easy *rbce;
2371
4712
  CURL *curl;
4713
+ VALUE args_ary;
2372
4714
 
2373
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4715
+ rb_scan_args(argc, argv, "*", &args_ary);
4716
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2374
4717
  curl = rbce->curl;
2375
4718
 
2376
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2377
- ruby_curl_easy_put_data_set(self, data);
4719
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
4720
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
2378
4721
 
2379
- return rb_funcall(self, rb_intern("perform"), 0);
4722
+ /* New: if no arguments were provided, treat as an empty PUT */
4723
+ if (RARRAY_LEN(args_ary) == 0) {
4724
+ /* Option 1: explicitly set an empty body */
4725
+ ruby_curl_easy_put_data_set(self, rb_str_new2(""));
4726
+ }
4727
+ /* If a single argument is given and it is a String or responds to read, use legacy behavior */
4728
+ else if (RARRAY_LEN(args_ary) == 1 &&
4729
+ (rb_type(rb_ary_entry(args_ary, 0)) == T_STRING ||
4730
+ rb_respond_to(rb_ary_entry(args_ary, 0), rb_intern("read")))) {
4731
+ ruby_curl_easy_put_data_set(self, rb_ary_entry(args_ary, 0));
4732
+ }
4733
+ /* Otherwise, if multipart_form_post is true, use multipart logic */
4734
+ else if (rbce->multipart_form_post) {
4735
+ VALUE ret;
4736
+ struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 1, 0 };
4737
+ ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args);
4738
+ return ret;
4739
+ }
4740
+ /* Fallback: join all arguments */
4741
+ else {
4742
+ VALUE post_body = join_easy_arguments(rbce, args_ary);
4743
+ if (post_body != Qnil && rb_type(post_body) == T_STRING &&
4744
+ RSTRING_LEN(post_body) > 0) {
4745
+ ruby_curl_easy_put_data_set(self, post_body);
4746
+ }
4747
+ }
4748
+ struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, 0, 0 };
4749
+ return rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args,
4750
+ perform_with_request_restore_ensure, (VALUE)&restore_args);
2380
4751
  }
2381
4752
 
2382
4753
 
@@ -2391,9 +4762,54 @@ static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
2391
4762
  * your own body handler, this string will be empty.
2392
4763
  */
2393
4764
  static VALUE ruby_curl_easy_body_str_get(VALUE self) {
4765
+ /*
4766
+ TODO: can we force_encoding on the return here if we see charset=utf-8 in the content-type header?
4767
+ Content-Type: application/json; charset=utf-8
4768
+ */
2394
4769
  CURB_OBJECT_HGETTER(ruby_curl_easy, body_data);
2395
4770
  }
2396
4771
 
4772
+ /*
4773
+ * call-seq:
4774
+ * easy.max_body_bytes = bytes_or_nil => bytes_or_nil
4775
+ *
4776
+ * Set an application-level cap for bytes accepted by the body write callback.
4777
+ * +nil+ or 0 clears the cap.
4778
+ */
4779
+ static VALUE ruby_curl_easy_max_body_bytes_set(VALUE self, VALUE val) {
4780
+ ruby_curl_easy *rbce;
4781
+ long long limit;
4782
+
4783
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4784
+
4785
+ if (NIL_P(val)) {
4786
+ rb_hash_delete(rbce->opts, rb_easy_hkey("max_body_bytes"));
4787
+ return Qnil;
4788
+ }
4789
+
4790
+ limit = NUM2LL(val);
4791
+ if (limit < 0) {
4792
+ rb_raise(rb_eArgError, "max_body_bytes must be greater than or equal to zero");
4793
+ }
4794
+
4795
+ if (limit == 0) {
4796
+ rb_hash_delete(rbce->opts, rb_easy_hkey("max_body_bytes"));
4797
+ } else {
4798
+ val = LL2NUM(limit);
4799
+ rb_hash_aset(rbce->opts, rb_easy_hkey("max_body_bytes"), val);
4800
+ }
4801
+
4802
+ return val;
4803
+ }
4804
+
4805
+ /*
4806
+ * call-seq:
4807
+ * easy.max_body_bytes => bytes_or_nil
4808
+ */
4809
+ static VALUE ruby_curl_easy_max_body_bytes_get(VALUE self) {
4810
+ CURB_OBJECT_HGETTER(ruby_curl_easy, max_body_bytes);
4811
+ }
4812
+
2397
4813
  /*
2398
4814
  * call-seq:
2399
4815
  * easy.header_str => "response header"
@@ -2421,7 +4837,7 @@ static VALUE ruby_curl_easy_last_effective_url_get(VALUE self) {
2421
4837
  ruby_curl_easy *rbce;
2422
4838
  char* url;
2423
4839
 
2424
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4840
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2425
4841
  curl_easy_getinfo(rbce->curl, CURLINFO_EFFECTIVE_URL, &url);
2426
4842
 
2427
4843
  if (url && url[0]) { // curl returns empty string if none
@@ -2444,7 +4860,7 @@ static VALUE ruby_curl_easy_response_code_get(VALUE self) {
2444
4860
  ruby_curl_easy *rbce;
2445
4861
  long code;
2446
4862
 
2447
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4863
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2448
4864
  #ifdef HAVE_CURLINFO_RESPONSE_CODE
2449
4865
  curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &code);
2450
4866
  #else
@@ -2467,8 +4883,8 @@ static VALUE ruby_curl_easy_response_code_get(VALUE self) {
2467
4883
  static VALUE ruby_curl_easy_primary_ip_get(VALUE self) {
2468
4884
  ruby_curl_easy *rbce;
2469
4885
  char* ip;
2470
-
2471
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4886
+
4887
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2472
4888
  curl_easy_getinfo(rbce->curl, CURLINFO_PRIMARY_IP, &ip);
2473
4889
 
2474
4890
  if (ip && ip[0]) { // curl returns empty string if none
@@ -2489,7 +4905,7 @@ static VALUE ruby_curl_easy_http_connect_code_get(VALUE self) {
2489
4905
  ruby_curl_easy *rbce;
2490
4906
  long code;
2491
4907
 
2492
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4908
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2493
4909
  curl_easy_getinfo(rbce->curl, CURLINFO_HTTP_CONNECTCODE, &code);
2494
4910
 
2495
4911
  return LONG2NUM(code);
@@ -2517,13 +4933,13 @@ static VALUE ruby_curl_easy_file_time_get(VALUE self) {
2517
4933
  ruby_curl_easy *rbce;
2518
4934
  long time;
2519
4935
 
2520
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4936
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2521
4937
  curl_easy_getinfo(rbce->curl, CURLINFO_FILETIME, &time);
2522
4938
 
2523
4939
  return LONG2NUM(time);
2524
4940
  #else
2525
4941
  rb_warn("Installed libcurl is too old to support file_time");
2526
- return INT2FIX(0);
4942
+ return LONG2NUM(0);
2527
4943
  #endif
2528
4944
  }
2529
4945
 
@@ -2538,7 +4954,7 @@ static VALUE ruby_curl_easy_total_time_get(VALUE self) {
2538
4954
  ruby_curl_easy *rbce;
2539
4955
  double time;
2540
4956
 
2541
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4957
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2542
4958
  curl_easy_getinfo(rbce->curl, CURLINFO_TOTAL_TIME, &time);
2543
4959
 
2544
4960
  return rb_float_new(time);
@@ -2555,7 +4971,7 @@ static VALUE ruby_curl_easy_name_lookup_time_get(VALUE self) {
2555
4971
  ruby_curl_easy *rbce;
2556
4972
  double time;
2557
4973
 
2558
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4974
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2559
4975
  curl_easy_getinfo(rbce->curl, CURLINFO_NAMELOOKUP_TIME, &time);
2560
4976
 
2561
4977
  return rb_float_new(time);
@@ -2572,7 +4988,7 @@ static VALUE ruby_curl_easy_connect_time_get(VALUE self) {
2572
4988
  ruby_curl_easy *rbce;
2573
4989
  double time;
2574
4990
 
2575
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4991
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2576
4992
  curl_easy_getinfo(rbce->curl, CURLINFO_CONNECT_TIME, &time);
2577
4993
 
2578
4994
  return rb_float_new(time);
@@ -2585,7 +5001,7 @@ static VALUE ruby_curl_easy_connect_time_get(VALUE self) {
2585
5001
  * Retrieve the time, in seconds, it took from the start until the SSL/SSH
2586
5002
  * connect/handshake to the remote host was completed. This time is most often
2587
5003
  * very near to the pre transfer time, except for cases such as HTTP
2588
- * pippelining where the pretransfer time can be delayed due to waits in line
5004
+ * pipelining where the pretransfer time can be delayed due to waits in line
2589
5005
  * for the pipeline and more.
2590
5006
  */
2591
5007
  #if defined(HAVE_CURLINFO_APPCONNECT_TIME)
@@ -2593,7 +5009,7 @@ static VALUE ruby_curl_easy_app_connect_time_get(VALUE self) {
2593
5009
  ruby_curl_easy *rbce;
2594
5010
  double time;
2595
5011
 
2596
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5012
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2597
5013
  curl_easy_getinfo(rbce->curl, CURLINFO_APPCONNECT_TIME, &time);
2598
5014
 
2599
5015
  return rb_float_new(time);
@@ -2614,7 +5030,7 @@ static VALUE ruby_curl_easy_pre_transfer_time_get(VALUE self) {
2614
5030
  ruby_curl_easy *rbce;
2615
5031
  double time;
2616
5032
 
2617
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5033
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2618
5034
  curl_easy_getinfo(rbce->curl, CURLINFO_PRETRANSFER_TIME, &time);
2619
5035
 
2620
5036
  return rb_float_new(time);
@@ -2632,7 +5048,7 @@ static VALUE ruby_curl_easy_start_transfer_time_get(VALUE self) {
2632
5048
  ruby_curl_easy *rbce;
2633
5049
  double time;
2634
5050
 
2635
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5051
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2636
5052
  curl_easy_getinfo(rbce->curl, CURLINFO_STARTTRANSFER_TIME, &time);
2637
5053
 
2638
5054
  return rb_float_new(time);
@@ -2654,7 +5070,7 @@ static VALUE ruby_curl_easy_redirect_time_get(VALUE self) {
2654
5070
  ruby_curl_easy *rbce;
2655
5071
  double time;
2656
5072
 
2657
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5073
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2658
5074
  curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_TIME, &time);
2659
5075
 
2660
5076
  return rb_float_new(time);
@@ -2677,13 +5093,13 @@ static VALUE ruby_curl_easy_redirect_count_get(VALUE self) {
2677
5093
  ruby_curl_easy *rbce;
2678
5094
  long count;
2679
5095
 
2680
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5096
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2681
5097
  curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_COUNT, &count);
2682
5098
 
2683
5099
  return LONG2NUM(count);
2684
5100
  #else
2685
5101
  rb_warn("Installed libcurl is too old to support redirect_count");
2686
- return INT2FIX(-1);
5102
+ return LONG2NUM(-1);
2687
5103
  #endif
2688
5104
 
2689
5105
  }
@@ -2692,7 +5108,7 @@ static VALUE ruby_curl_easy_redirect_count_get(VALUE self) {
2692
5108
  * call-seq:
2693
5109
  * easy.redirect_url => "http://some.url" or nil
2694
5110
  *
2695
- * Retrieve the URL a redirect would take you to if you
5111
+ * Retrieve the URL a redirect would take you to if you
2696
5112
  * would enable CURLOPT_FOLLOWLOCATION.
2697
5113
  *
2698
5114
  * Requires libcurl 7.18.2 or higher, otherwise -1 is always returned.
@@ -2702,7 +5118,7 @@ static VALUE ruby_curl_easy_redirect_url_get(VALUE self) {
2702
5118
  ruby_curl_easy *rbce;
2703
5119
  char* url;
2704
5120
 
2705
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5121
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2706
5122
  curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_URL, &url);
2707
5123
 
2708
5124
  if (url && url[0]) { // curl returns empty string if none
@@ -2712,7 +5128,7 @@ static VALUE ruby_curl_easy_redirect_url_get(VALUE self) {
2712
5128
  }
2713
5129
  #else
2714
5130
  rb_warn("Installed libcurl is too old to support redirect_url");
2715
- return INT2FIX(-1);
5131
+ return LONG2NUM(-1);
2716
5132
  #endif
2717
5133
  }
2718
5134
 
@@ -2727,12 +5143,17 @@ static VALUE ruby_curl_easy_redirect_url_get(VALUE self) {
2727
5143
  */
2728
5144
  static VALUE ruby_curl_easy_uploaded_bytes_get(VALUE self) {
2729
5145
  ruby_curl_easy *rbce;
2730
- double bytes;
5146
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2731
5147
 
2732
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5148
+ #ifdef HAVE_CURLINFO_SIZE_UPLOAD_T
5149
+ curl_off_t bytes;
5150
+ curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_UPLOAD_T, &bytes);
5151
+ return LL2NUM(bytes);
5152
+ #else
5153
+ double bytes;
2733
5154
  curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_UPLOAD, &bytes);
2734
-
2735
5155
  return rb_float_new(bytes);
5156
+ #endif
2736
5157
  }
2737
5158
 
2738
5159
  /*
@@ -2744,12 +5165,17 @@ static VALUE ruby_curl_easy_uploaded_bytes_get(VALUE self) {
2744
5165
  */
2745
5166
  static VALUE ruby_curl_easy_downloaded_bytes_get(VALUE self) {
2746
5167
  ruby_curl_easy *rbce;
2747
- double bytes;
5168
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2748
5169
 
2749
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5170
+ #ifdef HAVE_CURLINFO_SIZE_DOWNLOAD_T
5171
+ curl_off_t bytes;
5172
+ curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_DOWNLOAD_T, &bytes);
5173
+ return LL2NUM(bytes);
5174
+ #else
5175
+ double bytes;
2750
5176
  curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_DOWNLOAD, &bytes);
2751
-
2752
5177
  return rb_float_new(bytes);
5178
+ #endif
2753
5179
  }
2754
5180
 
2755
5181
  /*
@@ -2761,12 +5187,17 @@ static VALUE ruby_curl_easy_downloaded_bytes_get(VALUE self) {
2761
5187
  */
2762
5188
  static VALUE ruby_curl_easy_upload_speed_get(VALUE self) {
2763
5189
  ruby_curl_easy *rbce;
2764
- double bytes;
5190
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2765
5191
 
2766
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5192
+ #ifdef HAVE_CURLINFO_SPEED_UPLOAD_T
5193
+ curl_off_t bytes;
5194
+ curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_UPLOAD_T, &bytes);
5195
+ return LL2NUM(bytes);
5196
+ #else
5197
+ double bytes;
2767
5198
  curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_UPLOAD, &bytes);
2768
-
2769
5199
  return rb_float_new(bytes);
5200
+ #endif
2770
5201
  }
2771
5202
 
2772
5203
  /*
@@ -2778,12 +5209,17 @@ static VALUE ruby_curl_easy_upload_speed_get(VALUE self) {
2778
5209
  */
2779
5210
  static VALUE ruby_curl_easy_download_speed_get(VALUE self) {
2780
5211
  ruby_curl_easy *rbce;
2781
- double bytes;
5212
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2782
5213
 
2783
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5214
+ #ifdef HAVE_CURLINFO_SPEED_DOWNLOAD_T
5215
+ curl_off_t bytes;
5216
+ curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_DOWNLOAD_T, &bytes);
5217
+ return LL2NUM(bytes);
5218
+ #else
5219
+ double bytes;
2784
5220
  curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_DOWNLOAD, &bytes);
2785
-
2786
5221
  return rb_float_new(bytes);
5222
+ #endif
2787
5223
  }
2788
5224
 
2789
5225
  /*
@@ -2797,7 +5233,7 @@ static VALUE ruby_curl_easy_header_size_get(VALUE self) {
2797
5233
  ruby_curl_easy *rbce;
2798
5234
  long size;
2799
5235
 
2800
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5236
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2801
5237
  curl_easy_getinfo(rbce->curl, CURLINFO_HEADER_SIZE, &size);
2802
5238
 
2803
5239
  return LONG2NUM(size);
@@ -2815,7 +5251,7 @@ static VALUE ruby_curl_easy_request_size_get(VALUE self) {
2815
5251
  ruby_curl_easy *rbce;
2816
5252
  long size;
2817
5253
 
2818
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5254
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2819
5255
  curl_easy_getinfo(rbce->curl, CURLINFO_REQUEST_SIZE, &size);
2820
5256
 
2821
5257
  return LONG2NUM(size);
@@ -2832,7 +5268,7 @@ static VALUE ruby_curl_easy_ssl_verify_result_get(VALUE self) {
2832
5268
  ruby_curl_easy *rbce;
2833
5269
  long result;
2834
5270
 
2835
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5271
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2836
5272
  curl_easy_getinfo(rbce->curl, CURLINFO_SSL_VERIFYRESULT, &result);
2837
5273
 
2838
5274
  return LONG2NUM(result);
@@ -2855,12 +5291,17 @@ NOTE: you must call curl_slist_free_all(3) on the list pointer once you're done
2855
5291
  */
2856
5292
  static VALUE ruby_curl_easy_downloaded_content_length_get(VALUE self) {
2857
5293
  ruby_curl_easy *rbce;
2858
- double bytes;
5294
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2859
5295
 
2860
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5296
+ #ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
5297
+ curl_off_t bytes;
5298
+ curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &bytes);
5299
+ return LL2NUM(bytes);
5300
+ #else
5301
+ double bytes;
2861
5302
  curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &bytes);
2862
-
2863
5303
  return rb_float_new(bytes);
5304
+ #endif
2864
5305
  }
2865
5306
 
2866
5307
  /*
@@ -2871,12 +5312,17 @@ static VALUE ruby_curl_easy_downloaded_content_length_get(VALUE self) {
2871
5312
  */
2872
5313
  static VALUE ruby_curl_easy_uploaded_content_length_get(VALUE self) {
2873
5314
  ruby_curl_easy *rbce;
2874
- double bytes;
5315
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2875
5316
 
2876
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5317
+ #ifdef HAVE_CURLINFO_CONTENT_LENGTH_UPLOAD_T
5318
+ curl_off_t bytes;
5319
+ curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_UPLOAD_T, &bytes);
5320
+ return LL2NUM(bytes);
5321
+ #else
5322
+ double bytes;
2877
5323
  curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_UPLOAD, &bytes);
2878
-
2879
5324
  return rb_float_new(bytes);
5325
+ #endif
2880
5326
  }
2881
5327
 
2882
5328
  /*
@@ -2892,7 +5338,7 @@ static VALUE ruby_curl_easy_content_type_get(VALUE self) {
2892
5338
  ruby_curl_easy *rbce;
2893
5339
  char* type;
2894
5340
 
2895
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5341
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2896
5342
  curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_TYPE, &type);
2897
5343
 
2898
5344
  if (type && type[0]) { // curl returns empty string if none
@@ -2935,13 +5381,13 @@ static VALUE ruby_curl_easy_os_errno_get(VALUE self) {
2935
5381
  ruby_curl_easy *rbce;
2936
5382
  long result;
2937
5383
 
2938
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5384
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2939
5385
  curl_easy_getinfo(rbce->curl, CURLINFO_OS_ERRNO, &result);
2940
5386
 
2941
5387
  return LONG2NUM(result);
2942
5388
  #else
2943
5389
  rb_warn("Installed libcurl is too old to support os_errno");
2944
- return INT2FIX(0);
5390
+ return LONG2NUM(0);
2945
5391
  #endif
2946
5392
  }
2947
5393
 
@@ -2964,23 +5410,59 @@ static VALUE ruby_curl_easy_num_connects_get(VALUE self) {
2964
5410
  ruby_curl_easy *rbce;
2965
5411
  long result;
2966
5412
 
2967
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5413
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2968
5414
  curl_easy_getinfo(rbce->curl, CURLINFO_NUM_CONNECTS, &result);
2969
5415
 
2970
5416
  return LONG2NUM(result);
2971
5417
  #else
2972
5418
  rb_warn("Installed libcurl is too old to support num_connects");
2973
- return INT2FIX(-1);
5419
+ return LONG2NUM(-1);
2974
5420
  #endif
2975
5421
  }
2976
5422
 
2977
5423
 
2978
- /* TODO this needs to be implemented.
5424
+ /*
5425
+ * call-seq:
5426
+ * easy.cookielist => cookielist
5427
+ *
5428
+ * Retrieves the cookies curl knows in an array of strings.
5429
+ * Returned strings are in Netscape cookiejar format or in Set-Cookie format.
5430
+ * Since 7.43.0 cookies in the Set-Cookie format without a domain name are not exported.
5431
+ *
5432
+ * To modify the cookie engine (add/replace/remove), use +easy.cookielist= string+
5433
+ * or +easy.set(:cookielist, string)+ with one of the following accepted inputs:
5434
+ * - A Set-Cookie style header string: "Set-Cookie: name=value; Domain=example.com; Path=/; Expires=..."
5435
+ * - One or more lines in Netscape cookie file format (tab-separated fields)
5436
+ * - Special commands: "ALL" (clear all), "SESS" (remove session cookies), "FLUSH" (write to jar), "RELOAD" (reload from file)
5437
+ *
5438
+ * @see https://curl.se/libcurl/c/CURLINFO_COOKIELIST.html option <code>CURLINFO_COOKIELIST</code> of
5439
+ * <code>curl_easy_getopt(3)</code> to see how libcurl behaves.
5440
+ * @note requires libcurl 7.14.1 or higher, otherwise +-1+ is always returned
5441
+ * @return [Array<String>, nil, -1] array of strings, or +nil+ if there are no cookies, or +-1+ if the libcurl version is too old
5442
+ */
5443
+ static VALUE ruby_curl_easy_cookielist_get(VALUE self) {
5444
+ #ifdef HAVE_CURLINFO_COOKIELIST
5445
+ ruby_curl_easy *rbce;
5446
+ struct curl_slist *cookies;
5447
+ struct curl_slist *cookie;
5448
+ VALUE rb_cookies;
5449
+
5450
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
5451
+ curl_easy_getinfo(rbce->curl, CURLINFO_COOKIELIST, &cookies);
5452
+ if (!cookies)
5453
+ return Qnil;
5454
+ rb_cookies = rb_ary_new();
5455
+ for (cookie = cookies; cookie; cookie = cookie->next)
5456
+ rb_ary_push(rb_cookies, rb_str_new2(cookie->data));
5457
+ curl_slist_free_all(cookies);
5458
+ return rb_cookies;
2979
5459
 
2980
- CURLINFO_COOKIELIST
5460
+ #else
5461
+ rb_warn("Installed libcurl is too old to support cookielist");
5462
+ return INT2FIX(-1);
5463
+ #endif
5464
+ }
2981
5465
 
2982
- Pass a pointer to a 'struct curl_slist *' to receive a linked-list of all cookies cURL knows (expired ones, too). Don't forget to curl_slist_free_all(3) the list after it has been used. If there are no cookies (cookies for the handle have not been enabled or simply none have been received) 'struct curl_slist *' will be set to point to NULL. (Added in 7.14.1)
2983
- */
2984
5466
 
2985
5467
  /* TODO this needs to be implemented. Could probably support CONNECT_ONLY by having this
2986
5468
  * return an open Socket or something.
@@ -3005,7 +5487,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
3005
5487
  ruby_curl_easy *rbce;
3006
5488
  char* path = NULL;
3007
5489
 
3008
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5490
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3009
5491
  curl_easy_getinfo(rbce->curl, CURLINFO_FTP_ENTRY_PATH, &path);
3010
5492
 
3011
5493
  if (path && path[0]) { // curl returns NULL or empty string if none
@@ -3014,7 +5496,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
3014
5496
  return Qnil;
3015
5497
  }
3016
5498
  #else
3017
- rb_warn("Installed libcurl is too old to support num_connects");
5499
+ rb_warn("Installed libcurl is too old to support ftp_entry_path");
3018
5500
  return Qnil;
3019
5501
  #endif
3020
5502
  }
@@ -3025,7 +5507,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
3025
5507
  */
3026
5508
  static VALUE ruby_curl_easy_multi_get(VALUE self) {
3027
5509
  ruby_curl_easy *rbce;
3028
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5510
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3029
5511
  return rbce->multi;
3030
5512
  }
3031
5513
 
@@ -3035,7 +5517,28 @@ static VALUE ruby_curl_easy_multi_get(VALUE self) {
3035
5517
  */
3036
5518
  static VALUE ruby_curl_easy_multi_set(VALUE self, VALUE multi) {
3037
5519
  ruby_curl_easy *rbce;
3038
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5520
+ VALUE old_multi;
5521
+ ruby_curl_multi *old_rbcm;
5522
+ CURLMcode result;
5523
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
5524
+
5525
+ if (!NIL_P(multi) && rb_obj_is_kind_of(multi, cCurlMulti) != Qtrue) {
5526
+ rb_raise(rb_eTypeError, "expected Curl::Multi or nil");
5527
+ }
5528
+
5529
+ old_multi = rbce->multi;
5530
+ if (old_multi == multi) {
5531
+ return rbce->multi;
5532
+ }
5533
+
5534
+ old_rbcm = ruby_curl_multi_pointer_if_compatible(old_multi);
5535
+ if (old_rbcm) {
5536
+ result = rb_curl_multi_detach_easy(old_rbcm, rbce);
5537
+ if (result != CURLM_OK) {
5538
+ raise_curl_multi_error_exception(result);
5539
+ }
5540
+ }
5541
+
3039
5542
  rbce->multi = multi;
3040
5543
  return rbce->multi;
3041
5544
  }
@@ -3046,21 +5549,44 @@ static VALUE ruby_curl_easy_multi_set(VALUE self, VALUE multi) {
3046
5549
  */
3047
5550
  static VALUE ruby_curl_easy_last_result(VALUE self) {
3048
5551
  ruby_curl_easy *rbce;
3049
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3050
- return INT2FIX(rbce->last_result);
5552
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
5553
+ return LONG2NUM(rbce->last_result);
5554
+ }
5555
+
5556
+ /*
5557
+ * call-seq:
5558
+ * easy.last_error => "Error details" or nil
5559
+ */
5560
+ static VALUE ruby_curl_easy_last_error(VALUE self) {
5561
+ ruby_curl_easy *rbce;
5562
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
5563
+
5564
+ if (rbce->err_buf[0]) { // curl returns NULL or empty string if none
5565
+ return rb_str_new2(rbce->err_buf);
5566
+ } else {
5567
+ return Qnil;
5568
+ }
3051
5569
  }
3052
5570
 
3053
5571
  /*
3054
5572
  * call-seq:
3055
- * easy.setopt Fixnum, value => value
5573
+ * easy.setopt(opt, val) => val
5574
+ *
5575
+ * Initial access to libcurl curl_easy_setopt
3056
5576
  *
3057
- * Iniital access to libcurl curl_easy_setopt
5577
+ * @param [Fixnum] opt The option to set, see +Curl::CURLOPT_*+ constants
5578
+ * @param [Object] val
5579
+ * @return [Object] val
5580
+ * @raise [TypeError] if the option is not supported
5581
+ * @note Some options - like url or cookie - aren't set directly throught +curl_easy_setopt+, but stored in the Ruby object state.
5582
+ * @note When +curl_easy_setopt+ is called, return value is not checked here.
3058
5583
  */
3059
5584
  static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3060
5585
  ruby_curl_easy *rbce;
3061
- long option = FIX2LONG(opt);
5586
+ long option = NUM2LONG(opt);
5587
+ rb_io_t *open_f_ptr;
3062
5588
 
3063
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5589
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3064
5590
 
3065
5591
  switch (option) {
3066
5592
  /* BEHAVIOR OPTIONS */
@@ -3082,9 +5608,11 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3082
5608
  case CURLOPT_CUSTOMREQUEST:
3083
5609
  curl_easy_setopt(rbce->curl, CURLOPT_CUSTOMREQUEST, NIL_P(val) ? NULL : StringValueCStr(val));
3084
5610
  break;
3085
- case CURLOPT_HTTP_VERSION:
3086
- curl_easy_setopt(rbce->curl, CURLOPT_HTTP_VERSION, FIX2INT(val));
3087
- break;
5611
+ case CURLOPT_HTTP_VERSION: {
5612
+ long http_version = NIL_P(val) ? CURL_HTTP_VERSION_NONE : NUM2LONG(val);
5613
+ rbce->http_version = http_version;
5614
+ curl_easy_setopt(rbce->curl, CURLOPT_HTTP_VERSION, http_version);
5615
+ } break;
3088
5616
  case CURLOPT_PROXY: {
3089
5617
  VALUE proxy_url = val;
3090
5618
  CURB_OBJECT_HSETTER(ruby_curl_easy, proxy_url);
@@ -3096,6 +5624,12 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3096
5624
  case CURLOPT_HEADER:
3097
5625
  case CURLOPT_NOPROGRESS:
3098
5626
  case CURLOPT_NOSIGNAL:
5627
+ #ifdef HAVE_CURLOPT_PATH_AS_IS
5628
+ case CURLOPT_PATH_AS_IS:
5629
+ #endif
5630
+ #ifdef HAVE_CURLOPT_PIPEWAIT
5631
+ case CURLOPT_PIPEWAIT:
5632
+ #endif
3099
5633
  case CURLOPT_HTTPGET:
3100
5634
  case CURLOPT_NOBODY: {
3101
5635
  int type = rb_type(val);
@@ -3107,8 +5641,17 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3107
5641
  } else {
3108
5642
  value = rb_funcall(val, rb_intern("to_i"), 0);
3109
5643
  }
3110
- curl_easy_setopt(rbce->curl, option, FIX2INT(value));
5644
+ curl_easy_setopt(rbce->curl, option, NUM2LONG(value));
3111
5645
  } break;
5646
+ case CURLOPT_POST: {
5647
+ curl_easy_setopt(rbce->curl, CURLOPT_POST, rb_type(val) == T_TRUE);
5648
+ } break;
5649
+ case CURLOPT_MAXCONNECTS: {
5650
+ curl_easy_setopt(rbce->curl, CURLOPT_MAXCONNECTS, NUM2LONG(val));
5651
+ } break;
5652
+ case CURLOPT_POSTFIELDS: {
5653
+ ruby_curl_easy_post_body_set_with_mode(self, val, 0);
5654
+ } break;
3112
5655
  case CURLOPT_USERPWD: {
3113
5656
  VALUE userpwd = val;
3114
5657
  CURB_OBJECT_HSETTER(ruby_curl_easy, userpwd);
@@ -3117,6 +5660,12 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3117
5660
  VALUE proxypwd = val;
3118
5661
  CURB_OBJECT_HSETTER(ruby_curl_easy, proxypwd);
3119
5662
  } break;
5663
+ #ifdef HAVE_CURLOPT_NOPROXY
5664
+ case CURLOPT_NOPROXY: {
5665
+ VALUE noproxy = val;
5666
+ CURB_OBJECT_HSETTER(ruby_curl_easy, noproxy);
5667
+ } break;
5668
+ #endif
3120
5669
  case CURLOPT_COOKIE: {
3121
5670
  VALUE cookies = val;
3122
5671
  CURB_OBJECT_HSETTER(ruby_curl_easy, cookies);
@@ -3129,19 +5678,278 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3129
5678
  VALUE cookiejar = val;
3130
5679
  CURB_OBJECT_HSETTER(ruby_curl_easy, cookiejar);
3131
5680
  } break;
5681
+ #ifdef HAVE_CURLOPT_REQUEST_TARGET
5682
+ case CURLOPT_REQUEST_TARGET: {
5683
+ /* Forward request-target directly to libcurl as a string. */
5684
+ curl_easy_setopt(rbce->curl, CURLOPT_REQUEST_TARGET, NIL_P(val) ? NULL : StringValueCStr(val));
5685
+ } break;
5686
+ #endif
5687
+ #ifdef HAVE_CURLOPT_DOH_URL
5688
+ case CURLOPT_DOH_URL: {
5689
+ rb_hash_aset(rbce->opts, rb_easy_hkey("doh_url"), val);
5690
+ curl_easy_setopt(rbce->curl, CURLOPT_DOH_URL, NIL_P(val) ? NULL : StringValueCStr(val));
5691
+ } break;
5692
+ #endif
5693
+ case CURLOPT_TCP_NODELAY: {
5694
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val));
5695
+ } break;
5696
+ /* FTP-specific toggles */
5697
+ #ifdef HAVE_CURLOPT_DIRLISTONLY
5698
+ 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));
5709
+ } break;
5710
+ #endif
5711
+ #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;
5724
+ #endif
5725
+ #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;
5738
+ #endif
5739
+ #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;
5752
+ #endif
5753
+ case CURLOPT_RANGE: {
5754
+ curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val));
5755
+ } break;
3132
5756
  case CURLOPT_RESUME_FROM: {
3133
- curl_easy_setopt(rbce->curl, CURLOPT_RESUME_FROM, FIX2LONG(val));
5757
+ curl_easy_setopt(rbce->curl, CURLOPT_RESUME_FROM, NUM2LONG(val));
3134
5758
  } break;
3135
5759
  case CURLOPT_FAILONERROR: {
3136
- curl_easy_setopt(rbce->curl, CURLOPT_FAILONERROR, FIX2LONG(val));
5760
+ curl_easy_setopt(rbce->curl, CURLOPT_FAILONERROR, NUM2LONG(val));
3137
5761
  } break;
3138
- #if HAVE_CURLOPT_GSSAPI_DELEGATION
5762
+ case CURLOPT_SSL_CIPHER_LIST: {
5763
+ curl_easy_setopt(rbce->curl, CURLOPT_SSL_CIPHER_LIST, StringValueCStr(val));
5764
+ } break;
5765
+ case CURLOPT_FORBID_REUSE: {
5766
+ rbce->forbid_reuse = NUM2LONG(val);
5767
+ rbce->forbid_reuse_set = 1;
5768
+ curl_easy_setopt(rbce->curl, CURLOPT_FORBID_REUSE, rbce->forbid_reuse);
5769
+ } break;
5770
+ #ifdef HAVE_CURLOPT_GSSAPI_DELEGATION
3139
5771
  case CURLOPT_GSSAPI_DELEGATION: {
3140
- curl_easy_setopt(rbce->curl, CURLOPT_GSSAPI_DELEGATION, FIX2LONG(val));
5772
+ curl_easy_setopt(rbce->curl, CURLOPT_GSSAPI_DELEGATION, NUM2LONG(val));
3141
5773
  } break;
3142
5774
  #endif
3143
- default:
5775
+ #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
5776
+ case CURLOPT_UNIX_SOCKET_PATH: {
5777
+ rb_hash_aset(rbce->opts, rb_easy_hkey("unix_socket_path"), val);
5778
+ curl_easy_setopt(rbce->curl, CURLOPT_UNIX_SOCKET_PATH, NIL_P(val) ? NULL : StringValueCStr(val));
5779
+ } break;
5780
+ #endif
5781
+ #ifdef HAVE_CURLOPT_DNS_SERVERS
5782
+ case CURLOPT_DNS_SERVERS: {
5783
+ rb_hash_aset(rbce->opts, rb_easy_hkey("dns_servers"), val);
5784
+ curl_easy_setopt(rbce->curl, CURLOPT_DNS_SERVERS, NIL_P(val) ? NULL : StringValueCStr(val));
5785
+ } break;
5786
+ #endif
5787
+ #ifdef HAVE_CURLOPT_MAX_SEND_SPEED_LARGE
5788
+ case CURLOPT_MAX_SEND_SPEED_LARGE: {
5789
+ curl_easy_setopt(rbce->curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t) NUM2LL(val));
5790
+ } break;
5791
+ #endif
5792
+ #ifdef HAVE_CURLOPT_MAX_RECV_SPEED_LARGE
5793
+ case CURLOPT_MAX_RECV_SPEED_LARGE: {
5794
+ curl_easy_setopt(rbce->curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) NUM2LL(val));
5795
+ } break;
5796
+ #endif
5797
+ #ifdef HAVE_CURLOPT_MAXFILESIZE
5798
+ case CURLOPT_MAXFILESIZE:
5799
+ curl_easy_setopt(rbce->curl, CURLOPT_MAXFILESIZE, NUM2LONG(val));
5800
+ break;
5801
+ #endif
5802
+ #ifdef HAVE_CURLOPT_MAXFILESIZE_LARGE
5803
+ case CURLOPT_MAXFILESIZE_LARGE:
5804
+ curl_easy_setopt(rbce->curl, CURLOPT_MAXFILESIZE_LARGE, (curl_off_t)NUM2LL(val));
5805
+ break;
5806
+ #endif
5807
+ #ifdef HAVE_CURLOPT_TCP_KEEPALIVE
5808
+ case CURLOPT_TCP_KEEPALIVE:
5809
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPALIVE, NUM2LONG(val));
5810
+ break;
5811
+ case CURLOPT_TCP_KEEPIDLE:
5812
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPIDLE, NUM2LONG(val));
5813
+ break;
5814
+ case CURLOPT_TCP_KEEPINTVL:
5815
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPINTVL, NUM2LONG(val));
5816
+ break;
5817
+ #endif
5818
+ #ifdef HAVE_CURLOPT_HAPROXYPROTOCOL
5819
+ case CURLOPT_HAPROXYPROTOCOL:
5820
+ curl_easy_setopt(rbce->curl, CURLOPT_HAPROXYPROTOCOL, NUM2LONG(val));
5821
+ break;
5822
+ #endif
5823
+ case CURLOPT_STDERR:
5824
+ // libcurl requires raw FILE pointer and this should be IO object in Ruby.
5825
+ // Tempfile or StringIO won't work.
5826
+ Check_Type(val, T_FILE);
5827
+ GetOpenFile(val, open_f_ptr);
5828
+ curl_easy_setopt(rbce->curl, CURLOPT_STDERR, rb_io_stdio_file(open_f_ptr));
5829
+ /* Retain a Ruby reference to the IO to prevent GC/finalization
5830
+ * while libcurl still holds and writes to the underlying FILE*. */
5831
+ rb_easy_set("stderr_io", val);
5832
+ break;
5833
+ case CURLOPT_PROTOCOLS:
5834
+ case CURLOPT_REDIR_PROTOCOLS:
5835
+ curl_easy_setopt(rbce->curl, option, NUM2LONG(val));
5836
+ break;
5837
+ #ifdef HAVE_CURLOPT_PROTOCOLS_STR
5838
+ case CURLOPT_PROTOCOLS_STR:
5839
+ curl_easy_setopt(rbce->curl, CURLOPT_PROTOCOLS_STR, NIL_P(val) ? NULL : StringValueCStr(val));
5840
+ break;
5841
+ #endif
5842
+ #ifdef HAVE_CURLOPT_REDIR_PROTOCOLS_STR
5843
+ case CURLOPT_REDIR_PROTOCOLS_STR:
5844
+ curl_easy_setopt(rbce->curl, CURLOPT_REDIR_PROTOCOLS_STR, NIL_P(val) ? NULL : StringValueCStr(val));
5845
+ break;
5846
+ #endif
5847
+ #ifdef HAVE_CURLOPT_SSL_SESSIONID_CACHE
5848
+ case CURLOPT_SSL_SESSIONID_CACHE:
5849
+ curl_easy_setopt(rbce->curl, CURLOPT_SSL_SESSIONID_CACHE, NUM2LONG(val));
5850
+ break;
5851
+ #endif
5852
+ #ifdef HAVE_CURLOPT_COOKIELIST
5853
+ case CURLOPT_COOKIELIST: {
5854
+ /* Forward to libcurl */
5855
+ curl_easy_setopt(rbce->curl, CURLOPT_COOKIELIST, StringValueCStr(val));
5856
+ /* Track whether the cookie engine should be enabled for requests.
5857
+ * According to libcurl docs, CURLOPT_COOKIELIST also enables the cookie engine
5858
+ * when provided with a non-command string. Some environments may still require
5859
+ * an explicit "enable" via CURLOPT_COOKIEFILE="" to send cookies on requests.
5860
+ * We do that in the perform setup when this flag is set.
5861
+ */
5862
+ if (RB_TYPE_P(val, T_STRING)) {
5863
+ const char *s = StringValueCStr(val);
5864
+ if (!(strcmp(s, "ALL") == 0 || strcmp(s, "SESS") == 0 || strcmp(s, "FLUSH") == 0 || strcmp(s, "RELOAD") == 0)) {
5865
+ rbce->cookielist_engine_enabled = 1;
5866
+ }
5867
+ } else {
5868
+ /* Non-string values are unexpected; be conservative and do not enable. */
5869
+ }
5870
+ } break;
5871
+ #endif
5872
+ #ifdef HAVE_CURLOPT_PROXY_SSL_VERIFYHOST
5873
+ case CURLOPT_PROXY_SSL_VERIFYHOST:
5874
+ curl_easy_setopt(rbce->curl, CURLOPT_PROXY_SSL_VERIFYHOST, NUM2LONG(val));
5875
+ break;
5876
+ #endif
5877
+ #ifdef HAVE_CURLOPT_DOH_SSL_VERIFYPEER
5878
+ case CURLOPT_DOH_SSL_VERIFYPEER:
5879
+ curl_easy_setopt(rbce->curl, CURLOPT_DOH_SSL_VERIFYPEER, NUM2LONG(val));
3144
5880
  break;
5881
+ #endif
5882
+ #ifdef HAVE_CURLOPT_DOH_SSL_VERIFYHOST
5883
+ case CURLOPT_DOH_SSL_VERIFYHOST:
5884
+ curl_easy_setopt(rbce->curl, CURLOPT_DOH_SSL_VERIFYHOST, NUM2LONG(val));
5885
+ break;
5886
+ #endif
5887
+ #ifdef HAVE_CURLOPT_DOH_SSL_VERIFYSTATUS
5888
+ case CURLOPT_DOH_SSL_VERIFYSTATUS:
5889
+ curl_easy_setopt(rbce->curl, CURLOPT_DOH_SSL_VERIFYSTATUS, NUM2LONG(val));
5890
+ break;
5891
+ #endif
5892
+ #ifdef HAVE_CURLOPT_RESOLVE
5893
+ case CURLOPT_RESOLVE: {
5894
+ struct curl_slist *list = NULL;
5895
+ ruby_curl_easy_clear_resolve_list(rbce);
5896
+ if (NIL_P(val)) {
5897
+ /* When nil is passed, we clear any previous resolve list */
5898
+ list = NULL;
5899
+ } else if (TYPE(val) == T_ARRAY) {
5900
+ long i, len = RARRAY_LEN(val);
5901
+ for (i = 0; i < len; i++) {
5902
+ VALUE item = rb_ary_entry(val, i);
5903
+ struct curl_slist *new_list = curl_slist_append(list, StringValueCStr(item));
5904
+ if (!new_list) {
5905
+ curl_slist_free_all(list);
5906
+ rb_raise(rb_eNoMemError, "Failed to append to resolve list");
5907
+ }
5908
+ list = new_list;
5909
+ }
5910
+ } else {
5911
+ /* If a single string is passed, use it directly */
5912
+ list = curl_slist_append(NULL, StringValueCStr(val));
5913
+ if (!list) {
5914
+ rb_raise(rb_eNoMemError, "Failed to create resolve list");
5915
+ }
5916
+ }
5917
+ /* Save the list pointer in the ruby_curl_easy structure for cleanup later */
5918
+ rbce->curl_resolve = list;
5919
+ rb_hash_aset(rbce->opts, rb_easy_hkey("resolve"), val);
5920
+ curl_easy_setopt(rbce->curl, CURLOPT_RESOLVE, list);
5921
+ } break;
5922
+ #endif
5923
+ #ifdef HAVE_CURLOPT_CONNECT_TO
5924
+ case CURLOPT_CONNECT_TO: {
5925
+ struct curl_slist *list = NULL;
5926
+ ruby_curl_easy_clear_connect_to_list(rbce);
5927
+ if (NIL_P(val)) {
5928
+ list = NULL;
5929
+ } else if (TYPE(val) == T_ARRAY) {
5930
+ long i, len = RARRAY_LEN(val);
5931
+ for (i = 0; i < len; i++) {
5932
+ VALUE item = rb_ary_entry(val, i);
5933
+ struct curl_slist *new_list = curl_slist_append(list, StringValueCStr(item));
5934
+ if (!new_list) {
5935
+ curl_slist_free_all(list);
5936
+ rb_raise(rb_eNoMemError, "Failed to append to connect-to list");
5937
+ }
5938
+ list = new_list;
5939
+ }
5940
+ } else {
5941
+ list = curl_slist_append(NULL, StringValueCStr(val));
5942
+ if (!list) {
5943
+ rb_raise(rb_eNoMemError, "Failed to create connect-to list");
5944
+ }
5945
+ }
5946
+ rbce->curl_connect_to = list;
5947
+ rb_hash_aset(rbce->opts, rb_easy_hkey("connect_to"), val);
5948
+ curl_easy_setopt(rbce->curl, CURLOPT_CONNECT_TO, list);
5949
+ } break;
5950
+ #endif
5951
+ default:
5952
+ rb_raise(rb_eTypeError, "Curb unsupported option");
3145
5953
  }
3146
5954
 
3147
5955
  return val;
@@ -3149,9 +5957,13 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3149
5957
 
3150
5958
  /*
3151
5959
  * call-seq:
3152
- * easy.getinfo Fixnum => value
5960
+ * easy.getinfo(opt) => nil
3153
5961
  *
3154
5962
  * Iniital access to libcurl curl_easy_getinfo, remember getinfo doesn't return the same values as setopt
5963
+ *
5964
+ * @note This method is not implemented yet.
5965
+ * @param [Fixnum] code Constant +CURLINFO_*+ from libcurl
5966
+ * @return [nil]
3155
5967
  */
3156
5968
  static VALUE ruby_curl_easy_get_opt(VALUE self, VALUE opt) {
3157
5969
  return Qnil;
@@ -3164,7 +5976,7 @@ static VALUE ruby_curl_easy_get_opt(VALUE self, VALUE opt) {
3164
5976
  static VALUE ruby_curl_easy_inspect(VALUE self) {
3165
5977
  char buf[64];
3166
5978
  ruby_curl_easy *rbce;
3167
- Data_Get_Struct(self, ruby_curl_easy, rbce);
5979
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3168
5980
  /* if we don't have a url set... we'll crash... */
3169
5981
  if( !rb_easy_nil("url") && rb_easy_type_check("url", T_STRING)) {
3170
5982
  VALUE url = rb_easy_get("url");
@@ -3196,7 +6008,7 @@ static VALUE ruby_curl_easy_escape(VALUE self, VALUE svalue) {
3196
6008
  VALUE rresult;
3197
6009
  VALUE str = svalue;
3198
6010
 
3199
- Data_Get_Struct(self, ruby_curl_easy, rbce);
6011
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3200
6012
 
3201
6013
  /* NOTE: make sure the value is a string, if not call to_s */
3202
6014
  if( rb_type(str) != T_STRING ) { str = rb_funcall(str,rb_intern("to_s"),0); }
@@ -3227,12 +6039,12 @@ static VALUE ruby_curl_easy_unescape(VALUE self, VALUE str) {
3227
6039
  char *result;
3228
6040
  VALUE rresult;
3229
6041
 
3230
- Data_Get_Struct(self, ruby_curl_easy, rbce);
6042
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3231
6043
 
3232
6044
  #if (LIBCURL_VERSION_NUM >= 0x070f04)
3233
6045
  result = (char*)curl_easy_unescape(rbce->curl, StringValuePtr(str), (int)RSTRING_LEN(str), &rlen);
3234
6046
  #else
3235
- result = (char*)curl_unescape(StringValuePtr(str), RSTRING_LEN(str));
6047
+ result = (char*)curl_unescape(StringValuePtr(str), (int)RSTRING_LEN(str));
3236
6048
  rlen = strlen(result);
3237
6049
  #endif
3238
6050
 
@@ -3247,18 +6059,21 @@ static VALUE ruby_curl_easy_unescape(VALUE self, VALUE str) {
3247
6059
 
3248
6060
  /*
3249
6061
  * call-seq:
3250
- * Curl::Easy.error(code) => String
6062
+ * Curl::Easy.error(code) => [ErrCode, String]
3251
6063
  *
3252
6064
  * translate an internal libcurl error to ruby error class
3253
6065
  */
3254
6066
  static VALUE ruby_curl_easy_error_message(VALUE klass, VALUE code) {
3255
- return rb_curl_easy_error(FIX2INT(code));
6067
+ return rb_curl_easy_error(NUM2INT(code));
3256
6068
  }
3257
6069
 
3258
6070
  /* =================== INIT LIB =====================*/
6071
+ // TODO: https://bugs.ruby-lang.org/issues/18007
3259
6072
  void init_curb_easy() {
3260
6073
  idCall = rb_intern("call");
3261
6074
  idJoin = rb_intern("join");
6075
+ idNetworkPolicyNone = rb_intern("none");
6076
+ idNetworkPolicyPublic = rb_intern("public");
3262
6077
 
3263
6078
  rbstrAmp = rb_str_new2("&");
3264
6079
  rb_global_variable(&rbstrAmp);
@@ -3266,12 +6081,21 @@ void init_curb_easy() {
3266
6081
  cCurlEasy = rb_define_class_under(mCurl, "Easy", rb_cObject);
3267
6082
 
3268
6083
  /* Class methods */
3269
- rb_define_singleton_method(cCurlEasy, "new", ruby_curl_easy_new, -1);
6084
+ rb_define_alloc_func(cCurlEasy, ruby_curl_easy_allocate);
3270
6085
  rb_define_singleton_method(cCurlEasy, "error", ruby_curl_easy_error_message, 1);
3271
6086
 
6087
+ /* Initialize method */
6088
+ rb_define_method(cCurlEasy, "initialize", ruby_curl_easy_initialize, -1);
6089
+
3272
6090
  /* Attributes for config next perform */
3273
6091
  rb_define_method(cCurlEasy, "url", ruby_curl_easy_url_get, 0);
3274
6092
  rb_define_method(cCurlEasy, "proxy_url", ruby_curl_easy_proxy_url_get, 0);
6093
+ rb_define_method(cCurlEasy, "http_version=", ruby_curl_easy_http_version_set, 1);
6094
+ rb_define_method(cCurlEasy, "http_version", ruby_curl_easy_http_version_get, 0);
6095
+
6096
+ rb_define_method(cCurlEasy, "proxy_headers=", ruby_curl_easy_proxy_headers_set, 1);
6097
+ rb_define_method(cCurlEasy, "proxy_headers", ruby_curl_easy_proxy_headers_get, 0);
6098
+
3275
6099
  rb_define_method(cCurlEasy, "headers=", ruby_curl_easy_headers_set, 1);
3276
6100
  rb_define_method(cCurlEasy, "headers", ruby_curl_easy_headers_get, 0);
3277
6101
  rb_define_method(cCurlEasy, "interface", ruby_curl_easy_interface_get, 0);
@@ -3298,6 +6122,19 @@ void init_curb_easy() {
3298
6122
  rb_define_method(cCurlEasy, "put_data=", ruby_curl_easy_put_data_set, 1);
3299
6123
  rb_define_method(cCurlEasy, "ftp_commands=", ruby_curl_easy_ftp_commands_set, 1);
3300
6124
  rb_define_method(cCurlEasy, "ftp_commands", ruby_curl_easy_ftp_commands_get, 0);
6125
+ rb_define_method(cCurlEasy, "resolve=", ruby_curl_easy_resolve_set, 1);
6126
+ rb_define_method(cCurlEasy, "resolve", ruby_curl_easy_resolve_get, 0);
6127
+ rb_define_method(cCurlEasy, "connect_to=", ruby_curl_easy_connect_to_set, 1);
6128
+ rb_define_method(cCurlEasy, "connect_to", ruby_curl_easy_connect_to_get, 0);
6129
+ rb_define_method(cCurlEasy, "doh_url=", ruby_curl_easy_doh_url_set, 1);
6130
+ rb_define_method(cCurlEasy, "doh_url", ruby_curl_easy_doh_url_get, 0);
6131
+ #ifdef HAVE_CURLOPT_DNS_SERVERS
6132
+ rb_define_method(cCurlEasy, "dns_servers", ruby_curl_easy_dns_servers_get, 0);
6133
+ #endif
6134
+ rb_define_method(cCurlEasy, "allowed_cidrs=", ruby_curl_easy_allowed_cidrs_set, 1);
6135
+ rb_define_method(cCurlEasy, "allowed_cidrs", ruby_curl_easy_allowed_cidrs_get, 0);
6136
+ rb_define_method(cCurlEasy, "allowed_hosts=", ruby_curl_easy_allowed_hosts_set, 1);
6137
+ rb_define_method(cCurlEasy, "allowed_hosts", ruby_curl_easy_allowed_hosts_get, 0);
3301
6138
 
3302
6139
  rb_define_method(cCurlEasy, "local_port=", ruby_curl_easy_local_port_set, 1);
3303
6140
  rb_define_method(cCurlEasy, "local_port", ruby_curl_easy_local_port_get, 0);
@@ -3315,8 +6152,12 @@ void init_curb_easy() {
3315
6152
  rb_define_method(cCurlEasy, "max_redirects", ruby_curl_easy_max_redirects_get, 0);
3316
6153
  rb_define_method(cCurlEasy, "timeout=", ruby_curl_easy_timeout_set, 1);
3317
6154
  rb_define_method(cCurlEasy, "timeout", ruby_curl_easy_timeout_get, 0);
6155
+ rb_define_method(cCurlEasy, "timeout_ms=", ruby_curl_easy_timeout_ms_set, 1);
6156
+ rb_define_method(cCurlEasy, "timeout_ms", ruby_curl_easy_timeout_ms_get, 0);
3318
6157
  rb_define_method(cCurlEasy, "connect_timeout=", ruby_curl_easy_connect_timeout_set, 1);
3319
6158
  rb_define_method(cCurlEasy, "connect_timeout", ruby_curl_easy_connect_timeout_get, 0);
6159
+ rb_define_method(cCurlEasy, "connect_timeout_ms=", ruby_curl_easy_connect_timeout_ms_set, 1);
6160
+ rb_define_method(cCurlEasy, "connect_timeout_ms", ruby_curl_easy_connect_timeout_ms_get, 0);
3320
6161
  rb_define_method(cCurlEasy, "dns_cache_timeout=", ruby_curl_easy_dns_cache_timeout_set, 1);
3321
6162
  rb_define_method(cCurlEasy, "dns_cache_timeout", ruby_curl_easy_dns_cache_timeout_get, 0);
3322
6163
  rb_define_method(cCurlEasy, "ftp_response_timeout=", ruby_curl_easy_ftp_response_timeout_set, 1);
@@ -3325,6 +6166,10 @@ void init_curb_easy() {
3325
6166
  rb_define_method(cCurlEasy, "low_speed_limit", ruby_curl_easy_low_speed_limit_get, 0);
3326
6167
  rb_define_method(cCurlEasy, "low_speed_time=", ruby_curl_easy_low_speed_time_set, 1);
3327
6168
  rb_define_method(cCurlEasy, "low_speed_time", ruby_curl_easy_low_speed_time_get, 0);
6169
+ rb_define_method(cCurlEasy, "max_send_speed_large=", ruby_curl_easy_max_send_speed_large_set, 1);
6170
+ rb_define_method(cCurlEasy, "max_send_speed_large", ruby_curl_easy_max_send_speed_large_get, 0);
6171
+ rb_define_method(cCurlEasy, "max_recv_speed_large=", ruby_curl_easy_max_recv_speed_large_set, 1);
6172
+ rb_define_method(cCurlEasy, "max_recv_speed_large", ruby_curl_easy_max_recv_speed_large_get, 0);
3328
6173
  rb_define_method(cCurlEasy, "ssl_version=", ruby_curl_easy_ssl_version_set, 1);
3329
6174
  rb_define_method(cCurlEasy, "ssl_version", ruby_curl_easy_ssl_version_get, 0);
3330
6175
  rb_define_method(cCurlEasy, "use_ssl=", ruby_curl_easy_use_ssl_set, 1);
@@ -3363,6 +6208,8 @@ void init_curb_easy() {
3363
6208
  rb_define_method(cCurlEasy, "ignore_content_length?", ruby_curl_easy_ignore_content_length_q, 0);
3364
6209
  rb_define_method(cCurlEasy, "resolve_mode", ruby_curl_easy_resolve_mode, 0);
3365
6210
  rb_define_method(cCurlEasy, "resolve_mode=", ruby_curl_easy_resolve_mode_set, 1);
6211
+ rb_define_method(cCurlEasy, "network_policy", ruby_curl_easy_network_policy_get, 0);
6212
+ rb_define_method(cCurlEasy, "network_policy=", ruby_curl_easy_network_policy_set, 1);
3366
6213
 
3367
6214
  rb_define_method(cCurlEasy, "on_body", ruby_curl_easy_on_body_set, -1);
3368
6215
  rb_define_method(cCurlEasy, "on_header", ruby_curl_easy_on_header_set, -1);
@@ -3376,14 +6223,18 @@ void init_curb_easy() {
3376
6223
 
3377
6224
  rb_define_method(cCurlEasy, "http", ruby_curl_easy_perform_verb, 1);
3378
6225
  rb_define_method(cCurlEasy, "http_post", ruby_curl_easy_perform_post, -1);
3379
- rb_define_method(cCurlEasy, "http_put", ruby_curl_easy_perform_put, 1);
6226
+ rb_define_method(cCurlEasy, "http_put", ruby_curl_easy_perform_put, -1);
6227
+ rb_define_method(cCurlEasy, "http_patch", ruby_curl_easy_perform_patch, -1);
3380
6228
 
3381
6229
  /* Post-perform info methods */
3382
6230
  rb_define_method(cCurlEasy, "body_str", ruby_curl_easy_body_str_get, 0);
6231
+ rb_define_method(cCurlEasy, "max_body_bytes=", ruby_curl_easy_max_body_bytes_set, 1);
6232
+ rb_define_method(cCurlEasy, "max_body_bytes", ruby_curl_easy_max_body_bytes_get, 0);
3383
6233
  rb_define_method(cCurlEasy, "header_str", ruby_curl_easy_header_str_get, 0);
3384
6234
 
3385
6235
  rb_define_method(cCurlEasy, "last_effective_url", ruby_curl_easy_last_effective_url_get, 0);
3386
6236
  rb_define_method(cCurlEasy, "response_code", ruby_curl_easy_response_code_get, 0);
6237
+ rb_define_method(cCurlEasy, "code", ruby_curl_easy_response_code_get, 0);
3387
6238
  #if defined(HAVE_CURLINFO_PRIMARY_IP)
3388
6239
  rb_define_method(cCurlEasy, "primary_ip", ruby_curl_easy_primary_ip_get, 0);
3389
6240
  #endif
@@ -3412,6 +6263,7 @@ void init_curb_easy() {
3412
6263
  rb_define_method(cCurlEasy, "content_type", ruby_curl_easy_content_type_get, 0);
3413
6264
  rb_define_method(cCurlEasy, "os_errno", ruby_curl_easy_os_errno_get, 0);
3414
6265
  rb_define_method(cCurlEasy, "num_connects", ruby_curl_easy_num_connects_get, 0);
6266
+ rb_define_method(cCurlEasy, "cookielist", ruby_curl_easy_cookielist_get, 0);
3415
6267
  rb_define_method(cCurlEasy, "ftp_entry_path", ruby_curl_easy_ftp_entry_path_get, 0);
3416
6268
 
3417
6269
  rb_define_method(cCurlEasy, "close", ruby_curl_easy_close, 0);
@@ -3428,7 +6280,16 @@ void init_curb_easy() {
3428
6280
 
3429
6281
  rb_define_method(cCurlEasy, "multi", ruby_curl_easy_multi_get, 0);
3430
6282
  rb_define_method(cCurlEasy, "multi=", ruby_curl_easy_multi_set, 1);
6283
+ rb_define_private_method(cCurlEasy, "__curb_native_setup!", ruby_curl_easy_setup_self, 0);
6284
+ rb_define_private_method(cCurlEasy, "__curb_allow_proxy=", ruby_curl_easy_allow_proxy_set, 1);
6285
+ rb_define_private_method(cCurlEasy, "__curb_allow_unix_socket=", ruby_curl_easy_allow_unix_socket_set, 1);
6286
+ rb_define_private_method(cCurlEasy, "_take_callback_error", ruby_curl_easy_take_callback_error, 0);
3431
6287
  rb_define_method(cCurlEasy, "last_result", ruby_curl_easy_last_result, 0);
6288
+ rb_define_method(cCurlEasy, "last_error", ruby_curl_easy_last_error, 0);
6289
+ rb_define_method(cCurlEasy, "unsafe_destination_error", ruby_curl_easy_unsafe_destination_error_get, 0);
6290
+ #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
6291
+ rb_define_method(cCurlEasy, "unix_socket_path", ruby_curl_easy_unix_socket_path_get, 0);
6292
+ #endif
3432
6293
 
3433
6294
  rb_define_method(cCurlEasy, "setopt", ruby_curl_easy_set_opt, 2);
3434
6295
  rb_define_method(cCurlEasy, "getinfo", ruby_curl_easy_get_opt, 1);