curb 0.9.3 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/curb_easy.c CHANGED
@@ -25,12 +25,18 @@ static VALUE rbstrAmp;
25
25
 
26
26
  VALUE cCurlEasy;
27
27
 
28
+ // for Ruby 1.8
29
+ #ifndef HAVE_RB_IO_STDIO_FILE
30
+ static FILE * rb_io_stdio_file(rb_io_t *fptr) {
31
+ return fptr->f;
32
+ }
33
+ #endif
28
34
 
29
35
  /* ================== CURL HANDLER FUNCS ==============*/
30
36
 
31
37
  static VALUE callback_exception(VALUE unused) {
32
38
  return Qfalse;
33
- }
39
+ }
34
40
 
35
41
  /* These handle both body and header data */
36
42
  static size_t default_data_handler(char *stream,
@@ -223,12 +229,31 @@ static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
223
229
  curl_slist_free_all(rbce->curl_headers);
224
230
  }
225
231
 
232
+ if (rbce->curl_proxy_headers) {
233
+ curl_slist_free_all(rbce->curl_proxy_headers);
234
+ }
235
+
226
236
  if (rbce->curl_ftp_commands) {
227
237
  curl_slist_free_all(rbce->curl_ftp_commands);
228
238
  }
229
239
 
240
+ if (rbce->curl_resolve) {
241
+ curl_slist_free_all(rbce->curl_resolve);
242
+ }
243
+
230
244
  if (rbce->curl) {
245
+ /* disable any progress or debug events */
246
+ curl_easy_setopt(rbce->curl, CURLOPT_WRITEFUNCTION, NULL);
247
+ curl_easy_setopt(rbce->curl, CURLOPT_WRITEDATA, NULL);
248
+ curl_easy_setopt(rbce->curl, CURLOPT_HEADERFUNCTION, NULL);
249
+ curl_easy_setopt(rbce->curl, CURLOPT_HEADERDATA, NULL);
250
+ curl_easy_setopt(rbce->curl, CURLOPT_DEBUGFUNCTION, NULL);
251
+ curl_easy_setopt(rbce->curl, CURLOPT_DEBUGDATA, NULL);
252
+ curl_easy_setopt(rbce->curl, CURLOPT_VERBOSE, 0);
253
+ curl_easy_setopt(rbce->curl, CURLOPT_PROGRESSFUNCTION, NULL);
254
+ curl_easy_setopt(rbce->curl, CURLOPT_NOPROGRESS, 1);
231
255
  curl_easy_cleanup(rbce->curl);
256
+ rbce->curl = NULL;
232
257
  }
233
258
  }
234
259
 
@@ -243,8 +268,12 @@ void curl_easy_free(ruby_curl_easy *rbce) {
243
268
  static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
244
269
  rbce->opts = rb_hash_new();
245
270
 
271
+ memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
272
+
246
273
  rbce->curl_headers = NULL;
274
+ rbce->curl_proxy_headers = NULL;
247
275
  rbce->curl_ftp_commands = NULL;
276
+ rbce->curl_resolve = NULL;
248
277
 
249
278
  /* various-typed opts */
250
279
  rbce->local_port = 0;
@@ -262,6 +291,8 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
262
291
  rbce->ftp_response_timeout = 0;
263
292
  rbce->low_speed_limit = 0;
264
293
  rbce->low_speed_time = 0;
294
+ rbce->max_send_speed_large = 0;
295
+ rbce->max_recv_speed_large = 0;
265
296
  rbce->ssl_version = -1;
266
297
  rbce->use_ssl = -1;
267
298
  rbce->ftp_filemethod = -1;
@@ -283,25 +314,37 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
283
314
  rbce->callback_active = 0;
284
315
  }
285
316
 
317
+ /*
318
+ * Allocate space for a Curl::Easy instance.
319
+ */
320
+ static VALUE ruby_curl_easy_allocate(VALUE klass) {
321
+ ruby_curl_easy *rbce;
322
+ rbce = ALLOC(ruby_curl_easy);
323
+ rbce->curl = NULL;
324
+ rbce->opts = Qnil;
325
+ rbce->multi = Qnil;
326
+ ruby_curl_easy_zero(rbce);
327
+ return Data_Wrap_Struct(klass, curl_easy_mark, curl_easy_free, rbce);
328
+ }
329
+
286
330
  /*
287
331
  * call-seq:
288
- * Curl::Easy.new => #<Curl::Easy...>
289
- * Curl::Easy.new(url = nil) => #<Curl::Easy...>
290
- * Curl::Easy.new(url = nil) { |self| ... } => #<Curl::Easy...>
332
+ * Curl::Easy.new => #<Curl::Easy...>
333
+ * Curl::Easy.new(url = nil) => #<Curl::Easy...>
334
+ * Curl::Easy.new(url = nil) { |self| ... } => #<Curl::Easy...>
291
335
  *
292
- * Create a new Curl::Easy instance, optionally supplying the URL.
336
+ * Initialize a new Curl::Easy instance, optionally supplying the URL.
293
337
  * The block form allows further configuration to be supplied before
294
338
  * the instance is returned.
295
339
  */
296
- static VALUE ruby_curl_easy_new(int argc, VALUE *argv, VALUE klass) {
340
+ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
297
341
  CURLcode ecode;
298
342
  VALUE url, blk;
299
- VALUE new_curl;
300
343
  ruby_curl_easy *rbce;
301
344
 
302
345
  rb_scan_args(argc, argv, "01&", &url, &blk);
303
346
 
304
- rbce = ALLOC(ruby_curl_easy);
347
+ Data_Get_Struct(self, ruby_curl_easy, rbce);
305
348
 
306
349
  /* handler */
307
350
  rbce->curl = curl_easy_init();
@@ -309,32 +352,32 @@ static VALUE ruby_curl_easy_new(int argc, VALUE *argv, VALUE klass) {
309
352
  rb_raise(eCurlErrFailedInit, "Failed to initialize easy handle");
310
353
  }
311
354
 
312
- new_curl = Data_Wrap_Struct(klass, curl_easy_mark, curl_easy_free, rbce);
313
-
314
355
  rbce->multi = Qnil;
315
356
  rbce->opts = Qnil;
316
357
 
317
358
  ruby_curl_easy_zero(rbce);
318
359
 
360
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
361
+
319
362
  rb_easy_set("url", url);
320
363
 
321
- /* set the new_curl pointer to the curl handle */
322
- ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)new_curl);
364
+ /* set the pointer to the curl handle */
365
+ ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)self);
323
366
  if (ecode != CURLE_OK) {
324
367
  raise_curl_easy_error_exception(ecode);
325
368
  }
326
369
 
327
370
  if (blk != Qnil) {
328
- rb_funcall(blk, idCall, 1, new_curl);
371
+ rb_funcall(blk, idCall, 1, self);
329
372
  }
330
373
 
331
- return new_curl;
374
+ return self;
332
375
  }
333
376
 
334
377
  /*
335
378
  * call-seq:
336
- * easy.clone => #&lt;easy clone&gt;
337
- * easy.dup => #&lt;easy clone&gt;
379
+ * easy.clone => <easy clone>
380
+ * easy.dup => <easy clone>
338
381
  *
339
382
  * Clone this Curl::Easy instance, creating a new instance.
340
383
  * This method duplicates the underlying CURL* handle.
@@ -348,7 +391,11 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
348
391
  memcpy(newrbce, rbce, sizeof(ruby_curl_easy));
349
392
  newrbce->curl = curl_easy_duphandle(rbce->curl);
350
393
  newrbce->curl_headers = NULL;
394
+ newrbce->curl_proxy_headers = NULL;
351
395
  newrbce->curl_ftp_commands = NULL;
396
+ newrbce->curl_resolve = NULL;
397
+
398
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
352
399
 
353
400
  return Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, newrbce);
354
401
  }
@@ -358,7 +405,7 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
358
405
  * easy.close => nil
359
406
  *
360
407
  * Close the Curl::Easy instance. Any open connections are closed
361
- * The easy handle is reinitialized. If a previous multi handle was
408
+ * The easy handle is reinitialized. If a previous multi handle was
362
409
  * open it is set to nil and will be cleared after a GC.
363
410
  */
