Files
skyfireandJarvis-AI-42 687d7bcac5 fix: support IPv6 zone IDs in http commands (#19213)
Fixes #9065

## Description

`http get 'http://[fe80::13%eth0]'` (and the other `http *` commands)
fail because `url::Url::parse` does not support IPv6 zone IDs (e.g.
`%eth0`), per the WHATWG URL standard. `curl` handles these URLs fine.

The fix now validates and drives requests with `http::Uri`, the same
parser ureq uses, so nushell and ureq agree on which URLs are valid.

1. **`crates/nu-command/src/network/http/client.rs`**
- `http_parse_url` validates with `http::Uri` instead of `url::Url`
(which rejects zone IDs). It returns the raw URL string; the
`strip_ipv6_zone_id` helper and the returned `Url` are gone.
- A new `authority_is_well_formed` check rejects URLs that `http::Uri`
would otherwise accept leniently, e.g. trailing garbage after an IPv6
literal (`[fe80::13%eth0]foo`) or non-numeric/out-of-range ports
(`host:abc`, `host:99999`). These would silently send the request to a
different host or port.
- `request_set_host_header` sets the `Host` header without the zone ID
(matching curl) when the authority contains one, so strict servers don't
reject the request. A user-provided `Host` header is left untouched.
- `transform_response_using_content_type` derives the `text/plain` file
extension from `http::Uri::path()` so zone-ID URLs no longer fail after
the request has been sent.
2. **`crates/nu-command/src/network/http/resolver.rs`**
- `uri.host()` returns IPv6 addresses wrapped in square brackets (e.g.
`[fe80::13%eth0]`), which `dns_lookup::getaddrinfo` cannot resolve. The
brackets are stripped before resolution, so `getaddrinfo` handles the
zone ID natively (converting the interface name to a scope ID).
- The RFC 6874 `%25` form (e.g. `[fe80::1%25lo0]`) is decoded to a
literal `%` before `getaddrinfo`.
   - `host` stays an `Option` and is passed to `getaddrinfo` as-is.

## User-facing changes (Release notes)

`http get`, `http post`, and the other `http *` commands now accept
**`http://`** URLs with IPv6 literals and link-local zone IDs, e.g.
`http get 'http://[fe80::13%eth0]'`. Plain IPv6 literals (e.g. `http get
'http://[::1]:8080/a.txt'`) work as well. Previously these URLs failed
to parse or resolve.

Note: `https://` URLs with a zone ID are **not** supported. ureq builds
the rustls `ServerName` from the bracket-stripped host, and
`fe80::13%eth0` is neither a valid IP nor a valid DNS name, so TLS
handshakes fail. This is a ureq/rustls limitation and cannot be fixed
from nushell.

## Tests

- `cargo check -p nu-command` passes
- `cargo test -p nu-command --lib network::http` — 20 tests pass,
including:
  - `test_http_parse_url_with_ipv6_zone_id`
  - `test_http_parse_url_rejects_invalid_ipv6_with_zone_id`
  - `test_authority_is_well_formed`
  - `test_request_set_host_header_strips_zone_id`
  - `test_request_set_host_header_leaves_other_urls_alone`
- `cargo test -p nu-command --test tests -- commands::network::http` —
87 tests pass, including the new `http_get_ipv6_literal`, which binds
`[::1]` and makes a real `text/plain` request through
`DnsLookupResolver`.
- `cargo fmt --all -- --check`, `cargo clippy -p nu-command
--all-targets -D warnings` and `typos` are clean.

## Review feedback

All findings from the review have been addressed:

1. **Text/plain responses fail after the request is sent** —
`transform_response_using_content_type` now uses `http::Uri::path()`
instead of `url::Url::parse`.
2. **Zone ID ends up in the `Host` header** — `request_set_host_header`
strips it (like curl); a user-set `Host` header is respected.
3. **Validate with `http::Uri` instead of strip-and-reparse** — done;
`strip_ipv6_zone_id` and the returned `Url` are removed. URLs that ureq
rejects (e.g. `http://[fe80::1%lo 0]:18913/`) are now rejected up front
by `http_parse_url`.
4. **RFC 6874 `%25` form can't resolve** — the resolver decodes `%25` to
`%` before `getaddrinfo`.
5. **No test makes a real request to an IPv6 literal** — added
`http_get_ipv6_literal`.
6. **Keep `host` as an `Option`** — done.
7. **HTTPS zone-ID URLs still fail** — a ureq/rustls limitation; the
release notes above are now scoped to `http://`.

---------

Co-authored-by: Jarvis-AI-42 <skyfireitdiy@gmail.com>
2026-10-13 07:54:55 -05:00

74 lines
841 B
Plaintext

/target
/scratch
**/*.rs.bk
history.txt
tests/fixtures/nuplayground
crates/*/target
.mailmap
# Debian/Ubuntu
debian/.debhelper/
debian/debhelper-build-stamp
debian/files
debian/nu.substvars
debian/nu/
# macOS junk
.DS_Store
# JetBrains' IDE items
.idea/*
# VSCode's IDE items
.vscode/*
# JetBrains' Fleet IDE
.fleet/*
# Visual Studio Extension SourceGear Rust items
VSWorkspaceSettings.json
unstable_cargo_features.txt
# Helix configuration folder
.helix/*
.helix
wix/bin/
wix/obj/
wix/nu/
# Coverage tools
lcov.info
tarpaulin-report.html
# benchmarking
/tango
# Visual Studio
.vs/*
*.rsproj
*.rsproj.user
*.sln
*.code-workspace
# direnv
.direnv/
.envrc
# nix
/result
/scripts/nix/result
# pre-commit-hooks
.pre-commit-config.yaml
# skills
.github/skills/*
# Claude settings
.claude
# Splashboard
.splashboard.toml
.jarvis/