curb 1.3.5 → 1.3.7

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.
data/ext/curb_easy.c CHANGED
@@ -11,16 +11,36 @@
11
11
  #include "curb_multi.h"
12
12
 
13
13
  #include <errno.h>
14
+ #include <stdlib.h>
14
15
  #include <string.h>
15
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
16
26
  #include <strings.h>
17
27
  #endif
18
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
36
+
19
37
  extern VALUE mCurl;
20
38
 
21
39
  static VALUE idCall;
22
40
  static VALUE idJoin;
23
41
  static VALUE rbstrAmp;
42
+ static ID idNetworkPolicyNone;
43
+ static ID idNetworkPolicyPublic;
24
44
 
25
45
  #ifdef RDOC_NEVER_DEFINED
26
46
  mCurl = rb_define_module("Curl");
@@ -44,9 +64,649 @@ static FILE * rb_io_stdio_file(rb_io_t *fptr) {
44
64
  #endif
45
65
  static struct curl_slist *duplicate_curl_slist(struct curl_slist *list);
46
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);
47
68
 
48
69
  /* ================== CURL HANDLER FUNCS ==============*/
49
70
 
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
+
50
710
  static VALUE callback_exception(VALUE unused, VALUE exception) {
51
711
  return Qfalse;
52
712
  }
