1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package dirhash defines hashes over directory trees.
6 // These hashes are recorded in go.sum files and in the Go checksum database,
7 // to allow verifying that a newly-downloaded module has the expected content.
23 // DefaultHash is the default hash function used in new go.sum entries.
24 var DefaultHash Hash = Hash1
26 // A Hash is a directory hash function.
27 // It accepts a list of files along with a function that opens the content of each file.
28 // It opens, reads, hashes, and closes each file and returns the overall directory hash.
29 type Hash func(files []string, open func(string) (io.ReadCloser, error)) (string, error)
31 // Hash1 is the "h1:" directory hash function, using SHA-256.
33 // Hash1 is "h1:" followed by the base64-encoded SHA-256 hash of a summary
34 // prepared as if by the Unix command:
36 // find . -type f | sort | sha256sum
38 // More precisely, the hashed summary contains a single line for each file in the list,
39 // ordered by sort.Strings applied to the file names, where each line consists of
40 // the hexadecimal SHA-256 hash of the file content,
41 // two spaces (U+0020), the file name, and a newline (U+000A).
43 // File names with newlines (U+000A) are disallowed.
44 func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, error) {
46 files = append([]string(nil), files...)
48 for _, file := range files {
49 if strings.Contains(file, "\n") {
50 return "", errors.New("dirhash: filenames with newlines are not supported")
57 _, err = io.Copy(hf, r)
62 fmt.Fprintf(h, "%x %s\n", hf.Sum(nil), file)
64 return "h1:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
67 // HashDir returns the hash of the local file system directory dir,
68 // replacing the directory name itself with prefix in the file names
69 // used in the hash function.
70 func HashDir(dir, prefix string, hash Hash) (string, error) {
71 files, err := DirFiles(dir, prefix)
75 osOpen := func(name string) (io.ReadCloser, error) {
76 return os.Open(filepath.Join(dir, strings.TrimPrefix(name, prefix)))
78 return hash(files, osOpen)
81 // DirFiles returns the list of files in the tree rooted at dir,
82 // replacing the directory name dir with prefix in each name.
83 // The resulting names always use forward slashes.
84 func DirFiles(dir, prefix string) ([]string, error) {
86 dir = filepath.Clean(dir)
87 err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error {
96 rel = file[len(dir)+1:]
98 f := filepath.Join(prefix, rel)
99 files = append(files, filepath.ToSlash(f))
108 // HashZip returns the hash of the file content in the named zip file.
109 // Only the file names and their contents are included in the hash:
110 // the exact zip file format encoding, compression method,
111 // per-file modification times, and other metadata are ignored.
112 func HashZip(zipfile string, hash Hash) (string, error) {
113 z, err := zip.OpenReader(zipfile)
119 zfiles := make(map[string]*zip.File)
120 for _, file := range z.File {
121 files = append(files, file.Name)
122 zfiles[file.Name] = file
124 zipOpen := func(name string) (io.ReadCloser, error) {
127 return nil, fmt.Errorf("file %q not found in zip", name) // should never happen
131 return hash(files, zipOpen)