prop_test.go 15 KB

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