]> granicus.if.org Git - apache/commitdiff
Fix up the auto-versioning stuff. The new scheme more closely matches the
authorGreg Stein <gstein@apache.org>
Sat, 14 Apr 2001 13:10:23 +0000 (13:10 +0000)
committerGreg Stein <gstein@apache.org>
Sat, 14 Apr 2001 13:10:23 +0000 (13:10 +0000)
intent of DeltaV draft 14, simplifying some previous assumptions.

Includes some heavy fixes to MOVE/COPY in a versioning world.

Fix to CHECKOUT when a working resource is not created (checkout in place)

Submitted by: John Vasta <jvasta@rational.com>

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@88856 13f79535-47bb-0310-9956-ffa450edef68

modules/dav/main/mod_dav.c
modules/dav/main/mod_dav.h
modules/dav/main/util.c
modules/dav/main/util_lock.c

index 4e4a352cdaeb7b11349360c127826fbc0b781c78..f1a32bd9c437906c4f96c508601877b4bdda12de 100644 (file)
@@ -1001,9 +1001,9 @@ static int dav_method_put(request_rec *r)
     }
 
     /* make sure the resource can be modified (if versioning repository) */
-    if ((err = dav_ensure_resource_writable(r, resource,
-                                           0 /* not parent_only */,
-                                           &av_info)) != NULL) {
+    if ((err = dav_auto_checkout(r, resource,
+                                0 /* not parent_only */,
+                                &av_info)) != NULL) {
        /* ### add a higher-level description? */
        return dav_handle_err(r, err, NULL);
     }
@@ -1086,8 +1086,8 @@ static int dav_method_put(request_rec *r)
     }
 
     /* restore modifiability of resources back to what they were */
-    err2 = dav_revert_resource_writability(r, resource, err != NULL /* undo if error */,
-                                           &av_info);
+    err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */,
+                            0 /*unlock*/, &av_info);
 
     /* check for errors now */
     if (err != NULL) {
@@ -1097,7 +1097,7 @@ static int dav_method_put(request_rec *r)
        /* just log a warning */
        err2 = dav_push_error(r->pool, err->status, 0,
                              "The PUT was successful, but there "
-                             "was a problem reverting the writability of "
+                             "was a problem automatically checking in "
                              "the resource or its parent collection.",
                              err2);
        dav_log_err(r, err2, APLOG_WARNING);
@@ -1232,8 +1232,8 @@ static int dav_method_delete(request_rec *r)
     }
 
     /* if versioned resource, make sure parent is checked out */
-    if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
-                                           &av_info)) != NULL) {
+    if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+                                &av_info)) != NULL) {
        /* ### add a higher-level description? */
        return dav_handle_err(r, err, NULL);
     }
@@ -1242,8 +1242,8 @@ static int dav_method_delete(request_rec *r)
     err = (*resource->hooks->remove_resource)(resource, &multi_response);
 
     /* restore writability of parent back to what it was */
-    err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
-                                          &av_info);
+    err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+                           0 /*unlock*/, &av_info);
 
     /* check for errors now */
     if (err != NULL) {
@@ -1258,8 +1258,8 @@ static int dav_method_delete(request_rec *r)
        /* just log a warning */
        err = dav_push_error(r->pool, err2->status, 0,
                             "The DELETE was successful, but there "
-                            "was a problem reverting the writability of "
-                            "its parent collection.",
+                            "was a problem automatically checking in "
+                            "the parent collection.",
                             err2);
        dav_log_err(r, err, APLOG_WARNING);
     }
@@ -2146,6 +2146,7 @@ static int dav_method_proppatch(request_rec *r)
     ap_text *propstat_text;
     apr_array_header_t *ctx_list;
     dav_prop_ctx *ctx;
+    dav_auto_version_info av_info;
 
     /* Ask repository module to resolve the resource */
     err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
@@ -2178,8 +2179,19 @@ static int dav_method_proppatch(request_rec *r)
        return dav_handle_err(r, err, NULL);
     }
 
+    /* make sure the resource can be modified (if versioning repository) */
+    if ((err = dav_auto_checkout(r, resource,
+                                0 /* not parent_only */,
+                                &av_info)) != NULL) {
+       /* ### add a higher-level description? */
+       return dav_handle_err(r, err, NULL);
+    }
+
     if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
                               &propdb)) != NULL) {
+        /* undo any auto-checkout */
+        dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
+
        err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
                             apr_psprintf(r->pool,
                                         "Could not open the property "
@@ -2212,6 +2224,9 @@ static int dav_method_proppatch(request_rec *r)
        if ((prop_group = dav_find_child(child, "prop")) == NULL) {
            dav_close_propdb(propdb);
 
+            /* undo any auto-checkout */
+            dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
+
            /* This supplies additional information for the default message. */
            ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
                          "A \"prop\" element is missing inside "
@@ -2257,6 +2272,9 @@ static int dav_method_proppatch(request_rec *r)
     /* make sure this gets closed! */
     dav_close_propdb(propdb);
 
+    /* complete any auto-versioning */
+    dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info);
+
     /* log any errors that occurred */
     (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
 
@@ -2384,8 +2402,8 @@ static int dav_method_mkcol(request_rec *r)
     }
 
     /* if versioned resource, make sure parent is checked out */
-    if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
-                                           &av_info)) != NULL) {
+    if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+                                &av_info)) != NULL) {
        /* ### add a higher-level description? */
        return dav_handle_err(r, err, NULL);
     }
