]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/go-getter/checksum.go
update vendor and go.mod
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / go-getter / checksum.go
CommitLineData
107c1cdb
ND
1package getter
2
3import (
4 "bufio"
5 "bytes"
6 "crypto/md5"
7 "crypto/sha1"
8 "crypto/sha256"
9 "crypto/sha512"
10 "encoding/hex"
11 "fmt"
12 "hash"
13 "io"
14 "net/url"
15 "os"
16 "path/filepath"
17 "strings"
18
19 urlhelper "github.com/hashicorp/go-getter/helper/url"
20)
21
863486a6
AG
22// FileChecksum helps verifying the checksum for a file.
23type FileChecksum struct {
107c1cdb
ND
24 Type string
25 Hash hash.Hash
26 Value []byte
27 Filename string
28}
29
30// A ChecksumError is returned when a checksum differs
31type ChecksumError struct {
32 Hash hash.Hash
33 Actual []byte
34 Expected []byte
35 File string
36}
37
38func (cerr *ChecksumError) Error() string {
39 if cerr == nil {
40 return "<nil>"
41 }
42 return fmt.Sprintf(
43 "Checksums did not match for %s.\nExpected: %s\nGot: %s\n%T",
44 cerr.File,
45 hex.EncodeToString(cerr.Expected),
46 hex.EncodeToString(cerr.Actual),
47 cerr.Hash, // ex: *sha256.digest
48 )
49}
50
51// checksum is a simple method to compute the checksum of a source file
52// and compare it to the given expected value.
863486a6 53func (c *FileChecksum) checksum(source string) error {
107c1cdb
ND
54 f, err := os.Open(source)
55 if err != nil {
56 return fmt.Errorf("Failed to open file for checksum: %s", err)
57 }
58 defer f.Close()
59
60 c.Hash.Reset()
61 if _, err := io.Copy(c.Hash, f); err != nil {
62 return fmt.Errorf("Failed to hash: %s", err)
63 }
64
65 if actual := c.Hash.Sum(nil); !bytes.Equal(actual, c.Value) {
66 return &ChecksumError{
67 Hash: c.Hash,
68 Actual: actual,
69 Expected: c.Value,
70 File: source,
71 }
72 }
73
74 return nil
75}
76
863486a6 77// extractChecksum will return a FileChecksum based on the 'checksum'
107c1cdb
ND
78// parameter of u.
79// ex:
80// http://hashicorp.com/terraform?checksum=<checksumValue>
81// http://hashicorp.com/terraform?checksum=<checksumType>:<checksumValue>
82// http://hashicorp.com/terraform?checksum=file:<checksum_url>
83// when checksumming from a file, extractChecksum will go get checksum_url
84// in a temporary directory, parse the content of the file then delete it.
85// Content of files are expected to be BSD style or GNU style.
86//
87// BSD-style checksum:
88// MD5 (file1) = <checksum>
89// MD5 (file2) = <checksum>
90//
91// GNU-style:
92// <checksum> file1
93// <checksum> *file2
94//
95// see parseChecksumLine for more detail on checksum file parsing
863486a6 96func (c *Client) extractChecksum(u *url.URL) (*FileChecksum, error) {
107c1cdb
ND
97 q := u.Query()
98 v := q.Get("checksum")
99
100 if v == "" {
101 return nil, nil
102 }
103
104 vs := strings.SplitN(v, ":", 2)
105 switch len(vs) {
106 case 2:
107 break // good
108 default:
109 // here, we try to guess the checksum from it's length
110 // if the type was not passed
111 return newChecksumFromValue(v, filepath.Base(u.EscapedPath()))
112 }
113
114 checksumType, checksumValue := vs[0], vs[1]
115
116 switch checksumType {
117 case "file":
863486a6 118 return c.ChecksumFromFile(checksumValue, u)
107c1cdb
ND
119 default:
120 return newChecksumFromType(checksumType, checksumValue, filepath.Base(u.EscapedPath()))
121 }
122}
123
863486a6
AG
124func newChecksum(checksumValue, filename string) (*FileChecksum, error) {
125 c := &FileChecksum{
107c1cdb
ND
126 Filename: filename,
127 }
128 var err error
129 c.Value, err = hex.DecodeString(checksumValue)
130 if err != nil {
131 return nil, fmt.Errorf("invalid checksum: %s", err)
132 }
133 return c, nil
134}
135
863486a6 136func newChecksumFromType(checksumType, checksumValue, filename string) (*FileChecksum, error) {
107c1cdb
ND
137 c, err := newChecksum(checksumValue, filename)
138 if err != nil {
139 return nil, err
140 }
141
142 c.Type = strings.ToLower(checksumType)
143 switch c.Type {
144 case "md5":
145 c.Hash = md5.New()
146 case "sha1":
147 c.Hash = sha1.New()
148 case "sha256":
149 c.Hash = sha256.New()
150 case "sha512":
151 c.Hash = sha512.New()
152 default:
153 return nil, fmt.Errorf(
154 "unsupported checksum type: %s", checksumType)
155 }
156
157 return c, nil
158}
159
863486a6 160func newChecksumFromValue(checksumValue, filename string) (*FileChecksum, error) {
107c1cdb
ND
161 c, err := newChecksum(checksumValue, filename)
162 if err != nil {
163 return nil, err
164 }
165
166 switch len(c.Value) {
167 case md5.Size:
168 c.Hash = md5.New()
169 c.Type = "md5"
170 case sha1.Size:
171 c.Hash = sha1.New()
172 c.Type = "sha1"
173 case sha256.Size:
174 c.Hash = sha256.New()
175 c.Type = "sha256"
176 case sha512.Size:
177 c.Hash = sha512.New()
178 c.Type = "sha512"
179 default:
180 return nil, fmt.Errorf("Unknown type for checksum %s", checksumValue)
181 }
182
183 return c, nil
184}
185
863486a6 186// ChecksumFromFile will return all the FileChecksums found in file
107c1cdb 187//
863486a6 188// ChecksumFromFile will try to guess the hashing algorithm based on content
107c1cdb
ND
189// of checksum file
190//
863486a6 191// ChecksumFromFile will only return checksums for files that match file
107c1cdb 192// behind src
863486a6 193func (c *Client) ChecksumFromFile(checksumFile string, src *url.URL) (*FileChecksum, error) {
107c1cdb
ND
194 checksumFileURL, err := urlhelper.Parse(checksumFile)
195 if err != nil {
196 return nil, err
197 }
198
199 tempfile, err := tmpFile("", filepath.Base(checksumFileURL.Path))
200 if err != nil {
201 return nil, err
202 }
203 defer os.Remove(tempfile)
204
205 c2 := &Client{
206 Ctx: c.Ctx,
207 Getters: c.Getters,
208 Decompressors: c.Decompressors,
209 Detectors: c.Detectors,
210 Pwd: c.Pwd,
211 Dir: false,
212 Src: checksumFile,
213 Dst: tempfile,
214 ProgressListener: c.ProgressListener,
215 }
216 if err = c2.Get(); err != nil {
217 return nil, fmt.Errorf(
218 "Error downloading checksum file: %s", err)
219 }
220
221 filename := filepath.Base(src.Path)
222 absPath, err := filepath.Abs(src.Path)
223 if err != nil {
224 return nil, err
225 }
226 checksumFileDir := filepath.Dir(checksumFileURL.Path)
227 relpath, err := filepath.Rel(checksumFileDir, absPath)
228 switch {
229 case err == nil ||
230 err.Error() == "Rel: can't make "+absPath+" relative to "+checksumFileDir:
231 // ex: on windows C:\gopath\...\content.txt cannot be relative to \
232 // which is okay, may be another expected path will work.
233 break
234 default:
235 return nil, err
236 }
237
238 // possible file identifiers:
239 options := []string{
240 filename, // ubuntu-14.04.1-server-amd64.iso
241 "*" + filename, // *ubuntu-14.04.1-server-amd64.iso Standard checksum
242 "?" + filename, // ?ubuntu-14.04.1-server-amd64.iso shasum -p
243 relpath, // dir/ubuntu-14.04.1-server-amd64.iso
244 "./" + relpath, // ./dir/ubuntu-14.04.1-server-amd64.iso
245 absPath, // fullpath; set if local
246 }
247
248 f, err := os.Open(tempfile)
249 if err != nil {
250 return nil, fmt.Errorf(
251 "Error opening downloaded file: %s", err)
252 }
253 defer f.Close()
254 rd := bufio.NewReader(f)
255 for {
256 line, err := rd.ReadString('\n')
257 if err != nil {
258 if err != io.EOF {
259 return nil, fmt.Errorf(
260 "Error reading checksum file: %s", err)
261 }
262 break
263 }
264 checksum, err := parseChecksumLine(line)
265 if err != nil || checksum == nil {
266 continue
267 }
268 if checksum.Filename == "" {
269 // filename not sure, let's try
270 return checksum, nil
271 }
272 // make sure the checksum is for the right file
273 for _, option := range options {
274 if option != "" && checksum.Filename == option {
275 // any checksum will work so we return the first one
276 return checksum, nil
277 }
278 }
279 }
280 return nil, fmt.Errorf("no checksum found in: %s", checksumFile)
281}
282
283// parseChecksumLine takes a line from a checksum file and returns
284// checksumType, checksumValue and filename parseChecksumLine guesses the style
285// of the checksum BSD vs GNU by splitting the line and by counting the parts.
286// of a line.
287// for BSD type sums parseChecksumLine guesses the hashing algorithm
288// by checking the length of the checksum.
863486a6 289func parseChecksumLine(line string) (*FileChecksum, error) {
107c1cdb
ND
290 parts := strings.Fields(line)
291
292 switch len(parts) {
293 case 4:
294 // BSD-style checksum:
295 // MD5 (file1) = <checksum>
296 // MD5 (file2) = <checksum>
297 if len(parts[1]) <= 2 ||
298 parts[1][0] != '(' || parts[1][len(parts[1])-1] != ')' {
299 return nil, fmt.Errorf(
300 "Unexpected BSD-style-checksum filename format: %s", line)
301 }
302 filename := parts[1][1 : len(parts[1])-1]
303 return newChecksumFromType(parts[0], parts[3], filename)
304 case 2:
305 // GNU-style:
306 // <checksum> file1
307 // <checksum> *file2
308 return newChecksumFromValue(parts[0], parts[1])
309 case 0:
310 return nil, nil // empty line
311 default:
312 return newChecksumFromValue(parts[0], "")
313 }
314}