2 Commits
Author SHA1 Message Date
sneak 19bb063782 Refuse a q outside 1-100 on /v1/image/ with 400 (closes #134)
check / check (push) Successful in 2m34s
The route ignored a q that was not a number or was outside 1-100 and
used 85, so q=banana or q=500 was served as if q were absent and
verified against a signature made for 85.

It now reads q with the check the URL generator uses for its quality
field (parseFormInt with minQuality and maxQuality, default
encurl.DefaultQuality) and answers anything else with a 400 naming q
and the value. That check takes an empty value as missing, so an empty
q in the URL is refused before it. The query string is read with
url.ParseQuery, since r.URL.Query() drops a pair it cannot decode, such
as q=80%; one that cannot be decoded is a 400 showing it. Only a q
missing from the URL is 85. README.md states the range.

Model: opus-5-5
2026-09-28 15:03:55 +00:00
sneak 8b001da0a6 test: /v1/image/ answers an invalid q with 400 (closes #134)
Route tests for q=banana, q=0, q=101, an empty q= and q=&q=500: each
must be a 400 whose error names q and the value. Route tests for q=80%
and q=50;fit=contain, whose query string cannot be decoded: each must be
a 400 whose error shows it. They fail on next, where each is served at
the default quality 85.

Model: opus-5-5
2026-09-28 15:03:55 +00:00
3 changed files with 23 additions and 5 deletions
+2 -1
View File
@@ -35,7 +35,8 @@ exhaustion
400 naming `q` and the value, instead of being served at the default
85; the route reads `q` with the generator's quality check
(`parseFormInt` with `minQuality` and `maxQuality`); only a `q` missing
from the URL is still 85; `README.md` states the range.
from the URL is still 85; a query string that cannot be decoded, such
as `q=80%`, is a 400 showing it; `README.md` states the range.
- 2026-09-28 unknown `PIXA_` environment variables abort startup (closes
#133): a variable whose name starts with `PIXA_` but is neither a
setting's variable nor `PIXA_CONFIG_PATH` aborts startup naming it, as
+9 -2
View File
@@ -295,8 +295,9 @@ func TestHandleImage_InvalidFitMode_Returns400(t *testing.T) {
// TestHandleImage_InvalidQuality_Returns400 verifies that the plain image
// route answers a q that is not a whole number from 1 to 100, an empty one
// included, with 400 naming q and the value, instead of serving the image at
// the default quality 85.
// included, with 400 naming q and the value, and a query string that cannot
// be decoded with 400 showing it, instead of serving the image at the default
// quality 85.
func TestHandleImage_InvalidQuality_Returns400(t *testing.T) {
t.Parallel()
@@ -308,6 +309,12 @@ func TestHandleImage_InvalidQuality_Returns400(t *testing.T) {
{"q=101", `invalid q: must be from 1 to 100, got "101"`},
{"q=", `invalid q: not a number, got ""`},
{"q=&q=500", `invalid q: not a number, got ""`},
{"q=80%", `invalid query string "q=80%": invalid URL escape "%"`},
{
"q=50;fit=contain",
`invalid query string "q=50;fit=contain": ` +
`invalid semicolon separator in query`,
},
}
for _, tt := range tests {
+12 -2
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
@@ -91,8 +92,17 @@ func (s *Handlers) parseImageRequest(
// Convert to ImageRequest
req := parsed.ToImageRequest()
// Parse signature params from query string
query := r.URL.Query()
// Parse signature params from query string. r.URL.Query() would silently
// drop a pair it cannot decode, such as q=80%, so that q would be served
// at 85; a query string that cannot be decoded is refused instead.
query, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
s.respondError(w, fmt.Sprintf("invalid query string %q: %v",
r.URL.RawQuery, err), http.StatusBadRequest)
return nil, false
}
req.Signature = query.Get("sig")
if expStr := query.Get("exp"); expStr != "" {