Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/v2.0/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7533,6 +7533,14 @@ definitions:
type: string
description: 'Whether the proxy cache project should proxy OCI 1.1 referrer API requests to the upstream registry. The valid values are "true", "false".'
x-nullable: true
proxy_cache_filter_pattern:
type: string
description: 'Repository filter pattern for proxy cache project. Only repositories matching the pattern will be proxied. Empty means allow all. Only has value when the current project is a proxy cache project.'
x-nullable: true
proxy_cache_filter_kind:
type: string
description: 'Matching mode for proxy_cache_filter_pattern: "doublestar" (default, glob-style with ** support) or "regex". Only has value when the current project is a proxy cache project.'
x-nullable: true
ProjectSummary:
type: object
properties:
Expand Down
117 changes: 117 additions & 0 deletions src/lib/pattern/matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pattern

import (
"regexp"
"strings"

"github.com/bmatcuk/doublestar"

"github.com/goharbor/harbor/src/lib/errors"
)

const (
// KindRegex indicates regular expression pattern matching
KindRegex = "regex"
// KindDoublestar indicates doublestar (glob) pattern matching
KindDoublestar = "doublestar"
)

// RepositoryFilter represents a repository filter configuration with pattern and kind
type RepositoryFilter struct {
// Filter is the pattern expression to match against
Filter string
// Kind is the type of pattern matching: "regex" or "doublestar"
Kind string
}

// NewRepositoryFilter creates a RepositoryFilter from separate pattern and kind strings.
// If kind is empty, it defaults to KindDoublestar.
func NewRepositoryFilter(filterPattern, kind string) *RepositoryFilter {
return &RepositoryFilter{
Filter: strings.TrimSpace(filterPattern),
Kind: strings.TrimSpace(kind),
}
}

// Match returns true if the value matches the filter pattern.
// Returns true if the filter is empty.
func (rf *RepositoryFilter) Match(value string) (bool, error) {
if rf == nil || rf.Filter == "" {
return true, nil
}
matcher := NewMatcher(rf.Kind)
return matcher.Match(value, rf.Filter)
}

// ValidateRepositoryFilter validates the repository filter kind and pattern.
// Empty pattern is valid and means all repositories are allowed.
func ValidateRepositoryFilter(filterPattern, kind string) error {
rf := NewRepositoryFilter(filterPattern, kind)
if rf.Kind != "" && rf.Kind != KindRegex && rf.Kind != KindDoublestar {
return errors.Errorf("unsupported repository filter kind %q", kind)
}
_, err := rf.Match("")
return err
}

// Matcher is an interface for matching strings against patterns
type Matcher interface {
// Match returns true if the value matches the pattern
Match(value, pattern string) (bool, error)
}

// RegexMatcher implements Matcher using regular expressions
type RegexMatcher struct{}

// Match matches value against a regular expression pattern
func (r *RegexMatcher) Match(value, pattern string) (bool, error) {
pattern = strings.TrimSpace(pattern)
if pattern == "" {
return true, nil
}
re, err := regexp.Compile(pattern)
if err != nil {
return false, err
}
match := re.FindStringIndex(value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FindStringIndex returns the leftmost match, not necessarily a full-string match, so this can return false negatives.

Example: pattern a|ab against value ab — the leftmost match is a ([0,1]), so this returns false even though the pattern matches the whole string.

return match != nil && match[0] == 0 && match[1] == len(value), nil
}

// DoublestarMatcher implements Matcher using doublestar (glob) patterns
type DoublestarMatcher struct{}

// Match matches value against a doublestar (glob) pattern
func (d *DoublestarMatcher) Match(value, pattern string) (bool, error) {
pattern = strings.TrimSpace(pattern)
if pattern == "" {
return true, nil
}
return doublestar.Match(pattern, value)
}

// NewMatcher creates a new Matcher based on the specified kind.
// Defaults to KindDoublestar when kind is empty or unrecognised.
func NewMatcher(kind string) Matcher {
switch kind {
case KindRegex:
return &RegexMatcher{}
case KindDoublestar:
fallthrough
default:
return &DoublestarMatcher{}
}
}
Comment on lines +1 to +117

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func ValidateRepositoryFilter(filterPattern, kind string) error {
    filterPattern = strings.TrimSpace(filterPattern)
    if filterPattern == "" {
        return nil
    }
    switch kind {
    case KindRegex:
        _, err := regexp.Compile(filterPattern)
        return err
    case KindDoublestar, "":
        if !doublestar.ValidatePattern(filterPattern) {
            return doublestar.ErrBadPattern
        }
        return nil
    default:
        return errors.Errorf("unsupported repository filter kind %q", kind)
    }
}


Loading
Loading