make multistatus response ready for proppatch requests webdav

Sat, 25 Jan 2020 11:16:55 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 25 Jan 2020 11:16:55 +0100
branch
webdav
changeset 239
d5031c30022c
parent 238
e820d433f405
child 240
cd74667f6c85

make multistatus response ready for proppatch requests

src/server/test/main.c file | annotate | diff | comparison | revisions
src/server/test/webdav.c file | annotate | diff | comparison | revisions
src/server/test/webdav.h file | annotate | diff | comparison | revisions
src/server/webdav/multistatus.c file | annotate | diff | comparison | revisions
src/server/webdav/multistatus.h file | annotate | diff | comparison | revisions
src/server/webdav/operation.c file | annotate | diff | comparison | revisions
src/server/webdav/operation.h file | annotate | diff | comparison | revisions
src/server/webdav/requestparser.c file | annotate | diff | comparison | revisions
src/server/webdav/webdav.c file | annotate | diff | comparison | revisions
--- 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,

mercurial