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"
"mime"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
@ -23,12 +24,14 @@ import (
//type SFileList 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 staticFileMutex sync.RWMutex
// ? Is it efficient to have two maps for this?
type SFileList struct {
Prefix string
Long map[string]*SFile
Short map[string]*SFile
}
@ -305,7 +308,7 @@ func (l SFileList) JSTmplInit() error {
hasher.Write(data)
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)
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)
return nil
@ -424,7 +427,7 @@ func (l SFileList) Add(path, prefix string) error {
hasher.Write(data)
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)
return nil
@ -448,8 +451,13 @@ func (l SFileList) GetShort(name string) (file *SFile, exists bool) {
func (l SFileList) Set(name string, data *SFile) {
staticFileMutex.Lock()
defer staticFileMutex.Unlock()
l.Long[name] = data
l.Short[strings.TrimPrefix("/s/",name)] = data
// TODO: Propagate errors back up
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

View File

@ -2,11 +2,13 @@ package common
import (
"encoding/json"
"errors"
"io/ioutil"
"log"
"net/url"
"strconv"
"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
@ -119,6 +121,9 @@ type config struct {
RefNoRef bool
NoEmbed bool
ExtraCSPOrigins string
StaticResBase string // /s/
Noavatar string // ? - Move this into the settings table?
ItemsPerPage int // ? - Move this into the settings table?
MaxTopicTitleLength int
@ -238,6 +243,21 @@ func ProcessConfig() (err error) {
}
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 {
Config.PostIPCutoff = 120 // Default cutoff
}

View File

@ -878,6 +878,17 @@ func initDefaultTmplFuncMap() {
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
}

View File

@ -119,6 +119,7 @@ func NewCTemplateSet(in string) *CTemplateSet {
"js": true,
"index": true,
"flush": true,
"res": true,
},
logger: log.New(f, "", log.LstdFlags),
loggerf: f,
@ -683,8 +684,9 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
for _, cmd := range node.Pipe.Cmds {
c.detail("If Node Bit:", cmd)
c.detail("Bit Type:", reflect.ValueOf(cmd).Type().Name())
expr += c.compileExprSwitch(con, cmd)
c.detail("Expression Step:", c.compileExprSwitch(con, cmd))
exprStep := c.compileExprSwitch(con, cmd)
expr += exprStep
c.detail("Expression Step:", exprStep)
}
c.detail("Expression:", expr)
@ -1031,8 +1033,7 @@ func (c *CTemplateSet) compileExprSwitch(con CContext, node *parse.CommandNode)
case *parse.NilNode:
panic("Nil is not a command x.x")
case *parse.PipeNode:
c.detail("Pipe Node!")
c.detail(n)
c.detail("Pipe Node:", n)
c.detail("Node Args:", node.Args)
out += c.compileIdentSwitchN(con, node)
default:
@ -1043,9 +1044,9 @@ func (c *CTemplateSet) compileExprSwitch(con CContext, node *parse.CommandNode)
}
func (c *CTemplateSet) unknownNode(n parse.Node) {
elem := reflect.ValueOf(n).Elem()
c.logger.Println("Unknown Kind:", elem.Kind())
c.logger.Println("Unknown Type:", elem.Type().Name())
el := reflect.ValueOf(n).Elem()
c.logger.Println("Unknown Kind:", el.Kind())
c.logger.Println("Unknown Type:", el.Type().Name())
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
}
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)
litString := func(inner string, bytes bool) {
if !bytes {
@ -1135,9 +1136,16 @@ ArgLoop:
var rout string
pos, rout = c.compareJoin(con, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this
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))
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":
rout, rval := c.simpleMath(con, pos, node, c.funcMap[id.String()].(string))
out += rout
@ -1236,7 +1244,7 @@ ArgLoop:
// ! Slightly crude but it does the job
leftParam := strings.Replace(leftOp, "\"", "", -1)
c.langIndexToName = append(c.langIndexToName, leftParam)
notident = true
notIdent = true
con.PushPhrase(len(c.langIndexToName) - 1)
} else {
leftParam := leftOp
@ -1386,17 +1394,37 @@ ArgLoop:
// TODO: Refactor this
// 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 += "if err != nil {\nreturn err\n}\n}\n"
out = "{\ne := " + headParam + ".Theme.RunTmpl(" + nameParam + "," + pageParam + ",w)\n"
out += "if e != nil {\nreturn e\n}\n}\n"
literal = true
break ArgLoop
case "flush":
if c.lang == "js" {
literal = true
break ArgLoop
/*if c.lang == "js" {
continue
}
out = "if fl, ok := iw.(http.Flusher); ok {\nfl.Flush()\n}\n"
literal = true
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
default:
c.detail("Variable!")
@ -1410,7 +1438,7 @@ ArgLoop:
}
}
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) {

View File

@ -284,7 +284,7 @@ func (t *Theme) AddThemeStaticFiles() error {
hasher.Write(data)
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 + ".")
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
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
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()
if c.Config.SslSchema {
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 {
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 {
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 {
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>
<title>{{.Title}} | {{.Header.Site.Name}}</title>
{{range .Header.Stylesheets}}
<link href="/s/{{.}}"rel="stylesheet"type="text/css">{{end}}
<link href="{{.}}"rel="stylesheet"type="text/css">{{end}}
{{range .Header.PreScriptsAsync}}
<script async src="/s/{{.}}"></script>{{end}}
<script async src="{{.}}"></script>{{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}}
<script async src="/s/{{.}}"></script>{{end}}
<script src="/s/jquery-3.1.1.min.js"></script>
<script async src="{{.}}"></script>{{end}}
<script src="{{res "jquery-3.1.1.min.js"}}"></script>
{{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">
{{if .Header.MetaDesc}}<meta name="description"content="{{.Header.MetaDesc}}">{{end}}
{{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}}

View File

@ -1,8 +1,8 @@
<main id="topicPage">
{{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 not .CurrentUser.Loggedin}}<link rel="canonical" href="//{{.Site.URL}}{{.Topic.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}"/>{{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 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 class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}">