From 4676efa739860c96466789443e9c44ea0a49a3c1 Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Tue, 12 Dec 2023 03:58:05 +0700 Subject: [PATCH 1/4] Chore Docs Security (#39) - [+] chore(SECURITY.md): add SECURITY.md file with information about supported versions and reporting vulnerabilities --- SECURITY.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..b2bf52e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,28 @@ +# Security Policy + +## Supported Versions + +Use this section to tell users about which versions of your project are currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 1.3.3.7 | :white_check_mark: | +| < 1.3.3.7 | :x: | + +## Reporting a Vulnerability + +The security of this project is taken seriously. If you find a security vulnerability within the project, please report it responsibly. Follow these steps to report a vulnerability: + +1. **Do not report security vulnerabilities through public GitHub issues.** + +2. Instead, please visit the [Security Advisories](https://github.com/H0llyW00dzZ/ChatGPT-Next-Web-Session-Exporter/security/advisories/new) section of the GitHub repository. + +3. Click on 'New draft security advisory' to create a report. + +4. Provide a clear and comprehensive description of the vulnerability. Include any steps required to reproduce the issue if applicable. + +5. Submit the draft advisory. This will notify me while keeping the issue confidential until it's resolved. + +Your report will be acknowledged within 24 hours, and you’ll receive a more detailed response within 48 hours indicating the next steps in handling your report. + +After the initial reply to your report, the owner of this repository will endeavor to keep you informed of the progress towards a fix and full announcement. From 83728870235cf31084c0167e24853e10a249d24c Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Tue, 12 Dec 2023 02:37:25 +0700 Subject: [PATCH 2/4] Feat [Golang] [Package] Updater - [+] feat(updater): add updater package for automatically updating Go applications --- updater/app.go | 182 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 updater/app.go diff --git a/updater/app.go b/updater/app.go new file mode 100644 index 0000000..dc036b2 --- /dev/null +++ b/updater/app.go @@ -0,0 +1,182 @@ +// Copyright (c) 2023 H0llyW00dzZ +// +// Package updater provides functionality to automatically update a Go application +// by checking for the latest release on GitHub and, if available, downloading and +// applying the update. It is designed to work with applications that are distributed +// with GitHub releases. +// +// The updater checks the latest release by calling the GitHub Releases API and +// compares the tag name of the latest release with the current version of the +// application. If the tag name indicates a newer version, the updater downloads +// the release asset that matches the running application's operating system and +// architecture, replaces the current executable, and restarts the application. +// +// Usage: +// +// To use the updater, you should include it in your application's main package: +// +// import "github.com/H0llyW00dzZ/ChatGPT-Next-Web-Session-Exporter/updater" +// +// func main() { +// if err := updater.UpdateApplication(); err != nil { +// // Handle error +// } +// // Continue with application logic +// } +// +// The updater assumes that the GitHub repository's release assets follow a +// naming convention that includes the OS and architecture. It also assumes that +// the binary to be updated is named "myapp" and is located in the current working +// directory of the running application. +// +// Note that the updater package defines a constant `currentVersion` that must +// be updated to match the application's current version string before building +// a new release. This version string is used to compare against the tag name of +// the latest release on GitHub. +// +// The updater package is designed with simplicity in mind and does not handle +// complex update scenarios such as database migrations, configuration changes, +// or rollback of failed updates. It is recommended to test the update process +// thoroughly in a controlled environment before deploying it in a production setting. +// +// Security Considerations: +// +// The updater performs a direct binary replacement and restarts the application. +// Users should ensure that the GitHub repository and release assets are secure +// and that the release process includes steps to verify the integrity and +// authenticity of the binaries, such as signing the releases. +// +// # Additional Note: This Package Currently under development. +package updater + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "runtime" +) + +const ( + currentVersion = "1.3.3.7" + githubRepo = "H0llyW00dzZ/ChatGPT-Next-Web-Session-Exporter" +) + +// releaseInfo defines the structure for storing information about a GitHub release. +// It captures the tag name of the release and a slice of assets that are part of the release. +type releaseInfo struct { + TagName string `json:"tag_name"` // The name of the tag for the release. + Assets []struct { + Name string `json:"name"` // The name of the asset. + BrowserDownloadURL string `json:"browser_download_url"` // The URL for downloading the asset. + } `json:"assets"` // A list of assets available for the release. +} + +// getLatestRelease fetches the latest release information from the GitHub repository. +// It constructs a request to the GitHub API to retrieve the latest release and parses +// the response into a releaseInfo struct. +// +// Returns a pointer to a releaseInfo struct and nil error on success. +// On failure, it returns nil and an error indicating what went wrong. +func getLatestRelease() (*releaseInfo, error) { + resp, err := http.Get(fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", githubRepo)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("GitHub API response status: %s", resp.Status) + } + + var release releaseInfo + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return nil, err + } + + return &release, nil +} + +// UpdateApplication checks the GitHub repository for a newer release of the application. +// If a newer release is found, it downloads the corresponding binary for the current +// platform and architecture, replaces the current executable with the downloaded binary, +// and restarts the application. +// +// Returns nil if the application is up to date or the update is successfully applied. +// If an error occurs during the update process, it returns a non-nil error. +func UpdateApplication() error { + release, err := getLatestRelease() + if err != nil { + return fmt.Errorf("error fetching latest release: %w", err) + } + + if release.TagName == currentVersion { + fmt.Println("No update available.") + return nil + } + + fmt.Printf("Update available: %s\n", release.TagName) + fmt.Println("Downloading update...") + + // Find the asset that matches our platform + var assetURL string + for _, asset := range release.Assets { + if asset.Name == fmt.Sprintf("ChatGPT-Next-Web-Session-Exporter-%s-%s", runtime.GOOS, runtime.GOARCH) { + assetURL = asset.BrowserDownloadURL + break + } + } + + if assetURL == "" { + return fmt.Errorf("no binary for the current platform") + } + + // Download the new binary + resp, err := http.Get(assetURL) + if err != nil { + return fmt.Errorf("error downloading update: %w", err) + } + defer resp.Body.Close() + + // Create a new file with a temporary name + out, err := os.CreateTemp("", "ChatGPT-Next-Web-Session-Exporter-*") + if err != nil { + return fmt.Errorf("error creating temp file: %w", err) + } + defer out.Close() + + // Write the downloaded content to the file + _, err = io.Copy(out, resp.Body) + if err != nil { + return fmt.Errorf("error writing update to file: %w", err) + } + + // Close the file before renaming it + if err := out.Close(); err != nil { + return fmt.Errorf("error closing update file: %w", err) + } + + fmt.Println("Update downloaded. Applying update...") + + // Replace the current binary with the new one + if err := os.Rename(out.Name(), "ChatGPT-Next-Web-Session-Exporter"); err != nil { + return fmt.Errorf("error replacing binary: %w", err) + } + + fmt.Println("Update applied. Restarting application...") + + // Restart the application + cmd := exec.Command("ChatGPT-Next-Web-Session-Exporter") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { + return fmt.Errorf("error restarting application: %w", err) + } + + // Exit the current process + os.Exit(0) + + return nil +} From 650e6e38ea4932ca7f2081f7af2442a1a9654a20 Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Tue, 12 Dec 2023 03:25:35 +0700 Subject: [PATCH 3/4] [Golang] [Package] [Updater] Reduce gocyclo - [+] feat(updater): add functionality to download and update the application - [+] fix(updater): change temporary file name for downloaded asset - [+] feat(updater): add functionality to find matching asset for current platform - [+] feat(updater): add functionality to download asset from URL and write it to temporary file - [+] feat(updater): add functionality to apply update by replacing current binary with new one - [+] feat(updater): add functionality to restart the application after update is applied --- updater/app.go | 85 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 29 deletions(-) diff --git a/updater/app.go b/updater/app.go index dc036b2..12b0581 100644 --- a/updater/app.go +++ b/updater/app.go @@ -117,66 +117,93 @@ func UpdateApplication() error { return nil } + tempFileName, err := downloadAndUpdate(release) + if err != nil { + return err + } + + if err := applyUpdate(tempFileName); err != nil { + return err + } + + restartApplication() + return nil +} + +// downloadAndUpdate handles the downloading and updating of the application. +// It returns the name of the downloaded file or an error. +func downloadAndUpdate(release *releaseInfo) (string, error) { fmt.Printf("Update available: %s\n", release.TagName) fmt.Println("Downloading update...") - // Find the asset that matches our platform - var assetURL string - for _, asset := range release.Assets { - if asset.Name == fmt.Sprintf("ChatGPT-Next-Web-Session-Exporter-%s-%s", runtime.GOOS, runtime.GOARCH) { - assetURL = asset.BrowserDownloadURL - break - } + assetURL, err := findMatchingAsset(release) + if err != nil { + return "", err } - if assetURL == "" { - return fmt.Errorf("no binary for the current platform") + tempFileName, err := downloadAsset(assetURL) + if err != nil { + return "", err } - // Download the new binary + fmt.Println("Update downloaded.") + return tempFileName, nil +} + +// findMatchingAsset finds and returns the URL of the asset that matches the current platform. +func findMatchingAsset(release *releaseInfo) (string, error) { + for _, asset := range release.Assets { + if asset.Name == fmt.Sprintf("myapp-%s-%s", runtime.GOOS, runtime.GOARCH) { + return asset.BrowserDownloadURL, nil + } + } + return "", fmt.Errorf("no binary for the current platform") +} + +// downloadAsset downloads the asset from the given URL and writes it to a temporary file. +// It returns the name of the temporary file or an error. +func downloadAsset(assetURL string) (string, error) { resp, err := http.Get(assetURL) if err != nil { - return fmt.Errorf("error downloading update: %w", err) + return "", fmt.Errorf("error downloading update: %w", err) } defer resp.Body.Close() - // Create a new file with a temporary name - out, err := os.CreateTemp("", "ChatGPT-Next-Web-Session-Exporter-*") + out, err := os.CreateTemp("", "ChatGPT-Next-Web-Session-Exporter-update-*") if err != nil { - return fmt.Errorf("error creating temp file: %w", err) + return "", fmt.Errorf("error creating temp file: %w", err) } defer out.Close() - // Write the downloaded content to the file _, err = io.Copy(out, resp.Body) if err != nil { - return fmt.Errorf("error writing update to file: %w", err) - } - - // Close the file before renaming it - if err := out.Close(); err != nil { - return fmt.Errorf("error closing update file: %w", err) + return "", err } - fmt.Println("Update downloaded. Applying update...") + return out.Name(), nil +} +// applyUpdate applies the update by replacing the current binary with the new one. +// It takes the name of the temporary file containing the new binary as an argument. +func applyUpdate(tempFileName string) error { // Replace the current binary with the new one - if err := os.Rename(out.Name(), "ChatGPT-Next-Web-Session-Exporter"); err != nil { + if err := os.Rename(tempFileName, "ChatGPT-Next-Web-Session-Exporter"); err != nil { return fmt.Errorf("error replacing binary: %w", err) } + return nil +} +// restartApplication restarts the application. +func restartApplication() { fmt.Println("Update applied. Restarting application...") - - // Restart the application - cmd := exec.Command("ChatGPT-Next-Web-Session-Exporter") + cmd := exec.Command("myapp") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err != nil { - return fmt.Errorf("error restarting application: %w", err) + fmt.Fprintf(os.Stderr, "error restarting application: %v", err) + return } // Exit the current process os.Exit(0) - - return nil } From 416df2141a525359c1d61916903c565ccdf260c6 Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Tue, 12 Dec 2023 03:33:29 +0700 Subject: [PATCH 4/4] [Golang] [Package] [Updater] Fix Assets Name - [+] fix(app.go): update asset name format to match the current project name - [+] fix(app.go): update command to restart the application with the correct executable name --- updater/app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/updater/app.go b/updater/app.go index 12b0581..310a7bb 100644 --- a/updater/app.go +++ b/updater/app.go @@ -153,7 +153,7 @@ func downloadAndUpdate(release *releaseInfo) (string, error) { // findMatchingAsset finds and returns the URL of the asset that matches the current platform. func findMatchingAsset(release *releaseInfo) (string, error) { for _, asset := range release.Assets { - if asset.Name == fmt.Sprintf("myapp-%s-%s", runtime.GOOS, runtime.GOARCH) { + if asset.Name == fmt.Sprintf("ChatGPT-Next-Web-Session-Exporter-%s-%s", runtime.GOOS, runtime.GOARCH) { return asset.BrowserDownloadURL, nil } } @@ -196,7 +196,7 @@ func applyUpdate(tempFileName string) error { // restartApplication restarts the application. func restartApplication() { fmt.Println("Update applied. Restarting application...") - cmd := exec.Command("myapp") + cmd := exec.Command("ChatGPT-Next-Web-Session-Exporter") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err != nil {