more updates

License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
This commit is contained in:
Henrique Dias 2019-01-04 14:30:26 +00:00
parent 18ad9d78b3
commit 9f58170575
15 changed files with 523 additions and 755 deletions

View File

@ -1,185 +0,0 @@
package lib
/*
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"hash"
"io"
"log"
"mime"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"sync"
"github.com/filebrowser/filebrowser/files"
"github.com/filebrowser/filebrowser/rules"
"github.com/filebrowser/filebrowser/settings"
"github.com/filebrowser/filebrowser/users"
"github.com/mholt/caddy"
"github.com/spf13/afero"
)
// FileBrowser represents a File Browser instance which must
// be created through NewFileBrowser.
type FileBrowser struct {
settings *settings.Settings
mux sync.RWMutex
}
// NewFileBrowser creates a new File Browser instance from a
// storage backend. If that backend doesn't contain settings
// on it (returns ErrNotExist), then we generate a new key
// and base settings.
func NewFileBrowser(backend StorageBackend) (*FileBrowser, error) {
set, err := backend.GetSettings()
if err == ErrNotExist {
var key []byte
key, err = generateRandomBytes(64)
if err != nil {
return nil, err
}
set = &settings.Settings{Key: key}
err = backend.SaveSettings(set)
}
if err != nil {
return nil, err
}
return &FileBrowser{
settings: set,
storage: backend,
}, nil
}
// RLockSettings locks the settings for reading.
func (f *FileBrowser) RLockSettings() {
f.mux.RLock()
}
// RUnlockSettings unlocks the settings for reading.
func (f *FileBrowser) RUnlockSettings() {
f.mux.RUnlock()
}
// CheckRules matches the rules against user rules and global rules.
func (f *FileBrowser) CheckRules(path string, user *users.User) bool {
f.RLockSettings()
val := rules.Check(path, user, f.settings)
f.RUnlockSettings()
return val
}
// RunHook runs the hooks for the before and after event.
func (f *FileBrowser) RunHook(fn func() error, evt, path, dst string, user *users.User) error {
path = user.FullPath(path)
dst = user.FullPath(dst)
if val, ok := f.settings.Commands["before_"+evt]; ok {
for _, command := range val {
err := f.exec(command, "before_"+evt, path, dst, user)
if err != nil {
return err
}
}
}
err := fn()
if err != nil {
return err
}
if val, ok := f.settings.Commands["after_"+evt]; ok {
for _, command := range val {
err := f.exec(command, "after_"+evt, path, dst, user)
if err != nil {
return err
}
}
}
return nil
}
// ParseCommand parses the command taking in account if the current
// instance uses a shell to run the commands or just calls the binary
// directyly.
func (f *FileBrowser) ParseCommand(raw string) ([]string, error) {
f.RLockSettings()
defer f.RUnlockSettings()
command := []string{}
if len(f.settings.Shell) == 0 {
cmd, args, err := caddy.SplitCommandAndArgs(raw)
if err != nil {
return nil, err
}
_, err = exec.LookPath(cmd)
if err != nil {
return nil, err
}
command = append(command, cmd)
command = append(command, args...)
} else {
command = append(f.settings.Shell, raw)
}
return command, nil
}
func (f *FileBrowser) exec(raw, evt, path, dst string, user *users.User) error {
blocking := true
if strings.HasSuffix(raw, "&") {
blocking = false
raw = strings.TrimSpace(strings.TrimSuffix(raw, "&"))
}
command, err := f.ParseCommand(raw)
if err != nil {
return err
}
cmd := exec.Command(command[0], command[1:]...)
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope))
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", evt))
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", dst))
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if !blocking {
log.Printf("[INFO] Nonblocking Command: \"%s\"", strings.Join(command, " "))
return cmd.Start()
}
log.Printf("[INFO] Blocking Command: \"%s\"", strings.Join(command, " "))
return cmd.Run()
}
*/

View File