@@ -93,6 +753,20 @@ static VALUE with_easy_callback_active(ruby_curl_easy *rbce, VALUE (*func)(VALUE
93
753
  return rb_ensure(func, arg, ensure_clear_easy_callback_active, (VALUE)rbce);
94
754
  }
95
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
+
96
770
  struct stream_read_call_args {
97
771
  VALUE stream;
98
772
  size_t read_bytes;
@@ -170,6 +844,30 @@ static int curl_seek_fail_result(void) {
170
844
  #endif
171
845
  }
172
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
+
173
871
  /* Default body handler appends to easy.body_data buffer */
174
872
  static size_t default_body_handler(char *stream,
175
873
  size_t size,
@@ -178,6 +876,11 @@ static size_t default_body_handler(char *stream,
178
876
  ruby_curl_easy *rbce = (ruby_curl_easy *)userdata;
179
877
  size_t total = size * nmemb;
180
878
  VALUE out = rb_easy_get("body_data");
879
+
880
+ if (ruby_curl_easy_body_limit_exceeded(rbce, total)) {
881
+ return 0;
882
+ }
883
+
181
884
  if (NIL_P(out)) {
182
885
  out = rb_easy_set("body_data", rb_str_buf_new(32768));
183
886
  }
@@ -346,6 +1049,10 @@ static size_t proc_data_handler_body(char *stream,
346
1049
  args.nmemb = nmemb;
347
1050
  args.proc = rb_easy_get("body_proc");
348
1051
 
1052
+ if (ruby_curl_easy_body_limit_exceeded(rbce, size * nmemb)) {
1053
+ return 0;
1054
+ }
1055
+
349
1056
  dispatch_args.rbce = rbce;
350
1057
  dispatch_args.func = call_proc_data_handler_wrapped;
351
1058
  dispatch_args.arg = (VALUE)&args;
@@ -480,6 +1187,7 @@ static int proc_debug_handler(CURL *curl,
480
1187
  static void curl_easy_mark(void *ptr) {
481
1188
  ruby_curl_easy *rbce = (ruby_curl_easy *)ptr;
482
1189
  if (rbce) {
1190
+ if (!NIL_P(rbce->self)) { rb_gc_mark(rbce->self); }
483
1191
  if (!NIL_P(rbce->opts)) { rb_gc_mark(rbce->opts); }
484
1192
  if (!NIL_P(rbce->multi)) { rb_gc_mark(rbce->multi); }
485
1193
  if (!NIL_P(rbce->callback_error)) { rb_gc_mark(rbce->callback_error); }
@@ -534,6 +1242,27 @@ static void ruby_curl_easy_clear_resolve_list(ruby_curl_easy *rbce) {
534
1242
  rbce->curl_resolve = NULL;
535
1243
  }
536
1244
 
1245
+ static void ruby_curl_easy_clear_connect_to_list(ruby_curl_easy *rbce) {
1246
+ if (!rbce || !rbce->curl_connect_to) {
1247
+ return;
1248
+ }
1249
+ #ifdef HAVE_CURLOPT_CONNECT_TO
1250
+ if (rbce->curl) {
1251
+ curl_easy_setopt(rbce->curl, CURLOPT_CONNECT_TO, NULL);
1252
+ }
1253
+ #endif
1254
+ curl_slist_free_all(rbce->curl_connect_to);
1255
+ rbce->curl_connect_to = NULL;
1256
+ }
1257
+
1258
+ static void ruby_curl_easy_clear_setup_lists(ruby_curl_easy *rbce) {
1259
+ ruby_curl_easy_clear_headers_list(rbce);
1260
+ ruby_curl_easy_clear_proxy_headers_list(rbce);
1261
+ ruby_curl_easy_clear_ftp_commands_list(rbce);
1262
+ ruby_curl_easy_clear_resolve_list(rbce);
1263
+ ruby_curl_easy_clear_connect_to_list(rbce);
1264
+ }
1265
+
537
1266
  /* Legacy wrapper for external callers */
538
1267
  void ruby_curl_easy_mark(ruby_curl_easy *rbce) {
539
1268
  curl_easy_mark((void *)rbce);
@@ -590,6 +1319,9 @@ static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
590
1319
  ruby_curl_easy_clear_proxy_headers_list(rbce);
591
1320
  ruby_curl_easy_clear_ftp_commands_list(rbce);
592
1321
  ruby_curl_easy_clear_resolve_list(rbce);
1322
+ ruby_curl_easy_clear_connect_to_list(rbce);
1323
+ curb_clear_network_allowed_cidr_rules(rbce);
1324
+ curb_clear_network_allowed_hosts(rbce);
593
1325
 
594
1326
  if (rbce->curl) {
595
1327
  /* disable any progress or debug events */
@@ -659,12 +1391,18 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
659
1391
  rbce->opts = rb_hash_new();
660
1392
 
661
1393
  memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
1394
+ memset(rbce->unsafe_destination_error, 0, CURL_ERROR_SIZE);
662
1395
 
663
1396
  rbce->self = Qnil;
664
1397
  rbce->curl_headers = NULL;
665
1398
  rbce->curl_proxy_headers = NULL;
666
1399
  rbce->curl_ftp_commands = NULL;
667
1400
  rbce->curl_resolve = NULL;
1401
+ rbce->curl_connect_to = NULL;
1402
+ rbce->network_allowed_cidr_rules = NULL;
1403
+ rbce->network_allowed_hosts = NULL;
1404
+ rbce->network_allowed_cidr_rule_count = 0;
1405
+ rbce->network_allowed_host_count = 0;
668
1406
 
669
1407
  /* various-typed opts */
670
1408
  rbce->local_port = 0;
@@ -689,6 +1427,7 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
689
1427
  rbce->ftp_filemethod = -1;
690
1428
  rbce->http_version = CURL_HTTP_VERSION_NONE;
691
1429
  rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER;
1430
+ rbce->network_policy = CURB_NETWORK_POLICY_NONE;
692
1431
 
693
1432
  /* bool opts */
694
1433
  rbce->proxy_tunnel = 0;
@@ -705,6 +1444,12 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
705
1444
  rbce->cookielist_engine_enabled = 0;
706
1445
  rbce->ignore_content_length = 0;
707
1446
  rbce->callback_active = 0;
1447
+ rbce->unsafe_destination_blocked = 0;
1448
+ rbce->allow_proxy = 0;
1449
+ rbce->allow_unix_socket = 0;
1450
+ rbce->forbid_reuse_set = 0;
1451
+ rbce->native_active = 0;
1452
+ rbce->forbid_reuse = 0;
708
1453
  rbce->callback_error = Qnil;
709
1454
  rbce->last_result = 0;
710
1455
  }
@@ -837,10 +1582,18 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
837
1582
  newrbce->curl_proxy_headers = (rbce->curl_proxy_headers) ? duplicate_curl_slist(rbce->curl_proxy_headers) : NULL;
838
1583
  newrbce->curl_ftp_commands = (rbce->curl_ftp_commands) ? duplicate_curl_slist(rbce->curl_ftp_commands) : NULL;
839
1584
  newrbce->curl_resolve = (rbce->curl_resolve) ? duplicate_curl_slist(rbce->curl_resolve) : NULL;
1585
+ newrbce->curl_connect_to = (rbce->curl_connect_to) ? duplicate_curl_slist(rbce->curl_connect_to) : NULL;
1586
+ newrbce->network_allowed_cidr_rules = NULL;
1587
+ newrbce->network_allowed_cidr_rule_count = 0;
1588
+ newrbce->network_allowed_hosts = NULL;
1589
+ newrbce->network_allowed_host_count = 0;
840
1590
 
841
1591
  /* A cloned easy should not retain ownership reference to the original multi. */
842
1592
  newrbce->multi = Qnil;
843
1593
  newrbce->callback_error = Qnil;
1594
+ newrbce->unsafe_destination_blocked = 0;
1595
+ memset(newrbce->unsafe_destination_error, 0, CURL_ERROR_SIZE);
1596
+ newrbce->native_active = 0;
844
1597
 
845
1598
  if (rbce->opts != Qnil) {
846
1599
  newrbce->opts = rb_funcall(rbce->opts, rb_intern("dup"), 0);
@@ -889,6 +1642,10 @@ static VALUE ruby_curl_easy_close(VALUE self) {
889
1642
  rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback");
890
1643
  }
891
1644
 
1645
+ if (rbce->native_active) {
1646
+ rb_raise(rb_eRuntimeError, "Cannot close an active curl handle during native operation");
1647
+ }
1648
+
892
1649
  ruby_curl_easy_free(rbce);
893
1650
 
894
1651
  /* reinit the handle */
@@ -935,6 +1692,10 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
935
1692
  rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback");
936
1693
  }
937
1694
 
1695
+ if (rbce->native_active) {
1696
+ rb_raise(rb_eRuntimeError, "Cannot reset an active curl handle during native operation");
1697
+ }
1698
+
938
1699
  opts_dup = rb_funcall(rbce->opts, rb_intern("dup"), 0);
939
1700
 
940
1701
  ruby_curl_easy_cleanup(self, rbce);
@@ -1281,7 +2042,17 @@ static VALUE ruby_curl_easy_useragent_get(VALUE self) {
1281
2042
  *
1282
2043
  * This is handy if you want to perform a POST against a Curl::Multi instance.
1283
2044
  */
1284
- static VALUE ruby_curl_easy_post_body_set_with_mode(VALUE self, VALUE post_body, int force_http_get_on_nil) {
2045
+ struct post_body_set_args {
2046
+ VALUE self;
2047
+ VALUE post_body;
2048
+ int force_http_get_on_nil;
2049
+ };
2050
+
2051
+ static VALUE ruby_curl_easy_post_body_set_with_mode_body(VALUE argp) {
2052
+ struct post_body_set_args *args = (struct post_body_set_args *)argp;
2053
+ VALUE self = args->self;
2054
+ VALUE post_body = args->post_body;
2055
+ int force_http_get_on_nil = args->force_http_get_on_nil;
1285
2056
  ruby_curl_easy *rbce;
1286
2057
  CURL *curl;
1287
2058
 
@@ -1345,6 +2116,17 @@ static VALUE ruby_curl_easy_post_body_set_with_mode(VALUE self, VALUE post_body,
1345
2116
  return Qnil;
1346
2117
  }
1347
2118
 
2119
+ static VALUE ruby_curl_easy_post_body_set_with_mode(VALUE self, VALUE post_body, int force_http_get_on_nil) {
2120
+ ruby_curl_easy *rbce;
2121
+ struct post_body_set_args args = { self, post_body, force_http_get_on_nil };
2122
+
2123
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2124
+ ruby_curl_easy_enter_native(rbce);
2125
+
2126
+ return rb_ensure(ruby_curl_easy_post_body_set_with_mode_body, (VALUE)&args,
2127
+ ruby_curl_easy_leave_native, (VALUE)rbce);
2128
+ }
2129
+
1348
2130
  static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
1349
2131
  return ruby_curl_easy_post_body_set_with_mode(self, post_body, 1);
1350
2132
  }
@@ -1510,6 +2292,147 @@ static VALUE ruby_curl_easy_resolve_get(VALUE self) {
1510
2292
  CURB_OBJECT_HGETTER(ruby_curl_easy, resolve);
1511
2293
  }
1512
2294
 
2295
+ /*
2296
+ * call-seq:
2297
+ * easy.connect_to = [ "example.com:80:127.0.0.1:80" ] => [ "example.com:80:127.0.0.1:80" ]
2298
+ *
2299
+ * Set the connect-to list to redirect matching request host/port pairs to
2300
+ * alternate connection host/port pairs.
2301
+ */
2302
+ static VALUE ruby_curl_easy_connect_to_set(VALUE self, VALUE connect_to) {
2303
+ CURB_OBJECT_HSETTER(ruby_curl_easy, connect_to);
2304
+ }
2305
+
2306
+ /*
2307
+ * call-seq:
2308
+ * easy.connect_to => array or nil
2309
+ */
2310
+ static VALUE ruby_curl_easy_connect_to_get(VALUE self) {
2311
+ CURB_OBJECT_HGETTER(ruby_curl_easy, connect_to);
2312
+ }
2313
+
2314
+ /*
2315
+ * call-seq:
2316
+ * easy.doh_url = "https://dns.example/dns-query" => "https://dns.example/dns-query"
2317
+ *
2318
+ * Set the DNS-over-HTTPS URL to use for resolving hostnames.
2319
+ */
2320
+ static VALUE ruby_curl_easy_doh_url_set(VALUE self, VALUE doh_url) {
2321
+ CURB_OBJECT_HSETTER(ruby_curl_easy, doh_url);
2322
+ }
2323
+
2324
+ /*
2325
+ * call-seq:
2326
+ * easy.doh_url => string or nil
2327
+ */
2328
+ static VALUE ruby_curl_easy_doh_url_get(VALUE self) {
2329
+ CURB_OBJECT_HGETTER(ruby_curl_easy, doh_url);
2330
+ }
2331
+
2332
+ #ifdef HAVE_CURLOPT_DNS_SERVERS
2333
+ /*
2334
+ * call-seq:
2335
+ * easy.dns_servers => string or nil
2336
+ */
2337
+ static VALUE ruby_curl_easy_dns_servers_get(VALUE self) {
2338
+ CURB_OBJECT_HGETTER(ruby_curl_easy, dns_servers);
2339
+ }
2340
+ #endif
2341
+
2342
+ static VALUE ruby_curl_easy_allow_unix_socket_set(VALUE self, VALUE allow) {
2343
+ ruby_curl_easy *rbce;
2344
+
2345
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2346
+ rbce->allow_unix_socket = RTEST(allow) ? 1 : 0;
2347
+
2348
+ return allow;
2349
+ }
2350
+
2351
+ static VALUE ruby_curl_easy_allow_proxy_set(VALUE self, VALUE allow) {
2352
+ ruby_curl_easy *rbce;
2353
+
2354
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2355
+ rbce->allow_proxy = RTEST(allow) ? 1 : 0;
2356
+
2357
+ return allow;
2358
+ }
2359
+
2360
+ /*
2361
+ * call-seq:
2362
+ * easy.allowed_cidrs = ["203.0.113.0/24"] => ["203.0.113.0/24"]
2363
+ *
2364
+ * Set resolved-peer CIDR ranges that are allowed when network_policy is :public.
2365
+ * Private/local unsafe ranges are still blocked before this allowlist is
2366
+ * evaluated.
2367
+ */
2368
+ static VALUE ruby_curl_easy_allowed_cidrs_set(VALUE self, VALUE cidrs) {
2369
+ ruby_curl_easy *rbce;
2370
+ VALUE normalized;
2371
+ VALUE stored;
2372
+
2373
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2374
+ normalized = curb_normalize_cidr_list(cidrs);
2375
+ stored = curb_dup_string_array(normalized);
2376
+ rb_hash_aset(rbce->opts, rb_easy_hkey("allowed_cidrs"), stored);
2377
+ if (rbce->network_policy == CURB_NETWORK_POLICY_PUBLIC) {
2378
+ curb_prepare_network_allowed_cidr_rules(rbce);
2379
+ } else {
2380
+ curb_clear_network_allowed_cidr_rules(rbce);
2381
+ }
2382
+
2383
+ return curb_dup_string_array(stored);
2384
+ }
2385
+
2386
+ /*
2387
+ * call-seq:
2388
+ * easy.allowed_cidrs => array or nil
2389
+ */
2390
+ static VALUE ruby_curl_easy_allowed_cidrs_get(VALUE self) {
2391
+ ruby_curl_easy *rbce;
2392
+ VALUE cidrs;
2393
+
2394
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2395
+ cidrs = rb_hash_aref(rbce->opts, rb_easy_hkey("allowed_cidrs"));
2396
+
2397
+ return curb_dup_string_array(cidrs);
2398
+ }
2399
+
2400
+ /*
2401
+ * call-seq:
2402
+ * easy.allowed_hosts = ["api.example.com"] => ["api.example.com"]
2403
+ *
2404
+ * Set URL hosts allowed for this handle. When libcurl supports
2405
+ * CURLOPT_PREREQFUNCTION, this is checked before each request, including
2406
+ * followed redirects.
2407
+ */
2408
+ static VALUE ruby_curl_easy_allowed_hosts_set(VALUE self, VALUE hosts) {
2409
+ ruby_curl_easy *rbce;
2410
+ VALUE normalized;
2411
+ VALUE stored;
2412
+
2413
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2414
+ normalized = curb_normalize_host_list(hosts);
2415
+ stored = curb_dup_string_array(normalized);
2416
+ rb_hash_aset(rbce->opts, rb_easy_hkey("allowed_hosts"), stored);
2417
+ curb_prepare_network_allowed_hosts(rbce);
2418
+
2419
+ return curb_dup_string_array(stored);
2420
+ }
2421
+
2422
+ /*
2423
+ * call-seq:
2424
+ * easy.allowed_hosts => array or nil
2425
+ */
2426
+ static VALUE ruby_curl_easy_allowed_hosts_get(VALUE self) {
2427
+ ruby_curl_easy *rbce;
2428
+ VALUE hosts;
2429
+
2430
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2431
+ hosts = rb_hash_aref(rbce->opts, rb_easy_hkey("allowed_hosts"));
2432
+
2433
+ return curb_dup_string_array(hosts);
2434
+ }
2435
+
1513
2436
  /* ================== IMMED ATTRS ==================*/
1514
2437
 
1515
2438
  /*
@@ -2458,41 +3381,136 @@ static VALUE ruby_curl_easy_resolve_mode(VALUE self) {
2458
3381
 
2459
3382
  /*
2460
3383
  * call-seq:
2461
- * easy.resolve_mode = symbol => symbol
2462
- *
2463
- * Configures what type of IP address this Curl::Easy instance
2464
- * resolves DNS names to. Valid options are:
3384
+ * easy.resolve_mode = symbol => symbol
3385
+ *
3386
+ * Configures what type of IP address this Curl::Easy instance
3387
+ * resolves DNS names to. Valid options are:
3388
+ *
3389
+ * [:auto] resolves DNS names to all IP versions your system allows
3390
+ * [:ipv4] resolves DNS names to IPv4 only
3391
+ * [:ipv6] resolves DNS names to IPv6 only
3392
+ */
3393
+ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
3394
+ if (TYPE(resolve_mode) != T_SYMBOL) {
3395
+ rb_raise(rb_eTypeError, "Must pass a symbol");
3396
+ return Qnil;
3397
+ } else {
3398
+ ruby_curl_easy *rbce;
3399
+ ID resolve_mode_id;
3400
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3401
+
3402
+ resolve_mode_id = rb_to_id(resolve_mode);
3403
+
3404
+ if (resolve_mode_id == rb_intern("auto")) {
3405
+ rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER;
3406
+ return resolve_mode;
3407
+ } else if (resolve_mode_id == rb_intern("ipv4")) {
3408
+ rbce->resolve_mode = CURL_IPRESOLVE_V4;
3409
+ return resolve_mode;
3410
+ } else if (resolve_mode_id == rb_intern("ipv6")) {
3411
+ rbce->resolve_mode = CURL_IPRESOLVE_V6;
3412
+ return resolve_mode;
3413
+ } else {
3414
+ rb_raise(rb_eArgError, "Must set to one of :auto, :ipv4, :ipv6");
3415
+ return Qnil;
3416
+ }
3417
+ }
3418
+ }
3419
+
3420
+ /*
3421
+ * call-seq:
3422
+ * easy.network_policy => symbol
3423
+ *
3424
+ * Determine which native network policy will be enforced when libcurl opens
3425
+ * sockets for this handle.
3426
+ */
3427
+ static VALUE ruby_curl_easy_network_policy_get(VALUE self) {
3428
+ ruby_curl_easy *rbce;
3429
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3430
+
3431
+ switch (rbce->network_policy) {
3432
+ case CURB_NETWORK_POLICY_PUBLIC:
3433
+ return ID2SYM(idNetworkPolicyPublic);
3434
+ case CURB_NETWORK_POLICY_NONE:
3435
+ default:
3436
+ return ID2SYM(idNetworkPolicyNone);
3437
+ }
3438
+ }
3439
+
3440
+ /*
3441
+ * call-seq:
3442
+ * easy.network_policy = symbol => symbol
3443
+ *
3444
+ * Supported values:
3445
+ * [:none] disables native destination checks.
3446
+ * [:public] blocks loopback, private, link-local, multicast,
3447
+ * unspecified, metadata, and other non-public addresses.
3448
+ */
3449
+ static VALUE ruby_curl_easy_network_policy_set(VALUE self, VALUE network_policy) {
3450
+ ruby_curl_easy *rbce;
3451
+ ID network_policy_id;
3452
+
3453
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3454
+
3455
+ if (NIL_P(network_policy)) {
3456
+ rbce->network_policy = CURB_NETWORK_POLICY_NONE;
3457
+ return ID2SYM(idNetworkPolicyNone);
3458
+ }
3459
+
3460
+ if (TYPE(network_policy) != T_SYMBOL) {
3461
+ rb_raise(rb_eTypeError, "network_policy must be a Symbol");
3462
+ }
3463
+
3464
+ network_policy_id = rb_to_id(network_policy);
3465
+
3466
+ if (network_policy_id == idNetworkPolicyNone) {
3467
+ rbce->network_policy = CURB_NETWORK_POLICY_NONE;
3468
+ rbce->allow_proxy = 0;
3469
+ return network_policy;
3470
+ } else if (network_policy_id == idNetworkPolicyPublic) {
3471
+ #ifndef CURB_HAVE_OPENSOCKET_NETWORK_POLICY
3472
+ rb_raise(rb_eNotImpError, "network_policy=:public requires CURLOPT_OPENSOCKETFUNCTION support");
3473
+ #else
3474
+ rbce->network_policy = CURB_NETWORK_POLICY_PUBLIC;
3475
+ rbce->allow_proxy = 0;
3476
+ return network_policy;
3477
+ #endif
3478
+ }
3479
+
3480
+ rb_raise(rb_eArgError, "network_policy must be one of :none, :public");
3481
+ }
3482
+
3483
+ /*
3484
+ * call-seq:
3485
+ * easy.unsafe_destination_error => string or nil
3486
+ *
3487
+ * Return the native network policy block reason from the most recent transfer.
3488
+ */
3489
+ static VALUE ruby_curl_easy_unsafe_destination_error_get(VALUE self) {
3490
+ ruby_curl_easy *rbce;
3491
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3492
+
3493
+ if (rbce->unsafe_destination_blocked && rbce->unsafe_destination_error[0]) {
3494
+ return rb_str_new2(rbce->unsafe_destination_error);
3495
+ }
3496
+
3497
+ return Qnil;
3498
+ }
3499
+
3500
+ #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
3501
+ /*
3502
+ * call-seq:
3503
+ * easy.unix_socket_path => string or nil
2465
3504
  *
2466
- * [:auto] resolves DNS names to all IP versions your system allows
2467
- * [:ipv4] resolves DNS names to IPv4 only
2468
- * [:ipv6] resolves DNS names to IPv6 only
3505
+ * Return the configured Unix socket path, if set through CURLOPT_UNIX_SOCKET_PATH.
2469
3506
  */
2470
- static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
2471
- if (TYPE(resolve_mode) != T_SYMBOL) {
2472
- rb_raise(rb_eTypeError, "Must pass a symbol");
2473
- return Qnil;
2474
- } else {
2475
- ruby_curl_easy *rbce;
2476
- ID resolve_mode_id;
2477
- TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2478
-
2479
- resolve_mode_id = rb_to_id(resolve_mode);
3507
+ static VALUE ruby_curl_easy_unix_socket_path_get(VALUE self) {
3508
+ ruby_curl_easy *rbce;
3509
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2480
3510
 
2481
- if (resolve_mode_id == rb_intern("auto")) {
2482
- rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER;
2483
- return resolve_mode;
2484
- } else if (resolve_mode_id == rb_intern("ipv4")) {
2485
- rbce->resolve_mode = CURL_IPRESOLVE_V4;
2486
- return resolve_mode;
2487
- } else if (resolve_mode_id == rb_intern("ipv6")) {
2488
- rbce->resolve_mode = CURL_IPRESOLVE_V6;
2489
- return resolve_mode;
2490
- } else {
2491
- rb_raise(rb_eArgError, "Must set to one of :auto, :ipv4, :ipv6");
2492
- return Qnil;
2493
- }
2494
- }
3511
+ return rb_easy_get("unix_socket_path");
2495
3512
  }
3513
+ #endif
2496
3514
 
2497
3515
  /*
2498
3516
  * call-seq:
@@ -2802,13 +3820,32 @@ static VALUE cb_each_resolve(VALUE resolve, VALUE wrap, int _c, const VALUE *_pt
2802
3820
  return resolve_string;
2803
3821
  }
2804
3822
 
3823
+ /***********************************************
3824
+ * This is an rb_iterate callback used to set up the connect-to list.
3825
+ */
3826
+ static VALUE cb_each_connect_to(VALUE connect_to, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
3827
+ struct curl_slist **list;
3828
+ VALUE connect_to_string;
3829
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
3830
+
3831
+ connect_to_string = rb_obj_as_string(connect_to);
3832
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(connect_to_string));
3833
+ if (!new_list) {
3834
+ rb_raise(rb_eNoMemError, "Failed to append to connect-to list");
3835
+ }
3836
+ *list = new_list;
3837
+
3838
+ return connect_to_string;
3839
+ }
3840
+
2805
3841
  /***********************************************
2806
3842
  *
2807
3843
  * Setup a connection
2808
3844
  *
2809
3845
  * Always returns Qtrue, rb_raise on error.
2810
3846
  */
2811
- VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
3847
+ static VALUE ruby_curl_easy_setup_body(VALUE arg) {
3848
+ ruby_curl_easy *rbce = (ruby_curl_easy *)arg;
2812
3849
  // TODO this could do with a bit of refactoring...
2813
3850
  CURL *curl;
2814
3851
  VALUE url, _url = rb_easy_get("url");
@@ -2816,9 +3853,14 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2816
3853
  struct curl_slist **phdrs = &(rbce->curl_proxy_headers);
2817
3854
  struct curl_slist **cmds = &(rbce->curl_ftp_commands);
2818
3855
  struct curl_slist **rslv = &(rbce->curl_resolve);
3856
+ struct curl_slist **cnto = &(rbce->curl_connect_to);
3857
+ int public_policy_disables_proxy;
2819
3858
 
2820
3859
  curl = rbce->curl;
3860
+ public_policy_disables_proxy = rbce->network_policy == CURB_NETWORK_POLICY_PUBLIC && !rbce->allow_proxy;
2821
3861
  rbce->callback_error = Qnil;
3862
+ rbce->unsafe_destination_blocked = 0;
3863
+ memset(rbce->unsafe_destination_error, 0, CURL_ERROR_SIZE);
2822
3864
 
2823
3865
  if (_url == Qnil) {
2824
3866
  rb_raise(eCurlErrError, "No URL supplied");
@@ -2827,6 +3869,60 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2827
3869
  url = rb_check_string_type(_url);
2828
3870
  curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
2829
3871
 
3872
+ #ifdef HAVE_CURLOPT_DOH_URL
3873
+ curl_easy_setopt(curl, CURLOPT_DOH_URL, rb_easy_nil("doh_url") ? NULL : rb_easy_get_str("doh_url"));
3874
+ #endif
3875
+
3876
+ #ifdef HAVE_CURLOPT_DNS_SERVERS
3877
+ if (rbce->network_policy == CURB_NETWORK_POLICY_PUBLIC && !rb_easy_nil("dns_servers")) {
3878
+ rb_raise(eCurlErrUnsafeDestination, "DNS server overrides are disabled by public network policy");
3879
+ }
3880
+ curl_easy_setopt(curl, CURLOPT_DNS_SERVERS, rb_easy_nil("dns_servers") ? NULL : rb_easy_get_str("dns_servers"));
3881
+ #endif
3882
+
3883
+ #ifdef CURB_HAVE_PREREQ_HOST_POLICY
3884
+ if (!rb_easy_nil("allowed_hosts")) {
3885
+ curb_prepare_network_allowed_hosts(rbce);
3886
+ curl_easy_setopt(curl, CURLOPT_PREREQFUNCTION, curb_host_allowlist_prereq);
3887
+ curl_easy_setopt(curl, CURLOPT_PREREQDATA, rbce);
3888
+ } else {
3889
+ curb_clear_network_allowed_hosts(rbce);
3890
+ curl_easy_setopt(curl, CURLOPT_PREREQFUNCTION, NULL);
3891
+ curl_easy_setopt(curl, CURLOPT_PREREQDATA, NULL);
3892
+ }
3893
+ #else
3894
+ curb_clear_network_allowed_hosts(rbce);
3895
+ #endif
3896
+
3897
+ #ifdef CURB_HAVE_OPENSOCKET_NETWORK_POLICY
3898
+ if (rbce->network_policy == CURB_NETWORK_POLICY_PUBLIC) {
3899
+ curb_prepare_network_allowed_cidr_rules(rbce);
3900
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, curb_public_network_opensocket);
3901
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, rbce);
3902
+ #ifdef HAVE_CURLOPT_FRESH_CONNECT
3903
+ curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1L);
3904
+ #endif
3905
+ #ifdef HAVE_CURLOPT_FORBID_REUSE
3906
+ curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1L);
3907
+ #endif
3908
+ #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
3909
+ if (!rbce->allow_unix_socket) {
3910
+ curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, NULL);
3911
+ }
3912
+ #endif
3913
+ } else {
3914
+ curb_clear_network_allowed_cidr_rules(rbce);
3915
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, NULL);
3916
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, NULL);
3917
+ #ifdef HAVE_CURLOPT_FRESH_CONNECT
3918
+ curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 0L);
3919
+ #endif
3920
+ #ifdef HAVE_CURLOPT_FORBID_REUSE
3921
+ curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, rbce->forbid_reuse_set ? rbce->forbid_reuse : 0L);
3922
+ #endif
3923
+ }
3924
+ #endif
3925
+
2830
3926
  // network stuff and auth
2831
3927
  if (!rb_easy_nil("interface_hm")) {
2832
3928
  curl_easy_setopt(curl, CURLOPT_INTERFACE, rb_easy_get_str("interface_hm"));
@@ -2858,13 +3954,17 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2858
3954
  curl_easy_setopt(curl, CURLOPT_USERPWD, NULL);
2859
3955
  }
2860
3956
 
2861
- if (rb_easy_nil("proxy_url")) {
3957
+ if (public_policy_disables_proxy) {
3958
+ curl_easy_setopt(curl, CURLOPT_PROXY, "");
3959
+ } else if (rb_easy_nil("proxy_url")) {
2862
3960
  curl_easy_setopt(curl, CURLOPT_PROXY, NULL);
2863
3961
  } else {
2864
3962
  curl_easy_setopt(curl, CURLOPT_PROXY, rb_easy_get_str("proxy_url"));
2865
3963
  }
2866
3964
 
2867
- if (rb_easy_nil("proxypwd")) {
3965
+ if (public_policy_disables_proxy) {
3966
+ curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, NULL);
3967
+ } else if (rb_easy_nil("proxypwd")) {
2868
3968
  curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, NULL);
2869
3969
  } else {
2870
3970
  curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, rb_easy_get_str("proxypwd"));
@@ -2879,6 +3979,8 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2879
3979
  #endif
2880
3980
 
2881
3981
  // body/header procs
3982
+ rbce->downloaded_body_bytes = 0;
3983
+
2882
3984
  if (!rb_easy_nil("body_proc")) {
2883
3985
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&proc_data_handler_body);
2884
3986
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, rbce);
@@ -2949,7 +4051,8 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2949
4051
  curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs);
