github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/common.go (about) 1 /* 2 * 3 * Gosora Common Resources 4 * Copyright Azareal 2018 - 2020 5 * 6 */ 7 package common // import "github.com/Azareal/Gosora/common" 8 9 import ( 10 "database/sql" 11 "io" 12 "log" 13 "net" 14 "net/http" 15 "os" 16 "runtime/debug" 17 "strconv" 18 "strings" 19 "sync/atomic" 20 "time" 21 22 meta "github.com/Azareal/Gosora/common/meta" 23 qgen "github.com/Azareal/Gosora/query_gen" 24 ) 25 26 var SoftwareVersion = Version{Major: 0, Minor: 3, Patch: 0, Tag: "dev"} 27 28 var Meta meta.MetaStore 29 30 // nolint I don't want to write comments for each of these o.o 31 const Hour int = 60 * 60 32 const Day = Hour * 24 33 const Week = Day * 7 34 const Month = Day * 30 35 const Year = Day * 365 36 const Kilobyte int = 1024 37 const Megabyte = Kilobyte * 1024 38 const Gigabyte = Megabyte * 1024 39 const Terabyte = Gigabyte * 1024 40 const Petabyte = Terabyte * 1024 41 42 var StartTime time.Time 43 var GzipStartEtag string 44 var StartEtag string 45 var TmplPtrMap = make(map[string]interface{}) 46 47 // Anti-spam token with rotated key 48 var JSTokenBox atomic.Value // TODO: Move this and some of these other globals somewhere else 49 var SessionSigningKeyBox atomic.Value // For MFA to avoid hitting the database unneccessarily 50 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 51 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 52 53 // ErrNoRows is an alias of sql.ErrNoRows, just in case we end up with non-database/sql datastores 54 var ErrNoRows = sql.ErrNoRows 55 56 //var StrSlicePool sync.Pool 57 58 // ? - Make this more customisable? 59 /*var ExternalSites = map[string]string{ 60 "YT": "https://www.youtube.com/", 61 }*/ 62 63 // TODO: Make this more customisable 64 var SpammyDomainBits = []string{"porn", "sex", "acup", "nude", "milf", "tits", "vape", "busty", "kink", "lingerie", "strapon", "problog", "fet", "xblog", "blogin", "blognetwork", "relayblog"} 65 66 var Chrome, Firefox int // ! Temporary Hack for http push 67 var SimpleBots []int // ! Temporary hack to stop semrush, ahrefs, python bots and other from wasting resources 68 69 type StringList []string 70 71 // ? - 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 72 // TODO: Let admins manage this from the Control Panel 73 // apng is commented out for now, as we have no way of re-encoding it into a smaller file 74 var AllowedFileExts = StringList{ 75 "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 76 77 "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 78 79 "wav", "mp3", "oga", "m4a", "flac", "ac3", "aac", "opus", // audio 80 81 "mp4", "avi", "ogg", "ogv", "ogx", "wmv", "webm", "flv", "f4v", "xvid", "mov", "movie", "qt", // video 82 83 "otf", "woff2", "woff", "ttf", "eot", // fonts 84 85 "bz2", "zip", "zipx", "gz", "7z", "tar", "cab", "rar", "kgb", "pea", "xz", "zz", "tgz", "xpi", // archives 86 87 "docx", "pdf", // documents 88 } 89 var ImageFileExts = StringList{ 90 "png", "jpg", "jpe", "jpeg", "jif", "jfi", "jfif", "svg", "bmp", "gif", "tiff", "tif", "webp", /* "apng", "bpg", "avif", */ 91 } 92 var TextFileExts = StringList{ 93 "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",*/ 94 } 95 var VideoFileExts = StringList{ 96 "mp4", "avi", "ogg", "ogv", "ogx", "wmv", "webm", "flv", "f4v", "xvid", "mov", "movie", "qt", 97 } 98 var WebVideoFileExts = StringList{ 99 "mp4", "avi", "ogg", "ogv", "webm", 100 } 101 var WebAudioFileExts = StringList{ 102 "wav", "mp3", "oga", "m4a", "flac", 103 } 104 var ArchiveFileExts = StringList{ 105 "bz2", "zip", "zipx", "gz", "7z", "tar", "cab", "rar", "kgb", "pea", "xz", "zz", "tgz", "xpi", 106 } 107 var ExecutableFileExts = StringList{ 108 "exe", "jar", "phar", "shar", "iso", "apk", "deb", 109 } 110 111 func init() { 112 JSTokenBox.Store("") 113 SessionSigningKeyBox.Store("") 114 OldSessionSigningKeyBox.Store("") 115 } 116 117 // TODO: Write a test for this 118 func (sl StringList) Contains(needle string) bool { 119 for _, it := range sl { 120 if it == needle { 121 return true 122 } 123 } 124 return false 125 } 126 127 /*var DbTables []string 128 var TableToID = make(map[string]int) 129 var IDToTable = make(map[int]string) 130 131 func InitTables(acc *qgen.Accumulator) error { 132 stmt := acc.Select("tables").Columns("id,name").Prepare() 133 if e := acc.FirstError(); e != nil { 134 return e 135 } 136 return eachall(stmt, func(r *sql.Rows) error { 137 var id int 138 var name string 139 if e := r.Scan(&id, &name); e != nil { 140 return e 141 } 142 TableToID[name] = id 143 IDToTable[id] = name 144 return nil 145 }) 146 }*/ 147 148 type dbInits []func(acc *qgen.Accumulator) error 149 150 var DbInits dbInits 151 152 func (inits dbInits) Run() error { 153 for _, i := range inits { 154 if e := i(qgen.NewAcc()); e != nil { 155 return e 156 } 157 } 158 return nil 159 } 160 161 func (inits dbInits) Add(i ...func(acc *qgen.Accumulator) error) { 162 DbInits = dbInits(append(DbInits, i...)) 163 } 164 165 // TODO: Add a graceful shutdown function 166 func StoppedServer(msg ...interface{}) { 167 //log.Print("stopped server") 168 StopServerChan <- msg 169 } 170 171 var StopServerChan = make(chan []interface{}) 172 173 var LogWriter = io.MultiWriter(os.Stdout) 174 var ErrLogWriter = io.MultiWriter(os.Stderr) 175 var ErrLogger = log.New(os.Stderr, "", log.LstdFlags) 176 177 func DebugDetail(args ...interface{}) { 178 if Dev.SuperDebug { 179 log.Print(args...) 180 } 181 } 182 183 func DebugDetailf(str string, args ...interface{}) { 184 if Dev.SuperDebug { 185 log.Printf(str, args...) 186 } 187 } 188 189 func DebugLog(args ...interface{}) { 190 if Dev.DebugMode { 191 log.Print(args...) 192 } 193 } 194 195 func DebugLogf(str string, args ...interface{}) { 196 if Dev.DebugMode { 197 log.Printf(str, args...) 198 } 199 } 200 201 func Log(args ...interface{}) { 202 log.Print(args...) 203 } 204 func Logf(str string, args ...interface{}) { 205 log.Printf(str, args...) 206 } 207 func Err(args ...interface{}) { 208 ErrLogger.Print(args...) 209 } 210 211 func Count(stmt *sql.Stmt) (count int) { 212 e := stmt.QueryRow().Scan(&count) 213 if e != nil { 214 LogError(e) 215 } 216 return count 217 } 218 func Countf(stmt *sql.Stmt, args ...interface{}) (count int) { 219 e := stmt.QueryRow(args...).Scan(&count) 220 if e != nil { 221 LogError(e) 222 } 223 return count 224 } 225 func Createf(stmt *sql.Stmt, args ...interface{}) (id int, e error) { 226 res, e := stmt.Exec(args...) 227 if e != nil { 228 return 0, e 229 } 230 id64, e := res.LastInsertId() 231 return int(id64), e 232 } 233 234 func eachall(stmt *sql.Stmt, f func(r *sql.Rows) error) error { 235 rows, e := stmt.Query() 236 if e != nil { 237 return e 238 } 239 defer rows.Close() 240 for rows.Next() { 241 if e := f(rows); e != nil { 242 return e 243 } 244 } 245 return rows.Err() 246 } 247 248 var qcache = []string{0: "?", 1: "?,?", 2: "?,?,?", 3: "?,?,?,?", 4: "?,?,?,?,?", 5: "?,?,?,?,?,?", 6: "?,?,?,?,?,?,?", 7: "?,?,?,?,?,?,?,?", 8: "?,?,?,?,?,?,?,?,?"} 249 250 func inqbuild(ids []int) ([]interface{}, string) { 251 if len(ids) < 8 { 252 idList := make([]interface{}, len(ids)) 253 for i, id := range ids { 254 idList[i] = strconv.Itoa(id) 255 } 256 return idList, qcache[len(ids)-1] 257 } 258 259 var sb strings.Builder 260 sb.Grow((len(ids) * 2) - 1) 261 idList := make([]interface{}, len(ids)) 262 for i, id := range ids { 263 idList[i] = strconv.Itoa(id) 264 if i == 0 { 265 sb.WriteRune('?') 266 } else { 267 sb.WriteString(",?") 268 } 269 } 270 return idList, sb.String() 271 } 272 273 func inqbuild2(count int) string { 274 if count <= 8 { 275 return qcache[count-1] 276 } 277 var sb strings.Builder 278 sb.Grow((count * 2) - 1) 279 for i := 0; i < count; i++ { 280 if i == 0 { 281 sb.WriteRune('?') 282 } else { 283 sb.WriteString(",?") 284 } 285 } 286 return sb.String() 287 } 288 289 func inqbuildstr(strs []string) ([]interface{}, string) { 290 if len(strs) < 8 { 291 idList := make([]interface{}, len(strs)) 292 for i, id := range strs { 293 idList[i] = id 294 } 295 return idList, qcache[len(strs)-1] 296 } 297 298 var sb strings.Builder 299 sb.Grow((len(strs) * 2) - 1) 300 idList := make([]interface{}, len(strs)) 301 for i, id := range strs { 302 idList[i] = id 303 if i == 0 { 304 sb.WriteRune('?') 305 } else { 306 sb.WriteString(",?") 307 } 308 } 309 return idList, sb.String() 310 } 311 312 var ConnWatch = &ConnWatcher{} 313 314 type ConnWatcher struct { 315 n int64 316 } 317 318 func (cw *ConnWatcher) StateChange(conn net.Conn, state http.ConnState) { 319 switch state { 320 case http.StateNew: 321 atomic.AddInt64(&cw.n, 1) 322 case http.StateHijacked, http.StateClosed: 323 atomic.AddInt64(&cw.n, -1) 324 } 325 } 326 327 func (cw *ConnWatcher) Count() int { 328 return int(atomic.LoadInt64(&cw.n)) 329 } 330 331 func EatPanics() { 332 if r := recover(); r != nil { 333 log.Print(r) 334 debug.PrintStack() 335 log.Fatal("Fatal error.") 336 } 337 }