fix(05): WR-01 bound thumb dimensions and decoded image size
This commit is contained in:
@@ -16,6 +16,12 @@ import (
|
|||||||
"gocloud.dev/blob"
|
"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
|
// 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
|
// thumb filename. Both are interpolated into a blob key, which fileblob maps
|
||||||
// to a filesystem path, so separators and dots must never reach it.
|
// 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) {
|
if !thumbToken.MatchString(mode) {
|
||||||
return "", fmt.Errorf("attach: invalid thumb mode %q", 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)
|
ext := fileExt(f.DiskName)
|
||||||
if !thumbToken.MatchString(ext) {
|
if !thumbToken.MatchString(ext) {
|
||||||
return "", fmt.Errorf("attach: invalid thumb extension %q", 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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("attach: read original: %w", err)
|
return "", fmt.Errorf("attach: read original: %w", err)
|
||||||
}
|
}
|
||||||
src, _, err := image.Decode(r)
|
src, _, err := image.Decode(io.LimitReader(r, maxThumbSourceBytes))
|
||||||
closeErr := r.Close()
|
closeErr := r.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("attach: decode original: %w", err)
|
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 {
|
if closeErr != nil {
|
||||||
return "", closeErr
|
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)
|
resized := resizeImage(src, w, h, mode)
|
||||||
contentType := "image/jpeg"
|
contentType := "image/jpeg"
|
||||||
switch ext {
|
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) {
|
func TestFileThumbRejectsTraversalMode(t *testing.T) {
|
||||||
bucket := memblob.OpenBucket(nil)
|
bucket := memblob.OpenBucket(nil)
|
||||||
defer bucket.Close()
|
defer bucket.Close()
|
||||||
|
|||||||
Reference in New Issue
Block a user