@@ -2395,8 +2413,8 @@ static int dav_method_mkcol(request_rec *r)
     err = (*resource->hooks->create_collection)(resource);
 
     /* restore modifiability of parent back to what it was */
-    err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
-                                          &av_info);
+    err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+                           0 /*unlock*/, &av_info);
 
     /* check for errors now */
     if (err != NULL) {
@@ -2406,8 +2424,8 @@ static int dav_method_mkcol(request_rec *r)
        /* just log a warning */
        err = dav_push_error(r->pool, err->status, 0,
                             "The MKCOL was successful, but there "
-                            "was a problem reverting the writability of "
-                            "its parent collection.",
+                            "was a problem automatically checking in "
+                            "the parent collection.",
                             err2);
        dav_log_err(r, err, APLOG_WARNING);
     }
@@ -2450,9 +2468,9 @@ static int dav_method_mkcol(request_rec *r)
 static int dav_method_copymove(request_rec *r, int is_move)
 {
     dav_resource *resource;
-    dav_auto_version_info src_av_info = { 0 };
     dav_resource *resnew;
-    dav_auto_version_info dst_av_info;
+    dav_auto_version_info src_av_info = { 0 };
+    dav_auto_version_info dst_av_info = { 0 };
     const char *body;
     const char *dest;
     dav_error *err;
@@ -2465,8 +2483,8 @@ static int dav_method_copymove(request_rec *r, int is_move)
     int depth;
     int result;
     dav_lockdb *lockdb;
-    int replaced;
-    int resource_state;
+    int replace_dest;
+    int resnew_state;
 
     /* Ask repository module to resolve the resource */
     err = dav_get_resource(r, !is_move /* label_allowed */,
@@ -2479,6 +2497,7 @@ static int dav_method_copymove(request_rec *r, int is_move)
     }
 
     /* If not a file or collection resource, COPY/MOVE not allowed */
+    /* ### allow COPY/MOVE of DeltaV resource types */
     if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
         body = apr_psprintf(r->pool,
                            "Cannot COPY/MOVE resource %s.",
@@ -2686,13 +2705,10 @@ static int dav_method_copymove(request_rec *r, int is_move)
        (void)dav_unlock(r, resource, NULL);
     }
 
-    /* remember whether target resource existed */
-    replaced = resnew->exists;
-
     /* if this is a move, then the source parent collection will be modified */
     if (is_move) {
-        if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
-                                               &src_av_info)) != NULL) {
+        if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+                                    &src_av_info)) != NULL) {
            if (lockdb != NULL)
                (*lockdb->hooks->close_lockdb)(lockdb);
 
@@ -2701,44 +2717,76 @@ static int dav_method_copymove(request_rec *r, int is_move)
         }
     }
 
-    /* prepare the destination collection for modification */
-    if ((err = dav_ensure_resource_writable(r, resnew, 1 /* parent_only */,
-                                           &dst_av_info)) != NULL) {
-        /* could not make destination writable:
-        * if move, restore state of source parent
-        */
-        if (is_move) {
-            (void) dav_revert_resource_writability(r, NULL, 1 /* undo */,
-                                                  &src_av_info);
-        }
+    /*
+     * Remember the initial state of the destination, so the lock system
+     * can be notified as to how it changed.
+     */
+    resnew_state = dav_get_resource_state(lookup.rnew, resnew);
 
-       if (lockdb != NULL)
-           (*lockdb->hooks->close_lockdb)(lockdb);
+    /* If destination does not exist, initialize resource object
+     * to be same type as the source.
+     */
+    if (!resnew->exists) {
+        resnew->type = resource->type;
+        resnew->collection = resource->collection;
+    }
 
-       /* ### add a higher-level description? */
-       return dav_handle_err(r, err, NULL);
+    /* In a MOVE operation, the destination is replaced by the source.
+     * In a COPY operation, if the destination exists, is under version
+     * control, and is the same resource type as the source,
+     * then it should not be replaced, but modified to be a copy of
+     * the source.
+     */
+    if (!resnew->exists)
+        replace_dest = 0;
+    else if (is_move || !resource->versioned)
+        replace_dest = 1;
+    else if (resource->type != resnew->type)
+        replace_dest = 1;
+    else if ((resource->collection == 0) != (resnew->collection == 0))
+        replace_dest = 1;
+    else
+        replace_dest = 0;
+
+    /* If the destination must be created or replaced,
+     * make sure the parent collection is writable
+     */
+    if (!resnew->exists || replace_dest) {
+        if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/,
+                                    &dst_av_info)) != NULL) {
+            /* could not make destination writable:
+            * if move, restore state of source parent
+            */
+            if (is_move) {
+                (void) dav_auto_checkin(r, NULL, 1 /* undo */,
+                                       0 /*unlock*/, &src_av_info);
+            }
+
+           if (lockdb != NULL)
+               (*lockdb->hooks->close_lockdb)(lockdb);
+
+           /* ### add a higher-level description? */
+           return dav_handle_err(r, err, NULL);
+        }
     }
 
     /* If source and destination parents are the same, then
-     * use the same object, so status updates to one are reflected
-     * in the other, when reverting their writable states.
+     * use the same resource object, so status updates to one are reflected
+     * in the other, when doing auto-versioning. Otherwise,
+     * we may try to checkin the parent twice.
      */
     if (src_av_info.parent_resource != NULL
+        && dst_av_info.parent_resource != NULL
         && (*src_av_info.parent_resource->hooks->is_same_resource)
             (src_av_info.parent_resource, dst_av_info.parent_resource)) {
 
         dst_av_info.parent_resource = src_av_info.parent_resource;
     }
 
