curb 1.3.1 → 1.3.3

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_multi.c CHANGED
@@ -28,6 +28,7 @@
28
28
 
29
29
  #include <errno.h>
30
30
  #include <fcntl.h>
31
+ #include <stdint.h>
31
32
  #include <stdarg.h>
32
33
 
33
34
  /*
@@ -52,9 +53,7 @@
52
53
  #include <fcntl.h>
53
54
  #endif
54
55
 
55
- #if 0 /* disabled curl_multi_wait in favor of scheduler-aware fdsets */
56
- #include <stdint.h> /* for intptr_t */
57
-
56
+ #if defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT)
58
57
  struct wait_args {
59
58
  CURLM *handle;
60
59
  long timeout_ms;
@@ -95,6 +94,34 @@ static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy);
95
94
  static VALUE ruby_curl_multi_mark_closed(VALUE self);
96
95
  static VALUE ruby_curl_multi_alloc(VALUE klass);
97
96
  static VALUE ruby_curl_multi_initialize(VALUE self);
97
+ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self);
98
+ #if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
99
+ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE self);
100
+ #endif
101
+
102
+ static ruby_curl_multi *ruby_curl_multi_pointer_if_compatible(VALUE multi_val) {
103
+ if (NIL_P(multi_val) || !RB_TYPE_P(multi_val, T_DATA)) {
104
+ return NULL;
105
+ }
106
+
107
+ #if defined(RTYPEDDATA_P) && defined(RTYPEDDATA_TYPE) && defined(RTYPEDDATA_DATA)
108
+ if (!RTYPEDDATA_P(multi_val)) {
109
+ return NULL;
110
+ }
111
+
112
+ if (RTYPEDDATA_TYPE(multi_val) != &ruby_curl_multi_data_type) {
113
+ return NULL;
114
+ }
115
+
116
+ return (ruby_curl_multi *)RTYPEDDATA_DATA(multi_val);
117
+ #else
118
+ if (!rb_typeddata_is_kind_of(multi_val, &ruby_curl_multi_data_type)) {
119
+ return NULL;
120
+ }
121
+
122
+ return DATA_PTR(multi_val);
123
+ #endif
124
+ }
98
125
 