@ -8,6 +8,7 @@ var (
ErrNotExist = errors.New("the resource does not exist") ErrNotExist = errors.New("the resource does not exist")
ErrEmptyPassword = errors.New("password is empty") ErrEmptyPassword = errors.New("password is empty")
ErrEmptyUsername = errors.New("username is empty") ErrEmptyUsername = errors.New("username is empty")
ErrEmptyRequest = errors.New("empty request")
ErrScopeIsRelative = errors.New("scope is a relative path") ErrScopeIsRelative = errors.New("scope is a relative path")
ErrInvalidDataType = errors.New("invalid data type") ErrInvalidDataType = errors.New("invalid data type")
ErrIsDirectory = errors.New("file is directory") ErrIsDirectory = errors.New("file is directory")

View File

@ -1,7 +1,6 @@
package http package http
import ( import (
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"os" "os"
@ -14,76 +13,6 @@ import (
"github.com/filebrowser/filebrowser/users" "github.com/filebrowser/filebrowser/users"
) )
func (e *env) loginHandler(w http.ResponseWriter, r *http.Request) {
user, err := e.auther.Auth(r)
if err == os.ErrPermission {
httpErr(w, r, http.StatusForbidden, nil)
} else if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
} else {
e.printToken(w, r, user)
}
}
type signupBody struct {
Username string `json:"username"`
Password string `json:"password"`
}
func (e *env) signupHandler(w http.ResponseWriter, r *http.Request) {
settings, err := e.Settings.Get()
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
return
}
if !settings.Signup {
httpErr(w, r, http.StatusForbidden, nil)
return
}
if r.Body == nil {
httpErr(w, r, http.StatusBadRequest, nil)
return
}
info := &signupBody{}
err = json.NewDecoder(r.Body).Decode(info)
if err != nil {
httpErr(w, r, http.StatusBadRequest, nil)
return
}
if info.Password == "" || info.Username == "" {
httpErr(w, r, http.StatusBadRequest, nil)
return
}
user := &users.User{
Username: info.Username,
}
settings.Defaults.Apply(user)
pwd, err := users.HashPwd(info.Password)
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
return
}
user.Password = pwd
err = e.Users.Save(user)
if err == errors.ErrExist {
httpErr(w, r, http.StatusConflict, nil)
return
} else if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
return
}
httpErr(w, r, http.StatusOK, nil)
}
type userInfo struct { type userInfo struct {
ID uint `json:"id"` ID uint `json:"id"`
Locale string `json:"locale"` Locale string `json:"locale"`
@ -118,28 +47,17 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
return auth, nil return auth, nil
} }
func (e *env) auth(next http.HandlerFunc) http.HandlerFunc { func withUser(fn handleFunc) handleFunc {
keyFunc := func(token *jwt.Token) (interface{}, error) { return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
settings, err := e.Settings.Get() keyFunc := func(token *jwt.Token) (interface{}, error) {
if err != nil { return d.settings.Key, nil
return nil, err
} }
return settings.Key, nil
}
nextWithUser := func(w http.ResponseWriter, r *http.Request, id uint) {
ctx := context.WithValue(r.Context(), keyUserID, id)
next(w, r.WithContext(ctx))
}
return func(w http.ResponseWriter, r *http.Request) {
var tk authToken var tk authToken
token, err := request.ParseFromRequestWithClaims(r, &extractor{}, &tk, keyFunc) token, err := request.ParseFromRequestWithClaims(r, &extractor{}, &tk, keyFunc)
if err != nil || !token.Valid { if err != nil || !token.Valid {
httpErr(w, r, http.StatusForbidden, nil) return http.StatusForbidden, nil
return
} }
if !tk.VerifyExpiresAt(time.Now().Add(time.Hour).Unix(), true) { if !tk.VerifyExpiresAt(time.Now().Add(time.Hour).Unix(), true) {
@ -147,20 +65,88 @@ func (e *env) auth(next http.HandlerFunc) http.HandlerFunc {
w.Header().Add("X-Renew-Token", "true") w.Header().Add("X-Renew-Token", "true")
} }
nextWithUser(w, r, tk.User.ID) d.user, err = d.store.Users.Get(tk.User.ID)
return fn(w, r, d)
} }
} }
func (e *env) renew(w http.ResponseWriter, r *http.Request) { func withAdmin(fn handleFunc) handleFunc {
user, ok := e.getUser(w, r) return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !ok { if !d.user.Perm.Admin {
return return http.StatusForbidden, nil
} }
e.printToken(w, r, user) return fn(w, r, d)
})
} }
func (e *env) printToken(w http.ResponseWriter, r *http.Request, user *users.User) { var loginHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
auther, err := d.store.Auth.Get(d.settings.AuthMethod)
if err != nil {
return http.StatusInternalServerError, err
}
user, err := auther.Auth(r)
if err == os.ErrPermission {
return http.StatusForbidden, nil
} else if err != nil {
return http.StatusInternalServerError, err
} else {
return printToken(w, r, d, user)
}
}
type signupBody struct {
Username string `json:"username"`
Password string `json:"password"`
}
var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.settings.Signup {
return http.StatusMethodNotAllowed, nil
}
if r.Body == nil {
return http.StatusBadRequest, nil
}
info := &signupBody{}
err := json.NewDecoder(r.Body).Decode(info)
if err != nil {
return http.StatusBadRequest, err
}
if info.Password == "" || info.Username == "" {
return http.StatusBadRequest, nil
}
user := &users.User{
Username: info.Username,
}
d.settings.Defaults.Apply(user)
pwd, err := users.HashPwd(info.Password)
if err != nil {
return http.StatusInternalServerError, err
}
user.Password = pwd
err = d.store.Users.Save(user)
if err == errors.ErrExist {
return http.StatusConflict, err
} else if err != nil {
return http.StatusInternalServerError, err
}
return http.StatusOK, nil
}
var renewHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
return printToken(w, r, d, d.user)
})
func printToken(w http.ResponseWriter, r *http.Request, d *data, user *users.User) (int, error) {
claims := &authToken{ claims := &authToken{
User: userInfo{ User: userInfo{
ID: user.ID, ID: user.ID,
@ -177,19 +163,12 @@ func (e *env) printToken(w http.ResponseWriter, r *http.Request, user *users.Use
} }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := token.SignedString(d.settings.Key)
settings, err := e.Settings.Get()
if err != nil { if err != nil {
httpErr(w, r, http.StatusInternalServerError, err) return http.StatusInternalServerError, err
return
} }
signed, err := token.SignedString(settings.Key) w.Header().Set("Content-Type", "cty")
w.Write([]byte(signed))
if err != nil { return 0, nil
httpErr(w, r, http.StatusInternalServerError, err)
} else {
w.Header().Set("Content-Type", "cty")
w.Write([]byte(signed))
}
} }

70
http/data.go Normal file
View File

@ -0,0 +1,70 @@
package http
import (
"log"
"net/http"
"strconv"
"github.com/filebrowser/filebrowser/runner"
"github.com/filebrowser/filebrowser/settings"
"github.com/filebrowser/filebrowser/storage"
"github.com/filebrowser/filebrowser/users"
)
type handleFunc func(w http.ResponseWriter, r *http.Request, d *data) (int, error)
type data struct {
*runner.Runner
settings *settings.Settings
store *storage.Storage
user *users.User
raw interface{}
}
// Check implements rules.Checker.
func (d *data) Check(path string) bool {
for _, rule := range d.user.Rules {
if rule.Matches(path) {
return rule.Allow
}
}
for _, rule := range d.settings.Rules {
if rule.Matches(path) {
return rule.Allow
}
}
return true
}
func handle(fn handleFunc, prefix string, storage *storage.Storage) http.Handler {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
settings, err := storage.Settings.Get()
if err != nil {
log.Fatalln("ERROR: couldn't get settings")
return
}
status, err := fn(w, r, &data{
Runner: &runner.Runner{Settings: settings},
store: storage,
settings: settings,
})
if status != 0 {
txt := http.StatusText(status)
http.Error(w, strconv.Itoa(status)+" "+txt, status)
}
if status >= 400 || err != nil {
log.Printf("%s: %v %s %v", r.URL.Path, status, r.RemoteAddr, err)
}
})
if prefix == "" {
return handler
}
return http.StripPrefix(prefix, handler)
}

