// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package dirhash defines hashes over directory trees. package dirhash import ( "archive/zip" "crypto/sha256" "encoding/base64" "errors" "fmt" "io" "os" "path/filepath" "sort" "strings" ) var DefaultHash = Hash1 type Hash func(files []string, open func(string) (io.ReadCloser, error)) (string, error) func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, error) { h := sha256.New() files = append([]string(nil), files...) sort.Strings(files) for _, file := range files { if strings.Contains(file, "\n") { return "", errors.New("filenames with newlines are not supported") } r, err := open(file) if err != nil { return "", err } hf := sha256.New() _, err = io.Copy(hf, r) r.Close() if err != nil { return "", err } fmt.Fprintf(h, "%x %s\n", hf.Sum(nil), file) } return "h1:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil } func HashDir(dir, prefix string, hash Hash) (string, error) { files, err := DirFiles(dir, prefix) if err != nil { return "", err } osOpen := func(name string) (io.ReadCloser, error) { return os.Open(filepath.Join(dir, strings.TrimPrefix(name, prefix))) } return hash(files, osOpen) } func DirFiles(dir, prefix string) ([]string, error) { var files []string dir = filepath.Clean(dir) err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } rel := file if dir != "." { rel = file[len(dir)+1:] } f := filepath.Join(prefix, rel) files = append(files, filepath.ToSlash(f)) return nil }) if err != nil { return nil, err } return files, nil } func HashZip(zipfile string, hash Hash) (string, error) { z, err := zip.OpenReader(zipfile) if err != nil { return "", err } defer z.Close() var files []string zfiles := make(map[string]*zip.File) for _, file := range z.File { files = append(files, file.Name) zfiles[file.Name] = file } zipOpen := func(name string) (io.ReadCloser, error) { f := zfiles[name] if f == nil { return nil, fmt.Errorf("file %q not found in zip", name) // should never happen } return f.Open() } return hash(files, zipOpen) }