Test of MOVE and PROPFIND predicated by ETag

Timothy Wood tjw at omnigroup.com
Tue Sep 25 00:34:59 EDT 2012


Here is an updated version of my patch that also checks whether PROPFIND can be predicated by ETag (both with If and If-Match since PROPFIND doesn't have a destination).

Sadly, Apache 2.4.3 fails, so I'll be submitting a patch to them too.

Thanks!

-tim



Index: configure
===================================================================
--- configure	(revision 172911)
+++ configure	(revision 173211)
@@ -3426,7 +3426,7 @@
 test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
 
 
-TESTS="basic copymove props locks http"
+TESTS="basic copymove props locks http if_etag"
 
 
 
Index: Makefile.in
===================================================================
--- Makefile.in	(revision 172911)
+++ Makefile.in	(revision 173211)
@@ -90,6 +90,9 @@
 http: src/http.o $(ODEPS)
 	$(CC) $(LDFLAGS) -o $@ src/http.o $(ALL_LIBS)
 
+if_etag: src/if_etag.o $(ODEPS)
+	$(CC) $(LDFLAGS) -o $@ src/if_etag.o $(ALL_LIBS)
+
 largefile: src/largefile.o $(ODEPS)
 	$(CC) $(LDFLAGS) -o $@ src/largefile.o $(ALL_LIBS)
 
@@ -120,4 +123,5 @@
 src/locks.o: src/locks.c $(HDRS)
 src/props.o: src/props.c $(HDRS)
 src/http.o: src/http.c $(HDRS)
+src/if_etag.o: src/if_etag.c $(HDRS)
 src/largefile.o: src/largefile.c $(HDRS)
Index: src/if_etag.c
===================================================================
--- src/if_etag.c	(revision 0)
+++ src/if_etag.c	(revision 173211)
@@ -0,0 +1,243 @@
+/* 
+   litmus: WebDAV server test suite
+   Copyright (C) 2001-2002, 2007, Joe Orton <joe at 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>
+
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <fcntl.h>
+
+#include <ne_request.h>
+#include <ne_string.h>
+#include <ne_props.h>
+
+#include "common.h"
+
+static char *mroot;
+
+static int if_etag_init(void)
+{
+    mroot = ne_concat(i_path, "mroot/", NULL);
+    
+    ONMREQ("MKCOL", mroot, ne_mkcol(i_session, mroot));
+
+    return OK;
+}
+
+static void prop_results(void *userdata, const ne_uri *uri, const ne_prop_result_set *results)
+{
+    const ne_propname ETagName = {"DAV:", "getetag"};
+    const char **etagp = userdata;
+    const char *ETag = ne_propset_value(results, &ETagName);
+    if (ETag)
+        *etagp = strdup(ETag);
+}
+
+// get_etag() not working for collections?
+static char *_propfind_etag(const char *url, const char *header, const char *value, int *klass, int *code)
+{
+    ne_propfind_handler *handler;
+    const ne_propname names[] = {
+        {"DAV:", "getetag"},
+        {NULL, NULL}
+    };
+
+    char *etag = NULL;
+    
+    handler = ne_propfind_create(i_session, url, NE_DEPTH_ZERO);
+    
+    ne_request *req = ne_propfind_get_request(handler);
+    if (header)
+        ne_print_request_header(req, header, "%s", value);
+    
+    int rc = ne_propfind_named(handler, names, prop_results, &etag);
+    
+    if (code) *code = ne_get_status(req)->code;
+    if (klass) *klass = ne_get_status(req)->klass;
+    
+    ne_propfind_destroy(handler);
+    return etag;
+}
+
+static char *propfind_etag(const char *url)
+{
+    return _propfind_etag(url, NULL, NULL, NULL, NULL);
+}
+
+// upload_foo() prepends i_path, which we've already done
+static int put_foo(const char *uri)
+{
+    int ret;
+    /* i_foo_fd is rewound automagically by ne_request.c */
+    ret = ne_put(i_session, uri, i_foo_fd);
+    if (ret)
+	t_context("PUT of `%s': %s", uri, ne_get_error(i_session));
+    return ret;
+}
+
+/* Perform a conditional MOVE request with given If: header value,
+ * placing response status-code in *code and class in *klass.  Fails
+ * if requests cannot be dispatched. */
+static int conditional_move(const char *src, const char *dst, const char *ifhdr, int *klass, int *code)
+{
+    ne_request *req;
+    
+    req = ne_request_create(i_session, "MOVE", src);
+
+    ne_print_request_header(req, "Destination", "%s://%s%s", 
+                            ne_get_scheme(i_session), 
+                            ne_get_server_hostport(i_session), dst);
+    ne_print_request_header(req, "If", "%s", ifhdr);
+    
+    ONMREQ("MOVE", src, ne_request_dispatch(req));
+
+    if (code) *code = ne_get_status(req)->code;
+    if (klass) *klass = ne_get_status(req)->klass;
+        
+    ne_request_destroy(req);
+    return OK;
+}
+
+static int move_coll_if_etag(const char *src, const char *dst)
+{
+    char *msrc = ne_concat(mroot, src, NULL);
+    char *mfile = ne_concat(msrc, "foo", NULL);
+    char *mdst = ne_concat(mroot, dst, NULL);
+
+    /* make a collection and get its etag */
+    ONMREQ("MKCOL", msrc, ne_mkcol(i_session, msrc));
+    char *etag1 = propfind_etag(msrc);
+
+    // put a file in the collection (changing its etag)
+    CALL(put_foo(mfile));
+
+    // get the new etag and assert they differ
+    char *etag2 = propfind_etag(msrc);
+    fprintf(stderr, "etag1 = %s\n", etag1);
+    fprintf(stderr, "etag2 = %s\n", etag2);
+    ONV(strcmp(etag1, etag2) == 0, ("Adding file should change the collection ETag"));
+
+    // move the collection, with the old etag as a requirement
+    char ifhdr[200];
+    ne_snprintf(ifhdr, sizeof ifhdr, "<%s> ([%s])", msrc, etag1);
+    
+    int klass, code;
+    CALL(conditional_move(msrc, mdst, ifhdr, &klass, &code));
+    ONV(code != 412, ("Conditional MOVE of modified collection should have failed with 412, but got %d", code));
+    
+    return OK;
+}
+
+static int move_coll_if_etag_basic(void)
+{
+    return move_coll_if_etag("src/", "dst/");
+}
+
+static int move_coll_if_etag_spaces(void)
+{
+    return move_coll_if_etag("s%20r%20c/", "d%20s%20t/");
+}
+
+enum {
+    DoNotPutFile,
+    PutFile,
+};
+enum {
+    IfHeader,
+    IfMatchHeader,
+};
+
+static int _propfind_if(const char *dir, int modify, int headerType, int expectedCode)
+{
+    /* make a collection and get its etag */
+    char *p_if = ne_concat(mroot, dir, NULL);
+    ONMREQ("MKCOL", p_if, ne_mkcol(i_session, p_if));
+    char *etag1 = propfind_etag(p_if);
+
+    /* put a file in the collection (changing its etag) */
+    if (modify == PutFile) {
+        char *mfile = ne_concat(p_if, "foo", NULL);
+        CALL(put_foo(mfile));
+    
+        char *etag2 = propfind_etag(p_if);
+        fprintf(stderr, "etag1 = %s\n", etag1);
+        fprintf(stderr, "etag2 = %s\n", etag2);
+        ONV(strcmp(etag1, etag2) == 0, ("Adding file should change the collection ETag"));
+    }
+
+    /* PROPFIND the collection with the old etag as a requirement */
+    const char *header;
+    char headerValue[200];
+    if (headerType == IfHeader) {
+        header = "If";
+        ne_snprintf(headerValue, sizeof headerValue, "<%s> ([%s])", p_if, etag1);
+    } else {
+        header = "If-Match";
+        ne_snprintf(headerValue, sizeof headerValue, "%s", etag1);
+    }
+    
+    int klass, code;
+    const char *etag3 = _propfind_etag(p_if, header, headerValue, &klass, &code);
+    
+    if (expectedCode >= 400) {
+        ONV(etag3 != NULL, ("Conditional PROPFIND of modified collection should have failed with %d, but got %d", expectedCode, code));
+    } else {
+        ONV(etag3 == NULL, ("Conditional PROPFIND of modified collection should not have failed, but got %d", code));
+    }
+    ONV(code != expectedCode, ("Conditional PROPFIND of modified collection should have returned code %d, but got %d", expectedCode, code));
+    
+    return OK;
+}
+
+static int propfind_if(void)
+{
+    CALL(_propfind_if("propfind_if1/", DoNotPutFile, IfHeader, 207));
+    CALL(_propfind_if("propfind_if2/", DoNotPutFile, IfMatchHeader, 207));
+    CALL(_propfind_if("propfind%20if3/", DoNotPutFile, IfHeader, 207));
+    CALL(_propfind_if("propfind%20if4/", DoNotPutFile, IfMatchHeader, 207));
+
+    CALL(_propfind_if("propfind_if5/", PutFile, IfHeader, 412));
+    CALL(_propfind_if("propfind_if6/", PutFile, IfMatchHeader, 412));
+    CALL(_propfind_if("propfind%20if7/", PutFile, IfHeader, 412));
+    CALL(_propfind_if("propfind%20if8/", PutFile, IfMatchHeader, 412));
+    
+    return OK;
+}
+
+static int if_etag_cleanup(void)
+{
+    ne_delete(i_session, mroot);
+    return OK;
+}
+
+ne_test tests[] = {
+   INIT_TESTS,
+ 
+   T(if_etag_init),
+   T(move_coll_if_etag_basic),
+   T(move_coll_if_etag_spaces),
+   T(propfind_if),
+   T(if_etag_cleanup),
+
+   FINISH_TESTS
+};

Property changes on: src/if_etag.c
___________________________________________________________________
Added: svn:keywords
## -0,0 +1 ##
+header
\ No newline at end of property
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: configure.ac
===================================================================
--- configure.ac	(revision 172911)
+++ configure.ac	(revision 173211)
@@ -18,7 +18,7 @@
 AC_PROG_INSTALL
 
 dnl List of tests
-AC_SUBST([TESTS], ["basic copymove props locks http"])
+AC_SUBST([TESTS], ["basic copymove props locks http if_etag"])
 
 NE_REQUIRE_VERSIONS([0], [25 26 27 28 29])
 NEON_WITHOUT_ZLIB




More information about the litmus mailing list