View File

@ -1,24 +1,10 @@
package http package http
import ( import (
"encoding/json"
"log"
"net/http" "net/http"
"strconv"
"time"
"github.com/filebrowser/filebrowser/auth"
"github.com/filebrowser/filebrowser/storage" "github.com/filebrowser/filebrowser/storage"
"github.com/filebrowser/filebrowser/errors"
"github.com/filebrowser/filebrowser/users"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/websocket"
)
type key int
const (
keyUserID key = iota
) )
type modifyRequest struct { type modifyRequest struct {
@ -26,67 +12,54 @@ type modifyRequest struct {
Which []string `json:"which"` // Answer to: which fields? Which []string `json:"which"` // Answer to: which fields?
} }
type env struct {
*storage.Storage
auther auth.Auther
}
// NewHandler builds an HTTP handler on the top of a File Browser instance.
func NewHandler(storage *storage.Storage) (http.Handler, error) { func NewHandler(storage *storage.Storage) (http.Handler, error) {
/* authMethod := fb.GetSettings().AuthMethod
auther, err := fb.GetAuther(authMethod)
if err != nil {
return nil, err
} */
e := &env{}
r := mux.NewRouter() r := mux.NewRouter()
index, static := e.getStaticHandlers() /* index, static := e.getStaticHandlers()
r.PathPrefix("/static").Handler(static) r.PathPrefix("/static").Handler(static)
r.NotFoundHandler = index r.NotFoundHandler = index */
api := r.PathPrefix("/api").Subrouter() api := r.PathPrefix("/api").Subrouter()
api.HandleFunc("/login", e.loginHandler)
api.HandleFunc("/signup", e.signupHandler)
api.HandleFunc("/renew", e.auth(e.renew))
users := api.PathPrefix("/users").Subrouter() api.Handle("/login", handle(loginHandler, "", storage))
api.Handle("/signup", handle(signupHandler, "", storage))
api.Handle("/renew", handle(renewHandler, "", storage))
/* users := api.PathPrefix("/users").Subrouter()
users.HandleFunc("", e.auth(e.usersGetHandler)).Methods("GET") users.HandleFunc("", e.auth(e.usersGetHandler)).Methods("GET")
users.HandleFunc("", e.auth(e.userPostHandler)).Methods("POST") users.HandleFunc("", e.auth(e.userPostHandler)).Methods("POST")
users.HandleFunc("/{id:[0-9]+}", e.auth(e.userPutHandler)).Methods("PUT") users.HandleFunc("/{id:[0-9]+}", e.auth(e.userPutHandler)).Methods("PUT")
users.HandleFunc("/{id:[0-9]+}", e.auth(e.userGetHandler)).Methods("GET") users.HandleFunc("/{id:[0-9]+}", e.auth(e.userGetHandler)).Methods("GET")
users.HandleFunc("/{id:[0-9]+}", e.auth(e.userDeleteHandler)).Methods("DELETE") users.HandleFunc("/{id:[0-9]+}", e.auth(e.userDeleteHandler)).Methods("DELETE") */
api.PathPrefix("/resources").HandlerFunc(e.auth(e.resourceGetHandler)).Methods("GET") api.PathPrefix("/resources").Handler(handle(resourceGetHandler, "/api/resources", storage)).Methods("GET")
api.PathPrefix("/resources").HandlerFunc(e.auth(e.resourceDeleteHandler)).Methods("DELETE") api.PathPrefix("/resources").Handler(handle(resourceDeleteHandler, "/api/resources", storage)).Methods("DELETE")
api.PathPrefix("/resources").HandlerFunc(e.auth(e.resourcePostPutHandler)).Methods("POST") api.PathPrefix("/resources").Handler(handle(resourcePostPutHandler, "/api/resources", storage)).Methods("POST")
api.PathPrefix("/resources").HandlerFunc(e.auth(e.resourcePostPutHandler)).Methods("PUT") api.PathPrefix("/resources").Handler(handle(resourcePostPutHandler, "/api/resources", storage)).Methods("PUT")
api.PathPrefix("/resources").HandlerFunc(e.auth(e.resourcePatchHandler)).Methods("PATCH") api.PathPrefix("/resources").Handler(handle(resourcePatchHandler, "/api/resources", storage)).Methods("PATCH")
api.PathPrefix("/share").HandlerFunc(e.auth(e.shareGetHandler)).Methods("GET") api.PathPrefix("/share").Handler(handle(shareGetHandler, "/api/share", storage)).Methods("GET")
api.PathPrefix("/share").HandlerFunc(e.auth(e.sharePostHandler)).Methods("POST") api.PathPrefix("/share").Handler(handle(sharePostHandler, "/api/share", storage)).Methods("POST")
api.PathPrefix("/share").HandlerFunc(e.auth(e.shareDeleteHandler)).Methods("DELETE") api.PathPrefix("/share").Handler(handle(shareDeleteHandler, "/api/share", storage)).Methods("DELETE")
api.HandleFunc("/settings", e.auth(e.settingsGetHandler)).Methods("GET") api.Handle("/settings", handle(settingsGetHandler, "", storage)).Methods("GET")
api.HandleFunc("/settings", e.auth(e.settingsPutHandler)).Methods("PUT") api.Handle("/settings", handle(settingsPutHandler, "", storage)).Methods("PUT")
api.PathPrefix("/raw").HandlerFunc(e.auth(e.rawHandler)).Methods("GET") /* api.PathPrefix("/raw").HandlerFunc(e.auth(e.rawHandler)).Methods("GET")
api.PathPrefix("/command").HandlerFunc(e.auth(e.commandsHandler)) api.PathPrefix("/command").HandlerFunc(e.auth(e.commandsHandler))
api.PathPrefix("/search").HandlerFunc(e.auth(e.searchHandler)) api.PathPrefix("/search").HandlerFunc(e.auth(e.searchHandler)) */
return r, nil return r, nil
} }
func httpErr(w http.ResponseWriter, r *http.Request, status int, err error) {
txt := http.StatusText(status) /*
if err != nil || status >= 400 { type key int
log.Printf("%s: %v %s %v", r.URL.Path, status, r.RemoteAddr, err)
}
http.Error(w, strconv.Itoa(status)+" "+txt, status)
}
func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) { func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) {
txt := http.StatusText(status) txt := http.StatusText(status)
@ -95,46 +68,4 @@ func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) {
} }
ws.WriteControl(websocket.CloseInternalServerErr, []byte(txt), time.Now().Add(10*time.Second)) ws.WriteControl(websocket.CloseInternalServerErr, []byte(txt), time.Now().Add(10*time.Second))
} }
*/
func renderJSON(w http.ResponseWriter, r *http.Request, data interface{}) {
marsh, err := json.Marshal(data)
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if _, err := w.Write(marsh); err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
}
}
func (e *env) getUser(w http.ResponseWriter, r *http.Request) (*users.User, bool) {
id := r.Context().Value(keyUserID).(uint)
user, err := e.Users.Get(id)
if err == errors.ErrNotExist {
httpErr(w, r, http.StatusForbidden, nil)
return nil, false
}
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
return nil, false
}
return user, true
}
func (e *env) getAdminUser(w http.ResponseWriter, r *http.Request) (*users.User, bool) {
user, ok := e.getUser(w, r)
if !ok {
return nil, false
}
if !user.Perm.Admin {
httpErr(w, r, http.StatusForbidden, nil)
return nil, false
}
return user, true
}