-    /* New resource will be same kind as source */
-    resnew->collection = resource->collection;
-
-    resource_state = dav_get_resource_state(lookup.rnew, resnew);
-
-    /* If target exists, remove it first (we know Ovewrite must be TRUE).
-     * Then try to copy/move the resource.
+    /* If destination is being replaced, remove it first
+     * (we know Ovewrite must be TRUE). Then try to copy/move the resource.
      */
-    if (resnew->exists)
+    if (replace_dest)
        err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
 
     if (err == NULL) {
@@ -2750,13 +2798,13 @@ static int dav_method_copymove(request_rec *r, int is_move)
                                                     &multi_response);
     }
 
-    /* restore parent collection states */
-    err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
-                                          &dst_av_info);
+    /* perform any auto-versioning cleanup */
+    err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+                           0 /*unlock*/, &dst_av_info);
 
     if (is_move) {
-        err3 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
-                                              &src_av_info);
+        err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
+                               0 /*unlock*/, &src_av_info);
     }
     else
        err3 = NULL;
@@ -2774,12 +2822,12 @@ static int dav_method_copymove(request_rec *r, int is_move)
        return dav_handle_err(r, err, multi_response);
     }
 
-    /* check for errors from reverting writability */
+    /* check for errors from auto-versioning */
     if (err2 != NULL) {
        /* just log a warning */
        err = dav_push_error(r->pool, err2->status, 0,
                             "The MOVE/COPY was successful, but there was a "
-                            "problem reverting the writability of the "
+                            "problem automatically checking in the "
                             "source parent collection.",
                             err2);
        dav_log_err(r, err, APLOG_WARNING);
@@ -2788,8 +2836,8 @@ static int dav_method_copymove(request_rec *r, int is_move)
        /* just log a warning */
        err = dav_push_error(r->pool, err3->status, 0,
                             "The MOVE/COPY was successful, but there was a "
-                            "problem reverting the writability of the "
-                            "destination parent collection.",
+                            "problem automatically checking in the "
+                            "destination or its parent collection.",
                             err3);
        dav_log_err(r, err, APLOG_WARNING);
     }
@@ -2798,7 +2846,7 @@ static int dav_method_copymove(request_rec *r, int is_move)
     if (lockdb != NULL) {
 
        /* notify lock system that we have created/replaced a resource */
-       err = dav_notify_created(r, lockdb, resnew, resource_state, depth);
+       err = dav_notify_created(r, lockdb, resnew, resnew_state, depth);
 
        (*lockdb->hooks->close_lockdb)(lockdb);
 
@@ -2814,7 +2862,8 @@ static int dav_method_copymove(request_rec *r, int is_move)
     }
 
     /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
-    return dav_created(r, lookup.rnew->uri, "Destination", replaced);
+    return dav_created(r, lookup.rnew->uri, "Destination",
+                       resnew_state == DAV_RESOURCE_EXISTS);
 }
 
 /* dav_method_lock:  Handler to implement the DAV LOCK method
@@ -3193,14 +3242,14 @@ static int dav_method_vsn_control(request_rec *r)
     }
 
     /* if in versioned collection, make sure parent is checked out */
-    if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
-                                           &av_info)) != NULL) {
+    if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
+                                &av_info)) != NULL) {
        return dav_handle_err(r, err, NULL);
     }
 
     /* attempt to version-control the resource */
     if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
-        dav_revert_resource_writability(r, resource, 1 /*undo*/, &av_info);
+        dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
        err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
                             apr_psprintf(r->pool,
                                         "Could not VERSION-CONTROL resource %s.",
@@ -3210,12 +3259,12 @@ static int dav_method_vsn_control(request_rec *r)
     }
 
     /* revert writability of parent directory */
-    err = dav_revert_resource_writability(r, resource, 0 /*undo*/, &av_info);
+    err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info);
     if (err != NULL) {
         /* just log a warning */
        err = dav_push_error(r->pool, err->status, 0,
                             "The VERSION-CONTROL was successful, but there "
-                            "was a problem reverting the writability of "
+                            "was a problem automatically checking in "
                             "the parent collection.",
                             err);
         dav_log_err(r, err, APLOG_WARNING);
@@ -3375,7 +3424,8 @@ static int dav_method_checkout(request_rec *r)
     /* ### do lock checks, once behavior is defined */
 
     /* Do the checkout */
-    if ((err = (*vsn_hooks->checkout)(resource, is_unreserved, is_fork_ok,
+    if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/,
+                                      is_unreserved, is_fork_ok,
                                       create_activity, activities,
                                       &working_resource)) != NULL) {
        err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
@@ -3389,9 +3439,14 @@ static int dav_method_checkout(request_rec *r)
     /* set the Cache-Control header, per the spec */
     apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
 
-    /* use appropriate URI for Location header */
-    if (working_resource == NULL)
-        working_resource = resource;
+    /* if no working resource created, return OK,
+     * else return CREATED with working resource URL in Location header
+     */
+    if (working_resource == NULL) {
+        /* no body */
+        ap_set_content_length(r, 0);
+        return DONE;
+    }
 
     return dav_created(r, working_resource->uri, "Checked-out resource", 0);
 }
@@ -3894,7 +3949,7 @@ static int dav_method_report(request_rec *r)
      * First determine whether a Target-Selector header is allowed
      * for this report.
      */
-    label_allowed = (*vsn_hooks->report_target_selector_allowed)(doc);
+    label_allowed = (*vsn_hooks->report_label_header_allowed)(doc);
     err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */,
                            &resource);
     if (err != NULL)
