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  }