diff --git a/Guardfile b/Guardfile index 2d2b666..c548e98 100644 --- a/Guardfile +++ b/Guardfile @@ -15,8 +15,4 @@ guard :shell do puts "#{Time.now}: #{m[0]}" system 'go build ./cmd/selfupdate-example/' end - - watch /^testdata\// do |m| - system "go test -v -short ./selfupdate/" - end end diff --git a/selfupdate/detect.go b/selfupdate/detect.go index 5f2d93a..3564a6f 100644 --- a/selfupdate/detect.go +++ b/selfupdate/detect.go @@ -25,7 +25,7 @@ func findSuitableReleaseAndAsset(rels []*github.RepositoryRelease) (*github.Repo // Generate candidates cs := make([]string, 0, 8) for _, sep := range []rune{'_', '-'} { - for _, ext := range []string{".zip", ".tar.gz", ".gzip", ""} { + for _, ext := range []string{".zip", ".tar.gz", ".gzip", ".gz", ""} { suffix := fmt.Sprintf("%s%c%s%s", runtime.GOOS, sep, runtime.GOARCH, ext) cs = append(cs, suffix) if runtime.GOOS == "windows" { diff --git a/selfupdate/testdata/bar-not-found.gzip b/selfupdate/testdata/bar-not-found.gzip new file mode 100644 index 0000000..05e1b20 Binary files /dev/null and b/selfupdate/testdata/bar-not-found.gzip differ diff --git a/selfupdate/testdata/bar-not-found.tar.gz b/selfupdate/testdata/bar-not-found.tar.gz new file mode 100644 index 0000000..1b1a339 Binary files /dev/null and b/selfupdate/testdata/bar-not-found.tar.gz differ diff --git a/selfupdate/testdata/bar-not-found.zip b/selfupdate/testdata/bar-not-found.zip new file mode 100644 index 0000000..d2b1a88 Binary files /dev/null and b/selfupdate/testdata/bar-not-found.zip differ diff --git a/selfupdate/testdata/empty.tar.gz b/selfupdate/testdata/empty.tar.gz new file mode 100644 index 0000000..959fb3e Binary files /dev/null and b/selfupdate/testdata/empty.tar.gz differ diff --git a/selfupdate/testdata/empty.zip b/selfupdate/testdata/empty.zip new file mode 100644 index 0000000..afdaf0c Binary files /dev/null and b/selfupdate/testdata/empty.zip differ diff --git a/selfupdate/testdata/foo.tar.gz b/selfupdate/testdata/foo.tar.gz new file mode 100644 index 0000000..b2f972f Binary files /dev/null and b/selfupdate/testdata/foo.tar.gz differ diff --git a/selfupdate/testdata/foo.zip b/selfupdate/testdata/foo.zip new file mode 100644 index 0000000..c0f5674 Binary files /dev/null and b/selfupdate/testdata/foo.zip differ diff --git a/selfupdate/testdata/invalid-gzip.tar.gz b/selfupdate/testdata/invalid-gzip.tar.gz new file mode 100644 index 0000000..e69de29 diff --git a/selfupdate/testdata/invalid-tar.tar.gz b/selfupdate/testdata/invalid-tar.tar.gz new file mode 100644 index 0000000..692bf2c Binary files /dev/null and b/selfupdate/testdata/invalid-tar.tar.gz differ diff --git a/selfupdate/testdata/invalid.gz b/selfupdate/testdata/invalid.gz new file mode 100644 index 0000000..e69de29 diff --git a/selfupdate/testdata/invalid.zip b/selfupdate/testdata/invalid.zip new file mode 100644 index 0000000..e69de29 diff --git a/selfupdate/testdata/single-file.gz b/selfupdate/testdata/single-file.gz new file mode 100644 index 0000000..a4766d6 Binary files /dev/null and b/selfupdate/testdata/single-file.gz differ diff --git a/selfupdate/testdata/single-file.gzip b/selfupdate/testdata/single-file.gzip new file mode 100644 index 0000000..5a5411c Binary files /dev/null and b/selfupdate/testdata/single-file.gzip differ diff --git a/selfupdate/testdata/single-file.zip b/selfupdate/testdata/single-file.zip new file mode 100644 index 0000000..838a01c Binary files /dev/null and b/selfupdate/testdata/single-file.zip differ diff --git a/selfupdate/uncompress.go b/selfupdate/uncompress.go index 9c3070d..fca4616 100644 --- a/selfupdate/uncompress.go +++ b/selfupdate/uncompress.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "io/ioutil" + "path/filepath" "strings" ) @@ -17,28 +18,27 @@ func uncompress(src io.Reader, url, cmd string) (io.Reader, error) { // So we need to read the HTTP response into a buffer at first. buf, err := ioutil.ReadAll(src) if err != nil { - return nil, err + 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, err + return nil, fmt.Errorf("Failed to uncompress zip file: %s", err) } for _, file := range z.File { - if file.Name == cmd { + _, name := filepath.Split(file.Name) + if !file.FileInfo().IsDir() && name == cmd { return file.Open() } } return nil, fmt.Errorf("File '%s' for the command is not found in %s", cmd, url) - } else if strings.HasSuffix(url, ".gzip") { - return gzip.NewReader(src) } else if strings.HasSuffix(url, ".tar.gz") { gz, err := gzip.NewReader(src) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to uncompress .tar.gz file: %s", err) } t := tar.NewReader(gz) @@ -48,14 +48,27 @@ func uncompress(src io.Reader, url, cmd string) (io.Reader, error) { break } if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to unarchive .tar file: %s", err) } - if h.Name == cmd { + _, name := filepath.Split(h.Name) + if name == cmd { return t, nil } } return nil, fmt.Errorf("File '%s' for the command is not found in %s", cmd, url) + } else if strings.HasSuffix(url, ".gzip") || strings.HasSuffix(url, ".gz") { + 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 name != cmd { + return nil, fmt.Errorf("File name '%s' does not match to command '%s' found in %s", name, cmd, url) + } + + return r, nil } return src, nil diff --git a/selfupdate/uncompress_test.go b/selfupdate/uncompress_test.go new file mode 100644 index 0000000..8cfb53b --- /dev/null +++ b/selfupdate/uncompress_test.go @@ -0,0 +1,159 @@ +package selfupdate + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestCompressionNotRequired(t *testing.T) { + buf := []byte{'a', 'b', 'c'} + want := bytes.NewReader(buf) + r, err := uncompress(want, "https://github.com/foo/bar/releases/download/v1.2.3/foo", "foo") + if err != nil { + t.Fatal(err) + } + have, err := ioutil.ReadAll(r) + if err != nil { + t.Fatal(err) + } + for i, b := range have { + if buf[i] != b { + t.Error(i, "th elem is not the same as wanted. want", buf[i], "but got", b) + } + } +} + +func TestUncompress(t *testing.T) { + for _, n := range []string{ + "testdata/foo.zip", + "testdata/single-file.zip", + "testdata/single-file.gz", + "testdata/single-file.gzip", + "testdata/foo.tar.gz", + } { + t.Run(n, func(t *testing.T) { + f, err := os.Open(n) + if err != nil { + t.Fatal(err) + } + + var ext string + if strings.HasSuffix(n, ".tar.gz") { + ext = ".tar.gz" + } else { + ext = filepath.Ext(n) + } + + url := "https://github.com/foo/bar/releases/download/v1.2.3/bar" + ext + r, err := uncompress(f, url, "bar") + if err != nil { + t.Fatal(err) + } + + bytes, err := ioutil.ReadAll(r) + if err != nil { + t.Fatal(err) + } + s := string(bytes) + if s != "this is test\n" { + t.Fatal("Uncompressing zip failed into unexpected content", s) + } + }) + } +} + +func TestUncompressInvalidArchive(t *testing.T) { + for _, a := range []struct { + name string + msg string + }{ + {"testdata/invalid.zip", "not a valid zip file"}, + {"testdata/invalid.gz", "Failed to uncompress gzip file"}, + {"testdata/invalid-tar.tar.gz", "Failed to unarchive .tar file"}, + {"testdata/invalid-gzip.tar.gz", "Failed to uncompress .tar.gz file"}, + } { + f, err := os.Open(a.name) + if err != nil { + t.Fatal(err) + } + + var ext string + if strings.HasSuffix(a.name, ".tar.gz") { + ext = ".tar.gz" + } else { + ext = filepath.Ext(a.name) + } + + url := "https://github.com/foo/bar/releases/download/v1.2.3/bar" + ext + _, err = uncompress(f, url, "bar") + if err == nil { + t.Fatal("Error should be raised") + } + if !strings.Contains(err.Error(), a.msg) { + t.Fatal("Unexpected error:", err) + } + } +} + +func TestTargetNotFoundInZip(t *testing.T) { + for _, f := range []string{ + "testdata/empty.zip", + "testdata/bar-not-found.zip", + } { + t.Run(f, func(t *testing.T) { + f, err := os.Open(f) + if err != nil { + t.Fatal(err) + } + + _, err = uncompress(f, "https://github.com/foo/bar/releases/download/v1.2.3/bar.zip", "bar") + if err == nil { + t.Fatal("Error should be raised for") + } + if !strings.Contains(err.Error(), "command is not found") { + t.Fatal("Unexpected error:", err) + } + }) + } +} + +func TestTargetNotFoundInGZip(t *testing.T) { + f, err := os.Open("testdata/bar-not-found.gzip") + if err != nil { + t.Fatal(err) + } + + _, err = uncompress(f, "https://github.com/foo/bar/releases/download/v1.2.3/bar.gzip", "bar") + if err == nil { + t.Fatal("Error should be raised for") + } + if !strings.Contains(err.Error(), "does not match to command") { + t.Fatal("Unexpected error:", err) + } +} + +func TestTargetNotFoundInTarGz(t *testing.T) { + for _, f := range []string{ + "testdata/empty.tar.gz", + "testdata/bar-not-found.tar.gz", + } { + t.Run(f, func(t *testing.T) { + f, err := os.Open(f) + if err != nil { + t.Fatal(err) + } + + _, err = uncompress(f, "https://github.com/foo/bar/releases/download/v1.2.3/bar.tar.gz", "bar") + if err == nil { + t.Fatal("Error should be raised for") + } + if !strings.Contains(err.Error(), "command is not found") { + t.Fatal("Unexpected error:", err) + } + }) + } +}