From 9e142d5de5dd58391ec40eb1fc4ccc85faac994c Mon Sep 17 00:00:00 2001 From: MickaelK Date: Tue, 12 Mar 2024 23:52:16 +1100 Subject: [PATCH] feature (dynamic): make configuration dynamic --- cmd/main.go | 11 +- server/common/config.go | 2 +- server/common/config_state.go | 21 +- server/common/constants.go | 29 +- server/common/plugin.go | 3 +- server/common/ssl/cert.go | 6 +- server/common/ssl/index.go | 14 +- server/common/ssl/private.go | 6 +- server/ctrl/files.go | 13 +- server/middleware/index.go | 26 +- server/model/index.go | 45 ++- server/model/webdav.go | 20 +- server/plugin/index.go | 3 +- server/plugin/plg_backend_backblaze/index.go | 17 +- server/plugin/plg_backend_ftp_only/index.go | 9 +- server/plugin/plg_backend_git/index.go | 10 +- server/plugin/plg_backend_mysql/index.go | 2 +- server/plugin/plg_backend_nfs4/index.go | 2 +- server/plugin/plg_editor_onlyoffice/index.go | 128 +++---- .../plugin/plg_handler_console/index_linux.go | 33 +- server/plugin/plg_handler_syncthing/index.go | 75 ++-- server/plugin/plg_image_light/index.go | 31 +- server/plugin/plg_search_sqlitefts/config.go | 59 ++-- server/plugin/plg_search_stateless/config.go | 5 +- server/plugin/plg_security_scanner/index.go | 329 +++++++++--------- server/plugin/plg_security_svg/index.go | 39 ++- server/plugin/plg_starter_http/index.go | 14 +- server/plugin/plg_starter_http2/index.go | 68 ++-- server/plugin/plg_starter_https/index.go | 5 +- server/plugin/plg_starter_tor/index.go | 51 +-- server/plugin/plg_video_transcoder/index.go | 97 +++--- 31 files changed, 631 insertions(+), 542 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 3b60b575..d9a492a6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -27,6 +27,11 @@ func start(routes *mux.Router) { os.Exit(1) return } + InitConfig() + InitPluginList(embed.EmbedPluginList) + for _, fn := range Hooks.Get.Onload() { + fn() + } var wg sync.WaitGroup for _, obj := range Hooks.Get.Starter() { wg.Add(1) @@ -35,11 +40,5 @@ func start(routes *mux.Router) { wg.Done() }() } - go func() { - InitPluginList(embed.EmbedPluginList) - for _, fn := range Hooks.Get.Onload() { - go fn() - } - }() wg.Wait() } diff --git a/server/common/config.go b/server/common/config.go index a5b4f1b0..3d7b7c45 100644 --- a/server/common/config.go +++ b/server/common/config.go @@ -49,7 +49,7 @@ type FormElement struct { Required bool `json:"required"` } -func init() { +func InitConfig() { Config = NewConfiguration() Config.Load() Config.Initialise() diff --git a/server/common/config_state.go b/server/common/config_state.go index 442e7fe8..61d0ee38 100644 --- a/server/common/config_state.go +++ b/server/common/config_state.go @@ -21,16 +21,17 @@ import ( "os" ) -var ( - configPath string = GetAbsolutePath(CONFIG_PATH, "config.json") - configKeysToEncrypt []string = []string{ - "middleware.identity_provider.params", - "middleware.attribute_mapping.params", - } -) +var configKeysToEncrypt []string = []string{ + "middleware.identity_provider.params", + "middleware.attribute_mapping.params", +} + +func configPath() string { + return GetAbsolutePath(CONFIG_PATH, "config.json") +} func LoadConfig() ([]byte, error) { - file, err := os.OpenFile(configPath, os.O_RDONLY, os.ModePerm) + file, err := os.OpenFile(configPath(), os.O_RDONLY, os.ModePerm) if err != nil { if os.IsNotExist(err) { os.MkdirAll(GetAbsolutePath(CONFIG_PATH), os.ModePerm) @@ -70,12 +71,12 @@ func LoadConfig() ([]byte, error) { } func SaveConfig(v []byte) error { - file, err := os.Create(configPath) + file, err := os.Create(configPath()) if err != nil { return fmt.Errorf( "Filestash needs to be able to create/edit its own configuration which it can't at the moment. "+ "Change the permission for filestash to create and edit `%s`", - configPath, + configPath(), ) } diff --git a/server/common/constants.go b/server/common/constants.go index 5b99c3a7..2430bddd 100644 --- a/server/common/constants.go +++ b/server/common/constants.go @@ -17,17 +17,32 @@ const ( ) var ( - LOG_PATH = "data/state/log/" - CONFIG_PATH = "data/state/config/" - DB_PATH = "data/state/db/" - FTS_PATH = "data/state/search/" - CERT_PATH = "data/state/certs/" - TMP_PATH = "data/cache/tmp/" + CONFIG_PATH = "state/config/" + CERT_PATH = "state/certs/" + DB_PATH = "state/db/" + FTS_PATH = "state/search/" + LOG_PATH = "state/log/" + TMP_PATH = "cache/tmp/" ) func init() { - os.MkdirAll(filepath.Join(GetCurrentDir(), LOG_PATH), os.ModePerm) + // STEP1: setup app path + rootPath := "data/" + if p := os.Getenv("FILESTASH_PATH"); p != "" { + rootPath = p + } + LOG_PATH = filepath.Join(rootPath, LOG_PATH) + CONFIG_PATH = filepath.Join(rootPath, CONFIG_PATH) + DB_PATH = filepath.Join(rootPath, DB_PATH) + FTS_PATH = filepath.Join(rootPath, FTS_PATH) + CERT_PATH = filepath.Join(rootPath, CERT_PATH) + TMP_PATH = filepath.Join(rootPath, TMP_PATH) + + // STEP2: initialise the config + os.MkdirAll(filepath.Join(GetCurrentDir(), CERT_PATH), os.ModePerm) + os.MkdirAll(filepath.Join(GetCurrentDir(), DB_PATH), os.ModePerm) os.MkdirAll(filepath.Join(GetCurrentDir(), FTS_PATH), os.ModePerm) + os.MkdirAll(filepath.Join(GetCurrentDir(), LOG_PATH), os.ModePerm) os.RemoveAll(filepath.Join(GetCurrentDir(), TMP_PATH)) os.MkdirAll(filepath.Join(GetCurrentDir(), TMP_PATH), os.ModePerm) } diff --git a/server/common/plugin.go b/server/common/plugin.go index b14d55b8..326364a2 100644 --- a/server/common/plugin.go +++ b/server/common/plugin.go @@ -1,12 +1,13 @@ package common import ( - "github.com/gorilla/mux" "io" "io/fs" "net/http" "path/filepath" "strings" + + "github.com/gorilla/mux" ) type Plugin struct { diff --git a/server/common/ssl/cert.go b/server/common/ssl/cert.go index b7be6688..2de762da 100644 --- a/server/common/ssl/cert.go +++ b/server/common/ssl/cert.go @@ -40,7 +40,7 @@ func generateNewCertificate(root *x509.Certificate, key *rsa.PrivateKey) (*x509. } func pullCertificateFromFS() (*x509.Certificate, []byte, error) { - file, err := os.OpenFile(certPEMPath, os.O_RDONLY, os.ModePerm) + file, err := os.OpenFile(certPEMPath(), os.O_RDONLY, os.ModePerm) if err != nil { return nil, nil, err } @@ -58,7 +58,7 @@ func pullCertificateFromFS() (*x509.Certificate, []byte, error) { } func saveCertificateToFS(certPEM []byte) error { - file, err := os.OpenFile(certPEMPath, os.O_WRONLY|os.O_CREATE, 0600) + file, err := os.OpenFile(certPEMPath(), os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return err } @@ -69,5 +69,5 @@ func saveCertificateToFS(certPEM []byte) error { } func clearCert() { - os.Remove(certPEMPath) + os.Remove(certPEMPath()) } diff --git a/server/common/ssl/index.go b/server/common/ssl/index.go index 382b8a43..020aa80a 100644 --- a/server/common/ssl/index.go +++ b/server/common/ssl/index.go @@ -2,14 +2,20 @@ package ssl import ( . "github.com/mickael-kerjean/filestash/server/common" - "os" ) -var keyPEMPath string = GetAbsolutePath(CERT_PATH, "key.pem") -var certPEMPath string = GetAbsolutePath(CERT_PATH, "cert.pem") +var ( + keyPEMPath func() string + certPEMPath func() string +) func init() { - os.MkdirAll(GetAbsolutePath(CERT_PATH), os.ModePerm) + keyPEMPath = func() string { + return GetAbsolutePath(CERT_PATH, "key.pem") + } + certPEMPath = func() string { + return GetAbsolutePath(CERT_PATH, "cert.pem") + } } func Clear() { diff --git a/server/common/ssl/private.go b/server/common/ssl/private.go index 4ff04be6..27d079aa 100644 --- a/server/common/ssl/private.go +++ b/server/common/ssl/private.go @@ -37,7 +37,7 @@ func generateNewPrivateKey() (*rsa.PrivateKey, []byte, error) { } func pullPrivateKeyFromFS() (*rsa.PrivateKey, []byte, error) { - file, err := os.OpenFile(keyPEMPath, os.O_RDONLY, os.ModePerm) + file, err := os.OpenFile(keyPEMPath(), os.O_RDONLY, os.ModePerm) if err != nil { return nil, nil, err } @@ -56,7 +56,7 @@ func pullPrivateKeyFromFS() (*rsa.PrivateKey, []byte, error) { } func savePrivateKeyToFS(privatePEM []byte) error { - file, err := os.OpenFile(keyPEMPath, os.O_WRONLY|os.O_CREATE, 0600) + file, err := os.OpenFile(keyPEMPath(), os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return err } @@ -69,5 +69,5 @@ func savePrivateKeyToFS(privatePEM []byte) error { } func clearPrivateKey() { - os.Remove(keyPEMPath) + os.Remove(keyPEMPath()) } diff --git a/server/ctrl/files.go b/server/ctrl/files.go index 372e8021..0f6e06e1 100644 --- a/server/ctrl/files.go +++ b/server/ctrl/files.go @@ -32,11 +32,6 @@ var ( ) func init() { - FileCache = NewAppCache() - cachePath := GetAbsolutePath(TMP_PATH) - FileCache.OnEvict(func(key string, value interface{}) { - os.RemoveAll(filepath.Join(cachePath, key)) - }) ZipTimeout = func() int { return Config.Get("features.protection.zip_timeout").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -50,7 +45,13 @@ func init() { return f }).Int() } - ZipTimeout() + FileCache = NewAppCache() + FileCache.OnEvict(func(key string, value interface{}) { + os.RemoveAll(filepath.Join(GetAbsolutePath(TMP_PATH), key)) + }) + Hooks.Register.Onload(func() { + ZipTimeout() + }) } func FileLs(ctx *App, res http.ResponseWriter, req *http.Request) { diff --git a/server/middleware/index.go b/server/middleware/index.go index 1d00dae8..fcfccc5c 100644 --- a/server/middleware/index.go +++ b/server/middleware/index.go @@ -9,6 +9,19 @@ import ( "time" ) +var telemetry = Telemetry{Data: make([]LogEntry, 0)} + +func init() { + Hooks.Register.Onload(func() { + go func() { + for { + time.Sleep(10 * time.Second) + telemetry.Flush() + } + }() + }) +} + type Middleware func(func(*App, http.ResponseWriter, *http.Request)) func(*App, http.ResponseWriter, *http.Request) func NewMiddlewareChain(fn func(*App, http.ResponseWriter, *http.Request), m []Middleware, app App) http.HandlerFunc { @@ -108,7 +121,7 @@ func Logger(ctx App, res http.ResponseWriter, req *http.Request) { RequestID: func() string { defer func() string { if r := recover(); r != nil { - Log.Debug("middleware::index get header '%s'", r) + return "oops" } return "null" }() @@ -161,14 +174,3 @@ func (this *Telemetry) Flush() { } resp.Body.Close() } - -var telemetry Telemetry = Telemetry{Data: make([]LogEntry, 0)} - -func init() { - go func() { - for { - time.Sleep(10 * time.Second) - telemetry.Flush() - } - }() -} diff --git a/server/model/index.go b/server/model/index.go index b85eacfa..a0971860 100644 --- a/server/model/index.go +++ b/server/model/index.go @@ -4,39 +4,38 @@ import ( "database/sql" . "github.com/mickael-kerjean/filestash/server/common" _ "modernc.org/sqlite" - "os" "time" ) var DB *sql.DB func init() { - cachePath := GetAbsolutePath(DB_PATH) - os.MkdirAll(cachePath, os.ModePerm) - var err error - if DB, err = sql.Open("sqlite", cachePath+"/share.sql?_fk=true"); err != nil { - Log.Error("model::index sqlite open error '%s'", err.Error()) - return - } + Hooks.Register.Onload(func() { + var err error + if DB, err = sql.Open("sqlite", GetAbsolutePath(DB_PATH)+"/share.sql?_fk=true"); err != nil { + Log.Error("model::index sqlite open error '%s'", err.Error()) + return + } - if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Location(backend VARCHAR(16), path VARCHAR(512), CONSTRAINT pk_location PRIMARY KEY(backend, path))"); err == nil { - stmt.Exec() - } - - if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Share(id VARCHAR(64) PRIMARY KEY, related_backend VARCHAR(16), related_path VARCHAR(512), params JSON, auth VARCHAR(4093) NOT NULL, FOREIGN KEY (related_backend, related_path) REFERENCES Location(backend, path) ON UPDATE CASCADE ON DELETE CASCADE)"); err == nil { - stmt.Exec() - } - - if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Verification(key VARCHAR(512), code VARCHAR(4), expire DATETIME DEFAULT (datetime('now', '+10 minutes')))"); err == nil { - stmt.Exec() - if stmt, err = DB.Prepare("CREATE INDEX idx_verification ON Verification(code, expire)"); err == nil { + if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Location(backend VARCHAR(16), path VARCHAR(512), CONSTRAINT pk_location PRIMARY KEY(backend, path))"); err == nil { stmt.Exec() } - } - go func() { - autovacuum() - }() + if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Share(id VARCHAR(64) PRIMARY KEY, related_backend VARCHAR(16), related_path VARCHAR(512), params JSON, auth VARCHAR(4093) NOT NULL, FOREIGN KEY (related_backend, related_path) REFERENCES Location(backend, path) ON UPDATE CASCADE ON DELETE CASCADE)"); err == nil { + stmt.Exec() + } + + if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Verification(key VARCHAR(512), code VARCHAR(4), expire DATETIME DEFAULT (datetime('now', '+10 minutes')))"); err == nil { + stmt.Exec() + if stmt, err = DB.Prepare("CREATE INDEX idx_verification ON Verification(code, expire)"); err == nil { + stmt.Exec() + } + } + + go func() { + autovacuum() + }() + }) } func autovacuum() { diff --git a/server/model/webdav.go b/server/model/webdav.go index 2bfdeabb..a3db484d 100644 --- a/server/model/webdav.go +++ b/server/model/webdav.go @@ -10,28 +10,20 @@ package model import ( "context" "fmt" - . "github.com/mickael-kerjean/filestash/server/common" - "github.com/mickael-kerjean/net/webdav" "io" "net/http" "os" "path/filepath" "strings" "time" + + . "github.com/mickael-kerjean/filestash/server/common" + "github.com/mickael-kerjean/net/webdav" ) -const DAVCachePath = "data/cache/webdav/" - -var ( - cachePath string - webdavCache AppCache -) +var webdavCache AppCache func init() { - cachePath = GetAbsolutePath(DAVCachePath) + "/" - os.RemoveAll(cachePath) - os.MkdirAll(cachePath, os.ModePerm) - webdavCache = NewQuickCache(20, 10) webdavCache.OnEvict(func(filename string, _ interface{}) { os.Remove(filename) @@ -64,7 +56,7 @@ func (this WebdavFs) Mkdir(ctx context.Context, name string, perm os.FileMode) e } func (this *WebdavFs) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { - cachePath := fmt.Sprintf("%stmp_%s", cachePath, Hash(this.id+name, 20)) + cachePath := filepath.Join(GetAbsolutePath(TMP_PATH), "webdav_"+Hash(this.id+name, 20)) fwriteFile := func() *os.File { if this.req.Method == "PUT" { f, err := os.OpenFile(cachePath+"_writer", os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm) @@ -119,7 +111,7 @@ func (this *WebdavFs) Stat(ctx context.Context, name string) (os.FileInfo, error this.webdavFile = &WebdavFile{ path: fullname, backend: this.backend, - cache: fmt.Sprintf("%stmp_%s", cachePath, Hash(this.id+name, 20)), + cache: filepath.Join(GetAbsolutePath(TMP_PATH), "webdav_"+Hash(this.id+name, 20)), } return this.webdavFile.Stat() } diff --git a/server/plugin/index.go b/server/plugin/index.go index 893bf967..8217e3b2 100644 --- a/server/plugin/index.go +++ b/server/plugin/index.go @@ -19,6 +19,7 @@ import ( _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_local" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_mysql" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nfs" + _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nfs4" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nop" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_s3" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_samba" @@ -39,5 +40,5 @@ import ( ) func init() { - Log.Debug("Plugin loader") + Hooks.Register.Onload(func() { Log.Debug("plugins loaded") }) } diff --git a/server/plugin/plg_backend_backblaze/index.go b/server/plugin/plg_backend_backblaze/index.go index 40ce1eb9..c047046f 100644 --- a/server/plugin/plg_backend_backblaze/index.go +++ b/server/plugin/plg_backend_backblaze/index.go @@ -6,21 +6,20 @@ import ( "encoding/base64" "encoding/json" "fmt" - . "github.com/mickael-kerjean/filestash/server/common" "io" "io/ioutil" "net/http" "net/url" "os" + "path/filepath" "strconv" "strings" "time" + + . "github.com/mickael-kerjean/filestash/server/common" ) -var ( - BackblazeCachePath string = "data/cache/tmp/" - BackblazeCache AppCache -) +var BackblazeCache AppCache type Backblaze struct { params map[string]string @@ -41,9 +40,6 @@ type BackblazeError struct { func init() { Backend.Register("backblaze", Backblaze{}) BackblazeCache = NewAppCache() - cachePath := GetAbsolutePath(BackblazeCachePath) - os.RemoveAll(cachePath) - os.MkdirAll(cachePath, os.ModePerm) } func (this Backblaze) Init(params map[string]string, app *App) (IBackend, error) { @@ -429,7 +425,10 @@ func (this Backblaze) Save(path string, file io.Reader) error { ContentLength int64 Sha1 []byte }{} - backblazeFileDetail.path = GetAbsolutePath(BackblazeCachePath + "data_" + QuickString(20) + ".dat") + backblazeFileDetail.path = filepath.Join( + GetAbsolutePath(TMP_PATH), + "data_"+QuickString(20)+".dat", + ) f, err := os.OpenFile(backblazeFileDetail.path, os.O_CREATE|os.O_RDWR, os.ModePerm) if err != nil { return err diff --git a/server/plugin/plg_backend_ftp_only/index.go b/server/plugin/plg_backend_ftp_only/index.go index 7aa140dc..16dcf97e 100644 --- a/server/plugin/plg_backend_ftp_only/index.go +++ b/server/plugin/plg_backend_ftp_only/index.go @@ -1,17 +1,18 @@ package plg_backend_ftp_only import ( - "crypto/tls" "fmt" - . "github.com/mickael-kerjean/filestash/server/common" - //"github.com/secsy/goftp" <- FTP issue with microsoft FTP - "github.com/prasad83/goftp" "io" "os" "regexp" "strconv" "strings" "time" + + . "github.com/mickael-kerjean/filestash/server/common" + + //"github.com/secsy/goftp" <- FTP issue with microsoft FTP + "github.com/prasad83/goftp" ) var FtpCache AppCache diff --git a/server/plugin/plg_backend_git/index.go b/server/plugin/plg_backend_git/index.go index 572694a4..94d94a21 100644 --- a/server/plugin/plg_backend_git/index.go +++ b/server/plugin/plg_backend_git/index.go @@ -17,8 +17,6 @@ import ( "time" ) -const GitCachePath = "data/cache/git/" - var GitCache AppCache type Git struct { @@ -29,9 +27,6 @@ func init() { Backend.Register("git", Git{}) GitCache = NewAppCache() - cachePath := GetAbsolutePath(GitCachePath) - os.RemoveAll(cachePath) - os.MkdirAll(cachePath, os.ModePerm) GitCache.OnEvict(func(key string, value interface{}) { g := value.(*Git) g.Close() @@ -94,7 +89,10 @@ func (git Git) Init(params map[string]string, app *App) (IBackend, error) { } hash := GenerateID(app) - p.basePath = GetAbsolutePath(GitCachePath + "repo_" + hash + "/") + p.basePath = filepath.Join( + GetAbsolutePath(TMP_PATH), + "git_"+hash, + ) + "/" repo, err := g.git.open(p, p.basePath) g.git.repo = repo diff --git a/server/plugin/plg_backend_mysql/index.go b/server/plugin/plg_backend_mysql/index.go index aa80c0dc..9fd596e6 100644 --- a/server/plugin/plg_backend_mysql/index.go +++ b/server/plugin/plg_backend_mysql/index.go @@ -195,7 +195,7 @@ func (this Mysql) Ls(path string) ([]os.FileInfo, error) { } rows, err := this.db.Query(fmt.Sprintf( - "SELECT CONCAT(%s) as filename %sFROM %s.%s %s LIMIT 15000", + "SELECT CONCAT(%s) as filename %sFROM %s.%s %s LIMIT 500000", func() string { q := strings.Join(extractNamePlus(sqlFields.Select), ", ' - ', ") if len(sqlFields.Esthetics) != 0 { diff --git a/server/plugin/plg_backend_nfs4/index.go b/server/plugin/plg_backend_nfs4/index.go index 4576c217..c36434f3 100644 --- a/server/plugin/plg_backend_nfs4/index.go +++ b/server/plugin/plg_backend_nfs4/index.go @@ -154,7 +154,7 @@ func (this Nfs4Share) Rm(path string) error { } func (this Nfs4Share) Mv(from string, to string) error { - return ErrNotImplemented + return this.client.Rename(from, to) } func (this Nfs4Share) Touch(path string) error { diff --git a/server/plugin/plg_editor_onlyoffice/index.go b/server/plugin/plg_editor_onlyoffice/index.go index 55e1af9e..05fb582e 100644 --- a/server/plugin/plg_editor_onlyoffice/index.go +++ b/server/plugin/plg_editor_onlyoffice/index.go @@ -23,6 +23,10 @@ import ( var ( SECRET_KEY_DERIVATE_FOR_ONLYOFFICE string OnlyOfficeCache *cache.Cache + + plugin_enable func() bool + server_url func() string + can_download func() bool ) type OnlyOfficeCacheData struct { @@ -32,7 +36,9 @@ type OnlyOfficeCacheData struct { } func init() { - plugin_enable := func() bool { + SECRET_KEY_DERIVATE_FOR_ONLYOFFICE = Hash("ONLYOFFICE_"+SECRET_KEY, len(SECRET_KEY)) + OnlyOfficeCache = cache.New(720*time.Minute, 720*time.Minute) + plugin_enable = func() bool { return Config.Get("features.office.enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} @@ -47,71 +53,73 @@ func init() { } return f }).Bool() - }() - Config.Get("features.office.onlyoffice_server").Schema(func(f *FormElement) *FormElement { - if f == nil { - f = &FormElement{} - } - f.Id = "onlyoffice_server" - f.Name = "onlyoffice_server" - f.Type = "text" - f.Description = "Location of your OnlyOffice server" - f.Default = "http://127.0.0.1:8080" - f.Placeholder = "Eg: http://127.0.0.1:8080" - if u := os.Getenv("ONLYOFFICE_URL"); u != "" { - f.Default = u - f.Placeholder = fmt.Sprintf("Default: '%s'", u) - } - return f - }) - Config.Get("features.office.can_download").Schema(func(f *FormElement) *FormElement { - if f == nil { - f = &FormElement{} - } - f.Id = "onlyoffice_can_download" - f.Name = "can_download" - f.Type = "boolean" - f.Description = "Display Download button in onlyoffice" - f.Default = true - return f - }) - - if plugin_enable == false { - return + } + server_url = func() string { + return Config.Get("features.office.onlyoffice_server").Schema(func(f *FormElement) *FormElement { + if f == nil { + f = &FormElement{} + } + f.Id = "onlyoffice_server" + f.Name = "onlyoffice_server" + f.Type = "text" + f.Description = "Location of your OnlyOffice server" + f.Default = "http://127.0.0.1:8080" + f.Placeholder = "Eg: http://127.0.0.1:8080" + if u := os.Getenv("ONLYOFFICE_URL"); u != "" { + f.Default = u + f.Placeholder = fmt.Sprintf("Default: '%s'", u) + } + return f + }).String() + } + can_download = func() bool { + return Config.Get("features.office.can_download").Schema(func(f *FormElement) *FormElement { + if f == nil { + f = &FormElement{} + } + f.Id = "onlyoffice_can_download" + f.Name = "can_download" + f.Type = "boolean" + f.Description = "Display Download button in onlyoffice" + f.Default = true + return f + }).Bool() } - SECRET_KEY_DERIVATE_FOR_ONLYOFFICE = Hash("ONLYOFFICE_"+SECRET_KEY, len(SECRET_KEY)) - Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error { - oods := r.PathPrefix("/onlyoffice").Subrouter() - oods.PathPrefix("/static/").HandlerFunc(StaticHandler).Methods("GET", "POST") - oods.HandleFunc("/event", OnlyOfficeEventHandler).Methods("POST") - oods.HandleFunc("/content", FetchContentHandler).Methods("GET") + Hooks.Register.Onload(func() { + if plugin_enable() == false { + return + } + Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error { + oods := r.PathPrefix("/onlyoffice").Subrouter() + oods.PathPrefix("/static/").HandlerFunc(StaticHandler).Methods("GET", "POST") + oods.HandleFunc("/event", OnlyOfficeEventHandler).Methods("POST") + oods.HandleFunc("/content", FetchContentHandler).Methods("GET") - r.HandleFunc( - COOKIE_PATH+"onlyoffice/iframe", - NewMiddlewareChain( - IframeContentHandler, - []Middleware{SessionStart, LoggedInOnly}, - *app, - ), - ).Methods("GET") - return nil - }) - Hooks.Register.XDGOpen(` - if(mime === "application/word" || mime === "application/msword" || - mime === "application/vnd.oasis.opendocument.text" || mime === "application/vnd.oasis.opendocument.spreadsheet" || - mime === "application/excel" || mime === "application/vnd.ms-excel" || mime === "application/powerpoint" || - mime === "application/vnd.ms-powerpoint" || mime === "application/vnd.oasis.opendocument.presentation" ) { + r.HandleFunc( + COOKIE_PATH+"onlyoffice/iframe", + NewMiddlewareChain( + IframeContentHandler, + []Middleware{SessionStart, LoggedInOnly}, + *app, + ), + ).Methods("GET") + return nil + }) + Hooks.Register.XDGOpen(` + if(mime === "application/word" || mime === "application/msword" || + mime === "application/vnd.oasis.opendocument.text" || mime === "application/vnd.oasis.opendocument.spreadsheet" || + mime === "application/excel" || mime === "application/vnd.ms-excel" || mime === "application/powerpoint" || + mime === "application/vnd.ms-powerpoint" || mime === "application/vnd.oasis.opendocument.presentation" ) { return ["appframe", {"endpoint": "/api/onlyoffice/iframe"}]; - } - `) - OnlyOfficeCache = cache.New(720*time.Minute, 720*time.Minute) + } + `) + }) } func StaticHandler(res http.ResponseWriter, req *http.Request) { req.URL.Path = strings.TrimPrefix(req.URL.Path, "/onlyoffice/static") - oodsLocation := Config.Get("features.office.onlyoffice_server").String() - u, err := url.Parse(oodsLocation) + u, err := url.Parse(server_url()) if err != nil { SendErrorResult(res, err) return @@ -161,7 +169,7 @@ func IframeContentHandler(ctx *App, res http.ResponseWriter, req *http.Request) if model.CanRead(ctx) == false { SendErrorResult(res, ErrPermissionDenied) return - } else if oodsLocation := Config.Get("features.office.onlyoffice_server").String(); oodsLocation == "" { + } else if server_url() == "" { res.WriteHeader(http.StatusServiceUnavailable) res.Write([]byte("

The Onlyoffice server hasn't been configured

")) res.Write([]byte("")) @@ -365,7 +373,7 @@ func IframeContentHandler(ctx *App, res http.ResponseWriter, req *http.Request) filetype, key, func() string { - if Config.Get("features.office.can_download").Bool() { + if can_download() { return "true" } return "false" diff --git a/server/plugin/plg_handler_console/index_linux.go b/server/plugin/plg_handler_console/index_linux.go index bd09ed47..f21ad7bf 100644 --- a/server/plugin/plg_handler_console/index_linux.go +++ b/server/plugin/plg_handler_console/index_linux.go @@ -8,11 +8,6 @@ import ( _ "embed" "encoding/base64" "encoding/json" - "github.com/creack/pty" - "github.com/gorilla/mux" - "github.com/gorilla/websocket" - . "github.com/mickael-kerjean/filestash/server/common" - "golang.org/x/crypto/bcrypt" "io" "net/http" "os" @@ -21,6 +16,13 @@ import ( "syscall" "time" "unsafe" + + . "github.com/mickael-kerjean/filestash/server/common" + + "github.com/creack/pty" + "github.com/gorilla/mux" + "github.com/gorilla/websocket" + "golang.org/x/crypto/bcrypt" ) //go:embed src/app.css @@ -47,18 +49,19 @@ var console_enable = func() bool { } func init() { - console_enable() - Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error { + Hooks.Register.Onload(func() { if console_enable() == false { - return nil + return } - r.PathPrefix("/admin/tty/").Handler( - AuthBasic( - func() (string, string) { return "admin", Config.Get("auth.admin").String() }, - TTYHandler("/admin/tty/"), - ), - ) - return nil + Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error { + r.PathPrefix("/admin/tty/").Handler( + AuthBasic( + func() (string, string) { return "admin", Config.Get("auth.admin").String() }, + TTYHandler("/admin/tty/"), + ), + ) + return nil + }) }) } diff --git a/server/plugin/plg_handler_syncthing/index.go b/server/plugin/plg_handler_syncthing/index.go index 804c09be..93b490d0 100644 --- a/server/plugin/plg_handler_syncthing/index.go +++ b/server/plugin/plg_handler_syncthing/index.go @@ -19,40 +19,53 @@ import ( const SYNCTHING_URI = "/admin/syncthing" +var ( + plugin_enable func() bool + server_url func() string +) + func init() { - plugin_enable := Config.Get("features.syncthing.enable").Schema(func(f *FormElement) *FormElement { - if f == nil { - f = &FormElement{} - } - f.Name = "enable" - f.Type = "enable" - f.Target = []string{"syncthing_server_url"} - f.Description = "Enable/Disable integration with the syncthing server. This will make your syncthing server available at `/admin/syncthing`" - f.Default = false - if u := os.Getenv("SYNCTHING_URL"); u != "" { - f.Default = true - } - return f - }).Bool() - Config.Get("features.syncthing.server_url").Schema(func(f *FormElement) *FormElement { - if f == nil { - f = &FormElement{} - } - f.Id = "syncthing_server_url" - f.Name = "server_url" - f.Type = "text" - f.Description = "Location of your Syncthing server" - f.Default = "" - f.Placeholder = "Eg: http://127.0.0.1:8080" - if u := os.Getenv("SYNCTHING_URL"); u != "" { - f.Default = u - f.Placeholder = fmt.Sprintf("Default: '%s'", u) - } - return f + plugin_enable = func() bool { + return Config.Get("features.syncthing.enable").Schema(func(f *FormElement) *FormElement { + if f == nil { + f = &FormElement{} + } + f.Name = "enable" + f.Type = "enable" + f.Target = []string{"syncthing_server_url"} + f.Description = "Enable/Disable integration with the syncthing server. This will make your syncthing server available at `/admin/syncthing`" + f.Default = false + if u := os.Getenv("SYNCTHING_URL"); u != "" { + f.Default = true + } + return f + }).Bool() + } + server_url = func() string { + return Config.Get("features.syncthing.server_url").Schema(func(f *FormElement) *FormElement { + if f == nil { + f = &FormElement{} + } + f.Id = "syncthing_server_url" + f.Name = "server_url" + f.Type = "text" + f.Description = "Location of your Syncthing server" + f.Default = "" + f.Placeholder = "Eg: http://127.0.0.1:8080" + if u := os.Getenv("SYNCTHING_URL"); u != "" { + f.Default = u + f.Placeholder = fmt.Sprintf("Default: '%s'", u) + } + return f + }).String() + } + Hooks.Register.Onload(func() { + plugin_enable() + server_url() }) Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error { - if plugin_enable == false { + if plugin_enable() == false { return nil } r.HandleFunc(SYNCTHING_URI, func(res http.ResponseWriter, req *http.Request) { @@ -121,7 +134,7 @@ func SyncthingProxyHandler(res http.ResponseWriter, req *http.Request) { } return "http" }()) - u, err := url.Parse(Config.Get("features.syncthing.server_url").String()) + u, err := url.Parse(server_url()) if err != nil { SendErrorResult(res, err) return diff --git a/server/plugin/plg_image_light/index.go b/server/plugin/plg_image_light/index.go index dd90a061..71b6db21 100644 --- a/server/plugin/plg_image_light/index.go +++ b/server/plugin/plg_image_light/index.go @@ -2,15 +2,15 @@ package plg_image_light import ( "fmt" - . "github.com/mickael-kerjean/filestash/server/common" "io" "net/http" "os" + "path/filepath" "strconv" "strings" -) -const ImageCachePath = "data/cache/image/" + . "github.com/mickael-kerjean/filestash/server/common" +) func init() { plugin_enable := func() bool { @@ -25,8 +25,6 @@ func init() { return f }).Bool() } - plugin_enable() - thumb_size := func() int { return Config.Get("features.image.thumbnail_size").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -41,8 +39,6 @@ func init() { return f }).Int() } - thumb_size() - thumb_quality := func() int { return Config.Get("features.image.thumbnail_quality").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -57,8 +53,6 @@ func init() { return f }).Int() } - thumb_quality() - thumb_caching := func() int { return Config.Get("features.image.thumbnail_caching").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -73,8 +67,6 @@ func init() { return f }).Int() } - thumb_caching() - image_quality := func() int { return Config.Get("features.image.image_quality").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -89,8 +81,6 @@ func init() { return f }).Int() } - image_quality() - image_caching := func() int { return Config.Get("features.image.image_caching").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -105,11 +95,14 @@ func init() { return f }).Int() } - image_caching() - - cachePath := GetAbsolutePath(ImageCachePath) - os.RemoveAll(cachePath) - os.MkdirAll(cachePath, os.ModePerm) + Hooks.Register.Onload(func() { + plugin_enable() + thumb_size() + thumb_quality() + thumb_caching() + image_quality() + image_caching() + }) Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) { if plugin_enable() == false { @@ -134,7 +127,7 @@ func init() { ///////////////////////// // Specify transformation transform := &Transform{ - Input: GetAbsolutePath(ImageCachePath + "imagein_" + QuickString(10)), + Input: filepath.Join(GetAbsolutePath(TMP_PATH), "imagein_"+QuickString(10)), Size: thumb_size(), Crop: true, Quality: thumb_quality(), diff --git a/server/plugin/plg_search_sqlitefts/config.go b/server/plugin/plg_search_sqlitefts/config.go index 1cfd827e..2d36fe95 100644 --- a/server/plugin/plg_search_sqlitefts/config.go +++ b/server/plugin/plg_search_sqlitefts/config.go @@ -37,7 +37,6 @@ func init() { return f }).Bool() } - SEARCH_ENABLE() SEARCH_PROCESS_MAX = func() int { return Config.Get("features.search.process_max").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -52,7 +51,6 @@ func init() { return f }).Int() } - SEARCH_PROCESS_MAX() SEARCH_PROCESS_PAR = func() int { return Config.Get("features.search.process_par").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -67,7 +65,6 @@ func init() { return f }).Int() } - SEARCH_PROCESS_PAR() SEARCH_REINDEX = func() int { return Config.Get("features.search.reindex_time").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -82,7 +79,6 @@ func init() { return f }).Int() } - SEARCH_REINDEX() CYCLE_TIME = func() int { return Config.Get("features.search.cycle_time").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -97,7 +93,6 @@ func init() { return f }).Int() } - CYCLE_TIME() MAX_INDEXING_FSIZE = func() int { return Config.Get("features.search.max_size").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -112,7 +107,6 @@ func init() { return f }).Int() } - MAX_INDEXING_FSIZE() INDEXING_EXT = func() string { return Config.Get("features.search.indexer_ext").Schema(func(f *FormElement) *FormElement { if f == nil { @@ -127,32 +121,41 @@ func init() { return f }).String() } - INDEXING_EXT() - onChange := Config.ListenForChange() - runner := func() { - startSearch := false - for { - if SEARCH_ENABLE() == false { - select { - case <-onChange.Listener: - startSearch = SEARCH_ENABLE() + Hooks.Register.Onload(func() { + SEARCH_ENABLE() + SEARCH_PROCESS_MAX() + SEARCH_PROCESS_PAR() + SEARCH_REINDEX() + CYCLE_TIME() + MAX_INDEXING_FSIZE() + INDEXING_EXT() + + onChange := Config.ListenForChange() + runner := func() { + startSearch := false + for { + if SEARCH_ENABLE() == false { + select { + case <-onChange.Listener: + startSearch = SEARCH_ENABLE() + } + if startSearch == false { + continue + } } - if startSearch == false { + sidx := SProc.Peek() + if sidx == nil { + time.Sleep(5 * time.Second) continue } + sidx.mu.Lock() + sidx.Execute() + sidx.mu.Unlock() } - sidx := SProc.Peek() - if sidx == nil { - time.Sleep(5 * time.Second) - continue - } - sidx.mu.Lock() - sidx.Execute() - sidx.mu.Unlock() } - } - for i := 0; i < SEARCH_PROCESS_PAR(); i++ { - go runner() - } + for i := 0; i < SEARCH_PROCESS_PAR(); i++ { + go runner() + } + }) } diff --git a/server/plugin/plg_search_stateless/config.go b/server/plugin/plg_search_stateless/config.go index e45c771f..6a5109a4 100644 --- a/server/plugin/plg_search_stateless/config.go +++ b/server/plugin/plg_search_stateless/config.go @@ -25,6 +25,7 @@ func init() { return f }).Int()) * time.Millisecond } - SEARCH_TIMEOUT() - + Hooks.Register.Onload(func() { + SEARCH_TIMEOUT() + }) } diff --git a/server/plugin/plg_security_scanner/index.go b/server/plugin/plg_security_scanner/index.go index e1f66b3f..34ce434a 100644 --- a/server/plugin/plg_security_scanner/index.go +++ b/server/plugin/plg_security_scanner/index.go @@ -14,22 +14,28 @@ import ( var ( gzipBomb *bytes.Buffer billionsOfLol *bytes.Buffer + + plugin_enable func() bool ) func init() { - if plugin_enable := Config.Get("features.protection.enable").Schema(func(f *FormElement) *FormElement { - if f == nil { - f = &FormElement{} - } - f.Default = true - f.Name = "enable" - f.Type = "boolean" - f.Target = []string{} - f.Description = "Enable/Disable active protection against scanners" - f.Placeholder = "Default: true" - return f - }).Bool(); plugin_enable == false { - return + Hooks.Register.Onload(setup) +} + +func setup() { + plugin_enable = func() bool { + return Config.Get("features.protection.enable").Schema(func(f *FormElement) *FormElement { + if f == nil { + f = &FormElement{} + } + f.Default = true + f.Name = "enable" + f.Type = "boolean" + f.Target = []string{} + f.Description = "Enable/Disable active protection against scanners" + f.Placeholder = "Default: true" + return f + }).Bool() } b, err := base64.StdEncoding.DecodeString("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") if err != nil { @@ -42,155 +48,160 @@ func init() { } billionsOfLol = bytes.NewBuffer(b) - Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error { - // DEFAULT - r.HandleFunc("/index.php", WelcomePackHandle) - r.PathPrefix("/html/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/public/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/webdav/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/www/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/MAMP/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/xampp/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/web/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/scripts/").Handler(http.HandlerFunc(WelcomePackHandle)) + Hooks.Register.Onload(func() { + if plugin_enable() == false { + return + } + Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error { + // DEFAULT + r.HandleFunc("/index.php", WelcomePackHandle) + r.PathPrefix("/html/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/public/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/webdav/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/www/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/MAMP/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/xampp/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/web/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/scripts/").Handler(http.HandlerFunc(WelcomePackHandle)) - // CMS - r.PathPrefix("/blog/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/cms/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wordpress/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp-admin/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp-content/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.HandleFunc("/wp-config.php", WelcomePackHandle) - r.HandleFunc("/wp-login.php", WelcomePackHandle) - r.PathPrefix("/wp1/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp2/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp3/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp4/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp5/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp6/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp7/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/wp8/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/images/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/joomla/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/libraries/joomla/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/administrator/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/components/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/templates/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/includes/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/modules/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/plugins/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.HandleFunc("/drupal/", WelcomePackHandle) - r.HandleFunc("/Drupal.php", WelcomePackHandle) - r.PathPrefix("/core/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.HandleFunc("/web.config", WelcomePackHandle) - r.HandleFunc("/server.php", WelcomePackHandle) - r.HandleFunc("/composer.json", WelcomePackHandle) - r.PathPrefix("/cacti/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/thinkphp/").Handler(http.HandlerFunc(WelcomePackHandle)) + // CMS + r.PathPrefix("/blog/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/cms/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wordpress/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp-admin/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp-content/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.HandleFunc("/wp-config.php", WelcomePackHandle) + r.HandleFunc("/wp-login.php", WelcomePackHandle) + r.PathPrefix("/wp1/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp2/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp3/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp4/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp5/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp6/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp7/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/wp8/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/images/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/joomla/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/libraries/joomla/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/administrator/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/components/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/templates/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/includes/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/modules/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/plugins/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.HandleFunc("/drupal/", WelcomePackHandle) + r.HandleFunc("/Drupal.php", WelcomePackHandle) + r.PathPrefix("/core/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.HandleFunc("/web.config", WelcomePackHandle) + r.HandleFunc("/server.php", WelcomePackHandle) + r.HandleFunc("/composer.json", WelcomePackHandle) + r.PathPrefix("/cacti/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/thinkphp/").Handler(http.HandlerFunc(WelcomePackHandle)) - // SQL - r.PathPrefix("/phpmyadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/pma/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/phpMyAdmin/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/mysqladmin/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/sql/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/myadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/mysql/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/db/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/phpmy/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/phppma/").Handler(http.HandlerFunc(WelcomePackHandle)) + // SQL + r.PathPrefix("/phpmyadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/pma/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/phpMyAdmin/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/mysqladmin/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/sql/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/myadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/mysql/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/db/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/phpmy/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/phppma/").Handler(http.HandlerFunc(WelcomePackHandle)) - // OTHER - r.HandleFunc("/echo.php", WelcomePackHandle) - r.HandleFunc("/composer.php", WelcomePackHandle) - r.HandleFunc("/uploader.php", WelcomePackHandle) - r.HandleFunc("/shell.php", WelcomePackHandle) - r.HandleFunc("/freenode-proxy-checker.txt", WelcomePackHandle) - r.HandleFunc("/cmd.php", WelcomePackHandle) - r.HandleFunc("/muhstiks.php", WelcomePackHandle) - r.HandleFunc("/muhstik.php", WelcomePackHandle) - r.HandleFunc("/jmx-console", WelcomePackHandle) - r.HandleFunc("/status.php", WelcomePackHandle) - r.PathPrefix("/TP/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/HNAP1/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/manager/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/program/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/shopdb/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/programs/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/jenkins/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.HandleFunc("/w00tw00t.at.blackhats.romanian.anti-sec:)", WelcomePackHandle) - r.HandleFunc("/judge.php", WelcomePackHandle) - r.HandleFunc("/muieblackcat", WelcomePackHandle) - r.HandleFunc("/.env", WelcomePackHandle) - r.HandleFunc("/log", WelcomePackHandle) - r.HandleFunc("/configs", WelcomePackHandle) - r.HandleFunc("/config", WelcomePackHandle) - r.HandleFunc("/cfg", WelcomePackHandle) - r.HandleFunc("/gs", WelcomePackHandle) - r.HandleFunc("/gsProvision", WelcomePackHandle) - r.HandleFunc("/overrides", WelcomePackHandle) - r.HandleFunc("/polycom", WelcomePackHandle) - r.HandleFunc("/spa.xml", WelcomePackHandle) - r.HandleFunc("/yealink", WelcomePackHandle) - r.HandleFunc("/help.php", WelcomePackHandle) - r.HandleFunc("/java.php", WelcomePackHandle) - r.HandleFunc("/_query.php", WelcomePackHandle) - r.HandleFunc("/test.php", WelcomePackHandle) - r.HandleFunc("/db_cts.php", WelcomePackHandle) - r.HandleFunc("/db_pma.php", WelcomePackHandle) - r.HandleFunc("/logon.php", WelcomePackHandle) - r.HandleFunc("/help-e.php", WelcomePackHandle) - r.HandleFunc("/license.php", WelcomePackHandle) - r.HandleFunc("/log.php", WelcomePackHandle) - r.HandleFunc("/hell.php", WelcomePackHandle) - r.HandleFunc("/pmd_online.php", WelcomePackHandle) - r.HandleFunc("/x.php", WelcomePackHandle) - r.HandleFunc("/htdocs.php", WelcomePackHandle) - r.HandleFunc("/b.php", WelcomePackHandle) - r.HandleFunc("/desktop.ini.php", WelcomePackHandle) - r.HandleFunc("/z.php", WelcomePackHandle) - r.HandleFunc("/lala.php", WelcomePackHandle) - r.HandleFunc("/lala-dpr.php", WelcomePackHandle) - r.HandleFunc("/wpc.php", WelcomePackHandle) - r.HandleFunc("/wpo.php", WelcomePackHandle) - r.HandleFunc("/t6nv.php", WelcomePackHandle) - r.HandleFunc("/text.php", WelcomePackHandle) - r.HandleFunc("/muhstik2.php", WelcomePackHandle) - r.HandleFunc("/muhstik-dpr.php", WelcomePackHandle) - r.HandleFunc("/lol.php", WelcomePackHandle) - r.HandleFunc("/cmv.php", WelcomePackHandle) - r.HandleFunc("/cmdd.php", WelcomePackHandle) - r.HandleFunc("/knal.php", WelcomePackHandle) - r.HandleFunc("/appserv.php", WelcomePackHandle) - r.HandleFunc("/d7.php", WelcomePackHandle) - r.HandleFunc("/rxr.php", WelcomePackHandle) - r.HandleFunc("/1x.php", WelcomePackHandle) - r.HandleFunc("/home.php", WelcomePackHandle) - r.HandleFunc("/undx.php", WelcomePackHandle) - r.HandleFunc("/spider.php", WelcomePackHandle) - r.HandleFunc("/payload.php", WelcomePackHandle) - r.HandleFunc("/composers.php", WelcomePackHandle) - r.HandleFunc("/izom.php", WelcomePackHandle) - r.HandleFunc("/hue2.php", WelcomePackHandle) - r.HandleFunc("/new_license.php", WelcomePackHandle) - r.HandleFunc("/up.php", WelcomePackHandle) - r.PathPrefix("/pmd/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/PMA/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/PMA2/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/pmamy/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/pmamy2/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/dbadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/tools/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/phpma/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/php-my-admin/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/websql/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.PathPrefix("/dbadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.HandleFunc("/xmlrpc.php", WelcomePackHandle) - r.PathPrefix("/user/").Handler(http.HandlerFunc(WelcomePackHandle)) - r.HandleFunc("/vuln.htm", WelcomePackHandle) - r.HandleFunc("/webconfig.txt.php", WelcomePackHandle) - return nil + // OTHER + r.HandleFunc("/echo.php", WelcomePackHandle) + r.HandleFunc("/composer.php", WelcomePackHandle) + r.HandleFunc("/uploader.php", WelcomePackHandle) + r.HandleFunc("/shell.php", WelcomePackHandle) + r.HandleFunc("/freenode-proxy-checker.txt", WelcomePackHandle) + r.HandleFunc("/cmd.php", WelcomePackHandle) + r.HandleFunc("/muhstiks.php", WelcomePackHandle) + r.HandleFunc("/muhstik.php", WelcomePackHandle) + r.HandleFunc("/jmx-console", WelcomePackHandle) + r.HandleFunc("/status.php", WelcomePackHandle) + r.PathPrefix("/TP/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/HNAP1/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/manager/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/program/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/shopdb/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/programs/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/jenkins/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.HandleFunc("/w00tw00t.at.blackhats.romanian.anti-sec:)", WelcomePackHandle) + r.HandleFunc("/judge.php", WelcomePackHandle) + r.HandleFunc("/muieblackcat", WelcomePackHandle) + r.HandleFunc("/.env", WelcomePackHandle) + r.HandleFunc("/log", WelcomePackHandle) + r.HandleFunc("/configs", WelcomePackHandle) + r.HandleFunc("/config", WelcomePackHandle) + r.HandleFunc("/cfg", WelcomePackHandle) + r.HandleFunc("/gs", WelcomePackHandle) + r.HandleFunc("/gsProvision", WelcomePackHandle) + r.HandleFunc("/overrides", WelcomePackHandle) + r.HandleFunc("/polycom", WelcomePackHandle) + r.HandleFunc("/spa.xml", WelcomePackHandle) + r.HandleFunc("/yealink", WelcomePackHandle) + r.HandleFunc("/help.php", WelcomePackHandle) + r.HandleFunc("/java.php", WelcomePackHandle) + r.HandleFunc("/_query.php", WelcomePackHandle) + r.HandleFunc("/test.php", WelcomePackHandle) + r.HandleFunc("/db_cts.php", WelcomePackHandle) + r.HandleFunc("/db_pma.php", WelcomePackHandle) + r.HandleFunc("/logon.php", WelcomePackHandle) + r.HandleFunc("/help-e.php", WelcomePackHandle) + r.HandleFunc("/license.php", WelcomePackHandle) + r.HandleFunc("/log.php", WelcomePackHandle) + r.HandleFunc("/hell.php", WelcomePackHandle) + r.HandleFunc("/pmd_online.php", WelcomePackHandle) + r.HandleFunc("/x.php", WelcomePackHandle) + r.HandleFunc("/htdocs.php", WelcomePackHandle) + r.HandleFunc("/b.php", WelcomePackHandle) + r.HandleFunc("/desktop.ini.php", WelcomePackHandle) + r.HandleFunc("/z.php", WelcomePackHandle) + r.HandleFunc("/lala.php", WelcomePackHandle) + r.HandleFunc("/lala-dpr.php", WelcomePackHandle) + r.HandleFunc("/wpc.php", WelcomePackHandle) + r.HandleFunc("/wpo.php", WelcomePackHandle) + r.HandleFunc("/t6nv.php", WelcomePackHandle) + r.HandleFunc("/text.php", WelcomePackHandle) + r.HandleFunc("/muhstik2.php", WelcomePackHandle) + r.HandleFunc("/muhstik-dpr.php", WelcomePackHandle) + r.HandleFunc("/lol.php", WelcomePackHandle) + r.HandleFunc("/cmv.php", WelcomePackHandle) + r.HandleFunc("/cmdd.php", WelcomePackHandle) + r.HandleFunc("/knal.php", WelcomePackHandle) + r.HandleFunc("/appserv.php", WelcomePackHandle) + r.HandleFunc("/d7.php", WelcomePackHandle) + r.HandleFunc("/rxr.php", WelcomePackHandle) + r.HandleFunc("/1x.php", WelcomePackHandle) + r.HandleFunc("/home.php", WelcomePackHandle) + r.HandleFunc("/undx.php", WelcomePackHandle) + r.HandleFunc("/spider.php", WelcomePackHandle) + r.HandleFunc("/payload.php", WelcomePackHandle) + r.HandleFunc("/composers.php", WelcomePackHandle) + r.HandleFunc("/izom.php", WelcomePackHandle) + r.HandleFunc("/hue2.php", WelcomePackHandle) + r.HandleFunc("/new_license.php", WelcomePackHandle) + r.HandleFunc("/up.php", WelcomePackHandle) + r.PathPrefix("/pmd/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/PMA/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/PMA2/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/pmamy/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/pmamy2/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/dbadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/tools/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/phpma/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/php-my-admin/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/websql/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.PathPrefix("/dbadmin/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.HandleFunc("/xmlrpc.php", WelcomePackHandle) + r.PathPrefix("/user/").Handler(http.HandlerFunc(WelcomePackHandle)) + r.HandleFunc("/vuln.htm", WelcomePackHandle) + r.HandleFunc("/webconfig.txt.php", WelcomePackHandle) + return nil + }) }) } diff --git a/server/plugin/plg_security_svg/index.go b/server/plugin/plg_security_svg/index.go index 3846f0a3..ccae8f89 100644 --- a/server/plugin/plg_security_svg/index.go +++ b/server/plugin/plg_security_svg/index.go @@ -8,8 +8,12 @@ import ( "regexp" ) +var ( + disable_svg func() bool +) + func init() { - disable_svg := func() bool { + disable_svg = func() bool { return Config.Get("features.protection.disable_svg").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} @@ -23,22 +27,25 @@ func init() { return f }).Bool() } - disable_svg() - - Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) { - if GetMimeType(req.URL.Query().Get("path")) != "image/svg+xml" { - return reader, nil - } else if disable_svg() == true { - return reader, ErrNotAllowed + Hooks.Register.Onload(func() { + if disable_svg() == false { + return } + Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) { + if GetMimeType(req.URL.Query().Get("path")) != "image/svg+xml" { + return reader, nil + } else if disable_svg() == true { + return reader, ErrNotAllowed + } - // XSS - (*res).Header().Set("Content-Security-Policy", "script-src 'none'; default-src 'none'; img-src 'self'") - // XML bomb - txt, _ := ioutil.ReadAll(reader) - if regexp.MustCompile("(?is)entity").Match(txt) { - txt = []byte("") - } - return NewReadCloserFromBytes(txt), nil + // XSS + (*res).Header().Set("Content-Security-Policy", "script-src 'none'; default-src 'none'; img-src 'self'") + // XML bomb + txt, _ := ioutil.ReadAll(reader) + if regexp.MustCompile("(?is)entity").Match(txt) { + txt = []byte("") + } + return NewReadCloserFromBytes(txt), nil + }) }) } diff --git a/server/plugin/plg_starter_http/index.go b/server/plugin/plg_starter_http/index.go index 5d149e7a..d3a96d00 100644 --- a/server/plugin/plg_starter_http/index.go +++ b/server/plugin/plg_starter_http/index.go @@ -2,22 +2,26 @@ package plg_starter_http import ( "fmt" - "github.com/gorilla/mux" - . "github.com/mickael-kerjean/filestash/server/common" "net/http" "time" + + . "github.com/mickael-kerjean/filestash/server/common" + + "github.com/gorilla/mux" ) func init() { - port := Config.Get("general.port").Int() - Hooks.Register.Starter(func(r *mux.Router) { Log.Info("[http] starting ...") + port := Config.Get("general.port").Int() srv := &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: r, } - go ensureAppHasBooted(fmt.Sprintf("http://127.0.0.1:%d/about", port), fmt.Sprintf("[http] listening on :%d", port)) + go ensureAppHasBooted( + fmt.Sprintf("http://127.0.0.1:%d/about", port), + fmt.Sprintf("[http] listening on :%d", port), + ) if err := srv.ListenAndServe(); err != nil { Log.Error("error: %v", err) return diff --git a/server/plugin/plg_starter_http2/index.go b/server/plugin/plg_starter_http2/index.go index 3c9c0347..5ac62db8 100644 --- a/server/plugin/plg_starter_http2/index.go +++ b/server/plugin/plg_starter_http2/index.go @@ -9,26 +9,37 @@ package plg_starter_http2 import ( "crypto/tls" "fmt" - "github.com/gorilla/mux" - . "github.com/mickael-kerjean/filestash/server/common" - "github.com/mickael-kerjean/filestash/server/common/ssl" - "golang.org/x/crypto/acme/autocert" "net/http" "os" "path/filepath" "time" + + . "github.com/mickael-kerjean/filestash/server/common" + "github.com/mickael-kerjean/filestash/server/common/ssl" + + "github.com/gorilla/mux" + "golang.org/x/crypto/acme/autocert" ) -var SSL_PATH string = GetAbsolutePath(CERT_PATH, "ssl") +var ( + SSL_PATH string + config_port func() int +) func init() { - os.MkdirAll(SSL_PATH, os.ModePerm) - domain := Config.Get("general.host").String() + config_port = func() int { + return Config.Get("general.port").Int() + } + Hooks.Register.Onload(func() { + SSL_PATH = filepath.Join(GetAbsolutePath(CERT_PATH), "ssl") + os.MkdirAll(SSL_PATH, os.ModePerm) + }) Hooks.Register.Starter(func(r *mux.Router) { + domain := Config.Get("general.host").String() Log.Info("[https] starting ...%s", domain) srv := &http.Server{ - Addr: fmt.Sprintf(":https"), + Addr: fmt.Sprintf(":%d", config_port()), Handler: r, TLSConfig: &DefaultTLSConfig, ErrorLog: NewNilLogger(), @@ -56,29 +67,32 @@ func init() { srv.TLSConfig.GetCertificate = mngr.GetCertificate } - go ensureAppHasBooted("https://127.0.0.1/about", fmt.Sprintf("[https] started")) go func() { - if err := srv.ListenAndServeTLS("", ""); err != nil { - Log.Error("[https]: listen_serve %v", err) + srv = &http.Server{ + Addr: fmt.Sprintf(":http"), + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Connection", "close") + http.Redirect( + w, + req, + "https://"+req.Host+req.URL.String(), + http.StatusMovedPermanently, + ) + }), + } + if err := srv.ListenAndServe(); err != nil { return } }() - srv := http.Server{ - Addr: fmt.Sprintf(":http"), - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Connection", "close") - http.Redirect( - w, - req, - "https://"+req.Host+req.URL.String(), - http.StatusMovedPermanently, - ) - }), - } - if err := srv.ListenAndServe(); err != nil { - Log.Error("[https]: http_redirect %v", err) + + go ensureAppHasBooted( + fmt.Sprintf("https://127.0.0.1:%d/about", config_port()), + fmt.Sprintf("[https] started"), + ) + if err := srv.ListenAndServeTLS("", ""); err != nil { + Log.Error("[https]: listen_serve %v", err) return } }) diff --git a/server/plugin/plg_starter_https/index.go b/server/plugin/plg_starter_https/index.go index b9a1ff08..5a5f0d36 100644 --- a/server/plugin/plg_starter_https/index.go +++ b/server/plugin/plg_starter_https/index.go @@ -11,10 +11,9 @@ import ( ) func init() { - domain := Config.Get("general.host").String() - port := Config.Get("general.port").Int() - Hooks.Register.Starter(func(r *mux.Router) { + domain := Config.Get("general.host").String() + port := Config.Get("general.port").Int() Log.Info("[https] starting ...%s", domain) srv := &http.Server{ Addr: fmt.Sprintf(":%d", port), diff --git a/server/plugin/plg_starter_tor/index.go b/server/plugin/plg_starter_tor/index.go index b1193a74..7fcd6448 100644 --- a/server/plugin/plg_starter_tor/index.go +++ b/server/plugin/plg_starter_tor/index.go @@ -7,15 +7,16 @@ import ( . "github.com/mickael-kerjean/filestash/server/common" "net/http" "os" - "path/filepath" "time" ) -var TOR_PATH string = GetAbsolutePath(CERT_PATH, "tor") +var ( + enable_plugin func() bool + tor_url func() string +) func init() { - os.MkdirAll(TOR_PATH, os.ModePerm) - enable_tor := func() bool { + enable_plugin = func() bool { return Config.Get("features.server.tor_enable").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} @@ -29,29 +30,37 @@ func init() { return f }).Bool() } - enable_tor() - Config.Get("features.server.tor_url").Schema(func(f *FormElement) *FormElement { - if f == nil { - f = &FormElement{} - } - f.Id = "tor_url" - f.Name = "tor_url" - f.Type = "text" - f.Target = []string{} - f.Description = "Your onion site" - f.ReadOnly = true - f.Placeholder = "LOADING... Refresh the page in a few seconds" - return f - }) + tor_url = func() string { + return Config.Get("features.server.tor_url").Schema(func(f *FormElement) *FormElement { + if f == nil { + f = &FormElement{} + } + f.Id = "tor_url" + f.Name = "tor_url" + f.Type = "text" + f.Target = []string{} + f.Description = "Your onion site" + f.ReadOnly = true + f.Placeholder = "LOADING... Refresh the page in a few seconds" + return f + }).String() + } + Hooks.Register.Onload(func() { + tor_url() + enable_plugin() + }) Hooks.Register.Starter(func(r *mux.Router) { - if enable_tor() == false { + torPath := GetAbsolutePath(CERT_PATH, "tor") + os.MkdirAll(torPath, os.ModePerm) + + if enable_plugin() == false { startTor := false onChange := Config.ListenForChange() for { select { case <-onChange.Listener: - startTor = enable_tor() + startTor = enable_plugin() } if startTor == true { break @@ -62,7 +71,7 @@ func init() { Log.Info("[tor] starting ...") t, err := tor.Start(nil, &tor.StartConf{ - DataDir: TOR_PATH, + DataDir: torPath, }) if err != nil { Log.Error("[tor] Unable to start Tor: %v", err) diff --git a/server/plugin/plg_video_transcoder/index.go b/server/plugin/plg_video_transcoder/index.go index 9d24602e..e96dce0a 100644 --- a/server/plugin/plg_video_transcoder/index.go +++ b/server/plugin/plg_video_transcoder/index.go @@ -2,8 +2,10 @@ package plg_video_transcoder import ( "bytes" + "context" "encoding/base64" "encoding/json" + "errors" "fmt" "io" "math" @@ -20,11 +22,17 @@ import ( ) const ( - HLS_SEGMENT_LENGTH = 30 + HLS_SEGMENT_LENGTH = 5 + FPS = 30 CLEAR_CACHE_AFTER = 12 VideoCachePath = "data/cache/video/" ) +var ( + plugin_enable func() bool + blacklist_format func() string +) + func init() { ffmpegIsInstalled := false ffprobeIsInstalled := false @@ -34,7 +42,7 @@ func init() { if _, err := exec.LookPath("ffprobe"); err == nil { ffprobeIsInstalled = true } - plugin_enable := func() bool { + plugin_enable = func() bool { return Config.Get("features.video.enable_transcoder").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} @@ -50,8 +58,7 @@ func init() { return f }).Bool() } - - blacklist_format := func() string { + blacklist_format = func() string { return Config.Get("features.video.blacklist_format").Schema(func(f *FormElement) *FormElement { if f == nil { f = &FormElement{} @@ -67,50 +74,52 @@ func init() { return f }).String() } - blacklist_format() - if plugin_enable() == false { - return - } else if ffmpegIsInstalled == false { - Log.Warning("[plugin video transcoder] ffmpeg needs to be installed") - return - } else if ffprobeIsInstalled == false { - Log.Warning("[plugin video transcoder] ffprobe needs to be installed") - return - } + Hooks.Register.Onload(func() { + blacklist_format() + if plugin_enable() == false { + return + } else if ffmpegIsInstalled == false { + Log.Warning("[plugin video transcoder] ffmpeg needs to be installed") + return + } else if ffprobeIsInstalled == false { + Log.Warning("[plugin video transcoder] ffprobe needs to be installed") + return + } - cachePath := GetAbsolutePath(VideoCachePath) - os.RemoveAll(cachePath) - os.MkdirAll(cachePath, os.ModePerm) + cachePath := GetAbsolutePath(VideoCachePath) + os.RemoveAll(cachePath) + os.MkdirAll(cachePath, os.ModePerm) - Hooks.Register.ProcessFileContentBeforeSend(hls_playlist) - Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error { - r.PathPrefix("/hls/hls_{segment}.ts").Handler(NewMiddlewareChain( - hls_transcode, - []Middleware{SecureHeaders}, - *app, - )).Methods("GET") - return nil - }) - - Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error { - r.HandleFunc(OverrideVideoSourceMapper, func(res http.ResponseWriter, req *http.Request) { - res.Header().Set("Content-Type", GetMimeType(req.URL.String())) - res.Write([]byte(`window.overrides["video-map-sources"] = function(sources){`)) - res.Write([]byte(` return sources.map(function(source){`)) - - blacklists := strings.Split(blacklist_format(), ",") - for i := 0; i < len(blacklists); i++ { - blacklists[i] = strings.TrimSpace(blacklists[i]) - res.Write([]byte(fmt.Sprintf(`if(source.type == "%s"){ return source; } `, GetMimeType("."+blacklists[i])))) - } - res.Write([]byte(` source.src = source.src + "&transcode=hls";`)) - res.Write([]byte(` source.type = "application/x-mpegURL";`)) - res.Write([]byte(` return source;`)) - res.Write([]byte(` })`)) - res.Write([]byte(`}`)) + Hooks.Register.ProcessFileContentBeforeSend(hls_playlist) + Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error { + r.PathPrefix("/hls/hls_{segment}.ts").Handler(NewMiddlewareChain( + hls_transcode, + []Middleware{SecureHeaders}, + *app, + )).Methods("GET") + return nil + }) + + Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error { + r.HandleFunc(OverrideVideoSourceMapper, func(res http.ResponseWriter, req *http.Request) { + res.Header().Set("Content-Type", GetMimeType(req.URL.String())) + res.Write([]byte(`window.overrides["video-map-sources"] = function(sources){`)) + res.Write([]byte(` return sources.map(function(source){`)) + + blacklists := strings.Split(blacklist_format(), ",") + for i := 0; i < len(blacklists); i++ { + blacklists[i] = strings.TrimSpace(blacklists[i]) + res.Write([]byte(fmt.Sprintf(`if(source.type == "%s"){ return source; } `, GetMimeType("."+blacklists[i])))) + } + res.Write([]byte(` source.src = source.src + "&transcode=hls";`)) + res.Write([]byte(` source.type = "application/x-mpegURL";`)) + res.Write([]byte(` return source;`)) + res.Write([]byte(` })`)) + res.Write([]byte(`}`)) + }) + return nil }) - return nil }) }