curb 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18ca81b9cb8fd33720ab17ba3eff9e9ae4fa1a10542feb60897ac94d7cd801ac
4
- data.tar.gz: e8747cc63a63ca1561288dd9480b6c30c9dda0a371a6909eb1e2ad2a9a8b6947
3
+ metadata.gz: 26faca47e47561b5ab762a32f58e2771db9a39c2482152fa1116a7082a27de91
4
+ data.tar.gz: a138509513b023f12537a1bff7fa11e6ef563cc5c59af860b73f119705ee1023
5
5
  SHA512:
6
- metadata.gz: 112d4c34fd70b3b77a58e133b9c873d5ac7f850d2644fe02a9713771b47be636cfbc2c66b44bbf59e047cc84d83aa496c2e67cbfd8f2987509577589a87d20f0
7
- data.tar.gz: a593f46d71b17af90cb9244ee7a92c133eca76024146d9097806f91ec70f34b4611988141063a7624fd51ea103d872495e1efa94b2b6d718ee0a700ca86ef325
6
+ metadata.gz: 4686d9ad528ba0f1931c95fa95d25266c8ffcc2214c3a5f527e437f30977f94bde4a48b38d27ad6342cc868c620e2fd4bd214baeae435725a1746d0c93a22aef
7
+ data.tar.gz: 2e4ba49a5906448e0ad381dad6017cf01140166ee78f1b025b9245cc79c6ff20b37ffd18abc61a56289e8bdeee0750a68f3d84369da36d360f7a61775da10566
data/ext/curb.h CHANGED
@@ -28,11 +28,11 @@
28
28
  #include "curb_macros.h"
29
29
 
30
30
  // These should be managed from the Rake 'release' task.
31
- #define CURB_VERSION "1.0.6"
32
- #define CURB_VER_NUM 1006
31
+ #define CURB_VERSION "1.0.8"
32
+ #define CURB_VER_NUM 1008
33
33
  #define CURB_VER_MAJ 1
34
34
  #define CURB_VER_MIN 0
35
- #define CURB_VER_MIC 6
35
+ #define CURB_VER_MIC 8
36
36
  #define CURB_VER_PATCH 0
37
37
 
38
38
 
data/ext/curb_easy.c CHANGED
@@ -31,6 +31,7 @@ static FILE * rb_io_stdio_file(rb_io_t *fptr) {
31
31
  return fptr->f;
32
32
  }
33
33
  #endif
34
+ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list);
34
35
 
35
36
  /* ================== CURL HANDLER FUNCS ==============*/
36
37
 
@@ -264,7 +265,7 @@ void curl_easy_free(ruby_curl_easy *rbce) {
264
265
  static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
265
266
  rbce->opts = rb_hash_new();
266
267
 
267
- memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
268
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
268
269
 
269
270
  rbce->curl_headers = NULL;
270
271
  rbce->curl_proxy_headers = NULL;
@@ -370,6 +371,16 @@ static VALUE ruby_curl_easy_initialize(int argc, VALUE *argv, VALUE self) {
370
371
  return self;
371
372
  }
372
373
 
374
+ /* Helper to duplicate a curl_slist */
375
+ static struct curl_slist *duplicate_curl_slist(struct curl_slist *list) {
376
+ struct curl_slist *dup = NULL;
377
+ struct curl_slist *tmp;
378
+ for (tmp = list; tmp; tmp = tmp->next) {
379
+ dup = curl_slist_append(dup, tmp->data);
380
+ }
381
+ return dup;
382
+ }
383
+
373
384
  /*
374
385
  * call-seq:
375
386
  * easy.clone => <easy clone>
@@ -384,14 +395,22 @@ static VALUE ruby_curl_easy_clone(VALUE self) {
384
395
  Data_Get_Struct(self, ruby_curl_easy, rbce);
385
396
 
386
397
  newrbce = ALLOC(ruby_curl_easy);
398
+ /* shallow copy */
387
399
  memcpy(newrbce, rbce, sizeof(ruby_curl_easy));
400
+
401
+ /* now deep copy */
388
402
  newrbce->curl = curl_easy_duphandle(rbce->curl);
389
- newrbce->curl_headers = NULL;
390
- newrbce->curl_proxy_headers = NULL;
391
- newrbce->curl_ftp_commands = NULL;
392
- newrbce->curl_resolve = NULL;
403
+ newrbce->curl_headers = (rbce->curl_headers) ? duplicate_curl_slist(rbce->curl_headers) : NULL;
404
+ newrbce->curl_proxy_headers = (rbce->curl_proxy_headers) ? duplicate_curl_slist(rbce->curl_proxy_headers) : NULL;
405
+ newrbce->curl_ftp_commands = (rbce->curl_ftp_commands) ? duplicate_curl_slist(rbce->curl_ftp_commands) : NULL;
406
+ newrbce->curl_resolve = (rbce->curl_resolve) ? duplicate_curl_slist(rbce->curl_resolve) : NULL;
393
407
 
394
- curl_easy_setopt(rbce->curl, CURLOPT_ERRORBUFFER, &rbce->err_buf);
408
+ if (rbce->opts != Qnil) {
409
+ newrbce->opts = rb_funcall(rbce->opts, rb_intern("dup"), 0);
410
+ }
411
+
412
+ /* Set the error buffer on the new curl handle using the new err_buf */
413
+ curl_easy_setopt(newrbce->curl, CURLOPT_ERRORBUFFER, newrbce->err_buf);
395
414
 
396
415
  return Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, newrbce);
397
416
  }
@@ -2614,7 +2633,7 @@ static VALUE ruby_curl_easy_perform_verb_str(VALUE self, const char *verb) {
2614
2633
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2615
2634
  curl = rbce->curl;
2616
2635
 
2617
- memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
2636
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
2618
2637
 
2619
2638
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb);
2620
2639
 
@@ -2681,7 +2700,7 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2681
2700
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2682
2701
  curl = rbce->curl;
2683
2702
 
2684
- memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
2703
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
2685
2704
 
2686
2705
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2687
2706
 
@@ -2739,6 +2758,88 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2739
2758
  }
2740
2759
  }
2741
2760
 
