first commit

This commit is contained in:
2026-06-19 12:32:14 +09:00
commit eb8d9b9460
36 changed files with 6502 additions and 0 deletions

82
httpdl/checksum.go Normal file
View 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
}