curb 1.4.0 → 1.4.1

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
@@ -93,6 +93,8 @@ typedef struct {
93
93
  char unrestricted_auth;
94
94
  char verbose;
95
95
  char multipart_form_post;
96
+ char multipart_building; /* Disallow reentry while Ruby builds the form. */
97
+ char multipart_form_active; /* Temporary CURLOPT_HTTPPOST owned by perform. */
96
98
  char enable_cookies;
97
99
  char cookielist_engine_enabled; /* track if CURLOPT_COOKIELIST was used with a non-command to enable engine */
98
100
  char ignore_content_length;
@@ -124,6 +126,9 @@ typedef struct {
124
126
  extern VALUE cCurlEasy;
125
127
  extern const rb_data_type_t ruby_curl_easy_data_type;
126
128
 
129
+ ruby_curl_easy *ruby_curl_easy_get_handle(VALUE self);
130
+ void ruby_curl_easy_check_access(ruby_curl_easy *rbce);
131
+
127
132
  VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce);
128
133
  VALUE ruby_curl_easy_cleanup(VALUE self, ruby_curl_easy *rbce);
129
134
  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);
@@ -1022,7 +1051,8 @@ static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) {
1022
1051
  VALUE easy_callback_error = Qnil;
1023
1052
  VALUE callargs;
1024
1053
  VALUE did_raise = rb_hash_new();
1025
- long redirect_count;
1054
+ long redirect_count = 0;
1055
+ CURLcode info_result;
1026
1056
 
1027
1057
  args->rbcm->callback_active = 1;
1028
1058
 
@@ -1040,12 +1070,18 @@ static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) {
1040
1070
  }
1041
1071
 
1042
1072
  #ifdef HAVE_CURLINFO_RESPONSE_CODE
