curb 0.8.4 → 1.3.5

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +522 -0
  3. data/Rakefile +79 -26
  4. data/ext/banned.h +32 -0
  5. data/ext/curb.c +549 -230
  6. data/ext/curb.h +19 -10
  7. data/ext/curb_easy.c +1935 -366
  8. data/ext/curb_easy.h +16 -0
  9. data/ext/curb_errors.c +115 -18
  10. data/ext/curb_errors.h +8 -5
  11. data/ext/curb_macros.h +33 -21
  12. data/ext/curb_multi.c +1858 -272
  13. data/ext/curb_multi.h +10 -3
  14. data/ext/curb_postfield.c +149 -77
  15. data/ext/curb_postfield.h +1 -0
  16. data/ext/curb_upload.c +38 -11
  17. data/ext/curb_upload.h +2 -0
  18. data/ext/extconf.rb +353 -35
  19. data/lib/curb.rb +1 -0
  20. data/lib/curl/easy.rb +314 -78
  21. data/lib/curl/multi.rb +134 -28
  22. data/lib/curl.rb +217 -11
  23. data/tests/bug_crash_on_debug.rb +14 -28
  24. data/tests/bug_crash_on_progress.rb +32 -16
  25. data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
  26. data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
  27. data/tests/bug_follow_redirect_288.rb +83 -0
  28. data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
  29. data/tests/bug_issue102.rb +1 -1
  30. data/tests/bug_issue_noproxy.rb +56 -0
  31. data/tests/bug_issue_post_redirect.rb +93 -0
  32. data/tests/bug_issue_spnego.rb +41 -0
  33. data/tests/bug_multi_segfault.rb +1 -0
  34. data/tests/bug_raise_on_callback.rb +30 -0
  35. data/tests/helper.rb +400 -44
  36. data/tests/leak_trace.rb +237 -0
  37. data/tests/mem_check.rb +3 -0
  38. data/tests/tc_curl.rb +31 -1
  39. data/tests/tc_curl_download.rb +12 -7
  40. data/tests/tc_curl_easy.rb +911 -63
  41. data/tests/tc_curl_easy_cookielist.rb +277 -0
  42. data/tests/tc_curl_easy_request_target.rb +41 -0
  43. data/tests/tc_curl_easy_resolve.rb +48 -0
  44. data/tests/tc_curl_maxfilesize.rb +12 -0
  45. data/tests/tc_curl_multi.rb +855 -53
  46. data/tests/tc_curl_native_coverage.rb +145 -0
  47. data/tests/tc_curl_postfield.rb +207 -30
  48. data/tests/tc_curl_protocols.rb +37 -0
  49. data/tests/tc_fiber_scheduler.rb +543 -0
  50. data/tests/tc_ftp_options.rb +39 -0
  51. data/tests/tc_gc_compact.rb +223 -0
  52. data/tests/tc_test_server_methods.rb +110 -0
  53. data/tests/timeout.rb +30 -6
  54. metadata +59 -36
  55. data/README +0 -194
data/ext/curb_easy.c CHANGED
@@ -12,6 +12,9 @@
12
12
 
13
13
  #include <errno.h>
14
14
  #include <string.h>
15
+ #ifndef _WIN32
16
+ #include <strings.h>
17
+ #endif
15
18
 
16
19
  extern VALUE mCurl;
17
20
 
@@ -25,20 +28,176 @@ static VALUE rbstrAmp;
25
28
 
26
29
  VALUE cCurlEasy;
27
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
+
39
+ // for Ruby 1.8
40
+ #ifndef HAVE_RB_IO_STDIO_FILE
41
+ static FILE * rb_io_stdio_file(rb_io_t *fptr) {
42
+ return fptr->f;
43
+ }
44
+ #endif
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);
28
47
 
29
48
  /* ================== CURL HANDLER FUNCS ==============*/
30
49
 
31
- static VALUE callback_exception(VALUE unused) {
50
+ static VALUE callback_exception(VALUE unused, VALUE exception) {
32
51
  return Qfalse;
33
- }
52
+ }
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
+ };
34
143
 
35
- /* These handle both body and header data */
36
- static size_t default_data_handler(char *stream,
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
+
173
+ /* Default body handler appends to easy.body_data buffer */
174
+ static size_t default_body_handler(char *stream,
37
175
  size_t size,
38
176
  size_t nmemb,
39
- VALUE out) {
40
- rb_str_buf_cat(out, stream, size * nmemb);
41
- return size * nmemb;
177
+ void *userdata) {
178
+ ruby_curl_easy *rbce = (ruby_curl_easy *)userdata;
179
+ size_t total = size * nmemb;
180
+ VALUE out = rb_easy_get("body_data");
181
+ if (NIL_P(out)) {
182
+ out = rb_easy_set("body_data", rb_str_buf_new(32768));
183
+ }
184
+ rb_str_buf_cat(out, stream, total);
185
+ return total;
186
+ }
187
+
188
+ /* Default header handler appends to easy.header_data buffer */
189
+ static size_t default_header_handler(char *stream,
190
+ size_t size,
191
+ size_t nmemb,
192
+ void *userdata) {
193
+ ruby_curl_easy *rbce = (ruby_curl_easy *)userdata;
194
+ size_t total = size * nmemb;
195
+ VALUE out = rb_easy_get("header_data");
196
+ if (NIL_P(out)) {
197
+ out = rb_easy_set("header_data", rb_str_buf_new(16384));
198
+ }
199
+ rb_str_buf_cat(out, stream, total);
200
+ return total;
42
201
  }
43
202
 
44
203
  // size_t function( void *ptr, size_t size, size_t nmemb, void *stream);
@@ -48,14 +207,36 @@ static size_t read_data_handler(void *ptr,
48
207
  ruby_curl_easy *rbce) {
49
208
  VALUE upload = rb_easy_get("upload");
50
209
  size_t read_bytes = (size*nmemb);
51
- 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);
52
217
 
53
218
  if (rb_respond_to(stream, rb_intern("read"))) {//if (rb_respond_to(stream, rb_intern("to_s"))) {
54
219
  /* copy read_bytes from stream into ptr */
55
- 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);
56
224
  if( str != Qnil ) {
57
- memcpy(ptr, RSTRING_PTR(str), RSTRING_LEN(str));
58
- 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;
59
240
  }
60
241
  else {
61
242
  return 0;
@@ -67,13 +248,22 @@ static size_t read_data_handler(void *ptr,
67
248
  size_t len;
68
249
  size_t remaining;
69
250
  char *str_ptr;
70
- Data_Get_Struct(upload, ruby_curl_upload, rbcu);
71
- 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
+
72
258
  len = RSTRING_LEN(str);
259
+ if (rbcu->offset >= len) {
260
+ return 0;
261
+ }
262
+
73
263
  remaining = len - rbcu->offset;
74
264
  str_ptr = RSTRING_PTR(str);
75
265
 
76
- if( remaining < read_bytes ) {
266
+ if( remaining <= read_bytes ) {
77
267
  if( remaining > 0 ) {
78
268
  memcpy(ptr, str_ptr+rbcu->offset, remaining);
79
269
  read_bytes = remaining;
@@ -81,14 +271,10 @@ static size_t read_data_handler(void *ptr,
81
271
  }
82
272
  return remaining;
83
273
  }
84
- else if( remaining > read_bytes ) { // read_bytes <= remaining - send what we can fit in the buffer(ptr)
274
+ else { // read_bytes < remaining - send what we can fit in the buffer(ptr)
85
275
  memcpy(ptr, str_ptr+rbcu->offset, read_bytes);
86
276
  rbcu->offset += read_bytes;
87
277
  }
88
- else { // they're equal
89
- memcpy(ptr, str_ptr+rbcu->offset, --read_bytes);
90
- rbcu->offset += read_bytes;
91
- }
92
278
  return read_bytes;
93
279
  }
94
280
  else {
@@ -96,6 +282,38 @@ static size_t read_data_handler(void *ptr,
96
282
  }
97
283
  }
98
284
 
285
+ int seek_data_handler(ruby_curl_easy *rbce,
286
+ curl_off_t offset,
287
+ int origin) {
288
+
289
+ VALUE upload = rb_easy_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);
297
+
298
+ if (rb_respond_to(stream, rb_intern("seek"))) {
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
+ }
307
+ } else {
308
+ ruby_curl_upload *rbcu;
309
+ TypedData_Get_Struct(upload, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
310
+ // This OK because curl only uses SEEK_SET as per the documentation
311
+ rbcu->offset = offset;
312
+ }
313
+
314
+ return 0;
315
+ }
316
+
99
317
  static size_t proc_data_handler(char *stream,
100
318
  size_t size,
101
319
  size_t nmemb,
@@ -120,22 +338,40 @@ static size_t proc_data_handler_body(char *stream,
120
338
  size_t nmemb,
121
339
  ruby_curl_easy *rbce)
122
340
  {
123
- size_t ret;
124
- rbce->callback_active = 1;
125
- ret = proc_data_handler(stream, size, nmemb, rb_easy_get("body_proc"));
126
- rbce->callback_active = 0;
127
- 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);
128
355
  }
129
356
  static size_t proc_data_handler_header(char *stream,
130
357
  size_t size,
131
358
  size_t nmemb,
132
359
  ruby_curl_easy *rbce)
133
360
  {
134
- size_t ret;
135
- rbce->callback_active = 1;
136
- ret = proc_data_handler(stream, size, nmemb, rb_easy_get("header_proc"));
137
- rbce->callback_active = 0;
138
- 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);
139
375
  }
140
376
 
141
377
 
@@ -147,11 +383,18 @@ static VALUE call_progress_handler(VALUE ary) {
147
383
  rb_ary_entry(ary, 4)); // rb_float_new(ulnow));
148
384
  }
149
385
 
150
- static int proc_progress_handler(VALUE proc,
386
+ /* CURLOPT_PROGRESSFUNCTION callback (deprecated since 7.32.0) */
387
+ #ifndef HAVE_CURLOPT_XFERINFOFUNCTION
388
+ static int proc_progress_handler(void *clientp,
151
389
  double dltotal,
152
390
  double dlnow,
153
391
  double ultotal,
154
392
  double ulnow) {
393
+ ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
394
+ VALUE proc = rb_easy_get("progress_proc");
395
+ if (proc == Qnil) {
396
+ return 0;
397
+ }
155
398
  VALUE procret;
156
399
  VALUE callargs = rb_ary_new2(5);
157
400
 
@@ -161,71 +404,267 @@ static int proc_progress_handler(VALUE proc,
161
404
  rb_ary_store(callargs, 3, rb_float_new(ultotal));
162
405
  rb_ary_store(callargs, 4, rb_float_new(ulnow));
163
406
 
164
- //v = rb_rescue(range_check, (VALUE)args, range_failed, 0);
165
- //procret = rb_funcall(proc, idCall, 4, rb_float_new(dltotal),
166
- // rb_float_new(dlnow),
167
- // rb_float_new(ultotal),
168
- // rb_float_new(ulnow));
169
- 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);
412
+
413
+ return(((procret == Qfalse) || (procret == Qnil)) ? -1 : 0);
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);
170
443
 
171
444
  return(((procret == Qfalse) || (procret == Qnil)) ? -1 : 0);
172
445
  }
446
+ #endif
173
447
 
174
448
  static VALUE call_debug_handler(VALUE ary) {
175
449
  return rb_funcall(rb_ary_entry(ary, 0), idCall, 2,
176
- rb_ary_entry(ary, 1), // INT2FIX(type),
450
+ rb_ary_entry(ary, 1), // INT2NUM(type),
177
451
  rb_ary_entry(ary, 2)); // rb_str_new(data, data_len)
178
452
  }
179
453
  static int proc_debug_handler(CURL *curl,
180
454
  curl_infotype type,
181
455
  char *data,
182
456
  size_t data_len,
183
- VALUE proc) {
457
+ void *clientp) {
458
+ ruby_curl_easy *rbce = (ruby_curl_easy *)clientp;
459
+ VALUE proc = rb_easy_get("debug_proc");
460
+ if (proc == Qnil) {
461
+ return 0;
462
+ }
184
463
  VALUE callargs = rb_ary_new2(3);
185
464
  rb_ary_store(callargs, 0, proc);
186
- rb_ary_store(callargs, 1, INT2FIX(type));
465
+ rb_ary_store(callargs, 1, INT2NUM(type));
187
466
  rb_ary_store(callargs, 2, rb_str_new(data, data_len));
188
- 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);
189
472
  /* no way to indicate to libcurl that we should break out given an exception in the on_debug handler...
190
473
  * this means exceptions will be swallowed
191
474
  */
192
- //rb_funcall(proc, idCall, 2, INT2FIX(type), rb_str_new(data, data_len));
475
+ //rb_funcall(proc, idCall, 2, INT2NUM(type), rb_str_new(data, data_len));
193
476
  return 0;
194
477
  }
195
478
 
196
- /* ================== MARK/FREE FUNC ==================*/
197
- void curl_easy_mark(ruby_curl_easy *rbce) {
198
- if (!NIL_P(rbce->opts)) { rb_gc_mark(rbce->opts); }
199
- 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
+ }
487
+ }
488
+
489
+ static void ruby_curl_easy_clear_headers_list(ruby_curl_easy *rbce) {
490
+ if (!rbce || !rbce->curl_headers) {
491
+ return;
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
+ }
499
+
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
+ }
512
+
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
+ }
523
+
524
+ static void ruby_curl_easy_clear_resolve_list(ruby_curl_easy *rbce) {
525
+ if (!rbce || !rbce->curl_resolve) {
526
+ return;
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
+ }
536
+
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;
545
+ }
546
+
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;
560
+ }
561
+
562
+ return DATA_PTR(multi_val);
563
+ #endif
200
564
  }
201
565
 
202
566
  static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
203
- if (rbce->curl_headers) {
204
- curl_slist_free_all(rbce->curl_headers);
567
+ if (!rbce) {
568
+ return;
205
569
  }
206
570
 
207
- if (rbce->curl_ftp_commands) {
208
- curl_slist_free_all(rbce->curl_ftp_commands);
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
+ }
209
587
  }
210
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
+
211
594
  if (rbce->curl) {
595
+ /* disable any progress or debug events */
596
+ curl_easy_setopt(rbce->curl, CURLOPT_WRITEFUNCTION, NULL);
597
+ curl_easy_setopt(rbce->curl, CURLOPT_WRITEDATA, NULL);
598
+ curl_easy_setopt(rbce->curl, CURLOPT_HEADERFUNCTION, NULL);
599
+ curl_easy_setopt(rbce->curl, CURLOPT_HEADERDATA, NULL);
600
+ curl_easy_setopt(rbce->curl, CURLOPT_DEBUGFUNCTION, NULL);
601
+ curl_easy_setopt(rbce->curl, CURLOPT_DEBUGDATA, NULL);
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
606
+ curl_easy_setopt(rbce->curl, CURLOPT_PROGRESSFUNCTION, NULL);
607
+ #endif
608
+ curl_easy_setopt(rbce->curl, CURLOPT_NOPROGRESS, 1);
212
609
  curl_easy_cleanup(rbce->curl);
610
+ rbce->curl = NULL;
611
+ }
612
+
613
+ rbce->self = Qnil;
614
+ }
615
+
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);
213
622
  }
214
623
  }
215
624
 
216
- void curl_easy_free(ruby_curl_easy *rbce) {
217
- ruby_curl_easy_free(rbce);
218
- free(rbce);
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);
219
628
  }
220
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
+
221
655
 
222
656
  /* ================= ALLOC METHODS ====================*/
223
657
 
224
658
  static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
225
659
  rbce->opts = rb_hash_new();
226
660
 
661
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
662
+
663
+ rbce->self = Qnil;
227
664
  rbce->curl_headers = NULL;
665
+ rbce->curl_proxy_headers = NULL;
228
666
  rbce->curl_ftp_commands = NULL;
667
+ rbce->curl_resolve = NULL;
229
668
 
230
669
  /* various-typed opts */
231
670
  rbce->local_port = 0;
@@ -236,14 +675,19 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
236
675
  rbce->proxy_auth_types = 0;
237
676
  rbce->max_redirs = -1;
238
677
  rbce->timeout = 0;
678
+ rbce->timeout_ms = 0;
239
679
  rbce->connect_timeout = 0;
680
+ rbce->connect_timeout_ms = 0;
240
681
  rbce->dns_cache_timeout = 60;
241
682
  rbce->ftp_response_timeout = 0;
242
683
  rbce->low_speed_limit = 0;
243
684
  rbce->low_speed_time = 0;
685
+ rbce->max_send_speed_large = 0;
686
+ rbce->max_recv_speed_large = 0;
244
687
  rbce->ssl_version = -1;
245
688
  rbce->use_ssl = -1;
246
689
  rbce->ftp_filemethod = -1;
690
+ rbce->http_version = CURL_HTTP_VERSION_NONE;
247
691
  rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER;
248
692
 
249
693
  /* bool opts */
@@ -258,29 +702,47 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
258
702
  rbce->verbose = 0;
259
703
  rbce->multipart_form_post = 0;
260
704
  rbce->enable_cookies = 0;
705
+ rbce->cookielist_engine_enabled = 0;
261
706
  rbce->ignore_content_length = 0;
262
707
  rbce->callback_active = 0;
708
+ rbce->callback_error = Qnil;
709
+ rbce->last_result = 0;
710
+ }
711
+
712
+ /*
713
+ * Allocate space for a Curl::Easy instance.
714
+ */
715
+ static VALUE ruby_curl_easy_allocate(VALUE klass) {
716
+ ruby_curl_easy *rbce;
717
+ rbce = ALLOC(ruby_curl_easy);
718
+ if (!rbce) {
719
+ rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Easy");
720
+ }
721
+ rbce->curl = NULL;
722
+ rbce->opts = Qnil;
723
+ rbce->multi = Qnil;
724
+ ruby_curl_easy_zero(rbce);
725
+ return TypedData_Wrap_Struct(klass, &ruby_curl_easy_data_type, rbce);
263
726
  }
264
727
 
265
728
  /*
266
729
  * call-seq:
267
- * Curl::Easy.new => #&lt;Curl::Easy...&gt;
268
- * Curl::Easy.new(url = nil) => #&lt;Curl::Easy...&gt;
269
- * Curl::Easy.new(url = nil) { |self| ... } => #&lt;Curl::Easy...&gt;
730
+ * Curl::Easy.new => #<Curl::Easy...>
731
+ * Curl::Easy.new(url = nil) => #<Curl::Easy...>
732
+ * Curl::Easy.new(url = nil) { |self| ... } => #<Curl::Easy...>
270
733
  *
271
- * Create a new Curl::Easy instance, optionally supplying the URL.
734
+ * Initialize a new Curl::Easy instance, optionally supplying the URL.
272
735
  * The block form allows further configuration to be supplied before
273
736
  * the instance is returned.
274
737
  */
275
- static VALUE ruby_curl_easy_new(int argc, VALUE *argv, VALUE klass) {
738
+ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
276
739
  CURLcode ecode;
277
740
  VALUE url, blk;
278
- VALUE new_curl;
279
741
  ruby_curl_easy *rbce;
280
742
 
281
743
  rb_scan_args(argc, argv, "01&", &url, &blk);
282
744
 
283
- rbce = ALLOC(ruby_curl_easy);
745
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
284
746
 
285
747
  /* handler */
286
748
  rbce->curl = curl_easy_init();
@@ -288,32 +750,67 @@ static VALUE ruby_curl_easy_new(int argc, VALUE *argv, VALUE klass) {
288
750
  rb_raise(eCurlErrFailedInit, "Failed to initialize easy handle");
289
751
  }
290
752
 
291
- new_curl = Data_Wrap_Struct(klass, curl_easy_mark, curl_easy_free, rbce);
292
-
293
753
  rbce->multi = Qnil;
294
754
  rbce->opts = Qnil;
295
755
 
296
756
  ruby_curl_easy_zero(rbce);
757
+ rbce->self = self;
758
+
759
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
297
760
 
298
761
  rb_easy_set("url", url);
299
762
 
300
- /* set the new_curl pointer to the curl handle */
301
- ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)new_curl);
763
+ /* set the pointer to the curl handle */
764
+ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
302
765
  if (ecode != CURLE_OK) {
303
766
  raise_curl_easy_error_exception(ecode);
304
767
  }
305
768
 
306
769
  if (blk != Qnil) {
307
- rb_funcall(blk, idCall, 1, new_curl);
770
+ rb_funcall(blk, idCall, 1, self);
308
771
  }
309
772
 
310
- return new_curl;
773
+ return self;
774
+ }
775
+
776
+ /* Helper to duplicate a curl_slist */
777
+ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list) {
778
+ struct curl_slist *dup = NULL;
779
+ struct curl_slist *tmp;
780
+ struct curl_slist *new_list;
781
+ for (tmp = list; tmp; tmp = tmp->next) {
782
+ new_list = curl_slist_append(dup, tmp->data);
783
+ if (!new_list) {
784
+ if (dup) { curl_slist_free_all(dup); }
785
+ rb_raise(rb_eNoMemError, "Failed to duplicate curl_slist");
786
+ }
787
+ dup = new_list;
788
+ }
789
+ return dup;
790
+ }
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;
311
808
  }
