2017-09-10 16:57:22 +00:00
package main
2017-09-10 17:05:13 +00:00
import (
"html"
"html/template"
2017-09-15 22:20:01 +00:00
"log"
2017-09-10 17:05:13 +00:00
"net"
"net/http"
2017-09-15 22:20:01 +00:00
"strconv"
2017-09-10 17:05:13 +00:00
"strings"
)
// nolint
var PreRoute func ( http . ResponseWriter , * http . Request ) ( User , bool ) = preRoute
2017-09-15 22:20:01 +00:00
// TODO: Come up with a better middleware solution
2017-09-10 17:05:13 +00:00
// nolint We need these types so people can tell what they are without scrolling to the bottom of the file
2017-09-10 17:39:16 +00:00
var PanelUserCheck func ( http . ResponseWriter , * http . Request , * User ) ( * HeaderVars , PanelStats , bool ) = panelUserCheck
var SimplePanelUserCheck func ( http . ResponseWriter , * http . Request , * User ) ( * HeaderLite , bool ) = simplePanelUserCheck
var SimpleForumUserCheck func ( w http . ResponseWriter , r * http . Request , user * User , fid int ) ( headerLite * HeaderLite , success bool ) = simpleForumUserCheck
var ForumUserCheck func ( w http . ResponseWriter , r * http . Request , user * User , fid int ) ( headerVars * HeaderVars , success bool ) = forumUserCheck
2017-09-15 22:20:01 +00:00
var MemberCheck func ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , success bool ) = memberCheck
2017-09-10 17:39:16 +00:00
var SimpleUserCheck func ( w http . ResponseWriter , r * http . Request , user * User ) ( headerLite * HeaderLite , success bool ) = simpleUserCheck
var UserCheck func ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , success bool ) = userCheck
2017-09-10 17:05:13 +00:00
2017-09-22 02:21:17 +00:00
// This is mostly for errors.go, please create *HeaderVars on the spot instead of relying on this or the atomic store underlying it, if possible
// TODO: Write a test for this
func getDefaultHeaderVar ( ) * HeaderVars {
return & HeaderVars { Site : site , ThemeName : fallbackTheme }
}
2017-09-10 17:05:13 +00:00
// TODO: Support for left sidebars and sidebars on both sides
// http.Request is for context.Context middleware. Mostly for plugin_socialgroups right now
func BuildWidgets ( zone string , data interface { } , headerVars * HeaderVars , r * http . Request ) {
if vhooks [ "intercept_build_widgets" ] != nil {
if runVhook ( "intercept_build_widgets" , zone , data , headerVars , r ) . ( bool ) {
return
}
}
//log.Print("themes[headerVars.ThemeName].Sidebars",themes[headerVars.ThemeName].Sidebars)
if themes [ headerVars . ThemeName ] . Sidebars == "right" {
if len ( docks . RightSidebar ) != 0 {
var sbody string
for _ , widget := range docks . RightSidebar {
if widget . Enabled {
if widget . Location == "global" || widget . Location == zone {
sbody += widget . Body
}
}
}
headerVars . Widgets . RightSidebar = template . HTML ( sbody )
}
}
}
2017-09-10 17:39:16 +00:00
func simpleForumUserCheck ( w http . ResponseWriter , r * http . Request , user * User , fid int ) ( headerLite * HeaderLite , success bool ) {
2017-09-10 17:05:13 +00:00
if ! fstore . Exists ( fid ) {
PreError ( "The target forum doesn't exist." , w , r )
return nil , false
}
success = true
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
if vhooks [ "simple_forum_check_pre_perms" ] != nil {
if runVhook ( "simple_forum_check_pre_perms" , w , r , user , & fid , & success , & headerLite ) . ( bool ) {
return headerLite , success
}
}
2017-09-15 22:20:01 +00:00
group , err := gstore . Get ( user . Group )
if err != nil {
PreError ( "Something weird happened" , w , r )
log . Print ( "Group #" + strconv . Itoa ( user . Group ) + " doesn't exist despite being used by User #" + strconv . Itoa ( user . ID ) )
return
}
fperms := group . Forums [ fid ]
2017-09-10 17:05:13 +00:00
if fperms . Overrides && ! user . IsSuperAdmin {
user . Perms . ViewTopic = fperms . ViewTopic
user . Perms . LikeItem = fperms . LikeItem
user . Perms . CreateTopic = fperms . CreateTopic
user . Perms . EditTopic = fperms . EditTopic
user . Perms . DeleteTopic = fperms . DeleteTopic
user . Perms . CreateReply = fperms . CreateReply
user . Perms . EditReply = fperms . EditReply
user . Perms . DeleteReply = fperms . DeleteReply
user . Perms . PinTopic = fperms . PinTopic
user . Perms . CloseTopic = fperms . CloseTopic
if len ( fperms . ExtData ) != 0 {
for name , perm := range fperms . ExtData {
user . PluginPerms [ name ] = perm
}
}
}
return headerLite , true
}
2017-09-10 17:39:16 +00:00
func forumUserCheck ( w http . ResponseWriter , r * http . Request , user * User , fid int ) ( headerVars * HeaderVars , success bool ) {
headerVars , success = UserCheck ( w , r , user )
2017-09-10 17:05:13 +00:00
if ! fstore . Exists ( fid ) {
NotFound ( w , r )
return headerVars , false
}
if vhooks [ "forum_check_pre_perms" ] != nil {
if runVhook ( "forum_check_pre_perms" , w , r , user , & fid , & success , & headerVars ) . ( bool ) {
return headerVars , success
}
}
2017-09-15 22:20:01 +00:00
group , err := gstore . Get ( user . Group )
if err != nil {
PreError ( "Something weird happened" , w , r )
log . Print ( "Group #" + strconv . Itoa ( user . Group ) + " doesn't exist despite being used by User #" + strconv . Itoa ( user . ID ) )
return
}
fperms := group . Forums [ fid ]
2017-09-10 17:05:13 +00:00
//log.Printf("user.Perms: %+v\n", user.Perms)
//log.Printf("fperms: %+v\n", fperms)
if fperms . Overrides && ! user . IsSuperAdmin {
user . Perms . ViewTopic = fperms . ViewTopic
user . Perms . LikeItem = fperms . LikeItem
user . Perms . CreateTopic = fperms . CreateTopic
user . Perms . EditTopic = fperms . EditTopic
user . Perms . DeleteTopic = fperms . DeleteTopic
user . Perms . CreateReply = fperms . CreateReply
user . Perms . EditReply = fperms . EditReply
user . Perms . DeleteReply = fperms . DeleteReply
user . Perms . PinTopic = fperms . PinTopic
user . Perms . CloseTopic = fperms . CloseTopic
if len ( fperms . ExtData ) != 0 {
for name , perm := range fperms . ExtData {
user . PluginPerms [ name ] = perm
}
}
}
return headerVars , success
}
// Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with
// TODO: Do a panel specific theme?
2017-09-10 17:39:16 +00:00
func panelUserCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , stats PanelStats , success bool ) {
2017-09-10 17:05:13 +00:00
var themeName = defaultThemeBox . Load ( ) . ( string )
cookie , err := r . Cookie ( "current_theme" )
if err == nil {
cookie := html . EscapeString ( cookie . Value )
theme , ok := themes [ cookie ]
if ok && ! theme . HideFromThemes {
themeName = cookie
}
}
headerVars = & HeaderVars {
Site : site ,
Settings : settingBox . Load ( ) . ( SettingBox ) ,
Themes : themes ,
ThemeName : themeName ,
}
// TODO: We should probably initialise headerVars.ExtData
if ! user . IsSuperMod {
NoPermissions ( w , r , * user )
return headerVars , stats , false
}
headerVars . Stylesheets = append ( headerVars . Stylesheets , headerVars . ThemeName + "/panel.css" )
2017-10-12 03:24:14 +00:00
if len ( themes [ headerVars . ThemeName ] . Resources ) > 0 {
2017-09-10 17:05:13 +00:00
rlist := themes [ headerVars . ThemeName ] . Resources
for _ , resource := range rlist {
if resource . Location == "global" || resource . Location == "panel" {
2017-10-12 03:24:14 +00:00
extarr := strings . Split ( resource . Name , "." )
ext := extarr [ len ( extarr ) - 1 ]
if ext == "css" {
2017-09-10 17:05:13 +00:00
headerVars . Stylesheets = append ( headerVars . Stylesheets , resource . Name )
2017-10-12 03:24:14 +00:00
} else if ext == "js" {
2017-09-10 17:05:13 +00:00
headerVars . Scripts = append ( headerVars . Scripts , resource . Name )
}
}
}
}
2017-09-18 17:03:52 +00:00
err = groupCountStmt . QueryRow ( ) . Scan ( & stats . Groups )
2017-09-10 17:05:13 +00:00
if err != nil {
InternalError ( err , w )
return headerVars , stats , false
}
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
stats . Users = users . GlobalCount ( )
stats . Forums = fstore . GlobalCount ( ) // TODO: Stop it from showing the blanked forums
2017-09-10 17:05:13 +00:00
stats . Settings = len ( headerVars . Settings )
stats . WordFilters = len ( wordFilterBox . Load ( ) . ( WordFilterBox ) )
stats . Themes = len ( themes )
stats . Reports = 0 // TODO: Do the report count. Only show open threads?
pusher , ok := w . ( http . Pusher )
if ok {
pusher . Push ( "/static/" + headerVars . ThemeName + "/main.css" , nil )
pusher . Push ( "/static/" + headerVars . ThemeName + "/panel.css" , nil )
pusher . Push ( "/static/global.js" , nil )
pusher . Push ( "/static/jquery-3.1.1.min.js" , nil )
// TODO: Push the theme CSS files
// TODO: Push the theme scripts
// TODO: Push avatars?
}
return headerVars , stats , true
}
2017-09-10 17:39:16 +00:00
func simplePanelUserCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerLite * HeaderLite , success bool ) {
2017-09-10 17:05:13 +00:00
if ! user . IsSuperMod {
NoPermissions ( w , r , * user )
return headerLite , false
}
headerLite = & HeaderLite {
Site : site ,
Settings : settingBox . Load ( ) . ( SettingBox ) ,
}
return headerLite , true
}
2017-09-15 22:20:01 +00:00
// TODO: Add this to the member routes
func memberCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , success bool ) {
headerVars , success = UserCheck ( w , r , user )
if ! user . Loggedin {
NoPermissions ( w , r , * user )
return headerVars , false
}
return headerVars , success
}
2017-09-10 17:39:16 +00:00
// SimpleUserCheck is back from the grave, yay :D
func simpleUserCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerLite * HeaderLite , success bool ) {
2017-09-10 17:05:13 +00:00
headerLite = & HeaderLite {
Site : site ,
Settings : settingBox . Load ( ) . ( SettingBox ) ,
}
return headerLite , true
}
// TODO: Add the ability for admins to restrict certain themes to certain groups?
2017-09-10 17:39:16 +00:00
func userCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , success bool ) {
2017-09-10 17:05:13 +00:00
var themeName = defaultThemeBox . Load ( ) . ( string )
cookie , err := r . Cookie ( "current_theme" )
if err == nil {
cookie := html . EscapeString ( cookie . Value )
theme , ok := themes [ cookie ]
if ok && ! theme . HideFromThemes {
themeName = cookie
}
}
headerVars = & HeaderVars {
Site : site ,
Settings : settingBox . Load ( ) . ( SettingBox ) ,
Themes : themes ,
ThemeName : themeName ,
}
if user . IsBanned {
headerVars . NoticeList = append ( headerVars . NoticeList , "Your account has been suspended. Some of your permissions may have been revoked." )
}
2017-10-12 03:24:14 +00:00
if len ( themes [ headerVars . ThemeName ] . Resources ) > 0 {
2017-09-10 17:05:13 +00:00
rlist := themes [ headerVars . ThemeName ] . Resources
for _ , resource := range rlist {
if resource . Location == "global" || resource . Location == "frontend" {
2017-10-12 03:24:14 +00:00
extarr := strings . Split ( resource . Name , "." )
ext := extarr [ len ( extarr ) - 1 ]
if ext == "css" {
2017-09-10 17:05:13 +00:00
headerVars . Stylesheets = append ( headerVars . Stylesheets , resource . Name )
2017-10-12 03:24:14 +00:00
} else if ext == "js" {
2017-09-10 17:05:13 +00:00
headerVars . Scripts = append ( headerVars . Scripts , resource . Name )
}
}
}
}
pusher , ok := w . ( http . Pusher )
if ok {
pusher . Push ( "/static/" + headerVars . ThemeName + "/main.css" , nil )
pusher . Push ( "/static/global.js" , nil )
pusher . Push ( "/static/jquery-3.1.1.min.js" , nil )
// TODO: Push the theme CSS files
// TODO: Push the theme scripts
// TODO: Push avatars?
}
return headerVars , true
}
func preRoute ( w http . ResponseWriter , r * http . Request ) ( User , bool ) {
user , halt := auth . SessionCheck ( w , r )
if halt {
return * user , false
}
if user == & guestUser {
return * user , true
}
host , _ , err := net . SplitHostPort ( r . RemoteAddr )
if err != nil {
PreError ( "Bad IP" , w , r )
return * user , false
}
if host != user . LastIP {
2017-09-18 17:03:52 +00:00
_ , err = updateLastIPStmt . Exec ( host , user . ID )
2017-09-10 17:05:13 +00:00
if err != nil {
InternalError ( err , w )
return * user , false
}
2017-09-22 02:21:17 +00:00
user . LastIP = host // ! - Is this racey?
2017-09-10 17:05:13 +00:00
}
h := w . Header ( )
h . Set ( "X-Frame-Options" , "deny" )
//h.Set("X-XSS-Protection", "1")
// TODO: Set the content policy header
return * user , true
}