99
126
  static VALUE callback_exception(VALUE did_raise, VALUE exception) {
100
127
  // TODO: we could have an option to enable exception reporting
@@ -342,6 +369,9 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
342
369
  rbcm->active = 0;
343
370
  rbcm->running = 0;
344
371
  rbcm->closed = 0;
372
+ rbcm->perform_active = 0;
373
+ rbcm->callback_active = 0;
374
+ rbcm->allow_close_during_perform = 0;
345
375
 
346
376
  if (rbcm->attached) {
347
377
  st_free_table(rbcm->attached);
@@ -537,11 +567,21 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
537
567
  CURLMcode mcode;
538
568
  ruby_curl_easy *rbce;
539
569
  ruby_curl_multi *rbcm;
570
+ ruby_curl_multi *existing_rbcm;
540
571
 
541
572
  TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
542
573
  TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
543
574
  ruby_curl_multi_ensure_handle(rbcm);
544
575
 
576
+ if (rb_curl_multi_has_easy(rbcm, rbce)) {
577
+ return self;
578
+ }
579
+
580
+ existing_rbcm = ruby_curl_multi_pointer_if_compatible(rbce->multi);
581
+ if (existing_rbcm && existing_rbcm != rbcm && rb_curl_multi_has_easy(existing_rbcm, rbce)) {
582
+ rb_raise(rb_eRuntimeError, "Cannot add an active Curl::Easy handle to another Curl::Multi");
583
+ }
584
+
545
585
  /* setup the easy handle */
546
586
  ruby_curl_easy_setup( rbce );
547
587
 
@@ -797,6 +837,8 @@ static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) {
797
837
  VALUE did_raise = rb_hash_new();
798
838
  long redirect_count;
799
839
 
840
+ args->rbcm->callback_active = 1;
841
+
800
842
  easy_callback_error = take_easy_callback_error_if_any(args->easy);
801
843
  if (!NIL_P(easy_callback_error)) {
802
844
  stash_multi_exception_if_unset(args->self, easy_callback_error, args->easy);
@@ -877,6 +919,10 @@ static VALUE rb_curl_multi_finish_completion_callbacks(VALUE argp) {
877
919
  args->rbce->callback_active = 0;
878
920
  }
879
921
 
922
+ if (args->rbcm) {
923
+ args->rbcm->callback_active = 0;
924
+ }
925
+
880
926
  if (args->rbce->multi == args->self && !rb_curl_multi_has_easy(args->rbcm, args->rbce)) {
881
927
  args->rbce->multi = Qnil;
882
928
  }
@@ -1072,6 +1118,41 @@ static VALUE fiber_io_wait_protected(VALUE argp) {
1072
1118
  }
1073
1119
  #endif
1074
1120
 
1121
+ #if defined(RB_INTEGER_TYPE_P)
1122
+ #define CURB_INTEGER_P(value) RB_INTEGER_TYPE_P(value)
1123
+ #else
1124
+ #define CURB_INTEGER_P(value) (FIXNUM_P(value) || RB_TYPE_P((value), T_BIGNUM))
1125
+ #endif
1126
+
1127
+ static int multi_socket_wait_events_for_curl_poll(int what) {
1128
+ if (what == CURL_POLL_IN) return RB_WAITFD_IN;
1129
+ if (what == CURL_POLL_OUT) return RB_WAITFD_OUT;
1130
+ if (what == CURL_POLL_INOUT) return RB_WAITFD_IN | RB_WAITFD_OUT;
1131
+ return RB_WAITFD_IN | RB_WAITFD_OUT;
1132
+ }
1133
+
1134
+ static int multi_socket_cselect_flags_for_curl_poll(int what) {
1135
+ int flags = 0;
1136
+
1137
+ if (what == CURL_POLL_IN || what == CURL_POLL_INOUT) flags |= CURL_CSELECT_IN;
1138
+ if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT) flags |= CURL_CSELECT_OUT;
1139
+ if (flags == 0) flags = CURL_CSELECT_IN | CURL_CSELECT_OUT;
1140
+
1141
+ return flags;
1142
+ }
1143
+
1144
+ static int multi_socket_cselect_flags_for_wait_events(int events) {
1145
+ int flags = 0;
1146
+
1147
+ if (events & RB_WAITFD_IN) flags |= CURL_CSELECT_IN;
1148
+ if (events & RB_WAITFD_OUT) flags |= CURL_CSELECT_OUT;
1149
+ #ifdef RB_WAITFD_PRI
1150
+ if (events & RB_WAITFD_PRI) flags |= CURL_CSELECT_ERR;
1151
+ #endif
1152
+
1153
+ return flags;
1154
+ }
1155
+
1075
1156
  static void multi_socket_forget_fd(multi_socket_ctx *ctx, int fd) {
1076
1157
  st_data_t key = (st_data_t)fd;
1077
1158
  st_data_t rec;
@@ -1236,6 +1317,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
1236
1317
 
1237
1318
  int did_timeout = 0;
1238
1319
  int any_ready = 0;
1320
+ int ready_flags = 0;
1239
1321
 
1240
1322
  int handled_wait = 0;
1241
1323
  if (count_tracked > 1) {
@@ -1270,10 +1352,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
1270
1352
  if (scheduler != Qnil) {
1271
1353
  int events = 0;
1272
1354
  if (wait_fd >= 0) {
1273
- if (wait_what == CURL_POLL_IN) events = RB_WAITFD_IN;
1274
- else if (wait_what == CURL_POLL_OUT) events = RB_WAITFD_OUT;
1275
- else if (wait_what == CURL_POLL_INOUT) events = RB_WAITFD_IN|RB_WAITFD_OUT;
1276
- else events = RB_WAITFD_IN|RB_WAITFD_OUT;
1355
+ events = multi_socket_wait_events_for_curl_poll(wait_what);
1277
1356
  }
1278
1357
  double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
1279
1358
  VALUE timeout = rb_float_new(timeout_s);
@@ -1305,8 +1384,21 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
1305
1384
  did_timeout = 1;
1306
1385
  any_ready = 0;
1307
1386
  } else {
1308
- any_ready = (ready != Qfalse);
1387
+ any_ready = (ready != Qfalse && !NIL_P(ready));
1309
1388
  did_timeout = !any_ready;
1389
+ if (any_ready) {
1390
+ if (ready == Qtrue) {
1391
+ ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
1392
+ } else if (CURB_INTEGER_P(ready)) {
1393
+ ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready));
1394
+ if (ready_flags == 0) {
1395
+ any_ready = 0;
1396
+ did_timeout = 1;
1397
+ }
1398
+ } else {
1399
+ ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
1400
+ }
1401
+ }
1310
1402
  }
1311
1403
  }
1312
1404
  }