364
411
  static VALUE ruby_curl_easy_close(VALUE self) {
@@ -419,7 +466,9 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
419
466
  curl_easy_reset(rbce->curl);
420
467
  ruby_curl_easy_zero(rbce);
421
468
 
422
- /* rest clobbers the private setting, so reset it to self */
469
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
470
+
471
+ /* reset clobbers the private setting, so reset it to self */
423
472
  ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)self);
424
473
  if (ecode != CURLE_OK) {
425
474
  raise_curl_easy_error_exception(ecode);
@@ -431,6 +480,12 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
431
480
  rbce->curl_headers = NULL;
432
481
  }
433
482
 
483
+ /* Free everything up */
484
+ if (rbce->curl_proxy_headers) {
485
+ curl_slist_free_all(rbce->curl_proxy_headers);
486
+ rbce->curl_proxy_headers = NULL;
487
+ }
488
+
434
489
  return opts_dup;
435
490
  }
436
491
 
@@ -483,6 +538,10 @@ static VALUE ruby_curl_easy_headers_set(VALUE self, VALUE headers) {
483
538
  CURB_OBJECT_HSETTER(ruby_curl_easy, headers);
484
539
  }
485
540
 
541
+ static VALUE ruby_curl_easy_proxy_headers_set(VALUE self, VALUE proxy_headers) {
542
+ CURB_OBJECT_HSETTER(ruby_curl_easy, proxy_headers);
543
+ }
544
+
486
545
  /*
487
546
  * call-seq:
488
547
  * easy.headers => Hash, Array or Str
@@ -493,11 +552,46 @@ static VALUE ruby_curl_easy_headers_get(VALUE self) {
493
552
  ruby_curl_easy *rbce;
494
553
  VALUE headers;
495
554
  Data_Get_Struct(self, ruby_curl_easy, rbce);
496
- headers = rb_easy_get("headers");//rb_hash_aref(rbce->opts, rb_intern("headers"));
555
+ headers = rb_easy_get("headers");//rb_hash_aref(rbce->opts, rb_intern("headers"));
497
556
  if (headers == Qnil) { headers = rb_easy_set("headers", rb_hash_new()); }
498
557
  return headers;
499
558
  }
500
559
 
560
+ /*
561
+ * call-seq:
562
+ * easy.proxy_headers = "Header: val" => "Header: val"
563
+ * easy.proxy_headers = {"Header" => "val" ..., "Header" => "val"} => {"Header: val", ...}
564
+ * easy.proxy_headers = ["Header: val" ..., "Header: val"] => ["Header: val", ...]
565
+ *
566
+ *
567
+ * For example to set a standard or custom header:
568
+ *
569
+ * easy.proxy_headers["MyHeader"] = "myval"
570
+ *
571
+ * To remove a standard header (this is useful when removing libcurls default
572
+ * 'Expect: 100-Continue' header when using HTTP form posts):
573
+ *
574
+ * easy.proxy_headers["Expect"] = ''
575
+ *
576
+ * Anything passed to libcurl as a header will be converted to a string during
577
+ * the perform step.
578
+ */
579
+
580
+ /*
581
+ * call-seq:
582
+ * easy.proxy_headers => Hash, Array or Str
583
+ *
584
+ * Obtain the custom HTTP proxy_headers for following requests.
585
+ */
586
+ static VALUE ruby_curl_easy_proxy_headers_get(VALUE self) {
587
+ ruby_curl_easy *rbce;
588
+ VALUE proxy_headers;
589
+ Data_Get_Struct(self, ruby_curl_easy, rbce);
590
+ proxy_headers = rb_easy_get("proxy_headers");//rb_hash_aref(rbce->opts, rb_intern("proxy_headers"));
591
+ if (proxy_headers == Qnil) { proxy_headers = rb_easy_set("proxy_headers", rb_hash_new()); }
592
+ return proxy_headers;
593
+ }
594
+
501
595
  /*
502
596
  * call-seq:
503
597
  * easy.interface => string
@@ -705,29 +799,29 @@ static VALUE ruby_curl_easy_useragent_get(VALUE self) {
705
799
  /*
706
800
  * call-seq:
707
801
  * easy.post_body = "some=form%20data&to=send" => string or nil
708
- *
802
+ *
709
803
  * Sets the POST body of this Curl::Easy instance. This is expected to be
710
804
  * URL encoded; no additional processing or encoding is done on the string.
711
805
  * The content-type header will be set to application/x-www-form-urlencoded.
712
- *
806
+ *
713
807
  * This is handy if you want to perform a POST against a Curl::Multi instance.
714
808
  */
715
809
  static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
716
810
  ruby_curl_easy *rbce;
717
811
  CURL *curl;
718
-
812
+
719
813
  char *data;
720
814
  long len;
721
815
 
722
816
  Data_Get_Struct(self, ruby_curl_easy, rbce);
723
-
817
+
724
818
  curl = rbce->curl;
