Add 'Never' expiry option for encrypted URLs

- Make ExpiresAt optional in CBOR (omitempty) for smaller tokens
- Treat ExpiresAt=0 as 'never expires' in parser
- URL-encode token with url.PathEscape() for safety
- Add 'Never' as default TTL option in generator form
This commit is contained in:
Jeffrey Paul 2026-01-08 11:08:11 -08:00
parent d9f4e2038e
commit 014c144d73
2 changed files with 23 additions and 12 deletions

View File

@ -40,7 +40,7 @@ type Payload struct {
Format imgcache.ImageFormat `cbor:"f,omitempty"` // default: orig
Quality int `cbor:"ql,omitempty"` // default: 85
FitMode imgcache.FitMode `cbor:"fm,omitempty"` // default: cover
ExpiresAt int64 `cbor:"e"` // required, Unix timestamp
ExpiresAt int64 `cbor:"e,omitempty"` // 0 = never expires
}
// Generator creates and parses encrypted URL tokens.
@ -90,8 +90,8 @@ func (g *Generator) Parse(token string) (*Payload, error) {
return nil, ErrInvalidFormat
}
// Check expiration
if time.Now().Unix() > p.ExpiresAt {
// Check expiration (0 = never expires)
if p.ExpiresAt != 0 && time.Now().Unix() > p.ExpiresAt {
return nil, ErrExpired
}

View File

@ -126,12 +126,17 @@ func (s *Handlers) HandleGenerateURL() http.HandlerFunc {
quality = 85
}
if ttl <= 0 {
ttl = 2592000 // 30 days default
}
// Create payload
expiresAt := time.Now().Add(time.Duration(ttl) * time.Second)
// ttl=0 means never expires
var expiresAt time.Time
var expiresAtUnix int64
if ttl > 0 {
expiresAt = time.Now().Add(time.Duration(ttl) * time.Second)
expiresAtUnix = expiresAt.Unix()
}
// else expiresAtUnix stays 0 (never expires)
payload := &encurl.Payload{
SourceHost: parsed.Host,
SourcePath: parsed.Path,
@ -141,7 +146,7 @@ func (s *Handlers) HandleGenerateURL() http.HandlerFunc {
Format: imgcache.ImageFormat(format),
Quality: quality,
FitMode: imgcache.FitMode(fit),
ExpiresAt: expiresAt.Unix(),
ExpiresAt: expiresAtUnix,
}
// Generate encrypted token
@ -153,18 +158,24 @@ func (s *Handlers) HandleGenerateURL() http.HandlerFunc {
return
}
// Build full URL
// Build full URL (URL-encode the token for safety)
scheme := "https"
if s.config.Debug {
scheme = "http"
}
host := r.Host
generatedURL := scheme + "://" + host + "/v1/e/" + token
generatedURL := scheme + "://" + host + "/v1/e/" + url.PathEscape(token)
// Format expiry for display
expiresAtStr := "Never"
if ttl > 0 {
expiresAtStr = expiresAt.Format(time.RFC3339)
}
s.renderGenerator(w, &generatorData{
GeneratedURL: generatedURL,
ExpiresAt: expiresAt.Format(time.RFC3339),
ExpiresAt: expiresAtStr,
FormURL: sourceURL,
FormWidth: widthStr,
FormHeight: heightStr,