The new package internal/imagerequest holds the request type, its two parts Source and Transform, the Format and Fit types, the dimension and fit checks, the /v1/image/ path parsing and the variant key; it imports no other package of pixa. imgcache.ParsedURL, imgcache.ImageRequest, signature.Request, imageprocessor.Request, the second Size, format and fit types and the conversions between them are gone. The image processor takes a Transform only. encurl keeps a private struct with the token's short CBOR field names. VariantKey moves with the function that makes it. Tests follow the moved and renamed code; the tests of the removed conversions go with them. Model: opus-5-5
This commit was merged in pull request #323.
This commit is contained in:
+52
-65
@@ -7,15 +7,15 @@ import (
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
|
||||
"sneak.berlin/go/pixa/internal/imgcache"
|
||||
"sneak.berlin/go/pixa/internal/imagerequest"
|
||||
"sneak.berlin/go/pixa/internal/seal"
|
||||
)
|
||||
|
||||
// Default values for optional fields.
|
||||
const (
|
||||
DefaultQuality = 85
|
||||
DefaultFormat = imgcache.FormatJXL
|
||||
DefaultFitMode = imgcache.FitCover
|
||||
DefaultFormat = imagerequest.FormatJXL
|
||||
DefaultFitMode = imagerequest.FitCover
|
||||
|
||||
// HKDF salt for URL encryption key derivation
|
||||
urlKeySalt = "pixa-urlenc-v1"
|
||||
@@ -28,19 +28,19 @@ var (
|
||||
ErrDecryptFailed = errors.New("failed to decrypt URL")
|
||||
)
|
||||
|
||||
// Payload contains all image request parameters in a compact format.
|
||||
// payload contains all image request parameters in a compact format.
|
||||
// Field names are short to minimize encoded size.
|
||||
// Fields with omitempty use sane defaults when absent.
|
||||
type Payload struct {
|
||||
SourceHost string `cbor:"h"` // required
|
||||
SourcePath string `cbor:"p"` // required
|
||||
SourceQuery string `cbor:"q,omitempty"` // optional
|
||||
Width int `cbor:"w,omitempty"` // 0 = original
|
||||
Height int `cbor:"ht,omitempty"` // 0 = original
|
||||
Format imgcache.ImageFormat `cbor:"f,omitempty"` // default: jxl
|
||||
Quality int `cbor:"ql,omitempty"` // default: 85
|
||||
FitMode imgcache.FitMode `cbor:"fm,omitempty"` // default: cover
|
||||
ExpiresAt int64 `cbor:"e,omitempty"` // 0 = never expires
|
||||
type payload struct {
|
||||
SourceHost string `cbor:"h"` // required
|
||||
SourcePath string `cbor:"p"` // required
|
||||
SourceQuery string `cbor:"q,omitempty"` // optional
|
||||
Width int `cbor:"w,omitempty"` // 0 = original
|
||||
Height int `cbor:"ht,omitempty"` // 0 = original
|
||||
Format imagerequest.Format `cbor:"f,omitempty"` // default: jxl
|
||||
Quality int `cbor:"ql,omitempty"` // default: 85
|
||||
FitMode imagerequest.Fit `cbor:"fm,omitempty"` // default: cover
|
||||
ExpiresAt int64 `cbor:"e,omitempty"` // 0 = never expires
|
||||
}
|
||||
|
||||
// Generator creates and parses encrypted URL tokens.
|
||||
@@ -59,9 +59,27 @@ func NewGenerator(signingKey string) (*Generator, error) {
|
||||
return &Generator{key: key}, nil
|
||||
}
|
||||
|
||||
// Generate creates an encrypted URL token from the payload.
|
||||
// The token is CBOR-encoded, encrypted with NaCl secretbox, and base64url-encoded.
|
||||
func (g *Generator) Generate(p *Payload) (string, error) {
|
||||
// Generate creates an encrypted URL token from the request's source,
|
||||
// transform and expiry, each as the request holds it; an empty format, a
|
||||
// quality of 0 or an empty fit mode is left out of the token, and a zero
|
||||
// Expires makes a token that never expires. The token is CBOR-encoded,
|
||||
// encrypted with NaCl secretbox, and base64url-encoded.
|
||||
func (g *Generator) Generate(req *imagerequest.Request) (string, error) {
|
||||
p := &payload{
|
||||
SourceHost: req.Source.Host,
|
||||
SourcePath: req.Source.Path,
|
||||
SourceQuery: req.Source.Query,
|
||||
Width: req.Transform.Size.Width,
|
||||
Height: req.Transform.Size.Height,
|
||||
Format: req.Transform.Format,
|
||||
Quality: req.Transform.Quality,
|
||||
FitMode: req.Transform.FitMode,
|
||||
}
|
||||
|
||||
if !req.Expires.IsZero() {
|
||||
p.ExpiresAt = req.Expires.Unix()
|
||||
}
|
||||
|
||||
// CBOR encode the payload
|
||||
data, err := cbor.Marshal(p)
|
||||
if err != nil {
|
||||
@@ -72,9 +90,11 @@ func (g *Generator) Generate(p *Payload) (string, error) {
|
||||
return seal.Encrypt(g.key, data)
|
||||
}
|
||||
|
||||
// Parse decrypts and validates an encrypted URL token.
|
||||
// Parse decrypts and validates an encrypted URL token and returns the
|
||||
// request it holds, with default values for omitted optional fields. A token
|
||||
// that never expires gives the zero Expires.
|
||||
// Returns ErrExpired if the token has expired, or other errors for invalid tokens.
|
||||
func (g *Generator) Parse(token string) (*Payload, error) {
|
||||
func (g *Generator) Parse(token string) (*imagerequest.Request, error) {
|
||||
// Decrypt
|
||||
data, err := seal.Decrypt(g.key, token)
|
||||
if err != nil {
|
||||
@@ -87,7 +107,7 @@ func (g *Generator) Parse(token string) (*Payload, error) {
|
||||
}
|
||||
|
||||
// CBOR decode
|
||||
var p Payload
|
||||
var p payload
|
||||
|
||||
err = cbor.Unmarshal(data, &p)
|
||||
if err != nil {
|
||||
@@ -99,13 +119,6 @@ func (g *Generator) Parse(token string) (*Payload, error) {
|
||||
return nil, ErrExpired
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// ToImageRequest converts the payload to an ImageRequest.
|
||||
// Applies default values for omitted optional fields. An ExpiresAt of 0, a URL
|
||||
// that never expires, gives the zero Expires.
|
||||
func (p *Payload) ToImageRequest() *imgcache.ImageRequest {
|
||||
format := p.Format
|
||||
if format == "" {
|
||||
format = DefaultFormat
|
||||
@@ -126,44 +139,18 @@ func (p *Payload) ToImageRequest() *imgcache.ImageRequest {
|
||||
expires = time.Unix(p.ExpiresAt, 0)
|
||||
}
|
||||
|
||||
return &imgcache.ImageRequest{
|
||||
SourceHost: p.SourceHost,
|
||||
SourcePath: p.SourcePath,
|
||||
SourceQuery: p.SourceQuery,
|
||||
Size: imgcache.Size{
|
||||
Width: p.Width,
|
||||
Height: p.Height,
|
||||
return &imagerequest.Request{
|
||||
Source: imagerequest.Source{
|
||||
Host: p.SourceHost,
|
||||
Path: p.SourcePath,
|
||||
Query: p.SourceQuery,
|
||||
},
|
||||
Transform: imagerequest.Transform{
|
||||
Size: imagerequest.Size{Width: p.Width, Height: p.Height},
|
||||
Format: format,
|
||||
Quality: quality,
|
||||
FitMode: fitMode,
|
||||
},
|
||||
Format: format,
|
||||
Quality: quality,
|
||||
FitMode: fitMode,
|
||||
Expires: expires,
|
||||
}
|
||||
}
|
||||
|
||||
// FromImageRequest creates a Payload from an ImageRequest with the given expiration.
|
||||
func FromImageRequest(req *imgcache.ImageRequest, expiresAt time.Time) *Payload {
|
||||
p := &Payload{
|
||||
SourceHost: req.SourceHost,
|
||||
SourcePath: req.SourcePath,
|
||||
SourceQuery: req.SourceQuery,
|
||||
Width: req.Size.Width,
|
||||
Height: req.Size.Height,
|
||||
ExpiresAt: expiresAt.Unix(),
|
||||
}
|
||||
|
||||
// Only set non-default values to benefit from omitempty
|
||||
if req.Format != "" && req.Format != DefaultFormat {
|
||||
p.Format = req.Format
|
||||
}
|
||||
|
||||
if req.Quality != 0 && req.Quality != DefaultQuality {
|
||||
p.Quality = req.Quality
|
||||
}
|
||||
|
||||
if req.FitMode != "" && req.FitMode != DefaultFitMode {
|
||||
p.FitMode = req.FitMode
|
||||
}
|
||||
|
||||
return p
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user