2950
4052
  #endif
2951
4053
 
2952
- curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, rbce->proxy_tunnel);
4054
+ curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL,
4055
+ public_policy_disables_proxy ? 0L : rbce->proxy_tunnel);
2953
4056
  curl_easy_setopt(curl, CURLOPT_FILETIME, rbce->fetch_file_time);
2954
4057
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, rbce->ssl_verify_peer);
2955
4058
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, rbce->ssl_verify_host);
@@ -3112,6 +4215,11 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
3112
4215
  curl_easy_setopt(curl, CURLOPT_USERAGENT, rb_easy_get_str("useragent"));
3113
4216
  }
3114
4217
 
4218
+ /* Setup can be rerun for safety-policy changes while a handle is attached.
4219
+ * Rebuild slist-backed options from Ruby opts instead of appending to stale
4220
+ * native lists. */
4221
+ ruby_curl_easy_clear_setup_lists(rbce);
4222
+
3115
4223
  /* Setup HTTP headers if necessary */
3116
4224
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL); // XXX: maybe we shouldn't be clearing this?
3117
4225
 
@@ -3189,8 +4297,44 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
3189
4297
  }
3190
4298
  #endif
3191
4299
 
4300
+ #ifdef HAVE_CURLOPT_CONNECT_TO
4301
+ /* Setup connect-to list if necessary */
4302
+ if (!rb_easy_nil("connect_to")) {
4303
+ if (rb_easy_type_check("connect_to", T_ARRAY)) {
4304
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, cnto);
4305
+ rb_block_call(rb_easy_get("connect_to"), rb_intern("each"), 0, NULL, cb_each_connect_to, wrap);
4306
+ } else {
4307
+ VALUE connect_to_str = rb_obj_as_string(rb_easy_get("connect_to"));
4308
+ struct curl_slist *new_list = curl_slist_append(*cnto, StringValuePtr(connect_to_str));
4309
+ if (!new_list) {
4310
+ rb_raise(rb_eNoMemError, "Failed to append to connect-to list");
4311
+ }
4312
+ *cnto = new_list;
4313
+ }
4314
+
4315
+ if (*cnto) {
4316
+ curl_easy_setopt(curl, CURLOPT_CONNECT_TO, *cnto);
4317
+ }
4318
+ }
4319
+ #endif
4320
+
3192
4321
  return Qnil;
