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