View File

@ -1,18 +1,6 @@
package http package http
import ( /*
"errors"
"net/http"
"net/url"
"path/filepath"
"strings"
"github.com/filebrowser/filebrowser/files"
"github.com/filebrowser/filebrowser/users"
"github.com/hacdias/fileutils"
"github.com/mholt/archiver"
)
const apiRawPrefix = "/api/raw" const apiRawPrefix = "/api/raw"
func parseQueryFiles(r *http.Request, f *files.FileInfo, u *users.User) ([]string, error) { func parseQueryFiles(r *http.Request, f *files.FileInfo, u *users.User) ([]string, error) {
@ -68,9 +56,7 @@ func (e *env) rawHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
files.NewFileInfo(user.Fs, path, user.Perm.Modify) file, err := files.NewFileInfo(user.Fs, path, user.Perm.Modify)
file, err := e.NewFile(path, user)
if err != nil { if err != nil {
httpErr(w, r, httpFsErr(err), err) httpErr(w, r, httpFsErr(err), err)
return return
@ -160,4 +146,4 @@ func fileHandler(w http.ResponseWriter, r *http.Request, file *files.File, user
} }
http.ServeContent(w, r, file.Name, file.ModTime, fd) http.ServeContent(w, r, file.Name, file.ModTime, fd)
} } */

View File

@ -9,116 +9,64 @@ import (
"os" "os"
"strings" "strings"
"github.com/filebrowser/filebrowser/files"
"github.com/filebrowser/filebrowser/errors"
"github.com/filebrowser/filebrowser/fileutils" "github.com/filebrowser/filebrowser/fileutils"
"github.com/filebrowser/filebrowser/users"
) )
const apiResourcePrefix = "/api/resources" // TODO: trim path
func httpFsErr(err error) int { var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
switch { file, err := files.NewFileInfo(d.user.Fs, r.URL.Path, d.user.Perm.Modify, d)
case err == nil:
return http.StatusOK
case os.IsPermission(err):
return http.StatusForbidden
case os.IsNotExist(err):
return http.StatusNotFound
case os.IsExist(err):
return http.StatusConflict
default:
return http.StatusInternalServerError
}
}
func (e *env) getResourceData(w http.ResponseWriter, r *http.Request, prefix string) (string, *users.User, bool) {
user, ok := e.getUser(w, r)
if !ok {
return "", nil, ok
}
path := strings.TrimPrefix(r.URL.Path, prefix)
path = strings.TrimSuffix(path, "/")
if path == "" {
path = "/"
}
return path, user, true
}
func (e *env) resourceGetHandler(w http.ResponseWriter, r *http.Request) {
path, user, ok := e.getResourceData(w, r, apiResourcePrefix)
if !ok {
return
}
file, err := e.NewFile(path, user)
if err != nil { if err != nil {
httpErr(w, r, httpFsErr(err), err) return errToStatus(err), err
return
} }
if file.IsDir { if file.IsDir {
file.Listing.Sorting = user.Sorting file.Listing.Sorting = d.user.Sorting
file.Listing.ApplySort() file.Listing.ApplySort()
renderJSON(w, r, file) return renderJSON(w, r, file)
return
} }
if checksum := r.URL.Query().Get("checksum"); checksum != "" { if checksum := r.URL.Query().Get("checksum"); checksum != "" {
err = e.Checksum(file, user, checksum) err := file.Checksum(checksum)
if err == lib.ErrInvalidOption { if err == errors.ErrInvalidOption {
httpErr(w, r, http.StatusBadRequest, nil) return http.StatusBadRequest, nil
return
} else if err != nil { } else if err != nil {
httpErr(w, r, http.StatusInternalServerError, err) return http.StatusInternalServerError, err
return
} }
// do not waste bandwidth if we just want the checksum // do not waste bandwidth if we just want the checksum
file.Content = "" file.Content = ""
} }
renderJSON(w, r, file) return renderJSON(w, r, file)
} })
func (e *env) resourceDeleteHandler(w http.ResponseWriter, r *http.Request) { var resourceDeleteHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
path, user, ok := e.getResourceData(w, r, apiResourcePrefix) if r.URL.Path == "/" || !d.user.Perm.Delete {
if !ok { return http.StatusForbidden, nil
return
} }
if path == "/" || !user.Perm.Delete { err := d.RunHook(func() error {
httpErr(w, r, http.StatusForbidden, nil) return d.user.Fs.RemoveAll(r.URL.Path)
return }, "delete", r.URL.Path, "", d.user)
}
err := e.RunHook(func() error {
return user.Fs.RemoveAll(path)
}, "delete", path, "", user)
if err != nil { if err != nil {
httpErr(w, r, httpFsErr(err), err) return errToStatus(err), err
return
} }
w.WriteHeader(http.StatusOK) return http.StatusOK, nil
} })
func (e *env) resourcePostPutHandler(w http.ResponseWriter, r *http.Request) { var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
path, user, ok := e.getResourceData(w, r, apiResourcePrefix) if !d.user.Perm.Create && r.Method == http.MethodPost {
if !ok { return http.StatusForbidden, nil
return
} }
if !user.Perm.Create && r.Method == http.MethodPost { if !d.user.Perm.Modify && r.Method == http.MethodPut {
httpErr(w, r, http.StatusForbidden, nil) return http.StatusForbidden, nil
return
}
if !user.Perm.Modify && r.Method == http.MethodPut {
httpErr(w, r, http.StatusForbidden, nil)
return
} }
defer func() { defer func() {
@ -128,24 +76,21 @@ func (e *env) resourcePostPutHandler(w http.ResponseWriter, r *http.Request) {
// For directories, only allow POST for creation. // For directories, only allow POST for creation.
if strings.HasSuffix(r.URL.Path, "/") { if strings.HasSuffix(r.URL.Path, "/") {
if r.Method == http.MethodPut { if r.Method == http.MethodPut {
httpErr(w, r, http.StatusMethodNotAllowed, nil) return http.StatusMethodNotAllowed, nil
} else {
err := user.Fs.MkdirAll(path, 0775)
httpErr(w, r, httpFsErr(err), err)
} }
return err := d.user.Fs.MkdirAll(r.URL.Path, 0775)
return errToStatus(err), err
} }
if r.Method == http.MethodPost && r.URL.Query().Get("override") != "true" { if r.Method == http.MethodPost && r.URL.Query().Get("override") != "true" {
if _, err := user.Fs.Stat(path); err == nil { if _, err := d.user.Fs.Stat(r.URL.Path); err == nil {
httpErr(w, r, http.StatusConflict, nil) return http.StatusConflict, nil
return
} }
} }
err := e.RunHook(func() error { err := d.RunHook(func() error {
file, err := user.Fs.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) file, err := d.user.Fs.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil { if err != nil {
return err return err
} }
@ -165,58 +110,45 @@ func (e *env) resourcePostPutHandler(w http.ResponseWriter, r *http.Request) {
etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size()) etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size())
w.Header().Set("ETag", etag) w.Header().Set("ETag", etag)
return nil return nil
}, "upload", path, "", user) }, "upload", r.URL.Path, "", d.user)
if err != nil { return errToStatus(err), err
httpErr(w, r, httpFsErr(err), err) })
return
}
httpErr(w, r, http.StatusOK, nil)
}
func (e *env) resourcePatchHandler(w http.ResponseWriter, r *http.Request) {
src, user, ok := e.getResourceData(w, r, apiResourcePrefix)
if !ok {
return
}
var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
src := r.URL.Path
dst := r.URL.Query().Get("destination") dst := r.URL.Query().Get("destination")
action := r.URL.Query().Get("action") action := r.URL.Query().Get("action")
dst, err := url.QueryUnescape(dst) dst, err := url.QueryUnescape(dst)
if err != nil { if err != nil {
httpErr(w, r, httpFsErr(err), err) return errToStatus(err), err
return
} }
if dst == "/" || src == "/" { if dst == "/" || src == "/" {
httpErr(w, r, http.StatusForbidden, nil) return http.StatusForbidden, nil
return
} }
switch action { switch action {
case "copy": case "copy":
if !user.Perm.Create { if !d.user.Perm.Create {
httpErr(w, r, http.StatusForbidden, nil) return http.StatusForbidden, nil
return
} }
case "rename": case "rename":
default: default:
action = "rename" action = "rename"
if !user.Perm.Rename { if !d.user.Perm.Rename {
httpErr(w, r, http.StatusForbidden, nil) return http.StatusForbidden, nil
return
} }
} }
err = e.RunHook(func() error { err = d.RunHook(func() error {
if action == "copy" { if action == "copy" {
return fileutils.Copy(user.Fs, src, dst) return fileutils.Copy(d.user.Fs, src, dst)
} }
return user.Fs.Rename(src, dst) return d.user.Fs.Rename(src, dst)
}, action, src, dst, user) }, action, src, dst, d.user)
httpErr(w, r, httpFsErr(err), err) return errToStatus(err), err
} })

