Files
seaweedfs/weed/images/gateway/options.go
T
8c67b75190 image: add an optional public image processing gateway (#11593)
* image: add an optional public image processing gateway

* image: fix representation metadata and processing bounds

* image: restrict passthrough to non-executable media types

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* image: tighten source media-type validation

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* image: write passthrough body on the inner response writer

CodeQL still flagged the passthrough write: the content type was set on
the wrapper while the body reached w.ResponseWriter, so the validated
header could not be associated with the write. Set headers and copy the
body on the same inner writer.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* image: serve processed output on the inner response writer

* image: reject XML source types and unsafe conditional metadata

---------

Co-authored-by: zhaoyuchen <yc.zhao@yinzon.com>
Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-05 19:49:02 +08:00

105 lines
2.9 KiB
Go

package gateway
import (
"fmt"
"strconv"
"strings"
)
type options struct {
width, height, quality int
format string
}
// parseOptions accepts the supported OSS subset and rejects duplicates and unknown operations.
// quality,Q is absolute quality; relative quality q has no equivalent in imgproxy.
func parseOptions(value string, maxDimension int) (options, error) {
o := options{quality: 85, format: "webp"}
parts := strings.Split(value, "/")
if len(value) > 256 || len(parts) < 2 || parts[0] != "image" {
return o, fmt.Errorf("expected image/processing-operation")
}
seen := make(map[string]bool)
for _, part := range parts[1:] {
fields := strings.Split(part, ",")
if seen[fields[0]] {
return o, fmt.Errorf("repeated processing operation")
}
seen[fields[0]] = true
switch fields[0] {
case "resize":
if len(fields) < 2 {
return o, fmt.Errorf("missing resize dimensions")
}
keys := make(map[string]bool)
for _, f := range fields[1:] {
kv := strings.SplitN(f, "_", 2)
if len(kv) != 2 || keys[kv[0]] {
return o, fmt.Errorf("invalid or repeated resize parameter")
}
keys[kv[0]] = true
switch kv[0] {
case "w", "h":
n, err := strconv.Atoi(kv[1])
if err != nil || n < 1 || n > maxDimension {
return o, fmt.Errorf("dimension exceeds allowed range")
}
if kv[0] == "w" {
o.width = n
} else {
o.height = n
}
case "m":
if kv[1] != "lfit" {
return o, fmt.Errorf("only aspect-preserving lfit is supported")
}
case "limit":
if kv[1] != "1" {
return o, fmt.Errorf("image enlargement is not allowed")
}
default:
return o, fmt.Errorf("unsupported resize parameter")
}
}
if o.width == 0 && o.height == 0 {
return o, fmt.Errorf("missing resize dimensions")
}
case "quality":
if len(fields) != 2 || !strings.HasPrefix(fields[1], "Q_") {
return o, fmt.Errorf("only absolute quality Q is supported")
}
q, err := strconv.Atoi(strings.TrimPrefix(fields[1], "Q_"))
if err != nil || q < 1 || q > 100 {
return o, fmt.Errorf("quality must be between 1 and 100")
}
o.quality = q
case "format":
if len(fields) != 2 {
return o, fmt.Errorf("invalid format parameter")
}
o.format = fields[1]
if o.format == "jpeg" {
o.format = "jpg"
}
if o.format != "jpg" && o.format != "png" && o.format != "webp" {
return o, fmt.Errorf("only JPEG, PNG, and WebP are supported")
}
default:
return o, fmt.Errorf("unsupported image processing operation")
}
}
// Bound both output axes, including format-only and single-dimension requests.
if o.width == 0 {
o.width = maxDimension
}
if o.height == 0 {
o.height = maxDimension
}
return o, nil
}
// path canonicalizes imgproxy operations so equivalent URLs share cached results.
func (o options) path() string {
return fmt.Sprintf("/rs:fit:%d:%d:0:0/q:%d/f:%s", o.width, o.height, o.quality, o.format)
}