1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
package selfupdate
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"fmt"
"github.com/ulikunitz/xz"
"io"
"io/ioutil"
"path/filepath"
"runtime"
"strings"
)
func matchExecutableName(cmd, target string) bool {
if cmd == target {
return true
}
o, a := runtime.GOOS, runtime.GOARCH
// When the contained executable name is full name (e.g. foo_darwin_amd64),
// it is also regarded as a target executable file. (#19)
for _, d := range []rune{'_', '-'} {
c := fmt.Sprintf("%s%c%s%c%s", cmd, d, o, d, a)
if o == "windows" {
c += ".exe"
}
if c == target {
return true
}
}
return false
}
func unarchiveTar(src io.Reader, url, cmd string) (io.Reader, error) {
t := tar.NewReader(src)
for {
h, err := t.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("Failed to unarchive .tar file: %s", err)
}
_, name := filepath.Split(h.Name)
if matchExecutableName(cmd, name) {
log.Println("Executable file", h.Name, "was found in tar archive")
return t, nil
}
}
return nil, fmt.Errorf("File '%s' for the command is not found in %s", cmd, url)
}
// UncompressCommand uncompresses the given source. Archive and compression format is
// automatically detected from 'url' parameter, which represents the URL of asset.
// This returns a reader for the uncompressed command given by 'cmd'. '.zip',
// '.tar.gz', '.tar.xz', '.tgz', '.gz' and '.xz' are supported.
func UncompressCommand(src io.Reader, url, cmd string) (io.Reader, error) {
if strings.HasSuffix(url, ".zip") {
log.Println("Uncompressing zip file", url)
// Zip format requires its file size for uncompressing.
// So we need to read the HTTP response into a buffer at first.
buf, err := ioutil.ReadAll(src)
if err != nil {
return nil, fmt.Errorf("Failed to create buffer for zip file: %s", err)
}
r := bytes.NewReader(buf)
z, err := zip.NewReader(r, r.Size())
if err != nil {
return nil, fmt.Errorf("Failed to uncompress zip file: %s", err)
}
for _, file := range z.File {
_, name := filepath.Split(file.Name)
if !file.FileInfo().IsDir() && matchExecutableName(cmd, name) {
log.Println("Executable file", file.Name, "was found in zip archive")
return file.Open()
}
}
return nil, fmt.Errorf("File '%s' for the command is not found in %s", cmd, url)
} else if strings.HasSuffix(url, ".tar.gz") || strings.HasSuffix(url, ".tgz") {
log.Println("Uncompressing tar.gz file", url)
gz, err := gzip.NewReader(src)
if err != nil {
return nil, fmt.Errorf("Failed to uncompress .tar.gz file: %s", err)
}
return unarchiveTar(gz, url, cmd)
} else if strings.HasSuffix(url, ".gzip") || strings.HasSuffix(url, ".gz") {
log.Println("Uncompressing gzip file", url)
r, err := gzip.NewReader(src)
if err != nil {
return nil, fmt.Errorf("Failed to uncompress gzip file downloaded from %s: %s", url, err)
}
name := r.Header.Name
if !matchExecutableName(cmd, name) {
return nil, fmt.Errorf("File name '%s' does not match to command '%s' found in %s", name, cmd, url)
}
log.Println("Executable file", name, "was found in gzip file")
return r, nil
} else if strings.HasSuffix(url, ".tar.xz") {
log.Println("Uncompressing tar.xz file", url)
xzip, err := xz.NewReader(src)
if err != nil {
return nil, fmt.Errorf("Failed to uncompress .tar.xz file: %s", err)
}
return unarchiveTar(xzip, url, cmd)
} else if strings.HasSuffix(url, ".xz") {
log.Println("Uncompressing xzip file", url)
xzip, err := xz.NewReader(src)
if err != nil {
return nil, fmt.Errorf("Failed to uncompress xzip file downloaded from %s: %s", url, err)
}
log.Println("Uncompressed file from xzip is assumed to be an executable", cmd)
return xzip, nil
}
log.Println("Uncompression is not needed", url)
return src, nil
}
|