feat: filebrowser with encfs

This commit is contained in:
2024-09-03 00:26:36 +08:00
parent 14024c56a3
commit b0b9f70129
330 changed files with 44745 additions and 87 deletions

11
settings/branding.go Normal file
View File

@@ -0,0 +1,11 @@
package settings
// Branding contains the branding settings of the app.
type Branding struct {
Name string `json:"name"`
DisableExternal bool `json:"disableExternal"`
DisableUsedPercentage bool `json:"disableUsedPercentage"`
Files string `json:"files"`
Theme string `json:"theme"`
Color string `json:"color"`
}

33
settings/defaults.go Normal file
View File

@@ -0,0 +1,33 @@
package settings
import (
"github.com/filebrowser/filebrowser/v2/files"
"github.com/filebrowser/filebrowser/v2/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"`
SingleClick bool `json:"singleClick"`
Sorting files.Sorting `json:"sorting"`
Perm users.Permissions `json:"perm"`
Commands []string `json:"commands"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
}
// 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.SingleClick = d.SingleClick
u.Perm = d.Perm
u.Sorting = d.Sorting
u.Commands = d.Commands
u.HideDotfiles = d.HideDotfiles
u.DateFormat = d.DateFormat
}

54
settings/dir.go Normal file
View File

@@ -0,0 +1,54 @@
package settings
import (
"errors"
"fmt"
"log"
"os"
"path"
"regexp"
"strings"
"github.com/spf13/afero"
)
var (
invalidFilenameChars = regexp.MustCompile(`[^0-9A-Za-z@_\-.]`)
dashes = regexp.MustCompile(`[\-]+`)
)
// MakeUserDir makes the user directory according to settings.
func (s *Settings) MakeUserDir(username, userScope, serverRoot string) (string, error) {
userScope = strings.TrimSpace(userScope)
if userScope == "" && s.CreateUserDir {
username = cleanUsername(username)
if username == "" || username == "-" || username == "." {
log.Printf("create user: invalid user for home dir creation: [%s]", username)
return "", errors.New("invalid user for home dir creation")
}
userScope = path.Join(s.UserHomeBasePath, username)
}
userScope = path.Join("/", userScope)
log.Println("Make user dir:", userScope, userScope, serverRoot)
fs := afero.NewBasePathFs(afero.NewOsFs(), serverRoot)
if err := fs.MkdirAll(userScope, os.ModePerm); err != nil {
return "", fmt.Errorf("failed to create user home dir: [%s]: %w", userScope, err)
}
return userScope, nil
}
func cleanUsername(s string) string {
// Remove any trailing space to avoid ending on -
s = strings.Trim(s, " ")
s = strings.Replace(s, "..", "", -1)
// Replace all characters which not in the list `0-9A-Za-z@_\-.` with a dash
s = invalidFilenameChars.ReplaceAllString(s, "-")
// Remove any multiple dashes caused by replacements above
s = dashes.ReplaceAllString(s, "-")
return s
}

83
settings/settings.go Normal file
View File

@@ -0,0 +1,83 @@
package settings
import (
"crypto/rand"
"log"
"strings"
"time"
"github.com/filebrowser/filebrowser/v2/rules"
)
const DefaultUsersHomeBasePath = "/users"
// AuthMethod describes an authentication method.
type AuthMethod string
// Settings contain the main settings of the application.
type Settings struct {
Key []byte `json:"key"`
Signup bool `json:"signup"`
CreateUserDir bool `json:"createUserDir"`
UserHomeBasePath string `json:"userHomeBasePath"`
Defaults UserDefaults `json:"defaults"`
AuthMethod AuthMethod `json:"authMethod"`
Branding Branding `json:"branding"`
Tus Tus `json:"tus"`
Commands map[string][]string `json:"commands"`
Shell []string `json:"shell"`
Rules []rules.Rule `json:"rules"`
}
// GetRules implements rules.Provider.
func (s *Settings) GetRules() []rules.Rule {
return s.Rules
}
// Server specific settings.
type Server struct {
Root string `json:"root"`
BaseURL string `json:"baseURL"`
Socket string `json:"socket"`
TLSKey string `json:"tlsKey"`
TLSCert string `json:"tlsCert"`
Port string `json:"port"`
Address string `json:"address"`
Log string `json:"log"`
EnableThumbnails bool `json:"enableThumbnails"`
ResizePreview bool `json:"resizePreview"`
EnableExec bool `json:"enableExec"`
TypeDetectionByHeader bool `json:"typeDetectionByHeader"`
AuthHook string `json:"authHook"`
TokenExpirationTime string `json:"tokenExpirationTime"`
}
// Clean cleans any variables that might need cleaning.
func (s *Server) Clean() {
s.BaseURL = strings.TrimSuffix(s.BaseURL, "/")
}
func (s *Server) GetTokenExpirationTime(fallback time.Duration) time.Duration {
if s.TokenExpirationTime == "" {
return fallback
}
duration, err := time.ParseDuration(s.TokenExpirationTime)
if err != nil {
log.Printf("[WARN] Failed to parse tokenExpirationTime: %v", err)
return fallback
}
return duration
}
// GenerateKey generates a key of 512 bits.
func GenerateKey() ([]byte, error) {
b := make([]byte, 64) //nolint:gomnd
_, err := rand.Read(b)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
return nil, err
}
return b, nil
}

110
settings/storage.go Normal file
View File

@@ -0,0 +1,110 @@
package settings
import (
"github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/rules"
"github.com/filebrowser/filebrowser/v2/users"
)
// StorageBackend is a settings storage backend.
type StorageBackend interface {
Get() (*Settings, error)
Save(*Settings) error
GetServer() (*Server, error)
SaveServer(*Server) 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) {
set, err := s.back.Get()
if err != nil {
return nil, err
}
if set.UserHomeBasePath == "" {
set.UserHomeBasePath = DefaultUsersHomeBasePath
}
if set.Tus == (Tus{}) {
set.Tus = Tus{
ChunkSize: DefaultTusChunkSize,
RetryCount: DefaultTusRetryCount,
}
}
return set, nil
}
var defaultEvents = []string{
"save",
"copy",
"rename",
"upload",
"delete",
}
// Save saves the settings for the current instance.
func (s *Storage) Save(set *Settings) error {
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
}
// GetServer wraps StorageBackend.GetServer.
func (s *Storage) GetServer() (*Server, error) {
return s.back.GetServer()
}
// SaveServer wraps StorageBackend.SaveServer and adds some verification.
func (s *Storage) SaveServer(ser *Server) error {
ser.Clean()
return s.back.SaveServer(ser)
}

10
settings/tus.go Normal file
View File

@@ -0,0 +1,10 @@
package settings
const DefaultTusChunkSize = 10 * 1024 * 1024 // 10MB
const DefaultTusRetryCount = 5
// Tus contains the tus.io settings of the app.
type Tus struct {
ChunkSize uint64 `json:"chunkSize"`
RetryCount uint16 `json:"retryCount"`
}