1 Commits
Author SHA1 Message Date
sneak d9b94f5185 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
2026-10-26 09:34:37 +00:00
7 changed files with 157 additions and 34 deletions
+3 -4
View File
@@ -36,16 +36,15 @@ P2: security: per-origin rate limiting
- `Service.Warm` and the `ImageCache` interface
- `Service.GenerateSignedURL` and `Signer.GenerateSignedURL`
- `signature.ParseParams`
- `imagerequest.ParseImagePath`
- `PeekAndValidate`, `IsSupportedMIMEType`, `ImageFormatToMIME` and
`FormatOriginal` of `internal/magic`
- `PeekAndValidate`, `IsSupportedMIMEType` and `ImageFormatToMIME` of
`internal/magic`
- `SupportedInputFormats` and `SupportedOutputFormats` of the image
processor
- `IsEmpty` and `Count` of the allowlist
- `ssrfSafeDialer`
- `CacheStale`, `ErrCacheMiss`, `ErrNegativeCache` and
`ImageResponse.LastModified`
- `ContentStorage.Load` and `VariantStorage.Load`
- `VariantStorage.Load`
- 2026-10-26 cache hits, misses, upstream fetches, the bytes those fetches read
and transforms are counted as Prometheus counters in memory (closes #311).
They are `pixa_cache_hits_total`, `pixa_cache_misses_total`,
+17
View File
@@ -66,6 +66,23 @@ var queryEscapeRegex = regexp.MustCompile(`%[0-9A-Fa-f]{2}|[^!-~]|["#'<>%]`)
var hostRegex = regexp.MustCompile(
`^(?:[A-Za-z0-9_.-]+|\[([0-9A-Fa-f:.]+)\])(?::[0-9]*)?$`)
// ParseImagePath parses an image URL's path after /v1/image/, decoded once:
// <host>/<path>/<size>.<format>, or <host>/<path>/<size> for JPEG XL.
// Examples:
// - cdn.example.com/photos/cat.jpg/800x600.webp
// - cdn.example.com/photos/cat.jpg/0x0.jpeg
// - cdn.example.com/photos/cat.jpg/orig.png
// - cdn.example.com/photos/cat.jpg/800x600
func ParseImagePath(path string) (*Request, error) {
// Strip leading slash if present (chi may include it)
path = strings.TrimPrefix(path, "/")
if path == "" {
return nil, ErrMissingHost
}
return parseImageComponents(path)
}
// ParseImageURL parses an image URL's path, decoded once as r.URL.Path holds
// it: /v1/image/<host>/<path>/<size>.<format>, or /v1/image/<host>/<path>/<size>
// for JPEG XL. A source query follows the source path after a "?", which the
+105 -15
View File
@@ -192,16 +192,76 @@ func TestParseImageURL_Errors(t *testing.T) {
}
}
// TestParseImageURL_LowerCasesHost verifies that the host is lower-cased, so
func TestParseImagePath(t *testing.T) {
t.Parallel()
// ParseImagePath is for chi wildcard capture (no /v1/image/ prefix)
tests := []struct {
name string
input string
want *Request
wantErr bool
}{
{
name: "chi wildcard capture",
input: "cdn.example.com/photos/cat.jpg/800x600.webp",
want: &Request{
Source: Source{
Host: testHostCDN,
Path: testPathCat,
},
Transform: Transform{
Size: Size{Width: 800, Height: 600},
Format: FormatWebP,
},
},
},
{
name: "with leading slash from chi",
input: "/cdn.example.com/photos/cat.jpg/800x600.webp",
want: &Request{
Source: Source{
Host: testHostCDN,
Path: testPathCat,
},
Transform: Transform{
Size: Size{Width: 800, Height: 600},
Format: FormatWebP,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := ParseImagePath(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ParseImagePath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil {
return
}
assertRequest(t, got, tt.want)
})
}
}
// TestParseImagePath_LowerCasesHost verifies that the host is lower-cased, so
// that every spelling of its letter case is the same host to the allowlist,
// the upstream connection limits and the cache, and that the path keeps its
// case.
func TestParseImageURL_LowerCasesHost(t *testing.T) {
func TestParseImagePath_LowerCasesHost(t *testing.T) {
t.Parallel()
got, err := ParseImageURL("/v1/image/CDN.Example.COM/Photos/Cat.jpg/800x600.webp")
got, err := ParseImagePath("CDN.Example.COM/Photos/Cat.jpg/800x600.webp")
if err != nil {
t.Fatalf("ParseImageURL() error = %v", err)
t.Fatalf("ParseImagePath() error = %v", err)
}
t.Logf("parsed host %q, path %q", got.Source.Host, got.Source.Path)
@@ -215,21 +275,21 @@ func TestParseImageURL_LowerCasesHost(t *testing.T) {
}
}
// TestParseImageURL_RefusesUserinfo verifies that a host holding "@" is
// TestParseImagePath_RefusesUserinfo verifies that a host holding "@" is
// refused: a URL reads what comes before the "@" as a user name and password
// and the rest as the host.
func TestParseImageURL_RefusesUserinfo(t *testing.T) {
func TestParseImagePath_RefusesUserinfo(t *testing.T) {
t.Parallel()
for _, input := range []string{
"/v1/image/user@cdn.example.com/photos/cat.jpg/800x600.webp",
"/v1/image/user:password@cdn.example.com/photos/cat.jpg/800x600.webp",
"user@cdn.example.com/photos/cat.jpg/800x600.webp",
"user:password@cdn.example.com/photos/cat.jpg/800x600.webp",
} {
got, err := ParseImageURL(input)
t.Logf("ParseImageURL(%q) = %+v, error = %v", input, got, err)
got, err := ParseImagePath(input)
t.Logf("ParseImagePath(%q) = %+v, error = %v", input, got, err)
if !errors.Is(err, ErrInvalidHost) {
t.Errorf("ParseImageURL(%q) error = %v, want %v",
t.Errorf("ParseImagePath(%q) error = %v, want %v",
input, err, ErrInvalidHost)
}
}
@@ -255,10 +315,6 @@ func TestParseImageURL_PathTraversal(t *testing.T) {
name: "parent in middle of path",
input: "/v1/image/cdn.example.com/photos/../../../etc/passwd/800x600.jpeg",
},
{
name: "parent directory in path",
input: "/v1/image/cdn.example.com/photos/../secret/800x600.jpeg",
},
{
name: "encoded parent directory",
input: "/v1/image/cdn.example.com/photos/%2e%2e/secret/800x600.jpeg",
@@ -301,6 +357,40 @@ func TestParseImageURL_PathTraversal(t *testing.T) {
}
}
func TestParseImagePath_PathTraversal(t *testing.T) {
t.Parallel()
// Test path traversal via ParseImagePath (chi wildcard)
tests := []struct {
name string
input string
}{
{
name: "parent directory in path",
input: "cdn.example.com/photos/../secret/800x600.jpeg",
},
{
name: "encoded traversal",
input: "cdn.example.com/photos/%2e%2e/secret/800x600.jpeg",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
_, err := ParseImagePath(tt.input)
if err == nil {
t.Error("ParseImagePath() should reject path traversal attempts")
}
if !errors.Is(err, ErrPathTraversal) {
t.Errorf("ParseImagePath() error = %v, want ErrPathTraversal", err)
}
})
}
}
// TestParseImageURL_SourceURLHoldingPercent25 verifies that a source path and
// a source query holding %25, the percent-escape of a literal "%", are
// accepted as they are, although decoding them once more leaves a "%" that
@@ -290,9 +290,9 @@ func TestContentStorage_StoreReplacesFileOfAnotherSize(t *testing.T) {
t.Fatalf("Store() error = %v", err)
}
stored, _, err := storage.LoadWithSize(key)
stored, err := storage.Load(key)
if err != nil {
t.Fatalf("LoadWithSize() error = %v", err)
t.Fatalf("Load() error = %v", err)
}
defer func() { _ = stored.Close() }()
+16
View File
@@ -62,6 +62,22 @@ func (s *ContentStorage) Store(
return int64(len(data)), nil
}
// Load returns a reader for the content with the given key.
func (s *ContentStorage) Load(key imagerequest.SourceKey) (io.ReadCloser, error) {
path := s.keyToPath(key)
f, err := os.Open(path) //nolint:gosec // path derived from source key
if err != nil {
if os.IsNotExist(err) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("failed to open content: %w", err)
}
return f, nil
}
// LoadWithSize returns a reader and file size for the content with the given key.
func (s *ContentStorage) LoadWithSize(
key imagerequest.SourceKey,
+7 -7
View File
@@ -11,7 +11,7 @@ import (
"sneak.berlin/go/pixa/internal/imagerequest"
)
func TestContentStorage_StoreAndLoadWithSize(t *testing.T) {
func TestContentStorage_StoreAndLoad(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
@@ -44,9 +44,9 @@ func TestContentStorage_StoreAndLoadWithSize(t *testing.T) {
}
// Load and verify content
r, _, err := storage.LoadWithSize(key)
r, err := storage.Load(key)
if err != nil {
t.Fatalf("LoadWithSize() error = %v", err)
t.Fatalf("Load() error = %v", err)
}
defer func() { _ = r.Close() }()
@@ -57,11 +57,11 @@ func TestContentStorage_StoreAndLoadWithSize(t *testing.T) {
}
if !bytes.Equal(loaded, content) {
t.Errorf("LoadWithSize() content = %q, want %q", loaded, content)
t.Errorf("Load() content = %q, want %q", loaded, content)
}
}
func TestContentStorage_LoadWithSizeNotFound(t *testing.T) {
func TestContentStorage_LoadNotFound(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
@@ -71,9 +71,9 @@ func TestContentStorage_LoadWithSizeNotFound(t *testing.T) {
t.Fatalf("NewContentStorage() error = %v", err)
}
_, _, err = storage.LoadWithSize(imagerequest.SourceKey("nonexistent"))
_, err = storage.Load(imagerequest.SourceKey("nonexistent"))
if !errors.Is(err, ErrNotFound) {
t.Errorf("LoadWithSize() error = %v, want ErrNotFound", err)
t.Errorf("Load() error = %v, want ErrNotFound", err)
}
}
+7 -6
View File
@@ -35,12 +35,13 @@ type ImageFormat string
// Supported image output formats.
const (
FormatJPEG ImageFormat = "jpeg"
FormatPNG ImageFormat = "png"
FormatWebP ImageFormat = "webp"
FormatAVIF ImageFormat = "avif"
FormatJXL ImageFormat = "jxl"
FormatGIF ImageFormat = "gif"
FormatOriginal ImageFormat = "orig"
FormatJPEG ImageFormat = "jpeg"
FormatPNG ImageFormat = "png"
FormatWebP ImageFormat = "webp"
FormatAVIF ImageFormat = "avif"
FormatJXL ImageFormat = "jxl"
FormatGIF ImageFormat = "gif"
)
// MinMagicBytes is the minimum number of bytes needed to detect format.