curb 0.9.7 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/ext/curb_easy.c CHANGED
@@ -25,6 +25,12 @@ 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
 
@@ -223,6 +229,10 @@ 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
  }
@@ -243,6 +253,7 @@ static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
243
253
  curl_easy_setopt(rbce->curl, CURLOPT_PROGRESSFUNCTION, NULL);
244
254
  curl_easy_setopt(rbce->curl, CURLOPT_NOPROGRESS, 1);
245
255
  curl_easy_cleanup(rbce->curl);
256
+ rbce->curl = NULL;
246
257
  }
247
258
  }
248
259
 
@@ -257,7 +268,10 @@ void curl_easy_free(ruby_curl_easy *rbce) {
257
268
  static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
258
269
  rbce->opts = rb_hash_new();
259
270
 
271
+ memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
272
+
260
273
  rbce->curl_headers = NULL;
274
+ rbce->curl_proxy_headers = NULL;
261
275
  rbce->curl_ftp_commands = NULL;
262
276
  rbce->curl_resolve = NULL;
263
277
 
@@ -277,6 +291,8 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
277
291
  rbce->ftp_response_timeout = 0;
278
292
  rbce->low_speed_limit = 0;
279
293
  rbce->low_speed_time = 0;
294
+ rbce->max_send_speed_large = 0;
295
+ rbce->max_recv_speed_large = 0;
280
296
  rbce->ssl_version = -1;
281
297
  rbce->use_ssl = -1;
282
298
  rbce->ftp_filemethod = -1;
@@ -313,9 +329,9 @@ static VALUE ruby_curl_easy_allocate(VALUE klass) {
313
329
 
314
330
  /*
315
331
  * call-seq:
316
- * Curl::Easy.new => #<Curl::Easy...>
317
- * Curl::Easy.new(url = nil) => #<Curl::Easy...>
318
- * 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...>
319
335
  *
320
336
  * Initialize a new Curl::Easy instance, optionally supplying the URL.
321
337
  * The block form allows further configuration to be supplied before
@@ -341,9 +357,11 @@ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
341
357
 
342
358
  ruby_curl_easy_zero(rbce);
343
359
 
360
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
361
+
344
362
  rb_easy_set("url", url);
345
363
 
346
- /* set the new_curl pointer to the curl handle */
364
+ /* set the pointer to the curl handle */
347
365
  ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)self);