725
-
819
+
726
820
  if ( post_body == Qnil ) {
727
821
  rb_easy_del("postdata_buffer");
728
822
  curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
729
-
730
- } else {
823
+
824
+ } else {
731
825
  if (rb_type(post_body) == T_STRING) {
732
826
  data = StringValuePtr(post_body);
733
827
  len = RSTRING_LEN(post_body);
@@ -740,19 +834,19 @@ static VALUE ruby_curl_easy_post_body_set(VALUE self, VALUE post_body) {
740
834
  else {
741
835
  rb_raise(rb_eRuntimeError, "post data must respond_to .to_s");
742
836
  }
743
-
744
- // Store the string, since it has to hang around for the duration of the
837
+
838
+ // Store the string, since it has to hang around for the duration of the
745
839
  // request. See CURLOPT_POSTFIELDS in the libcurl docs.
746
840
  //rbce->postdata_buffer = post_body;
747
841
  rb_easy_set("postdata_buffer", post_body);
748
-
842
+
749
843
  curl_easy_setopt(curl, CURLOPT_POST, 1);
750
844
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
751
845
  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
752
-
846
+
753
847
  return post_body;
754
848
  }
755
-
849
+
756
850
  return Qnil;
757
851
  }
758
852
 
@@ -769,7 +863,7 @@ static VALUE ruby_curl_easy_post_body_get(VALUE self) {
769
863
  /*
770
864
  * call-seq:
771
865
  * easy.put_data = data => ""
772
- *
866
+ *
773
867
  * Points this Curl::Easy instance to data to be uploaded via PUT. This
774
868
  * sets the request to a PUT type request - useful if you want to PUT via
775
869
  * a multi handle.
@@ -801,7 +895,7 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
801
895
  curl_easy_setopt(curl, CURLOPT_SEEKDATA, rbce);
802
896
  #endif
803
897
 
804
- /*
898
+ /*
805
899
  * we need to set specific headers for the PUT to work... so
806
900
  * convert the internal headers structure to a HASH if one is set
807
901
  */
@@ -815,7 +909,7 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
815
909
  if (NIL_P(data)) { return data; }
816
910
 
817
911
  headers = rb_easy_get("headers");
818
- if( headers == Qnil ) {
912
+ if( headers == Qnil ) {
819
913
  headers = rb_hash_new();
820
914
  }
821
915
 
@@ -863,13 +957,32 @@ static VALUE ruby_curl_easy_ftp_commands_set(VALUE self, VALUE ftp_commands) {
863
957
  }
864
958
 
865
959
  /*
866
- * call-seq
960
+ * call-seq:
867
961
  * easy.ftp_commands => array or nil
868
962
  */
869
963
  static VALUE ruby_curl_easy_ftp_commands_get(VALUE self) {
870
964
  CURB_OBJECT_HGETTER(ruby_curl_easy, ftp_commands);
871
965
  }
872
966
 
967
+ /*
968
+ * call-seq:
969
+ * easy.resolve = [ "example.com:80:127.0.0.1" ] => [ "example.com:80:127.0.0.1" ]
970
+ *
971
+ * Set the resolve list to statically resolve hostnames to IP addresses,
972
+ * bypassing DNS for matching hostname/port combinations.
973
+ */
974
+ static VALUE ruby_curl_easy_resolve_set(VALUE self, VALUE resolve) {
975
+ CURB_OBJECT_HSETTER(ruby_curl_easy, resolve);
976
+ }
977
+
978
+ /*
979
+ * call-seq:
980
+ * easy.resolve => array or nil
981
+ */
982
+ static VALUE ruby_curl_easy_resolve_get(VALUE self) {
983
+ CURB_OBJECT_HGETTER(ruby_curl_easy, resolve);
984
+ }
985
+
873
986
  /* ================== IMMED ATTRS ==================*/
874
987
 
875
988
  /*
@@ -980,7 +1093,7 @@ static VALUE ruby_curl_easy_proxy_type_get(VALUE self) {
980
1093
  (!strncmp("ntlm",node,4)) ? CURLAUTH_NTLM : \
981
1094
  (!strncmp("anysafe",node,7)) ? CURLAUTH_ANYSAFE : \
982
1095
  (!strncmp("any",node,3)) ? CURLAUTH_ANY : 0
983
- #else
1096
+ #else
984
1097
  #define CURL_HTTPAUTH_STR_TO_NUM(node) \
985
1098
  (!strncmp("basic",node,5)) ? CURLAUTH_BASIC : \
986
1099
  (!strncmp("digest",node,6)) ? CURLAUTH_DIGEST : \
@@ -1012,7 +1125,7 @@ static VALUE ruby_curl_easy_http_auth_types_set(int argc, VALUE *argv, VALUE sel
1012
1125
 
1013
1126
  if (len == 1 && (rb_ary_entry(args_ary,0) == Qnil || TYPE(rb_ary_entry(args_ary,0)) == T_FIXNUM ||
1014
1127
  TYPE(rb_ary_entry(args_ary,0)) == T_BIGNUM)) {
1015
- if (rb_ary_entry(args_ary,0) == Qnil) {
1128
+ if (rb_ary_entry(args_ary,0) == Qnil) {
1016
1129
  rbce->http_auth_types = 0;
1017
1130
  }
1018
1131
  else {
@@ -1094,7 +1207,7 @@ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) {
1094
1207
 
1095
1208
  /*
1096
1209
  * call-seq:
1097
- * easy.timeout = fixnum or nil => fixnum or nil
1210
+ * easy.timeout = float, fixnum or nil => numeric
1098
1211
  *
1099
1212
  * Set the maximum time in seconds that you allow the libcurl transfer
1100
1213
  * operation to take. Normally, name lookups can take a considerable time
@@ -1103,20 +1216,39 @@ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) {
1103
1216
  *
1104
1217
  * Set to nil (or zero) to disable timeout (it will then only timeout
1105
1218
  * on the system's internal timeouts).
1219
+ *
1220
+ * Uses timeout_ms internally instead of timeout because it allows for
1221
+ * better precision and libcurl will use the last set value when both
1222
+ * timeout and timeout_ms are set.
1223
+ *
1106
1224
  */
1107
- static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout) {
1108
- CURB_IMMED_SETTER(ruby_curl_easy, timeout, 0);
1225
+ static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout_s) {
1226
+ ruby_curl_easy *rbce;
1227
+ Data_Get_Struct(self, ruby_curl_easy, rbce);
1228
+
1229
+ if (Qnil == timeout_s || NUM2DBL(timeout_s) <= 0.0) {
1230
+ rbce->timeout_ms = 0;
1231
+ } else {
1232
+ rbce->timeout_ms = (unsigned long)(NUM2DBL(timeout_s) * 1000);
1233
+ }
1234
+
1235
+ return DBL2NUM(rbce->timeout_ms / 1000.0);
1109
1236
  }
1110
1237
 
1111
1238
  /*
1112
1239
  * call-seq:
1113
- * easy.timeout => fixnum or nil
1240
+ * easy.timeout => numeric
1114
1241
  *
1115
1242
  * Obtain the maximum time in seconds that you allow the libcurl transfer
1116
1243
  * operation to take.
1244
+ *
1245
+ * Uses timeout_ms internally instead of timeout.
1246
+ *
1117
1247
  */
1118
- static VALUE ruby_curl_easy_timeout_get(VALUE self, VALUE timeout) {
1119
- CURB_IMMED_GETTER(ruby_curl_easy, timeout, 0);
1248
+ static VALUE ruby_curl_easy_timeout_get(VALUE self) {
1249
+ ruby_curl_easy *rbce;
1250
+ Data_Get_Struct(self, ruby_curl_easy, rbce);
1251
+ return DBL2NUM(rbce->timeout_ms / 1000.0);
1120
1252
  }
1121
1253
 
1122
1254
  /*
@@ -1132,7 +1264,16 @@ static VALUE ruby_curl_easy_timeout_get(VALUE self, VALUE timeout) {
1132
1264
  * on the system's internal timeouts).
1133
1265
  */
1134
1266
  static VALUE ruby_curl_easy_timeout_ms_set(VALUE self, VALUE timeout_ms) {
1135
- CURB_IMMED_SETTER(ruby_curl_easy, timeout_ms, 0);
1267
+ ruby_curl_easy *rbce;
1268
+ Data_Get_Struct(self, ruby_curl_easy, rbce);
1269
+
1270
+ if (Qnil == timeout_ms || NUM2DBL(timeout_ms) <= 0.0) {
1271
+ rbce->timeout_ms = 0;
1272
+ } else {
1273
+ rbce->timeout_ms = NUM2ULONG(timeout_ms);
1274
+ }
1275
+
1276
+ return ULONG2NUM(rbce->timeout_ms);
1136
1277
  }
1137
1278
 
1138
1279
  /*
@@ -1142,8 +1283,10 @@ static VALUE ruby_curl_easy_timeout_ms_set(VALUE self, VALUE timeout_ms) {
1142
1283
  * Obtain the maximum time in milliseconds that you allow the libcurl transfer
1143
1284
  * operation to take.
1144
1285
  */
1145
- static VALUE ruby_curl_easy_timeout_ms_get(VALUE self, VALUE timeout_ms) {
1146
- CURB_IMMED_GETTER(ruby_curl_easy, timeout_ms, 0);
1286
+ static VALUE ruby_curl_easy_timeout_ms_get(VALUE self) {
1287
+ ruby_curl_easy *rbce;
1288
+ Data_Get_Struct(self, ruby_curl_easy, rbce);
1289
+ return LONG2NUM(rbce->timeout_ms);
1147
1290
  }
1148
1291
 
1149
1292
  /*
@@ -1293,6 +1436,46 @@ static VALUE ruby_curl_easy_low_speed_time_get(VALUE self, VALUE low_speed_time)
1293
1436
  CURB_IMMED_GETTER(ruby_curl_easy, low_speed_time, 0);
1294
1437
  }
1295
1438
 
1439
+ /*
1440
+ * call-seq:
1441
+ * easy.max_send_speed_large = fixnum or nil => fixnum or nil
1442
+ *
1443
+ * Set the maximal sending transfer speed (in bytes per second)
1444
+ */
1445
+ static VALUE ruby_curl_easy_max_send_speed_large_set(VALUE self, VALUE max_send_speed_large) {
1446
+ CURB_IMMED_SETTER(ruby_curl_easy, max_send_speed_large, 0);
1447
+ }
1448
+
1449
+ /*
1450
+ * call-seq:
1451
+ * easy.max_send_speed_large = fixnum or nil => fixnum or nil
1452
+ *
1453
+ * Get the maximal sending transfer speed (in bytes per second)
1454
+ */
1455
+ static VALUE ruby_curl_easy_max_send_speed_large_get(VALUE self, VALUE max_send_speed_large) {
1456
+ CURB_IMMED_GETTER(ruby_curl_easy, max_send_speed_large, 0);
1457
+ }
1458
+
1459
+ /*
1460
+ * call-seq:
1461
+ * easy.max_recv_speed_large = fixnum or nil => fixnum or nil
1462
+ *
1463
+ * Set the maximal receiving transfer speed (in bytes per second)
1464
+ */
1465
+ static VALUE ruby_curl_easy_max_recv_speed_large_set(VALUE self, VALUE max_recv_speed_large) {
1466
+ CURB_IMMED_SETTER(ruby_curl_easy, max_recv_speed_large, 0);
1467
+ }
1468
+
1469
+ /*
1470
+ * call-seq:
1471
+ * easy.max_recv_speed_large = fixnum or nil => fixnum or nil
1472
+ *
1473
+ * Get the maximal receiving transfer speed (in bytes per second)
1474
+ */
1475
+ static VALUE ruby_curl_easy_max_recv_speed_large_get(VALUE self, VALUE max_recv_speed_large) {
1476
+ CURB_IMMED_GETTER(ruby_curl_easy, max_recv_speed_large, 0);
1477
+ }
1478
+
1296
1479
  /*
1297
1480
  * call-seq:
1298
1481
  * easy.username = string => string
@@ -1310,7 +1493,7 @@ static VALUE ruby_curl_easy_username_set(VALUE self, VALUE username) {
1310
1493
  /*
1311
1494
  * call-seq:
1312
1495
  * easy.username => string
1313
- *
1496
+ *
1314
1497
  * Get the current username
1315
1498
  */
1316
1499
  static VALUE ruby_curl_easy_username_get(VALUE self, VALUE username) {
@@ -1338,7 +1521,7 @@ static VALUE ruby_curl_easy_password_set(VALUE self, VALUE password) {
1338
1521
  /*
1339
1522
  * call-seq:
1340
1523
  * easy.password => string
1341
- *
1524
+ *
1342
1525
  * Get the current password
1343
1526
  */
1344
1527
  static VALUE ruby_curl_easy_password_get(VALUE self, VALUE password) {
@@ -1363,6 +1546,7 @@ static VALUE ruby_curl_easy_password_get(VALUE self, VALUE password) {
1363
1546
  * Curl::CURL_SSLVERSION_TLSv1_0
1364
1547
  * Curl::CURL_SSLVERSION_TLSv1_1
1365
1548
  * Curl::CURL_SSLVERSION_TLSv1_2
1549
+ * Curl::CURL_SSLVERSION_TLSv1_3
1366
1550
  */
1367
1551
  static VALUE ruby_curl_easy_ssl_version_set(VALUE self, VALUE ssl_version) {
1368
1552
  CURB_IMMED_SETTER(ruby_curl_easy, ssl_version, -1);
@@ -1381,7 +1565,7 @@ static VALUE ruby_curl_easy_ssl_version_get(VALUE self, VALUE ssl_version) {
1381
1565
  /*
1382
1566
  * call-seq:
1383
1567
  * easy.use_ssl = value => fixnum or nil
1384
- *
1568
+ *
1385
1569
  * Ensure libcurl uses SSL for FTP connections. Valid options are Curl::CURL_USESSL_NONE,
1386
1570
  * Curl::CURL_USESSL_TRY, Curl::CURL_USESSL_CONTROL, and Curl::CURL_USESSL_ALL.
1387
1571
  */
@@ -1411,7 +1595,7 @@ static VALUE ruby_curl_easy_ftp_filemethod_set(VALUE self, VALUE ftp_filemethod)
1411
1595
  }
1412
1596
 
1413
1597
  /*
1414
- * call-seq
1598
+ * call-seq:
1415
1599
  * easy.ftp_filemethod => fixnum
1416
1600
  *
1417
1601
  * Get the configuration for how libcurl will reach files on the server.
@@ -1783,7 +1967,7 @@ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
1783
1967
 
1784
1968
  /*
1785
1969
  * call-seq:
1786
- * easy.on_body { |body_data| ... } => &lt;old handler&gt;
1970
+ * easy.on_body { |body_data| ... } => <old handler>
1787
1971
  *
1788
1972
  * Assign or remove the +on_body+ handler for this Curl::Easy instance.
1789
1973
  * To remove a previously-supplied handler, call this method with no
@@ -1802,7 +1986,7 @@ static VALUE ruby_curl_easy_on_body_set(int argc, VALUE *argv, VALUE self) {
1802
1986
 
1803
1987
  /*
1804
1988
  * call-seq:
1805
- * easy.on_success { |easy| ... } => &lt;old handler&gt;
1989
+ * easy.on_success { |easy| ... } => <old handler>
1806
1990
  *
1807
1991
  * Assign or remove the +on_success+ handler for this Curl::Easy instance.
1808
1992
  * To remove a previously-supplied handler, call this method with no
@@ -1817,7 +2001,7 @@ static VALUE ruby_curl_easy_on_success_set(int argc, VALUE *argv, VALUE self) {
1817
2001
 
1818
2002
  /*
1819
2003
  * call-seq:
1820
- * easy.on_failure {|easy,code| ... } => &lt;old handler&gt;
2004
+ * easy.on_failure {|easy,code| ... } => <old handler>
1821
2005
  *
1822
2006
  * Assign or remove the +on_failure+ handler for this Curl::Easy instance.
1823
2007
  * To remove a previously-supplied handler, call this method with no
@@ -1832,13 +2016,13 @@ static VALUE ruby_curl_easy_on_failure_set(int argc, VALUE *argv, VALUE self) {
1832
2016
 
1833
2017
  /*
1834
2018
  * call-seq:
1835
- * easy.on_missing {|easy,code| ... } => &lt;old handler;&gt;
2019
+ * easy.on_missing {|easy,code| ... } => <old handler;>
1836
2020
  *
1837
2021
  * Assign or remove the on_missing handler for this Curl::Easy instance.
1838
2022
  * To remove a previously-supplied handler, call this method with no attached
1839
2023
  * block.
1840
2024
  *
1841
- * The +on_missing+ handler is called when request is finished with a
2025
+ * The +on_missing+ handler is called when request is finished with a
1842
2026
  * status of 40x
1843
2027
  */
1844
2028
  static VALUE ruby_curl_easy_on_missing_set(int argc, VALUE *argv, VALUE self) {
@@ -1847,13 +2031,13 @@ static VALUE ruby_curl_easy_on_missing_set(int argc, VALUE *argv, VALUE self) {
1847
2031
 
1848
2032
  /*
1849
2033
  * call-seq:
1850
- * easy.on_redirect {|easy,code| ... } => &lt;old handler;&gt;
2034
+ * easy.on_redirect {|easy,code| ... } => <old handler;>
1851
2035
  *
1852
2036
  * Assign or remove the on_redirect handler for this Curl::Easy instance.
1853
2037
  * To remove a previously-supplied handler, call this method with no attached
1854
2038
  * block.
1855
2039
  *
1856
- * The +on_redirect+ handler is called when request is finished with a
2040
+ * The +on_redirect+ handler is called when request is finished with a
1857
2041
  * status of 30x
1858
2042
  */
1859
2043
  static VALUE ruby_curl_easy_on_redirect_set(int argc, VALUE *argv, VALUE self) {
@@ -1862,7 +2046,7 @@ static VALUE ruby_curl_easy_on_redirect_set(int argc, VALUE *argv, VALUE self) {
1862
2046
 
1863
2047
  /*
1864
2048
  * call-seq:
1865
- * easy.on_complete {|easy| ... } => &lt;old handler&gt;
2049
+ * easy.on_complete {|easy| ... } => <old handler>
1866
2050
  *
1867
2051
  * Assign or remove the +on_complete+ handler for this Curl::Easy instance.
1868
2052
  * To remove a previously-supplied handler, call this method with no
@@ -1876,7 +2060,7 @@ static VALUE ruby_curl_easy_on_complete_set(int argc, VALUE *argv, VALUE self) {
1876
2060
 
1877
2061
  /*
1878
2062
  * call-seq:
1879
- * easy.on_header { |header_data| ... } => &lt;old handler&gt;
2063
+ * easy.on_header { |header_data| ... } => <old handler>
1880
2064
  *
1881
2065
  * Assign or remove the +on_header+ handler for this Curl::Easy instance.
1882
2066
  * To remove a previously-supplied handler, call this method with no
@@ -1892,7 +2076,7 @@ static VALUE ruby_curl_easy_on_header_set(int argc, VALUE *argv, VALUE self) {
1892
2076
 
1893
2077
  /*
1894
2078
  * call-seq:
1895
- * easy.on_progress { |dl_total, dl_now, ul_total, ul_now| ... } => &lt;old handler&gt;
2079
+ * easy.on_progress { |dl_total, dl_now, ul_total, ul_now| ... } => <old handler>
1896
2080
  *
1897
2081
  * Assign or remove the +on_progress+ handler for this Curl::Easy instance.
1898
2082
  * To remove a previously-supplied handler, call this method with no
@@ -1913,7 +2097,7 @@ static VALUE ruby_curl_easy_on_progress_set(int argc, VALUE *argv, VALUE self) {
1913
2097
 
1914
2098
  /*
1915
2099
  * call-seq:
1916
- * easy.on_debug { |type, data| ... } => &lt;old handler&gt;
2100
+ * easy.on_debug { |type, data| ... } => <old handler>
1917
2101
  *
1918
2102
  * Assign or remove the +on_debug+ handler for this Curl::Easy instance.
1919
2103
  * To remove a previously-supplied handler, call this method with no
@@ -1952,11 +2136,14 @@ static VALUE cb_each_http_header(VALUE header, VALUE wrap) {
1952
2136
 
1953
2137
  name = rb_obj_as_string(rb_ary_entry(header, 0));
1954
2138
  value = rb_obj_as_string(rb_ary_entry(header, 1));
1955
-
1956
- // This is a bit inefficient, but we don't want to be modifying
1957
- // the actual values in the original hash.
1958
- header_str = rb_str_plus(name, rb_str_new2(": "));
1959
- header_str = rb_str_plus(header_str, value);
2139
+ 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
2140
+ header_str = rb_str_plus(name, rb_str_new2(":"));
2141
+ } else {
2142
+ // This is a bit inefficient, but we don't want to be modifying
2143
+ // the actual values in the original hash.
2144
+ header_str = rb_str_plus(name, rb_str_new2(": "));
2145
+ header_str = rb_str_plus(header_str, value);
2146
+ }
1960
2147
  } else {
1961
2148
  header_str = rb_obj_as_string(header);
1962
2149
  }
@@ -1967,6 +2154,38 @@ static VALUE cb_each_http_header(VALUE header, VALUE wrap) {
1967
2154
  return header_str;
1968
2155
  }
1969
2156
 
2157
+ /***********************************************
2158
+ * This is an rb_iterate callback used to set up http proxy headers.
2159
+ */
2160
+ static VALUE cb_each_http_proxy_header(VALUE proxy_header, VALUE wrap) {
2161
+ struct curl_slist **list;
2162
+ VALUE proxy_header_str = Qnil;
2163
+
2164
+ Data_Get_Struct(wrap, struct curl_slist *, list);
2165
+
2166
+ //rb_p(proxy_header);
2167
+
2168
+ if (rb_type(proxy_header) == T_ARRAY) {
2169
+ // we're processing a hash, proxy header is [name, val]
2170
+ VALUE name, value;
2171
+
2172
+ name = rb_obj_as_string(rb_ary_entry(proxy_header, 0));
2173
+ value = rb_obj_as_string(rb_ary_entry(proxy_header, 1));
2174
+
2175
+ // This is a bit inefficient, but we don't want to be modifying
2176
+ // the actual values in the original hash.
2177
+ proxy_header_str = rb_str_plus(name, rb_str_new2(": "));
2178
+ proxy_header_str = rb_str_plus(proxy_header_str, value);
2179
+ } else {
2180
+ proxy_header_str = rb_obj_as_string(proxy_header);
2181
+ }
2182
+
2183
+ //rb_p(header_str);
2184
+
2185
+ *list = curl_slist_append(*list, StringValuePtr(proxy_header_str));
2186
+ return proxy_header_str;
2187
+ }
2188
+
1970
2189
  /***********************************************
1971
2190
  * This is an rb_iterate callback used to set up ftp commands.
1972
2191
  */
@@ -1981,6 +2200,20 @@ static VALUE cb_each_ftp_command(VALUE ftp_command, VALUE wrap) {
1981
2200
  return ftp_command_string;
1982
2201
  }
1983
2202
 
2203
+ /***********************************************
2204
+ * This is an rb_iterate callback used to set up the resolve list.
2205
+ */
2206
+ static VALUE cb_each_resolve(VALUE resolve, VALUE wrap) {
2207
+ struct curl_slist **list;
2208
+ VALUE resolve_string;
2209
+ Data_Get_Struct(wrap, struct curl_slist *, list);
2210
+
2211
+ resolve_string = rb_obj_as_string(resolve);
2212
+ *list = curl_slist_append(*list, StringValuePtr(resolve));
2213
+
2214
+ return resolve_string;
2215
+ }
2216
+
1984
2217
  /***********************************************
1985
2218
  *
1986
2219
  * Setup a connection
@@ -1992,7 +2225,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
1992
2225
  CURL *curl;
1993
2226
  VALUE url, _url = rb_easy_get("url");
1994
2227
  struct curl_slist **hdrs = &(rbce->curl_headers);
2228
+ struct curl_slist **phdrs = &(rbce->curl_proxy_headers);
1995
2229
  struct curl_slist **cmds = &(rbce->curl_ftp_commands);
2230
+ struct curl_slist **rslv = &(rbce->curl_resolve);
1996
2231
 
1997
2232
  curl = rbce->curl;
1998
2233
 
@@ -2001,7 +2236,6 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2001
2236
  }
2002
2237
 
2003
2238
  url = rb_check_string_type(_url);
2004
-
2005
2239
  curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
2006
2240
 
2007
2241
  // network stuff and auth
@@ -2114,15 +2348,14 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2114
2348
 
2115
2349
  curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, rbce->unrestricted_auth);
2116
2350
 
2117
- /* timeouts override each other we cannot blindly set timeout and timeout_ms */
2118
- if (rbce->timeout && rbce->timeout > 0) {
2119
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, rbce->timeout);
2120
- }
2121
- if (rbce->timeout_ms && rbce->timeout_ms > 0) {
2122
- curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, rbce->timeout_ms);
2123
- }
2351
+ #if HAVE_CURLOPT_TIMEOUT_MS
2352
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, rbce->timeout_ms);
2353
+ #endif
2354
+
2124
2355
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, rbce->connect_timeout);
2356
+ #if HAVE_CURLOPT_CONNECTTIMEOUT_MS
2125
2357
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, rbce->connect_timeout_ms);
2358
+ #endif
2126
2359
  curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, rbce->dns_cache_timeout);
2127
2360
 
2128
2361
  curl_easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, rbce->ignore_content_length);
@@ -2141,6 +2374,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2141
2374
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, rbce->low_speed_limit);
2142
2375
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, rbce->low_speed_time);
2143
2376
 
2377
+ curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, rbce->max_recv_speed_large);
2378
+ curl_easy_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, rbce->max_send_speed_large);
2379
+
2144
2380
  // Set up localport / proxy port
2145
2381
  // FIXME these won't get returned to default if they're unset Ruby
2146
2382
  if (rbce->proxy_port > 0) {
@@ -2247,7 +2483,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2247
2483
  rb_warn("libcurl is not configured with SSL support");
2248
2484
  }
2249
2485
  #endif
2250
-
2486
+
2251
2487
  if (rbce->ftp_filemethod > 0) {
2252
2488
  curl_easy_setopt(curl, CURLOPT_FTP_FILEMETHOD, rbce->ftp_filemethod);
2253
2489
  }
@@ -2274,6 +2510,25 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2274
2510
  }
2275
2511
  }
