From 6f50eb20df196489f32a24b550686032c3dabb88 Mon Sep 17 00:00:00 2001 From: sneak Date: Fri, 16 Oct 2026 02:13:39 +0000 Subject: [PATCH] Fetch an IPv6 source host written without a port (closes #295) The fetcher's check of a source URL cut a port off the host with net.SplitHostPort, which fails when an IPv6 address in brackets has no port, so the brackets stayed on, the name lookup failed and the request answered 500. The check now parses the URL and takes the host from Hostname(), which drops the brackets with or without a port, so the address is looked up and a private one is refused with 403. Model: opus-5-5 --- TODO.md | 6 ++++++ internal/httpfetcher/httpfetcher.go | 16 ++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index da1345b..9ae228e 100644 --- a/TODO.md +++ b/TODO.md @@ -30,6 +30,12 @@ P2: security: per-origin rate limiting # Completed Steps +- 2026-10-16 a source host that is an IPv6 address in brackets with no port, + such as `[2001:db8::a]`, is fetched like one with a port, through `/v1/image/` + and through an encrypted URL (closes #295). The fetcher's check of the source + URL kept the brackets when no port followed them, could not look the host up, + and the request answered 500. It now checks the address without brackets, so a + private one is refused with 403, as with a port. - 2026-10-16 a `state_dir` whose name holds `%`, `?` or `#` keeps its database at `state.sqlite3` in that directory (closes #290): the `db_url` derived from it put the name into a `file:` URL as it was, so SQLite read `%41` as `A`, a diff --git a/internal/httpfetcher/httpfetcher.go b/internal/httpfetcher/httpfetcher.go index 1428040..159ee68 100644 --- a/internal/httpfetcher/httpfetcher.go +++ b/internal/httpfetcher/httpfetcher.go @@ -473,16 +473,16 @@ func validateURL(ctx context.Context, rawURL string, allowHTTP bool) error { return ErrUnsupportedScheme } - // Parse to extract host - host := extractHost(rawURL) - if host == "" { - return ErrInvalidHost + parsedURL, err := neturl.Parse(rawURL) + if err != nil { + return fmt.Errorf("%w: %w", ErrInvalidHost, err) } - // Remove port if present - h, _, err := net.SplitHostPort(host) - if err == nil { - host = h + // Hostname drops any port, and the brackets of an IPv6 address whether + // or not a port follows them + host := parsedURL.Hostname() + if host == "" { + return ErrInvalidHost } // Block obvious localhost patterns