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 } else if file == dir {
94 return fmt.Errorf("%s is not a directory", dir)
95 }
96
97 rel := file
98 if dir != "." {
99 rel = file[len(dir)+1:]
100 }
101 f := filepath.Join(prefix, rel)
102 files = append(files, filepath.ToSlash(f))
103 return nil
104 })
105 if err != nil {
106 return nil, err
107 }
108 return files, nil
109 }
110
111
112
113
114
115 func HashZip(zipfile string, hash Hash) (string, error) {
116 z, err := zip.OpenReader(zipfile)
117 if err != nil {
118 return "", err
119 }
120 defer z.Close()
121 var files []string
122 zfiles := make(map[string]*zip.File)
123 for _, file := range z.File {
124 files = append(files, file.Name)
125 zfiles[file.Name] = file
126 }
127 zipOpen := func(name string) (io.ReadCloser, error) {
128 f := zfiles[name]
129 if f == nil {
130 return nil, fmt.Errorf("file %q not found in zip", name)
131 }
132 return f.Open()
133 }
134 return hash(files, zipOpen)
135 }
136
View as plain text