338 lines
9.3 KiB
Go
338 lines
9.3 KiB
Go
/*
|
|
*
|
|
* Gosora Common Resources
|
|
* Copyright Azareal 2018 - 2020
|
|
*
|
|
*/
|
|
package common // import "git.tuxpa.in/a/gosora/common"
|
|
|
|
import (
|
|
"database/sql"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"runtime/debug"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
meta "git.tuxpa.in/a/gosora/common/meta"
|
|
qgen "git.tuxpa.in/a/gosora/query_gen"
|
|
)
|
|
|
|
var SoftwareVersion = Version{Major: 0, Minor: 3, Patch: 0, Tag: "dev"}
|
|
|
|
var Meta meta.MetaStore
|
|
|
|
// nolint I don't want to write comments for each of these o.o
|
|
const Hour int = 60 * 60
|
|
const Day = Hour * 24
|
|
const Week = Day * 7
|
|
const Month = Day * 30
|
|
const Year = Day * 365
|
|
const Kilobyte int = 1024
|
|
const Megabyte = Kilobyte * 1024
|
|
const Gigabyte = Megabyte * 1024
|
|
const Terabyte = Gigabyte * 1024
|
|
const Petabyte = Terabyte * 1024
|
|
|
|
var StartTime time.Time
|
|
var GzipStartEtag string
|
|
var StartEtag string
|
|
var TmplPtrMap = make(map[string]interface{})
|
|
|
|
// Anti-spam token with rotated key
|
|
var JSTokenBox atomic.Value // TODO: Move this and some of these other globals somewhere else
|
|
var SessionSigningKeyBox atomic.Value // For MFA to avoid hitting the database unneccessarily
|
|
var OldSessionSigningKeyBox atomic.Value // Just in case we've signed with a key that's about to go stale so we don't annoy the user too much
|
|
var IsDBDown int32 = 0 // 0 = false, 1 = true. this is value which should be manipulated with package atomic for representing whether the database is down so we don't spam the log with lots of redundant errors
|
|
|
|
// ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores
|
|
var ErrNoRows = sql.ErrNoRows
|
|
|
|
//var StrSlicePool sync.Pool
|
|
|
|
// ? - Make this more customisable?
|
|
/*var ExternalSites = map[string]string{
|
|
"YT": "https://www.youtube.com/",
|
|
}*/
|
|
|
|
// TODO: Make this more customisable
|
|
var SpammyDomainBits = []string{"porn", "sex", "acup", "nude", "milf", "tits", "vape", "busty", "kink", "lingerie", "strapon", "problog", "fet", "xblog", "blogin", "blognetwork", "relayblog"}
|
|
|
|
var Chrome, Firefox int // ! Temporary Hack for http push
|
|
var SimpleBots []int // ! Temporary hack to stop semrush, ahrefs, python bots and other from wasting resources
|
|
|
|
type StringList []string
|
|
|
|
// ? - Should we allow users to upload .php or .go files? It could cause security issues. We could store them with a mangled extension to render them inert
|
|
// TODO: Let admins manage this from the Control Panel
|
|
// apng is commented out for now, as we have no way of re-encoding it into a smaller file
|
|
var AllowedFileExts = StringList{
|
|
"png", "jpg", "jpe", "jpeg", "jif", "jfi", "jfif", "svg", "bmp", "gif", "tiff", "tif", "webp", "apng", "avif", "flif", "heif", "heic", "bpg", // images (encodable) + apng (browser support) + bpg + avif + flif + heif / heic
|
|
|
|
"txt", "xml", "json", "yaml", "toml", "ini", "md", "html", "rtf", "js", "py", "rb", "css", "scss", "less", "eqcss", "pcss", "java", "ts", "cs", "c", "cc", "cpp", "cxx", "C", "c++", "h", "hh", "hpp", "hxx", "h++", "rs", "rlib", "htaccess", "gitignore", /*"go","php",*/ // text
|
|
|
|
"wav", "mp3", "oga", "m4a", "flac", "ac3", "aac", "opus", // audio
|
|
|
|
"mp4", "avi", "ogg", "ogv", "ogx", "wmv", "webm", "flv", "f4v", "xvid", "mov", "movie", "qt", // video
|
|
|
|
"otf", "woff2", "woff", "ttf", "eot", // fonts
|
|
|
|
"bz2", "zip", "zipx", "gz", "7z", "tar", "cab", "rar", "kgb", "pea", "xz", "zz", "tgz", "xpi", // archives
|
|
|
|
"docx", "pdf", // documents
|
|
}
|
|
var ImageFileExts = StringList{
|
|
"png", "jpg", "jpe", "jpeg", "jif", "jfi", "jfif", "svg", "bmp", "gif", "tiff", "tif", "webp", /* "apng", "bpg", "avif", */
|
|
}
|
|
var TextFileExts = StringList{
|
|
"txt", "xml", "json", "yaml", "toml", "ini", "md", "html", "rtf", "js", "py", "rb", "css", "scss", "less", "eqcss", "pcss", "java", "ts", "cs", "c", "cc", "cpp", "cxx", "C", "c++", "h", "hh", "hpp", "hxx", "h++", "rs", "rlib", "htaccess", "gitignore", /*"go","php",*/
|
|
}
|
|
var VideoFileExts = StringList{
|
|
"mp4", "avi", "ogg", "ogv", "ogx", "wmv", "webm", "flv", "f4v", "xvid", "mov", "movie", "qt",
|
|
}
|
|
var WebVideoFileExts = StringList{
|
|
"mp4", "avi", "ogg", "ogv", "webm",
|
|
}
|
|
var WebAudioFileExts = StringList{
|
|
"wav", "mp3", "oga", "m4a", "flac",
|
|
}
|
|
var ArchiveFileExts = StringList{
|
|
"bz2", "zip", "zipx", "gz", "7z", "tar", "cab", "rar", "kgb", "pea", "xz", "zz", "tgz", "xpi",
|
|
}
|
|
var ExecutableFileExts = StringList{
|
|
"exe", "jar", "phar", "shar", "iso", "apk", "deb",
|
|
}
|
|
|
|
func init() {
|
|
JSTokenBox.Store("")
|
|
SessionSigningKeyBox.Store("")
|
|
OldSessionSigningKeyBox.Store("")
|
|
}
|
|
|
|
// TODO: Write a test for this
|
|
func (sl StringList) Contains(needle string) bool {
|
|
for _, it := range sl {
|
|
if it == needle {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
/*var DbTables []string
|
|
var TableToID = make(map[string]int)
|
|
var IDToTable = make(map[int]string)
|
|
|
|
func InitTables(acc *qgen.Accumulator) error {
|
|
stmt := acc.Select("tables").Columns("id,name").Prepare()
|
|
if e := acc.FirstError(); e != nil {
|
|
return e
|
|
}
|
|
return eachall(stmt, func(r *sql.Rows) error {
|
|
var id int
|
|
var name string
|
|
if e := r.Scan(&id, &name); e != nil {
|
|
return e
|
|
}
|
|
TableToID[name] = id
|
|
IDToTable[id] = name
|
|
return nil
|
|
})
|
|
}*/
|
|
|
|
type dbInits []func(acc *qgen.Accumulator) error
|
|
|
|
var DbInits dbInits
|
|
|
|
func (inits dbInits) Run() error {
|
|
for _, i := range inits {
|
|
if e := i(qgen.NewAcc()); e != nil {
|
|
return e
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (inits dbInits) Add(i ...func(acc *qgen.Accumulator) error) {
|
|
DbInits = dbInits(append(DbInits, i...))
|
|
}
|
|
|
|
// TODO: Add a graceful shutdown function
|
|
func StoppedServer(msg ...interface{}) {
|
|
//log.Print("stopped server")
|
|
StopServerChan <- msg
|
|
}
|
|
|
|
var StopServerChan = make(chan []interface{})
|
|
|
|
var LogWriter = io.MultiWriter(os.Stdout)
|
|
var ErrLogWriter = io.MultiWriter(os.Stderr)
|
|
var ErrLogger = log.New(os.Stderr, "", log.LstdFlags)
|
|
|
|
func DebugDetail(args ...interface{}) {
|
|
if Dev.SuperDebug {
|
|
log.Print(args...)
|
|
}
|
|
}
|
|
|
|
func DebugDetailf(str string, args ...interface{}) {
|
|
if Dev.SuperDebug {
|
|
log.Printf(str, args...)
|
|
}
|
|
}
|
|
|
|
func DebugLog(args ...interface{}) {
|
|
if Dev.DebugMode {
|
|
log.Print(args...)
|
|
}
|
|
}
|
|
|
|
func DebugLogf(str string, args ...interface{}) {
|
|
if Dev.DebugMode {
|
|
log.Printf(str, args...)
|
|
}
|
|
}
|
|
|
|
func Log(args ...interface{}) {
|
|
log.Print(args...)
|
|
}
|
|
func Logf(str string, args ...interface{}) {
|
|
log.Printf(str, args...)
|
|
}
|
|
func Err(args ...interface{}) {
|
|
ErrLogger.Print(args...)
|
|
}
|
|
|
|
func Count(stmt *sql.Stmt) (count int) {
|
|
e := stmt.QueryRow().Scan(&count)
|
|
if e != nil {
|
|
LogError(e)
|
|
}
|
|
return count
|
|
}
|
|
func Countf(stmt *sql.Stmt, args ...interface{}) (count int) {
|
|
e := stmt.QueryRow(args...).Scan(&count)
|
|
if e != nil {
|
|
LogError(e)
|
|
}
|
|
return count
|
|
}
|
|
func Createf(stmt *sql.Stmt, args ...interface{}) (id int, e error) {
|
|
res, e := stmt.Exec(args...)
|
|
if e != nil {
|
|
return 0, e
|
|
}
|
|
id64, e := res.LastInsertId()
|
|
return int(id64), e
|
|
}
|
|
|
|
func eachall(stmt *sql.Stmt, f func(r *sql.Rows) error) error {
|
|
rows, e := stmt.Query()
|
|
if e != nil {
|
|
return e
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
if e := f(rows); e != nil {
|
|
return e
|
|
}
|
|
}
|
|
return rows.Err()
|
|
}
|
|
|
|
var qcache = []string{0: "?", 1: "?,?", 2: "?,?,?", 3: "?,?,?,?", 4: "?,?,?,?,?", 5: "?,?,?,?,?,?", 6: "?,?,?,?,?,?,?", 7: "?,?,?,?,?,?,?,?", 8: "?,?,?,?,?,?,?,?,?"}
|
|
|
|
func inqbuild(ids []int) ([]interface{}, string) {
|
|
if len(ids) < 8 {
|
|
idList := make([]interface{}, len(ids))
|
|
for i, id := range ids {
|
|
idList[i] = strconv.Itoa(id)
|
|
}
|
|
return idList, qcache[len(ids)-1]
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.Grow((len(ids) * 2) - 1)
|
|
idList := make([]interface{}, len(ids))
|
|
for i, id := range ids {
|
|
idList[i] = strconv.Itoa(id)
|
|
if i == 0 {
|
|
sb.WriteRune('?')
|
|
} else {
|
|
sb.WriteString(",?")
|
|
}
|
|
}
|
|
return idList, sb.String()
|
|
}
|
|
|
|
func inqbuild2(count int) string {
|
|
if count <= 8 {
|
|
return qcache[count-1]
|
|
}
|
|
var sb strings.Builder
|
|
sb.Grow((count * 2) - 1)
|
|
for i := 0; i < count; i++ {
|
|
if i == 0 {
|
|
sb.WriteRune('?')
|
|
} else {
|
|
sb.WriteString(",?")
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
func inqbuildstr(strs []string) ([]interface{}, string) {
|
|
if len(strs) < 8 {
|
|
idList := make([]interface{}, len(strs))
|
|
for i, id := range strs {
|
|
idList[i] = id
|
|
}
|
|
return idList, qcache[len(strs)-1]
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.Grow((len(strs) * 2) - 1)
|
|
idList := make([]interface{}, len(strs))
|
|
for i, id := range strs {
|
|
idList[i] = id
|
|
if i == 0 {
|
|
sb.WriteRune('?')
|
|
} else {
|
|
sb.WriteString(",?")
|
|
}
|
|
}
|
|
return idList, sb.String()
|
|
}
|
|
|
|
var ConnWatch = &ConnWatcher{}
|
|
|
|
type ConnWatcher struct {
|
|
n int64
|
|
}
|
|
|
|
func (cw *ConnWatcher) StateChange(conn net.Conn, state http.ConnState) {
|
|
switch state {
|
|
case http.StateNew:
|
|
atomic.AddInt64(&cw.n, 1)
|
|
case http.StateHijacked, http.StateClosed:
|
|
atomic.AddInt64(&cw.n, -1)
|
|
}
|
|
}
|
|
|
|
func (cw *ConnWatcher) Count() int {
|
|
return int(atomic.LoadInt64(&cw.n))
|
|
}
|
|
|
|
func EatPanics() {
|
|
if r := recover(); r != nil {
|
|
log.Print(r)
|
|
debug.PrintStack()
|
|
log.Fatal("Fatal error.")
|
|
}
|
|
}
|