mirror of
https://github.com/rclone/rclone.git
synced 2026-10-09 14:39:28 -04:00
webdav: fix "XML syntax error" on paths which don't exist with ownCloud 10.16
ownCloud 10.16 answers a PROPFIND for a path which doesn't exist with
a 207 Multi-Status response, rather than a 404, with a body which
starts a multistatus document then appends a Sabre NotFound error to
it. This isn't valid XML so rclone failed with
read metadata failed: XML syntax error on line 2: expected attribute name in element
whenever it was pointed at a directory which didn't exist yet.
Detect the NotFound error in the response and treat it as a 404.
This commit is contained in:
1 parent
be8b238033
commit
f217c8a2aa
2 files changed
+83
-2
No files matched your search
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 := `<?xml version="1.0"?>` + "\n" + `<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns"`
|
||||
root := head + `><d:response><d:href>/</d:href><d:propstat><d:prop><d:resourcetype><d:collection/></d:resourcetype></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>`
|
||||
notFound := head + `<?xml version="1.0" encoding="utf-8"?>
|
||||
<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
|
||||
<s:exception>Sabre\DAV\Exception\NotFound</s:exception>
|
||||
<s:message>File with name missing could not be located</s:message>
|
||||
</d:error>
|
||||
`
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user