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.
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
- static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy);
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
- static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy) {
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
- TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
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
- TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
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
- if (stderr_io != Qnil) {
885
- /* Flush via Ruby IO API */
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
- #ifdef HAVE_RUBY_IO_H
888
- /* Additionally flush underlying FILE* to be extra safe. */
889
- rb_io_t *open_f_ptr;
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
- curl_easy_getinfo(args->rbce->curl, CURLINFO_REDIRECT_COUNT, &redirect_count);
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 = curb_multi_default_timeout();
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
- timeout_milliseconds = curb_multi_default_timeout();
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 > curb_multi_default_timeout()) {
2319
- timeout_milliseconds = curb_multi_default_timeout(); /* libcurl doesn't know how long to wait, use a default timeout */
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
- /* This gets called by the post method on Curl::Easy for each postfield
24
- * supplied in the arguments. It's job is to add the supplied field to
25
- * the list that's being built for a perform.
26
- *
27
- * THIS FUNC MODIFIES ITS ARGUMENTS. See curl_formadd(3) for details.
28
- */
29
- void append_to_form(VALUE self,
30
- struct curl_httppost **first,
31
- struct curl_httppost **last) {
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
- } else {
40
- if ((rbcpf->local_file != Qnil) || (rbcpf->remote_file != Qnil)) {
41
- // is a file upload field
42
- if (rbcpf->content_proc != Qnil) {
43
- // with content proc
44
- rbcpf->buffer_str = rb_funcall(rbcpf->content_proc, idCall, 1, self);
45
-
46
- if (rbcpf->remote_file == Qnil) {
47
- rb_raise(eCurlErrInvalidPostField, "Cannot post file upload field with no filename");
48
- } else {
49
- if (rbcpf->content_type == Qnil) {
50
- result = curl_formadd(first, last, CURLFORM_PTRNAME, StringValuePtr(rbcpf->name),
51
- CURLFORM_BUFFER, StringValuePtr(rbcpf->remote_file),
52
- CURLFORM_BUFFERPTR, StringValuePtr(rbcpf->buffer_str),
53
- CURLFORM_BUFFERLENGTH, RSTRING_LEN(rbcpf->buffer_str),
54
- CURLFORM_END);
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
- // is a content field
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