312
809
 
313
810
  /*
314
811
  * call-seq:
315
- * easy.clone => #&lt;easy clone&gt;
316
- * easy.dup => #&lt;easy clone&gt;
812
+ * easy.clone => <easy clone>
813
+ * easy.dup => <easy clone>
317
814
  *
318
815
  * Clone this Curl::Easy instance, creating a new instance.
319
816
  * This method duplicates the underlying CURL* handle.
@@ -321,15 +818,57 @@ static VALUE ruby_curl_easy_new(int argc, VALUE *argv, VALUE klass) {
321
818
  static VALUE ruby_curl_easy_clone(VALUE self) {
322
819
  ruby_curl_easy *rbce, *newrbce;
323
820
 
324
- Data_Get_Struct(self, ruby_curl_easy, rbce);
821
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
325
822
 
326
823
  newrbce = ALLOC(ruby_curl_easy);
824
+ if (!newrbce) {
825
+ rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Easy clone");
826
+ }
827
+ /* shallow copy */
327
828
  memcpy(newrbce, rbce, sizeof(ruby_curl_easy));
829
+
830
+ /* now deep copy */
328
831
  newrbce->curl = curl_easy_duphandle(rbce->curl);
329
- newrbce->curl_headers = NULL;
330
- newrbce->curl_ftp_commands = NULL;
832
+ if (!newrbce->curl) {
833
+ free(newrbce);
834
+ rb_raise(rb_eNoMemError, "Failed to duplicate Curl::Easy handle");
835
+ }
836
+ newrbce->curl_headers = (rbce->curl_headers) ? duplicate_curl_slist(rbce->curl_headers) : NULL;
837
+ newrbce->curl_proxy_headers = (rbce->curl_proxy_headers) ? duplicate_curl_slist(rbce->curl_proxy_headers) : NULL;
838
+ newrbce->curl_ftp_commands = (rbce->curl_ftp_commands) ? duplicate_curl_slist(rbce->curl_ftp_commands) : NULL;
839
+ newrbce->curl_resolve = (rbce->curl_resolve) ? duplicate_curl_slist(rbce->curl_resolve) : NULL;
331
840
 
332
- return Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, newrbce);
841
+ /* A cloned easy should not retain ownership reference to the original multi. */
842
+ newrbce->multi = Qnil;
843
+ newrbce->callback_error = Qnil;
844
+
845
+ if (rbce->opts != Qnil) {
846
+ newrbce->opts = rb_funcall(rbce->opts, rb_intern("dup"), 0);
847
+ }
848
+
849
+ /* Set the error buffer on the new curl handle using the new err_buf */
850
+ curl_easy_setopt(newrbce->curl, CURLOPT_ERRORBUFFER, newrbce->err_buf);
851
+
852
+ if (newrbce->opts != Qnil) {
853
+ VALUE upload = rb_hash_aref(newrbce->opts, rb_easy_hkey("upload"));
854
+ if (!NIL_P(upload)) {
855
+ rb_hash_aset(newrbce->opts, rb_easy_hkey("upload"), duplicate_upload(upload));
856
+ curl_easy_setopt(newrbce->curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler);
857
+ curl_easy_setopt(newrbce->curl, CURLOPT_READDATA, newrbce);
858
+ #ifdef HAVE_CURLOPT_SEEKFUNCTION
859
+ curl_easy_setopt(newrbce->curl, CURLOPT_SEEKFUNCTION, (curl_seek_callback)seek_data_handler);
860
+ #endif
861
+ #ifdef HAVE_CURLOPT_SEEKDATA
862
+ curl_easy_setopt(newrbce->curl, CURLOPT_SEEKDATA, newrbce);
863
+ #endif
864
+ }
865
+ }
866
+
867
+ VALUE clone = TypedData_Wrap_Struct(cCurlEasy, &ruby_curl_easy_data_type, newrbce);
868
+ newrbce->self = clone;
869
+ curl_easy_setopt(newrbce->curl, CURLOPT_PRIVATE, (void*)newrbce);
870
+
871
+ return clone;
333
872
  }
334
873
 
335
874
  /*
@@ -337,14 +876,14 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
337
876
  * easy.close => nil
338
877
  *
339
878
  * Close the Curl::Easy instance. Any open connections are closed
340
- * The easy handle is reinitialized. If a previous multi handle was
879
+ * The easy handle is reinitialized. If a previous multi handle was
341
880
  * open it is set to nil and will be cleared after a GC.
342
881
  */
343
882
  static VALUE ruby_curl_easy_close(VALUE self) {
344
883
  CURLcode ecode;
345
884
  ruby_curl_easy *rbce;
346
885
 
347
- Data_Get_Struct(self, ruby_curl_easy, rbce);
886
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
348
887
 
349
888
  if (rbce->callback_active) {
350
889
  rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback");
@@ -361,9 +900,12 @@ static VALUE ruby_curl_easy_close(VALUE self) {
361
900
  rbce->multi = Qnil;
362
901
 
363
902
  ruby_curl_easy_zero(rbce);
903
+ rbce->self = self;
904
+
905
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, rbce->err_buf);
364
906
 
365
907
  /* give the new curl handle a reference back to the ruby object */
366
- ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)self);
908
+ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
367
909
  if (ecode != CURLE_OK) {
368
910
  raise_curl_easy_error_exception(ecode);
369
911
  }
@@ -387,7 +929,7 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
387
929
  CURLcode ecode;
388
930
  ruby_curl_easy *rbce;
389
931
  VALUE opts_dup;
390
- Data_Get_Struct(self, ruby_curl_easy, rbce);
932
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
391
933
 
392
934
  if (rbce->callback_active) {
393
935
  rb_raise(rb_eRuntimeError, "Cannot close an active curl handle within a callback");
@@ -395,21 +937,19 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
395
937
 
396
938
  opts_dup = rb_funcall(rbce->opts, rb_intern("dup"), 0);
397
939
 
940
+ ruby_curl_easy_cleanup(self, rbce);
398
941
  curl_easy_reset(rbce->curl);
399
942
  ruby_curl_easy_zero(rbce);
943
+ rbce->self = self;
400
944
 
401
- /* rest clobbers the private setting, so reset it to self */
402
- ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)self);
945
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
946
+
947
+ /* reset clobbers the private setting, so reset it to self */
948
+ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)rbce);
403
949
  if (ecode != CURLE_OK) {
404
950
  raise_curl_easy_error_exception(ecode);
405
951
  }
406
952
 
407
- /* Free everything up */
408
- if (rbce->curl_headers) {
409
- curl_slist_free_all(rbce->curl_headers);
410
- rbce->curl_headers = NULL;
411
- }
412
-
413
953
  return opts_dup;
414
954
  }
415
955
 
@@ -462,6 +1002,10 @@ static VALUE ruby_curl_easy_headers_set(VALUE self, VALUE headers) {
462
1002
  CURB_OBJECT_HSETTER(ruby_curl_easy, headers);
463
1003
  }
464
1004
 
1005
+ static VALUE ruby_curl_easy_proxy_headers_set(VALUE self, VALUE proxy_headers) {
1006
+ CURB_OBJECT_HSETTER(ruby_curl_easy, proxy_headers);
1007
+ }
1008
+
465
1009
  /*
466
1010
  * call-seq:
467
1011
  * easy.headers => Hash, Array or Str
@@ -471,12 +1015,47 @@ static VALUE ruby_curl_easy_headers_set(VALUE self, VALUE headers) {
471
1015
  static VALUE ruby_curl_easy_headers_get(VALUE self) {
472
1016
  ruby_curl_easy *rbce;
473
1017
  VALUE headers;
474
- Data_Get_Struct(self, ruby_curl_easy, rbce);
475
- headers = rb_easy_get("headers");//rb_hash_aref(rbce->opts, rb_intern("headers"));
1018
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1019
+ headers = rb_easy_get("headers");//rb_hash_aref(rbce->opts, rb_intern("headers"));
476
1020
  if (headers == Qnil) { headers = rb_easy_set("headers", rb_hash_new()); }
477
1021
  return headers;
478
1022
  }
479
1023
 
1024
+ /*
1025
+ * call-seq:
1026
+ * easy.proxy_headers = "Header: val" => "Header: val"
1027
+ * easy.proxy_headers = {"Header" => "val" ..., "Header" => "val"} => {"Header: val", ...}
1028
+ * easy.proxy_headers = ["Header: val" ..., "Header: val"] => ["Header: val", ...]
1029
+ *
1030
+ *
1031
+ * For example to set a standard or custom header:
1032
+ *
1033
+ * easy.proxy_headers["MyHeader"] = "myval"
1034
+ *
1035
+ * To remove a standard header (this is useful when removing libcurls default
1036
+ * 'Expect: 100-Continue' header when using HTTP form posts):
1037
+ *
1038
+ * easy.proxy_headers["Expect"] = ''
1039
+ *
1040
+ * Anything passed to libcurl as a header will be converted to a string during
1041
+ * the perform step.
1042
+ */
1043
+
1044
+ /*
1045
+ * call-seq:
1046
+ * easy.proxy_headers => Hash, Array or Str
1047
+ *
1048
+ * Obtain the custom HTTP proxy_headers for following requests.
1049
+ */
1050
+ static VALUE ruby_curl_easy_proxy_headers_get(VALUE self) {
1051
+ ruby_curl_easy *rbce;
1052
+ VALUE proxy_headers;
1053
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1054
+ proxy_headers = rb_easy_get("proxy_headers");//rb_hash_aref(rbce->opts, rb_intern("proxy_headers"));
1055
+ if (proxy_headers == Qnil) { proxy_headers = rb_easy_set("proxy_headers", rb_hash_new()); }
1056
+ return proxy_headers;
1057
+ }
1058
+
480
1059
  /*
481
1060
  * call-seq:
482
1061
  * easy.interface => string
@@ -515,7 +1094,16 @@ static VALUE ruby_curl_easy_proxypwd_get(VALUE self) {
515
1094
  * call-seq:
516
1095
  * easy.cookies => "name1=content1; name2=content2;"
517
1096
  *
518
- * Obtain the cookies for this Curl::Easy instance.
1097
+ * Obtain the manually set Cookie header string for this Curl::Easy instance.
1098
+ *
1099
+ * Notes:
1100
+ * - This corresponds to libcurl's CURLOPT_COOKIE and only affects the outgoing
1101
+ * Cookie request header. It does NOT modify the internal libcurl cookie engine
1102
+ * that stores cookies received via Set-Cookie.
1103
+ * - To inspect or modify cookies stored in the cookie engine, use
1104
+ * +easy.cookielist+ (getter) and +easy.cookielist=+ or +easy.set(:cookielist, ...)+ (setter).
1105
+ * - To clear a previously set manual Cookie header, assign an empty string.
1106
+ * Assigning +nil+ currently has no effect.
519
1107
  */
520
1108
  static VALUE ruby_curl_easy_cookies_get(VALUE self) {
521
1109
  CURB_OBJECT_HGETTER(ruby_curl_easy, cookies);
@@ -525,7 +1113,8 @@ static VALUE ruby_curl_easy_cookies_get(VALUE self) {
525
1113
  * call-seq:
526
1114
  * easy.cookiefile => string
527
1115
  *
528
- * Obtain the cookiefile file for this Curl::Easy instance.
1116
+ * Obtain the cookiefile path for this Curl::Easy instance (used to load cookies when the
1117
+ * cookie engine is enabled).
529
1118
  */
530
1119
  static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
531
1120
  CURB_OBJECT_HGETTER(ruby_curl_easy, cookiefile);
@@ -535,7 +1124,8 @@ static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
535
1124
  * call-seq:
536
1125
  * easy.cookiejar => string
537
1126
  *
538
- * Obtain the cookiejar file to use for this Curl::Easy instance.
1127
+ * Obtain the cookiejar path for this Curl::Easy instance (used to persist cookies when the
1128
+ * cookie engine is enabled).
539
1129
  */
540
1130
  static VALUE ruby_curl_easy_cookiejar_get(VALUE self) {
541
1131
  CURB_OBJECT_HGETTER(ruby_curl_easy, cookiejar);
@@ -684,57 +1274,81 @@ static VALUE ruby_curl_easy_useragent_get(VALUE self) {
684
1274
  /*
685
1275
  * call-seq:
686
1276
  * easy.post_body = "some=form%20data&to=send" => string or nil
687
- *
1277
+ *
688
1278
  * Sets the POST body of this Curl::Easy instance. This is expected to be
689
1279
  * URL encoded; no additional processing or encoding is done on the string.
690
1280
  * The content-type header will be set to application/x-www-form-urlencoded.
691
- *
1281
+ *
692
1282
  * This is handy if you want to perform a POST against a Curl::Multi instance.
693
1283
  */
694
- static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
1284
+ static VALUE ruby_curl_easy_post_body_set_with_mode(VALUE self, VALUE post_body, int force_http_get_on_nil) {
695
1285
  ruby_curl_easy *rbce;
696
1286
  CURL *curl;
697
-
1287
+
1288
+ VALUE body_str;
1289
+ VALUE retained_body_str;
698
1290
  char *data;
699
1291
  long len;
700
1292
 
701
- Data_Get_Struct(self, ruby_curl_easy, rbce);
702
-
1293
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1294
+
703
1295
  curl = rbce->curl;
704
-
1296
+
705
1297
  if ( post_body == Qnil ) {
706
1298
  rb_easy_del("postdata_buffer");
707
- curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
708
-
709
- } else {
1299
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
1300
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0);
1301
+ if (force_http_get_on_nil) {
1302
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
1303
+ }
1304
+
1305
+ } else {
710
1306
  if (rb_type(post_body) == T_STRING) {
711
- data = StringValuePtr(post_body);
712
- len = RSTRING_LEN(post_body);
1307
+ body_str = post_body;
713
1308
  }
714
1309
  else if (rb_respond_to(post_body, rb_intern("to_s"))) {
715
- VALUE str_body = rb_funcall(post_body, rb_intern("to_s"), 0);
716
- data = StringValuePtr(str_body);
717
- len = RSTRING_LEN(post_body);
1310
+ body_str = rb_funcall(post_body, rb_intern("to_s"), 0);
718
1311
  }
719
1312
  else {
720
1313
  rb_raise(rb_eRuntimeError, "post data must respond_to .to_s");
721
1314
  }
722
-
723
- // Store the string, since it has to hang around for the duration of the
1315
+
1316
+ StringValue(body_str);
1317
+
1318
+ // Store the string, since it has to hang around for the duration of the
724
1319
  // request. See CURLOPT_POSTFIELDS in the libcurl docs.
725
- //rbce->postdata_buffer = post_body;
726
- rb_easy_set("postdata_buffer", post_body);
727
-
1320
+ #ifdef HAVE_CURLOPT_COPYPOSTFIELDS
1321
+ /*
1322
+ * libcurl copies the bytes immediately for COPYPOSTFIELDS, so retain a
1323
+ * matching Ruby snapshot for post_body instead of the caller's mutable
1324
+ * source string.
1325
+ */
1326
+ retained_body_str = rb_str_dup(body_str);
1327
+ #else
1328
+ retained_body_str = body_str;
1329
+ #endif
1330
+ data = StringValuePtr(retained_body_str);
1331
+ len = RSTRING_LEN(retained_body_str);
1332
+ rb_easy_set("postdata_buffer", retained_body_str);
1333
+
728
1334
  curl_easy_setopt(curl, CURLOPT_POST, 1);
729
- curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
730
1335
  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
731
-
1336
+ #ifdef HAVE_CURLOPT_COPYPOSTFIELDS
1337
+ curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, data);
1338
+ #else
1339
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
1340
+ #endif
1341
+
732
1342
  return post_body;
733
1343
  }
734
-
1344
+
735
1345
  return Qnil;
736
1346
  }
737
1347
 
1348
+ static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
1349
+ return ruby_curl_easy_post_body_set_with_mode(self, post_body, 1);
1350
+ }
1351
+
738
1352
  /*
739
1353
  * call-seq:
740
1354
  * easy.post_body => string or nil
@@ -748,7 +1362,7 @@ static VALUE ruby_curl_easy_post_body_get(VALUE self) {
748
1362
  /*
749
1363
  * call-seq:
750
1364
  * easy.put_data = data => ""
751
- *
1365
+ *
752
1366
  * Points this Curl::Easy instance to data to be uploaded via PUT. This
753
1367
  * sets the request to a PUT type request - useful if you want to PUT via
754
1368
  * a multi handle.
@@ -757,26 +1371,15 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
757
1371
  ruby_curl_easy *rbce;
758
1372
  CURL *curl;
759
1373
  VALUE upload;
1374
+ VALUE upload_stream = data;
760
1375
  VALUE headers;
1376
+ VALUE infile_size = Qnil;
761
1377
 
762
- Data_Get_Struct(self, ruby_curl_easy, rbce);
763
-
764
- upload = ruby_curl_upload_new(cCurlUpload);
765
- ruby_curl_upload_stream_set(upload,data);
766
-
767
- curl = rbce->curl;
768
- rb_easy_set("upload", upload); /* keep the upload object alive as long as
769
- the easy handle is active or until the upload
770
- is complete or terminated... */
771
-
772
- curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
773
- curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
774
- curl_easy_setopt(curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler);
775
- curl_easy_setopt(curl, CURLOPT_READDATA, rbce);
1378
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
776
1379
 
777
- /*
778
- * we need to set specific headers for the PUT to work... so
779
- * convert the internal headers structure to a HASH if one is set
1380
+ /*
1381
+ * Validate and prepare Ruby-visible state before mutating the CURL handle.
1382
+ * Several branches below can raise (header type, stat, size, to_s).
780
1383
  */
781
1384
  if (!rb_easy_nil("headers")) {
782
1385
  if (rb_easy_type_check("headers", T_ARRAY) || rb_easy_type_check("headers", T_STRING)) {
@@ -784,43 +1387,80 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
784
1387
  }
785
1388
  }
786
1389
 
787
- // exit fast if the payload is empty
788
- if (NIL_P(data)) { return data; }
1390
+ if (!NIL_P(data) && !rb_respond_to(data, rb_intern("read"))) {
1391
+ if (rb_respond_to(data, rb_intern("to_s"))) {
1392
+ upload_stream = rb_obj_as_string(data);
1393
+ } else {
1394
+ rb_raise(rb_eRuntimeError, "PUT data must respond to read or to_s");
1395
+ }
1396
+ }
789
1397
 
790
1398
  headers = rb_easy_get("headers");
791
- if( headers == Qnil ) {
1399
+ if( headers == Qnil ) {
792
1400
  headers = rb_hash_new();
793
1401
  }
794
1402
 
795
- if (rb_respond_to(data, rb_intern("read"))) {
796
- VALUE stat = rb_funcall(data, rb_intern("stat"), 0);
797
- if( stat && rb_hash_aref(headers, rb_str_new2("Content-Length")) == Qnil) {
1403
+ if (!NIL_P(data) && rb_respond_to(data, rb_intern("read"))) {
1404
+ VALUE stat = Qnil;
1405
+ if (rb_respond_to(data, rb_intern("stat"))) {
1406
+ stat = rb_funcall(data, rb_intern("stat"), 0);
1407
+ }
1408
+ if(!NIL_P(stat) && stat != Qfalse && rb_hash_aref(headers, rb_str_new2("Content-Length")) == Qnil) {
798
1409
  VALUE size;
799
1410
  if( rb_hash_aref(headers, rb_str_new2("Expect")) == Qnil ) {
800
1411
  rb_hash_aset(headers, rb_str_new2("Expect"), rb_str_new2(""));
801
1412
  }
802
1413
  size = rb_funcall(stat, rb_intern("size"), 0);
803
- curl_easy_setopt(curl, CURLOPT_INFILESIZE, FIX2LONG(size));
1414
+ infile_size = size;
804
1415
  }
805
1416
  else if( rb_hash_aref(headers, rb_str_new2("Content-Length")) == Qnil && rb_hash_aref(headers, rb_str_new2("Transfer-Encoding")) == Qnil ) {
806
1417
  rb_hash_aset(headers, rb_str_new2("Transfer-Encoding"), rb_str_new2("chunked"));
807
1418
  }
808
1419
  else if( rb_hash_aref(headers, rb_str_new2("Content-Length")) ) {
809
1420
  VALUE size = rb_funcall(rb_hash_aref(headers, rb_str_new2("Content-Length")), rb_intern("to_i"), 0);
810
- curl_easy_setopt(curl, CURLOPT_INFILESIZE, FIX2LONG(size));
1421
+ infile_size = size;
811
1422
  }
812
1423
  }
813
- else if (rb_respond_to(data, rb_intern("to_s"))) {
814
- curl_easy_setopt(curl, CURLOPT_INFILESIZE, RSTRING_LEN(data));
1424
+ else if (!NIL_P(data) && rb_respond_to(data, rb_intern("to_s"))) {
1425
+ infile_size = LONG2NUM(RSTRING_LEN(upload_stream));
815
1426
  if( rb_hash_aref(headers, rb_str_new2("Expect")) == Qnil ) {
816
1427
  rb_hash_aset(headers, rb_str_new2("Expect"), rb_str_new2(""));
817
1428
  }
818
1429
  }
1430
+ else if (NIL_P(data)) {
1431
+ /* Preserve legacy nil handling: configure an upload with no payload. */
1432
+ }
819
1433
  else {
820
1434
  rb_raise(rb_eRuntimeError, "PUT data must respond to read or to_s");
821
1435
  }
822
1436
  rb_easy_set("headers",headers);
823
1437
 
1438
+ upload = ruby_curl_upload_new(cCurlUpload);
1439
+ ruby_curl_upload_stream_set(upload, upload_stream);
1440
+
1441
+ curl = rbce->curl;
1442
+ rb_easy_set("upload", upload); /* keep the upload object alive as long as
1443
+ the easy handle is active or until the upload
1444
+ is complete or terminated... */
1445
+
1446
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
1447
+ curl_easy_setopt(curl, CURLOPT_POST, 0);
1448
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
1449
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0);
1450
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
1451
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler);
1452
+ #ifdef HAVE_CURLOPT_SEEKFUNCTION
1453
+ curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, (curl_seek_callback)seek_data_handler);
1454
+ #endif
1455
+ curl_easy_setopt(curl, CURLOPT_READDATA, rbce);
1456
+ #ifdef HAVE_CURLOPT_SEEKDATA
1457
+ curl_easy_setopt(curl, CURLOPT_SEEKDATA, rbce);
1458
+ #endif
1459
+
1460
+ if (!NIL_P(infile_size)) {
1461
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, NUM2LONG(infile_size));
1462
+ }
1463
+
824
1464
  // if we made it this far, all should be well.
825
1465
  return data;
826
1466
  }
