Validate dimensions and fit mode on encrypted URLs (closes #62)
check / check (push) Successful in 3m6s
check / check (push) Successful in 3m6s
The encrypted /v1/e/ route used the decrypted payload unchecked, so a token could request an over-limit size or an unknown fit mode; the generator turned unparseable numbers into 0. imgcache.ValidateDimension alone holds the MaxDimension bound and is used by the path parser, by the new ValidateImageRequest (which adds ValidateFitMode) and by the generator. Both image routes call ValidateImageRequest, so each answers 400. The generator answers 400 naming the field for a width or height that is not a number or fails that check, a quality that is not a number from 1 to 100, a ttl that is not a number from 0 to the largest the expiry calculation can hold, or an unknown fit. Empty quality is 85; empty ttl never expires. The form's size inputs stop at 8192. Model: opus-4-8 (implementation); opus-5-5 (rework)
This commit was merged in pull request #126.
This commit is contained in:
@@ -59,6 +59,24 @@ func ValidateFitMode(fit FitMode) error {
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateImageRequest checks a request's width and height with
|
||||
// ValidateDimension and its fit mode with ValidateFitMode. Both the plain
|
||||
// /v1/image/ route and the encrypted /v1/e/ route validate through this
|
||||
// function so a request from either source enforces identical bounds.
|
||||
func ValidateImageRequest(req *ImageRequest) error {
|
||||
err := ValidateDimension("width", req.Size.Width)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ValidateDimension("height", req.Size.Height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ValidateFitMode(req.FitMode)
|
||||
}
|
||||
|
||||
// ImageRequest represents a request for a processed image
|
||||
type ImageRequest struct {
|
||||
// SourceHost is the origin host (e.g., "cdn.example.com")
|
||||
|
||||
@@ -23,6 +23,21 @@ var (
|
||||
// MaxDimension is the maximum allowed width or height.
|
||||
const MaxDimension = 8192
|
||||
|
||||
// ValidateDimension checks one requested width or height; name ("width" or
|
||||
// "height") appears in the error. 0 means "original size" and is valid.
|
||||
func ValidateDimension(name string, value int) error {
|
||||
if value < 0 {
|
||||
return fmt.Errorf("%w: %s is negative", ErrInvalidSize, name)
|
||||
}
|
||||
|
||||
if value > MaxDimension {
|
||||
return fmt.Errorf("%w: %s is above %d",
|
||||
ErrDimensionTooLarge, name, MaxDimension)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sizeFormatRegex matches patterns like "800x600.webp", "0x0.jpeg", "orig.png"
|
||||
var sizeFormatRegex = regexp.MustCompile(`^(\d+)x(\d+)\.(\w+)$|^(orig)\.(\w+)$`)
|
||||
|
||||
@@ -225,14 +240,20 @@ func parseSizeFormat(s string) (Size, ImageFormat, error) {
|
||||
return Size{}, "", ErrInvalidSize
|
||||
}
|
||||
|
||||
if width > MaxDimension || height > MaxDimension {
|
||||
return Size{}, "", ErrDimensionTooLarge
|
||||
}
|
||||
|
||||
size = Size{Width: width, Height: height}
|
||||
formatStr = matches[3]
|
||||
}
|
||||
|
||||
err := ValidateDimension("width", size.Width)
|
||||
if err != nil {
|
||||
return Size{}, "", err
|
||||
}
|
||||
|
||||
err = ValidateDimension("height", size.Height)
|
||||
if err != nil {
|
||||
return Size{}, "", err
|
||||
}
|
||||
|
||||
format, err := parseFormat(formatStr)
|
||||
if err != nil {
|
||||
return Size{}, "", err
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package imgcache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateImageRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req ImageRequest
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "within bounds",
|
||||
req: ImageRequest{Size: Size{Width: 800, Height: 600}, FitMode: FitCover},
|
||||
},
|
||||
{
|
||||
name: "original size and empty fit",
|
||||
req: ImageRequest{Size: Size{Width: 0, Height: 0}},
|
||||
},
|
||||
{
|
||||
name: "width over limit",
|
||||
req: ImageRequest{Size: Size{Width: MaxDimension + 1, Height: 600}},
|
||||
wantErr: ErrDimensionTooLarge,
|
||||
},
|
||||
{
|
||||
name: "height over limit",
|
||||
req: ImageRequest{Size: Size{Width: 800, Height: MaxDimension + 1}},
|
||||
wantErr: ErrDimensionTooLarge,
|
||||
},
|
||||
{
|
||||
name: "negative width",
|
||||
req: ImageRequest{Size: Size{Width: -1, Height: 600}},
|
||||
wantErr: ErrInvalidSize,
|
||||
},
|
||||
{
|
||||
name: "invalid fit mode",
|
||||
req: ImageRequest{Size: Size{Width: 800, Height: 600}, FitMode: "bogus"},
|
||||
wantErr: ErrInvalidFitMode,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := ValidateImageRequest(&tt.req)
|
||||
if tt.wantErr == nil {
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateImageRequest() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if !errors.Is(err, tt.wantErr) {
|
||||
t.Fatalf("ValidateImageRequest() error = %v, want %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user