curb 1.3.1 → 1.3.3
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/ext/curb.h +3 -3
- data/ext/curb_easy.c +285 -142
- data/ext/curb_multi.c +185 -20
- data/ext/curb_multi.h +3 -0
- data/ext/curb_postfield.c +1 -0
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +3 -3
- data/tests/bug_follow_redirect_288.rb +7 -7
- data/tests/bug_raise_on_callback.rb +4 -3
- data/tests/helper.rb +7 -0
- data/tests/tc_curl_easy.rb +116 -0
- data/tests/tc_curl_easy_resolve.rb +32 -0
- data/tests/tc_curl_multi.rb +56 -0
- data/tests/tc_curl_native_coverage.rb +15 -0
- data/tests/tc_curl_postfield.rb +15 -0
- data/tests/tc_ftp_options.rb +14 -1
- metadata +2 -2
data/ext/curb_multi.c
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
#include <errno.h>
|
|
30
30
|
#include <fcntl.h>
|
|
31
|
+
#include <stdint.h>
|
|
31
32
|
#include <stdarg.h>
|
|
32
33
|
|
|
33
34
|
/*
|
|
@@ -52,9 +53,7 @@
|
|
|
52
53
|
#include <fcntl.h>
|
|
53
54
|
#endif
|
|
54
55
|
|
|
55
|
-
#if
|
|
56
|
-
#include <stdint.h> /* for intptr_t */
|
|
57
|
-
|
|
56
|
+
#if defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT)
|
|
58
57
|
struct wait_args {
|
|
59
58
|
CURLM *handle;
|
|
60
59
|
long timeout_ms;
|
|
@@ -95,6 +94,34 @@ static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy);
|
|
|
95
94
|
static VALUE ruby_curl_multi_mark_closed(VALUE self);
|
|
96
95
|
static VALUE ruby_curl_multi_alloc(VALUE klass);
|
|
97
96
|
static VALUE ruby_curl_multi_initialize(VALUE self);
|
|
97
|
+
static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self);
|
|
98
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
99
|
+
static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE self);
|
|
100
|
+
#endif
|
|
101
|
+
|
|
102
|
+
static ruby_curl_multi *ruby_curl_multi_pointer_if_compatible(VALUE multi_val) {
|
|
103
|
+
if (NIL_P(multi_val) || !RB_TYPE_P(multi_val, T_DATA)) {
|
|
104
|
+
return NULL;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#if defined(RTYPEDDATA_P) && defined(RTYPEDDATA_TYPE) && defined(RTYPEDDATA_DATA)
|
|
108
|
+
if (!RTYPEDDATA_P(multi_val)) {
|
|
109
|
+
return NULL;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (RTYPEDDATA_TYPE(multi_val) != &ruby_curl_multi_data_type) {
|
|
113
|
+
return NULL;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (ruby_curl_multi *)RTYPEDDATA_DATA(multi_val);
|
|
117
|
+
#else
|
|
118
|
+
if (!rb_typeddata_is_kind_of(multi_val, &ruby_curl_multi_data_type)) {
|
|
119
|
+
return NULL;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return DATA_PTR(multi_val);
|
|
123
|
+
#endif
|
|
124
|
+
}
|
|
98
125
|
|
|
99
126
|
static VALUE callback_exception(VALUE did_raise, VALUE exception) {
|
|
100
127
|
// TODO: we could have an option to enable exception reporting
|
|
@@ -342,6 +369,9 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
|
342
369
|
rbcm->active = 0;
|
|
343
370
|
rbcm->running = 0;
|
|
344
371
|
rbcm->closed = 0;
|
|
372
|
+
rbcm->perform_active = 0;
|
|
373
|
+
rbcm->callback_active = 0;
|
|
374
|
+
rbcm->allow_close_during_perform = 0;
|
|
345
375
|
|
|
346
376
|
if (rbcm->attached) {
|
|
347
377
|
st_free_table(rbcm->attached);
|
|
@@ -537,11 +567,21 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
|
537
567
|
CURLMcode mcode;
|
|
538
568
|
ruby_curl_easy *rbce;
|
|
539
569
|
ruby_curl_multi *rbcm;
|
|
570
|
+
ruby_curl_multi *existing_rbcm;
|
|
540
571
|
|
|
541
572
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
542
573
|
TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
|
|
543
574
|
ruby_curl_multi_ensure_handle(rbcm);
|
|
544
575
|
|
|
576
|
+
if (rb_curl_multi_has_easy(rbcm, rbce)) {
|
|
577
|
+
return self;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
existing_rbcm = ruby_curl_multi_pointer_if_compatible(rbce->multi);
|
|
581
|
+
if (existing_rbcm && existing_rbcm != rbcm && rb_curl_multi_has_easy(existing_rbcm, rbce)) {
|
|
582
|
+
rb_raise(rb_eRuntimeError, "Cannot add an active Curl::Easy handle to another Curl::Multi");
|
|
583
|
+
}
|
|
584
|
+
|
|
545
585
|
/* setup the easy handle */
|
|
546
586
|
ruby_curl_easy_setup( rbce );
|
|
547
587
|
|
|
@@ -797,6 +837,8 @@ static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) {
|
|
|
797
837
|
VALUE did_raise = rb_hash_new();
|
|
798
838
|
long redirect_count;
|
|
799
839
|
|
|
840
|
+
args->rbcm->callback_active = 1;
|
|
841
|
+
|
|
800
842
|
easy_callback_error = take_easy_callback_error_if_any(args->easy);
|
|
801
843
|
if (!NIL_P(easy_callback_error)) {
|
|
802
844
|
stash_multi_exception_if_unset(args->self, easy_callback_error, args->easy);
|
|
@@ -877,6 +919,10 @@ static VALUE rb_curl_multi_finish_completion_callbacks(VALUE argp) {
|
|
|
877
919
|
args->rbce->callback_active = 0;
|
|
878
920
|
}
|
|
879
921
|
|
|
922
|
+
if (args->rbcm) {
|
|
923
|
+
args->rbcm->callback_active = 0;
|
|
924
|
+
}
|
|
925
|
+
|
|
880
926
|
if (args->rbce->multi == args->self && !rb_curl_multi_has_easy(args->rbcm, args->rbce)) {
|
|
881
927
|
args->rbce->multi = Qnil;
|
|
882
928
|
}
|
|
@@ -1072,6 +1118,41 @@ static VALUE fiber_io_wait_protected(VALUE argp) {
|
|
|
1072
1118
|
}
|
|
1073
1119
|
#endif
|
|
1074
1120
|
|
|
1121
|
+
#if defined(RB_INTEGER_TYPE_P)
|
|
1122
|
+
#define CURB_INTEGER_P(value) RB_INTEGER_TYPE_P(value)
|
|
1123
|
+
#else
|
|
1124
|
+
#define CURB_INTEGER_P(value) (FIXNUM_P(value) || RB_TYPE_P((value), T_BIGNUM))
|
|
1125
|
+
#endif
|
|
1126
|
+
|
|
1127
|
+
static int multi_socket_wait_events_for_curl_poll(int what) {
|
|
1128
|
+
if (what == CURL_POLL_IN) return RB_WAITFD_IN;
|
|
1129
|
+
if (what == CURL_POLL_OUT) return RB_WAITFD_OUT;
|
|
1130
|
+
if (what == CURL_POLL_INOUT) return RB_WAITFD_IN | RB_WAITFD_OUT;
|
|
1131
|
+
return RB_WAITFD_IN | RB_WAITFD_OUT;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
static int multi_socket_cselect_flags_for_curl_poll(int what) {
|
|
1135
|
+
int flags = 0;
|
|
1136
|
+
|
|
1137
|
+
if (what == CURL_POLL_IN || what == CURL_POLL_INOUT) flags |= CURL_CSELECT_IN;
|
|
1138
|
+
if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT) flags |= CURL_CSELECT_OUT;
|
|
1139
|
+
if (flags == 0) flags = CURL_CSELECT_IN | CURL_CSELECT_OUT;
|
|
1140
|
+
|
|
1141
|
+
return flags;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
static int multi_socket_cselect_flags_for_wait_events(int events) {
|
|
1145
|
+
int flags = 0;
|
|
1146
|
+
|
|
1147
|
+
if (events & RB_WAITFD_IN) flags |= CURL_CSELECT_IN;
|
|
1148
|
+
if (events & RB_WAITFD_OUT) flags |= CURL_CSELECT_OUT;
|
|
1149
|
+
#ifdef RB_WAITFD_PRI
|
|
1150
|
+
if (events & RB_WAITFD_PRI) flags |= CURL_CSELECT_ERR;
|
|
1151
|
+
#endif
|
|
1152
|
+
|
|
1153
|
+
return flags;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1075
1156
|
static void multi_socket_forget_fd(multi_socket_ctx *ctx, int fd) {
|
|
1076
1157
|
st_data_t key = (st_data_t)fd;
|
|
1077
1158
|
st_data_t rec;
|
|
@@ -1236,6 +1317,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1236
1317
|
|
|
1237
1318
|
int did_timeout = 0;
|
|
1238
1319
|
int any_ready = 0;
|
|
1320
|
+
int ready_flags = 0;
|
|
1239
1321
|
|
|
1240
1322
|
int handled_wait = 0;
|
|
1241
1323
|
if (count_tracked > 1) {
|
|
@@ -1270,10 +1352,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1270
1352
|
if (scheduler != Qnil) {
|
|
1271
1353
|
int events = 0;
|
|
1272
1354
|
if (wait_fd >= 0) {
|
|
1273
|
-
|
|
1274
|
-
else if (wait_what == CURL_POLL_OUT) events = RB_WAITFD_OUT;
|
|
1275
|
-
else if (wait_what == CURL_POLL_INOUT) events = RB_WAITFD_IN|RB_WAITFD_OUT;
|
|
1276
|
-
else events = RB_WAITFD_IN|RB_WAITFD_OUT;
|
|
1355
|
+
events = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
1277
1356
|
}
|
|
1278
1357
|
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1279
1358
|
VALUE timeout = rb_float_new(timeout_s);
|
|
@@ -1305,8 +1384,21 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1305
1384
|
did_timeout = 1;
|
|
1306
1385
|
any_ready = 0;
|
|
1307
1386
|
} else {
|
|
1308
|
-
any_ready = (ready != Qfalse);
|
|
1387
|
+
any_ready = (ready != Qfalse && !NIL_P(ready));
|
|
1309
1388
|
did_timeout = !any_ready;
|
|
1389
|
+
if (any_ready) {
|
|
1390
|
+
if (ready == Qtrue) {
|
|
1391
|
+
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1392
|
+
} else if (CURB_INTEGER_P(ready)) {
|
|
1393
|
+
ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready));
|
|
1394
|
+
if (ready_flags == 0) {
|
|
1395
|
+
any_ready = 0;
|
|
1396
|
+
did_timeout = 1;
|
|
1397
|
+
}
|
|
1398
|
+
} else {
|
|
1399
|
+
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1310
1402
|
}
|
|
1311
1403
|
}
|
|
1312
1404
|
}
|
|
@@ -1316,10 +1408,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1316
1408
|
#endif
|
|
1317
1409
|
#if defined(HAVE_RB_WAIT_FOR_SINGLE_FD)
|
|
1318
1410
|
if (!handled_wait && wait_fd >= 0) {
|
|
1319
|
-
int ev =
|
|
1320
|
-
if (wait_what == CURL_POLL_IN) ev = RB_WAITFD_IN;
|
|
1321
|
-
else if (wait_what == CURL_POLL_OUT) ev = RB_WAITFD_OUT;
|
|
1322
|
-
else if (wait_what == CURL_POLL_INOUT) ev = RB_WAITFD_IN|RB_WAITFD_OUT;
|
|
1411
|
+
int ev = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
1323
1412
|
int rc = rb_wait_for_single_fd(wait_fd, ev, &tv);
|
|
1324
1413
|
curb_debugf("[curb.socket] rb_wait_for_single_fd rc=%d fd=%d ev=%d", rc, wait_fd, ev);
|
|
1325
1414
|
if (rc < 0) {
|
|
@@ -1328,6 +1417,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1328
1417
|
}
|
|
1329
1418
|
any_ready = (rc != 0);
|
|
1330
1419
|
did_timeout = (rc == 0);
|
|
1420
|
+
if (any_ready) ready_flags = multi_socket_cselect_flags_for_wait_events(rc);
|
|
1331
1421
|
handled_wait = 1;
|
|
1332
1422
|
}
|
|
1333
1423
|
#endif
|
|
@@ -1351,6 +1441,11 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1351
1441
|
}
|
|
1352
1442
|
any_ready = (rc > 0);
|
|
1353
1443
|
did_timeout = (rc == 0);
|
|
1444
|
+
if (any_ready && wait_fd >= 0) {
|
|
1445
|
+
if (rb_fd_isset(wait_fd, &rfds)) ready_flags |= CURL_CSELECT_IN;
|
|
1446
|
+
if (rb_fd_isset(wait_fd, &wfds)) ready_flags |= CURL_CSELECT_OUT;
|
|
1447
|
+
if (rb_fd_isset(wait_fd, &efds)) ready_flags |= CURL_CSELECT_ERR;
|
|
1448
|
+
}
|
|
1354
1449
|
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1355
1450
|
}
|
|
1356
1451
|
} else { /* count_tracked == 0 */
|
|
@@ -1368,10 +1463,8 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1368
1463
|
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1369
1464
|
} else if (any_ready) {
|
|
1370
1465
|
if (count_tracked == 1 && wait_fd >= 0) {
|
|
1371
|
-
int flags =
|
|
1372
|
-
if (
|
|
1373
|
-
if (wait_what == CURL_POLL_OUT || wait_what == CURL_POLL_INOUT) flags |= CURL_CSELECT_OUT;
|
|
1374
|
-
flags |= CURL_CSELECT_ERR;
|
|
1466
|
+
int flags = ready_flags;
|
|
1467
|
+
if (flags == 0) flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1375
1468
|
#if CURB_SOCKET_DEBUG
|
|
1376
1469
|
{
|
|
1377
1470
|
char b[32];
|
|
@@ -1415,7 +1508,7 @@ static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
|
1415
1508
|
return Qnil;
|
|
1416
1509
|
}
|
|
1417
1510
|
|
|
1418
|
-
VALUE
|
|
1511
|
+
static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
1419
1512
|
ruby_curl_multi *rbcm;
|
|
1420
1513
|
VALUE block = Qnil;
|
|
1421
1514
|
rb_scan_args(argc, argv, "0&", &block);
|
|
@@ -1445,7 +1538,11 @@ VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
1445
1538
|
/* finalize */
|
|
1446
1539
|
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1447
1540
|
rb_curl_multi_yield_if_given(self, block);
|
|
1448
|
-
if (cCurlMutiAutoClose == 1)
|
|
1541
|
+
if (cCurlMutiAutoClose == 1) {
|
|
1542
|
+
rbcm->allow_close_during_perform = 1;
|
|
1543
|
+
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
1544
|
+
rbcm->allow_close_during_perform = 0;
|
|
1545
|
+
}
|
|
1449
1546
|
|
|
1450
1547
|
return Qtrue;
|
|
1451
1548
|
}
|
|
@@ -1493,6 +1590,14 @@ static VALUE curb_select(void *args) {
|
|
|
1493
1590
|
int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
|
|
1494
1591
|
return INT2FIX(rc);
|
|
1495
1592
|
}
|
|
1593
|
+
|
|
1594
|
+
#ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
|
|
1595
|
+
static void *curb_select_without_gvl(void *args) {
|
|
1596
|
+
struct _select_set* set = args;
|
|
1597
|
+
int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
|
|
1598
|
+
return (void *)(intptr_t)rc;
|
|
1599
|
+
}
|
|
1600
|
+
#endif
|
|
1496
1601
|
#endif
|
|
1497
1602
|
|
|
1498
1603
|
/*
|
|
@@ -1510,7 +1615,7 @@ static VALUE curb_select(void *args) {
|
|
|
1510
1615
|
*
|
|
1511
1616
|
* Run multi handles, looping selecting when data can be transfered
|
|
1512
1617
|
*/
|
|
1513
|
-
VALUE
|
|
1618
|
+
static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
1514
1619
|
CURLMcode mcode;
|
|
1515
1620
|
ruby_curl_multi *rbcm;
|
|
1516
1621
|
int maxfd, rc = -1;
|
|
@@ -1691,7 +1796,7 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
1691
1796
|
rb_fd_term(&efds);
|
|
1692
1797
|
}
|
|
1693
1798
|
#elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
|
1694
|
-
rc = (int)(
|
|
1799
|
+
rc = (int)(intptr_t) rb_thread_call_without_gvl(curb_select_without_gvl, &fdset_args, RUBY_UBF_IO, 0);
|
|
1695
1800
|
#elif HAVE_RB_THREAD_BLOCKING_REGION
|
|
1696
1801
|
rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0);
|
|
1697
1802
|
#else
|
|
@@ -1725,11 +1830,66 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
1725
1830
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
1726
1831
|
rb_curl_multi_yield_if_given(self, block);
|
|
1727
1832
|
if (cCurlMutiAutoClose == 1) {
|
|
1833
|
+
rbcm->allow_close_during_perform = 1;
|
|
1728
1834
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
1835
|
+
rbcm->allow_close_during_perform = 0;
|
|
1729
1836
|
}
|
|
1730
1837
|
return Qtrue;
|
|
1731
1838
|
}
|
|
1732
1839
|
|
|
1840
|
+
struct multi_perform_call_args {
|
|
1841
|
+
int argc;
|
|
1842
|
+
VALUE *argv;
|
|
1843
|
+
VALUE self;
|
|
1844
|
+
VALUE result;
|
|
1845
|
+
VALUE (*func)(int, VALUE *, VALUE);
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
static VALUE ruby_curl_multi_perform_guard_body(VALUE argp) {
|
|
1849
|
+
struct multi_perform_call_args *args = (struct multi_perform_call_args *)argp;
|
|
1850
|
+
args->result = args->func(args->argc, args->argv, args->self);
|
|
1851
|
+
return args->result;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
static VALUE ruby_curl_multi_perform_guard_ensure(VALUE self) {
|
|
1855
|
+
ruby_curl_multi *rbcm;
|
|
1856
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
1857
|
+
rbcm->perform_active = 0;
|
|
1858
|
+
rbcm->callback_active = 0;
|
|
1859
|
+
rbcm->allow_close_during_perform = 0;
|
|
1860
|
+
return Qnil;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE self, VALUE (*func)(int, VALUE *, VALUE)) {
|
|
1864
|
+
ruby_curl_multi *rbcm;
|
|
1865
|
+
struct multi_perform_call_args args;
|
|
1866
|
+
|
|
1867
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
1868
|
+
if (rbcm->perform_active) {
|
|
1869
|
+
rb_raise(rb_eRuntimeError, "Cannot recursively perform an active Curl::Multi handle");
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
rbcm->perform_active = 1;
|
|
1873
|
+
args.argc = argc;
|
|
1874
|
+
args.argv = argv;
|
|
1875
|
+
args.self = self;
|
|
1876
|
+
args.result = Qnil;
|
|
1877
|
+
args.func = func;
|
|
1878
|
+
|
|
1879
|
+
return rb_ensure(ruby_curl_multi_perform_guard_body, (VALUE)&args,
|
|
1880
|
+
ruby_curl_multi_perform_guard_ensure, self);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
1884
|
+
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_perform_impl);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
1888
|
+
VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
|
|
1889
|
+
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_socket_perform_impl);
|
|
1890
|
+
}
|
|
1891
|
+
#endif
|
|
1892
|
+
|
|
1733
1893
|
/*
|
|
1734
1894
|
* call-seq:
|
|
1735
1895
|
*
|
|
@@ -1740,6 +1900,11 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
1740
1900
|
VALUE ruby_curl_multi_close(VALUE self) {
|
|
1741
1901
|
ruby_curl_multi *rbcm;
|
|
1742
1902
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
1903
|
+
|
|
1904
|
+
if ((rbcm->perform_active || rbcm->callback_active) && !rbcm->allow_close_during_perform) {
|
|
1905
|
+
rb_raise(rb_eRuntimeError, "Cannot close an active Curl::Multi handle during perform");
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1743
1908
|
rb_curl_multi_detach_all(rbcm);
|
|
1744
1909
|
|
|
1745
1910
|
if (rbcm->handle) {
|
data/ext/curb_multi.h
CHANGED
data/ext/curb_postfield.c
CHANGED
|
@@ -183,6 +183,7 @@ static void curl_postfield_mark(void *ptr) {
|
|
|
183
183
|
rb_gc_mark(rbcpf->name);
|
|
184
184
|
rb_gc_mark(rbcpf->content);
|
|
185
185
|
rb_gc_mark(rbcpf->content_type);
|
|
186
|
+
rb_gc_mark(rbcpf->content_proc);
|
|
186
187
|
rb_gc_mark(rbcpf->local_file);
|
|
187
188
|
rb_gc_mark(rbcpf->remote_file);
|
|
188
189
|
rb_gc_mark(rbcpf->buffer_str);
|
|
@@ -4,7 +4,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
|
|
4
4
|
include BugTestServerSetupTeardown
|
|
5
5
|
|
|
6
6
|
def setup
|
|
7
|
-
@port =
|
|
7
|
+
@port = unused_local_port
|
|
8
8
|
@response_proc = lambda do|res|
|
|
9
9
|
sleep 0.5
|
|
10
10
|
res.body = "hi"
|
|
@@ -19,7 +19,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
|
|
19
19
|
|
|
20
20
|
5.times do |i|
|
|
21
21
|
t = Thread.new do
|
|
22
|
-
c = Curl::Easy.perform(
|
|
22
|
+
c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test")
|
|
23
23
|
c.header_str
|
|
24
24
|
end
|
|
25
25
|
threads << t
|
|
@@ -35,7 +35,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
|
|
35
35
|
timer = Time.now
|
|
36
36
|
single_responses = []
|
|
37
37
|
5.times do |i|
|
|
38
|
-
c = Curl::Easy.perform(
|
|
38
|
+
c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test")
|
|
39
39
|
single_responses << c.header_str
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -4,7 +4,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
|
|
|
4
4
|
include BugTestServerSetupTeardown
|
|
5
5
|
|
|
6
6
|
def setup
|
|
7
|
-
@port =
|
|
7
|
+
@port = unused_local_port
|
|
8
8
|
super
|
|
9
9
|
@server.mount_proc("/redirect_to_test") do|req,res|
|
|
10
10
|
res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, "/test")
|
|
@@ -13,7 +13,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
|
|
|
13
13
|
|
|
14
14
|
def test_follow_redirect_with_no_redirect
|
|
15
15
|
|
|
16
|
-
c = Curl::Easy.new(
|
|
16
|
+
c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
|
|
17
17
|
did_call_redirect = false
|
|
18
18
|
c.on_redirect do|x|
|
|
19
19
|
did_call_redirect = true
|
|
@@ -22,7 +22,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
|
|
|
22
22
|
|
|
23
23
|
assert !did_call_redirect, "should reach this point redirect should not have been called"
|
|
24
24
|
|
|
25
|
-
c = Curl::Easy.new(
|
|
25
|
+
c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
|
|
26
26
|
did_call_redirect = false
|
|
27
27
|
c.on_redirect do|x|
|
|
28
28
|
did_call_redirect = true
|
|
@@ -33,7 +33,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
|
|
|
33
33
|
assert_equal 0, c.redirect_count
|
|
34
34
|
assert !did_call_redirect, "should reach this point redirect should not have been called"
|
|
35
35
|
|
|
36
|
-
c = Curl::Easy.new(
|
|
36
|
+
c = Curl::Easy.new("http://127.0.0.1:#{@port}/redirect_to_test")
|
|
37
37
|
did_call_redirect = false
|
|
38
38
|
c.on_redirect do|x|
|
|
39
39
|
did_call_redirect = true
|
|
@@ -43,7 +43,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
|
|
|
43
43
|
|
|
44
44
|
assert did_call_redirect, "we should have called on_redirect"
|
|
45
45
|
|
|
46
|
-
c = Curl::Easy.new(
|
|
46
|
+
c = Curl::Easy.new("http://127.0.0.1:#{@port}/redirect_to_test")
|
|
47
47
|
did_call_redirect = false
|
|
48
48
|
c.follow_location = true
|
|
49
49
|
# NOTE: while this API is not supported by libcurl e.g. there is no redirect function callback in libcurl we could
|
|
@@ -57,7 +57,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
|
|
|
57
57
|
|
|
58
58
|
assert did_call_redirect, "we should have called on_redirect"
|
|
59
59
|
|
|
60
|
-
c.url =
|
|
60
|
+
c.url = "http://127.0.0.1:#{@port}/test"
|
|
61
61
|
c.perform
|
|
62
62
|
assert_equal 0, c.redirect_count
|
|
63
63
|
assert_equal 200, c.response_code
|
|
@@ -65,7 +65,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
|
|
|
65
65
|
puts "checking for raise support"
|
|
66
66
|
did_raise = false
|
|
67
67
|
begin
|
|
68
|
-
c = Curl::Easy.new(
|
|
68
|
+
c = Curl::Easy.new("http://127.0.0.1:#{@port}/redirect_to_test")
|
|
69
69
|
did_call_redirect = false
|
|
70
70
|
c.on_redirect do|x|
|
|
71
71
|
raise "raise"
|
|
@@ -4,16 +4,17 @@ class BugRaiseOnCallback < Test::Unit::TestCase
|
|
|
4
4
|
include BugTestServerSetupTeardown
|
|
5
5
|
|
|
6
6
|
def setup
|
|
7
|
-
@port =
|
|
7
|
+
@port = unused_local_port
|
|
8
8
|
super
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def test_on_complte
|
|
12
|
-
|
|
12
|
+
url = "http://127.0.0.1:#{@port}/test"
|
|
13
|
+
c = Curl::Easy.new(url)
|
|
13
14
|
did_raise = false
|
|
14
15
|
begin
|
|
15
16
|
c.on_complete do|x|
|
|
16
|
-
assert_equal
|
|
17
|
+
assert_equal url, x.url
|
|
17
18
|
raise "error complete" # this will get swallowed
|
|
18
19
|
end
|
|
19
20
|
c.perform
|
data/tests/helper.rb
CHANGED
|
@@ -206,6 +206,13 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
|
206
206
|
end
|
|
207
207
|
|
|
208
208
|
module BugTestServerSetupTeardown
|
|
209
|
+
def unused_local_port
|
|
210
|
+
socket = TCPServer.new('127.0.0.1', 0)
|
|
211
|
+
socket.addr[1]
|
|
212
|
+
ensure
|
|
213
|
+
socket.close if socket
|
|
214
|
+
end
|
|
215
|
+
|
|
209
216
|
def setup
|
|
210
217
|
@port ||= 9992
|
|
211
218
|
@server = WEBrick::HTTPServer.new(:Port => @port, :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
data/tests/tc_curl_easy.rb
CHANGED
|
@@ -123,6 +123,50 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
123
123
|
assert_equal "", easy.body_str.to_s
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
+
def test_head_request_restores_easy_state_after_callback_exception
|
|
127
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
128
|
+
easy.on_complete { raise "head complete blew up" }
|
|
129
|
+
|
|
130
|
+
error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.http("HEAD") }
|
|
131
|
+
assert_equal "head complete blew up", error.message
|
|
132
|
+
|
|
133
|
+
easy.on_complete { |_curl| }
|
|
134
|
+
easy.perform
|
|
135
|
+
|
|
136
|
+
assert_equal "GET", easy.body_str
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_patch_request_does_not_leak_custom_method_to_next_perform
|
|
140
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
141
|
+
|
|
142
|
+
easy.http_patch("a=b")
|
|
143
|
+
assert_equal "PATCH\na=b", easy.body_str
|
|
144
|
+
|
|
145
|
+
easy.perform
|
|
146
|
+
assert_equal "GET", easy.body_str
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def test_put_request_does_not_leak_custom_method_to_next_perform
|
|
150
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
151
|
+
|
|
152
|
+
easy.http_put("payload")
|
|
153
|
+
assert_equal "PUT\npayload", easy.body_str
|
|
154
|
+
|
|
155
|
+
easy.perform
|
|
156
|
+
assert_equal "GET", easy.body_str
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_failed_put_data_setup_does_not_leave_easy_in_upload_mode
|
|
160
|
+
easy = Curl::Easy.new(TestServlet.url)
|
|
161
|
+
easy.headers = ["X-Test: true"]
|
|
162
|
+
|
|
163
|
+
assert_raise(RuntimeError) { easy.put_data = "payload" }
|
|
164
|
+
|
|
165
|
+
easy.headers = {}
|
|
166
|
+
easy.perform
|
|
167
|
+
assert_equal "GET", easy.body_str
|
|
168
|
+
end
|
|
169
|
+
|
|
126
170
|
def test_perform_releases_failed_implicit_multi_without_follow_up_easy_perform
|
|
127
171
|
Curl::Easy.flush_deferred_multi_closes
|
|
128
172
|
assert_equal 0, Curl::Easy.deferred_multi_closes.length
|
|
@@ -1301,6 +1345,24 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1301
1345
|
assert_equal "POST\nfoo=bar", curl.body_str
|
|
1302
1346
|
end
|
|
1303
1347
|
|
|
1348
|
+
def test_multipart_patch_build_failure_restores_easy_request_state
|
|
1349
|
+
curl = Curl::Easy.new(TestServlet.url)
|
|
1350
|
+
curl.multipart_form_post = true
|
|
1351
|
+
field = Curl::PostField.content('document_id', '5')
|
|
1352
|
+
|
|
1353
|
+
field.name = nil
|
|
1354
|
+
error = assert_raise(Curl::Err::InvalidPostFieldError) do
|
|
1355
|
+
# The first field allocates native multipart form state before the second field raises.
|
|
1356
|
+
curl.http_patch(Curl::PostField.content('keep', 'allocated'), field)
|
|
1357
|
+
end
|
|
1358
|
+
assert_match(/Cannot post unnamed field/, error.message)
|
|
1359
|
+
|
|
1360
|
+
curl.multipart_form_post = false
|
|
1361
|
+
curl.perform
|
|
1362
|
+
|
|
1363
|
+
assert_equal 'GET', curl.body_str
|
|
1364
|
+
end
|
|
1365
|
+
|
|
1304
1366
|
def test_delete_remote
|
|
1305
1367
|
curl = Curl::Easy.new(TestServlet.url)
|
|
1306
1368
|
curl.http_delete
|
|
@@ -1358,6 +1420,44 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1358
1420
|
assert_match(/message$/, curl.body_str)
|
|
1359
1421
|
end
|
|
1360
1422
|
|
|
1423
|
+
def test_put_data_from_to_s_object
|
|
1424
|
+
curl = Curl::Easy.new(TestServlet.url)
|
|
1425
|
+
curl.put_data = :message
|
|
1426
|
+
|
|
1427
|
+
curl.perform
|
|
1428
|
+
|
|
1429
|
+
assert_equal "PUT\nmessage", curl.body_str
|
|
1430
|
+
end
|
|
1431
|
+
|
|
1432
|
+
def test_put_data_read_exception_sets_result_and_releases_callback_state
|
|
1433
|
+
error_class = Class.new(StandardError)
|
|
1434
|
+
reader_class = Class.new do
|
|
1435
|
+
define_method(:read) do |_len|
|
|
1436
|
+
raise error_class, 'upload read failed'
|
|
1437
|
+
end
|
|
1438
|
+
|
|
1439
|
+
def seek(_offset, _whence = SEEK_SET)
|
|
1440
|
+
0
|
|
1441
|
+
end
|
|
1442
|
+
|
|
1443
|
+
def stat
|
|
1444
|
+
Struct.new(:size).new(1)
|
|
1445
|
+
end
|
|
1446
|
+
end
|
|
1447
|
+
|
|
1448
|
+
curl = Curl::Easy.new(TestServlet.url)
|
|
1449
|
+
curl.put_data = reader_class.new
|
|
1450
|
+
|
|
1451
|
+
error = assert_raise(error_class) { curl.perform }
|
|
1452
|
+
assert_equal 'upload read failed', error.message
|
|
1453
|
+
assert_not_equal 0, curl.last_result
|
|
1454
|
+
|
|
1455
|
+
curl.url = TestServlet.url
|
|
1456
|
+
curl.http_get
|
|
1457
|
+
|
|
1458
|
+
assert_equal 'GET', curl.body_str
|
|
1459
|
+
end
|
|
1460
|
+
|
|
1361
1461
|
# https://github.com/taf2/curb/issues/101
|
|
1362
1462
|
def test_put_data_null_bytes
|
|
1363
1463
|
curl = Curl::Easy.new(TestServlet.url)
|
|
@@ -1460,6 +1560,10 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1460
1560
|
# header value and encoding; skip the NTLM-specific assertion.
|
|
1461
1561
|
return
|
|
1462
1562
|
end
|
|
1563
|
+
# curl 8.20.0 disables NTLM by default and plans to remove it in
|
|
1564
|
+
# September 2026; libcurl silently downgrades to Basic when NTLM is
|
|
1565
|
+
# unavailable, so skip the NTLM-specific assertion in that case.
|
|
1566
|
+
return unless Curl.ntlm?
|
|
1463
1567
|
curl = Curl::Easy.new(TestServlet.url)
|
|
1464
1568
|
curl.username = "foo"
|
|
1465
1569
|
curl.password = "bar"
|
|
@@ -1501,6 +1605,18 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
|
1501
1605
|
easy.http_get
|
|
1502
1606
|
end
|
|
1503
1607
|
|
|
1608
|
+
def test_easy_close_restores_error_buffer
|
|
1609
|
+
easy = Curl::Easy.new('http://127.0.0.1:1')
|
|
1610
|
+
assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
|
|
1611
|
+
assert_not_nil easy.last_error
|
|
1612
|
+
|
|
1613
|
+
easy.close
|
|
1614
|
+
easy.url = 'http://127.0.0.1:1'
|
|
1615
|
+
assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
|
|
1616
|
+
|
|
1617
|
+
assert_not_nil easy.last_error
|
|
1618
|
+
end
|
|
1619
|
+
|
|
1504
1620
|
def test_easy_reset
|
|
1505
1621
|
easy = Curl::Easy.new
|
|
1506
1622
|
easy.url = TestServlet.url + "?query=foo"
|