digest.go 4.0 KB

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