3193
4322
  }
4323
+
4324
+ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
4325
+ ruby_curl_easy_enter_native(rbce);
4326
+ return rb_ensure(ruby_curl_easy_setup_body, (VALUE)rbce,
4327
+ ruby_curl_easy_leave_native, (VALUE)rbce);
4328
+ }
4329
+
4330
+ static VALUE ruby_curl_easy_setup_self(VALUE self) {
4331
+ ruby_curl_easy *rbce;
4332
+
4333
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4334
+ ruby_curl_easy_setup(rbce);
4335
+
4336
+ return self;
4337
+ }
3194
4338
  /***********************************************
3195
4339
  *
3196
4340
  * Clean up a connection
@@ -3202,6 +4346,7 @@ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
3202
4346
  CURL *curl = rbce->curl;
3203
4347
  struct curl_slist *ftp_commands;
3204
4348
  struct curl_slist *resolve;
4349
+ struct curl_slist *connect_to;
3205
4350
 
3206
4351
  ruby_curl_easy_clear_headers_list(rbce);
3207
4352
  ruby_curl_easy_clear_proxy_headers_list(rbce);
@@ -3216,6 +4361,14 @@ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
3216
4361
  ruby_curl_easy_clear_resolve_list(rbce);
3217
4362
  }
3218
4363
 
4364
+ connect_to = rbce->curl_connect_to;
4365
+ if (connect_to) {
4366
+ ruby_curl_easy_clear_connect_to_list(rbce);
4367
+ }
4368
+
4369
+ curb_clear_network_allowed_cidr_rules(rbce);
4370
+ curb_clear_network_allowed_hosts(rbce);
4371
+
3219
4372
  /* clean up a PUT request's curl options. */