@@ -829,20 +1469,47 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
829
1469
  * call-seq:
830
1470
  * easy.ftp_commands = ["CWD /", "MKD directory"] => ["CWD /", ...]
831
1471
  *
832
- * Explicitly sets the list of commands to execute on the FTP server when calling perform
1472
+ * Explicitly sets the list of commands to execute on the FTP server when calling perform.
1473
+ *
1474
+ * NOTE:
1475
+ * - This maps to libcurl CURLOPT_QUOTE; it sends commands on the control connection.
1476
+ * - Do not include data-transfer commands like LIST/NLST/RETR/STOR here. libcurl does not
1477
+ * parse PASV/EPSV replies from QUOTE commands and will not establish the required data
1478
+ * connection. For directory listings, set CURLOPT_DIRLISTONLY (via `easy.set(:dirlistonly, true)`)
1479
+ * and request an FTP directory URL (e.g. "ftp://host/path/") so libcurl manages PASV/EPSV
1480
+ * and the data connection for you.
833
1481
  */
834
1482
  static VALUE ruby_curl_easy_ftp_commands_set(VALUE self, VALUE ftp_commands) {
835
1483
  CURB_OBJECT_HSETTER(ruby_curl_easy, ftp_commands);
836
1484
  }
837
1485
 
838
1486
  /*
839
- * call-seq
1487
+ * call-seq:
840
1488
  * easy.ftp_commands => array or nil
841
1489
  */
842
1490
  static VALUE ruby_curl_easy_ftp_commands_get(VALUE self) {
843
1491
  CURB_OBJECT_HGETTER(ruby_curl_easy, ftp_commands);
844
1492
  }
845
1493
 
1494
+ /*
1495
+ * call-seq:
1496
+ * easy.resolve = [ "example.com:80:127.0.0.1" ] => [ "example.com:80:127.0.0.1" ]
1497
+ *
1498
+ * Set the resolve list to statically resolve hostnames to IP addresses,
1499
+ * bypassing DNS for matching hostname/port combinations.
1500
+ */
1501
+ static VALUE ruby_curl_easy_resolve_set(VALUE self, VALUE resolve) {
1502
+ CURB_OBJECT_HSETTER(ruby_curl_easy, resolve);
1503
+ }
1504
+
1505
+ /*
1506
+ * call-seq:
1507
+ * easy.resolve => array or nil
1508
+ */
1509
+ static VALUE ruby_curl_easy_resolve_get(VALUE self) {
1510
+ CURB_OBJECT_HGETTER(ruby_curl_easy, resolve);
1511
+ }
1512
+
846
1513
  /* ================== IMMED ATTRS ==================*/
847
1514
 
848
1515
  /*
@@ -951,16 +1618,16 @@ static VALUE ruby_curl_easy_proxy_type_get(VALUE self) {
951
1618
  (!strncmp("digest",node,6)) ? CURLAUTH_DIGEST : \
952
1619
  (!strncmp("gssnegotiate",node,12)) ? CURLAUTH_GSSNEGOTIATE : \
953
1620
  (!strncmp("ntlm",node,4)) ? CURLAUTH_NTLM : \
954
- (!strncmp("any",node,3)) ? CURLAUTH_ANY : \
955
- (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : 0
956
- #else
1621
+ (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : \
1622
+ (!strncmp("any",node,3)) ? CURLAUTH_ANY : 0
1623
+ #else
957
1624
  #define CURL_HTTPAUTH_STR_TO_NUM(node) \
958
1625
  (!strncmp("basic",node,5)) ? CURLAUTH_BASIC : \
959
1626
  (!strncmp("digest",node,6)) ? CURLAUTH_DIGEST : \
960
1627
  (!strncmp("gssnegotiate",node,12)) ? CURLAUTH_GSSNEGOTIATE : \
961
1628
  (!strncmp("ntlm",node,4)) ? CURLAUTH_NTLM : \
962
- (!strncmp("any",node,3)) ? CURLAUTH_ANY : \
963
- (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : 0
1629
+ (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : \
1630
+ (!strncmp("any",node,3)) ? CURLAUTH_ANY : 0
964
1631
  #endif
965
1632
  /*
966
1633
  * call-seq:
@@ -974,21 +1641,22 @@ static VALUE ruby_curl_easy_proxy_type_get(VALUE self) {
974
1641
  static VALUE ruby_curl_easy_http_auth_types_set(int argc, VALUE *argv, VALUE self) {//VALUE self, VALUE http_auth_types) {
975
1642
  ruby_curl_easy *rbce;
976
1643
  VALUE args_ary;
977
- int i, len;
1644
+ long i, len;
978
1645
  char* node = NULL;
979
- long mask = 0x000000;
1646
+ long mask = 0;
980
1647
 
981
1648
  rb_scan_args(argc, argv, "*", &args_ary);
982
- Data_Get_Struct(self, ruby_curl_easy, rbce);
1649
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
983
1650
 
984
- len = (int)RARRAY_LEN(args_ary);
1651
+ len = RARRAY_LEN(args_ary);
985
1652
 
986
- if (len == 1 && (TYPE(rb_ary_entry(args_ary,0)) == T_FIXNUM || rb_ary_entry(args_ary,0) == Qnil)) {
987
- if (rb_ary_entry(args_ary,0) == Qnil) {
1653
+ if (len == 1 && (rb_ary_entry(args_ary,0) == Qnil || TYPE(rb_ary_entry(args_ary,0)) == T_FIXNUM ||
1654
+ TYPE(rb_ary_entry(args_ary,0)) == T_BIGNUM)) {
1655
+ if (rb_ary_entry(args_ary,0) == Qnil) {
988
1656
  rbce->http_auth_types = 0;
989
1657
  }
990
1658
  else {
991
- rbce->http_auth_types = NUM2INT(rb_ary_entry(args_ary,0));
1659
+ rbce->http_auth_types = NUM2LONG(rb_ary_entry(args_ary,0));
992
1660
  }
993
1661
  }
994
1662
  else {
@@ -1001,7 +1669,7 @@ static VALUE ruby_curl_easy_http_auth_types_set(int argc, VALUE *argv, VALUE sel
1001
1669
  }
1002
1670
  rbce->http_auth_types = mask;
1003
1671
  }
1004
- return INT2NUM(rbce->http_auth_types);
1672
+ return LONG2NUM(rbce->http_auth_types);
1005
1673
  }
1006
1674
 
1007
1675
  /*
@@ -1066,7 +1734,7 @@ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) {
1066
1734
 
1067
1735
  /*
1068
1736
  * call-seq:
1069
- * easy.timeout = fixnum or nil => fixnum or nil
1737
+ * easy.timeout = float, fixnum or nil => numeric
1070
1738
  *
1071
1739
  * Set the maximum time in seconds that you allow the libcurl transfer
1072
1740
  * operation to take. Normally, name lookups can take a considerable time
@@ -1075,20 +1743,77 @@ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) {
1075
1743
  *
1076
1744
  * Set to nil (or zero) to disable timeout (it will then only timeout
1077
1745
  * on the system's internal timeouts).
1746
+ *
1747
+ * Uses timeout_ms internally instead of timeout because it allows for
1748
+ * better precision and libcurl will use the last set value when both
1749
+ * timeout and timeout_ms are set.
1750
+ *
1078
1751
  */
1079
- static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout) {
1080
- CURB_IMMED_SETTER(ruby_curl_easy, timeout, 0);
1752
+ static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout_s) {
1753
+ ruby_curl_easy *rbce;
1754
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1755
+
1756
+ if (Qnil == timeout_s || NUM2DBL(timeout_s) <= 0.0) {
1757
+ rbce->timeout_ms = 0;
1758
+ } else {
1759
+ rbce->timeout_ms = (unsigned long)(NUM2DBL(timeout_s) * 1000);
1760
+ }
1761
+
1762
+ return DBL2NUM(rbce->timeout_ms / 1000.0);
1081
1763
  }
1082
1764
 
1083
1765
  /*
1084
1766
  * call-seq:
1085
- * easy.timeout => fixnum or nil
1767
+ * easy.timeout => numeric
1086
1768
  *
1087
1769
  * Obtain the maximum time in seconds that you allow the libcurl transfer
1088
1770
  * operation to take.
1771
+ *
1772
+ * Uses timeout_ms internally instead of timeout.
1773
+ *
1774
+ */
1775
+ static VALUE ruby_curl_easy_timeout_get(VALUE self) {
1776
+ ruby_curl_easy *rbce;
1777
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1778
+ return DBL2NUM(rbce->timeout_ms / 1000.0);
1779
+ }
1780
+
1781
+ /*
1782
+ * call-seq:
1783
+ * easy.timeout_ms = fixnum or nil => fixnum or nil
1784
+ *
1785
+ * Set the maximum time in milliseconds that you allow the libcurl transfer
1786
+ * operation to take. Normally, name lookups can take a considerable time
1787
+ * and limiting operations to less than a few minutes risk aborting
1788
+ * perfectly normal operations.
1789
+ *
1790
+ * Set to nil (or zero) to disable timeout (it will then only timeout
1791
+ * on the system's internal timeouts).
1792
+ */
1793
+ static VALUE ruby_curl_easy_timeout_ms_set(VALUE self, VALUE timeout_ms) {
1794
+ ruby_curl_easy *rbce;
1795
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1796
+
1797
+ if (Qnil == timeout_ms || NUM2DBL(timeout_ms) <= 0.0) {
1798
+ rbce->timeout_ms = 0;
1799
+ } else {
1800
+ rbce->timeout_ms = NUM2ULONG(timeout_ms);
1801
+ }
1802
+
1803
+ return ULONG2NUM(rbce->timeout_ms);
1804
+ }
1805
+
1806
+ /*
1807
+ * call-seq:
1808
+ * easy.timeout_ms => fixnum or nil
1809
+ *
1810
+ * Obtain the maximum time in milliseconds that you allow the libcurl transfer
1811
+ * operation to take.
1089
1812
  */
