modules/funcmap: add funcs to calculate SRI hashes
All checks were successful
continuous-integration/drone/push Build is passing

* correctly handle LiveMode resp. whether or not to set/read embeds
This commit is contained in:
leo 2023-05-12 00:11:23 +02:00
parent b77c2fe941
commit e8ac4e39ce
Signed by: wanderer
SSH Key Fingerprint: SHA256:Dp8+iwKHSlrMEHzE3bJnPng70I7LEsa3IJXRH/U+idQ
3 changed files with 150 additions and 3 deletions

@ -2,9 +2,11 @@ package app
import (
"embed"
"io/fs"
"git.dotya.ml/mirre-mt/pcmt/app/settings"
"git.dotya.ml/mirre-mt/pcmt/ent"
modfuncmap "git.dotya.ml/mirre-mt/pcmt/modules/funcmap"
"git.dotya.ml/mirre-mt/pcmt/slogging"
"github.com/labstack/echo/v4"
gommonlog "github.com/labstack/gommon/log"
@ -111,9 +113,17 @@ func (a *App) SetSettings(s *settings.Settings) {
// SetEmbeds saves the embedded files to application state.
func (a *App) SetEmbeds(templates, assets embed.FS) {
a.Logger().Debug("saving embeds in app struct")
a.embeds.templates = templates
a.embeds.assets = assets
if !a.setting.IsLive() {
a.Logger().Debug("saving embeds in the app struct")
a.embeds.templates = templates
a.embeds.assets = assets
var fsfs fs.FS = &assets
// save pointer to assets to funcmap for integrity calculations.
modfuncmap.SetEmbeds(&fsfs)
}
}
// setDevel puts the app in devel mode, which loads a browser-sync script in

@ -2,10 +2,16 @@ package funcmap
import (
"html/template"
"io/fs"
modbluemonday "git.dotya.ml/mirre-mt/pcmt/modules/bluemonday"
)
var (
embedAssets *fs.FS
isLive = true
)
func FuncMap() template.FuncMap {
return template.FuncMap{
"ifIE": func() template.HTML { return template.HTML("<!--[if IE]>") },
@ -18,9 +24,25 @@ func FuncMap() template.FuncMap {
"pageIs": func(want, got string) bool {
return want == got
},
"sha256": func(path string) string {
t := New("sha256")
r, err := t.Integrity(path, isLive)
if err != nil {
return ""
}
return *r
},
}
}
// SetEmbeds saves the pointer to embedded assets (and toggles the isLive var).
func SetEmbeds(embeds *fs.FS) {
embedAssets = embeds
isLive = false
}
// TODO: mimic https://github.com/drone/funcmap/blob/master/funcmap.go
func setFuncMap(t *template.Template) { //nolint:unused
t.Funcs(FuncMap())

@ -0,0 +1,115 @@
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
}