2276
2512
 
2513
+ #if HAVE_CURLOPT_PROXYHEADER
2514
+ /* Setup HTTP proxy headers if necessary */
2515
+ curl_easy_setopt(curl, CURLOPT_PROXYHEADER, NULL); // XXX: maybe we shouldn't be clearing this?
2516
+
2517
+ if (!rb_easy_nil("proxy_headers")) {
2518
+ if (rb_easy_type_check("proxy_headers", T_ARRAY) || rb_easy_type_check("proxy_headers", T_HASH)) {
2519
+ VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, phdrs);
2520
+ rb_iterate(rb_each, rb_easy_get("proxy_headers"), cb_each_http_proxy_header, wrap);
2521
+ } else {
2522
+ VALUE proxy_headers_str = rb_obj_as_string(rb_easy_get("proxy_headers"));
2523
+ *phdrs = curl_slist_append(*hdrs, StringValuePtr(proxy_headers_str));
2524
+ }
2525
+
2526
+ if (*phdrs) {
2527
+ curl_easy_setopt(curl, CURLOPT_PROXYHEADER, *phdrs);
2528
+ }
2529
+ }
2530
+ #endif
2531
+
2277
2532
  /* Setup FTP commands if necessary */
2278
2533
  if (!rb_easy_nil("ftp_commands")) {
2279
2534
  if (rb_easy_type_check("ftp_commands", T_ARRAY)) {
@@ -2286,18 +2541,33 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2286
2541
  }
2287
2542
  }
