1
2
3
4
5 package webdav
6
7 import (
8 "context"
9 "errors"
10 "fmt"
11 "io"
12 "io/ioutil"
13 "net/http"
14 "net/http/httptest"
15 "net/url"
16 "os"
17 "reflect"
18 "regexp"
19 "sort"
20 "strings"
21 "testing"
22 )
23
24
25 func TestPrefix(t *testing.T) {
26 const dst, blah = "Destination", "blah blah blah"
27
28
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
39 do := func(method, urlStr string, body string, wantStatusCode int, headers ...string) (http.Header, error) {
40 var bodyReader io.Reader
41 if body != "" {
42 bodyReader = strings.NewReader(body)
43 }
44 req, err := http.NewRequest(method, urlStr, bodyReader)
45 if err != nil {
46 return nil, err
47 }
48 for len(headers) >= 2 {
49 req.Header.Add(headers[0], headers[1])
50 headers = headers[2:]
51 }
52 res, err := http.DefaultTransport.RoundTrip(req)
53 if err != nil {
54 return nil, err
55 }
56 defer res.Body.Close()
57 if res.StatusCode != wantStatusCode {
58 return nil, fmt.Errorf("got status code %d, want %d", res.StatusCode, wantStatusCode)
59 }
60 return res.Header, nil
61 }
62
63 prefixes := []string{
64 "/",
65 "/a/",
66 "/a/b/",
67 "/a/b/c/",
68 }
69 ctx := context.Background()
70 for _, prefix := range prefixes {
71 fs := NewMemFS()
72 h := &Handler{
73 FileSystem: fs,
74 LockSystem: NewMemLS(),
75 }
76 mux := http.NewServeMux()
77 if prefix != "/" {
78 h.Prefix = prefix
79 }
80 mux.Handle(prefix, h)
81 srv := httptest.NewServer(mux)
82 defer srv.Close()
83
84
85
86
87
88
89
90
91
92
93
94
95
96 wantA := map[string]int{
97 "/": http.StatusCreated,
98 "/a/": http.StatusMovedPermanently,
99 "/a/b/": http.StatusNotFound,
100 "/a/b/c/": http.StatusNotFound,
101 }[prefix]
102 if _, err := do("MKCOL", srv.URL+"/a", "", wantA); err != nil {
103 t.Errorf("prefix=%-9q MKCOL /a: %v", prefix, err)
104 continue
105 }
106
107 wantB := map[string]int{
108 "/": http.StatusCreated,
109 "/a/": http.StatusCreated,
110 "/a/b/": http.StatusMovedPermanently,
111 "/a/b/c/": http.StatusNotFound,
112 }[prefix]
113 if _, err := do("MKCOL", srv.URL+"/a/b", "", wantB); err != nil {
114 t.Errorf("prefix=%-9q MKCOL /a/b: %v", prefix, err)
115 continue
116 }
117
118 wantC := map[string]int{
119 "/": http.StatusCreated,
120 "/a/": http.StatusCreated,
121 "/a/b/": http.StatusCreated,
122 "/a/b/c/": http.StatusMovedPermanently,
123 }[prefix]
124 if _, err := do("PUT", srv.URL+"/a/b/c", blah, wantC); err != nil {
125 t.Errorf("prefix=%-9q PUT /a/b/c: %v", prefix, err)
126 continue
127 }
128
129 wantD := map[string]int{
130 "/": http.StatusCreated,
131 "/a/": http.StatusCreated,
132 "/a/b/": http.StatusCreated,
133 "/a/b/c/": http.StatusMovedPermanently,
134 }[prefix]
135 if _, err := do("COPY", srv.URL+"/a/b/c", "", wantD, dst, srv.URL+"/a/b/d"); err != nil {
136 t.Errorf("prefix=%-9q COPY /a/b/c /a/b/d: %v", prefix, err)
137 continue
138 }
139
140 wantE := map[string]int{
141 "/": http.StatusCreated,
142 "/a/": http.StatusCreated,
143 "/a/b/": http.StatusCreated,
144 "/a/b/c/": http.StatusNotFound,
145 }[prefix]
146 if _, err := do("MKCOL", srv.URL+"/a/b/e", "", wantE); err != nil {
147 t.Errorf("prefix=%-9q MKCOL /a/b/e: %v", prefix, err)
148 continue
149 }
150
151 wantF := map[string]int{
152 "/": http.StatusCreated,
153 "/a/": http.StatusCreated,
154 "/a/b/": http.StatusCreated,
155 "/a/b/c/": http.StatusNotFound,
156 }[prefix]
157 if _, err := do("MOVE", srv.URL+"/a/b/d", "", wantF, dst, srv.URL+"/a/b/e/f"); err != nil {
158 t.Errorf("prefix=%-9q MOVE /a/b/d /a/b/e/f: %v", prefix, err)
159 continue
160 }
161
162 var lockToken string
163 wantG := map[string]int{
164 "/": http.StatusCreated,
165 "/a/": http.StatusCreated,
166 "/a/b/": http.StatusCreated,
167 "/a/b/c/": http.StatusNotFound,
168 }[prefix]
169 if h, err := do("LOCK", srv.URL+"/a/b/e/g", createLockBody, wantG); err != nil {
170 t.Errorf("prefix=%-9q LOCK /a/b/e/g: %v", prefix, err)
171 continue
172 } else {
173 lockToken = h.Get("Lock-Token")
174 }
175
176 ifHeader := fmt.Sprintf("<%s/a/b/e/g> (%s)", srv.URL, lockToken)
177 wantH := map[string]int{
178 "/": http.StatusCreated,
179 "/a/": http.StatusCreated,
180 "/a/b/": http.StatusCreated,
181 "/a/b/c/": http.StatusNotFound,
182 }[prefix]
183 if _, err := do("PUT", srv.URL+"/a/b/e/g", blah, wantH, "If", ifHeader); err != nil {
184 t.Errorf("prefix=%-9q PUT /a/b/e/g: %v", prefix, err)
185 continue
186 }
187
188 got, err := find(ctx, nil, fs, "/")
189 if err != nil {
190 t.Errorf("prefix=%-9q find: %v", prefix, err)
191 continue
192 }
193 sort.Strings(got)
194 want := map[string][]string{
195 "/": {"/", "/a", "/a/b", "/a/b/c", "/a/b/e", "/a/b/e/f", "/a/b/e/g"},
196 "/a/": {"/", "/b", "/b/c", "/b/e", "/b/e/f", "/b/e/g"},
197 "/a/b/": {"/", "/c", "/e", "/e/f", "/e/g"},
198 "/a/b/c/": {"/"},
199 }[prefix]
200 if !reflect.DeepEqual(got, want) {
201 t.Errorf("prefix=%-9q find:\ngot %v\nwant %v", prefix, got, want)
202 continue
203 }
204 }
205 }
206
207 func TestEscapeXML(t *testing.T) {
208
209
210
211
212
213
214 testCases := map[string]string{
215 "": "",
216 " ": " ",
217 "&": "&",
218 "*": "*",
219 "+": "+",
220 ",": ",",
221 "-": "-",
222 ".": ".",
223 "/": "/",
224 "0": "0",
225 "9": "9",
226 ":": ":",
227 "<": "<",
228 ">": ">",
229 "A": "A",
230 "_": "_",
231 "a": "a",
232 "~": "~",
233 "\u0201": "\u0201",
234 "&": "&amp;",
235 "foo&<b/ar>baz": "foo&<b/ar>baz",
236 }
237
238 for in, want := range testCases {
239 if got := escapeXML(in); got != want {
240 t.Errorf("in=%q: got %q, want %q", in, got, want)
241 }
242 }
243 }
244
245 func TestFilenameEscape(t *testing.T) {
246 hrefRe := regexp.MustCompile(`<D:href>([^<]*)</D:href>`)
247 displayNameRe := regexp.MustCompile(`<D:displayname>([^<]*)</D:displayname>`)
248 do := func(method, urlStr string) (string, string, error) {
249 req, err := http.NewRequest(method, urlStr, nil)
250 if err != nil {
251 return "", "", err
252 }
253 res, err := http.DefaultClient.Do(req)
254 if err != nil {
255 return "", "", err
256 }
257 defer res.Body.Close()
258
259 b, err := ioutil.ReadAll(res.Body)
260 if err != nil {
261 return "", "", err
262 }
263 hrefMatch := hrefRe.FindStringSubmatch(string(b))
264 if len(hrefMatch) != 2 {
265 return "", "", errors.New("D:href not found")
266 }
267 displayNameMatch := displayNameRe.FindStringSubmatch(string(b))
268 if len(displayNameMatch) != 2 {
269 return "", "", errors.New("D:displayname not found")
270 }
271
272 return hrefMatch[1], displayNameMatch[1], nil
273 }
274
275 testCases := []struct {
276 name, wantHref, wantDisplayName string
277 }{{
278 name: `/foo%bar`,
279 wantHref: `/foo%25bar`,
280 wantDisplayName: `foo%bar`,
281 }, {
282 name: `/こんにちわ世界`,
283 wantHref: `/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%82%8F%E4%B8%96%E7%95%8C`,
284 wantDisplayName: `こんにちわ世界`,
285 }, {
286 name: `/Program Files/`,
287 wantHref: `/Program%20Files/`,
288 wantDisplayName: `Program Files`,
289 }, {
290 name: `/go+lang`,
291 wantHref: `/go+lang`,
292 wantDisplayName: `go+lang`,
293 }, {
294 name: `/go&lang`,
295 wantHref: `/go&lang`,
296 wantDisplayName: `go&lang`,
297 }, {
298 name: `/go<lang`,
299 wantHref: `/go%3Clang`,
300 wantDisplayName: `go<lang`,
301 }, {
302 name: `/`,
303 wantHref: `/`,
304 wantDisplayName: ``,
305 }}
306 ctx := context.Background()
307 fs := NewMemFS()
308 for _, tc := range testCases {
309 if tc.name != "/" {
310 if strings.HasSuffix(tc.name, "/") {
311 if err := fs.Mkdir(ctx, tc.name, 0755); err != nil {
312 t.Fatalf("name=%q: Mkdir: %v", tc.name, err)
313 }
314 } else {
315 f, err := fs.OpenFile(ctx, tc.name, os.O_CREATE, 0644)
316 if err != nil {
317 t.Fatalf("name=%q: OpenFile: %v", tc.name, err)
318 }
319 f.Close()
320 }
321 }
322 }
323
324 srv := httptest.NewServer(&Handler{
325 FileSystem: fs,
326 LockSystem: NewMemLS(),
327 })
328 defer srv.Close()
329
330 u, err := url.Parse(srv.URL)
331 if err != nil {
332 t.Fatal(err)
333 }
334
335 for _, tc := range testCases {
336 u.Path = tc.name
337 gotHref, gotDisplayName, err := do("PROPFIND", u.String())
338 if err != nil {
339 t.Errorf("name=%q: PROPFIND: %v", tc.name, err)
340 continue
341 }
342 if gotHref != tc.wantHref {
343 t.Errorf("name=%q: got href %q, want %q", tc.name, gotHref, tc.wantHref)
344 }
345 if gotDisplayName != tc.wantDisplayName {
346 t.Errorf("name=%q: got dispayname %q, want %q", tc.name, gotDisplayName, tc.wantDisplayName)
347 }
348 }
349 }
350
View as plain text