@@ -4331,8 +4386,8 @@ static int dav_method_bind(request_rec *r)
     }
 
     /* prepare the destination collection for modification */
-    if ((err = dav_ensure_resource_writable(r, binding, 1 /* parent_only */,
-                                           &av_info)) != NULL) {
+    if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */,
+                                &av_info)) != NULL) {
         /* could not make destination writable */
        return dav_handle_err(r, err, NULL);
     }
@@ -4348,9 +4403,9 @@ static int dav_method_bind(request_rec *r)
     }
 
     /* restore parent collection states */
-    err2 = dav_revert_resource_writability(r, NULL,
-                                          err != NULL /* undo if error */,
-                                          &av_info);
+    err2 = dav_auto_checkin(r, NULL,
+                           err != NULL /* undo if error */,
+                           0 /*unlock*/, &av_info);
 
     /* check for error from remove/bind operations */
     if (err != NULL) {
@@ -4367,7 +4422,7 @@ static int dav_method_bind(request_rec *r)
        /* just log a warning */
        err = dav_push_error(r->pool, err2->status, 0,
                             "The BIND was successful, but there was a "
-                            "problem reverting the writability of the "
+                            "problem automatically checking in the "
                             "source parent collection.",
                             err2);
        dav_log_err(r, err, APLOG_WARNING);
index 012cee10752b27a739c447656d532629804e0a07..466cb6d711cccea6755f5d060222d616e983635a 100644 (file)
@@ -1737,8 +1737,12 @@ struct dav_hooks_repository
         dav_resource *resource
     );
 
-    /* Copy one resource to another. The destination must not exist.
+    /* Copy one resource to another. The destination may exist, if it is
+     * versioned.
      * Handles both files and collections. Properties are copied as well.
+     * If the destination exists and is versioned, the provider must update
+     * the destination to have identical content to the source,
+     * recursively for collections.
      * The depth argument is ignored for a file, and can be either 0 or
      * DAV_INFINITY for a collection.
      * If an error occurs in a child resource, then the return value is
@@ -1813,15 +1817,42 @@ void dav_add_vary_header(request_rec *in_req,
                         request_rec *out_req,
                         const dav_resource *resource);
 
+/*
+** Flags specifying auto-versioning behavior, returned by
+** the auto_versionable hook. The value returned depends
+** on both the state of the resource and the value of the
+** DAV:auto-versioning property for the resource.
+**
+** If the resource does not exist (null or lock-null),
+** DAV_AUTO_VERSION_ALWAYS causes creation of a new version-controlled resource
+**
+** If the resource is checked in,
+** DAV_AUTO_VERSION_ALWAYS causes it to be checked out always,
+** DAV_AUTO_VERSION_LOCKED causes it to be checked out only when locked
+**
+** If the resource is checked out,
+** DAV_AUTO_VERSION_ALWAYS causes it to be checked in always,
+** DAV_AUTO_VERSION_LOCKED causes it to be checked in when unlocked
+** (note: a provider should allow auto-checkin only for resources which
+** were automatically checked out)
+**
+** In all cases, DAV_AUTO_VERSION_NEVER results in no auto-versioning behavior.
+*/
+typedef enum {
+    DAV_AUTO_VERSION_NEVER,
+    DAV_AUTO_VERSION_ALWAYS,
+    DAV_AUTO_VERSION_LOCKED
+} dav_auto_version;
+
 /*
 ** This structure is used to record what auto-versioning operations
 ** were done to make a resource writable, so that they can be undone
 ** at the end of a request.
 */
 typedef struct {
-    int resource_created;               /* 0 => resource existed previously */
-    int resource_checkedout;            /* 0 => resource was checked out */
-    int parent_checkedout;              /* 0 => parent was checked out */
+    int resource_versioned;             /* 1 => resource was auto-version-controlled */
+    int resource_checkedout;            /* 1 => resource was auto-checked-out */
+    int parent_checkedout;              /* 1 => parent was auto-checked-out */
     dav_resource *parent_resource;      /* parent resource, if it was needed */
 } dav_auto_version_info;
 
