]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blobdiff - vendor/github.com/hashicorp/go-getter/checksum.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / go-getter / checksum.go
diff --git a/vendor/github.com/hashicorp/go-getter/checksum.go b/vendor/github.com/hashicorp/go-getter/checksum.go
new file mode 100644 (file)
index 0000000..bea7ed1
--- /dev/null
@@ -0,0 +1,314 @@
+package getter
+
+import (
+       "bufio"
+       "bytes"
+       "crypto/md5"
+       "crypto/sha1"
+       "crypto/sha256"
+       "crypto/sha512"
+       "encoding/hex"
+       "fmt"
+       "hash"
+       "io"
+       "net/url"
+       "os"
+       "path/filepath"
+       "strings"
+
+       urlhelper "github.com/hashicorp/go-getter/helper/url"
+)
+
+// fileChecksum helps verifying the checksum for a file.
+type fileChecksum struct {
+       Type     string
+       Hash     hash.Hash
+       Value    []byte
+       Filename string
+}
+
+// A ChecksumError is returned when a checksum differs
+type ChecksumError struct {
+       Hash     hash.Hash
+       Actual   []byte
+       Expected []byte
+       File     string
+}
+
+func (cerr *ChecksumError) Error() string {
+       if cerr == nil {
+               return "<nil>"
+       }
+       return fmt.Sprintf(
+               "Checksums did not match for %s.\nExpected: %s\nGot: %s\n%T",
+               cerr.File,
+               hex.EncodeToString(cerr.Expected),
+               hex.EncodeToString(cerr.Actual),
+               cerr.Hash, // ex: *sha256.digest
+       )
+}
+
+// checksum is a simple method to compute the checksum of a source file
+// and compare it to the given expected value.
+func (c *fileChecksum) checksum(source string) error {
+       f, err := os.Open(source)
+       if err != nil {
+               return fmt.Errorf("Failed to open file for checksum: %s", err)
+       }
+       defer f.Close()
+
+       c.Hash.Reset()
+       if _, err := io.Copy(c.Hash, f); err != nil {
+               return fmt.Errorf("Failed to hash: %s", err)
+       }
+
+       if actual := c.Hash.Sum(nil); !bytes.Equal(actual, c.Value) {
+               return &ChecksumError{
+                       Hash:     c.Hash,
+                       Actual:   actual,
+                       Expected: c.Value,
+                       File:     source,
+               }
+       }
+
+       return nil
+}
+
+// extractChecksum will return a fileChecksum based on the 'checksum'
+// parameter of u.
+// ex:
+//  http://hashicorp.com/terraform?checksum=<checksumValue>
+//  http://hashicorp.com/terraform?checksum=<checksumType>:<checksumValue>
+//  http://hashicorp.com/terraform?checksum=file:<checksum_url>
+// when checksumming from a file, extractChecksum will go get checksum_url
+// in a temporary directory, parse the content of the file then delete it.
+// Content of files are expected to be BSD style or GNU style.
+//
+// BSD-style checksum:
+//  MD5 (file1) = <checksum>
+//  MD5 (file2) = <checksum>
+//
+// GNU-style:
+//  <checksum>  file1
+//  <checksum> *file2
+//
+// see parseChecksumLine for more detail on checksum file parsing
+func (c *Client) extractChecksum(u *url.URL) (*fileChecksum, error) {
+       q := u.Query()
+       v := q.Get("checksum")
+
+       if v == "" {
+               return nil, nil
+       }
+
+       vs := strings.SplitN(v, ":", 2)
+       switch len(vs) {
+       case 2:
+               break // good
+       default:
+               // here, we try to guess the checksum from it's length
+               // if the type was not passed
+               return newChecksumFromValue(v, filepath.Base(u.EscapedPath()))
+       }
+
+       checksumType, checksumValue := vs[0], vs[1]
+
+       switch checksumType {
+       case "file":
+               return c.checksumFromFile(checksumValue, u)
+       default:
+               return newChecksumFromType(checksumType, checksumValue, filepath.Base(u.EscapedPath()))
+       }
+}
+
+func newChecksum(checksumValue, filename string) (*fileChecksum, error) {
+       c := &fileChecksum{
+               Filename: filename,
+       }
+       var err error
+       c.Value, err = hex.DecodeString(checksumValue)
+       if err != nil {
+               return nil, fmt.Errorf("invalid checksum: %s", err)
+       }
+       return c, nil
+}
+
+func newChecksumFromType(checksumType, checksumValue, filename string) (*fileChecksum, error) {
+       c, err := newChecksum(checksumValue, filename)
+       if err != nil {
+               return nil, err
+       }
+
+       c.Type = strings.ToLower(checksumType)
+       switch c.Type {
+       case "md5":
+               c.Hash = md5.New()
+       case "sha1":
+               c.Hash = sha1.New()
+       case "sha256":
+               c.Hash = sha256.New()
+       case "sha512":
+               c.Hash = sha512.New()
+       default:
+               return nil, fmt.Errorf(
+                       "unsupported checksum type: %s", checksumType)
+       }
+
+       return c, nil
+}
+
+func newChecksumFromValue(checksumValue, filename string) (*fileChecksum, error) {
+       c, err := newChecksum(checksumValue, filename)
+       if err != nil {
+               return nil, err
+       }
+
+       switch len(c.Value) {
+       case md5.Size:
+               c.Hash = md5.New()
+               c.Type = "md5"
+       case sha1.Size:
+               c.Hash = sha1.New()
+               c.Type = "sha1"
+       case sha256.Size:
+               c.Hash = sha256.New()
+               c.Type = "sha256"
+       case sha512.Size:
+               c.Hash = sha512.New()
+               c.Type = "sha512"
+       default:
+               return nil, fmt.Errorf("Unknown type for checksum %s", checksumValue)
+       }
+
+       return c, nil
+}
+
+// checksumsFromFile will return all the fileChecksums found in file
+//
+// checksumsFromFile will try to guess the hashing algorithm based on content
+// of checksum file
+//
+// checksumsFromFile will only return checksums for files that match file
+// behind src
+func (c *Client) checksumFromFile(checksumFile string, src *url.URL) (*fileChecksum, error) {
+       checksumFileURL, err := urlhelper.Parse(checksumFile)
+       if err != nil {
+               return nil, err
+       }
+
+       tempfile, err := tmpFile("", filepath.Base(checksumFileURL.Path))
+       if err != nil {
+               return nil, err
+       }
+       defer os.Remove(tempfile)
+
+       c2 := &Client{
+               Ctx:              c.Ctx,
+               Getters:          c.Getters,
+               Decompressors:    c.Decompressors,
+               Detectors:        c.Detectors,
+               Pwd:              c.Pwd,
+               Dir:              false,
+               Src:              checksumFile,
+               Dst:              tempfile,
+               ProgressListener: c.ProgressListener,
+       }
+       if err = c2.Get(); err != nil {
+               return nil, fmt.Errorf(
+                       "Error downloading checksum file: %s", err)
+       }
+
+       filename := filepath.Base(src.Path)
+       absPath, err := filepath.Abs(src.Path)
+       if err != nil {
+               return nil, err
+       }
+       checksumFileDir := filepath.Dir(checksumFileURL.Path)
+       relpath, err := filepath.Rel(checksumFileDir, absPath)
+       switch {
+       case err == nil ||
+               err.Error() == "Rel: can't make "+absPath+" relative to "+checksumFileDir:
+               // ex: on windows C:\gopath\...\content.txt cannot be relative to \
+               // which is okay, may be another expected path will work.
+               break
+       default:
+               return nil, err
+       }
+
+       // possible file identifiers:
+       options := []string{
+               filename,       // ubuntu-14.04.1-server-amd64.iso
+               "*" + filename, // *ubuntu-14.04.1-server-amd64.iso  Standard checksum
+               "?" + filename, // ?ubuntu-14.04.1-server-amd64.iso  shasum -p
+               relpath,        // dir/ubuntu-14.04.1-server-amd64.iso
+               "./" + relpath, // ./dir/ubuntu-14.04.1-server-amd64.iso
+               absPath,        // fullpath; set if local
+       }
+
+       f, err := os.Open(tempfile)
+       if err != nil {
+               return nil, fmt.Errorf(
+                       "Error opening downloaded file: %s", err)
+       }
+       defer f.Close()
+       rd := bufio.NewReader(f)
+       for {
+               line, err := rd.ReadString('\n')
+               if err != nil {
+                       if err != io.EOF {
+                               return nil, fmt.Errorf(
+                                       "Error reading checksum file: %s", err)
+                       }
+                       break
+               }
+               checksum, err := parseChecksumLine(line)
+               if err != nil || checksum == nil {
+                       continue
+               }
+               if checksum.Filename == "" {
+                       // filename not sure, let's try
+                       return checksum, nil
+               }
+               // make sure the checksum is for the right file
+               for _, option := range options {
+                       if option != "" && checksum.Filename == option {
+                               // any checksum will work so we return the first one
+                               return checksum, nil
+                       }
+               }
+       }
+       return nil, fmt.Errorf("no checksum found in: %s", checksumFile)
+}
+
+// parseChecksumLine takes a line from a checksum file and returns
+// checksumType, checksumValue and filename parseChecksumLine guesses the style
+// of the checksum BSD vs GNU by splitting the line and by counting the parts.
+// of a line.
+// for BSD type sums parseChecksumLine guesses the hashing algorithm
+// by checking the length of the checksum.
+func parseChecksumLine(line string) (*fileChecksum, error) {
+       parts := strings.Fields(line)
+
+       switch len(parts) {
+       case 4:
+               // BSD-style checksum:
+               //  MD5 (file1) = <checksum>
+               //  MD5 (file2) = <checksum>
+               if len(parts[1]) <= 2 ||
+                       parts[1][0] != '(' || parts[1][len(parts[1])-1] != ')' {
+                       return nil, fmt.Errorf(
+                               "Unexpected BSD-style-checksum filename format: %s", line)
+               }
+               filename := parts[1][1 : len(parts[1])-1]
+               return newChecksumFromType(parts[0], parts[3], filename)
+       case 2:
+               // GNU-style:
+               //  <checksum>  file1
+               //  <checksum> *file2
+               return newChecksumFromValue(parts[0], parts[1])
+       case 0:
+               return nil, nil // empty line
+       default:
+               return newChecksumFromValue(parts[0], "")
+       }
+}