2017-11-10 03:33:11 +00:00
package common
2017-07-17 10:23:42 +00:00
2017-09-23 19:57:13 +00:00
import (
2018-06-17 07:28:18 +00:00
"encoding/json"
"io/ioutil"
"log"
2020-07-30 08:10:29 +00:00
"net/url"
2018-06-17 07:28:18 +00:00
"strconv"
2017-09-23 19:57:13 +00:00
"strings"
2020-07-30 08:10:29 +00:00
"github.com/pkg/errors"
2017-09-23 19:57:13 +00:00
)
2017-07-17 10:23:42 +00:00
2017-11-11 04:06:16 +00:00
// Site holds the basic settings which should be tweaked when setting up a site, we might move them to the settings table at some point
var Site = & site { Name : "Magical Fairy Land" , Language : "english" }
2017-07-17 10:23:42 +00:00
2017-11-11 04:06:16 +00:00
// DbConfig holds the database configuration
2018-06-17 07:28:18 +00:00
var DbConfig = & dbConfig { Host : "localhost" }
2017-11-11 04:06:16 +00:00
// Config holds the more technical settings
2018-06-17 07:28:18 +00:00
var Config = new ( config )
2017-11-11 04:06:16 +00:00
// Dev holds build flags and other things which should only be modified during developers or to gather additional test data
2018-06-17 07:28:18 +00:00
var Dev = new ( devConfig )
2017-11-11 04:06:16 +00:00
2019-04-06 01:08:49 +00:00
var PluginConfig = map [ string ] string { }
2017-11-11 04:06:16 +00:00
type site struct {
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
ShortName string
2017-11-11 04:06:16 +00:00
Name string
Email string
2017-09-13 15:09:13 +00:00
URL string
2018-01-25 04:57:33 +00:00
Host string
2019-04-01 05:44:38 +00:00
LocalHost bool // Used internally, do not modify as it will be overwritten
2017-09-10 16:57:22 +00:00
Port string
2019-04-01 05:44:38 +00:00
PortInt int // Alias for efficiency, do not modify, will be overwritten
2017-09-10 16:57:22 +00:00
EnableSsl bool
2017-07-17 10:23:42 +00:00
EnableEmails bool
2017-09-10 16:57:22 +00:00
HasProxy bool
2017-11-11 04:06:16 +00:00
Language string
2018-06-17 07:28:18 +00:00
MaxRequestSize int // Alias, do not modify, will be overwritten
2017-07-17 10:23:42 +00:00
}
2017-11-11 04:06:16 +00:00
type dbConfig struct {
2017-10-14 07:39:22 +00:00
// Production database
2018-04-22 14:27:04 +00:00
Adapter string
2017-09-10 16:57:22 +00:00
Host string
2017-07-17 10:23:42 +00:00
Username string
Password string
2017-09-10 16:57:22 +00:00
Dbname string
Port string
2017-10-14 07:39:22 +00:00
// Test database. Split this into a separate variable?
2018-04-22 14:27:04 +00:00
TestAdapter string
2017-10-14 07:39:22 +00:00
TestHost string
TestUsername string
TestPassword string
TestDbname string
TestPort string
2017-07-17 10:23:42 +00:00
}
2017-11-11 04:06:16 +00:00
type config struct {
2017-09-10 16:57:22 +00:00
SslPrivkey string
2017-07-17 10:23:42 +00:00
SslFullchain string
2018-05-27 09:36:35 +00:00
HashAlgo string // Defaults to bcrypt, and in the future, possibly something stronger
2019-11-04 11:55:52 +00:00
ConvoKey string
2017-07-17 10:23:42 +00:00
2018-06-17 07:28:18 +00:00
MaxRequestSizeStr string
2018-05-16 10:46:14 +00:00
MaxRequestSize int
2018-06-17 07:28:18 +00:00
UserCache string
2017-09-10 16:57:22 +00:00
UserCacheCapacity int
2018-06-17 07:28:18 +00:00
TopicCache string
2017-07-17 10:23:42 +00:00
TopicCacheCapacity int
2019-05-17 08:40:41 +00:00
ReplyCache string
ReplyCacheCapacity int
2017-07-17 10:23:42 +00:00
2019-03-03 04:00:04 +00:00
SMTPServer string
SMTPUsername string
SMTPPassword string
SMTPPort string
SMTPEnableTLS bool
2017-07-17 10:23:42 +00:00
2019-02-23 06:55:34 +00:00
Search string
2018-06-17 07:28:18 +00:00
DefaultPath string
DefaultGroup int // Should be a setting in the database
ActivationGroup int // Should be a setting in the database
2017-09-23 19:57:13 +00:00
StaffCSS string // ? - Move this into the settings table? Might be better to implement this as Group CSS
DefaultForum int // The forum posts go in by default, this used to be covered by the Uncategorised Forum, but we want to replace it with a more robust solution. Make this a setting?
MinifyTemplates bool
2018-04-03 04:34:07 +00:00
BuildSlugs bool // TODO: Make this a setting?
2019-04-01 05:44:38 +00:00
2019-11-04 11:55:52 +00:00
PrimaryServer bool
ServerCount int
2019-12-08 03:40:56 +00:00
LastIPCutoff int // Currently just -1, non--1, but will accept the number of months a user's last IP should be retained for in the future before being purged. Please note that the other two cutoffs below operate off the numbers of days instead.
2019-11-04 11:55:52 +00:00
PostIPCutoff int
2020-01-02 06:04:10 +00:00
PollIPCutoff int
2019-06-05 04:57:10 +00:00
LogPruneCutoff int
2020-02-07 12:27:33 +00:00
//SelfDeleteTruncCutoff int // Personal data is stripped from the mod action rows only leaving the TID and the action for later investigation.
2017-07-17 10:23:42 +00:00
2021-05-12 02:04:55 +00:00
DisableIP bool
2021-04-27 09:44:00 +00:00
DisableLastIP bool
DisablePostIP bool
DisablePollIP bool
DisableRegLog bool
DisableLoginLog bool
2020-02-07 12:27:33 +00:00
//DisableSelfDeleteLog bool
2020-01-02 06:04:10 +00:00
2018-06-24 13:49:29 +00:00
DisableLiveTopicList bool
2018-10-16 10:02:51 +00:00
DisableJSAntispam bool
2019-02-24 08:02:00 +00:00
//LooseCSP bool
2019-05-17 08:40:41 +00:00
LooseHost bool
LoosePort bool
2019-12-08 03:40:56 +00:00
SslSchema bool // Pretend we're using SSL, might be useful if a reverse-proxy terminates SSL in-front of Gosora
2019-05-17 08:40:41 +00:00
DisableServerPush bool
EnableCDNPush bool
DisableNoavatarRange bool
2019-05-03 08:34:18 +00:00
DisableDefaultNoavatar bool
2020-02-26 10:34:38 +00:00
DisableAnalytics bool
2018-06-24 13:49:29 +00:00
2019-08-14 10:39:04 +00:00
RefNoTrack bool
2019-11-04 11:55:52 +00:00
RefNoRef bool
2019-12-08 03:40:56 +00:00
NoEmbed bool
2019-08-14 10:39:04 +00:00
2020-07-30 08:10:29 +00:00
ExtraCSPOrigins string
StaticResBase string // /s/
2020-07-31 05:53:33 +00:00
//DynStaticResBase string
2020-07-31 12:45:20 +00:00
AvatarResBase string // /uploads/
2020-07-30 08:10:29 +00:00
2018-03-17 08:16:43 +00:00
Noavatar string // ? - Move this into the settings table?
ItemsPerPage int // ? - Move this into the settings table?
MaxTopicTitleLength int
MaxUsernameLength int
2019-03-16 12:50:31 +00:00
ReadTimeout int
WriteTimeout int
IdleTimeout int
2021-03-24 08:08:37 +00:00
2021-03-25 23:06:35 +00:00
LogDir string
DisableSuspLog bool
DisableBadRouteLog bool
2021-04-07 02:10:05 +00:00
DisableStdout bool
DisableStderr bool
2017-07-17 10:23:42 +00:00
}
2017-11-11 04:06:16 +00:00
type devConfig struct {
2017-09-23 21:28:50 +00:00
DebugMode bool
SuperDebug bool
TemplateDebug bool
Profiling bool
2017-10-14 07:39:22 +00:00
TestDB bool
2019-03-16 12:50:31 +00:00
NoFsnotify bool // Super Experimental!
FullReqLog bool
2019-05-27 09:00:40 +00:00
ExtraTmpls string // Experimental flag for adding compiled templates, we'll likely replace this with a better mechanism
2020-02-19 04:58:45 +00:00
//QuicPort int // Experimental!
2021-04-08 02:20:57 +00:00
2021-04-10 10:06:22 +00:00
//ExpFix1 bool // unlisted setting, experimental fix for http/1.1 conn hangs
2021-05-04 11:01:21 +00:00
LogLongTick bool // unlisted setting
LogNewLongRoute bool // unlisted setting
2017-07-17 10:23:42 +00:00
}
2017-09-23 19:57:13 +00:00
2018-06-17 07:28:18 +00:00
// configHolder is purely for having a big struct to unmarshal data into
type configHolder struct {
Site * site
Config * config
Database * dbConfig
Dev * devConfig
2019-04-06 01:08:49 +00:00
Plugin map [ string ] string
2018-06-17 07:28:18 +00:00
}
func LoadConfig ( ) error {
data , err := ioutil . ReadFile ( "./config/config.json" )
if err != nil {
return err
}
var config configHolder
err = json . Unmarshal ( data , & config )
if err != nil {
return err
}
Site = config . Site
Config = config . Config
DbConfig = config . Database
Dev = config . Dev
2019-04-06 01:08:49 +00:00
PluginConfig = config . Plugin
2018-06-17 07:28:18 +00:00
return nil
}
2020-02-07 12:27:33 +00:00
var noavatarCache200 [ ] string
var noavatarCache48 [ ] string
2020-12-17 21:13:31 +00:00
/ * var noavatarCache200Jpg [ ] string
var noavatarCache48Jpg [ ] string
var noavatarCache200Avif [ ] string
var noavatarCache48Avif [ ] string * /
2018-06-17 07:28:18 +00:00
func ProcessConfig ( ) ( err error ) {
2019-12-08 03:40:56 +00:00
// Strip these unnecessary bits, if we find them.
Site . URL = strings . TrimPrefix ( Site . URL , "http://" )
Site . URL = strings . TrimPrefix ( Site . URL , "https://" )
2018-01-25 04:57:33 +00:00
Site . Host = Site . URL
2019-04-01 05:44:38 +00:00
Site . LocalHost = Site . Host == "localhost" || Site . Host == "127.0.0.1" || Site . Host == "::1"
Site . PortInt , err = strconv . Atoi ( Site . Port )
if err != nil {
return errors . New ( "The port must be a valid integer" )
}
if Site . PortInt != 80 && Site . PortInt != 443 {
2017-11-11 04:06:16 +00:00
Site . URL = strings . TrimSuffix ( Site . URL , "/" )
Site . URL = strings . TrimSuffix ( Site . URL , "\\" )
Site . URL = strings . TrimSuffix ( Site . URL , ":" )
Site . URL = Site . URL + ":" + Site . Port
2017-09-23 19:57:13 +00:00
}
2020-08-12 23:46:10 +00:00
uurl , err := url . Parse ( Site . URL )
if err != nil {
return errors . Wrap ( err , "failed to parse Site.URL: " )
}
2019-11-04 11:55:52 +00:00
if Site . EnableSsl {
Config . SslSchema = Site . EnableSsl
}
2018-06-17 07:28:18 +00:00
if Config . DefaultPath == "" {
Config . DefaultPath = "/topics/"
}
// TODO: Bump the size of max request size up, if it's too low
Config . MaxRequestSize , err = strconv . Atoi ( Config . MaxRequestSizeStr )
if err != nil {
reqSizeStr := Config . MaxRequestSizeStr
if len ( reqSizeStr ) < 3 {
return errors . New ( "Invalid unit for MaxRequestSizeStr" )
}
quantity , err := strconv . Atoi ( reqSizeStr [ : len ( reqSizeStr ) - 2 ] )
if err != nil {
return errors . New ( "Unable to convert quantity to integer in MaxRequestSizeStr, found " + reqSizeStr [ : len ( reqSizeStr ) - 2 ] )
}
unit := reqSizeStr [ len ( reqSizeStr ) - 2 : ]
// TODO: Make it a named error just in case new errors are added in here in the future
Config . MaxRequestSize , err = FriendlyUnitToBytes ( quantity , unit )
if err != nil {
return errors . New ( "Unable to recognise unit for MaxRequestSizeStr, found " + unit )
}
}
if Dev . DebugMode {
log . Print ( "Set MaxRequestSize to " , Config . MaxRequestSize )
}
if Config . MaxRequestSize <= 0 {
log . Fatal ( "MaxRequestSize should not be zero or below" )
}
Site . MaxRequestSize = Config . MaxRequestSize
2018-03-17 08:16:43 +00:00
2020-07-30 08:10:29 +00:00
local := func ( h string ) bool {
return h == "localhost" || h == "127.0.0.1" || h == "::1" || h == Site . URL
}
2020-08-12 23:46:10 +00:00
uurl , err = url . Parse ( Config . StaticResBase )
2020-07-30 08:10:29 +00:00
if err != nil {
return errors . Wrap ( err , "failed to parse Config.StaticResBase: " )
}
host := uurl . Hostname ( )
if ! local ( host ) {
Config . ExtraCSPOrigins += " " + host
2020-07-31 05:53:33 +00:00
Config . RefNoRef = true // Avoid leaking origin data to the CDN
2020-07-30 08:10:29 +00:00
}
if Config . StaticResBase != "" {
StaticFiles . Prefix = Config . StaticResBase
}
2020-07-31 12:45:20 +00:00
uurl , err = url . Parse ( Config . AvatarResBase )
if err != nil {
return errors . Wrap ( err , "failed to parse Config.AvatarResBase: " )
}
host2 := uurl . Hostname ( )
if host != host2 && ! local ( host ) {
Config . ExtraCSPOrigins += " " + host
Config . RefNoRef = true // Avoid leaking origin data to the CDN
}
if Config . AvatarResBase == "" {
Config . AvatarResBase = "/uploads/"
}
2020-11-09 06:12:08 +00:00
2020-07-30 23:40:14 +00:00
if ! Config . DisableDefaultNoavatar {
2020-12-17 21:13:31 +00:00
cap := 11
noavatarCache200 = make ( [ ] string , cap )
noavatarCache48 = make ( [ ] string , cap )
/ * noavatarCache200Jpg = make ( [ ] string , cap )
noavatarCache48Jpg = make ( [ ] string , cap )
noavatarCache200Avif = make ( [ ] string , cap )
noavatarCache48Avif = make ( [ ] string , cap ) * /
for i := 0 ; i < cap ; i ++ {
2020-07-30 23:40:14 +00:00
noavatarCache200 [ i ] = StaticFiles . Prefix + "n" + strconv . Itoa ( i ) + "-" + strconv . Itoa ( 200 ) + ".png?i=0"
noavatarCache48 [ i ] = StaticFiles . Prefix + "n" + strconv . Itoa ( i ) + "-" + strconv . Itoa ( 48 ) + ".png?i=0"
2020-12-17 21:13:31 +00:00
/ * noavatarCache200Jpg [ i ] = StaticFiles . Prefix + "n" + strconv . Itoa ( i ) + "-" + strconv . Itoa ( 200 ) + ".jpg?i=0"
noavatarCache48Jpg [ i ] = StaticFiles . Prefix + "n" + strconv . Itoa ( i ) + "-" + strconv . Itoa ( 48 ) + ".jpg?i=0"
noavatarCache200Avif [ i ] = StaticFiles . Prefix + "n" + strconv . Itoa ( i ) + "-" + strconv . Itoa ( 200 ) + ".avif?i=0"
noavatarCache48Avif [ i ] = StaticFiles . Prefix + "n" + strconv . Itoa ( i ) + "-" + strconv . Itoa ( 48 ) + ".avif?i=0" * /
2020-07-30 23:40:14 +00:00
}
}
Config . Noavatar = strings . Replace ( Config . Noavatar , "{site_url}" , Site . URL , - 1 )
guestAvatar = GuestAvatar { buildNoavatar ( 0 , 200 ) , buildNoavatar ( 0 , 48 ) }
2020-07-30 08:10:29 +00:00
2021-05-12 02:04:55 +00:00
if Config . DisableIP {
Config . DisableLastIP = true
Config . DisablePostIP = true
Config . DisablePollIP = true
}
2019-05-08 09:50:10 +00:00
if Config . PostIPCutoff == 0 {
2021-03-24 08:08:37 +00:00
Config . PostIPCutoff = 90 // Default cutoff
2019-05-08 09:50:10 +00:00
}
2019-06-05 04:57:10 +00:00
if Config . LogPruneCutoff == 0 {
2019-12-19 07:09:56 +00:00
Config . LogPruneCutoff = 180 // Default cutoff
2019-06-05 04:57:10 +00:00
}
2019-12-31 21:57:54 +00:00
if Config . LastIPCutoff == 0 {
Config . LastIPCutoff = 3 // Default cutoff
}
if Config . LastIPCutoff > 12 {
Config . LastIPCutoff = 12
}
2020-01-02 05:28:36 +00:00
if Config . PollIPCutoff == 0 {
2020-02-19 05:00:55 +00:00
Config . PollIPCutoff = 90 // Default cutoff
2020-01-02 05:28:36 +00:00
}
2019-12-08 03:40:56 +00:00
if Config . NoEmbed {
DefaultParseSettings . NoEmbed = true
}
2019-05-08 09:50:10 +00:00
2018-03-17 08:16:43 +00:00
// ? Find a way of making these unlimited if zero? It might rule out some optimisations, waste memory, and break layouts
if Config . MaxTopicTitleLength == 0 {
Config . MaxTopicTitleLength = 100
}
if Config . MaxUsernameLength == 0 {
Config . MaxUsernameLength = 100
}
2018-07-28 12:52:23 +00:00
GuestUser . Avatar , GuestUser . MicroAvatar = BuildAvatar ( 0 , "" )
2018-05-27 09:36:35 +00:00
if Config . HashAlgo != "" {
// TODO: Set the alternate hash algo, e.g. argon2
}
2018-03-17 08:16:43 +00:00
2021-03-24 08:08:37 +00:00
if Config . LogDir == "" {
Config . LogDir = "./logs/"
}
2017-10-14 07:39:22 +00:00
// We need this in here rather than verifyConfig as switchToTestDB() currently overwrites the values it verifies
2017-11-11 04:06:16 +00:00
if DbConfig . TestDbname == DbConfig . Dbname {
2017-10-14 07:39:22 +00:00
return errors . New ( "Your test database can't have the same name as your production database" )
}
2017-11-11 04:06:16 +00:00
if Dev . TestDB {
SwitchToTestDB ( )
2017-10-14 07:39:22 +00:00
}
return nil
2017-09-23 19:57:13 +00:00
}
2019-12-08 03:40:56 +00:00
func VerifyConfig ( ) ( err error ) {
switch {
case ! Forums . Exists ( Config . DefaultForum ) :
err = errors . New ( "Invalid default forum" )
case Config . ServerCount < 1 :
err = errors . New ( "You can't have less than one server" )
case Config . MaxTopicTitleLength > 100 :
err = errors . New ( "The max topic title length cannot be over 100 as that's unable to fit in the database row" )
case Config . MaxUsernameLength > 100 :
err = errors . New ( "The max username length cannot be over 100 as that's unable to fit in the database row" )
2018-02-15 13:15:27 +00:00
}
2019-12-08 03:40:56 +00:00
return err
2017-09-23 19:57:13 +00:00
}
2017-10-14 07:39:22 +00:00
2017-11-11 04:06:16 +00:00
func SwitchToTestDB ( ) {
DbConfig . Host = DbConfig . TestHost
DbConfig . Username = DbConfig . TestUsername
DbConfig . Password = DbConfig . TestPassword
DbConfig . Dbname = DbConfig . TestDbname
DbConfig . Port = DbConfig . TestPort
2017-10-14 07:39:22 +00:00
}