Files

387 lines
9.6 KiB
Go

package kernel
import (
"bytes"
"context"
"io"
"io/fs"
"net/http"
"path/filepath"
"reflect"
"runtime"
"sort"
"strings"
. "github.com/mickael-kerjean/filestash/server/pkg/core"
. "github.com/mickael-kerjean/filestash/server/pkg/mime"
"github.com/mickael-kerjean/filestash/server/pkg/tracer"
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
"github.com/gorilla/mux"
)
type Register struct{}
type Get struct{}
var Hooks = struct {
Get Get
Register Register
}{
Get: Get{},
Register: Register{},
}
type Options struct {
ID string
}
type Option func(*Options)
func WithID(id string) Option {
return func(o *Options) {
o.ID = id
}
}
/*
* 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, bool, error)
func (this Register) ProcessFileContentBeforeSend(fn func(io.ReadCloser, *App, *http.ResponseWriter, *http.Request) (io.ReadCloser, bool, 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, bool, 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) error
func (this Register) HttpEndpoint(fn func(*mux.Router) error) {
http_endpoint = append(http_endpoint, fn)
}
func (this Get) HttpEndpoint() []func(*mux.Router) 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) 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(context.Context, *mux.Router)
func (this Register) Starter(fn func(context.Context, *mux.Router)) {
starter_process = fn
}
func (this Get) Starter() func(context.Context, *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
}
/*
* Pluggable Trace Engine
*/
func (this Register) Tracer(t ITracer) {
tracer.Register(t)
}
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 = map[string]string{}
func (this Register) CSS(stylesheet string, opts ...Option) { // idempotent
options := Options{}
for _, opt := range opts {
opt(&options)
}
if options.ID == "" {
options.ID = QuickHash(stylesheet, 10)
}
cssOverride[options.ID] = stylesheet
}
func (this Get) CSS() string {
s := ""
for _, v := range cssOverride {
s += v + "\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"
} else if bytes.HasPrefix(binary, []byte{0x47, 0x49, 0x46, 0x38}) {
favicon.mime = "image/vnd.microsoft.icon"
}
}
func (this Get) Favicon() ([]byte, string) {
return favicon.binary, favicon.mime
}
var afterload []func()
func (this Register) Onload(fn func()) {
afterload = append(afterload, fn)
}
func (this Get) Onload() []func() {
return afterload
}
var onquit []func()
func (this Register) OnQuit(fn func()) {
onquit = append(onquit, fn)
}
func (this Get) OnQuit() []func() {
return onquit
}
var configChange []func()
func (this Register) OnConfig(fn func()) {
configChange = append(configChange, fn)
}
func (this Get) OnConfig() []func() {
return configChange
}
var middlewares []func(HandlerFunc) HandlerFunc
func (this Register) Middleware(m func(HandlerFunc) HandlerFunc) {
fname := func(m func(HandlerFunc) HandlerFunc) string {
name := runtime.FuncForPC(reflect.ValueOf(m).Pointer()).Name()
return name[strings.LastIndex(name, ".")+1:]
}
middlewares = append(middlewares, m)
sort.SliceStable(middlewares, func(i, j int) bool {
return fname(middlewares[i]) > fname(middlewares[j])
})
}
func (this Get) Middleware() []func(HandlerFunc) HandlerFunc {
return middlewares
}
type patch struct {
id string
diff []byte
}
var patches = []patch{}
func (this Register) StaticPatch(patchFile []byte, opts ...Option) {
options := Options{}
for _, opt := range opts {
opt(&options)
}
if options.ID == "" {
options.ID = QuickHash(string(patchFile), 10)
}
inserted := false
for i := 0; i < len(patches); i++ {
if patches[i].id == options.ID {
patches[i].diff = patchFile
inserted = true
}
}
if !inserted {
patches = append(patches, patch{
id: options.ID,
diff: patchFile,
})
}
sort.SliceStable(patches, func(i, j int) bool {
return patches[i].id > patches[j].id
})
}
func (this Get) StaticPatch() [][]byte {
s := [][]byte{}
for _, v := range patches {
s = append(s, v.diff)
}
return s
}
var meta IMetadata
func (this Register) Metadata(m IMetadata) {
meta = m
}
func (this Get) Metadata() IMetadata {
return meta
}
var workflow_triggers []ITrigger
func (this Register) WorkflowTrigger(t ITrigger) {
workflow_triggers = append(workflow_triggers, t)
sort.Slice(workflow_triggers, func(i, j int) bool {
return workflow_triggers[i].Manifest().Order < workflow_triggers[j].Manifest().Order
})
}
func (this Get) WorkflowTriggers() []ITrigger {
return workflow_triggers
}
var workflow_actions []IAction
func (this Register) WorkflowAction(a IAction) {
workflow_actions = append(workflow_actions, a)
sort.Slice(workflow_actions, func(i, j int) bool {
return workflow_actions[i].Manifest().Order < workflow_actions[j].Manifest().Order
})
}
func (this Get) WorkflowActions() []IAction {
return workflow_actions
}
var directory IDirectoryService
func (this Register) DirectoryService(d IDirectoryService) {
directory = d
}
func (this Get) DirectoryService() IDirectoryService {
return directory
}