3220
4373
  if (!rb_easy_nil("upload")) {
3221
4374
  rb_easy_del("upload"); // set the upload object to Qnil to let the GC clean up
@@ -3350,6 +4503,23 @@ struct easy_form_perform_args {
3350
4503
  int form_set_on_curl;
3351
4504
  };
3352
4505
 
4506
+ struct easy_join_args {
4507
+ VALUE args_ary;
4508
+ };
4509
+
4510
+ static VALUE join_easy_arguments_body(VALUE argp) {
4511
+ struct easy_join_args *args = (struct easy_join_args *)argp;
4512
+ return rb_funcall(args->args_ary, idJoin, 1, rbstrAmp);
4513
+ }
4514
+
4515
+ static VALUE join_easy_arguments(ruby_curl_easy *rbce, VALUE args_ary) {
4516
+ struct easy_join_args args = { args_ary };
4517
+
4518
+ ruby_curl_easy_enter_native(rbce);
4519
+ return rb_ensure(join_easy_arguments_body, (VALUE)&args,
4520
+ ruby_curl_easy_leave_native, (VALUE)rbce);
4521
+ }
4522
+
3353
4523
  static void append_multipart_form_argument(VALUE arg,
3354
4524
  struct curl_httppost **first,
3355
4525
  struct curl_httppost **last) {
@@ -3452,7 +4622,7 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
3452
4622
  } else {
3453
4623
  VALUE post_body = Qnil;
3454
4624
  /* TODO: check for PostField.file and raise error before to_s fails */
3455
- if ((post_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp)) == Qnil) {
4625
+ if ((post_body = join_easy_arguments(rbce, args_ary)) == Qnil) {
3456
4626
  rb_raise(eCurlErrError, "Failed to join arguments");
3457
4627
  return Qnil;
3458
4628
  } else {
@@ -3511,7 +4681,7 @@ static VALUE ruby_curl_easy_perform_patch(int argc, VALUE *argv, VALUE self) {
3511
4681
  return ret;
3512
4682
  } else {
3513
4683
  /* Join arguments into a raw PATCH body */
3514
- VALUE patch_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp);
4684
+ VALUE patch_body = join_easy_arguments(rbce, args_ary);
3515
4685
  if (patch_body == Qnil) {
3516
4686
  rb_raise(eCurlErrError, "Failed to join arguments");
3517
4687
  return Qnil;
@@ -3570,7 +4740,7 @@ static VALUE ruby_curl_easy_perform_put(int argc, VALUE *argv, VALUE self) {
3570
4740
  }
3571
4741
  /* Fallback: join all arguments */
3572
4742
  else {
3573
- VALUE post_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp);
4743
+ VALUE post_body = join_easy_arguments(rbce, args_ary);
3574
4744
  if (post_body != Qnil && rb_type(post_body) == T_STRING &&
3575
4745
  RSTRING_LEN(post_body) > 0) {
3576
4746
  ruby_curl_easy_put_data_set(self, post_body);
@@ -3600,6 +4770,47 @@ static VALUE ruby_curl_easy_body_str_get(VALUE self) {
3600
4770
  CURB_OBJECT_HGETTER(ruby_curl_easy, body_data);
3601
4771
  }
3602
4772
 
4773
+ /*
4774
+ * call-seq:
4775
+ * easy.max_body_bytes = bytes_or_nil => bytes_or_nil
4776
+ *
4777
+ * Set an application-level cap for bytes accepted by the body write callback.
4778
+ * +nil+ or 0 clears the cap.
4779
+ */
4780
+ static VALUE ruby_curl_easy_max_body_bytes_set(VALUE self, VALUE val) {
4781
+ ruby_curl_easy *rbce;
4782
+ long long limit;
4783
+
4784
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4785
+
4786
+ if (NIL_P(val)) {
4787
+ rb_hash_delete(rbce->opts, rb_easy_hkey("max_body_bytes"));
4788
+ return Qnil;
4789
+ }
4790
+
4791
+ limit = NUM2LL(val);
4792
+ if (limit < 0) {
4793
+ rb_raise(rb_eArgError, "max_body_bytes must be greater than or equal to zero");
4794
+ }
4795
+
4796
+ if (limit == 0) {
4797
+ rb_hash_delete(rbce->opts, rb_easy_hkey("max_body_bytes"));
4798
+ } else {
4799
+ val = LL2NUM(limit);
4800
+ rb_hash_aset(rbce->opts, rb_easy_hkey("max_body_bytes"), val);
4801
+ }
4802
+
4803
+ return val;
4804
+ }
4805
+
4806
+ /*
4807
+ * call-seq:
4808
+ * easy.max_body_bytes => bytes_or_nil
4809
+ */
4810
+ static VALUE ruby_curl_easy_max_body_bytes_get(VALUE self) {
4811
+ CURB_OBJECT_HGETTER(ruby_curl_easy, max_body_bytes);
4812
+ }
4813
+
3603
4814
  /*
3604
4815
  * call-seq:
3605
4816
  * easy.header_str => "response header"
@@ -4307,12 +5518,28 @@ static VALUE ruby_curl_easy_multi_get(VALUE self) {
4307
5518
  */
4308
5519
  static VALUE ruby_curl_easy_multi_set(VALUE self, VALUE multi) {
4309
5520
  ruby_curl_easy *rbce;
5521
+ VALUE old_multi;
5522
+ ruby_curl_multi *old_rbcm;
5523
+ CURLMcode result;
4310
5524
  TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4311
5525
 
4312
5526
  if (!NIL_P(multi) && rb_obj_is_kind_of(multi, cCurlMulti) != Qtrue) {
4313
5527
  rb_raise(rb_eTypeError, "expected Curl::Multi or nil");
4314
5528
  }
4315
5529
 
5530
+ old_multi = rbce->multi;
5531
+ if (old_multi == multi) {
5532
+ return rbce->multi;
5533
+ }
5534
+
5535
+ old_rbcm = ruby_curl_multi_pointer_if_compatible(old_multi);
5536
+ if (old_rbcm) {
5537
+ result = rb_curl_multi_detach_easy(old_rbcm, rbce);
5538
+ if (result != CURLM_OK) {
5539
+ raise_curl_multi_error_exception(result);
5540
+ }
5541
+ }
5542
+
4316
5543
  rbce->multi = multi;
4317
5544
  return rbce->multi;
4318
5545
  }
@@ -4355,6 +5582,17 @@ static VALUE ruby_curl_easy_last_error(VALUE self) {
4355
5582
  * @note Some options - like url or cookie - aren't set directly throught +curl_easy_setopt+, but stored in the Ruby object state.
4356
5583
  * @note When +curl_easy_setopt+ is called, return value is not checked here.
4357
5584
  */
5585
+ static long ruby_curl_easy_option_to_long(VALUE value) {
5586
+ if (value == Qtrue) {
5587
+ return 1L;
5588
+ }
5589
+ if (value == Qfalse) {
5590
+ return 0L;
5591
+ }
5592
+
5593
+ return NUM2LONG(rb_funcall(value, rb_intern("to_i"), 0));
5594
+ }
5595
+
4358
5596
  static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4359
5597
  ruby_curl_easy *rbce;
4360
5598
  long option = NUM2LONG(opt);
@@ -4457,6 +5695,12 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4457
5695
  /* Forward request-target directly to libcurl as a string. */
4458
5696
  curl_easy_setopt(rbce->curl, CURLOPT_REQUEST_TARGET, NIL_P(val) ? NULL : StringValueCStr(val));
4459
5697
  } break;
5698
+ #endif
5699
+ #ifdef HAVE_CURLOPT_DOH_URL
5700
+ case CURLOPT_DOH_URL: {
5701
+ rb_hash_aset(rbce->opts, rb_easy_hkey("doh_url"), val);
5702
+ curl_easy_setopt(rbce->curl, CURLOPT_DOH_URL, NIL_P(val) ? NULL : StringValueCStr(val));
5703
+ } break;
4460
5704
  #endif
4461
5705
  case CURLOPT_TCP_NODELAY: {
4462
5706
  curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val));
@@ -4464,59 +5708,76 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4464
5708
  /* FTP-specific toggles */
4465
5709
  #ifdef HAVE_CURLOPT_DIRLISTONLY
4466
5710
  case CURLOPT_DIRLISTONLY: {
4467
- int type = rb_type(val);
4468
- VALUE value;
4469
- if (type == T_TRUE) {
4470
- value = rb_int_new(1);
4471
- } else if (type == T_FALSE) {
4472
- value = rb_int_new(0);
4473
- } else {
4474
- value = rb_funcall(val, rb_intern("to_i"), 0);
4475
- }
4476
- curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, NUM2LONG(value));
5711
+ curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, ruby_curl_easy_option_to_long(val));
4477
5712
  } break;
4478
5713
  #endif
5714
+ #ifdef HAVE_CURLOPT_FTPPORT
5715
+ case CURLOPT_FTPPORT:
5716
+ curl_easy_setopt(rbce->curl, CURLOPT_FTPPORT, NIL_P(val) ? NULL : StringValueCStr(val));
5717
+ break;
5718
+ #endif
5719
+ #ifdef HAVE_CURLOPT_APPEND
5720
+ case CURLOPT_APPEND:
5721
+ curl_easy_setopt(rbce->curl, CURLOPT_APPEND, ruby_curl_easy_option_to_long(val));
5722
+ break;
5723
+ #endif
4479
5724
  #ifdef HAVE_CURLOPT_FTP_USE_EPSV
4480
- case CURLOPT_FTP_USE_EPSV: {
4481
- int type = rb_type(val);
4482
- VALUE value;
4483
- if (type == T_TRUE) {
4484
- value = rb_int_new(1);
4485
- } else if (type == T_FALSE) {
4486
- value = rb_int_new(0);
4487
- } else {
4488
- value = rb_funcall(val, rb_intern("to_i"), 0);
4489
- }
4490
- curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, NUM2LONG(value));
4491
- } break;
5725
+ case CURLOPT_FTP_USE_EPSV:
5726
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, ruby_curl_easy_option_to_long(val));
5727
+ break;
4492
5728
  #endif
