Use glob matching for package paths (#1409)

- Match the behavior of GOPRIVATE/GONOPROXY/GONOSUMDB
This commit is contained in:
Rob Prentiss
2019-10-02 05:45:58 -07:00
committed by Marwan Sulaiman
parent 90b3c1dc97
commit ed66d85f3f
5 changed files with 160 additions and 31 deletions
+3 -28
View File
@@ -4,8 +4,9 @@ import (
"net/http"
"net/http/httputil"
"net/url"
"path"
"strings"
"github.com/gomods/athens/pkg/paths"
)
func sumdbPoxy(url *url.URL, nosumPatterns []string) http.Handler {
@@ -25,7 +26,7 @@ func noSumWrapper(h http.Handler, host string, patterns []string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/lookup/") {
for _, p := range patterns {
if matchesPattern(p, r.URL.Path[len("/lookup/"):]) {
if paths.MatchesPattern(p, r.URL.Path[len("/lookup/"):]) {
w.WriteHeader(http.StatusForbidden)
return
}
@@ -34,29 +35,3 @@ func noSumWrapper(h http.Handler, host string, patterns []string) http.Handler {
h.ServeHTTP(w, r)
})
}
// matchesPattern is adopted from
// https://github.com/golang/go/blob/a11644a26557ea436d456f005f39f4e01902bafe/src/cmd/go/internal/str/path.go#L58
// this function matches based on path prefixes and
// tries to keep the same behavior as GONOSUMDB and friends
func matchesPattern(pattern, target string) bool {
n := strings.Count(pattern, "/")
prefix := target
for i := 0; i < len(target); i++ {
if target[i] == '/' {
if n == 0 {
prefix = target[:i]
break
}
n--
}
}
if n > 0 {
return false
}
matched, _ := path.Match(pattern, prefix)
if matched {
return true
}
return false
}
+3 -3
View File
@@ -4,10 +4,10 @@ import (
"encoding/base64"
"fmt"
"io/ioutil"
"path"
"strings"
"github.com/gomods/athens/pkg/errors"
"github.com/gomods/athens/pkg/paths"
"github.com/hashicorp/hcl2/gohcl"
"github.com/hashicorp/hcl2/hclparse"
)
@@ -118,7 +118,7 @@ func (d *DownloadFile) validate() error {
// exist or match.
func (d *DownloadFile) Match(mod string) Mode {
for _, p := range d.Paths {
if hasMatch, err := path.Match(p.Pattern, mod); hasMatch && err == nil {
if paths.MatchesPattern(p.Pattern, mod) {
return p.Mode
}
}
@@ -130,7 +130,7 @@ func (d *DownloadFile) Match(mod string) Mode {
// the top level downloadURL is returned.
func (d *DownloadFile) URL(mod string) string {
for _, p := range d.Paths {
if hasMatch, err := path.Match(p.Pattern, mod); hasMatch && err == nil {
if paths.MatchesPattern(p.Pattern, mod) {
if p.DownloadURL != "" {
return p.DownloadURL
}
+22
View File
@@ -36,6 +36,28 @@ var testCases = []struct {
input: "github.com/gomods/athens",
expectedMode: None,
},
{
name: "multiple depth pattern match",
file: &DownloadFile{
Mode: Sync,
Paths: []*DownloadPath{
{Pattern: "github.com/*", Mode: None},
},
},
input: "github.com/gomods/athens/pkg/mode",
expectedMode: None,
},
{
name: "subdomain pattern match",
file: &DownloadFile{
Mode: Sync,
Paths: []*DownloadPath{
{Pattern: "*.github.com/gomods/*", Mode: None},
},
},
input: "athens.github.com/gomods/pkg/mode",
expectedMode: None,
},
{
name: "pattern fallback",
file: &DownloadFile{
+27
View File
@@ -2,6 +2,8 @@ package paths
import (
"net/http"
"path"
"strings"
"github.com/gomods/athens/pkg/errors"
"github.com/gorilla/mux"
@@ -50,3 +52,28 @@ func GetAllParams(r *http.Request) (*AllPathParams, error) {
return &AllPathParams{Module: mod, Version: version}, nil
}
// MatchesPattern reports whether the path prefix of target matches
// pattern (as defined by path.Match)
//
// This tries to keep the same behavior as GOPRIVATE/GONOPROXY/GONOSUMDB,
// and is adopted from:
// https://github.com/golang/go/blob/a11644a26557ea436d456f005f39f4e01902bafe/src/cmd/go/internal/str/path.go#L58
func MatchesPattern(pattern, target string) bool {
n := strings.Count(pattern, "/")
prefix := target
for i := 0; i < len(target); i++ {
if target[i] == '/' {
if n == 0 {
prefix = target[:i]
break
}
n--
}
}
if n > 0 {
return false
}
matched, _ := path.Match(pattern, prefix)
return matched
}
+105
View File
@@ -0,0 +1,105 @@
package paths
import (
"fmt"
"strings"
"testing"
)
func TestMatchesPattern(t *testing.T) {
type args struct {
pattern string
name string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "standard match",
args: args{
pattern: "example.com/*",
name: "example.com/athens",
},
want: true,
},
{
name: "mutiple depth match",
args: args{
pattern: "example.com/*",
name: "example.com/athens/pkg",
},
want: true,
},
{
name: "subdomain match",
args: args{
pattern: "*.example.com/*",
name: "go.example.com/athens/pkg",
},
want: true,
},
{
name: "subdirectory exact match",
args: args{
pattern: "*.example.com/mod",
name: "go.example.com/mod/example",
},
want: true,
},
{
name: "subdirectory mismatch",
args: args{
pattern: "*.example.com/mod",
name: "go.example.com/pkg/example",
},
want: false,
},
{
name: "shorter name mismatch",
args: args{
pattern: "*.example.com/mod/pkg",
name: "go.example.com/pkg",
},
want: false,
},
{
name: "no subdirectory mismatch",
args: args{
pattern: "*.example.com/mod/pkg",
name: "go.example.com/pkg",
},
want: false,
},
{
name: "bad pattern",
args: args{
pattern: "[]a]",
name: "go.example.com/pkg",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := MatchesPattern(tt.args.pattern, tt.args.name)
if got != tt.want {
t.Errorf("MatchGlobPattern() = %v, want %v", got, tt.want)
}
})
}
}
func BenchmarkMatchesPattern(b *testing.B) {
for i := 1; i < 5; i++ {
target := "git.example.com" + strings.Repeat("/path", i) + "/pkg"
b.Run(fmt.Sprintf("MatchPattern/%d", i), func(b *testing.B) {
for n := 0; n < b.N; n++ {
if !MatchesPattern("*.example.com/*", target) {
b.Error("mismatch")
}
}
})
}
}