2761
+ /*
2762
+ * call-seq:
2763
+ * easy.http_patch("url=encoded%20form%20data;and=so%20on") => true
2764
+ * easy.http_patch("url=encoded%20form%20data", "and=so%20on", ...) => true
2765
+ * easy.http_patch("url=encoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
2766
+ * easy.http_patch(Curl::PostField, Curl::PostField ..., Curl::PostField) => true
2767
+ *
2768
+ * PATCH the specified formdata to the currently configured URL using
2769
+ * the current options set for this Curl::Easy instance. This method
2770
+ * always returns true, or raises an exception (defined under
2771
+ * Curl::Err) on error.
2772
+ *
2773
+ * When multipart_form_post is true the multipart logic is used; otherwise,
2774
+ * the arguments are joined into a raw PATCH body.
2775
+ */
2776
+ static VALUE ruby_curl_easy_perform_patch(int argc, VALUE *argv, VALUE self) {
2777
+ ruby_curl_easy *rbce;
2778
+ CURL *curl;
2779
+ int i;
2780
+ VALUE args_ary;
2781
+
2782
+ rb_scan_args(argc, argv, "*", &args_ary);
2783
+ Data_Get_Struct(self, ruby_curl_easy, rbce);
2784
+ curl = rbce->curl;
2785
+
2786
+ /* Clear the error buffer */
2787
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
2788
+
2789
+ /* Set the custom HTTP method to PATCH */
2790
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
2791
+
2792
+ if (rbce->multipart_form_post) {
2793
+ VALUE ret;
2794
+ struct curl_httppost *first = NULL, *last = NULL;
2795
+
2796
+ /* Build the multipart form (same logic as for POST) */
2797
+ for (i = 0; i < argc; i++) {
2798
+ if (rb_obj_is_instance_of(argv[i], cCurlPostField)) {
2799
+ append_to_form(argv[i], &first, &last);
2800
+ } else if (rb_type(argv[i]) == T_ARRAY) {
2801
+ long j, argv_len = RARRAY_LEN(argv[i]);
2802
+ for (j = 0; j < argv_len; ++j) {
2803
+ if (rb_obj_is_instance_of(rb_ary_entry(argv[i], j), cCurlPostField)) {
2804
+ append_to_form(rb_ary_entry(argv[i], j), &first, &last);
2805
+ } else {
2806
+ rb_raise(eCurlErrInvalidPostField,
2807
+ "You must use PostFields only with multipart form posts");
2808
+ return Qnil;
2809
+ }
2810
+ }
2811
+ } else {
2812
+ rb_raise(eCurlErrInvalidPostField,
2813
+ "You must use PostFields only with multipart form posts");
2814
+ return Qnil;
2815
+ }
2816
+ }
2817
+ /* Disable the POST flag */
2818
+ curl_easy_setopt(curl, CURLOPT_POST, 0);
2819
+ /* Use the built multipart form as the request body */
2820
+ curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
2821
+ ret = rb_funcall(self, rb_intern("perform"), 0);
2822
+ curl_formfree(first);
2823
+ return ret;
2824
+ } else {
2825
+ /* Join arguments into a raw PATCH body */
2826
+ VALUE patch_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp);
2827
+ if (patch_body == Qnil) {
2828
+ rb_raise(eCurlErrError, "Failed to join arguments");
2829
+ return Qnil;
2830
+ } else {
2831
+ if (rb_type(patch_body) == T_STRING && RSTRING_LEN(patch_body) > 0) {
2832
+ ruby_curl_easy_post_body_set(self, patch_body);
2833
+ }
2834
+ /* If postdata_buffer is still nil, set it so that the PATCH header is enabled */
2835
+ if (rb_easy_nil("postdata_buffer")) {
2836
+ ruby_curl_easy_post_body_set(self, patch_body);
2837
+ }
2838
+ return rb_funcall(self, rb_intern("perform"), 0);
2839
+ }
2840
+ }
2841
+ }
2842
+
2742
2843
  /*
2743
2844
  * call-seq:
2744
2845
  * easy.http_put(data) => true
@@ -2747,18 +2848,70 @@ static VALUE ruby_curl_easy_perform_post(int argc, VALUE *argv, VALUE self) {
2747
2848
  * current options set for this Curl::Easy instance. This method always
2748
2849
  * returns true, or raises an exception (defined under Curl::Err) on error.
2749
2850
  */
2750
- static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
2851
+ static VALUE ruby_curl_easy_perform_put(int argc, VALUE *argv, VALUE self) {
2751
2852
  ruby_curl_easy *rbce;
2752
2853
  CURL *curl;
2854
+ VALUE args_ary;
2855
+ int i;
2753
2856
 
2857
+ rb_scan_args(argc, argv, "*", &args_ary);
2754
2858
  Data_Get_Struct(self, ruby_curl_easy, rbce);
2755
2859
  curl = rbce->curl;
2756
2860
 
2757
- memset(rbce->err_buf, 0, sizeof(rbce->err_buf));
2758
-
2759
- curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
2760
- ruby_curl_easy_put_data_set(self, data);
2861
+ memset(rbce->err_buf, 0, CURL_ERROR_SIZE);
2862
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
2761
2863
 
2864
+ /* New: if no arguments were provided, treat as an empty PUT */
2865
+ if (RARRAY_LEN(args_ary) == 0) {
2866
+ /* Option 1: explicitly set an empty body */
2867
+ ruby_curl_easy_put_data_set(self, rb_str_new2(""));
2868
+ }
2869
+ /* If a single argument is given and it is a String or responds to read, use legacy behavior */
2870
+ else if (RARRAY_LEN(args_ary) == 1 &&
2871
+ (rb_type(rb_ary_entry(args_ary, 0)) == T_STRING ||
2872
+ rb_respond_to(rb_ary_entry(args_ary, 0), rb_intern("read")))) {
2873
+ ruby_curl_easy_put_data_set(self, rb_ary_entry(args_ary, 0));
2874
+ }
2875
+ /* Otherwise, if multipart_form_post is true, use multipart logic */
2876
+ else if (rbce->multipart_form_post) {
2877
+ VALUE ret;
2878
+ struct curl_httppost *first = NULL, *last = NULL;
2879
+ for (i = 0; i < RARRAY_LEN(args_ary); i++) {
2880
+ VALUE field = rb_ary_entry(args_ary, i);
2881
+ if (rb_obj_is_instance_of(field, cCurlPostField)) {
2882
+ append_to_form(field, &first, &last);
2883
+ } else if (rb_type(field) == T_ARRAY) {
2884
+ long j;
2885
+ for (j = 0; j < RARRAY_LEN(field); j++) {
2886
+ VALUE subfield = rb_ary_entry(field, j);
2887
+ if (rb_obj_is_instance_of(subfield, cCurlPostField)) {
2888
+ append_to_form(subfield, &first, &last);
2889
+ } else {
2890
+ rb_raise(eCurlErrInvalidPostField,
2891
+ "You must use PostFields only with multipart form posts");
2892
+ }
2893
+ }
2894
+ } else {
2895
+ rb_raise(eCurlErrInvalidPostField,
2896
+ "You must use PostFields only with multipart form posts");
2897
+ }
2898
+ }
2899
+ curl_easy_setopt(curl, CURLOPT_POST, 0);
2900
+ curl_easy_setopt(curl, CURLOPT_HTTPPOST, first);
2901
+ /* Set the method explicitly to PUT */
2902
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
2903
+ ret = rb_funcall(self, rb_intern("perform"), 0);
2904
+ curl_formfree(first);
2905
+ return ret;
2906
+ }
2907
+ /* Fallback: join all arguments */
2908
+ else {
2909
+ VALUE post_body = rb_funcall(args_ary, idJoin, 1, rbstrAmp);
2910
+ if (post_body != Qnil && rb_type(post_body) == T_STRING &&
2911
+ RSTRING_LEN(post_body) > 0) {
2912
+ ruby_curl_easy_put_data_set(self, post_body);
2913
+ }
2914
+ }
2762
2915
  return rb_funcall(self, rb_intern("perform"), 0);
2763
2916
  }
2764
2917
 
