curb 1.2.1 → 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 +33 -0
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +75 -20
- data/ext/curb_easy.h +1 -0
- data/ext/curb_multi.c +44 -7
- data/tests/tc_curl_easy_request_target.rb +41 -0
- data/tests/tc_ftp_options.rb +26 -0
- data/tests/tc_gc_compact.rb +61 -0
- data/tests/test_basic.rb +29 -0
- data/tests/test_fiber_debug.rb +69 -0
- data/tests/test_fiber_simple.rb +65 -0
- data/tests/test_real_url.rb +65 -0
- data/tests/test_simple_fiber.rb +34 -0
- metadata +18 -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
@@ -105,6 +105,39 @@ puts list.body
|
|
105
105
|
|
106
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
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
|
+
|
137
|
+
list.perform
|
138
|
+
puts list.body
|
139
|
+
```
|
140
|
+
|
108
141
|
### Advanced FTP Usage with Various Options
|
109
142
|
```
|
110
143
|
puts "\n=== Advanced FTP Example ==="
|
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));
|
@@ -238,6 +269,12 @@ static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
|
|
238
269
|
if (!NIL_P(multi_val) && RB_TYPE_P(multi_val, T_DATA)) {
|
239
270
|
Data_Get_Struct(multi_val, ruby_curl_multi, rbcm);
|
240
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
|
+
}
|
241
278
|
rb_curl_multi_forget_easy(rbcm, rbce);
|
242
279
|
}
|
243
280
|
}
|
@@ -273,6 +310,8 @@ static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
|
|
273
310
|
curl_easy_cleanup(rbce->curl);
|
274
311
|
rbce->curl = NULL;
|
275
312
|
}
|
313
|
+
|
314
|
+
rbce->self = Qnil;
|
276
315
|
}
|
277
316
|
|
278
317
|
void curl_easy_free(ruby_curl_easy *rbce) {
|
@@ -288,6 +327,7 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
|
|
288
327
|
|
289
328
|
memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
|
290
329
|
|
330
|
+
rbce->self = Qnil;
|
291
331
|
rbce->curl_headers = NULL;
|
292
332
|
rbce->curl_proxy_headers = NULL;
|
293
333
|
rbce->curl_ftp_commands = NULL;
|
@@ -379,13 +419,14 @@ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
|
|
379
419
|
rbce->opts = Qnil;
|
380
420
|
|
381
421
|
ruby_curl_easy_zero(rbce);
|
422
|
+
rbce->self = self;
|
382
423
|
|
383
424
|
curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
|
384
425
|
|
385
426
|
rb_easy_set("url", url);
|
386
427
|
|
387
428
|
/* set the pointer to the curl handle */
|
388
|
-
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)
|
429
|
+
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
|
389
430
|
if (ecode != CURLE_OK) {
|
390
431
|
raise_curl_easy_error_exception(ecode);
|
391
432
|
}
|
@@ -450,7 +491,11 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
|
|
450
491
|
/* Set the error buffer on the new curl handle using the new err_buf */
|
451
492
|
curl_easy_setopt(newrbce->curl, CURLOPT_ERRORBUFFER, newrbce->err_buf);
|
452
493
|
|
453
|
-
|
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;
|
454
499
|
}
|
455
500
|
|
456
501
|
/*
|
@@ -482,9 +527,10 @@ static VALUE ruby_curl_easy_close(VALUE self) {
|
|
482
527
|
rbce->multi = Qnil;
|
483
528
|
|
484
529
|
ruby_curl_easy_zero(rbce);
|
530
|
+
rbce->self = self;
|
485
531
|
|
486
532
|
/* give the new curl handle a reference back to the ruby object */
|
487
|
-
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)
|
533
|
+
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
|
488
534
|
if (ecode != CURLE_OK) {
|
489
535
|
raise_curl_easy_error_exception(ecode);
|
490
536
|
}
|
@@ -518,11 +564,12 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
|
|
518
564
|
|
519
565
|
curl_easy_reset(rbce->curl);
|
520
566
|
ruby_curl_easy_zero(rbce);
|
567
|
+
rbce->self = self;
|
521
568
|
|
522
569
|
curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
|
523
570
|
|
524
571
|
/* reset clobbers the private setting, so reset it to self */
|
525
|
-
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)
|
572
|
+
ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
|
526
573
|
if (ecode != CURLE_OK) {
|
527
574
|
raise_curl_easy_error_exception(ecode);
|
528
575
|
}
|
@@ -2389,9 +2436,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2389
2436
|
/* clear out the body_data if it was set */
|
2390
2437
|
rb_easy_del("body_data");
|
2391
2438
|
} else {
|
2392
|
-
|
2393
|
-
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&
|
2394
|
-
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);
|
2395
2442
|
}
|
2396
2443
|
|
2397
2444
|
if (!rb_easy_nil("header_proc")) {
|
@@ -2400,9 +2447,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2400
2447
|
/* clear out the header_data if it was set */
|
2401
2448
|
rb_easy_del("header_data");
|
2402
2449
|
} else {
|
2403
|
-
|
2404
|
-
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&
|
2405
|
-
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);
|
2406
2453
|
}
|
2407
2454
|
|
2408
2455
|
/* encoding */
|
@@ -2413,20 +2460,21 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2413
2460
|
// progress and debug procs
|
2414
2461
|
if (!rb_easy_nil("progress_proc")) {
|
2415
2462
|
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, (curl_progress_callback)&proc_progress_handler);
|
2416
|
-
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA,
|
2463
|
+
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
|
2417
2464
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
2418
2465
|
} else {
|
2419
2466
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
|
2467
|
+
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
|
2420
2468
|
}
|
2421
2469
|
|
2422
2470
|
if (!rb_easy_nil("debug_proc")) {
|
2423
2471
|
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, (curl_debug_callback)&proc_debug_handler);
|
2424
|
-
curl_easy_setopt(curl, CURLOPT_DEBUGDATA,
|
2472
|
+
curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce);
|
2425
2473
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
|
2426
2474
|
} else {
|
2427
2475
|
// have to remove handler to re-enable standard verbosity
|
2428
2476
|
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL);
|
2429
|
-
curl_easy_setopt(curl, CURLOPT_DEBUGDATA,
|
2477
|
+
curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce);
|
2430
2478
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, rbce->verbose);
|
2431
2479
|
}
|
2432
2480
|
|
@@ -2434,7 +2482,14 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2434
2482
|
|
2435
2483
|
curl_easy_setopt(curl, CURLOPT_HEADER, rbce->header_in_body);
|
2436
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
|
2437
2491
|
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs);
|
2492
|
+
#endif
|
2438
2493
|
|
2439
2494
|
curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, rbce->proxy_tunnel);
|
2440
2495
|
curl_easy_setopt(curl, CURLOPT_FILETIME, rbce->fetch_file_time);
|
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
|
|
data/ext/curb_multi.c
CHANGED
@@ -454,15 +454,56 @@ static void flush_stderr_if_any(ruby_curl_easy *rbce) {
|
|
454
454
|
}
|
455
455
|
}
|
456
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
|
+
|
457
477
|
static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
|
458
478
|
long response_code = -1;
|
459
|
-
VALUE easy;
|
479
|
+
VALUE easy = Qnil;
|
460
480
|
ruby_curl_easy *rbce = NULL;
|
461
481
|
VALUE callargs;
|
482
|
+
ruby_curl_multi *rbcm = NULL;
|
462
483
|
|
463
|
-
|
484
|
+
Data_Get_Struct(self, ruby_curl_multi, rbcm);
|
464
485
|
|
465
|
-
|
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
|
+
}
|
466
507
|
|
467
508
|
rbce->last_result = result; /* save the last easy result code */
|
468
509
|
|
@@ -482,10 +523,6 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
482
523
|
/* Flush again after removal to cover any last buffered data. */
|
483
524
|
flush_stderr_if_any(rbce);
|
484
525
|
|
485
|
-
if (ecode != 0) {
|
486
|
-
raise_curl_easy_error_exception(ecode);
|
487
|
-
}
|
488
|
-
|
489
526
|
VALUE did_raise = rb_hash_new();
|
490
527
|
|
491
528
|
if (!rb_easy_nil("complete_proc")) {
|
@@ -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
|
+
|
@@ -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
|
data/tests/test_basic.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
|
3
|
+
class TestBasic < Test::Unit::TestCase
|
4
|
+
include TestServerMethods
|
5
|
+
|
6
|
+
def setup
|
7
|
+
server_setup
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_basic_request
|
11
|
+
puts "\n=== Testing basic request ==="
|
12
|
+
easy = Curl::Easy.new(TestServlet.url)
|
13
|
+
easy.perform
|
14
|
+
puts "Response code: #{easy.response_code}"
|
15
|
+
puts "Body (first 100 chars): #{easy.body_str[0..100]}"
|
16
|
+
assert_equal 200, easy.response_code
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_slow_request
|
20
|
+
puts "\n=== Testing slow request ==="
|
21
|
+
url = TestServlet.url_to("/slow?seconds=0.1")
|
22
|
+
puts "URL: #{url}"
|
23
|
+
easy = Curl::Easy.new(url)
|
24
|
+
easy.perform
|
25
|
+
puts "Response code: #{easy.response_code}"
|
26
|
+
puts "Body: #{easy.body_str}"
|
27
|
+
assert_equal 200, easy.response_code
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
require 'async'
|
3
|
+
|
4
|
+
class TestFiberDebug < Test::Unit::TestCase
|
5
|
+
include TestServerMethods
|
6
|
+
|
7
|
+
def setup
|
8
|
+
server_setup
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_simple_fiber_request
|
12
|
+
puts "\n=== Starting simple fiber request test ==="
|
13
|
+
|
14
|
+
run_async do |task|
|
15
|
+
puts "Inside Async block"
|
16
|
+
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
17
|
+
|
18
|
+
multi = Curl::Multi.new
|
19
|
+
easy = Curl::Easy.new(TestServlet.url)
|
20
|
+
easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
|
21
|
+
|
22
|
+
multi.add(easy)
|
23
|
+
puts "Added easy handle to multi"
|
24
|
+
|
25
|
+
# Perform without block first
|
26
|
+
puts "Calling perform..."
|
27
|
+
multi.perform
|
28
|
+
puts "Perform completed"
|
29
|
+
end
|
30
|
+
|
31
|
+
puts "Test completed"
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_fiber_with_block
|
35
|
+
puts "\n=== Starting fiber with block test ==="
|
36
|
+
|
37
|
+
run_async do |task|
|
38
|
+
puts "Inside Async block"
|
39
|
+
|
40
|
+
multi = Curl::Multi.new
|
41
|
+
easy = Curl::Easy.new(TestServlet.url_to("/slow?seconds=0.1"))
|
42
|
+
easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
|
43
|
+
|
44
|
+
multi.add(easy)
|
45
|
+
|
46
|
+
block_calls = 0
|
47
|
+
multi.perform do
|
48
|
+
block_calls += 1
|
49
|
+
puts "Block called: #{block_calls}"
|
50
|
+
if block_calls < 5 # Limit iterations to prevent infinite loop
|
51
|
+
Async::Task.yield
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
puts "Perform completed, block called #{block_calls} times"
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "Test completed"
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def run_async(&block)
|
63
|
+
if defined?(Async) && Async.respond_to?(:run)
|
64
|
+
Async.run(&block)
|
65
|
+
else
|
66
|
+
Async(&block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
require 'async'
|
3
|
+
|
4
|
+
class TestFiberSimple < Test::Unit::TestCase
|
5
|
+
include TestServerMethods
|
6
|
+
|
7
|
+
def setup
|
8
|
+
server_setup
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_simple_concurrent
|
12
|
+
puts "\n=== Testing simple concurrent requests ==="
|
13
|
+
|
14
|
+
results = []
|
15
|
+
|
16
|
+
if Async.respond_to?(:run)
|
17
|
+
Async.run do |task|
|
18
|
+
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
19
|
+
|
20
|
+
multi = Curl::Multi.new
|
21
|
+
|
22
|
+
# Add 3 requests
|
23
|
+
3.times do |i|
|
24
|
+
easy = Curl::Easy.new(TestServlet.url_to("/slow?seconds=0.2&id=#{i}"))
|
25
|
+
easy.on_complete { |curl|
|
26
|
+
results << { id: i, code: curl.response_code }
|
27
|
+
puts "Request #{i} completed"
|
28
|
+
}
|
29
|
+
multi.add(easy)
|
30
|
+
end
|
31
|
+
|
32
|
+
puts "Starting perform..."
|
33
|
+
start_time = Time.now
|
34
|
+
multi.perform # No block
|
35
|
+
elapsed = Time.now - start_time
|
36
|
+
puts "Perform completed in #{elapsed.round(2)}s"
|
37
|
+
end
|
38
|
+
else
|
39
|
+
Async do |task|
|
40
|
+
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
41
|
+
|
42
|
+
multi = Curl::Multi.new
|
43
|
+
|
44
|
+
# Add 3 requests
|
45
|
+
3.times do |i|
|
46
|
+
easy = Curl::Easy.new(TestServlet.url_to("/slow?seconds=0.2&id=#{i}"))
|
47
|
+
easy.on_complete { |curl|
|
48
|
+
results << { id: i, code: curl.response_code }
|
49
|
+
puts "Request #{i} completed"
|
50
|
+
}
|
51
|
+
multi.add(easy)
|
52
|
+
end
|
53
|
+
|
54
|
+
puts "Starting perform..."
|
55
|
+
start_time = Time.now
|
56
|
+
multi.perform # No block
|
57
|
+
elapsed = Time.now - start_time
|
58
|
+
puts "Perform completed in #{elapsed.round(2)}s"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
assert_equal 3, results.size
|
63
|
+
results.each { |r| assert_equal 200, r[:code] }
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/curb'))
|
2
|
+
require 'async'
|
3
|
+
|
4
|
+
puts "Testing fiber scheduler with real URLs..."
|
5
|
+
|
6
|
+
# Test without fiber scheduler
|
7
|
+
puts "\n1. Without fiber scheduler:"
|
8
|
+
start = Time.now
|
9
|
+
multi = Curl::Multi.new
|
10
|
+
|
11
|
+
easies = []
|
12
|
+
3.times do |i|
|
13
|
+
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
14
|
+
easy.on_complete { |curl| puts "Request #{i} completed: #{curl.response_code}" }
|
15
|
+
multi.add(easy)
|
16
|
+
easies << easy
|
17
|
+
end
|
18
|
+
|
19
|
+
multi.perform
|
20
|
+
elapsed = Time.now - start
|
21
|
+
puts "Total time: #{elapsed.round(2)}s"
|
22
|
+
|
23
|
+
# Test with fiber scheduler
|
24
|
+
puts "\n2. With fiber scheduler:"
|
25
|
+
if Async.respond_to?(:run)
|
26
|
+
Async.run do
|
27
|
+
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
28
|
+
|
29
|
+
start = Time.now
|
30
|
+
multi = Curl::Multi.new
|
31
|
+
|
32
|
+
easies = []
|
33
|
+
3.times do |i|
|
34
|
+
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
35
|
+
easy.on_complete { |curl| puts "Request #{i} completed: #{curl.response_code}" }
|
36
|
+
multi.add(easy)
|
37
|
+
easies << easy
|
38
|
+
end
|
39
|
+
|
40
|
+
multi.perform
|
41
|
+
elapsed = Time.now - start
|
42
|
+
puts "Total time: #{elapsed.round(2)}s"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
Async do
|
46
|
+
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
47
|
+
|
48
|
+
start = Time.now
|
49
|
+
multi = Curl::Multi.new
|
50
|
+
|
51
|
+
easies = []
|
52
|
+
3.times do |i|
|
53
|
+
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
54
|
+
easy.on_complete { |curl| puts "Request #{i} completed: #{curl.response_code}" }
|
55
|
+
multi.add(easy)
|
56
|
+
easies << easy
|
57
|
+
end
|
58
|
+
|
59
|
+
multi.perform
|
60
|
+
elapsed = Time.now - start
|
61
|
+
puts "Total time: #{elapsed.round(2)}s"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
puts "\nDone!"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/curb'))
|
2
|
+
require 'async'
|
3
|
+
|
4
|
+
puts "Testing simple fiber scheduler..."
|
5
|
+
|
6
|
+
if Async.respond_to?(:run)
|
7
|
+
Async.run do
|
8
|
+
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
9
|
+
|
10
|
+
multi = Curl::Multi.new
|
11
|
+
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
12
|
+
easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
|
13
|
+
|
14
|
+
multi.add(easy)
|
15
|
+
puts "Starting perform..."
|
16
|
+
multi.perform
|
17
|
+
puts "Perform completed"
|
18
|
+
end
|
19
|
+
else
|
20
|
+
Async do
|
21
|
+
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
22
|
+
|
23
|
+
multi = Curl::Multi.new
|
24
|
+
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
25
|
+
easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
|
26
|
+
|
27
|
+
multi.add(easy)
|
28
|
+
puts "Starting perform..."
|
29
|
+
multi.perform
|
30
|
+
puts "Perform completed"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
puts "Done!"
|
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-09-
|
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,13 @@ 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
|
82
|
+
- tests/test_basic.rb
|
83
|
+
- tests/test_fiber_debug.rb
|
84
|
+
- tests/test_fiber_simple.rb
|
85
|
+
- tests/test_real_url.rb
|
86
|
+
- tests/test_simple_fiber.rb
|
79
87
|
- tests/timeout.rb
|
80
88
|
- tests/timeout_server.rb
|
81
89
|
- tests/unittests.rb
|
@@ -130,6 +138,7 @@ test_files:
|
|
130
138
|
- tests/tc_curl_download.rb
|
131
139
|
- tests/tc_curl_easy.rb
|
132
140
|
- tests/tc_curl_easy_cookielist.rb
|
141
|
+
- tests/tc_curl_easy_request_target.rb
|
133
142
|
- tests/tc_curl_easy_resolve.rb
|
134
143
|
- tests/tc_curl_easy_setopt.rb
|
135
144
|
- tests/tc_curl_maxfilesize.rb
|
@@ -137,6 +146,13 @@ test_files:
|
|
137
146
|
- tests/tc_curl_postfield.rb
|
138
147
|
- tests/tc_curl_protocols.rb
|
139
148
|
- tests/tc_fiber_scheduler.rb
|
149
|
+
- tests/tc_ftp_options.rb
|
150
|
+
- tests/tc_gc_compact.rb
|
151
|
+
- tests/test_basic.rb
|
152
|
+
- tests/test_fiber_debug.rb
|
153
|
+
- tests/test_fiber_simple.rb
|
154
|
+
- tests/test_real_url.rb
|
155
|
+
- tests/test_simple_fiber.rb
|
140
156
|
- tests/timeout.rb
|
141
157
|
- tests/timeout_server.rb
|
142
158
|
- tests/unittests.rb
|