props.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. /*
  2. Tests for property handling
  3. Copyright (C) 2002-2009, Joe Orton <[email protected]>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15. */
  16. #include "config.h"
  17. #include <sys/types.h>
  18. #ifdef HAVE_STDLIB_H
  19. #include <stdlib.h>
  20. #endif
  21. #ifdef HAVE_UNISTD_H
  22. #include <unistd.h>
  23. #endif
  24. #include "ne_props.h"
  25. #include "tests.h"
  26. #include "child.h"
  27. #include "utils.h"
  28. static const ne_propname p_alpha = {"DAV:", "alpha"},
  29. p_beta = {"http://webdav.org/random/namespace", "beta"};
  30. /* Tests little except that ne_proppatch() doesn't segfault. */
  31. static int patch_simple(void)
  32. {
  33. ne_session *sess;
  34. ne_proppatch_operation ops[] = {
  35. { &p_alpha, ne_propset, "fish" },
  36. { &p_beta, ne_propremove, NULL },
  37. { NULL, ne_propset, NULL }
  38. };
  39. CALL(make_session(&sess, single_serve_string,
  40. "HTTP/1.1 200 Goferit\r\n"
  41. "Connection: close\r\n\r\n"));
  42. ONREQ(ne_proppatch(sess, "/fish", ops));
  43. return destroy_and_wait(sess);
  44. }
  45. #define RESP207 "HTTP/1.0 207 Stuff\r\n" "Server: foo\r\n\r\n"
  46. static void dummy_results(void *ud, const ne_uri *uri,
  47. const ne_prop_result_set *rset)
  48. {
  49. NE_DEBUG(NE_DBG_HTTP, "dummy_results.\n");
  50. }
  51. /* Regression tests for propfind bodies which caused segfaults. */
  52. static int regress(void)
  53. {
  54. static const char *bodies[] = {
  55. RESP207 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
  56. "<multistatus xmlns=\"DAV:\">"
  57. "<response><propstat><prop><href>"
  58. "</href></prop></propstat></response>"
  59. "</multistatus>",
  60. /* segfaults with neon <= 0.23.5 */
  61. RESP207 "<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\">"
  62. "<D:response><D:href>/foo/</D:href>"
  63. "<D:propstat/>"
  64. "<D:status>HTTP/1.1 404 Not Found</D:status>"
  65. "</D:multistatus>",
  66. /* format string handling with neon <= 0.24.4 */
  67. RESP207 "<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\">"
  68. "<D:response><D:href>/foo/</D:href>"
  69. "<D:propstat/>"
  70. "<D:status>%s%s%s%s</D:status>"
  71. "</D:response></D:multistatus>",
  72. NULL,
  73. };
  74. ne_session *sess;
  75. int n;
  76. for (n = 0; bodies[n] != NULL; n++) {
  77. CALL(make_session(&sess, single_serve_string, (void *)bodies[n]));
  78. ne_simple_propfind(sess, "/", 0, NULL, dummy_results, NULL);
  79. ne_session_destroy(sess);
  80. CALL(await_server());
  81. }
  82. return OK;
  83. }
  84. static int patch_regress(void)
  85. {
  86. static const char *bodies[] = {
  87. /* format string handling bugs with neon <= 0.24.4 */
  88. RESP207 "<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\">"
  89. "<D:response><D:href>/foo/</D:href>"
  90. "<D:status>HTTP/1.1 500 Bad Voodoo</D:status>"
  91. "<D:responsedescription>%s%s%s%s</D:responsedescription>"
  92. "</D:response></D:multistatus>",
  93. RESP207 "<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\">"
  94. "<D:response><D:href>/foo/</D:href>"
  95. "<D:status>HTTP/1.1 %s%s%s%s</D:status>",
  96. NULL
  97. };
  98. ne_session *sess;
  99. int n;
  100. static const ne_propname pn = { "DAV:", "foobar" };
  101. ne_proppatch_operation pops[] = {
  102. { &pn, ne_propset, "fish" },
  103. { NULL, ne_propset, NULL }
  104. };
  105. for (n = 0; bodies[n] != NULL; n++) {
  106. CALL(make_session(&sess, single_serve_string, (void *)bodies[n]));
  107. ne_proppatch(sess, "/", pops);
  108. ne_session_destroy(sess);
  109. CALL(await_server());
  110. }
  111. return OK;
  112. }
  113. /* Serialize propfind result callbacks into a string */
  114. static int simple_iterator(void *vbuf, const ne_propname *name,
  115. const char *value, const ne_status *st)
  116. {
  117. char code[20];
  118. ne_buffer *buf = vbuf;
  119. ne_buffer_concat(buf, "prop:[{", name->nspace, ",",
  120. name->name, "}=", NULL);
  121. if (value)
  122. ne_buffer_concat(buf, "'", value, "'", NULL);
  123. else
  124. ne_buffer_zappend(buf, "#novalue#");
  125. sprintf(code, ":{%d ", st->code);
  126. if (st->reason_phrase)
  127. ne_buffer_concat(buf, code, st->reason_phrase, "}];", NULL);
  128. else
  129. ne_buffer_concat(buf, code, "#noreason#}];", NULL);
  130. return 0;
  131. }
  132. static void simple_results(void *buf, const ne_uri *uri,
  133. const ne_prop_result_set *rset)
  134. {
  135. ne_buffer_concat(buf, "results(", uri->path, ",", NULL);
  136. ne_propset_iterate(rset, simple_iterator, buf);
  137. ne_buffer_czappend(buf, ")//");
  138. }
  139. /* Test function to compare two long strings and print a digestible
  140. * failure message. */
  141. static int diffcmp(const char *expected, const char *actual)
  142. {
  143. size_t n;
  144. if (!strcmp(expected, actual)) return OK;
  145. NE_DEBUG(NE_DBG_HTTP,
  146. "diffcmp: Expect: [%s]\n"
  147. "diffcmp: Actual: [%s]\n",
  148. expected, actual);
  149. for (n = 0; expected[n] && actual[n]; n++) {
  150. if (expected[n] != actual[n]) {
  151. t_context("difference at byte %" NE_FMT_SIZE_T ": "
  152. "`%.10s...' not `%.10s...'",
  153. n, actual+n, expected+n);
  154. break;
  155. }
  156. }
  157. return FAIL;
  158. }
  159. /* PROPFIND creator callback. */
  160. static void *pf_creator(void *userdata, const ne_uri *uri)
  161. {
  162. ne_buffer *buf = userdata;
  163. NE_DEBUG(NE_DBG_HTTP, "pf: Creator at %s\n", uri->path);
  164. ne_buffer_concat(buf, "creator[", uri->path, "]//", NULL);
  165. return ne_strdup(uri->path);
  166. }
  167. /* PROPFIND destructor callback. */
  168. static void pf_destructor(void *userdata, void *private)
  169. {
  170. ne_buffer *buf = userdata;
  171. char *cookie = private;
  172. NE_DEBUG(NE_DBG_HTTP, "pf: Destructor at %s\n", cookie);
  173. ne_buffer_concat(buf, "destructor[", cookie, "]//", NULL);
  174. ne_free(cookie);
  175. }
  176. /* PROPFIND test type. */
  177. enum pftype {
  178. PF_SIMPLE, /* using ne_simple_propfind */
  179. PF_NAMED, /* using ne_propfind_named */
  180. PF_SP_NAMED, /* using ne_propfind_named w/SHAREPOINT hacks */
  181. PF_ALLPROP /* using ne_propfind_allprop */
  182. };
  183. static int run_propfind(const ne_propname *props, char *resp,
  184. int depth, const char *expected, enum pftype type)
  185. {
  186. ne_session *sess;
  187. ne_buffer *buf = ne_buffer_create();
  188. CALL(make_session(&sess, single_serve_string, resp));
  189. if (type == PF_SIMPLE) {
  190. ONREQ(ne_simple_propfind(sess, "/propfind", depth, props,
  191. simple_results, buf));
  192. }
  193. else {
  194. ne_propfind_handler *hdl;
  195. if (type == PF_SP_NAMED) {
  196. ne_set_session_flag(sess, NE_SESSFLAG_SHAREPOINT, 1);
  197. type = PF_NAMED;
  198. }
  199. hdl = ne_propfind_create(sess, "/propfind", depth);
  200. ne_propfind_set_private(hdl, pf_creator, pf_destructor,
  201. buf);
  202. if (type == PF_NAMED) {
  203. ONREQ(ne_propfind_named(hdl, props, simple_results, buf));
  204. }
  205. else {
  206. ONREQ(ne_propfind_allprop(hdl, simple_results, buf));
  207. }
  208. ne_propfind_destroy(hdl);
  209. }
  210. ne_session_destroy(sess);
  211. CALL(await_server());
  212. CALL(diffcmp(expected, buf->data));
  213. ne_buffer_destroy(buf);
  214. return OK;
  215. }
  216. /* a PROPFIND response body for the {DAV:}fishbone property, using
  217. * given property value and status. */
  218. #define FISHBONE_RESP(value, status) MULTI_207(RESP_207("/foop", \
  219. PSTAT_207(PROPS_207(APROP_207("fishbone", value)) \
  220. STAT_207(status))))
  221. static int propfind(void)
  222. {
  223. static const struct {
  224. char *resp;
  225. const char *expected;
  226. int depth;
  227. enum pftype type;
  228. } ts[] = {
  229. /* simple single property. */
  230. { FISHBONE_RESP("hello, world", "212 Well OK"),
  231. "results(/foop,prop:[{DAV:,fishbone}='hello, world':{212 Well OK}];)//",
  232. 0, PF_SIMPLE },
  233. /* property with some nested elements. */
  234. { FISHBONE_RESP("this is <foo/> a property <bar><lemon>fish</lemon></bar> value",
  235. "299 Just About OK"),
  236. "results(/foop,prop:[{DAV:,fishbone}="
  237. "'this is <foo></foo> a property "
  238. "<bar><lemon>fish</lemon></bar> value':"
  239. "{299 Just About OK}];)//",
  240. 0, PF_SIMPLE },
  241. /* failed to fetch a property. */
  242. { FISHBONE_RESP("property value is ignored",
  243. "404 Il n'ya pas de property"),
  244. "results(/foop,prop:[{DAV:,fishbone}=#novalue#:"
  245. "{404 Il n'ya pas de property}];)//",
  246. 0, PF_SIMPLE },
  247. #if 0
  248. /* propstat missing status should be ignored; if a response contains no
  249. * valid propstats, it should also be ignored. */
  250. { MULTI_207(RESP_207("/alpha", PSTAT_207(APROP_207("fishbone", "unseen")))
  251. RESP_207("/beta", PSTAT_207(APROP_207("fishbone", "hello, world")
  252. STAT_207("200 OK")))),
  253. "results(/beta,prop:[{DAV:,fishbone}='hello, world':{200 OK}];)//", 0,
  254. PF_SIMPLE },
  255. #endif
  256. /* props on several resources */
  257. { MULTI_207(RESP_207("/alpha",
  258. PSTAT_207(PROPS_207(APROP_207("fishbone", "strike one"))
  259. STAT_207("234 First is OK")))
  260. RESP_207("/beta",
  261. PSTAT_207(PROPS_207(APROP_207("fishbone", "strike two"))
  262. STAT_207("256 Second is OK")))),
  263. "results(/alpha,prop:[{DAV:,fishbone}='strike one':{234 First is OK}];)//"
  264. "results(/beta,prop:[{DAV:,fishbone}='strike two':{256 Second is OK}];)//",
  265. 0, PF_SIMPLE},
  266. /* whitespace handling. */
  267. { MULTI_207(RESP_207("\r\nhttp://localhost/alpha ",
  268. PSTAT_207(PROPS_207(APROP_207("alpha", "beta"))
  269. "<D:status>\r\nHTTP/1.1 200 OK </D:status>"))),
  270. "results(/alpha,prop:[{DAV:,alpha}='beta':{200 OK}];)//",
  271. 0, PF_SIMPLE},
  272. /* attribute handling. */
  273. { MULTI_207(RESP_207("\r\nhttp://localhost/alpha ",
  274. PSTAT_207(PROPS_207("<D:alpha>"
  275. "<D:foo D:fee='bar' bar='fee'>beta</D:foo></D:alpha>")
  276. "<D:status>\r\nHTTP/1.1 200 OK </D:status>"))),
  277. "results(/alpha,prop:[{DAV:,alpha}='<DAV:foo DAV:fee='bar' bar='fee'>beta</DAV:foo>':{200 OK}];)//",
  278. 0, PF_SIMPLE},
  279. /* "complex" propfinds. */
  280. { FISHBONE_RESP("hello, world", "212 Well OK"),
  281. "creator[/foop]//"
  282. "results(/foop,prop:[{DAV:,fishbone}='hello, world':{212 Well OK}];)//"
  283. "destructor[/foop]//",
  284. 0, PF_NAMED },
  285. /* 207 with badly encoded URI in href */
  286. { MULTI_207(RESP_207("http://example.com/foo€bar", \
  287. PSTAT_207(PROPS_207(APROP_207("fishbone", "hello, world")) \
  288. STAT_207("209 Good News")))),
  289. "creator[/foo%e2%82%acbar]//"
  290. "results(/foo%e2%82%acbar,prop:[{DAV:,fishbone}='hello, world':{209 Good News}];)//"
  291. "destructor[/foo%e2%82%acbar]//",
  292. 0, PF_SP_NAMED },
  293. { MULTI_207(RESP_207("/foo%20bar/€bar", \
  294. PSTAT_207(PROPS_207(APROP_207("fishbone", "hello, world")) \
  295. STAT_207("209 Good News")))),
  296. "creator[/foo%20bar/%e2%82%acbar]//"
  297. "results(/foo%20bar/%e2%82%acbar,prop:[{DAV:,fishbone}='hello, world':{209 Good News}];)//"
  298. "destructor[/foo%20bar/%e2%82%acbar]//",
  299. 0, PF_SP_NAMED }
  300. };
  301. const ne_propname pset1[] = {
  302. { "DAV:", "fishbone", },
  303. { NULL, NULL }
  304. };
  305. unsigned n;
  306. for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) {
  307. const ne_propname *pset = pset1;
  308. NE_DEBUG(NE_DBG_HTTP, "--> running test %u for %s\n", n, ts[n].resp);
  309. CALL(run_propfind(pset, ts[n].resp, ts[n].depth,
  310. ts[n].expected, ts[n].type));
  311. }
  312. return OK;
  313. }
  314. static int unbounded_response(const char *header, const char *repeats)
  315. {
  316. ne_session *sess;
  317. struct infinite i = { header, repeats};
  318. CALL(make_session(&sess, serve_infinite, &i));
  319. ONN("unbounded PROPFIND response did not fail",
  320. ne_simple_propfind(sess, "/", 0, NULL,
  321. dummy_results, NULL) != NE_ERROR);
  322. CALL(reap_server());
  323. ne_session_destroy(sess);
  324. return OK;
  325. }
  326. static int unbounded_propstats(void)
  327. {
  328. return unbounded_response(
  329. RESP207 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
  330. "<multistatus xmlns=\"DAV:\">"
  331. "<response><href>/</href>",
  332. "<propstat></propstat>");
  333. }
  334. static int unbounded_props(void)
  335. {
  336. return unbounded_response(
  337. RESP207 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
  338. "<multistatus xmlns=\"DAV:\">"
  339. "<response><href>/</href><propstat>",
  340. "<prop><jim>hello, world</jim></prop>");
  341. }
  342. ne_test tests[] = {
  343. T(patch_simple),
  344. T(propfind),
  345. T(regress),
  346. T(patch_regress),
  347. T(unbounded_props),
  348. T(unbounded_propstats),
  349. T(NULL)
  350. };