@@ -3364,14 +3517,16 @@ static VALUE ruby_curl_easy_num_connects_get(VALUE self) {
3364
3517
 
3365
3518
  /*
3366
3519
  * call-seq:
3367
- * easy.cookielist => array
3520
+ * easy.cookielist => cookielist
3368
3521
  *
3369
3522
  * Retrieves the cookies curl knows in an array of strings.
3370
3523
  * Returned strings are in Netscape cookiejar format or in Set-Cookie format.
3524
+ * Since 7.43.0 cookies in the Set-Cookie format without a domain name are not exported.
3371
3525
  *
3372
- * See also option CURLINFO_COOKIELIST of curl_easy_getopt(3) to see how libcurl behaves.
3373
- *
3374
- * (requires libcurl 7.14.1 or higher, otherwise -1 is always returned).
3526
+ * @see https://curl.se/libcurl/c/CURLINFO_COOKIELIST.html option <code>CURLINFO_COOKIELIST</code> of
3527
+ * <code>curl_easy_getopt(3)</code> to see how libcurl behaves.
3528
+ * @note requires libcurl 7.14.1 or higher, otherwise +-1+ is always returned
3529
+ * @return [Array<String>, nil, -1] array of strings, or +nil+ if there are no cookies, or +-1+ if the libcurl version is too old
3375
3530
  */
3376
3531
  static VALUE ruby_curl_easy_cookielist_get(VALUE self) {
3377
3532
  #ifdef HAVE_CURLINFO_COOKIELIST
@@ -3482,9 +3637,16 @@ static VALUE ruby_curl_easy_last_error(VALUE self) {
3482
3637
 
3483
3638
  /*
3484
3639
  * call-seq:
3485
- * easy.setopt Fixnum, value => value
3640
+ * easy.setopt(opt, val) => val
3486
3641
  *
3487
3642
  * Initial access to libcurl curl_easy_setopt
3643
+ *
3644
+ * @param [Fixnum] opt The option to set, see +Curl::CURLOPT_*+ constants
3645
+ * @param [Object] val
3646
+ * @return [Object] val
3647
+ * @raise [TypeError] if the option is not supported
3648
+ * @note Some options - like url or cookie - aren't set directly throught +curl_easy_setopt+, but stored in the Ruby object state.
3649
+ * @note When +curl_easy_setopt+ is called, return value is not checked here.
3488
3650
  */
3489
3651
  static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3490
3652
  ruby_curl_easy *rbce;
@@ -3650,6 +3812,11 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3650
3812
  curl_easy_setopt(rbce->curl, CURLOPT_SSL_SESSIONID_CACHE, NUM2LONG(val));
3651
3813
  break;
3652
3814
  #endif
3815
+ #if HAVE_CURLOPT_COOKIELIST
3816
+ case CURLOPT_COOKIELIST: {
3817
+ curl_easy_setopt(rbce->curl, CURLOPT_COOKIELIST, StringValueCStr(val));
3818
+ } break;
3819
+ #endif
3653
3820
  #if HAVE_CURLOPT_PROXY_SSL_VERIFYHOST
3654
3821
  case CURLOPT_PROXY_SSL_VERIFYHOST:
3655
3822
  curl_easy_setopt(rbce->curl, CURLOPT_PROXY_SSL_VERIFYHOST, NUM2LONG(val));
@@ -3664,9 +3831,13 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
3664
3831
 
3665
3832
  /*
3666
3833
  * call-seq:
3667
- * easy.getinfo Fixnum => value
3834
+ * easy.getinfo(opt) => nil
3668
3835
  *
3669
3836
  * Iniital access to libcurl curl_easy_getinfo, remember getinfo doesn't return the same values as setopt
3837
+ *
3838
+ * @note This method is not implemented yet.
3839
+ * @param [Fixnum] code Constant +CURLINFO_*+ from libcurl
3840
+ * @return [nil]
3670
3841
  */
3671
3842
  static VALUE ruby_curl_easy_get_opt(VALUE self, VALUE opt) {
3672
3843
  return Qnil;
@@ -3909,7 +4080,8 @@ void init_curb_easy() {
3909
4080
 
3910
4081
  rb_define_method(cCurlEasy, "http", ruby_curl_easy_perform_verb, 1);
3911
4082
  rb_define_method(cCurlEasy, "http_post", ruby_curl_easy_perform_post, -1);
3912
- rb_define_method(cCurlEasy, "http_put", ruby_curl_easy_perform_put, 1);
4083
+ rb_define_method(cCurlEasy, "http_put", ruby_curl_easy_perform_put, -1);
4084
+ rb_define_method(cCurlEasy, "http_patch", ruby_curl_easy_perform_patch, -1);
3913
4085
 
3914
4086
  /* Post-perform info methods */
3915
4087
  rb_define_method(cCurlEasy, "body_str", ruby_curl_easy_body_str_get, 0);
data/ext/curb_multi.c CHANGED
@@ -27,6 +27,21 @@
27
27
  #include <fcntl.h>
28
28
  #endif
29
29
 
30
+ #ifdef HAVE_CURL_MULTI_WAIT
31
+ #include <stdint.h> /* for intptr_t */
32
+
33
+ struct wait_args {
34
+ CURLM *handle;
35
+ long timeout_ms;
36
+ int numfds;
37
+ };
38
+ static void *curl_multi_wait_wrapper(void *p) {
39
+ struct wait_args *args = p;
40
+ CURLMcode code = curl_multi_wait(args->handle, NULL, 0, args->timeout_ms, &args->numfds);
41
+ return (void *)(intptr_t)code;
42
+ }
43
+ #endif
44
+
30
45
  extern VALUE mCurl;
31
46
  static VALUE idCall;
32
47
 
@@ -226,6 +241,8 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
226
241
 
227
242
  mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
228
243
  if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
244
+ ruby_curl_easy_cleanup(easy, rbce);
245
+
229
246
  raise_curl_multi_error_exception(mcode);
230
247
  }
231
248
 
@@ -264,6 +281,7 @@ VALUE ruby_curl_multi_remove(VALUE self, VALUE rb_easy_handle) {
264
281
 
265
282
  return self;
266
283
  }
284
+
267
285
  static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
268
286
  CURLMcode result;
269
287
  ruby_curl_easy *rbce;
@@ -528,7 +546,6 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
528
546
 
529
547
  do {
530
548
  while (rbcm->running) {
531
-
532
549
  #ifdef HAVE_CURL_MULTI_TIMEOUT
533
550
  /* get the curl suggested time out */
534
551
  mcode = curl_multi_timeout(rbcm->handle, &timeout_milliseconds);
@@ -552,6 +569,31 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
552
569
  /* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
553
570
  }
554
571
 
572
+ #ifdef HAVE_CURL_MULTI_WAIT
573
+ {
574
+ struct wait_args wait_args;
575
+ wait_args.handle = rbcm->handle;
576
+ wait_args.timeout_ms = timeout_milliseconds;
577
+ wait_args.numfds = 0;
578
+ #if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
579
+ CURLMcode wait_rc = (CURLMcode)(intptr_t)
580
+ rb_thread_call_without_gvl(curl_multi_wait_wrapper, &wait_args, RUBY_UBF_IO, NULL);
581
+ #else
582
+ CURLMcode wait_rc = curl_multi_wait(rbcm->handle, NULL, 0, timeout_milliseconds, &wait_args.numfds);
583
+ #endif
584
+ if (wait_rc != CURLM_OK) {
585
+ raise_curl_multi_error_exception(wait_rc);
586
+ }
587
+ if (wait_args.numfds == 0) {
588
+ rb_thread_wait_for(tv_100ms);
589
+ }
590
+ /* Process pending transfers after waiting */
591
+ rb_curl_multi_run(self, rbcm->handle, &(rbcm->running));
592
+ rb_curl_multi_read_info(self, rbcm->handle);
593
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
594
+ }
595
+ #else
596
+
555
597
  tv.tv_sec = 0; /* never wait longer than 1 second */
556
598
  tv.tv_usec = (int)(timeout_milliseconds * 1000); /* XXX: int is the right type for OSX, what about linux? */
557
599
 
@@ -618,6 +660,7 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
618
660
  if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
619
661
  break;
620
662
  }
663
+ #endif /* HAVE_CURL_MULTI_WAIT */
621
664
  }
622
665
 
623
666
  } while( rbcm->running );
data/ext/curb_postfield.c CHANGED
@@ -421,71 +421,108 @@ static VALUE ruby_curl_postfield_content_proc_set(int argc, VALUE *argv, VALUE s
421
421
  * Only content fields may be converted to strings.
422
422
  */
423
423
  static VALUE ruby_curl_postfield_to_str(VALUE self) {
424
- // FIXME This is using the deprecated curl_escape func
425
424
  ruby_curl_postfield *rbcpf;
426
425
  VALUE result = Qnil;
427
426
  VALUE name = Qnil;
428
427
  char *tmpchrs;
429
-
428
+ #ifdef HAVE_CURL_EASY_ESCAPE
429
+ CURL *curl_handle = NULL;
430
+ #endif
431
+
430
432
  Data_Get_Struct(self, ruby_curl_postfield, rbcpf);
431
433
 
432
- if (rbcpf->name != Qnil) {
433
- name = rbcpf->name;
434
- if (rb_type(name) == T_STRING) {
435
- name = rbcpf->name;
436
- } else if (rb_respond_to(name,rb_intern("to_s"))) {
434
+ if (rbcpf->name != Qnil) {
435
+ name = rbcpf->name;
436
+ if (TYPE(name) != T_STRING) {
437
+ if (rb_respond_to(name, rb_intern("to_s")))
437
438
  name = rb_funcall(name, rb_intern("to_s"), 0);
438
- }
439
- else {
440
- name = Qnil; // we can't handle this object
441
- }
442
- }
443
- if (name == Qnil) {
444
- rb_raise(eCurlErrInvalidPostField, "Cannot convert unnamed field to string %s:%d, make sure your field name responds_to :to_s", __FILE__, __LINE__);
439
+ else
440
+ name = Qnil;
445
441
  }
442
+ }
443
+ if (name == Qnil) {
444
+ rb_raise(eCurlErrInvalidPostField,
445
+ "Cannot convert unnamed field to string %s:%d, make sure your field name responds_to :to_s",
446
+ __FILE__, __LINE__);
447
+ }
448
+ /* Force field name to UTF-8 before escaping */
449
+ VALUE name_utf8 = rb_str_export_to_enc(name, rb_utf8_encoding());
446
450
 
447
- tmpchrs = curl_escape(StringValuePtr(name), (int)RSTRING_LEN(name));
448
-
449
- if (!tmpchrs) {
450
- rb_raise(eCurlErrInvalidPostField, "Failed to url-encode name `%s'", tmpchrs);
451
- } else {
452
- VALUE tmpcontent = Qnil;
453
- VALUE escd_name = rb_str_new2(tmpchrs);
454
- curl_free(tmpchrs);
455
-
456
- if (rbcpf->content_proc != Qnil) {
457
- tmpcontent = rb_funcall(rbcpf->content_proc, idCall, 1, self);
458
- } else if (rbcpf->content != Qnil) {
459
- tmpcontent = rbcpf->content;
460
- } else if (rbcpf->local_file != Qnil) {
461
- tmpcontent = rbcpf->local_file;
462
- } else if (rbcpf->remote_file != Qnil) {
463
- tmpcontent = rbcpf->remote_file;
464
- } else {
465
- tmpcontent = rb_str_new2("");
466
- }
467
- if (TYPE(tmpcontent) != T_STRING) {
468
- if (rb_respond_to(tmpcontent, rb_intern("to_s"))) {
469
- tmpcontent = rb_funcall(tmpcontent, rb_intern("to_s"), 0);
470
- }
471
- else {
472
- rb_raise(rb_eRuntimeError, "postfield(%s) is not a string and does not respond_to to_s", RSTRING_PTR(escd_name) );
473
- }
474
- }
475
- //fprintf(stderr, "encoding content: %ld - %s\n", RSTRING_LEN(tmpcontent), RSTRING_PTR(tmpcontent) );
476
- tmpchrs = curl_escape(RSTRING_PTR(tmpcontent), (int)RSTRING_LEN(tmpcontent));
477
- if (!tmpchrs) {
478
- rb_raise(eCurlErrInvalidPostField, "Failed to url-encode content `%s'", tmpchrs);
479
- } else {
480
- VALUE escd_content = rb_str_new2(tmpchrs);
481
- curl_free(tmpchrs);
482
-
483
- result = escd_name;
484
- rb_str_cat(result, "=", 1);
485
- rb_str_concat(result, escd_content);
486
- }
451
+ #ifdef HAVE_CURL_EASY_ESCAPE
452
+ curl_handle = curl_easy_init();
453
+ if (!curl_handle) {
454
+ rb_raise(eCurlErrInvalidPostField, "Failed to initialize curl handle for escaping");
455
+ }
456
+ tmpchrs = curl_easy_escape(curl_handle, StringValuePtr(name_utf8), (int)RSTRING_LEN(name_utf8));
457
+ if (!tmpchrs) {
458
+ curl_easy_cleanup(curl_handle);
459
+ rb_raise(eCurlErrInvalidPostField, "Failed to url-encode name");
460
+ }
461
+ #else
462
+ tmpchrs = curl_escape(StringValuePtr(name_utf8), (int)RSTRING_LEN(name_utf8));
463
+ if (!tmpchrs) {
464
+ rb_raise(eCurlErrInvalidPostField, "Failed to url-encode name");
465
+ }
466
+ #endif
467
+
468
+ VALUE escd_name = rb_str_new2(tmpchrs);
469
+ #ifdef HAVE_CURL_EASY_ESCAPE
470
+ curl_free(tmpchrs);
471
+ #else
472
+ curl_free(tmpchrs);
473
+ #endif
474
+
475
+ VALUE tmpcontent = Qnil;
476
+ if (rbcpf->content_proc != Qnil) {
477
+ tmpcontent = rb_funcall(rbcpf->content_proc, idCall, 1, self);
478
+ } else if (rbcpf->content != Qnil) {
479
+ tmpcontent = rbcpf->content;
480
+ } else if (rbcpf->local_file != Qnil) {
481
+ tmpcontent = rbcpf->local_file;
482
+ } else if (rbcpf->remote_file != Qnil) {
483
+ tmpcontent = rbcpf->remote_file;
484
+ } else {
485
+ tmpcontent = rb_str_new2("");
486
+ }
487
+ if (TYPE(tmpcontent) != T_STRING) {
488
+ if (rb_respond_to(tmpcontent, rb_intern("to_s")))
489
+ tmpcontent = rb_funcall(tmpcontent, rb_intern("to_s"), 0);
490
+ else {
491
+ #ifdef HAVE_CURL_EASY_ESCAPE
492
+ curl_easy_cleanup(curl_handle);
493
+ #endif
494
+ rb_raise(rb_eRuntimeError,
495
+ "postfield(%s) is not a string and does not respond_to to_s",
496
+ RSTRING_PTR(escd_name));
487
497
  }
488
-
498
+ }
499
+ /* Force content to UTF-8 before escaping */
500
+ VALUE content_utf8 = rb_str_export_to_enc(tmpcontent, rb_utf8_encoding());
501
+ #ifdef HAVE_CURL_EASY_ESCAPE
502
+ tmpchrs = curl_easy_escape(curl_handle, StringValuePtr(content_utf8), (int)RSTRING_LEN(content_utf8));
503
+ if (!tmpchrs) {
504
+ curl_easy_cleanup(curl_handle);
505
+ rb_raise(eCurlErrInvalidPostField, "Failed to url-encode content");
506
+ }
507
+ #else
508
+ tmpchrs = curl_escape(StringValuePtr(content_utf8), (int)RSTRING_LEN(content_utf8));
509
+ if (!tmpchrs) {
510
+ rb_raise(eCurlErrInvalidPostField, "Failed to url-encode content");
511
+ }
512
+ #endif
513
+
514
+ VALUE escd_content = rb_str_new2(tmpchrs);
515
+ #ifdef HAVE_CURL_EASY_ESCAPE
516
+ curl_free(tmpchrs);
517
+ curl_easy_cleanup(curl_handle);
518
+ #else
519
+ curl_free(tmpchrs);
520
+ #endif
521
+
522
+ result = escd_name;
523
+ rb_str_cat(result, "=", 1);
524
+ rb_str_concat(result, escd_content);
525
+
489
526
  return result;
490
527
  }
491
528
 
data/ext/extconf.rb CHANGED
@@ -467,6 +467,8 @@ have_func('rb_thread_blocking_region')
467
467
  have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
468
468
  have_header('ruby/io.h')
469
469
  have_func('rb_io_stdio_file')
470
+ have_func('curl_multi_wait')
471
+ have_func('curl_easy_duphandle')
470
472
 
471
473
  create_header('curb_config.h')
472
474
  create_makefile('curb_core')
data/lib/curl/easy.rb CHANGED
@@ -374,13 +374,36 @@ module Curl
374
374
  #
375
375
  # call-seq:
376
376
  # Curl::Easy.http_put(url, data) {|c| ... }
377
+ # Curl::Easy.http_put(url, "some=urlencoded%20form%20data&and=so%20on") => true
378
+ # Curl::Easy.http_put(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true
379
+ # Curl::Easy.http_put(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
380
+ # Curl::Easy.http_put(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true
377
381
  #
378
382
  # see easy.http_put
379
383
  #
380
- def http_put(url, data)
381
- c = Curl::Easy.new url
384
+ def http_put(*args)
385
+ url = args.shift
386
+ c = Curl::Easy.new(url)
387
+ yield c if block_given?
388
+ c.http_put(*args)
389
+ c
390
+ end
391
+
392
+ #
393
+ # call-seq:
394
+ # Curl::Easy.http_patch(url, data) {|c| ... }
395
+ # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data&and=so%20on") => true
396
+ # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true
397
+ # Curl::Easy.http_patch(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
398
+ # Curl::Easy.http_patch(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true
399
+ #
400
+ # see easy.http_patch
401
+ #
402
+ def http_patch(*args)
403
+ url = args.shift
404
+ c = Curl::Easy.new(url)
382
405
  yield c if block_given?
383
- c.http_put data
406
+ c.http_patch(*args)
384
407
  c
385
408
  end
386
409
 
data/lib/curl/multi.rb CHANGED
@@ -95,7 +95,7 @@ module Curl
95
95
 
96
96
  # configure the multi handle
97
97
  multi_options.each { |k,v| m.send("#{k}=", v) }
98
- callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_redirect,:on_body,:on_header]
98
+ callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_redirect,:on_missing,:on_body,:on_header]
99
99
 
100
100
  add_free_handle = proc do|conf, easy|
101
101
  c = conf.dup # avoid being destructive to input
data/tests/helper.rb CHANGED
@@ -80,6 +80,10 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
80
80
  res.status = 404
81
81
  elsif req.path.match(/error$/)
82
82
  res.status = 500
83
+ elsif req.path.match(/get_cookies$/)
84
+ res['Content-Type'] = "text/plain"
85
+ res.body = req['Cookie']
86
+ return
83
87
  end
84
88
  respond_with("GET#{req.query_string}",req,res)
85
89
  end
@@ -90,7 +94,18 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
90
94
  end
91
95
 
92
96
  def do_POST(req,res)
93
- if req.query['filename'].nil?
97
+ if req.path.match(/set_cookies$/)
98
+ JSON.parse(req.body || '[]', symbolize_names: true).each do |hash|
99
+ cookie = WEBrick::Cookie.new(hash.fetch(:name), hash.fetch(:value))
100
+ cookie.domain = hash[:domain] if hash.key?(:domain)
101
+ cookie.expires = hash[:expires] if hash.key?(:expires)
102
+ cookie.path = hash[:path] if hash.key?(:path)
103
+ cookie.secure = hash[:secure] if hash.key?(:secure)
104
+ cookie.max_age = hash[:max_age] if hash.key?(:max_age)
105
+ res.cookies.push(cookie)
106
+ end
107
+ respond_with('OK', req, res)
108
+ elsif req.query['filename'].nil?
94
109
  if req.body
95
110
  params = {}
96
111
  req.body.split('&').map{|s| k,v=s.split('='); params[k] = v }
@@ -110,7 +110,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
110
110
  end
111
111
 
112
112
  def test_class_perform_02
113
- data = ""
113
+ data = String.new
114
114
  assert_instance_of Curl::Easy, c = Curl::Easy.perform($TEST_URL) { |curl| curl.on_body { |d| data << d; d.length } }
115
115
 
116
116
  assert_nil c.body_str
@@ -211,7 +211,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
211
211
  end
212
212
 
213
213
  def test_get_02
214
- data = ""
214
+ data = String.new
215
215
  c = Curl::Easy.new($TEST_URL) do |curl|
216
216
  curl.on_body { |d| data << d; d.length }
217
217
  end
@@ -765,20 +765,27 @@ class TestCurbCurlEasy < Test::Unit::TestCase
765
765
  assert_equal 'GET', curl.body_str
766
766
  end
767
767
 
768
- def test_post_remote
768
+ def test_remote
769
769
  curl = Curl::Easy.new(TestServlet.url)
770
770
  curl.http_post([Curl::PostField.content('document_id', 5)])
771
771
  assert_equal "POST\ndocument_id=5", curl.unescape(curl.body_str)
772
+
773
+ curl.http_put([Curl::PostField.content('document_id', 5)])
774
+ assert_equal "PUT\ndocument_id=5", curl.unescape(curl.body_str)
775
+
776
+ curl.http_patch([Curl::PostField.content('document_id', 5)])
777
+ assert_equal "PATCH\ndocument_id=5", curl.unescape(curl.body_str)
772
778
  end
773
779
 
774
- def test_post_remote_is_easy_handle
780
+ def test_remote_is_easy_handle
775
781
  # see: http://pastie.org/560852 and
776
782
  # http://groups.google.com/group/curb---ruby-libcurl-bindings/browse_thread/thread/216bb2d9b037f347?hl=en
777
- [:post, :get, :head, :delete].each do |method|
783
+ [:post, :patch, :put, :get, :head, :delete].each do |method|
778
784
  retries = 0
779
785
  begin
780
786
  count = 0
781
- Curl::Easy.send("http_#{method}", TestServlet.url) do|c|
787
+ m = "http_#{method}".to_sym
788
+ Curl::Easy.send(m, TestServlet.url) do|c|
782
789
  count += 1
783
790
  assert_equal Curl::Easy, c.class
784
791
  end
@@ -787,12 +794,14 @@ class TestCurbCurlEasy < Test::Unit::TestCase
787
794
  retries+=1
788
795
  retry if retries < 3
789
796
  raise e
797
+ rescue ArgumentError => e
798
+ assert false, "#{m} - #{e.message}"
790
799
  end
791
800
  end
792
801
  end
793
802
 
794
803
  # see: https://github.com/rvanlieshout/curb/commit/8bcdefddc0162484681ebd1a92d52a642666a445
795
- def test_post_multipart_array_remote
804
+ def test_multipart_array_remote
796
805
  curl = Curl::Easy.new(TestServlet.url)
797
806
  curl.multipart_form_post = true
798
807
  fields = [
@@ -802,9 +811,23 @@ class TestCurbCurlEasy < Test::Unit::TestCase
802
811
  curl.http_post(fields)
803
812
  assert_match(/HTTP POST file upload/, curl.body_str)
804
813
  assert_match(/Content-Disposition: form-data/, curl.body_str)
814
+
815
+ curl = Curl::Easy.new(TestServlet.url)
816
+ curl.multipart_form_post = true
817
+ fields = [
818
+ Curl::PostField.file('foo', File.expand_path(File.join(File.dirname(__FILE__),'..','README.markdown'))),
819
+ Curl::PostField.file('bar', File.expand_path(File.join(File.dirname(__FILE__),'..','README.markdown')))
820
+ ]
821
+ curl.http_put(fields)
822
+ assert_match(/HTTP POST file upload/, curl.body_str)
823
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
824
+
825
+ curl.http_patch(fields)
826
+ assert_match(/HTTP POST file upload/, curl.body_str)
827
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
805
828
  end
806
829
 
807
- def test_post_with_body_remote
830
+ def test_with_body_remote
808
831
  curl = Curl::Easy.new(TestServlet.url)
809
832
  curl.post_body = 'foo=bar&encoded%20string=val'
810
833
 
@@ -812,23 +835,42 @@ class TestCurbCurlEasy < Test::Unit::TestCase
812
835
 
813
836
  assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
814
837
  assert_equal 'foo=bar&encoded%20string=val', curl.post_body
838
+
839
+ curl = Curl::Easy.new(TestServlet.url)
840
+ curl.put_data = 'foo=bar&encoded%20string=val'
841
+
842
+ curl.perform
843
+
844
+ assert_equal "PUT\nfoo=bar&encoded%20string=val", curl.body_str
815
845
  end
816
846
 
817
- def test_form_post_body_remote
847
+ def test_form_body_remote
818
848
  curl = Curl::Easy.new(TestServlet.url)
819
849
  curl.http_post('foo=bar', 'encoded%20string=val')
820
850
 
821
851
  assert_equal "POST\nfoo=bar&encoded%20string=val", curl.body_str
822
852
  assert_equal 'foo=bar&encoded%20string=val', curl.post_body
823
- end
824
853
 
825
- def test_post_multipart_file_remote
826
854
  curl = Curl::Easy.new(TestServlet.url)
827
- curl.multipart_form_post = true
828
- pf = Curl::PostField.file('readme', File.expand_path(File.join(File.dirname(__FILE__),'..','README.markdown')))
829
- curl.http_post(pf)
830
- assert_match(/HTTP POST file upload/, curl.body_str)
831
- assert_match(/Content-Disposition: form-data/, curl.body_str)
855
+ curl.http_put('foo=bar', 'encoded%20string=val')
856
+
857
+ assert_equal "PUT\nfoo=bar&encoded%20string=val", curl.body_str
858
+
859
+ curl = Curl::Easy.new(TestServlet.url)
860
+ curl.http_patch('foo=bar', 'encoded%20string=val')
861
+
862
+ assert_equal "PATCH\nfoo=bar&encoded%20string=val", curl.body_str
863
+ end
864
+
865
+ def test_multipart_file_remote
866
+ [:put, :post, :patch].each {|method|
867
+ curl = Curl::Easy.new(TestServlet.url)
868
+ curl.multipart_form_post = true
869
+ pf = Curl::PostField.file('readme', File.expand_path(File.join(File.dirname(__FILE__),'..','README.markdown')))
870
+ curl.send("http_#{method}", pf)
871
+ assert_match(/HTTP POST file upload/, curl.body_str)
872
+ assert_match(/Content-Disposition: form-data/, curl.body_str)
873
+ }
832
874
  end
833
875
 
834
876
  def test_delete_remote
@@ -1065,16 +1107,22 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1065
1107
  assert_equal "PUT\nhello", curl.body_str
1066
1108
  curl.http('COPY')
1067
1109
  assert_equal 'COPY', curl.body_str
1110
+
1111
+ curl.http_patch
1112
+ assert_equal "PATCH\n", curl.body_str
1113
+
1114
+ curl.http_put
1115
+ assert_equal "PUT\n", curl.body_str
1068
1116
  end
1069
1117
 
1070
1118
  def test_easy_http_verbs_must_respond_to_str
1071
- # issue http://github.com/taf2/curb/issues#issue/45
1119
+ # issue http://github.com/taf2/curb/issues/45
1072
1120
  assert_nothing_raised do
1073
- c = Curl::Easy.new ; c.url = 'http://example.com' ; c.http(:get)
1121
+ c = Curl::Easy.new ; c.url = TestServlet.url ; c.http(:get)
1074
1122
  end
1075
1123
 
1076
1124
  assert_raise RuntimeError do
1077
- c = Curl::Easy.new ; c.url = 'http://example.com' ; c.http(FooNoToS.new)
1125
+ c = Curl::Easy.new ; c.url = TestServlet.url ; c.http(FooNoToS.new)
1078
1126
  end
1079
1127
 
1080
1128
  end
@@ -0,0 +1,277 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+ require 'json'
3
+
4
+ class TestCurbCurlEasyCookielist < Test::Unit::TestCase
5
+ def test_setopt_cookielist
6
+ easy = Curl::Easy.new
7
+ # DateTime handles time zone correctly
8
+ expires = (Date.today + 2).to_datetime
9
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c1=v1; domain=localhost; expires=#{expires.httpdate};")
10
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: c2=v2; domain=localhost')
11
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c3=v3; expires=#{expires.httpdate};")
12
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: c4=v4;')
13
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c5=v5; domain=127.0.0.1; expires=#{expires.httpdate};")
14
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: c6=v6; domain=127.0.0.1;;')
15
+
16
+ # Since 7.43.0 cookies that were imported in the Set-Cookie format without a domain name are not exported by this option.
17
+ # So, before 7.43.0, c3 and c4 will be exported too; but that version is far too old for current curb version, so it's not handled here.
18
+ if Curl::CURL_VERSION.to_f > 8
19
+ expected_cookielist = [
20
+ ".127.0.0.1\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc5\tv5",
21
+ ".127.0.0.1\tTRUE\t/\tFALSE\t0\tc6\tv6",
22
+ ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc1\tv1",
23
+ ".localhost\tTRUE\t/\tFALSE\t0\tc2\tv2",
24
+ ]
25
+ else
26
+ expected_cookielist = [
27
+ "127.0.0.1\tFALSE\t/\tFALSE\t#{expires.to_time.to_i}\tc5\tv5",
28
+ "127.0.0.1\tFALSE\t/\tFALSE\t0\tc6\tv6",
29
+ ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc1\tv1",
30
+ ".localhost\tTRUE\t/\tFALSE\t0\tc2\tv2",
31
+ ]
32
+ end
33
+ assert_equal expected_cookielist, easy.cookielist
34
+
35
+ easy.url = "#{TestServlet.url}/get_cookies"
36
+ easy.perform
37
+ assert_equal 'c6=v6; c5=v5; c4=v4; c3=v3', easy.body_str
38
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
39
+ easy.perform
40
+ assert_equal 'c2=v2; c1=v1', easy.body_str
41
+ end
42
+
43
+ # libcurl documentation says: "This option also enables the cookie engine", but it's not tracked on the curb level
44
+ def test_setopt_cookielist_enables_cookie_engine
45
+ easy = Curl::Easy.new
46
+ expires = (Date.today + 2).to_datetime
47
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/set_cookies"
48
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c1=v1; domain=localhost; expires=#{expires.httpdate};")
49
+ easy.post_body = JSON.generate([{ name: 'c2', value: 'v2', domain: 'localhost', expires: expires.httpdate, path: '/' }])
50
+ easy.perform
51
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
52
+ easy.post_body = nil
53
+ easy.perform
54
+
55
+ assert !easy.enable_cookies?
56
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc1\tv1", ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc2\tv2"], easy.cookielist
57
+ assert_equal 'c2=v2; c1=v1', easy.body_str
58
+ end
59
+
60
+ def test_setopt_cookielist_invalid_format
61
+ easy = Curl::Easy.new
62
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
63
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Not a cookie')
64
+ assert_nil easy.cookielist
65
+ easy.perform
66
+ assert_equal '', easy.body_str
67
+ end
68
+
69
+ def test_setopt_cookielist_netscape_format
70
+ easy = Curl::Easy.new
71
+ expires = (Date.today + 2).to_datetime
72
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
73
+ # Note domain changes for include subdomains
74
+ [
75
+ ['localhost', 'FALSE', '/', 'TRUE', 0, 'session_http_only', '42'].join("\t"),
76
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '43'].join("\t"),
77
+ ['localhost', 'TRUE', '/', 'FALSE', expires.to_time.to_i, 'permanent', '44'].join("\t"),
78
+ ['.localhost', 'FALSE', '/', 'TRUE', expires.to_time.to_i, 'permanent_http_only', '45'].join("\t"),
79
+ ].each { |cookie| easy.setopt(Curl::CURLOPT_COOKIELIST, cookie) }
80
+
81
+ expected_cookielist = [
82
+ ['localhost', 'FALSE', '/', 'TRUE', 0, 'session_http_only', '42'].join("\t"),
83
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '43'].join("\t"),
84
+ ['.localhost', 'TRUE', '/', 'FALSE', expires.to_time.to_i, 'permanent', '44'].join("\t"),
85
+ ['localhost', 'FALSE', '/', 'TRUE', expires.to_time.to_i, 'permanent_http_only', '45'].join("\t"),
86
+ ]
87
+ assert_equal expected_cookielist, easy.cookielist
88
+ easy.perform
89
+ assert_equal 'permanent_http_only=45; session_http_only=42; permanent=44; session=43', easy.body_str
90
+ end
91
+
92
+ # Multiple cookies and comments are not supported
93
+ def test_setopt_cookielist_netscape_format_mutliline
94
+ easy = Curl::Easy.new
95
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
96
+ easy.setopt(
97
+ Curl::CURLOPT_COOKIELIST,
98
+ [
99
+ '# Netscape HTTP Cookie File',
100
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '42'].join("\t"),
101
+ '',
102
+ ].join("\n"),
103
+ )
104
+ assert_nil easy.cookielist
105
+ easy.perform
106
+ assert_equal '', easy.body_str
107
+
108
+ easy.setopt(
109
+ Curl::CURLOPT_COOKIELIST,
110
+ [
111
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '42'].join("\t"),
112
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session2', '84'].join("\t"),
113
+ '',
114
+ ].join("\n"),
115
+ )
116
+ # Only first cookie is set
117
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t0\tsession\t42"], easy.cookielist
118
+ easy.perform
119
+ assert_equal 'session=42', easy.body_str
120
+ end
121
+
122
+ # ALL erases all cookies held in memory
123
+ # ALL was added in 7.14.1
124
+ def test_setopt_cookielist_command_all
125
+ expires = (Date.today + 2).to_datetime
126
+ with_permanent_and_session_cookies(expires) do |easy|
127
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'ALL')
128
+ assert_nil easy.cookielist
129
+ easy.perform
130
+ assert_equal '', easy.body_str
131
+ end
132
+ end
133
+
134
+ # SESS erases all session cookies held in memory
135
+ # SESS was added in 7.15.4
136
+ def test_setopt_cookielist_command_sess
137
+ expires = (Date.today + 2).to_datetime
138
+ with_permanent_and_session_cookies(expires) do |easy|
139
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'SESS')
140
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tpermanent\t42"], easy.cookielist
141
+ easy.perform
142
+ assert_equal 'permanent=42', easy.body_str
143
+ end
144
+ end
145
+
146
+ # FLUSH writes all known cookies to the file specified by CURLOPT_COOKIEJAR
147
+ # FLUSH was added in 7.17.1
148
+ def test_setopt_cookielist_command_flush
149
+ expires = (Date.today + 2).to_datetime
150
+ with_permanent_and_session_cookies(expires) do |easy|
151
+ cookiejar = File.join(Dir.tmpdir, 'curl_test_cookiejar')
152
+ assert !File.exist?(cookiejar)
153
+ begin
154
+ easy.cookiejar = cookiejar
155
+ # trick to actually set CURLOPT_COOKIEJAR
156
+ easy.enable_cookies = true
157
+ easy.perform
158
+ assert !File.exist?(cookiejar)
159
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'FLUSH')
160
+ expected_cookiejar = <<~COOKIEJAR
161
+ # Netscape HTTP Cookie File
162
+ # https://curl.se/docs/http-cookies.html
163
+ # This file was generated by libcurl! Edit at your own risk.
164
+
165
+ .localhost TRUE / FALSE 0 session 420
166
+ .localhost TRUE / FALSE #{expires.to_time.to_i} permanent 42
167
+ COOKIEJAR
168
+ assert_equal expected_cookiejar, File.read(cookiejar)
169
+ ensure
170
+ # Otherwise it'll create this file again
171
+ easy.close
172
+ File.unlink(cookiejar) if File.exist?(cookiejar)
173
+ end
174
+ end
175
+ end
176
+
177
+ # RELOAD loads all cookies from the files specified by CURLOPT_COOKIEFILE
178
+ # RELOAD was added in 7.39.0
179
+ def test_setopt_cookielist_command_reload
180
+ expires = (Date.today + 2).to_datetime
181
+ expires_file = (Date.today + 4).to_datetime
182
+ with_permanent_and_session_cookies(expires) do |easy|
183
+ cookiefile = File.join(Dir.tmpdir, 'curl_test_cookiefile')
184
+ assert !File.exist?(cookiefile)
185
+ begin
186
+ cookielist = [
187
+ # Won't be updated, added instead
188
+ ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent\t84",
189
+ ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent_file\t84",
190
+ # Won't be updated, added instead
191
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession\t840",
192
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession_file\t840",
193
+ '',
194
+ ]
195
+ File.write(cookiefile, cookielist.join("\n"))
196
+ easy.cookiefile = cookiefile
197
+ # trick to actually set CURLOPT_COOKIEFILE
198
+ easy.enable_cookies = true
199
+ easy.perform
200
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'RELOAD')
201
+ expected_cookielist = [
202
+ ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tpermanent\t42",
203
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession\t420",
204
+ ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent\t84",
205
+ ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent_file\t84",
206
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession\t840",
207
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession_file\t840",
208
+ ]
209
+ assert_equal expected_cookielist, easy.cookielist
210
+ easy.perform
211
+ # Be careful, duplicates are not removed
212
+ assert_equal 'permanent_file=84; session_file=840; permanent=84; session=840; permanent=42; session=420', easy.body_str
213
+ ensure
214
+ File.unlink(cookiefile) if File.exist?(cookiefile)
215
+ end
216
+ end
217
+ end
218
+
219
+ def test_commands_do_not_enable_cookie_engine
220
+ %w[ALL SESS FLUSH RELOAD].each do |command|
221
+ easy = Curl::Easy.new
222
+ expires = (Date.today + 2).to_datetime
223
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/set_cookies"
224
+ easy.setopt(Curl::CURLOPT_COOKIELIST, command)
225
+ easy.post_body = JSON.generate([{ name: 'c2', value: 'v2', domain: 'localhost', expires: expires.httpdate, path: '/' }])
226
+ easy.perform
227
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
228
+ easy.post_body = nil
229
+ easy.perform
230
+
231
+ assert !easy.enable_cookies?
232
+ assert_nil easy.cookielist
233
+ assert_equal '', easy.body_str
234
+ end
235
+ end
236
+
237
+
238
+ def test_strings_without_cookie_enable_cookie_engine
239
+ [
240
+ '',
241
+ '# Netscape HTTP Cookie File',
242
+ 'no_a_cookie',
243
+ ].each do |command|
244
+ easy = Curl::Easy.new
245
+ expires = (Date.today + 2).to_datetime
246
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/set_cookies"
247
+ easy.setopt(Curl::CURLOPT_COOKIELIST, command)
248
+ easy.post_body = JSON.generate([{ name: 'c2', value: 'v2', domain: 'localhost', expires: expires.httpdate, path: '/' }])
249
+ easy.perform
250
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
251
+ easy.post_body = nil
252
+ easy.perform
253
+
254
+ assert !easy.enable_cookies?
255
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc2\tv2"], easy.cookielist
256
+ assert_equal 'c2=v2', easy.body_str
257
+ end
258
+ end
259
+
260
+ def with_permanent_and_session_cookies(expires)
261
+ easy = Curl::Easy.new
262
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
263
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: permanent=42; domain=localhost; expires=#{expires.httpdate};")
264
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: session=420; domain=localhost;')
265
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tpermanent\t42", ".localhost\tTRUE\t/\tFALSE\t0\tsession\t420"], easy.cookielist
266
+ easy.perform
267
+ assert_equal 'permanent=42; session=420', easy.body_str
268
+
269
+ yield easy
270
+ end
271
+
272
+ include TestServerMethods
273
+
274
+ def setup
275
+ server_setup
276
+ end
277
+ end
@@ -13,7 +13,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
13
13
  # this test fails with libcurl 7.22.0. I didn't investigate, but it may be related
