diff --git a/reference/reference.go b/reference/reference.go index d3088b685..f1d180357 100644 --- a/reference/reference.go +++ b/reference/reference.go @@ -8,12 +8,13 @@ // // repository.go // repository := hostname ['/' component]+ // hostname := hostcomponent [':' port-number] -// component := alpha-numeric [separator alpha-numeric]* +// component := subcomponent [separator subcomponent]* +// subcomponent := alpha-numeric ['-'* alpha-numeric]* // hostcomponent := [hostpart '.']* hostpart -// alpha-numeric := /[a-zA-Z0-9]+/ -// separator := /[_-]/ +// alpha-numeric := /[a-z0-9]+/ +// separator := /([_.]|__)/ // port-number := /[0-9]+/ -// hostpart := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ +// hostpart := /([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])/ // // // tag.go // tag := /[\w][\w.-]{0,127}/ diff --git a/reference/regexp.go b/reference/regexp.go index aa3480c5b..579d5cda6 100644 --- a/reference/regexp.go +++ b/reference/regexp.go @@ -3,14 +3,19 @@ package reference import "regexp" var ( + // nameSubComponentRegexp defines the part of the name which must be + // begin and end with an alphanumeric character. These characters can + // be separated by any number of dashes. + nameSubComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[-]+[a-z0-9]+)*`) + // nameComponentRegexp restricts registry path component names to // start with at least one letter or number, with following parts able to - // be separated by one period, dash or underscore. - nameComponentRegexp = regexp.MustCompile(`[a-zA-Z0-9]+(?:[._-][a-z0-9]+)*`) + // be separated by one period, underscore or double underscore. + nameComponentRegexp = regexp.MustCompile(nameSubComponentRegexp.String() + `(?:(?:[._]|__)` + nameSubComponentRegexp.String() + `)*`) nameRegexp = regexp.MustCompile(`(?:` + nameComponentRegexp.String() + `/)*` + nameComponentRegexp.String()) - hostnameComponentRegexp = regexp.MustCompile(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) + hostnameComponentRegexp = regexp.MustCompile(`(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])`) // hostnameComponentRegexp restricts the registry hostname component of a repository name to // start with a component as defined by hostnameRegexp and followed by an optional port. diff --git a/reference/regexp_test.go b/reference/regexp_test.go index 9435ee2ae..530a6eb6c 100644 --- a/reference/regexp_test.go +++ b/reference/regexp_test.go @@ -16,14 +16,14 @@ func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) { matches := r.FindStringSubmatch(m.input) if m.match && matches != nil { if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input { - t.Fatalf("Bad match result: %#v", matches) + t.Fatalf("Bad match result %#v for %q", matches, m.input) } if len(matches) < (len(m.subs) + 1) { - t.Errorf("Expected %d sub matches, only have %d", len(m.subs), len(matches)-1) + t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input) } for i := range m.subs { if m.subs[i] != matches[i+1] { - t.Errorf("Unexpected submatch %d: %q, expected %q", i+1, matches[i+1], m.subs[i]) + t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input) } } } else if m.match { @@ -325,6 +325,75 @@ func TestFullNameRegexp(t *testing.T) { match: true, subs: []string{"xn--7o8h.com", "myimage"}, }, + { + input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode + match: true, + subs: []string{"example.com", "xn--7o8h.com/myimage"}, + }, + { + input: "example.com/some_separator__underscore/myimage", + match: true, + subs: []string{"example.com", "some_separator__underscore/myimage"}, + }, + { + input: "example.com/__underscore/myimage", + match: false, + }, + { + input: "example.com/..dots/myimage", + match: false, + }, + { + input: "example.com/.dots/myimage", + match: false, + }, + { + input: "example.com/nodouble..dots/myimage", + match: false, + }, + { + input: "example.com/nodouble..dots/myimage", + match: false, + }, + { + input: "docker./docker", + match: false, + }, + { + input: ".docker/docker", + match: false, + }, + { + input: "docker-/docker", + match: false, + }, + { + input: "-docker/docker", + match: false, + }, + { + input: "do..cker/docker", + match: false, + }, + { + input: "do__cker:8080/docker", + match: false, + }, + { + input: "do__cker/docker", + match: true, + subs: []string{"", "do__cker/docker"}, + }, + { + input: "b.gcr.io/test.example.com/my-app", + match: true, + subs: []string{"b.gcr.io", "test.example.com/my-app"}, + }, + { + input: "registry.io/foo/project--id.module--name.ver---sion--name", + match: true, + subs: []string{"registry.io", "foo/project--id.module--name.ver---sion--name"}, + }, } for i := range testcases { checkRegexp(t, anchoredNameRegexp, testcases[i])