From f96c4a514edf8b727ee1f37f51aca2378bec5c2b Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 24 Mar 2026 18:22:48 -0700 Subject: [PATCH 1/2] refactor: extract whitelist package from internal/imgcache Move HostWhitelist, NewHostWhitelist, IsWhitelisted, IsEmpty, and Count from internal/imgcache into the new internal/whitelist package. The whitelist package is completely self-contained, depending only on net/url and strings from the standard library. Updated import in internal/imgcache/service.go to use the new package. Tests moved and adapted to external test style (package whitelist_test). Part of https://git.eeqj.de/sneak/pixa/issues/39 --- internal/imgcache/service.go | 5 +++-- internal/{imgcache => whitelist}/whitelist.go | 3 ++- internal/{imgcache => whitelist}/whitelist_test.go | 10 ++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) rename internal/{imgcache => whitelist}/whitelist.go (95%) rename internal/{imgcache => whitelist}/whitelist_test.go (94%) diff --git a/internal/imgcache/service.go b/internal/imgcache/service.go index d44e8a2..65975c8 100644 --- a/internal/imgcache/service.go +++ b/internal/imgcache/service.go @@ -12,6 +12,7 @@ import ( "github.com/dustin/go-humanize" "sneak.berlin/go/pixa/internal/imageprocessor" + "sneak.berlin/go/pixa/internal/whitelist" ) // Service implements the ImageCache interface, orchestrating cache, fetcher, and processor. @@ -20,7 +21,7 @@ type Service struct { fetcher Fetcher processor *imageprocessor.ImageProcessor signer *Signer - whitelist *HostWhitelist + whitelist *whitelist.HostWhitelist log *slog.Logger allowHTTP bool maxResponseSize int64 @@ -85,7 +86,7 @@ func NewService(cfg *ServiceConfig) (*Service, error) { fetcher: fetcher, processor: imageprocessor.New(imageprocessor.Params{MaxInputBytes: maxResponseSize}), signer: signer, - whitelist: NewHostWhitelist(cfg.Whitelist), + whitelist: whitelist.NewHostWhitelist(cfg.Whitelist), log: log, allowHTTP: allowHTTP, maxResponseSize: maxResponseSize, diff --git a/internal/imgcache/whitelist.go b/internal/whitelist/whitelist.go similarity index 95% rename from internal/imgcache/whitelist.go rename to internal/whitelist/whitelist.go index df24be2..ed403b8 100644 --- a/internal/imgcache/whitelist.go +++ b/internal/whitelist/whitelist.go @@ -1,4 +1,5 @@ -package imgcache +// Package whitelist provides host-based URL whitelisting for the image proxy. +package whitelist import ( "net/url" diff --git a/internal/imgcache/whitelist_test.go b/internal/whitelist/whitelist_test.go similarity index 94% rename from internal/imgcache/whitelist_test.go rename to internal/whitelist/whitelist_test.go index 3e33b66..960c601 100644 --- a/internal/imgcache/whitelist_test.go +++ b/internal/whitelist/whitelist_test.go @@ -1,8 +1,10 @@ -package imgcache +package whitelist_test import ( "net/url" "testing" + + "sneak.berlin/go/pixa/internal/whitelist" ) func TestHostWhitelist_IsWhitelisted(t *testing.T) { @@ -94,7 +96,7 @@ func TestHostWhitelist_IsWhitelisted(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - w := NewHostWhitelist(tt.patterns) + w := whitelist.NewHostWhitelist(tt.patterns) var u *url.URL if tt.testURL != "" { @@ -143,7 +145,7 @@ func TestHostWhitelist_IsEmpty(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - w := NewHostWhitelist(tt.patterns) + w := whitelist.NewHostWhitelist(tt.patterns) if got := w.IsEmpty(); got != tt.want { t.Errorf("IsEmpty() = %v, want %v", got, tt.want) } @@ -181,7 +183,7 @@ func TestHostWhitelist_Count(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - w := NewHostWhitelist(tt.patterns) + w := whitelist.NewHostWhitelist(tt.patterns) if got := w.Count(); got != tt.want { t.Errorf("Count() = %v, want %v", got, tt.want) } -- 2.49.1 From 469b8bf54750739ec8491914013962e32635e9c2 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 24 Mar 2026 18:34:50 -0700 Subject: [PATCH 2/2] refactor: rename whitelist package to allowlist, fix stuttering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename internal/whitelist/ → internal/allowlist/ - Rename type HostWhitelist → HostAllowList - Rename constructor NewHostWhitelist() → New() - Rename method IsWhitelisted() → IsAllowed() - Update all imports and references in service.go No logic changes — purely naming improvements per review feedback. --- .../whitelist.go => allowlist/allowlist.go} | 26 +++++++++---------- .../allowlist_test.go} | 22 ++++++++-------- internal/imgcache/service.go | 12 ++++----- 3 files changed, 30 insertions(+), 30 deletions(-) rename internal/{whitelist/whitelist.go => allowlist/allowlist.go} (68%) rename internal/{whitelist/whitelist_test.go => allowlist/allowlist_test.go} (88%) diff --git a/internal/whitelist/whitelist.go b/internal/allowlist/allowlist.go similarity index 68% rename from internal/whitelist/whitelist.go rename to internal/allowlist/allowlist.go index ed403b8..35242c2 100644 --- a/internal/whitelist/whitelist.go +++ b/internal/allowlist/allowlist.go @@ -1,26 +1,26 @@ -// Package whitelist provides host-based URL whitelisting for the image proxy. -package whitelist +// Package allowlist provides host-based URL allow-listing for the image proxy. +package allowlist import ( "net/url" "strings" ) -// HostWhitelist implements the Whitelist interface for checking allowed source hosts. -type HostWhitelist struct { +// HostAllowList checks whether source hosts are permitted. +type HostAllowList struct { // exactHosts contains hosts that must match exactly (e.g., "cdn.example.com") exactHosts map[string]struct{} // suffixHosts contains domain suffixes to match (e.g., ".example.com" matches "cdn.example.com") suffixHosts []string } -// NewHostWhitelist creates a whitelist from a list of host patterns. +// New creates a HostAllowList from a list of host patterns. // Patterns starting with "." are treated as suffix matches. // Examples: // - "cdn.example.com" - exact match only // - ".example.com" - matches cdn.example.com, images.example.com, etc. -func NewHostWhitelist(patterns []string) *HostWhitelist { - w := &HostWhitelist{ +func New(patterns []string) *HostAllowList { + w := &HostAllowList{ exactHosts: make(map[string]struct{}), suffixHosts: make([]string, 0), } @@ -41,8 +41,8 @@ func NewHostWhitelist(patterns []string) *HostWhitelist { return w } -// IsWhitelisted checks if a URL's host is in the whitelist. -func (w *HostWhitelist) IsWhitelisted(u *url.URL) bool { +// IsAllowed checks if a URL's host is in the allow list. +func (w *HostAllowList) IsAllowed(u *url.URL) bool { if u == nil { return false } @@ -72,12 +72,12 @@ func (w *HostWhitelist) IsWhitelisted(u *url.URL) bool { return false } -// IsEmpty returns true if the whitelist has no entries. -func (w *HostWhitelist) IsEmpty() bool { +// IsEmpty returns true if the allow list has no entries. +func (w *HostAllowList) IsEmpty() bool { return len(w.exactHosts) == 0 && len(w.suffixHosts) == 0 } -// Count returns the total number of whitelist entries. -func (w *HostWhitelist) Count() int { +// Count returns the total number of allow list entries. +func (w *HostAllowList) Count() int { return len(w.exactHosts) + len(w.suffixHosts) } diff --git a/internal/whitelist/whitelist_test.go b/internal/allowlist/allowlist_test.go similarity index 88% rename from internal/whitelist/whitelist_test.go rename to internal/allowlist/allowlist_test.go index 960c601..ee238ec 100644 --- a/internal/whitelist/whitelist_test.go +++ b/internal/allowlist/allowlist_test.go @@ -1,13 +1,13 @@ -package whitelist_test +package allowlist_test import ( "net/url" "testing" - "sneak.berlin/go/pixa/internal/whitelist" + "sneak.berlin/go/pixa/internal/allowlist" ) -func TestHostWhitelist_IsWhitelisted(t *testing.T) { +func TestHostAllowList_IsAllowed(t *testing.T) { tests := []struct { name string patterns []string @@ -69,7 +69,7 @@ func TestHostWhitelist_IsWhitelisted(t *testing.T) { want: true, }, { - name: "empty whitelist", + name: "empty allow list", patterns: []string{}, testURL: "https://cdn.example.com/image.jpg", want: false, @@ -96,7 +96,7 @@ func TestHostWhitelist_IsWhitelisted(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - w := whitelist.NewHostWhitelist(tt.patterns) + w := allowlist.New(tt.patterns) var u *url.URL if tt.testURL != "" { @@ -107,15 +107,15 @@ func TestHostWhitelist_IsWhitelisted(t *testing.T) { } } - got := w.IsWhitelisted(u) + got := w.IsAllowed(u) if got != tt.want { - t.Errorf("IsWhitelisted() = %v, want %v", got, tt.want) + t.Errorf("IsAllowed() = %v, want %v", got, tt.want) } }) } } -func TestHostWhitelist_IsEmpty(t *testing.T) { +func TestHostAllowList_IsEmpty(t *testing.T) { tests := []struct { name string patterns []string @@ -145,7 +145,7 @@ func TestHostWhitelist_IsEmpty(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - w := whitelist.NewHostWhitelist(tt.patterns) + w := allowlist.New(tt.patterns) if got := w.IsEmpty(); got != tt.want { t.Errorf("IsEmpty() = %v, want %v", got, tt.want) } @@ -153,7 +153,7 @@ func TestHostWhitelist_IsEmpty(t *testing.T) { } } -func TestHostWhitelist_Count(t *testing.T) { +func TestHostAllowList_Count(t *testing.T) { tests := []struct { name string patterns []string @@ -183,7 +183,7 @@ func TestHostWhitelist_Count(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - w := whitelist.NewHostWhitelist(tt.patterns) + w := allowlist.New(tt.patterns) if got := w.Count(); got != tt.want { t.Errorf("Count() = %v, want %v", got, tt.want) } diff --git a/internal/imgcache/service.go b/internal/imgcache/service.go index 65975c8..a53825f 100644 --- a/internal/imgcache/service.go +++ b/internal/imgcache/service.go @@ -11,8 +11,8 @@ import ( "time" "github.com/dustin/go-humanize" + "sneak.berlin/go/pixa/internal/allowlist" "sneak.berlin/go/pixa/internal/imageprocessor" - "sneak.berlin/go/pixa/internal/whitelist" ) // Service implements the ImageCache interface, orchestrating cache, fetcher, and processor. @@ -21,7 +21,7 @@ type Service struct { fetcher Fetcher processor *imageprocessor.ImageProcessor signer *Signer - whitelist *whitelist.HostWhitelist + allowlist *allowlist.HostAllowList log *slog.Logger allowHTTP bool maxResponseSize int64 @@ -86,7 +86,7 @@ func NewService(cfg *ServiceConfig) (*Service, error) { fetcher: fetcher, processor: imageprocessor.New(imageprocessor.Params{MaxInputBytes: maxResponseSize}), signer: signer, - whitelist: whitelist.NewHostWhitelist(cfg.Whitelist), + allowlist: allowlist.New(cfg.Whitelist), log: log, allowHTTP: allowHTTP, maxResponseSize: maxResponseSize, @@ -382,7 +382,7 @@ func (s *Service) Stats(ctx context.Context) (*CacheStats, error) { // ValidateRequest validates the request signature if required. func (s *Service) ValidateRequest(req *ImageRequest) error { - // Check if host is whitelisted (no signature required) + // Check if host is allowed (no signature required) sourceURL := req.SourceURL() parsedURL, err := url.Parse(sourceURL) @@ -390,11 +390,11 @@ func (s *Service) ValidateRequest(req *ImageRequest) error { return fmt.Errorf("invalid source URL: %w", err) } - if s.whitelist.IsWhitelisted(parsedURL) { + if s.allowlist.IsAllowed(parsedURL) { return nil } - // Signature required for non-whitelisted hosts + // Signature required for non-allowed hosts return s.signer.Verify(req) } -- 2.49.1