check / check (push) Canceled after 0s
The fetcher accepts image/svg+xml and internal/magic detects SVG. orig of an SVG at the orig size is the SVG unchanged, cached as a variant; at another size it is ErrSVGNotOriginalSize (400). Any other format renders it with libvips' thumbnail at the least size covering the output, at most 8192 on a side, so it stays sharp. An SVG's own size is not limited; one with a side over 8192 times the other is ErrSVGTooThin (415). The cache reads up to 1024 first bytes to find a file's format. Tests that pinned the SVG refusal now expect SVG to be read. Render time and memory are unbounded: P1 in TODO.md until the owner decides on #68. Model: opus-5-5
168 lines
4.7 KiB
Go
168 lines
4.7 KiB
Go
// Package imagerequest holds the image request type, its parts, its checks,
|
|
// the parsing of the /v1/image/ path, the variant key, the source key and the
|
|
// source's upstream URL. It also holds the image format type and the MIME type
|
|
// of each format. It imports no other package of this repo, so every package
|
|
// that handles an image request can import it.
|
|
package imagerequest
|
|
|
|
import (
|
|
"errors"
|
|
"net/url"
|
|
)
|
|
|
|
// ErrInvalidFitMode is returned when an invalid fit mode is provided.
|
|
var ErrInvalidFitMode = errors.New("invalid fit mode")
|
|
|
|
// Format is an image format. pixa reads and writes the formats from JPEG to
|
|
// GIF below. It reads SVG and writes it only as orig of an SVG source. In a
|
|
// request, orig and auto stand for another format.
|
|
type Format string
|
|
|
|
// Supported image formats.
|
|
const (
|
|
FormatOriginal Format = "orig"
|
|
FormatJPEG Format = "jpeg"
|
|
FormatPNG Format = "png"
|
|
FormatWebP Format = "webp"
|
|
FormatAVIF Format = "avif"
|
|
FormatJXL Format = "jxl"
|
|
FormatGIF Format = "gif"
|
|
FormatSVG Format = "svg"
|
|
|
|
// FormatAuto stands for JPEG XL, AVIF, WebP or JPEG, chosen for each
|
|
// request from its Accept header once the URL's signature or token has
|
|
// been checked; it is never processed or cached as itself.
|
|
FormatAuto Format = "auto"
|
|
)
|
|
|
|
// MIMEType returns the MIME type of the format, such as image/jpeg for
|
|
// FormatJPEG. orig and auto stand for another format, so they have none:
|
|
// MIMEType returns "" for them.
|
|
func (f Format) MIMEType() string {
|
|
switch f {
|
|
case FormatJPEG:
|
|
return "image/jpeg"
|
|
case FormatPNG:
|
|
return "image/png"
|
|
case FormatWebP:
|
|
return "image/webp"
|
|
case FormatAVIF:
|
|
return "image/avif"
|
|
case FormatJXL:
|
|
return "image/jxl"
|
|
case FormatGIF:
|
|
return "image/gif"
|
|
case FormatSVG:
|
|
return "image/svg+xml"
|
|
case FormatOriginal, FormatAuto:
|
|
return ""
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// Size represents requested image dimensions
|
|
type Size struct {
|
|
Width int
|
|
Height int
|
|
}
|
|
|
|
// OriginalSize returns true if this represents "keep original size"
|
|
func (s Size) OriginalSize() bool {
|
|
return s.Width == 0 && s.Height == 0
|
|
}
|
|
|
|
// Fit represents how to fit image into requested dimensions.
|
|
type Fit string
|
|
|
|
// Supported image fit modes.
|
|
const (
|
|
FitCover Fit = "cover"
|
|
FitContain Fit = "contain"
|
|
FitFill Fit = "fill"
|
|
FitInside Fit = "inside"
|
|
FitOutside Fit = "outside"
|
|
)
|
|
|
|
// ValidateFitMode checks if the given fit mode is valid.
|
|
// Returns ErrInvalidFitMode for unrecognized fit modes.
|
|
func ValidateFitMode(fit Fit) error {
|
|
switch fit {
|
|
case FitCover, FitContain, FitFill, FitInside, FitOutside, "":
|
|
return nil
|
|
default:
|
|
return ErrInvalidFitMode
|
|
}
|
|
}
|
|
|
|
// 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 *Request) error {
|
|
err := ValidateDimension("width", req.Transform.Size.Width)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ValidateDimension("height", req.Transform.Size.Height)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ValidateFitMode(req.Transform.FitMode)
|
|
}
|
|
|
|
// Request represents a request for a processed image: the source to fetch
|
|
// and the transform to apply to it. It holds exactly what the variant key
|
|
// covers.
|
|
type Request struct {
|
|
// Source is what to fetch
|
|
Source Source
|
|
// Transform is what to do to it
|
|
Transform Transform
|
|
}
|
|
|
|
// Source is the image a request fetches from the origin.
|
|
type Source struct {
|
|
// Host is the origin host (e.g., "cdn.example.com")
|
|
Host string
|
|
// Path is the path on the origin with its percent-escapes decoded, as
|
|
// url.URL's Path holds it (e.g., "/photos/my cat.jpg")
|
|
Path string
|
|
// Query is the optional query string for the origin URL as the URL
|
|
// writes it, percent-escapes included, as url.URL's RawQuery holds it
|
|
Query string
|
|
}
|
|
|
|
// Transform is what a request does to its source; the image processor
|
|
// takes it alone.
|
|
type Transform struct {
|
|
// Size is the requested output dimensions
|
|
Size Size
|
|
// Format is the requested output format
|
|
Format Format
|
|
// Quality is the output quality (1-100) for lossy formats
|
|
Quality int
|
|
// FitMode is how to fit the image into requested dimensions
|
|
FitMode Fit
|
|
}
|
|
|
|
// URL returns the source's upstream URL, with the scheme http when allowHTTP
|
|
// is true and https otherwise. net/url writes its path percent-encoded again,
|
|
// so each source path is one upstream path. Its query is the source query as
|
|
// it is, with the characters escapeQuery encodes percent-encoded.
|
|
func (s Source) URL(allowHTTP bool) *url.URL {
|
|
scheme := "https"
|
|
if allowHTTP {
|
|
scheme = "http"
|
|
}
|
|
|
|
return &url.URL{
|
|
Scheme: scheme,
|
|
Host: s.Host,
|
|
Path: s.Path,
|
|
RawQuery: escapeQuery(s.Query),
|
|
}
|
|
}
|