curb 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 886abdbc88d51f4d214de43f3a4d12029bfa010bd419ac89e60d0d18a8be3e21
4
- data.tar.gz: 0a574a8d8f70daf2e28f4d0900075a67158ce12187ddb10d4da7700e6eea7ecb
3
+ metadata.gz: 3f62e867480076ceef877a4adcb5c3b4a344769207af06ded4535a3ce1e87375
4
+ data.tar.gz: 4caa926321d05804edbd4b0470951a617192ffd2504ac741524306edec1fe193
5
5
  SHA512:
6
- metadata.gz: 10438303d75fb75a75cfc2b42d9ff0bfcb92fa87cf4f1eea893f51556dfeab29639b9d30c635721224898ddc712fd62c2f46ca3d70c6622889da3821de9e2b19
7
- data.tar.gz: af1a29be7253c4b0c73e593e238f7e49b1c456994559eedd9ae170678b6ac1ecb9fb80982dce1cd9eb3bba422b282fc5c83336e1bc1a1a48c88b3845cc95c424
6
+ metadata.gz: 917f5e735b7187b5089896a83442462ed7f7b9311dbf616e58ab2cae3b2dff38e28f51f401e183214779d8f5159da6fa6dc7d842c0b5e21993cd428f61868d1f
7
+ data.tar.gz: 986d63577872fc80a563a4f2ac29a169888eb549d7ba462ecdef89636f3087be48ad7895e13954824e5af069614601ae30730a0af88cd4b3ad3391997b37c3e3
data/ext/curb.h CHANGED
@@ -28,11 +28,11 @@
28
28
  #include "curb_macros.h"
29
29
 
30
30
  // These should be managed from the Rake 'release' task.
31
- #define CURB_VERSION "1.3.0"
32
- #define CURB_VER_NUM 1030
31
+ #define CURB_VERSION "1.3.2"
32
+ #define CURB_VER_NUM 1032
33
33
  #define CURB_VER_MAJ 1
34
34
  #define CURB_VER_MIN 3
35
- #define CURB_VER_MIC 0
35
+ #define CURB_VER_MIC 2
36
36
  #define CURB_VER_PATCH 0
37
37
 
38
38
 
data/ext/curb_easy.c CHANGED
@@ -107,6 +107,11 @@ static VALUE call_stream_to_s(VALUE stream) {
107
107
  return rb_funcall(stream, rb_intern("to_s"), 0);
108
108
  }
109
109
 
110
+ static VALUE call_string_value(VALUE str) {
111
+ StringValue(str);
112
+ return str;
113
+ }
114
+
110
115
  struct stream_seek_call_args {
111
116
  VALUE stream;
112
117
  curl_off_t offset;
@@ -141,6 +146,30 @@ static VALUE call_with_easy_callback_active(VALUE argp) {
141
146
  return with_easy_callback_active(args->rbce, args->func, args->arg);
142
147
  }
143
148
 
149
+ static VALUE rescue_easy_callback(ruby_curl_easy *rbce, VALUE (*func)(VALUE), VALUE arg) {
150
+ struct easy_callback_dispatch_args dispatch_args;
151
+ dispatch_args.rbce = rbce;
152
+ dispatch_args.func = func;
153
+ dispatch_args.arg = arg;
154
+ return rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception_store_on_easy, (VALUE)rbce);
155
+ }
156
+
157
+ static size_t curl_read_abort_result(void) {
158
+ #ifdef CURL_READFUNC_ABORT
159
+ return CURL_READFUNC_ABORT;
160
+ #else
161
+ return 0;
162
+ #endif
163
+ }
164
+
165
+ static int curl_seek_fail_result(void) {
166
+ #ifdef CURL_SEEKFUNC_FAIL
167
+ return CURL_SEEKFUNC_FAIL;
168
+ #else
169
+ return 1;
170
+ #endif
171
+ }
172
+
144
173
  /* Default body handler appends to easy.body_data buffer */