View File

@ -17,63 +17,33 @@ type settingsData struct {
Commands map[string][]string `json:"commands"` Commands map[string][]string `json:"commands"`
} }
func (e *env) settingsGetHandler(w http.ResponseWriter, r *http.Request) { var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
_, ok := e.getAdminUser(w, r)
if !ok {
return
}
settings, err := e.Settings.Get()
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
return
}
data := &settingsData{ data := &settingsData{
Signup: settings.Signup, Signup: d.settings.Signup,
Defaults: settings.Defaults, Defaults: d.settings.Defaults,
Rules: settings.Rules, Rules: d.settings.Rules,
Branding: settings.Branding, Branding: d.settings.Branding,
Shell: settings.Shell, Shell: d.settings.Shell,
Commands: settings.Commands, Commands: d.settings.Commands,
} }
renderJSON(w, r, data) return renderJSON(w, r, data)
} })
func (e *env) settingsPutHandler(w http.ResponseWriter, r *http.Request) {
_, ok := e.getAdminUser(w, r)
if !ok {
return
}
var settingsPutHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
req := &settingsData{} req := &settingsData{}
err := json.NewDecoder(r.Body).Decode(req) err := json.NewDecoder(r.Body).Decode(req)
if err != nil { if err != nil {
httpErr(w, r, http.StatusBadRequest, err) return http.StatusBadRequest, err
return
} }
settings, err := e.Settings.Get() d.settings.Signup = req.Signup
if err != nil { d.settings.Defaults = req.Defaults
httpErr(w, r, http.StatusInternalServerError, err) d.settings.Rules = req.Rules
return d.settings.Branding = req.Branding
} d.settings.Shell = req.Shell
d.settings.Commands = req.Commands
if err != nil { err = d.store.Settings.Save(d.settings)
httpErr(w, r, http.StatusInternalServerError, err) return errToStatus(err), err
return })
}
settings.Signup = req.Signup
settings.Defaults = req.Defaults
settings.Rules = req.Rules
settings.Branding = req.Branding
settings.Shell = req.Shell
settings.Commands = req.Commands
err = e.Settings.Save(settings)
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
}
}

