fix(05): WR-01 bound thumb dimensions and decoded image size
This commit is contained in:
@@ -16,6 +16,12 @@ import (
|
||||
"gocloud.dev/blob"
|
||||
)
|
||||
|
||||
const (
|
||||
maxThumbEdge = 4096
|
||||
maxThumbSourceBytes = 32 << 20
|
||||
maxThumbSourcePixels = 4096 * 4096
|
||||
)
|
||||
|
||||
// thumbToken is the alphabet allowed for the mode and extension segments of a
|
||||
// thumb filename. Both are interpolated into a blob key, which fileblob maps
|
||||
// to a filesystem path, so separators and dots must never reach it.
|
||||
@@ -110,6 +116,9 @@ func (f *File) Thumb(ctx context.Context, bucket *blob.Bucket, w, h int, mode st
|
||||
if !thumbToken.MatchString(mode) {
|
||||
return "", fmt.Errorf("attach: invalid thumb mode %q", mode)
|
||||
}
|
||||
if w <= 0 || h <= 0 || w > maxThumbEdge || h > maxThumbEdge {
|
||||
return "", fmt.Errorf("attach: thumb size %dx%d is out of range", w, h)
|
||||
}
|
||||
ext := fileExt(f.DiskName)
|
||||
if !thumbToken.MatchString(ext) {
|
||||
return "", fmt.Errorf("attach: invalid thumb extension %q", ext)
|
||||
@@ -129,7 +138,7 @@ func (f *File) Thumb(ctx context.Context, bucket *blob.Bucket, w, h int, mode st
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("attach: read original: %w", err)
|
||||
}
|
||||
src, _, err := image.Decode(r)
|
||||
src, _, err := image.Decode(io.LimitReader(r, maxThumbSourceBytes))
|
||||
closeErr := r.Close()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("attach: decode original: %w", err)
|
||||
@@ -137,6 +146,10 @@ func (f *File) Thumb(ctx context.Context, bucket *blob.Bucket, w, h int, mode st
|
||||
if closeErr != nil {
|
||||
return "", closeErr
|
||||
}
|
||||
bounds := src.Bounds()
|
||||
if int64(bounds.Dx())*int64(bounds.Dy()) > maxThumbSourcePixels {
|
||||
return "", fmt.Errorf("attach: original image is too large")
|
||||
}
|
||||
resized := resizeImage(src, w, h, mode)
|
||||
contentType := "image/jpeg"
|
||||
switch ext {
|
||||
|
||||
@@ -43,6 +43,28 @@ func TestThumbFilenameRejectsUnsafeTokens(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileThumbRejectsOutOfRangeSize(t *testing.T) {
|
||||
bucket := memblob.OpenBucket(nil)
|
||||
defer bucket.Close()
|
||||
f := &File{ID: 1, DiskName: "abc123xyz.jpg"}
|
||||
for _, tc := range []struct{ w, h int }{
|
||||
{0, 200},
|
||||
{200, 0},
|
||||
{-1, -1},
|
||||
{maxThumbEdge + 1, 200},
|
||||
{200, maxThumbEdge + 1},
|
||||
{100000, 100000},
|
||||
} {
|
||||
if _, err := f.Thumb(t.Context(), bucket, tc.w, tc.h, "crop"); err == nil {
|
||||
t.Fatalf("size %dx%d must be rejected", tc.w, tc.h)
|
||||
}
|
||||
}
|
||||
iter := bucket.List(nil)
|
||||
if obj, err := iter.Next(t.Context()); err != io.EOF {
|
||||
t.Fatalf("rejected sizes must not touch the bucket, found %v (err %v)", obj, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileThumbRejectsTraversalMode(t *testing.T) {
|
||||
bucket := memblob.OpenBucket(nil)
|
||||
defer bucket.Close()
|
||||
|
||||
Reference in New Issue
Block a user