|
| 1 | +package cors |
| 2 | + |
| 3 | +import ( |
| 4 | + "strings" |
| 5 | +) |
| 6 | + |
| 7 | +// WildcardMatcher provides wildcard domain matching for CORS origins |
| 8 | +type WildcardMatcher struct { |
| 9 | + patterns []string |
| 10 | +} |
| 11 | + |
| 12 | +// NewWildcardMatcher creates a new wildcard matcher with the given patterns |
| 13 | +func NewWildcardMatcher(patterns []string) *WildcardMatcher { |
| 14 | + return &WildcardMatcher{ |
| 15 | + patterns: patterns, |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +// MatchOrigin checks if the given origin matches any of the wildcard patterns |
| 20 | +func (m *WildcardMatcher) MatchOrigin(origin string) bool { |
| 21 | + for _, pattern := range m.patterns { |
| 22 | + if m.matchPattern(origin, pattern) { |
| 23 | + return true |
| 24 | + } |
| 25 | + } |
| 26 | + return false |
| 27 | +} |
| 28 | + |
| 29 | +// matchPattern checks if origin matches a specific pattern |
| 30 | +// Supports patterns like: |
| 31 | +// - "*.example.com" matches "api.example.com", "auth.example.com", etc. |
| 32 | +// - "*.*.example.com" matches "api.v1.example.com", etc. |
| 33 | +// - "example.com" matches exactly "example.com" |
| 34 | +func (m *WildcardMatcher) matchPattern(origin, pattern string) bool { |
| 35 | + // Remove protocol from origin if present |
| 36 | + origin = strings.TrimPrefix(origin, "https://") |
| 37 | + origin = strings.TrimPrefix(origin, "http://") |
| 38 | + |
| 39 | + // Remove port if present |
| 40 | + if colonIndex := strings.LastIndex(origin, ":"); colonIndex != -1 && colonIndex > strings.LastIndex(origin, "]") { |
| 41 | + origin = origin[:colonIndex] |
| 42 | + } |
| 43 | + |
| 44 | + // Exact match |
| 45 | + if origin == pattern { |
| 46 | + return true |
| 47 | + } |
| 48 | + |
| 49 | + // Wildcard match |
| 50 | + if strings.Contains(pattern, "*") { |
| 51 | + return m.wildcardMatch(origin, pattern) |
| 52 | + } |
| 53 | + |
| 54 | + return false |
| 55 | +} |
| 56 | + |
| 57 | +// wildcardMatch performs wildcard matching |
| 58 | +func (m *WildcardMatcher) wildcardMatch(origin, pattern string) bool { |
| 59 | + // Handle simple case: *.domain.com |
| 60 | + if strings.HasPrefix(pattern, "*.") { |
| 61 | + suffix := pattern[2:] // Remove "*." |
| 62 | + |
| 63 | + // Check if origin ends with the suffix and has at least one subdomain |
| 64 | + if strings.HasSuffix(origin, "."+suffix) { |
| 65 | + // Ensure there's a subdomain (not just the suffix itself) |
| 66 | + prefix := strings.TrimSuffix(origin, "."+suffix) |
| 67 | + // Make sure the prefix doesn't contain dots (single-level subdomain wildcard) |
| 68 | + // If you want multi-level subdomains, remove this check |
| 69 | + return !strings.Contains(prefix, ".") |
| 70 | + } |
| 71 | + |
| 72 | + // Also check if origin exactly matches the suffix (without subdomain) |
| 73 | + return origin == suffix |
| 74 | + } |
| 75 | + |
| 76 | + // For more complex patterns, we could implement more sophisticated matching |
| 77 | + // For now, handle the common *.domain.com case |
| 78 | + return false |
| 79 | +} |
| 80 | + |
| 81 | +// GetAllowedOrigins returns the actual allowed origins for a request |
| 82 | +// This expands wildcard patterns based on the request origin |
| 83 | +func (m *WildcardMatcher) GetAllowedOrigins(requestOrigin string, staticOrigins []string) []string { |
| 84 | + allowedOrigins := make([]string, 0, len(staticOrigins)) |
| 85 | + |
| 86 | + for _, origin := range staticOrigins { |
| 87 | + if strings.Contains(origin, "*") { |
| 88 | + // This is a wildcard pattern |
| 89 | + if m.matchPattern(requestOrigin, origin) { |
| 90 | + // Add the actual request origin instead of the pattern |
| 91 | + allowedOrigins = append(allowedOrigins, requestOrigin) |
| 92 | + } |
| 93 | + } else { |
| 94 | + // This is a static origin, add as-is |
| 95 | + allowedOrigins = append(allowedOrigins, origin) |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + return allowedOrigins |
| 100 | +} |
0 commit comments