package rpm import ( "context" "crypto/sha256" "encoding/hex" "fmt" "io" "net/http" "os" "path/filepath" ) func verifyChecksum(data []byte, expected string) error { sum := sha256.Sum256(data) got := hex.EncodeToString(sum[:]) if got != expected { return fmt.Errorf("checksum mismatch: got %s, want %s", got, expected) } return nil } // DownloadAndVerify fetches url into destPath atomically (temp file + rename), // verifies the SHA256 checksum if checksumType is "sha256", and returns bytes written. func DownloadAndVerify(ctx context.Context, httpClient *http.Client, url, destPath, checksumType, expectedChecksum string) (int64, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return 0, err } resp, err := httpClient.Do(req) if err != nil { return 0, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return 0, fmt.Errorf("HTTP %d for %s", resp.StatusCode, url) } tmpPath := destPath + ".tmp" if err := os.MkdirAll(filepath.Dir(tmpPath), 0o755); err != nil { return 0, err } f, err := os.Create(tmpPath) if err != nil { return 0, err } h := sha256.New() n, err := io.Copy(io.MultiWriter(f, h), resp.Body) f.Close() if err != nil { os.Remove(tmpPath) return 0, err } if checksumType == "sha256" && expectedChecksum != "" { got := hex.EncodeToString(h.Sum(nil)) if got != expectedChecksum { os.Remove(tmpPath) return 0, fmt.Errorf("checksum mismatch for %s: got %s, want %s", url, got, expectedChecksum) } } if err := os.Rename(tmpPath, destPath); err != nil { os.Remove(tmpPath) return 0, err } return n, nil }