github.com/gernest/nezuko@v0.1.2/internal/dirhash/hash.go (about) 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 package dirhash 7 8 import ( 9 "archive/zip" 10 "crypto/sha256" 11 "encoding/base64" 12 "errors" 13 "fmt" 14 "io" 15 "os" 16 "path/filepath" 17 "sort" 18 "strings" 19 ) 20 21 var DefaultHash = Hash1 22 23 type Hash func(files []string, open func(string) (io.ReadCloser, error)) (string, error) 24 25 func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, error) { 26 h := sha256.New() 27 files = append([]string(nil), files...) 28 sort.Strings(files) 29 for _, file := range files { 30 if strings.Contains(file, "\n") { 31 return "", errors.New("filenames with newlines are not supported") 32 } 33 r, err := open(file) 34 if err != nil { 35 return "", err 36 } 37 hf := sha256.New() 38 _, err = io.Copy(hf, r) 39 r.Close() 40 if err != nil { 41 return "", err 42 } 43 fmt.Fprintf(h, "%x %s\n", hf.Sum(nil), file) 44 } 45 return "h1:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil 46 } 47 48 func HashDir(dir, prefix string, hash Hash) (string, error) { 49 files, err := DirFiles(dir, prefix) 50 if err != nil { 51 return "", err 52 } 53 osOpen := func(name string) (io.ReadCloser, error) { 54 return os.Open(filepath.Join(dir, strings.TrimPrefix(name, prefix))) 55 } 56 return hash(files, osOpen) 57 } 58 59 func DirFiles(dir, prefix string) ([]string, error) { 60 var files []string 61 dir = filepath.Clean(dir) 62 err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error { 63 if err != nil { 64 return err 65 } 66 if info.IsDir() { 67 return nil 68 } 69 rel := file 70 if dir != "." { 71 rel = file[len(dir)+1:] 72 } 73 f := filepath.Join(prefix, rel) 74 files = append(files, filepath.ToSlash(f)) 75 return nil 76 }) 77 if err != nil { 78 return nil, err 79 } 80 return files, nil 81 } 82 83 func HashZip(zipfile string, hash Hash) (string, error) { 84 z, err := zip.OpenReader(zipfile) 85 if err != nil { 86 return "", err 87 } 88 defer z.Close() 89 var files []string 90 zfiles := make(map[string]*zip.File) 91 for _, file := range z.File { 92 files = append(files, file.Name) 93 zfiles[file.Name] = file 94 } 95 zipOpen := func(name string) (io.ReadCloser, error) { 96 f := zfiles[name] 97 if f == nil { 98 return nil, fmt.Errorf("file %q not found in zip", name) // should never happen 99 } 100 return f.Open() 101 } 102 return hash(files, zipOpen) 103 }