From 375f3cc1365dd0f22553e85c973a538cb7453f58 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 12 Nov 2014 16:39:35 -0800 Subject: [PATCH] Define common regexps used across registry application This commit adds regular expression definitions for several string identifiers used througout the registry. The repository name regex supports up to five path path components and restricts repeated periods, dashes and underscores. The tag regex simply validates the length of the tag and that printable characters are required. Though we define a new package common, these definition should land in docker core. --- common/names.go | 19 +++++++++++ common/names_test.go | 62 +++++++++++++++++++++++++++++++++ common/tarsum.go | 70 ++++++++++++++++++++++++++++++++++++++ common/tarsum_test.go | 79 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 common/names.go create mode 100644 common/names_test.go create mode 100644 common/tarsum.go create mode 100644 common/tarsum_test.go diff --git a/common/names.go b/common/names.go new file mode 100644 index 000000000..ce25e4872 --- /dev/null +++ b/common/names.go @@ -0,0 +1,19 @@ +package common + +import ( + "regexp" +) + +// RepositoryNameComponentRegexp restricts registtry path components names to +// start with at least two letters or numbers, with following parts able to +// separated by one period, dash or underscore. +var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9](?:[a-z0-9]+[._-]?)*[a-z0-9]`) + +// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 2 to +// 5 path components, separated by a forward slash. +var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){1,4}` + RepositoryNameComponentRegexp.String()) + +// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go. +var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`) + +// TODO(stevvooe): Contribute these exports back to core, so they are shared. diff --git a/common/names_test.go b/common/names_test.go new file mode 100644 index 000000000..17655984f --- /dev/null +++ b/common/names_test.go @@ -0,0 +1,62 @@ +package common + +import ( + "testing" +) + +func TestRepositoryNameRegexp(t *testing.T) { + for _, testcase := range []struct { + input string + valid bool + }{ + { + input: "simple/name", + valid: true, + }, + { + input: "library/ubuntu", + valid: true, + }, + { + input: "docker/stevvooe/app", + valid: true, + }, + { + input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb", + valid: true, + }, + { + input: "a/a/a/a/a/a/b/b/b/b", + valid: false, + }, + { + input: "a/a/a/a/", + valid: false, + }, + { + input: "foo.com/bar/baz", + valid: true, + }, + { + input: "blog.foo.com/bar/baz", + valid: true, + }, + { + input: "asdf", + valid: false, + }, + { + input: "asdf$$^/", + valid: false, + }, + } { + if RepositoryNameRegexp.MatchString(testcase.input) != testcase.valid { + status := "invalid" + if testcase.valid { + status = "valid" + } + + t.Fatalf("expected %q to be %s repository name", testcase.input, status) + } + } +} diff --git a/common/tarsum.go b/common/tarsum.go new file mode 100644 index 000000000..5a6e7d215 --- /dev/null +++ b/common/tarsum.go @@ -0,0 +1,70 @@ +package common + +import ( + "fmt" + + "regexp" +) + +// TarSumRegexp defines a reguler expression to match tarsum identifiers. +var TarsumRegexp = regexp.MustCompile("tarsum(?:.[a-z0-9]+)?\\+[a-zA-Z0-9]+:[A-Fa-f0-9]+") + +// TarsumRegexpCapturing defines a reguler expression to match tarsum identifiers with +// capture groups corresponding to each component. +var TarsumRegexpCapturing = regexp.MustCompile("(tarsum)(.([a-z0-9]+))?\\+([a-zA-Z0-9]+):([A-Fa-f0-9]+)") + +// TarSumInfo contains information about a parsed tarsum. +type TarSumInfo struct { + // Version contains the version of the tarsum. + Version string + + // Algorithm contains the algorithm for the final digest + Algorithm string + + // Digest contains the hex-encoded digest. + Digest string +} + +type InvalidTarSumError struct { + TarSum string +} + +func (e InvalidTarSumError) Error() string { + return fmt.Sprintf("invalid tarsum: %q", e.TarSum) +} + +// ParseTarSum parses a tarsum string into its components of interest. For +// example, this method may receive the tarsum in the following format: +// +// tarsum.v1+sha256:220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e +// +// The function will return the following: +// +// TarSumInfo{ +// Version: "v1", +// Algorithm: "sha256", +// Digest: "220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e", +// } +// +func ParseTarSum(tarSum string) (tsi TarSumInfo, err error) { + components := TarsumRegexpCapturing.FindStringSubmatch(tarSum) + + if len(components) != 1+TarsumRegexpCapturing.NumSubexp() { + return TarSumInfo{}, InvalidTarSumError{TarSum: tarSum} + } + + return TarSumInfo{ + Version: components[3], + Algorithm: components[4], + Digest: components[5], + }, nil +} + +// String returns the valid, string representation of the tarsum info. +func (tsi TarSumInfo) String() string { + if tsi.Version == "" { + return fmt.Sprintf("tarsum+%s:%s", tsi.Algorithm, tsi.Digest) + } + + return fmt.Sprintf("tarsum.%s+%s:%s", tsi.Version, tsi.Algorithm, tsi.Digest) +} diff --git a/common/tarsum_test.go b/common/tarsum_test.go new file mode 100644 index 000000000..e860c9cdf --- /dev/null +++ b/common/tarsum_test.go @@ -0,0 +1,79 @@ +package common + +import ( + "reflect" + "testing" +) + +func TestParseTarSumComponents(t *testing.T) { + for _, testcase := range []struct { + input string + expected TarSumInfo + err error + }{ + { + input: "tarsum.v1+sha256:220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e", + expected: TarSumInfo{ + Version: "v1", + Algorithm: "sha256", + Digest: "220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e", + }, + }, + { + input: "", + err: InvalidTarSumError{}, + }, + { + input: "purejunk", + err: InvalidTarSumError{TarSum: "purejunk"}, + }, + { + input: "tarsum.v23+test:12341234123412341effefefe", + expected: TarSumInfo{ + Version: "v23", + Algorithm: "test", + Digest: "12341234123412341effefefe", + }, + }, + + // The following test cases are ported from docker core + { + // Version 0 tarsum + input: "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b", + expected: TarSumInfo{ + Algorithm: "sha256", + Digest: "e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b", + }, + }, + { + // Dev version tarsum + input: "tarsum.dev+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b", + expected: TarSumInfo{ + Version: "dev", + Algorithm: "sha256", + Digest: "e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b", + }, + }, + } { + tsi, err := ParseTarSum(testcase.input) + if err != nil { + if testcase.err != nil && err == testcase.err { + continue // passes + } + + t.Fatalf("unexpected error parsing tarsum: %v", err) + } + + if testcase.err != nil { + t.Fatalf("expected error not encountered on %q: %v", testcase.input, testcase.err) + } + + if !reflect.DeepEqual(tsi, testcase.expected) { + t.Fatalf("expected tarsum info: %v != %v", tsi, testcase.expected) + } + + if testcase.input != tsi.String() { + t.Fatalf("input should equal output: %q != %q", tsi.String(), testcase.input) + } + } +}