package common import ( "bytes" "io" "io/fs" "net/http" "path/filepath" "strings" "github.com/gorilla/mux" ) type Plugin struct { Type string Enable bool } type Register struct{} type Get struct{} type All struct{} var Hooks = struct { Get Get Register Register }{ Get: Get{}, Register: Register{}, } /* * ProcessFileContentBeforeSend is a processing hooks used in plugins like: * 1. pluggable image transcoding service: plg_image_light, plg_image_bimg, plg_image_golang * 2. video transcoding service: plg_video_transcode * 3. disallow certain type of file: plg_security_svg */ var process_file_content_before_send []func(io.ReadCloser, *App, *http.ResponseWriter, *http.Request) (io.ReadCloser, error) func (this Register) ProcessFileContentBeforeSend(fn func(io.ReadCloser, *App, *http.ResponseWriter, *http.Request) (io.ReadCloser, error)) { process_file_content_before_send = append(process_file_content_before_send, fn) } func (this Get) ProcessFileContentBeforeSend() []func(io.ReadCloser, *App, *http.ResponseWriter, *http.Request) (io.ReadCloser, error) { return process_file_content_before_send } /* * HttpEndpoint is a hook that makes it possible to register new endpoint in the application. * It is used in plugin like: * 1. plg_video_transcoder to server the transcoded video segment via hls * 2. plg_editor_onlyoffice to server the content for a custom type in an iframe * 3. plg_handler_syncthing to create better integration with syncthing * 4. plg_handler_console to server a full blown console for debugging the application */ var http_endpoint []func(*mux.Router, *App) error func (this Register) HttpEndpoint(fn func(*mux.Router, *App) error) { http_endpoint = append(http_endpoint, fn) } func (this Get) HttpEndpoint() []func(*mux.Router, *App) error { return http_endpoint } /* * Override some urls with static content. The main use case for this is to enable * plugins to change the frontend code and overwrite some core components */ func (this Register) Static(www fs.FS, chroot string) { fs.WalkDir(www, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } else if d.IsDir() { return nil } this.HttpEndpoint(func(r *mux.Router, app *App) error { r.PathPrefix("/" + strings.TrimPrefix(path, chroot)).HandlerFunc(func(w http.ResponseWriter, r *http.Request) { f, err := www.Open(path) if err != nil { w.Header().Set("Content-Type", "text/plain") w.Write([]byte("plugin.go::static::err " + err.Error())) return } w.Header().Set("Content-Type", GetMimeType(filepath.Ext(path))) io.Copy(w, f) f.Close() }) return nil }) return nil }) } /* * Starter is the meat that let us connect to a wide variety of server like: * - plg_starter_http which is the default that server the application under 8334 * - plg_starter_tor to serve the application via tor * - plg_starter_web that create ssl certificate via letsencrypt * - plg_started_http2 to create an HTTP2 server * - ... */ var starter_process []func(*mux.Router) func (this Register) Starter(fn func(*mux.Router)) { starter_process = append(starter_process, fn) } func (this Get) Starter() []func(*mux.Router) { return starter_process } /* * AuthenticationMiddleware is what enabled us to authenticate user via different means: * - plg_authentication_admin to enable connection to an admin * - plg_authentication_saml * - plg_authentication_openid * - plg_authentication_ldap * - ... */ var authentication_middleware map[string]IAuthentication = make(map[string]IAuthentication, 0) func (this Register) AuthenticationMiddleware(id string, am IAuthentication) { authentication_middleware[id] = am } func (this Get) AuthenticationMiddleware() map[string]IAuthentication { return authentication_middleware } /* * AuthorisationMiddleware is to enable custom rule for authorisation. eg: anonymous can see, registered * user can see/edit some files but not some others, admin can do everything */ var authorisation_middleware []IAuthorisation func (this Register) AuthorisationMiddleware(a IAuthorisation) { authorisation_middleware = append(authorisation_middleware, a) } func (this Get) AuthorisationMiddleware() []IAuthorisation { return authorisation_middleware } /* * Search is the pluggable search mechanism. By default, there's 2 options: * - plg_search_stateless which does stateless search based on filename only * - plg_search_statefull which does full text search with a sqlite data store * The idea here is to enable different type of usage like leveraging elastic search or solr * with custom stuff around it */ var search ISearch func (this Register) SearchEngine(s ISearch) { search = s } func (this Get) SearchEngine() ISearch { return search } /* * The idea here is to enable plugin to register their own thumbnailing process, typically * images but could also be videos, pdf, excel documents, ... */ var thumbnailer map[string]IThumbnailer = make(map[string]IThumbnailer) func (this Register) Thumbnailer(mimeType string, fn IThumbnailer) { thumbnailer[mimeType] = fn } func (this Get) Thumbnailer() map[string]IThumbnailer { return thumbnailer } /* * Pluggable Audit interface */ var audit IAuditPlugin func (this Register) AuditEngine(a IAuditPlugin) { audit = a } func (this Get) AuditEngine() IAuditPlugin { return audit } /* * UI Overrides * They are the means by which server plugin change the frontend behaviors. */ var overrides []string func (this Register) FrontendOverrides(url string) { overrides = append(overrides, url) } func (this Get) FrontendOverrides() []string { return overrides } var xdg_open []string func (this Register) XDGOpen(jsString string) { xdg_open = append(xdg_open, jsString) } func (this Get) XDGOpen() []string { return xdg_open } var cssOverride []func() string func (this Register) CSS(stylesheet string) { cssOverride = append(cssOverride, func() string { return stylesheet }) } func (this Register) CSSFunc(stylesheet func() string) { cssOverride = append(cssOverride, stylesheet) } func (this Get) CSS() string { s := "" for i := 0; i < len(cssOverride); i++ { s += cssOverride[i]() + "\n" } return s } var favicon struct { binary []byte mime string } func (this Register) Favicon(binary []byte) { favicon.binary = binary favicon.mime = "image/svg+xml" if bytes.HasPrefix(binary, []byte{0x00, 0x00, 0x01, 0x00}) { favicon.mime = "image/x-icon" } else if bytes.HasPrefix(binary, []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}) { favicon.mime = "image/png" } } func (this Get) Favicon() ([]byte, string) { return favicon.binary, favicon.mime } const OverrideVideoSourceMapper = "/overrides/video-transcoder.js" var afterload []func() func (this Register) Onload(fn func()) { afterload = append(afterload, fn) } func (this Get) Onload() []func() { return afterload } var middlewares []func(HandlerFunc) HandlerFunc func (this Register) Middleware(m func(HandlerFunc) HandlerFunc) { middlewares = append(middlewares, m) } func (this Get) Middleware() []func(HandlerFunc) HandlerFunc { return middlewares } var staticOverrides [][]byte func (this Register) StaticPatch(pathFile []byte) { staticOverrides = append(staticOverrides, pathFile) } func (this Get) StaticPatch() [][]byte { return staticOverrides } var meta IMetadata func (this Register) Metadata(m IMetadata) { meta = m } func (this Get) Metadata() IMetadata { return meta } func init() { Hooks.Register.FrontendOverrides(OverrideVideoSourceMapper) }