summaryrefslogtreecommitdiff
path: root/test/props.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/props.c')
-rw-r--r--test/props.c508
1 files changed, 508 insertions, 0 deletions
diff --git a/test/props.c b/test/props.c
new file mode 100644
index 0000000..61d3c22
--- /dev/null
+++ b/test/props.c
@@ -0,0 +1,508 @@
+/*
+ Tests for property handling
+ Copyright (C) 2002-2003, Joe Orton <joe@manyfish.co.uk>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "config.h"
+
+#include <sys/types.h>
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "ne_props.h"
+
+#include "tests.h"
+#include "child.h"
+#include "utils.h"
+
+static const ne_propname p_alpha = {"DAV:", "alpha"},
+ p_beta = {"http://webdav.org/random/namespace", "beta"},
+ p_delta = {NULL, "delta"};
+
+/* Tests little except that ne_proppatch() doesn't segfault. */
+static int patch_simple(void)
+{
+ ne_session *sess;
+ ne_proppatch_operation ops[] = {
+ { &p_alpha, ne_propset, "fish" },
+ { &p_beta, ne_propremove, NULL },
+ { NULL, ne_propset, NULL }
+ };
+
+ CALL(make_session(&sess, single_serve_string,
+ "HTTP/1.1 200 Goferit\r\n"
+ "Connection: close\r\n\r\n"));
+ ONREQ(ne_proppatch(sess, "/fish", ops));
+ ne_session_destroy(sess);
+ return await_server();
+}
+
+#define RESP207 "HTTP/1.0 207 Stuff\r\n" "Server: foo\r\n\r\n"
+
+static void dummy_results(void *ud, const char *href,
+ const ne_prop_result_set *rset)
+{
+ NE_DEBUG(NE_DBG_HTTP, "dummy_results.\n");
+}
+
+/* Regression tests for propfind bodies which caused segfaults. */
+static int regress(void)
+{
+ static const char *bodies[] = {
+ RESP207 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<multistatus xmlns=\"DAV:\">"
+ "<response><propstat><prop><href>"
+ "</href></prop></propstat></response>"
+ "</multistatus>",
+
+ /* segfaults with neon <= 0.23.5 */
+ RESP207 "<?xml version=\"1.0\"?><D:multistatus xmlns:D=\"DAV:\">"
+ "<D:response><D:href>/foo/</D:href>"
+ "<D:propstat/>"
+ "<D:status>HTTP/1.1 404 Not Found</D:status>"
+ "</D:multistatus>",
+ NULL,
+ };
+ ne_session *sess;
+ int n;
+
+ for (n = 0; bodies[n] != NULL; n++) {
+ CALL(make_session(&sess, single_serve_string, (void *)bodies[n]));
+ ne_simple_propfind(sess, "/", 0, NULL, dummy_results, NULL);
+ ne_session_destroy(sess);
+ CALL(await_server());
+ }
+
+ return OK;
+}
+
+static int pstat_count;
+
+/* tos_*: set of 207 callbacks which serialize the data back into a
+ * text stream, which can be easily checked for correctness. */
+static void *tos_startresp(void *buf, const char *href)
+{
+ ne_buffer_concat(buf, "start-resp[", href, "];", NULL);
+ pstat_count = 0;
+ return ne_strdup(href);
+}
+
+static void tos_status_descr(ne_buffer *buf, const ne_status *status,
+ const char *description)
+{
+ if (status) {
+ char s[50];
+ ne_snprintf(s, sizeof s, "-status={%d %s}", status->code,
+ status->reason_phrase);
+ ne_buffer_zappend(buf, s);
+ }
+ if (description)
+ ne_buffer_concat(buf, "-descr={", description, "}", NULL);
+}
+
+static void tos_endresp(void *buf, void *response,
+ const ne_status *status, const char *description)
+{
+ char *href = response;
+ ne_buffer_concat(buf, "end-resp[", href, "]", NULL);
+ ne_free(href);
+ tos_status_descr(buf, status, description);
+ ne_buffer_zappend(buf, ";");
+}
+
+static void *tos_startpstat(void *buf, void *resphref)
+{
+ char num[20], *href;
+ sprintf(num, "-%d", ++pstat_count);
+ href = ne_concat(resphref, num, NULL);
+ ne_buffer_concat(buf, "start-pstat[", href, "];", NULL);
+ return href;
+}
+
+static void tos_endpstat(void *buf, void *href,
+ const ne_status *status, const char *description)
+{
+ ne_buffer_concat(buf, "end-pstat[", href, "]", NULL);
+ tos_status_descr(buf, status, description);
+ ne_buffer_zappend(buf, ";");
+ ne_free(href);
+}
+
+struct propctx {
+ ne_207_parser *p207;
+ ne_buffer *buf;
+};
+
+#define STATE_myprop (NE_PROPS_STATE_TOP)
+
+static int tos_startprop(void *userdata, int parent,
+ const char *nspace, const char *name,
+ const char **atts)
+{
+ if (parent == NE_207_STATE_PROP &&
+ strcmp(nspace, "DAV:") == 0 &&
+ (strcmp(name, "propone") == 0 || strcmp(name, "proptwo") == 0)) {
+ /* Handle this! */
+ struct propctx *ctx = userdata;
+ char *resphref = ne_207_get_current_response(ctx->p207);
+ char *pstathref = ne_207_get_current_propstat(ctx->p207);
+
+ ne_buffer_concat(ctx->buf, "start-prop[", resphref, ",", pstathref,
+ ",", name, "];", NULL);
+
+ return STATE_myprop;
+ } else {
+ return NE_XML_DECLINE;
+ }
+}
+
+static int tos_cdata(void *userdata, int state,
+ const char *cdata, size_t len)
+{
+ struct propctx *ctx = userdata;
+
+ ne_buffer_zappend(ctx->buf, "cdata-prop[");
+ ne_buffer_append(ctx->buf, cdata, len);
+ ne_buffer_zappend(ctx->buf, "];");
+ return 0;
+}
+
+static int tos_endprop(void *userdata, int state,
+ const char *nspace, const char *name)
+{
+ struct propctx *ctx = userdata;
+
+ ne_buffer_concat(ctx->buf, "end-prop[", name, "];", NULL);
+ return 0;
+}
+
+
+static int run_207_response(char *resp, const char *expected)
+{
+ ne_buffer *buf = ne_buffer_create();
+ ne_session *sess = ne_session_create("http", "localhost", 7777);
+ ne_xml_parser *p = ne_xml_create();
+ ne_207_parser *p207 = ne_207_create(p, buf);
+ ne_request *req = ne_request_create(sess, "PROPFIND", "/foo");
+ struct propctx ctx;
+
+ ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v, p);
+
+ ne_207_set_response_handlers(p207, tos_startresp, tos_endresp);
+ ne_207_set_propstat_handlers(p207, tos_startpstat, tos_endpstat);
+
+ ctx.buf = buf;
+ ctx.p207 = p207;
+ ne_xml_push_handler(p, tos_startprop, tos_cdata, tos_endprop, &ctx);
+
+ CALL(spawn_server(7777, single_serve_string, resp));
+
+ ONREQ(ne_request_dispatch(req));
+
+ CALL(await_server());
+
+ ONV(!ne_xml_valid(p),
+ ("response body was invalid: %s", ne_xml_get_error(p)));
+
+ ONV(strcmp(buf->data, expected),
+ ("comparison failed.\n"
+ "expected string: `%s'\n"
+ "got string: `%s'", expected, buf->data));
+
+ ne_buffer_destroy(buf);
+ ne_207_destroy(p207);
+ ne_xml_destroy(p);
+ ne_request_destroy(req);
+ ne_session_destroy(sess);
+ return OK;
+}
+
+/* Macros for easily writing a 207 response body; all expand to
+ * a string literal. */
+#define MULTI_207(x) "HTTP/1.0 207 Foo\r\nConnection: close\r\n\r\n" \
+"<?xml version='1.0'?>\r\n" \
+"<D:multistatus xmlns:D='DAV:'>" x "</D:multistatus>"
+#define RESP_207(href, x) "<D:response><D:href>" href "</D:href>" x \
+"</D:response>"
+#define PSTAT_207(x) "<D:propstat>" x "</D:propstat>"
+#define STAT_207(s) "<D:status>HTTP/1.1 " s "</D:status>"
+#define DESCR_207(d) "<D:responsedescription>" d "</D:responsedescription>"
+#define DESCR_REM "The end of the world, as we know it"
+
+#define PROPS_207(x) "<D:prop>" x "</D:prop>"
+#define APROP_207(n, c) "<D:" n ">" c "</D:" n ">"
+
+/* Tests for the 207 interface: send a 207 response body, compare the
+ * re-serialized string returned with that expected. */
+static int two_oh_seven(void)
+{
+ static char *ts[][2] = {
+ { MULTI_207(RESP_207("/foo", "")),
+ "start-resp[/foo];end-resp[/foo];" },
+
+ /* test for response status handling */
+ { MULTI_207(RESP_207("/bar", STAT_207("200 OK"))),
+ "start-resp[/bar];end-resp[/bar]-status={200 OK};" },
+
+ /* test that empty description == NULL description argument */
+ { MULTI_207(RESP_207("/bar", STAT_207("200 OK") DESCR_207(""))),
+ "start-resp[/bar];end-resp[/bar]-status={200 OK};" },
+
+ /* test multiple responses */
+ { MULTI_207(RESP_207("/hello/world", STAT_207("200 OK"))
+ RESP_207("/foo/bar", STAT_207("999 French Fries"))),
+ "start-resp[/hello/world];end-resp[/hello/world]-status={200 OK};"
+ "start-resp[/foo/bar];end-resp[/foo/bar]"
+ "-status={999 French Fries};"
+ },
+
+ /* test multiple propstats in mulitple responses */
+ { MULTI_207(RESP_207("/al/pha",
+ PSTAT_207(STAT_207("321 Une"))
+ PSTAT_207(STAT_207("432 Deux"))
+ PSTAT_207(STAT_207("543 Trois")))
+ RESP_207("/be/ta",
+ PSTAT_207(STAT_207("787 Quatre"))
+ PSTAT_207(STAT_207("878 Cinq")))),
+ "start-resp[/al/pha];"
+ "start-pstat[/al/pha-1];end-pstat[/al/pha-1]-status={321 Une};"
+ "start-pstat[/al/pha-2];end-pstat[/al/pha-2]-status={432 Deux};"
+ "start-pstat[/al/pha-3];end-pstat[/al/pha-3]-status={543 Trois};"
+ "end-resp[/al/pha];"
+ "start-resp[/be/ta];"
+ "start-pstat[/be/ta-1];end-pstat[/be/ta-1]-status={787 Quatre};"
+ "start-pstat[/be/ta-2];end-pstat[/be/ta-2]-status={878 Cinq};"
+ "end-resp[/be/ta];"
+ },
+
+ /* test that incomplete responses are completely ignored. */
+ { MULTI_207("<D:response/>"
+ RESP_207("/", STAT_207("123 Hoorah"))
+ "<D:response/>"
+ "<D:response><D:propstat>hello</D:propstat></D:response>"
+ "<D:response><D:href/></D:response>"
+ RESP_207("/bar", STAT_207("200 OK"))),
+ "start-resp[/];end-resp[/]-status={123 Hoorah};"
+ "start-resp[/bar];end-resp[/bar]-status={200 OK};" },
+
+ /* tests for propstat status */
+ { MULTI_207(RESP_207("/pstat",
+ PSTAT_207("<D:prop/>" STAT_207("666 Doomed")))),
+ "start-resp[/pstat];start-pstat[/pstat-1];"
+ "end-pstat[/pstat-1]-status={666 Doomed};end-resp[/pstat];" },
+
+ { MULTI_207(RESP_207("/pstat", PSTAT_207("<D:status/>"))),
+ "start-resp[/pstat];start-pstat[/pstat-1];"
+ "end-pstat[/pstat-1];end-resp[/pstat];" },
+
+ /* tests for responsedescription handling */
+ { MULTI_207(RESP_207("/bar", STAT_207("200 OK") DESCR_207(DESCR_REM))),
+ "start-resp[/bar];end-resp[/bar]-status={200 OK}"
+ "-descr={" DESCR_REM "};" },
+
+ { MULTI_207(RESP_207("/bar",
+ PSTAT_207(STAT_207("456 Too Hungry")
+ DESCR_207("Not enough food available"))
+ STAT_207("200 OK") DESCR_207("Not " DESCR_REM))),
+ "start-resp[/bar];"
+ "start-pstat[/bar-1];end-pstat[/bar-1]-status={456 Too Hungry}"
+ "-descr={Not enough food available};"
+ "end-resp[/bar]-status={200 OK}-descr={Not " DESCR_REM "};" },
+
+ /* intermingle some random elements and cdata to make sure
+ * they are ignored. */
+ { MULTI_207("<D:fish-food/>blargl"
+ RESP_207("/b<ping-pong/>ar", "<D:sausages/>"
+ PSTAT_207("<D:hello-mum/>blergl")
+ STAT_207("200 <pong-ping/> OK") "foop"
+ DESCR_207(DESCR_REM) "carroon")
+ "carapi"),
+ "start-resp[/bar];start-pstat[/bar-1];end-pstat[/bar-1];"
+ "end-resp[/bar]-status={200 OK}-descr={" DESCR_REM "};" },
+
+ /* test for properties within a 207. */
+ { MULTI_207(RESP_207("/alpha",
+ PSTAT_207(PROPS_207(
+ APROP_207("propone", "hello")
+ APROP_207("proptwo", "foobar"))
+ STAT_207("200 OK")))),
+ "start-resp[/alpha];start-pstat[/alpha-1];"
+ "start-prop[/alpha,/alpha-1,propone];cdata-prop[hello];"
+ "end-prop[propone];"
+ "start-prop[/alpha,/alpha-1,proptwo];cdata-prop[foobar];"
+ "end-prop[proptwo];"
+ "end-pstat[/alpha-1]-status={200 OK};end-resp[/alpha];" }
+
+ };
+ size_t n;
+
+ for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++)
+ CALL(run_207_response(ts[n][0], ts[n][1]));
+
+ return OK;
+}
+
+/* Serialize propfind result callbacks into a string */
+static int simple_iterator(void *buf, const ne_propname *name,
+ const char *value, const ne_status *st)
+{
+ char code[20];
+ ne_buffer_concat(buf, "prop:[{", name->nspace, ",",
+ name->name, "}=", NULL);
+ if (value)
+ ne_buffer_concat(buf, "'", value, "'", NULL);
+ else
+ ne_buffer_zappend(buf, "#novalue#");
+ sprintf(code, ":{%d ", st->code);
+ if (st->reason_phrase)
+ ne_buffer_concat(buf, code, st->reason_phrase, "}];", NULL);
+ else
+ ne_buffer_concat(buf, code, "#noreason#}];", NULL);
+ return 0;
+}
+
+static void simple_results(void *buf, const char *href,
+ const ne_prop_result_set *rset)
+{
+ ne_buffer_concat(buf, "results(", href, ",", NULL);
+ ne_propset_iterate(rset, simple_iterator, buf);
+ ne_buffer_zappend(buf, ")//");
+}
+
+static int diffcmp(const char *expected, const char *actual)
+{
+ size_t n;
+
+ if (!strcmp(expected, actual)) return OK;
+
+ for (n = 0; expected[n] && actual[n]; n++) {
+ if (expected[n] != actual[n]) {
+ t_context("difference at byte %" NE_FMT_SIZE_T ": "
+ "`%.6s...' not `%.6s...'",
+ n, actual+n, expected+n);
+ break;
+ }
+ }
+
+ return FAIL;
+}
+
+
+static int run_simple_propfind(const ne_propname *props, char *resp,
+ int depth, const char *expected)
+{
+ ne_session *sess = ne_session_create("http", "localhost", 7777);
+ ne_buffer *buf = ne_buffer_create();
+
+ CALL(spawn_server(7777, single_serve_string, resp));
+
+ ONREQ(ne_simple_propfind(sess, "/propfind", depth, props,
+ simple_results, buf));
+
+ CALL(await_server());
+
+ CALL(diffcmp(expected, buf->data));
+
+ ne_buffer_destroy(buf);
+ ne_session_destroy(sess);
+ return OK;
+}
+
+/* a PROPFIND response body for the {DAV:}fishbone property, using
+ * given property value and status. */
+#define FISHBONE_RESP(value, status) MULTI_207(RESP_207("/foop", \
+ PSTAT_207(PROPS_207(APROP_207("fishbone", value)) \
+ STAT_207(status))))
+
+static int pfind_simple(void)
+{
+ static const struct {
+ char *resp;
+ const char *expected;
+ int depth, pset;
+ } ts[] = {
+ /* simple single property. */
+ { FISHBONE_RESP("hello, world", "212 Well OK"),
+ "results(/foop,prop:[{DAV:,fishbone}='hello, world':{212 Well OK}];)//",
+ 0, 0 },
+ /* property with some nested elements. */
+ { FISHBONE_RESP("this is <foo/> a property <bar><lemon>fish</lemon></bar> value",
+ "299 Just About OK"),
+ "results(/foop,prop:[{DAV:,fishbone}="
+ "'this is <foo></foo> a property "
+ "<bar><lemon>fish</lemon></bar> value':"
+ "{299 Just About OK}];)//",
+ 0, 0 },
+
+ /* failed to fetch a property. */
+ { FISHBONE_RESP("property value is ignored",
+ "404 Il n'ya pas de property"),
+ "results(/foop,prop:[{DAV:,fishbone}=#novalue#:"
+ "{404 Il n'ya pas de property}];)//",
+ 0, 0 },
+
+#if 0
+ /* propstat missing status should be ignored; if a response contains no
+ * valid propstats, it should also be ignored. */
+ { MULTI_207(RESP_207("/alpha", PSTAT_207(APROP_207("fishbone", "unseen")))
+ RESP_207("/beta", PSTAT_207(APROP_207("fishbone", "hello, world")
+ STAT_207("200 OK")))),
+ "results(/beta,prop:[{DAV:,fishbone}='hello, world':{200 OK}];)//", 0, 0},
+#endif
+
+ /* props on several resources */
+ { MULTI_207(RESP_207("/alpha",
+ PSTAT_207(PROPS_207(APROP_207("fishbone", "strike one"))
+ STAT_207("234 First is OK")))
+ RESP_207("/beta",
+ PSTAT_207(PROPS_207(APROP_207("fishbone", "strike two"))
+ STAT_207("256 Second is OK")))),
+ "results(/alpha,prop:[{DAV:,fishbone}='strike one':{234 First is OK}];)//"
+ "results(/beta,prop:[{DAV:,fishbone}='strike two':{256 Second is OK}];)//",
+ 0, 0}
+ };
+ const ne_propname pset1[] = {
+ { "DAV:", "fishbone", },
+ { NULL, NULL }
+ };
+ size_t n;
+
+ for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) {
+ const ne_propname *pset = pset1;
+
+ CALL(run_simple_propfind(pset, ts[n].resp, ts[n].depth,
+ ts[n].expected));
+ }
+
+
+ return OK;
+}
+
+ne_test tests[] = {
+ T(two_oh_seven),
+ T(patch_simple),
+ T(pfind_simple),
+ T(regress),
+ T(NULL)
+};
+