mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 16:32:31 +01:00
feature (plg_handler_site): discoverability by admin
This commit is contained in:
parent
32526f8bde
commit
6f2404d69a
5 changed files with 192 additions and 9 deletions
|
|
@ -31,6 +31,7 @@ import (
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_editor_wopi"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_editor_wopi"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_console"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_console"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_mcp"
|
||||||
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_site"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_ascii"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_ascii"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_c"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_c"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_license"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_license"
|
||||||
|
|
|
||||||
56
server/plugin/plg_handler_site/config.go
Normal file
56
server/plugin/plg_handler_site/config.go
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
package plg_handler_site
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/mickael-kerjean/filestash/server/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Hooks.Register.Onload(func() {
|
||||||
|
PluginEnable()
|
||||||
|
PluginParamAutoindex()
|
||||||
|
PluginParamCORSAllowOrigins()
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var PluginEnable = func() bool {
|
||||||
|
return Config.Get("features.site.enable").Schema(func(f *FormElement) *FormElement {
|
||||||
|
if f == nil {
|
||||||
|
f = &FormElement{}
|
||||||
|
}
|
||||||
|
f.Name = "enable"
|
||||||
|
f.Type = "enable"
|
||||||
|
f.Target = []string{"site_autoindex", "site_cors_allow_origins"}
|
||||||
|
f.Description = "Enable/Disable the creation of site via shared links. Sites will be made available under /public/{shareID}/"
|
||||||
|
f.Default = false
|
||||||
|
return f
|
||||||
|
}).Bool()
|
||||||
|
}
|
||||||
|
|
||||||
|
var PluginParamAutoindex = func() bool {
|
||||||
|
return Config.Get("features.site.autoindex").Schema(func(f *FormElement) *FormElement {
|
||||||
|
if f == nil {
|
||||||
|
f = &FormElement{}
|
||||||
|
}
|
||||||
|
f.Id = "site_autoindex"
|
||||||
|
f.Name = "autoindex"
|
||||||
|
f.Type = "boolean"
|
||||||
|
f.Description = "Enables or disables automatic directory listing when no index file is present."
|
||||||
|
f.Default = false
|
||||||
|
return f
|
||||||
|
}).Bool()
|
||||||
|
}
|
||||||
|
|
||||||
|
var PluginParamCORSAllowOrigins = func() string {
|
||||||
|
return Config.Get("features.site.cors_allow_origins").Schema(func(f *FormElement) *FormElement {
|
||||||
|
if f == nil {
|
||||||
|
f = &FormElement{}
|
||||||
|
}
|
||||||
|
f.Id = "site_cors_allow_origins"
|
||||||
|
f.Name = "cors_allow_origins"
|
||||||
|
f.Type = "text"
|
||||||
|
f.Placeholder = "* or https://example.com, https://app.example.com"
|
||||||
|
f.Description = "List of allowed origins for CORS. Use '*' to allow all origins, or provide a comma-separated list."
|
||||||
|
return f
|
||||||
|
}).String()
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package plg_handler_site
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
. "github.com/mickael-kerjean/filestash/server/common"
|
. "github.com/mickael-kerjean/filestash/server/common"
|
||||||
|
|
@ -15,17 +16,29 @@ import (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error {
|
Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error {
|
||||||
|
if PluginEnable() == false {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
r.PathPrefix("/public/{share}/").HandlerFunc(NewMiddlewareChain(
|
r.PathPrefix("/public/{share}/").HandlerFunc(NewMiddlewareChain(
|
||||||
publicHandler,
|
SiteHandler,
|
||||||
[]Middleware{SessionStart, SecureHeaders},
|
[]Middleware{SessionStart, SecureHeaders, cors},
|
||||||
*app,
|
*app,
|
||||||
)).Methods("GET", "HEAD")
|
)).Methods("GET", "HEAD")
|
||||||
|
|
||||||
|
r.HandleFunc("/public/", NewMiddlewareChain(
|
||||||
|
SharesListHandler,
|
||||||
|
[]Middleware{SecureHeaders, basicAdmin},
|
||||||
|
*app,
|
||||||
|
)).Methods("GET")
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func publicHandler(app *App, w http.ResponseWriter, r *http.Request) {
|
func SiteHandler(app *App, w http.ResponseWriter, r *http.Request) {
|
||||||
if app.Backend == nil {
|
if r.Method == http.MethodOptions {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
} else if app.Backend == nil {
|
||||||
SendErrorResult(w, ErrNotFound)
|
SendErrorResult(w, ErrNotFound)
|
||||||
return
|
return
|
||||||
} else if model.CanRead(app) == false {
|
} else if model.CanRead(app) == false {
|
||||||
|
|
@ -47,12 +60,53 @@ func publicHandler(app *App, w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f, err := app.Backend.Cat(path)
|
if f, err := app.Backend.Cat(path); err == nil {
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", GetMimeType(path))
|
w.Header().Set("Content-Type", GetMimeType(path))
|
||||||
io.Copy(w, f)
|
io.Copy(w, f)
|
||||||
f.Close()
|
f.Close()
|
||||||
|
return
|
||||||
|
} else if err == ErrNotFound && PluginParamAutoindex() {
|
||||||
|
if files, err := app.Backend.Ls(strings.TrimSuffix(path, "index.html")); err == nil {
|
||||||
|
if strings.HasSuffix(r.URL.Path, "/") == false {
|
||||||
|
http.Redirect(w, r, r.URL.Path+"/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
if err := TmplAutoindex.Execute(w, map[string]any{
|
||||||
|
"Base": r.URL.Path,
|
||||||
|
"Files": files,
|
||||||
|
}); err != nil {
|
||||||
|
SendErrorResult(w, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SendErrorResult(w, ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SharesListHandler(app *App, w http.ResponseWriter, r *http.Request) {
|
||||||
|
shares, err := model.ShareAll()
|
||||||
|
if err != nil {
|
||||||
|
SendErrorResult(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
files := make([]os.FileInfo, len(shares))
|
||||||
|
for i, share := range shares {
|
||||||
|
t := int64(-1)
|
||||||
|
if share.Expire != nil {
|
||||||
|
t = *share.Expire
|
||||||
|
}
|
||||||
|
files[i] = File{
|
||||||
|
FName: share.Id,
|
||||||
|
FType: "directory",
|
||||||
|
FTime: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
if err := TmplAutoindex.Execute(w, map[string]any{
|
||||||
|
"Base": r.URL.Path,
|
||||||
|
"Files": files,
|
||||||
|
}); err != nil {
|
||||||
|
SendErrorResult(w, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
server/plugin/plg_handler_site/middleware.go
Normal file
48
server/plugin/plg_handler_site/middleware.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package plg_handler_site
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
. "github.com/mickael-kerjean/filestash/server/common"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cors(fn HandlerFunc) HandlerFunc {
|
||||||
|
return HandlerFunc(func(ctx *App, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if allowed := PluginParamCORSAllowOrigins(); allowed != "" {
|
||||||
|
w.Header().Add("Vary", "Origin")
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
|
||||||
|
switch allowed {
|
||||||
|
case "*":
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
default:
|
||||||
|
origin := r.Header.Get("Origin")
|
||||||
|
for _, o := range strings.Split(allowed, ",") {
|
||||||
|
if strings.TrimSpace(o) == origin {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn(ctx, w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicAdmin(fn HandlerFunc) HandlerFunc {
|
||||||
|
return HandlerFunc(func(ctx *App, w http.ResponseWriter, r *http.Request) {
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
if !ok || user != "admin" {
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
} else if err := bcrypt.CompareHashAndPassword([]byte(Config.Get("auth.admin").String()), []byte(pass)); err != nil {
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn(ctx, w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
24
server/plugin/plg_handler_site/template.go
Normal file
24
server/plugin/plg_handler_site/template.go
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
package plg_handler_site
|
||||||
|
|
||||||
|
import "html/template"
|
||||||
|
|
||||||
|
var TmplAutoindex = template.Must(template.New("autoindex").Parse(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Index of {{ .Base }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Index of {{ .Base }}</h1><pre><a href="../">../</a><br>
|
||||||
|
{{- range .Files -}}
|
||||||
|
<a href="{{if .IsDir}}{{printf "./%s/" .Name}}{{else}}{{printf "./%s" .Name}}{{end}}">
|
||||||
|
{{- if .IsDir -}}
|
||||||
|
{{ printf "%-40.40s" (printf "%s/" .Name) }}
|
||||||
|
{{- else -}}
|
||||||
|
{{ printf "%-40.40s" .Name }}
|
||||||
|
{{- end -}}
|
||||||
|
</a> {{ (.ModTime).Format "2006-01-02 15:04:05" }} {{ printf "%8d" .Size }}<br>
|
||||||
|
{{- end }}
|
||||||
|
<hr>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>`))
|
||||||
Loading…
Reference in a new issue