348
366
  if (ecode != CURLE_OK) {
349
367
  raise_curl_easy_error_exception(ecode);
@@ -358,8 +376,8 @@ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
358
376
 
359
377
  /*
360
378
  * call-seq:
361
- * easy.clone => #&lt;easy clone&gt;
362
- * easy.dup => #&lt;easy clone&gt;
379
+ * easy.clone => <easy clone>
380
+ * easy.dup => <easy clone>
363
381
  *
364
382
  * Clone this Curl::Easy instance, creating a new instance.
365
383
  * This method duplicates the underlying CURL* handle.
@@ -373,9 +391,12 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
373
391
  memcpy(newrbce, rbce, sizeof(ruby_curl_easy));
374
392
  newrbce->curl = curl_easy_duphandle(rbce->curl);
375
393
  newrbce->curl_headers = NULL;
394
+ newrbce->curl_proxy_headers = NULL;
376
395
  newrbce->curl_ftp_commands = NULL;
377
396
  newrbce->curl_resolve = NULL;
378
397
 
398
+ curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
399
+
379
400
  return Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, newrbce);
380
401
  }
381
402
 
@@ -445,7 +466,9 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
445
466
  curl_easy_reset(rbce->curl);
446
467
  ruby_curl_easy_zero(rbce);
447
468
 
448
- /* 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 */
449
472
  ecode = curl_easy_setopt(rbce->curl, CURLOPT_PRIVATE, (void*)self);
450
473
  if (ecode != CURLE_OK) {
451
474
  raise_curl_easy_error_exception(ecode);
@@ -457,6 +480,12 @@ static VALUE ruby_curl_easy_reset(VALUE self) {
457
480
  rbce->curl_headers = NULL;
458
481
  }
459
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
+
460
489
  return opts_dup;
461
490
  }
462
491
 
@@ -509,6 +538,10 @@ static VALUE ruby_curl_easy_headers_set(VALUE self, VALUE headers) {
509
538
  CURB_OBJECT_HSETTER(ruby_curl_easy, headers);
510
539
  }
511
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
+
512
545
  /*
513
546
  * call-seq:
514
547
  * easy.headers => Hash, Array or Str
@@ -524,6 +557,41 @@ static VALUE ruby_curl_easy_headers_get(VALUE self) {
524
557
  return headers;
525
558
  }
526
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
+
527
595
  /*
528
596
  * call-seq:
529
597
  * easy.interface => string
@@ -889,7 +957,7 @@ static VALUE ruby_curl_easy_ftp_commands_set(VALUE self, VALUE ftp_commands) {
889
957
  }
890
958
 
891
959
  /*
892
- * call-seq
960
+ * call-seq:
893
961
  * easy.ftp_commands => array or nil
894
962
  */
895
963
  static VALUE ruby_curl_easy_ftp_commands_get(VALUE self) {
@@ -908,7 +976,7 @@ static VALUE ruby_curl_easy_resolve_set(VALUE self, VALUE resolve) {
908
976
  }
909
977
 
910
978
  /*
911
- * call-seq
979
+ * call-seq:
912
980
  * easy.resolve => array or nil
913
981
  */
914
982
  static VALUE ruby_curl_easy_resolve_get(VALUE self) {
@@ -1139,7 +1207,7 @@ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) {
1139
1207
 
1140
1208
  /*
1141
1209
  * call-seq:
1142
- * easy.timeout = fixnum or nil => fixnum or nil
1210
+ * easy.timeout = float, fixnum or nil => numeric
1143
1211
  *
1144
1212
  * Set the maximum time in seconds that you allow the libcurl transfer
1145
1213
  * operation to take. Normally, name lookups can take a considerable time
@@ -1148,20 +1216,39 @@ static VALUE ruby_curl_easy_max_redirects_get(VALUE self) {
1148
1216
  *
1149
1217
  * Set to nil (or zero) to disable timeout (it will then only timeout
1150
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
+ *
1151
1224
  */
1152
- static VALUE ruby_curl_easy_timeout_set(VALUE self, VALUE timeout) {
1153
- 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);
1154
1236
  }
1155
1237
 
1156
1238
  /*
1157
1239
  * call-seq:
1158
- * easy.timeout => fixnum or nil
1240
+ * easy.timeout => numeric
1159
1241
  *
1160
1242
  * Obtain the maximum time in seconds that you allow the libcurl transfer
1161
1243
  * operation to take.
1244
+ *
1245
+ * Uses timeout_ms internally instead of timeout.
1246
+ *
1162
1247
  */
1163
- static VALUE ruby_curl_easy_timeout_get(VALUE self, VALUE timeout) {
1164
- 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);
1165
1252
  }
1166
1253
 
1167
1254
  /*
@@ -1177,7 +1264,16 @@ static VALUE ruby_curl_easy_timeout_get(VALUE self, VALUE timeout) {
1177
1264
  * on the system's internal timeouts).
1178
1265
  */
1179
1266
  static VALUE ruby_curl_easy_timeout_ms_set(VALUE self, VALUE timeout_ms) {
1180
- 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);
1181
1277
  }
1182
1278
 
1183
1279
  /*
@@ -1187,8 +1283,10 @@ static VALUE ruby_curl_easy_timeout_ms_set(VALUE self, VALUE timeout_ms) {
1187
1283
  * Obtain the maximum time in milliseconds that you allow the libcurl transfer
1188
1284
  * operation to take.
1189
1285
  */
1190
- static VALUE ruby_curl_easy_timeout_ms_get(VALUE self, VALUE timeout_ms) {
1191
- 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);
1192
1290
  }
1193
1291
 