@@ -1836,14 +1867,18 @@ typedef struct {
  * child does not exist, then a new versioned resource is created and
  * checked out.
  *
+ * If auto-versioning is not enabled for a versioned resource, then an error is
+ * returned, since the resource cannot be modified.
+ *
  * The dav_auto_version_info structure is filled in with enough information
  * to restore both parent and child resources to the state they were in
  * before the auto-versioning operations occurred.
  */
-dav_error *dav_ensure_resource_writable(request_rec *r,
-                                       dav_resource *resource,
-                                        int parent_only,
-                                        dav_auto_version_info *av_info);
+dav_error *dav_auto_checkout(
+    request_rec *r,
+    dav_resource *resource,
+    int parent_only,
+    dav_auto_version_info *av_info);
 
 /* Revert the writability of resources back to what they were
  * before they were modified. If undo == 0, then the resource
@@ -1851,15 +1886,21 @@ dav_error *dav_ensure_resource_writable(request_rec *r,
  * If undo != 0, then resource modifications are discarded
  * (i.e. they are unchecked out).
  *
+ * Set the unlock flag to indicate that the resource is about
+ * to be unlocked; it will be checked in if the resource
+ * auto-versioning property indicates it should be. In this case,
+ * av_info is ignored, so it can be NULL.
+ *
  * The resource argument may be NULL if only the parent resource
- * was made writable (i.e. the parent_only was != 0 in the
- * dav_ensure_resource_writable call).
+ * was checked out (i.e. the parent_only was != 0 in the
+ * dav_auto_checkout call).
  */
-dav_error *dav_revert_resource_writability(
+dav_error *dav_auto_checkin(
     request_rec *r,
     dav_resource *resource,
     int undo,
-    const dav_auto_version_info *av_info);
+    int unlock,
+    dav_auto_version_info *av_info);
 
 /*
 ** This structure is used to describe available reports
@@ -1899,18 +1940,34 @@ struct dav_hooks_vsn
                               const ap_xml_elem *elem,
                               ap_text_header *option);
 
+    /* Determine whether a non-versioned (or non-existent) resource
+     * is versionable. Returns != 0 if resource can be versioned.
+     */
+    int (*versionable)(const dav_resource *resource);
+
+    /* Determine whether auto-versioning is enabled for a resource
+     * (which may not exist, or may not be versioned). If the resource
+     * is a checked-out resource, the provider must only enable
+     * auto-checkin if the resource was automatically checked out.
+     *
+     * The value returned depends on both the state of the resource
+     * and the value of its DAV:auto-version property. See the description
+     * of the dav_auto_version enumeration above for the details.
+     */
+    dav_auto_version (*auto_versionable)(const dav_resource *resource);
+
     /* Put a resource under version control. If the resource already
      * exists unversioned, then it becomes the initial version of the
      * new version history, and it is replaced by a version selector
      * which targets the new version.
      *
-     * If the resource does not exist, then a new version selector
-     * is created which either targets an existing version (if the
+     * If the resource does not exist, then a new version-controlled
+     * resource is created which either targets an existing version (if the
      * "target" argument is not NULL), or the initial, empty version
      * in a new history resource (if the "target" argument is NULL).
      *
      * If successful, the resource object state is updated appropriately
-     * (that is, changed to refer to the new version selector resource).
+     * (that is, changed to refer to the new version-controlled resource).
      */
     dav_error * (*vsn_control)(dav_resource *resource,
                                const char *target);
@@ -1918,6 +1975,13 @@ struct dav_hooks_vsn
     /* Checkout a resource. If successful, the resource
      * object state is updated appropriately.
      *
+     * The auto_checkout flag will be set if this checkout is being
+     * done automatically, as part of some method which modifies
+     * the resource. The provider must remember that the resource
+     * was automatically checked out, so it can determine whether it
+     * can be automatically checked in. (Auto-checkin should only be
+     * enabled for resources which were automatically checked out.)
+     *
      * If the working resource has a different URL from the
      * target resource, a dav_resource descriptor is returned
      * for the new working resource. Otherwise, the original
@@ -1934,6 +1998,7 @@ struct dav_hooks_vsn
      * no DAV:activity-set was provided or when create_activity is set.
      */
     dav_error * (*checkout)(dav_resource *resource,
+                            int auto_checkout,
                             int is_unreserved, int is_fork_ok,
                             int create_activity,
                             apr_array_header_t *activities,
@@ -1959,17 +2024,6 @@ struct dav_hooks_vsn
                            int keep_checked_out,
                            dav_resource **version_resource);
 
-    /* Determine whether a non-versioned (or non-existent) resource
-     * is versionable. Returns != 0 if resource can be versioned.
-     */
-    int (*versionable)(const dav_resource *resource);
-
-    /* Determine whether auto-versioning is enabled for a resource
-     * (which may not exist, or may not be versioned).
-     * Returns != 0 if auto-versioning is enabled.
-     */
-    int (*auto_version_enabled)(const dav_resource *resource);
-
     /*
     ** Return the set of reports available at this resource.
     **
@@ -1982,12 +2036,12 @@ struct dav_hooks_vsn
                                  const dav_report_elem **reports);
 
     /*
-    ** Determine whether a Target-Selector header can be used
+    ** Determine whether a Label header can be used
     ** with a particular report. The dav_xml_doc structure
     ** contains the parsed report request body.
-    ** Returns 0 if Target-Selector is not allowed.
+    ** Returns 0 if the Label header is not allowed.
     */
-    int (*report_target_selector_allowed)(const ap_xml_doc *doc);
+    int (*report_label_header_allowed)(const ap_xml_doc *doc);
 
     /*
     ** Generate a report on a resource. Since a provider is free
index 12929f7e465293195c28a9f4509e4d18ee8d158c..1d523fcb1106172fd3481ab735844720b7c8409b 100644 (file)
@@ -1609,183 +1609,313 @@ void dav_add_vary_header(request_rec *in_req,
     }
 }
 
+/* dav_can_auto_checkout
+ *
+ * Determine whether auto-checkout is enabled for a resource.
+ * r - the request_rec
+ * resource - the resource
+ * auto_version - the value of the auto_versionable hook for the resource
+ * lockdb - pointer to lock database (opened if necessary)
+ * auto_checkout - set to 1 if auto-checkout enabled
+ */
+static dav_error * dav_can_auto_checkout(
+    request_rec *r,                                         
+    dav_resource *resource,
+    dav_auto_version auto_version,
+    dav_lockdb **lockdb,
+    int *auto_checkout)
+{
+    dav_error *err;
+    dav_lock *lock_list;
+
+    *auto_checkout = 0;
+
+    if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
+        *auto_checkout = 1;
+    }
+    else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
+        if (*lockdb == NULL) {
+            const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+
+            if (locks_hooks == NULL) {
+                return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+                                     "Auto-checkout is only enabled for locked resources, "
+                                     "but there is no lock provider.");
+            }
+
+            if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) {
+                return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+                                      "Cannot open lock database to determine "
+                                      "auto-versioning behavior.",
+                                      err);
+            }
+        }
+
+        if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) {
+           return dav_push_error(r->pool,
+                                 HTTP_INTERNAL_SERVER_ERROR, 0,
+                                 "The locks could not be queried for "
+                                 "determining auto-versioning behavior.",
+                                 err);
+        }
+
+        if (lock_list != NULL)
+            *auto_checkout = 1;
+    }
+
+    return NULL;
+}
+
 /* see mod_dav.h for docco */