14
14
  # to CURLOPT_MAXCONNECTS bug fixed in 7.30.0:
15
15
  # https://github.com/curl/curl/commit/e87e76e2dc108efb1cae87df496416f49c55fca0
16
- omit("Skip, libcurl too old (< 7.22.0)") if Curl::CURL_VERSION.split('.')[1].to_i <= 22
16
+ omit("Skip, libcurl too old (< 7.22.0)") if Curl::CURL_VERSION.to_f < 8 && Curl::CURL_VERSION.split('.')[1].to_i <= 22
17
17
 
18
18
  @server.shutdown if @server
19
19
  @test_thread.kill if @test_thread
@@ -114,13 +114,13 @@ class TestCurbCurlMulti < Test::Unit::TestCase
114
114
  end
115
115
 
116
116
  def test_new_multi_01
117
- d1 = ""
117
+ d1 = String.new
118
118
  c1 = Curl::Easy.new($TEST_URL) do |curl|
119
119
  curl.headers["User-Agent"] = "myapp-0.0"
120
120
  curl.on_body {|d| d1 << d; d.length }
121
121
  end
122
122
 
123
- d2 = ""
123
+ d2 = String.new
124
124
  c2 = Curl::Easy.new($TEST_URL) do |curl|
125
125
  curl.headers["User-Agent"] = "myapp-0.0"
