diff --git a/internal/encurl/encurl.go b/internal/encurl/encurl.go index 1b36758..eb4f7ce 100644 --- a/internal/encurl/encurl.go +++ b/internal/encurl/encurl.go @@ -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 } diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index d892f87..4570b75 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -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,