-dav_error *dav_ensure_resource_writable(request_rec *r,
-                                       dav_resource *resource,
-                                        int parent_only,
-                                        dav_auto_version_info *av_info)
+dav_error *dav_auto_checkout(
+    request_rec *r,
+    dav_resource *resource,
+    int parent_only,
+    dav_auto_version_info *av_info)
 {
     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
-    const char *body;
-    dav_error *err;
+    dav_lockdb *lockdb = NULL;
+    dav_error *err = NULL;
 
     /* Initialize results */
     memset(av_info, 0, sizeof(*av_info));
 
+    /* if no versioning provider, just return */
+    if (vsn_hooks == NULL)
+        return NULL;
+
     /* check parent resource if requested or if resource must be created */
     if (!resource->exists || parent_only) {
        dav_resource *parent;
 
         if ((err = (*resource->hooks->get_parent_resource)(resource,
                                                            &parent)) != NULL)
-            return err;
+            goto done;
 
         if (parent == NULL || !parent->exists) {
-           body = apr_psprintf(r->pool,
-                                "Missing one or more intermediate collections. "
-                                "Cannot create resource %s.",
-                                ap_escape_html(r->pool, resource->uri));
-           return dav_new_error(r->pool, HTTP_CONFLICT, 0, body);
+           err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
+                               apr_psprintf(r->pool,
+                                           "Missing one or more intermediate "
+                                            "collections. Cannot create resource %s.",
+                                           ap_escape_html(r->pool, resource->uri)));
+            goto done;
         }
 
         av_info->parent_resource = parent;
 
-       /* if parent not versioned, assume child can be created */
-       if (!parent->versioned) {
-           return NULL;
-       }
+        /* if parent versioned and not checked out, see if it can be */
+       if (parent->versioned && !parent->working) {
+            int checkout_parent;
 
-       /* if no versioning provider, something is terribly wrong */
-       if (vsn_hooks == NULL) {
-           return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
-                                "INTERNAL ERROR: "
-                                 "versioned resource with no versioning "
-                                "provider?");
-       }
+            if ((err = dav_can_auto_checkout(r, parent,
+                                             (*vsn_hooks->auto_versionable)(parent),
+                                             &lockdb, &checkout_parent))
+                != NULL) {
+                goto done;
+            }
 
-       /* parent must be checked out */
-       if (!parent->working) {
-            /* if parent cannot be automatically checked out, fail */
-            if (!(*vsn_hooks->auto_version_enabled)(parent)) {
-               body = apr_psprintf(r->pool,
-                                    "Parent collection must be checked out. "
-                                    "Cannot create resource %s.",
-                                    ap_escape_html(r->pool, resource->uri));
-               return dav_new_error(r->pool, HTTP_CONFLICT, 0, body);
+            if (!checkout_parent) {
+               err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
+                                   "<DAV:cannot-modify-checked-in-parent>");
+                goto done;
             }
 
             /* Try to checkout the parent collection.
              * Note that auto-versioning can only be applied to a version selector,
              * so no separate working resource will be created.
              */
-           if ((err = (*vsn_hooks->checkout)(parent, 0, 0, 0, NULL, NULL))
+           if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
+                                              0, 0, 0, NULL, NULL))
                 != NULL)
             {
-               body = apr_psprintf(r->pool,
-                                    "Unable to checkout parent collection. "
-                                    "Cannot create resource %s.",
-                                    ap_escape_html(r->pool, resource->uri));
-               return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err);
+               err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+                                    apr_psprintf(r->pool,
+                                                "Unable to auto-checkout parent collection. "
+                                                "Cannot create resource %s.",
+                                                ap_escape_html(r->pool, resource->uri)),
+                                     err);
+                goto done;
            }
 
             /* remember that parent was checked out */
             av_info->parent_checkedout = 1;
        }