1194
1292
  /*
@@ -1338,6 +1436,46 @@ static VALUE ruby_curl_easy_low_speed_time_get(VALUE self, VALUE low_speed_time)
1338
1436
  CURB_IMMED_GETTER(ruby_curl_easy, low_speed_time, 0);
1339
1437
  }
1340
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
+
1341
1479
  /*
1342
1480
  * call-seq:
1343
1481
  * easy.username = string => string
@@ -1408,6 +1546,7 @@ static VALUE ruby_curl_easy_password_get(VALUE self, VALUE password) {
1408
1546
  * Curl::CURL_SSLVERSION_TLSv1_0
1409
1547
  * Curl::CURL_SSLVERSION_TLSv1_1
1410
1548
  * Curl::CURL_SSLVERSION_TLSv1_2
1549
+ * Curl::CURL_SSLVERSION_TLSv1_3
1411
1550
  */
1412
1551
  static VALUE ruby_curl_easy_ssl_version_set(VALUE self, VALUE ssl_version) {
1413
1552
  CURB_IMMED_SETTER(ruby_curl_easy, ssl_version, -1);
@@ -1456,7 +1595,7 @@ static VALUE ruby_curl_easy_ftp_filemethod_set(VALUE self, VALUE ftp_filemethod)
1456
1595
  }
1457
1596
 