1090
- static VALUE ruby_curl_easy_timeout_get(VALUE self, VALUE timeout) {
1091
- CURB_IMMED_GETTER(ruby_curl_easy, timeout, 0);
1813
+ static VALUE ruby_curl_easy_timeout_ms_get(VALUE self) {
1814
+ ruby_curl_easy *rbce;
1815
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1816
+ return LONG2NUM(rbce->timeout_ms);
1092
1817
  }
1093
1818
 
1094
1819
  /*
@@ -1113,10 +1838,36 @@ static VALUE ruby_curl_easy_connect_timeout_set(VALUE self, VALUE connect_timeou
1113
1838
  * Obtain the maximum time in seconds that you allow the connection to the
1114
1839
  * server to take.
1115
1840
  */
1116
- static VALUE ruby_curl_easy_connect_timeout_get(VALUE self, VALUE connect_timeout) {
1841
+ static VALUE ruby_curl_easy_connect_timeout_get(VALUE self) {
1117
1842
  CURB_IMMED_GETTER(ruby_curl_easy, connect_timeout, 0);
1118
1843
  }
1119
1844
 
1845
+ /*
1846
+ * call-seq:
1847
+ * easy.connect_timeout_ms = fixnum or nil => fixnum or nil
1848
+ *
1849
+ * Set the maximum time in milliseconds that you allow the connection to the
1850
+ * server to take. This only limits the connection phase, once it has
1851
+ * connected, this option is of no more use.
1852
+ *
1853
+ * Set to nil (or zero) to disable connection timeout (it will then only
1854
+ * timeout on the system's internal timeouts).
1855
+ */
1856
+ static VALUE ruby_curl_easy_connect_timeout_ms_set(VALUE self, VALUE connect_timeout_ms) {
1857
+ CURB_IMMED_SETTER(ruby_curl_easy, connect_timeout_ms, 0);
1858
+ }
1859
+
1860
+ /*
1861
+ * call-seq:
1862
+ * easy.connect_timeout_ms => fixnum or nil
1863
+ *
1864
+ * Obtain the maximum time in milliseconds that you allow the connection to the
1865
+ * server to take.
1866
+ */
1867
+ static VALUE ruby_curl_easy_connect_timeout_ms_get(VALUE self) {
1868
+ CURB_IMMED_GETTER(ruby_curl_easy, connect_timeout_ms, 0);
1869
+ }
1870
+
1120
1871
  /*
1121
1872
  * call-seq:
1122
1873
  * easy.dns_cache_timeout = fixnum or nil => fixnum or nil
@@ -1136,7 +1887,7 @@ static VALUE ruby_curl_easy_dns_cache_timeout_set(VALUE self, VALUE dns_cache_ti
1136
1887
  *
1137
1888
  * Obtain the dns cache timeout in seconds.
1138
1889
  */
1139
- static VALUE ruby_curl_easy_dns_cache_timeout_get(VALUE self, VALUE dns_cache_timeout) {
1890
+ static VALUE ruby_curl_easy_dns_cache_timeout_get(VALUE self) {
1140
1891
  CURB_IMMED_GETTER(ruby_curl_easy, dns_cache_timeout, -1);
1141
1892
  }
1142
1893
 
@@ -1163,7 +1914,7 @@ static VALUE ruby_curl_easy_ftp_response_timeout_set(VALUE self, VALUE ftp_respo
1163
1914
  *
1164
1915
  * Obtain the maximum time that libcurl will wait for FTP command responses.
1165
1916
  */
1166
- static VALUE ruby_curl_easy_ftp_response_timeout_get(VALUE self, VALUE ftp_response_timeout) {
1917
+ static VALUE ruby_curl_easy_ftp_response_timeout_get(VALUE self) {
1167
1918
  CURB_IMMED_GETTER(ruby_curl_easy, ftp_response_timeout, 0);
1168
1919
  }
1169
1920
 
@@ -1186,7 +1937,7 @@ static VALUE ruby_curl_easy_low_speed_limit_set(VALUE self, VALUE low_speed_limi
1186
1937
  * Obtain the minimum transfer speed over +low_speed+time+ below which the
1187
1938
  * transfer will be aborted.
1188
1939
  */
1189
- static VALUE ruby_curl_easy_low_speed_limit_get(VALUE self, VALUE low_speed_limit) {
1940
+ static VALUE ruby_curl_easy_low_speed_limit_get(VALUE self) {
1190
1941
  CURB_IMMED_GETTER(ruby_curl_easy, low_speed_limit, 0);
1191
1942
  }
1192
1943
 
@@ -1208,10 +1959,50 @@ static VALUE ruby_curl_easy_low_speed_time_set(VALUE self, VALUE low_speed_time)
1208
1959
  * Obtain the time that the transfer should be below +low_speed_limit+ for
1209
1960
  * the library to abort it.
1210
1961
  */
1211
- static VALUE ruby_curl_easy_low_speed_time_get(VALUE self, VALUE low_speed_time) {
1962
+ static VALUE ruby_curl_easy_low_speed_time_get(VALUE self) {
1212
1963
  CURB_IMMED_GETTER(ruby_curl_easy, low_speed_time, 0);
1213
1964
  }
1214
1965
 
1966
+ /*
1967
+ * call-seq:
1968
+ * easy.max_send_speed_large = fixnum or nil => fixnum or nil
1969
+ *
1970
+ * Set the maximal sending transfer speed (in bytes per second)
1971
+ */
1972
+ static VALUE ruby_curl_easy_max_send_speed_large_set(VALUE self, VALUE max_send_speed_large) {
1973
+ CURB_IMMED_SETTER(ruby_curl_easy, max_send_speed_large, 0);
1974
+ }
1975
+
1976
+ /*
1977
+ * call-seq:
1978
+ * easy.max_send_speed_large = fixnum or nil => fixnum or nil
1979
+ *
1980
+ * Get the maximal sending transfer speed (in bytes per second)
1981
+ */
1982
+ static VALUE ruby_curl_easy_max_send_speed_large_get(VALUE self) {
1983
+ CURB_IMMED_GETTER(ruby_curl_easy, max_send_speed_large, 0);
1984
+ }
1985
+
1986
+ /*
1987
+ * call-seq:
1988
+ * easy.max_recv_speed_large = fixnum or nil => fixnum or nil
1989
+ *
1990
+ * Set the maximal receiving transfer speed (in bytes per second)
1991
+ */
1992
+ static VALUE ruby_curl_easy_max_recv_speed_large_set(VALUE self, VALUE max_recv_speed_large) {
1993
+ CURB_IMMED_SETTER(ruby_curl_easy, max_recv_speed_large, 0);
1994
+ }
1995
+
1996
+ /*
1997
+ * call-seq:
1998
+ * easy.max_recv_speed_large = fixnum or nil => fixnum or nil
1999
+ *
2000
+ * Get the maximal receiving transfer speed (in bytes per second)
2001
+ */
2002
+ static VALUE ruby_curl_easy_max_recv_speed_large_get(VALUE self) {
2003
+ CURB_IMMED_GETTER(ruby_curl_easy, max_recv_speed_large, 0);
2004
+ }
2005
+
1215
2006
  /*
1216
2007
  * call-seq:
1217
2008
  * easy.username = string => string
@@ -1219,7 +2010,7 @@ static VALUE ruby_curl_easy_low_speed_time_get(VALUE self, VALUE low_speed_time)
1219
2010
  * Set the HTTP Authentication username.
1220
2011
  */
1221
2012
  static VALUE ruby_curl_easy_username_set(VALUE self, VALUE username) {
1222
- #if HAVE_CURLOPT_USERNAME
2013
+ #ifdef HAVE_CURLOPT_USERNAME
1223
2014
  CURB_OBJECT_HSETTER(ruby_curl_easy, username);
1224
2015
  #else
1225
2016
  return Qnil;
@@ -1229,11 +2020,11 @@ static VALUE ruby_curl_easy_username_set(VALUE self, VALUE username) {
1229
2020
  /*
1230
2021
  * call-seq:
1231
2022
  * easy.username => string
1232
- *
2023
+ *
1233
2024
  * Get the current username
1234
2025
  */
1235
- static VALUE ruby_curl_easy_username_get(VALUE self, VALUE username) {
1236
- #if HAVE_CURLOPT_USERNAME
2026
+ static VALUE ruby_curl_easy_username_get(VALUE self) {
2027
+ #ifdef HAVE_CURLOPT_USERNAME
1237
2028
  CURB_OBJECT_HGETTER(ruby_curl_easy, username);
1238
2029
  #else
1239
2030
  return Qnil;
@@ -1247,7 +2038,7 @@ static VALUE ruby_curl_easy_username_get(VALUE self, VALUE username) {
1247
2038
  * Set the HTTP Authentication password.
1248
2039
  */
1249
2040
  static VALUE ruby_curl_easy_password_set(VALUE self, VALUE password) {
1250
- #if HAVE_CURLOPT_PASSWORD
2041
+ #ifdef HAVE_CURLOPT_PASSWORD
1251
2042
  CURB_OBJECT_HSETTER(ruby_curl_easy, password);
1252
2043
  #else
1253
2044
  return Qnil;
@@ -1257,11 +2048,11 @@ static VALUE ruby_curl_easy_password_set(VALUE self, VALUE password) {
1257
2048
  /*
1258
2049
  * call-seq:
1259
2050
  * easy.password => string
1260
- *
2051
+ *
1261
2052
  * Get the current password
1262
2053
  */
1263
- static VALUE ruby_curl_easy_password_get(VALUE self, VALUE password) {
1264
- #if HAVE_CURLOPT_PASSWORD
2054
+ static VALUE ruby_curl_easy_password_get(VALUE self) {
2055
+ #ifdef HAVE_CURLOPT_PASSWORD
1265
2056
  CURB_OBJECT_HGETTER(ruby_curl_easy, password);
1266
2057
  #else
1267
2058
  return Qnil;
@@ -1273,8 +2064,16 @@ static VALUE ruby_curl_easy_password_get(VALUE self, VALUE password) {
1273
2064
  * easy.ssl_version = value => fixnum or nil
1274
2065
  *
1275
2066
  * Sets the version of SSL/TLS that libcurl will attempt to use. Valid
1276
- * options are Curl::CURL_SSLVERSION_TLSv1, Curl::CURL_SSLVERSION::SSLv2,
1277
- * Curl::CURL_SSLVERSION_SSLv3 and Curl::CURL_SSLVERSION_DEFAULT
2067
+ * options are:
2068
+ *
2069
+ * Curl::CURL_SSLVERSION_DEFAULT
2070
+ * Curl::CURL_SSLVERSION_TLSv1 (TLS 1.x)
2071
+ * Curl::CURL_SSLVERSION_SSLv2
2072
+ * Curl::CURL_SSLVERSION_SSLv3
2073
+ * Curl::CURL_SSLVERSION_TLSv1_0
2074
+ * Curl::CURL_SSLVERSION_TLSv1_1
2075
+ * Curl::CURL_SSLVERSION_TLSv1_2
2076
+ * Curl::CURL_SSLVERSION_TLSv1_3
1278
2077
  */
1279
2078
  static VALUE ruby_curl_easy_ssl_version_set(VALUE self, VALUE ssl_version) {
1280
2079
  CURB_IMMED_SETTER(ruby_curl_easy, ssl_version, -1);
@@ -1286,14 +2085,14 @@ static VALUE ruby_curl_easy_ssl_version_set(VALUE self, VALUE ssl_version) {
1286
2085
  *
1287
2086
  * Get the version of SSL/TLS that libcurl will attempt to use.
1288
2087
  */
1289
- static VALUE ruby_curl_easy_ssl_version_get(VALUE self, VALUE ssl_version) {
2088
+ static VALUE ruby_curl_easy_ssl_version_get(VALUE self) {
1290
2089
  CURB_IMMED_GETTER(ruby_curl_easy, ssl_version, -1);
1291
2090
  }
1292
2091
 
1293
2092
  /*
1294
2093
  * call-seq:
1295
2094
  * easy.use_ssl = value => fixnum or nil
1296
- *
2095
+ *
1297
2096
  * Ensure libcurl uses SSL for FTP connections. Valid options are Curl::CURL_USESSL_NONE,
1298
2097
  * Curl::CURL_USESSL_TRY, Curl::CURL_USESSL_CONTROL, and Curl::CURL_USESSL_ALL.
1299
2098
  */
@@ -1307,7 +2106,7 @@ static VALUE ruby_curl_easy_use_ssl_set(VALUE self, VALUE use_ssl) {
1307
2106
  *
1308
2107
  * Get the desired level for using SSL on FTP connections.
1309
2108
  */
1310
- static VALUE ruby_curl_easy_use_ssl_get(VALUE self, VALUE use_ssl) {
2109
+ static VALUE ruby_curl_easy_use_ssl_get(VALUE self) {
1311
2110
  CURB_IMMED_GETTER(ruby_curl_easy, use_ssl, -1);
1312
2111
  }
1313
2112
 
@@ -1323,12 +2122,12 @@ static VALUE ruby_curl_easy_ftp_filemethod_set(VALUE self, VALUE ftp_filemethod)
1323
2122
  }
1324
2123
 
1325
2124
  /*
1326
- * call-seq
2125
+ * call-seq:
1327
2126
  * easy.ftp_filemethod => fixnum
1328
2127
  *
1329
2128
  * Get the configuration for how libcurl will reach files on the server.
1330
2129
  */
1331
- static VALUE ruby_curl_easy_ftp_filemethod_get(VALUE self, VALUE ftp_filemethod) {
2130
+ static VALUE ruby_curl_easy_ftp_filemethod_get(VALUE self) {
1332
2131
  CURB_IMMED_GETTER(ruby_curl_easy, ftp_filemethod, -1);
1333
2132
  }
1334
2133
 
@@ -1486,7 +2285,7 @@ static VALUE ruby_curl_easy_use_netrc_q(VALUE self) {
1486
2285
  */
1487
2286
  static VALUE ruby_curl_easy_autoreferer_set(VALUE self, VALUE autoreferer) {
1488
2287
  ruby_curl_easy *rbce;
1489
- Data_Get_Struct(self, ruby_curl_easy, rbce);
2288
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1490
2289
 
1491
2290
  if (Qtrue == autoreferer) {
1492
2291
  curl_easy_setopt(rbce->curl, CURLOPT_AUTOREFERER, 1);
@@ -1587,7 +2386,12 @@ static VALUE ruby_curl_easy_multipart_form_post_q(VALUE self) {
1587
2386
  * easy.enable_cookies = boolean => boolean
1588
2387
  *
1589
2388
  * Configure whether the libcurl cookie engine is enabled for this Curl::Easy
1590
- * instance.
2389
+ * instance. When enabled, cookies received via Set-Cookie are stored by libcurl
2390
+ * and automatically sent on subsequent matching requests. Use +easy.cookiefile+
2391
+ * to load cookies and +easy.cookiejar+ to persist them.
2392
+ *
2393
+ * This setting is independent from the manual Cookie header set via +easy.cookies+.
2394
+ * The manual header is additive and can be cleared by assigning an empty string.
1591
2395
  */
1592
2396
  static VALUE ruby_curl_easy_enable_cookies_set(VALUE self, VALUE enable_cookies)
1593
2397
  {
@@ -1637,9 +2441,10 @@ static VALUE ruby_curl_easy_ignore_content_length_q(VALUE self) {
1637
2441
  */
1638
2442
  static VALUE ruby_curl_easy_resolve_mode(VALUE self) {
1639
2443
  ruby_curl_easy *rbce;
1640
- Data_Get_Struct(self, ruby_curl_easy, rbce);
2444
+ unsigned short rm;
2445
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1641
2446
 
1642
- unsigned short rm = rbce->resolve_mode;
2447
+ rm = rbce->resolve_mode;
1643
2448
 
1644
2449
  switch(rm) {
1645
2450
  case CURL_IPRESOLVE_V4:
@@ -1668,9 +2473,10 @@ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
1668
2473
  return Qnil;
1669
2474
  } else {
1670
2475
  ruby_curl_easy *rbce;
1671
- Data_Get_Struct(self, ruby_curl_easy, rbce);
2476
+ ID resolve_mode_id;
2477
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
1672
2478
 
1673
- ID resolve_mode_id = rb_to_id(resolve_mode);
2479
+ resolve_mode_id = rb_to_id(resolve_mode);
1674
2480
 
1675
2481
  if (resolve_mode_id == rb_intern("auto")) {
1676
2482
  rbce->resolve_mode = CURL_IPRESOLVE_WHATEVER;
@@ -1688,12 +2494,52 @@ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
1688
2494
  }
1689
2495
  }
1690
2496
 
2497
+ /*
2498
+ * call-seq:
2499
+ * easy.http_version = Curl::HTTP_1_1 => Curl::HTTP_1_1
2500
+ *
2501
+ * Force libcurl to use a specific HTTP protocol version. By default libcurl
2502
+ * negotiates the highest version supported by both peers. Supported constants
2503
+ * include Curl::HTTP_NONE, Curl::HTTP_1_0, Curl::HTTP_1_1, Curl::HTTP_2_0,
2504
+ * Curl::HTTP_2TLS, and Curl::HTTP_2_PRIOR_KNOWLEDGE (when provided by libcurl).
2505
+ */
2506
+ static VALUE ruby_curl_easy_http_version_set(VALUE self, VALUE version) {
2507
+ ruby_curl_easy *rbce;
2508
+ long http_version;
2509
+
2510
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2511
+
2512
+ if (NIL_P(version)) {
2513
+ http_version = CURL_HTTP_VERSION_NONE;
2514
+ } else {
2515
+ http_version = NUM2LONG(version);
2516
+ }
2517
+
2518
+ rbce->http_version = http_version;
2519
+
2520
+ return version;
2521
+ }
2522
+
2523
+ /*
2524
+ * call-seq:
2525
+ * easy.http_version => integer
2526
+ *
2527
+ * Returns the HTTP protocol version currently configured.
2528
+ */
2529
+ static VALUE ruby_curl_easy_http_version_get(VALUE self) {
2530
+ ruby_curl_easy *rbce;
2531
+
2532
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2533
+
2534
+ return LONG2NUM(rbce->http_version);
2535
+ }
2536
+
1691
2537
 
1692
2538
  /* ================= EVENT PROCS ================== */
1693
2539
 
1694
2540
  /*
1695
2541
  * call-seq:
1696
- * easy.on_body { |body_data| ... } => &lt;old handler&gt;
2542
+ * easy.on_body { |body_data| ... } => <old handler>
1697
2543
  *
1698
2544
  * Assign or remove the +on_body+ handler for this Curl::Easy instance.
1699
2545
  * To remove a previously-supplied handler, call this method with no
@@ -1712,7 +2558,7 @@ static VALUE ruby_curl_easy_on_body_set(int argc, VALUE *argv, VALUE self) {
1712
2558
 
1713
2559
  /*
1714
2560
  * call-seq:
1715
- * easy.on_success { |easy| ... } => &lt;old handler&gt;
2561
+ * easy.on_success { |easy| ... } => <old handler>
1716
2562
  *
1717
2563
  * Assign or remove the +on_success+ handler for this Curl::Easy instance.
1718
2564
  * To remove a previously-supplied handler, call this method with no
@@ -1727,7 +2573,7 @@ static VALUE ruby_curl_easy_on_success_set(int argc, VALUE *argv, VALUE self) {
1727
2573
 
1728
2574
  /*
1729
2575
  * call-seq:
1730
- * easy.on_failure {|easy,code| ... } => &lt;old handler&gt;
2576
+ * easy.on_failure {|easy,code| ... } => <old handler>
1731
2577
  *
1732
2578
  * Assign or remove the +on_failure+ handler for this Curl::Easy instance.
1733
2579
  * To remove a previously-supplied handler, call this method with no
@@ -1742,13 +2588,13 @@ static VALUE ruby_curl_easy_on_failure_set(int argc, VALUE *argv, VALUE self) {
1742
2588
 
1743
2589
  /*
1744
2590
  * call-seq:
1745
- * easy.on_missing {|easy,code| ... } => &lt;old handler;&gt;
2591
+ * easy.on_missing {|easy,code| ... } => <old handler;>
1746
2592
  *
1747
2593
  * Assign or remove the on_missing handler for this Curl::Easy instance.
1748
2594
  * To remove a previously-supplied handler, call this method with no attached
1749
2595
  * block.
1750
2596
  *
1751
- * The +on_missing+ handler is called when request is finished with a
2597
+ * The +on_missing+ handler is called when request is finished with a
1752
2598
  * status of 40x
1753
2599
  */
1754
2600
  static VALUE ruby_curl_easy_on_missing_set(int argc, VALUE *argv, VALUE self) {
@@ -1757,13 +2603,13 @@ static VALUE ruby_curl_easy_on_missing_set(int argc, VALUE *argv, VALUE self) {
1757
2603
 
1758
2604
  /*
1759
2605
  * call-seq:
1760
- * easy.on_redirect {|easy,code| ... } => &lt;old handler;&gt;
2606
+ * easy.on_redirect {|easy,code| ... } => <old handler;>
1761
2607
  *
1762
2608
  * Assign or remove the on_redirect handler for this Curl::Easy instance.
1763
2609
  * To remove a previously-supplied handler, call this method with no attached
1764
2610
  * block.
1765
2611
  *
1766
- * The +on_redirect+ handler is called when request is finished with a
2612
+ * The +on_redirect+ handler is called when request is finished with a
1767
2613
  * status of 30x
1768
2614
  */
1769
2615
  static VALUE ruby_curl_easy_on_redirect_set(int argc, VALUE *argv, VALUE self) {
@@ -1772,7 +2618,7 @@ static VALUE ruby_curl_easy_on_redirect_set(int argc, VALUE *argv, VALUE self) {
1772
2618
 
1773
2619
  /*
1774
2620
  * call-seq:
1775
- * easy.on_complete {|easy| ... } => &lt;old handler&gt;
2621
+ * easy.on_complete {|easy| ... } => <old handler>
1776
2622
  *
1777
2623
  * Assign or remove the +on_complete+ handler for this Curl::Easy instance.
1778
2624
  * To remove a previously-supplied handler, call this method with no
@@ -1786,7 +2632,7 @@ static VALUE ruby_curl_easy_on_complete_set(int argc, VALUE *argv, VALUE self) {
1786
2632
 
1787
2633
  /*
1788
2634
  * call-seq:
1789
- * easy.on_header { |header_data| ... } => &lt;old handler&gt;
2635
+ * easy.on_header { |header_data| ... } => <old handler>
1790
2636
  *
1791
2637
  * Assign or remove the +on_header+ handler for this Curl::Easy instance.
1792
2638
  * To remove a previously-supplied handler, call this method with no
@@ -1802,7 +2648,7 @@ static VALUE ruby_curl_easy_on_header_set(int argc, VALUE *argv, VALUE self) {
1802
2648
 
1803
2649
  /*
1804
2650
  * call-seq:
1805
- * easy.on_progress { |dl_total, dl_now, ul_total, ul_now| ... } => &lt;old handler&gt;
2651
+ * easy.on_progress { |dl_total, dl_now, ul_total, ul_now| ... } => <old handler>
1806
2652
  *
1807
2653
  * Assign or remove the +on_progress+ handler for this Curl::Easy instance.
1808
2654
  * To remove a previously-supplied handler, call this method with no
@@ -1823,7 +2669,7 @@ static VALUE ruby_curl_easy_on_progress_set(int argc, VALUE *argv, VALUE self) {
1823
2669
 
1824
2670
  /*
1825
2671
  * call-seq:
1826
- * easy.on_debug { |type, data| ... } => &lt;old handler&gt;
2672
+ * easy.on_debug { |type, data| ... } => <old handler>
1827
2673
  *
1828
2674
  * Assign or remove the +on_debug+ handler for this Curl::Easy instance.
1829
2675
  * To remove a previously-supplied handler, call this method with no
@@ -1848,12 +2694,12 @@ static VALUE ruby_curl_easy_on_debug_set(int argc, VALUE *argv, VALUE self) {
1848
2694
  /***********************************************
1849
2695
  * This is an rb_iterate callback used to set up http headers.
1850
2696
  */
1851
- static VALUE cb_each_http_header(VALUE header, VALUE wrap) {
2697
+ static VALUE cb_each_http_header(VALUE header, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
1852
2698
  struct curl_slist **list;
1853
- Data_Get_Struct(wrap, struct curl_slist *, list);
1854
-
1855
2699
  VALUE header_str = Qnil;
1856
2700
 
2701
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
2702
+
1857
2703
  //rb_p(header);
1858
2704
 
1859
2705
  if (rb_type(header) == T_ARRAY) {
@@ -1862,34 +2708,100 @@ static VALUE cb_each_http_header(VALUE header, VALUE wrap) {
1862
2708
 
1863
2709
  name = rb_obj_as_string(rb_ary_entry(header, 0));
1864
2710
  value = rb_obj_as_string(rb_ary_entry(header, 1));
2711
+ if (rb_str_strlen(value) == 0) { // removing the header e.g. Accept: with nothing trailing should remove it see: https://curl.se/libcurl/c/CURLOPT_HTTPHEADER.html
2712
+ header_str = rb_str_plus(name, rb_str_new2(":"));
2713
+ } else {
2714
+ // This is a bit inefficient, but we don't want to be modifying
2715
+ // the actual values in the original hash.
2716
+ header_str = rb_str_plus(name, rb_str_new2(": "));
2717
+ header_str = rb_str_plus(header_str, value);
2718
+ }
2719
+ } else {
2720
+ header_str = rb_obj_as_string(header);
2721
+ }
2722
+
2723
+ //rb_p(header_str);
2724
+
2725
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(header_str));
2726
+ if (!new_list) {
2727
+ rb_raise(rb_eNoMemError, "Failed to append to header list");
2728
+ }
2729
+ *list = new_list;
2730
+ return header_str;
2731
+ }
2732
+
2733
+ /***********************************************
2734
+ * This is an rb_iterate callback used to set up http proxy headers.
2735
+ */
2736
+ static VALUE cb_each_http_proxy_header(VALUE proxy_header, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
2737
+ struct curl_slist **list;
2738
+ VALUE proxy_header_str = Qnil;
2739
+
2740
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
2741
+
2742
+ //rb_p(proxy_header);
2743
+
2744
+ if (rb_type(proxy_header) == T_ARRAY) {
2745
+ // we're processing a hash, proxy header is [name, val]
2746
+ VALUE name, value;
2747
+
2748
+ name = rb_obj_as_string(rb_ary_entry(proxy_header, 0));
2749
+ value = rb_obj_as_string(rb_ary_entry(proxy_header, 1));
1865
2750
 
1866
2751
  // This is a bit inefficient, but we don't want to be modifying
1867
2752
  // the actual values in the original hash.
1868
- header_str = rb_str_plus(name, rb_str_new2(": "));
1869
- header_str = rb_str_plus(header_str, value);
2753
+ proxy_header_str = rb_str_plus(name, rb_str_new2(": "));
2754
+ proxy_header_str = rb_str_plus(proxy_header_str, value);
1870
2755
  } else {
1871
- header_str = rb_obj_as_string(header);
2756
+ proxy_header_str = rb_obj_as_string(proxy_header);
1872
2757
  }
1873
2758
 
1874
2759
  //rb_p(header_str);
1875
2760
 
1876
- *list = curl_slist_append(*list, StringValuePtr(header_str));
1877
- return header_str;
2761
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(proxy_header_str));
2762
+ if (!new_list) {
2763
+ rb_raise(rb_eNoMemError, "Failed to append to proxy header list");
2764
+ }
2765
+ *list = new_list;
2766
+ return proxy_header_str;
1878
2767
  }
1879
2768
 
1880
2769
  /***********************************************
1881
2770
  * This is an rb_iterate callback used to set up ftp commands.
1882
2771
  */
1883
- static VALUE cb_each_ftp_command(VALUE ftp_command, VALUE wrap) {
2772
+ static VALUE cb_each_ftp_command(VALUE ftp_command, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
1884
2773
  struct curl_slist **list;
1885
- Data_Get_Struct(wrap, struct curl_slist *, list);
2774
+ VALUE ftp_command_string;
2775
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
1886
2776
 
1887
- VALUE ftp_command_string = rb_obj_as_string(ftp_command);
1888
- *list = curl_slist_append(*list, StringValuePtr(ftp_command));
2777
+ ftp_command_string = rb_obj_as_string(ftp_command);
2778
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(ftp_command_string));
2779
+ if (!new_list) {
2780
+ rb_raise(rb_eNoMemError, "Failed to append to FTP command list");
2781
+ }
2782
+ *list = new_list;
1889
2783
 
1890
2784
  return ftp_command_string;
1891
2785
  }
1892
2786
 
2787
+ /***********************************************
2788
+ * This is an rb_iterate callback used to set up the resolve list.
2789
+ */
2790
+ static VALUE cb_each_resolve(VALUE resolve, VALUE wrap, int _c, const VALUE *_ptr, VALUE unused) {
2791
+ struct curl_slist **list;
2792
+ VALUE resolve_string;
2793
+ TypedData_Get_Struct(wrap, struct curl_slist *, &curl_slist_ptr_type, list);
2794
+
2795
+ resolve_string = rb_obj_as_string(resolve);
2796
+ struct curl_slist *new_list = curl_slist_append(*list, StringValuePtr(resolve_string));
2797
+ if (!new_list) {
2798
+ rb_raise(rb_eNoMemError, "Failed to append to resolve list");
2799
+ }
2800
+ *list = new_list;
2801
+
2802
+ return resolve_string;
2803
+ }
2804
+
1893
2805
  /***********************************************
1894
2806
  *
1895
2807
  * Setup a connection
@@ -1901,16 +2813,18 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1901
2813
  CURL *curl;
1902
2814
  VALUE url, _url = rb_easy_get("url");
1903
2815
  struct curl_slist **hdrs = &(rbce->curl_headers);
2816
+ struct curl_slist **phdrs = &(rbce->curl_proxy_headers);
1904
2817
  struct curl_slist **cmds = &(rbce->curl_ftp_commands);
2818
+ struct curl_slist **rslv = &(rbce->curl_resolve);
1905
2819
 
1906
2820
  curl = rbce->curl;
2821
+ rbce->callback_error = Qnil;
1907
2822
 
1908
2823
  if (_url == Qnil) {
1909
2824
  rb_raise(eCurlErrError, "No URL supplied");
1910
2825
  }
1911
2826
 
1912
2827
  url = rb_check_string_type(_url);
1913
-
1914
2828
  curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
1915
2829
 
1916
2830
  // network stuff and auth
@@ -1920,7 +2834,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1920
2834
  curl_easy_setopt(curl, CURLOPT_INTERFACE, NULL);
1921
2835
  }
1922
2836
 
1923
- #if HAVE_CURLOPT_USERNAME == 1 && HAVE_CURLOPT_PASSWORD == 1
2837
+ #if defined(HAVE_CURLOPT_USERNAME) && defined(HAVE_CURLOPT_PASSWORD)
1924
2838
  if (!rb_easy_nil("username")) {
1925
2839
  curl_easy_setopt(curl, CURLOPT_USERNAME, rb_easy_get_str("username"));
1926
2840
  } else {
@@ -1936,7 +2850,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1936
2850
 
1937
2851
  if (!rb_easy_nil("userpwd")) {
1938
2852
  curl_easy_setopt(curl, CURLOPT_USERPWD, rb_easy_get_str("userpwd"));
1939
- #if HAVE_CURLOPT_USERNAME == 1
2853
+ #if defined(HAVE_CURLOPT_USERNAME) && defined(HAVE_CURLOPT_PASSWORD)
1940
2854
  } else if (rb_easy_nil("username") && rb_easy_nil("password")) { /* don't set this even to NULL if we have set username and password */
1941
2855
  #else
1942
2856
  } else {
@@ -1956,6 +2870,14 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1956
2870
  curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, rb_easy_get_str("proxypwd"));
1957
2871
  }
1958
2872
 
2873
+ #ifdef HAVE_CURLOPT_NOPROXY
2874
+ if (rb_easy_nil("noproxy")) {
2875
+ curl_easy_setopt(curl, CURLOPT_NOPROXY, NULL);
2876
+ } else {
2877
+ curl_easy_setopt(curl, CURLOPT_NOPROXY, rb_easy_get_str("noproxy"));
2878
+ }
2879
+ #endif
2880
+
1959
2881
  // body/header procs
1960
2882
  if (!rb_easy_nil("body_proc")) {
1961
2883
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&proc_data_handler_body);
@@ -1963,9 +2885,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1963
2885
  /* clear out the body_data if it was set */
1964
2886
  rb_easy_del("body_data");
1965
2887
  } else {
1966
- VALUE body_buffer = rb_easy_set("body_data", rb_str_buf_new(32768));
1967
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&default_data_handler);
1968
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
2888
+ rb_easy_set("body_data", rb_str_buf_new(32768));
2889
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&default_body_handler);
2890
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, rbce);
1969
2891
  }
1970
2892
 
1971
2893
  if (!rb_easy_nil("header_proc")) {
@@ -1974,9 +2896,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1974
2896
  /* clear out the header_data if it was set */
1975
2897
  rb_easy_del("header_data");
1976
2898
  } else {
1977
- VALUE header_buffer = rb_easy_set("header_data", rb_str_buf_new(16384));
1978
- curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&default_data_handler);
1979
- curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
2899
+ rb_easy_set("header_data", rb_str_buf_new(16384));
2900
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&default_header_handler);
2901
+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, rbce);
1980
2902
  }
1981
2903
 
1982
2904
  /* encoding */
@@ -1986,21 +2908,31 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1986
2908
 
1987
2909
  // progress and debug procs
1988
2910
  if (!rb_easy_nil("progress_proc")) {
2911
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
2912
+ curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, &proc_xferinfo_handler);
2913
+ curl_easy_setopt(curl, CURLOPT_XFERINFODATA, rbce);
2914
+ #else
1989
2915
  curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, (curl_progress_callback)&proc_progress_handler);
1990
- curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rb_easy_get("progress_proc"));
2916
+ curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
2917
+ #endif
1991
2918
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
1992
2919
  } else {
1993
2920
  curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
2921
+ #ifdef HAVE_CURLOPT_XFERINFOFUNCTION
2922
+ curl_easy_setopt(curl, CURLOPT_XFERINFODATA, rbce);
2923
+ #else
2924
+ curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, rbce);
2925
+ #endif
1994
2926
  }
1995
2927
 
1996
2928
  if (!rb_easy_nil("debug_proc")) {
1997
2929
  curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, (curl_debug_callback)&proc_debug_handler);
1998
- curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rb_easy_get("debug_proc"));
2930
+ curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce);
1999
2931
  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
2000
2932
  } else {
2001
2933
  // have to remove handler to re-enable standard verbosity
2002
2934
  curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL);
2003
- curl_easy_setopt(curl, CURLOPT_DEBUGDATA, NULL);
2935
+ curl_easy_setopt(curl, CURLOPT_DEBUGDATA, rbce);
2004
2936
  curl_easy_setopt(curl, CURLOPT_VERBOSE, rbce->verbose);
2005
2937
  }
2006
2938
 
@@ -2008,7 +2940,14 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2008
2940
 
2009
2941
  curl_easy_setopt(curl, CURLOPT_HEADER, rbce->header_in_body);
2010
2942
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, rbce->follow_location);
2943
+ #if LIBCURL_VERSION_NUM == 0x081000
2944
+ /* Work around 8.16.0 regression that clamps -1 (infinite) to zero */
2945
+ if (rbce->max_redirs >= 0) {
2946
+ curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs);
2947
+ }
2948
+ #else
2011
2949
  curl_easy_setopt(curl, CURLOPT_MAXREDIRS, rbce->max_redirs);
2950
+ #endif
2012
2951
 
2013
2952
  curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, rbce->proxy_tunnel);
2014
2953
  curl_easy_setopt(curl, CURLOPT_FILETIME, rbce->fetch_file_time);
@@ -2023,13 +2962,22 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2023
2962
 
2024
2963
  curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, rbce->unrestricted_auth);
2025
2964
 
2026
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, rbce->timeout);
2965
+ #ifdef HAVE_CURLOPT_TIMEOUT_MS
2966
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, rbce->timeout_ms);
2967
+ #endif
2968
+
2027
2969
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, rbce->connect_timeout);
2970
+ #ifdef HAVE_CURLOPT_CONNECTTIMEOUT_MS
2971
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, rbce->connect_timeout_ms);
2972
+ #endif
2028
2973
  curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, rbce->dns_cache_timeout);
2029
2974
 
2030
2975
  curl_easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, rbce->ignore_content_length);
2031
2976
 
2032
2977
  curl_easy_setopt(curl, CURLOPT_IPRESOLVE, rbce->resolve_mode);
2978
+ #if HAVE_CURLOPT_HTTP_VERSION
2979
+ curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, rbce->http_version);
2980
+ #endif
2033
2981
 
2034
2982
 
2035
2983
  #if LIBCURL_VERSION_NUM >= 0x070a08
@@ -2043,6 +2991,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2043
2991
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, rbce->low_speed_limit);
2044
2992
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, rbce->low_speed_time);
2045
2993
 
2994
+ curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, rbce->max_recv_speed_large);
2995
+ curl_easy_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, rbce->max_send_speed_large);
2996
+
2046
2997
  // Set up localport / proxy port
2047
2998
  // FIXME these won't get returned to default if they're unset Ruby
2048
2999
  if (rbce->proxy_port > 0) {
@@ -2075,29 +3026,27 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2075
3026
  #endif
2076
3027
  }
2077
3028
 
2078
- if (rbce->http_auth_types > 0) {
3029
+ /*
3030
+ * NOTE: we used to set CURLAUTH_ANY but see: http://curl.haxx.se/mail/lib-2015-06/0033.html
3031
+ */
3032
+ if (rbce->http_auth_types != 0) {
2079
3033
  #if LIBCURL_VERSION_NUM >= 0x070a06
2080
3034
  curl_easy_setopt(curl, CURLOPT_HTTPAUTH, rbce->http_auth_types);
2081
- } else {
2082
- curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
2083
3035
  #else
2084
3036
  rb_warn("Installed libcurl is too old to support http_auth_types");
2085
3037
  #endif
2086
3038
  }
2087
3039
 
2088
- if (rbce->proxy_auth_types > 0) {
3040
+ if (rbce->proxy_auth_types != 0) {
2089
3041
  #if LIBCURL_VERSION_NUM >= 0x070a07
2090
3042
  curl_easy_setopt(curl, CURLOPT_PROXYAUTH, rbce->proxy_auth_types);
2091
- } else {
2092
- curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
2093
3043
  #else
2094
3044
  rb_warn("Installed libcurl is too old to support proxy_auth_types");
2095
3045
  #endif
2096
3046
  }
2097
3047
 
2098
- /* Set up HTTP cookie handling if necessary
2099
- FIXME this may not get disabled if it's enabled, the disabled again from ruby.
2100
- */
3048
+ /* Set up HTTP cookie handling if necessary */
3049
+ /* Enable/attach cookie engine if requested, or implicitly via COOKIELIST usage */
2101
3050
  if (rbce->enable_cookies) {
2102
3051
  if (!rb_easy_nil("cookiejar")) {
2103
3052
  curl_easy_setopt(curl, CURLOPT_COOKIEJAR, rb_easy_get_str("cookiejar"));
@@ -2108,6 +3057,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2108
3057
  } else {
2109
3058
  curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* "" = magic to just enable */
2110
3059
  }
3060
+ } else if (rbce->cookielist_engine_enabled) {
3061
+ /* Ensure cookie engine is enabled even if enable_cookies? is false. */
3062
+ curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
2111
3063
  }
2112
3064
 
2113
3065
  if (!rb_easy_nil("cookies")) {
@@ -2150,7 +3102,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2150
3102
  rb_warn("libcurl is not configured with SSL support");
2151
3103
  }
2152
3104
  #endif
2153
-
3105
+
2154
3106
  if (rbce->ftp_filemethod > 0) {
2155
3107
  curl_easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, rbce->ftp_filemethod);
2156
3108
  }
@@ -2165,11 +3117,15 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2165
3117
 
2166
3118
  if (!rb_easy_nil("headers")) {
2167
3119
  if (rb_easy_type_check("headers", T_ARRAY) || rb_easy_type_check("headers", T_HASH)) {
2168
- VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, hdrs);
2169
- rb_iterate(rb_each, rb_easy_get("headers"), cb_each_http_header, wrap);
3120
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, hdrs);
3121
+ rb_block_call(rb_easy_get("headers"), rb_intern("each"), 0, NULL, cb_each_http_header, wrap);
2170
3122
  } else {
2171
3123
  VALUE headers_str = rb_obj_as_string(rb_easy_get("headers"));
2172
- *hdrs = curl_slist_append(*hdrs, StringValuePtr(headers_str));
3124
+ struct curl_slist *new_list = curl_slist_append(*hdrs, StringValuePtr(headers_str));
3125
+ if (!new_list) {
3126
+ rb_raise(rb_eNoMemError, "Failed to append to headers list");
3127
+ }
3128
+ *hdrs = new_list;
2173
3129
  }
2174
3130
 
2175
3131
  if (*hdrs) {
@@ -2177,11 +3133,34 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2177
3133
  }
2178
3134
  }
2179
3135
 
3136
+ #ifdef HAVE_CURLOPT_PROXYHEADER
3137
+ /* Setup HTTP proxy headers if necessary */
3138
+ curl_easy_setopt(curl, CURLOPT_PROXYHEADER, NULL); // XXX: maybe we shouldn't be clearing this?
3139
+
3140
+ if (!rb_easy_nil("proxy_headers")) {
3141
+ if (rb_easy_type_check("proxy_headers", T_ARRAY) || rb_easy_type_check("proxy_headers", T_HASH)) {
3142
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, phdrs);
3143
+ rb_block_call(rb_easy_get("proxy_headers"), rb_intern("each"), 0, NULL, cb_each_http_proxy_header, wrap);
3144
+ } else {
3145
+ VALUE proxy_headers_str = rb_obj_as_string(rb_easy_get("proxy_headers"));
3146
+ struct curl_slist *new_list = curl_slist_append(*phdrs, StringValuePtr(proxy_headers_str));
3147
+ if (!new_list) {
3148
+ rb_raise(rb_eNoMemError, "Failed to append to proxy headers list");
3149
+ }
3150
+ *phdrs = new_list;
3151
+ }
3152
+
3153
+ if (*phdrs) {
3154
+ curl_easy_setopt(curl, CURLOPT_PROXYHEADER, *phdrs);
3155
+ }
3156
+ }
3157
+ #endif
3158
+
2180
3159
  /* Setup FTP commands if necessary */
