diff --git a/backend/webdav/webdav.go b/backend/webdav/webdav.go
index 4f1d7692b..3310887e2 100644
--- a/backend/webdav/webdav.go
+++ b/backend/webdav/webdav.go
@@ -345,6 +345,41 @@ func itemIsDir(item *api.Response) bool {
return false
}
+// sabreNotFound is the exception SabreDAV based servers report for a
+// path which doesn't exist
+var sabreNotFound = []byte(`Sabre\DAV\Exception\NotFound`)
+
+// callPropfind does the PROPFIND in opts and decodes the response into result
+//
+// ownCloud 10.16 starts a 207 Multi-Status response before it finds
+// that the path doesn't exist, then appends a Sabre NotFound error to
+// it which makes it invalid XML. This is returned as the 404 *api.Error
+// which servers normally give for a path which doesn't exist.
+func (f *Fs) callPropfind(ctx context.Context, opts *rest.Opts, result *api.Multistatus) (resp *http.Response, err error) {
+ resp, err = f.srv.Call(ctx, opts)
+ if err != nil {
+ return resp, err
+ }
+ // Keep the start of the body to look for the error in
+ start := make([]byte, 4096)
+ n, _ := io.ReadFull(resp.Body, start)
+ start = start[:n]
+ body := resp.Body
+ resp.Body = struct {
+ io.Reader
+ io.Closer
+ }{io.MultiReader(bytes.NewReader(start), body), body}
+ err = rest.DecodeXML(resp, result)
+ if err != nil && bytes.Contains(start, sabreNotFound) {
+ return resp, &api.Error{
+ Exception: string(sabreNotFound),
+ Status: http.StatusText(http.StatusNotFound),
+ StatusCode: http.StatusNotFound,
+ }
+ }
+ return resp, err
+}
+
// readMetaDataForPath reads the metadata from the path
func (f *Fs) readMetaDataForPath(ctx context.Context, path string) (info *api.Prop, err error) {
// FIXME how do we read back additional properties?
@@ -366,7 +401,7 @@ func (f *Fs) readMetaDataForPath(ctx context.Context, path string) (info *api.Pr
var resp *http.Response
err = f.pacer.Call(func() (bool, error) {
var attempt api.Multistatus
- resp, err = f.srv.CallXML(ctx, &opts, nil, &attempt)
+ resp, err = f.callPropfind(ctx, &opts, &attempt)
if err == nil {
result = attempt
}
@@ -820,7 +855,7 @@ func (f *Fs) listAll(ctx context.Context, dir string, directoriesOnly bool, file
var resp *http.Response
err = f.pacer.Call(func() (bool, error) {
var attempt api.Multistatus
- resp, err = f.srv.CallXML(ctx, &opts, nil, &attempt)
+ resp, err = f.callPropfind(ctx, &opts, &attempt)
if err == nil {
result = attempt
}
diff --git a/backend/webdav/webdav_internal_test.go b/backend/webdav/webdav_internal_test.go
index 2902ecb92..f5de11d8b 100644
--- a/backend/webdav/webdav_internal_test.go
+++ b/backend/webdav/webdav_internal_test.go
@@ -354,3 +354,49 @@ func TestListAllRetryDoesNotConcatenate(t *testing.T) {
}
assert.ElementsMatch(t, want, remotes)
}
+
+// TestNotFoundInMultistatus checks that a missing path is detected when the
+// server reports it by appending a Sabre NotFound error to a 207 response it
+// has already started, as ownCloud 10.16 does, rather than by returning 404.
+func TestNotFoundInMultistatus(t *testing.T) {
+ head := `` + "\n" + `/HTTP/1.1 200 OK`
+ notFound := head + `
+
+ Sabre\DAV\Exception\NotFound
+ File with name missing could not be located
+
+`
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ require.Equal(t, "PROPFIND", r.Method)
+ w.Header().Set("Content-Type", "application/xml; charset=utf-8")
+ w.WriteHeader(207)
+ body := notFound
+ if r.URL.Path == "/" {
+ body = root
+ }
+ _, err := fmt.Fprint(w, body)
+ require.NoError(t, err)
+ }))
+ defer ts.Close()
+
+ configfile.Install()
+ m := configmap.Simple{
+ "type": "webdav",
+ "url": ts.URL,
+ }
+ ctx := context.Background()
+
+ // A root which doesn't exist yet
+ _, err := webdav.NewFs(ctx, remoteName, "missing", m)
+ require.NoError(t, err)
+
+ f, err := webdav.NewFs(ctx, remoteName, "", m)
+ require.NoError(t, err)
+
+ _, err = f.NewObject(ctx, "missing")
+ assert.Equal(t, fs.ErrorObjectNotFound, err)
+
+ _, err = f.List(ctx, "missing")
+ assert.Equal(t, fs.ErrorDirNotFound, err)
+}