diff --git a/algorithm.go b/algorithm.go index 8813bd2..7d2534c 100644 --- a/algorithm.go +++ b/algorithm.go @@ -59,6 +59,11 @@ var ( SHA384: regexp.MustCompile(`^[a-f0-9]{96}$`), SHA512: regexp.MustCompile(`^[a-f0-9]{128}$`), } + + algorithmRegexp = regexp.MustCompile(`[a-z0-9]+(?:[.+_-][a-z0-9]+)*`) + algorithmRegexpAnchored = regexp.MustCompile(`^` + algorithmRegexp.String() + `$`) + encodedRegexp = regexp.MustCompile(`[a-zA-Z0-9=_-]+`) + encodedRegexpAnchored = regexp.MustCompile(`^` + encodedRegexp.String() + `$`) ) // Available returns true if the digest type is available for use. If this @@ -178,15 +183,28 @@ func (a Algorithm) FromString(s string) Digest { func (a Algorithm) Validate(encoded string) error { r, ok := anchoredEncodedRegexps[a] if !ok { - return ErrDigestUnsupported + r = encodedRegexpAnchored } - // Digests much always be hex-encoded, ensuring that their hex portion will - // always be size*2 - if a.Size()*2 != len(encoded) { - return ErrDigestInvalidLength + if a.Available() { + // Digests much always be hex-encoded, ensuring that their hex portion will + // always be size*2 + if a.Size()*2 != len(encoded) { + return ErrDigestInvalidLength + } } if r.MatchString(encoded) { - return nil + if ok { + return nil + } + return ErrDigestUnsupported } return ErrDigestInvalidFormat } + +// ValidateIdentifier validates the algorithm name. +func (a Algorithm) ValidateIdentifier() error { + if !algorithmRegexpAnchored.MatchString(a.String()) { + return ErrDigestInvalidFormat + } + return nil +} diff --git a/algorithm_test.go b/algorithm_test.go index d50e849..a3a79b3 100644 --- a/algorithm_test.go +++ b/algorithm_test.go @@ -112,3 +112,107 @@ func TestFroms(t *testing.T) { } } } + +func TestValidate(t *testing.T) { + for _, testcase := range []struct { + encoded string + algorithm Algorithm + err error + }{ + { + encoded: "e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b", + algorithm: "sha256", + }, + { + encoded: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d", + algorithm: "sha384", + }, + { + // empty encoded + encoded: "", + algorithm: "sha256", + err: ErrDigestInvalidLength, + }, + { + // not hex (contains an m) + encoded: "d41d8cd98f00b204e9800m98ecf8427e", + algorithm: "sha256", + err: ErrDigestInvalidLength, + }, + { + // too short (sha256) + encoded: "abcdef0123456789", + algorithm: "sha256", + err: ErrDigestInvalidLength, + }, + { + // too short (sha512) + encoded: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", + algorithm: "sha512", + err: ErrDigestInvalidLength, + }, + { + encoded: "d41d8cd98f00b204e9800998ecf8427e", + algorithm: "foo", + err: ErrDigestUnsupported, + }, + { + // unsupported, but the encoded part cannot possibly be valid because it contains invalid characters + encoded: "!", + algorithm: "foo", + err: ErrDigestInvalidFormat, + }, + { + // uppercase + encoded: "E58FCF7418D4390DEC8E8FB69D88C06EC07039D651FEDD3AA72AF9972E7D046B", + algorithm: "sha256", + err: ErrDigestInvalidFormat, + }, + } { + err := testcase.algorithm.Validate(testcase.encoded) + if err != testcase.err { + t.Fatalf("error differed from expected while validating %q: %v != %v", testcase.encoded, err, testcase.err) + } + } +} + +func TestValidateIdentifier(t *testing.T) { + for _, testcase := range []struct { + algorithm Algorithm + err error + }{ + { + algorithm: "sha256", + }, + { + algorithm: "sha384", + }, + { + // empty identifier + algorithm: "", + err: ErrDigestInvalidFormat, + }, + { + // repeated separators + algorithm: "sha384__foo+bar", + err: ErrDigestInvalidFormat, + }, + { + // ensure that we parse valid separators + algorithm: "sha384.foo+bar", + }, + { + // ensure that we parse valid separators + algorithm: "sha384_foo+bar", + }, + { + // ensure that we parse valid separators + algorithm: "sha256+b64", + }, + } { + err := testcase.algorithm.ValidateIdentifier() + if err != testcase.err { + t.Fatalf("error differed from expected while validating identifier %q: %v != %v", testcase.algorithm, err, testcase.err) + } + } +} diff --git a/digest.go b/digest.go index ad398cb..5a68f3f 100644 --- a/digest.go +++ b/digest.go @@ -59,7 +59,7 @@ func NewDigestFromEncoded(alg Algorithm, encoded string) Digest { } // DigestRegexp matches valid digest types. -var DigestRegexp = regexp.MustCompile(`[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+`) +var DigestRegexp = regexp.MustCompile(algorithmRegexp.String() + `:` + encodedRegexp.String()) // DigestRegexpAnchored matches valid digest types, anchored to the start and end of the match. var DigestRegexpAnchored = regexp.MustCompile(`^` + DigestRegexp.String() + `$`) @@ -99,20 +99,27 @@ func FromString(s string) Digest { // Validate checks that the contents of d is a valid digest, returning an // error if not. -func (d Digest) Validate() error { +func (d Digest) Validate() (err error) { s := string(d) i := strings.Index(s, ":") if i <= 0 || i+1 == len(s) { return ErrDigestInvalidFormat } algorithm, encoded := Algorithm(s[:i]), s[i+1:] - if !algorithm.Available() { - if !DigestRegexpAnchored.MatchString(s) { - return ErrDigestInvalidFormat + + err = algorithm.Validate(encoded) + if err == ErrDigestUnsupported { + err2 := algorithm.ValidateIdentifier() + if err2 != nil { + return err2 } - return ErrDigestUnsupported + return err + } + if err != nil { + return err } - return algorithm.Validate(encoded) + + return algorithm.ValidateIdentifier() } // Algorithm returns the algorithm portion of the digest. This will panic if diff --git a/digest_test.go b/digest_test.go index cc3b648..2df63ee 100644 --- a/digest_test.go +++ b/digest_test.go @@ -69,6 +69,11 @@ func TestParseDigest(t *testing.T) { input: "foo:d41d8cd98f00b204e9800998ecf8427e", err: ErrDigestUnsupported, }, + { + // unsupported, but the encoded part cannot possibly be valid because it contains invalid characters + input: "foo:!", + err: ErrDigestInvalidFormat, + }, { // repeated separators input: "sha384__foo+bar:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",