f8f46b3c48
Added the Account Dashboard and merged a few account views into it. BREAKING CHANGE: We now use config/config.json instead of config/config.go, be sure to setup one of these files, you can config_default.json as an example of what a config.json should look like. If you don't have an existing installation, you can just rely on the installer to do this for you. CSS Changes (does not include Nox Theme): Sidebar should no longer show up in the account manager in some odd situations or themes. Made a few CSS rules more generic. Forms have a new look in Cosora now. Config Changes: Removed the DefaultRoute config field. Added the DefaultPath config field. Added the MaxRequestSizeStr config field to make it easier for users to input custom max request sizes without having to use a calculator or figure out how many bytes there are in a megabyte. Removed the CacheTopicUser config field. Added the UserCache config field. Added the TopicCache config field Phrases: Removed ten english phrases. Added 21 english phrases. Changed eleven english phrases. Removed some duplicate indices in the english phrase pack. Removed some old benchmark code. Tweaked some things to make the linter happy. Added comments for all the MemoryUserCache and MemoryTopicCache methods. Added a comment for the null caches, consult the other caches for further information on the methods. Added a client-side check to make sure the user doesn't upload too much data in a single post. The server already did this, but it might be a while before feedback arrives from it. Simplified a lot of the control panel route code with the buildBasePage function. Renamed /user/edit/critical/ to /user/edit/password/ Renamed /user/edit/critical/submit/ to /user/edit/password/submit/ Made some small improvements to SEO with a couple of meta tags. Renamed some of the control panel templates so that they use _ instead of -. Fixed a bug where notices were being moved to the wrong place in some areas in Cosora. Added the writeJsonError function to help abstract writing json errors. Moved routePanelUsers to panel.Users Moved routePanelUsersEdit to panel.UsersEdit Moved routePanelUsersEditSubmit to panel.UsersEditSubmit Renamed routes.AccountEditCritical to routes.AccountEditPassword Renamed routes.AccountEditCriticalSubmit to routes.AccountEditPasswordSubmit Removed the routes.AccountEditAvatar and routes.AccountEditUsername routes. Fixed a data race in MemoryTopicCache.Add which could lead to the capacity limit being bypassed. Tweaked MemoryTopicCache.AddUnsafe under the assumption that it's not going to be safe anyway, but we might as-well try in case this call is properly synchronised. Fixed a data race in MemoryTopicCache.Remove which could lead to the length counter being decremented twice. Tweaked the behaviour of MemoryTopicCache.RemoveUnsafe to mirror that of Remove. Fixed a data race in MemoryUserCache.Add which could lead to the capacity limit being bypassed. User can no longer change their usernames to blank. Made a lot of progress on the Nox theme. Added modified FA5 SVGs as a dependency for Nox. Be sure to run the patcher or update script and don't forget to create a customised config/config.json file.
320 lines
11 KiB
Go
320 lines
11 KiB
Go
package common
|
|
|
|
import (
|
|
"log"
|
|
"net/http"
|
|
"runtime/debug"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
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?
|
|
var errorBufferMutex sync.RWMutex
|
|
var errorBuffer []ErrorItem
|
|
|
|
//var notfoundCountPerSecond int
|
|
//var nopermsCountPerSecond int
|
|
|
|
// A blank list to fill out that parameter in Page for routes which don't use it
|
|
var tList []interface{}
|
|
|
|
// WIP, a new system to propagate errors up from routes
|
|
type RouteError interface {
|
|
Type() string
|
|
Error() string
|
|
JSON() bool
|
|
Handled() bool
|
|
}
|
|
|
|
type RouteErrorImpl struct {
|
|
text string
|
|
system bool
|
|
json bool
|
|
handled bool
|
|
}
|
|
|
|
func (err *RouteErrorImpl) Type() string {
|
|
// System errors may contain sensitive information we don't want the user to see
|
|
if err.system {
|
|
return "system"
|
|
}
|
|
return "user"
|
|
}
|
|
|
|
func (err *RouteErrorImpl) Error() string {
|
|
return err.text
|
|
}
|
|
|
|
// Respond with JSON?
|
|
func (err *RouteErrorImpl) JSON() bool {
|
|
return err.json
|
|
}
|
|
|
|
// Has this error been dealt with elsewhere?
|
|
func (err *RouteErrorImpl) Handled() bool {
|
|
return err.handled
|
|
}
|
|
|
|
func HandledRouteError() RouteError {
|
|
return &RouteErrorImpl{"", false, false, true}
|
|
}
|
|
|
|
// LogError logs internal handler errors which can't be handled with InternalError() as a wrapper for log.Fatal(), we might do more with it in the future.
|
|
func LogError(err error) {
|
|
LogWarning(err)
|
|
log.Fatal("")
|
|
}
|
|
|
|
func LogWarning(err error) {
|
|
var errmsg string
|
|
if err == nil {
|
|
errmsg = "Unknown error"
|
|
} else {
|
|
errmsg = err.Error()
|
|
}
|
|
stack := debug.Stack()
|
|
log.Print(errmsg+"\n", string(stack))
|
|
errorBufferMutex.Lock()
|
|
defer errorBufferMutex.Unlock()
|
|
errorBuffer = append(errorBuffer, ErrorItem{err, stack})
|
|
}
|
|
|
|
func errorHeader(w http.ResponseWriter, user User, title string) *Header {
|
|
header := DefaultHeader(w, user)
|
|
header.Title = title
|
|
return header
|
|
}
|
|
|
|
// TODO: Dump the request?
|
|
// InternalError is the main function for handling internal errors, while simultaneously printing out a page for the end-user to let them know that *something* has gone wrong
|
|
// ? - Add a user parameter?
|
|
func InternalError(err error, w http.ResponseWriter, r *http.Request) RouteError {
|
|
pi := ErrorPage{errorHeader(w, GuestUser, "Internal Server Error"), "A problem has occurred in the system."}
|
|
handleErrorTemplate(w, r, pi)
|
|
LogError(err)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// InternalErrorJSQ is the JSON "maybe" version of InternalError which can handle both JSON and normal requests
|
|
// ? - Add a user parameter?
|
|
func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, isJs bool) RouteError {
|
|
if !isJs {
|
|
return InternalError(err, w, r)
|
|
}
|
|
return InternalErrorJS(err, w, r)
|
|
}
|
|
|
|
// InternalErrorJS is the JSON version of InternalError on routes we know will only be requested via JSON. E.g. An API.
|
|
// ? - Add a user parameter?
|
|
func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) RouteError {
|
|
w.WriteHeader(500)
|
|
writeJsonError("A problem has occurred in the system.", w)
|
|
LogError(err)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
var xmlInternalError = []byte(`<?xml version="1.0" encoding="UTF-8"?>
|
|
<error>A problem has occured</error>`)
|
|
|
|
func InternalErrorXML(err error, w http.ResponseWriter, r *http.Request) RouteError {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(500)
|
|
w.Write(xmlInternalError)
|
|
LogError(err)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// TODO: Stop killing the instance upon hitting an error with InternalError* and deprecate this
|
|
func SilentInternalErrorXML(err error, w http.ResponseWriter, r *http.Request) RouteError {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(500)
|
|
w.Write(xmlInternalError)
|
|
log.Print("InternalError: ", err)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
|
|
w.WriteHeader(500)
|
|
pi := ErrorPage{errorHeader(w, GuestUser, "Error"), errmsg}
|
|
handleErrorTemplate(w, r, pi)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
func PreErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
|
|
w.WriteHeader(500)
|
|
writeJsonError(errmsg, w)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs bool) RouteError {
|
|
if !isJs {
|
|
return PreError(errmsg, w, r)
|
|
}
|
|
return PreErrorJS(errmsg, w, r)
|
|
}
|
|
|
|
// LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault
|
|
// TODO: Pass header in for this and similar errors instead of having to pass in both user and w? Would also allow for more stateful things, although this could be a problem
|
|
func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError {
|
|
w.WriteHeader(500)
|
|
pi := ErrorPage{errorHeader(w, user, "Local Error"), errmsg}
|
|
handleErrorTemplate(w, r, pi)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
func LocalErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError {
|
|
if !isJs {
|
|
return LocalError(errmsg, w, r, user)
|
|
}
|
|
return LocalErrorJS(errmsg, w, r)
|
|
}
|
|
|
|
func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
|
|
w.WriteHeader(500)
|
|
writeJsonError(errmsg, w)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// TODO: We might want to centralise the error logic in the future and just return what the error handler needs to construct the response rather than handling it here
|
|
// NoPermissions is an error shown to the end-user when they try to access an area which they aren't authorised to access
|
|
func NoPermissions(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
|
w.WriteHeader(403)
|
|
pi := ErrorPage{errorHeader(w, user, "Local Error"), "You don't have permission to do that."}
|
|
handleErrorTemplate(w, r, pi)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
func NoPermissionsJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError {
|
|
if !isJs {
|
|
return NoPermissions(w, r, user)
|
|
}
|
|
return NoPermissionsJS(w, r, user)
|
|
}
|
|
|
|
func NoPermissionsJS(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
|
w.WriteHeader(403)
|
|
writeJsonError("You don't have permission to do that.", w)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// ? - Is this actually used? Should it be used? A ban in Gosora should be more of a permission revocation to stop them posting rather than something which spits up an error page, right?
|
|
func Banned(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
|
w.WriteHeader(403)
|
|
pi := ErrorPage{errorHeader(w, user, "Banned"), "You have been banned from this site."}
|
|
handleErrorTemplate(w, r, pi)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// nolint
|
|
// BannedJSQ is the version of the banned error page which handles both JavaScript requests and normal page loads
|
|
func BannedJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError {
|
|
if !isJs {
|
|
return Banned(w, r, user)
|
|
}
|
|
return BannedJS(w, r, user)
|
|
}
|
|
|
|
func BannedJS(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
|
w.WriteHeader(403)
|
|
writeJsonError("You have been banned from this site.", w)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// nolint
|
|
func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bool) RouteError {
|
|
if !isJs {
|
|
return LoginRequired(w, r, user)
|
|
}
|
|
return LoginRequiredJS(w, r, user)
|
|
}
|
|
|
|
// ? - Where is this used? Should we use it more?
|
|
// LoginRequired is an error shown to the end-user when they try to access an area which requires them to login
|
|
func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
|
w.WriteHeader(401)
|
|
pi := ErrorPage{errorHeader(w, user, "Local Error"), "You need to login to do that."}
|
|
handleErrorTemplate(w, r, pi)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// nolint
|
|
func LoginRequiredJS(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
|
w.WriteHeader(401)
|
|
writeJsonError("You need to login to do that.", w)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// SecurityError is used whenever a session mismatch is found
|
|
// ? - Should we add JS and JSQ versions of this?
|
|
func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError {
|
|
w.WriteHeader(403)
|
|
pi := ErrorPage{errorHeader(w, user, "Security Error"), "There was a security issue with your request."}
|
|
if RunPreRenderHook("pre_render_security_error", w, r, &user, &pi) {
|
|
return nil
|
|
}
|
|
err := Templates.ExecuteTemplate(w, "error.html", pi)
|
|
if err != nil {
|
|
LogError(err)
|
|
}
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// NotFound is used when the requested page doesn't exist
|
|
// ? - Add a JSQ and JS version of this?
|
|
// ? - Add a user parameter?
|
|
func NotFound(w http.ResponseWriter, r *http.Request, header *Header) RouteError {
|
|
return CustomError("The requested page doesn't exist.", 404, "Not Found", w, r, header, GuestUser)
|
|
}
|
|
|
|
// CustomError lets us make custom error types which aren't covered by the generic functions above
|
|
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, header *Header, user User) RouteError {
|
|
if header == nil {
|
|
header = DefaultHeader(w, user)
|
|
}
|
|
w.WriteHeader(errcode)
|
|
pi := ErrorPage{header, errmsg}
|
|
handleErrorTemplate(w, r, pi)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// CustomErrorJSQ is a version of CustomError which lets us handle both JSON and regular pages depending on how it's being accessed
|
|
func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, header *Header, user User, isJs bool) RouteError {
|
|
if !isJs {
|
|
if header == nil {
|
|
header = DefaultHeader(w, user)
|
|
}
|
|
return CustomError(errmsg, errcode, errtitle, w, r, header, user)
|
|
}
|
|
return CustomErrorJS(errmsg, errcode, w, r, user)
|
|
}
|
|
|
|
// CustomErrorJS is the pure JSON version of CustomError
|
|
func CustomErrorJS(errmsg string, errcode int, w http.ResponseWriter, r *http.Request, user User) RouteError {
|
|
w.WriteHeader(errcode)
|
|
writeJsonError(errmsg, w)
|
|
return HandledRouteError()
|
|
}
|
|
|
|
// TODO: Should we optimise this by caching these json strings?
|
|
func writeJsonError(errmsg string, w http.ResponseWriter) {
|
|
_, _ = w.Write([]byte(`{"errmsg":"` + strings.Replace(errmsg, "\"", "", -1) + `"}`))
|
|
}
|
|
|
|
func handleErrorTemplate(w http.ResponseWriter, r *http.Request, pi ErrorPage) {
|
|
// TODO: What to do about this hook?
|
|
if RunPreRenderHook("pre_render_error", w, r, &pi.Header.CurrentUser, &pi) {
|
|
return
|
|
}
|
|
err := RunThemeTemplate(pi.Header.Theme.Name, "error", pi, w)
|
|
if err != nil {
|
|
LogError(err)
|
|
}
|
|
}
|