Sat, 25 Jan 2020 11:16:55 +0100
make multistatus response ready for proppatch requests
--- a/src/server/test/main.c Sat Jan 25 09:00:27 2020 +0100 +++ b/src/server/test/main.c Sat Jan 25 11:16:55 2020 +0100 @@ -93,10 +93,12 @@ ucx_test_register(suite, test_webdav_propfind_init); ucx_test_register(suite, test_webdav_op_propfind_begin); ucx_test_register(suite, test_webdav_op_propfind_children); - + ucx_test_register(suite, test_proppatch_msresponse); + ucx_test_register(suite, test_msresponse_addproperty_with_errors); + // webdav methods ucx_test_register(suite, test_webdav_propfind); - + // run tests ucx_test_run(suite, stdout); fflush(stdout);
--- a/src/server/test/webdav.c Sat Jan 25 09:00:27 2020 +0100 +++ b/src/server/test/webdav.c Sat Jan 25 11:16:55 2020 +0100 @@ -208,7 +208,7 @@ return NULL; } - return webdav_operation_create( + return webdav_create_propfind_operation( (*out_sn), (*out_rq), &backend1, @@ -1054,3 +1054,184 @@ UCX_TEST_END; } + +/* ------------------------------------------------------------------------- + * + * PROPPATCH TESTS + * + * ------------------------------------------------------------------------ */ + +static int test_proppatch_init( + Session **out_sn, + Request **out_rq, + WebdavProppatchRequest **out_proppatch, + const char *xml) +{ + if(!webdav_is_initialized) { + if(webdav_init(NULL, NULL, NULL) != REQ_PROCEED) { + return 1; + } + webdav_is_initialized = 1; + } + + Session *sn = testutil_session(); + Request *rq = testutil_request(sn->pool, "PROPPATCH", "/"); + + int error = 0; + + WebdavProppatchRequest *proppatch = proppatch_parse( + sn, + rq, + xml, + strlen(xml), + &error); + + if(error) { + return 1; + } + + if(!proppatch || !(proppatch->set || proppatch->remove)) { + return 1; + } + + *out_sn = sn; + *out_rq = rq; + *out_proppatch = proppatch; + return 0; +} + +static WebdavOperation* test_proppatch_op1( + Session **out_sn, + Request **out_rq, + const char *xml) +{ + WebdavProppatchRequest *proppatch; + if(test_proppatch_init(out_sn, out_rq, &proppatch, xml)) { + return NULL; + } + + Multistatus *ms = multistatus_response(*out_sn, *out_rq); + if(!ms) { + return NULL; + } + // WebdavResponse is the public interface used by Backends + // for adding resources to the response + WebdavResponse *response = (WebdavResponse*)ms; + + return webdav_create_proppatch_operation( + (*out_sn), + (*out_rq), + &backend1, + proppatch, + response); +} + + +UCX_TEST(test_proppatch_msresponse) { + Session *sn; + Request *rq; + WebdavOperation *op; + + Multistatus *ms; + WebdavResource *res; + + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[1].vtype = 0; + } + + UCX_TEST_BEGIN; + + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2); + UCX_TEST_ASSERT(op, "failed to create proppatch operation"); + + ms = (Multistatus*)op->response; + ms->proppatch = TRUE; + res = ms->response.addresource(&ms->response, "/"); + UCX_TEST_ASSERT(res, "cannot create resource 1"); + + UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 200), "addproperty 3 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed"); + + UCX_TEST_ASSERT(!res->close(res), "close failed"); + + MSResponse *msres = (MSResponse*)res; + UCX_TEST_ASSERT(!msres->errors, "error list not NULL"); + UCX_TEST_ASSERT(msres->plist_begin, "elm1 missing"); + UCX_TEST_ASSERT(msres->plist_begin->next, "elm2 missing"); + UCX_TEST_ASSERT(msres->plist_begin->next->next, "elm3 missing"); + UCX_TEST_ASSERT(msres->plist_begin->next->next->next, "elm4 missing"); + UCX_TEST_ASSERT(!msres->plist_begin->next->next->next->next, "count != 4"); + + UCX_TEST_END; + testutil_destroy_session(sn); +} + +UCX_TEST(test_msresponse_addproperty_with_errors) { + Session *sn; + Request *rq; + WebdavOperation *op; + + Multistatus *ms; + WebdavResource *res; + + WebdavProperty p[16]; + const char *names[] = {"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}; + for(int i=0;i<8;i++) { + p[i].namespace = webdav_dav_namespace(); + p[i].name = names[i]; + p[i].lang = NULL; + p[i].value.node = NULL; + p[1].vtype = 0; + } + + UCX_TEST_BEGIN; + + op = test_proppatch_op1(&sn, &rq, TEST_PROPPATCH2); + UCX_TEST_ASSERT(op, "failed to create proppatch operation"); + + ms = (Multistatus*)op->response; + ms->proppatch = TRUE; + res = ms->response.addresource(&ms->response, "/"); + UCX_TEST_ASSERT(res, "cannot create resource 1"); + + UCX_TEST_ASSERT(!res->addproperty(res, &p[0], 200), "addproperty 1 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[1], 200), "addproperty 2 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[2], 409), "addproperty 3 failed"); + UCX_TEST_ASSERT(!res->addproperty(res, &p[3], 200), "addproperty 4 failed"); + + UCX_TEST_ASSERT(!res->close(res), "close failed"); + + // all properties should have an error status code now + // 1 x 409, 3 x 424 + + MSResponse *msres = (MSResponse*)res; + + UCX_TEST_ASSERT(!msres->plist_begin, "plist not NULL"); + UCX_TEST_ASSERT(msres->errors, "error list is NULL"); + UCX_TEST_ASSERT(msres->errors->next, "second error list is missing"); + UCX_TEST_ASSERT(!msres->errors->next->next, "wrong error list size"); + + // We know that we have 2 error lists, one with status code 409 and + // the other must have 409. However we don't enforce the order of the + // error lists, therefore check both variants + if(msres->errors->status == 409) { + UCX_TEST_ASSERT(msres->errors->next->status == 424, "wrong status code in second err elm"); + UCX_TEST_ASSERT(msres->errors->begin, "missing 409 property"); + UCX_TEST_ASSERT(msres->errors->next->begin, "missing 424 properties"); + } else { + UCX_TEST_ASSERT(msres->errors->next->status == 409, "wrong status code in second err elm"); + UCX_TEST_ASSERT(msres->errors->begin, "missing 424 properties"); + UCX_TEST_ASSERT(msres->errors->next->begin, "missing 409 property"); + } + + UCX_TEST_END; + testutil_destroy_session(sn); +}
--- a/src/server/test/webdav.h Sat Jan 25 09:00:27 2020 +0100 +++ b/src/server/test/webdav.h Sat Jan 25 11:16:55 2020 +0100 @@ -51,6 +51,7 @@ UCX_TEST(test_webdav_plist_iterator_remove_current); UCX_TEST(test_msresponse_addproperty); +UCX_TEST(test_msresponse_addproperty_with_errors); UCX_TEST(test_webdav_propfind_init); UCX_TEST(test_webdav_op_propfind_begin); @@ -58,6 +59,8 @@ UCX_TEST(test_webdav_propfind); +UCX_TEST(test_proppatch_msresponse); + /* --------------------------- PROPFIND --------------------------- */ #define TEST_PROPFIND1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
--- a/src/server/webdav/multistatus.c Sat Jan 25 09:00:27 2020 +0100 +++ b/src/server/webdav/multistatus.c Sat Jan 25 11:16:55 2020 +0100 @@ -52,6 +52,7 @@ ms->sn = sn; ms->rq = rq; ms->namespaces = ucx_map_new_a(session_get_allocator(ms->sn), 8); + ms->proppatch = FALSE; if(!ms->namespaces) { return NULL; } @@ -435,6 +436,13 @@ } } + if(response->multistatus->proppatch && response->errors) { + // in a proppatch request all operations must succeed + // if we have an error, the property update status code must be + // 424 Failed Dependency + status = 424; + } + // error properties will be added to a separate list if(status != 200) { return msresponse_addproperror(response, property, status); @@ -545,16 +553,17 @@ if(response->closing) { return 0; // close already in progress } + Multistatus *ms = response->multistatus; int ret = REQ_PROCEED; - WebdavOperation *op = response->multistatus->response.op; - if(webdav_op_propfiond_close_resource(op, res)) { + WebdavOperation *op = ms->response.op; + if(op->response_close(op, res)) { ret = REQ_ABORTED; } // add missing properties with status code 404 - UcxAllocator *a = session_get_allocator(response->multistatus->sn); - WebdavPList *pl = response->multistatus->response.op->reqprops; + UcxAllocator *a = session_get_allocator(ms->sn); + WebdavPList *pl = ms->response.op->reqprops; while(pl) { sstr_t key = sstrcat_a( a, @@ -564,15 +573,40 @@ sstr((char*)pl->property->name)); if(!ucx_map_sstr_get(response->properties, key)) { // property was not added to this response - if(msresponse_addproperror(response, pl->property, 404)) { - ret = REQ_ABORTED; - break; + if(ms->proppatch) { + if(msresponse_addproperror(response, pl->property, 424)) { + ret = REQ_ABORTED; + break; + } + } else { + if(msresponse_addproperror(response, pl->property, 404)) { + ret = REQ_ABORTED; + break; + } } } pl = pl->next; } + if(ms->proppatch && response->errors) { + // a proppatch response must succeed entirely + // if we have a single error prop, move all props with status 200 + // to the error list + PropertyOkList *elm = response->plist_begin; + PropertyOkList *nextelm; + while(elm) { + if(msresponse_addproperror(response, elm->property, 424)) { + return 1; + } + nextelm = elm->next; + pool_free(response->multistatus->sn->pool, elm); + elm = nextelm; + } + response->plist_begin = NULL; + response->plist_end = NULL; + } + // we don't need the properties anymore ucx_map_free(response->properties);
--- a/src/server/webdav/multistatus.h Sat Jan 25 09:00:27 2020 +0100 +++ b/src/server/webdav/multistatus.h Sat Jan 25 11:16:55 2020 +0100 @@ -56,12 +56,20 @@ MSResponse *current; /* - * map of document namespace definitions + * Map of document namespace definitions * * key: (char*) namespace prefix * value: WSNamespace* */ UcxMap *namespaces; + + /* + * Is this a proppatch request? + * + * In a proppatch response, when the first property with an error occurs, + * all already added properties will be set to 424 Failed Dependency. + */ + WSBool proppatch; }; /*
--- a/src/server/webdav/operation.c Sat Jan 25 09:00:27 2020 +0100 +++ b/src/server/webdav/operation.c Sat Jan 25 11:16:55 2020 +0100 @@ -37,7 +37,13 @@ #define WEBDAV_PATH_MAX 8192 -WebdavOperation* webdav_operation_create( +/**************************************************************************** + * + * PROPFIND OPERATION + * + ****************************************************************************/ + +WebdavOperation* webdav_create_propfind_operation( Session *sn, Request *rq, WebdavBackend *dav, @@ -53,7 +59,7 @@ op->reqprops = reqprops; op->requests = requests; op->response = response; - + op->response_close = webdav_op_propfiond_close_resource; response->op = op; return op; @@ -340,3 +346,37 @@ } return ret; } + +/**************************************************************************** + * + * PROPPATCH OPERATION + * + ****************************************************************************/ + +WebdavOperation* webdav_create_proppatch_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavProppatchRequest *proppatch, + WebdavResponse *response) +{ + WebdavOperation *op = pool_malloc(sn->pool, sizeof(WebdavOperation)); + ZERO(op, sizeof(WebdavOperation)); + op->dav = dav; + op->sn = sn; + op->rq = rq; + op->reqprops = NULL; + op->response = response; + op->response_close = webdav_op_proppatch_close_resource; + response->op = op; + + return op; +} + +int webdav_op_proppatch_close_resource( + WebdavOperation *op, + WebdavResource *resource) +{ + return 0; +} +
--- a/src/server/webdav/operation.h Sat Jan 25 09:00:27 2020 +0100 +++ b/src/server/webdav/operation.h Sat Jan 25 11:16:55 2020 +0100 @@ -35,21 +35,26 @@ extern "C" { #endif +typedef int(*response_close_func)(WebdavOperation *, WebdavResource *); + struct WebdavOperation { - WebdavBackend *dav; - Request *rq; - Session *sn; + WebdavBackend *dav; + Request *rq; + Session *sn; - WebdavPList *reqprops; /* requested properties */ - UcxList *requests; /* backend specific request objects */ + WebdavProppatchRequest *request; /* proppatch request or NULL */ + WebdavPList *reqprops; /* requested properties */ + UcxList *requests; /* backend specific request objects */ - WebdavResponse *response; + WebdavResponse *response; - VFS_DIR parent; /* current directory */ - struct stat *stat; /* current stat object */ + response_close_func response_close; + + VFS_DIR parent; /* current directory */ + struct stat *stat; /* current stat object */ }; -WebdavOperation* webdav_operation_create( +WebdavOperation* webdav_create_propfind_operation( Session *sn, Request *rq, WebdavBackend *dav, @@ -75,6 +80,17 @@ int webdav_op_propfind_finish(WebdavOperation *op); +WebdavOperation* webdav_create_proppatch_operation( + Session *sn, + Request *rq, + WebdavBackend *dav, + WebdavProppatchRequest *proppatch, + WebdavResponse *response); + +int webdav_op_proppatch_close_resource( + WebdavOperation *op, + WebdavResource *resource); + #ifdef __cplusplus } #endif
--- a/src/server/webdav/requestparser.c Sat Jan 25 09:00:27 2020 +0100 +++ b/src/server/webdav/requestparser.c Sat Jan 25 11:16:55 2020 +0100 @@ -358,7 +358,7 @@ node = node->next; } - ucx_map_free(propmap); // no allocated content must be freed + ucx_map_free(propmap); // allocated content must not be freed if(set_count + remove_count == 0) { *error = PROPPATCH_PARSER_NO_PROPERTIES;
--- a/src/server/webdav/webdav.c Sat Jan 25 09:00:27 2020 +0100 +++ b/src/server/webdav/webdav.c Sat Jan 25 11:16:55 2020 +0100 @@ -224,7 +224,7 @@ // for adding resources to the response WebdavResponse *response = (WebdavResponse*)ms; - WebdavOperation *op = webdav_operation_create( + WebdavOperation *op = webdav_create_propfind_operation( sn, rq, dav,