You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							253 lines
						
					
					
						
							7.0 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							253 lines
						
					
					
						
							7.0 KiB
						
					
					
				
								package policy_engine
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"regexp"
							 | 
						|
									"strings"
							 | 
						|
									"sync"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// WildcardMatcher provides unified wildcard matching functionality
							 | 
						|
								type WildcardMatcher struct {
							 | 
						|
									// Use regex for complex patterns with ? wildcards
							 | 
						|
									// Use string manipulation for simple * patterns (better performance)
							 | 
						|
									useRegex bool
							 | 
						|
									regex    *regexp.Regexp
							 | 
						|
									pattern  string
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// WildcardMatcherCache provides caching for WildcardMatcher instances
							 | 
						|
								type WildcardMatcherCache struct {
							 | 
						|
									mu          sync.RWMutex
							 | 
						|
									matchers    map[string]*WildcardMatcher
							 | 
						|
									maxSize     int
							 | 
						|
									accessOrder []string // For LRU eviction
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewWildcardMatcherCache creates a new WildcardMatcherCache with a configurable maxSize
							 | 
						|
								func NewWildcardMatcherCache(maxSize int) *WildcardMatcherCache {
							 | 
						|
									if maxSize <= 0 {
							 | 
						|
										maxSize = 1000 // Default value
							 | 
						|
									}
							 | 
						|
									return &WildcardMatcherCache{
							 | 
						|
										matchers: make(map[string]*WildcardMatcher),
							 | 
						|
										maxSize:  maxSize,
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Global cache instance
							 | 
						|
								var wildcardMatcherCache = NewWildcardMatcherCache(1000) // Default maxSize
							 | 
						|
								
							 | 
						|
								// GetCachedWildcardMatcher gets or creates a cached WildcardMatcher for the given pattern
							 | 
						|
								func GetCachedWildcardMatcher(pattern string) (*WildcardMatcher, error) {
							 | 
						|
									// Fast path: check if already in cache
							 | 
						|
									wildcardMatcherCache.mu.RLock()
							 | 
						|
									if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
							 | 
						|
										wildcardMatcherCache.mu.RUnlock()
							 | 
						|
										wildcardMatcherCache.updateAccessOrder(pattern)
							 | 
						|
										return matcher, nil
							 | 
						|
									}
							 | 
						|
									wildcardMatcherCache.mu.RUnlock()
							 | 
						|
								
							 | 
						|
									// Slow path: create new matcher and cache it
							 | 
						|
									wildcardMatcherCache.mu.Lock()
							 | 
						|
									defer wildcardMatcherCache.mu.Unlock()
							 | 
						|
								
							 | 
						|
									// Double-check after acquiring write lock
							 | 
						|
									if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
							 | 
						|
										wildcardMatcherCache.updateAccessOrderLocked(pattern)
							 | 
						|
										return matcher, nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create new matcher
							 | 
						|
									matcher, err := NewWildcardMatcher(pattern)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Evict old entries if cache is full
							 | 
						|
									if len(wildcardMatcherCache.matchers) >= wildcardMatcherCache.maxSize {
							 | 
						|
										wildcardMatcherCache.evictLeastRecentlyUsed()
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Cache it
							 | 
						|
									wildcardMatcherCache.matchers[pattern] = matcher
							 | 
						|
									wildcardMatcherCache.accessOrder = append(wildcardMatcherCache.accessOrder, pattern)
							 | 
						|
									return matcher, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// updateAccessOrder updates the access order for LRU eviction (with read lock)
							 | 
						|
								func (c *WildcardMatcherCache) updateAccessOrder(pattern string) {
							 | 
						|
									c.mu.Lock()
							 | 
						|
									defer c.mu.Unlock()
							 | 
						|
									c.updateAccessOrderLocked(pattern)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// updateAccessOrderLocked updates the access order for LRU eviction (without locking)
							 | 
						|
								func (c *WildcardMatcherCache) updateAccessOrderLocked(pattern string) {
							 | 
						|
									// Remove pattern from its current position
							 | 
						|
									for i, p := range c.accessOrder {
							 | 
						|
										if p == pattern {
							 | 
						|
											c.accessOrder = append(c.accessOrder[:i], c.accessOrder[i+1:]...)
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									// Add pattern to the end (most recently used)
							 | 
						|
									c.accessOrder = append(c.accessOrder, pattern)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evictLeastRecentlyUsed removes the least recently used pattern from the cache
							 | 
						|
								func (c *WildcardMatcherCache) evictLeastRecentlyUsed() {
							 | 
						|
									if len(c.accessOrder) == 0 {
							 | 
						|
										return
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Remove the least recently used pattern (first in the list)
							 | 
						|
									lruPattern := c.accessOrder[0]
							 | 
						|
									c.accessOrder = c.accessOrder[1:]
							 | 
						|
									delete(c.matchers, lruPattern)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ClearCache clears all cached patterns (useful for testing)
							 | 
						|
								func (c *WildcardMatcherCache) ClearCache() {
							 | 
						|
									c.mu.Lock()
							 | 
						|
									defer c.mu.Unlock()
							 | 
						|
									c.matchers = make(map[string]*WildcardMatcher)
							 | 
						|
									c.accessOrder = c.accessOrder[:0]
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetCacheStats returns cache statistics
							 | 
						|
								func (c *WildcardMatcherCache) GetCacheStats() (size int, maxSize int) {
							 | 
						|
									c.mu.RLock()
							 | 
						|
									defer c.mu.RUnlock()
							 | 
						|
									return len(c.matchers), c.maxSize
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewWildcardMatcher creates a new wildcard matcher for the given pattern
							 | 
						|
								func NewWildcardMatcher(pattern string) (*WildcardMatcher, error) {
							 | 
						|
									matcher := &WildcardMatcher{
							 | 
						|
										pattern: pattern,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Determine if we need regex (contains ? wildcards)
							 | 
						|
									if strings.Contains(pattern, "?") {
							 | 
						|
										matcher.useRegex = true
							 | 
						|
										regex, err := compileWildcardPattern(pattern)
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, err
							 | 
						|
										}
							 | 
						|
										matcher.regex = regex
							 | 
						|
									} else {
							 | 
						|
										matcher.useRegex = false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return matcher, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Match checks if a string matches the wildcard pattern
							 | 
						|
								func (m *WildcardMatcher) Match(str string) bool {
							 | 
						|
									if m.useRegex {
							 | 
						|
										return m.regex.MatchString(str)
							 | 
						|
									}
							 | 
						|
									return matchWildcardString(m.pattern, str)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// MatchesWildcard provides a simple function interface for wildcard matching
							 | 
						|
								// This function consolidates the logic from the previous separate implementations
							 | 
						|
								func MatchesWildcard(pattern, str string) bool {
							 | 
						|
									// Handle simple cases first
							 | 
						|
									if pattern == "*" {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
									if pattern == str {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Use regex for patterns with ? wildcards, string manipulation for * only
							 | 
						|
									if strings.Contains(pattern, "?") {
							 | 
						|
										return matchWildcardRegex(pattern, str)
							 | 
						|
									}
							 | 
						|
									return matchWildcardString(pattern, str)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// CompileWildcardPattern converts a wildcard pattern to a compiled regex
							 | 
						|
								// This replaces the previous compilePattern function
							 | 
						|
								func CompileWildcardPattern(pattern string) (*regexp.Regexp, error) {
							 | 
						|
									return compileWildcardPattern(pattern)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// matchWildcardString uses string manipulation for * wildcards only (more efficient)
							 | 
						|
								func matchWildcardString(pattern, str string) bool {
							 | 
						|
									// Handle simple cases
							 | 
						|
									if pattern == "*" {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
									if pattern == str {
							 | 
						|
										return true
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Split pattern by wildcards
							 | 
						|
									parts := strings.Split(pattern, "*")
							 | 
						|
									if len(parts) == 1 {
							 | 
						|
										// No wildcards, exact match
							 | 
						|
										return pattern == str
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check if string starts with first part
							 | 
						|
									if len(parts[0]) > 0 && !strings.HasPrefix(str, parts[0]) {
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check if string ends with last part
							 | 
						|
									if len(parts[len(parts)-1]) > 0 && !strings.HasSuffix(str, parts[len(parts)-1]) {
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check middle parts
							 | 
						|
									searchStr := str
							 | 
						|
									if len(parts[0]) > 0 {
							 | 
						|
										searchStr = searchStr[len(parts[0]):]
							 | 
						|
									}
							 | 
						|
									if len(parts[len(parts)-1]) > 0 {
							 | 
						|
										searchStr = searchStr[:len(searchStr)-len(parts[len(parts)-1])]
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for i := 1; i < len(parts)-1; i++ {
							 | 
						|
										if len(parts[i]) > 0 {
							 | 
						|
											index := strings.Index(searchStr, parts[i])
							 | 
						|
											if index == -1 {
							 | 
						|
												return false
							 | 
						|
											}
							 | 
						|
											searchStr = searchStr[index+len(parts[i]):]
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// matchWildcardRegex uses WildcardMatcher for patterns with ? wildcards
							 | 
						|
								func matchWildcardRegex(pattern, str string) bool {
							 | 
						|
									matcher, err := GetCachedWildcardMatcher(pattern)
							 | 
						|
									if err != nil {
							 | 
						|
										glog.Errorf("Error getting WildcardMatcher for pattern %s: %v. Falling back to matchWildcardString.", pattern, err)
							 | 
						|
										// Fallback to matchWildcardString
							 | 
						|
										return matchWildcardString(pattern, str)
							 | 
						|
									}
							 | 
						|
									return matcher.Match(str)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// compileWildcardPattern converts a wildcard pattern to regex
							 | 
						|
								func compileWildcardPattern(pattern string) (*regexp.Regexp, error) {
							 | 
						|
									// Escape special regex characters except * and ?
							 | 
						|
									escaped := regexp.QuoteMeta(pattern)
							 | 
						|
								
							 | 
						|
									// Replace escaped wildcards with regex equivalents
							 | 
						|
									escaped = strings.ReplaceAll(escaped, `\*`, `.*`)
							 | 
						|
									escaped = strings.ReplaceAll(escaped, `\?`, `.`)
							 | 
						|
								
							 | 
						|
									// Anchor the pattern
							 | 
						|
									escaped = "^" + escaped + "$"
							 | 
						|
								
							 | 
						|
									return regexp.Compile(escaped)
							 | 
						|
								}
							 |