1458
1597
  /*
1459
- * call-seq
1598
+ * call-seq:
1460
1599
  * easy.ftp_filemethod => fixnum
1461
1600
  *
1462
1601
  * Get the configuration for how libcurl will reach files on the server.
@@ -1828,7 +1967,7 @@ static VALUE ruby_curl_easy_resolve_mode_set(VALUE self, VALUE resolve_mode) {
1828
1967
 
1829
1968
  /*
1830
1969
  * call-seq:
1831
- * easy.on_body { |body_data| ... } => &lt;old handler&gt;
1970
+ * easy.on_body { |body_data| ... } => <old handler>
1832
1971
  *
1833
1972
  * Assign or remove the +on_body+ handler for this Curl::Easy instance.
1834
1973
  * To remove a previously-supplied handler, call this method with no
@@ -1847,7 +1986,7 @@ static VALUE ruby_curl_easy_on_body_set(int argc, VALUE *argv, VALUE self) {
1847
1986
 
1848
1987
  /*
1849
1988
  * call-seq:
1850
- * easy.on_success { |easy| ... } => &lt;old handler&gt;
1989
+ * easy.on_success { |easy| ... } => <old handler>
1851
1990
  *
1852
1991
  * Assign or remove the +on_success+ handler for this Curl::Easy instance.
1853
1992
  * To remove a previously-supplied handler, call this method with no
@@ -1862,7 +2001,7 @@ static VALUE ruby_curl_easy_on_success_set(int argc, VALUE *argv, VALUE self) {
1862
2001
 
1863
2002
  /*
1864
2003
  * call-seq:
1865
- * easy.on_failure {|easy,code| ... } => &lt;old handler&gt;
2004
+ * easy.on_failure {|easy,code| ... } => <old handler>
1866
2005
  *
1867
2006
  * Assign or remove the +on_failure+ handler for this Curl::Easy instance.
1868
2007
  * To remove a previously-supplied handler, call this method with no
@@ -1877,7 +2016,7 @@ static VALUE ruby_curl_easy_on_failure_set(int argc, VALUE *argv, VALUE self) {
1877
2016
 
1878
2017
  /*
1879
2018
  * call-seq:
1880
- * easy.on_missing {|easy,code| ... } => &lt;old handler;&gt;
2019
+ * easy.on_missing {|easy,code| ... } => <old handler;>
1881
2020
  *
1882
2021
  * Assign or remove the on_missing handler for this Curl::Easy instance.
1883
2022
  * To remove a previously-supplied handler, call this method with no attached
@@ -1892,7 +2031,7 @@ static VALUE ruby_curl_easy_on_missing_set(int argc, VALUE *argv, VALUE self) {
1892
2031
 
1893
2032
  /*
1894
2033
  * call-seq:
1895
- * easy.on_redirect {|easy,code| ... } => &lt;old handler;&gt;
2034
+ * easy.on_redirect {|easy,code| ... } => <old handler;>
1896
2035
  *
1897
2036
  * Assign or remove the on_redirect handler for this Curl::Easy instance.
1898
2037
  * To remove a previously-supplied handler, call this method with no attached
@@ -1907,7 +2046,7 @@ static VALUE ruby_curl_easy_on_redirect_set(int argc, VALUE *argv, VALUE self) {
1907
2046
 
1908
2047
  /*
1909
2048
  * call-seq:
1910
- * easy.on_complete {|easy| ... } => &lt;old handler&gt;
2049
+ * easy.on_complete {|easy| ... } => <old handler>
1911
2050
  *
1912
2051
  * Assign or remove the +on_complete+ handler for this Curl::Easy instance.
1913
2052
  * To remove a previously-supplied handler, call this method with no
@@ -1921,7 +2060,7 @@ static VALUE ruby_curl_easy_on_complete_set(int argc, VALUE *argv, VALUE self) {
1921
2060
 
1922
2061
  /*
1923
2062
  * call-seq:
1924
- * easy.on_header { |header_data| ... } => &lt;old handler&gt;
2063
+ * easy.on_header { |header_data| ... } => <old handler>
1925
2064
  *
1926
2065
  * Assign or remove the +on_header+ handler for this Curl::Easy instance.
1927
2066
  * To remove a previously-supplied handler, call this method with no
@@ -1937,7 +2076,7 @@ static VALUE ruby_curl_easy_on_header_set(int argc, VALUE *argv, VALUE self) {
1937
2076
 
1938
2077
  /*
1939
2078
  * call-seq:
1940
- * 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>
1941
2080
  *
1942
2081
  * Assign or remove the +on_progress+ handler for this Curl::Easy instance.
1943
2082
  * To remove a previously-supplied handler, call this method with no
@@ -1958,7 +2097,7 @@ static VALUE ruby_curl_easy_on_progress_set(int argc, VALUE *argv, VALUE self) {
1958
2097
 
1959
2098
  /*
1960
2099
  * call-seq:
1961
- * easy.on_debug { |type, data| ... } => &lt;old handler&gt;
2100
+ * easy.on_debug { |type, data| ... } => <old handler>
1962
2101
  *
1963
2102
  * Assign or remove the +on_debug+ handler for this Curl::Easy instance.
1964
2103
  * To remove a previously-supplied handler, call this method with no
@@ -1997,11 +2136,14 @@ static VALUE cb_each_http_header(VALUE header, VALUE wrap) {
1997
2136
 
1998
2137
  name = rb_obj_as_string(rb_ary_entry(header, 0));
1999
2138
  value = rb_obj_as_string(rb_ary_entry(header, 1));
2000
-
2001
- // This is a bit inefficient, but we don't want to be modifying
2002
- // the actual values in the original hash.
2003
- header_str = rb_str_plus(name, rb_str_new2(": "));
2004
- 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
+ }
2005
2147
  } else {
2006
2148
  header_str = rb_obj_as_string(header);
2007
2149
  }
@@ -2012,6 +2154,38 @@ static VALUE cb_each_http_header(VALUE header, VALUE wrap) {
2012
2154
  return header_str;
2013
2155
  }
2014
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
+
2015
2189
  /***********************************************
2016
2190
  * This is an rb_iterate callback used to set up ftp commands.
2017
2191
  */
@@ -2051,6 +2225,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2051
2225
  CURL *curl;
2052
2226
  VALUE url, _url = rb_easy_get("url");
2053
2227
  struct curl_slist **hdrs = &(rbce->curl_headers);
2228
+ struct curl_slist **phdrs = &(rbce->curl_proxy_headers);
2054
2229
  struct curl_slist **cmds = &(rbce->curl_ftp_commands);
2055
2230
  struct curl_slist **rslv = &(rbce->curl_resolve);
2056
2231
 
@@ -2061,7 +2236,6 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2061
2236
  }
2062
2237
 
2063
2238
  url = rb_check_string_type(_url);
2064
-
2065
2239
  curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
2066
2240
 
2067
2241
  // network stuff and auth
@@ -2174,15 +2348,14 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2174
2348
 