2181
3160
  if (!rb_easy_nil("ftp_commands")) {
2182
3161
  if (rb_easy_type_check("ftp_commands", T_ARRAY)) {
2183
- VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, cmds);
2184
- rb_iterate(rb_each, rb_easy_get("ftp_commands"), cb_each_ftp_command, wrap);
3162
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, cmds);
3163
+ rb_block_call(rb_easy_get("ftp_commands"), rb_intern("each"), 0, NULL, cb_each_ftp_command, wrap);
2185
3164
  }
2186
3165
 
2187
3166
  if (*cmds) {
@@ -2189,38 +3168,108 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2189
3168
  }
2190
3169
  }
2191
3170
 
3171
+ #ifdef HAVE_CURLOPT_RESOLVE
3172
+ /* Setup resolve list if necessary */
3173
+ if (!rb_easy_nil("resolve")) {
3174
+ if (rb_easy_type_check("resolve", T_ARRAY)) {
3175
+ VALUE wrap = TypedData_Wrap_Struct(rb_cObject, &curl_slist_ptr_type, rslv);
3176
+ rb_block_call(rb_easy_get("resolve"), rb_intern("each"), 0, NULL, cb_each_resolve, wrap);
3177
+ } else {
3178
+ VALUE resolve_str = rb_obj_as_string(rb_easy_get("resolve"));
3179
+ struct curl_slist *new_list = curl_slist_append(*rslv, StringValuePtr(resolve_str));
3180
+ if (!new_list) {
3181
+ rb_raise(rb_eNoMemError, "Failed to append to resolve list");
3182
+ }
3183
+ *rslv = new_list;
3184
+ }
3185
+
3186
+ if (*rslv) {
3187
+ curl_easy_setopt(curl, CURLOPT_RESOLVE, *rslv);
3188
+ }
3189
+ }
3190
+ #endif
3191
+
2192
3192
  return Qnil;
2193
3193
  }
2194
3194
  /***********************************************
2195
3195
  *
2196
3196
  * Clean up a connection
2197
3197
  *
2198
- * Always returns Qtrue.
3198
+ * Always returns Qnil.
2199
3199
  */
2200
3200
  VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2201
3201
 
2202
3202
  CURL *curl = rbce->curl;
2203
3203
  struct curl_slist *ftp_commands;
3204
+ struct curl_slist *resolve;
3205
+
3206
+ ruby_curl_easy_clear_headers_list(rbce);
3207
+ ruby_curl_easy_clear_proxy_headers_list(rbce);
2204
3208
 
2205
- /* Free everything up */
2206
- if (rbce->curl_headers) {
2207
- curl_slist_free_all(rbce->curl_headers);
2208
- rbce->curl_headers = NULL;
3209
+ ftp_commands = rbce->curl_ftp_commands;
3210
+ if (ftp_commands) {
3211
+ ruby_curl_easy_clear_ftp_commands_list(rbce);
3212
+ }
3213
+
3214
+ resolve = rbce->curl_resolve;
3215
+ if (resolve) {
3216
+ ruby_curl_easy_clear_resolve_list(rbce);
3217
+ }
3218
+
3219
+ /* clean up a PUT request's curl options. */
3220
+ if (!rb_easy_nil("upload")) {
3221
+ rb_easy_del("upload"); // set the upload object to Qnil to let the GC clean up
3222
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 0);
3223
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
3224
+ curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
3225
+ #ifdef HAVE_CURLOPT_SEEKFUNCTION
3226
+ curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, NULL);
3227
+ #endif
3228
+ #ifdef HAVE_CURLOPT_SEEKDATA
3229
+ curl_easy_setopt(curl, CURLOPT_SEEKDATA, NULL);
3230
+ #endif
3231
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0);
2209
3232
  }
2210
3233
 
2211
- ftp_commands = rbce->curl_ftp_commands;
2212
- if (ftp_commands) {
2213
- curl_slist_free_all(ftp_commands);
2214
- rbce->curl_ftp_commands = NULL;
2215
- }
3234
+ // set values on cleanup to nil
3235
+ //rb_easy_del("multi");
3236
+
3237
+ return Qnil;
3238
+ }
3239
+
3240
+ struct easy_perform_request_restore_args {
3241
+ VALUE self;
3242
+ CURL *curl;
3243
+ ruby_curl_easy *rbce;
3244
+ int clear_customrequest;
3245
+ int clear_nobody;
3246
+ int clear_postfields;
3247
+ };
3248
+
3249
+ static VALUE perform_with_request_restore_body(VALUE argp) {
3250
+ struct easy_perform_request_restore_args *args = (struct easy_perform_request_restore_args *)argp;
3251
+ return rb_funcall(args->self, rb_intern("perform"), 0);
3252
+ }
3253
+
3254
+ static VALUE perform_with_request_restore_ensure(VALUE argp) {
3255
+ struct easy_perform_request_restore_args *args = (struct easy_perform_request_restore_args *)argp;
2216
3256
 
2217
- /* clean up a PUT request's curl options. */
2218
- if (!rb_easy_nil("upload")) {
2219
- rb_easy_del("upload"); // set the upload object to Qnil to let the GC clean up
2220
- curl_easy_setopt(curl, CURLOPT_UPLOAD, 0);
2221
- curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL);
2222
- curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
2223
- curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0);
3257
+ if (args->curl) {
3258
+ if (args->clear_nobody) {
3259
+ curl_easy_setopt(args->curl, CURLOPT_NOBODY, 0L);
3260
+ }
3261
+ if (args->clear_customrequest) {
3262
+ curl_easy_setopt(args->curl, CURLOPT_CUSTOMREQUEST, NULL);
3263
+ }
3264
+ if (args->clear_postfields) {
3265
+ curl_easy_setopt(args->curl, CURLOPT_POST, 0L);
3266
+ curl_easy_setopt(args->curl, CURLOPT_POSTFIELDS, NULL);
3267
+ curl_easy_setopt(args->curl, CURLOPT_POSTFIELDSIZE, 0L);
3268
+ curl_easy_setopt(args->curl, CURLOPT_HTTPGET, 1L);
3269
+ if (args->rbce && !NIL_P(args->rbce->opts)) {
3270
+ rb_hash_delete(args->rbce->opts, rb_easy_hkey("postdata_buffer"));
3271
+ }
3272
+ }
2224
3273
  }
2225
3274
 
2226
3275
  return Qnil;
@@ -2234,14 +3283,33 @@ static VALUE ruby_curl_easy_perform_verb_str(VALUE self, const char *verb) {
2234
3283
  CURL *curl;
2235
3284
  VALUE retval;
2236
3285
 
2237
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3286
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2238
3287
  curl = rbce->curl;
2239
3288
 
3289
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
3290
+
3291
+ /* Use method override and adjust related options for special verbs. */
2240
3292
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb);
2241
3293
 
2242
- retval = rb_funcall(self, rb_intern("perform"), 0);
3294
+ /* For HEAD, ensure no body is requested/downloaded, as some servers
3295
+ * include a Content-Length header which should not cause libcurl to
3296
+ * wait for a body that will never arrive. */
3297
+ int is_head = (verb && (
3298
+ #ifdef _WIN32
3299
+ _stricmp(verb, "HEAD") == 0
3300
+ #else
3301
+ strcasecmp(verb, "HEAD") == 0
3302
+ #endif
3303
+ ));
3304
+ if (is_head) {
3305
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
3306
+ curl_easy_setopt(curl, CURLOPT_HTTPGET, 0L);
3307
+ curl_easy_setopt(curl, CURLOPT_POST, 0L);
3308
+ }
2243
3309
 
2244
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
3310
+ struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, is_head, 0 };
3311
+ retval = rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args,
3312
+ perform_with_request_restore_ensure, (VALUE)&restore_args);
2245
3313
 
2246
3314
  return retval;
2247
3315
  }
@@ -2267,6 +3335,76 @@ static VALUE ruby_curl_easy_perform_verb(VALUE self, VALUE verb) {
2267
3335
  }
2268
3336
  }
2269
3337
 
