package selfupdate import ( "crypto/sha256" "encoding/hex" "fmt" "io" "net/http" "os" "strings" ) func download(client *http.Client, url, dst string) error { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return err } req.Header.Set("User-Agent", "eeco-update/1") resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("http %d", resp.StatusCode) } tmp, err := os.CreateTemp(parentDir(dst), ".eeco-dl-*") if err != nil { return err } tmpName := tmp.Name() if _, err := io.Copy(tmp, resp.Body); err != nil { tmp.Close() os.Remove(tmpName) return err } if err := tmp.Close(); err != nil { os.Remove(tmpName) return err } return os.Rename(tmpName, dst) } func parentDir(path string) string { i := strings.LastIndexAny(path, `/\`) if i < 0 { return "." } return path[:i] } func sha256File(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close() h := sha256.New() if _, err := io.Copy(h, f); err != nil { return "", err } return hex.EncodeToString(h.Sum(nil)), nil } // checksumFor reads sumsPath (the `shasum -a 256` SHA256SUMS file) and // returns the recorded hash for archiveBasename. Each line is: // // <64-hex> // // (two spaces between, per shasum's default text mode). func checksumFor(sumsPath, archiveBasename string) (string, error) { b, err := os.ReadFile(sumsPath) if err != nil { return "", err } for _, raw := range strings.Split(string(b), "\n") { line := strings.TrimSpace(raw) if line == "" { continue } fields := strings.Fields(line) if len(fields) < 2 { continue } hash := fields[0] name := strings.TrimPrefix(fields[len(fields)-1], "*") if name == archiveBasename { if len(hash) != 64 { return "", fmt.Errorf("SHA256SUMS: bad hash for %s", archiveBasename) } return strings.ToLower(hash), nil } } return "", fmt.Errorf("SHA256SUMS: no entry for %s", archiveBasename) }