126
126
  curl.on_body {|d| d2 << d; d.length }
@@ -186,7 +186,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
186
186
  rescue Curl::Err::AbortedByCallbackError => e
187
187
  did_raise = true
188
188
  in_file = e.backtrace.detect {|err| err.match?(File.basename(__FILE__)) }
189
- in_file_stack = e.backtrace.select {|err| err.match?(File.basename(__FILE__)) }
189
+ #in_file_stack = e.backtrace.select {|err| err.match?(File.basename(__FILE__)) }
190
190
  assert_match(__FILE__, in_file)
191
191
  in_file.gsub!(__FILE__)
192
192
  parts = in_file.split(':')
@@ -206,7 +206,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
206
206
  m = Curl::Multi.new
207
207
  responses = []
208
208
  n.times do|i|
209
- responses[i] = ""
209
+ responses[i] = String.new
210
210
  c = Curl::Easy.new($TEST_URL) do|curl|
211
211
  curl.on_body{|data| responses[i] << data; data.size }
212
212
  end
@@ -230,7 +230,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
230
230
  5.times do|it|
231
231
  responses = []
232
232
  n.times do|i|
233
- responses[i] = ""
233
+ responses[i] = String.new
234
234
  c = Curl::Easy.new($TEST_URL) do|curl|