145
174
  static size_t default_body_handler(char *stream,
146
175
  size_t size,
@@ -178,18 +207,36 @@ static size_t read_data_handler(void *ptr,
178
207
  ruby_curl_easy *rbce) {
179
208
  VALUE upload = rb_easy_get("upload");
180
209
  size_t read_bytes = (size*nmemb);
181
- VALUE stream = ruby_curl_upload_stream_get(upload);
210
+ VALUE stream;
211
+
212
+ if (NIL_P(upload)) {
213
+ return curl_read_abort_result();
214
+ }
215
+
216
+ stream = ruby_curl_upload_stream_get(upload);
182
217
 
183
218
  if (rb_respond_to(stream, rb_intern("read"))) {//if (rb_respond_to(stream, rb_intern("to_s"))) {
184
219
  /* copy read_bytes from stream into ptr */
185
220
  struct stream_read_call_args args;
186
221
  args.stream = stream;
187
222
  args.read_bytes = read_bytes;
188
- VALUE str = with_easy_callback_active(rbce, call_stream_read, (VALUE)&args);
223
+ VALUE str = rescue_easy_callback(rbce, call_stream_read, (VALUE)&args);
189
224
  if( str != Qnil ) {
190
- StringValue(str);
191
- memcpy(ptr, RSTRING_PTR(str), RSTRING_LEN(str));
192
- return RSTRING_LEN(str);
225
+ size_t str_len;
226
+
227
+ str = rescue_easy_callback(rbce, call_string_value, str);
228
+ if (str == Qfalse || str == Qnil) {
229
+ return curl_read_abort_result();
230
+ }
231
+
232
+ str_len = (size_t)RSTRING_LEN(str);
233
+ if (str_len > read_bytes) {
234
+ snprintf(rbce->err_buf, CURL_ERROR_SIZE, "read callback returned more data than requested");
235
+ return curl_read_abort_result();
236
+ }
237
+
238
+ memcpy(ptr, RSTRING_PTR(str), str_len);
239
+ return str_len;
193
240
  }
194
241
  else {
195
242
  return 0;
@@ -202,9 +249,17 @@ static size_t read_data_handler(void *ptr,
202
249
  size_t remaining;
203
250
  char *str_ptr;
204
251
  TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
205
- str = with_easy_callback_active(rbce, call_stream_to_s, stream);
206
- StringValue(str);
252
+ str = rescue_easy_callback(rbce, call_stream_to_s, stream);
253
+ str = rescue_easy_callback(rbce, call_string_value, str);
254
+ if (str == Qfalse || str == Qnil) {
255
+ return curl_read_abort_result();
256
+ }
257
+
207
258
  len = RSTRING_LEN(str);
259
+ if (rbcu->offset >= len) {
260
+ return 0;
261
+ }
262
+
208
263
  remaining = len - rbcu->offset;
209
264
  str_ptr = RSTRING_PTR(str);
210
265
 
@@ -232,14 +287,23 @@ int seek_data_handler(ruby_curl_easy *rbce,
232
287
  int origin) {
233
288
 
234
289
  VALUE upload = rb_easy_get("upload");
235
- VALUE stream = ruby_curl_upload_stream_get(upload);
290
+ VALUE stream;
291
+
292
+ if (NIL_P(upload)) {
293
+ return curl_seek_fail_result();
294
+ }
295
+
296
+ stream = ruby_curl_upload_stream_get(upload);
236
297
 
237
298
  if (rb_respond_to(stream, rb_intern("seek"))) {
238
299
  struct stream_seek_call_args args;
239
300
  args.stream = stream;
240
301
  args.offset = offset;
241
- args.origin = SEEK_SET;
242
- with_easy_callback_active(rbce, call_stream_seek, (VALUE)&args);
302
+ args.origin = origin;
303
+ rescue_easy_callback(rbce, call_stream_seek, (VALUE)&args);
304
+ if (!NIL_P(rbce->callback_error)) {
305
+ return curl_seek_fail_result();
306
+ }
243
307
  } else {
244
308
  ruby_curl_upload *rbcu;
245
309
  TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
@@ -725,6 +789,24 @@ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list) {
725
789
  return dup;
726
790
  }
727
791
 
792
+ static VALUE duplicate_upload(VALUE upload) {
793
+ ruby_curl_upload *rbcu, *newrbcu;
794
+ VALUE new_upload;
795
+
796
+ if (NIL_P(upload)) {
797
+ return Qnil;
798
+ }
799
+
800
+ TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
801
+
802
+ new_upload = ruby_curl_upload_new(cCurlUpload);
803
+ TypedData_Get_Struct(new_upload, ruby_curl_upload, &ruby_curl_upload_data_type, newrbcu);
804
+ newrbcu->stream = rbcu->stream;
805
+ newrbcu->offset = rbcu->offset;
806
+
807
+ return new_upload;
808
+ }
809
+
728
810
  /*
729
811
  * call-seq:
730
812
  * easy.clone => <easy clone>
@@ -763,6 +845,21 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
763
845
  /* Set the error buffer on the new curl handle using the new err_buf */
764
846
  curl_easy_setopt(newrbce->curl, CURLOPT_ERRORBUFFER, newrbce->err_buf);
765
847
 
848
+ if (newrbce->opts != Qnil) {
849
+ VALUE upload = rb_hash_aref(newrbce->opts, rb_easy_hkey("upload"));
850
+ if (!NIL_P(upload)) {
851
+ rb_hash_aset(newrbce->opts, rb_easy_hkey("upload"), duplicate_upload(upload));
852
+ curl_easy_setopt(newrbce->curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler);
853
+ curl_easy_setopt(newrbce->curl, CURLOPT_READDATA, newrbce);
854
+ #ifdef HAVE_CURLOPT_SEEKFUNCTION
855
+ curl_easy_setopt(newrbce->curl, CURLOPT_SEEKFUNCTION, (curl_seek_callback)seek_data_handler);
856
+ #endif
857
+ #ifdef HAVE_CURLOPT_SEEKDATA
858
+ curl_easy_setopt(newrbce->curl, CURLOPT_SEEKDATA, newrbce);
859
+ #endif
860
+ }
861
+ }
862
+
766
863
  VALUE clone = TypedData_Wrap_Struct(cCurlEasy, &ruby_curl_easy_data_type, newrbce);
767
864
  newrbce->self = clone;
768
865
  curl_easy_setopt(newrbce->curl, CURLOPT_PRIVATE, (void*)newrbce);
@@ -801,6 +898,8 @@ static VALUE ruby_curl_easy_close(VALUE self) {
801
898
  ruby_curl_easy_zero(rbce);
802
899
  rbce->self = self;
803
900
 
901
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, rbce->err_buf);
902
+
804
903
  /* give the new curl handle a reference back to the ruby object */
805
904
  ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
806
905
  if (ecode != CURLE_OK) {
@@ -3095,6 +3194,12 @@ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
3095
3194
  curl_easy_setopt(curl, CURLOPT_UPLOAD, 0);
3096
3195
  curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
3097
3196
  curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
3197
+ #ifdef HAVE_CURLOPT_SEEKFUNCTION
3198
+ curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, NULL);
3199
+ #endif
3200
+ #ifdef HAVE_CURLOPT_SEEKDATA
3201
+ curl_easy_setopt(curl, CURLOPT_SEEKDATA, NULL);
3202
+ #endif
3098
3203
  curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0);
3099
3204
  }
3100
3205
 
@@ -3173,19 +3278,68 @@ static VALUE call_easy_perform(VALUE self) {
3173
3278
  }
3174
3279
 
3175
3280
  struct easy_form_perform_args {
3281
+ VALUE self;
3176
3282
  CURL *curl;
3283
+ int argc;
3284
+ VALUE *argv;
3177
3285
  struct curl_httppost *first;
3286
+ struct curl_httppost *last;
3287
+ int clear_customrequest;
3288
+ int form_set_on_curl;
3178
3289
  };
3179
3290
 
3291
+ static void append_multipart_form_argument(VALUE arg,
3292
+ struct curl_httppost **first,
3293
+ struct curl_httppost **last) {
3294
+ if (rb_obj_is_instance_of(arg, cCurlPostField)) {
3295
+ append_to_form(arg, first, last);
3296
+ } else if (rb_type(arg) == T_ARRAY) {
3297
+ long j, argv_len = RARRAY_LEN(arg);
3298
+ for (j = 0; j < argv_len; ++j) {
3299
+ VALUE field = rb_ary_entry(arg, j);
3300
+ if (rb_obj_is_instance_of(field, cCurlPostField)) {
3301
+ append_to_form(field, first, last);
3302
+ } else {
3303
+ rb_raise(eCurlErrInvalidPostField,
3304
+ "You must use PostFields only with multipart form posts");
3305
+ }
3306
+ }
3307
+ } else {
3308
+ rb_raise(eCurlErrInvalidPostField,
3309
+ "You must use PostFields only with multipart form posts");
3310
+ }
3311
+ }
3312
+
3313
+ static VALUE build_and_perform_multipart_form(VALUE argp) {
3314
+ struct easy_form_perform_args *args = (struct easy_form_perform_args *)argp;
3315
+ int i;
3316
+
3317
+ for (i = 0; i < args->argc; i++) {
3318
+ append_multipart_form_argument(args->argv[i], &args->first, &args->last);
3319
+ }
3320
+
3321
+ curl_easy_setopt(args->curl, CURLOPT_POST, 0);
3322
+ curl_easy_setopt(args->curl, CURLOPT_HTTPPOST, args->first);
3323
+ args->form_set_on_curl = 1;
3324
+
3325
+ return call_easy_perform(args->self);
3326
+ }
3327
+
3180
3328
  static VALUE ensure_free_form_post(VALUE argp) {
3181
3329
  struct easy_form_perform_args *args = (struct easy_form_perform_args *)argp;
3182
3330
  if (args->curl) {
3183
- curl_easy_setopt(args->curl, CURLOPT_HTTPPOST, NULL);
3331
+ if (args->form_set_on_curl) {
3332
+ curl_easy_setopt(args->curl, CURLOPT_HTTPPOST, NULL);
3333
+ }
3334
+ if (args->clear_customrequest) {
3335
+ curl_easy_setopt(args->curl, CURLOPT_CUSTOMREQUEST, NULL);
3336
+ }
3184
3337
  }
3185
3338
  if (args->first) {
3186
3339
  curl_formfree(args->first);
3187
3340
  args->first = NULL;
3188
3341
  }
3342
+ args->last = NULL;
3189
3343
  return Qnil;
3190
3344
  }
3191
3345
 
@@ -3216,7 +3370,6 @@ static VALUE ensure_free_form_post(VALUE argp) {
3216
3370
  static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
3217
3371
  ruby_curl_easy *rbce;
3218
3372
  CURL *curl;
3219
- int i;
3220
3373
  VALUE args_ary;
3221
3374
 
3222
3375
  rb_scan_args(argc, argv, "*", &args_ary);
@@ -3230,33 +3383,8 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
3230
3383
 
3231
3384
  if (rbce->multipart_form_post) {
3232
3385
  VALUE ret;
3233
- struct curl_httppost *first = NULL, *last = NULL;
3234
-
3235
- // Make the multipart form
3236
- for (i = 0; i < argc; i++) {
3237
- if (rb_obj_is_instance_of(argv[i], cCurlPostField)) {
3238
- append_to_form(argv[i], &first, &last);
3239
- } else if (rb_type(argv[i]) == T_ARRAY) {
3240
- // see: https://github.com/rvanlieshout/curb/commit/8bcdefddc0162484681ebd1a92d52a642666a445
3241
- long c = 0, argv_len = RARRAY_LEN(argv[i]);
3242
- for (; c < argv_len; ++c) {
3243
- if (rb_obj_is_instance_of(rb_ary_entry(argv[i],c), cCurlPostField)) {
3244
- append_to_form(rb_ary_entry(argv[i],c), &first, &last);
3245
- } else {
3246
- rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts");
3247
- return Qnil;
3248
- }
3249
- }
3250
- } else {
3251
- rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts");
3252
- return Qnil;
3253
- }
3254
- }
3255
-
3256
- curl_easy_setopt(curl, CURLOPT_POST, 0);
3257
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
3258
- struct easy_form_perform_args perform_args = { curl, first };
3259
- ret = rb_ensure(call_easy_perform, self, ensure_free_form_post, (VALUE)&perform_args);
3386
+ struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 0, 0 };
3387
+ ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args);
3260
3388
 
3261
3389
  return ret;
3262
3390
  } else {
@@ -3300,7 +3428,6 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
3300
3428
  static VALUE ruby_curl_easy_perform_patch(int argc, VALUE *argv, VALUE self) {
3301
3429
  ruby_curl_easy *rbce;
3302
3430
  CURL *curl;
3303
- int i;
3304
3431
  VALUE args_ary;
3305
3432
 
3306
3433
  rb_scan_args(argc, argv, "*", &args_ary);
@@ -3315,35 +3442,8 @@ static VALUE ruby_curl_easy_perform_patch(int argc, VALUE *argv, VALUE self) {
3315
3442
 
3316
3443
  if (rbce->multipart_form_post) {
3317
3444
  VALUE ret;
3318
- struct curl_httppost *first = NULL, *last = NULL;
3319
-
3320
- /* Build the multipart form (same logic as for POST) */
3321
- for (i = 0; i < argc; i++) {
3322
- if (rb_obj_is_instance_of(argv[i], cCurlPostField)) {
3323
- append_to_form(argv[i], &first, &last);
3324
- } else if (rb_type(argv[i]) == T_ARRAY) {
3325
- long j, argv_len = RARRAY_LEN(argv[i]);
3326
- for (j = 0; j < argv_len; ++j) {
3327
- if (rb_obj_is_instance_of(rb_ary_entry(argv[i], j), cCurlPostField)) {
3328
- append_to_form(rb_ary_entry(argv[i], j), &first, &last);
3329
- } else {
3330
- rb_raise(eCurlErrInvalidPostField,
3331
- "You must use PostFields only with multipart form posts");
3332
- return Qnil;
3333
- }
3334
- }
3335
- } else {
3336
- rb_raise(eCurlErrInvalidPostField,
3337
- "You must use PostFields only with multipart form posts");
3338
- return Qnil;
3339
- }
3340
- }
3341
- /* Disable the POST flag */
3342
- curl_easy_setopt(curl, CURLOPT_POST, 0);
3343
- /* Use the built multipart form as the request body */
3344
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
3345
- struct easy_form_perform_args perform_args = { curl, first };
3346
- ret = rb_ensure(call_easy_perform, self, ensure_free_form_post, (VALUE)&perform_args);
3445
+ struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 1, 0 };
3446
+ ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args);
3347
3447
  return ret;
3348
3448
  } else {
3349
3449
  /* Join arguments into a raw PATCH body */
@@ -3376,7 +3476,6 @@ static VALUE ruby_curl_easy_perform_put(int argc, VALUE *argv, VALUE self) {
3376
3476
  ruby_curl_easy *rbce;
3377
3477
  CURL *curl;
3378
3478
  VALUE args_ary;
3379
- int i;
3380
3479
 
3381
3480
  rb_scan_args(argc, argv, "*", &args_ary);
3382
3481
  TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
@@ -3399,33 +3498,8 @@ static VALUE ruby_curl_easy_perform_put(int argc, VALUE *argv, VALUE self) {
3399
3498
  /* Otherwise, if multipart_form_post is true, use multipart logic */
3400
3499
  else if (rbce->multipart_form_post) {
3401
3500
  VALUE ret;
3402
- struct curl_httppost *first = NULL, *last = NULL;
3403
- for (i = 0; i < RARRAY_LEN(args_ary); i++) {
3404
- VALUE field = rb_ary_entry(args_ary, i);
3405
- if (rb_obj_is_instance_of(field, cCurlPostField)) {
3406
- append_to_form(field, &first, &last);
3407
- } else if (rb_type(field) == T_ARRAY) {
3408
- long j;
3409
- for (j = 0; j < RARRAY_LEN(field); j++) {
3410
- VALUE subfield = rb_ary_entry(field, j);
3411
- if (rb_obj_is_instance_of(subfield, cCurlPostField)) {
3412
- append_to_form(subfield, &first, &last);
3413
- } else {
3414
- rb_raise(eCurlErrInvalidPostField,
3415
- "You must use PostFields only with multipart form posts");
3416
- }
3417
- }
3418
- } else {
3419
- rb_raise(eCurlErrInvalidPostField,
3420
- "You must use PostFields only with multipart form posts");
3421
- }
3422
- }
3423
- curl_easy_setopt(curl, CURLOPT_POST, 0);
3424
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
3425
- /* Set the method explicitly to PUT */
3426
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
3427
- struct easy_form_perform_args perform_args = { curl, first };
3428
- ret = rb_ensure(call_easy_perform, self, ensure_free_form_post, (VALUE)&perform_args);
3501
+ struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 1, 0 };
3502
+ ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args);
3429
3503
  return ret;
3430
3504
  }
3431
3505
  /* Fallback: join all arguments */
data/ext/curb_multi.c CHANGED
@@ -1695,7 +1695,7 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
1695
1695
  #elif HAVE_RB_THREAD_BLOCKING_REGION
1696
1696
  rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0);
1697
1697
  #else
1698
- rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
1698
+ rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
1699
1699
  #endif
1700
1700
 
1701
1701
  #ifdef _WIN32
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);
data/ext/extconf.rb CHANGED
@@ -685,7 +685,8 @@ test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
685
685
  have_func('rb_thread_blocking_region')
686
686
  have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
687
687
  have_header('ruby/io.h')
688
- have_func('rb_thread_fd_select', 'ruby/io.h')
688
+ # Ruby 4.x exports rb_thread_fd_select without declaring it in ruby/io.h.
689
+ have_func('rb_thread_fd_select')
689
690
  have_func('rb_wait_for_single_fd', 'ruby/io.h')
690
691
  have_header('ruby/fiber/scheduler.h')
691
692
  have_func('rb_fiber_scheduler_current', 'ruby/fiber/scheduler.h')
@@ -1301,6 +1301,24 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1301
1301
  assert_equal "POST\nfoo=bar", curl.body_str
1302
1302
  end
1303
1303
 
1304
+ def test_multipart_patch_build_failure_restores_easy_request_state
1305
+ curl = Curl::Easy.new(TestServlet.url)
1306
+ curl.multipart_form_post = true
1307
+ field = Curl::PostField.content('document_id', '5')
1308
+
1309
+ field.name = nil
1310
+ error = assert_raise(Curl::Err::InvalidPostFieldError) do
1311
+ # The first field allocates native multipart form state before the second field raises.
1312
+ curl.http_patch(Curl::PostField.content('keep', 'allocated'), field)
1313
+ end
1314
+ assert_match(/Cannot post unnamed field/, error.message)
1315
+
1316
+ curl.multipart_form_post = false
1317
+ curl.perform
1318
+
1319
+ assert_equal 'GET', curl.body_str
1320
+ end
1321
+
1304
1322
  def test_delete_remote
1305
1323
  curl = Curl::Easy.new(TestServlet.url)
1306
1324
  curl.http_delete
@@ -1358,6 +1376,35 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1358
1376
  assert_match(/message$/, curl.body_str)
1359
1377
  end
1360
1378
 
1379
+ def test_put_data_read_exception_sets_result_and_releases_callback_state
1380
+ error_class = Class.new(StandardError)
1381
+ reader_class = Class.new do
1382
+ define_method(:read) do |_len|
1383
+ raise error_class, 'upload read failed'
1384
+ end
1385
+
1386
+ def seek(_offset, _whence = SEEK_SET)
1387
+ 0
1388
+ end
1389
+
1390
+ def stat
1391
+ Struct.new(:size).new(1)
1392
+ end
1393
+ end
1394
+
1395
+ curl = Curl::Easy.new(TestServlet.url)
1396
+ curl.put_data = reader_class.new
1397
+
1398
+ error = assert_raise(error_class) { curl.perform }
1399
+ assert_equal 'upload read failed', error.message
1400
+ assert_not_equal 0, curl.last_result
1401
+
1402
+ curl.url = TestServlet.url
1403
+ curl.http_get
1404
+
1405
+ assert_equal 'GET', curl.body_str
1406
+ end
1407
+
1361
1408
  # https://github.com/taf2/curb/issues/101
1362
1409
  def test_put_data_null_bytes
1363
1410
  curl = Curl::Easy.new(TestServlet.url)
@@ -1501,6 +1548,18 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1501
1548
  easy.http_get
1502
1549
  end
1503
1550
 
1551
+ def test_easy_close_restores_error_buffer
1552
+ easy = Curl::Easy.new('http://127.0.0.1:1')
1553
+ assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
1554
+ assert_not_nil easy.last_error
1555
+
1556
+ easy.close
1557
+ easy.url = 'http://127.0.0.1:1'
1558
+ assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
1559
+
1560
+ assert_not_nil easy.last_error
1561
+ end
1562
+
1504
1563
  def test_easy_reset
1505
1564
  easy = Curl::Easy.new
1506
1565
  easy.url = TestServlet.url + "?query=foo"
@@ -90,6 +90,21 @@ class TestCurbCurlNativeCoverage < Test::Unit::TestCase
90
90
  easy.close if defined?(easy) && easy
91
91
  end
92
92
 
93
+ def test_clone_rebinds_upload_callbacks_to_clone_state
94
+ easy = Curl::Easy.new(TestServlet.url)
95
+ easy.put_data = 'clone-data'
96
+
97
+ clone = easy.clone
98
+ easy.put_data = 'other-data'
99
+
100
+ clone.perform
101
+
102
+ assert_equal "PUT\nclone-data", clone.body_str
103
+ ensure
104
+ clone.close if defined?(clone) && clone
105
+ easy.close if defined?(easy) && easy
106
+ end
107
+
93
108
  def test_native_accessors_round_trip
94
109
  easy = Curl::Easy.new(TestServlet.url)
95
110
 
@@ -136,11 +136,26 @@ class TestCurbCurlPostfield < Test::Unit::TestCase
136
136
  assert_equal "foo=FOOBAR", pf.to_s
137
137
  end
138
138
 
139
+ def test_content_proc_survives_gc
140
+ pf = postfield_with_only_native_proc_reference
141
+
142
+ 10.times do
143
+ GC.start(full_mark: true, immediate_sweep: true)
144
+ GC.compact if GC.respond_to?(:compact)
145
+ end
146
+
147
+ assert_equal "foo=FOOBAR", pf.to_s
148
+ end
149
+
139
150
  def test_to_s_04
140
151
  pf = Curl::PostField.file('foo.file', 'bar.file')
141
152
  assert_nothing_raised { pf.to_s }
142
153
  #assert_raise(Curl::Err::InvalidPostFieldError) { pf.to_s }
143
154
  end
155
+
156
+ def postfield_with_only_native_proc_reference
157
+ Curl::PostField.content('foo') { |field| field.name.upcase + "BAR" }
158
+ end
144
159
  end
145
160
 
146
161
  class TestCurbCurlPostfieldNativeCoverage < Test::Unit::TestCase
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ross Bamford
8
8
  - Todd A. Fisher
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-02 00:00:00.000000000 Z
11
+ date: 2026-04-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Curb (probably CUrl-RuBy or something) provides Ruby-language bindings
14
14
  for the libcurl(3), a fully-featured client-side URL transfer library. cURL and