Remove the code nothing in production calls (closes #312)
check / check (push) Canceled after 0s

Service.Warm, the ImageCache interface, both GenerateSignedURL methods,
signature.ParseParams, three functions of internal/magic, the image
processor's lists of supported formats, IsEmpty and Count of the
allowlist, ssrfSafeDialer, CacheStale, ErrCacheMiss, ErrNegativeCache,
ImageResponse.LastModified and VariantStorage.Load had no caller outside
tests. They are gone, with the tests that tested only them.

The handlers tests sign their image URLs with generateSignedURL, a test
helper that builds the URL as the removed generator did. The SSRF dialer
tests call dialSSRFSafe with no operator blocklist, which is all
ssrfSafeDialer did. ParseImageURL, MIMEToImageFormat, ContentStorage.Store
and CleanExpired stay, as pixa calls them.

Model: opus-5-5
This commit is contained in:
2026-10-26 09:34:37 +00:00
parent b49d0068df
commit d9b94f5185
23 changed files with 156 additions and 863 deletions
+7 -29
View File
@@ -22,11 +22,6 @@ type goldenVector struct {
// padded) HMAC-SHA256 signature for the request with Expires
// set to goldenExpiresUnix.
wantSignature string
// wantSignedPath is the exact path returned by
// GenerateSignedURL for the request. The signature and
// expiration are returned separately by GenerateSignedURL and
// are not embedded in the path.
wantSignedPath string
}
// goldenVectors returns the known-answer vectors. The expected values
@@ -50,8 +45,7 @@ func goldenVectors() []goldenVector {
},
// Signed data:
// "cdn.example.com:/photos/cat.jpg::800:600:webp:1704067200:85:cover"
wantSignature: "kdqeGoW2SX7qnaYtoB970wEnLydn0UnIgQYQLfAnjXQ=",
wantSignedPath: testSignedPath,
wantSignature: "kdqeGoW2SX7qnaYtoB970wEnLydn0UnIgQYQLfAnjXQ=",
},
{
name: "resized with query string",
@@ -71,8 +65,6 @@ func goldenVectors() []goldenVector {
// Signed data:
// "cdn.example.com:/photos/cat.jpg:token=abc&v=2:800:600:webp:1704067200:85:cover"
wantSignature: "pKgVBOTd_Q_EikI7MNQLC9Q8Hurdxzyv3EIYvVhqc2I=",
wantSignedPath: "/v1/image/cdn.example.com/photos/cat.jpg" +
"%3Ftoken=abc&v=2/800x600.webp",
},
{
name: "original size without query",
@@ -91,8 +83,7 @@ func goldenVectors() []goldenVector {
},
// Signed data:
// "cdn.example.com:/photos/cat.jpg::0:0:png:1704067200:85:cover"
wantSignature: "6_rZ0yyVbGZRs8kG7n7HLgLi5Jt8vjiWQljIEL1jbIs=",
wantSignedPath: "/v1/image/cdn.example.com/photos/cat.jpg/orig.png",
wantSignature: "6_rZ0yyVbGZRs8kG7n7HLgLi5Jt8vjiWQljIEL1jbIs=",
},
{
name: "non-default quality and fit",
@@ -112,22 +103,17 @@ func goldenVectors() []goldenVector {
// Signed data:
// "cdn.example.com:/photos/cat.jpg::800:600:webp:1704067200:40:contain"
wantSignature: "pGaXpPUbI3A7nMx-4T9bfq9bYWBNL0kY4bxlcv3g1F8=",
// The path is the same as for the default quality and fit:
// q=40&fit=contain go in the query string next to sig and
// exp (imgcache.Service.GenerateSignedURL adds all four).
wantSignedPath: testSignedPath,
},
}
}
// TestSigner_GoldenVectors pins the exact HMAC-SHA256 signature output and
// the exact generated signed URL path for fully-specified requests with a
// hardcoded signing key.
// TestSigner_GoldenVectors pins the exact HMAC-SHA256 signature output for
// fully-specified requests with a hardcoded signing key.
//
// If any of these assertions fail, the signed byte format
// ("host:path:query:width:height:format:expiration:quality:fit"), the
// base64url encoding, or the signed URL layout has changed. Update these
// constants only when that change is intended.
// ("host:path:query:width:height:format:expiration:quality:fit") or the
// base64url encoding has changed. Update these constants only when that
// change is intended.
func TestSigner_GoldenVectors(t *testing.T) {
t.Parallel()
@@ -145,14 +131,6 @@ func TestSigner_GoldenVectors(t *testing.T) {
t.Errorf("Sign() = %q, want %q (signed byte format changed?)",
gotSignature, tt.wantSignature)
}
urlReq := tt.req
gotPath, _, _ := signer.GenerateSignedURL(&urlReq, time.Hour)
if gotPath != tt.wantSignedPath {
t.Errorf("GenerateSignedURL() path = %q, want %q (signed URL layout changed?)",
gotPath, tt.wantSignedPath)
}
})
}
}
-54
View File
@@ -8,8 +8,6 @@ import (
"encoding/base64"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"
@@ -79,44 +77,6 @@ func (s *Signer) Verify(req *imagerequest.Request) error {
return nil
}
// GenerateSignedURL creates a complete URL with signature and expiration.
// Returns the path portion that should be appended to the base URL.
func (s *Signer) GenerateSignedURL(
req *imagerequest.Request, ttl time.Duration,
) (string, string, int64) {
// Set expiration
req.Expires = time.Now().Add(ttl)
exp := req.Expires.Unix()
// Generate signature
sig := s.Sign(req)
req.Signature = sig
// Build the size component
var sizeStr string
if req.Transform.Size.Width == 0 && req.Transform.Size.Height == 0 {
sizeStr = "orig"
} else {
sizeStr = fmt.Sprintf("%dx%d",
req.Transform.Size.Width, req.Transform.Size.Height)
}
// Build the path. A source query follows the source path after a "?".
// The image route reads the path decoded once, so the whole path is
// written in Go's own percent-encoding of a URL path, which writes "?"
// as %3F and "%" as %25, and leaves "/", ";", ",", "=" and "&" as they
// are.
sourcePathAndQuery := req.Source.Path
if req.Source.Query != "" {
sourcePathAndQuery += "?" + req.Source.Query
}
unescapedPath := fmt.Sprintf("/v1/image/%s%s/%s.%s",
req.Source.Host, sourcePathAndQuery, sizeStr, req.Transform.Format)
return (&url.URL{Path: unescapedPath}).EscapedPath(), sig, exp
}
// buildSignatureData creates the string to be signed.
// Format: "host:path:query:width:height:format:expiration:quality:fit"
// The path and query go through escapePercentAndColon, so that a ":" in
@@ -146,17 +106,3 @@ func escapePercentAndColon(s string) string {
return strings.ReplaceAll(s, ":", "%3A")
}
// ParseParams extracts signature and expiration from query parameters.
func ParseParams(sig, expStr string) (string, time.Time, error) {
if expStr == "" {
return sig, time.Time{}, nil
}
expUnix, err := strconv.ParseInt(expStr, 10, 64)
if err != nil {
return "", time.Time{}, fmt.Errorf("invalid expiration: %w", err)
}
return sig, time.Unix(expUnix, 0), nil
}
+4 -209
View File
@@ -2,7 +2,6 @@ package signature_test
import (
"errors"
"strings"
"testing"
"time"
@@ -18,8 +17,6 @@ const (
testFormatPNG = "png"
testFitCover = "cover"
testFitContain = "contain"
testSignedPath = "/v1/image/cdn.example.com/photos/cat.jpg/800x600.webp"
testSig = "abc123"
)
func TestSigner_Sign(t *testing.T) {
@@ -378,140 +375,10 @@ func TestSigner_DifferentKeys(t *testing.T) {
}
}
func TestGenerateSignedURL(t *testing.T) {
t.Parallel()
signer := signature.New("test-secret-key")
req := &imagerequest.Request{
Source: imagerequest.Source{
Host: testHost,
Path: testPath,
Query: "",
},
Transform: imagerequest.Transform{
Size: imagerequest.Size{Width: 800, Height: 600},
Format: testFormatWebP,
},
}
ttl := 1 * time.Hour
path, sig, exp := signer.GenerateSignedURL(req, ttl)
// Path should be correct format
if path != testSignedPath {
t.Errorf("GenerateSignedURL() path = %q, want %q", path, testSignedPath)
}
// Signature should be non-empty
if sig == "" {
t.Error("GenerateSignedURL() produced empty signature")
}
// Expiration should be approximately now + TTL
expTime := time.Unix(exp, 0)
expectedExp := time.Now().Add(ttl)
if expTime.Sub(expectedExp) > time.Second {
t.Errorf("GenerateSignedURL() exp time off by too much")
}
// Request should have been updated with signature and expiration
if req.Signature != sig {
t.Errorf("GenerateSignedURL() didn't update request signature")
}
}
func TestGenerateSignedURL_OrigSize(t *testing.T) {
t.Parallel()
signer := signature.New("test-secret-key")
req := &imagerequest.Request{
Source: imagerequest.Source{
Host: testHost,
Path: testPath,
},
Transform: imagerequest.Transform{
Size: imagerequest.Size{Width: 0, Height: 0}, // Original size
Format: testFormatPNG,
},
}
path, _, _ := signer.GenerateSignedURL(req, time.Hour)
expectedPath := "/v1/image/cdn.example.com/photos/cat.jpg/orig.png"
if path != expectedPath {
t.Errorf("GenerateSignedURL() path = %q, want %q", path, expectedPath)
}
}
func TestGenerateSignedURL_WithQueryString(t *testing.T) {
t.Parallel()
signer := signature.New("test-secret-key-for-testing!")
req := &imagerequest.Request{
Source: imagerequest.Source{
Host: testHost,
Path: testPath,
Query: "token=abc&v=2",
},
Transform: imagerequest.Transform{
Size: imagerequest.Size{Width: 800, Height: 600},
Format: testFormatWebP,
},
}
path, _, _ := signer.GenerateSignedURL(req, time.Hour)
// The path must NOT contain a bare "?" that would be interpreted as
// a query string delimiter. The size segment must appear as the last
// path component.
if strings.Contains(path, "?token=abc") {
t.Errorf("GenerateSignedURL() produced bare query string in path: %q", path)
}
// The size segment must be present in the path
if !strings.Contains(path, "/800x600.webp") {
t.Errorf("GenerateSignedURL() missing size segment in path: %q", path)
}
// Path should end with the size.format, not with query params
if !strings.HasSuffix(path, "/800x600.webp") {
t.Errorf("GenerateSignedURL() path should end with size.format: %q", path)
}
}
func TestGenerateSignedURL_WithoutQueryString(t *testing.T) {
t.Parallel()
signer := signature.New("test-secret-key-for-testing!")
req := &imagerequest.Request{
Source: imagerequest.Source{
Host: testHost,
Path: testPath,
},
Transform: imagerequest.Transform{
Size: imagerequest.Size{Width: 800, Height: 600},
Format: testFormatWebP,
},
}
path, _, _ := signer.GenerateSignedURL(req, time.Hour)
if path != testSignedPath {
t.Errorf("GenerateSignedURL() path = %q, want %q", path, testSignedPath)
}
}
// TestGenerateSignedURL_ReadmeQueryExample checks the README's example of a
// source URL with a query, the image at
// https://cdn.example.com/img?url=https://origin.example.com/my%20cat.jpg: its
// signature with the README's key, and the path GenerateSignedURL writes for
// it, with the "?" written as %3F and the "%" as %25.
func TestGenerateSignedURL_ReadmeQueryExample(t *testing.T) {
// TestSign_ReadmeQueryExample checks the signature, with the README's key, of
// the README's example of a source URL with a query, the image at
// https://cdn.example.com/img?url=https://origin.example.com/my%20cat.jpg.
func TestSign_ReadmeQueryExample(t *testing.T) {
t.Parallel()
signer := signature.New("example-signing-key-for-documentation")
@@ -537,76 +404,4 @@ func TestGenerateSignedURL_ReadmeQueryExample(t *testing.T) {
if gotSig != wantSig {
t.Errorf("Sign() = %q, want %q", gotSig, wantSig)
}
const wantPath = "/v1/image/cdn.example.com" +
"/img%3Furl=https://origin.example.com/my%2520cat.jpg/800x600.webp"
gotPath, _, _ := signer.GenerateSignedURL(&req, time.Hour)
if gotPath != wantPath {
t.Errorf("GenerateSignedURL() path = %q, want %q", gotPath, wantPath)
}
}
func TestParseParams(t *testing.T) {
t.Parallel()
tests := []struct {
name string
sig string
expStr string
wantSig string
wantErr bool
checkTime bool
}{
{
name: "valid params",
sig: testSig,
expStr: "1704067200",
wantSig: testSig,
wantErr: false,
},
{
name: "empty expiration",
sig: testSig,
expStr: "",
wantSig: testSig,
wantErr: false,
},
{
name: "invalid expiration",
sig: testSig,
expStr: "not-a-number",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
sig, exp, err := signature.ParseParams(tt.sig, tt.expStr)
if tt.wantErr {
if err == nil {
t.Error("ParseParams() expected error, got nil")
}
return
}
if err != nil {
t.Errorf("ParseParams() unexpected error = %v", err)
return
}
if sig != tt.wantSig {
t.Errorf("sig = %q, want %q", sig, tt.wantSig)
}
if tt.expStr != "" && exp.IsZero() {
t.Error("exp should not be zero when expStr is provided")
}
})
}
}