blob: 7c66c30c017a6d19f8bcf03b059ddeab95c90096 [file] [log] [blame]
David K. Bainbridge215e0242017-09-05 23:18:24 -07001package digest
2
3import (
4 "fmt"
5 "hash"
6 "io"
7 "regexp"
8 "strings"
9)
10
11// Digest allows simple protection of hex formatted digest strings, prefixed
12// by their algorithm. Strings of type Digest have some guarantee of being in
13// the correct format and it provides quick access to the components of a
14// digest string.
15//
16// The following is an example of the contents of Digest types:
17//
18// sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
19//
20// This allows to abstract the digest behind this type and work only in those
21// terms.
22type Digest string
23
24// NewDigest returns a Digest from alg and a hash.Hash object.
25func NewDigest(alg Algorithm, h hash.Hash) Digest {
26 return NewDigestFromBytes(alg, h.Sum(nil))
27}
28
29// NewDigestFromBytes returns a new digest from the byte contents of p.
30// Typically, this can come from hash.Hash.Sum(...) or xxx.SumXXX(...)
31// functions. This is also useful for rebuilding digests from binary
32// serializations.
33func NewDigestFromBytes(alg Algorithm, p []byte) Digest {
34 return Digest(fmt.Sprintf("%s:%x", alg, p))
35}
36
37// NewDigestFromHex returns a Digest from alg and a the hex encoded digest.
38func NewDigestFromHex(alg, hex string) Digest {
39 return Digest(fmt.Sprintf("%s:%s", alg, hex))
40}
41
42// DigestRegexp matches valid digest types.
43var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
44
45// DigestRegexpAnchored matches valid digest types, anchored to the start and end of the match.
46var DigestRegexpAnchored = regexp.MustCompile(`^` + DigestRegexp.String() + `$`)
47
48var (
49 // ErrDigestInvalidFormat returned when digest format invalid.
50 ErrDigestInvalidFormat = fmt.Errorf("invalid checksum digest format")
51
52 // ErrDigestInvalidLength returned when digest has invalid length.
53 ErrDigestInvalidLength = fmt.Errorf("invalid checksum digest length")
54
55 // ErrDigestUnsupported returned when the digest algorithm is unsupported.
56 ErrDigestUnsupported = fmt.Errorf("unsupported digest algorithm")
57)
58
59// Parse parses s and returns the validated digest object. An error will
60// be returned if the format is invalid.
61func Parse(s string) (Digest, error) {
62 d := Digest(s)
63 return d, d.Validate()
64}
65
66// FromReader consumes the content of rd until io.EOF, returning canonical digest.
67func FromReader(rd io.Reader) (Digest, error) {
68 return Canonical.FromReader(rd)
69}
70
71// FromBytes digests the input and returns a Digest.
72func FromBytes(p []byte) Digest {
73 return Canonical.FromBytes(p)
74}
75
76// FromString digests the input and returns a Digest.
77func FromString(s string) Digest {
78 return Canonical.FromString(s)
79}
80
81// Validate checks that the contents of d is a valid digest, returning an
82// error if not.
83func (d Digest) Validate() error {
84 s := string(d)
85
86 i := strings.Index(s, ":")
87
88 // validate i then run through regexp
89 if i < 0 || i+1 == len(s) || !DigestRegexpAnchored.MatchString(s) {
90 return ErrDigestInvalidFormat
91 }
92
93 algorithm := Algorithm(s[:i])
94 if !algorithm.Available() {
95 return ErrDigestUnsupported
96 }
97
98 // Digests much always be hex-encoded, ensuring that their hex portion will
99 // always be size*2
100 if algorithm.Size()*2 != len(s[i+1:]) {
101 return ErrDigestInvalidLength
102 }
103
104 return nil
105}
106
107// Algorithm returns the algorithm portion of the digest. This will panic if
108// the underlying digest is not in a valid format.
109func (d Digest) Algorithm() Algorithm {
110 return Algorithm(d[:d.sepIndex()])
111}
112
113// Verifier returns a writer object that can be used to verify a stream of
114// content against the digest. If the digest is invalid, the method will panic.
115func (d Digest) Verifier() Verifier {
116 return hashVerifier{
117 hash: d.Algorithm().Hash(),
118 digest: d,
119 }
120}
121
122// Hex returns the hex digest portion of the digest. This will panic if the
123// underlying digest is not in a valid format.
124func (d Digest) Hex() string {
125 return string(d[d.sepIndex()+1:])
126}
127
128func (d Digest) String() string {
129 return string(d)
130}
131
132func (d Digest) sepIndex() int {
133 i := strings.Index(string(d), ":")
134
135 if i < 0 {
136 panic(fmt.Sprintf("no ':' separator in digest %q", d))
137 }
138
139 return i
140}