4493
5729
  #ifdef HAVE_CURLOPT_FTP_USE_EPRT
4494
- case CURLOPT_FTP_USE_EPRT: {
4495
- int type = rb_type(val);
4496
- VALUE value;
4497
- if (type == T_TRUE) {
4498
- value = rb_int_new(1);
4499
- } else if (type == T_FALSE) {
4500
- value = rb_int_new(0);
4501
- } else {
4502
- value = rb_funcall(val, rb_intern("to_i"), 0);
4503
- }
4504
- curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, NUM2LONG(value));
4505
- } break;
5730
+ case CURLOPT_FTP_USE_EPRT:
5731
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, ruby_curl_easy_option_to_long(val));
5732
+ break;
5733
+ #endif
5734
+ #ifdef HAVE_CURLOPT_FTP_USE_PRET
5735
+ case CURLOPT_FTP_USE_PRET:
5736
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_PRET, ruby_curl_easy_option_to_long(val));
5737
+ break;
5738
+ #endif
5739
+ #ifdef HAVE_CURLOPT_FTP_CREATE_MISSING_DIRS
5740
+ case CURLOPT_FTP_CREATE_MISSING_DIRS:
5741
+ rb_easy_set("ftp_create_missing_dirs", val);
5742
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_CREATE_MISSING_DIRS, ruby_curl_easy_option_to_long(val));
5743
+ break;
5744
+ #endif
5745
+ #ifdef HAVE_CURLOPT_FTP_RESPONSE_TIMEOUT
5746
+ case CURLOPT_FTP_RESPONSE_TIMEOUT:
5747
+ rbce->ftp_response_timeout = ruby_curl_easy_option_to_long(val);
5748
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_RESPONSE_TIMEOUT, rbce->ftp_response_timeout);
5749
+ break;
5750
+ #endif
5751
+ #ifdef HAVE_CURLOPT_FTP_ALTERNATIVE_TO_USER
5752
+ case CURLOPT_FTP_ALTERNATIVE_TO_USER:
5753
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_ALTERNATIVE_TO_USER, NIL_P(val) ? NULL : StringValueCStr(val));
5754
+ break;
4506
5755
  #endif
4507
5756
  #ifdef HAVE_CURLOPT_FTP_SKIP_PASV_IP
4508
- case CURLOPT_FTP_SKIP_PASV_IP: {
4509
- int type = rb_type(val);
4510
- VALUE value;
4511
- if (type == T_TRUE) {
4512
- value = rb_int_new(1);
4513
- } else if (type == T_FALSE) {
4514
- value = rb_int_new(0);
4515
- } else {
4516
- value = rb_funcall(val, rb_intern("to_i"), 0);
4517
- }
4518
- curl_easy_setopt(rbce->curl, CURLOPT_FTP_SKIP_PASV_IP, NUM2LONG(value));
4519
- } break;
5757
+ case CURLOPT_FTP_SKIP_PASV_IP:
5758
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_SKIP_PASV_IP, ruby_curl_easy_option_to_long(val));
5759
+ break;
5760
+ #endif
5761
+ #ifdef HAVE_CURLOPT_FTPSSLAUTH
5762
+ case CURLOPT_FTPSSLAUTH:
5763
+ curl_easy_setopt(rbce->curl, CURLOPT_FTPSSLAUTH, ruby_curl_easy_option_to_long(val));
5764
+ break;
5765
+ #endif
5766
+ #ifdef HAVE_CURLOPT_FTP_SSL_CCC
5767
+ case CURLOPT_FTP_SSL_CCC:
5768
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_SSL_CCC, ruby_curl_easy_option_to_long(val));
5769
+ break;
5770
+ #endif
5771
+ #ifdef HAVE_CURLOPT_FTP_ACCOUNT
5772
+ case CURLOPT_FTP_ACCOUNT:
5773
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_ACCOUNT, NIL_P(val) ? NULL : StringValueCStr(val));
5774
+ break;
5775
+ #endif
5776
+ #ifdef HAVE_CURLOPT_FTP_FILEMETHOD
5777
+ case CURLOPT_FTP_FILEMETHOD:
5778
+ rbce->ftp_filemethod = ruby_curl_easy_option_to_long(val);
5779
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_FILEMETHOD, rbce->ftp_filemethod);
5780
+ break;
4520
5781
  #endif