+    }
 
-       /* if not just checking parent, create new child resource */
-        if (!parent_only) {
-           if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
-               body = apr_psprintf(r->pool,
-                                    "Unable to create versioned resource %s.",
-                                    ap_escape_html(r->pool, resource->uri));
-               return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err);
-           }
+    /* if only checking parent, we're done */
+    if (parent_only)
+        goto done;
+
+    /* if creating a new resource, see if it should be version-controlled */
+    if (!resource->exists
+        && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) {
+
+       if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
+           err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+                                apr_psprintf(r->pool,
+                                            "Unable to create versioned resource %s.",
+                                            ap_escape_html(r->pool, resource->uri)),
+                                 err);
+            goto done;
+       }
 
-            /* remember that resource was created */
-            av_info->resource_created = 1;
-        }
-    }
-    else if (!resource->versioned) {
-       /* resource exists and is not versioned; assume it is writable */
-       return NULL;
+        /* remember that resource was created */
+        av_info->resource_versioned = 1;
     }
 
-    /* if not just checking parent, make sure child resource is checked out */
-    if (!parent_only && !resource->working) {
+    /* if resource is versioned, make sure it is checked out */
+    if (resource->versioned && !resource->working) {
+        int checkout_resource;
+
+        if ((err = dav_can_auto_checkout(r, resource,
+                                         (*vsn_hooks->auto_versionable)(resource),
+                                         &lockdb, &checkout_resource)) != NULL) {
+            goto done;
+        }
+
+        if (!checkout_resource) {
+           err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
+                               "<DAV:cannot-modify-version-controlled-content>");
+            goto done;
+        }
+
         /* Auto-versioning can only be applied to version selectors, so
          * no separate working resource will be created. */
-       if ((err = (*vsn_hooks->checkout)(resource, 0, 0, 0, NULL, NULL))
+       if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/,
+                                          0, 0, 0, NULL, NULL))
             != NULL)
         {
-           body = apr_psprintf(r->pool,
-                                "Unable to checkout resource %s.",
-                                ap_escape_html(r->pool, resource->uri));
-           return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err);
+            err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+                                apr_psprintf(r->pool,
+                                            "Unable to checkout resource %s.",
+                                            ap_escape_html(r->pool, resource->uri)),
+                                 err);
+            goto done;
        }
 
         /* remember that resource was checked out */
         av_info->resource_checkedout = 1;
     }
 
+done:
+
+    /* make sure lock database is closed */
+    if (lockdb != NULL)
+        (*lockdb->hooks->close_lockdb)(lockdb);
+
+    /* if an error occurred, undo any auto-versioning operations already done */
+    if (err != NULL) {
+        dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
+        return err;
+    }
+
     return NULL;
 }
 
 /* see mod_dav.h for docco */
-dav_error *dav_revert_resource_writability(
+dav_error *dav_auto_checkin(
     request_rec *r,
     dav_resource *resource,
     int undo,
-    const dav_auto_version_info *av_info)
+    int unlock,
+    dav_auto_version_info *av_info)
 {
     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
-    const char *body;
-    dav_error *err;
+    dav_error *err = NULL;
+    dav_auto_version auto_version;
+
+    /* If no versioning provider, this is a no-op */
+    if (vsn_hooks == NULL)
+        return NULL;
+
+    /* If undoing auto-checkouts, then do uncheckouts */
+    if (undo) {
+        if (resource != NULL) {
+            if (av_info->resource_checkedout) {
+                if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
+                    return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+                                         apr_psprintf(r->pool,
+                                                     "Unable to undo auto-checkout "
+                                                      "of resource %s.",
+                                                     ap_escape_html(r->pool, resource->uri)),
+                                          err);
+                }
+            }
 
-    /* If a resource was provided, restore its writable state.
-     * Otherwise, only the parent must have been modified */
-    if (resource != NULL) {
-        if (av_info->resource_checkedout) {
-
-            if (undo)
-                err = (*vsn_hooks->uncheckout)(resource);
-            else
-                err = (*vsn_hooks->checkin)(resource,
-                                            0 /*keep_checked_out*/, NULL);
-
-            if (err != NULL) {
-               body = apr_psprintf(r->pool,
-                                    "Unable to %s resource %s.",
-                                    undo ? "uncheckout" : "checkin",
-                                    ap_escape_html(r->pool, resource->uri));
-                return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
-                                     body, err);
+            if (av_info->resource_versioned) {
+               dav_response *response;
+
+               /* ### should we do anything with the response? */
+                if ((err = (*resource->hooks->remove_resource)(resource,
+                                                              &response)) != NULL) {
+                    return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+                                         apr_psprintf(r->pool,
+                                                     "Unable to undo auto-version-control "
+                                                      "of resource %s.",
+                                                     ap_escape_html(r->pool, resource->uri)),
+                                          err);
+                }
             }
         }
 
-        /* If undoing because of an error, and the resource was created,
-         * then remove it */
-        if (undo && av_info->resource_created) {
-           dav_response *response;
-
-           /* ### should we do anything with the response? */
-            if ((err = (*resource->hooks->remove_resource)(resource,
-                                                          &response)) != NULL) {
-               body = apr_psprintf(r->pool,
-                                    "Unable to undo creation of resource %s.",
-                                    ap_escape_html(r->pool, resource->uri));
+        if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
+            if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) {
+               return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+                                     apr_psprintf(r->pool,
+                                                 "Unable to undo auto-checkout "
+                                                  "of parent collection %s.",
+                                                 ap_escape_html(r->pool, av_info->parent_resource->uri)),
+                                     err);
+           }
+        }
+
+        return NULL;
+    }
+
+    /* If the resource was checked out, and auto-checkin is enabled,
+     * then check it in.
+     */
+    if (resource != NULL && resource->working
+        && (unlock || av_info->resource_checkedout)) {
+
+        auto_version = (*vsn_hooks->auto_versionable)(resource);
+
+        if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
+            (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
+
+            if ((err = (*vsn_hooks->checkin)(resource,
+                                             0 /*keep_checked_out*/, NULL))
+                != NULL) {
                 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
-                                     body, err);
+                                     apr_psprintf(r->pool,
+                                                 "Unable to auto-checkin resource %s.",
+                                                 ap_escape_html(r->pool, resource->uri)),
+                                     err);
             }
         }
     }
 