View File

@ -12,104 +12,72 @@ import (
"github.com/filebrowser/filebrowser/share" "github.com/filebrowser/filebrowser/share"
) )
const apiSharePrefix = "/api/share" func withPermShare(fn handleFunc) handleFunc {
return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Share {
return http.StatusForbidden, nil
}
func (e *env) getShareData(w http.ResponseWriter, r *http.Request, prefix string) (string, bool) { return fn(w, r, d)
relPath, user, ok := e.getResourceData(w, r, apiSharePrefix) })
if !ok {
return "", false
}
if !user.Perm.Share {
httpErr(w, r, http.StatusForbidden, nil)
return "", false
}
return user.FullPath(relPath), ok
} }
func (e *env) shareGetHandler(w http.ResponseWriter, r *http.Request) { var shareGetHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
path, ok := e.getShareData(w, r, apiSharePrefix) s, err := d.store.Share.Gets(r.URL.Path)
if !ok {
return
}
s, err := e.Share.Gets(path)
if err == errors.ErrNotExist { if err == errors.ErrNotExist {
renderJSON(w, r, []*share.Link{}) return renderJSON(w, r, []*share.Link{})
return
} }
if err != nil { if err != nil {
httpErr(w, r, http.StatusInternalServerError, err) return http.StatusInternalServerError, err
return
} }
for i, link := range s { for i, link := range s {
if link.Expires && link.ExpireDate.Before(time.Now()) { if link.Expires && link.ExpireDate.Before(time.Now()) {
e.Share.Delete(link.Hash) d.store.Share.Delete(link.Hash)
s = append(s[:i], s[i+1:]...) s = append(s[:i], s[i+1:]...)
} }
} }
renderJSON(w, r, s) return renderJSON(w, r, s)
} })
func (e *env) shareDeleteHandler(w http.ResponseWriter, r *http.Request) { var shareDeleteHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
user, ok := e.getUser(w, r) hash := strings.TrimSuffix(r.URL.Path, "/")
if !ok {
return
}
if !user.Perm.Share {
httpErr(w, r, http.StatusForbidden, nil)
return
}
hash := strings.TrimPrefix(r.URL.Path, apiSharePrefix)
hash = strings.TrimSuffix(hash, "/")
hash = strings.TrimPrefix(hash, "/") hash = strings.TrimPrefix(hash, "/")
if hash == "" { if hash == "" {
return return http.StatusBadRequest, nil
} }
err := e.Share.Delete(hash) err := d.store.Share.Delete(hash)
if err != nil { return errToStatus(err), err
httpErr(w, r, http.StatusInternalServerError, err) })
return
}
}
func (e *env) sharePostHandler(w http.ResponseWriter, r *http.Request) { var sharePostHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
path, ok := e.getShareData(w, r, apiSharePrefix) var s *share.Link
if !ok {
return
}
var s *lib.ShareLink
expire := r.URL.Query().Get("expires") expire := r.URL.Query().Get("expires")
unit := r.URL.Query().Get("unit") unit := r.URL.Query().Get("unit")
if expire == "" { if expire == "" {
var err error var err error
s, err = e.GetLinkPermanent(path) s, err = d.store.Share.GetPermanent(r.URL.Path)
if err == nil { if err == nil {
w.Write([]byte(e.GetSettings().BaseURL + "/share/" + s.Hash)) w.Write([]byte(d.settings.BaseURL + "/share/" + s.Hash))
return return 0, nil
} }
} }
bytes := make([]byte, 6) bytes := make([]byte, 6)
_, err := rand.Read(bytes) _, err := rand.Read(bytes)
if err != nil { if err != nil {
httpErr(w, r, http.StatusInternalServerError, err) return http.StatusInternalServerError, err
return
} }
str := base64.URLEncoding.EncodeToString(bytes) str := base64.URLEncoding.EncodeToString(bytes)
s = &lib.ShareLink{ s = &share.Link{
Path: path, Path: r.URL.Path,
Hash: str, Hash: str,
Expires: expire != "", Expires: expire != "",
} }
@ -117,8 +85,7 @@ func (e *env) sharePostHandler(w http.ResponseWriter, r *http.Request) {
if expire != "" { if expire != "" {
num, err := strconv.Atoi(expire) num, err := strconv.Atoi(expire)
if err != nil { if err != nil {
httpErr(w, r, http.StatusInternalServerError, err) return http.StatusInternalServerError, err
return
} }
var add time.Duration var add time.Duration
@ -136,10 +103,9 @@ func (e *env) sharePostHandler(w http.ResponseWriter, r *http.Request) {
s.ExpireDate = time.Now().Add(add) s.ExpireDate = time.Now().Add(add)
} }
if err := e.SaveLink(s); err != nil { if err := d.store.Share.Save(s); err != nil {
httpErr(w, r, http.StatusInternalServerError, err) return http.StatusInternalServerError, err
return
} }
renderJSON(w, r, s) return renderJSON(w, r, s)
} })

View File