4521
5782
  case CURLOPT_RANGE: {
4522
5783
  curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val));
@@ -4531,7 +5792,9 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4531
5792
  curl_easy_setopt(rbce->curl, CURLOPT_SSL_CIPHER_LIST, StringValueCStr(val));
4532
5793
  } break;
4533
5794
  case CURLOPT_FORBID_REUSE: {
4534
- curl_easy_setopt(rbce->curl, CURLOPT_FORBID_REUSE, NUM2LONG(val));
5795
+ rbce->forbid_reuse = NUM2LONG(val);
5796
+ rbce->forbid_reuse_set = 1;
5797
+ curl_easy_setopt(rbce->curl, CURLOPT_FORBID_REUSE, rbce->forbid_reuse);
4535
5798
  } break;
4536
5799
  #ifdef HAVE_CURLOPT_GSSAPI_DELEGATION
4537
5800
  case CURLOPT_GSSAPI_DELEGATION: {
@@ -4540,7 +5803,14 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4540
5803
  #endif
4541
5804
  #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
4542
5805
  case CURLOPT_UNIX_SOCKET_PATH: {
4543
- curl_easy_setopt(rbce->curl, CURLOPT_UNIX_SOCKET_PATH, StringValueCStr(val));
5806
+ rb_hash_aset(rbce->opts, rb_easy_hkey("unix_socket_path"), val);
5807
+ curl_easy_setopt(rbce->curl, CURLOPT_UNIX_SOCKET_PATH, NIL_P(val) ? NULL : StringValueCStr(val));
5808
+ } break;
5809
+ #endif
5810
+ #ifdef HAVE_CURLOPT_DNS_SERVERS
5811
+ case CURLOPT_DNS_SERVERS: {
5812
+ rb_hash_aset(rbce->opts, rb_easy_hkey("dns_servers"), val);
5813
+ curl_easy_setopt(rbce->curl, CURLOPT_DNS_SERVERS, NIL_P(val) ? NULL : StringValueCStr(val));
4544
5814
  } break;
4545
5815
  #endif
4546
5816
  #ifdef HAVE_CURLOPT_MAX_SEND_SPEED_LARGE
@@ -4558,6 +5828,11 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4558
5828
  curl_easy_setopt(rbce->curl, CURLOPT_MAXFILESIZE, NUM2LONG(val));
4559
5829
  break;
4560
5830
  #endif
5831
+ #ifdef HAVE_CURLOPT_MAXFILESIZE_LARGE
5832
+ case CURLOPT_MAXFILESIZE_LARGE:
5833
+ curl_easy_setopt(rbce->curl, CURLOPT_MAXFILESIZE_LARGE, (curl_off_t)NUM2LL(val));
5834
+ break;
5835
+ #endif
4561
5836
  #ifdef HAVE_CURLOPT_TCP_KEEPALIVE
4562
5837
  case CURLOPT_TCP_KEEPALIVE:
4563
5838
  curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPALIVE, NUM2LONG(val));
@@ -4588,6 +5863,16 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4588
5863
  case CURLOPT_REDIR_PROTOCOLS:
4589
5864
  curl_easy_setopt(rbce->curl, option, NUM2LONG(val));
4590
5865
  break;
5866
+ #ifdef HAVE_CURLOPT_PROTOCOLS_STR
5867
+ case CURLOPT_PROTOCOLS_STR:
5868
+ curl_easy_setopt(rbce->curl, CURLOPT_PROTOCOLS_STR, NIL_P(val) ? NULL : StringValueCStr(val));
5869
+ break;
5870
+ #endif
5871
+ #ifdef HAVE_CURLOPT_REDIR_PROTOCOLS_STR
5872
+ case CURLOPT_REDIR_PROTOCOLS_STR:
5873
+ curl_easy_setopt(rbce->curl, CURLOPT_REDIR_PROTOCOLS_STR, NIL_P(val) ? NULL : StringValueCStr(val));
5874
+ break;
5875
+ #endif
4591
5876
  #ifdef HAVE_CURLOPT_SSL_SESSIONID_CACHE
4592
5877
  case CURLOPT_SSL_SESSIONID_CACHE:
4593
5878
  curl_easy_setopt(rbce->curl, CURLOPT_SSL_SESSIONID_CACHE, NUM2LONG(val));
@@ -4618,6 +5903,21 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4618
5903
  curl_easy_setopt(rbce->curl, CURLOPT_PROXY_SSL_VERIFYHOST, NUM2LONG(val));
4619
5904
  break;
4620
5905
  #endif
5906
+ #ifdef HAVE_CURLOPT_DOH_SSL_VERIFYPEER
5907
+ case CURLOPT_DOH_SSL_VERIFYPEER:
5908
+ curl_easy_setopt(rbce->curl, CURLOPT_DOH_SSL_VERIFYPEER, NUM2LONG(val));
5909
+ break;
5910
+ #endif
5911
+ #ifdef HAVE_CURLOPT_DOH_SSL_VERIFYHOST
5912
+ case CURLOPT_DOH_SSL_VERIFYHOST:
5913
+ curl_easy_setopt(rbce->curl, CURLOPT_DOH_SSL_VERIFYHOST, NUM2LONG(val));
5914
+ break;
5915
+ #endif
5916
+ #ifdef HAVE_CURLOPT_DOH_SSL_VERIFYSTATUS
5917
+ case CURLOPT_DOH_SSL_VERIFYSTATUS:
5918
+ curl_easy_setopt(rbce->curl, CURLOPT_DOH_SSL_VERIFYSTATUS, NUM2LONG(val));
5919
+ break;
5920
+ #endif
4621
5921
  #ifdef HAVE_CURLOPT_RESOLVE
4622
5922
  case CURLOPT_RESOLVE: {
4623
5923
  struct curl_slist *list = NULL;
@@ -4648,6 +5948,34 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4648
5948
  rb_hash_aset(rbce->opts, rb_easy_hkey("resolve"), val);
4649
5949
  curl_easy_setopt(rbce->curl, CURLOPT_RESOLVE, list);
4650
5950
  } break;
5951
+ #endif
5952
+ #ifdef HAVE_CURLOPT_CONNECT_TO
5953
+ case CURLOPT_CONNECT_TO: {
5954
+ struct curl_slist *list = NULL;
5955
+ ruby_curl_easy_clear_connect_to_list(rbce);
5956
+ if (NIL_P(val)) {
5957
+ list = NULL;
5958
+ } else if (TYPE(val) == T_ARRAY) {
5959
+ long i, len = RARRAY_LEN(val);
5960
+ for (i = 0; i < len; i++) {
5961
+ VALUE item = rb_ary_entry(val, i);
5962
+ struct curl_slist *new_list = curl_slist_append(list, StringValueCStr(item));
5963
+ if (!new_list) {
5964
+ curl_slist_free_all(list);
5965
+ rb_raise(rb_eNoMemError, "Failed to append to connect-to list");
5966
+ }
5967
+ list = new_list;
5968
+ }
5969
+ } else {
5970
+ list = curl_slist_append(NULL, StringValueCStr(val));
5971
+ if (!list) {
5972
+ rb_raise(rb_eNoMemError, "Failed to create connect-to list");
5973
+ }
5974
+ }
5975
+ rbce->curl_connect_to = list;
5976
+ rb_hash_aset(rbce->opts, rb_easy_hkey("connect_to"), val);
5977
+ curl_easy_setopt(rbce->curl, CURLOPT_CONNECT_TO, list);
5978
+ } break;
4651
5979
  #endif
4652
5980
  default:
4653
5981
  rb_raise(rb_eTypeError, "Curb unsupported option");
@@ -4656,6 +5984,34 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4656
5984
  return val;
4657
5985
  }
4658
5986
 