2288
2543
 
2544
+ #if HAVE_CURLOPT_RESOLVE
2545
+ /* Setup resolve list if necessary */
2546
+ if (!rb_easy_nil("resolve")) {
2547
+ if (rb_easy_type_check("resolve", T_ARRAY)) {
2548
+ VALUE wrap = Data_Wrap_Struct(rb_cObject, 0, 0, rslv);
2549
+ rb_iterate(rb_each, rb_easy_get("resolve"), cb_each_resolve, wrap);
2550
+ }
2551
+
2552
+ if (*rslv) {
2553
+ curl_easy_setopt(curl, CURLOPT_RESOLVE, *rslv);
2554
+ }
2555
+ }
2556
+ #endif
2557
+
2289
2558
  return Qnil;
2290
2559
  }
2291
2560
  /***********************************************
2292
2561
  *
2293
2562
  * Clean up a connection
2294
2563
  *
2295
- * Always returns Qtrue.
2564
+ * Always returns Qnil.
2296
2565
  */
2297
2566
  VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2298
2567
 
2299
2568
  CURL *curl = rbce->curl;
2300
2569
  struct curl_slist *ftp_commands;
2570
+ struct curl_slist *resolve;
2301
2571
 
2302
2572
  /* Free everything up */
2303
2573
  if (rbce->curl_headers) {
@@ -2305,12 +2575,23 @@ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2305
2575
  rbce->curl_headers = NULL;
2306
2576
  }
