// 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" "fmt" "os" "path" "path/filepath" "strings" "golang.org/x/net/webdav" ) type DirectoryMapping struct { DataDirName string ReadOnly bool } // 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 { directoryMapping := ctx.Value("mapping").(*DirectoryMapping) // 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, directoryMapping.DataDirName, filepath.FromSlash(slashClean(name))) } func (d BaseDir) Mkdir(ctx context.Context, name string, perm os.FileMode) error { directoryMapping := ctx.Value("mapping").(*DirectoryMapping) if directoryMapping.ReadOnly { return fmt.Errorf("forbidden") } 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) { directoryMapping := ctx.Value("mapping").(*DirectoryMapping) const writeFlags = os.O_APPEND | os.O_CREATE | os.O_RDWR | os.O_WRONLY | os.O_TRUNC if flag&writeFlags != 0 && directoryMapping.ReadOnly { return nil, fmt.Errorf("forbidden") } 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 { directoryMapping := ctx.Value("mapping").(*DirectoryMapping) if directoryMapping.ReadOnly { return fmt.Errorf("forbidden") } 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 { directoryMapping := ctx.Value("mapping").(*DirectoryMapping) if directoryMapping.ReadOnly { return fmt.Errorf("forbidden") } 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) }