2017-04-25 15:57:59 +02:00
|
|
|
package webdav
|
|
|
|
|
|
|
|
// FIXME need to fix directory listings reading each file - make an
|
|
|
|
// override for getcontenttype property?
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/ncw/rclone/cmd"
|
2018-02-14 21:39:11 +01:00
|
|
|
"github.com/ncw/rclone/cmd/serve/httplib"
|
|
|
|
"github.com/ncw/rclone/cmd/serve/httplib/httpflags"
|
2017-04-25 15:57:59 +02:00
|
|
|
"github.com/ncw/rclone/fs"
|
2018-01-12 17:30:54 +01:00
|
|
|
"github.com/ncw/rclone/fs/log"
|
2017-04-25 15:57:59 +02:00
|
|
|
"github.com/ncw/rclone/vfs"
|
|
|
|
"github.com/ncw/rclone/vfs/vfsflags"
|
|
|
|
"github.com/spf13/cobra"
|
2018-04-06 20:13:27 +02:00
|
|
|
"golang.org/x/net/context" // switch to "context" when we stop supporting go1.8
|
2017-04-25 15:57:59 +02:00
|
|
|
"golang.org/x/net/webdav"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2018-02-14 21:39:11 +01:00
|
|
|
httpflags.AddFlags(Command.Flags())
|
2017-04-25 15:57:59 +02:00
|
|
|
vfsflags.AddFlags(Command.Flags())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Command definition for cobra
|
|
|
|
var Command = &cobra.Command{
|
|
|
|
Use: "webdav remote:path",
|
|
|
|
Short: `Serve remote:path over webdav.`,
|
|
|
|
Long: `
|
|
|
|
rclone serve webdav implements a basic webdav server to serve the
|
|
|
|
remote over HTTP via the webdav protocol. This can be viewed with a
|
|
|
|
webdav client or you can make a remote of type webdav to read and
|
|
|
|
write it.
|
|
|
|
|
2017-11-13 18:54:21 +01:00
|
|
|
NB at the moment each directory listing reads the start of each file
|
|
|
|
which is undesirable: see https://github.com/golang/go/issues/22577
|
2018-02-14 21:39:11 +01:00
|
|
|
` + httplib.Help + vfs.Help,
|
2017-04-25 15:57:59 +02:00
|
|
|
Run: func(command *cobra.Command, args []string) {
|
|
|
|
cmd.CheckArgs(1, 1, command, args)
|
2018-02-14 21:39:11 +01:00
|
|
|
f := cmd.NewFsSrc(args)
|
2017-04-25 15:57:59 +02:00
|
|
|
cmd.Run(false, false, command, func() error {
|
2018-02-14 21:39:11 +01:00
|
|
|
w := newWebDAV(f, &httpflags.Opt)
|
|
|
|
w.serve()
|
|
|
|
return nil
|
2017-04-25 15:57:59 +02:00
|
|
|
})
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// WebDAV is a webdav.FileSystem interface
|
|
|
|
//
|
|
|
|
// A FileSystem implements access to a collection of named files. The elements
|
|
|
|
// in a file path are separated by slash ('/', U+002F) characters, regardless
|
|
|
|
// of host operating system convention.
|
|
|
|
//
|
|
|
|
// Each method has the same semantics as the os package's function of the same
|
|
|
|
// name.
|
|
|
|
//
|
|
|
|
// Note that the os.Rename documentation says that "OS-specific restrictions
|
|
|
|
// might apply". In particular, whether or not renaming a file or directory
|
|
|
|
// overwriting another existing file or directory is an error is OS-dependent.
|
|
|
|
type WebDAV struct {
|
|
|
|
f fs.Fs
|
|
|
|
vfs *vfs.VFS
|
2018-02-14 21:39:11 +01:00
|
|
|
srv *httplib.Server
|
2017-04-25 15:57:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// check interface
|
|
|
|
var _ webdav.FileSystem = (*WebDAV)(nil)
|
|
|
|
|
2018-02-14 21:39:11 +01:00
|
|
|
// Make a new WebDAV to serve the remote
|
|
|
|
func newWebDAV(f fs.Fs, opt *httplib.Options) *WebDAV {
|
|
|
|
w := &WebDAV{
|
|
|
|
f: f,
|
|
|
|
vfs: vfs.New(f, &vfsflags.Opt),
|
|
|
|
}
|
|
|
|
|
|
|
|
handler := &webdav.Handler{
|
|
|
|
FileSystem: w,
|
|
|
|
LockSystem: webdav.NewMemLS(),
|
|
|
|
Logger: w.logRequest, // FIXME
|
|
|
|
}
|
|
|
|
|
|
|
|
w.srv = httplib.NewServer(handler, opt)
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
|
|
|
// serve runs the http server - doesn't return
|
|
|
|
func (w *WebDAV) serve() {
|
2018-04-04 15:56:26 +02:00
|
|
|
err := w.srv.Serve()
|
|
|
|
if err != nil {
|
|
|
|
fs.Errorf(w.f, "Opening listener: %v", err)
|
|
|
|
}
|
2018-02-14 21:39:11 +01:00
|
|
|
fs.Logf(w.f, "WebDav Server started on %s", w.srv.URL())
|
2018-04-04 15:56:26 +02:00
|
|
|
w.srv.Wait()
|
2018-02-14 21:39:11 +01:00
|
|
|
}
|
|
|
|
|
2017-04-25 15:57:59 +02:00
|
|
|
// logRequest is called by the webdav module on every request
|
|
|
|
func (w *WebDAV) logRequest(r *http.Request, err error) {
|
|
|
|
fs.Infof(r.URL.Path, "%s from %s", r.Method, r.RemoteAddr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mkdir creates a directory
|
|
|
|
func (w *WebDAV) Mkdir(ctx context.Context, name string, perm os.FileMode) (err error) {
|
2018-01-12 17:30:54 +01:00
|
|
|
defer log.Trace(name, "perm=%v", perm)("err = %v", &err)
|
2017-04-25 15:57:59 +02:00
|
|
|
dir, leaf, err := w.vfs.StatParent(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = dir.Mkdir(leaf)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenFile opens a file or a directory
|
|
|
|
func (w *WebDAV) OpenFile(ctx context.Context, name string, flags int, perm os.FileMode) (file webdav.File, err error) {
|
2018-01-12 17:30:54 +01:00
|
|
|
defer log.Trace(name, "flags=%v, perm=%v", flags, perm)("err = %v", &err)
|
2017-04-25 15:57:59 +02:00
|
|
|
return w.vfs.OpenFile(name, flags, perm)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveAll removes a file or a directory and its contents
|
|
|
|
func (w *WebDAV) RemoveAll(ctx context.Context, name string) (err error) {
|
2018-01-12 17:30:54 +01:00
|
|
|
defer log.Trace(name, "")("err = %v", &err)
|
2017-04-25 15:57:59 +02:00
|
|
|
node, err := w.vfs.Stat(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = node.RemoveAll()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rename a file or a directory
|
|
|
|
func (w *WebDAV) Rename(ctx context.Context, oldName, newName string) (err error) {
|
2018-01-12 17:30:54 +01:00
|
|
|
defer log.Trace(oldName, "newName=%q", newName)("err = %v", &err)
|
2017-04-25 15:57:59 +02:00
|
|
|
return w.vfs.Rename(oldName, newName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stat returns info about the file or directory
|
|
|
|
func (w *WebDAV) Stat(ctx context.Context, name string) (fi os.FileInfo, err error) {
|
2018-01-12 17:30:54 +01:00
|
|
|
defer log.Trace(name, "")("fi=%+v, err = %v", &fi, &err)
|
2017-04-25 15:57:59 +02:00
|
|
|
return w.vfs.Stat(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// check interface
|
|
|
|
var _ os.FileInfo = vfs.Node(nil)
|