ugh
License: MIT Signed-off-by: Henrique Dias <hacdias@gmail.com>
This commit is contained in:
parent
27e12c687b
commit
18ad9d78b3
185
_remove/filebrowser.go
Normal file
185
_remove/filebrowser.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
*/
|
||||||
15
auth/auth.go
Normal file
15
auth/auth.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auther is the authentication interface.
|
||||||
|
type Auther interface {
|
||||||
|
// Auth is called to authenticate a request.
|
||||||
|
Auth(*http.Request) (*users.User, error)
|
||||||
|
// SetStorage attaches the Storage instance.
|
||||||
|
SetStorage(*users.Storage)
|
||||||
|
}
|
||||||
28
auth/json.go
28
auth/json.go
@ -4,13 +4,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
"github.com/filebrowser/filebrowser/settings"
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MethodJSONAuth is used to identify json auth.
|
// MethodJSONAuth is used to identify json auth.
|
||||||
const MethodJSONAuth lib.AuthMethod = "json"
|
const MethodJSONAuth settings.AuthMethod = "json"
|
||||||
|
|
||||||
type jsonCred struct {
|
type jsonCred struct {
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
@ -21,20 +23,20 @@ type jsonCred struct {
|
|||||||
// JSONAuth is a json implementaion of an Auther.
|
// JSONAuth is a json implementaion of an Auther.
|
||||||
type JSONAuth struct {
|
type JSONAuth struct {
|
||||||
ReCaptcha *ReCaptcha
|
ReCaptcha *ReCaptcha
|
||||||
instance *lib.FileBrowser
|
storage *users.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth authenticates the user via a json in content body.
|
// Auth authenticates the user via a json in content body.
|
||||||
func (a *JSONAuth) Auth(r *http.Request) (*lib.User, error) {
|
func (a *JSONAuth) Auth(r *http.Request) (*users.User, error) {
|
||||||
var cred jsonCred
|
var cred jsonCred
|
||||||
|
|
||||||
if r.Body == nil {
|
if r.Body == nil {
|
||||||
return nil, lib.ErrNoPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&cred)
|
err := json.NewDecoder(r.Body).Decode(&cred)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, lib.ErrNoPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
// If ReCaptcha is enabled, check the code.
|
// If ReCaptcha is enabled, check the code.
|
||||||
@ -46,21 +48,21 @@ func (a *JSONAuth) Auth(r *http.Request) (*lib.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, lib.ErrNoPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := a.instance.GetUser(cred.Username)
|
u, err := a.storage.Get(cred.Username)
|
||||||
if err != nil || !lib.CheckPwd(cred.Password, u.Password) {
|
if err != nil || !users.CheckPwd(cred.Password, u.Password) {
|
||||||
return nil, lib.ErrNoPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetInstance attaches the instance to the auther.
|
// SetStorage attaches the storage to the auther.
|
||||||
func (a *JSONAuth) SetInstance(i *lib.FileBrowser) {
|
func (a *JSONAuth) SetStorage(s *users.Storage) {
|
||||||
a.instance = i
|
a.storage = s
|
||||||
}
|
}
|
||||||
|
|
||||||
const reCaptchaAPI = "/recaptcha/api/siteverify"
|
const reCaptchaAPI = "/recaptcha/api/siteverify"
|
||||||
|
|||||||
17
auth/none.go
17
auth/none.go
@ -3,23 +3,24 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
"github.com/filebrowser/filebrowser/settings"
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MethodNoAuth is used to identify no auth.
|
// MethodNoAuth is used to identify no auth.
|
||||||
const MethodNoAuth lib.AuthMethod = "noauth"
|
const MethodNoAuth settings.AuthMethod = "noauth"
|
||||||
|
|
||||||
// NoAuth is no auth implementation of auther.
|
// NoAuth is no auth implementation of auther.
|
||||||
type NoAuth struct {
|
type NoAuth struct {
|
||||||
instance *lib.FileBrowser
|
storage *users.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth uses authenticates user 1.
|
// Auth uses authenticates user 1.
|
||||||
func (a *NoAuth) Auth(r *http.Request) (*lib.User, error) {
|
func (a *NoAuth) Auth(r *http.Request) (*users.User, error) {
|
||||||
return a.instance.GetUser(1)
|
return a.storage.Get(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetInstance attaches the instance to the auther.
|
// SetStorage attaches the storage to the auther.
|
||||||
func (a *NoAuth) SetInstance(i *lib.FileBrowser) {
|
func (a *NoAuth) SetStorage(s *users.Storage) {
|
||||||
a.instance = i
|
a.storage = s
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,31 +2,34 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
"github.com/filebrowser/filebrowser/settings"
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
|
"github.com/filebrowser/filebrowser/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MethodProxyAuth is used to identify no auth.
|
// MethodProxyAuth is used to identify no auth.
|
||||||
const MethodProxyAuth lib.AuthMethod = "proxy"
|
const MethodProxyAuth settings.AuthMethod = "proxy"
|
||||||
|
|
||||||
// ProxyAuth is a proxy implementation of an auther.
|
// ProxyAuth is a proxy implementation of an auther.
|
||||||
type ProxyAuth struct {
|
type ProxyAuth struct {
|
||||||
Header string
|
Header string
|
||||||
instance *lib.FileBrowser
|
storage *users.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth authenticates the user via an HTTP header.
|
// Auth authenticates the user via an HTTP header.
|
||||||
func (a *ProxyAuth) Auth(r *http.Request) (*lib.User, error) {
|
func (a *ProxyAuth) Auth(r *http.Request) (*users.User, error) {
|
||||||
username := r.Header.Get(a.Header)
|
username := r.Header.Get(a.Header)
|
||||||
user, err := a.instance.GetUser(username)
|
user, err := a.storage.Get(username)
|
||||||
if err == lib.ErrNotExist {
|
if err == errors.ErrNotExist {
|
||||||
return nil, lib.ErrNoPermission
|
return nil, os.ErrPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetInstance attaches the instance to the auther.
|
// SetStorage attaches the storage to the auther.
|
||||||
func (a *ProxyAuth) SetInstance(i *lib.FileBrowser) {
|
func (a *ProxyAuth) SetStorage(s *users.Storage) {
|
||||||
a.instance = i
|
a.storage = s
|
||||||
}
|
}
|
||||||
|
|||||||
39
auth/storage.go
Normal file
39
auth/storage.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/filebrowser/filebrowser/settings"
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageBackend is a storage backend for auth storage.
|
||||||
|
type StorageBackend interface {
|
||||||
|
Get(settings.AuthMethod) (Auther, error)
|
||||||
|
Save(Auther) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage is a auth storage.
|
||||||
|
type Storage struct {
|
||||||
|
back StorageBackend
|
||||||
|
users *users.Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorage creates a auth storage from a backend.
|
||||||
|
func NewStorage(back StorageBackend, users *users.Storage) *Storage {
|
||||||
|
return &Storage{back: back, users: users}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get wraps a StorageBackend.Get and calls SetStorage on the auther.
|
||||||
|
func (s *Storage) Get(t settings.AuthMethod) (Auther, error) {
|
||||||
|
auther, err := s.back.Get(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
auther.SetStorage(s.users)
|
||||||
|
return auther, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save wraps a StorageBackend.Save.
|
||||||
|
func (s *Storage) Save(a Auther) error {
|
||||||
|
return s.back.Save(a)
|
||||||
|
}
|
||||||
@ -9,7 +9,7 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/auth"
|
"github.com/filebrowser/filebrowser/auth"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/filebrowser/filebrowser/auth"
|
"github.com/filebrowser/filebrowser/auth"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
|
|
||||||
fbhttp "github.com/filebrowser/filebrowser/http"
|
fbhttp "github.com/filebrowser/filebrowser/http"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/filebrowser/filebrowser/bolt"
|
"github.com/filebrowser/filebrowser/bolt"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
15
errors/errors.go
Normal file
15
errors/errors.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrEmptyKey = errors.New("empty key")
|
||||||
|
ErrExist = errors.New("the resource already exists")
|
||||||
|
ErrNotExist = errors.New("the resource does not exist")
|
||||||
|
ErrEmptyPassword = errors.New("password is empty")
|
||||||
|
ErrEmptyUsername = errors.New("username is empty")
|
||||||
|
ErrScopeIsRelative = errors.New("scope is a relative path")
|
||||||
|
ErrInvalidDataType = errors.New("invalid data type")
|
||||||
|
ErrIsDirectory = errors.New("file is directory")
|
||||||
|
ErrInvalidOption = errors.New("invalid option")
|
||||||
|
)
|
||||||
241
files/file.go
Normal file
241
files/file.go
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/errors"
|
||||||
|
"github.com/filebrowser/filebrowser/rules"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileInfo describes a file.
|
||||||
|
type FileInfo struct {
|
||||||
|
*Listing
|
||||||
|
Fs afero.Fs `json:"-"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Extension string `json:"extension"`
|
||||||
|
ModTime time.Time `json:"modified"`
|
||||||
|
Mode os.FileMode `json:"mode"`
|
||||||
|
IsDir bool `json:"isDir"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Subtitles []string `json:"subtitles,omitempty"`
|
||||||
|
Content string `json:"content,omitempty"`
|
||||||
|
Checksums map[string]string `json:"checksums,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileInfo creates a File object from a path and a given user. This File
|
||||||
|
// object will be automatically filled depending on if it is a directory
|
||||||
|
// or a file. If it's a video file, it will also detect any subtitles.
|
||||||
|
func NewFileInfo(fs afero.Fs, path string, modify bool, checker rules.Checker) (*FileInfo, error) {
|
||||||
|
if !checker.Check(path) {
|
||||||
|
return nil, os.ErrPermission
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := fs.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file := &FileInfo{
|
||||||
|
Fs: fs,
|
||||||
|
Path: path,
|
||||||
|
Name: info.Name(),
|
||||||
|
ModTime: info.ModTime(),
|
||||||
|
Mode: info.Mode(),
|
||||||
|
IsDir: info.IsDir(),
|
||||||
|
Size: info.Size(),
|
||||||
|
Extension: filepath.Ext(info.Name()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.IsDir {
|
||||||
|
return file, file.readListing(checker)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.detectType(modify)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checksum checksums a given File for a given User, using a specific
|
||||||
|
// algorithm. The checksums data is saved on File object.
|
||||||
|
func (i *FileInfo) Checksum(algo string) error {
|
||||||
|
if i.IsDir {
|
||||||
|
return errors.ErrIsDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Checksums == nil {
|
||||||
|
i.Checksums = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := i.Fs.Open(i.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
var h hash.Hash
|
||||||
|
|
||||||
|
switch algo {
|
||||||
|
case "md5":
|
||||||
|
h = md5.New()
|
||||||
|
case "sha1":
|
||||||
|
h = sha1.New()
|
||||||
|
case "sha256":
|
||||||
|
h = sha256.New()
|
||||||
|
case "sha512":
|
||||||
|
h = sha512.New()
|
||||||
|
default:
|
||||||
|
return errors.ErrInvalidOption
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(h, reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Checksums[algo] = hex.EncodeToString(h.Sum(nil))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *FileInfo) detectType(modify bool) error {
|
||||||
|
reader, err := i.Fs.Open(i.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
buffer := make([]byte, 512)
|
||||||
|
n, err := reader.Read(buffer)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mimetype := mime.TypeByExtension(i.Extension)
|
||||||
|
if mimetype == "" {
|
||||||
|
mimetype = http.DetectContentType(buffer[:n])
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(mimetype, "video"):
|
||||||
|
i.Type = "video"
|
||||||
|
i.detectSubtitles()
|
||||||
|
return nil
|
||||||
|
case strings.HasPrefix(mimetype, "audio"):
|
||||||
|
i.Type = "audio"
|
||||||
|
return nil
|
||||||
|
case strings.HasPrefix(mimetype, "image"):
|
||||||
|
i.Type = "image"
|
||||||
|
return nil
|
||||||
|
case isBinary(string(buffer[:n])) || i.Size > 10*1024*1024: // 10 MB
|
||||||
|
i.Type = "blob"
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
i.Type = "text"
|
||||||
|
afs := &afero.Afero{Fs: i.Fs}
|
||||||
|
content, err := afs.ReadFile(i.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !modify {
|
||||||
|
i.Type = "textImmutable"
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Content = string(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *FileInfo) detectSubtitles() {
|
||||||
|
if i.Type != "video" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Subtitles = []string{}
|
||||||
|
ext := filepath.Ext(i.Path)
|
||||||
|
|
||||||
|
// TODO: detect multiple languages. Base.Lang.vtt
|
||||||
|
|
||||||
|
path := strings.TrimSuffix(i.Path, ext) + ".vtt"
|
||||||
|
if _, err := i.Fs.Stat(path); err == nil {
|
||||||
|
i.Subtitles = append(i.Subtitles, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *FileInfo) readListing(checker rules.Checker) error {
|
||||||
|
afs := &afero.Afero{Fs: i.Fs}
|
||||||
|
dir, err := afs.ReadDir(i.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
listing := &Listing{
|
||||||
|
Items: []*FileInfo{},
|
||||||
|
NumDirs: 0,
|
||||||
|
NumFiles: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range dir {
|
||||||
|
name := f.Name()
|
||||||
|
path := path.Join(i.Path, name)
|
||||||
|
|
||||||
|
if !checker.Check(path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(f.Mode().String(), "L") {
|
||||||
|
// It's a symbolic link. We try to follow it. If it doesn't work,
|
||||||
|
// we stay with the link information instead if the target's.
|
||||||
|
info, err := os.Stat(name)
|
||||||
|
if err == nil {
|
||||||
|
f = info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file := &FileInfo{
|
||||||
|
Fs: i.Fs,
|
||||||
|
Name: name,
|
||||||
|
Size: f.Size(),
|
||||||
|
ModTime: f.ModTime(),
|
||||||
|
Mode: f.Mode(),
|
||||||
|
IsDir: f.IsDir(),
|
||||||
|
Extension: filepath.Ext(name),
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.IsDir {
|
||||||
|
listing.NumDirs++
|
||||||
|
} else {
|
||||||
|
listing.NumFiles++
|
||||||
|
|
||||||
|
err := file.detectType(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listing.Items = append(listing.Items, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Listing = listing
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -1,32 +1,14 @@
|
|||||||
package lib
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/maruel/natural"
|
"github.com/maruel/natural"
|
||||||
)
|
)
|
||||||
|
|
||||||
// File describes a file.
|
|
||||||
type File struct {
|
|
||||||
*Listing
|
|
||||||
Path string `json:"path"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Extension string `json:"extension"`
|
|
||||||
ModTime time.Time `json:"modified"`
|
|
||||||
Mode os.FileMode `json:"mode"`
|
|
||||||
IsDir bool `json:"isDir"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Subtitles []string `json:"subtitles,omitempty"`
|
|
||||||
Content string `json:"content,omitempty"`
|
|
||||||
Checksums map[string]string `json:"checksums,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listing is a collection of files.
|
// Listing is a collection of files.
|
||||||
type Listing struct {
|
type Listing struct {
|
||||||
Items []*File `json:"items"`
|
Items []*FileInfo `json:"items"`
|
||||||
NumDirs int `json:"numDirs"`
|
NumDirs int `json:"numDirs"`
|
||||||
NumFiles int `json:"numFiles"`
|
NumFiles int `json:"numFiles"`
|
||||||
Sorting Sorting `json:"sorting"`
|
Sorting Sorting `json:"sorting"`
|
||||||
7
files/sorting.go
Normal file
7
files/sorting.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
// Sorting contains a sorting order.
|
||||||
|
type Sorting struct {
|
||||||
|
By string `json:"by"`
|
||||||
|
Asc bool `json:"asc"`
|
||||||
|
}
|
||||||
12
files/utils.go
Normal file
12
files/utils.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
func isBinary(content string) bool {
|
||||||
|
for _, b := range content {
|
||||||
|
// 65533 is the unknown char
|
||||||
|
// 8 and below are control chars (e.g. backspace, null, eof, etc)
|
||||||
|
if b <= 8 || b == 65533 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
53
http/auth.go
53
http/auth.go
@ -4,18 +4,19 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
"github.com/dgrijalva/jwt-go/request"
|
"github.com/dgrijalva/jwt-go/request"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
"github.com/filebrowser/filebrowser/errors"
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func (e *env) loginHandler(w http.ResponseWriter, r *http.Request) {
|
func (e *env) loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
user, err := e.Auther.Auth(r)
|
user, err := e.auther.Auth(r)
|
||||||
if err == lib.ErrNoPermission {
|
if err == os.ErrPermission {
|
||||||
httpErr(w, r, http.StatusForbidden, nil)
|
httpErr(w, r, http.StatusForbidden, nil)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
httpErr(w, r, http.StatusInternalServerError, err)
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
@ -30,24 +31,24 @@ type signupBody struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *env) signupHandler(w http.ResponseWriter, r *http.Request) {
|
func (e *env) signupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
e.RLockSettings()
|
settings, err := e.Settings.Get()
|
||||||
defer e.RUnlockSettings()
|
if err != nil {
|
||||||
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
settings := e.GetSettings()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !settings.Signup {
|
if !settings.Signup {
|
||||||
httpErr(w, r, http.StatusForbidden, nil)
|
httpErr(w, r, http.StatusForbidden, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if r.Body == nil {
|
if r.Body == nil {
|
||||||
httpErr(w, r, http.StatusBadRequest, nil)
|
httpErr(w, r, http.StatusBadRequest, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
info := &signupBody{}
|
info := &signupBody{}
|
||||||
err := json.NewDecoder(r.Body).Decode(info)
|
err = json.NewDecoder(r.Body).Decode(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpErr(w, r, http.StatusBadRequest, nil)
|
httpErr(w, r, http.StatusBadRequest, nil)
|
||||||
return
|
return
|
||||||
@ -58,21 +59,21 @@ func (e *env) signupHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user := &lib.User{
|
user := &users.User{
|
||||||
Username: info.Username,
|
Username: info.Username,
|
||||||
}
|
}
|
||||||
|
|
||||||
e.ApplyDefaults(user)
|
settings.Defaults.Apply(user)
|
||||||
|
|
||||||
pwd, err := lib.HashPwd(info.Password)
|
pwd, err := users.HashPwd(info.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpErr(w, r, http.StatusInternalServerError, err)
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Password = pwd
|
user.Password = pwd
|
||||||
err = e.SaveUser(user)
|
err = e.Users.Save(user)
|
||||||
if err == lib.ErrExist {
|
if err == errors.ErrExist {
|
||||||
httpErr(w, r, http.StatusConflict, nil)
|
httpErr(w, r, http.StatusConflict, nil)
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@ -86,8 +87,8 @@ func (e *env) signupHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
type userInfo struct {
|
type userInfo struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Locale string `json:"locale"`
|
Locale string `json:"locale"`
|
||||||
ViewMode lib.ViewMode `json:"viewMode"`
|
ViewMode users.ViewMode `json:"viewMode"`
|
||||||
Perm lib.Permissions `json:"perm"`
|
Perm users.Permissions `json:"perm"`
|
||||||
Commands []string `json:"commands"`
|
Commands []string `json:"commands"`
|
||||||
LockPassword bool `json:"lockPassword"`
|
LockPassword bool `json:"lockPassword"`
|
||||||
}
|
}
|
||||||
@ -119,7 +120,12 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
|
|||||||
|
|
||||||
func (e *env) auth(next http.HandlerFunc) http.HandlerFunc {
|
func (e *env) auth(next http.HandlerFunc) http.HandlerFunc {
|
||||||
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
||||||
return e.GetSettings().Key, nil
|
settings, err := e.Settings.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings.Key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
nextWithUser := func(w http.ResponseWriter, r *http.Request, id uint) {
|
nextWithUser := func(w http.ResponseWriter, r *http.Request, id uint) {
|
||||||
@ -154,7 +160,7 @@ func (e *env) renew(w http.ResponseWriter, r *http.Request) {
|
|||||||
e.printToken(w, r, user)
|
e.printToken(w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *env) printToken(w http.ResponseWriter, r *http.Request, user *lib.User) {
|
func (e *env) printToken(w http.ResponseWriter, r *http.Request, user *users.User) {
|
||||||
claims := &authToken{
|
claims := &authToken{
|
||||||
User: userInfo{
|
User: userInfo{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
@ -171,7 +177,14 @@ func (e *env) printToken(w http.ResponseWriter, r *http.Request, user *lib.User)
|
|||||||
}
|
}
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
signed, err := token.SignedString(e.GetSettings().Key)
|
|
||||||
|
settings, err := e.Settings.Get()
|
||||||
|
if err != nil {
|
||||||
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
signed, err := token.SignedString(settings.Key)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpErr(w, r, http.StatusInternalServerError, err)
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
|
|||||||
28
http/http.go
28
http/http.go
@ -7,7 +7,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
"github.com/filebrowser/filebrowser/auth"
|
||||||
|
"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"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
@ -24,22 +27,19 @@ type modifyRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type env struct {
|
type env struct {
|
||||||
*lib.FileBrowser
|
*storage.Storage
|
||||||
Auther lib.Auther
|
auther auth.Auther
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler builds an HTTP handler on the top of a File Browser instance.
|
// NewHandler builds an HTTP handler on the top of a File Browser instance.
|
||||||
func NewHandler(fb *lib.FileBrowser) (http.Handler, error) {
|
func NewHandler(storage *storage.Storage) (http.Handler, error) {
|
||||||
authMethod := fb.GetSettings().AuthMethod
|
/* authMethod := fb.GetSettings().AuthMethod
|
||||||
auther, err := fb.GetAuther(authMethod)
|
auther, err := fb.GetAuther(authMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
} */
|
||||||
|
|
||||||
e := &env{
|
e := &env{}
|
||||||
FileBrowser: fb,
|
|
||||||
Auther: auther,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
|
|
||||||
@ -109,10 +109,10 @@ func renderJSON(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *env) getUser(w http.ResponseWriter, r *http.Request) (*lib.User, bool) {
|
func (e *env) getUser(w http.ResponseWriter, r *http.Request) (*users.User, bool) {
|
||||||
id := r.Context().Value(keyUserID).(uint)
|
id := r.Context().Value(keyUserID).(uint)
|
||||||
user, err := e.GetUser(id)
|
user, err := e.Users.Get(id)
|
||||||
if err == lib.ErrNotExist {
|
if err == errors.ErrNotExist {
|
||||||
httpErr(w, r, http.StatusForbidden, nil)
|
httpErr(w, r, http.StatusForbidden, nil)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@ -125,7 +125,7 @@ func (e *env) getUser(w http.ResponseWriter, r *http.Request) (*lib.User, bool)
|
|||||||
return user, true
|
return user, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *env) getAdminUser(w http.ResponseWriter, r *http.Request) (*lib.User, bool) {
|
func (e *env) getAdminUser(w http.ResponseWriter, r *http.Request) (*users.User, bool) {
|
||||||
user, ok := e.getUser(w, r)
|
user, ok := e.getUser(w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
|
|||||||
@ -7,14 +7,15 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
"github.com/filebrowser/filebrowser/files"
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
"github.com/hacdias/fileutils"
|
"github.com/hacdias/fileutils"
|
||||||
"github.com/mholt/archiver"
|
"github.com/mholt/archiver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiRawPrefix = "/api/raw"
|
const apiRawPrefix = "/api/raw"
|
||||||
|
|
||||||
func parseQueryFiles(r *http.Request, f *lib.File, u *lib.User) ([]string, error) {
|
func parseQueryFiles(r *http.Request, f *files.FileInfo, u *users.User) ([]string, error) {
|
||||||
files := []string{}
|
files := []string{}
|
||||||
names := strings.Split(r.URL.Query().Get("files"), ",")
|
names := strings.Split(r.URL.Query().Get("files"), ",")
|
||||||
|
|
||||||
@ -67,6 +68,8 @@ func (e *env) rawHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
files.NewFileInfo(user.Fs, path, user.Perm.Modify)
|
||||||
|
|
||||||
file, err := e.NewFile(path, user)
|
file, err := e.NewFile(path, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpErr(w, r, httpFsErr(err), err)
|
httpErr(w, r, httpFsErr(err), err)
|
||||||
@ -141,7 +144,7 @@ func (e *env) rawHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileHandler(w http.ResponseWriter, r *http.Request, file *lib.File, user *lib.User) {
|
func fileHandler(w http.ResponseWriter, r *http.Request, file *files.File, user *users.User) {
|
||||||
fd, err := user.Fs.Open(file.Path)
|
fd, err := user.Fs.Open(file.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpErr(w, r, http.StatusInternalServerError, err)
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/fileutils"
|
"github.com/filebrowser/filebrowser/fileutils"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiResourcePrefix = "/api/resources"
|
const apiResourcePrefix = "/api/resources"
|
||||||
@ -30,7 +31,7 @@ func httpFsErr(err error) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *env) getResourceData(w http.ResponseWriter, r *http.Request, prefix string) (string, *lib.User, bool) {
|
func (e *env) getResourceData(w http.ResponseWriter, r *http.Request, prefix string) (string, *users.User, bool) {
|
||||||
user, ok := e.getUser(w, r)
|
user, ok := e.getUser(w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", nil, ok
|
return "", nil, ok
|
||||||
|
|||||||
@ -4,15 +4,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
"github.com/filebrowser/filebrowser/rules"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/filebrowser/filebrowser/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type settingsData struct {
|
type settingsData struct {
|
||||||
Signup bool `json:"signup"`
|
Signup bool `json:"signup"`
|
||||||
Defaults lib.UserDefaults `json:"defaults"`
|
Defaults settings.UserDefaults `json:"defaults"`
|
||||||
Rules []lib.Rule `json:"rules"`
|
Rules []rules.Rule `json:"rules"`
|
||||||
Branding lib.Branding `json:"branding"`
|
Branding settings.Branding `json:"branding"`
|
||||||
Shell []string `json:"shell"`
|
Shell []string `json:"shell"`
|
||||||
Commands map[string][]string `json:"commands"`
|
Commands map[string][]string `json:"commands"`
|
||||||
}
|
}
|
||||||
@ -23,16 +23,19 @@ func (e *env) settingsGetHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.RLockSettings()
|
settings, err := e.Settings.Get()
|
||||||
defer e.RUnlockSettings()
|
if err != nil {
|
||||||
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
data := &settingsData{
|
data := &settingsData{
|
||||||
Signup: e.GetSettings().Signup,
|
Signup: settings.Signup,
|
||||||
Defaults: e.GetSettings().Defaults,
|
Defaults: settings.Defaults,
|
||||||
Rules: e.GetSettings().Rules,
|
Rules: settings.Rules,
|
||||||
Branding: e.GetSettings().Branding,
|
Branding: settings.Branding,
|
||||||
Shell: e.GetSettings().Shell,
|
Shell: settings.Shell,
|
||||||
Commands: e.GetSettings().Commands,
|
Commands: settings.Commands,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSON(w, r, data)
|
renderJSON(w, r, data)
|
||||||
@ -51,10 +54,11 @@ func (e *env) settingsPutHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.RLockSettings()
|
settings, err := e.Settings.Get()
|
||||||
settings := &lib.Settings{}
|
if err != nil {
|
||||||
err = copier.Copy(settings, e.GetSettings())
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
e.RUnlockSettings()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpErr(w, r, http.StatusInternalServerError, err)
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
@ -68,7 +72,7 @@ func (e *env) settingsPutHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
settings.Shell = req.Shell
|
settings.Shell = req.Shell
|
||||||
settings.Commands = req.Commands
|
settings.Commands = req.Commands
|
||||||
|
|
||||||
err = e.SaveSettings(settings)
|
err = e.Settings.Save(settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpErr(w, r, http.StatusInternalServerError, err)
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
"github.com/filebrowser/filebrowser/errors"
|
||||||
|
"github.com/filebrowser/filebrowser/share"
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiSharePrefix = "/api/share"
|
const apiSharePrefix = "/api/share"
|
||||||
@ -33,9 +34,9 @@ func (e *env) shareGetHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := e.GetLinksByPath(path)
|
s, err := e.Share.Gets(path)
|
||||||
if err == lib.ErrNotExist {
|
if err == errors.ErrNotExist {
|
||||||
renderJSON(w, r, []*lib.ShareLink{})
|
renderJSON(w, r, []*share.Link{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ func (e *env) shareGetHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
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.DeleteLink(link.Hash)
|
e.Share.Delete(link.Hash)
|
||||||
s = append(s[:i], s[i+1:]...)
|
s = append(s[:i], s[i+1:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +73,7 @@ func (e *env) shareDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := e.DeleteLink(hash)
|
err := e.Share.Delete(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpErr(w, r, http.StatusInternalServerError, err)
|
httpErr(w, r, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"github.com/GeertJohan/go.rice"
|
"github.com/GeertJohan/go.rice"
|
||||||
"github.com/filebrowser/filebrowser/auth"
|
"github.com/filebrowser/filebrowser/auth"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e *env) getStaticData() map[string]interface{} {
|
func (e *env) getStaticData() map[string]interface{} {
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ func getUserID(r *http.Request) (uint, error) {
|
|||||||
|
|
||||||
type modifyUserRequest struct {
|
type modifyUserRequest struct {
|
||||||
modifyRequest
|
modifyRequest
|
||||||
Data *lib.User `json:"data"`
|
Data *users.User `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUser(w http.ResponseWriter, r *http.Request) (*modifyUserRequest, bool) {
|
func getUser(w http.ResponseWriter, r *http.Request) (*modifyUserRequest, bool) {
|
||||||
@ -74,7 +75,7 @@ func (e *env) usersGetHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
renderJSON(w, r, users)
|
renderJSON(w, r, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *env) userSelfOrAdmin(w http.ResponseWriter, r *http.Request) (*lib.User, uint, bool) {
|
func (e *env) userSelfOrAdmin(w http.ResponseWriter, r *http.Request) (*users.User, uint, bool) {
|
||||||
user, ok := e.getUser(w, r)
|
user, ok := e.getUser(w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, 0, false
|
return nil, 0, false
|
||||||
@ -197,7 +198,7 @@ func (e *env) userPutHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if req.Data.Password != "" {
|
if req.Data.Password != "" {
|
||||||
req.Data.Password, err = lib.HashPwd(req.Data.Password)
|
req.Data.Password, err = lib.HashPwd(req.Data.Password)
|
||||||
} else {
|
} else {
|
||||||
var suser *lib.User
|
var suser *users.User
|
||||||
suser, err = e.GetUser(modifiedID)
|
suser, err = e.GetUser(modifiedID)
|
||||||
req.Data.Password = suser.Password
|
req.Data.Password = suser.Password
|
||||||
}
|
}
|
||||||
|
|||||||
14
lib/auth.go
14
lib/auth.go
@ -1,14 +0,0 @@
|
|||||||
package lib
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// AuthMethod describes an authentication method.
|
|
||||||
type AuthMethod string
|
|
||||||
|
|
||||||
// Auther is the authentication interface.
|
|
||||||
type Auther interface {
|
|
||||||
// Auth is called to authenticate a request.
|
|
||||||
Auth(*http.Request) (*User, error)
|
|
||||||
// SetInstance attaches the File Browser instance.
|
|
||||||
SetInstance(*FileBrowser)
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
package lib
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrExist = errors.New("the resource already exists")
|
|
||||||
ErrNotExist = errors.New("the resource does not exist")
|
|
||||||
ErrIsDirectory = errors.New("file is directory")
|
|
||||||
ErrEmptyPassword = errors.New("password is empty")
|
|
||||||
ErrEmptyUsername = errors.New("username is empty")
|
|
||||||
ErrInvalidOption = errors.New("invalid option")
|
|
||||||
ErrPathIsRel = errors.New("path is relative")
|
|
||||||
ErrNoPermission = errors.New("permission denied")
|
|
||||||
ErrInvalidAuthMethod = errors.New("invalid auth method")
|
|
||||||
ErrEmptyKey = errors.New("empty key")
|
|
||||||
ErrInvalidDataType = errors.New("invalid data type")
|
|
||||||
)
|
|
||||||
@ -1,404 +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/mholt/caddy"
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultEvents = []string{
|
|
||||||
"save",
|
|
||||||
"copy",
|
|
||||||
"rename",
|
|
||||||
"upload",
|
|
||||||
"delete",
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileBrowser represents a File Browser instance which must
|
|
||||||
// be created through NewFileBrowser.
|
|
||||||
type FileBrowser struct {
|
|
||||||
settings *Settings
|
|
||||||
storage StorageBackend
|
|
||||||
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) {
|
|
||||||
settings, err := backend.GetSettings()
|
|
||||||
|
|
||||||
if err == ErrNotExist {
|
|
||||||
var key []byte
|
|
||||||
key, err = generateRandomBytes(64)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
settings = &Settings{Key: key}
|
|
||||||
err = backend.SaveSettings(settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &FileBrowser{
|
|
||||||
settings: settings,
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RulesCheck matches a path against the user rules and the
|
|
||||||
// global rules. Returns true if allowed, false if not.
|
|
||||||
func (f *FileBrowser) RulesCheck(u *User, path string) bool {
|
|
||||||
for _, rule := range u.Rules {
|
|
||||||
if rule.Matches(path) {
|
|
||||||
return rule.Allow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.mux.RLock()
|
|
||||||
defer f.mux.RUnlock()
|
|
||||||
|
|
||||||
for _, rule := range f.settings.Rules {
|
|
||||||
if rule.Matches(path) {
|
|
||||||
return rule.Allow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunHook runs the hooks for the before and after event.
|
|
||||||
func (f *FileBrowser) RunHook(fn func() error, evt, path, dst string, user *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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyDefaults applies the default options to a user.
|
|
||||||
func (f *FileBrowser) ApplyDefaults(u *User) {
|
|
||||||
f.RLockSettings()
|
|
||||||
u.Scope = f.settings.Defaults.Scope
|
|
||||||
u.Locale = f.settings.Defaults.Locale
|
|
||||||
u.ViewMode = f.settings.Defaults.ViewMode
|
|
||||||
u.Perm = f.settings.Defaults.Perm
|
|
||||||
u.Sorting = f.settings.Defaults.Sorting
|
|
||||||
u.Commands = f.settings.Defaults.Commands
|
|
||||||
f.RUnlockSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFile creates a File object from a path and a given user. This File
|
|
||||||
// object will be automatically filled depending on if it is a directory
|
|
||||||
// or a file. If it's a video file, it will also detect any subtitles.
|
|
||||||
func (f *FileBrowser) NewFile(path string, user *User) (*File, error) {
|
|
||||||
if !f.RulesCheck(user, path) {
|
|
||||||
return nil, os.ErrPermission
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := user.Fs.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
file := &File{
|
|
||||||
Path: path,
|
|
||||||
Name: info.Name(),
|
|
||||||
ModTime: info.ModTime(),
|
|
||||||
Mode: info.Mode(),
|
|
||||||
IsDir: info.IsDir(),
|
|
||||||
Size: info.Size(),
|
|
||||||
Extension: filepath.Ext(info.Name()),
|
|
||||||
}
|
|
||||||
|
|
||||||
if file.IsDir {
|
|
||||||
return file, f.readListing(file, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = f.detectType(file, user)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if file.Type == "video" {
|
|
||||||
f.detectSubtitles(file, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
return file, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checksum checksums a given File for a given User, using a specific
|
|
||||||
// algorithm. The checksums data is saved on File object.
|
|
||||||
func (f *FileBrowser) Checksum(file *File, user *User, algo string) error {
|
|
||||||
if file.IsDir {
|
|
||||||
return ErrIsDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
if file.Checksums == nil {
|
|
||||||
file.Checksums = map[string]string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
i, err := user.Fs.Open(file.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer i.Close()
|
|
||||||
|
|
||||||
var h hash.Hash
|
|
||||||
|
|
||||||
switch algo {
|
|
||||||
case "md5":
|
|
||||||
h = md5.New()
|
|
||||||
case "sha1":
|
|
||||||
h = sha1.New()
|
|
||||||
case "sha256":
|
|
||||||
h = sha256.New()
|
|
||||||
case "sha512":
|
|
||||||
h = sha512.New()
|
|
||||||
default:
|
|
||||||
return ErrInvalidOption
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(h, i)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
file.Checksums[algo] = hex.EncodeToString(h.Sum(nil))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FileBrowser) readListing(file *File, user *User) error {
|
|
||||||
afs := &afero.Afero{Fs: user.Fs}
|
|
||||||
files, err := afs.ReadDir(file.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
listing := &Listing{
|
|
||||||
Items: []*File{},
|
|
||||||
NumDirs: 0,
|
|
||||||
NumFiles: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, i := range files {
|
|
||||||
name := i.Name()
|
|
||||||
path := path.Join(file.Path, name)
|
|
||||||
|
|
||||||
if !f.RulesCheck(user, path) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(i.Mode().String(), "L") {
|
|
||||||
// It's a symbolic link. We try to follow it. If it doesn't work,
|
|
||||||
// we stay with the link information instead if the target's.
|
|
||||||
info, err := os.Stat(name)
|
|
||||||
if err == nil {
|
|
||||||
i = info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file := &File{
|
|
||||||
Name: name,
|
|
||||||
Size: i.Size(),
|
|
||||||
ModTime: i.ModTime(),
|
|
||||||
Mode: i.Mode(),
|
|
||||||
IsDir: i.IsDir(),
|
|
||||||
Extension: filepath.Ext(name),
|
|
||||||
Path: path,
|
|
||||||
}
|
|
||||||
|
|
||||||
if file.IsDir {
|
|
||||||
listing.NumDirs++
|
|
||||||
} else {
|
|
||||||
listing.NumFiles++
|
|
||||||
|
|
||||||
err := f.detectType(file, user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listing.Items = append(listing.Items, file)
|
|
||||||
}
|
|
||||||
|
|
||||||
file.Listing = listing
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FileBrowser) detectType(file *File, user *User) error {
|
|
||||||
i, err := user.Fs.Open(file.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer i.Close()
|
|
||||||
|
|
||||||
buffer := make([]byte, 512)
|
|
||||||
n, err := i.Read(buffer)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mimetype := mime.TypeByExtension(file.Extension)
|
|
||||||
if mimetype == "" {
|
|
||||||
mimetype = http.DetectContentType(buffer[:n])
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(mimetype, "video"):
|
|
||||||
file.Type = "video"
|
|
||||||
return nil
|
|
||||||
case strings.HasPrefix(mimetype, "audio"):
|
|
||||||
file.Type = "audio"
|
|
||||||
return nil
|
|
||||||
case strings.HasPrefix(mimetype, "image"):
|
|
||||||
file.Type = "image"
|
|
||||||
return nil
|
|
||||||
case isBinary(string(buffer[:n])) || file.Size > 10*1024*1024: // 10 MB
|
|
||||||
file.Type = "blob"
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
file.Type = "text"
|
|
||||||
afs := &afero.Afero{Fs: user.Fs}
|
|
||||||
content, err := afs.ReadFile(file.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
file.Content = string(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !user.Perm.Modify && file.Type == "text" {
|
|
||||||
file.Type = "textImmutable"
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FileBrowser) detectSubtitles(file *File, user *User) {
|
|
||||||
file.Subtitles = []string{}
|
|
||||||
ext := filepath.Ext(file.Path)
|
|
||||||
base := strings.TrimSuffix(file.Path, ext)
|
|
||||||
|
|
||||||
// TODO: detect multiple languages. Like base.lang.vtt
|
|
||||||
|
|
||||||
path := base + ".vtt"
|
|
||||||
if _, err := user.Fs.Stat(path); err == nil {
|
|
||||||
file.Subtitles = append(file.Subtitles, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FileBrowser) exec(raw, evt, path, dst string, user *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()
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
package lib
|
|
||||||
|
|
||||||
// Settings contain the main settings of the application.
|
|
||||||
type Settings struct {
|
|
||||||
Key []byte `json:"key"`
|
|
||||||
BaseURL string `json:"baseURL"`
|
|
||||||
Signup bool `json:"signup"`
|
|
||||||
Defaults UserDefaults `json:"defaults"`
|
|
||||||
AuthMethod AuthMethod `json:"authMethod"`
|
|
||||||
Branding Branding `json:"branding"`
|
|
||||||
Commands map[string][]string `json:"commands"`
|
|
||||||
Shell []string `json:"shell"`
|
|
||||||
Rules []Rule `json:"rules"` // TODO: use this add to cli
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sorting contains a sorting order.
|
|
||||||
type Sorting struct {
|
|
||||||
By string `json:"by"`
|
|
||||||
Asc bool `json:"asc"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Branding contains the branding settings of the app.
|
|
||||||
type Branding struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
DisableExternal bool `json:"disableExternal"`
|
|
||||||
Files string `json:"files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserDefaults is a type that holds the default values
|
|
||||||
// for some fields on User.
|
|
||||||
type UserDefaults struct {
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
Locale string `json:"locale"`
|
|
||||||
ViewMode ViewMode `json:"viewMode"`
|
|
||||||
Sorting Sorting `json:"sorting"`
|
|
||||||
Perm Permissions `json:"perm"`
|
|
||||||
Commands []string `json:"commands"`
|
|
||||||
}
|
|
||||||
199
lib/storage.go
199
lib/storage.go
@ -1,199 +0,0 @@
|
|||||||
package lib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StorageBackend is the interface used to persist data.
|
|
||||||
type StorageBackend interface {
|
|
||||||
GetUserByID(uint) (*User, error)
|
|
||||||
GetUserByUsername(string) (*User, error)
|
|
||||||
GetUsers() ([]*User, error)
|
|
||||||
SaveUser(u *User) error
|
|
||||||
UpdateUser(u *User, fields ...string) error
|
|
||||||
DeleteUserByID(uint) error
|
|
||||||
DeleteUserByUsername(string) error
|
|
||||||
GetSettings() (*Settings, error)
|
|
||||||
SaveSettings(*Settings) error
|
|
||||||
GetAuther(AuthMethod) (Auther, error)
|
|
||||||
SaveAuther(Auther) error
|
|
||||||
GetLinkByHash(hash string) (*ShareLink, error)
|
|
||||||
GetLinkPermanent(path string) (*ShareLink, error)
|
|
||||||
GetLinksByPath(path string) ([]*ShareLink, error)
|
|
||||||
SaveLink(s *ShareLink) error
|
|
||||||
DeleteLink(hash string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUser allows you to get a user by its name or username. The provided
|
|
||||||
// id must be a string for username lookup or a uint for id lookup. If id
|
|
||||||
// is neither, a ErrInvalidDataType will be returned.
|
|
||||||
func (f *FileBrowser) GetUser(id interface{}) (*User, error) {
|
|
||||||
var (
|
|
||||||
user *User
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
switch id.(type) {
|
|
||||||
case string:
|
|
||||||
user, err = f.storage.GetUserByUsername(id.(string))
|
|
||||||
case uint:
|
|
||||||
user, err = f.storage.GetUserByID(id.(uint))
|
|
||||||
default:
|
|
||||||
return nil, ErrInvalidDataType
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user.clean()
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsers gets a list of all users.
|
|
||||||
func (f *FileBrowser) GetUsers() ([]*User, error) {
|
|
||||||
users, err := f.storage.GetUsers()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range users {
|
|
||||||
user.clean()
|
|
||||||
}
|
|
||||||
|
|
||||||
return users, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUser updates a user in the database.
|
|
||||||
func (f *FileBrowser) UpdateUser(user *User, fields ...string) error {
|
|
||||||
err := user.clean(fields...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.storage.UpdateUser(user, fields...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveUser saves the user in a storage.
|
|
||||||
func (f *FileBrowser) SaveUser(user *User) error {
|
|
||||||
if err := user.clean(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.storage.SaveUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUser allows you to delete a user by its name or username. The provided
|
|
||||||
// id must be a string for username lookup or a uint for id lookup. If id
|
|
||||||
// is neither, a ErrInvalidDataType will be returned.
|
|
||||||
func (f *FileBrowser) DeleteUser(id interface{}) (err error) {
|
|
||||||
switch id.(type) {
|
|
||||||
case string:
|
|
||||||
err = f.storage.DeleteUserByUsername(id.(string))
|
|
||||||
case uint:
|
|
||||||
err = f.storage.DeleteUserByID(id.(uint))
|
|
||||||
default:
|
|
||||||
err = ErrInvalidDataType
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSettings returns the settings for the current instance.
|
|
||||||
func (f *FileBrowser) GetSettings() *Settings {
|
|
||||||
return f.settings
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveSettings saves the settings for the current instance.
|
|
||||||
func (f *FileBrowser) SaveSettings(s *Settings) error {
|
|
||||||
s.BaseURL = strings.TrimSuffix(s.BaseURL, "/")
|
|
||||||
|
|
||||||
if len(s.Key) == 0 {
|
|
||||||
return ErrEmptyKey
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Defaults.Locale == "" {
|
|
||||||
s.Defaults.Locale = "en"
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Defaults.Commands == nil {
|
|
||||||
s.Defaults.Commands = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Defaults.ViewMode == "" {
|
|
||||||
s.Defaults.ViewMode = MosaicViewMode
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Rules == nil {
|
|
||||||
s.Rules = []Rule{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Shell == nil {
|
|
||||||
s.Shell = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Commands == nil {
|
|
||||||
s.Commands = map[string][]string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, event := range defaultEvents {
|
|
||||||
if _, ok := s.Commands["before_"+event]; !ok {
|
|
||||||
s.Commands["before_"+event] = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := s.Commands["after_"+event]; !ok {
|
|
||||||
s.Commands["after_"+event] = []string{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := f.storage.SaveSettings(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f.mux.Lock()
|
|
||||||
f.settings = s
|
|
||||||
f.mux.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAuther wraps a StorageBackend.GetAuther and calls SetInstance on the auther.
|
|
||||||
func (f *FileBrowser) GetAuther(t AuthMethod) (Auther, error) {
|
|
||||||
auther, err := f.storage.GetAuther(t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
auther.SetInstance(f)
|
|
||||||
return auther, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveAuther wraps a StorageBackend.SaveAuther.
|
|
||||||
func (f *FileBrowser) SaveAuther(a Auther) error {
|
|
||||||
return f.storage.SaveAuther(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLinkByHash wraps a StorageBackend.GetLinkByHash.
|
|
||||||
func (f *FileBrowser) GetLinkByHash(hash string) (*ShareLink, error) {
|
|
||||||
return f.storage.GetLinkByHash(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLinkPermanent wraps a StorageBackend.GetLinkPermanent
|
|
||||||
func (f *FileBrowser) GetLinkPermanent(path string) (*ShareLink, error) {
|
|
||||||
return f.storage.GetLinkPermanent(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLinksByPath wraps a StorageBackend.GetLinksByPath
|
|
||||||
func (f *FileBrowser) GetLinksByPath(path string) ([]*ShareLink, error) {
|
|
||||||
return f.storage.GetLinksByPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveLink wraps a StorageBackend.SaveLink
|
|
||||||
func (f *FileBrowser) SaveLink(s *ShareLink) error {
|
|
||||||
return f.storage.SaveLink(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteLink wraps a StorageBackend.DeleteLink
|
|
||||||
func (f *FileBrowser) DeleteLink(hash string) error {
|
|
||||||
return f.storage.DeleteLink(hash)
|
|
||||||
}
|
|
||||||
37
lib/utils.go
37
lib/utils.go
@ -1,37 +0,0 @@
|
|||||||
package lib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generateRandomBytes(n int) ([]byte, error) {
|
|
||||||
b := make([]byte, n)
|
|
||||||
_, err := rand.Read(b)
|
|
||||||
// Note that err == nil only if we read len(b) bytes.
|
|
||||||
return b, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBinary(content string) bool {
|
|
||||||
for _, b := range content {
|
|
||||||
// 65533 is the unknown char
|
|
||||||
// 8 and below are control chars (e.g. backspace, null, eof, etc)
|
|
||||||
if b <= 8 || b == 65533 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashPwd hashes a password.
|
|
||||||
func HashPwd(password string) (string, error) {
|
|
||||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
||||||
return string(bytes), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckPwd checks if a password is correct.
|
|
||||||
func CheckPwd(password, hash string) bool {
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
@ -1,10 +1,15 @@
|
|||||||
package lib
|
package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Checker is a Rules checker.
|
||||||
|
type Checker interface {
|
||||||
|
Check(path string) bool
|
||||||
|
}
|
||||||
|
|
||||||
// Rule is a allow/disallow rule.
|
// Rule is a allow/disallow rule.
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Regex bool `json:"regex"`
|
Regex bool `json:"regex"`
|
||||||
8
settings/branding.go
Normal file
8
settings/branding.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
// Branding contains the branding settings of the app.
|
||||||
|
type Branding struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
DisableExternal bool `json:"disableExternal"`
|
||||||
|
Files string `json:"files"`
|
||||||
|
}
|
||||||
27
settings/defaults.go
Normal file
27
settings/defaults.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/filebrowser/filebrowser/files"
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserDefaults is a type that holds the default values
|
||||||
|
// for some fields on User.
|
||||||
|
type UserDefaults struct {
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Locale string `json:"locale"`
|
||||||
|
ViewMode users.ViewMode `json:"viewMode"`
|
||||||
|
Sorting files.Sorting `json:"sorting"`
|
||||||
|
Perm users.Permissions `json:"perm"`
|
||||||
|
Commands []string `json:"commands"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies the default options to a user.
|
||||||
|
func (d *UserDefaults) Apply(u *users.User) {
|
||||||
|
u.Scope = d.Scope
|
||||||
|
u.Locale = d.Locale
|
||||||
|
u.ViewMode = d.ViewMode
|
||||||
|
u.Perm = d.Perm
|
||||||
|
u.Sorting = d.Sorting
|
||||||
|
u.Commands = d.Commands
|
||||||
|
}
|
||||||
24
settings/settings.go
Normal file
24
settings/settings.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import "github.com/filebrowser/filebrowser/rules"
|
||||||
|
|
||||||
|
// AuthMethod describes an authentication method.
|
||||||
|
type AuthMethod string
|
||||||
|
|
||||||
|
// Settings contain the main settings of the application.
|
||||||
|
type Settings struct {
|
||||||
|
Key []byte `json:"key"`
|
||||||
|
BaseURL string `json:"baseURL"`
|
||||||
|
Signup bool `json:"signup"`
|
||||||
|
Defaults UserDefaults `json:"defaults"`
|
||||||
|
AuthMethod AuthMethod `json:"authMethod"`
|
||||||
|
Branding Branding `json:"branding"`
|
||||||
|
Commands map[string][]string `json:"commands"`
|
||||||
|
Shell []string `json:"shell"`
|
||||||
|
Rules []rules.Rule `json:"rules"` // TODO: use this add to cli
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRules implements rules.Provider.
|
||||||
|
func (s *Settings) GetRules() []rules.Rule {
|
||||||
|
return s.Rules
|
||||||
|
}
|
||||||
88
settings/storage.go
Normal file
88
settings/storage.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/errors"
|
||||||
|
"github.com/filebrowser/filebrowser/rules"
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageBackend is a settings storage backend.
|
||||||
|
type StorageBackend interface {
|
||||||
|
Get() (*Settings, error)
|
||||||
|
Save(*Settings) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage is a settings storage.
|
||||||
|
type Storage struct {
|
||||||
|
back StorageBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorage creates a settings storage from a backend.
|
||||||
|
func NewStorage(back StorageBackend) *Storage {
|
||||||
|
return &Storage{back: back}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the settings for the current instance.
|
||||||
|
func (s *Storage) Get() (*Settings, error) {
|
||||||
|
return s.back.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultEvents = []string{
|
||||||
|
"save",
|
||||||
|
"copy",
|
||||||
|
"rename",
|
||||||
|
"upload",
|
||||||
|
"delete",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save saves the settings for the current instance.
|
||||||
|
func (s *Storage) Save(set *Settings) error {
|
||||||
|
set.BaseURL = strings.TrimSuffix(set.BaseURL, "/")
|
||||||
|
|
||||||
|
if len(set.Key) == 0 {
|
||||||
|
return errors.ErrEmptyKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if set.Defaults.Locale == "" {
|
||||||
|
set.Defaults.Locale = "en"
|
||||||
|
}
|
||||||
|
|
||||||
|
if set.Defaults.Commands == nil {
|
||||||
|
set.Defaults.Commands = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if set.Defaults.ViewMode == "" {
|
||||||
|
set.Defaults.ViewMode = users.MosaicViewMode
|
||||||
|
}
|
||||||
|
|
||||||
|
if set.Rules == nil {
|
||||||
|
set.Rules = []rules.Rule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if set.Shell == nil {
|
||||||
|
set.Shell = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if set.Commands == nil {
|
||||||
|
set.Commands = map[string][]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range defaultEvents {
|
||||||
|
if _, ok := set.Commands["before_"+event]; !ok {
|
||||||
|
set.Commands["before_"+event] = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := set.Commands["after_"+event]; !ok {
|
||||||
|
set.Commands["after_"+event] = []string{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.back.Save(set)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -1,9 +1,9 @@
|
|||||||
package lib
|
package share
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// ShareLink is the information needed to build a shareable link.
|
// Link is the information needed to build a shareable link.
|
||||||
type ShareLink struct {
|
type Link struct {
|
||||||
Hash string `json:"hash" storm:"id,index"`
|
Hash string `json:"hash" storm:"id,index"`
|
||||||
Path string `json:"path" storm:"index"`
|
Path string `json:"path" storm:"index"`
|
||||||
Expires bool `json:"expires"`
|
Expires bool `json:"expires"`
|
||||||
45
share/storage.go
Normal file
45
share/storage.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package share
|
||||||
|
|
||||||
|
// StorageBackend is the interface to implement for a share storage.
|
||||||
|
type StorageBackend interface {
|
||||||
|
GetByHash(hash string) (*Link, error)
|
||||||
|
GetPermanent(path string) (*Link, error)
|
||||||
|
Gets(path string) ([]*Link, error)
|
||||||
|
Save(s *Link) error
|
||||||
|
Delete(hash string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage is a storage.
|
||||||
|
type Storage struct {
|
||||||
|
back StorageBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorage creates a share links storage from a backend.
|
||||||
|
func NewStorage(back StorageBackend) *Storage {
|
||||||
|
return &Storage{back: back}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByHash wraps a StorageBackend.GetByHash.
|
||||||
|
func (s *Storage) GetByHash(hash string) (*Link, error) {
|
||||||
|
return s.back.GetByHash(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPermanent wraps a StorageBackend.GetPermanent
|
||||||
|
func (s *Storage) GetPermanent(path string) (*Link, error) {
|
||||||
|
return s.back.GetPermanent(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets wraps a StorageBackend.Gets
|
||||||
|
func (s *Storage) Gets(path string) ([]*Link, error) {
|
||||||
|
return s.back.Gets(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save wraps a StorageBackend.Save
|
||||||
|
func (s *Storage) Save(l *Link) error {
|
||||||
|
return s.back.Save(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete wraps a StorageBackend.Delete
|
||||||
|
func (s *Storage) Delete(hash string) error {
|
||||||
|
return s.back.Delete(hash)
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ package bolt
|
|||||||
|
|
||||||
import "github.com/asdine/storm"
|
import "github.com/asdine/storm"
|
||||||
|
|
||||||
// Backend implements lib.StorageBackend
|
// Backend implements storage.Backend
|
||||||
// using Bolt DB.
|
// using Bolt DB.
|
||||||
type Backend struct {
|
type Backend struct {
|
||||||
DB *storm.DB
|
DB *storm.DB
|
||||||
@ -3,7 +3,7 @@ package bolt
|
|||||||
import (
|
import (
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/filebrowser/filebrowser/auth"
|
"github.com/filebrowser/filebrowser/auth"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b Backend) get(name string, to interface{}) error {
|
func (b Backend) get(name string, to interface{}) error {
|
||||||
@ -19,12 +19,12 @@ func (b Backend) save(name string, from interface{}) error {
|
|||||||
return b.DB.Set("config", name, from)
|
return b.DB.Set("config", name, from)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Backend) GetSettings() (*lib.Settings, error) {
|
func (b Backend) GetSettings() (*settings.Settings, error) {
|
||||||
settings := &lib.Settings{}
|
settings := &settings.Settings{}
|
||||||
return settings, b.get("settings", settings)
|
return settings, b.get("settings", settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Backend) SaveSettings(s *lib.Settings) error {
|
func (b Backend) SaveSettings(s *settings.Settings) error {
|
||||||
return b.save("settings", s)
|
return b.save("settings", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3,7 +3,7 @@ package bolt
|
|||||||
import (
|
import (
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/asdine/storm/q"
|
"github.com/asdine/storm/q"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s Backend) GetLinkByHash(hash string) (*lib.ShareLink, error) {
|
func (s Backend) GetLinkByHash(hash string) (*lib.ShareLink, error) {
|
||||||
@ -4,7 +4,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/filebrowser/filebrowser/lib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (st Backend) GetUserByID(id uint) (*lib.User, error) {
|
func (st Backend) GetUserByID(id uint) (*lib.User, error) {
|
||||||
17
storage/storage.go
Normal file
17
storage/storage.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/filebrowser/filebrowser/auth"
|
||||||
|
"github.com/filebrowser/filebrowser/settings"
|
||||||
|
"github.com/filebrowser/filebrowser/share"
|
||||||
|
"github.com/filebrowser/filebrowser/users"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Storage is a storage powered by a Backend whih makes the neccessary
|
||||||
|
// verifications when fetching and saving data to ensure consistency.
|
||||||
|
type Storage struct {
|
||||||
|
Users *users.Storage
|
||||||
|
Share *share.Storage
|
||||||
|
Auth *auth.Storage
|
||||||
|
Settings *settings.Storage
|
||||||
|
}
|
||||||
10
storage/utils.go
Normal file
10
storage/utils.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import "crypto/rand"
|
||||||
|
|
||||||
|
func generateRandomBytes(n int) ([]byte, error) {
|
||||||
|
b := make([]byte, n)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
// Note that err == nil only if we read len(b) bytes.
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
17
users/password.go
Normal file
17
users/password.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HashPwd hashes a password.
|
||||||
|
func HashPwd(password string) (string, error) {
|
||||||
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
return string(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPwd checks if a password is correct.
|
||||||
|
func CheckPwd(password, hash string) bool {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
13
users/permissions.go
Normal file
13
users/permissions.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
// Permissions describe a user's permissions.
|
||||||
|
type Permissions struct {
|
||||||
|
Admin bool `json:"admin"`
|
||||||
|
Execute bool `json:"execute"`
|
||||||
|
Create bool `json:"create"`
|
||||||
|
Rename bool `json:"rename"`
|
||||||
|
Modify bool `json:"modify"`
|
||||||
|
Delete bool `json:"delete"`
|
||||||
|
Share bool `json:"share"`
|
||||||
|
Download bool `json:"download"`
|
||||||
|
}
|
||||||
101
users/storage.go
Normal file
101
users/storage.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/filebrowser/filebrowser/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageBackend is the interface to implement for a users storage.
|
||||||
|
type StorageBackend interface {
|
||||||
|
GetByID(uint) (*User, error)
|
||||||
|
GetByUsername(string) (*User, error)
|
||||||
|
Gets() ([]*User, error)
|
||||||
|
Save(u *User) error
|
||||||
|
Update(u *User, fields ...string) error
|
||||||
|
DeleteByID(uint) error
|
||||||
|
DeleteByUsername(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage is a users storage.
|
||||||
|
type Storage struct {
|
||||||
|
back StorageBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorage creates a users storage from a backend.
|
||||||
|
func NewStorage(back StorageBackend) *Storage {
|
||||||
|
return &Storage{back: back}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get allows you to get a user by its name or username. The provided
|
||||||
|
// id must be a string for username lookup or a uint for id lookup. If id
|
||||||
|
// is neither, a ErrInvalidDataType will be returned.
|
||||||
|
func (s *Storage) Get(id interface{}) (*User, error) {
|
||||||
|
var (
|
||||||
|
user *User
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
switch id.(type) {
|
||||||
|
case string:
|
||||||
|
user, err = s.back.GetByUsername(id.(string))
|
||||||
|
case uint:
|
||||||
|
user, err = s.back.GetByID(id.(uint))
|
||||||
|
default:
|
||||||
|
return nil, errors.ErrInvalidDataType
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Clean()
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets gets a list of all users.
|
||||||
|
func (s *Storage) Gets() ([]*User, error) {
|
||||||
|
users, err := s.back.Gets()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
user.Clean()
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates a user in the database.
|
||||||
|
func (s *Storage) Update(user *User, fields ...string) error {
|
||||||
|
err := user.Clean(fields...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.back.Update(user, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save saves the user in a storage.
|
||||||
|
func (s *Storage) Save(user *User) error {
|
||||||
|
if err := user.Clean(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.back.Save(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete allows you to delete a user by its name or username. The provided
|
||||||
|
// id must be a string for username lookup or a uint for id lookup. If id
|
||||||
|
// is neither, a ErrInvalidDataType will be returned.
|
||||||
|
func (s *Storage) Delete(id interface{}) (err error) {
|
||||||
|
switch id.(type) {
|
||||||
|
case string:
|
||||||
|
err = s.back.DeleteByUsername(id.(string))
|
||||||
|
case uint:
|
||||||
|
err = s.back.DeleteByID(id.(uint))
|
||||||
|
default:
|
||||||
|
err = errors.ErrInvalidDataType
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@ -1,9 +1,12 @@
|
|||||||
package lib
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/filebrowser/filebrowser/errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/filebrowser/filebrowser/files"
|
||||||
|
"github.com/filebrowser/filebrowser/rules"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,18 +18,6 @@ const (
|
|||||||
MosaicViewMode ViewMode = "mosaic"
|
MosaicViewMode ViewMode = "mosaic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Permissions describe a user's permissions.
|
|
||||||
type Permissions struct {
|
|
||||||
Admin bool `json:"admin"`
|
|
||||||
Execute bool `json:"execute"`
|
|
||||||
Create bool `json:"create"`
|
|
||||||
Rename bool `json:"rename"`
|
|
||||||
Modify bool `json:"modify"`
|
|
||||||
Delete bool `json:"delete"`
|
|
||||||
Share bool `json:"share"`
|
|
||||||
Download bool `json:"download"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// User describes a user.
|
// User describes a user.
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uint `storm:"id,increment" json:"id"`
|
ID uint `storm:"id,increment" json:"id"`
|
||||||
@ -38,9 +29,14 @@ type User struct {
|
|||||||
ViewMode ViewMode `json:"viewMode"`
|
ViewMode ViewMode `json:"viewMode"`
|
||||||
Perm Permissions `json:"perm"`
|
Perm Permissions `json:"perm"`
|
||||||
Commands []string `json:"commands"`
|
Commands []string `json:"commands"`
|
||||||
Sorting Sorting `json:"sorting"`
|
Sorting files.Sorting `json:"sorting"`
|
||||||
Fs afero.Fs `json:"-"`
|
Fs afero.Fs `json:"-"`
|
||||||
Rules []Rule `json:"rules"`
|
Rules []rules.Rule `json:"rules"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRules implements rules.Provider.
|
||||||
|
func (u *User) GetRules() []rules.Rule {
|
||||||
|
return u.Rules
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkableFields = []string{
|
var checkableFields = []string{
|
||||||
@ -53,7 +49,9 @@ var checkableFields = []string{
|
|||||||
"Rules",
|
"Rules",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) clean(fields ...string) error {
|
// Clean cleans up a user and verifies if all its fields
|
||||||
|
// are alright to be saved.
|
||||||
|
func (u *User) Clean(fields ...string) error {
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
fields = checkableFields
|
fields = checkableFields
|
||||||
}
|
}
|
||||||
@ -62,15 +60,15 @@ func (u *User) clean(fields ...string) error {
|
|||||||
switch field {
|
switch field {
|
||||||
case "Username":
|
case "Username":
|
||||||
if u.Username == "" {
|
if u.Username == "" {
|
||||||
return ErrEmptyUsername
|
return errors.ErrEmptyUsername
|
||||||
}
|
}
|
||||||
case "Password":
|
case "Password":
|
||||||
if u.Password == "" {
|
if u.Password == "" {
|
||||||
return ErrEmptyPassword
|
return errors.ErrEmptyPassword
|
||||||
}
|
}
|
||||||
case "Scope":
|
case "Scope":
|
||||||
if !filepath.IsAbs(u.Scope) {
|
if !filepath.IsAbs(u.Scope) {
|
||||||
return ErrPathIsRel
|
return errors.ErrScopeIsRelative
|
||||||
}
|
}
|
||||||
case "ViewMode":
|
case "ViewMode":
|
||||||
if u.ViewMode == "" {
|
if u.ViewMode == "" {
|
||||||
@ -86,7 +84,7 @@ func (u *User) clean(fields ...string) error {
|
|||||||
}
|
}
|
||||||
case "Rules":
|
case "Rules":
|
||||||
if u.Rules == nil {
|
if u.Rules == nil {
|
||||||
u.Rules = []Rule{}
|
u.Rules = []rules.Rule{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package lib
|
package version
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Version is the current File Browser version.
|
// Version is the current File Browser version.
|
||||||
Loading…
Reference in New Issue
Block a user