curb 0.8.4 → 1.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +522 -0
  3. data/Rakefile +79 -26
  4. data/ext/banned.h +32 -0
  5. data/ext/curb.c +549 -230
  6. data/ext/curb.h +19 -10
  7. data/ext/curb_easy.c +1935 -366
  8. data/ext/curb_easy.h +16 -0
  9. data/ext/curb_errors.c +115 -18
  10. data/ext/curb_errors.h +8 -5
  11. data/ext/curb_macros.h +33 -21
  12. data/ext/curb_multi.c +1858 -272
  13. data/ext/curb_multi.h +10 -3
  14. data/ext/curb_postfield.c +149 -77
  15. data/ext/curb_postfield.h +1 -0
  16. data/ext/curb_upload.c +38 -11
  17. data/ext/curb_upload.h +2 -0
  18. data/ext/extconf.rb +353 -35
  19. data/lib/curb.rb +1 -0
  20. data/lib/curl/easy.rb +314 -78
  21. data/lib/curl/multi.rb +134 -28
  22. data/lib/curl.rb +217 -11
  23. data/tests/bug_crash_on_debug.rb +14 -28
  24. data/tests/bug_crash_on_progress.rb +32 -16
  25. data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
  26. data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
  27. data/tests/bug_follow_redirect_288.rb +83 -0
  28. data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
  29. data/tests/bug_issue102.rb +1 -1
  30. data/tests/bug_issue_noproxy.rb +56 -0
  31. data/tests/bug_issue_post_redirect.rb +93 -0
  32. data/tests/bug_issue_spnego.rb +41 -0
  33. data/tests/bug_multi_segfault.rb +1 -0
  34. data/tests/bug_raise_on_callback.rb +30 -0
  35. data/tests/helper.rb +400 -44
  36. data/tests/leak_trace.rb +237 -0
  37. data/tests/mem_check.rb +3 -0
  38. data/tests/tc_curl.rb +31 -1
  39. data/tests/tc_curl_download.rb +12 -7
  40. data/tests/tc_curl_easy.rb +911 -63
  41. data/tests/tc_curl_easy_cookielist.rb +277 -0
  42. data/tests/tc_curl_easy_request_target.rb +41 -0
  43. data/tests/tc_curl_easy_resolve.rb +48 -0
  44. data/tests/tc_curl_maxfilesize.rb +12 -0
  45. data/tests/tc_curl_multi.rb +855 -53
  46. data/tests/tc_curl_native_coverage.rb +145 -0
  47. data/tests/tc_curl_postfield.rb +207 -30
  48. data/tests/tc_curl_protocols.rb +37 -0
  49. data/tests/tc_fiber_scheduler.rb +543 -0
  50. data/tests/tc_ftp_options.rb +39 -0
  51. data/tests/tc_gc_compact.rb +223 -0
  52. data/tests/tc_test_server_methods.rb +110 -0
  53. data/tests/timeout.rb +30 -6
  54. metadata +59 -36
  55. data/README +0 -194
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a502fb4afad21a24a20302b7522b616b08afbbe3d4b5b2a3eeee46d83b53c50d
4
+ data.tar.gz: 5e68fd3f3380f2045dfc6d4e8e157ebbb9fc56e4b76601574c92a755665eeef2
5
+ SHA512:
6
+ metadata.gz: 940f343ad1d926ddcf27b994bca191e131176e766dce3d7f211f9ed5b28917ffb9ebfa5b8f71247ac8ca8a0b916e6565b60fb2ae57a964770b3273d1bd7d5d69
7
+ data.tar.gz: 77a44dd831eea50e786d4b4871c2f1702e3bee2c74d0484f7b0812a91d19d4bcdd79c12e20dce7cd8ffcbaf0e06ae77cc6cc7734b7c71fa2feabc636af82b0d8
data/README.md ADDED
@@ -0,0 +1,522 @@
1
+ # Curb - Libcurl bindings for Ruby
2
+
3
+ [![CI](https://github.com/taf2/curb/actions/workflows/ci.yml/badge.svg)](https://github.com/taf2/curb/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/taf2/curb/branch/master/graph/badge.svg)](https://codecov.io/gh/taf2/curb)
5
+ [![Gem Version](https://badge.fury.io/rb/curb.svg)](https://badge.fury.io/rb/curb)
6
+
7
+ * [CI Build Status](https://github.com/taf2/curb/actions/workflows/ci.yml)
8
+ * [rubydoc rdoc](http://www.rubydoc.info/github/taf2/curb/)
9
+ * [github project](http://github.com/taf2/curb/tree/master)
10
+
11
+ Curb (probably CUrl-RuBy or something) provides Ruby-language bindings for the
12
+ libcurl(3), a fully-featured client-side URL transfer library.
13
+ cURL and libcurl live at [https://curl.se/libcurl/](https://curl.se/libcurl/) .
14
+
15
+ Curb is a work-in-progress, and currently only supports libcurl's `easy` and `multi` modes.
16
+
17
+ A big advantage to Curb over all other known ruby http libraries is it's ability to handle timeouts without the use of threads.
18
+
19
+ ## License
20
+
21
+ Curb is copyright (c) 2006 Ross Bamford, and released under the terms of the
22
+ Ruby license. See the LICENSE file for the gory details.
23
+
24
+ ## Easy mode
25
+
26
+ GET request
27
+ ```
28
+ res = Curl.get("https://www.google.com/") {|http|
29
+ http.timeout = 10 # raise exception if request/response not handled within 10 seconds
30
+ }
31
+ puts res.code
32
+ puts res.head
33
+ puts res.body
34
+ ```
35
+
36
+ POST request
37
+ ```
38
+ res = Curl.post("https://your-server.com/endpoint", {post: "this"}.to_json) {|http|
39
+ http.headers["Content-Type"] = "application/json"
40
+ }
41
+ puts res.code
42
+ puts res.head
43
+ puts res.body
44
+ ```
45
+
46
+ ## FTP Support
47
+
48
+ require 'curb'
49
+
50
+ ### Basic FTP Download
51
+ ```ruby
52
+ puts "=== FTP Download Example ==="
53
+ ftp = Curl::Easy.new('ftp://ftp.example.com/remote/file.txt')
54
+ ftp.username = 'user'
55
+ ftp.password = 'password'
56
+ ftp.perform
57
+ puts ftp.body
58
+ ```
59
+
60
+ ### FTP Upload
61
+ ```ruby
62
+ puts "\n=== FTP Upload Example ==="
63
+ upload = Curl::Easy.new('ftp://ftp.example.com/remote/upload.txt')
64
+ upload.username = 'user'
65
+ upload.password = 'password'
66
+ upload.upload = true
67
+ upload.put_data = File.read('local_file.txt')
68
+ upload.perform
69
+ ```
70
+
71
+ ### List Directory Contents
72
+ ```ruby
73
+ puts "\n=== FTP Directory Listing Example ==="
74
+ list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
75
+ list.username = 'user'
76
+ list.password = 'password'
77
+ list.set(:dirlistonly, 1)
78
+ list.perform
79
+ puts list.body
80
+ ```
81
+
82
+ ### FTP over HTTP proxy tunnel (NLST/LIST)
83
+ When listing directories through an HTTP proxy with `proxy_tunnel` (CONNECT), let libcurl manage the passive data connection. Do not send `PASV`/`EPSV` or `NLST` via `easy.ftp_commands` — QUOTE commands run on the control connection and libcurl will not open the data connection, resulting in 425 errors.
84
+
85
+ To get NLST-like output safely:
86
+
87
+ ```ruby
88
+ list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
89
+ list.username = 'user'
90
+ list.password = 'password'
91
+ list.proxy_url = 'http://proxy.example.com:80'
92
+ list.proxy_tunnel = true
93
+
94
+ # Ask libcurl to perform a listing (names only)
95
+ list.set(:dirlistonly, 1)
96
+
97
+ # If the proxy or server has trouble with EPSV/EPRT, you can adjust:
98
+ # list.set(:ftp_use_epsv, 0) # disable EPSV
99
+ # list.set(:ftp_use_eprt, 0) # disable EPRT (stick to IPv4 PASV)
100
+ # list.set(:ftp_skip_pasv_ip, 1) # ignore PASV host, reuse control host
101
+
102
+ list.perform
103
+ puts list.body
104
+ ```
105
+
106
+ If you need a full `LIST` output instead of just names, omit `dirlistonly` and parse the server response accordingly. The key is to let libcurl initiate the data connection (PASV/EPSV) instead of trying to drive it via `ftp_commands`.
107
+
108
+ #### Full LIST directory listing
109
+ To retrieve the full `LIST` output (permissions, owner, size, timestamp, name), simply do not set `dirlistonly`:
110
+
111
+ ```ruby
112
+ list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
113
+ list.username = 'user'
114
+ list.password = 'password'
115
+
116
+ # Explicitly ensure names+metadata (LIST) rather than NLST
117
+ # list.set(:dirlistonly, 0) # optional; default is LIST for directory URLs
118
+
119
+ list.perform
120
+ puts list.body # multi-line LIST output
121
+ ```
122
+
123
+ Through an HTTP proxy tunnel, the same considerations apply as the NLST example above — just omit `dirlistonly` and keep the optional EPSV/EPRT/PASV tweaks if needed:
124
+
125
+ ```ruby
126
+ list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
127
+ list.username = 'user'
128
+ list.password = 'password'
129
+ list.proxy_url = 'http://proxy.example.com:80'
130
+ list.proxy_tunnel = true
131
+
132
+ # Optional tweaks if the proxy/server combination struggles
133
+ # list.set(:ftp_use_epsv, 0)
134
+ # list.set(:ftp_use_eprt, 0)
135
+ # list.set(:ftp_skip_pasv_ip, 1)
136
+
137
+ list.perform
138
+ puts list.body
139
+ ```
140
+
141
+ ### Advanced FTP Usage with Various Options
142
+ ```
143
+ puts "\n=== Advanced FTP Example ==="
144
+ advanced = Curl::Easy.new do |curl|
145
+ curl.url = 'ftp://ftp.example.com/remote/file.txt'
146
+ curl.username = 'user'
147
+ curl.password = 'password'
148
+
149
+ # FTP Options
150
+ curl.ftp_response_timeout = 30
151
+ curl.ftp_create_missing_dirs = true # Create directories if they don't exist
152
+ curl.ftp_filemethod = Curl::CURL_MULTICWD # Use multicwd method for traversing paths
153
+
154
+ # SSL/TLS Options for FTPS
155
+ curl.use_ssl = Curl::CURLUSESSL_ALL # Use SSL/TLS for control and data
156
+ curl.ssl_verify_peer = true
157
+ curl.ssl_verify_host = true
158
+ curl.cacert = "/path/to/cacert.pem"
159
+
160
+ # Progress callback
161
+ curl.on_progress do |dl_total, dl_now, ul_total, ul_now|
162
+ puts "Download: #{dl_now}/#{dl_total} Upload: #{ul_now}/#{ul_total}"
163
+ true # must return true to continue
164
+ end
165
+
166
+ # Debug output
167
+ curl.verbose = true
168
+ curl.on_debug do |type, data|
169
+ puts "#{type}: #{data}"
170
+ true
171
+ end
172
+ end
173
+
174
+ advanced.perform
175
+ ```
176
+
177
+ ### Parallel FTP Downloads
178
+ ```
179
+ puts "\n=== Parallel FTP Downloads Example ==="
180
+ urls = [
181
+ 'ftp://ftp.example.com/file1.txt',
182
+ 'ftp://ftp.example.com/file2.txt',
183
+ 'ftp://ftp.example.com/file3.txt'
184
+ ]
185
+ ```
186
+
187
+ ### Common options for all connections
188
+ ```
189
+ options = {
190
+ :username => 'user',
191
+ :password => 'password',
192
+ :timeout => 30,
193
+ :on_success => proc { |easy| puts "Successfully downloaded: #{easy.url}" },
194
+ :on_failure => proc { |easy, code| puts "Failed to download: #{easy.url} (#{code})" }
195
+ }
196
+
197
+ Curl::Multi.download(urls, options) do |curl, file_path|
198
+ puts "Completed downloading to: #{file_path}"
199
+ end
200
+ ```
201
+
202
+ ## You will need
203
+
204
+ * A working Ruby installation (`2.0.0+` will work but `2.1+` preferred) (it's possible it still works with 1.8.7 but you'd have to tell me if not...)
205
+ * A working libcurl development installation
206
+ (Ideally one of the versions listed in the compatibility chart below that maps to your `curb` version)
207
+ * A sane build environment (e.g. gcc, make)
208
+
209
+ ## Version Compatibility chart
210
+
211
+ A **non-exhaustive** set of compatibility versions of the libcurl library
212
+ with this gem are as follows. (Note that these are only the ones that have been
213
+ tested and reported to work across a variety of platforms / rubies)
214
+
215
+ | Gem Version | Release Date | libcurl versions |
216
+ | ----------- | -------------- | ----------------- |
217
+ | 1.0.8 | Feb 10, 2025 | 7.58 – 8.12.1 |
218
+ | 1.0.7 | Feb 09, 2025 | 7.58 – 8.12.1 |
219
+ | 1.0.6 | Aug 23, 2024 | 7.58 – 8.12.1 |
220
+ | 1.0.5 | Jan 2023 | 7.58 – 8.12.1 |
221
+ | 1.0.4 | Jan 2023 | 7.58 – 8.12.1 |
222
+ | 1.0.3* | Dec 2022 | 7.58 – 8.12.1 |
223
+ | 1.0.2* | Dec 2022 | 7.58 – 8.12.1 |
224
+ | 1.0.1 | Apr 2022 | 7.58 – 8.12.1 |
225
+ | 1.0.0 | Jan 2022 | 7.58 – 8.12.1 |
226
+ | 0.9.8 | Jan 2019 | 7.58 – 7.81 |
227
+ | 0.9.7 | Nov 2018 | 7.56 – 7.60 |
228
+ | 0.9.6 | May 2018 | 7.51 – 7.59 |
229
+ | 0.9.5 | May 2018 | 7.51 – 7.59 |
230
+ | 0.9.4 | Aug 2017 | 7.41 – 7.58 |
231
+ | 0.9.3 | Apr 2016 | 7.26 – 7.58 |
232
+
233
+ ```*avoid using these version are known to have issues with segmentation faults```
234
+
235
+ ## Installation...
236
+
237
+ ... will usually be as simple as:
238
+
239
+ $ gem install curb
240
+
241
+ On Windows, make sure you're using the [DevKit](http://rubyinstaller.org/downloads/) and
242
+ the [development version of libcurl](http://curl.se/gknw.net/7.39.0/dist-w32/curl-7.39.0-devel-mingw32.zip). Unzip, then run this in your command
243
+ line (alter paths to your curl location, but remember to use forward slashes):
244
+
245
+ gem install curb --platform=ruby -- --with-curl-lib=C:/curl-7.39.0-devel-mingw32/lib --with-curl-include=C:/curl-7.39.0-devel-mingw32/include
246
+
247
+ Note that with Windows moving from one method of compiling to another as of Ruby `2.4` (DevKit -> MYSYS2),
248
+ the usage of Ruby `2.4+` with this gem on windows is unlikely to work. It is advised to use the
249
+ latest version of Ruby 2.3 available [HERE](https://dl.bintray.com/oneclick/rubyinstaller/rubyinstaller-2.3.3.exe)
250
+
251
+ Or, if you downloaded the archive:
252
+
253
+ $ rake compile && rake install
254
+
255
+ If you have a weird setup, you might need extconf options. In this case, pass
256
+ them like so:
257
+
258
+ $ rake compile EXTCONF_OPTS='--with-curl-dir=/path/to/libcurl --prefix=/what/ever' && rake install
259
+
260
+ Curb is tested only on GNU/Linux x86 and Mac OSX - YMMV on other platforms.
261
+ If you do use another platform and experience problems, or if you can
262
+ expand on the above instructions, please report the issue at http://github.com/taf2/curb/issues
263
+
264
+ On Ubuntu, the dependencies can be satisfied by installing the following packages:
265
+
266
+ 18.04 and onwards
267
+
268
+ $ sudo apt-get install libcurl4 libcurl3-gnutls libcurl4-openssl-dev
269
+
270
+ < 18.04
271
+
272
+ $ sudo apt-get install libcurl3 libcurl3-gnutls libcurl4-openssl-dev
273
+
274
+ On RedHat:
275
+
276
+ $ sudo yum install ruby-devel libcurl-devel openssl-devel
277
+
278
+ Curb has fairly extensive RDoc comments in the source. You can build the
279
+ documentation with:
280
+
281
+ $ rake doc
282
+
283
+ ## Usage & examples
284
+
285
+ Curb provides two classes:
286
+
287
+ * `Curl::Easy` - simple API, for day-to-day tasks.
288
+ * `Curl::Multi` - more advanced API, for operating on multiple URLs simultaneously.
289
+
290
+ To use either, you will need to require the curb gem:
291
+
292
+ ```ruby
293
+ require 'curb'
294
+ ```
295
+
296
+ ### Super simple API (less typing)
297
+
298
+ ```ruby
299
+ http = Curl.get("http://www.google.com/")
300
+ puts http.body
301
+
302
+ http = Curl.post("http://www.google.com/", {:foo => "bar"})
303
+ puts http.body
304
+
305
+ http = Curl.get("http://www.google.com/") do |http|
306
+ http.headers['Cookie'] = 'foo=1;bar=2'
307
+ end
308
+ puts http.body
309
+ ```
310
+
311
+ ### Simple fetch via HTTP:
312
+
313
+ ```ruby
314
+ c = Curl::Easy.perform("http://www.google.co.uk")
315
+ puts c.body
316
+ ```
317
+
318
+ Same thing, more manual:
319
+
320
+ ```ruby
321
+ c = Curl::Easy.new("http://www.google.co.uk")
322
+ c.perform
323
+ puts c.body
324
+ ```
325
+
326
+ ### Additional config:
327
+
328
+ ```ruby
329
+ http = Curl::Easy.perform("http://www.google.co.uk") do |curl|
330
+ curl.headers["User-Agent"] = "myapp-0.0"
331
+ curl.verbose = true
332
+ end
333
+ ```
334
+
335
+ Same thing, more manual:
336
+
337
+ ```ruby
338
+ c = Curl::Easy.new("http://www.google.co.uk") do |curl|
339
+ curl.headers["User-Agent"] = "myapp-0.0"
340
+ curl.verbose = true
341
+ end
342
+
343
+ c.perform
344
+ ```
345
+
346
+ ### HTTP basic authentication:
347
+
348
+ ```ruby
349
+ c = Curl::Easy.new("http://github.com/")
350
+ c.http_auth_types = :basic
351
+ c.username = 'foo'
352
+ c.password = 'bar'
353
+ c.perform
354
+ ```
355
+
356
+ ### HTTP "insecure" SSL connections (like curl -k, --insecure) to avoid Curl::Err::SSLCACertificateError:
357
+
358
+ ```ruby
359
+ c = Curl::Easy.new("https://github.com/")
360
+ c.ssl_verify_peer = false
361
+ c.perform
362
+ ```
363
+
364
+ ### Supplying custom handlers:
365
+
366
+ ```ruby
367
+ c = Curl::Easy.new("http://www.google.co.uk")
368
+
369
+ c.on_body { |data| print(data) }
370
+ c.on_header { |data| print(data) }
371
+
372
+ c.perform
373
+ ```
374
+
375
+ ### Reusing Curls:
376
+
377
+ ```ruby
378
+ c = Curl::Easy.new
379
+
380
+ ["http://www.google.co.uk", "http://www.ruby-lang.org/"].map do |url|
381
+ c.url = url
382
+ c.perform
383
+ c.body
384
+ end
385
+ ```
386
+
387
+ ### HTTP POST form:
388
+
389
+ Note: Instance methods like `easy.http_post(...)` do not accept a URL argument. Set the URL first (for example, `Curl::Easy.new(url)` or `easy.url = url`) and then call `easy.http_post(...)`. If you want to pass the URL directly to the call, use the class/module helpers such as `Curl::Easy.http_post(url, ...)` or `Curl.post(url, ...)`.
390
+
391
+ ```ruby
392
+ c = Curl::Easy.http_post("http://my.rails.box/thing/create",
393
+ Curl::PostField.content('thing[name]', 'box'),
394
+ Curl::PostField.content('thing[type]', 'storage'))
395
+ ```
396
+
397
+ ### HTTP POST file upload:
398
+
399
+ ```ruby
400
+ c = Curl::Easy.new("http://my.rails.box/files/upload")
401
+ c.multipart_form_post = true
402
+ c.http_post(Curl::PostField.file('thing[file]', 'myfile.rb'))
403
+
404
+ ### Custom request target
405
+
406
+ Some advanced scenarios need a request-target that differs from the URL host/path (for example, absolute-form targets or special values like `*`). If your libcurl supports `CURLOPT_REQUEST_TARGET` (libcurl ≥ 7.55), you can override it:
407
+
408
+ ```ruby
409
+ c = Curl::Easy.new("http://127.0.0.1:9129/methods")
410
+ c.request_target = "http://localhost:9129/methods" # absolute-form target
411
+ c.headers = { 'Host' => 'example.com' } # override Host header if needed
412
+ c.perform
413
+ ```
414
+
415
+ For HTTPS, prefer `easy.resolve = ["host:443:IP"]` to keep Host/SNI/certificates aligned.
416
+ ```
417
+
418
+ ### Using HTTP/2
419
+
420
+ ```ruby
421
+ c = Curl::Easy.new("https://http2.akamai.com")
422
+ c.set(:HTTP_VERSION, Curl::HTTP_2_0)
423
+
424
+ c.perform
425
+ puts (c.body.include? "You are using HTTP/2 right now!") ? "HTTP/2" : "HTTP/1.x"
426
+ ```
427
+
428
+ ### Multi Interface (Basic HTTP GET):
429
+
430
+ ```ruby
431
+ # make multiple GET requests
432
+ easy_options = {:follow_location => true}
433
+ # Use Curl::CURLPIPE_MULTIPLEX for HTTP/2 multiplexing
434
+ multi_options = {:pipeline => Curl::CURLPIPE_HTTP1}
435
+
436
+ Curl::Multi.get(['url1','url2','url3','url4','url5'], easy_options, multi_options) do|easy|
437
+ # do something interesting with the easy response
438
+ puts easy.last_effective_url
439
+ end
440
+ ```
441
+
442
+ ### Multi Interface (Basic HTTP POST):
443
+
444
+ ```ruby
445
+ # make multiple POST requests
446
+ easy_options = {:follow_location => true, :multipart_form_post => true}
447
+ multi_options = {:pipeline => Curl::CURLPIPE_HTTP1}
448
+
449
+
450
+ url_fields = [
451
+ { :url => 'url1', :post_fields => {'f1' => 'v1'} },
452
+ { :url => 'url2', :post_fields => {'f1' => 'v1'} },
453
+ { :url => 'url3', :post_fields => {'f1' => 'v1'} }
454
+ ]
455
+
456
+ Curl::Multi.post(url_fields, easy_options, multi_options) do|easy|
457
+ # do something interesting with the easy response
458
+ puts easy.last_effective_url
459
+ end
460
+ ```
461
+
462
+ ### Multi Interface (Advanced):
463
+
464
+ ```ruby
465
+ responses = {}
466
+ requests = ["http://www.google.co.uk/", "http://www.ruby-lang.org/"]
467
+ m = Curl::Multi.new
468
+ # add a few easy handles
469
+ requests.each do |url|
470
+ responses[url] = ""
471
+ c = Curl::Easy.new(url) do|curl|
472
+ curl.follow_location = true
473
+ curl.on_body{|data| responses[url] << data; data.size }
474
+ curl.on_success {|easy| puts "success, add more easy handles" }
475
+ end
476
+ m.add(c)
477
+ end
478
+
479
+ m.perform do
480
+ puts "idling... can do some work here"
481
+ end
482
+
483
+ requests.each do|url|
484
+ puts responses[url]
485
+ end
486
+ ```
487
+
488
+ ### Easy Callbacks
489
+
490
+ * `on_success` is called when the response code is 2xx
491
+ * `on_redirect` is called when the response code is 3xx
492
+ * `on_missing` is called when the response code is 4xx
493
+ * `on_failure` is called when the response code is 5xx
494
+ * `on_complete` is called in all cases.
495
+
496
+ ### Cookies
497
+
498
+ - Manual cookies: Set the outgoing `Cookie` header via `easy.cookies = "name=value; other=val"`. This only affects the request header and does not modify libcurl's internal cookie engine.
499
+ - Cookie engine: Enable with `easy.enable_cookies = true`. Optionally set `easy.cookiefile` (to load) and/or `easy.cookiejar` (to persist). Cookies received via `Set-Cookie` go into this engine.
500
+ - Inspect engine cookies: `easy.cookielist` returns an array of strings (Netscape or Set-Cookie format).
501
+ - Modify engine cookies: use `easy.cookielist = ...` or `easy.set(:cookielist, ...)` with either a `Set-Cookie` style string, Netscape cookie lines, or special commands: `"ALL"` (clear), `"SESS"` (remove session cookies), `"FLUSH"` (write to jar), `"RELOAD"` (reload from file).
502
+ - Clearing manual cookies: assign an empty string (`easy.cookies = ''`). Assigning `nil` has no effect in current versions.
503
+
504
+ Examples:
505
+
506
+ ```ruby
507
+ easy = Curl::Easy.new("https://example.com")
508
+
509
+ # Use the cookie engine and persist cookies
510
+ easy.enable_cookies = true
511
+ easy.cookiejar = "/tmp/cookies.txt"
512
+ easy.perform
513
+
514
+ # Later: inspect and tweak engine cookies
515
+ p easy.cookielist
516
+ easy.cookielist = 'ALL' # clear stored cookies
517
+
518
+ # Send custom Cookie header for a single request
519
+ easy.cookies = "flag=1; session_override=abc"
520
+ easy.perform
521
+ easy.cookies = '' # clear manual Cookie header
522
+ ```
data/Rakefile CHANGED
@@ -2,10 +2,11 @@
2
2
  #
3
3
  require 'rake/clean'
4
4
  require 'rake/testtask'
5
+ require "ruby_memcheck"
6
+ require 'shellwords'
5
7
  begin
6
- require 'rdoc/task'
7
- rescue LoadError => e
8
- require 'rake/rdoctask'
8
+ require 'mixlib/shellout'
9
+ rescue LoadError
9
10
  end
10
11
 
11
12
  CLEAN.include '**/*.o'
@@ -15,6 +16,15 @@ CLOBBER.include '**/*.log'
15
16
  CLOBBER.include '**/Makefile'
16
17
  CLOBBER.include '**/extconf.h'
17
18
 
19
+ # Load support ruby and rake files (in this order)
20
+ Dir.glob('tasks/*.rb').each { |r| load r}
21
+ Dir.glob('tasks/*.rake').each { |r| load r}
22
+
23
+ desc 'Print Ruby major version (ie "2_5")'
24
+ task :ruby_version do
25
+ print current_ruby_major
26
+ end
27
+
18
28
  def announce(msg='')
19
29
  $stderr.puts msg
20
30
  end
@@ -43,40 +53,31 @@ end
43
53
  make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
44
54
  MAKECMD = ENV['MAKE_CMD'] || make_program
45
55
  MAKEOPTS = ENV['MAKE_OPTS'] || ''
46
-
47
56
  CURB_SO = "ext/curb_core.#{(defined?(RbConfig) ? RbConfig : Config)::MAKEFILE_CONFIG['DLEXT']}"
48
57
 
49
58
  file 'ext/Makefile' => 'ext/extconf.rb' do
50
- Dir.chdir('ext') do
51
- ruby "extconf.rb #{ENV['EXTCONF_OPTS']}"
52
- end
59
+ shell(['ruby', 'extconf.rb', ENV['EXTCONF_OPTS'].to_s],
60
+ { :live_stdout => STDOUT , :cwd => "#{Dir.pwd}/ext" }
61
+ ).error!
53
62
  end
54
63
 
55
64
  def make(target = '')
56
- Dir.chdir('ext') do
57
- pid = system("#{MAKECMD} #{MAKEOPTS} #{target}")
58
- $?.exitstatus
59
- end
65
+ shell(["#{MAKECMD}", "#{MAKEOPTS}", "#{target}"].reject(&:empty?),
66
+ { :live_stdout => STDOUT, :cwd => "#{Dir.pwd}/ext" }
67
+ ).error!
60
68
  end
61
69
 
62
70
  # Let make handle dependencies between c/o/so - we'll just run it.
63
71
  file CURB_SO => (['ext/Makefile'] + Dir['ext/*.c'] + Dir['ext/*.h']) do
64
- m = make
65
- fail "Make failed (status #{m})" unless m == 0
72
+ make
66
73
  end
67
74
 
68
75
  desc "Compile the shared object"
69
76
  task :compile => [CURB_SO]
70
77
 
71
- desc "Create the markdown file"
72
- task :markdown do
73
- cp "README", "README.markdown"
74
- end
75
-
76
78
  desc "Install to your site_ruby directory"
77
- task :install => :alltests do
78
- m = make 'install'
79
- fail "Make install failed (status #{m})" unless m == 0
79
+ task :install do
80
+ make 'install'
80
81
  end
81
82
 
82
83
  # Test Tasks ---------------------------------------------------------
@@ -88,18 +89,64 @@ task :rmpid do
88
89
  FileUtils.rm_rf Dir.glob("tests/server_lock-*")
89
90
  end
90
91
 
92
+ at_exit do
93
+ next unless defined?(Rake.application)
94
+
95
+ top_level_tasks = Rake.application.top_level_tasks
96
+ test_tasks = %w[default test ta tu alltests unittests bugtests test:valgrind]
97
+ should_cleanup = top_level_tasks.empty? || top_level_tasks.any? { |task| test_tasks.include?(task) }
98
+
99
+ FileUtils.rm_rf Dir.glob("tests/server_lock-*") if should_cleanup
100
+ end
101
+
91
102
  if ENV['RELTEST']
92
103
  announce "Release task testing - not running regression tests on alltests"
93
104
  task :alltests => [:unittests]
94
105
  else
95
106
  task :alltests => [:unittests, :bugtests]
96
107
  end
97
-
108
+
109
+ ruby_memcheck_config = { binary_name: 'curb_core' }
110
+
111
+ if RUBY_ENGINE == 'ruby' && RUBY_VERSION == '4.0.4'
112
+ # Ruby 4.0.4 reports fiber/block-handler VM stack accesses under Valgrind.
113
+ # Keep reporting errors that originate in curb_core, but filter Ruby-side noise.
114
+ ruby_memcheck_config[:filter_all_errors] = true
115
+ if RubyMemcheck::Configuration.instance_method(:initialize).parameters.any? { |type, name|
116
+ type == :key && name == :use_only_ruby_free_at_exit
117
+ }
118
+ ruby_memcheck_config[:use_only_ruby_free_at_exit] = false
119
+ end
120
+ ruby_memcheck_config[:skipped_ruby_functions] =
121
+ RubyMemcheck::Configuration::DEFAULT_SKIPPED_RUBY_FUNCTIONS + [
122
+ /\Arb_vm_frame_block_handler\z/
123
+ ]
124
+ end
125
+
126
+ RubyMemcheck.config(**ruby_memcheck_config)
127
+ namespace :test do
128
+ RubyMemcheck::TestTask.new(valgrind: :compile) do|t|
129
+ t.test_files = FileList['tests/tc_*.rb']
130
+ t.verbose = false
131
+ end
132
+
133
+ desc 'Run the standalone leak trace helper'
134
+ task :leak_trace => :compile do
135
+ ruby_opts = Shellwords.split(ENV['LEAK_TRACE_OPTS'].to_s)
136
+ sh RbConfig.ruby, '-Ilib', '-Iext', 'tests/leak_trace.rb', *ruby_opts
137
+ end
138
+ end
139
+
140
+ Rake::Task['test:valgrind'].enhance([:rmpid]) do
141
+ Rake::Task[:rmpid].reenable
142
+ Rake::Task[:rmpid].invoke
143
+ end
144
+
98
145
  Rake::TestTask.new(:unittests) do |t|
99
146
  t.test_files = FileList['tests/tc_*.rb']
100
147
  t.verbose = false
101
148
  end
102
-
149
+
103
150
  Rake::TestTask.new(:bugtests) do |t|
104
151
  t.test_files = FileList['tests/bug_*.rb']
105
152
  t.verbose = false
@@ -141,6 +188,12 @@ end
141
188
 
142
189
  desc "Publish the RDoc documentation to project web site"
143
190
  task :doc_upload => [ :doc ] do
191
+ begin
192
+ require 'rdoc/task'
193
+ rescue LoadError => e
194
+ require 'rake/rdoctask'
195
+ end
196
+
144
197
  if ENV['RELTEST']
145
198
  announce "Release Task Testing, skipping doc upload"
146
199
  else
@@ -171,13 +224,13 @@ else
171
224
 
172
225
  desc 'Build gem'
173
226
  task :package => :gemspec do
174
- require 'rubygems/specification'
227
+ require 'rubygems/package'
175
228
  spec_source = File.read File.join(File.dirname(__FILE__),'curb.gemspec')
176
229
  spec = nil
177
230
  # see: http://gist.github.com/16215
178
- Thread.new { spec = eval("$SAFE = 3\n#{spec_source}") }.join
231
+ Thread.new { spec = eval("#{spec_source}") }.join
179
232
  spec.validate
180
- Gem::Builder.new(spec).build
233
+ Gem::Package.build(spec)
181
234
  end
182
235
 
183
236
  task :static do