/* Test cases for the ne_207.h interface. Copyright (C) 2023, 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_207.h" #include "tests.h" #include "utils.h" #define PFX "\r\n" #define RESP(status, rdesc) "" \ "http://localhost/container/resource3" \ status rdesc \ "" #define MS_207_1(status, rdesc) PFX \ "" \ RESP(status, rdesc) \ "" #define MS_207_2(s1, rd1, s2, rd2) PFX \ "" \ RESP(s1, rd1) \ RESP(s2, rd2) \ "" static int errors(void) { static const struct { int status; const char *ctype; const char *body; const char *expect; } ts[] = { { 207, "application/xml", MS_207_1("HTTP/1.1 423 Locked", ""), "423 Locked" }, { 207, "application/xml", MS_207_1("HTTP/1.1 423 Locked", "The thing was locked"), "The thing was" }, #if 0 { 207, "application/xml", MS_207_1("HTTP/1.1 423 Locked", ""), "Resource locked" }, #endif { 207, "application/xml", MS_207_2("HTTP/1.1 423 Locked", "The thing was locked", "HTTP/1.1 404 Gone", "No such thingy"), "such thingy" } }; unsigned n; for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) { char resp[1024]; ne_session *sess; ne_request *req; char *err; int ret; ne_snprintf(resp, sizeof resp, "HTTP/1.1 %d OK\r\n" "Content-Type: %s\r\n" "Connection: close\r\n" "\r\n" "%s", ts[n].status, ts[n].ctype, ts[n].body); CALL(make_session(&sess, single_serve_string, resp)); req = ne_request_create(sess, "SIMPLE", "/"); ret = ne_simple_request(sess, req); ONN("ne_simple_request didn't fail", ret == NE_OK); err = ne_strclean(ne_strdup(ne_get_error(sess))); ONV(strcmp(err, ne_get_error(sess)), ("error string wasn't cleaned: %s", ne_get_error(sess))); NE_DEBUG(NE_DBG_HTTP, "test: got error string: %s\n", err); ne_free(err); ONV(strstr(ne_get_error(sess), ts[n].expect) == NULL, ("error string didn't match: '%s' - expected '%s'", ne_get_error(sess), ts[n].expect)); 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_207_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(const 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, (char *)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; } /* 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 const 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("599 French Fries"))), "start-resp[/hello/world];end-resp[/hello/world]-status={200 OK};" "start-resp[/foo/bar];end-resp[/foo/bar]" "-status={599 French Fries};" }, /* test multiple propstats in multiple 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("587 Quatre")) PSTAT_207(STAT_207("578 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={587 Quatre};" "start-pstat[/be/ta-2];end-pstat[/be/ta-2]-status={578 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("466 Doomed")))), "start-resp[/pstat];start-pstat[/pstat-1];" "end-pstat[/pstat-1]-status={466 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];" }, /* test for whitespace around href, which should be * ignored. */ { MULTI_207(RESP_207(" /spaces ", STAT_207("200 OK") DESCR_207(""))), "start-resp[/spaces];end-resp[/spaces]-status={200 OK};" }, /* test for whitespace around href, which should be * ignored. */ { MULTI_207(RESP_207(" /spaces ", STAT_207("200 OK\n") DESCR_207(""))), "start-resp[/spaces];end-resp[/spaces]-status={200 OK};" }, /* test for omitted reason-phrase in status-line, which is * valid, per https://github.com/notroj/neon/issues/188 */ { MULTI_207(RESP_207("/bar", STAT_207("200 ") DESCR_207(""))), "start-resp[/bar];end-resp[/bar]-status={200 };" } }; unsigned n; for (n = 0; n < sizeof(ts)/sizeof(ts[0]); n++) CALL(run_207_response(ts[n][0], ts[n][1])); return OK; } ne_test tests[] = { T(errors), T(two_oh_seven), T(NULL) };