19 urlhelper "github.com/hashicorp/go-getter/helper/url"
22 // fileChecksum helps verifying the checksum for a file.
23 type fileChecksum struct {
30 // A ChecksumError is returned when a checksum differs
31 type ChecksumError struct {
38 func (cerr *ChecksumError) Error() string {
43 "Checksums did not match for %s.\nExpected: %s\nGot: %s\n%T",
45 hex.EncodeToString(cerr.Expected),
46 hex.EncodeToString(cerr.Actual),
47 cerr.Hash, // ex: *sha256.digest
51 // checksum is a simple method to compute the checksum of a source file
52 // and compare it to the given expected value.
53 func (c *fileChecksum) checksum(source string) error {
54 f, err := os.Open(source)
56 return fmt.Errorf("Failed to open file for checksum: %s", err)
61 if _, err := io.Copy(c.Hash, f); err != nil {
62 return fmt.Errorf("Failed to hash: %s", err)
65 if actual := c.Hash.Sum(nil); !bytes.Equal(actual, c.Value) {
66 return &ChecksumError{
77 // extractChecksum will return a fileChecksum based on the 'checksum'
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.
87 // BSD-style checksum:
88 // MD5 (file1) = <checksum>
89 // MD5 (file2) = <checksum>
95 // see parseChecksumLine for more detail on checksum file parsing
96 func (c *Client) extractChecksum(u *url.URL) (*fileChecksum, error) {
98 v := q.Get("checksum")
104 vs := strings.SplitN(v, ":", 2)
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()))
114 checksumType, checksumValue := vs[0], vs[1]
116 switch checksumType {
118 return c.checksumFromFile(checksumValue, u)
120 return newChecksumFromType(checksumType, checksumValue, filepath.Base(u.EscapedPath()))
124 func newChecksum(checksumValue, filename string) (*fileChecksum, error) {
129 c.Value, err = hex.DecodeString(checksumValue)
131 return nil, fmt.Errorf("invalid checksum: %s", err)
136 func newChecksumFromType(checksumType, checksumValue, filename string) (*fileChecksum, error) {
137 c, err := newChecksum(checksumValue, filename)
142 c.Type = strings.ToLower(checksumType)
149 c.Hash = sha256.New()
151 c.Hash = sha512.New()
153 return nil, fmt.Errorf(
154 "unsupported checksum type: %s", checksumType)
160 func newChecksumFromValue(checksumValue, filename string) (*fileChecksum, error) {
161 c, err := newChecksum(checksumValue, filename)
166 switch len(c.Value) {
174 c.Hash = sha256.New()
177 c.Hash = sha512.New()
180 return nil, fmt.Errorf("Unknown type for checksum %s", checksumValue)
186 // checksumsFromFile will return all the fileChecksums found in file
188 // checksumsFromFile will try to guess the hashing algorithm based on content
191 // checksumsFromFile will only return checksums for files that match file
193 func (c *Client) checksumFromFile(checksumFile string, src *url.URL) (*fileChecksum, error) {
194 checksumFileURL, err := urlhelper.Parse(checksumFile)
199 tempfile, err := tmpFile("", filepath.Base(checksumFileURL.Path))
203 defer os.Remove(tempfile)
208 Decompressors: c.Decompressors,
209 Detectors: c.Detectors,
214 ProgressListener: c.ProgressListener,
216 if err = c2.Get(); err != nil {
217 return nil, fmt.Errorf(
218 "Error downloading checksum file: %s", err)
221 filename := filepath.Base(src.Path)
222 absPath, err := filepath.Abs(src.Path)
226 checksumFileDir := filepath.Dir(checksumFileURL.Path)
227 relpath, err := filepath.Rel(checksumFileDir, absPath)
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.
238 // possible file identifiers:
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
248 f, err := os.Open(tempfile)
250 return nil, fmt.Errorf(
251 "Error opening downloaded file: %s", err)
254 rd := bufio.NewReader(f)
256 line, err := rd.ReadString('\n')
259 return nil, fmt.Errorf(
260 "Error reading checksum file: %s", err)
264 checksum, err := parseChecksumLine(line)
265 if err != nil || checksum == nil {
268 if checksum.Filename == "" {
269 // filename not sure, let's try
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
280 return nil, fmt.Errorf("no checksum found in: %s", checksumFile)
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.
287 // for BSD type sums parseChecksumLine guesses the hashing algorithm
288 // by checking the length of the checksum.
289 func parseChecksumLine(line string) (*fileChecksum, error) {
290 parts := strings.Fields(line)
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)
302 filename := parts[1][1 : len(parts[1])-1]
303 return newChecksumFromType(parts[0], parts[3], filename)
308 return newChecksumFromValue(parts[0], parts[1])
310 return nil, nil // empty line
312 return newChecksumFromValue(parts[0], "")