Skip to content
Draft
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
4 changes: 2 additions & 2 deletions normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type normalizedNamed interface {
// _ "crypto/sha256"
// )
func ParseNormalizedNamed(s string) (Named, error) {
if ok := anchoredIdentifierRegexp.MatchString(s); ok {
if ok := anchoredIdentifierRegexp().MatchString(s); ok {
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
}
domain, remainder := splitDockerDomain(s)
Expand Down Expand Up @@ -274,7 +274,7 @@ func TagNameOnly(ref Named) Named {
// _ "crypto/sha256"
// )
func ParseAnyReference(ref string) (Reference, error) {
if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
if ok := anchoredIdentifierRegexp().MatchString(ref); ok {
return digestReference("sha256:" + ref), nil
}
if dgst, err := digest.Parse(ref); err == nil {
Expand Down
14 changes: 7 additions & 7 deletions reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func Path(named Named) (name string) {
// If no valid hostname is found, the hostname is empty and the full value
// is returned as name
func splitDomain(name string) (string, string) {
match := anchoredNameRegexp.FindStringSubmatch(name)
match := anchoredNameRegexp().FindStringSubmatch(name)
if len(match) != 3 {
return "", name
}
Expand All @@ -232,9 +232,9 @@ func Parse(s string) (Reference, error) {
return nil, ErrNameEmpty
}

matches := referenceRegexp.FindStringSubmatch(s)
matches := referenceRegexp().FindStringSubmatch(s)
if matches == nil {
if sl := strings.ToLower(s); sl != s && referenceRegexp.FindStringSubmatch(sl) != nil {
if sl := strings.ToLower(s); sl != s && referenceRegexp().FindStringSubmatch(sl) != nil {
// Succeeds when lower-casing, so input contains an invalid repository name.
return nil, ErrNameContainsUppercase
}
Expand All @@ -243,7 +243,7 @@ func Parse(s string) (Reference, error) {

var repo repository

nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
nameMatch := anchoredNameRegexp().FindStringSubmatch(matches[1])
if len(nameMatch) == 3 {
repo.domain = nameMatch[1]
repo.path = nameMatch[2]
Expand Down Expand Up @@ -294,7 +294,7 @@ func ParseNamed(s string) (Named, error) {
// WithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
func WithName(name string) (Named, error) {
match := anchoredNameRegexp.FindStringSubmatch(name)
match := anchoredNameRegexp().FindStringSubmatch(name)
if match == nil || len(match) != 3 {
return nil, ErrReferenceInvalidFormat
}
Expand All @@ -312,7 +312,7 @@ func WithName(name string) (Named, error) {
// WithTag combines the name from "name" and the tag from "tag" to form a
// reference incorporating both the name and the tag.
func WithTag(name Named, tag string) (NamedTagged, error) {
if !anchoredTagRegexp.MatchString(tag) {
if !anchoredTagRegexp().MatchString(tag) {
return nil, ErrTagInvalidFormat
}
var repo repository
Expand All @@ -338,7 +338,7 @@ func WithTag(name Named, tag string) (NamedTagged, error) {
// WithDigest combines the name from "name" and the digest from "digest" to form
// a reference incorporating both the name and the digest.
func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
if !anchoredDigestRegexp.MatchString(digest.String()) {
if !anchoredDigestRegexp().MatchString(digest.String()) {
return nil, ErrDigestInvalidFormat
}
var repo repository
Expand Down
25 changes: 18 additions & 7 deletions regexp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package reference
import (
"regexp"
"strings"
"sync"
)

// DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:<encoded>").
Expand Down Expand Up @@ -31,7 +32,7 @@ var NameRegexp = regexp.MustCompile(namePat)
// ReferenceRegexp is the full supported format of a reference. The regexp
// is anchored and has capturing groups for name, tag, and digest
// components.
var ReferenceRegexp = referenceRegexp
var ReferenceRegexp = referenceRegexp()

// TagRegexp matches valid tag names. From [docker/docker:graph/tags.go].
//
Expand Down Expand Up @@ -112,15 +113,21 @@ var (
// referenceRegexp is the full supported format of a reference. The regexp
// is anchored and has capturing groups for name, tag, and digest
// components.
referenceRegexp = regexp.MustCompile(referencePat)
referenceRegexp = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(referencePat)
})

// anchoredTagRegexp matches valid tag names, anchored at the start and
// end of the matched string.
anchoredTagRegexp = regexp.MustCompile(anchored(tag))
anchoredTagRegexp = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(anchored(tag))
})

// anchoredDigestRegexp matches valid digests, anchored at the start and
// end of the matched string.
anchoredDigestRegexp = regexp.MustCompile(anchored(digestPat))
anchoredDigestRegexp = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(anchored(digestPat))
})

// pathComponent restricts path-components to start with an alphanumeric
// character, with following parts able to be separated by a separator
Expand All @@ -136,14 +143,18 @@ var (

// anchoredNameRegexp is used to parse a name value, capturing the
// domain and trailing components.
anchoredNameRegexp = regexp.MustCompile(anchoredNamePat)
anchoredNamePat = anchored(optional(capture(domainAndPort), `/`), capture(remoteName))
anchoredNameRegexp = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(anchoredNamePat)
})
anchoredNamePat = anchored(optional(capture(domainAndPort), `/`), capture(remoteName))

referencePat = anchored(capture(namePat), optional(`:`, capture(tag)), optional(`@`, capture(digestPat)))

// anchoredIdentifierRegexp is used to check or match an
// identifier value, anchored at start and end of string.
anchoredIdentifierRegexp = regexp.MustCompile(anchored(identifier))
anchoredIdentifierRegexp = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(anchored(identifier))
})
)

// optional wraps the expression in a non-capturing group and makes the
Expand Down
8 changes: 4 additions & 4 deletions regexp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@

func TestFullNameRegexp(t *testing.T) {
t.Parallel()
if n := anchoredNameRegexp.NumSubexp(); n != 2 {
if n := anchoredNameRegexp().NumSubexp(); n != 2 {
t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2", anchoredNamePat, n)
}

Expand Down Expand Up @@ -471,14 +471,14 @@
tc := tc
t.Run(tc.input, func(t *testing.T) {
t.Parallel()
checkRegexp(t, anchoredNameRegexp, tc)
checkRegexp(t, anchoredNameRegexp(), tc)
})
}
}

func TestReferenceRegexp(t *testing.T) {
t.Parallel()
if n := referenceRegexp.NumSubexp(); n != 3 {
if n := referenceRegexp().NumSubexp(); n != 3 {
t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3", referencePat, n)
}

Expand Down Expand Up @@ -545,7 +545,7 @@
tc := tc
t.Run(tc.input, func(t *testing.T) {
t.Parallel()
checkRegexp(t, referenceRegexp, tc)

Check failure on line 548 in regexp_test.go

View workflow job for this annotation

GitHub Actions / build (oldstable, ubuntu-latest)

cannot use referenceRegexp (variable of type func() *regexp.Regexp) as *regexp.Regexp value in argument to checkRegexp (typecheck)
})
}
}
Expand Down Expand Up @@ -581,7 +581,7 @@
tc := tc
t.Run(tc.input, func(t *testing.T) {
t.Parallel()
match := anchoredIdentifierRegexp.MatchString(tc.input)
match := anchoredIdentifierRegexp().MatchString(tc.input)
if match != tc.match {
t.Errorf("Expected match=%t, got %t", tc.match, match)
}
Expand Down
Loading