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"
"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-10-30 09:57:08 +00:00
var PanelUserCheck func ( http . ResponseWriter , * http . Request , * User ) ( * HeaderVars , PanelStats , RouteError ) = panelUserCheck
var SimplePanelUserCheck func ( http . ResponseWriter , * http . Request , * User ) ( * HeaderLite , RouteError ) = simplePanelUserCheck
var SimpleForumUserCheck func ( w http . ResponseWriter , r * http . Request , user * User , fid int ) ( headerLite * HeaderLite , err RouteError ) = simpleForumUserCheck
var ForumUserCheck func ( w http . ResponseWriter , r * http . Request , user * User , fid int ) ( headerVars * HeaderVars , err RouteError ) = forumUserCheck
var MemberCheck func ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , err RouteError ) = memberCheck
var SimpleUserCheck func ( w http . ResponseWriter , r * http . Request , user * User ) ( headerLite * HeaderLite , err RouteError ) = simpleUserCheck
var UserCheck func ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , err RouteError ) = 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-10-30 09:57:08 +00:00
func simpleForumUserCheck ( w http . ResponseWriter , r * http . Request , user * User , fid int ) ( headerLite * HeaderLite , rerr RouteError ) {
2017-09-10 17:05:13 +00:00
if ! fstore . Exists ( fid ) {
2017-10-30 09:57:08 +00:00
return nil , PreError ( "The target forum doesn't exist." , w , r )
2017-09-10 17:05:13 +00:00
}
// 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 {
2017-10-30 09:57:08 +00:00
if runVhook ( "simple_forum_check_pre_perms" , w , r , user , & fid , & rerr , & headerLite ) . ( bool ) {
return headerLite , rerr
2017-09-10 17:05:13 +00:00
}
}
2017-09-15 22:20:01 +00:00
group , err := gstore . Get ( user . Group )
if err != nil {
2017-10-30 09:57:08 +00:00
// TODO: Refactor this
log . Printf ( "Group #%d doesn't exist despite being used by User #%d" , user . Group , user . ID )
return nil , PreError ( "Something weird happened" , w , r )
2017-09-15 22:20:01 +00:00
}
fperms := group . Forums [ fid ]
2017-10-31 07:26:44 +00:00
cascadeForumPerms ( fperms , user )
2017-10-30 09:57:08 +00:00
return headerLite , nil
2017-09-10 17:05:13 +00:00
}
2017-10-30 09:57:08 +00:00
func forumUserCheck ( w http . ResponseWriter , r * http . Request , user * User , fid int ) ( headerVars * HeaderVars , ferr RouteError ) {
headerVars , ferr = UserCheck ( w , r , user )
if ferr != nil {
return headerVars , ferr
}
2017-09-10 17:05:13 +00:00
if ! fstore . Exists ( fid ) {
2017-10-30 09:57:08 +00:00
return headerVars , NotFound ( w , r )
2017-09-10 17:05:13 +00:00
}
if vhooks [ "forum_check_pre_perms" ] != nil {
2017-10-30 09:57:08 +00:00
if runVhook ( "forum_check_pre_perms" , w , r , user , & fid , & ferr , & headerVars ) . ( bool ) {
return headerVars , ferr
2017-09-10 17:05:13 +00:00
}
}
2017-09-15 22:20:01 +00:00
group , err := gstore . Get ( user . Group )
if err != nil {
2017-10-30 09:57:08 +00:00
// TODO: Refactor this
log . Printf ( "Group #%d doesn't exist despite being used by User #%d" , user . Group , user . ID )
return headerVars , PreError ( "Something weird happened" , w , r )
2017-09-15 22:20:01 +00:00
}
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)
2017-10-31 07:26:44 +00:00
cascadeForumPerms ( fperms , user )
return headerVars , ferr
}
// TODO: Put this on the user instance? Do we really want forum specific logic in there? Maybe, a method which spits a new pointer with the same contents as user?
func cascadeForumPerms ( fperms ForumPerms , user * User ) {
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
}
}
}
}
// 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-10-30 09:57:08 +00:00
func panelUserCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , stats PanelStats , rerr RouteError ) {
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
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 {
2017-10-30 09:57:08 +00:00
return headerVars , stats , InternalError ( err , w , r )
2017-09-10 17:05:13 +00:00
}
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?
}
2017-10-30 09:57:08 +00:00
return headerVars , stats , nil
2017-09-10 17:05:13 +00:00
}
2017-10-30 09:57:08 +00:00
func simplePanelUserCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerLite * HeaderLite , rerr RouteError ) {
return & HeaderLite {
2017-09-10 17:05:13 +00:00
Site : site ,
Settings : settingBox . Load ( ) . ( SettingBox ) ,
2017-10-30 09:57:08 +00:00
} , nil
2017-09-10 17:05:13 +00:00
}
2017-09-15 22:20:01 +00:00
// TODO: Add this to the member routes
2017-10-30 09:57:08 +00:00
func memberCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , rerr RouteError ) {
headerVars , rerr = UserCheck ( w , r , user )
2017-09-15 22:20:01 +00:00
if ! user . Loggedin {
2017-10-30 09:57:08 +00:00
return headerVars , NoPermissions ( w , r , * user )
2017-09-15 22:20:01 +00:00
}
2017-10-30 09:57:08 +00:00
return headerVars , rerr
2017-09-15 22:20:01 +00:00
}
2017-09-10 17:39:16 +00:00
// SimpleUserCheck is back from the grave, yay :D
2017-10-30 09:57:08 +00:00
func simpleUserCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerLite * HeaderLite , rerr RouteError ) {
2017-09-10 17:05:13 +00:00
headerLite = & HeaderLite {
Site : site ,
Settings : settingBox . Load ( ) . ( SettingBox ) ,
}
2017-10-30 09:57:08 +00:00
return headerLite , nil
2017-09-10 17:05:13 +00:00
}
// TODO: Add the ability for admins to restrict certain themes to certain groups?
2017-10-30 09:57:08 +00:00
func userCheck ( w http . ResponseWriter , r * http . Request , user * User ) ( headerVars * HeaderVars , rerr RouteError ) {
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?
}
2017-10-30 09:57:08 +00:00
return headerVars , nil
2017-09-10 17:05:13 +00:00
}
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 {
2017-10-30 09:57:08 +00:00
InternalError ( err , w , r )
2017-09-10 17:05:13 +00:00
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
}
2017-10-30 09:57:08 +00:00
// SuperModeOnly makes sure that only super mods or higher can access the panel routes
func SuperModOnly ( w http . ResponseWriter , r * http . Request , user User ) RouteError {
if ! user . IsSuperMod {
return NoPermissions ( w , r , user )
}
return nil
}