235
235
  curl.on_body{|data| responses[i] << data; data.size }
236
236
  end
@@ -376,7 +376,7 @@ class TestCurbCurlMulti < Test::Unit::TestCase
376
376
  attr_reader :buf
377
377
 
378
378
  def t_method
379
- @buf = ""
379
+ @buf = String.new
380
380
  @m = Curl::Multi.new
381
381
  10.times do|i|
382
382
  c = Curl::Easy.new($TEST_URL)
@@ -422,8 +422,8 @@ class TestCurbCurlMulti < Test::Unit::TestCase
422
422
  m = Curl::Multi.new
423
423
  # add a few easy handles
424
424
  requests.each do |url|
425
- responses[url] = ""
426
- responses["#{url}-header"] = ""
425
+ responses[url] = String.new
426
+ responses["#{url}-header"] = String.new
427
427
  c = Curl::Easy.new(url) do|curl|
428
428
  curl.follow_location = true
429
429
  curl.on_header{|data| responses["#{url}-header"] << data; data.size }
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ross Bamford
8
8
  - Todd A. Fisher
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2024-08-23 00:00:00.000000000 Z
11
+ date: 2025-02-09 00:00:00.000000000 Z
13
12
  dependencies: []
14
13
  description: Curb (probably CUrl-RuBy or something) provides Ruby-language bindings
