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