Refuse SVG at the fetcher and never save an unknown input as JPEG (closes #68)
check / check (push) Waiting to run

The fetcher no longer accepts image/svg+xml, so an SVG source gets the
refusal every unaccepted upstream type gets, which both image routes now
answer with 415 where they answered 500. orig of an input the processor
cannot name is ErrUnsupportedInputFormat, also answered 415, where it
was saved as JPEG. README.md drops SVG from the source formats and lists
the 415.

Model: opus-5-5
This commit is contained in:
2026-10-13 02:19:41 +00:00
parent c5f318033d
commit 06e537c458
6 changed files with 53 additions and 19 deletions
+8
View File
@@ -247,6 +247,14 @@ func (s *Handlers) respondImageError(
return
}
if errors.Is(err, httpfetcher.ErrInvalidContentType) ||
errors.Is(err, imageprocessor.ErrUnsupportedInputFormat) {
s.respondError(w, "unsupported source format",
http.StatusUnsupportedMediaType)
return
}
s.respondError(w, "internal error", http.StatusInternalServerError)
}
+4
View File
@@ -161,6 +161,10 @@ func (s *Handlers) handleImageError(w http.ResponseWriter, err error) {
errors.Is(err, imageprocessor.ErrTooManyImages):
s.respondError(w, "server busy, try again later",
http.StatusServiceUnavailable)
case errors.Is(err, httpfetcher.ErrInvalidContentType),
errors.Is(err, imageprocessor.ErrUnsupportedInputFormat):
s.respondError(w, "unsupported source format",
http.StatusUnsupportedMediaType)
default:
s.log.Error("image request failed", "error", err)
s.respondError(w, "internal error", http.StatusInternalServerError)
-1
View File
@@ -158,7 +158,6 @@ func DefaultConfig() *Config {
contentTypeWebP,
contentTypeAVIF,
contentTypeJXL,
contentTypeSVG,
},
AllowHTTP: false,
MaxConnectionsPerHost: DefaultMaxConnectionsPerHost,
+21 -11
View File
@@ -137,6 +137,11 @@ var ErrInputDataTooLarge = errors.New("input data exceeds maximum allowed size")
// not supported.
var ErrUnsupportedOutputFormat = errors.New("unsupported output format")
// ErrUnsupportedInputFormat is returned when the requested format is orig and
// the input is in a format the processor cannot name, so it cannot be saved
// in that format.
var ErrUnsupportedInputFormat = errors.New("unsupported input format")
// ErrTooManyImages is returned when MaxConcurrentProcessing images are being
// processed and none finishes within ProcessingWaitTimeout.
var ErrTooManyImages = errors.New("too many images being processed at once")
@@ -256,10 +261,14 @@ func (p *ImageProcessor) Process(
}
}
// orig is the source's own format; encode refuses an empty format
// orig is the source's own format, and an error when the processor
// cannot name it; encode refuses an empty format
outputFormat := req.Format
if outputFormat == FormatOriginal {
outputFormat = p.formatFromString(inputFormat)
outputFormat, err = p.formatFromString(inputFormat)
if err != nil {
return nil, err
}
}
// Encode to target format
@@ -687,22 +696,23 @@ func jxlDistance(quality int) float64 {
return 53.0/3000.0*q*q - 23.0/20.0*q + 25.0
}
// formatFromString converts a format string to Format.
func (p *ImageProcessor) formatFromString(format string) Format {
// formatFromString converts a format string from detectFormat to Format. A
// format detectFormat could not name is ErrUnsupportedInputFormat.
func (p *ImageProcessor) formatFromString(format string) (Format, error) {
switch format {
case "jpeg":
return FormatJPEG
return FormatJPEG, nil
case "png":
return FormatPNG
return FormatPNG, nil
case "gif":
return FormatGIF
return FormatGIF, nil
case "webp":
return FormatWebP
return FormatWebP, nil
case string(FormatAVIF):
return FormatAVIF
return FormatAVIF, nil
case string(FormatJXL):
return FormatJXL
return FormatJXL, nil
default:
return FormatJPEG
return "", fmt.Errorf("%w: %s", ErrUnsupportedInputFormat, format)
}
}