Skip to content

v0.9.5

Choose a tag to compare

@Hindurable Hindurable released this 04 Jun 21:39
· 262 commits to main since this release
ac18964

[0.9.5] — 2026-06-04

Added

  • Playground shows its deployed version, and the API auto-deploys on
    release tags.
    The playground header now carries a version pill — a
    __NURL_VERSION__ placeholder in index.html is stamped at image-build
    time from the NURL_VERSION build-arg (dev for local builds). A new
    .github/workflows/api-deploy.yml builds the API image on a v* tag (or
    manual dispatch), pushes it to Docker Hub under the exact semver
    (nurllang/nurl:vX.Y.Z — no :latest), pins cloudflare/Dockerfile's
    FROM to that tag and runs wrangler deploy, so a git tag is now a
    reproducible playground release. The Docker image was renamed
    hindurable/nurlnurllang/nurl; registry-deploy.yml is now
    manual-only (the registry changes rarely).

  • MQTT-over-WebSocket transport — mqtt_connect_ws. Adds a WebSocket
    transport alongside the raw TCP/TLS path so a client can reach a broker's
    MQTT-over-WS endpoint (e.g. wss://host:8084/mqtt) — handy when a firewall
    only permits the WS port inbound. wss:// enables TLS with certificate
    verification and negotiates the mqtt subprotocol automatically; the codec
    and framed packet reader stay transport-blind behind two chokepoints. New
    entrypoints mqtt_connect_ws / mqtt_connect_ws_cfg; mqtt_disconnect
    also sends a WS Close frame, and mqtt_reconnect rejects WS clients (no URL
    to redo the upgrade). stdlib/ext/mqtt.nu.

  • Package manager → MLP: login/search/info, yank, token-revoke, catalog UI
    (ROADMAP §4).
    Rounds the registry out into a minimum lovable product.

    • CLI ergonomics. nurlpkg login stores a per-registry publish token
      in ~/.nurl/credentials (chmod 600) — publish/yank resolve the token
      $NURL_TOKEN → credentials, so it no longer has to live in the
      environment. nurlpkg logout [--revoke] forgets it (and optionally
      revokes it server-side). nurlpkg search <q> and nurlpkg info <name>
      query the registry (info with no arg still prints the local manifest).
      New stdlib/ext/credentials.nu.
    • Registry hygiene. nurlpkg yank|unyank <name> <version> flips a
      version's yanked flag (owner-only, via POST /api/v1/{yank,unyank}); the
      resolver already skips yanked versions, so a yanked release disappears
      from resolution. nurlpkg logout --revoke (POST /api/v1/revoke) deletes
      the presented token from D1.
    • Catalog UI. The Worker's / is now a searchable package list and
      /packages/<name> a detail page (versions with yank state, latest
      dependencies, an install snippet); GET /api/v1/search?q= backs the CLI.
    • Client helpers: pkg_search (pkg_fetch.nu), pkg_yank / pkg_revoke
      (pkg_publish.nu). Regression compiler/tests/credentials_basic.nu
      (set/get/upsert/multi-registry/remove; gated NURL_CREDS_TESTS=1, clean
      under ASan/UBSan). Whole feature set verified end-to-end against the
      Worker under wrangler dev: login → creds-based publish → search → info
      → yank (install then fails ResolveNoMatch) → unyank → catalog → logout
      --revoke → publish rejected (PubAuth).
  • Transitive registry dependencies — nurlpkg publish sends X-Nurl-Deps.
    Publishing now includes the manifest's registry dependencies (a JSON
    [{name, req}] built by __deps_json) as the X-Nurl-Deps header, which
    the registry records in the package index. pkg_publish gained a
    deps_json parameter. With the deps in the index, resolve_registry
    pulls sub-dependencies transitively — previously the index always
    recorded deps: [], so only leaf registry packages installed correctly.
    Verified end-to-end against the local Cloudflare Worker: publish tdep-b,
    publish tdep-a (depends on tdep-b ^1.0), then install a consumer of
    only tdep-a → both land in deps/ and the lock. Registry now supports
    real dependency graphs. stdlib/ext/pkg_publish.nu, tools/nurlpkg/main.nu.

  • Package registry service — Cloudflare Worker + R2 + D1 (registry/,
    ROADMAP §4 phase 6).
    The deployable server side of the ecosystem, in
    TypeScript. The read path serves the static index/<name>.json +
    content-addressed pkgs/<name>/<name>-<v>.tar.gz from R2 (cacheable, no
    compute); the write path POST /api/v1/publish authenticates a Bearer
    token (peppered SHA-256 looked up in D1), enforces first-publisher name
    ownership
    + version immutability, recomputes the tarball SHA-256
    server-side
    (never trusts a client digest), and writes the tarball +
    updated index to R2. Identity bootstraps via GitHub OAuth (/login
    /auth/callback mints a one-time CLI token). D1 schema in
    migrations/0001_init.sql (users / tokens / packages / versions).
    Implements exactly the wire contract the NURL client already drives.
    Validated end-to-end locally (no Cloudflare account): under
    wrangler dev (miniflare R2 + D1), the real nurlpkg binary completes a
    full publish → install round-trip plus immutability (409) and bad-token
    (401) rejections — registry/test-local.sh. Ships with
    registry/DEPLOY.md, a registry-deploy.yml GitHub Actions workflow
    (guarded so a placeholder token can't trigger a broken deploy), and
    secrets kept out of the repo (wrangler secret put for
    GITHUB_CLIENT_SECRET / TOKEN_PEPPER; GH Actions secrets for the
    Cloudflare deploy token). This completes the registry-backed package
    manager: nurlpkg publish + nurlpkg install against a deployable
    registry, all pure-NURL on the client and standing up locally today.

  • Package publishing — stdlib/ext/pkg_publish.nu + nurlpkg publish
    (ROADMAP §4 phase 5).
    The write side. pkg_pack walks a project tree
    into a .tar.gz (excluding deps, .git/dotfiles, nurl.lock,
    target, build); pkg_publish uploads it with POST <registry>/api/v1/publish, Authorization: Bearer <token>, and
    X-Nurl-Package / X-Nurl-Version headers (binary body via
    http_request_bytes), mapping status to PubAuth (401/403) / PubConflict
    (409, version immutability) / PubRejected. nurlpkg publish packs the
    current project, prints its size + SHA-256, and uploads using the token
    from $NURL_TOKEN and the registry from $NURL_REGISTRY
    [package].registry → default; a missing token or any non-2xx exits
    non-zero. The registry recomputes the checksum server-side — no
    client-supplied digest is trusted. Regression
    compiler/tests/pkg_pack_basic.nu (offline pack + gunzip + tar_parse
    membership: nested source included, deps/ + dotfiles excluded), clean
    under ASan/UBSan + leak-free. Verified end-to-end against a static
    python registry: a full publish → install round-trip (a library
    packed, uploaded with a Bearer token, then resolved + installed into a
    consumer's deps/), plus immutability (409), bad-token (401), and
    no-token rejections. This is the exact contract the Cloudflare
    Worker + R2 write endpoint (phase 6) will implement.

  • Verified registry install — stdlib/ext/pkg_fetch.nu (ROADMAP §4
    phase 4b).
    The I/O side that turns a resolved LockPkg into files on
    disk against a static-HTTP registry (R2 + CDN shape). pkg_fetch_index
    GETs <registry>/index/<name>.json; pkg_install_one downloads
    <registry>/pkgs/<name>/<name>-<v>.tar.gz, verifies its SHA-256
    against the recorded checksum
    , gunzips, and path-safe tar_unpacks it
    into <dest>/<name> — composing the whole pure-NURL package stack (http
    binary body + sha256 + gzip + tar). Capstone regression
    compiler/tests/pkg_install_e2e.nu stands up a loopback NURL registry
    server
    (serves a real tar_create+gzip tarball + an index carrying
    its true checksum) and drives the full pipeline resolve → download →
    verify → unpack end-to-end, plus a wrong-checksum rejection
    (PkgChecksumMismatch); NURL_NET_TESTS=1. Clean under ASan/UBSan.

    nurlpkg install is now registry-aware. It resolves the manifest's
    registry deps (foo = "^1.2" or { version, registry }), downloads +
    verifies + unpacks each into deps/<name>, and writes a nurl.lock
    whose registry entries carry source = "registry+<url>" + the tarball
    checksum (path deps keep their local source). The registry URL comes
    from $NURL_REGISTRY[package].registry → a built-in default. A
    failed download or checksum mismatch makes install exit non-zero.
    Verified end-to-end against a static python -m http.server registry
    serving a GNU-tar --format=ustar | gzip package (differential interop):
    the happy path installs + locks with the sha256sum-computed checksum,
    and a tampered index checksum is rejected with the package left
    uninstalled.

  • Binary-safe HTTP response body — http_body_bytes / http_body_len.
    http_body_str reads the response body through a NUL-terminated carrier
    (truncates at the first embedded NUL). The new http_body_bytes returns
    an owned, length-accurate ( Vec u ) copy, and http_body_len exposes
    the byte count — required for binary downloads (package tarballs, images,
    compressed payloads). Completes the binary HTTP story alongside the
    earlier binary-safe request body. Regression:
    compiler/tests/http_response_binary.nu (loopback server replies a
    5-byte A B \0 C D body; client confirms full length + the NUL via
    http_body_bytes; NURL_NET_TESTS=1). Clean under ASan/UBSan.
    stdlib/ext/http.nu.

  • Registry resolution core — stdlib/ext/registry_index.nu +
    stdlib/ext/resolver.nu (ROADMAP §4 phase 4).
    The read side of the
    package registry. A registry serves a static JSON index per package at
    <registry>/index/<name>.json (versions, each with a tarball SHA-256
    checksum, yanked flag, and deps); tarballs live at the
    content-addressed <registry>/pkgs/<name>/<name>-<ver>.tar.gz, so the
    whole read path is a cacheable CDN with no compute.
    registry_index.nu parses an index and regindex_select picks the
    highest non-yanked version satisfying a semver requirement.
    resolver.nu's resolve_registry walks the transitive dependency graph
    (BFS) and emits a Vec[LockPkg] ready for lock_serialize — the index
    fetcher is injected as a closure (name → index-JSON), so resolution is
    pure and offline-testable; nurlpkg will wire it to an HTTP GET. v1 policy:
    one version per name (first requirement wins; a later one must share that
    version or it's ResolveConflict), sub-deps from the parent's registry,
    path deps left to the existing symlink installer. Regressions:
    compiler/tests/registry_index_basic.nu (parse + select + yanked
    exclusion + tarball URL) and compiler/tests/resolver_basic.nu
    (transitive resolve with a mock index, ResolveNoMatch, ResolveNotFound).
    Both clean under ASan/UBSan and leak-free.

Changed

  • Signedness is now coupled to the value's type instead of a free-floating
    side-channel — the structural fix for the whole u-vs-signed-i8 bug
    class.
    The LLVM type (i8/i16/i32) can't distinguish NURL's unsigned
    u/u16/u32 from the signed types, so signedness travelled in a
    separate __last_unsigned__ syms entry that each of ~83 value-producing
    sites had to remember to update — and ~67 didn't, leaving a stale flag
    that silently sign- or zero-extended the next widen (the source of a long
    run of miscompiles). Now signedness lives in g_last_unsigned_p right
    next to g_last_type_ptr, and nurl_set_last_type always resets it
    (signed default): every value-producing site already calls the type-setter
    (IR needs the type), so a stale "unsigned" can no longer leak. The handful
    of unsigned-PRODUCING sites assert it atomically with the type via
    nurl_set_last_type_u / nurl_mark_unsigned, and widen/op-selection
    readers consult nurl_last_unsigned. This eliminated the stale-leak
    subclass structurally; the migration also surfaced and fixed a latent gap
    the old leaky channel had masked by accident — bitwise &/|
    (gen_bitwise_binary) never set its result's signedness, relying on the
    last operand's flag happening to survive. Net: fewer, simpler, faster
    (a global vs a string-keyed map) and no longer forgettable. Bootstrap
    fixed point holds; full suite + ASan/UBSan green; 500 fuzzer seeds clean
    across every dimension.

Fixed

  • Two more silent unsigned-widening miscompiles (same __last_unsigned__
    side-channel hazard; fixed in compiler/nurlc.nu). Regression
    compiler/tests/const_ternary_signedness.nu (7 known-answer checks).

    1. An unsigned global const load sign-extended. # i GU over
      : u GU 200 gave −56. gen_const_decl now records <const>__unsigned
      (which gen_ident already turns into __last_unsigned__ on load).
    2. A ? (ternary) result didn't carry its arms' signedness.
      # i ? c (# u 200) (# u 100) sign-extended the selected value.
      gen_cond now snapshots each arm's __last_unsigned__ and sets the
      result flag (the arms share a type, so either suffices).
  • A call to an unsigned-returning function sign-extended at the call
    site.
    # i ( f ) where f → u returns 200 gave −56 (and likewise for
    u16/u32): the call site never carried the callee's return signedness
    onto the __last_unsigned__ side-channel the enclosing widening cast
    reads (the LLVM return type i8/i16/i32 can't distinguish u from i8).
    scan_fn_sigs now records <fn>__ret_unsigned in the persistent pre-pass
    symbol table (the per-function gen_fn_decl scope doesn't reach call
    sites), and gen_call re-asserts it on __last_unsigned__ after the
    call. Regression compiler/tests/fn_return_signedness.nu (5 known-answer
    checks). Bootstrap fixed point holds; full suite + ASan/UBSan green.

  • Narrow sized-int enum payloads now compile and round-trip correctly.
    An enum variant carrying a u/i8/u16/i16/u32/i32 payload (e.g.
    : | E { None Val u }) was accepted by the front-end but emitted invalid
    IR: gen_agg_lit only converted i64/i32 payloads into the enum's pointer
    slot (so an i8 payload hit insertvalue …, i8 … against a ptr field —
    clang reject), and gen_match only un-converted i1/i64 (storing a ptr
    into an i8 binding). Now construction widens a narrow payload to i64
    (zext for an unsigned payload, sext for signed — from the payload
    signedness gen_enum_decl now records) before inttoptr, and the match
    ptrtoints back and truncs to the payload width, carrying the payload's
    signedness onto the binding so a later widen zero-extends an unsigned
    payload. Found by hand-probing the fuzzer's struct dimension outward.
    Bootstrap fixed point holds; full suite + ASan/UBSan green. Regression
    compiler/tests/enum_payload_signedness.nu (5 known-answer checks).

  • Two silent struct-field signedness miscompiles (same fuzzer, extended
    with a struct dimension; same root cause — the LLVM field type can't carry
    NURL's signedness). Both fixed in compiler/nurlc.nu; regression
    compiler/tests/struct_field_signedness.nu (8 known-answer checks);
    validated by 600 fuzzer seeds with the struct dimension.

    1. Reading an unsigned field sign-extended. # i . rec u8field over a
      u/u16/u32 field holding e.g. 200 read back −56: gen_member never
      surfaced the field's declared signedness onto __last_unsigned__.
      gen_struct_decl now records <S>__<field>__unsigned, and both the
      value (extractvalue) and pointer (GEP+load) field-load paths set the
      flag from it.
    2. Constructing a wider field from a narrower unsigned value
      sign-extended.
      @ Wide { # u 130 } into an i64 field stored −126
      instead of 130 — gen_agg_lit's field-store widening hardcoded sext.
      It now picks zext when the field value is unsigned (the
      __last_unsigned__ snapshot it already takes), sext otherwise.
  • Two silent integer miscompiles, found by a new differential fuzzer
    (tools/fuzz) and fixed at the root in compiler/nurlc.nu.
    Both
    produced wrong values with no error — the worst class of bug.

    1. Unsigned-byte cast widening sign-extended. # i64 # u 217 gave
      −39 instead of 217: a nested cast-to-unsigned never set the
      __last_unsigned__ side-channel the enclosing widening cast consults,
      so it defaulted to sext. gen_cast now records the cast target's
      signedness for an enclosing widen / binop / shift. (Previously only
      casts whose subject was a typed binding — where gen_ident sets the
      flag — widened correctly.)
    2. Signed i8 arithmetic treated as unsigned. gen_binary inferred
      unsignedness from the LLVM type i8, but both the unsigned NURL u
      and the signed i8 lower to LLVM i8 — so signed i8 / % >> <
      selected udiv/urem/lshr/icmp u* and the result was marked
      unsigned, silently zero-extending a negative value at the next widen.
      Signedness now comes solely from the __last_unsigned__ flag (set by
      gen_ident from a binding's __unsigned and by gen_cast from an
      unsigned cast target), never from the ambiguous LLVM type.
      Bootstrap fixed point holds; full suite + ASan/UBSan green. Regression
      compiler/tests/cast_signedness.nu (12 known-answer checks). Validated
      by 340 fuzzer seeds (0 divergences).
  • Three silent int↔float conversion miscompiles (same fuzzer, extended
    with float round-trip + comparison + store-coercion probes; fixed in
    gen_cast). Same root cause — the LLVM integer type can't carry
    signedness, so it must ride the __last_unsigned__ side-channel.

    1. Unsigned int → float used sitofp. # f # u32 0x80000001 became a
      negative float (≈ −2.1e9 instead of +2.1e9); # f # u 200 became
      −56. Now uitofp when the source is unsigned.
    2. Float → int ignored target signedness. Now fptoui for an unsigned
      target (a value above the signed max no longer becomes poison), else
      fptosi.
    3. A float result leaked its source int's stale unsigned flag. After
      # i64 # f # u …, the still-set __last_unsigned__ made a surrounding
      *// pick udiv on a negative product (e.g. −65 / 7 computed as
      an unsigned divide → garbage). Float-producing casts now clear the
      flag; float→int casts set it from the target. Regression
      compiler/tests/cast_int_float.nu (9 known-answer checks). Validated by
      600 fuzzer seeds with the new float dimension (0 divergences).

Added

  • Differential fuzzer for nurlc integer codegen (tools/fuzz).
    gen.py generates random sized-integer expression trees and, from the
    same tree, both a self-checking NURL program (prints each result's exact
    64-bit pattern) and a Python reference oracle with explicit
    two's-complement / width / signedness semantics. fuzz.sh compiles each
    at -O0 and -O2 and requires stdout(-O0) == stdout(-O2) == oracle,
    catching miscompiles that are wrong at every optimisation level. Biased
    toward the historically fragile surface (width coercions, unsigned
    arithmetic, mixed signed/unsigned); generates no UB. Found and fixed two
    silent miscompiles on its first run (see Fixed, above). See
    tools/fuzz/README.md. Subsequently extended with let-binding store
    coercion, variable reuse, comparison operators, and int→float→int
    round-trips — which surfaced three more (see Fixed).

  • USTAR tar reader + writer — stdlib/ext/tar.nu. Pure-NURL POSIX.1-1988
    tar: tar_create (entries → archive bytes), tar_parse (bytes → entries,
    in-memory), and tar_unpack (path-safe extract to disk). Composes with
    gzip_compress/_decompress to make the .tar.gz package format the
    registry will use. The reader treats archives as untrusted input:
    tar_unpack rejects absolute paths and .. components (TarUnsafePath) and
    refuses symlink/hardlink/device members (TarUnsupported) so nothing can
    escape the destination; every header checksum is verified (TarBadChecksum)
    and an over-long declared size is TarTruncated. v1 supports the 100-byte
    name field on write (TarPathTooLong otherwise) and honours the prefix
    field on read. Bidirectionally interop-tested against GNU tar (NURL→tar xf and tar cf→NURL both round-trip). Regression:
    compiler/tests/tar_basic.nu (round-trip incl. embedded NUL in file data,
    gzip composition, checksum tamper, ../ rejection, unpack + binary
    read-back); verified clean under ASan/UBSan. First building block of the
    registry-backed package manager (ROADMAP §4).

  • Semantic Versioning 2.0.0 — stdlib/ext/semver.nu. Pure-NURL semver
    parse / compare / render with full precedence ordering, including the
    prerelease rules (§11: numeric < alphanumeric identifiers, fewer < more
    identifiers, prerelease < release; build metadata ignored). Plus
    version requirements: semver_req_parse turns a constraint (^1.2.3,
    ~1.2, >=1.0, <2.0.0, =1.2.3, 1.*, *, or a bare 1.2.3) into a
    half-open range, semver_req_matches tests a version, and
    semver_req_max_satisfying picks the highest matching version — the
    resolution primitive the registry-backed package manager needs (ROADMAP
    §4). Constraint dialect is Cargo-shaped: a bare 1.2.3 means ^1.2.3,
    use =1.2.3 to pin. v1 matches prereleases by pure range containment (no
    Cargo-style prerelease comparator special-casing yet). Regression:
    compiler/tests/semver_basic.nu (round-trip, the canonical §11 precedence
    chain, every constraint operator, max_satisfying, parse errors); clean
    under ASan/UBSan and leak-free.

  • Registry-ready manifest + typed lockfile. stdlib/ext/manifest.nu's
    Dep gained a registry field and Manifest gained a default
    [package].registry, so a dependency can now be expressed as a path dep
    ({ path = "…" }), a bare registry dep (foo = "^1.2", default
    registry), or an explicit registry dep
    ({ version = "1.0", registry = "…" }); dep_is_path / dep_is_registry
    discriminate. New stdlib/ext/lockfile.nu is a typed view over
    nurl.lock: a LockPkg { name, version, source, checksum } with
    lock_serialize (deterministic, name-sorted, Cargo-shaped [[package]]
    blocks; source/checksum omitted for path/local packages) and
    lock_parse / lock_load (round-trips through toml.nu's
    array-of-tables). checksum is the hex SHA-256 of the package tarball —
    the integrity pin a registry install verifies. Regressions:
    compiler/tests/manifest_registry.nu, compiler/tests/lockfile_basic.nu;
    clean under ASan/UBSan, leak-free. ROADMAP §4 phase 3 (data model for
    registry deps). nurlpkg's two Dep construction sites updated for the
    new field.

  • WebSocket client (RFC 6455 §4.1 + §5.3). stdlib/ext/websocket.nu
    gained the full client side to match the existing server. ws_connect
    / ws_connect_with parse a ws://… / wss://… URL, dial out (plain or
    TLS-with-cert-verification via the runtime client-connect primitives),
    send the HTTP Upgrade request with a fresh random Sec-WebSocket-Key,
    and validate the 101 response's Sec-WebSocket-Accept. Outbound frames
    are masked with a CSPRNG-drawn 4-byte key (ws_client_send_text /
    _binary / _ping / _pong / _close, ws_client_write_frame,
    ws_serialize_frame_masked); inbound server frames are read and required
    to be unmasked (ws_client_read_frame / _read_message /
    _serve_messages, which auto-pong masked). The frame reader/assembler is
    now shared between both directions via an internal __ws_read_frame_ex /
    __ws_read_message_ex parameterised on direction — no duplicated framing
    logic. Regression: compiler/tests/websocket_client.nu (RFC 6455 §5.7
    masked-frame byte vector, URL parsing, and a live NURL_NET_TESTS=1
    client↔server echo round-trip proving interop with the server stack).
    Example: examples/ws_client.nu (pairs with examples/ws_echo.nu).

  • Binary-safe HTTP request bodies — http_*_bytes family. The s-body
    http_request / http_post / http_put family recovers the body length
    via strlen, so a request body with embedded NUL bytes (binary file
    uploads, MessagePack, protobuf) truncated at the first NUL. New
    length-carrying variants take the body as a ( Vec u ) and ship it via
    CURLOPT_COPYPOSTFIELDS + an explicit POSTFIELDSIZE, so the exact byte
    count is sent: http_request_bytes / http_request_bytes_to,
    http_post_bytes, http_put_bytes, and the streaming
    http_stream_open_bytes_to. The body argument is borrowed (the caller
    still owns it). Binary fidelity requires the libcurl backend; the
    WinHTTP/stub fallback round-trips through a NUL-terminated s and
    degrades to the old truncation. stdlib/ext/http.nu.

Fixed

  • Reverse-proxy request body is now binary-safe + a latent use-after-free
    is closed.
    proxy_stream_to_conn_with forwarded the upstream request
    body by converting the request's ( Vec u ) body to a NUL-terminated s
    and shipping it through CURLOPT_POSTFIELDS (strlen-sized), truncating
    binary uploads at the first NUL. Worse, the streaming opener set
    non-copying CURLOPT_POSTFIELDS and the proxy freed the body buffer
    before the first multi_perform read it — a dangling-pointer read that
    only escaped notice on small JSON bodies. Both are fixed by routing the
    length-tracked body through http_stream_open_bytes_to, which uses
    CURLOPT_COPYPOSTFIELDS (libcurl snapshots the bytes at open time, so the
    caller may free immediately and embedded NULs survive). Regression:
    compiler/tests/http_binary_body.nu (NURL client http_post_bytes of a
    5-byte A B \0 C D body to a loopback NURL server, asserting the server
    parsed all 5 bytes; NURL_NET_TESTS=1). Verified clean under ASan/UBSan.
    stdlib/ext/http.nu, stdlib/ext/http_proxy.nu.