1043
- curl_easy_getinfo(args->rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
1073
+ info_result = curl_easy_getinfo(args->rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
1044
1074
  #else /* use fdsets path for waiting */
1045
1075
  // old libcurl
1046
- curl_easy_getinfo(args->rbce->curl, CURLINFO_HTTP_CODE, &response_code);
1076
+ info_result = curl_easy_getinfo(args->rbce->curl, CURLINFO_HTTP_CODE, &response_code);
1047
1077
  #endif
1048
- curl_easy_getinfo(args->rbce->curl, CURLINFO_REDIRECT_COUNT, &redirect_count);
1078
+ if (info_result != CURLE_OK) {
1079
+ raise_curl_easy_error_exception(info_result);
1080
+ }
1081
+ info_result = curl_easy_getinfo(args->rbce->curl, CURLINFO_REDIRECT_COUNT, &redirect_count);
1082
+ if (info_result != CURLE_OK) {
1083
+ raise_curl_easy_error_exception(info_result);
1084
+ }
1049
1085
 
1050
1086
  if (args->result != 0) {
1051
1087
  if (!rb_easy_nil("failure_proc")) {
@@ -2507,6 +2543,7 @@ static VALUE ruby_curl_multi_perform_guard_ensure(VALUE self) {
2507
2543
  ruby_curl_multi *rbcm;
2508
2544
  TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
2509
2545
  rbcm->perform_active = 0;
2546
+ rbcm->perform_thread = Qnil;
2510
2547
  rbcm->callback_active = 0;
2511
2548
  rbcm->allow_close_during_perform = 0;
2512
2549
  return Qnil;
@@ -2521,7 +2558,11 @@ static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE sel
2521
2558
  rb_raise(rb_eRuntimeError, "Cannot recursively perform an active Curl::Multi handle");
2522
2559
  }
2523
2560
 
2561
+ /* Reopening an autoclosed multi initializes its guard fields. Do this before
2562
+ * claiming ownership so repeated performs retain the full-lifetime guard. */
2563
+ ruby_curl_multi_ensure_handle(rbcm);
2524
2564
  rbcm->perform_active = 1;
2565
+ rbcm->perform_thread = rb_thread_current();
2525
2566
  args.argc = argc;
2526
2567
  args.argv = argv;
2527
2568
  args.self = self;
@@ -2578,6 +2619,7 @@ VALUE ruby_curl_multi_close(VALUE self) {
2578
2619
  ruby_curl_multi *rbcm;
2579
2620
  TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
2580
2621
 
2622
+ ruby_curl_multi_check_thread(rbcm);
2581
2623
  if ((rbcm->perform_active || rbcm->callback_active) && !rbcm->allow_close_during_perform) {
2582
2624
  rb_raise(rb_eRuntimeError, "Cannot close an active Curl::Multi handle during perform");
2583
2625
  }
@@ -2615,6 +2657,7 @@ static int mark_attached_i(st_data_t key, st_data_t val, st_data_t arg) {
2615
2657
  static void curl_multi_mark(void *ptr) {
2616
2658
  ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr;
2617
2659
  if (!rbcm) return;
2660
+ rb_gc_mark(rbcm->perform_thread);
2618
2661
  if (rbcm->attached) {
2619
2662
  st_foreach(rbcm->attached, mark_attached_i, (st_data_t)0);
2620
2663
  }
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
 
data/lib/curl/easy.rb CHANGED
@@ -184,10 +184,16 @@ module Curl
184
184
  set_protocol_allowlist('CURLOPT_REDIR_PROTOCOLS_STR', 'CURLOPT_REDIR_PROTOCOLS', protocols)
185
185
  end
186
186
 
187
- def safe_http!
187
+ def safe_http!(options = {})
188
+ options = Curl.__send__(:safe_http_options, options)
189
+ config = Curl.__send__(:ractor_local_state)[:safety_config] || Curl::SafetyConfig.new
190
+ limit = Curl::SafetyConfig.new
191
+ limit.max_body_bytes = options.fetch(:max_body_bytes, config.max_body_bytes)
188
192
  __curb_set_safety_override!(
189
193
  :protocols => [:http, :https],
190
- :redirect_protocols => [:http, :https]
194
+ :redirect_protocols => [:http, :https],
195
+ :network_policy => :public,
196
+ :max_body_bytes => limit.max_body_bytes
191
197
  )
192
198
  end
193
199
 
@@ -197,7 +203,8 @@ module Curl
197
203
  @__curb_safety_override = (defined?(@__curb_safety_override) && @__curb_safety_override) ? @__curb_safety_override.dup : {}
198
204
  @__curb_safety_override[:protocols] = Array(options[:protocols]).map { |protocol| protocol.to_s.downcase.to_sym } if options.key?(:protocols)
199
205
  @__curb_safety_override[:redirect_protocols] = Array(options[:redirect_protocols]).map { |protocol| protocol.to_s.downcase.to_sym } if options.key?(:redirect_protocols)
200
- @__curb_safety_override[:max_body_bytes] = options[:max_body_bytes] if options.key?(:max_body_bytes) && options[:max_body_bytes]
206
+ @__curb_safety_override[:max_body_bytes] = options[:max_body_bytes] if options.key?(:max_body_bytes)
207
+ @__curb_safety_override[:network_policy] = options[:network_policy] if options.key?(:network_policy)
201
208
  @__curb_safety_override_generation = __curb_safety_override_generation + 1
202
209
  Curl.__send__(:apply_safety!, self) if Curl.respond_to?(:apply_safety!, true)
203
210
  self
data/lib/curl/multi.rb CHANGED
@@ -454,9 +454,9 @@ module Curl
454
454
 
455
455
  def remove(easy)
456
456
  return self if !requests[easy.object_id]
457
+ _remove(easy)
457
458
  requests.delete(easy.object_id)
458
459
  __curb_native_safety_signatures.delete(easy.object_id)
459
- _remove(easy)
460
460
  self
461
461
  end
462
462
 
data/lib/curl.rb CHANGED
@@ -23,6 +23,7 @@ module Curl
23
23
 
24
24
  class SafetyConfig
25
25
  DEFAULT_PROTOCOLS = [:http, :https].freeze
26
+ DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024
26
27
 
27
28
  attr_reader :max_body_bytes, :network_policy
28
29
  attr_accessor :allow_proxies, :allow_resolve, :allow_connect_to, :allow_doh, :allow_unix_socket
@@ -30,8 +31,8 @@ module Curl
30
31
  def initialize
31
32
  @protocols = DEFAULT_PROTOCOLS
32
33
  @redirect_protocols = nil
33
- @max_body_bytes = nil
34
- @network_policy = nil
34
+ @max_body_bytes = DEFAULT_MAX_BODY_BYTES
35
+ @network_policy = :public
35
36
  @allowed_hosts = nil
36
37
  @allowed_proxy_hosts = nil
37
38
  @allowed_cidrs = nil
@@ -217,9 +218,11 @@ module Curl
217
218
  apply_allowed_hosts!(easy, config.allowed_hosts) if config&.allowed_hosts
218
219
  apply_allowed_cidrs!(easy, config) if config&.allowed_cidrs
219
220
 
220
- if config&.network_policy
221
- easy.network_policy = config.network_policy
222
- apply_public_network_policy_controls!(easy, config) if config.network_policy == :public
221
+ network_policy = config&.network_policy
222
+ network_policy = :public if override && override[:network_policy] == :public
223
+ if network_policy
224
+ easy.network_policy = network_policy
225
+ apply_public_network_policy_controls!(easy, config || SafetyConfig.new) if network_policy == :public
223
226
  end
224
227
 
225
228
  apply_max_body_bytes!(easy, config.max_body_bytes) if config&.max_body_bytes
@@ -653,8 +656,7 @@ module Curl
653
656
 
654
657
  http(verb, url, post_body, put_data) do |handle|
655
658
  yield handle if block
656
- handle.safe_http!
657
- handle.max_body_bytes = options[:max_body_bytes] if options.key?(:max_body_bytes)
659
+ handle.safe_http!(options)
658
660
  end
659
661
  end
660
662