Skip to content

Commit

Permalink
allow to get checksum from go-gettere'd file
Browse files Browse the repository at this point in the history
  • Loading branch information
azr committed Jan 8, 2019
1 parent 9884ea1 commit a67abad
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 40 deletions.
164 changes: 164 additions & 0 deletions checksum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package getter

import (
"bufio"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"hash"
"io/ioutil"
"net/url"
"path/filepath"
"strings"

urlhelper "github.com/hashicorp/go-getter/helper/url"
)

var checksummers = map[string]func() hash.Hash{
"md5": md5.New,
"sha1": sha1.New,
"sha256": sha256.New,
"sha512": sha512.New,
}

func checksumHashAndValue(u *url.URL) (checksumHash hash.Hash, checksumValue []byte, err error) {
q := u.Query()
v := q.Get("checksum")

if v == "" {
return nil, nil, nil
}

// Determine the checksum hash type
checksumType := ""
idx := strings.Index(v, ":")
if idx > -1 {
checksumType = v[:idx]
}
if fn, found := checksummers[checksumType]; found {
checksumHash = fn()
// Get the remainder of the value and parse it into bytes
checksumValue, err = hex.DecodeString(v[idx+1:])
return
}

if checksumType != "file" {
return nil, nil, fmt.Errorf(
"unsupported checksum type: %s", checksumType)
}

checkSums, err := checksumsFromFile(v[idx+1:], u)
if err != nil {
return nil, nil, err
}

var checksumValueString string
for checksumType, checksumValueString = range checkSums {
if fn, found := checksummers[checksumType]; found {
checksumHash = fn()
checksumValue, err = hex.DecodeString(checksumValueString)
return
}
}

return
}

func checksumsFromFile(checksumFile string, src *url.URL) (checkSums map[string]string, err error) {

checksumFileURL, err := urlhelper.Parse(checksumFile)
if err != nil {
return nil, err
}

f, err := ioutil.TempFile("", "go-getter.checksum")
if err != nil {
return nil, err
}
defer f.Close()

if err = GetFile(f.Name(), checksumFile); err != nil {
return nil, fmt.Errorf(
"Error downloading checksum file: %s", err)
}

filename := filepath.Base(src.Path)
absPath, _ := filepath.Abs(src.Path)
relpath, _ := filepath.Rel(filepath.Dir(checksumFileURL.Path), absPath)

// possible file identifiers:
options := []string{
filename, // ubuntu-14.04.1-server-amd64.iso
"*" + filename, // *ubuntu-14.04.1-server-amd64.iso Standard checksum
relpath, // dir/ubuntu-14.04.1-server-amd64.iso
"./" + relpath, // ./dir/ubuntu-14.04.1-server-amd64.iso
absPath, // fullpath; set if local
}

rd := bufio.NewReader(f)
res := map[string]string{}
for {

line, err := rd.ReadString('\n')
if err != nil && line == "" {
break
}
checksumType, checksumValue, filename, ok := parseChecksumLine(checksumFile, line)
if !ok {
continue
}
for _, option := range options {
// filename matches src ?
if filename == option {
res[checksumType] = checksumValue
}
}

}
return
}

// parseChecksumLine takes a line from a checksum file and returns
// checksumType, checksumValue and filename
// For GNU-style entries parseChecksumLine will try to guess checksumType
// based on checksumFile which should usually contain the checksum type.
// checksumType will be lowercase.
//
// BSD-style checksum:
// MD5 (file1) = <checksum>
// MD5 (file2) = <checksum>
//
// GNU-style:
// <checksum> file1
// <checksum> *file2
func parseChecksumLine(checksumFilename, line string) (checksumType, checksumValue, filename string, ok bool) {
parts := strings.Fields(line)

ok = true
switch len(parts) {
case 4: // BSD-style
if len(parts[1]) <= 2 || parts[1][0] != '(' || parts[1][len(parts[1])-1] != ')' {
return "", "", "", false
}
checksumType = strings.ToLower(parts[0])
filename = parts[1][1 : len(parts[1])-1]
checksumValue = parts[3]
case 2: // GNU-style

for ctype := range checksummers {
// guess checksum type from filename
if strings.ContainsAny(checksumFilename, ctype) {
checksumType = ctype
}
}
checksumValue = parts[0]
filename = parts[1]
default:
ok = false
}

return
}
66 changes: 66 additions & 0 deletions checksum_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package getter

