first commit
This commit is contained in:
82
httpdl/checksum.go
Normal file
82
httpdl/checksum.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package httpdl
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrChecksum reports that a finished download did not match the digest given
|
||||
// with --checksum. main maps it to exit code 32 via errors.Is, so it
|
||||
// must stay on the error chain (CONTRACT).
|
||||
var ErrChecksum = errors.New("checksum mismatch")
|
||||
|
||||
// hashers maps each supported hash-type name to a constructor. These are exactly
|
||||
// the names accepted in --checksum.
|
||||
var hashers = map[string]func() hash.Hash{
|
||||
"md5": md5.New,
|
||||
"sha-1": sha1.New,
|
||||
"sha-224": sha256.New224,
|
||||
"sha-256": sha256.New,
|
||||
"sha-384": sha512.New384,
|
||||
"sha-512": sha512.New,
|
||||
}
|
||||
|
||||
// parseChecksum splits a "<type>=<digest>" spec into a hash constructor
|
||||
// and the expected lowercase-hex digest. Type and digest are matched
|
||||
// case-insensitively. An unknown type or a malformed digest is an
|
||||
// error, so a bad --checksum fails before the download rather than after it.
|
||||
func parseChecksum(spec string) (func() hash.Hash, string, error) {
|
||||
typ, want, ok := strings.Cut(spec, "=")
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("bad --checksum %q: want TYPE=DIGEST", spec)
|
||||
}
|
||||
newHash, ok := hashers[strings.ToLower(strings.TrimSpace(typ))]
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("unsupported checksum type %q (md5, sha-1, sha-224, sha-256, sha-384, sha-512)", typ)
|
||||
}
|
||||
want = strings.ToLower(strings.TrimSpace(want))
|
||||
if _, err := hex.DecodeString(want); err != nil {
|
||||
return nil, "", fmt.Errorf("bad --checksum digest %q: not hex", want)
|
||||
}
|
||||
if n := newHash().Size() * 2; len(want) != n {
|
||||
return nil, "", fmt.Errorf("bad --checksum digest %q: %s wants %d hex chars", want, typ, n)
|
||||
}
|
||||
return newHash, want, nil
|
||||
}
|
||||
|
||||
// ValidChecksum reports whether spec is a well-formed --checksum value: a
|
||||
// supported type and a correctly-sized hex digest. main calls it once at startup
|
||||
// so a bad spec aborts as a usage error before any download begins, validating
|
||||
// --checksum at option-parse time.
|
||||
func ValidChecksum(spec string) error {
|
||||
_, _, err := parseChecksum(spec)
|
||||
return err
|
||||
}
|
||||
|
||||
// verifyFile streams the file at path through newHash and compares the result to
|
||||
// want, returning ErrChecksum on a mismatch. Streaming keeps memory use constant
|
||||
// regardless of file size.
|
||||
func verifyFile(path string, newHash func() hash.Hash, want string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
h := newHash()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return err
|
||||
}
|
||||
if got := hex.EncodeToString(h.Sum(nil)); got != want {
|
||||
return fmt.Errorf("%s: %w (want %s, got %s)", path, ErrChecksum, want, got)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user