Make one image URL per source on the generator page for host case and an empty path (closes #293)
check / check (push) Canceled after 0s

The generator page put the source host and path into the encrypted URL
as entered, so CDN.Example.com and cdn.example.com, or a source URL with
no path and with the path /, made two cache keys and two upstream
requests for one image. It now parses the source URL with the host
lower-cased and an empty path written as /, as /v1/image/ reads a
source URL, before the check that pixa fetches the URL as entered. That
is a small function of its own because HandleGenerateURL is at the
linter's length limit. The README says what the token keeps.

Model: opus-5-5
This commit is contained in:
2026-10-16 01:46:26 +00:00
parent 2bedd1dd03
commit 96b32dab3e
3 changed files with 30 additions and 4 deletions
+5 -3
View File
@@ -473,9 +473,11 @@ nor change what it asks for.
`jxl` for a form with no format. `jxl` for a form with no format.
The token holds the source's host, path and query and the size, format, quality, The token holds the source's host, path and query and the size, format, quality,
fit and expiry, encrypted with a key derived from `signing_key`. The source fit and expiry, encrypted with a key derived from `signing_key`. The host is
URL's scheme is not kept: the image is fetched like any other (see Routes), and kept in lower case and an empty path as `/`, as for `/v1/image/`, so
the blocked networks still apply. `https://CDN.Example.com` and `https://cdn.example.com/` are one source image,
fetched and cached once. The source URL's scheme is not kept: the image is
fetched like any other (see Routes), and the blocked networks still apply.
How long the URL lasts is chosen on the page, from 1 minute to 1 year, or never. How long the URL lasts is chosen on the page, from 1 minute to 1 year, or never.
The expiry is fixed in the token when the URL is made and cannot be changed or The expiry is fixed in the token when the URL is made and cannot be changed or
+5
View File
@@ -30,6 +30,11 @@ P2: security: per-origin rate limiting
# Completed Steps # Completed Steps
- 2026-10-16 the generator page lower-cases the source host and writes an empty
source path as `/` before it makes the encrypted URL, as `/v1/image/` does
(closes #293). A host written in upper and in lower case, or a source URL with
no path and with the path `/`, made two cache keys and two upstream requests
for one image.
- 2026-10-16 the source URL a request fetches is built with `net/url` from the - 2026-10-16 the source URL a request fetches is built with `net/url` from the
source host, path and query, not joined as text (closes #288). The source source host, path and query, not joined as text (closes #288). The source
path, decoded, is fetched percent-encoded again, so `100%25.png` fetches path, decoded, is fetched percent-encoded again, so `100%25.png` fetches
+20 -1
View File
@@ -127,7 +127,7 @@ func (s *Handlers) HandleGenerateURL() http.HandlerFunc {
sourceURL := r.FormValue("url") sourceURL := r.FormValue("url")
// Validate source URL // Validate source URL
parsed, err := url.Parse(sourceURL) parsed, err := parseSourceURL(sourceURL)
if err != nil || parsed.Host == "" { if err != nil || parsed.Host == "" {
s.renderGeneratorWithForm(w, r, "Invalid source URL", r.Form, s.renderGeneratorWithForm(w, r, "Invalid source URL", r.Form,
http.StatusBadRequest) http.StatusBadRequest)
@@ -198,6 +198,25 @@ func (s *Handlers) HandleGenerateURL() http.HandlerFunc {
} }
} }
// parseSourceURL parses the source URL entered on the generator page with its
// host lower-cased and an empty path written as "/", as /v1/image/ reads a
// source URL, so that each spelling of one source URL makes one cache key and
// one upstream request.
func parseSourceURL(sourceURL string) (*url.URL, error) {
parsed, err := url.Parse(sourceURL)
if err != nil {
return nil, err
}
parsed.Host = strings.ToLower(parsed.Host)
if parsed.Path == "" {
parsed.Path = "/"
}
return parsed, nil
}
// fetchedAsWritten reports whether fetchURL, the URL pixa fetches for an // fetchedAsWritten reports whether fetchURL, the URL pixa fetches for an
// image URL made from the source URL source, is source as it was written, // image URL made from the source URL source, is source as it was written,
// apart from the scheme, which the token does not keep. It is not when source // apart from the scheme, which the token does not keep. It is not when source