curb 1.2.2 → 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.
data/ext/curb_easy.c CHANGED
@@ -28,6 +28,14 @@ static VALUE rbstrAmp;
28
28
 
29
29
  VALUE cCurlEasy;
30
30
 
31
+ /* Internal wrapper type for passing pointers through rb_iterate callbacks.
32
+ * No mark/free needed - these are temporary wrappers that don't own memory. */
33
+ static const rb_data_type_t curl_slist_ptr_type = {
34
+ "curl_slist_ptr_wrapper",
35
+ { NULL, NULL, NULL },
36
+ NULL, NULL, 0
37
+ };
38
+
31
39
  // for Ruby 1.8
32
40
  #ifndef HAVE_RB_IO_STDIO_FILE
33
41
  static FILE * rb_io_stdio_file(rb_io_t *fptr) {
@@ -35,6 +43,7 @@ static FILE * rb_io_stdio_file(rb_io_t *fptr) {
35
43
  }
36
44
  #endif
37
45
  static struct curl_slist *duplicate_curl_slist(struct curl_slist *list);
46
+ static size_t proc_data_handler(char *stream, size_t size, size_t nmemb, VALUE proc);
38
47
 
39
48
  /* ================== CURL HANDLER FUNCS ==============*/
40
49
 
@@ -42,6 +51,125 @@ static VALUE callback_exception(VALUE unused, VALUE exception) {
42
51
  return Qfalse;
43
52
  }
44
53
 
54
+ static VALUE callback_exception_store_on_easy(VALUE arg, VALUE exception) {
55
+ ruby_curl_easy *rbce = (ruby_curl_easy *)arg;
56
+
57
+ if (rbce && NIL_P(rbce->callback_error)) {
58
+ rbce->callback_error = exception;
59
+ }
60
+
61
+ return Qfalse;
62
+ }
63
+
64
+ VALUE rb_curl_easy_take_callback_error(ruby_curl_easy *rbce) {
65
+ VALUE exception = Qnil;
66
+
67
+ if (!rbce) {
68
+ return Qnil;
69
+ }
70
+
71
+ exception = rbce->callback_error;
72
+ rbce->callback_error = Qnil;
73
+ return exception;
74
+ }
75
+
76
+ static VALUE ruby_curl_easy_take_callback_error(VALUE self) {
77
+ ruby_curl_easy *rbce = NULL;
78
+
79
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
80
+ return rb_curl_easy_take_callback_error(rbce);
81
+ }
82
+
83
+ static VALUE ensure_clear_easy_callback_active(VALUE arg) {
84
+ ruby_curl_easy *rbce = (ruby_curl_easy *)arg;
85
+ if (rbce) {
86
+ rbce->callback_active = 0;
87
+ }
88
+ return Qnil;
89
+ }
90
+
91
+ static VALUE with_easy_callback_active(ruby_curl_easy *rbce, VALUE (*func)(VALUE), VALUE arg) {
92
+ rbce->callback_active = 1;
93
+ return rb_ensure(func, arg, ensure_clear_easy_callback_active, (VALUE)rbce);
94
+ }
95
+
96
+ struct stream_read_call_args {
97
+ VALUE stream;
98
+ size_t read_bytes;
99
+ };
100
+
101
+ static VALUE call_stream_read(VALUE argp) {
102
+ struct stream_read_call_args *args = (struct stream_read_call_args *)argp;
103
+ return rb_funcall(args->stream, rb_intern("read"), 1, ULONG2NUM((unsigned long)args->read_bytes));
104
+ }
105
+
106
+ static VALUE call_stream_to_s(VALUE stream) {
107
+ return rb_funcall(stream, rb_intern("to_s"), 0);
108
+ }
109
+
110
+ static VALUE call_string_value(VALUE str) {
111
+ StringValue(str);
112
+ return str;
113
+ }
114
+
115
+ struct stream_seek_call_args {
116
+ VALUE stream;
117
+ curl_off_t offset;
118
+ int origin;
119
+ };
120
+
121
+ static VALUE call_stream_seek(VALUE argp) {
122
+ struct stream_seek_call_args *args = (struct stream_seek_call_args *)argp;
123
+ return rb_funcall(args->stream, rb_intern("seek"), 2, LL2NUM(args->offset), INT2NUM(args->origin));
124
+ }
125
+
126
+ struct proc_data_call_args {
127
+ char *stream;
128
+ size_t size;
129
+ size_t nmemb;
130
+ VALUE proc;
131
+ };
132
+
133
+ static VALUE call_proc_data_handler_wrapped(VALUE argp) {
134
+ struct proc_data_call_args *args = (struct proc_data_call_args *)argp;
135
+ return ULONG2NUM((unsigned long)proc_data_handler(args->stream, args->size, args->nmemb, args->proc));
136
+ }
137
+
138
+ struct easy_callback_dispatch_args {
139
+ ruby_curl_easy *rbce;
140
+ VALUE (*func)(VALUE);
141
+ VALUE arg;
142
+ };
143
+
144
+ static VALUE call_with_easy_callback_active(VALUE argp) {
145
+ struct easy_callback_dispatch_args *args = (struct easy_callback_dispatch_args *)argp;
146
+ return with_easy_callback_active(args->rbce, args->func, args->arg);
147
+ }
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
+
45
173
  /* Default body handler appends to easy.body_data buffer */
46
174
  static size_t default_body_handler(char *stream,
47
175
  size_t size,
@@ -79,14 +207,36 @@ static size_t read_data_handler(void *ptr,
79
207
  ruby_curl_easy *rbce) {
80
208
  VALUE upload = rb_easy_get("upload");
81
209
  size_t read_bytes = (size*nmemb);
82
- 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);
83
217
 
84
218
  if (rb_respond_to(stream, rb_intern("read"))) {//if (rb_respond_to(stream, rb_intern("to_s"))) {
85
219
  /* copy read_bytes from stream into ptr */
86
- VALUE str = rb_funcall(stream, rb_intern("read"), 1, rb_int_new(read_bytes) );
220
+ struct stream_read_call_args args;
221
+ args.stream = stream;
222
+ args.read_bytes = read_bytes;
223
+ VALUE str = rescue_easy_callback(rbce, call_stream_read, (VALUE)&args);
87
224
  if( str != Qnil ) {
88
- memcpy(ptr, RSTRING_PTR(str), RSTRING_LEN(str));
89
- 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;
90
240
  }
91
241
  else {
92
242
  return 0;
@@ -98,9 +248,18 @@ static size_t read_data_handler(void *ptr,
98
248
  size_t len;
99
249
  size_t remaining;
100
250
  char *str_ptr;
101
- Data_Get_Struct(upload, ruby_curl_upload, rbcu);
102
- str = rb_funcall(stream, rb_intern("to_s"), 0);
251
+ TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
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
+
103
258
  len = RSTRING_LEN(str);
259
+ if (rbcu->offset >= len) {
260
+ return 0;
261
+ }
262
+
104
263
  remaining = len - rbcu->offset;
105
264
  str_ptr = RSTRING_PTR(str);
106
265
 
@@ -128,13 +287,26 @@ int seek_data_handler(ruby_curl_easy *rbce,
128
287
  int origin) {
129
288
 
130
289
  VALUE upload = rb_easy_get("upload");
131
- 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);
132
297
 
133
298
  if (rb_respond_to(stream, rb_intern("seek"))) {
134
- rb_funcall(stream, rb_intern("seek"), 2, SEEK_SET, offset);
299
+ struct stream_seek_call_args args;
300
+ args.stream = stream;
301
+ args.offset = offset;
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
+ }
135
307
  } else {
136
308
  ruby_curl_upload *rbcu;
137
- Data_Get_Struct(upload, ruby_curl_upload, rbcu);
309
+ TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
138
310
  // This OK because curl only uses SEEK_SET as per the documentation
139
311
  rbcu->offset = offset;
140
312
  }
@@ -166,22 +338,40 @@ static size_t proc_data_handler_body(char *stream,
166
338
  size_t nmemb,
167
339
  ruby_curl_easy *rbce)
168
340
  {
169
- size_t ret;
170
- rbce->callback_active = 1;
171
- ret = proc_data_handler(stream, size, nmemb, rb_easy_get("body_proc"));
172
- rbce->callback_active = 0;
173
- return ret;
341
+ struct proc_data_call_args args;
342
+ struct easy_callback_dispatch_args dispatch_args;
343
+ VALUE procret;
344
+ args.stream = stream;
345
+ args.size = size;
346
+ args.nmemb = nmemb;
347
+ args.proc = rb_easy_get("body_proc");
348
+
349
+ dispatch_args.rbce = rbce;
350
+ dispatch_args.func = call_proc_data_handler_wrapped;
351
+ dispatch_args.arg = (VALUE)&args;
352
+ procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception_store_on_easy, (VALUE)rbce);
353
+
354
+ return ((procret == Qfalse) || (procret == Qnil)) ? 0 : NUM2ULONG(procret);
174
355
  }
175
356
  static size_t proc_data_handler_header(char *stream,
176
357
  size_t size,
177
358
  size_t nmemb,
178
359
  ruby_curl_easy *rbce)
179
360
  {
180
- size_t ret;
181
- rbce->callback_active = 1;
182
- ret = proc_data_handler(stream, size, nmemb, rb_easy_get("header_proc"));
183
- rbce->callback_active = 0;
184
- return ret;
361
+ struct proc_data_call_args args;
362
+ struct easy_callback_dispatch_args dispatch_args;
363
+ VALUE procret;
364
+ args.stream = stream;
365
+ args.size = size;
366
+ args.nmemb = nmemb;
367
+ args.proc = rb_easy_get("header_proc");
368
+
369
+ dispatch_args.rbce = rbce;
370
+ dispatch_args.func = call_proc_data_handler_wrapped;
371
+ dispatch_args.arg = (VALUE)&args;
372
+ procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception_store_on_easy, (VALUE)rbce);
373
+
374
+ return ((procret == Qfalse) || (procret == Qnil)) ? 0 : NUM2ULONG(procret);
185
375
  }
186
376
 
187
377
 
@@ -193,6 +383,8 @@ static VALUE call_progress_handler(VALUE ary) {
193
383
  rb_ary_entry(ary, 4)); // rb_float_new(ulnow));
194
384
  }
195
385
 
386
+ /* CURLOPT_PROGRESSFUNCTION callback (deprecated since 7.32.0) */
387
+ #ifndef HAVE_CURLOPT_XFERINFOFUNCTION
196
388
  static int proc_progress_handler(void *clientp,
197
389
  double dltotal,
198
390
  double dlnow,
@@ -212,15 +404,46 @@ static int proc_progress_handler(void *clientp,
212
404
  rb_ary_store(callargs, 3, rb_float_new(ultotal));
213
405
  rb_ary_store(callargs, 4, rb_float_new(ulnow));
214
406
 
215
- //v = rb_rescue(range_check, (VALUE)args, range_failed, 0);
216
- //procret = rb_funcall(proc, idCall, 4, rb_float_new(dltotal),
217
- // rb_float_new(dlnow),
218
- // rb_float_new(ultotal),
219
- // rb_float_new(ulnow));
220
- procret = rb_rescue(call_progress_handler, callargs, callback_exception, Qnil);
407
+ struct easy_callback_dispatch_args dispatch_args;
408
+ dispatch_args.rbce = rbce;
409
+ dispatch_args.func = call_progress_handler;
410
+ dispatch_args.arg = callargs;
411
+ procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception, Qnil);
221
412
 
222
413
  return(((procret == Qfalse) || (procret == Qnil)) ? -1 : 0);
223
414
  }
415
+ #endif
416
+
417
+ /* CURLOPT_XFERINFOFUNCTION callback (since 7.32.0, replaces PROGRESSFUNCTION) */
418
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
419
+ static int proc_xferinfo_handler(void *clientp,
420
+ curl_off_t dltotal,
421
+ curl_off_t dlnow,
422
+ curl_off_t ultotal,
423
+ curl_off_t ulnow) {
424
+ ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
425
+ VALUE proc = rb_easy_get("progress_proc");
426
+ if (proc == Qnil) {
427
+ return 0;
428
+ }
429
+ VALUE procret;
430
+ VALUE callargs = rb_ary_new2(5);
431
+
432
+ rb_ary_store(callargs, 0, proc);
433
+ rb_ary_store(callargs, 1, LL2NUM(dltotal));
434
+ rb_ary_store(callargs, 2, LL2NUM(dlnow));
435
+ rb_ary_store(callargs, 3, LL2NUM(ultotal));
436
+ rb_ary_store(callargs, 4, LL2NUM(ulnow));
437
+
438
+ struct easy_callback_dispatch_args dispatch_args;
439
+ dispatch_args.rbce = rbce;
440
+ dispatch_args.func = call_progress_handler;
441
+ dispatch_args.arg = callargs;
442
+ procret = rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception, Qnil);
443
+
444
+ return(((procret == Qfalse) || (procret == Qnil)) ? -1 : 0);
445
+ }
446
+ #endif
224
447
 
