fix: rename/copy when using prefix

This commit is contained in:
Henrique Dias
2025-01-04 18:45:20 +01:00
parent 3790d7de08
commit 617496c019
2 changed files with 115 additions and 9 deletions
+39 -9
View File
@@ -19,6 +19,7 @@ type handlerUser struct {
type Handler struct { type Handler struct {
noPassword bool noPassword bool
behindProxy bool behindProxy bool
prefix string
user *handlerUser user *handlerUser
users map[string]*handlerUser users map[string]*handlerUser
} }
@@ -27,12 +28,12 @@ func NewHandler(c *Config) (http.Handler, error) {
h := &Handler{ h := &Handler{
noPassword: c.NoPassword, noPassword: c.NoPassword,
behindProxy: c.BehindProxy, behindProxy: c.BehindProxy,
prefix: c.Prefix,
user: &handlerUser{ user: &handlerUser{
User: User{ User: User{
UserPermissions: c.UserPermissions, UserPermissions: c.UserPermissions,
}, },
Handler: webdav.Handler{ Handler: webdav.Handler{
Prefix: c.Prefix,
FileSystem: Dir{ FileSystem: Dir{
Dir: webdav.Dir(c.Directory), Dir: webdav.Dir(c.Directory),
noSniff: c.NoSniff, noSniff: c.NoSniff,
@@ -47,7 +48,6 @@ func NewHandler(c *Config) (http.Handler, error) {
h.users[u.Username] = &handlerUser{ h.users[u.Username] = &handlerUser{
User: u, User: u,
Handler: webdav.Handler{ Handler: webdav.Handler{
Prefix: c.Prefix,
FileSystem: Dir{ FileSystem: Dir{
Dir: webdav.Dir(u.Directory), Dir: webdav.Dir(u.Directory),
noSniff: c.NoSniff, noSniff: c.NoSniff,
@@ -116,19 +116,49 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
zap.L().Info("user authorized", zap.String("username", username), zap.String("remote_address", remoteAddr)) zap.L().Info("user authorized", zap.String("username", username), zap.String("remote_address", remoteAddr))
} }
// Cleanup destination header if it's present by stripping out the prefix // Validate and clean destination header if it exists, by stripping out the
// and only keeping the path. // prefix and only keeping the actual destination path, always prefixed by
// a forward slash to ensure that the rules can successful match the path.
if destination := r.Header.Get("Destination"); destination != "" { if destination := r.Header.Get("Destination"); destination != "" {
u, err := url.Parse(destination) u, err := url.Parse(destination)
if err == nil { if err != nil {
destination = strings.TrimPrefix(u.Path, user.Prefix) http.Error(w, "Invalid Destination header", http.StatusBadRequest)
if !strings.HasPrefix(destination, "/") { return
destination = "/" + destination }
if h.prefix != "" {
destination = strings.TrimPrefix(u.Path, h.prefix)
if len(destination) >= len(u.Path) {
http.Error(w, "Invalid URL prefix", http.StatusBadRequest)
return
} }
r.Header.Set("Destination", destination) }
if !strings.HasPrefix(destination, "/") {
destination = "/" + destination
}
r.Header.Set("Destination", destination)
}
// Clean up URL path by stripping out the prefix, and ensuring it always begins
// with a forward slash, so that it can match against the rules.
path := r.URL.Path
if h.prefix != "" {
path = strings.TrimPrefix(r.URL.Path, h.prefix)
if len(path) >= len(r.URL.Path) {
http.Error(w, "Invalid URL prefix", http.StatusBadRequest)
return
} }
} }
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
r.URL.Path = path
// Checks for user permissions relatively to this PATH. // Checks for user permissions relatively to this PATH.
allowed := user.Allowed(r, func(filename string) bool { allowed := user.Allowed(r, func(filename string) bool {
_, err := user.FileSystem.Stat(r.Context(), filename) _, err := user.FileSystem.Stat(r.Context(), filename)
+76
View File
@@ -292,6 +292,82 @@ users:
require.ErrorContains(t, err, "403") require.ErrorContains(t, err, "403")
} }
func TestServerRulesPrefix(t *testing.T) {
t.Parallel()
dir := makeTestDirectory(t, map[string][]byte{
"foo.txt": []byte("foo"),
"bar.js": []byte("foo js"),
"a/foo.js": []byte("foo js"),
"a/foo.txt": []byte("foo txt"),
"b/foo.txt": []byte("foo b"),
"c/a.txt": []byte("b"),
"c/b.txt": []byte("b"),
"c/c.txt": []byte("b"),
})
srv := makeTestServer(t, fmt.Sprintf(`
directory: %s
permissions: CRUD
prefix: /prefix
users:
- username: basic
password: basic
rules:
- regex: "^.+.js$"
permissions: R
- path: "/b/"
permissions: R
- path: "/a/foo.txt"
permissions: none
- path: "/c/"
permissions: none
`, dir))
client := gowebdav.NewClient(srv.URL, "basic", "basic")
files, err := client.ReadDir("/prefix")
require.NoError(t, err)
require.Len(t, files, 5)
err = client.Write("/prefix/foo.txt", []byte("new"), 0666)
require.NoError(t, err)
err = client.Write("/prefix/new.txt", []byte("new"), 0666)
require.NoError(t, err)
err = client.Copy("/prefix/bar.js", "/prefix/b/bar.js", false)
require.ErrorContains(t, err, "403")
err = client.Copy("/prefix/bar.js", "/prefix/bar.jsx", false)
require.NoError(t, err)
err = client.Copy("/prefix/b/foo.txt", "/prefix/foo1.txt", false)
require.NoError(t, err)
err = client.Rename("/prefix/b/foo.txt", "/prefix/foo2.txt", false)
require.ErrorContains(t, err, "403")
_, err = client.Read("/prefix/a/foo.txt")
require.ErrorContains(t, err, "403")
err = client.Write("/prefix/a/foo.js", []byte("new"), 0666)
require.ErrorContains(t, err, "403")
err = client.Write("/prefix/b/foo.txt", []byte("new"), 0666)
require.ErrorContains(t, err, "403")
_, err = client.ReadDir("/prefix/c")
require.ErrorContains(t, err, "403")
_, err = client.Read("/prefix/c/a.txt")
require.ErrorContains(t, err, "403")
err = client.Write("/prefix/c/b.txt", []byte("new"), 0666)
require.ErrorContains(t, err, "403")
}
func TestServerPermissions(t *testing.T) { func TestServerPermissions(t *testing.T) {
t.Parallel() t.Parallel()