@ -1,23 +1,23 @@
package http package http
/*
import ( import (
"encoding/json" "encoding/json"
"log" "log"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"text/template"
"github.com/GeertJohan/go.rice"
"github.com/filebrowser/filebrowser/auth" "github.com/filebrowser/filebrowser/auth"
"github.com/filebrowser/filebrowser/storage"
"github.com/filebrowser/filebrowser/version"
) )
func (e *env) getStaticData() map[string]interface{} { func getStaticData(storage *storage.Storage) (map[string]interface{}, error) {
e.RLockSettings() settings, err := storage.Settings.Get()
defer e.RUnlockSettings() if err != nil {
return nil, err
settings := e.GetSettings() }
staticURL := strings.TrimPrefix(settings.BaseURL+"/static", "/") staticURL := strings.TrimPrefix(settings.BaseURL+"/static", "/")
@ -25,7 +25,7 @@ func (e *env) getStaticData() map[string]interface{} {
"Name": settings.Branding.Name, "Name": settings.Branding.Name,
"DisableExternal": settings.Branding.DisableExternal, "DisableExternal": settings.Branding.DisableExternal,
"BaseURL": settings.BaseURL, "BaseURL": settings.BaseURL,
"Version": lib.Version, "Version": version.Version,
"StaticURL": staticURL, "StaticURL": staticURL,
"Signup": settings.Signup, "Signup": settings.Signup,
"NoAuth": settings.AuthMethod == auth.MethodNoAuth, "NoAuth": settings.AuthMethod == auth.MethodNoAuth,
@ -47,7 +47,12 @@ func (e *env) getStaticData() map[string]interface{} {
} }
if settings.AuthMethod == auth.MethodJSONAuth { if settings.AuthMethod == auth.MethodJSONAuth {
auther := e.Auther.(*auth.JSONAuth) raw, err := storage.Auth.Get()
if err != nil {
return nil, err
}
auther := raw.(*auth.JSONAuth)
if auther.ReCaptcha != nil { if auther.ReCaptcha != nil {
data["ReCaptcha"] = auther.ReCaptcha.Key != "" && auther.ReCaptcha.Secret != "" data["ReCaptcha"] = auther.ReCaptcha.Key != "" && auther.ReCaptcha.Secret != ""
@ -58,17 +63,21 @@ func (e *env) getStaticData() map[string]interface{} {
b, _ := json.MarshalIndent(data, "", " ") b, _ := json.MarshalIndent(data, "", " ")
data["Json"] = string(b) data["Json"] = string(b)
return data, nil
return data
} }
func (e *env) getStaticHandlers() (http.Handler, http.Handler) { func getStaticHandlers(storage *storage.Storage) (http.Handler, http.Handler, error) {
box := rice.MustFindBox("../frontend/dist") box := rice.MustFindBox("../frontend/dist")
handler := http.FileServer(box.HTTPBox()) handler := http.FileServer(box.HTTPBox())
handleWithData := func(w http.ResponseWriter, r *http.Request, file string, contentType string) { handleWithData := func(w http.ResponseWriter, r *http.Request, file string, contentType string) {
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)
index := template.Must(template.New("index").Delims("[{[", "]}]").Parse(box.MustString(file))) index := template.Must(template.New("index").Delims("[{[", "]}]").Parse(box.MustString(file)))
data, err := getStaticData(storage)
if err != nil {
}
err := index.Execute(w, e.getStaticData()) err := index.Execute(w, e.getStaticData())
if err != nil { if err != nil {
@ -115,3 +124,5 @@ func (e *env) getStaticHandlers() (http.Handler, http.Handler) {
return index, static return index, static
} }
*/

View File

@ -5,13 +5,17 @@ import (
"net/http" "net/http"
"sort" "sort"
"strconv" "strconv"
"strings"
"github.com/filebrowser/filebrowser/errors"
"github.com/filebrowser/filebrowser/users" "github.com/filebrowser/filebrowser/users"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
type modifyUserRequest struct {
modifyRequest
Data *users.User `json:"data"`
}
func getUserID(r *http.Request) (uint, error) { func getUserID(r *http.Request) (uint, error) {
vars := mux.Vars(r) vars := mux.Vars(r)
i, err := strconv.ParseUint(vars["id"], 10, 0) i, err := strconv.ParseUint(vars["id"], 10, 0)
@ -21,117 +25,81 @@ func getUserID(r *http.Request) (uint, error) {
return uint(i), err return uint(i), err
} }
type modifyUserRequest struct { func getUser(w http.ResponseWriter, r *http.Request) (*modifyUserRequest, error) {
modifyRequest
Data *users.User `json:"data"`
}
func getUser(w http.ResponseWriter, r *http.Request) (*modifyUserRequest, bool) {
if r.Body == nil { if r.Body == nil {
httpErr(w, r, http.StatusBadRequest, nil) return nil, errors.ErrEmptyRequest
return nil, false
} }
req := &modifyUserRequest{} req := &modifyUserRequest{}
err := json.NewDecoder(r.Body).Decode(req) err := json.NewDecoder(r.Body).Decode(req)
if err != nil { if err != nil {
httpErr(w, r, http.StatusBadRequest, err) return nil, err
return nil, false
} }
if req.What != "user" { if req.What != "user" {
httpErr(w, r, http.StatusBadRequest, nil) return nil, errors.ErrInvalidDataType
return nil, false
} }
return req, true return req, nil
} }
func (e *env) usersGetHandler(w http.ResponseWriter, r *http.Request) { func withSelfOrAdmin(fn handleFunc) handleFunc {
user, ok := e.getUser(w, r) return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !ok { id, err := getUserID(r)
return if err != nil {
} return http.StatusInternalServerError, err
}
if !user.Perm.Admin { if d.user.ID != id && !d.user.Perm.Admin {
httpErr(w, r, http.StatusForbidden, nil) return http.StatusForbidden, nil
return }
}
users, err := e.GetUsers() d.raw = id
return fn(w, r, d)
})
}
var usersGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
users, err := d.store.Users.Gets()
if err != nil { if err != nil {
httpErr(w, r, http.StatusInternalServerError, err) return http.StatusInternalServerError, err
return
} }
for _, u := range users { for _, u := range users {
u.Password = "" u.Password = ""
} }
sort.Slice(users, func(i, j int) bool { sort.Slice(users, func(i, j int) bool {
return users[i].ID < users[j].ID return users[i].ID < users[j].ID
}) })
return renderJSON(w, r, users)
})
renderJSON(w, r, users) var userGetHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
} u, err := d.store.Users.Get(d.raw.(uint))
if err == errors.ErrNotExist {
func (e *env) userSelfOrAdmin(w http.ResponseWriter, r *http.Request) (*users.User, uint, bool) { return http.StatusNotFound, err
user, ok := e.getUser(w, r)
if !ok {
return nil, 0, false
} }
id, err := getUserID(r)
if err != nil { if err != nil {
httpErr(w, r, http.StatusInternalServerError, err) return http.StatusInternalServerError, err
return nil, 0, false
}
if user.ID != id && !user.Perm.Admin {
httpErr(w, r, http.StatusForbidden, nil)
return nil, 0, false
}
return user, id, true
}
func (e *env) userGetHandler(w http.ResponseWriter, r *http.Request) {
_, id, ok := e.userSelfOrAdmin(w, r)
if !ok {
return
}
u, err := e.GetUser(id)
if err == lib.ErrNotExist {
httpErr(w, r, http.StatusNotFound, nil)
return
}
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
return
} }
u.Password = "" u.Password = ""
renderJSON(w, r, u) return renderJSON(w, r, u)
} })
func (e *env) userDeleteHandler(w http.ResponseWriter, r *http.Request) { var userDeleteHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
_, id, ok := e.userSelfOrAdmin(w, r) err := d.store.Users.Delete(d.raw.(uint))
if !ok { if err == errors.ErrNotExist {
return return http.StatusNotFound, err
} }
return http.StatusOK, nil
})
err := e.DeleteUser(id) /*
if err == lib.ErrNotExist {
httpErr(w, r, http.StatusNotFound, nil)
return
}
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
}
}
func (e *env) userPostHandler(w http.ResponseWriter, r *http.Request) { func (e *env) userPostHandler(w http.ResponseWriter, r *http.Request) {
_, ok := e.getAdminUser(w, r) _, ok := e.getAdminUser(w, r)
@ -237,4 +205,4 @@ func (e *env) userPutHandler(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
httpErr(w, r, http.StatusInternalServerError, err) httpErr(w, r, http.StatusInternalServerError, err)
} }
} } */

