pcmt/modules/funcmap/integrity.go
leo e8ac4e39ce
All checks were successful
continuous-integration/drone/push Build is passing
modules/funcmap: add funcs to calculate SRI hashes
* correctly handle LiveMode resp. whether or not to set/read embeds
2023-05-12 00:11:23 +02:00

116 lines
2.2 KiB
Go

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 <algo>-<digest>.
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
}