2175
2349
  curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, rbce->unrestricted_auth);
2176
2350
 
2177
- /* timeouts override each other we cannot blindly set timeout and timeout_ms */
2178
- if (rbce->timeout && rbce->timeout > 0) {
2179
- curl_easy_setopt(curl, CURLOPT_TIMEOUT, rbce->timeout);
2180
- }
2181
- if (rbce->timeout_ms && rbce->timeout_ms > 0) {
2182
- curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, rbce->timeout_ms);
2183
- }
2351
+ #if HAVE_CURLOPT_TIMEOUT_MS
2352
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, rbce->timeout_ms);
2353
+ #endif
2354
+
2184
2355
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, rbce->connect_timeout);
2356
+ #if HAVE_CURLOPT_CONNECTTIMEOUT_MS
2185
2357
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, rbce->connect_timeout_ms);
2358
+ #endif
2186
2359
  curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, rbce->dns_cache_timeout);
2187
2360
 
2188
2361
  curl_easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, rbce->ignore_content_length);
@@ -2201,6 +2374,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2201
2374
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, rbce->low_speed_limit);
2202
2375
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, rbce->low_speed_time);
2203
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
+
2204
2380
  // Set up localport / proxy port
2205
2381
  // FIXME these won't get returned to default if they're unset Ruby
2206
2382
  if (rbce->proxy_port > 0) {
@@ -2334,6 +2510,25 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2334
2510
  }
2335
2511
  }
2336
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
+
2337
2532
  /* Setup FTP commands if necessary */
2338
2533
  if (!rb_easy_nil("ftp_commands")) {
2339
2534
  if (rb_easy_type_check("ftp_commands", T_ARRAY)) {
@@ -2366,7 +2561,7 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
2366
2561
  *
2367
2562
  * Clean up a connection
2368
2563
  *
2369
- * Always returns Qtrue.
2564
+ * Always returns Qnil.
2370
2565
  */
2371
2566
  VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2372
2567
 
@@ -2380,6 +2575,11 @@ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2380
2575
  rbce->curl_headers = NULL;
2381
2576
  }
2382
2577
 
2578
+ if (rbce->curl_proxy_headers) {
2579
+ curl_slist_free_all(rbce->curl_proxy_headers);
2580
+ rbce->curl_proxy_headers = NULL;
2581
+ }
2582
+
2383
2583
  ftp_commands = rbce->curl_ftp_commands;
2384
2584
  if (ftp_commands) {
2385
2585
  curl_slist_free_all(ftp_commands);
@@ -2401,6 +2601,9 @@ VALUE ruby_curl_easy_cleanup( VALUE self, ruby_curl_easy *rbce ) {
2401
2601
  curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0);
2402
2602
  }
2403
2603
 
2604
+ // set values on cleanup to nil
2605
+ rb_easy_del("multi");
2606
+
2404
2607
  return Qnil;
2405
2608
  }
2406
2609
 
@@ -2415,6 +2618,8 @@ static VALUE ruby_curl_easy_perform_verb_str(VALUE self, const char *verb) {
2415
2618
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2416
2619
  curl = rbce->curl;
2417
2620
 
2621
+ memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
2622
+
2418
2623
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb);
2419
2624
 
2420
2625
  retval = rb_funcall(self, rb_intern("perform"), 0);
@@ -2480,6 +2685,8 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2480
2685
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2481
2686
  curl = rbce->curl;
2482
2687
 
2688
+ memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
2689
+
2483
2690
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2484
2691
 
2485
2692
  if (rbce->multipart_form_post) {
@@ -2551,6 +2758,8 @@ static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
2551
2758
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2552
2759
  curl = rbce->curl;
2553
2760
 
2761
+ memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
2762
+
2554
2763
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2555
2764
  ruby_curl_easy_put_data_set(self, data);
2556
2765
 
@@ -2569,6 +2778,10 @@ static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
2569
2778
  * your own body handler, this string will be empty.
2570
2779
  */
2571
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
+ */
2572
2785
  CURB_OBJECT_HGETTER(ruby_curl_easy, body_data);
2573
2786
  }