2307
2577
 
2578
+ if (rbce->curl_proxy_headers) {
2579
+ curl_slist_free_all(rbce->curl_proxy_headers);
2580
+ rbce->curl_proxy_headers = NULL;
2581
+ }
2582
+
2308
2583
  ftp_commands = rbce->curl_ftp_commands;
2309
2584
  if (ftp_commands) {
2310
2585
  curl_slist_free_all(ftp_commands);
2311
2586
  rbce->curl_ftp_commands = NULL;
2312
2587
  }
2313
2588
 
2589
+ resolve = rbce->curl_resolve;
2590
+ if (resolve) {
2591
+ curl_slist_free_all(resolve);
2592
+ rbce->curl_resolve = NULL;
2593
+ }
2594
+
2314
2595
  /* clean up a PUT request's curl options. */
2315
2596
  if (!rb_easy_nil("upload")) {
2316
2597
  rb_easy_del("upload"); // set the upload object to Qnil to let the GC clean up
@@ -2320,6 +2601,9 @@ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2320
2601
  curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0);
2321
2602
  }
2322
2603
 
2604
+ // set values on cleanup to nil
2605
+ rb_easy_del("multi");
2606
+
2323
2607
  return Qnil;
2324
2608
  }
2325
2609
 
@@ -2334,6 +2618,8 @@ static VALUE ruby_curl_easy_perform_verb_str(VALUE self, const char *verb) {
2334
2618
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2335
2619
  curl = rbce->curl;
2336
2620
 
2621
+ memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
2622
+
2337
2623
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb);
2338
2624
 
2339
2625
  retval = rb_funcall(self, rb_intern("perform"), 0);
@@ -2399,6 +2685,8 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2399
2685
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2400
2686
  curl = rbce->curl;
2401
2687
 
2688
+ memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
2689
+
2402
2690
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2403
2691
 
