Added a couple of simple anti-spam measures.

Made progress with an internal error logging component for the Control Panel.
Made LogWarning less susceptible to races and slightly improved log contention.
Revamped the registration page on Cosora.
Added the SanitiseSingleLine and SanitiseBody functions to better centralise sanitisation and to ensure more consistent sanitisation.
Zero length spaces are no longer permitted in usernames to help prevent impersonation. More to come in this area.
Plugins / internal components can now schedule hourly tasks.
Reduced the chances of newlines breaking the visual layout in areas which expect none.

Added the register_account_anti_spam phrase.
This commit is contained in:
Azareal 2018-05-31 16:51:31 +10:00
parent e311e088c2
commit f85bf51103
24 changed files with 157 additions and 82 deletions

View File

@ -3,6 +3,7 @@ package common
import ( import (
"database/sql" "database/sql"
"log" "log"
"sync/atomic"
"time" "time"
"../query_gen/lib" "../query_gen/lib"
@ -23,6 +24,9 @@ const Petabyte int = Terabyte * 1024
var StartTime time.Time var StartTime time.Time
var TmplPtrMap = make(map[string]interface{}) var TmplPtrMap = make(map[string]interface{})
// Anti-spam token with rotated key
var JSTokenBox atomic.Value // TODO: Move this and some of these other globals somewhere else
// ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores // ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores
var ErrNoRows = sql.ErrNoRows var ErrNoRows = sql.ErrNoRows
@ -54,6 +58,10 @@ var ExecutableFileExts = StringList{
"exe", "jar", "phar", "shar", "iso", "exe", "jar", "phar", "shar", "iso",
} }
func init() {
JSTokenBox.Store("")
}
// TODO: Write a test for this // TODO: Write a test for this
func (slice StringList) Contains(needle string) bool { func (slice StringList) Contains(needle string) bool {
for _, item := range slice { for _, item := range slice {

View File

@ -7,10 +7,16 @@ import (
"sync" "sync"
) )
// TODO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page? type ErrorItem struct {
error
Stack []byte
}
// ! The errorBuffer uses o(n) memory, we should probably do something about that
// TODO: Use the errorBuffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page?
// ? - Should we pass Header / HeaderLite rather than forcing the errors to pull the global Header instance? // ? - Should we pass Header / HeaderLite rather than forcing the errors to pull the global Header instance?
var errorBufferMutex sync.RWMutex var errorBufferMutex sync.RWMutex
var errorBuffer []error var errorBuffer []ErrorItem
//var notfoundCountPerSecond int //var notfoundCountPerSecond int
//var nopermsCountPerSecond int //var nopermsCountPerSecond int
@ -66,11 +72,11 @@ func LogError(err error) {
} }
func LogWarning(err error) { func LogWarning(err error) {
log.Print(err) stack := debug.Stack()
debug.PrintStack() log.Print(err.Error()+"\n", string(stack))
errorBufferMutex.Lock() errorBufferMutex.Lock()
defer errorBufferMutex.Unlock() defer errorBufferMutex.Unlock()
errorBuffer = append(errorBuffer, err) errorBuffer = append(errorBuffer, ErrorItem{err, stack})
} }
// TODO: Dump the request? // TODO: Dump the request?

View File

@ -76,6 +76,7 @@ func (forum *Forum) Update(name string, desc string, active bool, preset string)
if name == "" { if name == "" {
name = forum.Name name = forum.Name
} }
// TODO: Do a line sanitise? Does it matter?
preset = strings.TrimSpace(preset) preset = strings.TrimSpace(preset)
_, err := forumStmts.update.Exec(name, desc, active, preset, forum.ID) _, err := forumStmts.update.Exec(name, desc, active, preset, forum.ID)
if err != nil { if err != nil {

View File

@ -2,7 +2,6 @@ package common
import ( import (
"bytes" "bytes"
"html"
"net/url" "net/url"
"regexp" "regexp"
"strconv" "strconv"
@ -171,9 +170,9 @@ func PreparseMessage(msg string) string {
msg = strings.Replace(msg, "<p>", "\n\n", -1) msg = strings.Replace(msg, "<p>", "\n\n", -1)
msg = strings.Replace(msg, "</p>", "", -1) msg = strings.Replace(msg, "</p>", "", -1)
msg = strings.Replace(msg, "<br>", "\n\n", -1) msg = strings.Replace(msg, "<br>", "\n\n", -1)
msg = strings.TrimSpace(msg) // There are a few useful cases for having spaces, but I'd like to stop the WYSIWYG from inserting random lines here and there
msg = RunSshook("preparse_preassign", msg) msg = RunSshook("preparse_preassign", msg)
msg = html.EscapeString(msg) // There are a few useful cases for having spaces, but I'd like to stop the WYSIWYG from inserting random lines here and there
msg = SanitiseBody(msg)
msg = strings.Replace(msg, "&nbsp;", "", -1) msg = strings.Replace(msg, "&nbsp;", "", -1)
var runes = []rune(msg) var runes = []rune(msg)

View File

@ -22,6 +22,7 @@ type TaskStmts struct {
var ScheduledHalfSecondTasks []func() error var ScheduledHalfSecondTasks []func() error
var ScheduledSecondTasks []func() error var ScheduledSecondTasks []func() error
var ScheduledFifteenMinuteTasks []func() error var ScheduledFifteenMinuteTasks []func() error
var ScheduledHourTasks []func() error
var ShutdownTasks []func() error var ShutdownTasks []func() error
var taskStmts TaskStmts var taskStmts TaskStmts
var lastSync time.Time var lastSync time.Time
@ -53,6 +54,11 @@ func AddScheduledFifteenMinuteTask(task func() error) {
ScheduledFifteenMinuteTasks = append(ScheduledFifteenMinuteTasks, task) ScheduledFifteenMinuteTasks = append(ScheduledFifteenMinuteTasks, task)
} }
// AddScheduledHourTask is not concurrency safe
func AddScheduledHourTask(task func() error) {
ScheduledHourTasks = append(ScheduledHourTasks, task)
}
// AddShutdownTask is not concurrency safe // AddShutdownTask is not concurrency safe
func AddShutdownTask(task func() error) { func AddShutdownTask(task func() error) {
ShutdownTasks = append(ShutdownTasks, task) ShutdownTasks = append(ShutdownTasks, task)
@ -86,6 +92,7 @@ func HandleExpiredScheduledGroups() error {
// TODO: Use AddScheduledSecondTask // TODO: Use AddScheduledSecondTask
// TODO: Be a little more granular with the synchronisation // TODO: Be a little more granular with the synchronisation
// TODO: Synchronise more things
func HandleServerSync() error { func HandleServerSync() error {
// We don't want to run any unnecessary queries when there is nothing to synchronise // We don't want to run any unnecessary queries when there is nothing to synchronise
/*if Config.ServerCount > 1 { /*if Config.ServerCount > 1 {

View File

@ -233,7 +233,7 @@ func CompileTemplates() error {
return err return err
} }
registerPage := Page{"Registration Page", user, header, tList, nil} registerPage := Page{"Registration Page", user, header, tList, "nananana"}
registerTmpl, err := c.Compile("register.html", "templates/", "common.Page", registerPage, varList) registerTmpl, err := c.Compile("register.html", "templates/", "common.Page", registerPage, varList)
if err != nil { if err != nil {
return err return err

View File

@ -11,7 +11,6 @@ import (
"html" "html"
"html/template" "html/template"
"strconv" "strconv"
"strings"
"time" "time"
"../query_gen/lib" "../query_gen/lib"
@ -257,10 +256,7 @@ func (topic *Topic) Delete() error {
// TODO: Write tests for this // TODO: Write tests for this
func (topic *Topic) Update(name string, content string) error { func (topic *Topic) Update(name string, content string) error {
name = html.EscapeString(strings.Replace(html.UnescapeString(name), "\n", "", -1)) name = SanitiseSingleLine(html.UnescapeString(name))
// TODO: Stop duplicating this logic?
name = strings.TrimSpace(name)
if name == "" { if name == "" {
return ErrNoTitle return ErrNoTitle
} }

View File

@ -121,7 +121,6 @@ func (mts *DefaultTopicStore) Exists(id int) bool {
} }
func (mts *DefaultTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) { func (mts *DefaultTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) {
topicName = strings.TrimSpace(topicName)
if topicName == "" { if topicName == "" {
return 0, ErrNoTitle return 0, ErrNoTitle
} }
@ -130,9 +129,8 @@ func (mts *DefaultTopicStore) Create(fid int, topicName string, content string,
return 0, ErrLongTitle return 0, ErrLongTitle
} }
content = strings.TrimSpace(content) parsedContent := strings.TrimSpace(ParseMessage(content, fid, "forums"))
parsedContent := ParseMessage(content, fid, "forums") if parsedContent == "" {
if strings.TrimSpace(parsedContent) == "" {
return 0, ErrNoBody return 0, ErrNoBody
} }

View File

@ -11,6 +11,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"html"
"math" "math"
"os" "os"
"strconv" "strconv"
@ -119,9 +120,8 @@ func ConvertByteUnit(bytes float64) (float64, string) {
return bytes / float64(Megabyte), "MB" return bytes / float64(Megabyte), "MB"
case bytes >= float64(Kilobyte): case bytes >= float64(Kilobyte):
return bytes / float64(Kilobyte), "KB" return bytes / float64(Kilobyte), "KB"
default:
return bytes, " bytes"
} }
return bytes, " bytes"
} }
// TODO: Write a test for this // TODO: Write a test for this
@ -387,6 +387,24 @@ func GetLevels(maxLevel int) []float64 {
return out return out
} }
// TODO: Write a test for this
// SanitiseSingleLine is a generic function for escaping html entities and removing silly characters from usernames and topic titles. It also strips newline characters
func SanitiseSingleLine(in string) string {
in = strings.Replace(in, "\n", "", -1)
in = strings.Replace(in, "\r", "", -1)
return SanitiseBody(in)
}
// TODO: Write a test for this
// TODO: Add more strange characters
// TODO: Strip all sub-32s minus \r and \n?
// SanitiseBody is the same as SanitiseSingleLine, but it doesn't strip newline characters
func SanitiseBody(in string) string {
in = strings.Replace(in, "", "", -1) // Strip Zero length space
in = html.EscapeString(in)
return strings.TrimSpace(in)
}
func BuildSlug(slug string, id int) string { func BuildSlug(slug string, id int) string {
if slug == "" || !Config.BuildSlugs { if slug == "" || !Config.BuildSlugs {
return strconv.Itoa(id) return strconv.Itoa(id)

View File

@ -5,7 +5,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"html"
"html/template" "html/template"
"net/http" "net/http"
"strconv" "strconv"
@ -254,9 +253,9 @@ func RouteCreateGuildSubmit(w http.ResponseWriter, r *http.Request, user common.
} }
var guildActive = true var guildActive = true
var guildName = html.EscapeString(r.PostFormValue("group_name")) var guildName = common.SanitiseSingleLine(r.PostFormValue("group_name"))
// TODO: Allow Markdown / BBCode / Limited HTML in the description? // TODO: Allow Markdown / BBCode / Limited HTML in the description?
var guildDesc = html.EscapeString(r.PostFormValue("group_desc")) var guildDesc = common.SanitiseBody(r.PostFormValue("group_desc"))
var gprivacy = r.PostFormValue("group_privacy") var gprivacy = r.PostFormValue("group_privacy")
var guildPrivacy int var guildPrivacy int

View File

@ -574,26 +574,21 @@ func (router *GenRouter) RemoveFunc(pattern string) error {
return nil return nil
} }
func (router *GenRouter) StripNewlines(data string) string {
// TODO: Strip out all sub-32s?
return strings.Replace(strings.Replace(data,"\n","",-1),"\r","",-1)
}
func (router *GenRouter) DumpRequest(req *http.Request, prepend string) { func (router *GenRouter) DumpRequest(req *http.Request, prepend string) {
var heads string var heads string
for key, value := range req.Header { for key, value := range req.Header {
for _, vvalue := range value { for _, vvalue := range value {
heads += "Header '" + router.StripNewlines(key) + "': " + router.StripNewlines(vvalue) + "!!\n" heads += "Header '" + common.SanitiseSingleLine(key) + "': " + common.SanitiseSingleLine(vvalue) + "!!\n"
} }
} }
router.requestLogger.Print(prepend + router.requestLogger.Print(prepend +
"\nUA: " + router.StripNewlines(req.UserAgent()) + "\n" + "\nUA: " + common.SanitiseSingleLine(req.UserAgent()) + "\n" +
"Method: " + router.StripNewlines(req.Method) + "\n" + heads + "Method: " + common.SanitiseSingleLine(req.Method) + "\n" + heads +
"req.Host: " + router.StripNewlines(req.Host) + "\n" + "req.Host: " + common.SanitiseSingleLine(req.Host) + "\n" +
"req.URL.Path: " + router.StripNewlines(req.URL.Path) + "\n" + "req.URL.Path: " + common.SanitiseSingleLine(req.URL.Path) + "\n" +
"req.URL.RawQuery: " + router.StripNewlines(req.URL.RawQuery) + "\n" + "req.URL.RawQuery: " + common.SanitiseSingleLine(req.URL.RawQuery) + "\n" +
"req.Referer(): " + router.StripNewlines(req.Referer()) + "\n" + "req.Referer(): " + common.SanitiseSingleLine(req.Referer()) + "\n" +
"req.RemoteAddr: " + req.RemoteAddr + "\n") "req.RemoteAddr: " + req.RemoteAddr + "\n")
} }

View File

@ -344,6 +344,7 @@
"register_account_email":"Email", "register_account_email":"Email",
"register_account_password":"Password", "register_account_password":"Password",
"register_account_confirm_password":"Confirm Password", "register_account_confirm_password":"Confirm Password",
"register_account_anti_spam":"Are you a spambot?",
"register_submit_button":"Create Account", "register_submit_button":"Create Account",
"account_menu_head":"My Account", "account_menu_head":"My Account",

17
main.go
View File

@ -217,6 +217,12 @@ func main() {
fmt.Println("") fmt.Println("")
common.StartTime = time.Now() common.StartTime = time.Now()
jsToken, err := common.GenerateSafeString(80)
if err != nil {
log.Fatal(err)
}
common.JSTokenBox.Store(jsToken)
log.Print("Processing configuration data") log.Print("Processing configuration data")
err = common.ProcessConfig() err = common.ProcessConfig()
if err != nil { if err != nil {
@ -338,7 +344,7 @@ func main() {
halfSecondTicker := time.NewTicker(time.Second / 2) halfSecondTicker := time.NewTicker(time.Second / 2)
secondTicker := time.NewTicker(time.Second) secondTicker := time.NewTicker(time.Second)
fifteenMinuteTicker := time.NewTicker(15 * time.Minute) fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
//hourTicker := time.NewTicker(time.Hour) hourTicker := time.NewTicker(time.Hour)
go func() { go func() {
var runHook = func(name string) { var runHook = func(name string) {
err := common.RunTaskHook(name) err := common.RunTaskHook(name)
@ -382,6 +388,15 @@ func main() {
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled. // TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TODO: Publish scheduled posts. // TODO: Publish scheduled posts.
runHook("after_fifteen_minute_tick") runHook("after_fifteen_minute_tick")
case <-hourTicker.C:
runHook("before_hour_tick")
jsToken, err := common.GenerateSafeString(80)
if err != nil {
common.LogError(err)
}
common.JSTokenBox.Store(jsToken)
runTasks(common.ScheduledHourTasks)
runHook("after_hour_tick")
} }
// TODO: Handle the daily clean-up. // TODO: Handle the daily clean-up.

View File

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"html"
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
@ -189,6 +188,7 @@ func routePanelWordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, u
} }
isJs := (r.PostFormValue("js") == "1") isJs := (r.PostFormValue("js") == "1")
// ? - We're not doing a full sanitise here, as it would be useful if admins were able to put down rules for replacing things with HTML, etc.
find := strings.TrimSpace(r.PostFormValue("find")) find := strings.TrimSpace(r.PostFormValue("find"))
if find == "" { if find == "" {
return common.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs) return common.LocalErrorJSQ("You need to specify what word you want to match", w, r, user, isJs)
@ -573,14 +573,14 @@ func routePanelUsersEditSubmit(w http.ResponseWriter, r *http.Request, user comm
return common.LocalError("Only administrators can edit the account of other administrators.", w, r, user) return common.LocalError("Only administrators can edit the account of other administrators.", w, r, user)
} }
newname := html.EscapeString(strings.Replace(r.PostFormValue("user-name"), "\n", "", -1)) newname := common.SanitiseSingleLine(r.PostFormValue("user-name"))
if newname == "" { if newname == "" {
return common.LocalError("You didn't put in a username.", w, r, user) return common.LocalError("You didn't put in a username.", w, r, user)
} }
// TODO: How should activation factor into admin set emails? // TODO: How should activation factor into admin set emails?
// TODO: How should we handle secondary emails? Do we even have secondary emails implemented? // TODO: How should we handle secondary emails? Do we even have secondary emails implemented?
newemail := html.EscapeString(strings.Replace(r.PostFormValue("user-email"), "\n", "", -1)) newemail := common.SanitiseSingleLine(r.PostFormValue("user-email"))
if newemail == "" { if newemail == "" {
return common.LocalError("You didn't put in an email address.", w, r, user) return common.LocalError("You didn't put in an email address.", w, r, user)
} }
@ -1222,7 +1222,7 @@ func routePanelThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, user c
func routePanelThemesMenuItemSetters(r *http.Request, menuItem common.MenuItem) common.MenuItem { func routePanelThemesMenuItemSetters(r *http.Request, menuItem common.MenuItem) common.MenuItem {
var getItem = func(name string) string { var getItem = func(name string) string {
return html.EscapeString(strings.Replace(r.PostFormValue("item-"+name), "\n", "", -1)) return common.SanitiseSingleLine(r.PostFormValue("item-" + name))
} }
menuItem.Name = getItem("name") menuItem.Name = getItem("name")
menuItem.HTMLID = getItem("htmlid") menuItem.HTMLID = getItem("htmlid")

View File

@ -390,26 +390,21 @@ func (router *GenRouter) RemoveFunc(pattern string) error {
return nil return nil
} }
func (router *GenRouter) StripNewlines(data string) string {
// TODO: Strip out all sub-32s?
return strings.Replace(strings.Replace(data,"\n","",-1),"\r","",-1)
}
func (router *GenRouter) DumpRequest(req *http.Request, prepend string) { func (router *GenRouter) DumpRequest(req *http.Request, prepend string) {
var heads string var heads string
for key, value := range req.Header { for key, value := range req.Header {
for _, vvalue := range value { for _, vvalue := range value {
heads += "Header '" + router.StripNewlines(key) + "': " + router.StripNewlines(vvalue) + "!!\n" heads += "Header '" + common.SanitiseSingleLine(key) + "': " + common.SanitiseSingleLine(vvalue) + "!!\n"
} }
} }
router.requestLogger.Print(prepend + router.requestLogger.Print(prepend +
"\nUA: " + router.StripNewlines(req.UserAgent()) + "\n" + "\nUA: " + common.SanitiseSingleLine(req.UserAgent()) + "\n" +
"Method: " + router.StripNewlines(req.Method) + "\n" + heads + "Method: " + common.SanitiseSingleLine(req.Method) + "\n" + heads +
"req.Host: " + router.StripNewlines(req.Host) + "\n" + "req.Host: " + common.SanitiseSingleLine(req.Host) + "\n" +
"req.URL.Path: " + router.StripNewlines(req.URL.Path) + "\n" + "req.URL.Path: " + common.SanitiseSingleLine(req.URL.Path) + "\n" +
"req.URL.RawQuery: " + router.StripNewlines(req.URL.RawQuery) + "\n" + "req.URL.RawQuery: " + common.SanitiseSingleLine(req.URL.RawQuery) + "\n" +
"req.Referer(): " + router.StripNewlines(req.Referer()) + "\n" + "req.Referer(): " + common.SanitiseSingleLine(req.Referer()) + "\n" +
"req.RemoteAddr: " + req.RemoteAddr + "\n") "req.RemoteAddr: " + req.RemoteAddr + "\n")
} }

View File

@ -1,8 +1,9 @@
package routes package routes
import ( import (
"crypto/sha256"
"database/sql" "database/sql"
"html" "encoding/hex"
"io" "io"
"log" "log"
"net/http" "net/http"
@ -45,7 +46,7 @@ func AccountLoginSubmit(w http.ResponseWriter, r *http.Request, user common.User
return common.LocalError("You're already logged in.", w, r, user) return common.LocalError("You're already logged in.", w, r, user)
} }
username := html.EscapeString(strings.Replace(r.PostFormValue("username"), "\n", "", -1)) username := common.SanitiseSingleLine(r.PostFormValue("username"))
uid, err := common.Auth.Authenticate(username, r.PostFormValue("password")) uid, err := common.Auth.Authenticate(username, r.PostFormValue("password"))
if err != nil { if err != nil {
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
@ -91,7 +92,11 @@ func AccountRegister(w http.ResponseWriter, r *http.Request, user common.User) c
if user.Loggedin { if user.Loggedin {
return common.LocalError("You're already logged in.", w, r, user) return common.LocalError("You're already logged in.", w, r, user)
} }
pi := common.Page{common.GetTitlePhrase("register"), user, header, tList, nil} h := sha256.New()
h.Write([]byte(common.JSTokenBox.Load().(string)))
h.Write([]byte(user.LastIP))
jsToken := hex.EncodeToString(h.Sum(nil))
pi := common.Page{common.GetTitlePhrase("register"), user, header, tList, jsToken}
if common.RunPreRenderHook("pre_render_register", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_register", w, r, &user, &pi) {
return nil return nil
} }
@ -117,8 +122,19 @@ func AccountRegisterSubmit(w http.ResponseWriter, r *http.Request, user common.U
regErrReason += reason + "|" regErrReason += reason + "|"
} }
username := html.EscapeString(strings.Replace(r.PostFormValue("username"), "\n", "", -1)) if r.PostFormValue("tos") != "0" {
email := html.EscapeString(strings.Replace(r.PostFormValue("email"), "\n", "", -1)) regError("You might be a machine", "trap-question")
}
h := sha256.New()
h.Write([]byte(common.JSTokenBox.Load().(string)))
h.Write([]byte(user.LastIP))
if r.PostFormValue("antispam") != hex.EncodeToString(h.Sum(nil)) {
regError("You might be a machine", "js-antispam")
}
username := common.SanitiseSingleLine(r.PostFormValue("username"))
// TODO: Add a dedicated function for validating emails
email := common.SanitiseSingleLine(r.PostFormValue("email"))
if username == "" { if username == "" {
regError("You didn't put in a username.", "no-username") regError("You didn't put in a username.", "no-username")
} }
@ -390,7 +406,7 @@ func AccountEditUsernameSubmit(w http.ResponseWriter, r *http.Request, user comm
return ferr return ferr
} }
newUsername := html.EscapeString(strings.Replace(r.PostFormValue("account-new-username"), "\n", "", -1)) newUsername := common.SanitiseSingleLine(r.PostFormValue("account-new-username"))
err := user.ChangeName(newUsername) err := user.ChangeName(newUsername)
if err != nil { if err != nil {
return common.LocalError("Unable to change the username. Does someone else already have this name?", w, r, user) return common.LocalError("Unable to change the username. Does someone else already have this name?", w, r, user)

View File

@ -3,7 +3,6 @@ package routes
import ( import (
"bytes" "bytes"
"database/sql" "database/sql"
"html"
"io" "io"
"net/http" "net/http"
"path/filepath" "path/filepath"
@ -157,7 +156,7 @@ func ChangeTheme(w http.ResponseWriter, r *http.Request, user common.User) commo
//headerLite, _ := SimpleUserCheck(w, r, &user) //headerLite, _ := SimpleUserCheck(w, r, &user)
// TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly? // TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly?
isJs := (r.PostFormValue("isJs") == "1") isJs := (r.PostFormValue("isJs") == "1")
newTheme := html.EscapeString(r.PostFormValue("newTheme")) newTheme := common.SanitiseSingleLine(r.PostFormValue("newTheme"))
theme, ok := common.Themes[newTheme] theme, ok := common.Themes[newTheme]
if !ok || theme.HideFromThemes { if !ok || theme.HideFromThemes {

View File

@ -1,7 +1,6 @@
package routes package routes
import ( import (
"html"
"net/http" "net/http"
"../common" "../common"
@ -18,7 +17,7 @@ func IPSearch(w http.ResponseWriter, r *http.Request, user common.User) common.R
} }
// TODO: Reject IP Addresses with illegal characters // TODO: Reject IP Addresses with illegal characters
var ip = html.EscapeString(r.FormValue("ip")) var ip = common.SanitiseSingleLine(r.FormValue("ip"))
uids, err := common.IPSearch.Lookup(ip) uids, err := common.IPSearch.Lookup(ip)
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)

View File

@ -3,7 +3,6 @@ package panel
import ( import (
"database/sql" "database/sql"
"errors" "errors"
"html"
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
@ -185,7 +184,7 @@ func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.Use
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList} graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
common.DebugLogf("graph: %+v\n", graph) common.DebugLogf("graph: %+v\n", graph)
pi := common.PanelAnalyticsRoutePage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(route), graph, viewItems, timeRange.Range} pi := common.PanelAnalyticsRoutePage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", common.SanitiseSingleLine(route), graph, viewItems, timeRange.Range}
return panelRenderTemplate("panel_analytics_route_views", w, r, user, &pi) return panelRenderTemplate("panel_analytics_route_views", w, r, user, &pi)
} }
@ -205,7 +204,7 @@ func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.Use
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange) revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
// ? Only allow valid agents? The problem with this is that agents wind up getting renamed and it would take a migration to get them all up to snuff // ? Only allow valid agents? The problem with this is that agents wind up getting renamed and it would take a migration to get them all up to snuff
agent = html.EscapeString(agent) agent = common.SanitiseSingleLine(agent)
common.DebugLog("in panel.AnalyticsAgentViews") common.DebugLog("in panel.AnalyticsAgentViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
@ -299,7 +298,7 @@ func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.Us
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
} }
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange) revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
system = html.EscapeString(system) system = common.SanitiseSingleLine(system)
common.DebugLog("in panel.AnalyticsSystemViews") common.DebugLog("in panel.AnalyticsSystemViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
@ -344,7 +343,7 @@ func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common.
return common.LocalError(err.Error(), w, r, user) return common.LocalError(err.Error(), w, r, user)
} }
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange) revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
lang = html.EscapeString(lang) lang = common.SanitiseSingleLine(lang)
common.DebugLog("in panel.AnalyticsLanguageViews") common.DebugLog("in panel.AnalyticsLanguageViews")
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
@ -410,7 +409,7 @@ func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common.
graph := common.PanelTimeGraph{Series: viewList, Labels: labelList} graph := common.PanelTimeGraph{Series: viewList, Labels: labelList}
common.DebugLogf("graph: %+v\n", graph) common.DebugLogf("graph: %+v\n", graph)
pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", html.EscapeString(domain), "", graph, timeRange.Range} pi := common.PanelAnalyticsAgentPage{common.GetTitlePhrase("panel_analytics"), user, headerVars, stats, "analytics", common.SanitiseSingleLine(domain), "", graph, timeRange.Range}
return panelRenderTemplate("panel_analytics_referrer_views", w, r, user, &pi) return panelRenderTemplate("panel_analytics_referrer_views", w, r, user, &pi)
} }
@ -736,7 +735,7 @@ func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User
var refItems []common.PanelAnalyticsAgentsItem var refItems []common.PanelAnalyticsAgentsItem
for domain, count := range refMap { for domain, count := range refMap {
refItems = append(refItems, common.PanelAnalyticsAgentsItem{ refItems = append(refItems, common.PanelAnalyticsAgentsItem{
Agent: html.EscapeString(domain), Agent: common.SanitiseSingleLine(domain),
Count: count, Count: count,
}) })
} }

View File

@ -3,7 +3,6 @@ package panel
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"html"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -104,7 +103,7 @@ func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user common.User,
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
scontent := html.EscapeString(r.PostFormValue("setting-value")) scontent := common.SanitiseBody(r.PostFormValue("setting-value"))
err := headerLite.Settings.Update(sname, scontent) err := headerLite.Settings.Update(sname, scontent)
if err != nil { if err != nil {
if common.SafeSettingError(err) { if common.SafeSettingError(err) {

View File

@ -4,7 +4,6 @@ import (
"crypto/sha256" "crypto/sha256"
"database/sql" "database/sql"
"encoding/hex" "encoding/hex"
"html"
"io" "io"
"log" "log"
"net/http" "net/http"
@ -142,8 +141,9 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack // If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
_, exists := pollInputItems[index] _, exists := pollInputItems[index]
if !exists && len(html.EscapeString(value)) != 0 { // TODO: Should we use SanitiseBody instead to keep the newlines?
pollInputItems[index] = html.EscapeString(value) if !exists && len(common.SanitiseSingleLine(value)) != 0 {
pollInputItems[index] = common.SanitiseSingleLine(value)
if len(pollInputItems) >= maxPollOptions { if len(pollInputItems) >= maxPollOptions {
break break
} }

View File

@ -5,7 +5,6 @@ import (
"database/sql" "database/sql"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"html"
"io" "io"
"log" "log"
"net/http" "net/http"
@ -334,7 +333,7 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User)
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
topicName := html.EscapeString(strings.Replace(r.PostFormValue("topic-name"), "\n", "", -1)) topicName := common.SanitiseSingleLine(r.PostFormValue("topic-name"))
content := common.PreparseMessage(r.PostFormValue("topic-content")) content := common.PreparseMessage(r.PostFormValue("topic-content"))
// TODO: Fully parse the post and store it in the parsed column // TODO: Fully parse the post and store it in the parsed column
tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP) tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP)
@ -377,9 +376,9 @@ func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User)
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack // If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
_, exists := pollInputItems[index] _, exists := pollInputItems[index]
if !exists && len(html.EscapeString(value)) != 0 { // TODO: Should we use SanitiseBody instead to keep the newlines?
pollInputItems[index] = html.EscapeString(value) if !exists && len(common.SanitiseSingleLine(value)) != 0 {
pollInputItems[index] = common.SanitiseSingleLine(value)
if len(pollInputItems) >= maxPollOptions { if len(pollInputItems) >= maxPollOptions {
break break
} }

View File

@ -21,10 +21,21 @@
<div class="formitem formlabel"><a id="confirm_password_label">{{lang "register_account_confirm_password"}}</a></div> <div class="formitem formlabel"><a id="confirm_password_label">{{lang "register_account_confirm_password"}}</a></div>
<div class="formitem"><input name="confirm_password" type="password" placeholder="*****" autocomplete="new-password" aria-labelledby="confirm_password_label" required /></div> <div class="formitem"><input name="confirm_password" type="password" placeholder="*****" autocomplete="new-password" aria-labelledby="confirm_password_label" required /></div>
</div> </div>
<div class="formrow"> <div class="formrow">{{/** This is not a TOS, that text is there to fool the spambots **/}}
<div class="formitem formlabel"><a id="accept_tos_label">{{lang "register_account_anti_spam"}}</a></div>
<div class="formitem"><select name="tos" aria-labelledby="accept_tos_label" required>
<option value="1" selected>{{lang "option_yes"}}</option>
<option value="0">{{lang "option_no"}}</option>
</select></div>
</div>
<div class="formrow register_button_row">
<div class="formitem"><button name="register-button" class="formbutton">{{lang "register_submit_button"}}</button></div> <div class="formitem"><button name="register-button" class="formbutton">{{lang "register_submit_button"}}</button></div>
</div> </div>
<input id="js-antispam" name="antispam" value="nananana" type="hidden" />
</form> </form>
</div> </div>
</main> </main>
<script>{{/** TODO: Are we sure we want to do this by ID, just in case we reuse this and have multiple antispams on the page? **/}}
document.getElementById("js-antispam").value = "{{.Something}}";
</script>
{{template "footer.html" . }} {{template "footer.html" . }}

View File

@ -1292,7 +1292,7 @@ textarea {
margin-bottom: 10px; margin-bottom: 10px;
} }
#create_topic_page .close_form, #create_topic_page .formlabel, #login_page .formlabel, #register_page .formlabel { #create_topic_page .close_form, #create_topic_page .formlabel, #login_page .formlabel {
display: none; display: none;
} }
#login_page .formrow:not(:first-child):not(:last-child), #register_page .formrow:not(:first-child):not(:last-child) { #login_page .formrow:not(:first-child):not(:last-child), #register_page .formrow:not(:first-child):not(:last-child) {
@ -1301,12 +1301,27 @@ textarea {
#login_page .formrow:not(:first-child), #register_page .formrow:not(:first-child) { #login_page .formrow:not(:first-child), #register_page .formrow:not(:first-child) {
padding-top: 3px; padding-top: 3px;
} }
#login_page .formrow:not(:last-child), #register_page .formrow:not(:last-child) { #login_page .formrow:not(:last-child) {
padding-bottom: 0px; padding-bottom: 0px;
} }
#login_page .formrow, #register_page .formrow { #login_page .formrow, #register_page .formrow {
padding: 16px; padding: 16px;
} }
#register_page .formrow:not(:last-child) {
padding-bottom: 4px;
}
#register_page .formlabel {
display: block;
font-size: 15px;
}
#register_page .register_button_row {
padding: 12px !important;
padding-top: 0px !important;
margin-top: -2px !important;
}
#register_page .register_button_row .formbutton {
margin-left: 2px;
}
/* TODO: Add a generic button_row class and add this to them all? */ /* TODO: Add a generic button_row class and add this to them all? */
.login_button_row { .login_button_row {