1
2
3
4
5
6
7
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
24 var DefaultHash Hash = Hash1
25
26
27
28
29 type Hash func(files []string, open func(string) (io.ReadCloser, error)) (string, error)
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
68
69
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
82
83
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
109
110
111
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)
128 }
129 return f.Open()
130 }
131 return hash(files, zipOpen)
132 }
133
View as plain text