curb 1.4.1 → 1.5.0
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 +4 -4
- data/ChangeLog.md +16 -1
- data/README.md +17 -6
- data/ext/curb.h +4 -4
- data/ext/curb_easy.c +302 -34
- data/ext/curb_easy.h +5 -0
- data/ext/curb_multi.c +32 -15
- data/ext/extconf.rb +1 -0
- data/lib/curl/easy.rb +15 -2
- data/lib/curl/multi.rb +30 -8
- data/lib/curl.rb +69 -8
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +1 -1
- data/tests/bug_follow_redirect_288.rb +1 -1
- data/tests/bug_poison.rb +1 -1
- data/tests/bug_raise_on_callback.rb +1 -1
- data/tests/helper.rb +9 -8
- data/tests/tc_curl_easy.rb +188 -0
- data/tests/tc_curl_multi.rb +32 -0
- data/tests/tc_curl_network_policy.rb +25 -32
- data/tests/tc_curl_security_hardening.rb +386 -0
- data/tests/tc_fiber_scheduler.rb +46 -6
- data/tests/tc_ractor.rb +5 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d59fbe309c871b35fe03022151783ee2f88a2a348847758034c4ef83034e868
|
|
4
|
+
data.tar.gz: 3c143d3940a09679b69bd9d975342bbdbe1a200d1cf24fa6756714570d977d1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41ecf3ceed954e7a79b19db4b4969e2186407cf9d071b570471e4867d1a20521adf1a0091fa2c2c98902817f3c6c2ed435d11d0f9c3b9137ffd9923a11792468
|
|
7
|
+
data.tar.gz: 04cdc459106867e381df0c89db22448636cca5dbe6f7073ba0f7b70d9124030bd5a909b3c90de6242d3e2fe865ea6a296fcbd1fd35960baba5d6ca51f241c6c0
|
data/ChangeLog.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# ChangeLog
|
|
2
|
-
## 1.
|
|
2
|
+
## 1.5.0
|
|
3
|
+
* Behavior changes (may affect code that does not use the safety features): redirects are now capped at 30 when `max_redirects` is unset, where curb previously passed unlimited (set `max_redirects = -1` to restore unlimited); CR, LF, and NUL in headers, proxy headers, `useragent`, `cookies`, custom request methods, and `CURLOPT_REQUEST_TARGET` now raise `ArgumentError` instead of being sent; `easy.cookies=` is no longer sent to other hosts after a cross-host redirect on libcurl 7.83+; and replacing `post_body`/`put_data` or calling `http_post`/`http_put`/`http_patch`/`http_delete`/`http(verb)` while a transfer is active now raises `RuntimeError`. Under `network_policy = :public` (including `Curl.safe!`), `file://` URLs without a protocol allowlist, non-IP-literal DoH URLs, and Unix sockets without `allow_unix_socket` are refused, and safe mode now defaults to a 60s timeout and 20 redirects. Details for each are below.
|
|
4
|
+
* Read `Curl::Multi.default_timeout` once per `Curl::Multi#perform` instead of on every wait-loop pass. The setting lives in Ractor-local storage, so each read cost two Ruby method calls, and the polling loop read it up to twice per pass (50 MB download: 132–166 `Ractor.current` calls per perform → 14, independent of transfer length). Applies to both the polling and Fiber-scheduler socket-action loops. Changing `default_timeout` from a callback or perform block now takes effect on the next `perform`.
|
|
5
|
+
* Fix a use-after-free when request state is replaced while a transfer is active: `post_body=`, `put_data=`, `autoreferer=`, `http_post`, `http_put`, `http_patch`, `http_delete`, and `http(verb)` called from a callback on the same handle, or on a sibling handle in the same performing `Curl::Multi`, freed the body buffer libcurl was still sending and could crash or send freed memory. These now raise `RuntimeError` like `setopt` does, and re-running native setup on an active handle is rejected the same way. Reconfiguring a handle from completion callbacks or once its transfer finishes is unchanged. Add regression coverage for own-handle and sibling-handle mutation.
|
|
6
|
+
* Test suite: fix intermittent "Could not connect" failures in the per-test WEBrick fixture (seen in the macOS Fiber scheduler tests). It reserved a free port, released it, and re-bound it without a bind address; if the IPv4 bind then lost a race for that ephemeral port, WEBrick silently kept only its IPv6 listener and `127.0.0.1` refused connections. The fixture now binds `127.0.0.1` explicitly and lets the kernel choose the port at bind time.
|
|
7
|
+
* Make `CURLOPT_STDERR` safer against programmer mistakes such as closing the IO too early: previously libcurl wrote verbose output through the IO's own `FILE*`, so closing the IO (including the block form of `File.open`, or closing it after `Curl::Multi#add`) and performing again wrote through freed memory and could corrupt the heap. curb now hands libcurl a stream it owns on a duplicate descriptor, so verbose output keeps reaching the file after the caller closes their IO. The descriptor is released on replacement, `reset`, `close`, and GC; clones get their own. Non-writable IOs now raise `IOError` instead of silently dropping output. Add regression coverage for early close, multi transfers, clones, and descriptor release.
|
|
8
|
+
* Fix a `network_policy = :public` bypass through non-IP sockets: a Unix socket path set with `setopt(CURLOPT_UNIX_SOCKET_PATH, ...)` after `Curl::Multi#add` was used without any destination check. The socket-open policy check now refuses every non-IP socket unless Unix sockets were explicitly allowed (`allow_unix_socket`), regardless of when the option was set. Add regression coverage for late Unix socket configuration.
|
|
9
|
+
* Require public IP-literal DoH endpoints under `network_policy = :public`: libcurl's DoH lookups do not go through the socket policy, so a DoH URL such as `https://localhost/dns-query` (or `127.1`, `[::1]`, a private address) reached local services even with `allow_doh` enabled, and the per-handle policy did not check DoH URLs at all. DoH hostnames are now rejected because they resolve outside the policy; use an address such as `https://1.1.1.1/dns-query`. The host is taken from libcurl's own URL parser, so numeric shorthand forms are normalized before the check. Add regression coverage for loopback, private, shorthand, IPv6, and hostname endpoints.
|
|
10
|
+
* Reject `file://` URLs under `network_policy = :public` when no protocol allowlist is configured, since reading a local file opens no socket for the policy to check. An explicit allowlist that includes `:file` is still honored, and `Curl.safe!`/`safe_*` keep raising `UnsupportedProtocolError` through their HTTP/HTTPS allowlist. Add regression coverage.
|
|
11
|
+
* Stop sending `easy.cookies=` to other hosts on redirect: cookies were passed as `CURLOPT_COOKIE`, which libcurl sends to every host a redirect reaches. They are now sent as a `Cookie` header, which libcurl 7.83+ drops on cross-host redirects. An explicit `Cookie` header still takes precedence, and clearing `cookies` now stops sending them on a reused handle. With the cookie engine enabled (`enable_cookies` or `cookielist`), `cookies=` is still passed as `CURLOPT_COOKIE` so libcurl merges both into one `Cookie` line, and is still forwarded on redirect; use the cookie engine's domain-scoped cookies there. Add regression coverage for cross-host and same-host redirects.
|
|
12
|
+
* Cap redirects by default: curb passed `-1` (unlimited) as `CURLOPT_MAXREDIRS` whenever `max_redirects` was unset, overriding libcurl's own limit. Unset now means at most 30 redirects (libcurl 8.3+'s default, applied on every libcurl version); `max_redirects = -1` still allows unlimited redirects. `Curl.safe!`, `safe_*` helpers, and `Easy#safe_http!` additionally default to a 60-second timeout and at most 20 redirects, configurable through `SafetyConfig#timeout`/`#max_redirects` and the `:timeout`/`:max_redirects` helper options, and applied only when a handle has not set its own (unlimited redirects are capped). Add regression coverage for redirect loops and safe-mode limits.
|
|
13
|
+
* Reject CR, LF, and NUL characters in request headers, proxy headers, `useragent`, `cookies`, custom request methods (including `http(verb)`), and `CURLOPT_REQUEST_TARGET`, raising `ArgumentError`. libcurl sends these values verbatim, so a value built from user input could inject extra headers or split the request. Add regression coverage.
|
|
14
|
+
* Stop reserving 32 KB/16 KB body and header buffers on every perform: that capacity stayed attached to each returned `body_str`/`header_str`, so a 2 KB response held 32 KB. The buffers now start small, grow as data arrives, and are trimmed to fit once the transfer completes (5,000 retained 2 KB bodies: 164 MB → 10.4 MB; 20,000 reused-handle GETs: 29 minor GCs → 2). Request time and large downloads are unchanged. Add regression coverage for retained capacity and large bodies.
|
|
15
|
+
* Cut fiber-scheduler overhead for `Curl::Easy#perform`: the scheduler driver yielded on every `Curl::Multi#perform` pass, about five yields per request, although the socket-action loop already waits through the scheduler on each pass. It now yields only on the fallback wait loop (Windows and builds without socket-action support), which blocks the thread. Under Async, a sequential request drops from ~42 µs/53 allocations to ~33 µs/41, and 100 concurrent fibers from ~30 µs to ~19 µs per request. Add regression coverage that sibling fibers keep running while a large body streams.
|
|
16
|
+
|
|
17
|
+
## 1.4.1
|
|
3
18
|
* Fix CI fixtures for Windows file-URL normalization and libcurl/libpsl localhost cookie loading. Keep parallel multipart Ractor coverage on Windows without triggering Ruby VM bug #21537 through explicit worker GC; retain multipart compaction coverage in the PostField tests. Include this changelog and the transfer-guard regression tests in the gem package.
|
|
4
19
|
* Make `Curl.safe!` default to `network_policy = :public`, including calls without a configuration block. Global safe mode, `safe_*` helpers, and `Easy#safe_http!` now default to a configurable 10 MiB response-body cap, enforced for buffered responses and body callbacks. Explicit helper limits cannot weaken a tighter global cap. Trusted private destinations require an explicit global policy opt-out; raw requests outside safe mode retain their existing defaults. Add regression coverage for default SSRF blocking, multi policy refresh, body limits, and explicit overrides.
|
|
5
20
|
* Complete idle easy-handle detachment during repeated `initialize`, `close`, and `reset`: clear native attachment counts and Ruby request bookkeeping so the handle can be re-added immediately. Check failed libcurl escape/unescape allocations before accessing their results, raising `Curl::Err::CurlError`, and free returned buffers even if Ruby string allocation raises.
|
data/README.md
CHANGED
|
@@ -208,12 +208,17 @@ helpers without application-level validation. For user-supplied URLs, enable the
|
|
|
208
208
|
safety policy for the current Ractor before making requests. `Curl.safe!` defaults
|
|
209
209
|
to HTTP/HTTPS and `network_policy = :public`, blocking local/private destinations
|
|
210
210
|
even when called without a block. Safe mode also defaults to a 10 MiB
|
|
211
|
-
(10,485,760-byte) response-body limit
|
|
211
|
+
(10,485,760-byte) response-body limit, a 60-second transfer timeout, and at most
|
|
212
|
+
20 redirects. The timeout and redirect cap only fill in limits a handle has not
|
|
213
|
+
set itself (an unlimited `max_redirects = -1` is capped); set either to `nil` to
|
|
214
|
+
disable it:
|
|
212
215
|
|
|
213
216
|
```ruby
|
|
214
217
|
Curl.safe! do |config|
|
|
215
218
|
config.network_policy = :public # default; block local/private destination IPs
|
|
216
219
|
config.max_body_bytes = 1_000_000 # optional tighter cap; default is 10 MiB
|
|
220
|
+
config.timeout = 30 # seconds; default is 60
|
|
221
|
+
config.max_redirects = 5 # default is 20
|
|
217
222
|
end
|
|
218
223
|
|
|
219
224
|
curl = Curl.get(user_url) # allows only http/https, including redirects
|
|
@@ -239,21 +244,27 @@ exceptions from `Curl.safe!` honored. Unsupported libcurl builds fail closed.
|
|
|
239
244
|
Safe helpers and `easy.safe_http!` default to the global safe-mode body limit,
|
|
240
245
|
or 10 MiB when global safe mode is not enabled. Override it explicitly with
|
|
241
246
|
`Curl.safe_get(user_url, :max_body_bytes => 1_000_000)` or
|
|
242
|
-
`easy.safe_http!(:max_body_bytes => 20 * 1024 * 1024)
|
|
247
|
+
`easy.safe_http!(:max_body_bytes => 20 * 1024 * 1024)`; `:timeout` and
|
|
248
|
+
`:max_redirects` are accepted the same way. A tighter global limit
|
|
243
249
|
always takes precedence. Set `config.max_body_bytes = nil` (or `0`) to disable
|
|
244
250
|
the global cap; a helper's explicit `:max_body_bytes => nil` or `0` disables its
|
|
245
251
|
own cap, but cannot bypass a global cap or an existing tighter handle limit.
|
|
246
|
-
Raw requests outside safe mode
|
|
252
|
+
Raw requests outside safe mode have no body limit or timeout unless configured
|
|
253
|
+
otherwise, and follow at most 30 redirects when `max_redirects` is unset.
|
|
247
254
|
These limits cover body bytes, not response headers.
|
|
248
255
|
For trusted local services, use the raw helpers with explicit protocol limits.
|
|
249
256
|
If global safe mode is enabled, explicitly set `config.network_policy = :none`
|
|
250
257
|
to opt out of peer-address filtering; `safe_*` helpers still enforce `:public`.
|
|
251
258
|
|
|
252
259
|
With `network_policy = :public`, curb checks peer addresses when libcurl opens
|
|
253
|
-
the socket and blocks local/private destinations
|
|
260
|
+
the socket and blocks local/private destinations, `file://` URLs (unless an
|
|
261
|
+
explicit protocol allowlist includes `:file`), and any non-IP socket such as a
|
|
262
|
+
Unix socket path set after the policy was applied. Proxies, `resolve`,
|
|
254
263
|
`connect_to`, DoH URL overrides, and Unix socket paths are disabled by default
|
|
255
|
-
under this policy unless explicitly allowed in the safety config.
|
|
256
|
-
|
|
264
|
+
under this policy unless explicitly allowed in the safety config. When DoH is
|
|
265
|
+
allowed, the DoH URL must use a public IP address (for example
|
|
266
|
+
`https://1.1.1.1/dns-query`): libcurl resolves a DoH hostname outside the
|
|
267
|
+
policy, so hostnames are rejected. Custom DNS server overrides are rejected. To use a trusted explicit proxy without
|
|
257
268
|
re-enabling environment proxies, set `allowed_proxy_hosts` and configure
|
|
258
269
|
`easy.proxy_url` on the request.
|
|
259
270
|
|
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.
|
|
32
|
-
#define CURB_VER_NUM
|
|
31
|
+
#define CURB_VERSION "1.5.0"
|
|
32
|
+
#define CURB_VER_NUM 1050
|
|
33
33
|
#define CURB_VER_MAJ 1
|
|
34
|
-
#define CURB_VER_MIN
|
|
35
|
-
#define CURB_VER_MIC
|
|
34
|
+
#define CURB_VER_MIN 5
|
|
35
|
+
#define CURB_VER_MIC 0
|
|
36
36
|
#define CURB_VER_PATCH 0
|
|
37
37
|
|
|
38
38
|
|