2574
2787
 
@@ -2763,7 +2976,7 @@ static VALUE ruby_curl_easy_connect_time_get(VALUE self) {
2763
2976
  * Retrieve the time, in seconds, it took from the start until the SSL/SSH
2764
2977
  * connect/handshake to the remote host was completed. This time is most often
2765
2978
  * very near to the pre transfer time, except for cases such as HTTP
2766
- * 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
2767
2980
  * for the pipeline and more.
2768
2981
  */
2769
2982
  #if defined(HAVE_CURLINFO_APPCONNECT_TIME)
@@ -3256,6 +3469,21 @@ static VALUE ruby_curl_easy_last_result(VALUE self) {
3256
3469
  return LONG2NUM(rbce->last_result);
3257
3470
  }
3258
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
+
3259
3487
  /*
3260
3488
  * call-seq:
3261
3489
  * easy.setopt Fixnum, value => value
@@ -3265,6 +3493,7 @@ static VALUE ruby_curl_easy_last_result(VALUE self) {
3265
3493
  static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3266
3494
  ruby_curl_easy *rbce;
3267
3495
  long option = NUM2LONG(opt);
3496
+ rb_io_t *open_f_ptr;
3268
3497
 
3269
3498
  Data_Get_Struct(self, ruby_curl_easy, rbce);
3270
3499
 
@@ -3353,6 +3582,9 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3353
3582
  case CURLOPT_TCP_NODELAY: {
3354
3583
  curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val));
3355
3584
  } break;
3585
+ case CURLOPT_RANGE: {
3586
+ curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val));
3587
+ } break;
3356
3588
  case CURLOPT_RESUME_FROM: {
3357
3589
  curl_easy_setopt(rbce->curl, CURLOPT_RESUME_FROM, NUM2LONG(val));
3358
3590
  } break;
@@ -3384,6 +3616,43 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3384
3616
  case CURLOPT_MAX_RECV_SPEED_LARGE: {
3385
3617
  curl_easy_setopt(rbce->curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) NUM2LL(val));
3386
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;
3387
3656
  #endif
3388
3657
  default:
3389
3658
  rb_raise(rb_eTypeError, "Curb unsupported option");
@@ -3520,6 +3789,10 @@ void init_curb_easy() {
3520
3789
  /* Attributes for config next perform */
3521
3790
  rb_define_method(cCurlEasy, "url", ruby_curl_easy_url_get, 0);
3522
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
+
3523
3796
  rb_define_method(cCurlEasy, "headers=", ruby_curl_easy_headers_set, 1);
3524
3797
  rb_define_method(cCurlEasy, "headers", ruby_curl_easy_headers_get, 0);
3525
3798
  rb_define_method(cCurlEasy, "interface", ruby_curl_easy_interface_get, 0);
@@ -3579,6 +3852,10 @@ void init_curb_easy() {
3579
3852
  rb_define_method(cCurlEasy, "low_speed_limit", ruby_curl_easy_low_speed_limit_get, 0);
3580
3853
  rb_define_method(cCurlEasy, "low_speed_time=", ruby_curl_easy_low_speed_time_set, 1);
3581
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);
3582
3859
  rb_define_method(cCurlEasy, "ssl_version=", ruby_curl_easy_ssl_version_set, 1);
3583
3860
  rb_define_method(cCurlEasy, "ssl_version", ruby_curl_easy_ssl_version_get, 0);
3584
3861
  rb_define_method(cCurlEasy, "use_ssl=", ruby_curl_easy_use_ssl_set, 1);
@@ -3684,6 +3961,7 @@ void init_curb_easy() {
3684
3961
  rb_define_method(cCurlEasy, "multi", ruby_curl_easy_multi_get, 0);
3685
3962
  rb_define_method(cCurlEasy, "multi=", ruby_curl_easy_multi_set, 1);
3686
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);
3687
3965
 
3688
3966
  rb_define_method(cCurlEasy, "setopt", ruby_curl_easy_set_opt, 2);
3689
3967
  rb_define_method(cCurlEasy, "getinfo", ruby_curl_easy_get_opt, 1);