diff --git a/go.mod b/go.mod index 14857b2..57b6bab 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/briandowns/spinner v1.11.1 github.com/coreos/etcd v3.3.10+incompatible github.com/go-acme/lego/v3 v3.7.0 + github.com/google/go-github v17.0.0+incompatible // indirect + github.com/hashicorp/go-version v1.2.1 // indirect github.com/kyokomi/emoji v2.2.4+incompatible github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 github.com/mattn/go-colorable v0.1.2 @@ -15,6 +17,7 @@ require ( github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c github.com/spf13/cobra v1.0.0 github.com/spf13/viper v1.4.0 + github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e go.uber.org/zap v1.15.0 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect diff --git a/go.sum b/go.sum index d9c11dd..f666a41 100644 --- a/go.sum +++ b/go.sum @@ -150,6 +150,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -176,6 +179,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -332,6 +337,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k= +github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM= github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= diff --git a/main.go b/main.go index b78b88a..0ec6422 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,21 @@ package main import ( + "archive/tar" + "archive/zip" + "bufio" + "compress/gzip" + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + "github.com/loophole/cli/cmd" + "github.com/tcnksm/go-latest" ) // Will be filled in during build @@ -9,5 +23,198 @@ var version = "development" var commit = "unknown" func main() { + githubTag := &latest.GithubTag{ + Owner: "loophole", + Repository: "cli", + } + + if version == "development" { + fmt.Println("Update check disabled while using development version.") + } else { + res, err := latest.Check(githubTag, version) + if err != nil { + log.Fatal("GithubTag error:" + err.Error()) + } + if _, err := os.Stat("loophole_version_" + res.Current); err == nil { + fmt.Println("################") + fmt.Println("It looks like you recently downloaded a newer version of Loophole, please use it instead of this one!") + fmt.Println("It should be located in the folder \"loophole_version_" + res.Current + "\"") + fmt.Println("################") + fmt.Println() + res.Outdated = false + } + + if res.Outdated { + reader := bufio.NewReader(os.Stdin) + fmt.Println("Your version of Loophole is outdated. Do you wish to download version " + res.Current + " now?") + + fmt.Print("Y/n : ") + text, _ := reader.ReadString('\n') + // convert CRLF to LF + text = strings.Replace(text, "\n", "", -1) + + fmt.Println(text) + + if strings.Contains(text, "n") || strings.Contains(text, "N") { + //skip update + } else { + archiveExt := ".tar.gz" + if runtime.GOOS == "windows" { + archiveExt = ".zip" + } + urlBase := "https://github.com/loophole/cli/releases/download/" + url := fmt.Sprintf("%s%s%s%s%s%s%s%s%s", urlBase, res.Current, "/loophole_", res.Current, "_", runtime.GOOS, "_", runtime.GOARCH, archiveExt) + fileName := "loophole_version_" + res.Current + archiveName := res.Current + err := download(archiveName+archiveExt, url) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + if runtime.GOOS == "windows" { + err = extractZip(archiveName+archiveExt, fileName) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + } else { + err = extractTarGz(archiveName+archiveExt, fileName) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + } + err = os.Remove(archiveName + archiveExt) + if err != nil { + fmt.Println("Unable to delete downloaded compressed file: " + err.Error()) + } + fmt.Println("Download finished! Please start the new version located in the folder: loophole_version_" + res.Current) + os.Exit(0) + } + } + } cmd.Execute(version, commit) } + +func download(filepath string, url string) error { + + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + file, err := os.Create(filepath) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(file, resp.Body) + return err +} + +func extractTarGz(src, dest string) error { + sourceFile, err := os.Open(src) + if err != nil { + return err + } + + gzipReader, err := gzip.NewReader(sourceFile) + if err != nil { + return err + } + + tarReader := tar.NewReader(gzipReader) + + if err := os.Mkdir(dest, 0755); err != nil { + return err + } + + for true { + header, err := tarReader.Next() + + if err == io.EOF { + break + } + + if err != nil { + return err + } + + if strings.Contains(header.Name, "..") { + fmt.Println("Illegal path: File name contains '..'") + os.Exit(1) + } + + switch header.Typeflag { + case tar.TypeDir: + if err := os.Mkdir(header.Name, 0755); err != nil { + return err + } + case tar.TypeReg: + outFile, err := os.Create(dest + "/" + header.Name) + if err != nil { + return err + } + if _, err := io.Copy(outFile, tarReader); err != nil { + return err + } + outFile.Close() + + default: + fmt.Printf("extractTarGz: unknown type: %b in %s", header.Typeflag, header.Name) + os.Exit(1) + } + + } + return nil +} + +func extractZip(src, dest string) error { + reader, err := zip.OpenReader(src) + if err != nil { + return err + } + defer reader.Close() + + os.MkdirAll(dest, 0755) + + for _, file := range reader.File { + data, err := file.Open() + + if err != nil { + return err + } + defer data.Close() + + if strings.Contains(file.Name, "..") { + fmt.Println("Illegal path: File name contains '..'") + os.Exit(1) + } + + path := filepath.Join(dest, file.Name) + + if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) { + return fmt.Errorf("illegal file path: %s", path) + } + + if file.FileInfo().IsDir() { + os.MkdirAll(path, file.Mode()) + } else { + os.MkdirAll(filepath.Dir(path), file.Mode()) + endFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) + if err != nil { + return err + } + + _, err = io.Copy(endFile, data) + endFile.Close() + if err != nil { + return err + } + } + } + + return nil +}