3338
+ static VALUE call_easy_perform(VALUE self) {
3339
+ return rb_funcall(self, rb_intern("perform"), 0);
3340
+ }
3341
+
3342
+ struct easy_form_perform_args {
3343
+ VALUE self;
3344
+ CURL *curl;
3345
+ int argc;
3346
+ VALUE *argv;
3347
+ struct curl_httppost *first;
3348
+ struct curl_httppost *last;
3349
+ int clear_customrequest;
3350
+ int form_set_on_curl;
3351
+ };
3352
+
3353
+ static void append_multipart_form_argument(VALUE arg,
3354
+ struct curl_httppost **first,
3355
+ struct curl_httppost **last) {
3356
+ if (rb_obj_is_instance_of(arg, cCurlPostField)) {
3357
+ append_to_form(arg, first, last);
3358
+ } else if (rb_type(arg) == T_ARRAY) {
3359
+ long j, argv_len = RARRAY_LEN(arg);
3360
+ for (j = 0; j < argv_len; ++j) {
3361
+ VALUE field = rb_ary_entry(arg, j);
3362
+ if (rb_obj_is_instance_of(field, cCurlPostField)) {
3363
+ append_to_form(field, first, last);
3364
+ } else {
3365
+ rb_raise(eCurlErrInvalidPostField,
3366
+ "You must use PostFields only with multipart form posts");
3367
+ }
3368
+ }
3369
+ } else {
3370
+ rb_raise(eCurlErrInvalidPostField,
3371
+ "You must use PostFields only with multipart form posts");
3372
+ }
3373
+ }
3374
+
3375
+ static VALUE build_and_perform_multipart_form(VALUE argp) {
3376
+ struct easy_form_perform_args *args = (struct easy_form_perform_args *)argp;
3377
+ int i;
3378
+
3379
+ for (i = 0; i < args->argc; i++) {
3380
+ append_multipart_form_argument(args->argv[i], &args->first, &args->last);
3381
+ }
3382
+
3383
+ curl_easy_setopt(args->curl, CURLOPT_POST, 0);
3384
+ curl_easy_setopt(args->curl, CURLOPT_HTTPPOST, args->first);
3385
+ args->form_set_on_curl = 1;
3386
+
3387
+ return call_easy_perform(args->self);
3388
+ }
3389
+
3390
+ static VALUE ensure_free_form_post(VALUE argp) {
3391
+ struct easy_form_perform_args *args = (struct easy_form_perform_args *)argp;
3392
+ if (args->curl) {
3393
+ if (args->form_set_on_curl) {
3394
+ curl_easy_setopt(args->curl, CURLOPT_HTTPPOST, NULL);
3395
+ }
3396
+ if (args->clear_customrequest) {
3397
+ curl_easy_setopt(args->curl, CURLOPT_CUSTOMREQUEST, NULL);
3398
+ }
3399
+ }
3400
+ if (args->first) {
3401
+ curl_formfree(args->first);
3402
+ args->first = NULL;
3403
+ }
3404
+ args->last = NULL;
3405
+ return Qnil;
3406
+ }
3407
+
2270
3408
  /*
2271
3409
  * call-seq:
2272
3410
  * easy.http_post("url=encoded%20form%20data;and=so%20on") => true
@@ -2294,45 +3432,21 @@ static VALUE ruby_curl_easy_perform_verb(VALUE self, VALUE verb) {
2294
3432
  static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2295
3433
  ruby_curl_easy *rbce;
2296
3434
  CURL *curl;
2297
- int i;
2298
3435
  VALUE args_ary;
2299
3436
 
2300
3437
  rb_scan_args(argc, argv, "*", &args_ary);
2301
3438
 
2302
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3439
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2303
3440
  curl = rbce->curl;
2304
3441
 
3442
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
3443
+
2305
3444
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2306
3445
 
2307
3446
  if (rbce->multipart_form_post) {
2308
3447
  VALUE ret;
2309
- struct curl_httppost *first = NULL, *last = NULL;
2310
-
2311
- // Make the multipart form
2312
- for (i = 0; i < argc; i++) {
2313
- if (rb_obj_is_instance_of(argv[i], cCurlPostField)) {
2314
- append_to_form(argv[i], &first, &last);
2315
- } else if (rb_type(argv[i]) == T_ARRAY) {
2316
- // see: https://github.com/rvanlieshout/curb/commit/8bcdefddc0162484681ebd1a92d52a642666a445
2317
- int c = 0, argv_len = (int)RARRAY_LEN(argv[i]);
2318
- for (; c < argv_len; ++c) {
2319
- if (rb_obj_is_instance_of(rb_ary_entry(argv[i],c), cCurlPostField)) {
2320
- append_to_form(rb_ary_entry(argv[i],c), &first, &last);
2321
- } else {
2322
- rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts");
2323
- return Qnil;
2324
- }
2325
- }
2326
- } else {
2327
- rb_raise(eCurlErrInvalidPostField, "You must use PostFields only with multipart form posts");
2328
- return Qnil;
2329
- }
2330
- }
2331
-
2332
- curl_easy_setopt(curl, CURLOPT_POST, 0);
2333
- curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
2334
- ret = rb_funcall(self, rb_intern("perform"), 0);
2335
- curl_formfree(first);
3448
+ struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 0, 0 };
3449
+ ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args);
2336
3450
 
2337
3451
  return ret;
2338
3452
  } else {
@@ -2353,7 +3467,65 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2353
3467
  ruby_curl_easy_post_body_set(self, post_body);
2354
3468
  }
2355
3469
 
2356
- return rb_funcall(self, rb_intern("perform"), 0);
3470
+ struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, 0, 0 };
3471
+ return rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args,
3472
+ perform_with_request_restore_ensure, (VALUE)&restore_args);
3473
+ }
3474
+ }
3475
+ }
3476
+
3477
+ /*
3478
+ * call-seq:
3479
+ * easy.http_patch("url=encoded%20form%20data;and=so%20on") => true
3480
+ * easy.http_patch("url=encoded%20form%20data", "and=so%20on", ...) => true
3481
+ * easy.http_patch("url=encoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
3482
+ * easy.http_patch(Curl::PostField, Curl::PostField ..., Curl::PostField) => true
3483
+ *
3484
+ * PATCH the specified formdata to the currently configured URL using
3485
+ * the current options set for this Curl::Easy instance. This method
3486
+ * always returns true, or raises an exception (defined under
3487
+ * Curl::Err) on error.
3488
+ *
3489
+ * When multipart_form_post is true the multipart logic is used; otherwise,
3490
+ * the arguments are joined into a raw PATCH body.
3491
+ */
3492
+ static VALUE ruby_curl_easy_perform_patch(int argc, VALUE *argv, VALUE self) {
3493
+ ruby_curl_easy *rbce;
3494
+ CURL *curl;
3495
+ VALUE args_ary;
3496
+
3497
+ rb_scan_args(argc, argv, "*", &args_ary);
3498
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3499
+ curl = rbce->curl;
3500
+
3501
+ /* Clear the error buffer */
3502
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
3503
+
3504
+ /* Set the custom HTTP method to PATCH */
3505
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
3506
+
3507
+ if (rbce->multipart_form_post) {
3508
+ VALUE ret;
3509
+ struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 1, 0 };
3510
+ ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args);
3511
+ return ret;
3512
+ } else {
3513
+ /* Join arguments into a raw PATCH body */
3514
+ VALUE patch_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp);
3515
+ if (patch_body == Qnil) {
3516
+ rb_raise(eCurlErrError, "Failed to join arguments");
3517
+ return Qnil;
3518
+ } else {
3519
+ if (rb_type(patch_body) == T_STRING && RSTRING_LEN(patch_body) > 0) {
3520
+ ruby_curl_easy_post_body_set(self, patch_body);
3521
+ }
3522
+ /* If postdata_buffer is still nil, set it so that the PATCH header is enabled */
3523
+ if (rb_easy_nil("postdata_buffer")) {
3524
+ ruby_curl_easy_post_body_set(self, patch_body);
3525
+ }
3526
+ struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, 0, 1 };
3527
+ return rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args,
3528
+ perform_with_request_restore_ensure, (VALUE)&restore_args);
2357
3529
  }
2358
3530
  }
2359
3531
  }
@@ -2366,17 +3538,47 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2366
3538
  * current options set for this Curl::Easy instance. This method always
2367
3539
  * returns true, or raises an exception (defined under Curl::Err) on error.
2368
3540
  */
2369
- static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
3541
+ static VALUE ruby_curl_easy_perform_put(int argc, VALUE *argv, VALUE self) {
2370
3542
  ruby_curl_easy *rbce;
2371
3543
  CURL *curl;
3544
+ VALUE args_ary;
2372
3545
 
2373
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3546
+ rb_scan_args(argc, argv, "*", &args_ary);
3547
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2374
3548
  curl = rbce->curl;
2375
3549
 
2376
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2377
- ruby_curl_easy_put_data_set(self, data);
3550
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
3551
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
2378
3552
 
2379
- return rb_funcall(self, rb_intern("perform"), 0);
3553
+ /* New: if no arguments were provided, treat as an empty PUT */
3554
+ if (RARRAY_LEN(args_ary) == 0) {
3555
+ /* Option 1: explicitly set an empty body */
3556
+ ruby_curl_easy_put_data_set(self, rb_str_new2(""));
3557
+ }
3558
+ /* If a single argument is given and it is a String or responds to read, use legacy behavior */
3559
+ else if (RARRAY_LEN(args_ary) == 1 &&
3560
+ (rb_type(rb_ary_entry(args_ary, 0)) == T_STRING ||
3561
+ rb_respond_to(rb_ary_entry(args_ary, 0), rb_intern("read")))) {
3562
+ ruby_curl_easy_put_data_set(self, rb_ary_entry(args_ary, 0));
3563
+ }
3564
+ /* Otherwise, if multipart_form_post is true, use multipart logic */
3565
+ else if (rbce->multipart_form_post) {
3566
+ VALUE ret;
3567
+ struct easy_form_perform_args perform_args = { self, curl, argc, argv, NULL, NULL, 1, 0 };
3568
+ ret = rb_ensure(build_and_perform_multipart_form, (VALUE)&perform_args, ensure_free_form_post, (VALUE)&perform_args);
3569
+ return ret;
3570
+ }
3571
+ /* Fallback: join all arguments */
3572
+ else {
3573
+ VALUE post_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp);
3574
+ if (post_body != Qnil && rb_type(post_body) == T_STRING &&
3575
+ RSTRING_LEN(post_body) > 0) {
3576
+ ruby_curl_easy_put_data_set(self, post_body);
3577
+ }
3578
+ }
3579
+ struct easy_perform_request_restore_args restore_args = { self, curl, rbce, 1, 0, 0 };
3580
+ return rb_ensure(perform_with_request_restore_body, (VALUE)&restore_args,
3581
+ perform_with_request_restore_ensure, (VALUE)&restore_args);
2380
3582
  }
2381
3583
 
2382
3584
 
@@ -2391,6 +3593,10 @@ static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
2391
3593
  * your own body handler, this string will be empty.
2392
3594
  */
2393
3595
  static VALUE ruby_curl_easy_body_str_get(VALUE self) {
3596
+ /*
3597
+ TODO: can we force_encoding on the return here if we see charset=utf-8 in the content-type header?
3598
+ Content-Type: application/json; charset=utf-8
3599
+ */
2394
3600
  CURB_OBJECT_HGETTER(ruby_curl_easy, body_data);
2395
3601
  }
2396
3602
 
@@ -2421,7 +3627,7 @@ static VALUE ruby_curl_easy_last_effective_url_get(VALUE self) {
2421
3627
  ruby_curl_easy *rbce;
2422
3628
  char* url;
2423
3629
 
2424
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3630
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2425
3631
  curl_easy_getinfo(rbce->curl, CURLINFO_EFFECTIVE_URL, &url);
2426
3632
 
2427
3633
  if (url && url[0]) { // curl returns empty string if none
@@ -2444,7 +3650,7 @@ static VALUE ruby_curl_easy_response_code_get(VALUE self) {
2444
3650
  ruby_curl_easy *rbce;
2445
3651
  long code;
2446
3652
 
2447
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3653
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2448
3654
  #ifdef HAVE_CURLINFO_RESPONSE_CODE
2449
3655
  curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &code);
2450
3656
  #else
@@ -2467,8 +3673,8 @@ static VALUE ruby_curl_easy_response_code_get(VALUE self) {
2467
3673
  static VALUE ruby_curl_easy_primary_ip_get(VALUE self) {
2468
3674
  ruby_curl_easy *rbce;
2469
3675
  char* ip;
2470
-
2471
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3676
+
3677
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2472
3678
  curl_easy_getinfo(rbce->curl, CURLINFO_PRIMARY_IP, &ip);
2473
3679
 
2474
3680
  if (ip && ip[0]) { // curl returns empty string if none
@@ -2489,7 +3695,7 @@ static VALUE ruby_curl_easy_http_connect_code_get(VALUE self) {
2489
3695
  ruby_curl_easy *rbce;
2490
3696
  long code;
2491
3697
 
2492
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3698
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2493
3699
  curl_easy_getinfo(rbce->curl, CURLINFO_HTTP_CONNECTCODE, &code);
2494
3700
 
2495
3701
  return LONG2NUM(code);
@@ -2517,13 +3723,13 @@ static VALUE ruby_curl_easy_file_time_get(VALUE self) {
2517
3723
  ruby_curl_easy *rbce;
2518
3724
  long time;
2519
3725
 
2520
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3726
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2521
3727
  curl_easy_getinfo(rbce->curl, CURLINFO_FILETIME, &time);
2522
3728
 
2523
3729
  return LONG2NUM(time);
2524
3730
  #else
2525
3731
  rb_warn("Installed libcurl is too old to support file_time");
2526
- return INT2FIX(0);
3732
+ return LONG2NUM(0);
2527
3733
  #endif
2528
3734
  }
2529
3735
 
@@ -2538,7 +3744,7 @@ static VALUE ruby_curl_easy_total_time_get(VALUE self) {
2538
3744
  ruby_curl_easy *rbce;
2539
3745
  double time;
2540
3746
 
2541
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3747
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2542
3748
  curl_easy_getinfo(rbce->curl, CURLINFO_TOTAL_TIME, &time);
2543
3749
 
2544
3750
  return rb_float_new(time);
@@ -2555,7 +3761,7 @@ static VALUE ruby_curl_easy_name_lookup_time_get(VALUE self) {
2555
3761
  ruby_curl_easy *rbce;
2556
3762
  double time;
2557
3763
 
2558
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3764
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2559
3765
  curl_easy_getinfo(rbce->curl, CURLINFO_NAMELOOKUP_TIME, &time);
2560
3766
 
2561
3767
  return rb_float_new(time);
@@ -2572,12 +3778,35 @@ static VALUE ruby_curl_easy_connect_time_get(VALUE self) {
2572
3778
  ruby_curl_easy *rbce;
2573
3779
  double time;
2574
3780
 
2575
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3781
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2576
3782
  curl_easy_getinfo(rbce->curl, CURLINFO_CONNECT_TIME, &time);
2577
3783
 
2578
3784
  return rb_float_new(time);
2579
3785
  }
2580
3786
 
3787
+ /*
3788
+ * call-seq:
3789
+ * easy.app_connect_time => float
3790
+ *
3791
+ * Retrieve the time, in seconds, it took from the start until the SSL/SSH
3792
+ * connect/handshake to the remote host was completed. This time is most often
3793
+ * very near to the pre transfer time, except for cases such as HTTP
3794
+ * pipelining where the pretransfer time can be delayed due to waits in line
3795
+ * for the pipeline and more.
3796
+ */
3797
+ #if defined(HAVE_CURLINFO_APPCONNECT_TIME)
3798
+ static VALUE ruby_curl_easy_app_connect_time_get(VALUE self) {
3799
+ ruby_curl_easy *rbce;
3800
+ double time;
3801
+
3802
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3803
+ curl_easy_getinfo(rbce->curl, CURLINFO_APPCONNECT_TIME, &time);
3804
+
3805
+ return rb_float_new(time);
3806
+ }
3807
+ #endif
3808
+
3809
+
2581
3810
  /*
2582
3811
  * call-seq:
2583
3812
  * easy.pre_transfer_time => float
@@ -2591,7 +3820,7 @@ static VALUE ruby_curl_easy_pre_transfer_time_get(VALUE self) {
2591
3820
  ruby_curl_easy *rbce;
2592
3821
  double time;
2593
3822
 
2594
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3823
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2595
3824
  curl_easy_getinfo(rbce->curl, CURLINFO_PRETRANSFER_TIME, &time);
2596
3825
 
2597
3826
  return rb_float_new(time);
@@ -2609,7 +3838,7 @@ static VALUE ruby_curl_easy_start_transfer_time_get(VALUE self) {
2609
3838
  ruby_curl_easy *rbce;
2610
3839
  double time;
2611
3840
 
2612
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3841
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2613
3842
  curl_easy_getinfo(rbce->curl, CURLINFO_STARTTRANSFER_TIME, &time);
2614
3843
 
2615
3844
  return rb_float_new(time);
@@ -2631,7 +3860,7 @@ static VALUE ruby_curl_easy_redirect_time_get(VALUE self) {
2631
3860
  ruby_curl_easy *rbce;
2632
3861
  double time;
2633
3862
 
2634
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3863
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2635
3864
  curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_TIME, &time);
2636
3865
 
2637
3866
  return rb_float_new(time);
@@ -2654,13 +3883,13 @@ static VALUE ruby_curl_easy_redirect_count_get(VALUE self) {
2654
3883
  ruby_curl_easy *rbce;
2655
3884
  long count;
2656
3885
 
2657
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3886
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2658
3887
  curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_COUNT, &count);
2659
3888
 
2660
3889
  return LONG2NUM(count);
2661
3890
  #else
2662
3891
  rb_warn("Installed libcurl is too old to support redirect_count");
2663
- return INT2FIX(-1);
3892
+ return LONG2NUM(-1);
2664
3893
  #endif
2665
3894
 
2666
3895
  }
@@ -2669,7 +3898,7 @@ static VALUE ruby_curl_easy_redirect_count_get(VALUE self) {
2669
3898
  * call-seq:
2670
3899
  * easy.redirect_url => "http://some.url" or nil
2671
3900
  *
2672
- * Retrieve the URL a redirect would take you to if you
3901
+ * Retrieve the URL a redirect would take you to if you
2673
3902
  * would enable CURLOPT_FOLLOWLOCATION.
2674
3903
  *
2675
3904
  * Requires libcurl 7.18.2 or higher, otherwise -1 is always returned.
@@ -2679,7 +3908,7 @@ static VALUE ruby_curl_easy_redirect_url_get(VALUE self) {
2679
3908
  ruby_curl_easy *rbce;
2680
3909
  char* url;
2681
3910
 
2682
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3911
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2683
3912
  curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_URL, &url);
2684
3913
 
2685
3914
  if (url && url[0]) { // curl returns empty string if none
@@ -2689,7 +3918,7 @@ static VALUE ruby_curl_easy_redirect_url_get(VALUE self) {
2689
3918
  }
2690
3919
  #else
2691
3920
  rb_warn("Installed libcurl is too old to support redirect_url");
2692
- return INT2FIX(-1);
3921
+ return LONG2NUM(-1);
2693
3922
  #endif
2694
3923
  }
2695
3924
 
@@ -2704,12 +3933,17 @@ static VALUE ruby_curl_easy_redirect_url_get(VALUE self) {
2704
3933
  */
2705
3934
  static VALUE ruby_curl_easy_uploaded_bytes_get(VALUE self) {
2706
3935
  ruby_curl_easy *rbce;
2707
- double bytes;
3936
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2708
3937
 
2709
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3938
+ #ifdef HAVE_CURLINFO_SIZE_UPLOAD_T
3939
+ curl_off_t bytes;
3940
+ curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_UPLOAD_T, &bytes);
3941
+ return LL2NUM(bytes);
3942
+ #else
3943
+ double bytes;
2710
3944
  curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_UPLOAD, &bytes);
2711
-
2712
3945
  return rb_float_new(bytes);
3946
+ #endif
2713
3947
  }
2714
3948
 
2715
3949
  /*
@@ -2721,12 +3955,17 @@ static VALUE ruby_curl_easy_uploaded_bytes_get(VALUE self) {
2721
3955
  */
2722
3956
  static VALUE ruby_curl_easy_downloaded_bytes_get(VALUE self) {
2723
3957
  ruby_curl_easy *rbce;
2724
- double bytes;
3958
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2725
3959
 
2726
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3960
+ #ifdef HAVE_CURLINFO_SIZE_DOWNLOAD_T
3961
+ curl_off_t bytes;
3962
+ curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_DOWNLOAD_T, &bytes);
3963
+ return LL2NUM(bytes);
3964
+ #else
3965
+ double bytes;
2727
3966
  curl_easy_getinfo(rbce->curl, CURLINFO_SIZE_DOWNLOAD, &bytes);
2728
-
2729
3967
  return rb_float_new(bytes);
3968
+ #endif
2730
3969
  }
2731
3970
 
2732
3971
  /*
@@ -2738,12 +3977,17 @@ static VALUE ruby_curl_easy_downloaded_bytes_get(VALUE self) {
2738
3977
  */
2739
3978
  static VALUE ruby_curl_easy_upload_speed_get(VALUE self) {
2740
3979
  ruby_curl_easy *rbce;
2741
- double bytes;
3980
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2742
3981
 
2743
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3982
+ #ifdef HAVE_CURLINFO_SPEED_UPLOAD_T
3983
+ curl_off_t bytes;
3984
+ curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_UPLOAD_T, &bytes);
3985
+ return LL2NUM(bytes);
3986
+ #else
3987
+ double bytes;
2744
3988
  curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_UPLOAD, &bytes);
2745
-
2746
3989
  return rb_float_new(bytes);
3990
+ #endif
2747
3991
  }
2748
3992
 
2749
3993
  /*
@@ -2755,12 +3999,17 @@ static VALUE ruby_curl_easy_upload_speed_get(VALUE self) {
2755
3999
  */
2756
4000
  static VALUE ruby_curl_easy_download_speed_get(VALUE self) {
2757
4001
  ruby_curl_easy *rbce;
2758
- double bytes;
4002
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2759
4003
 
2760
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4004
+ #ifdef HAVE_CURLINFO_SPEED_DOWNLOAD_T
4005
+ curl_off_t bytes;
4006
+ curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_DOWNLOAD_T, &bytes);
4007
+ return LL2NUM(bytes);
4008
+ #else
4009
+ double bytes;
2761
4010
  curl_easy_getinfo(rbce->curl, CURLINFO_SPEED_DOWNLOAD, &bytes);
2762
-
2763
4011
  return rb_float_new(bytes);
4012
+ #endif
2764
4013
  }
2765
4014
 
2766
4015
  /*
@@ -2774,7 +4023,7 @@ static VALUE ruby_curl_easy_header_size_get(VALUE self) {
2774
4023
  ruby_curl_easy *rbce;
2775
4024
  long size;
2776
4025
 
2777
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4026
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2778
4027
  curl_easy_getinfo(rbce->curl, CURLINFO_HEADER_SIZE, &size);
2779
4028
 
2780
4029
  return LONG2NUM(size);
@@ -2792,7 +4041,7 @@ static VALUE ruby_curl_easy_request_size_get(VALUE self) {
2792
4041
  ruby_curl_easy *rbce;
2793
4042
  long size;
2794
4043
 
2795
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4044
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2796
4045
  curl_easy_getinfo(rbce->curl, CURLINFO_REQUEST_SIZE, &size);
2797
4046
 
2798
4047
  return LONG2NUM(size);
@@ -2809,7 +4058,7 @@ static VALUE ruby_curl_easy_ssl_verify_result_get(VALUE self) {
2809
4058
  ruby_curl_easy *rbce;
2810
4059
  long result;
2811
4060
 
2812
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4061
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2813
4062
  curl_easy_getinfo(rbce->curl, CURLINFO_SSL_VERIFYRESULT, &result);
2814
4063
 
2815
4064
  return LONG2NUM(result);
@@ -2832,12 +4081,17 @@ NOTE: you must call curl_slist_free_all(3) on the list pointer once you're done
2832
4081
  */
2833
4082
  static VALUE ruby_curl_easy_downloaded_content_length_get(VALUE self) {
2834
4083
  ruby_curl_easy *rbce;
2835
- double bytes;
4084
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2836
4085
 
2837
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4086
+ #ifdef HAVE_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
4087
+ curl_off_t bytes;
4088
+ curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &bytes);
4089
+ return LL2NUM(bytes);
4090
+ #else
4091
+ double bytes;
2838
4092
  curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &bytes);
2839
-
2840
4093
  return rb_float_new(bytes);
4094
+ #endif
2841
4095
  }
