// Package imagerequest holds the image request type, its parts, its checks, // the parsing of the /v1/image/ path and the variant key. 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" "time" ) // ErrInvalidFitMode is returned when an invalid fit mode is provided. var ErrInvalidFitMode = errors.New("invalid fit mode") // Format represents supported output image formats. type Format string // Supported image output formats. const ( FormatOriginal Format = "orig" FormatJPEG Format = "jpeg" FormatPNG Format = "png" FormatWebP Format = "webp" FormatAVIF Format = "avif" FormatJXL Format = "jxl" FormatGIF Format = "gif" // 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" ) // 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. type Request struct { // Source is what to fetch Source Source // Transform is what to do to it Transform Transform // Signature is the HMAC signature for non-allowlisted hosts Signature string // Expires is when the URL expires: the exp of a signed URL, or the expiry // of an encrypted URL; the zero time if it has none Expires time.Time // AllowHTTP indicates whether HTTP (non-TLS) is allowed for this request AllowHTTP bool } // 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 } // SourceURL returns the full upstream URL to fetch: the source path // percent-encoded again, as net/url writes a path, so each source path is one // upstream path, and the source query as it is, with the characters // escapeQuery encodes percent-encoded. Uses http:// scheme when AllowHTTP is // true, otherwise https://. func (r *Request) SourceURL() string { scheme := "https" if r.AllowHTTP { scheme = "http" } sourceURL := url.URL{ Scheme: scheme, Host: r.Source.Host, Path: r.Source.Path, RawQuery: escapeQuery(r.Source.Query), } return sourceURL.String() }