From f0a95397ee6c57a4466960f55b5616828eaa49d8 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Sat, 24 Nov 2018 14:59:42 +0100 Subject: [PATCH] Initial POC implementation of a user based WebDAV server --- .gitignore | 1 + filesystem.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 ++ go.sum | 2 + main.go | 53 ++++++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 .gitignore create mode 100644 filesystem.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62c8935 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/filesystem.go b/filesystem.go new file mode 100644 index 0000000..4ffdbaf --- /dev/null +++ b/filesystem.go @@ -0,0 +1,119 @@ +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// ---------- +// The following has been slightly modified from golang.org/x/net/webdav +// to allow context/request specific subdirectories, which could be +// derived from the authentication information. + +package main + +import ( + "context" + "golang.org/x/net/webdav" + "os" + "path" + "path/filepath" + "strings" +) + +// slashClean is equivalent to but slightly more efficient than +// path.Clean("/" + name). +func slashClean(name string) string { + if name == "" || name[0] != '/' { + name = "/" + name + } + return path.Clean(name) +} + +type BaseDir string + +func (d BaseDir) resolve(ctx context.Context, name string) string { + subDir := ctx.Value("dir").(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) + if dir == "" { + dir = "." + } + return filepath.Join(dir, subDir, filepath.FromSlash(slashClean(name))) +} + +func (d BaseDir) Mkdir(ctx context.Context, name string, perm os.FileMode) error { + if name = d.resolve(ctx, name); name == "" { + return os.ErrNotExist + } + return os.Mkdir(name, perm) +} + +func (d BaseDir) 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 + } + return f, nil +} + +func (d BaseDir) RemoveAll(ctx context.Context, name string) error { + if name = d.resolve(ctx, name); name == "" { + return os.ErrNotExist + } + if name == filepath.Clean(string(d)) { + // Prohibit removing the virtual root directory. + return os.ErrInvalid + } + return os.RemoveAll(name) +} + +func (d BaseDir) 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)); root == oldName || root == newName { + // Prohibit renaming from or to the virtual root directory. + return os.ErrInvalid + } + return os.Rename(oldName, newName) +} + +func (d BaseDir) Stat(ctx context.Context, name string) (os.FileInfo, error) { + if name = d.resolve(ctx, name); name == "" { + return nil, os.ErrNotExist + } + return os.Stat(name) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..43ff479 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module ShareDAV + +require golang.org/x/net v0.0.0-20181114220301-adae6a3d119a diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..531b5b3 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..b99b3c2 --- /dev/null +++ b/main.go @@ -0,0 +1,53 @@ +// Copyright (c) 2018, Andreas Schneider +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "context" + "golang.org/x/net/webdav" + "log" + "net/http" +) + +func main() { + h := &webdav.Handler{} + h.LockSystem = webdav.NewMemLS() + h.FileSystem = BaseDir("") + + authenticatedWebdavHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + if !ok || (username != "user1" && username != "user2") || password != "test" { + log.Println("invalid user pass", ok, username, password) + w.Header().Set("WWW-Authenticate", `Basic realm="ShareDAV"`) + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + + h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "dir", username))) + }) + + http.ListenAndServe(":3000", authenticatedWebdavHandler) +}