2842
4096
 
2843
4097
  /*
@@ -2848,12 +4102,17 @@ static VALUE ruby_curl_easy_downloaded_content_length_get(VALUE self) {
2848
4102
  */
2849
4103
  static VALUE ruby_curl_easy_uploaded_content_length_get(VALUE self) {
2850
4104
  ruby_curl_easy *rbce;
2851
- double bytes;
4105
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2852
4106
 
2853
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4107
+ #ifdef HAVE_CURLINFO_CONTENT_LENGTH_UPLOAD_T
4108
+ curl_off_t bytes;
4109
+ curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_UPLOAD_T, &bytes);
4110
+ return LL2NUM(bytes);
4111
+ #else
4112
+ double bytes;
2854
4113
  curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_LENGTH_UPLOAD, &bytes);
2855
-
2856
4114
  return rb_float_new(bytes);
4115
+ #endif
2857
4116
  }
2858
4117
 
2859
4118
  /*
@@ -2869,7 +4128,7 @@ static VALUE ruby_curl_easy_content_type_get(VALUE self) {
2869
4128
  ruby_curl_easy *rbce;
2870
4129
  char* type;
2871
4130
 
2872
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4131
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2873
4132
  curl_easy_getinfo(rbce->curl, CURLINFO_CONTENT_TYPE, &type);
2874
4133
 
2875
4134
  if (type && type[0]) { // curl returns empty string if none
@@ -2912,13 +4171,13 @@ static VALUE ruby_curl_easy_os_errno_get(VALUE self) {
2912
4171
  ruby_curl_easy *rbce;
2913
4172
  long result;
2914
4173
 
2915
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4174
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2916
4175
  curl_easy_getinfo(rbce->curl, CURLINFO_OS_ERRNO, &result);
2917
4176
 
2918
4177
  return LONG2NUM(result);
2919
4178
  #else
2920
4179
  rb_warn("Installed libcurl is too old to support os_errno");
2921
- return INT2FIX(0);
4180
+ return LONG2NUM(0);
2922
4181
  #endif
2923
4182
  }
2924
4183
 
@@ -2941,23 +4200,59 @@ static VALUE ruby_curl_easy_num_connects_get(VALUE self) {
2941
4200
  ruby_curl_easy *rbce;
2942
4201
  long result;
2943
4202
 
2944
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4203
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2945
4204
  curl_easy_getinfo(rbce->curl, CURLINFO_NUM_CONNECTS, &result);
2946
4205
 
2947
4206
  return LONG2NUM(result);
2948
4207
  #else
2949
4208
  rb_warn("Installed libcurl is too old to support num_connects");
2950
- return INT2FIX(-1);
4209
+ return LONG2NUM(-1);
2951
4210
  #endif
2952
4211
  }
2953
4212
 
2954
4213
 
2955
- /* TODO this needs to be implemented.
4214
+ /*
4215
+ * call-seq:
4216
+ * easy.cookielist => cookielist
4217
+ *
4218
+ * Retrieves the cookies curl knows in an array of strings.
4219
+ * Returned strings are in Netscape cookiejar format or in Set-Cookie format.
4220
+ * Since 7.43.0 cookies in the Set-Cookie format without a domain name are not exported.
4221
+ *
4222
+ * To modify the cookie engine (add/replace/remove), use +easy.cookielist= string+
4223
+ * or +easy.set(:cookielist, string)+ with one of the following accepted inputs:
4224
+ * - A Set-Cookie style header string: "Set-Cookie: name=value; Domain=example.com; Path=/; Expires=..."
4225
+ * - One or more lines in Netscape cookie file format (tab-separated fields)
4226
+ * - Special commands: "ALL" (clear all), "SESS" (remove session cookies), "FLUSH" (write to jar), "RELOAD" (reload from file)
4227
+ *
4228
+ * @see https://curl.se/libcurl/c/CURLINFO_COOKIELIST.html option <code>CURLINFO_COOKIELIST</code> of
4229
+ * <code>curl_easy_getopt(3)</code> to see how libcurl behaves.
4230
+ * @note requires libcurl 7.14.1 or higher, otherwise +-1+ is always returned
4231
+ * @return [Array<String>, nil, -1] array of strings, or +nil+ if there are no cookies, or +-1+ if the libcurl version is too old
4232
+ */
4233
+ static VALUE ruby_curl_easy_cookielist_get(VALUE self) {
4234
+ #ifdef HAVE_CURLINFO_COOKIELIST
4235
+ ruby_curl_easy *rbce;
4236
+ struct curl_slist *cookies;
4237
+ struct curl_slist *cookie;
4238
+ VALUE rb_cookies;
4239
+
4240
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4241
+ curl_easy_getinfo(rbce->curl, CURLINFO_COOKIELIST, &cookies);
4242
+ if (!cookies)
4243
+ return Qnil;
4244
+ rb_cookies = rb_ary_new();
4245
+ for (cookie = cookies; cookie; cookie = cookie->next)
4246
+ rb_ary_push(rb_cookies, rb_str_new2(cookie->data));
4247
+ curl_slist_free_all(cookies);
4248
+ return rb_cookies;
2956
4249
 
2957
- CURLINFO_COOKIELIST
4250
+ #else
4251
+ rb_warn("Installed libcurl is too old to support cookielist");
4252
+ return INT2FIX(-1);
4253
+ #endif
4254
+ }
2958
4255
 
2959
- Pass a pointer to a 'struct curl_slist *' to receive a linked-list of all cookies cURL knows (expired ones, too). Don't forget to curl_slist_free_all(3) the list after it has been used. If there are no cookies (cookies for the handle have not been enabled or simply none have been received) 'struct curl_slist *' will be set to point to NULL. (Added in 7.14.1)
2960
- */
2961
4256
 
2962
4257
  /* TODO this needs to be implemented. Could probably support CONNECT_ONLY by having this
2963
4258
  * return an open Socket or something.
@@ -2982,7 +4277,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
2982
4277
  ruby_curl_easy *rbce;
2983
4278
  char* path = NULL;
2984
4279
 
2985
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4280
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
2986
4281
  curl_easy_getinfo(rbce->curl, CURLINFO_FTP_ENTRY_PATH, &path);
2987
4282
 
2988
4283
  if (path && path[0]) { // curl returns NULL or empty string if none
@@ -2991,7 +4286,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
2991
4286
  return Qnil;
2992
4287
  }
2993
4288
  #else
2994
- rb_warn("Installed libcurl is too old to support num_connects");
4289
+ rb_warn("Installed libcurl is too old to support ftp_entry_path");
2995
4290
  return Qnil;
2996
4291
  #endif
2997
4292
  }
@@ -3002,7 +4297,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
3002
4297
  */
3003
4298
  static VALUE ruby_curl_easy_multi_get(VALUE self) {
3004
4299
  ruby_curl_easy *rbce;
3005
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4300
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3006
4301
  return rbce->multi;
3007
4302
  }
3008
4303
 
@@ -3012,7 +4307,12 @@ static VALUE ruby_curl_easy_multi_get(VALUE self) {
3012
4307
  */
3013
4308
  static VALUE ruby_curl_easy_multi_set(VALUE self, VALUE multi) {
3014
4309
  ruby_curl_easy *rbce;
3015
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4310
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4311
+
4312
+ if (!NIL_P(multi) && rb_obj_is_kind_of(multi, cCurlMulti) != Qtrue) {
4313
+ rb_raise(rb_eTypeError, "expected Curl::Multi or nil");
4314
+ }
4315
+
3016
4316
  rbce->multi = multi;
3017
4317
  return rbce->multi;
3018
4318
  }
@@ -3023,21 +4323,44 @@ static VALUE ruby_curl_easy_multi_set(VALUE self, VALUE multi) {
3023
4323
  */
3024
4324
  static VALUE ruby_curl_easy_last_result(VALUE self) {
3025
4325
  ruby_curl_easy *rbce;
3026
- Data_Get_Struct(self, ruby_curl_easy, rbce);
3027
- return INT2FIX(rbce->last_result);
4326
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4327
+ return LONG2NUM(rbce->last_result);
4328
+ }
4329
+
4330
+ /*
4331
+ * call-seq:
4332
+ * easy.last_error => "Error details" or nil
4333
+ */
4334
+ static VALUE ruby_curl_easy_last_error(VALUE self) {
4335
+ ruby_curl_easy *rbce;
4336
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
4337
+
4338
+ if (rbce->err_buf[0]) { // curl returns NULL or empty string if none
4339
+ return rb_str_new2(rbce->err_buf);
4340
+ } else {
4341
+ return Qnil;
4342
+ }
3028
4343
  }
3029
4344
 
3030
4345
  /*
3031
4346
  * call-seq:
3032
- * easy.setopt Fixnum, value => value
4347
+ * easy.setopt(opt, val) => val
3033
4348
  *
3034
- * Iniital access to libcurl curl_easy_setopt
4349
+ * Initial access to libcurl curl_easy_setopt
4350
+ *
4351
+ * @param [Fixnum] opt The option to set, see +Curl::CURLOPT_*+ constants
4352
+ * @param [Object] val
4353
+ * @return [Object] val
4354
+ * @raise [TypeError] if the option is not supported
4355
+ * @note Some options - like url or cookie - aren't set directly throught +curl_easy_setopt+, but stored in the Ruby object state.
4356
+ * @note When +curl_easy_setopt+ is called, return value is not checked here.
3035
4357
  */
3036
4358
  static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3037
4359
  ruby_curl_easy *rbce;
3038
- long option = FIX2LONG(opt);
4360
+ long option = NUM2LONG(opt);
4361
+ rb_io_t *open_f_ptr;
3039
4362
 
3040
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4363
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3041
4364
 
3042
4365
  switch (option) {
3043
4366
  /* BEHAVIOR OPTIONS */
@@ -3059,9 +4382,11 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3059
4382
  case CURLOPT_CUSTOMREQUEST:
3060
4383
  curl_easy_setopt(rbce->curl, CURLOPT_CUSTOMREQUEST, NIL_P(val) ? NULL : StringValueCStr(val));
3061
4384
  break;
3062
- case CURLOPT_HTTP_VERSION:
3063
- curl_easy_setopt(rbce->curl, CURLOPT_HTTP_VERSION, FIX2INT(val));
3064
- break;
4385
+ case CURLOPT_HTTP_VERSION: {
4386
+ long http_version = NIL_P(val) ? CURL_HTTP_VERSION_NONE : NUM2LONG(val);
4387
+ rbce->http_version = http_version;
4388
+ curl_easy_setopt(rbce->curl, CURLOPT_HTTP_VERSION, http_version);
4389
+ } break;
3065
4390
  case CURLOPT_PROXY: {
3066
4391
  VALUE proxy_url = val;
3067
4392
  CURB_OBJECT_HSETTER(ruby_curl_easy, proxy_url);
@@ -3073,6 +4398,12 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3073
4398
  case CURLOPT_HEADER:
3074
4399
  case CURLOPT_NOPROGRESS:
3075
4400
  case CURLOPT_NOSIGNAL:
4401
+ #ifdef HAVE_CURLOPT_PATH_AS_IS
4402
+ case CURLOPT_PATH_AS_IS:
4403
+ #endif
4404
+ #ifdef HAVE_CURLOPT_PIPEWAIT
4405
+ case CURLOPT_PIPEWAIT:
4406
+ #endif
3076
4407
  case CURLOPT_HTTPGET:
3077
4408
  case CURLOPT_NOBODY: {
3078
4409
  int type = rb_type(val);
@@ -3084,8 +4415,17 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3084
4415
  } else {
3085
4416
  value = rb_funcall(val, rb_intern("to_i"), 0);
3086
4417
  }
3087
- curl_easy_setopt(rbce->curl, option, FIX2INT(value));
4418
+ curl_easy_setopt(rbce->curl, option, NUM2LONG(value));
3088
4419
  } break;
4420
+ case CURLOPT_POST: {
4421
+ curl_easy_setopt(rbce->curl, CURLOPT_POST, rb_type(val) == T_TRUE);
4422
+ } break;
4423
+ case CURLOPT_MAXCONNECTS: {
4424
+ curl_easy_setopt(rbce->curl, CURLOPT_MAXCONNECTS, NUM2LONG(val));
4425
+ } break;
4426
+ case CURLOPT_POSTFIELDS: {
4427
+ ruby_curl_easy_post_body_set_with_mode(self, val, 0);
4428
+ } break;
3089
4429
  case CURLOPT_USERPWD: {
3090
4430
  VALUE userpwd = val;
3091
4431
  CURB_OBJECT_HSETTER(ruby_curl_easy, userpwd);
@@ -3094,6 +4434,12 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3094
4434
  VALUE proxypwd = val;
3095
4435
  CURB_OBJECT_HSETTER(ruby_curl_easy, proxypwd);
3096
4436
  } break;
4437
+ #ifdef HAVE_CURLOPT_NOPROXY
4438
+ case CURLOPT_NOPROXY: {
4439
+ VALUE noproxy = val;
4440
+ CURB_OBJECT_HSETTER(ruby_curl_easy, noproxy);
4441
+ } break;
4442
+ #endif
3097
4443
  case CURLOPT_COOKIE: {
3098
4444
  VALUE cookies = val;
3099
4445
  CURB_OBJECT_HSETTER(ruby_curl_easy, cookies);
@@ -3106,14 +4452,205 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3106
4452
  VALUE cookiejar = val;
3107
4453
  CURB_OBJECT_HSETTER(ruby_curl_easy, cookiejar);
3108
4454
  } break;
4455
+ #ifdef HAVE_CURLOPT_REQUEST_TARGET
4456
+ case CURLOPT_REQUEST_TARGET: {
4457
+ /* Forward request-target directly to libcurl as a string. */
4458
+ curl_easy_setopt(rbce->curl, CURLOPT_REQUEST_TARGET, NIL_P(val) ? NULL : StringValueCStr(val));
4459
+ } break;
4460
+ #endif
4461
+ case CURLOPT_TCP_NODELAY: {
4462
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val));
4463
+ } break;
4464
+ /* FTP-specific toggles */
4465
+ #ifdef HAVE_CURLOPT_DIRLISTONLY
4466
+ case CURLOPT_DIRLISTONLY: {
4467
+ int type = rb_type(val);
4468
+ VALUE value;
4469
+ if (type == T_TRUE) {
4470
+ value = rb_int_new(1);
4471
+ } else if (type == T_FALSE) {
4472
+ value = rb_int_new(0);
4473
+ } else {
4474
+ value = rb_funcall(val, rb_intern("to_i"), 0);
4475
+ }
4476
+ curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, NUM2LONG(value));
4477
+ } break;
4478
+ #endif
4479
+ #ifdef HAVE_CURLOPT_FTP_USE_EPSV
4480
+ case CURLOPT_FTP_USE_EPSV: {
4481
+ int type = rb_type(val);
4482
+ VALUE value;
4483
+ if (type == T_TRUE) {
4484
+ value = rb_int_new(1);
4485
+ } else if (type == T_FALSE) {
4486
+ value = rb_int_new(0);
4487
+ } else {
4488
+ value = rb_funcall(val, rb_intern("to_i"), 0);
4489
+ }
4490
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, NUM2LONG(value));
4491
+ } break;
4492
+ #endif
4493
+ #ifdef HAVE_CURLOPT_FTP_USE_EPRT
4494
+ case CURLOPT_FTP_USE_EPRT: {
4495
+ int type = rb_type(val);
4496
+ VALUE value;
4497
+ if (type == T_TRUE) {
4498
+ value = rb_int_new(1);
4499
+ } else if (type == T_FALSE) {
4500
+ value = rb_int_new(0);
4501
+ } else {
4502
+ value = rb_funcall(val, rb_intern("to_i"), 0);
4503
+ }
4504
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, NUM2LONG(value));
4505
+ } break;
4506
+ #endif
4507
+ #ifdef HAVE_CURLOPT_FTP_SKIP_PASV_IP
4508
+ case CURLOPT_FTP_SKIP_PASV_IP: {
4509
+ int type = rb_type(val);
4510
+ VALUE value;
4511
+ if (type == T_TRUE) {
4512
+ value = rb_int_new(1);
4513
+ } else if (type == T_FALSE) {
4514
+ value = rb_int_new(0);
4515
+ } else {
4516
+ value = rb_funcall(val, rb_intern("to_i"), 0);
4517
+ }
4518
+ curl_easy_setopt(rbce->curl, CURLOPT_FTP_SKIP_PASV_IP, NUM2LONG(value));
4519
+ } break;
4520
+ #endif
4521
+ case CURLOPT_RANGE: {
4522
+ curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val));
4523
+ } break;
3109
4524
  case CURLOPT_RESUME_FROM: {
3110
- curl_easy_setopt(rbce->curl, CURLOPT_RESUME_FROM, FIX2LONG(val));
4525
+ curl_easy_setopt(rbce->curl, CURLOPT_RESUME_FROM, NUM2LONG(val));
3111
4526
  } break;
3112
4527
  case CURLOPT_FAILONERROR: {
3113
- curl_easy_setopt(rbce->curl, CURLOPT_FAILONERROR, FIX2LONG(val));
4528
+ curl_easy_setopt(rbce->curl, CURLOPT_FAILONERROR, NUM2LONG(val));
3114
4529
  } break;