15
14
  for the libcurl(3), a fully-featured client-side URL transfer library. cURL and
@@ -67,6 +66,7 @@ files:
67
66
  - tests/tc_curl.rb
68
67
  - tests/tc_curl_download.rb
69
68
  - tests/tc_curl_easy.rb
69
+ - tests/tc_curl_easy_cookielist.rb
70
70
  - tests/tc_curl_easy_resolve.rb
71
71
  - tests/tc_curl_easy_setopt.rb
72
72
  - tests/tc_curl_maxfilesize.rb
@@ -78,9 +78,8 @@ files:
78
78
  - tests/unittests.rb
79
79
  homepage: https://github.com/taf2/curb
80
80
  licenses:
81
- - MIT
81
+ - Ruby
82
82
  metadata: {}
83
- post_install_message:
84
83
  rdoc_options:
85
84
  - "--main"
86
85
  - README.markdown
@@ -98,8 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
97
  - !ruby/object:Gem::Version
99
98
  version: '0'
100
99
  requirements: []
101
- rubygems_version: 3.4.6
102
- signing_key:
100
+ rubygems_version: 3.6.2
103
101
  specification_version: 4
104
102
  summary: Ruby libcurl bindings
105
103
  test_files:
@@ -125,6 +123,7 @@ test_files:
125
123
  - tests/tc_curl.rb
126
124
  - tests/tc_curl_download.rb
127
125
  - tests/tc_curl_easy.rb
126
+ - tests/tc_curl_easy_cookielist.rb
128
127
  - tests/tc_curl_easy_resolve.rb
129
128
  - tests/tc_curl_easy_setopt.rb
130
129
  - tests/tc_curl_maxfilesize.rb