subresource integrity

This commit is contained in:
Azareal 2020-07-31 15:33:29 +10:00
parent 26ad61057a
commit f502bf4f53
6 changed files with 63 additions and 64 deletions

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"crypto/sha256" "crypto/sha256"
"encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
@ -43,6 +44,7 @@ type SFile struct {
BrData []byte BrData []byte
Sha256 string Sha256 string
Sha256I string
OName string OName string
Pos int64 Pos int64
@ -306,9 +308,11 @@ func (l SFileList) JSTmplInit() error {
// Get a checksum for CSPs and cache busting // Get a checksum for CSPs and cache busting
hasher := sha256.New() hasher := sha256.New()
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) sum := hasher.Sum(nil)
checksum := hex.EncodeToString(sum)
integrity := base64.StdEncoding.EncodeToString(sum)
l.Set(l.Prefix+path, &SFile{data, gzipData, brData, checksum, l.Prefix + path + "?h=" + checksum, 0, int64(len(data)), strconv.Itoa(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), int64(len(brData)), strconv.Itoa(len(brData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) l.Set(l.Prefix+path, &SFile{data, gzipData, brData, checksum, integrity, l.Prefix + path + "?h=" + checksum, 0, int64(len(data)), strconv.Itoa(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), int64(len(brData)), strconv.Itoa(len(brData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLogf("Added the '%s' static file.", path) DebugLogf("Added the '%s' static file.", path)
return nil return nil
@ -336,7 +340,9 @@ func (l SFileList) Init() error {
// Get a checksum for CSPs and cache busting // Get a checksum for CSPs and cache busting
hasher := sha256.New() hasher := sha256.New()
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) sum := hasher.Sum(nil)
checksum := hex.EncodeToString(sum)
integrity := base64.StdEncoding.EncodeToString(sum)
// Avoid double-compressing images // Avoid double-compressing images
var gzipData, brData []byte var gzipData, brData []byte
@ -370,7 +376,7 @@ func (l SFileList) Init() error {
} }
} }
l.Set(l.Prefix+path, &SFile{data, gzipData, brData, checksum, l.Prefix + path + "?h=" + checksum, 0, int64(len(data)), strconv.Itoa(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), int64(len(brData)), strconv.Itoa(len(brData)), mimetype, f, f.ModTime().UTC().Format(http.TimeFormat)}) l.Set(l.Prefix+path, &SFile{data, gzipData, brData, checksum, integrity, l.Prefix + path + "?h=" + checksum, 0, int64(len(data)), strconv.Itoa(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), int64(len(brData)), strconv.Itoa(len(brData)), mimetype, f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLogf("Added the '%s' static file.", path) DebugLogf("Added the '%s' static file.", path)
return nil return nil
@ -425,9 +431,11 @@ func (l SFileList) Add(path, prefix string) error {
// Get a checksum for CSPs and cache busting // Get a checksum for CSPs and cache busting
hasher := sha256.New() hasher := sha256.New()
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) sum := hasher.Sum(nil)
checksum := hex.EncodeToString(sum)
integrity := base64.StdEncoding.EncodeToString(sum)
l.Set(l.Prefix+path, &SFile{data, gzipData, brData, checksum, l.Prefix + path + "?h=" + checksum, 0, int64(len(data)), strconv.Itoa(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), int64(len(brData)), strconv.Itoa(len(brData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) l.Set(l.Prefix+path, &SFile{data, gzipData, brData, checksum, integrity, l.Prefix + path + "?h=" + checksum, 0, int64(len(data)), strconv.Itoa(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), int64(len(brData)), strconv.Itoa(len(brData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLogf("Added the '%s' static file", path) DebugLogf("Added the '%s' static file", path)
return nil return nil

View File

@ -21,11 +21,11 @@ type Header struct {
Title string Title string
//Title []byte // Experimenting with []byte for increased efficiency, let's avoid converting too many things to []byte, as it involves a lot of extra boilerplate //Title []byte // Experimenting with []byte for increased efficiency, let's avoid converting too many things to []byte, as it involves a lot of extra boilerplate
NoticeList []string NoticeList []string
Scripts []string Scripts []HScript
PreScriptsAsync []string PreScriptsAsync []HScript
ScriptsAsync []string ScriptsAsync []HScript
//Preload []string //Preload []string
Stylesheets []string Stylesheets []HScript
Widgets PageWidgets Widgets PageWidgets
Site *site Site *site
Settings SettingMap Settings SettingMap
@ -52,38 +52,33 @@ type Header struct {
ExtData ExtData ExtData ExtData
} }
func (h *Header) AddScript(name string) { type HScript struct {
Name string
Hash string
}
func (h *Header) getScript(name string) HScript {
if name[0] == '/' && name[1] == '/' { if name[0] == '/' && name[1] == '/' {
} else { } else {
file, ok := StaticFiles.GetShort(name) file, ok := StaticFiles.GetShort(name)
if ok { if ok {
name = file.OName return HScript{file.OName,file.Sha256I}
} }
} }
return HScript{name,""}
}
func (h *Header) AddScript(name string) {
//log.Print("name:", name) //log.Print("name:", name)
h.Scripts = append(h.Scripts, name) h.Scripts = append(h.Scripts, h.getScript(name))
} }
func (h *Header) AddPreScriptAsync(name string) { func (h *Header) AddPreScriptAsync(name string) {
if name[0] == '/' && name[1] == '/' { h.PreScriptsAsync = append(h.PreScriptsAsync, h.getScript(name))
} else {
file, ok := StaticFiles.GetShort(name)
if ok {
name = file.OName
}
}
h.PreScriptsAsync = append(h.PreScriptsAsync, name)
} }
func (h *Header) AddScriptAsync(name string) { func (h *Header) AddScriptAsync(name string) {
if name[0] == '/' && name[1] == '/' { h.ScriptsAsync = append(h.ScriptsAsync, h.getScript(name))
} else {
file, ok := StaticFiles.GetShort(name)
if ok {
name = file.OName
}
}
h.ScriptsAsync = append(h.ScriptsAsync, name)
} }
/*func (h *Header) Preload(name string) { /*func (h *Header) Preload(name string) {
@ -91,14 +86,7 @@ func (h *Header) AddScriptAsync(name string) {
}*/ }*/
func (h *Header) AddSheet(name string) { func (h *Header) AddSheet(name string) {
if name[0] == '/' && name[1] == '/' { h.Stylesheets = append(h.Stylesheets, h.getScript(name))
} else {
file, ok := StaticFiles.GetShort(name)
if ok {
name = file.OName
}
}
h.Stylesheets = append(h.Stylesheets, name)
} }
// ! Experimental // ! Experimental

View File

@ -106,31 +106,31 @@ func tmplInitUsers() (*User, *User, *User) {
return &u, &u2, &u3 return &u, &u2, &u3
} }
func tmplInitHeaders(user, user2, user3 *User) (*Header, *Header, *Header) { func tmplInitHeaders(u, u2, u3 *User) (*Header, *Header, *Header) {
header := &Header{ header := &Header{
Site: Site, Site: Site,
Settings: SettingBox.Load().(SettingMap), Settings: SettingBox.Load().(SettingMap),
Themes: Themes, Themes: Themes,
Theme: Themes[DefaultThemeBox.Load().(string)], Theme: Themes[DefaultThemeBox.Load().(string)],
CurrentUser: user, CurrentUser: u,
NoticeList: []string{"test"}, NoticeList: []string{"test"},
Stylesheets: []string{"panel.css"}, Stylesheets: []HScript{HScript{"panel.css",""}},
Scripts: []string{"whatever.js"}, Scripts: []HScript{HScript{"whatever.js",""}},
PreScriptsAsync: []string{"whatever.js"}, PreScriptsAsync: []HScript{HScript{"whatever.js",""}},
ScriptsAsync: []string{"whatever.js"}, ScriptsAsync: []HScript{HScript{"whatever.js",""}},
Widgets: PageWidgets{ Widgets: PageWidgets{
LeftSidebar: template.HTML("lalala"), LeftSidebar: template.HTML("lalala"),
}, },
} }
buildHeader := func(user *User) *Header { buildHeader := func(u *User) *Header {
head := &Header{Site: Site} head := &Header{Site: Site}
*head = *header *head = *header
head.CurrentUser = user head.CurrentUser = u
return head return head
} }
return header, buildHeader(user2), buildHeader(user3) return header, buildHeader(u2), buildHeader(u3)
} }
type TmplLoggedin struct { type TmplLoggedin struct {

View File

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"database/sql" "database/sql"
"encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
htmpl "html/template" htmpl "html/template"
@ -282,9 +283,11 @@ func (t *Theme) AddThemeStaticFiles() error {
// Get a checksum for CSPs and cache busting // Get a checksum for CSPs and cache busting
hasher := sha256.New() hasher := sha256.New()
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) sum := hasher.Sum(nil)
checksum := hex.EncodeToString(sum)
integrity := base64.StdEncoding.EncodeToString(sum)
StaticFiles.Set(StaticFiles.Prefix+t.Name+path, &SFile{data, gzipData, brData, checksum, StaticFiles.Prefix + t.Name + path + "?h=" + checksum, 0, int64(len(data)), strconv.Itoa(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), int64(len(brData)), strconv.Itoa(len(brData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) StaticFiles.Set(StaticFiles.Prefix+t.Name+path, &SFile{data, gzipData, brData, checksum, integrity, StaticFiles.Prefix + t.Name + path + "?h=" + checksum, 0, int64(len(data)), strconv.Itoa(len(data)), int64(len(gzipData)), strconv.Itoa(len(gzipData)), int64(len(brData)), strconv.Itoa(len(brData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLog("Added the '/" + t.Name + path + "' static file for theme " + t.Name + ".") DebugLog("Added the '/" + t.Name + path + "' static file for theme " + t.Name + ".")
return nil return nil

View File

@ -44,14 +44,14 @@ func doPush(w http.ResponseWriter, h *c.Header) {
sb.Reset() sb.Reset()
}*/ }*/
sb.Grow((slen1 * (len(h.Scripts) + len(h.ScriptsAsync))) + ((slen2 + 7) * len(h.Stylesheets))) sb.Grow((slen1 * (len(h.Scripts) + len(h.ScriptsAsync))) + ((slen2 + 7) * len(h.Stylesheets)))
push := func(in []string) { push := func(in []c.HScript) {
for i, path := range in { for i, s := range in {
if i != 0 { if i != 0 {
sb.WriteString(",</s/") sb.WriteString(",</s/")
} else { } else {
sb.WriteString("</s/") sb.WriteString("</s/")
} }
sb.WriteString(path) sb.WriteString(s.Name)
sb.WriteString(">;rel=preload;as=script") sb.WriteString(">;rel=preload;as=script")
} }
} }
@ -60,13 +60,13 @@ func doPush(w http.ResponseWriter, h *c.Header) {
push(h.ScriptsAsync) push(h.ScriptsAsync)
if len(h.Stylesheets) > 0 { if len(h.Stylesheets) > 0 {
for i, path := range h.Stylesheets { for i, s := range h.Stylesheets {
if i != 0 { if i != 0 {
sb.WriteString(",</s/") sb.WriteString(",</s/")
} else { } else {
sb.WriteString("</s/") sb.WriteString("</s/")
} }
sb.WriteString(path) sb.WriteString(s.Name)
sb.WriteString(">;rel=preload;as=style") sb.WriteString(">;rel=preload;as=style")
} }
} }
@ -100,11 +100,11 @@ func doPush(w http.ResponseWriter, h *c.Header) {
sb.Reset() sb.Reset()
}*/ }*/
sb.Grow(6 * (len(h.Scripts) + len(h.ScriptsAsync) + len(h.Stylesheets))) sb.Grow(6 * (len(h.Scripts) + len(h.ScriptsAsync) + len(h.Stylesheets)))
push := func(in []string) { push := func(in []c.HScript) {
for _, path := range in { for _, s := range in {
//fmt.Println("pushing /s/" + path) //fmt.Println("pushing /s/" + path)
sb.WriteString("/s/") sb.WriteString("/s/")
sb.WriteString(path) sb.WriteString(s.Name)
err := pusher.Push(sb.String(), nil) err := pusher.Push(sb.String(), nil)
if err != nil { if err != nil {
break break

View File

@ -3,16 +3,16 @@
<head> <head>
<title>{{.Title}} | {{.Header.Site.Name}}</title> <title>{{.Title}} | {{.Header.Site.Name}}</title>
{{range .Header.Stylesheets}} {{range .Header.Stylesheets}}
<link href="{{.}}"rel="stylesheet"type="text/css">{{end}} <link href="{{.Name}}"rel="stylesheet"type="text/css"{{if .Hash}}integrity="sha256-{{.Hash}}"{{end}}>{{end}}
{{range .Header.PreScriptsAsync}} {{range .Header.PreScriptsAsync}}
<script async src="{{.}}"></script>{{end}} <script async src="{{.Name}}"{{if .Hash}}integrity="sha256-{{.Hash}}"{{end}}></script>{{end}}
{{if .CurrentUser.Loggedin}}<meta property="x-mem"content="1">{{end}} {{if .CurrentUser.Loggedin}}<meta property="x-mem"content="1">{{end}}
<script src="{{res "init.js"}}"></script> <script src="{{res "init.js"}}"></script>
{{range .Header.ScriptsAsync}} {{range .Header.ScriptsAsync}}
<script async src="{{.}}"></script>{{end}} <script async src="{{.Name}}"{{if .Hash}}integrity="sha256-{{.Hash}}"{{end}}></script>{{end}}
<script src="{{res "jquery-3.1.1.min.js"}}"></script> <script src="{{res "jquery-3.1.1.min.js"}}"></script>
{{range .Header.Scripts}} {{range .Header.Scripts}}
<script src="{{.}}"></script>{{end}} <script src="{{.Name}}"{{if .Hash}}integrity="sha256-{{.Hash}}"{{end}}></script>{{end}}
<meta name="viewport"content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"> <meta name="viewport"content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
{{if .Header.MetaDesc}}<meta name="description"content="{{.Header.MetaDesc}}">{{end}} {{if .Header.MetaDesc}}<meta name="description"content="{{.Header.MetaDesc}}">{{end}}
{{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}} {{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}}