From 4698a97974ae82e7bd8592828c58294b222a58ff Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Thu, 30 May 2024 13:54:29 +0200 Subject: feat: enable sub-resource integrity for assets --- frontend/assets.go | 95 +++++++++++++++++++++++++++++++++ frontend/templates/blocks/search.gotmpl | 8 ++- frontend/templates/index.gotmpl | 9 +++- internal/server/mux.go | 4 ++ 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 frontend/assets.go diff --git a/frontend/assets.go b/frontend/assets.go new file mode 100644 index 0000000..a6a5e79 --- /dev/null +++ b/frontend/assets.go @@ -0,0 +1,95 @@ +package frontend + +import ( + "crypto/sha256" + "encoding/base64" + "io" + "io/fs" + "net/url" + + "github.com/pkg/errors" +) + +var Assets = AssetCollection{ + Scripts: make(map[string]Asset), + Stylesheets: make(map[string]Asset), +} + +type Asset struct { + URL string + Base64SHA256 string +} + +type AssetCollection struct { + Scripts map[string]Asset + Stylesheets map[string]Asset +} + +func hashFile(filename string) ([]byte, error) { + file, err := Files.Open(filename) + if err != nil { + return nil, errors.WithMessagef(err, "could not open file %s", filename) + } + sum := sha256.New() + defer file.Close() + if _, err := io.Copy(sum, file); err != nil { + return nil, errors.WithMessagef(err, "could not hash file %s", filename) + } + + return sum.Sum(nil), nil +} + +func newAsset(filename string, hash []byte) Asset { + u, err := url.JoinPath("/", filename) + if err != nil { + panic(err) + } + + return Asset{ + URL: u, + Base64SHA256: base64.StdEncoding.EncodeToString(hash), + } +} + +func hashScripts() error { + scripts, err := fs.Glob(Files, "static/**.js") + if err != nil { + return errors.WithMessage(err, "could not glob files") + } + for _, filename := range scripts { + hash, err := hashFile(filename) + if err != nil { + return err + } + Assets.Scripts[filename] = newAsset(filename, hash) + } + + return nil +} + +func hashStyles() error { + styles, err := fs.Glob(Files, "static/**.css") + if err != nil { + return errors.WithMessage(err, "could not glob files") + } + for _, filename := range styles { + hash, err := hashFile(filename) + if err != nil { + return err + } + Assets.Stylesheets[filename] = newAsset(filename, hash) + } + + return nil +} + +func init() { + err := hashScripts() + if err != nil { + panic(err) + } + err = hashStyles() + if err != nil { + panic(err) + } +} diff --git a/frontend/templates/blocks/search.gotmpl b/frontend/templates/blocks/search.gotmpl index 1be001a..9320376 100644 --- a/frontend/templates/blocks/search.gotmpl +++ b/frontend/templates/blocks/search.gotmpl @@ -22,5 +22,11 @@ {{- end }} {{- define "head" }} - + {{- with (index .Assets.Scripts "static/search.js") }} + + {{- end }} {{- end }} diff --git a/frontend/templates/index.gotmpl b/frontend/templates/index.gotmpl index ee37c0f..7732dc8 100644 --- a/frontend/templates/index.gotmpl +++ b/frontend/templates/index.gotmpl @@ -4,8 +4,13 @@