Source file src/cmd/vendor/golang.org/x/mod/sumdb/dirhash/hash.go

     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.
     4  
     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.
     8  package dirhash
     9  
    10  import (
    11  	"archive/zip"
    12  	"crypto/sha256"
    13  	"encoding/base64"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"os"
    18  	"path/filepath"
    19  	"sort"
    20  	"strings"
    21  )
    22  
    23  // DefaultHash is the default hash function used in new go.sum entries.
    24  var DefaultHash Hash = Hash1
    25  
    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)
    30  
    31  // Hash1 is the "h1:" directory hash function, using SHA-256.
    32  //
    33  // Hash1 is "h1:" followed by the base64-encoded SHA-256 hash of a summary
    34  // prepared as if by the Unix command:
    35  //
    36  //	find . -type f | sort | sha256sum
    37  //
    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).
    42  //
    43  // File names with newlines (U+000A) are disallowed.
    44  func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, error) {
    45  	h := sha256.New()
    46  	files = append([]string(nil), files...)
    47  	sort.Strings(files)
    48  	for _, file := range files {
    49  		if strings.Contains(file, "\n") {
    50  			return "", errors.New("dirhash: filenames with newlines are not supported")
    51  		}
    52  		r, err := open(file)
    53  		if err != nil {
    54  			return "", err
    55  		}
    56  		hf := sha256.New()
    57  		_, err = io.Copy(hf, r)
    58  		r.Close()
    59  		if err != nil {
    60  			return "", err
    61  		}
    62  		fmt.Fprintf(h, "%x  %s\n", hf.Sum(nil), file)
    63  	}
    64  	return "h1:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
    65  }
    66  
    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)
    72  	if err != nil {
    73  		return "", err
    74  	}
    75  	osOpen := func(name string) (io.ReadCloser, error) {
    76  		return os.Open(filepath.Join(dir, strings.TrimPrefix(name, prefix)))
    77  	}
    78  	return hash(files, osOpen)
    79  }
    80  
    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) {
    85  	var files []string
    86  	dir = filepath.Clean(dir)
    87  	err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error {
    88  		if err != nil {
    89  			return err
    90  		}
    91  		if info.IsDir() {
    92  			return nil
    93  		}
    94  		rel := file
    95  		if dir != "." {
    96  			rel = file[len(dir)+1:]
    97  		}
    98  		f := filepath.Join(prefix, rel)
    99  		files = append(files, filepath.ToSlash(f))
   100  		return nil
   101  	})
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	return files, nil
   106  }
   107  
   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)
   114  	if err != nil {
   115  		return "", err
   116  	}
   117  	defer z.Close()
   118  	var files []string
   119  	zfiles := make(map[string]*zip.File)
   120  	for _, file := range z.File {
   121  		files = append(files, file.Name)
   122  		zfiles[file.Name] = file
   123  	}
   124  	zipOpen := func(name string) (io.ReadCloser, error) {
   125  		f := zfiles[name]
   126  		if f == nil {
   127  			return nil, fmt.Errorf("file %q not found in zip", name) // should never happen
   128  		}
   129  		return f.Open()
   130  	}
   131  	return hash(files, zipOpen)
   132  }
   133  

View as plain text