import "testing"

func Test_parseChecksumLine(t *testing.T) {
type args struct {
checksumFile string
line string
}
tests := []struct {
name string
args args
wantChecksumType string
wantChecksumValue string
wantFilename string
wantOk bool
}{
{"gnu SHA256SUMS",
args{
"http://old-releases.ubuntu.com/releases/14.04.1/SHA256SUMS",
"d9a217e80fb6dc2576d5ccca4c44376c25e6016de47a48e07345678d660fac51 *ubuntu-14.04-desktop-amd64+mac.iso",
},
"sha512",
"d9a217e80fb6dc2576d5ccca4c44376c25e6016de47a48e07345678d660fac51",
"*ubuntu-14.04-desktop-amd64+mac.iso",
true,
},
{"bsd CHECKSUM.SHA256",
args{
"ftp://ftp.freebsd.org/pub/FreeBSD/snapshots/VM-IMAGES/10.4-STABLE/i386/Latest/CHECKSUM.SHA256",
"SHA256 (FreeBSD-10.4-STABLE-i386-20181012-r339297.qcow2.xz) = cedf5203525ef1c7048631d7d26ca54b81f224fccf6b9185eab2cf4b894e8651",
},
"sha256",
"cedf5203525ef1c7048631d7d26ca54b81f224fccf6b9185eab2cf4b894e8651",
"FreeBSD-10.4-STABLE-i386-20181012-r339297.qcow2.xz",
true,
},
{"potato",
args{
"blip",
"potato chips 3 4",
},
"",
"",
"",
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotChecksumType, gotChecksumValue, gotFilename, gotOk := parseChecksumLine(tt.args.checksumFile, tt.args.line)
if gotChecksumType != tt.wantChecksumType {
t.Errorf("parseChecksumLine() gotChecksumType = %v, want %v", gotChecksumType, tt.wantChecksumType)
}
if gotChecksumValue != tt.wantChecksumValue {
t.Errorf("parseChecksumLine() gotChecksumValue = %v, want %v", gotChecksumValue, tt.wantChecksumValue)
}
if gotFilename != tt.wantFilename {
t.Errorf("parseChecksumLine() gotFilename = %v, want %v", gotFilename, tt.wantFilename)
}
if gotOk != tt.wantOk {
t.Errorf("parseChecksumLine() gotOk = %v, want %v", gotOk, tt.wantOk)
}
})
}
}
48 changes: 8 additions & 40 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ package getter

import (
"bytes"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"hash"
Expand Down Expand Up @@ -176,44 +172,16 @@ func (c *Client) Get() error {
mode = ClientModeFile
}

// Determine if we have a checksum
var checksumHash hash.Hash
var checksumValue []byte
if v := q.Get("checksum"); v != "" {
// Delete the query parameter if we have it.
q.Del("checksum")
u.RawQuery = q.Encode()

// Determine the checksum hash type
checksumType := ""
idx := strings.Index(v, ":")
if idx > -1 {
checksumType = v[:idx]
}
switch checksumType {
case "md5":
checksumHash = md5.New()
case "sha1":
checksumHash = sha1.New()
case "sha256":
checksumHash = sha256.New()
case "sha512":
checksumHash = sha512.New()
default:
return fmt.Errorf(
"unsupported checksum type: %s", checksumType)
}

// Get the remainder of the value and parse it into bytes
b, err := hex.DecodeString(v[idx+1:])
if err != nil {
return fmt.Errorf("invalid checksum: %s", err)
}

// Set our value
checksumValue = b
// Determine checksum if we have one
checksumHash, checksumValue, err := checksumHashAndValue(u) // can return nil nil nil
if err != nil {
return fmt.Errorf("invalid checksum: %s", err)
}

// Delete the query parameter if we have it.
q.Del("checksum")
u.RawQuery = q.Encode()

if mode == ClientModeAny {
// Ask the getter which client mode to use
mode, err = g.ClientMode(u)
Expand Down

0 comments on commit a67abad

Please sign in to comment.