b698d1bdc0
Classify runs on every proxied request and recompiled each remote's pattern lists every time. Cache compiled regexes keyed by pattern text so each distinct pattern compiles once and is reused thereafter. Refs #73
101 lines
2.2 KiB
Go
101 lines
2.2 KiB
Go
package proxy
|
|
|
|
import (
|
|
"regexp"
|
|
"sync"
|
|
|
|
"git.unkin.net/unkin/artifactapi/internal/provider"
|
|
"git.unkin.net/unkin/artifactapi/pkg/models"
|
|
)
|
|
|
|
type Classification int
|
|
|
|
const (
|
|
ClassImmutable Classification = iota
|
|
ClassMutable
|
|
ClassDenied
|
|
)
|
|
|
|
func (c Classification) String() string {
|
|
switch c {
|
|
case ClassImmutable:
|
|
return "immutable"
|
|
case ClassMutable:
|
|
return "mutable"
|
|
case ClassDenied:
|
|
return "denied"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
type Classifier struct {
|
|
provider provider.Provider
|
|
}
|
|
|
|
func NewClassifier(p provider.Provider) *Classifier {
|
|
return &Classifier{provider: p}
|
|
}
|
|
|
|
func (c *Classifier) Classify(remote models.Remote, path string) Classification {
|
|
if matchesAny(path, compilePatterns(remote.Blocklist)) {
|
|
return ClassDenied
|
|
}
|
|
|
|
if len(remote.Patterns) > 0 && !matchesAny(path, compilePatterns(remote.Patterns)) {
|
|
return ClassDenied
|
|
}
|
|
|
|
if matchesAny(path, compilePatterns(remote.ImmutablePatterns)) {
|
|
return ClassImmutable
|
|
}
|
|
|
|
if matchesAny(path, compilePatterns(remote.MutablePatterns)) {
|
|
return ClassMutable
|
|
}
|
|
|
|
if c.provider.Classify(path) == provider.Mutable {
|
|
return ClassMutable
|
|
}
|
|
|
|
return ClassImmutable
|
|
}
|
|
|
|
// patternCache memoises regex compilation. Classify runs on every proxied
|
|
// request and previously recompiled each remote's pattern lists every time;
|
|
// keying by the pattern string lets each distinct pattern compile once and
|
|
// then be reused, with no invalidation needed (the pattern text is the key).
|
|
// A pattern that fails to compile is cached as a typed nil so we don't retry.
|
|
var patternCache sync.Map // map[string]*regexp.Regexp
|
|
|
|
func compileCached(pattern string) *regexp.Regexp {
|
|
if v, ok := patternCache.Load(pattern); ok {
|
|
return v.(*regexp.Regexp)
|
|
}
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
re = nil
|
|
}
|
|
patternCache.Store(pattern, re)
|
|
return re
|
|
}
|
|
|
|
func compilePatterns(patterns []string) []*regexp.Regexp {
|
|
compiled := make([]*regexp.Regexp, 0, len(patterns))
|
|
for _, p := range patterns {
|
|
if re := compileCached(p); re != nil {
|
|
compiled = append(compiled, re)
|
|
}
|
|
}
|
|
return compiled
|
|
}
|
|
|
|
func matchesAny(path string, patterns []*regexp.Regexp) bool {
|
|
for _, re := range patterns {
|
|
if re.MatchString(path) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|