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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48135be870a425c76338a773b41dc9438883b30cab622cc8c2d81394b860047d
4
- data.tar.gz: e7a439c010c1bdbd3ab26b92f2126a4d518a2c7808d7acc6af2e57a03ec07376
3
+ metadata.gz: 784cff29d3284c08afa7a76364d5643b5d08ecff8802bb7b17e047438074bc37
4
+ data.tar.gz: 336bff9af874b57b0099da1e9fbde7c9b256e831de819635bc57c947d245c70c
5
5
  SHA512:
6
- metadata.gz: b5c61d835a10a80211b625dd3ef8cf949fdcf30be9584d2a6793d69e309e3cb283097c2502a8912801ab9ee13679f1e71344034a197c54c88872157e66df74a0
7
- data.tar.gz: 20fbde1832dc7338dba7f78c08556eceab4532247f41f0bb1c0c15ba11fc0cd2f07aabeaef62525eba24afc95b5e9854dbb3d71f6d6f493097d614ae913818f8
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.1"
32
- #define CURB_VER_NUM 1021
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 1
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
- /* These handle both body and header data */
46
- static size_t default_data_handler(char *stream,
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
- VALUE out) {
50
- rb_str_buf_cat(out, stream, size * nmemb);
51
- return size * nmemb;
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(VALUE proc,
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
- VALUE proc) {
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*)self);
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
- return Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, newrbce);
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*)self);
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*)self);
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
- VALUE body_buffer = rb_easy_set("body_data", rb_str_buf_new(32768));
2393
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&default_data_handler);
2394
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
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
- VALUE header_buffer = rb_easy_set("header_data", rb_str_buf_new(16384));
2404
- curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&default_data_handler);
2405
- curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
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, rb_easy_get("progress_proc"));
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, rb_easy_get("debug_proc"));
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, NULL);
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
- CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&easy);
484
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
464
485
 
465
- Data_Get_Struct(easy, ruby_curl_easy, rbce);
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
@@ -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.1
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-17 00:00:00.000000000 Z
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