119 lines
2.3 KiB
Go
119 lines
2.3 KiB
Go
// Copyright 2023 wanderer <a_mirre at utb dot cz>
|
|
// 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 <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
|
|
}
|