OSDN Git Service

new repo
[bytom/vapor.git] / vendor / golang.org / x / net / webdav / prop_test.go
1 // Copyright 2015 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package webdav
6
7 import (
8         "encoding/xml"
9         "fmt"
10         "net/http"
11         "os"
12         "reflect"
13         "sort"
14         "testing"
15
16         "golang.org/x/net/context"
17 )
18
19 func TestMemPS(t *testing.T) {
20         ctx := context.Background()
21         // calcProps calculates the getlastmodified and getetag DAV: property
22         // values in pstats for resource name in file-system fs.
23         calcProps := func(name string, fs FileSystem, ls LockSystem, pstats []Propstat) error {
24                 fi, err := fs.Stat(ctx, name)
25                 if err != nil {
26                         return err
27                 }
28                 for _, pst := range pstats {
29                         for i, p := range pst.Props {
30                                 switch p.XMLName {
31                                 case xml.Name{Space: "DAV:", Local: "getlastmodified"}:
32                                         p.InnerXML = []byte(fi.ModTime().Format(http.TimeFormat))
33                                         pst.Props[i] = p
34                                 case xml.Name{Space: "DAV:", Local: "getetag"}:
35                                         if fi.IsDir() {
36                                                 continue
37                                         }
38                                         etag, err := findETag(ctx, fs, ls, name, fi)
39                                         if err != nil {
40                                                 return err
41                                         }
42                                         p.InnerXML = []byte(etag)
43                                         pst.Props[i] = p
44                                 }
45                         }
46                 }
47                 return nil
48         }
49
50         const (
51                 lockEntry = `` +
52                         `<D:lockentry xmlns:D="DAV:">` +
53                         `<D:lockscope><D:exclusive/></D:lockscope>` +
54                         `<D:locktype><D:write/></D:locktype>` +
55                         `</D:lockentry>`
56                 statForbiddenError = `<D:cannot-modify-protected-property xmlns:D="DAV:"/>`
57         )
58
59         type propOp struct {
60                 op            string
61                 name          string
62                 pnames        []xml.Name
63                 patches       []Proppatch
64                 wantPnames    []xml.Name
65                 wantPropstats []Propstat
66         }
67
68         testCases := []struct {
69                 desc        string
70                 noDeadProps bool
71                 buildfs     []string
72                 propOp      []propOp
73         }{{
74                 desc:    "propname",
75                 buildfs: []string{"mkdir /dir", "touch /file"},
76                 propOp: []propOp{{
77                         op:   "propname",
78                         name: "/dir",
79                         wantPnames: []xml.Name{
80                                 {Space: "DAV:", Local: "resourcetype"},
81                                 {Space: "DAV:", Local: "displayname"},
82                                 {Space: "DAV:", Local: "supportedlock"},
83                                 {Space: "DAV:", Local: "getlastmodified"},
84                         },
85                 }, {
86                         op:   "propname",
87                         name: "/file",
88                         wantPnames: []xml.Name{
89                                 {Space: "DAV:", Local: "resourcetype"},
90                                 {Space: "DAV:", Local: "displayname"},
91                                 {Space: "DAV:", Local: "getcontentlength"},
92                                 {Space: "DAV:", Local: "getlastmodified"},
93                                 {Space: "DAV:", Local: "getcontenttype"},
94                                 {Space: "DAV:", Local: "getetag"},
95                                 {Space: "DAV:", Local: "supportedlock"},
96                         },
97                 }},
98         }, {
99                 desc:    "allprop dir and file",
100                 buildfs: []string{"mkdir /dir", "write /file foobarbaz"},
101                 propOp: []propOp{{
102                         op:   "allprop",
103                         name: "/dir",
104                         wantPropstats: []Propstat{{
105                                 Status: http.StatusOK,
106                                 Props: []Property{{
107                                         XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
108                                         InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`),
109                                 }, {
110                                         XMLName:  xml.Name{Space: "DAV:", Local: "displayname"},
111                                         InnerXML: []byte("dir"),
112                                 }, {
113                                         XMLName:  xml.Name{Space: "DAV:", Local: "getlastmodified"},
114                                         InnerXML: nil, // Calculated during test.
115                                 }, {
116                                         XMLName:  xml.Name{Space: "DAV:", Local: "supportedlock"},
117                                         InnerXML: []byte(lockEntry),
118                                 }},
119                         }},
120                 }, {
121                         op:   "allprop",
122                         name: "/file",
123                         wantPropstats: []Propstat{{
124                                 Status: http.StatusOK,
125                                 Props: []Property{{
126                                         XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
127                                         InnerXML: []byte(""),
128                                 }, {
129                                         XMLName:  xml.Name{Space: "DAV:", Local: "displayname"},
130                                         InnerXML: []byte("file"),
131                                 }, {
132                                         XMLName:  xml.Name{Space: "DAV:", Local: "getcontentlength"},
133                                         InnerXML: []byte("9"),
134                                 }, {
135                                         XMLName:  xml.Name{Space: "DAV:", Local: "getlastmodified"},
136                                         InnerXML: nil, // Calculated during test.
137                                 }, {
138                                         XMLName:  xml.Name{Space: "DAV:", Local: "getcontenttype"},
139                                         InnerXML: []byte("text/plain; charset=utf-8"),
140                                 }, {
141                                         XMLName:  xml.Name{Space: "DAV:", Local: "getetag"},
142                                         InnerXML: nil, // Calculated during test.
143                                 }, {
144                                         XMLName:  xml.Name{Space: "DAV:", Local: "supportedlock"},
145                                         InnerXML: []byte(lockEntry),
146                                 }},
147                         }},
148                 }, {
149                         op:   "allprop",
150                         name: "/file",
151                         pnames: []xml.Name{
152                                 {"DAV:", "resourcetype"},
153                                 {"foo", "bar"},
154                         },
155                         wantPropstats: []Propstat{{
156                                 Status: http.StatusOK,
157                                 Props: []Property{{
158                                         XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
159                                         InnerXML: []byte(""),
160                                 }, {
161                                         XMLName:  xml.Name{Space: "DAV:", Local: "displayname"},
162                                         InnerXML: []byte("file"),
163                                 }, {
164                                         XMLName:  xml.Name{Space: "DAV:", Local: "getcontentlength"},
165                                         InnerXML: []byte("9"),
166                                 }, {
167                                         XMLName:  xml.Name{Space: "DAV:", Local: "getlastmodified"},
168                                         InnerXML: nil, // Calculated during test.
169                                 }, {
170                                         XMLName:  xml.Name{Space: "DAV:", Local: "getcontenttype"},
171                                         InnerXML: []byte("text/plain; charset=utf-8"),
172                                 }, {
173                                         XMLName:  xml.Name{Space: "DAV:", Local: "getetag"},
174                                         InnerXML: nil, // Calculated during test.
175                                 }, {
176                                         XMLName:  xml.Name{Space: "DAV:", Local: "supportedlock"},
177                                         InnerXML: []byte(lockEntry),
178                                 }}}, {
179                                 Status: http.StatusNotFound,
180                                 Props: []Property{{
181                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
182                                 }}},
183                         },
184                 }},
185         }, {
186                 desc:    "propfind DAV:resourcetype",
187                 buildfs: []string{"mkdir /dir", "touch /file"},
188                 propOp: []propOp{{
189                         op:     "propfind",
190                         name:   "/dir",
191                         pnames: []xml.Name{{"DAV:", "resourcetype"}},
192                         wantPropstats: []Propstat{{
193                                 Status: http.StatusOK,
194                                 Props: []Property{{
195                                         XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
196                                         InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`),
197                                 }},
198                         }},
199                 }, {
200                         op:     "propfind",
201                         name:   "/file",
202                         pnames: []xml.Name{{"DAV:", "resourcetype"}},
203                         wantPropstats: []Propstat{{
204                                 Status: http.StatusOK,
205                                 Props: []Property{{
206                                         XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
207                                         InnerXML: []byte(""),
208                                 }},
209                         }},
210                 }},
211         }, {
212                 desc:    "propfind unsupported DAV properties",
213                 buildfs: []string{"mkdir /dir"},
214                 propOp: []propOp{{
215                         op:     "propfind",
216                         name:   "/dir",
217                         pnames: []xml.Name{{"DAV:", "getcontentlanguage"}},
218                         wantPropstats: []Propstat{{
219                                 Status: http.StatusNotFound,
220                                 Props: []Property{{
221                                         XMLName: xml.Name{Space: "DAV:", Local: "getcontentlanguage"},
222                                 }},
223                         }},
224                 }, {
225                         op:     "propfind",
226                         name:   "/dir",
227                         pnames: []xml.Name{{"DAV:", "creationdate"}},
228                         wantPropstats: []Propstat{{
229                                 Status: http.StatusNotFound,
230                                 Props: []Property{{
231                                         XMLName: xml.Name{Space: "DAV:", Local: "creationdate"},
232                                 }},
233                         }},
234                 }},
235         }, {
236                 desc:    "propfind getetag for files but not for directories",
237                 buildfs: []string{"mkdir /dir", "touch /file"},
238                 propOp: []propOp{{
239                         op:     "propfind",
240                         name:   "/dir",
241                         pnames: []xml.Name{{"DAV:", "getetag"}},
242                         wantPropstats: []Propstat{{
243                                 Status: http.StatusNotFound,
244                                 Props: []Property{{
245                                         XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
246                                 }},
247                         }},
248                 }, {
249                         op:     "propfind",
250                         name:   "/file",
251                         pnames: []xml.Name{{"DAV:", "getetag"}},
252                         wantPropstats: []Propstat{{
253                                 Status: http.StatusOK,
254                                 Props: []Property{{
255                                         XMLName:  xml.Name{Space: "DAV:", Local: "getetag"},
256                                         InnerXML: nil, // Calculated during test.
257                                 }},
258                         }},
259                 }},
260         }, {
261                 desc:        "proppatch property on no-dead-properties file system",
262                 buildfs:     []string{"mkdir /dir"},
263                 noDeadProps: true,
264                 propOp: []propOp{{
265                         op:   "proppatch",
266                         name: "/dir",
267                         patches: []Proppatch{{
268                                 Props: []Property{{
269                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
270                                 }},
271                         }},
272                         wantPropstats: []Propstat{{
273                                 Status: http.StatusForbidden,
274                                 Props: []Property{{
275                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
276                                 }},
277                         }},
278                 }, {
279                         op:   "proppatch",
280                         name: "/dir",
281                         patches: []Proppatch{{
282                                 Props: []Property{{
283                                         XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
284                                 }},
285                         }},
286                         wantPropstats: []Propstat{{
287                                 Status:   http.StatusForbidden,
288                                 XMLError: statForbiddenError,
289                                 Props: []Property{{
290                                         XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
291                                 }},
292                         }},
293                 }},
294         }, {
295                 desc:    "proppatch dead property",
296                 buildfs: []string{"mkdir /dir"},
297                 propOp: []propOp{{
298                         op:   "proppatch",
299                         name: "/dir",
300                         patches: []Proppatch{{
301                                 Props: []Property{{
302                                         XMLName:  xml.Name{Space: "foo", Local: "bar"},
303                                         InnerXML: []byte("baz"),
304                                 }},
305                         }},
306                         wantPropstats: []Propstat{{
307                                 Status: http.StatusOK,
308                                 Props: []Property{{
309                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
310                                 }},
311                         }},
312                 }, {
313                         op:     "propfind",
314                         name:   "/dir",
315                         pnames: []xml.Name{{Space: "foo", Local: "bar"}},
316                         wantPropstats: []Propstat{{
317                                 Status: http.StatusOK,
318                                 Props: []Property{{
319                                         XMLName:  xml.Name{Space: "foo", Local: "bar"},
320                                         InnerXML: []byte("baz"),
321                                 }},
322                         }},
323                 }},
324         }, {
325                 desc:    "proppatch dead property with failed dependency",
326                 buildfs: []string{"mkdir /dir"},
327                 propOp: []propOp{{
328                         op:   "proppatch",
329                         name: "/dir",
330                         patches: []Proppatch{{
331                                 Props: []Property{{
332                                         XMLName:  xml.Name{Space: "foo", Local: "bar"},
333                                         InnerXML: []byte("baz"),
334                                 }},
335                         }, {
336                                 Props: []Property{{
337                                         XMLName:  xml.Name{Space: "DAV:", Local: "displayname"},
338                                         InnerXML: []byte("xxx"),
339                                 }},
340                         }},
341                         wantPropstats: []Propstat{{
342                                 Status:   http.StatusForbidden,
343                                 XMLError: statForbiddenError,
344                                 Props: []Property{{
345                                         XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
346                                 }},
347                         }, {
348                                 Status: StatusFailedDependency,
349                                 Props: []Property{{
350                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
351                                 }},
352                         }},
353                 }, {
354                         op:     "propfind",
355                         name:   "/dir",
356                         pnames: []xml.Name{{Space: "foo", Local: "bar"}},
357                         wantPropstats: []Propstat{{
358                                 Status: http.StatusNotFound,
359                                 Props: []Property{{
360                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
361                                 }},
362                         }},
363                 }},
364         }, {
365                 desc:    "proppatch remove dead property",
366                 buildfs: []string{"mkdir /dir"},
367                 propOp: []propOp{{
368                         op:   "proppatch",
369                         name: "/dir",
370                         patches: []Proppatch{{
371                                 Props: []Property{{
372                                         XMLName:  xml.Name{Space: "foo", Local: "bar"},
373                                         InnerXML: []byte("baz"),
374                                 }, {
375                                         XMLName:  xml.Name{Space: "spam", Local: "ham"},
376                                         InnerXML: []byte("eggs"),
377                                 }},
378                         }},
379                         wantPropstats: []Propstat{{
380                                 Status: http.StatusOK,
381                                 Props: []Property{{
382                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
383                                 }, {
384                                         XMLName: xml.Name{Space: "spam", Local: "ham"},
385                                 }},
386                         }},
387                 }, {
388                         op:   "propfind",
389                         name: "/dir",
390                         pnames: []xml.Name{
391                                 {Space: "foo", Local: "bar"},
392                                 {Space: "spam", Local: "ham"},
393                         },
394                         wantPropstats: []Propstat{{
395                                 Status: http.StatusOK,
396                                 Props: []Property{{
397                                         XMLName:  xml.Name{Space: "foo", Local: "bar"},
398                                         InnerXML: []byte("baz"),
399                                 }, {
400                                         XMLName:  xml.Name{Space: "spam", Local: "ham"},
401                                         InnerXML: []byte("eggs"),
402                                 }},
403                         }},
404                 }, {
405                         op:   "proppatch",
406                         name: "/dir",
407                         patches: []Proppatch{{
408                                 Remove: true,
409                                 Props: []Property{{
410                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
411                                 }},
412                         }},
413                         wantPropstats: []Propstat{{
414                                 Status: http.StatusOK,
415                                 Props: []Property{{
416                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
417                                 }},
418                         }},
419                 }, {
420                         op:   "propfind",
421                         name: "/dir",
422                         pnames: []xml.Name{
423                                 {Space: "foo", Local: "bar"},
424                                 {Space: "spam", Local: "ham"},
425                         },
426                         wantPropstats: []Propstat{{
427                                 Status: http.StatusNotFound,
428                                 Props: []Property{{
429                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
430                                 }},
431                         }, {
432                                 Status: http.StatusOK,
433                                 Props: []Property{{
434                                         XMLName:  xml.Name{Space: "spam", Local: "ham"},
435                                         InnerXML: []byte("eggs"),
436                                 }},
437                         }},
438                 }},
439         }, {
440                 desc:    "propname with dead property",
441                 buildfs: []string{"touch /file"},
442                 propOp: []propOp{{
443                         op:   "proppatch",
444                         name: "/file",
445                         patches: []Proppatch{{
446                                 Props: []Property{{
447                                         XMLName:  xml.Name{Space: "foo", Local: "bar"},
448                                         InnerXML: []byte("baz"),
449                                 }},
450                         }},
451                         wantPropstats: []Propstat{{
452                                 Status: http.StatusOK,
453                                 Props: []Property{{
454                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
455                                 }},
456                         }},
457                 }, {
458                         op:   "propname",
459                         name: "/file",
460                         wantPnames: []xml.Name{
461                                 {Space: "DAV:", Local: "resourcetype"},
462                                 {Space: "DAV:", Local: "displayname"},
463                                 {Space: "DAV:", Local: "getcontentlength"},
464                                 {Space: "DAV:", Local: "getlastmodified"},
465                                 {Space: "DAV:", Local: "getcontenttype"},
466                                 {Space: "DAV:", Local: "getetag"},
467                                 {Space: "DAV:", Local: "supportedlock"},
468                                 {Space: "foo", Local: "bar"},
469                         },
470                 }},
471         }, {
472                 desc:    "proppatch remove unknown dead property",
473                 buildfs: []string{"mkdir /dir"},
474                 propOp: []propOp{{
475                         op:   "proppatch",
476                         name: "/dir",
477                         patches: []Proppatch{{
478                                 Remove: true,
479                                 Props: []Property{{
480                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
481                                 }},
482                         }},
483                         wantPropstats: []Propstat{{
484                                 Status: http.StatusOK,
485                                 Props: []Property{{
486                                         XMLName: xml.Name{Space: "foo", Local: "bar"},
487                                 }},
488                         }},
489                 }},
490         }, {
491                 desc:    "bad: propfind unknown property",
492                 buildfs: []string{"mkdir /dir"},
493                 propOp: []propOp{{
494                         op:     "propfind",
495                         name:   "/dir",
496                         pnames: []xml.Name{{"foo:", "bar"}},
497                         wantPropstats: []Propstat{{
498                                 Status: http.StatusNotFound,
499                                 Props: []Property{{
500                                         XMLName: xml.Name{Space: "foo:", Local: "bar"},
501                                 }},
502                         }},
503                 }},
504         }}
505
506         for _, tc := range testCases {
507                 fs, err := buildTestFS(tc.buildfs)
508                 if err != nil {
509                         t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err)
510                 }
511                 if tc.noDeadProps {
512                         fs = noDeadPropsFS{fs}
513                 }
514                 ls := NewMemLS()
515                 for _, op := range tc.propOp {
516                         desc := fmt.Sprintf("%s: %s %s", tc.desc, op.op, op.name)
517                         if err = calcProps(op.name, fs, ls, op.wantPropstats); err != nil {
518                                 t.Fatalf("%s: calcProps: %v", desc, err)
519                         }
520
521                         // Call property system.
522                         var propstats []Propstat
523                         switch op.op {
524                         case "propname":
525                                 pnames, err := propnames(ctx, fs, ls, op.name)
526                                 if err != nil {
527                                         t.Errorf("%s: got error %v, want nil", desc, err)
528                                         continue
529                                 }
530                                 sort.Sort(byXMLName(pnames))
531                                 sort.Sort(byXMLName(op.wantPnames))
532                                 if !reflect.DeepEqual(pnames, op.wantPnames) {
533                                         t.Errorf("%s: pnames\ngot  %q\nwant %q", desc, pnames, op.wantPnames)
534                                 }
535                                 continue
536                         case "allprop":
537                                 propstats, err = allprop(ctx, fs, ls, op.name, op.pnames)
538                         case "propfind":
539                                 propstats, err = props(ctx, fs, ls, op.name, op.pnames)
540                         case "proppatch":
541                                 propstats, err = patch(ctx, fs, ls, op.name, op.patches)
542                         default:
543                                 t.Fatalf("%s: %s not implemented", desc, op.op)
544                         }
545                         if err != nil {
546                                 t.Errorf("%s: got error %v, want nil", desc, err)
547                                 continue
548                         }
549                         // Compare return values from allprop, propfind or proppatch.
550                         for _, pst := range propstats {
551                                 sort.Sort(byPropname(pst.Props))
552                         }
553                         for _, pst := range op.wantPropstats {
554                                 sort.Sort(byPropname(pst.Props))
555                         }
556                         sort.Sort(byStatus(propstats))
557                         sort.Sort(byStatus(op.wantPropstats))
558                         if !reflect.DeepEqual(propstats, op.wantPropstats) {
559                                 t.Errorf("%s: propstat\ngot  %q\nwant %q", desc, propstats, op.wantPropstats)
560                         }
561                 }
562         }
563 }
564
565 func cmpXMLName(a, b xml.Name) bool {
566         if a.Space != b.Space {
567                 return a.Space < b.Space
568         }
569         return a.Local < b.Local
570 }
571
572 type byXMLName []xml.Name
573
574 func (b byXMLName) Len() int           { return len(b) }
575 func (b byXMLName) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
576 func (b byXMLName) Less(i, j int) bool { return cmpXMLName(b[i], b[j]) }
577
578 type byPropname []Property
579
580 func (b byPropname) Len() int           { return len(b) }
581 func (b byPropname) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
582 func (b byPropname) Less(i, j int) bool { return cmpXMLName(b[i].XMLName, b[j].XMLName) }
583
584 type byStatus []Propstat
585
586 func (b byStatus) Len() int           { return len(b) }
587 func (b byStatus) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
588 func (b byStatus) Less(i, j int) bool { return b[i].Status < b[j].Status }
589
590 type noDeadPropsFS struct {
591         FileSystem
592 }
593
594 func (fs noDeadPropsFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
595         f, err := fs.FileSystem.OpenFile(ctx, name, flag, perm)
596         if err != nil {
597                 return nil, err
598         }
599         return noDeadPropsFile{f}, nil
600 }
601
602 // noDeadPropsFile wraps a File but strips any optional DeadPropsHolder methods
603 // provided by the underlying File implementation.
604 type noDeadPropsFile struct {
605         f File
606 }
607
608 func (f noDeadPropsFile) Close() error                              { return f.f.Close() }
609 func (f noDeadPropsFile) Read(p []byte) (int, error)                { return f.f.Read(p) }
610 func (f noDeadPropsFile) Readdir(count int) ([]os.FileInfo, error)  { return f.f.Readdir(count) }
611 func (f noDeadPropsFile) Seek(off int64, whence int) (int64, error) { return f.f.Seek(off, whence) }
612 func (f noDeadPropsFile) Stat() (os.FileInfo, error)                { return f.f.Stat() }
613 func (f noDeadPropsFile) Write(p []byte) (int, error)               { return f.f.Write(p) }