wip allow for more cdns

add res template function
add ExtraCSPOrigins config setting
add StaticResBase config setting
skip flush directives
This commit is contained in:
Azareal 2020-07-30 18:10:29 +10:00
parent d4fd85f75c
commit 43d72e6f3b
9 changed files with 136 additions and 65 deletions

View File

@ -10,6 +10,7 @@ import (
"io/ioutil" "io/ioutil"
"mime" "mime"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -23,12 +24,14 @@ import (
//type SFileList map[string]*SFile //type SFileList map[string]*SFile
//type SFileListShort map[string]*SFile //type SFileListShort map[string]*SFile
var StaticFiles = SFileList{make(map[string]*SFile),make(map[string]*SFile)} var StaticFiles = SFileList{"/s/", make(map[string]*SFile), make(map[string]*SFile)}
//var StaticFilesShort SFileList = make(map[string]*SFile) //var StaticFilesShort SFileList = make(map[string]*SFile)
var staticFileMutex sync.RWMutex var staticFileMutex sync.RWMutex
// ? Is it efficient to have two maps for this? // ? Is it efficient to have two maps for this?
type SFileList struct { type SFileList struct {
Prefix string
Long map[string]*SFile Long map[string]*SFile
Short map[string]*SFile Short map[string]*SFile
} }
@ -305,7 +308,7 @@ func (l SFileList) JSTmplInit() error {
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) checksum := hex.EncodeToString(hasher.Sum(nil))
l.Set("/s/"+path, &SFile{data, gzipData, brData, checksum, 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, 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
@ -367,7 +370,7 @@ func (l SFileList) Init() error {
} }
} }
l.Set("/s/"+path, &SFile{data, gzipData, brData, checksum, 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, 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
@ -424,7 +427,7 @@ func (l SFileList) Add(path, prefix string) error {
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) checksum := hex.EncodeToString(hasher.Sum(nil))
l.Set("/s/"+path, &SFile{data, gzipData, brData, checksum, 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, 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
@ -448,8 +451,13 @@ func (l SFileList) GetShort(name string) (file *SFile, exists bool) {
func (l SFileList) Set(name string, data *SFile) { func (l SFileList) Set(name string, data *SFile) {
staticFileMutex.Lock() staticFileMutex.Lock()
defer staticFileMutex.Unlock() defer staticFileMutex.Unlock()
l.Long[name] = data // TODO: Propagate errors back up
l.Short[strings.TrimPrefix("/s/",name)] = data uurl, err := url.Parse(name)
if err != nil {
return
}
l.Long[uurl.Path] = data
l.Short[strings.TrimPrefix(strings.TrimPrefix(name, l.Prefix), "/")] = data
} }
var gzipBestCompress sync.Pool var gzipBestCompress sync.Pool

View File

@ -2,11 +2,13 @@ package common
import ( import (
"encoding/json" "encoding/json"
"errors"
"io/ioutil" "io/ioutil"
"log" "log"
"net/url"
"strconv" "strconv"
"strings" "strings"
"github.com/pkg/errors"
) )
// Site holds the basic settings which should be tweaked when setting up a site, we might move them to the settings table at some point // Site holds the basic settings which should be tweaked when setting up a site, we might move them to the settings table at some point
@ -119,6 +121,9 @@ type config struct {
RefNoRef bool RefNoRef bool
NoEmbed bool NoEmbed bool
ExtraCSPOrigins string
StaticResBase string // /s/
Noavatar string // ? - Move this into the settings table? Noavatar string // ? - Move this into the settings table?
ItemsPerPage int // ? - Move this into the settings table? ItemsPerPage int // ? - Move this into the settings table?
MaxTopicTitleLength int MaxTopicTitleLength int
@ -238,6 +243,21 @@ func ProcessConfig() (err error) {
} }
Site.MaxRequestSize = Config.MaxRequestSize Site.MaxRequestSize = Config.MaxRequestSize
local := func(h string) bool {
return h == "localhost" || h == "127.0.0.1" || h == "::1" || h == Site.URL
}
uurl, err := url.Parse(Config.StaticResBase)
if err != nil {
return errors.Wrap(err, "failed to parse Config.StaticResBase: ")
}
host := uurl.Hostname()
if !local(host) {
Config.ExtraCSPOrigins += " " + host
}
if Config.StaticResBase != "" {
StaticFiles.Prefix = Config.StaticResBase
}
if Config.PostIPCutoff == 0 { if Config.PostIPCutoff == 0 {
Config.PostIPCutoff = 120 // Default cutoff Config.PostIPCutoff = 120 // Default cutoff
} }

View File

@ -878,6 +878,17 @@ func initDefaultTmplFuncMap() {
return nil return nil
} }
fmap["res"] = func(nameInt interface{}) interface{} {
n := nameInt.(string)
if n[0] == '/' && n[1] == '/' {
} else {
if f, ok := StaticFiles.GetShort(n); ok {
n = f.OName
}
}
return n
}
DefaultTemplateFuncMap = fmap DefaultTemplateFuncMap = fmap
} }

View File

@ -119,6 +119,7 @@ func NewCTemplateSet(in string) *CTemplateSet {
"js": true, "js": true,
"index": true, "index": true,
"flush": true, "flush": true,
"res": true,
}, },
logger: log.New(f, "", log.LstdFlags), logger: log.New(f, "", log.LstdFlags),
loggerf: f, loggerf: f,
@ -683,8 +684,9 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
for _, cmd := range node.Pipe.Cmds { for _, cmd := range node.Pipe.Cmds {
c.detail("If Node Bit:", cmd) c.detail("If Node Bit:", cmd)
c.detail("Bit Type:", reflect.ValueOf(cmd).Type().Name()) c.detail("Bit Type:", reflect.ValueOf(cmd).Type().Name())
expr += c.compileExprSwitch(con, cmd) exprStep := c.compileExprSwitch(con, cmd)
c.detail("Expression Step:", c.compileExprSwitch(con, cmd)) expr += exprStep
c.detail("Expression Step:", exprStep)
} }
c.detail("Expression:", expr) c.detail("Expression:", expr)
@ -1031,8 +1033,7 @@ func (c *CTemplateSet) compileExprSwitch(con CContext, node *parse.CommandNode)
case *parse.NilNode: case *parse.NilNode:
panic("Nil is not a command x.x") panic("Nil is not a command x.x")
case *parse.PipeNode: case *parse.PipeNode:
c.detail("Pipe Node!") c.detail("Pipe Node:", n)
c.detail(n)
c.detail("Node Args:", node.Args) c.detail("Node Args:", node.Args)
out += c.compileIdentSwitchN(con, node) out += c.compileIdentSwitchN(con, node)
default: default:
@ -1043,9 +1044,9 @@ func (c *CTemplateSet) compileExprSwitch(con CContext, node *parse.CommandNode)
} }
func (c *CTemplateSet) unknownNode(n parse.Node) { func (c *CTemplateSet) unknownNode(n parse.Node) {
elem := reflect.ValueOf(n).Elem() el := reflect.ValueOf(n).Elem()
c.logger.Println("Unknown Kind:", elem.Kind()) c.logger.Println("Unknown Kind:", el.Kind())
c.logger.Println("Unknown Type:", elem.Type().Name()) c.logger.Println("Unknown Type:", el.Type().Name())
panic("I don't know what node this is! Grr...") panic("I don't know what node this is! Grr...")
} }
@ -1114,7 +1115,7 @@ func (c *CTemplateSet) compareJoin(con CContext, pos int, node *parse.CommandNod
return pos, out return pos, out
} }
func (c *CTemplateSet) compileIdentSwitch(con CContext, node *parse.CommandNode) (out string, val reflect.Value, literal, notident bool) { func (c *CTemplateSet) compileIdentSwitch(con CContext, node *parse.CommandNode) (out string, val reflect.Value, literal, notIdent bool) {
c.dumpCall("compileIdentSwitch", con, node) c.dumpCall("compileIdentSwitch", con, node)
litString := func(inner string, bytes bool) { litString := func(inner string, bytes bool) {
if !bytes { if !bytes {
@ -1135,9 +1136,16 @@ ArgLoop:
var rout string var rout string
pos, rout = c.compareJoin(con, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this pos, rout = c.compareJoin(con, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this
out += rout out += rout
case "le", "lt", "gt", "ge", "eq", "ne": case "le", "lt", "gt", "ge":
out += c.compareFunc(con, pos, node, c.funcMap[id.String()].(string)) out += c.compareFunc(con, pos, node, c.funcMap[id.String()].(string))
break ArgLoop break ArgLoop
case "eq", "ne":
o := c.compareFunc(con, pos, node, c.funcMap[id.String()].(string))
if out == "!" {
o = "(" + o + ")"
}
out += o
break ArgLoop
case "add", "subtract", "divide", "multiply": case "add", "subtract", "divide", "multiply":
rout, rval := c.simpleMath(con, pos, node, c.funcMap[id.String()].(string)) rout, rval := c.simpleMath(con, pos, node, c.funcMap[id.String()].(string))
out += rout out += rout
@ -1236,7 +1244,7 @@ ArgLoop:
// ! Slightly crude but it does the job // ! Slightly crude but it does the job
leftParam := strings.Replace(leftOp, "\"", "", -1) leftParam := strings.Replace(leftOp, "\"", "", -1)
c.langIndexToName = append(c.langIndexToName, leftParam) c.langIndexToName = append(c.langIndexToName, leftParam)
notident = true notIdent = true
con.PushPhrase(len(c.langIndexToName) - 1) con.PushPhrase(len(c.langIndexToName) - 1)
} else { } else {
leftParam := leftOp leftParam := leftOp
@ -1386,17 +1394,37 @@ ArgLoop:
// TODO: Refactor this // TODO: Refactor this
// TODO: Call the template function directly rather than going through RunThemeTemplate to eliminate a round of indirection? // TODO: Call the template function directly rather than going through RunThemeTemplate to eliminate a round of indirection?
out = "{\nerr := " + headParam + ".Theme.RunTmpl(" + nameParam + "," + pageParam + ",w)\n" out = "{\ne := " + headParam + ".Theme.RunTmpl(" + nameParam + "," + pageParam + ",w)\n"
out += "if err != nil {\nreturn err\n}\n}\n" out += "if e != nil {\nreturn e\n}\n}\n"
literal = true literal = true
break ArgLoop break ArgLoop
case "flush": case "flush":
if c.lang == "js" { literal = true
break ArgLoop
/*if c.lang == "js" {
continue continue
} }
out = "if fl, ok := iw.(http.Flusher); ok {\nfl.Flush()\n}\n" out = "if fl, ok := iw.(http.Flusher); ok {\nfl.Flush()\n}\n"
literal = true literal = true
c.importMap["net/http"] = "net/http" c.importMap["net/http"] = "net/http"
break ArgLoop*/
// TODO: Test this
case "res":
leftOp := node.Args[pos+1].String()
if len(leftOp) == 0 {
panic("The leftoperand for function res cannot be left blank")
}
leftParam, _ := c.compileIfVarSub(con, leftOp)
literal = true
if leftParam[0] == '"' {
if leftParam[1] == '/' && leftParam[2] == '/' {
litString(leftParam, false)
break ArgLoop
}
out = "{n := " + leftParam + "\nif f, ok := c.StaticFiles.GetShort(n); ok {\nw.Write(StringToBytes(f.OName))\n} else {\nw.Write(StringToBytes(n))\n}}\n"
break ArgLoop
}
out = "{n := " + leftParam + "\nif n[0] == '/' && n[1] == '/' {\n} else {\nif f, ok := c.StaticFiles.GetShort(n); ok {\nn = f.OName\n}\nw.Write(StringToBytes(n))\n}\n"
break ArgLoop break ArgLoop
default: default:
c.detail("Variable!") c.detail("Variable!")
@ -1410,7 +1438,7 @@ ArgLoop:
} }
} }
c.retCall("compileIdentSwitch", out, val, literal) c.retCall("compileIdentSwitch", out, val, literal)
return out, val, literal, notident return out, val, literal, notIdent
} }
func (c *CTemplateSet) compileReflectSwitch(con CContext, node *parse.CommandNode) (out string, outVal reflect.Value) { func (c *CTemplateSet) compileReflectSwitch(con CContext, node *parse.CommandNode) (out string, outVal reflect.Value) {

View File

@ -284,7 +284,7 @@ func (t *Theme) AddThemeStaticFiles() error {
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) checksum := hex.EncodeToString(hasher.Sum(nil))
StaticFiles.Set("/s/"+t.Name+path, &SFile{data, gzipData, brData, checksum, 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, 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

@ -126,6 +126,10 @@ RefNoRef - This switch makes it so that if a user clicks on a link, then the inc
NoEmbed - Don't expand links into videos or images. Default: false NoEmbed - Don't expand links into videos or images. Default: false
ExtraCSPOrigins - Extra origins which may want whitelisted in the default Content Security Policy.
StaticResBase - The default prefix for static resource files. May be a path or an external domain like a CDN domain. Default: /s/
NoAvatar - The default avatar to use for users when they don't have their own. The default for this may change in the near future to better utilise HTTP/2. Example: https://api.adorable.io/avatars/{width}/{id}.png NoAvatar - The default avatar to use for users when they don't have their own. The default for this may change in the near future to better utilise HTTP/2. Example: https://api.adorable.io/avatars/{width}/{id}.png
ItemsPerPage - The number of posts, topics, etc. you want on each page. ItemsPerPage - The number of posts, topics, etc. you want on each page.

View File

@ -139,15 +139,15 @@ func FootHeaders(w http.ResponseWriter, h *c.Header) {
he := w.Header() he := w.Header()
if c.Config.SslSchema { if c.Config.SslSchema {
if h.ExternalMedia { if h.ExternalMedia {
he.Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self' www.youtube-nocookie.com embed.nicovideo.jp;upgrade-insecure-requests") he.Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'"+c.Config.ExtraCSPOrigins+"; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self' www.youtube-nocookie.com embed.nicovideo.jp;upgrade-insecure-requests")
} else { } else {
he.Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self';upgrade-insecure-requests") he.Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'"+c.Config.ExtraCSPOrigins+"; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self';upgrade-insecure-requests")
} }
} else { } else {
if h.ExternalMedia { if h.ExternalMedia {
he.Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self' www.youtube-nocookie.com embed.nicovideo.jp") he.Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'"+c.Config.ExtraCSPOrigins+"; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self' www.youtube-nocookie.com embed.nicovideo.jp")
} else { } else {
he.Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self'") he.Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'"+c.Config.ExtraCSPOrigins+"; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self'")
} }
} }
} }

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="/s/{{.}}"rel="stylesheet"type="text/css">{{end}} <link href="{{.}}"rel="stylesheet"type="text/css">{{end}}
{{range .Header.PreScriptsAsync}} {{range .Header.PreScriptsAsync}}
<script async src="/s/{{.}}"></script>{{end}} <script async src="{{.}}"></script>{{end}}
{{if .CurrentUser.Loggedin}}<meta property="x-mem"content="1">{{end}} {{if .CurrentUser.Loggedin}}<meta property="x-mem"content="1">{{end}}
<script src="/s/init.js?i=12"></script> <script src="{{res "init.js"}}"></script>
{{range .Header.ScriptsAsync}} {{range .Header.ScriptsAsync}}
<script async src="/s/{{.}}"></script>{{end}} <script async src="{{.}}"></script>{{end}}
<script src="/s/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="/s/{{.}}"></script>{{end}} <script src="{{.}}"></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 **/}}

View File

@ -1,8 +1,8 @@
<main id="topicPage"> <main id="topicPage">
{{if gt .Page 1}}<link rel="prev"href="{{.Topic.Link}}?page={{subtract .Page 1}}"/>{{end}} {{if gt .Page 1}}<link rel="prev"href="{{.Topic.Link}}?page={{subtract .Page 1}}">{{end}}
{{if ne .LastPage .Page}}<link rel="prerender next"href="{{.Topic.Link}}?page={{add .Page 1}}"/>{{end}} {{if ne .LastPage .Page}}<link rel="prerender next"href="{{.Topic.Link}}?page={{add .Page 1}}">{{end}}
{{if not .CurrentUser.Loggedin}}<link rel="canonical" href="//{{.Site.URL}}{{.Topic.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}"/>{{end}} {{if not .CurrentUser.Loggedin}}<link rel="canonical"href="//{{.Site.URL}}{{.Topic.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}">{{end}}
<div {{scope "topic_title_block"}} class="rowblock rowhead topic_block"aria-label="{{lang "topic.topic_info_aria"}}"> <div {{scope "topic_title_block"}} class="rowblock rowhead topic_block"aria-label="{{lang "topic.topic_info_aria"}}">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}"> <div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}">