225
448
  static VALUE call_debug_handler(VALUE ary) {
226
449
  return rb_funcall(rb_ary_entry(ary, 0), idCall, 2,
@@ -241,7 +464,11 @@ static int proc_debug_handler(CURL *curl,
241
464
  rb_ary_store(callargs, 0, proc);
242
465
  rb_ary_store(callargs, 1, INT2NUM(type));
243
466
  rb_ary_store(callargs, 2, rb_str_new(data, data_len));
244
- rb_rescue(call_debug_handler, callargs, callback_exception, Qnil);
467
+ struct easy_callback_dispatch_args dispatch_args;
468
+ dispatch_args.rbce = rbce;
469
+ dispatch_args.func = call_debug_handler;
470
+ dispatch_args.arg = callargs;
471
+ rb_rescue(call_with_easy_callback_active, (VALUE)&dispatch_args, callback_exception, Qnil);
245
472
  /* no way to indicate to libcurl that we should break out given an exception in the on_debug handler...
246
473
  * this means exceptions will be swallowed
247
474
  */
@@ -249,53 +476,121 @@ static int proc_debug_handler(CURL *curl,
249
476
  return 0;
250
477
  }
251
478
 
252
- /* ================== MARK/FREE FUNC ==================*/
253
- void curl_easy_mark(ruby_curl_easy *rbce) {
254
- if (!NIL_P(rbce->opts)) { rb_gc_mark(rbce->opts); }
255
- if (!NIL_P(rbce->multi)) { rb_gc_mark(rbce->multi); }
479
+ /* ================== MARK/FREE/SIZE FUNCS ==================*/
480
+ static void curl_easy_mark(void *ptr) {
481
+ ruby_curl_easy *rbce = (ruby_curl_easy *)ptr;
482
+ if (rbce) {
483
+ if (!NIL_P(rbce->opts)) { rb_gc_mark(rbce->opts); }
484
+ if (!NIL_P(rbce->multi)) { rb_gc_mark(rbce->multi); }
485
+ if (!NIL_P(rbce->callback_error)) { rb_gc_mark(rbce->callback_error); }
486
+ }
256
487
  }
257
488
 
258
- static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
259
- if (!rbce) {
489
+ static void ruby_curl_easy_clear_headers_list(ruby_curl_easy *rbce) {
490
+ if (!rbce || !rbce->curl_headers) {
260
491
  return;
261
492
  }
493
+ if (rbce->curl) {
494
+ curl_easy_setopt(rbce->curl, CURLOPT_HTTPHEADER, NULL);
495
+ }
496
+ curl_slist_free_all(rbce->curl_headers);
497
+ rbce->curl_headers = NULL;
498
+ }
262
499
 
263
- if (!NIL_P(rbce->multi)) {
264
- VALUE multi_val = rbce->multi;
265
- ruby_curl_multi *rbcm = NULL;
500
+ static void ruby_curl_easy_clear_proxy_headers_list(ruby_curl_easy *rbce) {
501
+ if (!rbce || !rbce->curl_proxy_headers) {
502
+ return;
503
+ }
504
+ #ifdef HAVE_CURLOPT_PROXYHEADER
505
+ if (rbce->curl) {
506
+ curl_easy_setopt(rbce->curl, CURLOPT_PROXYHEADER, NULL);
507
+ }
508
+ #endif
509
+ curl_slist_free_all(rbce->curl_proxy_headers);
510
+ rbce->curl_proxy_headers = NULL;
511
+ }
266
512
 
267
- rbce->multi = Qnil;
513
+ static void ruby_curl_easy_clear_ftp_commands_list(ruby_curl_easy *rbce) {
514
+ if (!rbce || !rbce->curl_ftp_commands) {
515
+ return;
516
+ }
517
+ if (rbce->curl) {
518
+ curl_easy_setopt(rbce->curl, CURLOPT_QUOTE, NULL);
519
+ }
520
+ curl_slist_free_all(rbce->curl_ftp_commands);
521
+ rbce->curl_ftp_commands = NULL;
522
+ }
268
523
 
269
- if (!NIL_P(multi_val) && RB_TYPE_P(multi_val, T_DATA)) {
270
- Data_Get_Struct(multi_val, ruby_curl_multi, rbcm);
271
- if (rbcm) {
272
- /* Best-effort: ensure the handle is detached from the multi to
273
- * avoid libcurl retaining a dangling pointer to a soon-to-be
274
- * cleaned-up easy handle. We cannot raise from GC, so ignore errors. */
275
- if (rbcm->handle && rbce->curl) {
276
- curl_multi_remove_handle(rbcm->handle, rbce->curl);
277
- }
278
- rb_curl_multi_forget_easy(rbcm, rbce);
279
- }
280
- }
524
+ static void ruby_curl_easy_clear_resolve_list(ruby_curl_easy *rbce) {
525
+ if (!rbce || !rbce->curl_resolve) {
526
+ return;
281
527
  }
528
+ #ifdef HAVE_CURLOPT_RESOLVE
529
+ if (rbce->curl) {
530
+ curl_easy_setopt(rbce->curl, CURLOPT_RESOLVE, NULL);
531
+ }
532
+ #endif
533
+ curl_slist_free_all(rbce->curl_resolve);
534
+ rbce->curl_resolve = NULL;
535
+ }
282
536
 
283
- if (rbce->curl_headers) {
284
- curl_slist_free_all(rbce->curl_headers);
537
+ /* Legacy wrapper for external callers */
538
+ void ruby_curl_easy_mark(ruby_curl_easy *rbce) {
539
+ curl_easy_mark((void *)rbce);
540
+ }
541
+
542
+ static ruby_curl_multi *ruby_curl_multi_pointer_if_compatible(VALUE multi_val) {
543
+ if (NIL_P(multi_val) || !RB_TYPE_P(multi_val, T_DATA)) {
544
+ return NULL;
285
545
  }
286
546
 
287
- if (rbce->curl_proxy_headers) {
288
- curl_slist_free_all(rbce->curl_proxy_headers);
547
+ #if defined(RTYPEDDATA_P) && defined(RTYPEDDATA_TYPE) && defined(RTYPEDDATA_DATA)
548
+ if (!RTYPEDDATA_P(multi_val)) {
549
+ return NULL;
550
+ }
551
+
552
+ if (RTYPEDDATA_TYPE(multi_val) != &ruby_curl_multi_data_type) {
553
+ return NULL;
554
+ }
555
+
556
+ return (ruby_curl_multi *)RTYPEDDATA_DATA(multi_val);
557
+ #else
558
+ if (!rb_typeddata_is_kind_of(multi_val, &ruby_curl_multi_data_type)) {
559
+ return NULL;
289
560
  }
290
561
 
291
- if (rbce->curl_ftp_commands) {
292
- curl_slist_free_all(rbce->curl_ftp_commands);
562
+ return DATA_PTR(multi_val);
563
+ #endif
564
+ }
565
+
566
+ static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
567
+ if (!rbce) {
568
+ return;
293
569
  }
294
570
 
295
- if (rbce->curl_resolve) {
296
- curl_slist_free_all(rbce->curl_resolve);
571
+ if (!NIL_P(rbce->multi)) {
572
+ VALUE multi_val = rbce->multi;
573
+ ruby_curl_multi *rbcm = ruby_curl_multi_pointer_if_compatible(multi_val);
574
+
575
+ rbce->multi = Qnil;
576
+
577
+ if (rbcm) {
578
+ /* Best-effort: ensure the handle is detached from the multi to avoid
579
+ * libcurl retaining a dangling pointer to a soon-to-be cleaned-up easy
580
+ * handle. This path runs during GC, so it must not invoke Ruby APIs that
581
+ * can allocate or raise. */
582
+ if (rbcm->handle && rbce->curl) {
583
+ curl_multi_remove_handle(rbcm->handle, rbce->curl);
584
+ }
585
+ rb_curl_multi_forget_easy(rbcm, rbce);
586
+ }
297
587
  }
298
588
 
589
+ ruby_curl_easy_clear_headers_list(rbce);
590
+ ruby_curl_easy_clear_proxy_headers_list(rbce);
591
+ ruby_curl_easy_clear_ftp_commands_list(rbce);
592
+ ruby_curl_easy_clear_resolve_list(rbce);
593
+
299
594
  if (rbce->curl) {
300
595
  /* disable any progress or debug events */
301
596
  curl_easy_setopt(rbce->curl, CURLOPT_WRITEFUNCTION, NULL);
@@ -305,7 +600,11 @@ static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
305
600
  curl_easy_setopt(rbce->curl, CURLOPT_DEBUGFUNCTION, NULL);
306
601
  curl_easy_setopt(rbce->curl, CURLOPT_DEBUGDATA, NULL);
307
602
  curl_easy_setopt(rbce->curl, CURLOPT_VERBOSE, 0);
603
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
604
+ curl_easy_setopt(rbce->curl, CURLOPT_XFERINFOFUNCTION, NULL);
605
+ #else
308
606
  curl_easy_setopt(rbce->curl, CURLOPT_PROGRESSFUNCTION, NULL);
607
+ #endif
309
608
  curl_easy_setopt(rbce->curl, CURLOPT_NOPROGRESS, 1);
310
609
  curl_easy_cleanup(rbce->curl);
311
610
  rbce->curl = NULL;
@@ -314,11 +613,45 @@ static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
314
613
  rbce->self = Qnil;
315
614
  }
316
615
 
317
- void curl_easy_free(ruby_curl_easy *rbce) {
318
- ruby_curl_easy_free(rbce);
319
- free(rbce);
616
+ /* TypedData-compatible free function */
617
+ static void curl_easy_free(void *ptr) {
618
+ ruby_curl_easy *rbce = (ruby_curl_easy *)ptr;
619
+ if (rbce) {
620
+ ruby_curl_easy_free(rbce);
621
+ free(rbce);
622
+ }
320
623
  }
321
624
 
625
+ /* Legacy wrapper for external callers (e.g., curb_multi) */
626
+ void ruby_curl_easy_free_wrapper(ruby_curl_easy *rbce) {
627
+ curl_easy_free((void *)rbce);
628
+ }
629
+
630
+ static size_t curl_easy_memsize(const void *ptr) {
631
+ const ruby_curl_easy *rbce = (const ruby_curl_easy *)ptr;
632
+ size_t size = sizeof(ruby_curl_easy);
633
+ /* Note: We don't count curl_slist or CURL handle memory as they're
634
+ * managed by libcurl and would require complex introspection */
635
+ (void)rbce; /* silence unused warning */
636
+ return size;
637
+ }
638
+
639
+ const rb_data_type_t ruby_curl_easy_data_type = {
640
+ "Curl::Easy",
641
+ {
642
+ curl_easy_mark,
643
+ curl_easy_free,
644
+ curl_easy_memsize,
645
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
646
+ NULL, /* compact - not needed */
647
+ #endif
648
+ },
649
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
650
+ NULL, NULL, /* parent, data */
651
+ RUBY_TYPED_FREE_IMMEDIATELY
652
+ #endif
653
+ };
654
+
322
655
 
323
656
  /* ================= ALLOC METHODS ====================*/
324
657
 
@@ -354,6 +687,7 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
354
687
  rbce->ssl_version = -1;
355
688
  rbce->use_ssl = -1;
356
689
  rbce->ftp_filemethod = -1;
690
+ rbce->http_version = CURL_HTTP_VERSION_NONE;
357
691
  rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER;
358
692
 
359
693
  /* bool opts */
@@ -371,6 +705,7 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
371
705
  rbce->cookielist_engine_enabled = 0;
372
706
  rbce->ignore_content_length = 0;
373
707
  rbce->callback_active = 0;
708
+ rbce->callback_error = Qnil;
374
709
  rbce->last_result = 0;
375
710
  }
376
711
 
@@ -387,7 +722,7 @@ static VALUE ruby_curl_easy_allocate(VALUE klass) {
387
722
  rbce->opts = Qnil;
388
723
  rbce->multi = Qnil;
389
724
  ruby_curl_easy_zero(rbce);
390
- return Data_Wrap_Struct(klass, curl_easy_mark, curl_easy_free, rbce);
725
+ return TypedData_Wrap_Struct(klass, &ruby_curl_easy_data_type, rbce);
391
726
  }
392
727
 
393
728
  /*
@@ -407,7 +742,7 @@ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
407
742
 
408
743
  rb_scan_args(argc, argv, "01&", &url, &blk);
409
744
 
410
- Data_Get_Struct(self, ruby_curl_easy, rbce);
745
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
411
746
 
412
747
  /* handler */
413
748
  rbce->curl = curl_easy_init();
@@ -454,6 +789,24 @@ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list) {
454
789
  return dup;
455
790
  }
456
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
+
457
810
  /*
458
811
  * call-seq:
459
812
  * easy.clone => <easy clone>
@@ -465,7 +818,7 @@ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list) {
465
818
  static VALUE ruby_curl_easy_clone(VALUE self) {
466
819
  ruby_curl_easy *rbce, *newrbce;
467
820
 
468
- Data_Get_Struct(self, ruby_curl_easy, rbce);
821
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
469
822
 
470
823
  newrbce = ALLOC(ruby_curl_easy);
471
824
  if (!newrbce) {
@@ -483,6 +836,7 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
483
836
 
484
837
  /* A cloned easy should not retain ownership reference to the original multi. */
485
838
  newrbce->multi = Qnil;
839
+ newrbce->callback_error = Qnil;
486
840
 
487
841
  if (rbce->opts != Qnil) {
488
842
  newrbce->opts = rb_funcall(rbce->opts, rb_intern("dup"), 0);
@@ -491,7 +845,22 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
491
845
  /* Set the error buffer on the new curl handle using the new err_buf */
492
846
  curl_easy_setopt(newrbce->curl, CURLOPT_ERRORBUFFER, newrbce->err_buf);
493
847
 
494
- VALUE clone = Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, newrbce);
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
+
863
+ VALUE clone = TypedData_Wrap_Struct(cCurlEasy, &ruby_curl_easy_data_type, newrbce);
495
864
  newrbce->self = clone;
496
865
  curl_easy_setopt(newrbce->curl, CURLOPT_PRIVATE, (void*)newrbce);
497
866
 
@@ -510,7 +879,7 @@ static VALUE ruby_curl_easy_close(VALUE self) {
510
879
  CURLcode ecode;
511
880
  ruby_curl_easy *rbce;
512
881
 
513
- Data_Get_Struct(self, ruby_curl_easy, rbce);
882
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
514
883
 
515
884
  if (rbce->callback_active) {
516
885
  rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback");
@@ -529,6 +898,8 @@ static VALUE ruby_curl_easy_close(VALUE self) {
529
898
  ruby_curl_easy_zero(rbce);
530
899
  rbce->self = self;
531
900
 
901
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, rbce->err_buf);
902
+
532
903
  /* give the new curl handle a reference back to the ruby object */
533
904
  ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
534
905
  if (ecode != CURLE_OK) {
@@ -554,7 +925,7 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
554
925
  CURLcode ecode;
555
926
  ruby_curl_easy *rbce;
556
927
  VALUE opts_dup;
557
- Data_Get_Struct(self, ruby_curl_easy, rbce);
928
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
558
929
 
559
930
  if (rbce->callback_active) {
560
931
  rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback");
@@ -562,6 +933,7 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
562
933
 
563
934
  opts_dup = rb_funcall(rbce->opts, rb_intern("dup"), 0);
564
935
 
936
+ ruby_curl_easy_cleanup(self, rbce);
565
937
  curl_easy_reset(rbce->curl);
566
938
  ruby_curl_easy_zero(rbce);
567
939
  rbce->self = self;
@@ -574,18 +946,6 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
574
946
  raise_curl_easy_error_exception(ecode);
575
947
  }
576
948
 
577
- /* Free everything up */
578
- if (rbce->curl_headers) {
579
- curl_slist_free_all(rbce->curl_headers);
580
- rbce->curl_headers = NULL;
581
- }
582
-
583
- /* Free everything up */
584
- if (rbce->curl_proxy_headers) {
585
- curl_slist_free_all(rbce->curl_proxy_headers);
586
- rbce->curl_proxy_headers = NULL;
587
- }
588
-
589
949
  return opts_dup;
590
950
  }
591
951
 
@@ -651,7 +1011,7 @@ static VALUE ruby_curl_easy_proxy_headers_set(VALUE self, VALUE proxy_headers) {
651
1011
  static VALUE ruby_curl_easy_headers_get(VALUE self) {
652
1012
  ruby_curl_easy *rbce;
653
1013
  VALUE headers;
654
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1014
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
655
1015
  headers = rb_easy_get("headers");//rb_hash_aref(rbce->opts, rb_intern("headers"));
656
1016
  if (headers == Qnil) { headers = rb_easy_set("headers", rb_hash_new()); }
657
1017
  return headers;
@@ -686,7 +1046,7 @@ static VALUE ruby_curl_easy_headers_get(VALUE self) {
686
1046
  static VALUE ruby_curl_easy_proxy_headers_get(VALUE self) {
687
1047
  ruby_curl_easy *rbce;
688
1048
  VALUE proxy_headers;
689
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1049
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
690
1050
  proxy_headers = rb_easy_get("proxy_headers");//rb_hash_aref(rbce->opts, rb_intern("proxy_headers"));
691
1051
  if (proxy_headers == Qnil) { proxy_headers = rb_easy_set("proxy_headers", rb_hash_new()); }
692
1052
  return proxy_headers;
@@ -917,43 +1277,63 @@ static VALUE ruby_curl_easy_useragent_get(VALUE self) {
917
1277
  *
918
1278
  * This is handy if you want to perform a POST against a Curl::Multi instance.
919
1279
  */
920
- static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
1280
+ static VALUE ruby_curl_easy_post_body_set_with_mode(VALUE self, VALUE post_body, int force_http_get_on_nil) {
921
1281
  ruby_curl_easy *rbce;
922
1282
  CURL *curl;
923
1283
 
1284
+ VALUE body_str;
1285
+ VALUE retained_body_str;
924
1286
  char *data;
925
1287
  long len;
926
1288
 
927
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1289
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
928
1290
 
929
1291
  curl = rbce->curl;
930
1292
 
931
1293
  if ( post_body == Qnil ) {
932
1294
  rb_easy_del("postdata_buffer");
933
- curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
1295
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
1296
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0);
1297
+ if (force_http_get_on_nil) {
1298
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
1299
+ }
934
1300
 
935
1301
  } else {
936
1302
  if (rb_type(post_body) == T_STRING) {
937
- data = StringValuePtr(post_body);
938
- len = RSTRING_LEN(post_body);
1303
+ body_str = post_body;
939
1304
  }
940
1305
  else if (rb_respond_to(post_body, rb_intern("to_s"))) {
941
- VALUE str_body = rb_funcall(post_body, rb_intern("to_s"), 0);
942
- data = StringValuePtr(str_body);
943
- len = RSTRING_LEN(post_body);
1306
+ body_str = rb_funcall(post_body, rb_intern("to_s"), 0);
944
1307
  }
945
1308
  else {
946
1309
  rb_raise(rb_eRuntimeError, "post data must respond_to .to_s");
947
1310
  }
948
1311
 
1312
+ StringValue(body_str);
1313
+
949
1314
  // Store the string, since it has to hang around for the duration of the
950
1315
  // request. See CURLOPT_POSTFIELDS in the libcurl docs.
951
- //rbce->postdata_buffer = post_body;
952
- rb_easy_set("postdata_buffer", post_body);
1316
+ #ifdef HAVE_CURLOPT_COPYPOSTFIELDS
1317
+ /*
1318
+ * libcurl copies the bytes immediately for COPYPOSTFIELDS, so retain a
1319
+ * matching Ruby snapshot for post_body instead of the caller's mutable
1320
+ * source string.
1321
+ */
1322
+ retained_body_str = rb_str_dup(body_str);
1323
+ #else
1324
+ retained_body_str = body_str;
1325
+ #endif
1326
+ data = StringValuePtr(retained_body_str);
1327
+ len = RSTRING_LEN(retained_body_str);
1328
+ rb_easy_set("postdata_buffer", retained_body_str);
953
1329
 
954
1330
  curl_easy_setopt(curl, CURLOPT_POST, 1);
955
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
956
1331
  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
1332
+ #ifdef HAVE_CURLOPT_COPYPOSTFIELDS
1333
+ curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, data);
1334
+ #else
1335
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
1336
+ #endif
957
1337
 
958
1338
  return post_body;
959
1339
  }
@@ -961,6 +1341,10 @@ static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
961
1341
  return Qnil;
962
1342
  }
963
1343
 
1344
+ static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
1345
+ return ruby_curl_easy_post_body_set_with_mode(self, post_body, 1);
1346
+ }
1347
+
964
1348
  /*
965
1349
  * call-seq:
966
1350
  * easy.post_body => string or nil
@@ -985,7 +1369,7 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
985
1369
  VALUE upload;
986
1370
  VALUE headers;
987
1371
 
988
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1372
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
989
1373
 
990
1374
  upload = ruby_curl_upload_new(cCurlUpload);
991
1375
  ruby_curl_upload_stream_set(upload,data);
@@ -996,13 +1380,16 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
996
1380
  is complete or terminated... */
997
1381
 
998
1382
  curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
1383
+ curl_easy_setopt(curl, CURLOPT_POST, 0);
1384
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
1385
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0);
999
1386
  curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
1000
1387
  curl_easy_setopt(curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler);
1001
- #if HAVE_CURLOPT_SEEKFUNCTION
1388
+ #ifdef HAVE_CURLOPT_SEEKFUNCTION
1002
1389
  curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, (curl_seek_callback)seek_data_handler);
1003
1390
  #endif
1004
1391
  curl_easy_setopt(curl, CURLOPT_READDATA, rbce);
1005
- #if HAVE_CURLOPT_SEEKDATA
1392
+ #ifdef HAVE_CURLOPT_SEEKDATA
1006
1393
  curl_easy_setopt(curl, CURLOPT_SEEKDATA, rbce);
1007
1394
  #endif
1008
1395
 
@@ -1238,7 +1625,7 @@ static VALUE ruby_curl_easy_http_auth_types_set(int argc, VALUE *argv, VALUE sel
1238
1625
  long mask = 0;
1239
1626
 
1240
1627
  rb_scan_args(argc, argv, "*", &args_ary);
1241
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1628
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1242
1629
 
1243
1630
  len = RARRAY_LEN(args_ary);
1244
1631
 
@@ -1343,7 +1730,7 @@ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) {
1343
1730
  */
1344
1731
  static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout_s) {
1345
1732
  ruby_curl_easy *rbce;
1346
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1733
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1347
1734
 
1348
1735
  if (Qnil == timeout_s || NUM2DBL(timeout_s) <= 0.0) {
1349
1736
  rbce->timeout_ms = 0;
@@ -1366,7 +1753,7 @@ static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout_s) {
1366
1753
  */
1367
1754
  static VALUE ruby_curl_easy_timeout_get(VALUE self) {
1368
1755
  ruby_curl_easy *rbce;
1369
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1756
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1370
1757
  return DBL2NUM(rbce->timeout_ms / 1000.0);
1371
1758
  }
1372
1759
 
@@ -1384,7 +1771,7 @@ static VALUE ruby_curl_easy_timeout_get(VALUE self) {
1384
1771
  */
1385
1772
  static VALUE ruby_curl_easy_timeout_ms_set(VALUE self, VALUE timeout_ms) {
1386
1773
  ruby_curl_easy *rbce;
1387
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1774
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1388
1775
 
1389
1776
  if (Qnil == timeout_ms || NUM2DBL(timeout_ms) <= 0.0) {
1390
1777
  rbce->timeout_ms = 0;
@@ -1404,7 +1791,7 @@ static VALUE ruby_curl_easy_timeout_ms_set(VALUE self, VALUE timeout_ms) {
1404
1791
  */
1405
1792
  static VALUE ruby_curl_easy_timeout_ms_get(VALUE self) {
1406
1793
  ruby_curl_easy *rbce;
1407
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1794
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1408
1795
  return LONG2NUM(rbce->timeout_ms);
1409
1796
  }
1410
1797
 
@@ -1602,7 +1989,7 @@ static VALUE ruby_curl_easy_max_recv_speed_large_get(VALUE self) {
1602
1989
  * Set the HTTP Authentication username.
1603
1990
  */
1604
1991
  static VALUE ruby_curl_easy_username_set(VALUE self, VALUE username) {
1605
- #if HAVE_CURLOPT_USERNAME
1992
+ #ifdef HAVE_CURLOPT_USERNAME
1606
1993
  CURB_OBJECT_HSETTER(ruby_curl_easy, username);
1607
1994
  #else
1608
1995
  return Qnil;
@@ -1616,7 +2003,7 @@ static VALUE ruby_curl_easy_username_set(VALUE self, VALUE username) {
1616
2003
  * Get the current username
1617
2004
  */
1618
2005
  static VALUE ruby_curl_easy_username_get(VALUE self) {
1619
- #if HAVE_CURLOPT_USERNAME
2006
+ #ifdef HAVE_CURLOPT_USERNAME
1620
2007
  CURB_OBJECT_HGETTER(ruby_curl_easy, username);
1621
2008
  #else
1622
2009
  return Qnil;
@@ -1630,7 +2017,7 @@ static VALUE ruby_curl_easy_username_get(VALUE self) {
1630
2017
  * Set the HTTP Authentication password.
1631
2018
  */
1632
2019
  static VALUE ruby_curl_easy_password_set(VALUE self, VALUE password) {
1633
- #if HAVE_CURLOPT_PASSWORD
2020
+ #ifdef HAVE_CURLOPT_PASSWORD
1634
2021
  CURB_OBJECT_HSETTER(ruby_curl_easy, password);
1635
2022
  #else
1636
2023
  return Qnil;
@@ -1644,7 +2031,7 @@ static VALUE ruby_curl_easy_password_set(VALUE self, VALUE password) {
1644
2031
  * Get the current password
1645
2032
  */
1646
2033
  static VALUE ruby_curl_easy_password_get(VALUE self) {
1647
- #if HAVE_CURLOPT_PASSWORD
2034
+ #ifdef HAVE_CURLOPT_PASSWORD
1648
2035
  CURB_OBJECT_HGETTER(ruby_curl_easy, password);
1649
2036
  #else
1650
2037
  return Qnil;
@@ -1877,7 +2264,7 @@ static VALUE ruby_curl_easy_use_netrc_q(VALUE self) {
1877
2264
  */
1878
2265
  static VALUE ruby_curl_easy_autoreferer_set(VALUE self, VALUE autoreferer) {
1879
2266
  ruby_curl_easy *rbce;
1880
- Data_Get_Struct(self, ruby_curl_easy, rbce);
2267
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1881
2268
 
1882
2269
  if (Qtrue == autoreferer) {
1883
2270
  curl_easy_setopt(rbce->curl, CURLOPT_AUTOREFERER, 1);
@@ -2034,7 +2421,7 @@ static VALUE ruby_curl_easy_ignore_content_length_q(VALUE self) {
2034
2421
  static VALUE ruby_curl_easy_resolve_mode(VALUE self) {
2035
2422
  ruby_curl_easy *rbce;
2036
2423
  unsigned short rm;
2037
- Data_Get_Struct(self, ruby_curl_easy, rbce);
2424
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2038
2425
 
2039
2426
  rm = rbce->resolve_mode;
2040
2427
 
@@ -2066,7 +2453,7 @@ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
2066
2453
  } else {
2067
2454
  ruby_curl_easy *rbce;
2068
2455
  ID resolve_mode_id;
2069
- Data_Get_Struct(self, ruby_curl_easy, rbce);
2456
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2070
2457
 
2071
2458
  resolve_mode_id = rb_to_id(resolve_mode);
2072
2459
 
@@ -2086,6 +2473,46 @@ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
2086
2473
  }
2087
2474
  }
2088
2475
 
2476
+ /*
2477
+ * call-seq:
2478
+ * easy.http_version = Curl::HTTP_1_1 => Curl::HTTP_1_1
2479
+ *
2480
+ * Force libcurl to use a specific HTTP protocol version. By default libcurl
2481
+ * negotiates the highest version supported by both peers. Supported constants
2482
+ * include Curl::HTTP_NONE, Curl::HTTP_1_0, Curl::HTTP_1_1, Curl::HTTP_2_0,
2483
+ * Curl::HTTP_2TLS, and Curl::HTTP_2_PRIOR_KNOWLEDGE (when provided by libcurl).
2484
+ */
2485
+ static VALUE ruby_curl_easy_http_version_set(VALUE self, VALUE version) {
2486
+ ruby_curl_easy *rbce;
2487
+ long http_version;
2488
+
2489
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2490
+
2491
+ if (NIL_P(version)) {
2492
+ http_version = CURL_HTTP_VERSION_NONE;
2493
+ } else {
2494
+ http_version = NUM2LONG(version);
2495
+ }
2496
+
2497
+ rbce->http_version = http_version;
2498
+
2499
+ return version;
2500
+ }
2501
+
2502
+ /*
2503
+ * call-seq:
2504
+ * easy.http_version => integer
2505
+ *
2506
+ * Returns the HTTP protocol version currently configured.
2507
+ */
2508
+ static VALUE ruby_curl_easy_http_version_get(VALUE self) {
2509
+ ruby_curl_easy *rbce;
2510
+
2511
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2512
+
2513
+ return LONG2NUM(rbce->http_version);
2514
+ }
2515
+
2089
2516
 
2090
2517
  /* ================= EVENT PROCS ================== */
2091
2518
 
@@ -2250,7 +2677,7 @@ static VALUE cb_each_http_header(VALUE header, VALUE wrap, int _c, const VALUE *
2250
2677
  struct curl_slist **list;
2251
2678
  VALUE header_str = Qnil;
2252
2679
 
2253
- Data_Get_Struct(wrap, struct curl_slist *, list);
2680
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
2254
2681
 
2255
2682
  //rb_p(header);
2256
2683
 
@@ -2289,7 +2716,7 @@ static VALUE cb_each_http_proxy_header(VALUE proxy_header, VALUE wrap, int _c, c
2289
2716
  struct curl_slist **list;
2290
2717
  VALUE proxy_header_str = Qnil;
2291
2718
 
2292
- Data_Get_Struct(wrap, struct curl_slist *, list);
2719
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
2293
2720
 
2294
2721
  //rb_p(proxy_header);
2295
2722
 
@@ -2324,7 +2751,7 @@ static VALUE cb_each_http_proxy_header(VALUE proxy_header, VALUE wrap, int _c, c
2324
2751
  static VALUE cb_each_ftp_command(VALUE ftp_command, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
2325
2752
  struct curl_slist **list;
2326
2753
  VALUE ftp_command_string;
2327
- Data_Get_Struct(wrap, struct curl_slist *, list);
2754
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
2328
2755
 
2329
2756
  ftp_command_string = rb_obj_as_string(ftp_command);
2330
2757
  struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(ftp_command));
@@ -2342,7 +2769,7 @@ static VALUE cb_each_ftp_command(VALUE ftp_command, VALUE wrap, int _c, const VA
2342
2769
  static VALUE cb_each_resolve(VALUE resolve, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
2343
2770
  struct curl_slist **list;
2344
2771
  VALUE resolve_string;
2345
- Data_Get_Struct(wrap, struct curl_slist *, list);
2772
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
2346
2773
 
2347
2774
  resolve_string = rb_obj_as_string(resolve);
2348
2775
  struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(resolve));
@@ -2370,6 +2797,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2370
2797
  struct curl_slist **rslv = &(rbce->curl_resolve);
2371
2798
 
2372
2799
  curl = rbce->curl;
2800
+ rbce->callback_error = Qnil;
2373
2801
 
2374
2802
  if (_url == Qnil) {
2375
2803
  rb_raise(eCurlErrError, "No URL supplied");
@@ -2385,7 +2813,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2385
2813
  curl_easy_setopt(curl, CURLOPT_INTERFACE, NULL);
2386
2814
  }
2387
2815
 
2388
- #if HAVE_CURLOPT_USERNAME == 1 && HAVE_CURLOPT_PASSWORD == 1
2816
+ #if defined(HAVE_CURLOPT_USERNAME) && defined(HAVE_CURLOPT_PASSWORD)
2389
2817
  if (!rb_easy_nil("username")) {
2390
2818
  curl_easy_setopt(curl, CURLOPT_USERNAME, rb_easy_get_str("username"));
2391
2819
  } else {
@@ -2401,7 +2829,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2401
2829
 
2402
2830
  if (!rb_easy_nil("userpwd")) {
2403
2831
  curl_easy_setopt(curl, CURLOPT_USERPWD, rb_easy_get_str("userpwd"));
2404
- #if HAVE_CURLOPT_USERNAME == 1
2832
+ #if defined(HAVE_CURLOPT_USERNAME) && defined(HAVE_CURLOPT_PASSWORD)
2405
2833
  } else if (rb_easy_nil("username") && rb_easy_nil("password")) { /* don't set this even to NULL if we have set username and password */
2406
2834
  #else
2407
2835
  } else {
@@ -2421,7 +2849,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2421
2849
  curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, rb_easy_get_str("proxypwd"));
2422
2850
  }
2423
2851
 
2424
- #if HAVE_CURLOPT_NOPROXY
2852
+ #ifdef HAVE_CURLOPT_NOPROXY
2425
2853
  if (rb_easy_nil("noproxy")) {
2426
2854
  curl_easy_setopt(curl, CURLOPT_NOPROXY, NULL);
2427
2855
  } else {
@@ -2459,12 +2887,21 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2459
2887
 
2460
2888
  // progress and debug procs
2461
2889
  if (!rb_easy_nil("progress_proc")) {
2890
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
2891
+ curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, &proc_xferinfo_handler);
2892
+ curl_easy_setopt(curl, CURLOPT_XFERINFODATA, rbce);
2893
+ #else
2462
2894
  curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, (curl_progress_callback)&proc_progress_handler);
2463
2895
  curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
2896
+ #endif
2464
2897
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
2465
2898
  } else {
2466
2899
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
2900
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
2901
+ curl_easy_setopt(curl, CURLOPT_XFERINFODATA, rbce);
2902
+ #else
2467
2903
  curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
2904
+ #endif
2468
2905
  }
2469
2906
 
2470
2907
  if (!rb_easy_nil("debug_proc")) {
@@ -2504,12 +2941,12 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2504
2941
 
2505
2942
  curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, rbce->unrestricted_auth);
2506
2943
 
2507
- #if HAVE_CURLOPT_TIMEOUT_MS
2944
+ #ifdef HAVE_CURLOPT_TIMEOUT_MS
2508
2945
  curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, rbce->timeout_ms);
2509
2946
  #endif
2510
2947
 
2511
2948
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, rbce->connect_timeout);
2512
- #if HAVE_CURLOPT_CONNECTTIMEOUT_MS
2949
+ #ifdef HAVE_CURLOPT_CONNECTTIMEOUT_MS
2513
2950
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, rbce->connect_timeout_ms);
2514
2951
  #endif
2515
2952
  curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, rbce->dns_cache_timeout);
@@ -2517,6 +2954,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2517
2954
  curl_easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, rbce->ignore_content_length);
2518
2955
 
2519
2956
  curl_easy_setopt(curl, CURLOPT_IPRESOLVE, rbce->resolve_mode);
2957
+ #if HAVE_CURLOPT_HTTP_VERSION
2958
+ curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, rbce->http_version);
2959
+ #endif
2520
2960
 
2521
2961
 
2522
2962
  #if LIBCURL_VERSION_NUM >= 0x070a08
@@ -2656,8 +3096,8 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2656
3096
 
2657
3097
  if (!rb_easy_nil("headers")) {
2658
3098
  if (rb_easy_type_check("headers", T_ARRAY) || rb_easy_type_check("headers", T_HASH)) {
2659
- VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, hdrs);
2660
- rb_iterate(rb_each, rb_easy_get("headers"), cb_each_http_header, wrap);
3099
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, hdrs);
3100
+ rb_block_call(rb_easy_get("headers"), rb_intern("each"), 0, NULL, cb_each_http_header, wrap);
2661
3101
  } else {
2662
3102
  VALUE headers_str = rb_obj_as_string(rb_easy_get("headers"));
2663
3103
  struct curl_slist *new_list = curl_slist_append(*hdrs, StringValuePtr(headers_str));
@@ -2672,14 +3112,14 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2672
3112
  }
2673
3113
  }
2674
3114
 
2675
- #if HAVE_CURLOPT_PROXYHEADER
3115
+ #ifdef HAVE_CURLOPT_PROXYHEADER
2676
3116
  /* Setup HTTP proxy headers if necessary */
2677
3117
  curl_easy_setopt(curl, CURLOPT_PROXYHEADER, NULL); // XXX: maybe we shouldn't be clearing this?
2678
3118
 
2679
3119
  if (!rb_easy_nil("proxy_headers")) {
2680
3120
  if (rb_easy_type_check("proxy_headers", T_ARRAY) || rb_easy_type_check("proxy_headers", T_HASH)) {
2681
- VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, phdrs);
2682
- rb_iterate(rb_each, rb_easy_get("proxy_headers"), cb_each_http_proxy_header, wrap);
3121
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, phdrs);
3122
+ rb_block_call(rb_easy_get("proxy_headers"), rb_intern("each"), 0, NULL, cb_each_http_proxy_header, wrap);
2683
3123
  } else {
2684
3124
  VALUE proxy_headers_str = rb_obj_as_string(rb_easy_get("proxy_headers"));
2685
3125
  struct curl_slist *new_list = curl_slist_append(*phdrs, StringValuePtr(proxy_headers_str));
@@ -2698,8 +3138,8 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2698
3138
  /* Setup FTP commands if necessary */
2699
3139
  if (!rb_easy_nil("ftp_commands")) {
2700
3140
  if (rb_easy_type_check("ftp_commands", T_ARRAY)) {
2701
- VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, cmds);
2702
- rb_iterate(rb_each, rb_easy_get("ftp_commands"), cb_each_ftp_command, wrap);
3141
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, cmds);
3142
+ rb_block_call(rb_easy_get("ftp_commands"), rb_intern("each"), 0, NULL, cb_each_ftp_command, wrap);
2703
3143
  }
2704
3144
 
2705
3145
  if (*cmds) {
@@ -2707,12 +3147,12 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2707
3147
  }
2708
3148
  }
2709
3149
 
2710
- #if HAVE_CURLOPT_RESOLVE
3150
+ #ifdef HAVE_CURLOPT_RESOLVE
2711
3151
  /* Setup resolve list if necessary */
2712
3152
  if (!rb_easy_nil("resolve")) {
2713
3153
  if (rb_easy_type_check("resolve", T_ARRAY)) {
2714
- VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, rslv);
2715
- rb_iterate(rb_each, rb_easy_get("resolve"), cb_each_resolve, wrap);
3154
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, rslv);
3155
+ rb_block_call(rb_easy_get("resolve"), rb_intern("each"), 0, NULL, cb_each_resolve, wrap);
2716
3156
  }
2717
3157
 
2718
3158
  if (*rslv) {
@@ -2735,27 +3175,17 @@ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2735
3175
  struct curl_slist *ftp_commands;
2736
3176
  struct curl_slist *resolve;
2737
3177
 
2738
- /* Free everything up */
2739
- if (rbce->curl_headers) {
2740
- curl_slist_free_all(rbce->curl_headers);
2741
- rbce->curl_headers = NULL;
2742
- }
2743
-
2744
- if (rbce->curl_proxy_headers) {
2745
- curl_slist_free_all(rbce->curl_proxy_headers);
2746
- rbce->curl_proxy_headers = NULL;
2747
- }
3178
+ ruby_curl_easy_clear_headers_list(rbce);
3179
+ ruby_curl_easy_clear_proxy_headers_list(rbce);
2748
3180
 
2749
3181
  ftp_commands = rbce->curl_ftp_commands;
2750
3182
  if (ftp_commands) {
2751
- curl_slist_free_all(ftp_commands);
2752
- rbce->curl_ftp_commands = NULL;
3183
+ ruby_curl_easy_clear_ftp_commands_list(rbce);
2753
3184
  }
2754
3185
 
2755
3186
  resolve = rbce->curl_resolve;
2756
3187
  if (resolve) {
2757
- curl_slist_free_all(resolve);
2758
- rbce->curl_resolve = NULL;
3188
+ ruby_curl_easy_clear_resolve_list(rbce);
2759
3189
  }
2760
3190
 
2761
3191
  /* clean up a PUT request's curl options. */
@@ -2764,6 +3194,12 @@ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2764
3194
  curl_easy_setopt(curl, CURLOPT_UPLOAD, 0);
2765
3195
  curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
2766
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
2767
3203
  curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0);
2768
3204
  }
2769
3205
 
@@ -2781,7 +3217,7 @@ static VALUE ruby_curl_easy_perform_verb_str(VALUE self, const char *verb) {
2781
3217
  CURL *curl;
2782
3218
  VALUE retval;
2783
3219
 
2784
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3220
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2785
3221
  curl = rbce->curl;
2786
3222
 
2787
3223
  memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
@@ -2837,6 +3273,76 @@ static VALUE ruby_curl_easy_perform_verb(VALUE self, VALUE verb) {
2837
3273
  }
2838
3274
  }
2839
3275
 
3276
+ static VALUE call_easy_perform(VALUE self) {
3277
+ return rb_funcall(self, rb_intern("perform"), 0);
3278
+ }
3279
+
3280
+ struct easy_form_perform_args {
3281
+ VALUE self;
3282
+ CURL *curl;
3283
+ int argc;
3284
+ VALUE *argv;
3285
+ struct curl_httppost *first;
3286
+ struct curl_httppost *last;
3287
+ int clear_customrequest;
3288
+ int form_set_on_curl;
3289
+ };
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
+
3328
+ static VALUE ensure_free_form_post(VALUE argp) {
3329
+ struct easy_form_perform_args *args = (struct easy_form_perform_args *)argp;
3330
+ if (args->curl) {
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
+ }
3337
+ }
3338
+ if (args->first) {
3339
+ curl_formfree(args->first);
3340
+ args->first = NULL;
3341
+ }
3342
+ args->last = NULL;
3343
+ return Qnil;
3344
+ }
3345
+
2840
3346
  /*
2841
3347
  * call-seq:
2842
3348
  * easy.http_post("url=encoded%20form%20data;and=so%20on") => true
@@ -2864,12 +3370,11 @@ static VALUE ruby_curl_easy_perform_verb(VALUE self, VALUE verb) {
2864
3370
  static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2865
3371
  ruby_curl_easy *rbce;
2866
3372
  CURL *curl;
2867
- int i;
2868
3373
  VALUE args_ary;
2869
3374
 
2870
3375
  rb_scan_args(argc, argv, "*", &args_ary);
2871
3376
 
2872
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3377
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2873
3378
  curl = rbce->curl;
2874
3379
 
2875
3380
  memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
@@ -2878,33 +3383,8 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2878
3383
 
2879
3384
  if (rbce->multipart_form_post) {
2880
3385
  VALUE ret;
2881
- struct curl_httppost *first = NULL, *last = NULL;
2882
-
2883
- // Make the multipart form
2884
- for (i = 0; i < argc; i++) {
2885
- if (rb_obj_is_instance_of(argv[i], cCurlPostField)) {
2886
- append_to_form(argv[i], &first, &last);
2887
- } else if (rb_type(argv[i]) == T_ARRAY) {
2888
- // see: https://github.com/rvanlieshout/curb/commit/8bcdefddc0162484681ebd1a92d52a642666a445
2889
- long c = 0, argv_len = RARRAY_LEN(argv[i]);
2890
- for (; c < argv_len; ++c) {
2891
- if (rb_obj_is_instance_of(rb_ary_entry(argv[i],c), cCurlPostField)) {
2892
- append_to_form(rb_ary_entry(argv[i],c), &first, &last);
2893
- } else {
2894
- rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts");
2895
- return Qnil;
2896
- }
2897
- }
2898
- } else {
2899
- rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts");
2900
- return Qnil;
2901
- }
2902
- }
2903
-
2904
- curl_easy_setopt(curl, CURLOPT_POST, 0);
2905
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
2906
- ret = rb_funcall(self, rb_intern("perform"), 0);
2907
- curl_formfree(first);
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);
2908
3388
 
2909
3389
  return ret;
2910
3390
  } else {
@@ -2948,11 +3428,10 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2948
3428
  static VALUE ruby_curl_easy_perform_patch(int argc, VALUE *argv, VALUE self) {
2949
3429
  ruby_curl_easy *rbce;
2950
3430
  CURL *curl;
2951
- int i;
2952
3431
  VALUE args_ary;
2953
3432
 
2954
3433
  rb_scan_args(argc, argv, "*", &args_ary);
2955
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3434
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2956
3435
  curl = rbce->curl;
2957
3436
 
2958
3437
  /* Clear the error buffer */
@@ -2963,35 +3442,8 @@ static VALUE ruby_curl_easy_perform_patch(int argc, VALUE *argv, VALUE self) {
2963
3442
 
2964
3443
  if (rbce->multipart_form_post) {
2965
3444
  VALUE ret;
2966
- struct curl_httppost *first = NULL, *last = NULL;
2967
-
2968
- /* Build the multipart form (same logic as for POST) */
2969
- for (i = 0; i < argc; i++) {
2970
- if (rb_obj_is_instance_of(argv[i], cCurlPostField)) {
2971
- append_to_form(argv[i], &first, &last);
2972
- } else if (rb_type(argv[i]) == T_ARRAY) {
2973
- long j, argv_len = RARRAY_LEN(argv[i]);
2974
- for (j = 0; j < argv_len; ++j) {
2975
- if (rb_obj_is_instance_of(rb_ary_entry(argv[i], j), cCurlPostField)) {
2976
- append_to_form(rb_ary_entry(argv[i], j), &first, &last);
2977
- } else {
2978
- rb_raise(eCurlErrInvalidPostField,
2979
- "You must use PostFields only with multipart form posts");
2980
- return Qnil;
2981
- }
2982
- }
2983
- } else {
2984
- rb_raise(eCurlErrInvalidPostField,
2985
- "You must use PostFields only with multipart form posts");
2986
- return Qnil;
2987
- }
2988
- }
2989
- /* Disable the POST flag */
2990
- curl_easy_setopt(curl, CURLOPT_POST, 0);
2991
- /* Use the built multipart form as the request body */
2992
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
2993
- ret = rb_funcall(self, rb_intern("perform"), 0);
2994
- curl_formfree(first);
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);
2995
3447
  return ret;
2996
3448
  } else {
2997
3449
  /* Join arguments into a raw PATCH body */
@@ -3024,10 +3476,9 @@ static VALUE ruby_curl_easy_perform_put(int argc, VALUE *argv, VALUE self) {
3024
3476
  ruby_curl_easy *rbce;
3025
3477
  CURL *curl;
3026
3478
  VALUE args_ary;
3027
- int i;
3028
3479
 
3029
3480
  rb_scan_args(argc, argv, "*", &args_ary);
3030
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3481
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3031
3482
  curl = rbce->curl;
3032
3483
 
3033
3484
  memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
@@ -3047,33 +3498,8 @@ static VALUE ruby_curl_easy_perform_put(int argc, VALUE *argv, VALUE self) {
3047
3498
  /* Otherwise, if multipart_form_post is true, use multipart logic */
3048
3499
  else if (rbce->multipart_form_post) {
3049
3500
  VALUE ret;
3050
- struct curl_httppost *first = NULL, *last = NULL;
3051
- for (i = 0; i < RARRAY_LEN(args_ary); i++) {
3052
- VALUE field = rb_ary_entry(args_ary, i);
3053
- if (rb_obj_is_instance_of(field, cCurlPostField)) {
3054
- append_to_form(field, &first, &last);
3055
- } else if (rb_type(field) == T_ARRAY) {
3056
- long j;
3057
- for (j = 0; j < RARRAY_LEN(field); j++) {
3058
- VALUE subfield = rb_ary_entry(field, j);
3059
- if (rb_obj_is_instance_of(subfield, cCurlPostField)) {
3060
- append_to_form(subfield, &first, &last);
3061
- } else {
3062
- rb_raise(eCurlErrInvalidPostField,
3063
- "You must use PostFields only with multipart form posts");
3064
- }
3065
- }
3066
- } else {
3067
- rb_raise(eCurlErrInvalidPostField,
3068
- "You must use PostFields only with multipart form posts");
3069
- }
3070
- }
3071
- curl_easy_setopt(curl, CURLOPT_POST, 0);
3072
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
3073
- /* Set the method explicitly to PUT */
3074
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
3075
- ret = rb_funcall(self, rb_intern("perform"), 0);
3076
- curl_formfree(first);
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);
3077
3503
  return ret;
3078
3504
  }
3079
3505
  /* Fallback: join all arguments */
@@ -3133,7 +3559,7 @@ static VALUE ruby_curl_easy_last_effective_url_get(VALUE self) {
3133
3559
  ruby_curl_easy *rbce;
3134
3560
  char* url;
3135
3561
 
3136
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3562
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3137
3563
  curl_easy_getinfo(rbce->curl, CURLINFO_EFFECTIVE_URL, &url);
3138
3564
 
3139
3565
  if (url && url[0]) { // curl returns empty string if none
@@ -3156,7 +3582,7 @@ static VALUE ruby_curl_easy_response_code_get(VALUE self) {
3156
3582
  ruby_curl_easy *rbce;
3157
3583
  long code;
3158
3584
 
3159
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3585
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3160
3586
  #ifdef HAVE_CURLINFO_RESPONSE_CODE
3161
3587
  curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &code);
3162
3588
  #else
@@ -3180,7 +3606,7 @@ static VALUE ruby_curl_easy_primary_ip_get(VALUE self) {
3180
3606
  ruby_curl_easy *rbce;
3181
3607
  char* ip;
3182
3608
 
3183
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3609
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3184
3610
  curl_easy_getinfo(rbce->curl, CURLINFO_PRIMARY_IP, &ip);
3185
3611
 
3186
3612
  if (ip && ip[0]) { // curl returns empty string if none
@@ -3201,7 +3627,7 @@ static VALUE ruby_curl_easy_http_connect_code_get(VALUE self) {
3201
3627
  ruby_curl_easy *rbce;
3202
3628
  long code;
3203
3629
 
3204
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3630
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3205
3631
  curl_easy_getinfo(rbce->curl, CURLINFO_HTTP_CONNECTCODE, &code);
3206
3632
 
3207
3633
  return LONG2NUM(code);
@@ -3229,7 +3655,7 @@ static VALUE ruby_curl_easy_file_time_get(VALUE self) {
3229
3655
  ruby_curl_easy *rbce;
3230
3656
  long time;
3231
3657
 
3232
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3658
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3233
3659
  curl_easy_getinfo(rbce->curl, CURLINFO_FILETIME, &time);
3234
3660
 
3235
3661
  return LONG2NUM(time);
@@ -3250,7 +3676,7 @@ static VALUE ruby_curl_easy_total_time_get(VALUE self) {
3250
3676
  ruby_curl_easy *rbce;
3251
3677
  double time;
3252
3678
 
3253
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3679
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3254
3680
  curl_easy_getinfo(rbce->curl, CURLINFO_TOTAL_TIME, &time);
3255
3681
 
3256
3682
  return rb_float_new(time);
@@ -3267,7 +3693,7 @@ static VALUE ruby_curl_easy_name_lookup_time_get(VALUE self) {
3267
3693
  ruby_curl_easy *rbce;
3268
3694
  double time;
3269
3695
 
3270
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3696
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3271
3697
  curl_easy_getinfo(rbce->curl, CURLINFO_NAMELOOKUP_TIME, &time);
3272
3698
 
3273
3699
  return rb_float_new(time);
@@ -3284,7 +3710,7 @@ static VALUE ruby_curl_easy_connect_time_get(VALUE self) {
3284
3710
  ruby_curl_easy *rbce;
3285
3711
  double time;
3286
3712
 
3287
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3713
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3288
3714
  curl_easy_getinfo(rbce->curl, CURLINFO_CONNECT_TIME, &time);
3289
3715
 
3290
3716
  return rb_float_new(time);
@@ -3305,7 +3731,7 @@ static VALUE ruby_curl_easy_app_connect_time_get(VALUE self) {
3305
3731
  ruby_curl_easy *rbce;
3306
3732
  double time;
3307
3733
 
3308
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3734
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3309
3735
  curl_easy_getinfo(rbce->curl, CURLINFO_APPCONNECT_TIME, &time);
3310
3736
 
3311
3737
  return rb_float_new(time);
@@ -3326,7 +3752,7 @@ static VALUE ruby_curl_easy_pre_transfer_time_get(VALUE self) {
3326
3752
  ruby_curl_easy *rbce;
3327
3753
  double time;
3328
3754
 
3329
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3755
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3330
3756
  curl_easy_getinfo(rbce->curl, CURLINFO_PRETRANSFER_TIME, &time);
3331
3757
 
3332
3758
  return rb_float_new(time);
@@ -3344,7 +3770,7 @@ static VALUE ruby_curl_easy_start_transfer_time_get(VALUE self) {
3344
3770
  ruby_curl_easy *rbce;
3345
3771
  double time;
3346
3772
 
3347
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3773
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3348
3774
  curl_easy_getinfo(rbce->curl, CURLINFO_STARTTRANSFER_TIME, &time);
3349
3775
 
3350
3776
  return rb_float_new(time);
@@ -3366,7 +3792,7 @@ static VALUE ruby_curl_easy_redirect_time_get(VALUE self) {
3366
3792
  ruby_curl_easy *rbce;
3367
3793
  double time;
3368
3794
 
3369
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3795
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3370
3796
  curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_TIME, &time);
3371
3797
 
3372
3798
  return rb_float_new(time);
@@ -3389,7 +3815,7 @@ static VALUE ruby_curl_easy_redirect_count_get(VALUE self) {
3389
3815
  ruby_curl_easy *rbce;
3390
3816
  long count;
3391
3817
 
3392
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3818
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3393
3819
  curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_COUNT, &count);
3394
3820
 
3395
3821
  return LONG2NUM(count);
@@ -3414,7 +3840,7 @@ static VALUE ruby_curl_easy_redirect_url_get(VALUE self) {
3414
3840
  ruby_curl_easy *rbce;
3415
3841
  char* url;
3416
3842
 
3417
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3843
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3418
3844
  curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_URL, &url);
3419
3845
 
3420
3846
  if (url && url[0]) { // curl returns empty string if none
@@ -3439,12 +3865,17 @@ static VALUE ruby_curl_easy_redirect_url_get(VALUE self) {
3439
3865
  */
3440
3866
  static VALUE ruby_curl_easy_uploaded_bytes_get(VALUE self) {
3441
3867
  ruby_curl_easy *rbce;
3442
- double bytes;
3868
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3443
3869
 
3444
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3870
+ #ifdef HAVE_CURLINFO_SIZE_UPLOAD_T
3871
+ curl_off_t bytes;
3872
+ curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_UPLOAD_T, &bytes);
3873
+ return LL2NUM(bytes);
3874
+ #else
3875
+ double bytes;
3445
3876
  curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_UPLOAD, &bytes);
3446
-
3447
3877
  return rb_float_new(bytes);
3878
+ #endif
3448
3879
  }
3449
3880
 
3450
3881
  /*
@@ -3456,12 +3887,17 @@ static VALUE ruby_curl_easy_uploaded_bytes_get(VALUE self) {
3456
3887
  */
3457
3888
  static VALUE ruby_curl_easy_downloaded_bytes_get(VALUE self) {
3458
3889
  ruby_curl_easy *rbce;
3459
- double bytes;
3890
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3460
3891
 
3461
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3892
+ #ifdef HAVE_CURLINFO_SIZE_DOWNLOAD_T
3893
+ curl_off_t bytes;
3894
+ curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_DOWNLOAD_T, &bytes);
3895
+ return LL2NUM(bytes);
3896
+ #else
3897
+ double bytes;
3462
3898
  curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_DOWNLOAD, &bytes);
3463
-
3464
3899
  return rb_float_new(bytes);
3900
+ #endif
3465
3901
  }
3466
3902
 
3467
3903
  /*
@@ -3473,12 +3909,17 @@ static VALUE ruby_curl_easy_downloaded_bytes_get(VALUE self) {
3473
3909
  */
3474
3910
  static VALUE ruby_curl_easy_upload_speed_get(VALUE self) {
3475
3911
  ruby_curl_easy *rbce;
3476
- double bytes;
3912
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3477
3913
 
3478
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3914
+ #ifdef HAVE_CURLINFO_SPEED_UPLOAD_T
3915
+ curl_off_t bytes;
3916
+ curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_UPLOAD_T, &bytes);
3917
+ return LL2NUM(bytes);
3918
+ #else
3919
+ double bytes;
3479
3920
  curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_UPLOAD, &bytes);
3480
-
3481
3921
  return rb_float_new(bytes);
3922
+ #endif
3482
3923
  }
3483
3924
 
3484
3925
  /*
@@ -3490,12 +3931,17 @@ static VALUE ruby_curl_easy_upload_speed_get(VALUE self) {
3490
3931
  */
3491
3932
  static VALUE ruby_curl_easy_download_speed_get(VALUE self) {
3492
3933
  ruby_curl_easy *rbce;
3493
- double bytes;
3934
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3494
3935
 
3495
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3936
+ #ifdef HAVE_CURLINFO_SPEED_DOWNLOAD_T
3937
+ curl_off_t bytes;
3938
+ curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_DOWNLOAD_T, &bytes);
3939
+ return LL2NUM(bytes);
3940
+ #else
3941
+ double bytes;
3496
3942
  curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_DOWNLOAD, &bytes);
3497
-
3498
3943
  return rb_float_new(bytes);
3944
+ #endif
3499
3945
  }
3500
3946
 
3501
3947
  /*
@@ -3509,7 +3955,7 @@ static VALUE ruby_curl_easy_header_size_get(VALUE self) {
3509
3955
  ruby_curl_easy *rbce;
3510
3956
  long size;
3511
3957
 
3512
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3958
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3513
3959
  curl_easy_getinfo(rbce->curl, CURLINFO_HEADER_SIZE, &size);
3514
3960
 
3515
3961
  return LONG2NUM(size);
@@ -3527,7 +3973,7 @@ static VALUE ruby_curl_easy_request_size_get(VALUE self) {
3527
3973
  ruby_curl_easy *rbce;
3528
3974
  long size;
3529
3975
 
3530
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3976
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3531
3977
  curl_easy_getinfo(rbce->curl, CURLINFO_REQUEST_SIZE, &size);
3532
3978
 
3533
3979
  return LONG2NUM(size);
@@ -3544,7 +3990,7 @@ static VALUE ruby_curl_easy_ssl_verify_result_get(VALUE self) {
3544
3990
  ruby_curl_easy *rbce;
3545
3991
  long result;
3546
3992
 
3547
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3993
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3548
3994
  curl_easy_getinfo(rbce->curl, CURLINFO_SSL_VERIFYRESULT, &result);
3549
3995
 
3550
3996
  return LONG2NUM(result);
@@ -3567,12 +4013,17 @@ NOTE: you must call curl_slist_free_all(3) on the list pointer once you're done
3567
4013
  */
3568
4014
  static VALUE ruby_curl_easy_downloaded_content_length_get(VALUE self) {
3569
4015
  ruby_curl_easy *rbce;
3570
- double bytes;
4016
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3571
4017
 
3572
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4018
+ #ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
4019
+ curl_off_t bytes;
4020
+ curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &bytes);
4021
+ return LL2NUM(bytes);
4022
+ #else
4023
+ double bytes;
3573
4024
  curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &bytes);
3574
-
3575
4025
  return rb_float_new(bytes);
4026
+ #endif
3576
4027
  }
3577
4028
 
3578
4029
  /*
@@ -3583,12 +4034,17 @@ static VALUE ruby_curl_easy_downloaded_content_length_get(VALUE self) {
3583
4034
  */
3584
4035
  static VALUE ruby_curl_easy_uploaded_content_length_get(VALUE self) {
3585
4036
  ruby_curl_easy *rbce;
3586
- double bytes;
4037
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3587
4038
 
3588
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4039
+ #ifdef HAVE_CURLINFO_CONTENT_LENGTH_UPLOAD_T
4040
+ curl_off_t bytes;
4041
+ curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_UPLOAD_T, &bytes);
4042
+ return LL2NUM(bytes);
4043
+ #else
4044
+ double bytes;
3589
4045
  curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_UPLOAD, &bytes);
3590
-
3591
4046
  return rb_float_new(bytes);
4047
+ #endif
3592
4048
  }
3593
4049
 
3594
4050
  /*
@@ -3604,7 +4060,7 @@ static VALUE ruby_curl_easy_content_type_get(VALUE self) {
3604
4060
  ruby_curl_easy *rbce;
3605
4061
  char* type;
3606
4062
 
3607
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4063
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3608
4064
  curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_TYPE, &type);
3609
4065
 
3610
4066
  if (type && type[0]) { // curl returns empty string if none
@@ -3647,7 +4103,7 @@ static VALUE ruby_curl_easy_os_errno_get(VALUE self) {
3647
4103
  ruby_curl_easy *rbce;
3648
4104
  long result;
3649
4105
 
3650
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4106
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3651
4107
  curl_easy_getinfo(rbce->curl, CURLINFO_OS_ERRNO, &result);
3652
4108
 
3653
4109
  return LONG2NUM(result);
@@ -3676,7 +4132,7 @@ static VALUE ruby_curl_easy_num_connects_get(VALUE self) {
3676
4132
  ruby_curl_easy *rbce;
3677
4133
  long result;
3678
4134
 
3679
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4135
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3680
4136
  curl_easy_getinfo(rbce->curl, CURLINFO_NUM_CONNECTS, &result);
3681
4137
 
3682
4138
  return LONG2NUM(result);
@@ -3713,7 +4169,7 @@ static VALUE ruby_curl_easy_cookielist_get(VALUE self) {
3713
4169
  struct curl_slist *cookie;
3714
4170
  VALUE rb_cookies;
3715
4171
 
3716
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4172
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3717
4173
  curl_easy_getinfo(rbce->curl, CURLINFO_COOKIELIST, &cookies);
3718
4174
  if (!cookies)
3719
4175
  return Qnil;
@@ -3753,7 +4209,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
3753
4209
  ruby_curl_easy *rbce;
3754
4210
  char* path = NULL;
3755
4211
 
3756
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4212
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3757
4213
  curl_easy_getinfo(rbce->curl, CURLINFO_FTP_ENTRY_PATH, &path);
3758
4214
 
3759
4215
  if (path && path[0]) { // curl returns NULL or empty string if none
@@ -3773,7 +4229,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
3773
4229
  */
3774
4230
  static VALUE ruby_curl_easy_multi_get(VALUE self) {
3775
4231
  ruby_curl_easy *rbce;
3776
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4232
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3777
4233
  return rbce->multi;
3778
4234
  }
3779
4235
 
@@ -3783,7 +4239,12 @@ static VALUE ruby_curl_easy_multi_get(VALUE self) {
3783
4239
  */
3784
4240
  static VALUE ruby_curl_easy_multi_set(VALUE self, VALUE multi) {
3785
4241
  ruby_curl_easy *rbce;
3786
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4242
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4243
+
4244
+ if (!NIL_P(multi) && rb_obj_is_kind_of(multi, cCurlMulti) != Qtrue) {
4245
+ rb_raise(rb_eTypeError, "expected Curl::Multi or nil");
4246
+ }
4247
+
3787
4248
  rbce->multi = multi;
3788
4249
  return rbce->multi;
3789
4250
  }
@@ -3794,7 +4255,7 @@ static VALUE ruby_curl_easy_multi_set(VALUE self, VALUE multi) {
3794
4255
  */
3795
4256
  static VALUE ruby_curl_easy_last_result(VALUE self) {
3796
4257
  ruby_curl_easy *rbce;
3797
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4258
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3798
4259
  return LONG2NUM(rbce->last_result);
3799
4260
  }
3800
4261
 
@@ -3804,7 +4265,7 @@ static VALUE ruby_curl_easy_last_result(VALUE self) {
3804
4265
  */
3805
4266
  static VALUE ruby_curl_easy_last_error(VALUE self) {
3806
4267
  ruby_curl_easy *rbce;
3807
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4268
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3808
4269
 
3809
4270
  if (rbce->err_buf[0]) { // curl returns NULL or empty string if none
3810
4271
  return rb_str_new2(rbce->err_buf);
@@ -3831,7 +4292,7 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3831
4292
  long option = NUM2LONG(opt);
3832
4293
  rb_io_t *open_f_ptr;
3833
4294
 
3834
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4295
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3835
4296
 
3836
4297
  switch (option) {
3837
4298
  /* BEHAVIOR OPTIONS */
@@ -3853,9 +4314,11 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3853
4314
  case CURLOPT_CUSTOMREQUEST:
3854
4315
  curl_easy_setopt(rbce->curl, CURLOPT_CUSTOMREQUEST, NIL_P(val) ? NULL : StringValueCStr(val));
3855
4316
  break;
3856
- case CURLOPT_HTTP_VERSION:
3857
- curl_easy_setopt(rbce->curl, CURLOPT_HTTP_VERSION, NUM2LONG(val));
3858
- break;
4317
+ case CURLOPT_HTTP_VERSION: {
4318
+ long http_version = NIL_P(val) ? CURL_HTTP_VERSION_NONE : NUM2LONG(val);
4319
+ rbce->http_version = http_version;
4320
+ curl_easy_setopt(rbce->curl, CURLOPT_HTTP_VERSION, http_version);
4321
+ } break;
3859
4322
  case CURLOPT_PROXY: {
3860
4323
  VALUE proxy_url = val;
3861
4324
  CURB_OBJECT_HSETTER(ruby_curl_easy, proxy_url);
@@ -3867,10 +4330,10 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3867
4330
  case CURLOPT_HEADER:
3868
4331
  case CURLOPT_NOPROGRESS:
3869
4332
  case CURLOPT_NOSIGNAL:
3870
- #if HAVE_CURLOPT_PATH_AS_IS
4333
+ #ifdef HAVE_CURLOPT_PATH_AS_IS
3871
4334
  case CURLOPT_PATH_AS_IS:
3872
4335
  #endif
3873
- #if HAVE_CURLOPT_PIPEWAIT
4336
+ #ifdef HAVE_CURLOPT_PIPEWAIT
3874
4337
  case CURLOPT_PIPEWAIT:
3875
4338
  #endif
3876
4339
  case CURLOPT_HTTPGET:
@@ -3893,7 +4356,7 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3893
4356
  curl_easy_setopt(rbce->curl, CURLOPT_MAXCONNECTS, NUM2LONG(val));
3894
4357
  } break;
3895
4358
  case CURLOPT_POSTFIELDS: {
3896
- curl_easy_setopt(rbce->curl, CURLOPT_POSTFIELDS, NIL_P(val) ? NULL : StringValueCStr(val));
4359
+ ruby_curl_easy_post_body_set_with_mode(self, val, 0);
3897
4360
  } break;
3898
4361
  case CURLOPT_USERPWD: {
3899
4362
  VALUE userpwd = val;
@@ -3903,7 +4366,7 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3903
4366
  VALUE proxypwd = val;
3904
4367
  CURB_OBJECT_HSETTER(ruby_curl_easy, proxypwd);
3905
4368
  } break;
3906
- #if HAVE_CURLOPT_NOPROXY
4369
+ #ifdef HAVE_CURLOPT_NOPROXY
3907
4370
  case CURLOPT_NOPROXY: {
3908
4371
  VALUE noproxy = val;
3909
4372
  CURB_OBJECT_HSETTER(ruby_curl_easy, noproxy);
@@ -3921,7 +4384,7 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3921
4384
  VALUE cookiejar = val;
3922
4385
  CURB_OBJECT_HSETTER(ruby_curl_easy, cookiejar);
3923
4386
  } break;
3924
- #if HAVE_CURLOPT_REQUEST_TARGET
4387
+ #ifdef HAVE_CURLOPT_REQUEST_TARGET
3925
4388
  case CURLOPT_REQUEST_TARGET: {
3926
4389
  /* Forward request-target directly to libcurl as a string. */
3927
4390
  curl_easy_setopt(rbce->curl, CURLOPT_REQUEST_TARGET, NIL_P(val) ? NULL : StringValueCStr(val));
@@ -3931,7 +4394,7 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3931
4394
  curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val));
3932
4395
  } break;
3933
4396
  /* FTP-specific toggles */
3934
- #if HAVE_CURLOPT_DIRLISTONLY
4397
+ #ifdef HAVE_CURLOPT_DIRLISTONLY
3935
4398
  case CURLOPT_DIRLISTONLY: {
3936
4399
  int type = rb_type(val);
3937
4400
  VALUE value;
@@ -3945,7 +4408,7 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3945
4408
  curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, NUM2LONG(value));
3946
4409
  } break;
3947
4410
  #endif
3948
- #if HAVE_CURLOPT_FTP_USE_EPSV
4411
+ #ifdef HAVE_CURLOPT_FTP_USE_EPSV
3949
4412
  case CURLOPT_FTP_USE_EPSV: {
3950
4413
  int type = rb_type(val);
3951
4414
  VALUE value;
@@ -3959,7 +4422,7 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3959
4422
  curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, NUM2LONG(value));
3960
4423
  } break;
3961
4424
  #endif
3962
- #if HAVE_CURLOPT_FTP_USE_EPRT
4425
+ #ifdef HAVE_CURLOPT_FTP_USE_EPRT
3963
4426
  case CURLOPT_FTP_USE_EPRT: {
3964
4427
  int type = rb_type(val);
3965
4428
  VALUE value;
@@ -3973,7 +4436,7 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3973
4436
  curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, NUM2LONG(value));
3974
4437
  } break;
3975
4438
  #endif
3976
- #if HAVE_CURLOPT_FTP_SKIP_PASV_IP
4439
+ #ifdef HAVE_CURLOPT_FTP_SKIP_PASV_IP
3977
4440
  case CURLOPT_FTP_SKIP_PASV_IP: {
3978
4441
  int type = rb_type(val);
3979
4442
  VALUE value;
@@ -4002,32 +4465,32 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4002
4465
  case CURLOPT_FORBID_REUSE: {
4003
4466
  curl_easy_setopt(rbce->curl, CURLOPT_FORBID_REUSE, NUM2LONG(val));
4004
4467
  } break;
4005
- #if HAVE_CURLOPT_GSSAPI_DELEGATION
4468
+ #ifdef HAVE_CURLOPT_GSSAPI_DELEGATION
4006
4469
  case CURLOPT_GSSAPI_DELEGATION: {
4007
4470
  curl_easy_setopt(rbce->curl, CURLOPT_GSSAPI_DELEGATION, NUM2LONG(val));
4008
4471
  } break;
4009
4472
  #endif
4010
- #if HAVE_CURLOPT_UNIX_SOCKET_PATH
4473
+ #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
4011
4474
  case CURLOPT_UNIX_SOCKET_PATH: {
4012
4475
  curl_easy_setopt(rbce->curl, CURLOPT_UNIX_SOCKET_PATH, StringValueCStr(val));
4013
4476
  } break;
4014
4477
  #endif
4015
- #if HAVE_CURLOPT_MAX_SEND_SPEED_LARGE
4478
+ #ifdef HAVE_CURLOPT_MAX_SEND_SPEED_LARGE
4016
4479
  case CURLOPT_MAX_SEND_SPEED_LARGE: {
4017
4480
  curl_easy_setopt(rbce->curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t) NUM2LL(val));
4018
4481
  } break;
4019
4482
  #endif
4020
- #if HAVE_CURLOPT_MAX_RECV_SPEED_LARGE
4483
+ #ifdef HAVE_CURLOPT_MAX_RECV_SPEED_LARGE
4021
4484
  case CURLOPT_MAX_RECV_SPEED_LARGE: {
4022
4485
  curl_easy_setopt(rbce->curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) NUM2LL(val));
4023
4486
  } break;
4024
4487
  #endif
4025
- #if HAVE_CURLOPT_MAXFILESIZE
4488
+ #ifdef HAVE_CURLOPT_MAXFILESIZE
4026
4489
  case CURLOPT_MAXFILESIZE:
4027
4490
  curl_easy_setopt(rbce->curl, CURLOPT_MAXFILESIZE, NUM2LONG(val));
4028
4491
  break;
4029
4492
  #endif
4030
- #if HAVE_CURLOPT_TCP_KEEPALIVE
4493
+ #ifdef HAVE_CURLOPT_TCP_KEEPALIVE
4031
4494
  case CURLOPT_TCP_KEEPALIVE:
4032
4495
  curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPALIVE, NUM2LONG(val));
4033
4496
  break;
@@ -4038,7 +4501,7 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4038
4501
  curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPINTVL, NUM2LONG(val));
4039
4502
  break;
4040
4503
  #endif
4041
- #if HAVE_CURLOPT_HAPROXYPROTOCOL
4504
+ #ifdef HAVE_CURLOPT_HAPROXYPROTOCOL
4042
4505
  case CURLOPT_HAPROXYPROTOCOL:
4043
4506
  curl_easy_setopt(rbce->curl, CURLOPT_HAPROXYPROTOCOL, NUM2LONG(val));
4044
4507
  break;
@@ -4057,12 +4520,12 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4057
4520
  case CURLOPT_REDIR_PROTOCOLS:
4058
4521
  curl_easy_setopt(rbce->curl, option, NUM2LONG(val));
4059
4522
  break;
4060
- #if HAVE_CURLOPT_SSL_SESSIONID_CACHE
4523
+ #ifdef HAVE_CURLOPT_SSL_SESSIONID_CACHE
4061
4524
  case CURLOPT_SSL_SESSIONID_CACHE:
4062
4525
  curl_easy_setopt(rbce->curl, CURLOPT_SSL_SESSIONID_CACHE, NUM2LONG(val));
4063
4526
  break;
4064
4527
  #endif
4065
- #if HAVE_CURLOPT_COOKIELIST
4528
+ #ifdef HAVE_CURLOPT_COOKIELIST
4066
4529
  case CURLOPT_COOKIELIST: {
4067
4530
  /* Forward to libcurl */
4068
4531
  curl_easy_setopt(rbce->curl, CURLOPT_COOKIELIST, StringValueCStr(val));
@@ -4082,14 +4545,15 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
4082
4545
  }
4083
4546
  } break;
4084
4547
  #endif
4085
- #if HAVE_CURLOPT_PROXY_SSL_VERIFYHOST
4548
+ #ifdef HAVE_CURLOPT_PROXY_SSL_VERIFYHOST
4086
4549
  case CURLOPT_PROXY_SSL_VERIFYHOST:
4087
4550
  curl_easy_setopt(rbce->curl, CURLOPT_PROXY_SSL_VERIFYHOST, NUM2LONG(val));
4088
4551
  break;
4089
4552
  #endif
4090
- #if HAVE_CURLOPT_RESOLVE
4553
+ #ifdef HAVE_CURLOPT_RESOLVE
4091
4554
  case CURLOPT_RESOLVE: {
4092
4555
  struct curl_slist *list = NULL;
4556
+ ruby_curl_easy_clear_resolve_list(rbce);
4093
4557
  if (NIL_P(val)) {
4094
4558
  /* When nil is passed, we clear any previous resolve list */
4095
4559
  list = NULL;
@@ -4144,7 +4608,7 @@ static VALUE ruby_curl_easy_get_opt(VALUE self, VALUE opt) {
4144
4608
  static VALUE ruby_curl_easy_inspect(VALUE self) {
4145
4609
  char buf[64];
4146
4610
  ruby_curl_easy *rbce;
4147
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4611
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4148
4612
  /* if we don't have a url set... we'll crash... */
4149
4613
  if( !rb_easy_nil("url") && rb_easy_type_check("url", T_STRING)) {
4150
4614
  VALUE url = rb_easy_get("url");
@@ -4176,7 +4640,7 @@ static VALUE ruby_curl_easy_escape(VALUE self, VALUE svalue) {
4176
4640
  VALUE rresult;
4177
4641
  VALUE str = svalue;
4178
4642
 
4179
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4643
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4180
4644
 
4181
4645
  /* NOTE: make sure the value is a string, if not call to_s */
4182
4646
  if( rb_type(str) != T_STRING ) { str = rb_funcall(str,rb_intern("to_s"),0); }
@@ -4207,7 +4671,7 @@ static VALUE ruby_curl_easy_unescape(VALUE self, VALUE str) {
4207
4671
  char *result;
4208
4672
  VALUE rresult;
4209
4673
 
4210
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4674
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4211
4675
 
4212
4676
  #if (LIBCURL_VERSION_NUM >= 0x070f04)
4213
4677
  result = (char*)curl_easy_unescape(rbce->curl, StringValuePtr(str), (int)RSTRING_LEN(str), &rlen);
@@ -4256,6 +4720,8 @@ void init_curb_easy() {
4256
4720
  /* Attributes for config next perform */
4257
4721
  rb_define_method(cCurlEasy, "url", ruby_curl_easy_url_get, 0);
4258
4722
  rb_define_method(cCurlEasy, "proxy_url", ruby_curl_easy_proxy_url_get, 0);
4723
+ rb_define_method(cCurlEasy, "http_version=", ruby_curl_easy_http_version_set, 1);
4724
+ rb_define_method(cCurlEasy, "http_version", ruby_curl_easy_http_version_get, 0);
4259
4725
 
4260
4726
  rb_define_method(cCurlEasy, "proxy_headers=", ruby_curl_easy_proxy_headers_set, 1);
4261
4727
  rb_define_method(cCurlEasy, "proxy_headers", ruby_curl_easy_proxy_headers_get, 0);
@@ -4429,6 +4895,7 @@ void init_curb_easy() {
4429
4895
 
4430
4896
  rb_define_method(cCurlEasy, "multi", ruby_curl_easy_multi_get, 0);
4431
4897
  rb_define_method(cCurlEasy, "multi=", ruby_curl_easy_multi_set, 1);
4898
+ rb_define_private_method(cCurlEasy, "_take_callback_error", ruby_curl_easy_take_callback_error, 0);
4432
4899
  rb_define_method(cCurlEasy, "last_result", ruby_curl_easy_last_result, 0);
4433
4900
  rb_define_method(cCurlEasy, "last_error", ruby_curl_easy_last_error, 0);
4434
4901