5987
+ #ifdef HAVE_CURLOPT_FTP_CREATE_MISSING_DIRS
5988
+ /*
5989
+ * call-seq:
5990
+ * easy.ftp_create_missing_dirs = boolean_or_mode => boolean_or_mode
5991
+ *
5992
+ * Configure whether libcurl creates missing directories for an FTP or SFTP
5993
+ * transfer. In addition to true and false, this accepts the
5994
+ * Curl::CURLFTP_CREATE_DIR_* mode constants when provided by libcurl.
5995
+ */
5996
+ static VALUE ruby_curl_easy_ftp_create_missing_dirs_set(VALUE self, VALUE value) {
5997
+ return ruby_curl_easy_set_opt(
5998
+ self,
5999
+ LONG2NUM(CURLOPT_FTP_CREATE_MISSING_DIRS),
6000
+ value
6001
+ );
6002
+ }
6003
+
6004
+ /*
6005
+ * call-seq:
6006
+ * easy.ftp_create_missing_dirs => boolean, number, or nil
6007
+ *
6008
+ * Return the configured missing-directory creation mode.
6009
+ */
6010
+ static VALUE ruby_curl_easy_ftp_create_missing_dirs_get(VALUE self) {
6011
+ CURB_OBJECT_HGETTER(ruby_curl_easy, ftp_create_missing_dirs);
6012
+ }
6013
+ #endif
6014
+
4659
6015
  /*
4660
6016
  * call-seq:
4661
6017
  * easy.getinfo(opt) => nil
@@ -4773,6 +6129,8 @@ static VALUE ruby_curl_easy_error_message(VALUE klass, VALUE code) {
4773
6129
  void init_curb_easy() {
4774
6130
  idCall = rb_intern("call");
4775
6131
  idJoin = rb_intern("join");
6132
+ idNetworkPolicyNone = rb_intern("none");
6133
+ idNetworkPolicyPublic = rb_intern("public");
4776
6134
 
4777
6135
  rbstrAmp = rb_str_new2("&");
4778
6136
  rb_global_variable(&rbstrAmp);
@@ -4823,6 +6181,17 @@ void init_curb_easy() {
4823
6181
  rb_define_method(cCurlEasy, "ftp_commands", ruby_curl_easy_ftp_commands_get, 0);
4824
6182
  rb_define_method(cCurlEasy, "resolve=", ruby_curl_easy_resolve_set, 1);
4825
6183
  rb_define_method(cCurlEasy, "resolve", ruby_curl_easy_resolve_get, 0);
6184
+ rb_define_method(cCurlEasy, "connect_to=", ruby_curl_easy_connect_to_set, 1);
6185
+ rb_define_method(cCurlEasy, "connect_to", ruby_curl_easy_connect_to_get, 0);
6186
+ rb_define_method(cCurlEasy, "doh_url=", ruby_curl_easy_doh_url_set, 1);
6187
+ rb_define_method(cCurlEasy, "doh_url", ruby_curl_easy_doh_url_get, 0);
6188
+ #ifdef HAVE_CURLOPT_DNS_SERVERS
6189
+ rb_define_method(cCurlEasy, "dns_servers", ruby_curl_easy_dns_servers_get, 0);
6190
+ #endif
6191
+ rb_define_method(cCurlEasy, "allowed_cidrs=", ruby_curl_easy_allowed_cidrs_set, 1);
6192
+ rb_define_method(cCurlEasy, "allowed_cidrs", ruby_curl_easy_allowed_cidrs_get, 0);
6193
+ rb_define_method(cCurlEasy, "allowed_hosts=", ruby_curl_easy_allowed_hosts_set, 1);
6194
+ rb_define_method(cCurlEasy, "allowed_hosts", ruby_curl_easy_allowed_hosts_get, 0);
4826
6195
 
4827
6196
  rb_define_method(cCurlEasy, "local_port=", ruby_curl_easy_local_port_set, 1);
4828
6197
  rb_define_method(cCurlEasy, "local_port", ruby_curl_easy_local_port_get, 0);
@@ -4850,6 +6219,10 @@ void init_curb_easy() {
4850
6219
  rb_define_method(cCurlEasy, "dns_cache_timeout", ruby_curl_easy_dns_cache_timeout_get, 0);
4851
6220
  rb_define_method(cCurlEasy, "ftp_response_timeout=", ruby_curl_easy_ftp_response_timeout_set, 1);
4852
6221
  rb_define_method(cCurlEasy, "ftp_response_timeout", ruby_curl_easy_ftp_response_timeout_get, 0);
6222
+ #ifdef HAVE_CURLOPT_FTP_CREATE_MISSING_DIRS
6223
+ rb_define_method(cCurlEasy, "ftp_create_missing_dirs=", ruby_curl_easy_ftp_create_missing_dirs_set, 1);
6224
+ rb_define_method(cCurlEasy, "ftp_create_missing_dirs", ruby_curl_easy_ftp_create_missing_dirs_get, 0);
6225
+ #endif
4853
6226
  rb_define_method(cCurlEasy, "low_speed_limit=", ruby_curl_easy_low_speed_limit_set, 1);
4854
6227
  rb_define_method(cCurlEasy, "low_speed_limit", ruby_curl_easy_low_speed_limit_get, 0);
4855
6228
  rb_define_method(cCurlEasy, "low_speed_time=", ruby_curl_easy_low_speed_time_set, 1);
@@ -4896,6 +6269,8 @@ void init_curb_easy() {
4896
6269
  rb_define_method(cCurlEasy, "ignore_content_length?", ruby_curl_easy_ignore_content_length_q, 0);
4897
6270
  rb_define_method(cCurlEasy, "resolve_mode", ruby_curl_easy_resolve_mode, 0);
4898
6271
  rb_define_method(cCurlEasy, "resolve_mode=", ruby_curl_easy_resolve_mode_set, 1);
6272
+ rb_define_method(cCurlEasy, "network_policy", ruby_curl_easy_network_policy_get, 0);
6273
+ rb_define_method(cCurlEasy, "network_policy=", ruby_curl_easy_network_policy_set, 1);
4899
6274
 
4900
6275
  rb_define_method(cCurlEasy, "on_body", ruby_curl_easy_on_body_set, -1);
4901
6276
  rb_define_method(cCurlEasy, "on_header", ruby_curl_easy_on_header_set, -1);
@@ -4914,6 +6289,8 @@ void init_curb_easy() {
4914
6289
 
4915
6290
  /* Post-perform info methods */
4916
6291
  rb_define_method(cCurlEasy, "body_str", ruby_curl_easy_body_str_get, 0);
6292
+ rb_define_method(cCurlEasy, "max_body_bytes=", ruby_curl_easy_max_body_bytes_set, 1);
6293
+ rb_define_method(cCurlEasy, "max_body_bytes", ruby_curl_easy_max_body_bytes_get, 0);
4917
6294
  rb_define_method(cCurlEasy, "header_str", ruby_curl_easy_header_str_get, 0);
4918
6295
 
4919
6296
  rb_define_method(cCurlEasy, "last_effective_url", ruby_curl_easy_last_effective_url_get, 0);
@@ -4964,9 +6341,16 @@ void init_curb_easy() {
4964
6341
 
4965
6342
  rb_define_method(cCurlEasy, "multi", ruby_curl_easy_multi_get, 0);
4966
6343
  rb_define_method(cCurlEasy, "multi=", ruby_curl_easy_multi_set, 1);
6344
+ rb_define_private_method(cCurlEasy, "__curb_native_setup!", ruby_curl_easy_setup_self, 0);
6345
+ rb_define_private_method(cCurlEasy, "__curb_allow_proxy=", ruby_curl_easy_allow_proxy_set, 1);
6346
+ rb_define_private_method(cCurlEasy, "__curb_allow_unix_socket=", ruby_curl_easy_allow_unix_socket_set, 1);
4967
6347
  rb_define_private_method(cCurlEasy, "_take_callback_error", ruby_curl_easy_take_callback_error, 0);
4968
6348
  rb_define_method(cCurlEasy, "last_result", ruby_curl_easy_last_result, 0);
4969
6349
  rb_define_method(cCurlEasy, "last_error", ruby_curl_easy_last_error, 0);
6350
+ rb_define_method(cCurlEasy, "unsafe_destination_error", ruby_curl_easy_unsafe_destination_error_get, 0);
6351
+ #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
6352
+ rb_define_method(cCurlEasy, "unix_socket_path", ruby_curl_easy_unix_socket_path_get, 0);
6353
+ #endif
4970
6354
 
4971
6355
  rb_define_method(cCurlEasy, "setopt", ruby_curl_easy_set_opt, 2);
4972
6356
  rb_define_method(cCurlEasy, "getinfo", ruby_curl_easy_get_opt, 1);