37
http/utils.go Normal file
View File

@ -0,0 +1,37 @@
package http
import (
"encoding/json"
"net/http"
"os"
)
func renderJSON(w http.ResponseWriter, r *http.Request, data interface{}) (int, error) {
marsh, err := json.Marshal(data)
if err != nil {
return http.StatusInternalServerError, err
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if _, err := w.Write(marsh); err != nil {
return http.StatusInternalServerError, err
}
return 0, nil
}
func errToStatus(err error) int {
switch {
case err == nil:
return http.StatusOK
case os.IsPermission(err):
return http.StatusForbidden
case os.IsNotExist(err):
return http.StatusNotFound
case os.IsExist(err):
return http.StatusConflict
default:
return http.StatusInternalServerError
}
}

View File

@ -1,19 +1,6 @@
package http package http
import ( /*
"bufio"
"encoding/json"
"io"
"net/http"
"os"
"os/exec"
"strings"
"github.com/filebrowser/filebrowser/search"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
ReadBufferSize: 1024, ReadBufferSize: 1024,
WriteBufferSize: 1024, WriteBufferSize: 1024,
@ -147,4 +134,4 @@ func (e *env) searchHandler(w http.ResponseWriter, r *http.Request) {
httpErr(w, r, http.StatusInternalServerError, err) httpErr(w, r, http.StatusInternalServerError, err)
return return
} }
} } */

34
runner/parser.go Normal file
View File

@ -0,0 +1,34 @@
package runner
import (
"os/exec"
"github.com/mholt/caddy"
"github.com/filebrowser/filebrowser/settings"
)
// ParseCommand parses the command taking in account if the current
// instance uses a shell to run the commands or just calls the binary
// directyly.
func ParseCommand(s *settings.Settings, raw string) ([]string, error) {
command := []string{}
if len(s.Shell) == 0 {
cmd, args, err := caddy.SplitCommandAndArgs(raw)
if err != nil {
return nil, err
}
_, err = exec.LookPath(cmd)
if err != nil {
return nil, err
}
command = append(command, cmd)
command = append(command, args...)
} else {
command = append(s.Shell, raw)
}
return command, nil
}

81
runner/runner.go Normal file
View File

@ -0,0 +1,81 @@
package runner
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
"github.com/filebrowser/filebrowser/settings"
"github.com/filebrowser/filebrowser/users"
)
// Runner is a commands runner.
type Runner struct {
*settings.Settings
}
// RunHook runs the hooks for the before and after event.
func (r *Runner) RunHook(fn func() error, evt, path, dst string, user *users.User) error {
path = user.FullPath(path)
dst = user.FullPath(dst)
if val, ok := r.Commands["before_"+evt]; ok {
for _, command := range val {
err := r.exec(command, "before_"+evt, path, dst, user)
if err != nil {
return err
}
}
}
err := fn()
if err != nil {
return err
}
if val, ok := r.Commands["after_"+evt]; ok {
for _, command := range val {
err := r.exec(command, "after_"+evt, path, dst, user)
if err != nil {
return err
}
}
}
return nil
}
func (r *Runner) exec(raw, evt, path, dst string, user *users.User) error {
blocking := true
if strings.HasSuffix(raw, "&") {
blocking = false
raw = strings.TrimSpace(strings.TrimSuffix(raw, "&"))
}
command, err := ParseCommand(r.Settings, raw)
if err != nil {
return err
}
cmd := exec.Command(command[0], command[1:]...)
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope))
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", evt))
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", dst))
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if !blocking {
log.Printf("[INFO] Nonblocking Command: \"%s\"", strings.Join(command, " "))
return cmd.Start()
}
log.Printf("[INFO] Blocking Command: \"%s\"", strings.Join(command, " "))
return cmd.Run()
}