curb 1.4.0 → 1.5.0
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/ChangeLog.md +447 -0
- data/README.md +39 -8
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +567 -186
- data/ext/curb_easy.h +10 -0
- data/ext/curb_macros.h +16 -0
- data/ext/curb_multi.c +83 -23
- data/ext/curb_multi.h +4 -0
- data/ext/curb_postfield.c +103 -114
- data/ext/curb_postfield.h +10 -1
- data/ext/extconf.rb +1 -0
- data/lib/curl/easy.rb +24 -4
- data/lib/curl/multi.rb +31 -9
- data/lib/curl.rb +78 -15
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +1 -1
- data/tests/bug_follow_redirect_288.rb +1 -1
- data/tests/bug_poison.rb +1 -1
- data/tests/bug_raise_on_callback.rb +1 -1
- data/tests/helper.rb +9 -8
- data/tests/tc_curl_easy.rb +304 -0
- data/tests/tc_curl_easy_cookielist.rb +10 -8
- data/tests/tc_curl_maxfilesize.rb +109 -5
- data/tests/tc_curl_multi.rb +32 -0
- data/tests/tc_curl_native_coverage.rb +57 -0
- data/tests/tc_curl_network_policy.rb +133 -33
- data/tests/tc_curl_postfield.rb +137 -0
- data/tests/tc_curl_protocols.rb +19 -7
- data/tests/tc_curl_security_hardening.rb +386 -0
- data/tests/tc_curl_transfer_guard.rb +112 -0
- data/tests/tc_fiber_scheduler.rb +46 -6
- data/tests/tc_ractor.rb +52 -1
- metadata +7 -2
data/ext/curb_easy.h
CHANGED
|
@@ -65,6 +65,8 @@ typedef struct {
|
|
|
65
65
|
long http_auth_types;
|
|
66
66
|
long proxy_auth_types;
|
|
67
67
|
long max_redirs;
|
|
68
|
+
char max_redirs_set; /* user configured max_redirs (nil = libcurl-style default) */
|
|
69
|
+
char protocols_set; /* CURLOPT_PROTOCOLS(_STR) allowlist configured */
|
|
68
70
|
unsigned long timeout;
|
|
69
71
|
unsigned long timeout_ms;
|
|
70
72
|
unsigned long connect_timeout;
|
|
@@ -93,6 +95,8 @@ typedef struct {
|
|
|
93
95
|
char unrestricted_auth;
|
|
94
96
|
char verbose;
|
|
95
97
|
char multipart_form_post;
|
|
98
|
+
char multipart_building; /* Disallow reentry while Ruby builds the form. */
|
|
99
|
+
char multipart_form_active; /* Temporary CURLOPT_HTTPPOST owned by perform. */
|
|
96
100
|
char enable_cookies;
|
|
97
101
|
char cookielist_engine_enabled; /* track if CURLOPT_COOKIELIST was used with a non-command to enable engine */
|
|
98
102
|
char ignore_content_length;
|
|
@@ -111,6 +115,9 @@ typedef struct {
|
|
|
111
115
|
struct curl_slist *curl_connect_to;
|
|
112
116
|
curb_cidr_rule *network_allowed_cidr_rules;
|
|
113
117
|
char **network_allowed_hosts;
|
|
118
|
+
/* CURLOPT_STDERR target. curb owns this FILE* (on a duplicated descriptor)
|
|
119
|
+
* so closing or collecting the caller's IO cannot free it under libcurl. */
|
|
120
|
+
FILE *stderr_file;
|
|
114
121
|
|
|
115
122
|
/* attachment_sequence of the multi at the time this easy was last added. */
|
|
116
123
|
unsigned long multi_attachment_generation;
|
|
@@ -124,6 +131,9 @@ typedef struct {
|
|
|
124
131
|
extern VALUE cCurlEasy;
|
|
125
132
|
extern const rb_data_type_t ruby_curl_easy_data_type;
|
|
126
133
|
|
|
134
|
+
ruby_curl_easy *ruby_curl_easy_get_handle(VALUE self);
|
|
135
|
+
void ruby_curl_easy_check_access(ruby_curl_easy *rbce);
|
|
136
|
+
|
|
127
137
|
VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce);
|
|
128
138
|
VALUE ruby_curl_easy_cleanup(VALUE self, ruby_curl_easy *rbce);
|
|
129
139
|
VALUE rb_curl_easy_take_callback_error(ruby_curl_easy *rbce);
|
data/ext/curb_macros.h
CHANGED
|
@@ -20,12 +20,17 @@
|
|
|
20
20
|
#define rb_easy_get_str(key) \
|
|
21
21
|
RSTRING_PTR((rb_easy_type_check(key,T_STRING) ? rb_easy_get(key) : rb_str_to_str(rb_easy_get(key))))
|
|
22
22
|
|
|
23
|
+
/* Easy attribute macros also participate in the native access guard. */
|
|
24
|
+
#define CURB_CHECK_ACCESS_ruby_curl_easy(ptr) ruby_curl_easy_check_access(ptr)
|
|
25
|
+
#define CURB_CHECK_ACCESS_ruby_curl_postfield(ptr) ((void)0)
|
|
26
|
+
|
|
23
27
|
/* getter/setter macros for various things */
|
|
24
28
|
/* setter for anything that stores a ruby VALUE in the struct */
|
|
25
29
|
#define CURB_OBJECT_SETTER(type, attr) \
|
|
26
30
|
type *ptr; \
|
|
27
31
|
\
|
|
28
32
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
33
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
29
34
|
ptr->attr = attr; \
|
|
30
35
|
\
|
|
31
36
|
return attr;
|
|
@@ -35,6 +40,7 @@
|
|
|
35
40
|
type *ptr; \
|
|
36
41
|
\
|
|
37
42
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
43
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
38
44
|
return ptr->attr;
|
|
39
45
|
|
|
40
46
|
/* setter for anything that stores a ruby VALUE in the struct opts hash */
|
|
@@ -42,6 +48,7 @@
|
|
|
42
48
|
type *ptr; \
|
|
43
49
|
\
|
|
44
50
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
51
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
45
52
|
rb_hash_aset(ptr->opts, rb_easy_hkey(#attr), attr); \
|
|
46
53
|
\
|
|
47
54
|
return attr;
|
|
@@ -51,12 +58,14 @@
|
|
|
51
58
|
type *ptr; \
|
|
52
59
|
\
|
|
53
60
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
61
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
54
62
|
return rb_hash_aref(ptr->opts, rb_easy_hkey(#attr));
|
|
55
63
|
|
|
56
64
|
/* setter for bool flags */
|
|
57
65
|
#define CURB_BOOLEAN_SETTER(type, attr) \
|
|
58
66
|
type *ptr; \
|
|
59
67
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
68
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
60
69
|
\
|
|
61
70
|
if (attr == Qnil || attr == Qfalse) { \
|
|
62
71
|
ptr->attr = 0; \
|
|
@@ -70,6 +79,7 @@
|
|
|
70
79
|
#define CURB_BOOLEAN_GETTER(type, attr) \
|
|
71
80
|
type *ptr; \
|
|
72
81
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
82
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
73
83
|
\
|
|
74
84
|
return((ptr->attr) ? Qtrue : Qfalse);
|
|
75
85
|
|
|
@@ -79,6 +89,7 @@
|
|
|
79
89
|
VALUE oldproc; \
|
|
80
90
|
\
|
|
81
91
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
92
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
82
93
|
\
|
|
83
94
|
oldproc = ptr->handler; \
|
|
84
95
|
rb_scan_args(argc, argv, "0&", &ptr->handler); \
|
|
@@ -91,6 +102,7 @@
|
|
|
91
102
|
VALUE oldproc, newproc; \
|
|
92
103
|
\
|
|
93
104
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
105
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
94
106
|
\
|
|
95
107
|
oldproc = rb_hash_aref(ptr->opts, rb_easy_hkey(#handler)); \
|
|
96
108
|
rb_scan_args(argc, argv, "0&", &newproc); \
|
|
@@ -104,6 +116,7 @@
|
|
|
104
116
|
type *ptr; \
|
|
105
117
|
\
|
|
106
118
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
119
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
107
120
|
if (attr == Qnil) { \
|
|
108
121
|
ptr->attr = nilval; \
|
|
109
122
|
} else { \
|
|
@@ -117,6 +130,7 @@
|
|
|
117
130
|
type *ptr; \
|
|
118
131
|
\
|
|
119
132
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
133
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
120
134
|
if (ptr->attr == nilval) { \
|
|
121
135
|
return Qnil; \
|
|
122
136
|
} else { \
|
|
@@ -128,6 +142,7 @@
|
|
|
128
142
|
type *ptr; \
|
|
129
143
|
\
|
|
130
144
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
145
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
131
146
|
if (attr == Qnil) { \
|
|
132
147
|
ptr->attr = 0; \
|
|
133
148
|
} else { \
|
|
@@ -147,6 +162,7 @@
|
|
|
147
162
|
type *ptr; \
|
|
148
163
|
\
|
|
149
164
|
TypedData_Get_Struct(self, type, &type##_data_type, ptr); \
|
|
165
|
+
CURB_CHECK_ACCESS_##type(ptr); \
|
|
150
166
|
if (ptr->attr == 0) { \
|
|
151
167
|
return Qnil; \
|
|
152
168
|
} else { \
|
data/ext/curb_multi.c
CHANGED
|
@@ -250,7 +250,7 @@ static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm);
|
|
|
250
250
|
static void curl_multi_mark(void *ptr);
|
|
251
251
|
static void ruby_curl_multi_ensure_handle(ruby_curl_multi *rbcm);
|
|
252
252
|
static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce);
|
|
253
|
-
|
|
253
|
+
void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy);
|
|
254
254
|
static VALUE ruby_curl_multi_mark_closed(VALUE self);
|
|
255
255
|
static VALUE ruby_curl_multi_alloc(VALUE klass);
|
|
256
256
|
static VALUE ruby_curl_multi_initialize(VALUE self);
|
|
@@ -494,7 +494,29 @@ static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce) {
|
|
|
494
494
|
return st_lookup(rbcm->attached, (st_data_t)rbce, &value);
|
|
495
495
|
}
|
|
496
496
|
|
|
497
|
-
|
|
497
|
+
/* Attachment, rather than easy.multi alone, distinguishes a live transfer
|
|
498
|
+
* from a completed easy whose status callback still sees its former multi. */
|
|
499
|
+
int rb_curl_multi_easy_performing(void *rbce_ptr) {
|
|
500
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
|
501
|
+
ruby_curl_multi *rbcm = ruby_curl_multi_pointer_if_compatible(rbce->multi);
|
|
502
|
+
return rbcm && rbcm->perform_active && rb_curl_multi_has_easy(rbcm, rbce);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
static void ruby_curl_multi_check_thread(ruby_curl_multi *rbcm) {
|
|
506
|
+
if (rbcm->perform_active && rbcm->perform_thread != rb_thread_current()) {
|
|
507
|
+
rb_raise(rb_eRuntimeError, "Cannot access a Curl::Multi handle performing on another thread");
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
void rb_curl_multi_check_easy_thread(void *rbce_ptr) {
|
|
512
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
|
513
|
+
ruby_curl_multi *rbcm = ruby_curl_multi_pointer_if_compatible(rbce->multi);
|
|
514
|
+
if (rbcm && rb_curl_multi_has_easy(rbcm, rbce)) {
|
|
515
|
+
ruby_curl_multi_check_thread(rbcm);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy) {
|
|
498
520
|
VALUE requests;
|
|
499
521
|
VALUE object_id;
|
|
500
522
|
VALUE safety_signatures;
|
|
@@ -569,6 +591,7 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
|
569
591
|
rbcm->running = 0;
|
|
570
592
|
rbcm->closed = 0;
|
|
571
593
|
rbcm->perform_active = 0;
|
|
594
|
+
rbcm->perform_thread = Qnil;
|
|
572
595
|
rbcm->callback_active = 0;
|
|
573
596
|
rbcm->allow_close_during_perform = 0;
|
|
574
597
|
|
|
@@ -586,6 +609,7 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
|
586
609
|
}
|
|
587
610
|
|
|
588
611
|
static void ruby_curl_multi_ensure_handle(ruby_curl_multi *rbcm) {
|
|
612
|
+
ruby_curl_multi_check_thread(rbcm);
|
|
589
613
|
if (!rbcm->handle) {
|
|
590
614
|
if (rbcm->closed) {
|
|
591
615
|
raise_curl_multi_error_exception(CURLM_BAD_HANDLE);
|
|
@@ -615,6 +639,10 @@ static VALUE ruby_curl_multi_initialize(VALUE self) {
|
|
|
615
639
|
|
|
616
640
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
617
641
|
|
|
642
|
+
if (rbcm->perform_active) {
|
|
643
|
+
rb_raise(rb_eRuntimeError, "Cannot initialize an active Curl::Multi handle during perform");
|
|
644
|
+
}
|
|
645
|
+
|
|
618
646
|
ruby_curl_multi_init(rbcm);
|
|
619
647
|
|
|
620
648
|
/*
|
|
@@ -781,7 +809,7 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
|
781
809
|
ruby_curl_multi *existing_rbcm;
|
|
782
810
|
|
|
783
811
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
784
|
-
|
|
812
|
+
rbce = ruby_curl_easy_get_handle(easy);
|
|
785
813
|
ruby_curl_multi_ensure_handle(rbcm);
|
|
786
814
|
|
|
787
815
|
if (rb_curl_multi_has_easy(rbcm, rbce)) {
|
|
@@ -846,6 +874,7 @@ VALUE ruby_curl_multi_remove(VALUE self, VALUE rb_easy_handle) {
|
|
|
846
874
|
|
|
847
875
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
848
876
|
|
|
877
|
+
ruby_curl_multi_check_thread(rbcm);
|
|
849
878
|
rb_curl_multi_remove(rbcm, rb_easy_handle);
|
|
850
879
|
|
|
851
880
|
return self;
|
|
@@ -855,7 +884,7 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
|
|
|
855
884
|
CURLMcode result;
|
|
856
885
|
ruby_curl_easy *rbce;
|
|
857
886
|
|
|
858
|
-
|
|
887
|
+
rbce = ruby_curl_easy_get_handle(easy);
|
|
859
888
|
result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
|
860
889
|
if (result != 0) {
|
|
861
890
|
raise_curl_multi_error_exception(result);
|
|
@@ -879,20 +908,29 @@ static VALUE call_status_handler2(VALUE ary) {
|
|
|
879
908
|
return rb_funcall(rb_ary_entry(ary, 0), idCall, 2, rb_ary_entry(ary, 1), rb_ary_entry(ary, 2));
|
|
880
909
|
}
|
|
881
910
|
|
|
911
|
+
/* The default body/header buffers grow geometrically, leaving up to half
|
|
912
|
+
* their capacity unused. Once the transfer is complete, give that back so a
|
|
913
|
+
* retained body_str/header_str is exact-fit. rb_str_resize only reallocates
|
|
914
|
+
* when the excess is significant. */
|
|
915
|
+
static void trim_default_buffers(ruby_curl_easy *rbce) {
|
|
916
|
+
VALUE body = rb_easy_get("body_data");
|
|
917
|
+
VALUE header = rb_easy_get("header_data");
|
|
918
|
+
if (RB_TYPE_P(body, T_STRING) && !OBJ_FROZEN(body)) {
|
|
919
|
+
rb_str_resize(body, RSTRING_LEN(body));
|
|
920
|
+
}
|
|
921
|
+
if (RB_TYPE_P(header, T_STRING) && !OBJ_FROZEN(header)) {
|
|
922
|
+
rb_str_resize(header, RSTRING_LEN(header));
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
882
926
|
static void flush_stderr_if_any(ruby_curl_easy *rbce) {
|
|
883
927
|
VALUE stderr_io = rb_easy_get("stderr_io");
|
|
884
|
-
|
|
885
|
-
|
|
928
|
+
/* The caller may have closed their IO; curb's own stream stays valid. */
|
|
929
|
+
if (stderr_io != Qnil && !RTEST(rb_funcall(stderr_io, rb_intern("closed?"), 0))) {
|
|
886
930
|
rb_funcall(stderr_io, rb_intern("flush"), 0);
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
if (RB_TYPE_P(stderr_io, T_FILE)) {
|
|
891
|
-
GetOpenFile(stderr_io, open_f_ptr);
|
|
892
|
-
FILE *fp = rb_io_stdio_file(open_f_ptr);
|
|
893
|
-
if (fp) fflush(fp);
|
|
894
|
-
}
|
|
895
|
-
#endif
|
|
931
|
+
}
|
|
932
|
+
if (rbce->stderr_file) {
|
|
933
|
+
fflush(rbce->stderr_file);
|
|
896
934
|
}
|
|
897
935
|
}
|
|
898
936
|
|
|
@@ -1022,7 +1060,8 @@ static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) {
|
|
|
1022
1060
|
VALUE easy_callback_error = Qnil;
|
|
1023
1061
|
VALUE callargs;
|
|
1024
1062
|
VALUE did_raise = rb_hash_new();
|
|
1025
|
-
long redirect_count;
|
|
1063
|
+
long redirect_count = 0;
|
|
1064
|
+
CURLcode info_result;
|
|
1026
1065
|
|
|
1027
1066
|
args->rbcm->callback_active = 1;
|
|
1028
1067
|
|
|
@@ -1040,12 +1079,18 @@ static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) {
|
|
|
1040
1079
|
}
|
|
1041
1080
|
|
|
1042
1081
|
#ifdef HAVE_CURLINFO_RESPONSE_CODE
|
|
1043
|
-
curl_easy_getinfo(args->rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
|
|
1082
|
+
info_result = curl_easy_getinfo(args->rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
|
|
1044
1083
|
#else /* use fdsets path for waiting */
|
|
1045
1084
|
// old libcurl
|
|
1046
|
-
curl_easy_getinfo(args->rbce->curl, CURLINFO_HTTP_CODE, &response_code);
|
|
1085
|
+
info_result = curl_easy_getinfo(args->rbce->curl, CURLINFO_HTTP_CODE, &response_code);
|
|
1047
1086
|
#endif
|
|
1048
|
-
|
|
1087
|
+
if (info_result != CURLE_OK) {
|
|
1088
|
+
raise_curl_easy_error_exception(info_result);
|
|
1089
|
+
}
|
|
1090
|
+
info_result = curl_easy_getinfo(args->rbce->curl, CURLINFO_REDIRECT_COUNT, &redirect_count);
|
|
1091
|
+
if (info_result != CURLE_OK) {
|
|
1092
|
+
raise_curl_easy_error_exception(info_result);
|
|
1093
|
+
}
|
|
1049
1094
|
|
|
1050
1095
|
if (args->result != 0) {
|
|
1051
1096
|
if (!rb_easy_nil("failure_proc")) {
|
|
@@ -1167,6 +1212,7 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
|
1167
1212
|
|
|
1168
1213
|
/* Flush again after removal to cover any last buffered data. */
|
|
1169
1214
|
flush_stderr_if_any(rbce);
|
|
1215
|
+
trim_default_buffers(rbce);
|
|
1170
1216
|
|
|
1171
1217
|
struct multi_complete_callback_args args = {
|
|
1172
1218
|
self,
|
|
@@ -1752,6 +1798,9 @@ static int collect_io_select_ready_i(st_data_t key, st_data_t val, st_data_t arg
|
|
|
1752
1798
|
|
|
1753
1799
|
static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE block) {
|
|
1754
1800
|
CURLMcode mrc;
|
|
1801
|
+
/* Looking up the Ractor-local default costs two method calls; read it once
|
|
1802
|
+
* per drive rather than on every wait. */
|
|
1803
|
+
const long default_timeout_ms = curb_multi_default_timeout();
|
|
1755
1804
|
|
|
1756
1805
|
do {
|
|
1757
1806
|
/* Prime the state: let libcurl act on timeouts to set up sockets. */
|
|
@@ -1763,7 +1812,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1763
1812
|
|
|
1764
1813
|
while (rbcm->running) {
|
|
1765
1814
|
struct timeval tv = {0, 0};
|
|
1766
|
-
long wait_ms =
|
|
1815
|
+
long wait_ms = default_timeout_ms;
|
|
1767
1816
|
|
|
1768
1817
|
if (multi_socket_timer_due(ctx)) {
|
|
1769
1818
|
ctx->timeout_deadline_ms = -1;
|
|
@@ -2253,6 +2302,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2253
2302
|
CURLMcode mcode;
|
|
2254
2303
|
ruby_curl_multi *rbcm;
|
|
2255
2304
|
long timeout_milliseconds;
|
|
2305
|
+
long default_timeout_ms;
|
|
2256
2306
|
VALUE block = Qnil;
|
|
2257
2307
|
#ifndef CURB_USE_MULTI_POLL
|
|
2258
2308
|
int maxfd, rc = -1;
|
|
@@ -2275,7 +2325,10 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2275
2325
|
clear_multi_deferred_exception_source_id_if_any(self);
|
|
2276
2326
|
}
|
|
2277
2327
|
|
|
2278
|
-
|
|
2328
|
+
/* Looking up the Ractor-local default costs two method calls; read it once
|
|
2329
|
+
* per perform rather than up to twice per wait-loop iteration. */
|
|
2330
|
+
default_timeout_ms = curb_multi_default_timeout();
|
|
2331
|
+
timeout_milliseconds = default_timeout_ms;
|
|
2279
2332
|
|
|
2280
2333
|
// Run curl_multi_perform for the first time to get the ball rolling
|
|
2281
2334
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
@@ -2315,8 +2368,8 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2315
2368
|
continue;
|
|
2316
2369
|
}
|
|
2317
2370
|
|
|
2318
|
-
if (timeout_milliseconds < 0 || timeout_milliseconds >
|
|
2319
|
-
timeout_milliseconds =
|
|
2371
|
+
if (timeout_milliseconds < 0 || timeout_milliseconds > default_timeout_ms) {
|
|
2372
|
+
timeout_milliseconds = default_timeout_ms; /* libcurl doesn't know how long to wait, use a default timeout */
|
|
2320
2373
|
/* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
|
|
2321
2374
|
}
|
|
2322
2375
|
|
|
@@ -2507,6 +2560,7 @@ static VALUE ruby_curl_multi_perform_guard_ensure(VALUE self) {
|
|
|
2507
2560
|
ruby_curl_multi *rbcm;
|
|
2508
2561
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
2509
2562
|
rbcm->perform_active = 0;
|
|
2563
|
+
rbcm->perform_thread = Qnil;
|
|
2510
2564
|
rbcm->callback_active = 0;
|
|
2511
2565
|
rbcm->allow_close_during_perform = 0;
|
|
2512
2566
|
return Qnil;
|
|
@@ -2521,7 +2575,11 @@ static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE sel
|
|
|
2521
2575
|
rb_raise(rb_eRuntimeError, "Cannot recursively perform an active Curl::Multi handle");
|
|
2522
2576
|
}
|
|
2523
2577
|
|
|
2578
|
+
/* Reopening an autoclosed multi initializes its guard fields. Do this before
|
|
2579
|
+
* claiming ownership so repeated performs retain the full-lifetime guard. */
|
|
2580
|
+
ruby_curl_multi_ensure_handle(rbcm);
|
|
2524
2581
|
rbcm->perform_active = 1;
|
|
2582
|
+
rbcm->perform_thread = rb_thread_current();
|
|
2525
2583
|
args.argc = argc;
|
|
2526
2584
|
args.argv = argv;
|
|
2527
2585
|
args.self = self;
|
|
@@ -2578,6 +2636,7 @@ VALUE ruby_curl_multi_close(VALUE self) {
|
|
|
2578
2636
|
ruby_curl_multi *rbcm;
|
|
2579
2637
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
2580
2638
|
|
|
2639
|
+
ruby_curl_multi_check_thread(rbcm);
|
|
2581
2640
|
if ((rbcm->perform_active || rbcm->callback_active) && !rbcm->allow_close_during_perform) {
|
|
2582
2641
|
rb_raise(rb_eRuntimeError, "Cannot close an active Curl::Multi handle during perform");
|
|
2583
2642
|
}
|
|
@@ -2615,6 +2674,7 @@ static int mark_attached_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
|
2615
2674
|
static void curl_multi_mark(void *ptr) {
|
|
2616
2675
|
ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr;
|
|
2617
2676
|
if (!rbcm) return;
|
|
2677
|
+
rb_gc_mark(rbcm->perform_thread);
|
|
2618
2678
|
if (rbcm->attached) {
|
|
2619
2679
|
st_foreach(rbcm->attached, mark_attached_i, (st_data_t)0);
|
|
2620
2680
|
}
|
data/ext/curb_multi.h
CHANGED
|
@@ -17,6 +17,7 @@ typedef struct {
|
|
|
17
17
|
int running;
|
|
18
18
|
char closed;
|
|
19
19
|
char perform_active;
|
|
20
|
+
VALUE perform_thread; /* Owner while a drive loop may release the GVL. */
|
|
20
21
|
char callback_active;
|
|
21
22
|
char allow_close_during_perform;
|
|
22
23
|
CURLM *handle;
|
|
@@ -31,6 +32,9 @@ extern VALUE cCurlMulti;
|
|
|
31
32
|
extern const rb_data_type_t ruby_curl_multi_data_type;
|
|
32
33
|
|
|
33
34
|
void init_curb_multi();
|
|
35
|
+
int rb_curl_multi_easy_performing(void *rbce_ptr);
|
|
36
|
+
void rb_curl_multi_check_easy_thread(void *rbce_ptr);
|
|
37
|
+
void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy);
|
|
34
38
|
void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr);
|
|
35
39
|
CURLMcode rb_curl_multi_detach_easy(ruby_curl_multi *rbcm, void *rbce_ptr);
|
|
36
40
|
|
data/ext/curb_postfield.c
CHANGED
|
@@ -20,128 +20,117 @@ VALUE cCurlPostField;
|
|
|
20
20
|
|
|
21
21
|
/* ================= APPEND FORM FUNC ================ */
|
|
22
22
|
|
|
23
|
-
/*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
/* Snapshot before invoking further Ruby coercions. Native storage is private
|
|
24
|
+
* to this request and cannot move under GC or require VM-global GC roots. */
|
|
25
|
+
static ruby_curl_form_string *form_string_snapshot(VALUE value, ruby_curl_form_string **strings) {
|
|
26
|
+
ruby_curl_form_string *entry;
|
|
27
|
+
long length;
|
|
28
|
+
StringValue(value);
|
|
29
|
+
length = RSTRING_LEN(value);
|
|
30
|
+
/* One allocation for the list entry, payload and terminating NUL. */
|
|
31
|
+
entry = ruby_xmalloc(sizeof(*entry) + (size_t)length + 1);
|
|
32
|
+
entry->data = (char *)(entry + 1);
|
|
33
|
+
entry->length = length;
|
|
34
|
+
memcpy(entry->data, RSTRING_PTR(value), (size_t)length);
|
|
35
|
+
entry->data[length] = '\0';
|
|
36
|
+
entry->next = *strings;
|
|
37
|
+
*strings = entry;
|
|
38
|
+
RB_GC_GUARD(value);
|
|
39
|
+
return entry;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static void free_form_strings_until(ruby_curl_form_string **strings,
|
|
43
|
+
ruby_curl_form_string *stop) {
|
|
44
|
+
while (*strings != stop) {
|
|
45
|
+
ruby_curl_form_string *next = (*strings)->next;
|
|
46
|
+
xfree(*strings);
|
|
47
|
+
*strings = next;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
void free_form_strings(ruby_curl_form_string *strings) {
|
|
52
|
+
free_form_strings_until(&strings, NULL);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void append_to_form(VALUE self,
|
|
56
|
+
struct curl_httppost **first,
|
|
57
|
+
struct curl_httppost **last,
|
|
58
|
+
ruby_curl_form_string **strings) {
|
|
32
59
|
ruby_curl_postfield *rbcpf;
|
|
33
60
|
CURLFORMcode result = -1;
|
|
34
|
-
|
|
61
|
+
ruby_curl_form_string *name, *content = NULL, *remote = NULL, *local = NULL, *type = NULL;
|
|
62
|
+
ruby_curl_form_string *previous_strings = *strings;
|
|
63
|
+
int is_file;
|
|
64
|
+
struct curl_forms form[8];
|
|
65
|
+
int n = 0;
|
|
66
|
+
|
|
35
67
|
TypedData_Get_Struct(self, ruby_curl_postfield, &ruby_curl_postfield_data_type, rbcpf);
|
|
36
|
-
|
|
37
|
-
if (rbcpf->name == Qnil) {
|
|
68
|
+
if (NIL_P(rbcpf->name)) {
|
|
38
69
|
rb_raise(eCurlErrInvalidPostField, "Cannot post unnamed field");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} else {
|
|
56
|
-
result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
|
|
57
|
-
CURLFORM_BUFFER, StringValuePtr(rbcpf->remote_file),
|
|
58
|
-
CURLFORM_BUFFERPTR, StringValuePtr(rbcpf->buffer_str),
|
|
59
|
-
CURLFORM_BUFFERLENGTH, RSTRING_LEN(rbcpf->buffer_str),
|
|
60
|
-
CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type),
|
|
61
|
-
CURLFORM_END);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
} else if (rbcpf->content != Qnil) {
|
|
65
|
-
// with content
|
|
66
|
-
if (rbcpf->remote_file == Qnil) {
|
|
67
|
-
rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field with no filename");
|
|
68
|
-
} else {
|
|
69
|
-
if (rbcpf->content_type == Qnil) {
|
|
70
|
-
result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
|
|
71
|
-
CURLFORM_BUFFER, StringValuePtr(rbcpf->remote_file),
|
|
72
|
-
CURLFORM_BUFFERPTR, StringValuePtr(rbcpf->content),
|
|
73
|
-
CURLFORM_BUFFERLENGTH, RSTRING_LEN(rbcpf->content),
|
|
74
|
-
CURLFORM_END);
|
|
75
|
-
} else {
|
|
76
|
-
result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
|
|
77
|
-
CURLFORM_BUFFER, StringValuePtr(rbcpf->remote_file),
|
|
78
|
-
CURLFORM_BUFFERPTR, StringValuePtr(rbcpf->content),
|
|
79
|
-
CURLFORM_BUFFERLENGTH, RSTRING_LEN(rbcpf->content),
|
|
80
|
-
CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type),
|
|
81
|
-
CURLFORM_END);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
} else if (rbcpf->local_file != Qnil) {
|
|
85
|
-
// with local filename
|
|
86
|
-
if (rbcpf->local_file == Qnil) {
|
|
87
|
-
rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field no filename");
|
|
88
|
-
} else {
|
|
89
|
-
if (rbcpf->remote_file == Qnil) {
|
|
90
|
-
rbcpf->remote_file = rbcpf->local_file;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (rbcpf->content_type == Qnil) {
|
|
94
|
-
result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
|
|
95
|
-
CURLFORM_FILE, StringValuePtr(rbcpf->local_file),
|
|
96
|
-
CURLFORM_FILENAME, StringValuePtr(rbcpf->remote_file),
|
|
97
|
-
CURLFORM_END);
|
|
98
|
-
} else {
|
|
99
|
-
result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
|
|
100
|
-
CURLFORM_FILE, StringValuePtr(rbcpf->local_file),
|
|
101
|
-
CURLFORM_FILENAME, StringValuePtr(rbcpf->remote_file),
|
|
102
|
-
CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type),
|
|
103
|
-
CURLFORM_END);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
} else {
|
|
107
|
-
rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field with no data");
|
|
70
|
+
}
|
|
71
|
+
is_file = !NIL_P(rbcpf->local_file) || !NIL_P(rbcpf->remote_file);
|
|
72
|
+
if (!NIL_P(rbcpf->content_proc)) {
|
|
73
|
+
rbcpf->buffer_str = rb_funcall(rbcpf->content_proc, idCall, 1, self);
|
|
74
|
+
StringValue(rbcpf->buffer_str);
|
|
75
|
+
content = form_string_snapshot(rbcpf->buffer_str, strings);
|
|
76
|
+
} else if (!NIL_P(rbcpf->content)) {
|
|
77
|
+
StringValue(rbcpf->content);
|
|
78
|
+
content = form_string_snapshot(rbcpf->content, strings);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
name = form_string_snapshot(rbcpf->name, strings);
|
|
82
|
+
if (is_file) {
|
|
83
|
+
if (content != NULL) {
|
|
84
|
+
if (NIL_P(rbcpf->remote_file)) {
|
|
85
|
+
rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field with no filename");
|
|
108
86
|
}
|
|
87
|
+
} else if (!NIL_P(rbcpf->local_file)) {
|
|
88
|
+
local = form_string_snapshot(rbcpf->local_file, strings);
|
|
89
|
+
if (NIL_P(rbcpf->remote_file)) rbcpf->remote_file = rbcpf->local_file;
|
|
109
90
|
} else {
|
|
110
|
-
|
|
111
|
-
if (rbcpf->content_proc != Qnil) {
|
|
112
|
-
rbcpf->buffer_str = rb_funcall(rbcpf->content_proc, idCall, 1, self);
|
|
113
|
-
|
|
114
|
-
if (rbcpf->content_type == Qnil) {
|
|
115
|
-
result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
|
|
116
|
-
CURLFORM_PTRCONTENTS, StringValuePtr(rbcpf->buffer_str),
|
|
117
|
-
CURLFORM_CONTENTSLENGTH, RSTRING_LEN(rbcpf->buffer_str),
|
|
118
|
-
CURLFORM_END);
|
|
119
|
-
} else {
|
|
120
|
-
result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
|
|
121
|
-
CURLFORM_PTRCONTENTS, StringValuePtr(rbcpf->buffer_str),
|
|
122
|
-
CURLFORM_CONTENTSLENGTH, RSTRING_LEN(rbcpf->buffer_str),
|
|
123
|
-
CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type),
|
|
124
|
-
CURLFORM_END);
|
|
125
|
-
}
|
|
126
|
-
} else if (rbcpf->content != Qnil) {
|
|
127
|
-
if (rbcpf->content_type == Qnil) {
|
|
128
|
-
result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
|
|
129
|
-
CURLFORM_PTRCONTENTS, StringValuePtr(rbcpf->content),
|
|
130
|
-
CURLFORM_CONTENTSLENGTH, RSTRING_LEN(rbcpf->content),
|
|
131
|
-
CURLFORM_END);
|
|
132
|
-
} else {
|
|
133
|
-
result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
|
|
134
|
-
CURLFORM_PTRCONTENTS, StringValuePtr(rbcpf->content),
|
|
135
|
-
CURLFORM_CONTENTSLENGTH, RSTRING_LEN(rbcpf->content),
|
|
136
|
-
CURLFORM_CONTENTTYPE, StringValuePtr(rbcpf->content_type),
|
|
137
|
-
CURLFORM_END);
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
rb_raise(eCurlErrInvalidPostField, "Cannot post content field with no data");
|
|
141
|
-
}
|
|
91
|
+
rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field with no data");
|
|
142
92
|
}
|
|
93
|
+
remote = form_string_snapshot(rbcpf->remote_file, strings);
|
|
94
|
+
} else if (content == NULL) {
|
|
95
|
+
rb_raise(eCurlErrInvalidPostField, "Cannot post content field with no data");
|
|
143
96
|
}
|
|
144
|
-
|
|
97
|
+
if (!NIL_P(rbcpf->content_type)) {
|
|
98
|
+
type = form_string_snapshot(rbcpf->content_type, strings);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* libcurl copies metadata but borrows the private payload for the request.
|
|
102
|
+
* This avoids a second form-owned copy of ordinary field contents. */
|
|
103
|
+
form[n].option = CURLFORM_COPYNAME;
|
|
104
|
+
form[n++].value = name->data;
|
|
105
|
+
if (is_file) {
|
|
106
|
+
form[n].option = content == NULL ? CURLFORM_FILE : CURLFORM_BUFFER;
|
|
107
|
+
form[n++].value = (content == NULL ? local : remote)->data;
|
|
108
|
+
if (content == NULL) {
|
|
109
|
+
form[n].option = CURLFORM_FILENAME;
|
|
110
|
+
form[n++].value = remote->data;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (type != NULL) {
|
|
114
|
+
form[n].option = CURLFORM_CONTENTTYPE;
|
|
115
|
+
form[n++].value = type->data;
|
|
116
|
+
}
|
|
117
|
+
form[n].option = CURLFORM_END;
|
|
118
|
+
form[n].value = NULL;
|
|
119
|
+
|
|
120
|
+
if (content == NULL) {
|
|
121
|
+
result = curl_formadd(first, last, CURLFORM_ARRAY, form, CURLFORM_END);
|
|
122
|
+
} else {
|
|
123
|
+
result = curl_formadd(first, last, CURLFORM_ARRAY, form,
|
|
124
|
+
is_file ? CURLFORM_BUFFERPTR : CURLFORM_PTRCONTENTS,
|
|
125
|
+
content->data,
|
|
126
|
+
is_file ? CURLFORM_BUFFERLENGTH : CURLFORM_CONTENTSLENGTH,
|
|
127
|
+
content->length, CURLFORM_END);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Metadata was copied by curl_formadd. Retain only the payload, if any,
|
|
131
|
+
* which was the first snapshot allocated for this field. */
|
|
132
|
+
free_form_strings_until(strings, content ? content : previous_strings);
|
|
133
|
+
|
|
145
134
|
if (result != 0) {
|
|
146
135
|
const char *reason = NULL;
|
|
147
136
|
|
data/ext/curb_postfield.h
CHANGED
|
@@ -32,9 +32,18 @@ typedef struct {
|
|
|
32
32
|
extern VALUE cCurlPostField;
|
|
33
33
|
extern const rb_data_type_t ruby_curl_postfield_data_type;
|
|
34
34
|
|
|
35
|
+
typedef struct ruby_curl_form_string {
|
|
36
|
+
struct ruby_curl_form_string *next;
|
|
37
|
+
long length;
|
|
38
|
+
char *data;
|
|
39
|
+
} ruby_curl_form_string;
|
|
40
|
+
|
|
41
|
+
void free_form_strings(ruby_curl_form_string *strings);
|
|
42
|
+
|
|
35
43
|
void append_to_form(VALUE self,
|
|
36
44
|
struct curl_httppost **first,
|
|
37
|
-
struct curl_httppost **last
|
|
45
|
+
struct curl_httppost **last,
|
|
46
|
+
ruby_curl_form_string **strings);
|
|
38
47
|
|
|
39
48
|
void init_curb_postfield();
|
|
40
49
|
|