2404
2692
  if (rbce->multipart_form_post) {
@@ -2470,6 +2758,8 @@ static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
2470
2758
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2471
2759
  curl = rbce->curl;
2472
2760
 
2761
+ memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
2762
+
2473
2763
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2474
2764
  ruby_curl_easy_put_data_set(self, data);
2475
2765
 
@@ -2488,6 +2778,10 @@ static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
2488
2778
  * your own body handler, this string will be empty.
2489
2779
  */
2490
2780
  static VALUE ruby_curl_easy_body_str_get(VALUE self) {
2781
+ /*
2782
+ TODO: can we force_encoding on the return here if we see charset=utf-8 in the content-type header?
2783
+ Content-Type: application/json; charset=utf-8
2784
+ */
2491
2785
  CURB_OBJECT_HGETTER(ruby_curl_easy, body_data);
2492
2786
  }
2493
2787
 
@@ -2564,7 +2858,7 @@ static VALUE ruby_curl_easy_response_code_get(VALUE self) {
2564
2858
  static VALUE ruby_curl_easy_primary_ip_get(VALUE self) {
2565
2859
  ruby_curl_easy *rbce;
2566
2860
  char* ip;
2567
-
2861
+
2568
2862
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2569
2863
  curl_easy_getinfo(rbce->curl, CURLINFO_PRIMARY_IP, &ip);
2570
2864
 
@@ -2682,7 +2976,7 @@ static VALUE ruby_curl_easy_connect_time_get(VALUE self) {
2682
2976
  * Retrieve the time, in seconds, it took from the start until the SSL/SSH
2683
2977
  * connect/handshake to the remote host was completed. This time is most often
2684
2978
  * very near to the pre transfer time, except for cases such as HTTP
2685
- * pippelining where the pretransfer time can be delayed due to waits in line
2979
+ * pipelining where the pretransfer time can be delayed due to waits in line
2686
2980
  * for the pipeline and more.
2687
2981
  */
2688
2982
  #if defined(HAVE_CURLINFO_APPCONNECT_TIME)
@@ -2789,7 +3083,7 @@ static VALUE ruby_curl_easy_redirect_count_get(VALUE self) {
2789
3083
  * call-seq:
2790
3084
  * easy.redirect_url => "http://some.url" or nil
2791
3085
  *
2792
- * Retrieve the URL a redirect would take you to if you
3086
+ * Retrieve the URL a redirect would take you to if you
2793
3087
  * would enable CURLOPT_FOLLOWLOCATION.
2794
3088
  *
2795
3089
  * Requires libcurl 7.18.2 or higher, otherwise -1 is always returned.
@@ -3072,12 +3366,40 @@ static VALUE ruby_curl_easy_num_connects_get(VALUE self) {
3072
3366
  }
3073
3367
 
3074
3368
 
3075
- /* TODO this needs to be implemented.
3369
+ /*
3370
+ * call-seq:
3371
+ * easy.cookielist => array
3372
+ *
3373
+ * Retrieves the cookies curl knows in an array of strings.
3374
+ * Returned strings are in Netscape cookiejar format or in Set-Cookie format.
3375
+ *
3376
+ * See also option CURLINFO_COOKIELIST of curl_easy_getopt(3) to see how libcurl behaves.
3377
+ *
3378
+ * (requires libcurl 7.14.1 or higher, otherwise -1 is always returned).
3379
+ */
3380
+ static VALUE ruby_curl_easy_cookielist_get(VALUE self) {
3381
+ #ifdef HAVE_CURLINFO_COOKIELIST
3382
+ ruby_curl_easy *rbce;
3383
+ struct curl_slist *cookies;
3384
+ struct curl_slist *cookie;
3385
+ VALUE rb_cookies;
3386
+
3387
+ Data_Get_Struct(self, ruby_curl_easy, rbce);
3388
+ curl_easy_getinfo(rbce->curl, CURLINFO_COOKIELIST, &cookies);
3389
+ if (!cookies)
3390
+ return Qnil;
3391
+ rb_cookies = rb_ary_new();
3392
+ for (cookie = cookies; cookie; cookie = cookie->next)
3393
+ rb_ary_push(rb_cookies, rb_str_new2(cookie->data));
3394
+ curl_slist_free_all(cookies);
3395
+ return rb_cookies;
3076
3396
 
3077
- CURLINFO_COOKIELIST
3397
+ #else
3398
+ rb_warn("Installed libcurl is too old to support cookielist");
3399
+ return INT2FIX(-1);
3400
+ #endif
3401
+ }
3078
3402
 
3079
- 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)
3080
- */
3081
3403
 
3082
3404
  /* TODO this needs to be implemented. Could probably support CONNECT_ONLY by having this
3083
3405
  * return an open Socket or something.
@@ -3147,6 +3469,21 @@ static VALUE ruby_curl_easy_last_result(VALUE self) {
3147
3469
  return LONG2NUM(rbce->last_result);
3148
3470
  }
3149
3471
 
3472
+ /*
3473
+ * call-seq:
3474
+ * easy.last_error => "Error details" or nil
3475
+ */
3476
+ static VALUE ruby_curl_easy_last_error(VALUE self) {
3477
+ ruby_curl_easy *rbce;
3478
+ Data_Get_Struct(self, ruby_curl_easy, rbce);
3479
+
3480
+ if (rbce->err_buf[0]) { // curl returns NULL or empty string if none
3481
+ return rb_str_new2(rbce->err_buf);
3482
+ } else {
3483
+ return Qnil;
3484
+ }
3485
+ }
3486
+
3150
3487
  /*
3151
3488
  * call-seq:
3152
3489
  * easy.setopt Fixnum, value => value
@@ -3156,6 +3493,7 @@ static VALUE ruby_curl_easy_last_result(VALUE self) {
3156
3493
  static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3157
3494
  ruby_curl_easy *rbce;
3158
3495
  long option = NUM2LONG(opt);
3496
+ rb_io_t *open_f_ptr;
3159
3497
 
3160
3498
  Data_Get_Struct(self, ruby_curl_easy, rbce);
3161
3499
 
@@ -3193,6 +3531,12 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3193
3531
  case CURLOPT_HEADER:
3194
3532
  case CURLOPT_NOPROGRESS:
3195
3533
  case CURLOPT_NOSIGNAL:
3534
+ #if HAVE_CURLOPT_PATH_AS_IS
3535
+ case CURLOPT_PATH_AS_IS:
3536
+ #endif
3537
+ #if HAVE_CURLOPT_PIPEWAIT
3538
+ case CURLOPT_PIPEWAIT:
3539
+ #endif
3196
3540
  case CURLOPT_HTTPGET:
3197
3541
  case CURLOPT_NOBODY: {
3198
3542
  int type = rb_type(val);
@@ -3238,6 +3582,9 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3238
3582
  case CURLOPT_TCP_NODELAY: {
3239
3583
  curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val));
3240
3584
  } break;
3585
+ case CURLOPT_RANGE: {
3586
+ curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val));
3587
+ } break;
3241
3588
  case CURLOPT_RESUME_FROM: {
3242
3589
  curl_easy_setopt(rbce->curl, CURLOPT_RESUME_FROM, NUM2LONG(val));
3243
3590
  } break;
@@ -3259,6 +3606,53 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3259
3606
  case CURLOPT_UNIX_SOCKET_PATH: {
3260
3607
  curl_easy_setopt(rbce->curl, CURLOPT_UNIX_SOCKET_PATH, StringValueCStr(val));
3261
3608
  } break;
3609
+ #endif
3610
+ #if HAVE_CURLOPT_MAX_SEND_SPEED_LARGE
3611
+ case CURLOPT_MAX_SEND_SPEED_LARGE: {
3612
+ curl_easy_setopt(rbce->curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t) NUM2LL(val));
3613
+ } break;
3614
+ #endif
3615
+ #if HAVE_CURLOPT_MAX_RECV_SPEED_LARGE
3616
+ case CURLOPT_MAX_RECV_SPEED_LARGE: {
3617
+ curl_easy_setopt(rbce->curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) NUM2LL(val));
3618
+ } break;
3619
+ #endif
3620
+ #if HAVE_CURLOPT_MAXFILESIZE
3621
+ case CURLOPT_MAXFILESIZE:
3622
+ curl_easy_setopt(rbce->curl, CURLOPT_MAXFILESIZE, NUM2LONG(val));
3623
+ break;
3624
+ #endif
3625
+ #if HAVE_CURLOPT_TCP_KEEPALIVE
3626
+ case CURLOPT_TCP_KEEPALIVE:
3627
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPALIVE, NUM2LONG(val));
3628
+ break;
3629
+ case CURLOPT_TCP_KEEPIDLE:
3630
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPIDLE, NUM2LONG(val));
3631
+ break;
3632
+ case CURLOPT_TCP_KEEPINTVL:
3633
+ curl_easy_setopt(rbce->curl, CURLOPT_TCP_KEEPINTVL, NUM2LONG(val));
3634
+ break;
3635
+ #endif
3636
+ #if HAVE_CURLOPT_HAPROXYPROTOCOL
3637
+ case CURLOPT_HAPROXYPROTOCOL:
3638
+ curl_easy_setopt(rbce->curl, CURLOPT_HAPROXYPROTOCOL, NUM2LONG(val));
3639
+ break;
3640
+ #endif
3641
+ case CURLOPT_STDERR:
3642
+ // libcurl requires raw FILE pointer and this should be IO object in Ruby.
3643
+ // Tempfile or StringIO won't work.
3644
+ Check_Type(val, T_FILE);
3645
+ GetOpenFile(val, open_f_ptr);
3646
+ curl_easy_setopt(rbce->curl, CURLOPT_STDERR, rb_io_stdio_file(open_f_ptr));
3647
+ break;
3648
+ case CURLOPT_PROTOCOLS:
3649
+ case CURLOPT_REDIR_PROTOCOLS:
3650
+ curl_easy_setopt(rbce->curl, option, NUM2LONG(val));
3651
+ break;
3652
+ #if HAVE_CURLOPT_SSL_SESSIONID_CACHE
3653
+ case CURLOPT_SSL_SESSIONID_CACHE:
3654
+ curl_easy_setopt(rbce->curl, CURLOPT_SSL_SESSIONID_CACHE, NUM2LONG(val));
3655
+ break;
3262
3656
  #endif
3263
3657
  default:
3264
3658
  rb_raise(rb_eTypeError, "Curb unsupported option");
@@ -3386,12 +3780,19 @@ void init_curb_easy() {
3386
3780
  cCurlEasy = rb_define_class_under(mCurl, "Easy", rb_cObject);
3387
3781
 
3388
3782
  /* Class methods */
3389
- rb_define_singleton_method(cCurlEasy, "new", ruby_curl_easy_new, -1);
3783
+ rb_define_alloc_func(cCurlEasy, ruby_curl_easy_allocate);
3390
3784
  rb_define_singleton_method(cCurlEasy, "error", ruby_curl_easy_error_message, 1);
3391
3785
 
3786
+ /* Initialize method */
3787
+ rb_define_method(cCurlEasy, "initialize", ruby_curl_easy_initialize, -1);
3788
+
3392
3789
  /* Attributes for config next perform */
3393
3790
  rb_define_method(cCurlEasy, "url", ruby_curl_easy_url_get, 0);
3394
3791
  rb_define_method(cCurlEasy, "proxy_url", ruby_curl_easy_proxy_url_get, 0);
3792
+
3793
+ rb_define_method(cCurlEasy, "proxy_headers=", ruby_curl_easy_proxy_headers_set, 1);
3794
+ rb_define_method(cCurlEasy, "proxy_headers", ruby_curl_easy_proxy_headers_get, 0);
3795
+
3395
3796
  rb_define_method(cCurlEasy, "headers=", ruby_curl_easy_headers_set, 1);
3396
3797
  rb_define_method(cCurlEasy, "headers", ruby_curl_easy_headers_get, 0);
3397
3798
  rb_define_method(cCurlEasy, "interface", ruby_curl_easy_interface_get, 0);
@@ -3418,6 +3819,8 @@ void init_curb_easy() {
3418
3819
  rb_define_method(cCurlEasy, "put_data=", ruby_curl_easy_put_data_set, 1);
3419
3820
  rb_define_method(cCurlEasy, "ftp_commands=", ruby_curl_easy_ftp_commands_set, 1);
3420
3821
  rb_define_method(cCurlEasy, "ftp_commands", ruby_curl_easy_ftp_commands_get, 0);
3822
+ rb_define_method(cCurlEasy, "resolve=", ruby_curl_easy_resolve_set, 1);
3823
+ rb_define_method(cCurlEasy, "resolve", ruby_curl_easy_resolve_get, 0);
3421
3824
 
3422
3825
  rb_define_method(cCurlEasy, "local_port=", ruby_curl_easy_local_port_set, 1);
3423
3826
  rb_define_method(cCurlEasy, "local_port", ruby_curl_easy_local_port_get, 0);
@@ -3449,6 +3852,10 @@ void init_curb_easy() {
3449
3852
  rb_define_method(cCurlEasy, "low_speed_limit", ruby_curl_easy_low_speed_limit_get, 0);
3450
3853
  rb_define_method(cCurlEasy, "low_speed_time=", ruby_curl_easy_low_speed_time_set, 1);
3451
3854
  rb_define_method(cCurlEasy, "low_speed_time", ruby_curl_easy_low_speed_time_get, 0);
3855
+ rb_define_method(cCurlEasy, "max_send_speed_large=", ruby_curl_easy_max_send_speed_large_set, 1);
3856
+ rb_define_method(cCurlEasy, "max_send_speed_large", ruby_curl_easy_max_send_speed_large_get, 0);
3857
+ rb_define_method(cCurlEasy, "max_recv_speed_large=", ruby_curl_easy_max_recv_speed_large_set, 1);
3858
+ rb_define_method(cCurlEasy, "max_recv_speed_large", ruby_curl_easy_max_recv_speed_large_get, 0);
3452
3859
  rb_define_method(cCurlEasy, "ssl_version=", ruby_curl_easy_ssl_version_set, 1);
3453
3860
  rb_define_method(cCurlEasy, "ssl_version", ruby_curl_easy_ssl_version_get, 0);
3454
3861
  rb_define_method(cCurlEasy, "use_ssl=", ruby_curl_easy_use_ssl_set, 1);
@@ -3536,6 +3943,7 @@ void init_curb_easy() {
3536
3943
  rb_define_method(cCurlEasy, "content_type", ruby_curl_easy_content_type_get, 0);
3537
3944
  rb_define_method(cCurlEasy, "os_errno", ruby_curl_easy_os_errno_get, 0);
3538
3945
  rb_define_method(cCurlEasy, "num_connects", ruby_curl_easy_num_connects_get, 0);
3946
+ rb_define_method(cCurlEasy, "cookielist", ruby_curl_easy_cookielist_get, 0);
3539
3947
  rb_define_method(cCurlEasy, "ftp_entry_path", ruby_curl_easy_ftp_entry_path_get, 0);
3540
3948
 
3541
3949
  rb_define_method(cCurlEasy, "close", ruby_curl_easy_close, 0);
@@ -3553,6 +3961,7 @@ void init_curb_easy() {
3553
3961
  rb_define_method(cCurlEasy, "multi", ruby_curl_easy_multi_get, 0);
3554
3962
  rb_define_method(cCurlEasy, "multi=", ruby_curl_easy_multi_set, 1);
3555
3963
  rb_define_method(cCurlEasy, "last_result", ruby_curl_easy_last_result, 0);
3964
+ rb_define_method(cCurlEasy, "last_error", ruby_curl_easy_last_error, 0);
3556
3965
 
3557
3966
  rb_define_method(cCurlEasy, "setopt", ruby_curl_easy_set_opt, 2);
3558
3967
  rb_define_method(cCurlEasy, "getinfo", ruby_curl_easy_get_opt, 1);