curb 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +75 -2
- data/ext/curb.c +11 -4
- data/ext/curb.h +4 -4
- data/ext/curb_easy.c +236 -18
- data/ext/curb_easy.h +1 -0
- data/ext/curb_multi.c +595 -20
- data/ext/curb_multi.h +4 -1
- data/ext/curb_postfield.c +6 -0
- data/ext/curb_upload.c +3 -0
- data/ext/extconf.rb +215 -17
- data/lib/curl/easy.rb +56 -5
- data/tests/bug_issue_noproxy.rb +56 -0
- data/tests/bug_issue_post_redirect.rb +93 -0
- data/tests/bug_issue_spnego.rb +41 -0
- data/tests/helper.rb +33 -0
- data/tests/mem_check.rb +3 -0
- data/tests/tc_curl_download.rb +2 -1
- data/tests/tc_curl_easy.rb +65 -6
- data/tests/tc_curl_multi.rb +31 -0
- data/tests/tc_fiber_scheduler.rb +190 -0
- metadata +10 -6
- data/tests/bug_issue277.rb +0 -32
- data/tests/bug_resolve.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48135be870a425c76338a773b41dc9438883b30cab622cc8c2d81394b860047d
|
4
|
+
data.tar.gz: e7a439c010c1bdbd3ab26b92f2126a4d518a2c7808d7acc6af2e57a03ec07376
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5c61d835a10a80211b625dd3ef8cf949fdcf30be9584d2a6793d69e309e3cb283097c2502a8912801ab9ee13679f1e71344034a197c54c88872157e66df74a0
|
7
|
+
data.tar.gz: 20fbde1832dc7338dba7f78c08556eceab4532247f41f0bb1c0c15ba11fc0cd2f07aabeaef62525eba24afc95b5e9854dbb3d71f6d6f493097d614ae913818f8
|
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# Curb - Libcurl bindings for Ruby
|
2
2
|
|
3
|
-
|
3
|
+
[](https://github.com/taf2/curb/actions/workflows/ci.yml)
|
4
|
+
[](https://codecov.io/gh/taf2/curb)
|
5
|
+
[](https://badge.fury.io/rb/curb)
|
6
|
+
|
7
|
+
* [CI Build Status](https://github.com/taf2/curb/actions/workflows/ci.yml)
|
4
8
|
* [rubydoc rdoc](http://www.rubydoc.info/github/taf2/curb/)
|
5
9
|
* [github project](http://github.com/taf2/curb/tree/master)
|
6
10
|
|
@@ -70,11 +74,37 @@ puts "\n=== FTP Directory Listing Example ==="
|
|
70
74
|
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
|
71
75
|
list.username = 'user'
|
72
76
|
list.password = 'password'
|
73
|
-
list.dirlistonly
|
77
|
+
list.set(:dirlistonly, 1)
|
78
|
+
list.perform
|
79
|
+
puts list.body
|
80
|
+
```
|
81
|
+
|
82
|
+
### FTP over HTTP proxy tunnel (NLST/LIST)
|
83
|
+
When listing directories through an HTTP proxy with `proxy_tunnel` (CONNECT), let libcurl manage the passive data connection. Do not send `PASV`/`EPSV` or `NLST` via `easy.ftp_commands` — QUOTE commands run on the control connection and libcurl will not open the data connection, resulting in 425 errors.
|
84
|
+
|
85
|
+
To get NLST-like output safely:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
|
89
|
+
list.username = 'user'
|
90
|
+
list.password = 'password'
|
91
|
+
list.proxy_url = 'http://proxy.example.com:80'
|
92
|
+
list.proxy_tunnel = true
|
93
|
+
|
94
|
+
# Ask libcurl to perform a listing (names only)
|
95
|
+
list.set(:dirlistonly, 1)
|
96
|
+
|
97
|
+
# If the proxy or server has trouble with EPSV/EPRT, you can adjust:
|
98
|
+
# list.set(:ftp_use_epsv, 0) # disable EPSV
|
99
|
+
# list.set(:ftp_use_eprt, 0) # disable EPRT (stick to IPv4 PASV)
|
100
|
+
# list.set(:ftp_skip_pasv_ip, 1) # ignore PASV host, reuse control host
|
101
|
+
|
74
102
|
list.perform
|
75
103
|
puts list.body
|
76
104
|
```
|
77
105
|
|
106
|
+
If you need a full `LIST` output instead of just names, omit `dirlistonly` and parse the server response accordingly. The key is to let libcurl initiate the data connection (PASV/EPSV) instead of trying to drive it via `ftp_commands`.
|
107
|
+
|
78
108
|
### Advanced FTP Usage with Various Options
|
79
109
|
```
|
80
110
|
puts "\n=== Advanced FTP Example ==="
|
@@ -323,6 +353,8 @@ end
|
|
323
353
|
|
324
354
|
### HTTP POST form:
|
325
355
|
|
356
|
+
Note: Instance methods like `easy.http_post(...)` do not accept a URL argument. Set the URL first (for example, `Curl::Easy.new(url)` or `easy.url = url`) and then call `easy.http_post(...)`. If you want to pass the URL directly to the call, use the class/module helpers such as `Curl::Easy.http_post(url, ...)` or `Curl.post(url, ...)`.
|
357
|
+
|
326
358
|
```ruby
|
327
359
|
c = Curl::Easy.http_post("http://my.rails.box/thing/create",
|
328
360
|
Curl::PostField.content('thing[name]', 'box'),
|
@@ -335,6 +367,19 @@ c = Curl::Easy.http_post("http://my.rails.box/thing/create",
|
|
335
367
|
c = Curl::Easy.new("http://my.rails.box/files/upload")
|
336
368
|
c.multipart_form_post = true
|
337
369
|
c.http_post(Curl::PostField.file('thing[file]', 'myfile.rb'))
|
370
|
+
|
371
|
+
### Custom request target
|
372
|
+
|
373
|
+
Some advanced scenarios need a request-target that differs from the URL host/path (for example, absolute-form targets or special values like `*`). If your libcurl supports `CURLOPT_REQUEST_TARGET` (libcurl ≥ 7.55), you can override it:
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
c = Curl::Easy.new("http://127.0.0.1:9129/methods")
|
377
|
+
c.request_target = "http://localhost:9129/methods" # absolute-form target
|
378
|
+
c.headers = { 'Host' => 'example.com' } # override Host header if needed
|
379
|
+
c.perform
|
380
|
+
```
|
381
|
+
|
382
|
+
For HTTPS, prefer `easy.resolve = ["host:443:IP"]` to keep Host/SNI/certificates aligned.
|
338
383
|
```
|
339
384
|
|
340
385
|
### Using HTTP/2
|
@@ -414,3 +459,31 @@ end
|
|
414
459
|
* `on_missing` is called when the response code is 4xx
|
415
460
|
* `on_failure` is called when the response code is 5xx
|
416
461
|
* `on_complete` is called in all cases.
|
462
|
+
|
463
|
+
### Cookies
|
464
|
+
|
465
|
+
- Manual cookies: Set the outgoing `Cookie` header via `easy.cookies = "name=value; other=val"`. This only affects the request header and does not modify libcurl's internal cookie engine.
|
466
|
+
- Cookie engine: Enable with `easy.enable_cookies = true`. Optionally set `easy.cookiefile` (to load) and/or `easy.cookiejar` (to persist). Cookies received via `Set-Cookie` go into this engine.
|
467
|
+
- Inspect engine cookies: `easy.cookielist` returns an array of strings (Netscape or Set-Cookie format).
|
468
|
+
- Modify engine cookies: use `easy.cookielist = ...` or `easy.set(:cookielist, ...)` with either a `Set-Cookie` style string, Netscape cookie lines, or special commands: `"ALL"` (clear), `"SESS"` (remove session cookies), `"FLUSH"` (write to jar), `"RELOAD"` (reload from file).
|
469
|
+
- Clearing manual cookies: assign an empty string (`easy.cookies = ''`). Assigning `nil` has no effect in current versions.
|
470
|
+
|
471
|
+
Examples:
|
472
|
+
|
473
|
+
```ruby
|
474
|
+
easy = Curl::Easy.new("https://example.com")
|
475
|
+
|
476
|
+
# Use the cookie engine and persist cookies
|
477
|
+
easy.enable_cookies = true
|
478
|
+
easy.cookiejar = "/tmp/cookies.txt"
|
479
|
+
easy.perform
|
480
|
+
|
481
|
+
# Later: inspect and tweak engine cookies
|
482
|
+
p easy.cookielist
|
483
|
+
easy.cookielist = 'ALL' # clear stored cookies
|
484
|
+
|
485
|
+
# Send custom Cookie header for a single request
|
486
|
+
easy.cookies = "flag=1; session_override=abc"
|
487
|
+
easy.perform
|
488
|
+
easy.cookies = '' # clear manual Cookie header
|
489
|
+
```
|
data/ext/curb.c
CHANGED
@@ -143,12 +143,14 @@ static VALUE ruby_curl_asyncdns_q(VALUE mod) {
|
|
143
143
|
* in RFC 2478). For libcurl versions < 7.10.8, always returns false.
|
144
144
|
*/
|
145
145
|
static VALUE ruby_curl_spnego_q(VALUE mod) {
|
146
|
-
#ifdef HAVE_CURL_VERSION_SPNEGO
|
147
146
|
curl_version_info_data *ver = curl_version_info(CURLVERSION_NOW);
|
148
|
-
|
149
|
-
|
150
|
-
return Qfalse;
|
147
|
+
#ifdef HAVE_CURL_VERSION_SPNEGO
|
148
|
+
if (ver->features & CURL_VERSION_SPNEGO) return Qtrue;
|
151
149
|
#endif
|
150
|
+
#ifdef HAVE_CURL_VERSION_GSSNEGOTIATE
|
151
|
+
if (ver->features & CURL_VERSION_GSSNEGOTIATE) return Qtrue;
|
152
|
+
#endif
|
153
|
+
return Qfalse;
|
152
154
|
}
|
153
155
|
|
154
156
|
/*
|
@@ -614,6 +616,11 @@ void Init_curb_core() {
|
|
614
616
|
#if HAVE_CURLOPT_PROXYHEADER
|
615
617
|
CURB_DEFINE(CURLOPT_PROXYHEADER);
|
616
618
|
#endif
|
619
|
+
#if HAVE_CURLOPT_REQUEST_TARGET
|
620
|
+
/* Allows overriding the Request-URI target used in the request line.
|
621
|
+
* Useful for absolute-form requests or special targets like "*". */
|
622
|
+
CURB_DEFINE(CURLOPT_REQUEST_TARGET);
|
623
|
+
#endif
|
617
624
|
#if HAVE_CURLOPT_HTTP200ALIASES
|
618
625
|
CURB_DEFINE(CURLOPT_HTTP200ALIASES);
|
619
626
|
#endif
|
data/ext/curb.h
CHANGED
@@ -28,11 +28,11 @@
|
|
28
28
|
#include "curb_macros.h"
|
29
29
|
|
30
30
|
// These should be managed from the Rake 'release' task.
|
31
|
-
#define CURB_VERSION "1.1
|
32
|
-
#define CURB_VER_NUM
|
31
|
+
#define CURB_VERSION "1.2.1"
|
32
|
+
#define CURB_VER_NUM 1021
|
33
33
|
#define CURB_VER_MAJ 1
|
34
|
-
#define CURB_VER_MIN
|
35
|
-
#define CURB_VER_MIC
|
34
|
+
#define CURB_VER_MIN 2
|
35
|
+
#define CURB_VER_MIC 1
|
36
36
|
#define CURB_VER_PATCH 0
|
37
37
|
|
38
38
|
|
data/ext/curb_easy.c
CHANGED
@@ -12,6 +12,9 @@
|
|
12
12
|
|
13
13
|
#include <errno.h>
|
14
14
|
#include <string.h>
|
15
|
+
#ifndef _WIN32
|
16
|
+
#include <strings.h>
|
17
|
+
#endif
|
15
18
|
|
16
19
|
extern VALUE mCurl;
|
17
20
|
|
@@ -222,6 +225,24 @@ void curl_easy_mark(ruby_curl_easy *rbce) {
|
|
222
225
|
}
|
223
226
|
|
224
227
|
static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
|
228
|
+
if (!rbce) {
|
229
|
+
return;
|
230
|
+
}
|
231
|
+
|
232
|
+
if (!NIL_P(rbce->multi)) {
|
233
|
+
VALUE multi_val = rbce->multi;
|
234
|
+
ruby_curl_multi *rbcm = NULL;
|
235
|
+
|
236
|
+
rbce->multi = Qnil;
|
237
|
+
|
238
|
+
if (!NIL_P(multi_val) && RB_TYPE_P(multi_val, T_DATA)) {
|
239
|
+
Data_Get_Struct(multi_val, ruby_curl_multi, rbcm);
|
240
|
+
if (rbcm) {
|
241
|
+
rb_curl_multi_forget_easy(rbcm, rbce);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
225
246
|
if (rbce->curl_headers) {
|
226
247
|
curl_slist_free_all(rbce->curl_headers);
|
227
248
|
}
|
@@ -307,8 +328,10 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
|
|
307
328
|
rbce->verbose = 0;
|
308
329
|
rbce->multipart_form_post = 0;
|
309
330
|
rbce->enable_cookies = 0;
|
331
|
+
rbce->cookielist_engine_enabled = 0;
|
310
332
|
rbce->ignore_content_length = 0;
|
311
333
|
rbce->callback_active = 0;
|
334
|
+
rbce->last_result = 0;
|
312
335
|
}
|
313
336
|
|
314
337
|
/*
|
@@ -317,6 +340,9 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
|
|
317
340
|
static VALUE ruby_curl_easy_allocate(VALUE klass) {
|
318
341
|
ruby_curl_easy *rbce;
|
319
342
|
rbce = ALLOC(ruby_curl_easy);
|
343
|
+
if (!rbce) {
|
344
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Easy");
|
345
|
+
}
|
320
346
|
rbce->curl = NULL;
|
321
347
|
rbce->opts = Qnil;
|
322
348
|
rbce->multi = Qnil;
|
@@ -375,8 +401,14 @@ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
|
|
375
401
|
static struct curl_slist *duplicate_curl_slist(struct curl_slist *list) {
|
376
402
|
struct curl_slist *dup = NULL;
|
377
403
|
struct curl_slist *tmp;
|
404
|
+
struct curl_slist *new_list;
|
378
405
|
for (tmp = list; tmp; tmp = tmp->next) {
|
379
|
-
|
406
|
+
new_list = curl_slist_append(dup, tmp->data);
|
407
|
+
if (!new_list) {
|
408
|
+
if (dup) { curl_slist_free_all(dup); }
|
409
|
+
rb_raise(rb_eNoMemError, "Failed to duplicate curl_slist");
|
410
|
+
}
|
411
|
+
dup = new_list;
|
380
412
|
}
|
381
413
|
return dup;
|
382
414
|
}
|
@@ -395,6 +427,9 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
|
|
395
427
|
Data_Get_Struct(self, ruby_curl_easy, rbce);
|
396
428
|
|
397
429
|
newrbce = ALLOC(ruby_curl_easy);
|
430
|
+
if (!newrbce) {
|
431
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Easy clone");
|
432
|
+
}
|
398
433
|
/* shallow copy */
|
399
434
|
memcpy(newrbce, rbce, sizeof(ruby_curl_easy));
|
400
435
|
|
@@ -405,6 +440,9 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
|
|
405
440
|
newrbce->curl_ftp_commands = (rbce->curl_ftp_commands) ? duplicate_curl_slist(rbce->curl_ftp_commands) : NULL;
|
406
441
|
newrbce->curl_resolve = (rbce->curl_resolve) ? duplicate_curl_slist(rbce->curl_resolve) : NULL;
|
407
442
|
|
443
|
+
/* A cloned easy should not retain ownership reference to the original multi. */
|
444
|
+
newrbce->multi = Qnil;
|
445
|
+
|
408
446
|
if (rbce->opts != Qnil) {
|
409
447
|
newrbce->opts = rb_funcall(rbce->opts, rb_intern("dup"), 0);
|
410
448
|
}
|
@@ -645,7 +683,16 @@ static VALUE ruby_curl_easy_proxypwd_get(VALUE self) {
|
|
645
683
|
* call-seq:
|
646
684
|
* easy.cookies => "name1=content1; name2=content2;"
|
647
685
|
*
|
648
|
-
* Obtain the
|
686
|
+
* Obtain the manually set Cookie header string for this Curl::Easy instance.
|
687
|
+
*
|
688
|
+
* Notes:
|
689
|
+
* - This corresponds to libcurl's CURLOPT_COOKIE and only affects the outgoing
|
690
|
+
* Cookie request header. It does NOT modify the internal libcurl cookie engine
|
691
|
+
* that stores cookies received via Set-Cookie.
|
692
|
+
* - To inspect or modify cookies stored in the cookie engine, use
|
693
|
+
* +easy.cookielist+ (getter) and +easy.cookielist=+ or +easy.set(:cookielist, ...)+ (setter).
|
694
|
+
* - To clear a previously set manual Cookie header, assign an empty string.
|
695
|
+
* Assigning +nil+ currently has no effect.
|
649
696
|
*/
|
650
697
|
static VALUE ruby_curl_easy_cookies_get(VALUE self) {
|
651
698
|
CURB_OBJECT_HGETTER(ruby_curl_easy, cookies);
|
@@ -655,7 +702,8 @@ static VALUE ruby_curl_easy_cookies_get(VALUE self) {
|
|
655
702
|
* call-seq:
|
656
703
|
* easy.cookiefile => string
|
657
704
|
*
|
658
|
-
* Obtain the cookiefile
|
705
|
+
* Obtain the cookiefile path for this Curl::Easy instance (used to load cookies when the
|
706
|
+
* cookie engine is enabled).
|
659
707
|
*/
|
660
708
|
static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
|
661
709
|
CURB_OBJECT_HGETTER(ruby_curl_easy, cookiefile);
|
@@ -665,7 +713,8 @@ static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
|
|
665
713
|
* call-seq:
|
666
714
|
* easy.cookiejar => string
|
667
715
|
*
|
668
|
-
* Obtain the cookiejar
|
716
|
+
* Obtain the cookiejar path for this Curl::Easy instance (used to persist cookies when the
|
717
|
+
* cookie engine is enabled).
|
669
718
|
*/
|
670
719
|
static VALUE ruby_curl_easy_cookiejar_get(VALUE self) {
|
671
720
|
CURB_OBJECT_HGETTER(ruby_curl_easy, cookiejar);
|
@@ -965,7 +1014,15 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
|
|
965
1014
|
* call-seq:
|
966
1015
|
* easy.ftp_commands = ["CWD /", "MKD directory"] => ["CWD /", ...]
|
967
1016
|
*
|
968
|
-
* Explicitly sets the list of commands to execute on the FTP server when calling perform
|
1017
|
+
* Explicitly sets the list of commands to execute on the FTP server when calling perform.
|
1018
|
+
*
|
1019
|
+
* NOTE:
|
1020
|
+
* - This maps to libcurl CURLOPT_QUOTE; it sends commands on the control connection.
|
1021
|
+
* - Do not include data-transfer commands like LIST/NLST/RETR/STOR here. libcurl does not
|
1022
|
+
* parse PASV/EPSV replies from QUOTE commands and will not establish the required data
|
1023
|
+
* connection. For directory listings, set CURLOPT_DIRLISTONLY (via `easy.set(:dirlistonly, true)`)
|
1024
|
+
* and request an FTP directory URL (e.g. "ftp://host/path/") so libcurl manages PASV/EPSV
|
1025
|
+
* and the data connection for you.
|
969
1026
|
*/
|
970
1027
|
static VALUE ruby_curl_easy_ftp_commands_set(VALUE self, VALUE ftp_commands) {
|
971
1028
|
CURB_OBJECT_HSETTER(ruby_curl_easy, ftp_commands);
|
@@ -1874,7 +1931,12 @@ static VALUE ruby_curl_easy_multipart_form_post_q(VALUE self) {
|
|
1874
1931
|
* easy.enable_cookies = boolean => boolean
|
1875
1932
|
*
|
1876
1933
|
* Configure whether the libcurl cookie engine is enabled for this Curl::Easy
|
1877
|
-
* instance.
|
1934
|
+
* instance. When enabled, cookies received via Set-Cookie are stored by libcurl
|
1935
|
+
* and automatically sent on subsequent matching requests. Use +easy.cookiefile+
|
1936
|
+
* to load cookies and +easy.cookiejar+ to persist them.
|
1937
|
+
*
|
1938
|
+
* This setting is independent from the manual Cookie header set via +easy.cookies+.
|
1939
|
+
* The manual header is additive and can be cleared by assigning an empty string.
|
1878
1940
|
*/
|
1879
1941
|
static VALUE ruby_curl_easy_enable_cookies_set(VALUE self, VALUE enable_cookies)
|
1880
1942
|
{
|
@@ -2165,7 +2227,11 @@ static VALUE cb_each_http_header(VALUE header, VALUE wrap, int _c, const VALUE *
|
|
2165
2227
|
|
2166
2228
|
//rb_p(header_str);
|
2167
2229
|
|
2168
|
-
*
|
2230
|
+
struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(header_str));
|
2231
|
+
if (!new_list) {
|
2232
|
+
rb_raise(rb_eNoMemError, "Failed to append to header list");
|
2233
|
+
}
|
2234
|
+
*list = new_list;
|
2169
2235
|
return header_str;
|
2170
2236
|
}
|
2171
2237
|
|
@@ -2197,7 +2263,11 @@ static VALUE cb_each_http_proxy_header(VALUE proxy_header, VALUE wrap, int _c, c
|
|
2197
2263
|
|
2198
2264
|
//rb_p(header_str);
|
2199
2265
|
|
2200
|
-
*
|
2266
|
+
struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(proxy_header_str));
|
2267
|
+
if (!new_list) {
|
2268
|
+
rb_raise(rb_eNoMemError, "Failed to append to proxy header list");
|
2269
|
+
}
|
2270
|
+
*list = new_list;
|
2201
2271
|
return proxy_header_str;
|
2202
2272
|
}
|
2203
2273
|
|
@@ -2210,7 +2280,11 @@ static VALUE cb_each_ftp_command(VALUE ftp_command, VALUE wrap, int _c, const VA
|
|
2210
2280
|
Data_Get_Struct(wrap, struct curl_slist *, list);
|
2211
2281
|
|
2212
2282
|
ftp_command_string = rb_obj_as_string(ftp_command);
|
2213
|
-
*
|
2283
|
+
struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(ftp_command));
|
2284
|
+
if (!new_list) {
|
2285
|
+
rb_raise(rb_eNoMemError, "Failed to append to FTP command list");
|
2286
|
+
}
|
2287
|
+
*list = new_list;
|
2214
2288
|
|
2215
2289
|
return ftp_command_string;
|
2216
2290
|
}
|
@@ -2224,7 +2298,11 @@ static VALUE cb_each_resolve(VALUE resolve, VALUE wrap, int _c, const VALUE *_pt
|
|
2224
2298
|
Data_Get_Struct(wrap, struct curl_slist *, list);
|
2225
2299
|
|
2226
2300
|
resolve_string = rb_obj_as_string(resolve);
|
2227
|
-
*
|
2301
|
+
struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(resolve));
|
2302
|
+
if (!new_list) {
|
2303
|
+
rb_raise(rb_eNoMemError, "Failed to append to resolve list");
|
2304
|
+
}
|
2305
|
+
*list = new_list;
|
2228
2306
|
|
2229
2307
|
return resolve_string;
|
2230
2308
|
}
|
@@ -2296,6 +2374,14 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2296
2374
|
curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, rb_easy_get_str("proxypwd"));
|
2297
2375
|
}
|
2298
2376
|
|
2377
|
+
#if HAVE_CURLOPT_NOPROXY
|
2378
|
+
if (rb_easy_nil("noproxy")) {
|
2379
|
+
curl_easy_setopt(curl, CURLOPT_NOPROXY, NULL);
|
2380
|
+
} else {
|
2381
|
+
curl_easy_setopt(curl, CURLOPT_NOPROXY, rb_easy_get_str("noproxy"));
|
2382
|
+
}
|
2383
|
+
#endif
|
2384
|
+
|
2299
2385
|
// body/header procs
|
2300
2386
|
if (!rb_easy_nil("body_proc")) {
|
2301
2387
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&proc_data_handler_body);
|
@@ -2443,9 +2529,8 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2443
2529
|
#endif
|
2444
2530
|
}
|
2445
2531
|
|
2446
|
-
/* Set up HTTP cookie handling if necessary
|
2447
|
-
|
2448
|
-
*/
|
2532
|
+
/* Set up HTTP cookie handling if necessary */
|
2533
|
+
/* Enable/attach cookie engine if requested, or implicitly via COOKIELIST usage */
|
2449
2534
|
if (rbce->enable_cookies) {
|
2450
2535
|
if (!rb_easy_nil("cookiejar")) {
|
2451
2536
|
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, rb_easy_get_str("cookiejar"));
|
@@ -2456,6 +2541,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2456
2541
|
} else {
|
2457
2542
|
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* "" = magic to just enable */
|
2458
2543
|
}
|
2544
|
+
} else if (rbce->cookielist_engine_enabled) {
|
2545
|
+
/* Ensure cookie engine is enabled even if enable_cookies? is false. */
|
2546
|
+
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
|
2459
2547
|
}
|
2460
2548
|
|
2461
2549
|
if (!rb_easy_nil("cookies")) {
|
@@ -2517,7 +2605,11 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2517
2605
|
rb_iterate(rb_each, rb_easy_get("headers"), cb_each_http_header, wrap);
|
2518
2606
|
} else {
|
2519
2607
|
VALUE headers_str = rb_obj_as_string(rb_easy_get("headers"));
|
2520
|
-
*
|
2608
|
+
struct curl_slist *new_list = curl_slist_append(*hdrs, StringValuePtr(headers_str));
|
2609
|
+
if (!new_list) {
|
2610
|
+
rb_raise(rb_eNoMemError, "Failed to append to headers list");
|
2611
|
+
}
|
2612
|
+
*hdrs = new_list;
|
2521
2613
|
}
|
2522
2614
|
|
2523
2615
|
if (*hdrs) {
|
@@ -2535,7 +2627,11 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2535
2627
|
rb_iterate(rb_each, rb_easy_get("proxy_headers"), cb_each_http_proxy_header, wrap);
|
2536
2628
|
} else {
|
2537
2629
|
VALUE proxy_headers_str = rb_obj_as_string(rb_easy_get("proxy_headers"));
|
2538
|
-
*
|
2630
|
+
struct curl_slist *new_list = curl_slist_append(*phdrs, StringValuePtr(proxy_headers_str));
|
2631
|
+
if (!new_list) {
|
2632
|
+
rb_raise(rb_eNoMemError, "Failed to append to proxy headers list");
|
2633
|
+
}
|
2634
|
+
*phdrs = new_list;
|
2539
2635
|
}
|
2540
2636
|
|
2541
2637
|
if (*phdrs) {
|
@@ -2635,10 +2731,31 @@ static VALUE ruby_curl_easy_perform_verb_str(VALUE self, const char *verb) {
|
|
2635
2731
|
|
2636
2732
|
memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
|
2637
2733
|
|
2734
|
+
/* Use method override and adjust related options for special verbs. */
|
2638
2735
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb);
|
2639
2736
|
|
2737
|
+
/* For HEAD, ensure no body is requested/downloaded, as some servers
|
2738
|
+
* include a Content-Length header which should not cause libcurl to
|
2739
|
+
* wait for a body that will never arrive. */
|
2740
|
+
int is_head = (verb && (
|
2741
|
+
#ifdef _WIN32
|
2742
|
+
_stricmp(verb, "HEAD") == 0
|
2743
|
+
#else
|
2744
|
+
strcasecmp(verb, "HEAD") == 0
|
2745
|
+
#endif
|
2746
|
+
));
|
2747
|
+
if (is_head) {
|
2748
|
+
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
|
2749
|
+
curl_easy_setopt(curl, CURLOPT_HTTPGET, 0L);
|
2750
|
+
curl_easy_setopt(curl, CURLOPT_POST, 0L);
|
2751
|
+
}
|
2752
|
+
|
2640
2753
|
retval = rb_funcall(self, rb_intern("perform"), 0);
|
2641
2754
|
|
2755
|
+
/* Restore state after request. */
|
2756
|
+
if (is_head) {
|
2757
|
+
curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);
|
2758
|
+
}
|
2642
2759
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
|
2643
2760
|
|
2644
2761
|
return retval;
|
@@ -3523,6 +3640,12 @@ static VALUE ruby_curl_easy_num_connects_get(VALUE self) {
|
|
3523
3640
|
* Returned strings are in Netscape cookiejar format or in Set-Cookie format.
|
3524
3641
|
* Since 7.43.0 cookies in the Set-Cookie format without a domain name are not exported.
|
3525
3642
|
*
|
3643
|
+
* To modify the cookie engine (add/replace/remove), use +easy.cookielist= string+
|
3644
|
+
* or +easy.set(:cookielist, string)+ with one of the following accepted inputs:
|
3645
|
+
* - A Set-Cookie style header string: "Set-Cookie: name=value; Domain=example.com; Path=/; Expires=..."
|
3646
|
+
* - One or more lines in Netscape cookie file format (tab-separated fields)
|
3647
|
+
* - Special commands: "ALL" (clear all), "SESS" (remove session cookies), "FLUSH" (write to jar), "RELOAD" (reload from file)
|
3648
|
+
*
|
3526
3649
|
* @see https://curl.se/libcurl/c/CURLINFO_COOKIELIST.html option <code>CURLINFO_COOKIELIST</code> of
|
3527
3650
|
* <code>curl_easy_getopt(3)</code> to see how libcurl behaves.
|
3528
3651
|
* @note requires libcurl 7.14.1 or higher, otherwise +-1+ is always returned
|
@@ -3584,7 +3707,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
|
|
3584
3707
|
return Qnil;
|
3585
3708
|
}
|
3586
3709
|
#else
|
3587
|
-
rb_warn("Installed libcurl is too old to support
|
3710
|
+
rb_warn("Installed libcurl is too old to support ftp_entry_path");
|
3588
3711
|
return Qnil;
|
3589
3712
|
#endif
|
3590
3713
|
}
|
@@ -3725,6 +3848,12 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
|
|
3725
3848
|
VALUE proxypwd = val;
|
3726
3849
|
CURB_OBJECT_HSETTER(ruby_curl_easy, proxypwd);
|
3727
3850
|
} break;
|
3851
|
+
#if HAVE_CURLOPT_NOPROXY
|
3852
|
+
case CURLOPT_NOPROXY: {
|
3853
|
+
VALUE noproxy = val;
|
3854
|
+
CURB_OBJECT_HSETTER(ruby_curl_easy, noproxy);
|
3855
|
+
} break;
|
3856
|
+
#endif
|
3728
3857
|
case CURLOPT_COOKIE: {
|
3729
3858
|
VALUE cookies = val;
|
3730
3859
|
CURB_OBJECT_HSETTER(ruby_curl_easy, cookies);
|
@@ -3737,9 +3866,72 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
|
|
3737
3866
|
VALUE cookiejar = val;
|
3738
3867
|
CURB_OBJECT_HSETTER(ruby_curl_easy, cookiejar);
|
3739
3868
|
} break;
|
3869
|
+
#if HAVE_CURLOPT_REQUEST_TARGET
|
3870
|
+
case CURLOPT_REQUEST_TARGET: {
|
3871
|
+
/* Forward request-target directly to libcurl as a string. */
|
3872
|
+
curl_easy_setopt(rbce->curl, CURLOPT_REQUEST_TARGET, NIL_P(val) ? NULL : StringValueCStr(val));
|
3873
|
+
} break;
|
3874
|
+
#endif
|
3740
3875
|
case CURLOPT_TCP_NODELAY: {
|
3741
3876
|
curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val));
|
3742
3877
|
} break;
|
3878
|
+
/* FTP-specific toggles */
|
3879
|
+
#if HAVE_CURLOPT_DIRLISTONLY
|
3880
|
+
case CURLOPT_DIRLISTONLY: {
|
3881
|
+
int type = rb_type(val);
|
3882
|
+
VALUE value;
|
3883
|
+
if (type == T_TRUE) {
|
3884
|
+
value = rb_int_new(1);
|
3885
|
+
} else if (type == T_FALSE) {
|
3886
|
+
value = rb_int_new(0);
|
3887
|
+
} else {
|
3888
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3889
|
+
}
|
3890
|
+
curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, NUM2LONG(value));
|
3891
|
+
} break;
|
3892
|
+
#endif
|
3893
|
+
#if HAVE_CURLOPT_FTP_USE_EPSV
|
3894
|
+
case CURLOPT_FTP_USE_EPSV: {
|
3895
|
+
int type = rb_type(val);
|
3896
|
+
VALUE value;
|
3897
|
+
if (type == T_TRUE) {
|
3898
|
+
value = rb_int_new(1);
|
3899
|
+
} else if (type == T_FALSE) {
|
3900
|
+
value = rb_int_new(0);
|
3901
|
+
} else {
|
3902
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3903
|
+
}
|
3904
|
+
curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, NUM2LONG(value));
|
3905
|
+
} break;
|
3906
|
+
#endif
|
3907
|
+
#if HAVE_CURLOPT_FTP_USE_EPRT
|
3908
|
+
case CURLOPT_FTP_USE_EPRT: {
|
3909
|
+
int type = rb_type(val);
|
3910
|
+
VALUE value;
|
3911
|
+
if (type == T_TRUE) {
|
3912
|
+
value = rb_int_new(1);
|
3913
|
+
} else if (type == T_FALSE) {
|
3914
|
+
value = rb_int_new(0);
|
3915
|
+
} else {
|
3916
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3917
|
+
}
|
3918
|
+
curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, NUM2LONG(value));
|
3919
|
+
} break;
|
3920
|
+
#endif
|
3921
|
+
#if HAVE_CURLOPT_FTP_SKIP_PASV_IP
|
3922
|
+
case CURLOPT_FTP_SKIP_PASV_IP: {
|
3923
|
+
int type = rb_type(val);
|
3924
|
+
VALUE value;
|
3925
|
+
if (type == T_TRUE) {
|
3926
|
+
value = rb_int_new(1);
|
3927
|
+
} else if (type == T_FALSE) {
|
3928
|
+
value = rb_int_new(0);
|
3929
|
+
} else {
|
3930
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3931
|
+
}
|
3932
|
+
curl_easy_setopt(rbce->curl, CURLOPT_FTP_SKIP_PASV_IP, NUM2LONG(value));
|
3933
|
+
} break;
|
3934
|
+
#endif
|
3743
3935
|
case CURLOPT_RANGE: {
|
3744
3936
|
curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val));
|
3745
3937
|
} break;
|
@@ -3802,6 +3994,9 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
|
|
3802
3994
|
Check_Type(val, T_FILE);
|
3803
3995
|
GetOpenFile(val, open_f_ptr);
|
3804
3996
|
curl_easy_setopt(rbce->curl, CURLOPT_STDERR, rb_io_stdio_file(open_f_ptr));
|
3997
|
+
/* Retain a Ruby reference to the IO to prevent GC/finalization
|
3998
|
+
* while libcurl still holds and writes to the underlying FILE*. */
|
3999
|
+
rb_easy_set("stderr_io", val);
|
3805
4000
|
break;
|
3806
4001
|
case CURLOPT_PROTOCOLS:
|
3807
4002
|
case CURLOPT_REDIR_PROTOCOLS:
|
@@ -3814,8 +4009,23 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
|
|
3814
4009
|
#endif
|
3815
4010
|
#if HAVE_CURLOPT_COOKIELIST
|
3816
4011
|
case CURLOPT_COOKIELIST: {
|
4012
|
+
/* Forward to libcurl */
|
3817
4013
|
curl_easy_setopt(rbce->curl, CURLOPT_COOKIELIST, StringValueCStr(val));
|
3818
|
-
|
4014
|
+
/* Track whether the cookie engine should be enabled for requests.
|
4015
|
+
* According to libcurl docs, CURLOPT_COOKIELIST also enables the cookie engine
|
4016
|
+
* when provided with a non-command string. Some environments may still require
|
4017
|
+
* an explicit "enable" via CURLOPT_COOKIEFILE="" to send cookies on requests.
|
4018
|
+
* We do that in the perform setup when this flag is set.
|
4019
|
+
*/
|
4020
|
+
if (RB_TYPE_P(val, T_STRING)) {
|
4021
|
+
const char *s = StringValueCStr(val);
|
4022
|
+
if (!(strcmp(s, "ALL") == 0 || strcmp(s, "SESS") == 0 || strcmp(s, "FLUSH") == 0 || strcmp(s, "RELOAD") == 0)) {
|
4023
|
+
rbce->cookielist_engine_enabled = 1;
|
4024
|
+
}
|
4025
|
+
} else {
|
4026
|
+
/* Non-string values are unexpected; be conservative and do not enable. */
|
4027
|
+
}
|
4028
|
+
} break;
|
3819
4029
|
#endif
|
3820
4030
|
#if HAVE_CURLOPT_PROXY_SSL_VERIFYHOST
|
3821
4031
|
case CURLOPT_PROXY_SSL_VERIFYHOST:
|
@@ -3832,11 +4042,19 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
|
|
3832
4042
|
long i, len = RARRAY_LEN(val);
|
3833
4043
|
for (i = 0; i < len; i++) {
|
3834
4044
|
VALUE item = rb_ary_entry(val, i);
|
3835
|
-
|
4045
|
+
struct curl_slist *new_list = curl_slist_append(list, StringValueCStr(item));
|
4046
|
+
if (!new_list) {
|
4047
|
+
curl_slist_free_all(list);
|
4048
|
+
rb_raise(rb_eNoMemError, "Failed to append to resolve list");
|
4049
|
+
}
|
4050
|
+
list = new_list;
|
3836
4051
|
}
|
3837
4052
|
} else {
|
3838
4053
|
/* If a single string is passed, use it directly */
|
3839
4054
|
list = curl_slist_append(NULL, StringValueCStr(val));
|
4055
|
+
if (!list) {
|
4056
|
+
rb_raise(rb_eNoMemError, "Failed to create resolve list");
|
4057
|
+
}
|
3840
4058
|
}
|
3841
4059
|
/* Save the list pointer in the ruby_curl_easy structure for cleanup later */
|
3842
4060
|
rbce->curl_resolve = list;
|
data/ext/curb_easy.h
CHANGED