3115
- default:
4530
+ case CURLOPT_SSL_CIPHER_LIST: {
4531
+ curl_easy_setopt(rbce->curl, CURLOPT_SSL_CIPHER_LIST, StringValueCStr(val));
4532
+ } break;
4533
+ case CURLOPT_FORBID_REUSE: {
4534
+ curl_easy_setopt(rbce->curl, CURLOPT_FORBID_REUSE, NUM2LONG(val));
4535
+ } break;
4536
+ #ifdef HAVE_CURLOPT_GSSAPI_DELEGATION
4537
+ case CURLOPT_GSSAPI_DELEGATION: {
4538
+ curl_easy_setopt(rbce->curl, CURLOPT_GSSAPI_DELEGATION, NUM2LONG(val));
4539
+ } break;
4540
+ #endif
4541
+ #ifdef HAVE_CURLOPT_UNIX_SOCKET_PATH
4542
+ case CURLOPT_UNIX_SOCKET_PATH: {
4543
+ curl_easy_setopt(rbce->curl, CURLOPT_UNIX_SOCKET_PATH, StringValueCStr(val));
4544
+ } break;
4545
+ #endif
4546
+ #ifdef HAVE_CURLOPT_MAX_SEND_SPEED_LARGE
4547
+ case CURLOPT_MAX_SEND_SPEED_LARGE: {
4548
+ curl_easy_setopt(rbce->curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t) NUM2LL(val));
4549
+ } break;
4550
+ #endif
4551
+ #ifdef HAVE_CURLOPT_MAX_RECV_SPEED_LARGE
4552
+ case CURLOPT_MAX_RECV_SPEED_LARGE: {
4553
+ curl_easy_setopt(rbce->curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) NUM2LL(val));
4554
+ } break;
4555
+ #endif
4556
+ #ifdef HAVE_CURLOPT_MAXFILESIZE
4557
+ case CURLOPT_MAXFILESIZE:
4558
+ curl_easy_setopt(rbce->curl, CURLOPT_MAXFILESIZE, NUM2LONG(val));
4559
+ break;
4560
+ #endif
4561
+ #ifdef HAVE_CURLOPT_TCP_KEEPALIVE
4562
+ case CURLOPT_TCP_KEEPALIVE:
4563
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPALIVE, NUM2LONG(val));
4564
+ break;
4565
+ case CURLOPT_TCP_KEEPIDLE:
4566
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPIDLE, NUM2LONG(val));
4567
+ break;
4568
+ case CURLOPT_TCP_KEEPINTVL:
4569
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPINTVL, NUM2LONG(val));
4570
+ break;
4571
+ #endif
4572
+ #ifdef HAVE_CURLOPT_HAPROXYPROTOCOL
4573
+ case CURLOPT_HAPROXYPROTOCOL:
4574
+ curl_easy_setopt(rbce->curl, CURLOPT_HAPROXYPROTOCOL, NUM2LONG(val));
4575
+ break;
4576
+ #endif
4577
+ case CURLOPT_STDERR:
4578
+ // libcurl requires raw FILE pointer and this should be IO object in Ruby.
4579
+ // Tempfile or StringIO won't work.
4580
+ Check_Type(val, T_FILE);
4581
+ GetOpenFile(val, open_f_ptr);
4582
+ curl_easy_setopt(rbce->curl, CURLOPT_STDERR, rb_io_stdio_file(open_f_ptr));
4583
+ /* Retain a Ruby reference to the IO to prevent GC/finalization
4584
+ * while libcurl still holds and writes to the underlying FILE*. */
4585
+ rb_easy_set("stderr_io", val);
4586
+ break;
4587
+ case CURLOPT_PROTOCOLS:
4588
+ case CURLOPT_REDIR_PROTOCOLS:
4589
+ curl_easy_setopt(rbce->curl, option, NUM2LONG(val));
4590
+ break;
4591
+ #ifdef HAVE_CURLOPT_SSL_SESSIONID_CACHE
4592
+ case CURLOPT_SSL_SESSIONID_CACHE:
4593
+ curl_easy_setopt(rbce->curl, CURLOPT_SSL_SESSIONID_CACHE, NUM2LONG(val));
3116
4594
  break;
4595
+ #endif
4596
+ #ifdef HAVE_CURLOPT_COOKIELIST
4597
+ case CURLOPT_COOKIELIST: {
4598
+ /* Forward to libcurl */
4599
+ curl_easy_setopt(rbce->curl, CURLOPT_COOKIELIST, StringValueCStr(val));
4600
+ /* Track whether the cookie engine should be enabled for requests.
4601
+ * According to libcurl docs, CURLOPT_COOKIELIST also enables the cookie engine
4602
+ * when provided with a non-command string. Some environments may still require
4603
+ * an explicit "enable" via CURLOPT_COOKIEFILE="" to send cookies on requests.
4604
+ * We do that in the perform setup when this flag is set.
4605
+ */
4606
+ if (RB_TYPE_P(val, T_STRING)) {
4607
+ const char *s = StringValueCStr(val);
4608
+ if (!(strcmp(s, "ALL") == 0 || strcmp(s, "SESS") == 0 || strcmp(s, "FLUSH") == 0 || strcmp(s, "RELOAD") == 0)) {
4609
+ rbce->cookielist_engine_enabled = 1;
4610
+ }
4611
+ } else {
4612
+ /* Non-string values are unexpected; be conservative and do not enable. */
4613
+ }
4614
+ } break;
4615
+ #endif
4616
+ #ifdef HAVE_CURLOPT_PROXY_SSL_VERIFYHOST
4617
+ case CURLOPT_PROXY_SSL_VERIFYHOST:
4618
+ curl_easy_setopt(rbce->curl, CURLOPT_PROXY_SSL_VERIFYHOST, NUM2LONG(val));
4619
+ break;
4620
+ #endif
4621
+ #ifdef HAVE_CURLOPT_RESOLVE
4622
+ case CURLOPT_RESOLVE: {
4623
+ struct curl_slist *list = NULL;
4624
+ ruby_curl_easy_clear_resolve_list(rbce);
4625
+ if (NIL_P(val)) {
4626
+ /* When nil is passed, we clear any previous resolve list */
4627
+ list = NULL;
4628
+ } else if (TYPE(val) == T_ARRAY) {
4629
+ long i, len = RARRAY_LEN(val);
4630
+ for (i = 0; i < len; i++) {
4631
+ VALUE item = rb_ary_entry(val, i);
4632
+ struct curl_slist *new_list = curl_slist_append(list, StringValueCStr(item));
4633
+ if (!new_list) {
4634
+ curl_slist_free_all(list);
4635
+ rb_raise(rb_eNoMemError, "Failed to append to resolve list");
4636
+ }
4637
+ list = new_list;
4638
+ }
4639
+ } else {
4640
+ /* If a single string is passed, use it directly */
4641
+ list = curl_slist_append(NULL, StringValueCStr(val));
4642
+ if (!list) {
4643
+ rb_raise(rb_eNoMemError, "Failed to create resolve list");
4644
+ }
4645
+ }
4646
+ /* Save the list pointer in the ruby_curl_easy structure for cleanup later */
4647
+ rbce->curl_resolve = list;
4648
+ rb_hash_aset(rbce->opts, rb_easy_hkey("resolve"), val);
4649
+ curl_easy_setopt(rbce->curl, CURLOPT_RESOLVE, list);
4650
+ } break;
4651
+ #endif
4652
+ default:
4653
+ rb_raise(rb_eTypeError, "Curb unsupported option");
3117
4654
  }
3118
4655
 
3119
4656
  return val;
@@ -3121,9 +4658,13 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3121
4658
 
3122
4659
  /*
3123
4660
  * call-seq:
3124
- * easy.getinfo Fixnum => value
4661
+ * easy.getinfo(opt) => nil
3125
4662
  *
3126
4663
  * Iniital access to libcurl curl_easy_getinfo, remember getinfo doesn't return the same values as setopt
4664
+ *
4665
+ * @note This method is not implemented yet.
4666
+ * @param [Fixnum] code Constant +CURLINFO_*+ from libcurl
4667
+ * @return [nil]
3127
4668
  */
3128
4669
  static VALUE ruby_curl_easy_get_opt(VALUE self, VALUE opt) {
3129
4670
  return Qnil;
@@ -3136,7 +4677,7 @@ static VALUE ruby_curl_easy_get_opt(VALUE self, VALUE opt) {
3136
4677
  static VALUE ruby_curl_easy_inspect(VALUE self) {
3137
4678
  char buf[64];
3138
4679
  ruby_curl_easy *rbce;
3139
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4680
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3140
4681
  /* if we don't have a url set... we'll crash... */
3141
4682
  if( !rb_easy_nil("url") && rb_easy_type_check("url", T_STRING)) {
3142
4683
  VALUE url = rb_easy_get("url");
@@ -3168,7 +4709,7 @@ static VALUE ruby_curl_easy_escape(VALUE self, VALUE svalue) {
3168
4709
  VALUE rresult;
3169
4710
  VALUE str = svalue;
3170
4711
 
3171
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4712
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3172
4713
 
3173
4714
  /* NOTE: make sure the value is a string, if not call to_s */
3174
4715
  if( rb_type(str) != T_STRING ) { str = rb_funcall(str,rb_intern("to_s"),0); }
@@ -3199,12 +4740,12 @@ static VALUE ruby_curl_easy_unescape(VALUE self, VALUE str) {
3199
4740
  char *result;
3200
4741
  VALUE rresult;
3201
4742
 
3202
- Data_Get_Struct(self, ruby_curl_easy, rbce);
4743
+ TypedData_Get_Struct(self, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
3203
4744
 
3204
4745
  #if (LIBCURL_VERSION_NUM >= 0x070f04)
3205
4746
  result = (char*)curl_easy_unescape(rbce->curl, StringValuePtr(str), (int)RSTRING_LEN(str), &rlen);
3206
4747
  #else
3207
- result = (char*)curl_unescape(StringValuePtr(str), RSTRING_LEN(str));
4748
+ result = (char*)curl_unescape(StringValuePtr(str), (int)RSTRING_LEN(str));
3208
4749
  rlen = strlen(result);
3209
4750
  #endif
3210
4751
 
@@ -3219,15 +4760,16 @@ static VALUE ruby_curl_easy_unescape(VALUE self, VALUE str) {
3219
4760
 
3220
4761
  /*
3221
4762
  * call-seq:
3222
- * Curl::Easy.error(code) => String
4763
+ * Curl::Easy.error(code) => [ErrCode, String]
3223
4764
  *
3224
4765
  * translate an internal libcurl error to ruby error class
3225
4766
  */
3226
4767
  static VALUE ruby_curl_easy_error_message(VALUE klass, VALUE code) {
3227
- return rb_curl_easy_error(FIX2INT(code));
4768
+ return rb_curl_easy_error(NUM2INT(code));
3228
4769
  }
3229
4770
 
3230
4771
  /* =================== INIT LIB =====================*/
4772
+ // TODO: https://bugs.ruby-lang.org/issues/18007
3231
4773
  void init_curb_easy() {
3232
4774
  idCall = rb_intern("call");
3233
4775
  idJoin = rb_intern("join");
@@ -3238,12 +4780,21 @@ void init_curb_easy() {
3238
4780
  cCurlEasy = rb_define_class_under(mCurl, "Easy", rb_cObject);
3239
4781
 
3240
4782
  /* Class methods */
3241
- rb_define_singleton_method(cCurlEasy, "new", ruby_curl_easy_new, -1);
4783
+ rb_define_alloc_func(cCurlEasy, ruby_curl_easy_allocate);
3242
4784
  rb_define_singleton_method(cCurlEasy, "error", ruby_curl_easy_error_message, 1);
3243
4785
 
4786
+ /* Initialize method */
4787
+ rb_define_method(cCurlEasy, "initialize", ruby_curl_easy_initialize, -1);
4788
+
3244
4789
  /* Attributes for config next perform */
3245
4790
  rb_define_method(cCurlEasy, "url", ruby_curl_easy_url_get, 0);
3246
4791
  rb_define_method(cCurlEasy, "proxy_url", ruby_curl_easy_proxy_url_get, 0);
4792
+ rb_define_method(cCurlEasy, "http_version=", ruby_curl_easy_http_version_set, 1);
4793
+ rb_define_method(cCurlEasy, "http_version", ruby_curl_easy_http_version_get, 0);
4794
+
4795
+ rb_define_method(cCurlEasy, "proxy_headers=", ruby_curl_easy_proxy_headers_set, 1);
4796
+ rb_define_method(cCurlEasy, "proxy_headers", ruby_curl_easy_proxy_headers_get, 0);
4797
+
3247
4798
  rb_define_method(cCurlEasy, "headers=", ruby_curl_easy_headers_set, 1);
3248
4799
  rb_define_method(cCurlEasy, "headers", ruby_curl_easy_headers_get, 0);
3249
4800
  rb_define_method(cCurlEasy, "interface", ruby_curl_easy_interface_get, 0);
@@ -3270,6 +4821,8 @@ void init_curb_easy() {
3270
4821
  rb_define_method(cCurlEasy, "put_data=", ruby_curl_easy_put_data_set, 1);
3271
4822
  rb_define_method(cCurlEasy, "ftp_commands=", ruby_curl_easy_ftp_commands_set, 1);
3272
4823
  rb_define_method(cCurlEasy, "ftp_commands", ruby_curl_easy_ftp_commands_get, 0);
4824
+ rb_define_method(cCurlEasy, "resolve=", ruby_curl_easy_resolve_set, 1);
4825
+ rb_define_method(cCurlEasy, "resolve", ruby_curl_easy_resolve_get, 0);
3273
4826
 
3274
4827
  rb_define_method(cCurlEasy, "local_port=", ruby_curl_easy_local_port_set, 1);
3275
4828
  rb_define_method(cCurlEasy, "local_port", ruby_curl_easy_local_port_get, 0);
@@ -3287,8 +4840,12 @@ void init_curb_easy() {
3287
4840
  rb_define_method(cCurlEasy, "max_redirects", ruby_curl_easy_max_redirects_get, 0);
3288
4841
  rb_define_method(cCurlEasy, "timeout=", ruby_curl_easy_timeout_set, 1);
3289
4842
  rb_define_method(cCurlEasy, "timeout", ruby_curl_easy_timeout_get, 0);
4843
+ rb_define_method(cCurlEasy, "timeout_ms=", ruby_curl_easy_timeout_ms_set, 1);
4844
+ rb_define_method(cCurlEasy, "timeout_ms", ruby_curl_easy_timeout_ms_get, 0);
3290
4845
  rb_define_method(cCurlEasy, "connect_timeout=", ruby_curl_easy_connect_timeout_set, 1);
3291
4846
  rb_define_method(cCurlEasy, "connect_timeout", ruby_curl_easy_connect_timeout_get, 0);
4847
+ rb_define_method(cCurlEasy, "connect_timeout_ms=", ruby_curl_easy_connect_timeout_ms_set, 1);
4848
+ rb_define_method(cCurlEasy, "connect_timeout_ms", ruby_curl_easy_connect_timeout_ms_get, 0);
3292
4849
  rb_define_method(cCurlEasy, "dns_cache_timeout=", ruby_curl_easy_dns_cache_timeout_set, 1);
3293
4850
  rb_define_method(cCurlEasy, "dns_cache_timeout", ruby_curl_easy_dns_cache_timeout_get, 0);
3294
4851
  rb_define_method(cCurlEasy, "ftp_response_timeout=", ruby_curl_easy_ftp_response_timeout_set, 1);
@@ -3297,6 +4854,10 @@ void init_curb_easy() {
3297
4854
  rb_define_method(cCurlEasy, "low_speed_limit", ruby_curl_easy_low_speed_limit_get, 0);
3298
4855
  rb_define_method(cCurlEasy, "low_speed_time=", ruby_curl_easy_low_speed_time_set, 1);
3299
4856
  rb_define_method(cCurlEasy, "low_speed_time", ruby_curl_easy_low_speed_time_get, 0);
4857
+ rb_define_method(cCurlEasy, "max_send_speed_large=", ruby_curl_easy_max_send_speed_large_set, 1);
4858
+ rb_define_method(cCurlEasy, "max_send_speed_large", ruby_curl_easy_max_send_speed_large_get, 0);
4859
+ rb_define_method(cCurlEasy, "max_recv_speed_large=", ruby_curl_easy_max_recv_speed_large_set, 1);
4860
+ rb_define_method(cCurlEasy, "max_recv_speed_large", ruby_curl_easy_max_recv_speed_large_get, 0);
3300
4861
  rb_define_method(cCurlEasy, "ssl_version=", ruby_curl_easy_ssl_version_set, 1);
3301
4862
  rb_define_method(cCurlEasy, "ssl_version", ruby_curl_easy_ssl_version_get, 0);
3302
4863
  rb_define_method(cCurlEasy, "use_ssl=", ruby_curl_easy_use_ssl_set, 1);
@@ -3348,7 +4909,8 @@ void init_curb_easy() {
3348
4909
 
3349
4910
  rb_define_method(cCurlEasy, "http", ruby_curl_easy_perform_verb, 1);
3350
4911
  rb_define_method(cCurlEasy, "http_post", ruby_curl_easy_perform_post, -1);
3351
- rb_define_method(cCurlEasy, "http_put", ruby_curl_easy_perform_put, 1);
4912
+ rb_define_method(cCurlEasy, "http_put", ruby_curl_easy_perform_put, -1);
4913
+ rb_define_method(cCurlEasy, "http_patch", ruby_curl_easy_perform_patch, -1);
3352
4914
 
3353
4915
  /* Post-perform info methods */
3354
4916
  rb_define_method(cCurlEasy, "body_str", ruby_curl_easy_body_str_get, 0);
@@ -3356,6 +4918,7 @@ void init_curb_easy() {
3356
4918
 
3357
4919
  rb_define_method(cCurlEasy, "last_effective_url", ruby_curl_easy_last_effective_url_get, 0);
3358
4920
  rb_define_method(cCurlEasy, "response_code", ruby_curl_easy_response_code_get, 0);
4921
+ rb_define_method(cCurlEasy, "code", ruby_curl_easy_response_code_get, 0);
3359
4922
  #if defined(HAVE_CURLINFO_PRIMARY_IP)
3360
4923
  rb_define_method(cCurlEasy, "primary_ip", ruby_curl_easy_primary_ip_get, 0);
3361
4924
  #endif
@@ -3364,6 +4927,9 @@ void init_curb_easy() {
3364
4927
  rb_define_method(cCurlEasy, "total_time", ruby_curl_easy_total_time_get, 0);
3365
4928
  rb_define_method(cCurlEasy, "name_lookup_time", ruby_curl_easy_name_lookup_time_get, 0);
3366
4929
  rb_define_method(cCurlEasy, "connect_time", ruby_curl_easy_connect_time_get, 0);
4930
+ #if defined(HAVE_CURLINFO_APPCONNECT_TIME)
4931
+ rb_define_method(cCurlEasy, "app_connect_time", ruby_curl_easy_app_connect_time_get, 0);
4932
+ #endif
3367
4933
  rb_define_method(cCurlEasy, "pre_transfer_time", ruby_curl_easy_pre_transfer_time_get, 0);
3368
4934
  rb_define_method(cCurlEasy, "start_transfer_time", ruby_curl_easy_start_transfer_time_get, 0);
3369
4935
  rb_define_method(cCurlEasy, "redirect_time", ruby_curl_easy_redirect_time_get, 0);
@@ -3381,6 +4947,7 @@ void init_curb_easy() {
3381
4947
  rb_define_method(cCurlEasy, "content_type", ruby_curl_easy_content_type_get, 0);
3382
4948
  rb_define_method(cCurlEasy, "os_errno", ruby_curl_easy_os_errno_get, 0);
3383
4949
  rb_define_method(cCurlEasy, "num_connects", ruby_curl_easy_num_connects_get, 0);
4950
+ rb_define_method(cCurlEasy, "cookielist", ruby_curl_easy_cookielist_get, 0);
3384
4951
  rb_define_method(cCurlEasy, "ftp_entry_path", ruby_curl_easy_ftp_entry_path_get, 0);
3385
4952
 
3386
4953
  rb_define_method(cCurlEasy, "close", ruby_curl_easy_close, 0);
@@ -3397,7 +4964,9 @@ void init_curb_easy() {
3397
4964
 
3398
4965
  rb_define_method(cCurlEasy, "multi", ruby_curl_easy_multi_get, 0);
3399
4966
  rb_define_method(cCurlEasy, "multi=", ruby_curl_easy_multi_set, 1);
4967
+ rb_define_private_method(cCurlEasy, "_take_callback_error", ruby_curl_easy_take_callback_error, 0);
3400
4968
  rb_define_method(cCurlEasy, "last_result", ruby_curl_easy_last_result, 0);
4969
+ rb_define_method(cCurlEasy, "last_error", ruby_curl_easy_last_error, 0);
3401
4970
 
3402
4971
  rb_define_method(cCurlEasy, "setopt", ruby_curl_easy_set_opt, 2);
3403
4972
  rb_define_method(cCurlEasy, "getinfo", ruby_curl_easy_get_opt, 1);