156 lines
3.9 KiB
Go
156 lines
3.9 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/net/webdav"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// This file is an extension of golang.org/x/net/webdav/file.go.
|
|
|
|
// Dir is specialization of webdav.Dir with respect of an authenticated
|
|
// user to allow configuration access.
|
|
type Dir struct {
|
|
Config *Config
|
|
}
|
|
|
|
func (d Dir) resolveUser(ctx context.Context) string {
|
|
authInfo := AuthFromContext(ctx)
|
|
if authInfo != nil && authInfo.Authenticated {
|
|
return authInfo.Username
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// resolve tries to gain authentication information and suffixes the BaseDir with the
|
|
// username of the authentication information. If none authentication information can
|
|
// achieved during the process, the BaseDir is used
|
|
func (d Dir) resolve(ctx context.Context, name string) string {
|
|
// This implementation is based on Dir.Open's code in the standard net/http package.
|
|
if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
|
|
strings.Contains(name, "\x00") {
|
|
return ""
|
|
}
|
|
dir := string(d.Config.Dir)
|
|
if dir == "" {
|
|
dir = "."
|
|
}
|
|
|
|
// Second barrier after basic auth process
|
|
authInfo := AuthFromContext(ctx)
|
|
if authInfo != nil && authInfo.Authenticated {
|
|
userInfo := d.Config.Users[authInfo.Username]
|
|
if userInfo != nil && userInfo.Subdir != nil {
|
|
return filepath.Join(dir, *userInfo.Subdir, filepath.FromSlash(path.Clean("/"+name)))
|
|
}
|
|
}
|
|
|
|
return filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
|
|
}
|
|
|
|
// Mkdir resolves the physical file and delegates this to an os.Mkdir execution
|
|
func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
|
if name = d.resolve(ctx, name); name == "" {
|
|
return os.ErrNotExist
|
|
}
|
|
err := os.Mkdir(name, perm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.Config.Log.Create {
|
|
log.WithFields(log.Fields{
|
|
"path": name,
|
|
"user": d.resolveUser(ctx),
|
|
}).Info("Created directory")
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// OpenFile resolves the physical file and delegates this to an os.OpenFile execution
|
|
func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
|
if name = d.resolve(ctx, name); name == "" {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
f, err := os.OpenFile(name, flag, perm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if d.Config.Log.Read {
|
|
log.WithFields(log.Fields{
|
|
"path": name,
|
|
"user": d.resolveUser(ctx),
|
|
}).Info("Opened file")
|
|
}
|
|
|
|
return f, nil
|
|
}
|
|
|
|
// RemoveAll resolves the physical file and delegates this to an os.RemoveAll execution
|
|
func (d Dir) RemoveAll(ctx context.Context, name string) error {
|
|
if name = d.resolve(ctx, name); name == "" {
|
|
return os.ErrNotExist
|
|
}
|
|
if name == filepath.Clean(string(d.Config.Dir)) {
|
|
// Prohibit removing the virtual root directory.
|
|
return os.ErrInvalid
|
|
}
|
|
|
|
err := os.RemoveAll(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.Config.Log.Delete {
|
|
log.WithFields(log.Fields{
|
|
"path": name,
|
|
"user": d.resolveUser(ctx),
|
|
}).Info("Deleted file or directory")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Rename resolves the physical file and delegates this to an os.Rename execution
|
|
func (d Dir) Rename(ctx context.Context, oldName, newName string) error {
|
|
if oldName = d.resolve(ctx, oldName); oldName == "" {
|
|
return os.ErrNotExist
|
|
}
|
|
if newName = d.resolve(ctx, newName); newName == "" {
|
|
return os.ErrNotExist
|
|
}
|
|
if root := filepath.Clean(string(d.Config.Dir)); root == oldName || root == newName {
|
|
// Prohibit renaming from or to the virtual root directory.
|
|
return os.ErrInvalid
|
|
}
|
|
|
|
err := os.Rename(oldName, newName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if d.Config.Log.Update {
|
|
log.WithFields(log.Fields{
|
|
"oldPath": oldName,
|
|
"newPath": newName,
|
|
"user": d.resolveUser(ctx),
|
|
}).Info("Renamed file or directory")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stat resolves the physical file and delegates this to an os.Stat execution
|
|
func (d Dir) Stat(ctx context.Context, name string) (os.FileInfo, error) {
|
|
if name = d.resolve(ctx, name); name == "" {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
return os.Stat(name)
|
|
}
|