/* Tests for property handling Copyright (C) 2002-2009, Joe Orton 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 #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_UNISTD_H #include #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 ne_uri *uri, 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 "\n" "" "" "" "", /* segfaults with neon <= 0.23.5 */ RESP207 "" "/foo/" "" "HTTP/1.1 404 Not Found" "", /* format string handling with neon <= 0.24.4 */ RESP207 "" "/foo/" "" "%s%s%s%s" "", 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 patch_regress(void) { static const char *bodies[] = { /* format string handling bugs with neon <= 0.24.4 */ RESP207 "" "/foo/" "HTTP/1.1 500 Bad Voodoo" "%s%s%s%s" "", RESP207 "" "/foo/" "HTTP/1.1 %s%s%s%s", NULL }; ne_session *sess; int n; static const ne_propname pn = { "DAV:", "foobar" }; ne_proppatch_operation pops[] = { { &pn, ne_propset, "fish" }, { NULL, ne_propset, NULL } }; for (n = 0; bodies[n] != NULL; n++) { CALL(make_session(&sess, single_serve_string, (void *)bodies[n])); ne_proppatch(sess, "/", pops); 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 ne_uri *uri) { ne_buffer_concat(buf, "start-resp[", uri->path, "];", NULL); pstat_count = 0; return ne_strdup(uri->path); } 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_xml_parser *p = ne_xml_create(); ne_207_parser *p207; ne_request *req; ne_uri base = {0}; struct propctx ctx; CALL(session_server(&sess, single_serve_string, resp)); req = ne_request_create(sess, "PROPFIND", "/foo"); ne_fill_server_uri(sess, &base); base.path = ne_strdup("/foo"); p207 = ne_207_create(p, &base, buf); ne_uri_free(&base); 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); ONREQ(ne_request_dispatch(req)); CALL(await_server()); ONV(ne_xml_failed(p), ("parse error in response body: %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" \ "\r\n" \ "" x "" #define RESP_207(href, x) "" href "" x \ "" #define PSTAT_207(x) "" x "" #define STAT_207(s) "HTTP/1.1 " s "" #define DESCR_207(d) "" d "" #define DESCR_REM "The end of the world, as we know it" #define PROPS_207(x) "" x "" #define APROP_207(n, c) "" c "" /* 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("" RESP_207("/", STAT_207("123 Hoorah")) "" "hello" "" 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("" 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(""))), "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("blargl" RESP_207("/bar", "" PSTAT_207("blergl") STAT_207("200 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 *vbuf, const ne_propname *name, const char *value, const ne_status *st) { char code[20]; ne_buffer *buf = vbuf; 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 ne_uri *uri, const ne_prop_result_set *rset) { ne_buffer_concat(buf, "results(", uri->path, ",", NULL); ne_propset_iterate(rset, simple_iterator, buf); ne_buffer_czappend(buf, ")//"); } /* Test function to compare two long strings and print a digestible * failure message. */ static int diffcmp(const char *expected, const char *actual) { size_t n; if (!strcmp(expected, actual)) return OK; NE_DEBUG(NE_DBG_HTTP, "diffcmp: Expect: [%s]\n" "diffcmp: Actual: [%s]\n", expected, actual); for (n = 0; expected[n] && actual[n]; n++) { if (expected[n] != actual[n]) { t_context("difference at byte %" NE_FMT_SIZE_T ": " "`%.10s...' not `%.10s...'", n, actual+n, expected+n); break; } } return FAIL; } /* PROPFIND creator callback. */ static void *pf_creator(void *userdata, const ne_uri *uri) { ne_buffer *buf = userdata; NE_DEBUG(NE_DBG_HTTP, "pf: Creator at %s\n", uri->path); ne_buffer_concat(buf, "creator[", uri->path, "]//", NULL); return ne_strdup(uri->path); } /* PROPFIND destructor callback. */ static void pf_destructor(void *userdata, void *private) { ne_buffer *buf = userdata; char *cookie = private; NE_DEBUG(NE_DBG_HTTP, "pf: Destructor at %s\n", cookie); ne_buffer_concat(buf, "destructor[", cookie, "]//", NULL); ne_free(cookie); } /* PROPFIND test type. */ enum pftype { PF_SIMPLE, /* using ne_simple_propfind */ PF_NAMED, /* using ne_propfind_named */ PF_ALLPROP /* using ne_propfind_allprop */ }; static int run_propfind(const ne_propname *props, char *resp, int depth, const char *expected, enum pftype type) { ne_session *sess; ne_buffer *buf = ne_buffer_create(); CALL(make_session(&sess, single_serve_string, resp)); if (type == PF_SIMPLE) { ONREQ(ne_simple_propfind(sess, "/propfind", depth, props, simple_results, buf)); } else { ne_propfind_handler *hdl; hdl = ne_propfind_create(sess, "/propfind", depth); ne_propfind_set_private(hdl, pf_creator, pf_destructor, buf); if (type == PF_NAMED) { ONREQ(ne_propfind_named(hdl, props, simple_results, buf)); } else { ONREQ(ne_propfind_allprop(hdl, simple_results, buf)); } ne_propfind_destroy(hdl); } ne_session_destroy(sess); CALL(await_server()); CALL(diffcmp(expected, buf->data)); ne_buffer_destroy(buf); 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 propfind(void) { static const struct { char *resp; const char *expected; int depth; enum pftype type; } ts[] = { /* simple single property. */ { FISHBONE_RESP("hello, world", "212 Well OK"), "results(/foop,prop:[{DAV:,fishbone}='hello, world':{212 Well OK}];)//", 0, PF_SIMPLE }, /* property with some nested elements. */ { FISHBONE_RESP("this is a property fish value", "299 Just About OK"), "results(/foop,prop:[{DAV:,fishbone}=" "'this is a property " "fish value':" "{299 Just About OK}];)//", 0, PF_SIMPLE }, /* 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, PF_SIMPLE }, #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, PF_SIMPLE }, #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, PF_SIMPLE}, /* whitespace handling. */ { MULTI_207(RESP_207("\r\nhttp://localhost/alpha ", PSTAT_207(PROPS_207(APROP_207("alpha", "beta")) "\r\nHTTP/1.1 200 OK "))), "results(/alpha,prop:[{DAV:,alpha}='beta':{200 OK}];)//", 0, PF_SIMPLE}, /* attribute handling. */ { MULTI_207(RESP_207("\r\nhttp://localhost/alpha ", PSTAT_207(PROPS_207("" "beta") "\r\nHTTP/1.1 200 OK "))), "results(/alpha,prop:[{DAV:,alpha}='beta':{200 OK}];)//", 0, PF_SIMPLE}, /* "complex" propfinds. */ { FISHBONE_RESP("hello, world", "212 Well OK"), "creator[/foop]//" "results(/foop,prop:[{DAV:,fishbone}='hello, world':{212 Well OK}];)//" "destructor[/foop]//", 0, PF_NAMED } }; 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_propfind(pset, ts[n].resp, ts[n].depth, ts[n].expected, ts[n].type)); } return OK; } static int unbounded_response(const char *header, const char *repeats) { ne_session *sess; struct infinite i = { header, repeats}; CALL(make_session(&sess, serve_infinite, &i)); ONN("unbounded PROPFIND response did not fail", ne_simple_propfind(sess, "/", 0, NULL, dummy_results, NULL) != NE_ERROR); CALL(reap_server()); ne_session_destroy(sess); return OK; } static int unbounded_propstats(void) { return unbounded_response( RESP207 "\n" "" "/", ""); } static int unbounded_props(void) { return unbounded_response( RESP207 "\n" "" "/", "hello, world"); } ne_test tests[] = { T(two_oh_seven), T(patch_simple), T(propfind), T(regress), T(patch_regress), T(unbounded_props), T(unbounded_propstats), T(NULL) };