@@ -1316,10 +1408,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
1316
1408
  #endif
1317
1409
  #if defined(HAVE_RB_WAIT_FOR_SINGLE_FD)
1318
1410
  if (!handled_wait && wait_fd >= 0) {
1319
- int ev = 0;
1320
- if (wait_what == CURL_POLL_IN) ev = RB_WAITFD_IN;
1321
- else if (wait_what == CURL_POLL_OUT) ev = RB_WAITFD_OUT;
1322
- else if (wait_what == CURL_POLL_INOUT) ev = RB_WAITFD_IN|RB_WAITFD_OUT;
1411
+ int ev = multi_socket_wait_events_for_curl_poll(wait_what);
1323
1412
  int rc = rb_wait_for_single_fd(wait_fd, ev, &tv);
1324
1413
  curb_debugf("[curb.socket] rb_wait_for_single_fd rc=%d fd=%d ev=%d", rc, wait_fd, ev);
1325
1414
  if (rc < 0) {
@@ -1328,6 +1417,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
1328
1417
  }
1329
1418
  any_ready = (rc != 0);
1330
1419
  did_timeout = (rc == 0);
1420
+ if (any_ready) ready_flags = multi_socket_cselect_flags_for_wait_events(rc);
1331
1421
  handled_wait = 1;
1332
1422
  }
1333
1423
  #endif
@@ -1351,6 +1441,11 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
1351
1441
  }
1352
1442
  any_ready = (rc > 0);
1353
1443
  did_timeout = (rc == 0);
1444
+ if (any_ready && wait_fd >= 0) {
1445
+ if (rb_fd_isset(wait_fd, &rfds)) ready_flags |= CURL_CSELECT_IN;
1446
+ if (rb_fd_isset(wait_fd, &wfds)) ready_flags |= CURL_CSELECT_OUT;
1447
+ if (rb_fd_isset(wait_fd, &efds)) ready_flags |= CURL_CSELECT_ERR;
1448
+ }
1354
1449
  rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
1355
1450
  }
1356
1451
  } else { /* count_tracked == 0 */
@@ -1368,10 +1463,8 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
1368
1463
  if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
1369
1464
  } else if (any_ready) {
1370
1465
  if (count_tracked == 1 && wait_fd >= 0) {
1371
- int flags = 0;
1372
- if (wait_what == CURL_POLL_IN || wait_what == CURL_POLL_INOUT) flags |= CURL_CSELECT_IN;
1373
- if (wait_what == CURL_POLL_OUT || wait_what == CURL_POLL_INOUT) flags |= CURL_CSELECT_OUT;
1374
- flags |= CURL_CSELECT_ERR;
1466
+ int flags = ready_flags;
1467
+ if (flags == 0) flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
1375
1468
  #if CURB_SOCKET_DEBUG
1376
1469
  {
1377
1470
  char b[32];
@@ -1415,7 +1508,7 @@ static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
1415
1508
  return Qnil;
1416
1509
  }
1417
1510
 
1418
- VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
1511
+ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE self) {
1419
1512
  ruby_curl_multi *rbcm;
1420
1513
  VALUE block = Qnil;
1421
1514
  rb_scan_args(argc, argv, "0&", &block);
@@ -1445,7 +1538,11 @@ VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
1445
1538
  /* finalize */
1446
1539
  rb_curl_multi_read_info(self, rbcm->handle);
1447
1540
  rb_curl_multi_yield_if_given(self, block);
1448
- if (cCurlMutiAutoClose == 1) rb_funcall(self, rb_intern("_autoclose"), 0);
1541
+ if (cCurlMutiAutoClose == 1) {
1542
+ rbcm->allow_close_during_perform = 1;
1543
+ rb_funcall(self, rb_intern("_autoclose"), 0);
1544
+ rbcm->allow_close_during_perform = 0;
1545
+ }
1449
1546
 
1450
1547
  return Qtrue;
1451
1548
  }
@@ -1493,6 +1590,14 @@ static VALUE curb_select(void *args) {
1493
1590
  int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
1494
1591
  return INT2FIX(rc);
1495
1592
  }
1593
+
1594
+ #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
1595
+ static void *curb_select_without_gvl(void *args) {
1596
+ struct _select_set* set = args;
1597
+ int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
1598
+ return (void *)(intptr_t)rc;
1599
+ }
1600
+ #endif
1496
1601
  #endif
1497
1602
 
1498
1603
  /*
@@ -1510,7 +1615,7 @@ static VALUE curb_select(void *args) {
1510
1615
  *
1511
1616
  * Run multi handles, looping selecting when data can be transfered
1512
1617
  */
1513
- VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
1618
+ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
1514
1619
  CURLMcode mcode;
1515
1620
  ruby_curl_multi *rbcm;
1516
1621
  int maxfd, rc = -1;
@@ -1691,7 +1796,7 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
1691
1796
  rb_fd_term(&efds);
1692
1797
  }
