twooh7.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. /*
  2. Test cases for the ne_207.h interface.
  3. Copyright (C) 2023, 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_207.h"
  25. #include "tests.h"
  26. #include "utils.h"
  27. #define PFX "<?xml version='1.0' encoding='utf-8'?>\r\n"
  28. #define RESP(status, rdesc) "<d:response>" \
  29. "<d:href>http://localhost/container/resource3</d:href>" \
  30. status rdesc \
  31. "</d:response>"
  32. #define MS_207_1(status, rdesc) PFX \
  33. "<d:multistatus xmlns:d=\"DAV:\">" \
  34. RESP(status, rdesc) \
  35. "</d:multistatus>"
  36. #define MS_207_2(s1, rd1, s2, rd2) PFX \
  37. "<d:multistatus xmlns:d=\"DAV:\">" \
  38. RESP(s1, rd1) \
  39. RESP(s2, rd2) \
  40. "</d:multistatus>"
  41. static int errors(void)
  42. {
  43. static const struct {
  44. int status;
  45. const char *ctype;
  46. const char *body;
  47. const char *expect;
  48. } ts[] = {
  49. { 207, "application/xml",
  50. MS_207_1("<d:status>HTTP/1.1 423 Locked</d:status>", ""),
  51. "423 Locked" },
  52. { 207, "application/xml",
  53. MS_207_1("<d:status>HTTP/1.1 423 Locked</d:status>",
  54. "<d:responsedescription>The thing was locked</d:responsedescription>"),
  55. "The thing was" },
  56. #if 0
  57. { 207, "application/xml",
  58. MS_207_1("<d:status>HTTP/1.1 423 Locked</d:status>",
  59. "<d:error><d:lock-token-submitted/></d:error>"),
  60. "Resource locked" },
  61. #endif
  62. { 207, "application/xml",
  63. MS_207_2("<d:status>HTTP/1.1 423 Locked</d:status>",
  64. "<d:responsedescription>The thing was locked</d:responsedescription>",
  65. "<d:status>HTTP/1.1 404 Gone</d:status>",
  66. "<d:responsedescription>No such thingy</d:responsedescription>"),
  67. "such thingy" }
  68. };
  69. unsigned n;
  70. for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) {
  71. char resp[1024];
  72. ne_session *sess;
  73. ne_request *req;
  74. char *err;
  75. int ret;
  76. ne_snprintf(resp, sizeof resp,
  77. "HTTP/1.1 %d OK\r\n"
  78. "Content-Type: %s\r\n"
  79. "Connection: close\r\n" "\r\n"
  80. "%s", ts[n].status, ts[n].ctype, ts[n].body);
  81. CALL(make_session(&sess, single_serve_string, resp));
  82. req = ne_request_create(sess, "SIMPLE", "/");
  83. ret = ne_simple_request(sess, req);
  84. ONN("ne_simple_request didn't fail", ret == NE_OK);
  85. err = ne_strclean(ne_strdup(ne_get_error(sess)));
  86. ONV(strcmp(err, ne_get_error(sess)),
  87. ("error string wasn't cleaned: %s", ne_get_error(sess)));
  88. NE_DEBUG(NE_DBG_HTTP, "test: got error string: %s\n", err);
  89. ne_free(err);
  90. ONV(strstr(ne_get_error(sess), ts[n].expect) == NULL,
  91. ("error string didn't match: '%s' - expected '%s'",
  92. ne_get_error(sess), ts[n].expect));
  93. ne_session_destroy(sess);
  94. CALL(await_server());
  95. }
  96. return OK;
  97. }
  98. static int pstat_count;
  99. /* tos_*: set of 207 callbacks which serialize the data back into a
  100. * text stream, which can be easily checked for correctness. */
  101. static void *tos_startresp(void *buf, const ne_uri *uri)
  102. {
  103. ne_buffer_concat(buf, "start-resp[", uri->path, "];", NULL);
  104. pstat_count = 0;
  105. return ne_strdup(uri->path);
  106. }
  107. static void tos_status_descr(ne_buffer *buf, const ne_status *status,
  108. const char *description)
  109. {
  110. if (status) {
  111. char s[50];
  112. ne_snprintf(s, sizeof s, "-status={%d %s}", status->code,
  113. status->reason_phrase);
  114. ne_buffer_zappend(buf, s);
  115. }
  116. if (description)
  117. ne_buffer_concat(buf, "-descr={", description, "}", NULL);
  118. }
  119. static void tos_endresp(void *buf, void *response,
  120. const ne_status *status, const char *description)
  121. {
  122. char *href = response;
  123. ne_buffer_concat(buf, "end-resp[", href, "]", NULL);
  124. ne_free(href);
  125. tos_status_descr(buf, status, description);
  126. ne_buffer_zappend(buf, ";");
  127. }
  128. static void *tos_startpstat(void *buf, void *resphref)
  129. {
  130. char num[20], *href;
  131. sprintf(num, "-%d", ++pstat_count);
  132. href = ne_concat(resphref, num, NULL);
  133. ne_buffer_concat(buf, "start-pstat[", href, "];", NULL);
  134. return href;
  135. }
  136. static void tos_endpstat(void *buf, void *href,
  137. const ne_status *status, const char *description)
  138. {
  139. ne_buffer_concat(buf, "end-pstat[", href, "]", NULL);
  140. tos_status_descr(buf, status, description);
  141. ne_buffer_zappend(buf, ";");
  142. ne_free(href);
  143. }
  144. struct propctx {
  145. ne_207_parser *p207;
  146. ne_buffer *buf;
  147. };
  148. #define STATE_myprop (NE_207_STATE_TOP)
  149. static int tos_startprop(void *userdata, int parent,
  150. const char *nspace, const char *name,
  151. const char **atts)
  152. {
  153. if (parent == NE_207_STATE_PROP
  154. && strcmp(nspace, "DAV:") == 0
  155. && (strcmp(name, "propone") == 0 || strcmp(name, "proptwo") == 0)) {
  156. /* Handle this! */
  157. struct propctx *ctx = userdata;
  158. char *resphref = ne_207_get_current_response(ctx->p207);
  159. char *pstathref = ne_207_get_current_propstat(ctx->p207);
  160. ne_buffer_concat(ctx->buf, "start-prop[", resphref, ",", pstathref,
  161. ",", name, "];", NULL);
  162. return STATE_myprop;
  163. }
  164. else {
  165. return NE_XML_DECLINE;
  166. }
  167. }
  168. static int tos_cdata(void *userdata, int state,
  169. const char *cdata, size_t len)
  170. {
  171. struct propctx *ctx = userdata;
  172. ne_buffer_zappend(ctx->buf, "cdata-prop[");
  173. ne_buffer_append(ctx->buf, cdata, len);
  174. ne_buffer_zappend(ctx->buf, "];");
  175. return 0;
  176. }
  177. static int tos_endprop(void *userdata, int state,
  178. const char *nspace, const char *name)
  179. {
  180. struct propctx *ctx = userdata;
  181. ne_buffer_concat(ctx->buf, "end-prop[", name, "];", NULL);
  182. return 0;
  183. }
  184. static int run_207_response(const char *resp, const char *expected)
  185. {
  186. ne_buffer *buf = ne_buffer_create();
  187. ne_session *sess;
  188. ne_xml_parser *p = ne_xml_create();
  189. ne_207_parser *p207;
  190. ne_request *req;
  191. ne_uri base = {0};
  192. struct propctx ctx;
  193. CALL(session_server(&sess, single_serve_string, (char *)resp));
  194. req = ne_request_create(sess, "PROPFIND", "/foo");
  195. ne_fill_server_uri(sess, &base);
  196. base.path = ne_strdup("/foo");
  197. p207 = ne_207_create(p, &base, buf);
  198. ne_uri_free(&base);
  199. ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v, p);
  200. ne_207_set_response_handlers(p207, tos_startresp, tos_endresp);
  201. ne_207_set_propstat_handlers(p207, tos_startpstat, tos_endpstat);
  202. ctx.buf = buf;
  203. ctx.p207 = p207;
  204. ne_xml_push_handler(p, tos_startprop, tos_cdata, tos_endprop, &ctx);
  205. ONREQ(ne_request_dispatch(req));
  206. CALL(await_server());
  207. ONV(ne_xml_failed(p),
  208. ("parse error in response body: %s", ne_xml_get_error(p)));
  209. ONV(strcmp(buf->data, expected),
  210. ("comparison failed.\n"
  211. "expected string: `%s'\n"
  212. "got string: `%s'", expected, buf->data));
  213. ne_buffer_destroy(buf);
  214. ne_207_destroy(p207);
  215. ne_xml_destroy(p);
  216. ne_request_destroy(req);
  217. ne_session_destroy(sess);
  218. return OK;
  219. }
  220. /* Tests for the 207 interface: send a 207 response body, compare the
  221. * re-serialized string returned with that expected. */
  222. static int two_oh_seven(void)
  223. {
  224. static const char *ts[][2] = {
  225. { MULTI_207(RESP_207("/foo", "")),
  226. "start-resp[/foo];end-resp[/foo];" },
  227. /* test for response status handling */
  228. { MULTI_207(RESP_207("/bar", STAT_207("200 OK"))),
  229. "start-resp[/bar];end-resp[/bar]-status={200 OK};" },
  230. /* test that empty description == NULL description argument */
  231. { MULTI_207(RESP_207("/bar", STAT_207("200 OK") DESCR_207(""))),
  232. "start-resp[/bar];end-resp[/bar]-status={200 OK};" },
  233. /* test multiple responses */
  234. { MULTI_207(RESP_207("/hello/world", STAT_207("200 OK"))
  235. RESP_207("/foo/bar", STAT_207("599 French Fries"))),
  236. "start-resp[/hello/world];end-resp[/hello/world]-status={200 OK};"
  237. "start-resp[/foo/bar];end-resp[/foo/bar]"
  238. "-status={599 French Fries};"
  239. },
  240. /* test multiple propstats in multiple responses */
  241. { MULTI_207(RESP_207("/al/pha",
  242. PSTAT_207(STAT_207("321 Une"))
  243. PSTAT_207(STAT_207("432 Deux"))
  244. PSTAT_207(STAT_207("543 Trois")))
  245. RESP_207("/be/ta",
  246. PSTAT_207(STAT_207("587 Quatre"))
  247. PSTAT_207(STAT_207("578 Cinq")))),
  248. "start-resp[/al/pha];"
  249. "start-pstat[/al/pha-1];end-pstat[/al/pha-1]-status={321 Une};"
  250. "start-pstat[/al/pha-2];end-pstat[/al/pha-2]-status={432 Deux};"
  251. "start-pstat[/al/pha-3];end-pstat[/al/pha-3]-status={543 Trois};"
  252. "end-resp[/al/pha];"
  253. "start-resp[/be/ta];"
  254. "start-pstat[/be/ta-1];end-pstat[/be/ta-1]-status={587 Quatre};"
  255. "start-pstat[/be/ta-2];end-pstat[/be/ta-2]-status={578 Cinq};"
  256. "end-resp[/be/ta];"
  257. },
  258. /* test that incomplete responses are completely ignored. */
  259. { MULTI_207("<D:response/>"
  260. RESP_207("/", STAT_207("123 Hoorah"))
  261. "<D:response/>"
  262. "<D:response><D:propstat>hello</D:propstat></D:response>"
  263. "<D:response><D:href/></D:response>"
  264. RESP_207("/bar", STAT_207("200 OK"))),
  265. "start-resp[/];end-resp[/]-status={123 Hoorah};"
  266. "start-resp[/bar];end-resp[/bar]-status={200 OK};" },
  267. /* tests for propstat status */
  268. { MULTI_207(RESP_207("/pstat",
  269. PSTAT_207("<D:prop/>" STAT_207("466 Doomed")))),
  270. "start-resp[/pstat];start-pstat[/pstat-1];"
  271. "end-pstat[/pstat-1]-status={466 Doomed};end-resp[/pstat];" },
  272. { MULTI_207(RESP_207("/pstat", PSTAT_207("<D:status/>"))),
  273. "start-resp[/pstat];start-pstat[/pstat-1];"
  274. "end-pstat[/pstat-1];end-resp[/pstat];" },
  275. /* tests for responsedescription handling */
  276. { MULTI_207(RESP_207("/bar", STAT_207("200 OK") DESCR_207(DESCR_REM))),
  277. "start-resp[/bar];end-resp[/bar]-status={200 OK}"
  278. "-descr={" DESCR_REM "};" },
  279. { MULTI_207(RESP_207("/bar",
  280. PSTAT_207(STAT_207("456 Too Hungry")
  281. DESCR_207("Not enough food available"))
  282. STAT_207("200 OK") DESCR_207("Not " DESCR_REM))),
  283. "start-resp[/bar];"
  284. "start-pstat[/bar-1];end-pstat[/bar-1]-status={456 Too Hungry}"
  285. "-descr={Not enough food available};"
  286. "end-resp[/bar]-status={200 OK}-descr={Not " DESCR_REM "};" },
  287. /* intermingle some random elements and cdata to make sure
  288. * they are ignored. */
  289. { MULTI_207("<D:fish-food/>blargl"
  290. RESP_207("/b<ping-pong/>ar", "<D:sausages/>"
  291. PSTAT_207("<D:hello-mum/>blergl")
  292. STAT_207("200 OK") "<D:blah>foop</D:blah>"
  293. DESCR_207(DESCR_REM) "carroon")
  294. "carapi"),
  295. "start-resp[/bar];start-pstat[/bar-1];end-pstat[/bar-1];"
  296. "end-resp[/bar]-status={200 OK}-descr={" DESCR_REM "};" },
  297. /* test for properties within a 207. */
  298. { MULTI_207(RESP_207("/alpha",
  299. PSTAT_207(PROPS_207(
  300. APROP_207("propone", "hello")
  301. APROP_207("proptwo", "foobar"))
  302. STAT_207("200 OK")))),
  303. "start-resp[/alpha];start-pstat[/alpha-1];"
  304. "start-prop[/alpha,/alpha-1,propone];cdata-prop[hello];"
  305. "end-prop[propone];"
  306. "start-prop[/alpha,/alpha-1,proptwo];cdata-prop[foobar];"
  307. "end-prop[proptwo];"
  308. "end-pstat[/alpha-1]-status={200 OK};end-resp[/alpha];" },
  309. /* test for whitespace around href, which should be
  310. * ignored. */
  311. { MULTI_207(RESP_207(" /spaces ", STAT_207("200 OK") DESCR_207(""))),
  312. "start-resp[/spaces];end-resp[/spaces]-status={200 OK};" },
  313. /* test for whitespace around href, which should be
  314. * ignored. */
  315. { MULTI_207(RESP_207(" /spaces ", STAT_207("200 OK\n") DESCR_207(""))),
  316. "start-resp[/spaces];end-resp[/spaces]-status={200 OK};" },
  317. /* test for omitted reason-phrase in status-line, which is
  318. * valid, per https://github.com/notroj/neon/issues/188 */
  319. { MULTI_207(RESP_207("/bar", STAT_207("200 ") DESCR_207(""))),
  320. "start-resp[/bar];end-resp[/bar]-status={200 };" }
  321. };
  322. unsigned n;
  323. for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++)
  324. CALL(run_207_response(ts[n][0], ts[n][1]));
  325. return OK;
  326. }
  327. ne_test tests[] = {
  328. T(errors),
  329. T(two_oh_seven),
  330. T(NULL)
  331. };