Files
sausage/userland/curl

Curl for the world

This CLI sends HTTP and HTTPS requests through standard WASI HTTP. The host supplies TLS and transparent credential injection. The CLI does not request or read host credentials. Each redirect creates a separate request, so the host checks every hop. Every request has Content-Length, including empty requests. The client finishes the body before it waits for a response. It does not flush or poll the outgoing stream after its final byte. A fast peer can close that stream after it consumes the framed body. Verbose request output contains only headers that the CLI supplies.

Supported options

  • A positional HTTP or HTTPS URL. A URL without a scheme defaults to HTTP.
  • -X, --request selects the method.
  • Repeatable -H, --header overrides defaults or adds headers. An empty value suppresses a default header.
  • -d, --data reads literal data, @file, or @-. File input removes CR, LF, and NUL bytes.
  • --data-raw treats all input literally, including an initial @.
  • --data-binary preserves file and stdin bytes.
  • Data options imply POST and the form content type. Repeated data options join with & in argument order.
  • --json implies POST, preserves its body, and sets JSON Content-Type and Accept headers.
  • -o, --output writes to a world file. -o - writes to stdout.
  • -O, --remote-name uses the original URL's last path segment.
  • -s, --silent suppresses errors. -S, --show-error restores errors with silent mode.
  • -i, --include includes response headers. -I, --head selects HEAD and prints headers.
  • -L, --location follows redirects. --max-redirs defaults to 50. A negative value removes the limit.
  • Redirects 301, 302, and 303 convert POST to GET. Redirects 307 and 308 preserve the body.
  • Redirects to another origin remove explicit Authorization and Cookie headers.
  • -f, --fail suppresses error bodies and returns 22 for status codes of at least 400.
  • --fail-with-body returns 22 but retains the body.
  • -w, --write-out supports %{http_code}, %{url_effective}, %{content_type}, %{size_download}, and \n.
  • -v, --verbose prints request and response headers to stderr.
  • -u, --user supplies Basic authentication. A missing password uses an empty password without a prompt.
  • -A, --user-agent supplies the User-Agent header.
  • -k, --insecure is accepted and ignored. The host controls TLS verification.
  • -m, --max-time bounds network operations across redirects. Zero removes the deadline.
  • --help prints the CLI's supported options.

Unknown options return 2 with curl's unknown-option message. HTTP failures return 22 only when a fail option applies. Network deadlines return 28. Input errors return 26. Output errors return 23.

Known differences

The CLI supports one URL per invocation. It does not implement multipart -F, --compressed, or curl's other options. It buffers request and response bodies. It does not show a progress meter. The default User-Agent is curl/8.0.0, not the native curl version. WASI exposes the status code, but not the HTTP version or reason phrase. The CLI reconstructs header status lines as HTTP/1.1 with standard reason phrases for common codes. The host can normalize response header names and order. WASI omits hop-by-hop headers, such as Connection, from the response. WASI rejects forbidden request headers. The CLI sets Content-Length from the body size. Verbose output omits native curl's connection and TLS diagnostics. Transport diagnostics describe WASI errors instead of native libcurl errors. Argument diagnostics other than unknown options use clap's text with exit code 2. Write-out supports only the listed substitutions and a literal format string. The CLI does not implement curl's credential prompts, cookie jar, config file, or proxy options.

Validation

Run these commands from userland/:

CARGO_BUILD_JOBS=2 CARGO_TARGET_DIR=/tmp/p1-curl/target nix develop .. -c cargo build -p curl --release --locked

The shared cwd-init/init.o must exist first. Generate it from the repository root if needed:

nix develop -c nu userland/cwd-init/build.nu

Run the comparisons from the repository root:

nix develop -c nu userland/curl/test.nu

The script builds the native fixture with a persistent cache and starts it on a free port. Each run uses a fresh readiness file. The script stops the fixture after success or failure. The script compares deterministic local responses with native curl. It also checks a public HTTPS GET and verifies WASI-only component imports. The script retains its temporary files for inspection.