1693
1798
  #elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
1694
- rc = (int)(VALUE) rb_thread_call_without_gvl((void *(*)(void *))curb_select, &fdset_args, RUBY_UBF_IO, 0);
1799
+ rc = (int)(intptr_t) rb_thread_call_without_gvl(curb_select_without_gvl, &fdset_args, RUBY_UBF_IO, 0);
1695
1800
  #elif HAVE_RB_THREAD_BLOCKING_REGION
1696
1801
  rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0);
1697
1802
  #else
@@ -1725,11 +1830,66 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
1725
1830
  rb_curl_multi_read_info( self, rbcm->handle );
1726
1831
  rb_curl_multi_yield_if_given(self, block);
1727
1832
  if (cCurlMutiAutoClose == 1) {
1833
+ rbcm->allow_close_during_perform = 1;
1728
1834
  rb_funcall(self, rb_intern("_autoclose"), 0);
1835
+ rbcm->allow_close_during_perform = 0;
1729
1836
  }
1730
1837
  return Qtrue;
1731
1838
  }
1732
1839
 
1840
+ struct multi_perform_call_args {
1841
+ int argc;
1842
+ VALUE *argv;
1843
+ VALUE self;
1844
+ VALUE result;
1845
+ VALUE (*func)(int, VALUE *, VALUE);
1846
+ };
1847
+
1848
+ static VALUE ruby_curl_multi_perform_guard_body(VALUE argp) {
1849
+ struct multi_perform_call_args *args = (struct multi_perform_call_args *)argp;
1850
+ args->result = args->func(args->argc, args->argv, args->self);
1851
+ return args->result;
1852
+ }
1853
+
1854
+ static VALUE ruby_curl_multi_perform_guard_ensure(VALUE self) {
1855
+ ruby_curl_multi *rbcm;
1856
+ TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
1857
+ rbcm->perform_active = 0;
1858
+ rbcm->callback_active = 0;
1859
+ rbcm->allow_close_during_perform = 0;
1860
+ return Qnil;
1861
+ }
1862
+
1863
+ static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE self, VALUE (*func)(int, VALUE *, VALUE)) {
1864
+ ruby_curl_multi *rbcm;
1865
+ struct multi_perform_call_args args;
1866
+
1867
+ TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
1868
+ if (rbcm->perform_active) {
1869
+ rb_raise(rb_eRuntimeError, "Cannot recursively perform an active Curl::Multi handle");
1870
+ }
1871
+
1872
+ rbcm->perform_active = 1;
1873
+ args.argc = argc;
1874
+ args.argv = argv;
1875
+ args.self = self;
1876
+ args.result = Qnil;
1877
+ args.func = func;
1878
+
1879
+ return rb_ensure(ruby_curl_multi_perform_guard_body, (VALUE)&args,
1880
+ ruby_curl_multi_perform_guard_ensure, self);
1881
+ }
1882
+
1883
+ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
1884
+ return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_perform_impl);
1885
+ }
1886
+
1887
+ #if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
1888
+ VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
1889
+ return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_socket_perform_impl);
1890
+ }
1891
+ #endif
1892
+
1733
1893
  /*
1734
1894
  * call-seq:
1735
1895
  *
@@ -1740,6 +1900,11 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
1740
1900
  VALUE ruby_curl_multi_close(VALUE self) {
1741
1901
  ruby_curl_multi *rbcm;
1742
1902
  TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
1903
+
1904
+ if ((rbcm->perform_active || rbcm->callback_active) && !rbcm->allow_close_during_perform) {
1905
+ rb_raise(rb_eRuntimeError, "Cannot close an active Curl::Multi handle during perform");
1906
+ }
1907
+
1743
1908
  rb_curl_multi_detach_all(rbcm);
1744
1909
 
1745
1910
  if (rbcm->handle) {
data/ext/curb_multi.h CHANGED
@@ -16,6 +16,9 @@ typedef struct {
16
16
  int active;
17
17
  int running;
18
18
  char closed;
19
+ char perform_active;
20
+ char callback_active;
21
+ char allow_close_during_perform;
19
22
  CURLM *handle;
20
23
  struct st_table *attached;
21
24
  } ruby_curl_multi;
data/ext/curb_postfield.c CHANGED
@@ -183,6 +183,7 @@ static void curl_postfield_mark(void *ptr) {
183
183
  rb_gc_mark(rbcpf->name);
184
184
  rb_gc_mark(rbcpf->content);
185
185
  rb_gc_mark(rbcpf->content_type);
186
+ rb_gc_mark(rbcpf->content_proc);
186
187
  rb_gc_mark(rbcpf->local_file);
187
188
  rb_gc_mark(rbcpf->remote_file);
188
189
  rb_gc_mark(rbcpf->buffer_str);
@@ -4,7 +4,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
4
4
  include BugTestServerSetupTeardown
5
5
 
6
6
  def setup
7
- @port = 9999
7
+ @port = unused_local_port
8
8
  @response_proc = lambda do|res|
9
9
  sleep 0.5
10
10
  res.body = "hi"
@@ -19,7 +19,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
19
19
 
20
20
  5.times do |i|
21
21
  t = Thread.new do
22
- c = Curl::Easy.perform('http://127.0.0.1:9999/test')
22
+ c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test")
23
23
  c.header_str
24
24
  end
25
25
  threads << t
@@ -35,7 +35,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
35
35
  timer = Time.now
36
36
  single_responses = []
37
37
  5.times do |i|
38
- c = Curl::Easy.perform('http://127.0.0.1:9999/test')
38
+ c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test")
39
39
  single_responses << c.header_str
40
40
  end
41
41
 
@@ -4,7 +4,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
4
4
  include BugTestServerSetupTeardown
5
5
 
6
6
  def setup
7
- @port = 9999
7
+ @port = unused_local_port
8
8
  super
9
9
  @server.mount_proc("/redirect_to_test") do|req,res|
10
10
  res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, "/test")
@@ -13,7 +13,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
13
13
 
14
14
  def test_follow_redirect_with_no_redirect
15
15
 
16
- c = Curl::Easy.new('http://127.0.0.1:9999/test')
16
+ c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
17
17
  did_call_redirect = false
18
18
  c.on_redirect do|x|
19
19
  did_call_redirect = true
@@ -22,7 +22,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
22
22
 
23
23
  assert !did_call_redirect, "should reach this point redirect should not have been called"
24
24
 
25
- c = Curl::Easy.new('http://127.0.0.1:9999/test')
25
+ c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
26
26
  did_call_redirect = false
27
27
  c.on_redirect do|x|
28
28
  did_call_redirect = true
@@ -33,7 +33,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
33
33
  assert_equal 0, c.redirect_count
34
34
  assert !did_call_redirect, "should reach this point redirect should not have been called"
35
35
 
36
- c = Curl::Easy.new('http://127.0.0.1:9999/redirect_to_test')
36
+ c = Curl::Easy.new("http://127.0.0.1:#{@port}/redirect_to_test")
37
37
  did_call_redirect = false
38
38
  c.on_redirect do|x|
39
39
  did_call_redirect = true
@@ -43,7 +43,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
43
43
 
44
44
  assert did_call_redirect, "we should have called on_redirect"
45
45
 
46
- c = Curl::Easy.new('http://127.0.0.1:9999/redirect_to_test')
46
+ c = Curl::Easy.new("http://127.0.0.1:#{@port}/redirect_to_test")
47
47
  did_call_redirect = false
48
48
  c.follow_location = true
49
49
  # NOTE: while this API is not supported by libcurl e.g. there is no redirect function callback in libcurl we could
@@ -57,7 +57,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
57
57
 
58
58
  assert did_call_redirect, "we should have called on_redirect"
59
59
 
60
- c.url = 'http://127.0.0.1:9999/test'
60
+ c.url = "http://127.0.0.1:#{@port}/test"
61
61
  c.perform
62
62
  assert_equal 0, c.redirect_count
63
63
  assert_equal 200, c.response_code
@@ -65,7 +65,7 @@ class BugFollowRedirect288 < Test::Unit::TestCase
65
65
  puts "checking for raise support"
66
66
  did_raise = false
67
67
  begin
68
- c = Curl::Easy.new('http://127.0.0.1:9999/redirect_to_test')
68
+ c = Curl::Easy.new("http://127.0.0.1:#{@port}/redirect_to_test")
69
69
  did_call_redirect = false
70
70
  c.on_redirect do|x|
71
71
  raise "raise"
@@ -4,16 +4,17 @@ class BugRaiseOnCallback < Test::Unit::TestCase
4
4
  include BugTestServerSetupTeardown
5
5
 
6
6
  def setup
7
- @port = 9999
7
+ @port = unused_local_port
8
8
  super
9
9
  end
10
10
 
11
11
  def test_on_complte
12
- c = Curl::Easy.new('http://127.0.0.1:9999/test')
12
+ url = "http://127.0.0.1:#{@port}/test"
13
+ c = Curl::Easy.new(url)
13
14
  did_raise = false
14
15
  begin
15
16
  c.on_complete do|x|
16
- assert_equal 'http://127.0.0.1:9999/test', x.url
17
+ assert_equal url, x.url
17
18
  raise "error complete" # this will get swallowed
18
19
  end
19
20
  c.perform
data/tests/helper.rb CHANGED
@@ -206,6 +206,13 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
206
206
  end
207
207
 
208
208
  module BugTestServerSetupTeardown
209
+ def unused_local_port
210
+ socket = TCPServer.new('127.0.0.1', 0)
211
+ socket.addr[1]
212
+ ensure
213
+ socket.close if socket
214
+ end
215
+
209
216
  def setup
210
217
  @port ||= 9992
211
218
  @server = WEBrick::HTTPServer.new(:Port => @port, :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
@@ -123,6 +123,50 @@ class TestCurbCurlEasy < Test::Unit::TestCase
123
123
  assert_equal "", easy.body_str.to_s
124
124
  end
125
125
 
126
+ def test_head_request_restores_easy_state_after_callback_exception
127
+ easy = Curl::Easy.new(TestServlet.url)
128
+ easy.on_complete { raise "head complete blew up" }
129
+
130
+ error = assert_raise(Curl::Err::AbortedByCallbackError) { easy.http("HEAD") }
131
+ assert_equal "head complete blew up", error.message
132
+
133
+ easy.on_complete { |_curl| }
134
+ easy.perform
135
+
136
+ assert_equal "GET", easy.body_str
137
+ end
138
+
139
+ def test_patch_request_does_not_leak_custom_method_to_next_perform
140
+ easy = Curl::Easy.new(TestServlet.url)
141
+
142
+ easy.http_patch("a=b")
143
+ assert_equal "PATCH\na=b", easy.body_str
144
+
145
+ easy.perform
146
+ assert_equal "GET", easy.body_str
147
+ end
148
+
149
+ def test_put_request_does_not_leak_custom_method_to_next_perform
150
+ easy = Curl::Easy.new(TestServlet.url)
151
+
152
+ easy.http_put("payload")
153
+ assert_equal "PUT\npayload", easy.body_str
154
+
155
+ easy.perform
156
+ assert_equal "GET", easy.body_str
157
+ end
158
+
159
+ def test_failed_put_data_setup_does_not_leave_easy_in_upload_mode
160
+ easy = Curl::Easy.new(TestServlet.url)
161
+ easy.headers = ["X-Test: true"]
162
+
163
+ assert_raise(RuntimeError) { easy.put_data = "payload" }
164
+
165
+ easy.headers = {}
166
+ easy.perform
167
+ assert_equal "GET", easy.body_str
168
+ end
169
+
126
170
  def test_perform_releases_failed_implicit_multi_without_follow_up_easy_perform
127
171
  Curl::Easy.flush_deferred_multi_closes
128
172
  assert_equal 0, Curl::Easy.deferred_multi_closes.length
@@ -1301,6 +1345,24 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1301
1345
  assert_equal "POST\nfoo=bar", curl.body_str
1302
1346
  end
1303
1347
 
1348
+ def test_multipart_patch_build_failure_restores_easy_request_state
1349
+ curl = Curl::Easy.new(TestServlet.url)
1350
+ curl.multipart_form_post = true
1351
+ field = Curl::PostField.content('document_id', '5')
1352
+
1353
+ field.name = nil
1354
+ error = assert_raise(Curl::Err::InvalidPostFieldError) do
1355
+ # The first field allocates native multipart form state before the second field raises.
1356
+ curl.http_patch(Curl::PostField.content('keep', 'allocated'), field)
1357
+ end
1358
+ assert_match(/Cannot post unnamed field/, error.message)
1359
+
1360
+ curl.multipart_form_post = false
1361
+ curl.perform
1362
+
1363
+ assert_equal 'GET', curl.body_str
1364
+ end
1365
+
1304
1366
  def test_delete_remote
1305
1367
  curl = Curl::Easy.new(TestServlet.url)
1306
1368
  curl.http_delete
@@ -1358,6 +1420,44 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1358
1420
  assert_match(/message$/, curl.body_str)
1359
1421
  end
1360
1422
 
1423
+ def test_put_data_from_to_s_object
1424
+ curl = Curl::Easy.new(TestServlet.url)
1425
+ curl.put_data = :message
1426
+
1427
+ curl.perform
1428
+
1429
+ assert_equal "PUT\nmessage", curl.body_str
1430
+ end
1431
+
1432
+ def test_put_data_read_exception_sets_result_and_releases_callback_state
1433
+ error_class = Class.new(StandardError)
1434
+ reader_class = Class.new do
1435
+ define_method(:read) do |_len|
1436
+ raise error_class, 'upload read failed'
1437
+ end
1438
+
1439
+ def seek(_offset, _whence = SEEK_SET)
1440
+ 0
1441
+ end
1442
+
1443
+ def stat
1444
+ Struct.new(:size).new(1)
1445
+ end
1446
+ end
1447
+
1448
+ curl = Curl::Easy.new(TestServlet.url)
1449
+ curl.put_data = reader_class.new
1450
+
1451
+ error = assert_raise(error_class) { curl.perform }
1452
+ assert_equal 'upload read failed', error.message
1453
+ assert_not_equal 0, curl.last_result
1454
+
1455
+ curl.url = TestServlet.url
1456
+ curl.http_get
1457
+
1458
+ assert_equal 'GET', curl.body_str
1459
+ end
1460
+
1361
1461
  # https://github.com/taf2/curb/issues/101
1362
1462
  def test_put_data_null_bytes
1363
1463
  curl = Curl::Easy.new(TestServlet.url)
@@ -1460,6 +1560,10 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1460
1560
  # header value and encoding; skip the NTLM-specific assertion.
1461
1561
  return
1462
1562
  end
1563
+ # curl 8.20.0 disables NTLM by default and plans to remove it in
1564
+ # September 2026; libcurl silently downgrades to Basic when NTLM is
1565
+ # unavailable, so skip the NTLM-specific assertion in that case.
1566
+ return unless Curl.ntlm?
1463
1567
  curl = Curl::Easy.new(TestServlet.url)
1464
1568
  curl.username = "foo"
1465
1569
  curl.password = "bar"
@@ -1501,6 +1605,18 @@ class TestCurbCurlEasy < Test::Unit::TestCase
1501
1605
  easy.http_get
1502
1606
  end
1503
1607
 
1608
+ def test_easy_close_restores_error_buffer
1609
+ easy = Curl::Easy.new('http://127.0.0.1:1')
1610
+ assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
1611
+ assert_not_nil easy.last_error
1612
+
1613
+ easy.close
1614
+ easy.url = 'http://127.0.0.1:1'
1615
+ assert_raise(Curl::Err::ConnectionFailedError) { easy.perform }
1616
+
1617
+ assert_not_nil easy.last_error
1618
+ end
1619
+
1504
1620
  def test_easy_reset
1505
1621
  easy = Curl::Easy.new
1506
1622
  easy.url = TestServlet.url + "?query=foo"