curb 1.2.0 → 1.2.2
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 +103 -1
- data/ext/curb.c +5 -0
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +214 -30
- data/ext/curb_easy.h +2 -0
- data/ext/curb_multi.c +162 -12
- data/ext/curb_multi.h +4 -1
- data/ext/extconf.rb +1 -0
- data/lib/curl/easy.rb +56 -5
- data/tests/tc_curl_easy_request_target.rb +41 -0
- data/tests/tc_curl_multi.rb +29 -0
- data/tests/tc_ftp_options.rb +26 -0
- data/tests/tc_gc_compact.rb +61 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 784cff29d3284c08afa7a76364d5643b5d08ecff8802bb7b17e047438074bc37
|
4
|
+
data.tar.gz: 336bff9af874b57b0099da1e9fbde7c9b256e831de819635bc57c947d245c70c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02c0c968ada08138391d35a09a85ccc82ab6cf0b17cb9ef71c7a496c6008f74aff97ecacae74bfe10ae958cb6c20907096459c71ab227ad0638a6381e55dd142
|
7
|
+
data.tar.gz: cb575a3ec72ac5b8f1080c7893cd49410ac4285a02044a267335846ff4981a7d3372a087101215413ff2e994ec2f8484787a8b5621a777dd5ca70dd1e8f5c9b3
|
data/README.md
CHANGED
@@ -74,7 +74,66 @@ puts "\n=== FTP Directory Listing Example ==="
|
|
74
74
|
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
|
75
75
|
list.username = 'user'
|
76
76
|
list.password = 'password'
|
77
|
-
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
|
+
|
102
|
+
list.perform
|
103
|
+
puts list.body
|
104
|
+
```
|
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
|
+
|
108
|
+
#### Full LIST directory listing
|
109
|
+
To retrieve the full `LIST` output (permissions, owner, size, timestamp, name), simply do not set `dirlistonly`:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
|
113
|
+
list.username = 'user'
|
114
|
+
list.password = 'password'
|
115
|
+
|
116
|
+
# Explicitly ensure names+metadata (LIST) rather than NLST
|
117
|
+
# list.set(:dirlistonly, 0) # optional; default is LIST for directory URLs
|
118
|
+
|
119
|
+
list.perform
|
120
|
+
puts list.body # multi-line LIST output
|
121
|
+
```
|
122
|
+
|
123
|
+
Through an HTTP proxy tunnel, the same considerations apply as the NLST example above — just omit `dirlistonly` and keep the optional EPSV/EPRT/PASV tweaks if needed:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
|
127
|
+
list.username = 'user'
|
128
|
+
list.password = 'password'
|
129
|
+
list.proxy_url = 'http://proxy.example.com:80'
|
130
|
+
list.proxy_tunnel = true
|
131
|
+
|
132
|
+
# Optional tweaks if the proxy/server combination struggles
|
133
|
+
# list.set(:ftp_use_epsv, 0)
|
134
|
+
# list.set(:ftp_use_eprt, 0)
|
135
|
+
# list.set(:ftp_skip_pasv_ip, 1)
|
136
|
+
|
78
137
|
list.perform
|
79
138
|
puts list.body
|
80
139
|
```
|
@@ -327,6 +386,8 @@ end
|
|
327
386
|
|
328
387
|
### HTTP POST form:
|
329
388
|
|
389
|
+
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, ...)`.
|
390
|
+
|
330
391
|
```ruby
|
331
392
|
c = Curl::Easy.http_post("http://my.rails.box/thing/create",
|
332
393
|
Curl::PostField.content('thing[name]', 'box'),
|
@@ -339,6 +400,19 @@ c = Curl::Easy.http_post("http://my.rails.box/thing/create",
|
|
339
400
|
c = Curl::Easy.new("http://my.rails.box/files/upload")
|
340
401
|
c.multipart_form_post = true
|
341
402
|
c.http_post(Curl::PostField.file('thing[file]', 'myfile.rb'))
|
403
|
+
|
404
|
+
### Custom request target
|
405
|
+
|
406
|
+
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:
|
407
|
+
|
408
|
+
```ruby
|
409
|
+
c = Curl::Easy.new("http://127.0.0.1:9129/methods")
|
410
|
+
c.request_target = "http://localhost:9129/methods" # absolute-form target
|
411
|
+
c.headers = { 'Host' => 'example.com' } # override Host header if needed
|
412
|
+
c.perform
|
413
|
+
```
|
414
|
+
|
415
|
+
For HTTPS, prefer `easy.resolve = ["host:443:IP"]` to keep Host/SNI/certificates aligned.
|
342
416
|
```
|
343
417
|
|
344
418
|
### Using HTTP/2
|
@@ -418,3 +492,31 @@ end
|
|
418
492
|
* `on_missing` is called when the response code is 4xx
|
419
493
|
* `on_failure` is called when the response code is 5xx
|
420
494
|
* `on_complete` is called in all cases.
|
495
|
+
|
496
|
+
### Cookies
|
497
|
+
|
498
|
+
- 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.
|
499
|
+
- 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.
|
500
|
+
- Inspect engine cookies: `easy.cookielist` returns an array of strings (Netscape or Set-Cookie format).
|
501
|
+
- 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).
|
502
|
+
- Clearing manual cookies: assign an empty string (`easy.cookies = ''`). Assigning `nil` has no effect in current versions.
|
503
|
+
|
504
|
+
Examples:
|
505
|
+
|
506
|
+
```ruby
|
507
|
+
easy = Curl::Easy.new("https://example.com")
|
508
|
+
|
509
|
+
# Use the cookie engine and persist cookies
|
510
|
+
easy.enable_cookies = true
|
511
|
+
easy.cookiejar = "/tmp/cookies.txt"
|
512
|
+
easy.perform
|
513
|
+
|
514
|
+
# Later: inspect and tweak engine cookies
|
515
|
+
p easy.cookielist
|
516
|
+
easy.cookielist = 'ALL' # clear stored cookies
|
517
|
+
|
518
|
+
# Send custom Cookie header for a single request
|
519
|
+
easy.cookies = "flag=1; session_override=abc"
|
520
|
+
easy.perform
|
521
|
+
easy.cookies = '' # clear manual Cookie header
|
522
|
+
```
|
data/ext/curb.c
CHANGED
@@ -616,6 +616,11 @@ void Init_curb_core() {
|
|
616
616
|
#if HAVE_CURLOPT_PROXYHEADER
|
617
617
|
CURB_DEFINE(CURLOPT_PROXYHEADER);
|
618
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
|
619
624
|
#if HAVE_CURLOPT_HTTP200ALIASES
|
620
625
|
CURB_DEFINE(CURLOPT_HTTP200ALIASES);
|
621
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.2.
|
32
|
-
#define CURB_VER_NUM
|
31
|
+
#define CURB_VERSION "1.2.2"
|
32
|
+
#define CURB_VER_NUM 1022
|
33
33
|
#define CURB_VER_MAJ 1
|
34
34
|
#define CURB_VER_MIN 2
|
35
|
-
#define CURB_VER_MIC
|
35
|
+
#define CURB_VER_MIC 2
|
36
36
|
#define CURB_VER_PATCH 0
|
37
37
|
|
38
38
|
|
data/ext/curb_easy.c
CHANGED
@@ -42,13 +42,34 @@ static VALUE callback_exception(VALUE unused, VALUE exception) {
|
|
42
42
|
return Qfalse;
|
43
43
|
}
|
44
44
|
|
45
|
-
/*
|
46
|
-
static size_t
|
45
|
+
/* Default body handler appends to easy.body_data buffer */
|
46
|
+
static size_t default_body_handler(char *stream,
|
47
47
|
size_t size,
|
48
48
|
size_t nmemb,
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
void *userdata) {
|
50
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)userdata;
|
51
|
+
size_t total = size * nmemb;
|
52
|
+
VALUE out = rb_easy_get("body_data");
|
53
|
+
if (NIL_P(out)) {
|
54
|
+
out = rb_easy_set("body_data", rb_str_buf_new(32768));
|
55
|
+
}
|
56
|
+
rb_str_buf_cat(out, stream, total);
|
57
|
+
return total;
|
58
|
+
}
|
59
|
+
|
60
|
+
/* Default header handler appends to easy.header_data buffer */
|
61
|
+
static size_t default_header_handler(char *stream,
|
62
|
+
size_t size,
|
63
|
+
size_t nmemb,
|
64
|
+
void *userdata) {
|
65
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)userdata;
|
66
|
+
size_t total = size * nmemb;
|
67
|
+
VALUE out = rb_easy_get("header_data");
|
68
|
+
if (NIL_P(out)) {
|
69
|
+
out = rb_easy_set("header_data", rb_str_buf_new(16384));
|
70
|
+
}
|
71
|
+
rb_str_buf_cat(out, stream, total);
|
72
|
+
return total;
|
52
73
|
}
|
53
74
|
|
54
75
|
// size_t function( void *ptr, size_t size, size_t nmemb, void *stream);
|
@@ -172,11 +193,16 @@ static VALUE call_progress_handler(VALUE ary) {
|
|
172
193
|
rb_ary_entry(ary, 4)); // rb_float_new(ulnow));
|
173
194
|
}
|
174
195
|
|
175
|
-
static int proc_progress_handler(
|
196
|
+
static int proc_progress_handler(void *clientp,
|
176
197
|
double dltotal,
|
177
198
|
double dlnow,
|
178
199
|
double ultotal,
|
179
200
|
double ulnow) {
|
201
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
|
202
|
+
VALUE proc = rb_easy_get("progress_proc");
|
203
|
+
if (proc == Qnil) {
|
204
|
+
return 0;
|
205
|
+
}
|
180
206
|
VALUE procret;
|
181
207
|
VALUE callargs = rb_ary_new2(5);
|
182
208
|
|
@@ -205,7 +231,12 @@ static int proc_debug_handler(CURL *curl,
|
|
205
231
|
curl_infotype type,
|
206
232
|
char *data,
|
207
233
|
size_t data_len,
|
208
|
-
|
234
|
+
void *clientp) {
|
235
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
|
236
|
+
VALUE proc = rb_easy_get("debug_proc");
|
237
|
+
if (proc == Qnil) {
|
238
|
+
return 0;
|
239
|
+
}
|
209
240
|
VALUE callargs = rb_ary_new2(3);
|
210
241
|
rb_ary_store(callargs, 0, proc);
|
211
242
|
rb_ary_store(callargs, 1, INT2NUM(type));
|
@@ -225,6 +256,30 @@ void curl_easy_mark(ruby_curl_easy *rbce) {
|
|
225
256
|
}
|
226
257
|
|
227
258
|
static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
|
259
|
+
if (!rbce) {
|
260
|
+
return;
|
261
|
+
}
|
262
|
+
|
263
|
+
if (!NIL_P(rbce->multi)) {
|
264
|
+
VALUE multi_val = rbce->multi;
|
265
|
+
ruby_curl_multi *rbcm = NULL;
|
266
|
+
|
267
|
+
rbce->multi = Qnil;
|
268
|
+
|
269
|
+
if (!NIL_P(multi_val) && RB_TYPE_P(multi_val, T_DATA)) {
|
270
|
+
Data_Get_Struct(multi_val, ruby_curl_multi, rbcm);
|
271
|
+
if (rbcm) {
|
272
|
+
/* Best-effort: ensure the handle is detached from the multi to
|
273
|
+
* avoid libcurl retaining a dangling pointer to a soon-to-be
|
274
|
+
* cleaned-up easy handle. We cannot raise from GC, so ignore errors. */
|
275
|
+
if (rbcm->handle && rbce->curl) {
|
276
|
+
curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
277
|
+
}
|
278
|
+
rb_curl_multi_forget_easy(rbcm, rbce);
|
279
|
+
}
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
228
283
|
if (rbce->curl_headers) {
|
229
284
|
curl_slist_free_all(rbce->curl_headers);
|
230
285
|
}
|
@@ -255,6 +310,8 @@ static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
|
|
255
310
|
curl_easy_cleanup(rbce->curl);
|
256
311
|
rbce->curl = NULL;
|
257
312
|
}
|
313
|
+
|
314
|
+
rbce->self = Qnil;
|
258
315
|
}
|
259
316
|
|
260
317
|
void curl_easy_free(ruby_curl_easy *rbce) {
|
@@ -270,6 +327,7 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
|
|
270
327
|
|
271
328
|
memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
|
272
329
|
|
330
|
+
rbce->self = Qnil;
|
273
331
|
rbce->curl_headers = NULL;
|
274
332
|
rbce->curl_proxy_headers = NULL;
|
275
333
|
rbce->curl_ftp_commands = NULL;
|
@@ -310,6 +368,7 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
|
|
310
368
|
rbce->verbose = 0;
|
311
369
|
rbce->multipart_form_post = 0;
|
312
370
|
rbce->enable_cookies = 0;
|
371
|
+
rbce->cookielist_engine_enabled = 0;
|
313
372
|
rbce->ignore_content_length = 0;
|
314
373
|
rbce->callback_active = 0;
|
315
374
|
rbce->last_result = 0;
|
@@ -360,13 +419,14 @@ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
|
|
360
419
|
rbce->opts = Qnil;
|
361
420
|
|
362
421
|
ruby_curl_easy_zero(rbce);
|
422
|
+
rbce->self = self;
|
363
423
|
|
364
424
|
curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
|
365
425
|
|
366
426
|
rb_easy_set("url", url);
|
367
427
|
|
368
428
|
/* set the pointer to the curl handle */
|
369
|
-
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)
|
429
|
+
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
|
370
430
|
if (ecode != CURLE_OK) {
|
371
431
|
raise_curl_easy_error_exception(ecode);
|
372
432
|
}
|
@@ -431,7 +491,11 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
|
|
431
491
|
/* Set the error buffer on the new curl handle using the new err_buf */
|
432
492
|
curl_easy_setopt(newrbce->curl, CURLOPT_ERRORBUFFER, newrbce->err_buf);
|
433
493
|
|
434
|
-
|
494
|
+
VALUE clone = Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, newrbce);
|
495
|
+
newrbce->self = clone;
|
496
|
+
curl_easy_setopt(newrbce->curl, CURLOPT_PRIVATE, (void*)newrbce);
|
497
|
+
|
498
|
+
return clone;
|
435
499
|
}
|
436
500
|
|
437
501
|
/*
|
@@ -463,9 +527,10 @@ static VALUE ruby_curl_easy_close(VALUE self) {
|
|
463
527
|
rbce->multi = Qnil;
|
464
528
|
|
465
529
|
ruby_curl_easy_zero(rbce);
|
530
|
+
rbce->self = self;
|
466
531
|
|
467
532
|
/* give the new curl handle a reference back to the ruby object */
|
468
|
-
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)
|
533
|
+
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
|
469
534
|
if (ecode != CURLE_OK) {
|
470
535
|
raise_curl_easy_error_exception(ecode);
|
471
536
|
}
|
@@ -499,11 +564,12 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
|
|
499
564
|
|
500
565
|
curl_easy_reset(rbce->curl);
|
501
566
|
ruby_curl_easy_zero(rbce);
|
567
|
+
rbce->self = self;
|
502
568
|
|
503
569
|
curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
|
504
570
|
|
505
571
|
/* reset clobbers the private setting, so reset it to self */
|
506
|
-
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)
|
572
|
+
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
|
507
573
|
if (ecode != CURLE_OK) {
|
508
574
|
raise_curl_easy_error_exception(ecode);
|
509
575
|
}
|
@@ -664,7 +730,16 @@ static VALUE ruby_curl_easy_proxypwd_get(VALUE self) {
|
|
664
730
|
* call-seq:
|
665
731
|
* easy.cookies => "name1=content1; name2=content2;"
|
666
732
|
*
|
667
|
-
* Obtain the
|
733
|
+
* Obtain the manually set Cookie header string for this Curl::Easy instance.
|
734
|
+
*
|
735
|
+
* Notes:
|
736
|
+
* - This corresponds to libcurl's CURLOPT_COOKIE and only affects the outgoing
|
737
|
+
* Cookie request header. It does NOT modify the internal libcurl cookie engine
|
738
|
+
* that stores cookies received via Set-Cookie.
|
739
|
+
* - To inspect or modify cookies stored in the cookie engine, use
|
740
|
+
* +easy.cookielist+ (getter) and +easy.cookielist=+ or +easy.set(:cookielist, ...)+ (setter).
|
741
|
+
* - To clear a previously set manual Cookie header, assign an empty string.
|
742
|
+
* Assigning +nil+ currently has no effect.
|
668
743
|
*/
|
669
744
|
static VALUE ruby_curl_easy_cookies_get(VALUE self) {
|
670
745
|
CURB_OBJECT_HGETTER(ruby_curl_easy, cookies);
|
@@ -674,7 +749,8 @@ static VALUE ruby_curl_easy_cookies_get(VALUE self) {
|
|
674
749
|
* call-seq:
|
675
750
|
* easy.cookiefile => string
|
676
751
|
*
|
677
|
-
* Obtain the cookiefile
|
752
|
+
* Obtain the cookiefile path for this Curl::Easy instance (used to load cookies when the
|
753
|
+
* cookie engine is enabled).
|
678
754
|
*/
|
679
755
|
static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
|
680
756
|
CURB_OBJECT_HGETTER(ruby_curl_easy, cookiefile);
|
@@ -684,7 +760,8 @@ static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
|
|
684
760
|
* call-seq:
|
685
761
|
* easy.cookiejar => string
|
686
762
|
*
|
687
|
-
* Obtain the cookiejar
|
763
|
+
* Obtain the cookiejar path for this Curl::Easy instance (used to persist cookies when the
|
764
|
+
* cookie engine is enabled).
|
688
765
|
*/
|
689
766
|
static VALUE ruby_curl_easy_cookiejar_get(VALUE self) {
|
690
767
|
CURB_OBJECT_HGETTER(ruby_curl_easy, cookiejar);
|
@@ -984,7 +1061,15 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
|
|
984
1061
|
* call-seq:
|
985
1062
|
* easy.ftp_commands = ["CWD /", "MKD directory"] => ["CWD /", ...]
|
986
1063
|
*
|
987
|
-
* Explicitly sets the list of commands to execute on the FTP server when calling perform
|
1064
|
+
* Explicitly sets the list of commands to execute on the FTP server when calling perform.
|
1065
|
+
*
|
1066
|
+
* NOTE:
|
1067
|
+
* - This maps to libcurl CURLOPT_QUOTE; it sends commands on the control connection.
|
1068
|
+
* - Do not include data-transfer commands like LIST/NLST/RETR/STOR here. libcurl does not
|
1069
|
+
* parse PASV/EPSV replies from QUOTE commands and will not establish the required data
|
1070
|
+
* connection. For directory listings, set CURLOPT_DIRLISTONLY (via `easy.set(:dirlistonly, true)`)
|
1071
|
+
* and request an FTP directory URL (e.g. "ftp://host/path/") so libcurl manages PASV/EPSV
|
1072
|
+
* and the data connection for you.
|
988
1073
|
*/
|
989
1074
|
static VALUE ruby_curl_easy_ftp_commands_set(VALUE self, VALUE ftp_commands) {
|
990
1075
|
CURB_OBJECT_HSETTER(ruby_curl_easy, ftp_commands);
|
@@ -1893,7 +1978,12 @@ static VALUE ruby_curl_easy_multipart_form_post_q(VALUE self) {
|
|
1893
1978
|
* easy.enable_cookies = boolean => boolean
|
1894
1979
|
*
|
1895
1980
|
* Configure whether the libcurl cookie engine is enabled for this Curl::Easy
|
1896
|
-
* instance.
|
1981
|
+
* instance. When enabled, cookies received via Set-Cookie are stored by libcurl
|
1982
|
+
* and automatically sent on subsequent matching requests. Use +easy.cookiefile+
|
1983
|
+
* to load cookies and +easy.cookiejar+ to persist them.
|
1984
|
+
*
|
1985
|
+
* This setting is independent from the manual Cookie header set via +easy.cookies+.
|
1986
|
+
* The manual header is additive and can be cleared by assigning an empty string.
|
1897
1987
|
*/
|
1898
1988
|
static VALUE ruby_curl_easy_enable_cookies_set(VALUE self, VALUE enable_cookies)
|
1899
1989
|
{
|
@@ -2346,9 +2436,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2346
2436
|
/* clear out the body_data if it was set */
|
2347
2437
|
rb_easy_del("body_data");
|
2348
2438
|
} else {
|
2349
|
-
|
2350
|
-
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&
|
2351
|
-
curl_easy_setopt(curl, CURLOPT_WRITEDATA,
|
2439
|
+
rb_easy_set("body_data", rb_str_buf_new(32768));
|
2440
|
+
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&default_body_handler);
|
2441
|
+
curl_easy_setopt(curl, CURLOPT_WRITEDATA, rbce);
|
2352
2442
|
}
|
2353
2443
|
|
2354
2444
|
if (!rb_easy_nil("header_proc")) {
|
@@ -2357,9 +2447,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2357
2447
|
/* clear out the header_data if it was set */
|
2358
2448
|
rb_easy_del("header_data");
|
2359
2449
|
} else {
|
2360
|
-
|
2361
|
-
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&
|
2362
|
-
curl_easy_setopt(curl, CURLOPT_HEADERDATA,
|
2450
|
+
rb_easy_set("header_data", rb_str_buf_new(16384));
|
2451
|
+
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&default_header_handler);
|
2452
|
+
curl_easy_setopt(curl, CURLOPT_HEADERDATA, rbce);
|
2363
2453
|
}
|
2364
2454
|
|
2365
2455
|
/* encoding */
|
@@ -2370,20 +2460,21 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2370
2460
|
// progress and debug procs
|
2371
2461
|
if (!rb_easy_nil("progress_proc")) {
|
2372
2462
|
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, (curl_progress_callback)&proc_progress_handler);
|
2373
|
-
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
|
2463
|
+
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
|
2374
2464
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
2375
2465
|
} else {
|
2376
2466
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
|
2467
|
+
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
|
2377
2468
|
}
|
2378
2469
|
|
2379
2470
|
if (!rb_easy_nil("debug_proc")) {
|
2380
2471
|
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, (curl_debug_callback)&proc_debug_handler);
|
2381
|
-
curl_easy_setopt(curl, CURLOPT_DEBUGDATA,
|
2472
|
+
curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce);
|
2382
2473
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
|
2383
2474
|
} else {
|
2384
2475
|
// have to remove handler to re-enable standard verbosity
|
2385
2476
|
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL);
|
2386
|
-
curl_easy_setopt(curl, CURLOPT_DEBUGDATA,
|
2477
|
+
curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce);
|
2387
2478
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, rbce->verbose);
|
2388
2479
|
}
|
2389
2480
|
|
@@ -2391,7 +2482,14 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2391
2482
|
|
2392
2483
|
curl_easy_setopt(curl, CURLOPT_HEADER, rbce->header_in_body);
|
2393
2484
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, rbce->follow_location);
|
2485
|
+
#if LIBCURL_VERSION_NUM == 0x081000
|
2486
|
+
/* Work around 8.16.0 regression that clamps -1 (infinite) to zero */
|
2487
|
+
if (rbce->max_redirs >= 0) {
|
2488
|
+
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs);
|
2489
|
+
}
|
2490
|
+
#else
|
2394
2491
|
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs);
|
2492
|
+
#endif
|
2395
2493
|
|
2396
2494
|
curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, rbce->proxy_tunnel);
|
2397
2495
|
curl_easy_setopt(curl, CURLOPT_FILETIME, rbce->fetch_file_time);
|
@@ -2486,9 +2584,8 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2486
2584
|
#endif
|
2487
2585
|
}
|
2488
2586
|
|
2489
|
-
/* Set up HTTP cookie handling if necessary
|
2490
|
-
|
2491
|
-
*/
|
2587
|
+
/* Set up HTTP cookie handling if necessary */
|
2588
|
+
/* Enable/attach cookie engine if requested, or implicitly via COOKIELIST usage */
|
2492
2589
|
if (rbce->enable_cookies) {
|
2493
2590
|
if (!rb_easy_nil("cookiejar")) {
|
2494
2591
|
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, rb_easy_get_str("cookiejar"));
|
@@ -2499,6 +2596,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2499
2596
|
} else {
|
2500
2597
|
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* "" = magic to just enable */
|
2501
2598
|
}
|
2599
|
+
} else if (rbce->cookielist_engine_enabled) {
|
2600
|
+
/* Ensure cookie engine is enabled even if enable_cookies? is false. */
|
2601
|
+
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
|
2502
2602
|
}
|
2503
2603
|
|
2504
2604
|
if (!rb_easy_nil("cookies")) {
|
@@ -3595,6 +3695,12 @@ static VALUE ruby_curl_easy_num_connects_get(VALUE self) {
|
|
3595
3695
|
* Returned strings are in Netscape cookiejar format or in Set-Cookie format.
|
3596
3696
|
* Since 7.43.0 cookies in the Set-Cookie format without a domain name are not exported.
|
3597
3697
|
*
|
3698
|
+
* To modify the cookie engine (add/replace/remove), use +easy.cookielist= string+
|
3699
|
+
* or +easy.set(:cookielist, string)+ with one of the following accepted inputs:
|
3700
|
+
* - A Set-Cookie style header string: "Set-Cookie: name=value; Domain=example.com; Path=/; Expires=..."
|
3701
|
+
* - One or more lines in Netscape cookie file format (tab-separated fields)
|
3702
|
+
* - Special commands: "ALL" (clear all), "SESS" (remove session cookies), "FLUSH" (write to jar), "RELOAD" (reload from file)
|
3703
|
+
*
|
3598
3704
|
* @see https://curl.se/libcurl/c/CURLINFO_COOKIELIST.html option <code>CURLINFO_COOKIELIST</code> of
|
3599
3705
|
* <code>curl_easy_getopt(3)</code> to see how libcurl behaves.
|
3600
3706
|
* @note requires libcurl 7.14.1 or higher, otherwise +-1+ is always returned
|
@@ -3656,7 +3762,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
|
|
3656
3762
|
return Qnil;
|
3657
3763
|
}
|
3658
3764
|
#else
|
3659
|
-
rb_warn("Installed libcurl is too old to support
|
3765
|
+
rb_warn("Installed libcurl is too old to support ftp_entry_path");
|
3660
3766
|
return Qnil;
|
3661
3767
|
#endif
|
3662
3768
|
}
|
@@ -3815,9 +3921,72 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
|
|
3815
3921
|
VALUE cookiejar = val;
|
3816
3922
|
CURB_OBJECT_HSETTER(ruby_curl_easy, cookiejar);
|
3817
3923
|
} break;
|
3924
|
+
#if HAVE_CURLOPT_REQUEST_TARGET
|
3925
|
+
case CURLOPT_REQUEST_TARGET: {
|
3926
|
+
/* Forward request-target directly to libcurl as a string. */
|
3927
|
+
curl_easy_setopt(rbce->curl, CURLOPT_REQUEST_TARGET, NIL_P(val) ? NULL : StringValueCStr(val));
|
3928
|
+
} break;
|
3929
|
+
#endif
|
3818
3930
|
case CURLOPT_TCP_NODELAY: {
|
3819
3931
|
curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val));
|
3820
3932
|
} break;
|
3933
|
+
/* FTP-specific toggles */
|
3934
|
+
#if HAVE_CURLOPT_DIRLISTONLY
|
3935
|
+
case CURLOPT_DIRLISTONLY: {
|
3936
|
+
int type = rb_type(val);
|
3937
|
+
VALUE value;
|
3938
|
+
if (type == T_TRUE) {
|
3939
|
+
value = rb_int_new(1);
|
3940
|
+
} else if (type == T_FALSE) {
|
3941
|
+
value = rb_int_new(0);
|
3942
|
+
} else {
|
3943
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3944
|
+
}
|
3945
|
+
curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, NUM2LONG(value));
|
3946
|
+
} break;
|
3947
|
+
#endif
|
3948
|
+
#if HAVE_CURLOPT_FTP_USE_EPSV
|
3949
|
+
case CURLOPT_FTP_USE_EPSV: {
|
3950
|
+
int type = rb_type(val);
|
3951
|
+
VALUE value;
|
3952
|
+
if (type == T_TRUE) {
|
3953
|
+
value = rb_int_new(1);
|
3954
|
+
} else if (type == T_FALSE) {
|
3955
|
+
value = rb_int_new(0);
|
3956
|
+
} else {
|
3957
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3958
|
+
}
|
3959
|
+
curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, NUM2LONG(value));
|
3960
|
+
} break;
|
3961
|
+
#endif
|
3962
|
+
#if HAVE_CURLOPT_FTP_USE_EPRT
|
3963
|
+
case CURLOPT_FTP_USE_EPRT: {
|
3964
|
+
int type = rb_type(val);
|
3965
|
+
VALUE value;
|
3966
|
+
if (type == T_TRUE) {
|
3967
|
+
value = rb_int_new(1);
|
3968
|
+
} else if (type == T_FALSE) {
|
3969
|
+
value = rb_int_new(0);
|
3970
|
+
} else {
|
3971
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3972
|
+
}
|
3973
|
+
curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, NUM2LONG(value));
|
3974
|
+
} break;
|
3975
|
+
#endif
|
3976
|
+
#if HAVE_CURLOPT_FTP_SKIP_PASV_IP
|
3977
|
+
case CURLOPT_FTP_SKIP_PASV_IP: {
|
3978
|
+
int type = rb_type(val);
|
3979
|
+
VALUE value;
|
3980
|
+
if (type == T_TRUE) {
|
3981
|
+
value = rb_int_new(1);
|
3982
|
+
} else if (type == T_FALSE) {
|
3983
|
+
value = rb_int_new(0);
|
3984
|
+
} else {
|
3985
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3986
|
+
}
|
3987
|
+
curl_easy_setopt(rbce->curl, CURLOPT_FTP_SKIP_PASV_IP, NUM2LONG(value));
|
3988
|
+
} break;
|
3989
|
+
#endif
|
3821
3990
|
case CURLOPT_RANGE: {
|
3822
3991
|
curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val));
|
3823
3992
|
} break;
|
@@ -3895,8 +4064,23 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
|
|
3895
4064
|
#endif
|
3896
4065
|
#if HAVE_CURLOPT_COOKIELIST
|
3897
4066
|
case CURLOPT_COOKIELIST: {
|
4067
|
+
/* Forward to libcurl */
|
3898
4068
|
curl_easy_setopt(rbce->curl, CURLOPT_COOKIELIST, StringValueCStr(val));
|
3899
|
-
|
4069
|
+
/* Track whether the cookie engine should be enabled for requests.
|
4070
|
+
* According to libcurl docs, CURLOPT_COOKIELIST also enables the cookie engine
|
4071
|
+
* when provided with a non-command string. Some environments may still require
|
4072
|
+
* an explicit "enable" via CURLOPT_COOKIEFILE="" to send cookies on requests.
|
4073
|
+
* We do that in the perform setup when this flag is set.
|
4074
|
+
*/
|
4075
|
+
if (RB_TYPE_P(val, T_STRING)) {
|
4076
|
+
const char *s = StringValueCStr(val);
|
4077
|
+
if (!(strcmp(s, "ALL") == 0 || strcmp(s, "SESS") == 0 || strcmp(s, "FLUSH") == 0 || strcmp(s, "RELOAD") == 0)) {
|
4078
|
+
rbce->cookielist_engine_enabled = 1;
|
4079
|
+
}
|
4080
|
+
} else {
|
4081
|
+
/* Non-string values are unexpected; be conservative and do not enable. */
|
4082
|
+
}
|
4083
|
+
} break;
|
3900
4084
|
#endif
|
3901
4085
|
#if HAVE_CURLOPT_PROXY_SSL_VERIFYHOST
|
3902
4086
|
case CURLOPT_PROXY_SSL_VERIFYHOST:
|
data/ext/curb_easy.h
CHANGED
@@ -39,6 +39,7 @@ typedef struct {
|
|
39
39
|
/* Buffer for error details from CURLOPT_ERRORBUFFER */
|
40
40
|
char err_buf[CURL_ERROR_SIZE];
|
41
41
|
|
42
|
+
VALUE self; /* owning Ruby object */
|
42
43
|
VALUE opts; /* rather then allocate everything we might need to store, allocate a Hash and only store objects we actually use... */
|
43
44
|
VALUE multi; /* keep a multi handle alive for each easy handle not being used by a multi handle. This improves easy performance when not within a multi context */
|
44
45
|
|
@@ -77,6 +78,7 @@ typedef struct {
|
|
77
78
|
char verbose;
|
78
79
|
char multipart_form_post;
|
79
80
|
char enable_cookies;
|
81
|
+
char cookielist_engine_enabled; /* track if CURLOPT_COOKIELIST was used with a non-command to enable engine */
|
80
82
|
char ignore_content_length;
|
81
83
|
char callback_active;
|
82
84
|
|
data/ext/curb_multi.c
CHANGED
@@ -76,6 +76,10 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
|
|
76
76
|
static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
|
77
77
|
static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
|
78
78
|
|
79
|
+
static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg);
|
80
|
+
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm);
|
81
|
+
static void curl_multi_mark(void *ptr);
|
82
|
+
|
79
83
|
static VALUE callback_exception(VALUE did_raise, VALUE exception) {
|
80
84
|
// TODO: we could have an option to enable exception reporting
|
81
85
|
/* VALUE ret = rb_funcall(exception, rb_intern("message"), 0);
|
@@ -97,8 +101,67 @@ static VALUE callback_exception(VALUE did_raise, VALUE exception) {
|
|
97
101
|
return exception;
|
98
102
|
}
|
99
103
|
|
104
|
+
static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg) {
|
105
|
+
ruby_curl_multi *rbcm = (ruby_curl_multi *)arg;
|
106
|
+
VALUE easy = (VALUE)val;
|
107
|
+
ruby_curl_easy *rbce = NULL;
|
108
|
+
|
109
|
+
if (RB_TYPE_P(easy, T_DATA)) {
|
110
|
+
Data_Get_Struct(easy, ruby_curl_easy, rbce);
|
111
|
+
}
|
112
|
+
|
113
|
+
if (!rbce) {
|
114
|
+
return ST_CONTINUE;
|
115
|
+
}
|
116
|
+
|
117
|
+
if (rbcm && rbcm->handle && rbce->curl) {
|
118
|
+
curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
119
|
+
}
|
120
|
+
|
121
|
+
rbce->multi = Qnil;
|
122
|
+
|
123
|
+
return ST_CONTINUE;
|
124
|
+
}
|
125
|
+
|
126
|
+
void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr) {
|
127
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
128
|
+
|
129
|
+
if (!rbcm || !rbce || !rbcm->attached) {
|
130
|
+
return;
|
131
|
+
}
|
132
|
+
|
133
|
+
st_data_t key = (st_data_t)rbce;
|
134
|
+
st_delete(rbcm->attached, &key, NULL);
|
135
|
+
}
|
136
|
+
|
137
|
+
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm) {
|
138
|
+
if (!rbcm || !rbcm->attached) {
|
139
|
+
return;
|
140
|
+
}
|
141
|
+
|
142
|
+
st_table *attached = rbcm->attached;
|
143
|
+
rbcm->attached = NULL;
|
144
|
+
|
145
|
+
st_foreach(attached, detach_easy_entry, (st_data_t)rbcm);
|
146
|
+
|
147
|
+
st_free_table(attached);
|
148
|
+
|
149
|
+
rbcm->active = 0;
|
150
|
+
rbcm->running = 0;
|
151
|
+
}
|
152
|
+
|
100
153
|
void curl_multi_free(ruby_curl_multi *rbcm) {
|
101
|
-
|
154
|
+
if (!rbcm) {
|
155
|
+
return;
|
156
|
+
}
|
157
|
+
|
158
|
+
rb_curl_multi_detach_all(rbcm);
|
159
|
+
|
160
|
+
if (rbcm->handle) {
|
161
|
+
curl_multi_cleanup(rbcm->handle);
|
162
|
+
rbcm->handle = NULL;
|
163
|
+
}
|
164
|
+
|
102
165
|
free(rbcm);
|
103
166
|
}
|
104
167
|
|
@@ -110,6 +173,18 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
110
173
|
|
111
174
|
rbcm->active = 0;
|
112
175
|
rbcm->running = 0;
|
176
|
+
|
177
|
+
if (rbcm->attached) {
|
178
|
+
st_free_table(rbcm->attached);
|
179
|
+
rbcm->attached = NULL;
|
180
|
+
}
|
181
|
+
|
182
|
+
rbcm->attached = st_init_numtable();
|
183
|
+
if (!rbcm->attached) {
|
184
|
+
curl_multi_cleanup(rbcm->handle);
|
185
|
+
rbcm->handle = NULL;
|
186
|
+
rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table");
|
187
|
+
}
|
113
188
|
}
|
114
189
|
|
115
190
|
/*
|
@@ -124,6 +199,8 @@ VALUE ruby_curl_multi_new(VALUE klass) {
|
|
124
199
|
rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Multi");
|
125
200
|
}
|
126
201
|
|
202
|
+
MEMZERO(rbcm, ruby_curl_multi, 1);
|
203
|
+
|
127
204
|
ruby_curl_multi_init(rbcm);
|
128
205
|
|
129
206
|
/*
|
@@ -131,8 +208,8 @@ VALUE ruby_curl_multi_new(VALUE klass) {
|
|
131
208
|
* If your structure references other Ruby objects, then your mark function needs to
|
132
209
|
* identify these objects using rb_gc_mark(value). If the structure doesn't reference
|
133
210
|
* other Ruby objects, you can simply pass 0 as a function pointer.
|
134
|
-
|
135
|
-
return Data_Wrap_Struct(klass,
|
211
|
+
*/
|
212
|
+
return Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
|
136
213
|
}
|
137
214
|
|
138
215
|
/*
|
@@ -292,6 +369,17 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
292
369
|
* If this number is not correct, the next call to curl_multi_perform will correct it. */
|
293
370
|
rbcm->running++;
|
294
371
|
|
372
|
+
if (!rbcm->attached) {
|
373
|
+
rbcm->attached = st_init_numtable();
|
374
|
+
if (!rbcm->attached) {
|
375
|
+
curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
376
|
+
ruby_curl_easy_cleanup(easy, rbce);
|
377
|
+
rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table");
|
378
|
+
}
|
379
|
+
}
|
380
|
+
|
381
|
+
st_insert(rbcm->attached, (st_data_t)rbce, (st_data_t)easy);
|
382
|
+
|
295
383
|
/* track a reference to associated multi handle */
|
296
384
|
rbce->multi = self;
|
297
385
|
|
@@ -332,9 +420,13 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
|
|
332
420
|
raise_curl_multi_error_exception(result);
|
333
421
|
}
|
334
422
|
|
335
|
-
rbcm->active
|
423
|
+
if (rbcm->active > 0) {
|
424
|
+
rbcm->active--;
|
425
|
+
}
|
336
426
|
|
337
427
|
ruby_curl_easy_cleanup( easy, rbce );
|
428
|
+
|
429
|
+
rb_curl_multi_forget_easy(rbcm, rbce);
|
338
430
|
}
|
339
431
|
|
340
432
|
// on_success, on_failure, on_complete
|
@@ -362,15 +454,56 @@ static void flush_stderr_if_any(ruby_curl_easy *rbce) {
|
|
362
454
|
}
|
363
455
|
}
|
364
456
|
|
457
|
+
/* Helper to locate the Ruby Easy VALUE from the attached table using the
|
458
|
+
* underlying CURL* handle when CURLINFO_PRIVATE is unavailable or stale. */
|
459
|
+
struct find_easy_ctx { CURL *handle; VALUE easy; };
|
460
|
+
static int find_easy_by_handle_i(st_data_t key, st_data_t val, st_data_t arg) {
|
461
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
462
|
+
struct find_easy_ctx *ctx = (struct find_easy_ctx *)arg;
|
463
|
+
if (rbce && rbce->curl == ctx->handle) {
|
464
|
+
ctx->easy = (VALUE)val;
|
465
|
+
return ST_STOP;
|
466
|
+
}
|
467
|
+
return ST_CONTINUE;
|
468
|
+
}
|
469
|
+
|
470
|
+
static VALUE find_easy_by_handle(ruby_curl_multi *rbcm, CURL *easy_handle) {
|
471
|
+
if (!rbcm || !rbcm->attached) return Qnil;
|
472
|
+
struct find_easy_ctx ctx; ctx.handle = easy_handle; ctx.easy = Qnil;
|
473
|
+
st_foreach(rbcm->attached, find_easy_by_handle_i, (st_data_t)&ctx);
|
474
|
+
return ctx.easy;
|
475
|
+
}
|
476
|
+
|
365
477
|
static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
|
366
478
|
long response_code = -1;
|
367
|
-
VALUE easy;
|
479
|
+
VALUE easy = Qnil;
|
368
480
|
ruby_curl_easy *rbce = NULL;
|
369
481
|
VALUE callargs;
|
482
|
+
ruby_curl_multi *rbcm = NULL;
|
370
483
|
|
371
|
-
|
484
|
+
Data_Get_Struct(self, ruby_curl_multi, rbcm);
|
372
485
|
|
373
|
-
|
486
|
+
/* Try to recover the ruby_curl_easy pointer stored via CURLOPT_PRIVATE. */
|
487
|
+
CURLcode private_rc = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&rbce);
|
488
|
+
if (private_rc == CURLE_OK && rbce) {
|
489
|
+
easy = rbce->self;
|
490
|
+
}
|
491
|
+
|
492
|
+
/* If PRIVATE is unavailable or invalid, fall back to scanning attachments. */
|
493
|
+
if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA)) {
|
494
|
+
easy = find_easy_by_handle(rbcm, easy_handle);
|
495
|
+
if (!NIL_P(easy) && RB_TYPE_P(easy, T_DATA)) {
|
496
|
+
Data_Get_Struct(easy, ruby_curl_easy, rbce);
|
497
|
+
}
|
498
|
+
}
|
499
|
+
|
500
|
+
/* If we still cannot identify the easy handle, remove it and bail. */
|
501
|
+
if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA) || !rbce) {
|
502
|
+
if (rbcm && rbcm->handle && easy_handle) {
|
503
|
+
curl_multi_remove_handle(rbcm->handle, easy_handle);
|
504
|
+
}
|
505
|
+
return;
|
506
|
+
}
|
374
507
|
|
375
508
|
rbce->last_result = result; /* save the last easy result code */
|
376
509
|
|
@@ -390,10 +523,6 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
390
523
|
/* Flush again after removal to cover any last buffered data. */
|
391
524
|
flush_stderr_if_any(rbce);
|
392
525
|
|
393
|
-
if (ecode != 0) {
|
394
|
-
raise_curl_easy_error_exception(ecode);
|
395
|
-
}
|
396
|
-
|
397
526
|
VALUE did_raise = rb_hash_new();
|
398
527
|
|
399
528
|
if (!rb_easy_nil("complete_proc")) {
|
@@ -1158,11 +1287,32 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
1158
1287
|
VALUE ruby_curl_multi_close(VALUE self) {
|
1159
1288
|
ruby_curl_multi *rbcm;
|
1160
1289
|
Data_Get_Struct(self, ruby_curl_multi, rbcm);
|
1161
|
-
|
1290
|
+
rb_curl_multi_detach_all(rbcm);
|
1291
|
+
|
1292
|
+
if (rbcm->handle) {
|
1293
|
+
curl_multi_cleanup(rbcm->handle);
|
1294
|
+
rbcm->handle = NULL;
|
1295
|
+
}
|
1296
|
+
|
1162
1297
|
ruby_curl_multi_init(rbcm);
|
1163
1298
|
return self;
|
1164
1299
|
}
|
1165
1300
|
|
1301
|
+
/* GC mark: keep attached easy VALUEs alive while associated. */
|
1302
|
+
static int mark_attached_i(st_data_t key, st_data_t val, st_data_t arg) {
|
1303
|
+
VALUE easy = (VALUE)val;
|
1304
|
+
if (!NIL_P(easy)) rb_gc_mark(easy);
|
1305
|
+
return ST_CONTINUE;
|
1306
|
+
}
|
1307
|
+
|
1308
|
+
static void curl_multi_mark(void *ptr) {
|
1309
|
+
ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr;
|
1310
|
+
if (!rbcm) return;
|
1311
|
+
if (rbcm->attached) {
|
1312
|
+
st_foreach(rbcm->attached, mark_attached_i, (st_data_t)0);
|
1313
|
+
}
|
1314
|
+
}
|
1315
|
+
|
1166
1316
|
|
1167
1317
|
/* =================== INIT LIB =====================*/
|
1168
1318
|
void init_curb_multi() {
|
data/ext/curb_multi.h
CHANGED
@@ -8,18 +8,21 @@
|
|
8
8
|
#define __CURB_MULTI_H
|
9
9
|
|
10
10
|
#include "curb.h"
|
11
|
-
#include "curb_easy.h"
|
12
11
|
#include <curl/multi.h>
|
13
12
|
|
13
|
+
struct st_table;
|
14
|
+
|
14
15
|
typedef struct {
|
15
16
|
int active;
|
16
17
|
int running;
|
17
18
|
CURLM *handle;
|
19
|
+
struct st_table *attached;
|
18
20
|
} ruby_curl_multi;
|
19
21
|
|
20
22
|
extern VALUE cCurlMulti;
|
21
23
|
void init_curb_multi();
|
22
24
|
VALUE ruby_curl_multi_new(VALUE klass);
|
25
|
+
void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr);
|
23
26
|
|
24
27
|
|
25
28
|
#endif
|
data/ext/extconf.rb
CHANGED
@@ -500,6 +500,7 @@ have_constant "curlusessl_try"
|
|
500
500
|
have_constant "curlusessl_control"
|
501
501
|
have_constant "curlusessl_all"
|
502
502
|
have_constant "curlopt_resolve"
|
503
|
+
have_constant "curlopt_request_target"
|
503
504
|
have_constant "curlopt_sslcert"
|
504
505
|
have_constant "curlopt_sslcerttype"
|
505
506
|
have_constant "curlopt_sslkey"
|
data/lib/curl/easy.rb
CHANGED
@@ -158,6 +158,22 @@ module Curl
|
|
158
158
|
set :proxy, url
|
159
159
|
end
|
160
160
|
|
161
|
+
#
|
162
|
+
# call-seq:
|
163
|
+
# easy.request_target = string => string
|
164
|
+
#
|
165
|
+
# Set the request-target used in the HTTP request line (libcurl CURLOPT_REQUEST_TARGET).
|
166
|
+
# Useful for absolute-form request targets (e.g., when speaking to proxies) or
|
167
|
+
# special targets like "*" (OPTIONS *). Requires libcurl with CURLOPT_REQUEST_TARGET support.
|
168
|
+
#
|
169
|
+
def request_target=(value)
|
170
|
+
if Curl.const_defined?(:CURLOPT_REQUEST_TARGET)
|
171
|
+
set :request_target, value
|
172
|
+
else
|
173
|
+
raise NotImplementedError, "CURLOPT_REQUEST_TARGET is not supported by this libcurl"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
161
177
|
def ssl_verify_host=(value)
|
162
178
|
value = 1 if value.class == TrueClass
|
163
179
|
value = 0 if value.class == FalseClass
|
@@ -216,9 +232,18 @@ module Curl
|
|
216
232
|
# call-seq:
|
217
233
|
# easy.cookies = "name1=content1; name2=content2;" => string
|
218
234
|
#
|
219
|
-
# Set
|
220
|
-
# be NAME=CONTENTS, where NAME is the cookie name and
|
221
|
-
# Set multiple cookies in one string like this:
|
235
|
+
# Set the manual Cookie request header for this Curl::Easy instance.
|
236
|
+
# The format of the string should be NAME=CONTENTS, where NAME is the cookie name and
|
237
|
+
# CONTENTS is what the cookie should contain. Set multiple cookies in one string like this:
|
238
|
+
# "name1=content1; name2=content2;".
|
239
|
+
#
|
240
|
+
# Notes:
|
241
|
+
# - This only affects the outgoing Cookie header (libcurl CURLOPT_COOKIE) and does NOT
|
242
|
+
# alter the internal libcurl cookie engine (which stores cookies from Set-Cookie).
|
243
|
+
# - To change cookies stored in the engine, use {#cookielist} / {#cookielist=} or
|
244
|
+
# {#set} with :cookielist.
|
245
|
+
# - To clear a previously set manual Cookie header, assign an empty string ('').
|
246
|
+
# Assigning +nil+ has no effect in current curb versions.
|
222
247
|
#
|
223
248
|
def cookies=(value)
|
224
249
|
set :cookie, value
|
@@ -233,6 +258,8 @@ module Curl
|
|
233
258
|
# *Note* that you must set enable_cookies true to enable the cookie
|
234
259
|
# engine, or this option will be ignored.
|
235
260
|
#
|
261
|
+
# Note: assigning +nil+ has no effect; pass a path string to use a cookie file.
|
262
|
+
#
|
236
263
|
def cookiefile=(value)
|
237
264
|
set :cookiefile, value
|
238
265
|
end
|
@@ -241,16 +268,40 @@ module Curl
|
|
241
268
|
# call-seq:
|
242
269
|
# easy.cookiejar = string => string
|
243
270
|
#
|
244
|
-
# Set a cookiejar file to use for this Curl::Easy instance.
|
245
|
-
#
|
271
|
+
# Set a cookiejar file to use for this Curl::Easy instance. Cookies from the response
|
272
|
+
# will be written into this file.
|
246
273
|
#
|
247
274
|
# *Note* that you must set enable_cookies true to enable the cookie
|
248
275
|
# engine, or this option will be ignored.
|
249
276
|
#
|
277
|
+
# Note: assigning +nil+ has no effect; pass a path string to persist cookies to a file.
|
278
|
+
#
|
250
279
|
def cookiejar=(value)
|
251
280
|
set :cookiejar, value
|
252
281
|
end
|
253
282
|
|
283
|
+
#
|
284
|
+
# call-seq:
|
285
|
+
# easy.cookielist = string => string
|
286
|
+
#
|
287
|
+
# Modify cookies in libcurl's internal cookie engine (CURLOPT_COOKIELIST).
|
288
|
+
# Accepts a Set-Cookie style string, one or more lines in Netscape cookie file format,
|
289
|
+
# or one of the special commands: "ALL" (clear), "SESS" (remove session cookies),
|
290
|
+
# "FLUSH" (write to jar), "RELOAD" (reload from file).
|
291
|
+
#
|
292
|
+
# Examples:
|
293
|
+
# easy.cookielist = "Set-Cookie: session=42; Domain=example.com; Path=/;"
|
294
|
+
# easy.cookielist = [
|
295
|
+
# ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c1', 'v1'].join("\t"),
|
296
|
+
# ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c2', 'v2'].join("\t"),
|
297
|
+
# ''
|
298
|
+
# ].join("\n")
|
299
|
+
# easy.cookielist = 'ALL' # clear all cookies in the engine
|
300
|
+
#
|
301
|
+
def cookielist=(value)
|
302
|
+
set :cookielist, value
|
303
|
+
end
|
304
|
+
|
254
305
|
#
|
255
306
|
# call-seq:
|
256
307
|
# easy = Curl::Easy.new("url") do|c|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
|
3
|
+
class TestCurbCurlEasyRequestTarget < Test::Unit::TestCase
|
4
|
+
include TestServerMethods
|
5
|
+
|
6
|
+
def setup
|
7
|
+
server_setup
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_request_target_absolute_form
|
11
|
+
unless Curl.const_defined?(:CURLOPT_REQUEST_TARGET)
|
12
|
+
omit('libcurl lacks CURLOPT_REQUEST_TARGET support')
|
13
|
+
end
|
14
|
+
|
15
|
+
tmp = Tempfile.new('curb_test_request_target')
|
16
|
+
path = tmp.path
|
17
|
+
fd = IO.sysopen(path, 'w')
|
18
|
+
io = IO.new(fd, 'w')
|
19
|
+
io.sync = true
|
20
|
+
|
21
|
+
easy = Curl::Easy.new(TestServlet.url)
|
22
|
+
easy.verbose = true
|
23
|
+
easy.setopt(Curl::CURLOPT_STDERR, io)
|
24
|
+
|
25
|
+
# Force absolute-form request target, different from the URL host
|
26
|
+
easy.request_target = "http://localhost:#{TestServlet.port}#{TestServlet.path}"
|
27
|
+
easy.headers = { 'Host' => "example.com" }
|
28
|
+
|
29
|
+
easy.perform
|
30
|
+
|
31
|
+
io.flush
|
32
|
+
io.close
|
33
|
+
output = File.read(path)
|
34
|
+
|
35
|
+
assert_match(/GET\s+http:\/\/localhost:#{TestServlet.port}#{Regexp.escape(TestServlet.path)}\s+HTTP\/1\.1/, output)
|
36
|
+
assert_match(/Host:\s+example\.com/, output)
|
37
|
+
ensure
|
38
|
+
tmp.close! if defined?(tmp) && tmp
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/tests/tc_curl_multi.rb
CHANGED
@@ -573,6 +573,35 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
573
573
|
end
|
574
574
|
end
|
575
575
|
|
576
|
+
# Regression test for issue #267 (2015): ensure that when reusing
|
577
|
+
# easy handles with constrained concurrency, the callback receives
|
578
|
+
# the correct URL for each completed request rather than repeating
|
579
|
+
# the first URL.
|
580
|
+
def test_multi_easy_http_urls_unique_across_max_connects
|
581
|
+
urls = [
|
582
|
+
{ :url => TestServlet.url + '?q=1', :method => :get },
|
583
|
+
{ :url => TestServlet.url + '?q=2', :method => :get },
|
584
|
+
{ :url => TestServlet.url + '?q=3', :method => :get }
|
585
|
+
]
|
586
|
+
|
587
|
+
[1, 2, 3].each do |max|
|
588
|
+
results = []
|
589
|
+
Curl::Multi.http(urls.dup, {:pipeline => true, :max_connects => max}) do |easy, code, method|
|
590
|
+
assert_equal 200, code
|
591
|
+
assert_equal :get, method
|
592
|
+
results << easy.last_effective_url
|
593
|
+
end
|
594
|
+
|
595
|
+
# Ensure we saw one completion per input URL
|
596
|
+
assert_equal urls.size, results.size, "expected #{urls.size} results with max_connects=#{max}"
|
597
|
+
|
598
|
+
# And that each URL completed exactly once (no accidental reuse/mis-reporting)
|
599
|
+
expected_urls = urls.map { |u| u[:url] }
|
600
|
+
assert_equal expected_urls.to_set, results.to_set, "unexpected URLs for max_connects=#{max}: #{results.inspect}"
|
601
|
+
assert_equal expected_urls.size, results.uniq.size, "duplicate URLs seen for max_connects=#{max}: #{results.inspect}"
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
576
605
|
def test_multi_recieves_500
|
577
606
|
m = Curl::Multi.new
|
578
607
|
e = Curl::Easy.new("http://127.0.0.1:9129/methods")
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
|
3
|
+
class TestCurbFtpOptions < Test::Unit::TestCase
|
4
|
+
# Ensure FTP-related set(:option, ...) mappings are accepted and do not raise
|
5
|
+
# a TypeError (they used to be unsupported in setopt dispatch).
|
6
|
+
def test_can_set_ftp_listing_related_flags
|
7
|
+
c = Curl::Easy.new('ftp://example.com/')
|
8
|
+
|
9
|
+
assert_nothing_raised do
|
10
|
+
c.set(:dirlistonly, true) if Curl.const_defined?(:CURLOPT_DIRLISTONLY)
|
11
|
+
c.set(:ftp_use_epsv, 0) if Curl.const_defined?(:CURLOPT_FTP_USE_EPSV)
|
12
|
+
# These may not be present on all libcurl builds; guard by constant
|
13
|
+
c.set(:ftp_use_eprt, 0) if Curl.const_defined?(:CURLOPT_FTP_USE_EPRT)
|
14
|
+
c.set(:ftp_skip_pasv_ip, 1) if Curl.const_defined?(:CURLOPT_FTP_SKIP_PASV_IP)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Setting ftp_commands remains supported for control-connection commands.
|
19
|
+
def test_can_assign_ftp_commands
|
20
|
+
c = Curl::Easy.new('ftp://example.com/')
|
21
|
+
c.ftp_commands = ["PWD", "CWD /"]
|
22
|
+
assert_kind_of(Array, c.ftp_commands)
|
23
|
+
assert_equal ["PWD", "CWD /"], c.ftp_commands
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('helper', __dir__)
|
4
|
+
|
5
|
+
class TestGcCompact < Test::Unit::TestCase
|
6
|
+
ITERATIONS = (ENV['CURB_GC_COMPACT_ITERATIONS'] || 5).to_i
|
7
|
+
EASY_PER_MULTI = 3
|
8
|
+
|
9
|
+
def setup
|
10
|
+
omit('GC.compact unavailable on this Ruby') unless defined?(GC.compact)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_multi_perform_with_gc_compact
|
14
|
+
ITERATIONS.times do
|
15
|
+
multi = Curl::Multi.new
|
16
|
+
add_easy_handles(multi)
|
17
|
+
|
18
|
+
compact
|
19
|
+
assert_nothing_raised { multi.perform }
|
20
|
+
compact
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_gc_compact_during_multi_cleanup
|
25
|
+
ITERATIONS.times do
|
26
|
+
multi = Curl::Multi.new
|
27
|
+
add_easy_handles(multi)
|
28
|
+
|
29
|
+
compact
|
30
|
+
multi = nil
|
31
|
+
compact
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_gc_compact_after_detach
|
36
|
+
multi = Curl::Multi.new
|
37
|
+
handles = add_easy_handles(multi)
|
38
|
+
|
39
|
+
compact
|
40
|
+
assert_nothing_raised { multi.perform }
|
41
|
+
|
42
|
+
handles.each { |easy| multi.remove(easy) }
|
43
|
+
compact
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def add_easy_handles(multi)
|
49
|
+
Array.new(EASY_PER_MULTI) do
|
50
|
+
Curl::Easy.new($TEST_URL) do |easy|
|
51
|
+
easy.timeout = 5
|
52
|
+
easy.on_complete { |_e| }
|
53
|
+
easy.on_failure { |_e, _code| }
|
54
|
+
end.tap { |easy| multi.add(easy) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def compact
|
59
|
+
GC.compact
|
60
|
+
end
|
61
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: curb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ross Bamford
|
8
8
|
- Todd A. Fisher
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Curb (probably CUrl-RuBy or something) provides Ruby-language bindings
|
14
14
|
for the libcurl(3), a fully-featured client-side URL transfer library. cURL and
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- tests/tc_curl_download.rb
|
70
70
|
- tests/tc_curl_easy.rb
|
71
71
|
- tests/tc_curl_easy_cookielist.rb
|
72
|
+
- tests/tc_curl_easy_request_target.rb
|
72
73
|
- tests/tc_curl_easy_resolve.rb
|
73
74
|
- tests/tc_curl_easy_setopt.rb
|
74
75
|
- tests/tc_curl_maxfilesize.rb
|
@@ -76,6 +77,8 @@ files:
|
|
76
77
|
- tests/tc_curl_postfield.rb
|
77
78
|
- tests/tc_curl_protocols.rb
|
78
79
|
- tests/tc_fiber_scheduler.rb
|
80
|
+
- tests/tc_ftp_options.rb
|
81
|
+
- tests/tc_gc_compact.rb
|
79
82
|
- tests/test_basic.rb
|
80
83
|
- tests/test_fiber_debug.rb
|
81
84
|
- tests/test_fiber_simple.rb
|
@@ -135,6 +138,7 @@ test_files:
|
|
135
138
|
- tests/tc_curl_download.rb
|
136
139
|
- tests/tc_curl_easy.rb
|
137
140
|
- tests/tc_curl_easy_cookielist.rb
|
141
|
+
- tests/tc_curl_easy_request_target.rb
|
138
142
|
- tests/tc_curl_easy_resolve.rb
|
139
143
|
- tests/tc_curl_easy_setopt.rb
|
140
144
|
- tests/tc_curl_maxfilesize.rb
|
@@ -142,6 +146,8 @@ test_files:
|
|
142
146
|
- tests/tc_curl_postfield.rb
|
143
147
|
- tests/tc_curl_protocols.rb
|
144
148
|
- tests/tc_fiber_scheduler.rb
|
149
|
+
- tests/tc_ftp_options.rb
|
150
|
+
- tests/tc_gc_compact.rb
|
145
151
|
- tests/test_basic.rb
|
146
152
|
- tests/test_fiber_debug.rb
|
147
153
|
- tests/test_fiber_simple.rb
|