2017-11-11 04:06:16 +00:00
package common
2017-09-10 16:57:22 +00:00
2017-09-10 17:05:13 +00:00
import (
2022-02-21 03:53:13 +00:00
"crypto/subtle"
"html"
"io"
"net"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"time"
2022-02-21 03:32:53 +00:00
2022-02-21 03:53:13 +00:00
"git.tuxpa.in/a/gosora/common/phrases"
"git.tuxpa.in/a/gosora/uutils"
2017-09-10 17:05:13 +00:00
)
// 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
2018-04-22 12:33:56 +00:00
var PanelUserCheck func ( http . ResponseWriter , * http . Request , * User ) ( * Header , PanelStats , RouteError ) = panelUserCheck
2017-11-11 04:06:16 +00:00
var SimplePanelUserCheck func ( http . ResponseWriter , * http . Request , * User ) ( * HeaderLite , RouteError ) = simplePanelUserCheck
2020-03-22 23:14:08 +00:00
var SimpleForumUserCheck func ( w http . ResponseWriter , r * http . Request , u * User , fid int ) ( headerLite * HeaderLite , err RouteError ) = simpleForumUserCheck
2021-05-10 07:53:52 +00:00
var ForumUserCheck func ( h * Header , w http . ResponseWriter , r * http . Request , u * User , fid int ) ( err RouteError ) = forumUserCheck
2020-03-22 23:14:08 +00:00
var SimpleUserCheck func ( w http . ResponseWriter , r * http . Request , u * User ) ( headerLite * HeaderLite , err RouteError ) = simpleUserCheck
var UserCheck func ( w http . ResponseWriter , r * http . Request , u * User ) ( h * Header , err RouteError ) = userCheck
var UserCheckNano func ( w http . ResponseWriter , r * http . Request , u * User , nano int64 ) ( h * Header , err RouteError ) = userCheck2
2020-05-26 01:46:02 +00:00
func simpleForumUserCheck ( w http . ResponseWriter , r * http . Request , u * User , fid int ) ( h * HeaderLite , rerr RouteError ) {
2022-02-21 03:32:53 +00:00
h , rerr = SimpleUserCheck ( w , r , u )
if rerr != nil {
return h , rerr
}
if ! Forums . Exists ( fid ) {
return nil , PreError ( "The target forum doesn't exist." , w , r )
}
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
/ * skip , rerr := h . Hooks . VhookSkippable ( "simple_forum_check_pre_perms" , w , r , u , & fid , h )
if skip || rerr != nil {
return h , rerr
} * /
skip , rerr := H_simple_forum_check_pre_perms_hook ( h . Hooks , w , r , u , & fid , h )
if skip || rerr != nil {
return h , rerr
}
fp , err := FPStore . Get ( fid , u . Group )
if err == ErrNoRows {
fp = BlankForumPerms ( )
} else if err != nil {
return h , InternalError ( err , w , r )
}
cascadeForumPerms ( fp , u )
return h , nil
2017-09-10 17:05:13 +00:00
}
2020-03-22 23:14:08 +00:00
func forumUserCheck ( h * Header , w http . ResponseWriter , r * http . Request , u * User , fid int ) ( rerr RouteError ) {
2022-02-21 03:32:53 +00:00
if ! Forums . Exists ( fid ) {
return NotFound ( w , r , h )
}
/ * skip , rerr := h . Hooks . VhookSkippable ( "forum_check_pre_perms" , w , r , u , & fid , h )
if skip || rerr != nil {
return rerr
} * /
/ * skip , rerr := VhookSkippableTest ( h . Hooks , "forum_check_pre_perms" , w , r , u , & fid , h )
if skip || rerr != nil {
return rerr
} * /
skip , rerr := H_forum_check_pre_perms_hook ( h . Hooks , w , r , u , & fid , h )
if skip || rerr != nil {
return rerr
}
fp , err := FPStore . Get ( fid , u . Group )
if err == ErrNoRows {
fp = BlankForumPerms ( )
} else if err != nil {
return InternalError ( err , w , r )
}
cascadeForumPerms ( fp , u )
h . CurrentUser = u // TODO: Use a pointer instead for CurrentUser, so we don't have to do this
return rerr
2017-10-31 07:26:44 +00:00
}
// 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?
2019-08-31 22:59:00 +00:00
func cascadeForumPerms ( fp * ForumPerms , u * User ) {
2022-02-21 03:32:53 +00:00
if fp . Overrides && ! u . IsSuperAdmin {
u . Perms . ViewTopic = fp . ViewTopic
u . Perms . LikeItem = fp . LikeItem
u . Perms . CreateTopic = fp . CreateTopic
u . Perms . EditTopic = fp . EditTopic
u . Perms . DeleteTopic = fp . DeleteTopic
u . Perms . CreateReply = fp . CreateReply
u . Perms . EditReply = fp . EditReply
u . Perms . DeleteReply = fp . DeleteReply
u . Perms . PinTopic = fp . PinTopic
u . Perms . CloseTopic = fp . CloseTopic
u . Perms . MoveTopic = fp . MoveTopic
if len ( fp . ExtData ) != 0 {
for name , perm := range fp . ExtData {
u . PluginPerms [ name ] = perm
}
}
}
2017-09-10 17:05:13 +00:00
}
// 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?
2020-03-22 23:14:08 +00:00
func panelUserCheck ( w http . ResponseWriter , r * http . Request , u * User ) ( h * Header , stats PanelStats , rerr RouteError ) {
2022-02-21 03:32:53 +00:00
theme := GetThemeByReq ( r )
h = & Header {
Site : Site ,
Settings : SettingBox . Load ( ) . ( SettingMap ) ,
//Themes: Themes,
ThemesSlice : ThemesSlice ,
Theme : theme ,
CurrentUser : u ,
Hooks : GetHookTable ( ) ,
Zone : "panel" ,
Writer : w ,
IsoCode : phrases . GetLangPack ( ) . IsoCode ,
//StartedAt: time.Now(),
StartedAt : uutils . Nanotime ( ) ,
}
// TODO: We should probably initialise header.ExtData
// ? - Should we only show this in debug mode? It might be useful for detecting issues in production, if we show it there as-well
//if user.IsAdmin {
//h.StartedAt = time.Now()
//}
h . AddSheet ( theme . Name + "/main.css" )
h . AddSheet ( theme . Name + "/panel.css" )
if len ( theme . Resources ) > 0 {
rlist := theme . Resources
for _ , res := range rlist {
if res . LocID == LocGlobal || res . LocID == LocPanel {
if res . Type == ResTypeSheet {
h . AddSheet ( res . Name )
} else if res . Type == ResTypeScript {
if res . Async {
h . AddScriptAsync ( res . Name )
} else {
h . AddScript ( res . Name )
}
}
}
}
}
//h := w.Header()
//h.Set("Content-Security-Policy", "default-src 'self'")
// TODO: GDPR. Add a global control panel notice warning the admins of staff members who don't have 2FA enabled
stats . Users = Users . Count ( )
stats . Groups = Groups . Count ( )
stats . Forums = Forums . Count ( )
stats . Pages = Pages . Count ( )
stats . Settings = len ( h . Settings )
stats . WordFilters = WordFilters . EstCount ( )
stats . Themes = len ( Themes )
stats . Reports = 0 // TODO: Do the report count. Only show open threads?
addPreScript := func ( name string , i int ) {
// TODO: Optimise this by removing a superfluous string alloc
if theme . OverridenMap != nil {
//fmt.Printf("name %+v\n", name)
//fmt.Printf("theme.OverridenMap %+v\n", theme.OverridenMap)
if _ , ok := theme . OverridenMap [ name ] ; ok {
tname := "_" + theme . Name
//fmt.Printf("tname %+v\n", tname)
h . AddPreScriptAsync ( "tmpl_" + name + tname + ".js" )
return
}
}
//fmt.Printf("tname %+v\n", tname)
h . AddPreScriptAsync ( ucstrs [ i ] )
}
addPreScript ( "alert" , 3 )
addPreScript ( "notice" , 4 )
return h , stats , nil
2017-09-10 17:05:13 +00:00
}
2020-03-22 23:14:08 +00:00
func simplePanelUserCheck ( w http . ResponseWriter , r * http . Request , u * User ) ( lite * HeaderLite , rerr RouteError ) {
2022-02-21 03:32:53 +00:00
return SimpleUserCheck ( w , r , u )
2017-09-15 22:20:01 +00:00
}
2017-09-10 17:39:16 +00:00
// SimpleUserCheck is back from the grave, yay :D
2020-03-22 23:14:08 +00:00
func simpleUserCheck ( w http . ResponseWriter , r * http . Request , u * User ) ( lite * HeaderLite , rerr RouteError ) {
2022-02-21 03:32:53 +00:00
return & HeaderLite {
Site : Site ,
Settings : SettingBox . Load ( ) . ( SettingMap ) ,
Hooks : GetHookTable ( ) ,
} , nil
2017-09-10 17:05:13 +00:00
}
2019-04-29 12:23:17 +00:00
func GetThemeByReq ( r * http . Request ) * Theme {
2022-02-21 03:32:53 +00:00
theme := & Theme { Name : "" }
cookie , e := r . Cookie ( "current_theme" )
if e == nil {
inTheme , ok := Themes [ html . EscapeString ( cookie . Value ) ]
if ok && ! theme . HideFromThemes {
theme = inTheme
}
}
if theme . Name == "" {
theme = Themes [ DefaultThemeBox . Load ( ) . ( string ) ]
}
return theme
2019-04-18 01:00:39 +00:00
}
2020-03-22 23:14:08 +00:00
func userCheck ( w http . ResponseWriter , r * http . Request , u * User ) ( h * Header , rerr RouteError ) {
2022-02-21 03:32:53 +00:00
return userCheck2 ( w , r , u , uutils . Nanotime ( ) )
2020-02-29 01:10:39 +00:00
}
2019-04-18 01:00:39 +00:00
// TODO: Add the ability for admins to restrict certain themes to certain groups?
// ! Be careful about firing errors off here as CustomError uses this
2020-03-22 23:14:08 +00:00
func userCheck2 ( w http . ResponseWriter , r * http . Request , u * User , nano int64 ) ( h * Header , rerr RouteError ) {
2022-02-21 03:32:53 +00:00
theme := GetThemeByReq ( r )
h = & Header {
Site : Site ,
Settings : SettingBox . Load ( ) . ( SettingMap ) ,
//Themes: Themes,
ThemesSlice : ThemesSlice ,
Theme : theme ,
CurrentUser : u , // ! Some things rely on this being a pointer downstream from this function
Hooks : GetHookTable ( ) ,
Zone : ucstrs [ 0 ] ,
Writer : w ,
IsoCode : phrases . GetLangPack ( ) . IsoCode ,
StartedAt : nano ,
}
// TODO: Optimise this by avoiding accessing a map string index
if ! u . Loggedin {
h . GoogSiteVerify = h . Settings [ "google_site_verify" ] . ( string )
}
if u . IsBanned {
h . AddNotice ( "account_banned" )
}
if u . Loggedin && ! u . Active {
h . AddNotice ( "account_inactive" )
}
/ * h . Scripts , _ = StrSlicePool . Get ( ) . ( [ ] string )
if h . Scripts != nil {
h . Scripts = h . Scripts [ : 0 ]
}
h . PreScriptsAsync , _ = StrSlicePool . Get ( ) . ( [ ] string )
if h . PreScriptsAsync != nil {
h . PreScriptsAsync = h . PreScriptsAsync [ : 0 ]
} * /
// An optimisation so we don't populate StartedAt for users who shouldn't see the stat anyway
// ? - Should we only show this in debug mode? It might be useful for detecting issues in production, if we show it there as-well
//if u.IsAdmin {
//h.StartedAt = time.Now()
//}
//PrepResources(u,h,theme)
return h , nil
2019-04-18 01:00:39 +00:00
}
2020-03-22 04:37:45 +00:00
func PrepResources ( u * User , h * Header , theme * Theme ) {
2022-02-21 03:32:53 +00:00
h . AddSheet ( theme . Name + "/main.css" )
if len ( theme . Resources ) > 0 {
rlist := theme . Resources
for _ , res := range rlist {
if res . Loggedin && ! u . Loggedin {
continue
}
if res . LocID == LocGlobal || res . LocID == LocFront {
if res . Type == ResTypeSheet {
h . AddSheet ( res . Name )
} else if res . Type == ResTypeScript {
if res . Async {
h . AddScriptAsync ( res . Name )
} else {
h . AddScript ( res . Name )
}
}
}
}
}
addPreScript := func ( name string , i int ) {
// TODO: Optimise this by removing a superfluous string alloc
if theme . OverridenMap != nil {
//fmt.Printf("name %+v\n", name)
//fmt.Printf("theme.OverridenMap %+v\n", theme.OverridenMap)
if _ , ok := theme . OverridenMap [ name ] ; ok {
tname := "_" + theme . Name
//fmt.Printf("tname %+v\n", tname)
h . AddPreScriptAsync ( "tmpl_" + name + tname + ".js" )
return
}
}
//fmt.Printf("tname %+v\n", tname)
h . AddPreScriptAsync ( ucstrs [ i ] )
}
addPreScript ( "topics_topic" , 1 )
addPreScript ( "paginator" , 2 )
addPreScript ( "alert" , 3 )
addPreScript ( "notice" , 4 )
if u . Loggedin {
addPreScript ( "topic_c_edit_post" , 5 )
addPreScript ( "topic_c_attach_item" , 6 )
addPreScript ( "topic_c_poll_input" , 7 )
}
2017-09-10 17:05:13 +00:00
}
2021-05-10 07:53:52 +00:00
func pstr ( name string ) string {
2022-02-21 03:32:53 +00:00
return "tmpl_" + name + ".js"
2021-05-10 07:53:52 +00:00
}
var ucstrs = [ ... ] string {
2022-02-21 03:32:53 +00:00
"frontend" ,
2021-05-10 07:53:52 +00:00
2022-02-21 03:32:53 +00:00
pstr ( "topics_topic" ) ,
pstr ( "paginator" ) ,
pstr ( "alert" ) ,
pstr ( "notice" ) ,
2021-05-10 07:53:52 +00:00
2022-02-21 03:32:53 +00:00
pstr ( "topic_c_edit_post" ) ,
pstr ( "topic_c_attach_item" ) ,
pstr ( "topic_c_poll_input" ) ,
2021-05-10 07:53:52 +00:00
}
2017-09-10 17:05:13 +00:00
func preRoute ( w http . ResponseWriter , r * http . Request ) ( User , bool ) {
2022-02-21 03:32:53 +00:00
userptr , halt := Auth . SessionCheck ( w , r )
if halt {
return * userptr , false
}
var usercpy * User = BlankUser ( )
* usercpy = * userptr
usercpy . Init ( ) // TODO: Can we reduce the amount of work we do here?
// TODO: Add a config setting to disable this header
// TODO: Have this header cover more things
if Config . SslSchema {
w . Header ( ) . Set ( "Content-Security-Policy" , "upgrade-insecure-requests" )
}
// TODO: WIP. Refactor this to eliminate the unnecessary query
// TODO: Better take proxies into consideration
if ! Config . DisableIP {
var host string
// TODO: Prefer Cf-Connecting-Ip header, fewer shenanigans
if Site . HasProxy {
// TODO: Check the right-most IP, might get tricky with multiple proxies, maybe have a setting for the number of hops we jump through
xForwardedFor := r . Header . Get ( "X-Forwarded-For" )
if xForwardedFor != "" {
forwardedFor := strings . Split ( xForwardedFor , "," )
// TODO: Check if this is a valid IP Address, reject if not
host = forwardedFor [ len ( forwardedFor ) - 1 ]
}
}
if host == "" {
var e error
host , _ , e = net . SplitHostPort ( r . RemoteAddr )
if e != nil {
_ = PreError ( "Bad IP" , w , r )
return * usercpy , false
}
}
if ! Config . DisableLastIP && usercpy . Loggedin && host != usercpy . GetIP ( ) {
mon := time . Now ( ) . Month ( )
e := usercpy . UpdateIP ( strconv . Itoa ( int ( mon ) ) + "-" + host )
if e != nil {
_ = InternalError ( e , w , r )
return * usercpy , false
}
}
usercpy . LastIP = host
}
return * usercpy , true
2017-09-10 17:05:13 +00:00
}
2017-10-30 09:57:08 +00:00
2021-05-10 07:53:52 +00:00
func UploadAvatar ( w http . ResponseWriter , r * http . Request , u * User , tuid int ) ( ext string , ferr RouteError ) {
2022-02-21 03:32:53 +00:00
// We don't want multiple files
// TODO: Are we doing this correctly?
filenameMap := make ( map [ string ] bool )
for _ , fheaders := range r . MultipartForm . File {
for _ , hdr := range fheaders {
if hdr . Filename == "" {
continue
}
filenameMap [ hdr . Filename ] = true
}
}
if len ( filenameMap ) > 1 {
return "" , LocalError ( "You may only upload one avatar" , w , r , u )
}
for _ , fheaders := range r . MultipartForm . File {
for _ , hdr := range fheaders {
if hdr . Filename == "" {
continue
}
inFile , err := hdr . Open ( )
if err != nil {
return "" , LocalError ( "Upload failed" , w , r , u )
}
defer inFile . Close ( )
if ext == "" {
extarr := strings . Split ( hdr . Filename , "." )
if len ( extarr ) < 2 {
return "" , LocalError ( "Bad file" , w , r , u )
}
ext = extarr [ len ( extarr ) - 1 ]
// TODO: Can we do this without a regex?
reg , err := regexp . Compile ( "[^A-Za-z0-9]+" )
if err != nil {
return "" , LocalError ( "Bad file extension" , w , r , u )
}
ext = reg . ReplaceAllString ( ext , "" )
ext = strings . ToLower ( ext )
if ! ImageFileExts . Contains ( ext ) {
return "" , LocalError ( "You can only use an image for your avatar" , w , r , u )
}
}
// TODO: Centralise this string, so we don't have to change it in two different places when it changes
outFile , err := os . Create ( "./uploads/avatar_" + strconv . Itoa ( tuid ) + "." + ext )
if err != nil {
return "" , LocalError ( "Upload failed [File Creation Failed]" , w , r , u )
}
defer outFile . Close ( )
_ , err = io . Copy ( outFile , inFile )
if err != nil {
return "" , LocalError ( "Upload failed [Copy Failed]" , w , r , u )
}
}
}
if ext == "" {
return "" , LocalError ( "No file" , w , r , u )
}
return ext , nil
2019-06-10 22:00:57 +00:00
}
2021-05-10 07:53:52 +00:00
func ChangeAvatar ( path string , w http . ResponseWriter , r * http . Request , u * User ) RouteError {
2022-02-21 03:32:53 +00:00
e := u . ChangeAvatar ( path )
if e != nil {
return InternalError ( e , w , r )
}
// Clean up the old avatar data, so we don't end up with too many dead files in /uploads/
if len ( u . RawAvatar ) > 2 {
if u . RawAvatar [ 0 ] == '.' && u . RawAvatar [ 1 ] == '.' {
e := os . Remove ( "./uploads/avatar_" + strconv . Itoa ( u . ID ) + "_tmp" + u . RawAvatar [ 1 : ] )
if e != nil && ! os . IsNotExist ( e ) {
LogWarning ( e )
return LocalError ( "Something went wrong" , w , r , u )
}
e = os . Remove ( "./uploads/avatar_" + strconv . Itoa ( u . ID ) + "_w48" + u . RawAvatar [ 1 : ] )
if e != nil && ! os . IsNotExist ( e ) {
LogWarning ( e )
return LocalError ( "Something went wrong" , w , r , u )
}
}
}
return nil
2019-06-10 22:00:57 +00:00
}
2017-12-19 03:53:13 +00:00
// SuperAdminOnly makes sure that only super admin can access certain critical panel routes
2020-03-22 23:14:08 +00:00
func SuperAdminOnly ( w http . ResponseWriter , r * http . Request , u * User ) RouteError {
2022-02-21 03:32:53 +00:00
if ! u . IsSuperAdmin {
return NoPermissions ( w , r , u )
}
return nil
2017-12-19 03:53:13 +00:00
}
2017-11-11 23:34:27 +00:00
// AdminOnly makes sure that only admins can access certain panel routes
2020-03-22 23:14:08 +00:00
func AdminOnly ( w http . ResponseWriter , r * http . Request , u * User ) RouteError {
2022-02-21 03:32:53 +00:00
if ! u . IsAdmin {
return NoPermissions ( w , r , u )
}
return nil
2017-11-11 23:34:27 +00:00
}
2017-10-30 09:57:08 +00:00
// SuperModeOnly makes sure that only super mods or higher can access the panel routes
2020-03-22 23:14:08 +00:00
func SuperModOnly ( w http . ResponseWriter , r * http . Request , u * User ) RouteError {
2022-02-21 03:32:53 +00:00
if ! u . IsSuperMod {
return NoPermissions ( w , r , u )
}
return nil
2017-10-30 09:57:08 +00:00
}
2017-11-05 09:55:34 +00:00
// MemberOnly makes sure that only logged in users can access this route
2020-03-22 23:14:08 +00:00
func MemberOnly ( w http . ResponseWriter , r * http . Request , u * User ) RouteError {
2022-02-21 03:32:53 +00:00
if ! u . Loggedin {
return LoginRequired ( w , r , u )
}
return nil
2017-11-08 07:28:33 +00:00
}
// NoBanned stops any banned users from accessing this route
2020-03-22 23:14:08 +00:00
func NoBanned ( w http . ResponseWriter , r * http . Request , u * User ) RouteError {
2022-02-21 03:32:53 +00:00
if u . IsBanned {
return Banned ( w , r , u )
}
return nil
2017-11-08 07:28:33 +00:00
}
2020-03-22 23:14:08 +00:00
func ParseForm ( w http . ResponseWriter , r * http . Request , u * User ) RouteError {
2022-02-21 03:32:53 +00:00
if e := r . ParseForm ( ) ; e != nil {
return LocalError ( "Bad Form" , w , r , u )
}
return nil
2017-11-08 07:28:33 +00:00
}
2020-03-22 23:14:08 +00:00
func NoSessionMismatch ( w http . ResponseWriter , r * http . Request , u * User ) RouteError {
2022-02-21 03:32:53 +00:00
if e := r . ParseForm ( ) ; e != nil {
return LocalError ( "Bad Form" , w , r , u )
}
if len ( u . Session ) == 0 {
return SecurityError ( w , r , u )
}
// TODO: Try to eliminate some of these allocations
sess := [ ] byte ( u . Session )
if subtle . ConstantTimeCompare ( [ ] byte ( r . FormValue ( "session" ) ) , sess ) != 1 && subtle . ConstantTimeCompare ( [ ] byte ( r . FormValue ( "s" ) ) , sess ) != 1 {
return SecurityError ( w , r , u )
}
return nil
2017-11-05 09:55:34 +00:00
}
2017-11-23 05:37:08 +00:00
func ReqIsJson ( r * http . Request ) bool {
2022-02-21 03:32:53 +00:00
return r . Header . Get ( "Content-type" ) == "application/json"
2017-11-23 05:37:08 +00:00
}
2018-01-14 12:03:20 +00:00
2020-03-22 23:14:08 +00:00
func HandleUploadRoute ( w http . ResponseWriter , r * http . Request , u * User , maxFileSize int ) RouteError {
2022-02-21 03:32:53 +00:00
// TODO: Reuse this code more
if r . ContentLength > int64 ( maxFileSize ) {
size , unit := ConvertByteUnit ( float64 ( maxFileSize ) )
return CustomError ( "Your upload is too big. Your files need to be smaller than " + strconv . Itoa ( int ( size ) ) + unit + "." , http . StatusExpectationFailed , "Error" , w , r , nil , u )
}
r . Body = http . MaxBytesReader ( w , r . Body , r . ContentLength )
e := r . ParseMultipartForm ( int64 ( Megabyte ) )
if e != nil {
return LocalError ( "Bad Form" , w , r , u )
}
return nil
2018-01-14 12:03:20 +00:00
}
2020-03-22 23:14:08 +00:00
func NoUploadSessionMismatch ( w http . ResponseWriter , r * http . Request , u * User ) RouteError {
2022-02-21 03:32:53 +00:00
if len ( u . Session ) == 0 {
return SecurityError ( w , r , u )
}
// TODO: Try to eliminate some of these allocations
sess := [ ] byte ( u . Session )
if subtle . ConstantTimeCompare ( [ ] byte ( r . FormValue ( "session" ) ) , sess ) != 1 && subtle . ConstantTimeCompare ( [ ] byte ( r . FormValue ( "s" ) ) , sess ) != 1 {
return SecurityError ( w , r , u )
}
return nil
2018-01-14 12:03:20 +00:00
}