-    /* If parent resource was made writable, restore its state */
-    if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
-
-       if (undo)
-           err = (*vsn_hooks->uncheckout)(av_info->parent_resource);
-       else
-           err = (*vsn_hooks->checkin)(av_info->parent_resource,
-                                        0 /*keep_checked_out*/, NULL);
-
-       if (err != NULL) {
-           body = apr_psprintf(r->pool,
-                                "Unable to %s parent collection %s.",
-                                undo ? "uncheckout" : "checkin",
-                                ap_escape_html(r->pool, av_info->parent_resource->uri));
-           return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
-                                 body, err);
-       }
+    /* If parent resource was checked out, and auto-checkin is enabled,
+     * then check it in.
+     */
+    if (av_info->parent_resource != NULL && av_info->parent_resource->working
+        && (unlock || av_info->parent_checkedout)) {
+
+        auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
+
+        if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
+            (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
+
+           if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
+                                             0 /*keep_checked_out*/, NULL))
+                != NULL) {
+               return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+                                     apr_psprintf(r->pool,
+                                                 "Unable to auto-checkin parent collection %s.",
+                                                 ap_escape_html(r->pool, av_info->parent_resource->uri)),
+                                                 err);
+           }
+        }
     }
 
     return NULL;
index e4c231aa71b8170523c7bb08aaddd96fb08a04fb..ff59b58e4a89808c8f92911072a373c29dc3106a 100644 (file)
@@ -417,6 +417,16 @@ static dav_error * dav_unlock_walker(dav_walk_resource *wres, int calltype)
     dav_walker_ctx *ctx = wres->walk_ctx;
     dav_error *err;
 
+    /* Before removing the lock, do any auto-checkin required */
+    if (wres->resource->working) {
+        /* ### get rid of this typecast */
+        if ((err = dav_auto_checkin(ctx->r, (dav_resource *) wres->resource,
+                                    0 /*undo*/, 1 /*unlock*/, NULL))
+            != NULL) {
+            return err;
+        }
+    }
+
     if ((err = (*ctx->w.lockdb->hooks->remove_lock)(ctx->w.lockdb,
                                                     wres->resource,
                                                     ctx->locktoken)) != NULL) {
@@ -521,6 +531,8 @@ int dav_unlock(request_rec *r, const dav_resource *resource,
     const dav_resource *lock_resource = resource;
     const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
     const dav_hooks_repository *repos_hooks = resource->hooks;
+    dav_walker_ctx ctx = { { 0 } };
+    dav_response *multi_status;
     dav_error *err;
 
     /* If no locks provider, then there is nothing to unlock. */
@@ -558,35 +570,21 @@ int dav_unlock(request_rec *r, const dav_resource *resource,
     /* At this point, lock_resource/locktoken refers to a direct lock (key), ie
      * the root of a depth > 0 lock, or locktoken is null.
      */
-    if ((err = (*hooks->remove_lock)(lockdb, lock_resource,
-                                    locktoken)) != NULL) {
-       /* ### add a higher-level desc? */
-       /* ### return err! */
-       return HTTP_INTERNAL_SERVER_ERROR;
-    }
-
-    if (lock_resource->collection) {
-        dav_walker_ctx ctx = { { 0 } };
-        dav_response *multi_status;
-
-       ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
-       ctx.w.func = dav_unlock_walker;
-        ctx.w.walk_ctx = &ctx;
-       ctx.w.pool = r->pool;
-        ctx.w.root = lock_resource;
-       ctx.w.lockdb = lockdb;
+    ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
+    ctx.w.func = dav_unlock_walker;
+    ctx.w.walk_ctx = &ctx;
+    ctx.w.pool = r->pool;
+    ctx.w.root = lock_resource;
+    ctx.w.lockdb = lockdb;
 
-       ctx.r = r;
-       ctx.locktoken = locktoken;
+    ctx.r = r;
+    ctx.locktoken = locktoken;
 
-       err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
+    err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
 
-       /* ### fix this! */
-        /* ### do something with multi_status */
-       result = err == NULL ? OK : err->status;
-    }
-    else
-       result = OK;
+    /* ### fix this! */
+    /* ### do something with multi_status */
+    result = err == NULL ? OK : err->status;
 
     (*hooks->close_lockdb)(lockdb);