package imgcache import ( "net/url" "strings" ) // HostWhitelist implements the Whitelist interface for checking allowed source hosts. type HostWhitelist 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. // 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{ exactHosts: make(map[string]struct{}), suffixHosts: make([]string, 0), } for _, pattern := range patterns { pattern = strings.ToLower(strings.TrimSpace(pattern)) if pattern == "" { continue } if strings.HasPrefix(pattern, ".") { w.suffixHosts = append(w.suffixHosts, pattern) } else { w.exactHosts[pattern] = struct{}{} } } return w } // IsWhitelisted checks if a URL's host is in the whitelist. func (w *HostWhitelist) IsWhitelisted(u *url.URL) bool { if u == nil { return false } host := strings.ToLower(u.Hostname()) if host == "" { return false } // Check exact match if _, ok := w.exactHosts[host]; ok { return true } // Check suffix match for _, suffix := range w.suffixHosts { if strings.HasSuffix(host, suffix) { return true } // Also match if host equals the suffix without the leading dot // e.g., pattern ".example.com" should match "example.com" if host == strings.TrimPrefix(suffix, ".") { return true } } return false } // IsEmpty returns true if the whitelist has no entries. func (w *HostWhitelist) IsEmpty() bool { return len(w.exactHosts) == 0 && len(w.suffixHosts) == 0 } // Count returns the total number of whitelist entries. func (w *HostWhitelist) Count() int { return len(w.exactHosts) + len(w.suffixHosts) }