Skip to content

Commit 806b1f1

Browse files
committed
webdav: use url.URL to escape lockroot
The lockroot is a path in the webdav library, meaning it may have slashes in it. Use the url.URL struct to handle this case when escaping the lockroot. Also add some unit tests.
1 parent c72303b commit 806b1f1

File tree

2 files changed

+102
-12
lines changed

2 files changed

+102
-12
lines changed

webdav/webdav_test.go

+100-11
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ import (
2121
"testing"
2222
)
2323

24+
// createLockBody comes from the example in Section 9.10.7.
25+
const createLockBody = `<?xml version="1.0" encoding="utf-8" ?>
26+
<D:lockinfo xmlns:D='DAV:'>
27+
<D:lockscope><D:exclusive/></D:lockscope>
28+
<D:locktype><D:write/></D:locktype>
29+
<D:owner>
30+
<D:href>http://example.org/~ejw/contact.html</D:href>
31+
</D:owner>
32+
</D:lockinfo>
33+
`
34+
2435
// TODO: add tests to check XML responses with the expected prefix path
2536
func TestPrefix(t *testing.T) {
2637
const dst, blah = "Destination", "blah blah blah"
2738

28-
// createLockBody comes from the example in Section 9.10.7.
29-
const createLockBody = `<?xml version="1.0" encoding="utf-8" ?>
30-
<D:lockinfo xmlns:D='DAV:'>
31-
<D:lockscope><D:exclusive/></D:lockscope>
32-
<D:locktype><D:write/></D:locktype>
33-
<D:owner>
34-
<D:href>http://example.org/~ejw/contact.html</D:href>
35-
</D:owner>
36-
</D:lockinfo>
37-
`
38-
3939
do := func(method, urlStr string, body string, wantStatusCode int, headers ...string) (http.Header, error) {
4040
var bodyReader io.Reader
4141
if body != "" {
@@ -347,3 +347,92 @@ func TestFilenameEscape(t *testing.T) {
347347
}
348348
}
349349
}
350+
351+
func TestLockrootEscape(t *testing.T) {
352+
lockrootRe := regexp.MustCompile(`<D:lockroot><D:href>([^<]*)</D:href></D:lockroot>`)
353+
do := func(urlStr string) (string, error) {
354+
bodyReader := strings.NewReader(createLockBody)
355+
req, err := http.NewRequest("LOCK", urlStr, bodyReader)
356+
if err != nil {
357+
return "", err
358+
}
359+
res, err := http.DefaultClient.Do(req)
360+
if err != nil {
361+
return "", err
362+
}
363+
defer res.Body.Close()
364+
365+
b, err := ioutil.ReadAll(res.Body)
366+
if err != nil {
367+
return "", err
368+
}
369+
lockrootMatch := lockrootRe.FindStringSubmatch(string(b))
370+
if len(lockrootMatch) != 2 {
371+
return "", errors.New("D:lockroot not found")
372+
}
373+
374+
return lockrootMatch[1], nil
375+
}
376+
377+
testCases := []struct {
378+
name, wantLockroot string
379+
}{{
380+
name: `/foo%bar`,
381+
wantLockroot: `/foo%25bar`,
382+
}, {
383+
name: `/こんにちわ世界`,
384+
wantLockroot: `/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%82%8F%E4%B8%96%E7%95%8C`,
385+
}, {
386+
name: `/Program Files/`,
387+
wantLockroot: `/Program%20Files/`,
388+
}, {
389+
name: `/go+lang`,
390+
wantLockroot: `/go+lang`,
391+
}, {
392+
name: `/go&lang`,
393+
wantLockroot: `/go&amp;lang`,
394+
}, {
395+
name: `/go<lang`,
396+
wantLockroot: `/go%3Clang`,
397+
}}
398+
ctx := context.Background()
399+
fs := NewMemFS()
400+
for _, tc := range testCases {
401+
if tc.name != "/" {
402+
if strings.HasSuffix(tc.name, "/") {
403+
if err := fs.Mkdir(ctx, tc.name, 0755); err != nil {
404+
t.Fatalf("name=%q: Mkdir: %v", tc.name, err)
405+
}
406+
} else {
407+
f, err := fs.OpenFile(ctx, tc.name, os.O_CREATE, 0644)
408+
if err != nil {
409+
t.Fatalf("name=%q: OpenFile: %v", tc.name, err)
410+
}
411+
f.Close()
412+
}
413+
}
414+
}
415+
416+
srv := httptest.NewServer(&Handler{
417+
FileSystem: fs,
418+
LockSystem: NewMemLS(),
419+
})
420+
defer srv.Close()
421+
422+
u, err := url.Parse(srv.URL)
423+
if err != nil {
424+
t.Fatal(err)
425+
}
426+
427+
for _, tc := range testCases {
428+
u.Path = tc.name
429+
gotLockroot, err := do(u.String())
430+
if err != nil {
431+
t.Errorf("name=%q: LOCK: %v", tc.name, err)
432+
continue
433+
}
434+
if gotLockroot != tc.wantLockroot {
435+
t.Errorf("name=%q: got lockroot %q, want %q", tc.name, gotLockroot, tc.wantLockroot)
436+
}
437+
}
438+
}

webdav/xml.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
9191
// PathEscape the root. Any URLs in this response body should match data on the wire
9292
// meaning if a request came in escaped (which it should have), it should go out that
9393
// way as well.
94-
root := url.PathEscape(ld.Root)
94+
rootUrl := url.URL{Path: ld.Root}
95+
root := rootUrl.EscapedPath()
9596
return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
9697
"<D:prop xmlns:D=\"DAV:\"><D:lockdiscovery><D:activelock>\n"+
9798
" <D:locktype><D:write/></D:locktype>\n"+

0 commit comments

Comments
 (0)