// Copyright 2023 wanderer // SPDX-License-Identifier: AGPL-3.0-only package funcmap import ( "crypto/sha256" "crypto/sha512" "encoding/base64" "fmt" "hash" "io" "io/fs" "os" "path/filepath" ) // resIntegrity type implements several methods that allow getting a formatted // hash digest for the purpose of checking integrity of a web resource using // the algo specified. type resIntegrity struct { algo string } const defaultHashAlgo = "sha256" // New returns a new resource integrity (resIntegrity) struct for the // particular algorithm. Supported argument values are sha256, sha384 and // sha512. func New(algo string) *resIntegrity { //nolint:revive return &resIntegrity{algo: algo} } // Integrity reads the resource at path, optionally from an fs.FS if parameter // live is false (i.e. we're checking embedded resources), and returns a // pointer to the integrity string in the form -. func (i *resIntegrity) Integrity(path string, live bool) (*string, error) { r := "" // e is an fs.FS created from embed.FS for convenience. var e fs.FS if embedAssets != nil { e = *embedAssets } h, err := newHash(i.algo) if err != nil { return &r, err } if live { path = filepath.Join("assets", "public", path) f, err := os.Open(path) if err != nil { return &r, err } defer f.Close() _, err = io.Copy(h, f) if err != nil { return &r, err } } else { path = filepath.Join("assets", "public", path) f, err := e.Open(path) if err != nil { return &r, err } defer f.Close() _, err = io.Copy(h, f) if err != nil { return &r, err } } d, err := digest(h) if err != nil { return &r, err } r = integrity(i.algo, d) return &r, nil } func newHash(algo string) (hash.Hash, error) { switch algo { case "sha256": return sha256.New(), nil case "sha384": return sha512.New384(), nil case "sha512": return sha512.New(), nil default: return nil, fmt.Errorf("unsupported hash algorithm: %q, use either sha256, sha384 or sha512", algo) } } func integrity(algo string, sum []byte) string { if algo == "" { algo = defaultHashAlgo } encoded := base64.StdEncoding.EncodeToString(sum) return algo + "-" + encoded } func digest(h hash.Hash) ([]byte, error) { sum := h.Sum(nil) return sum, nil }