]> granicus.if.org Git - apache/commitdiff
Apply a patch from John Vasta for adding (some/more) DeltaV support to
authorGreg Stein <gstein@apache.org>
Wed, 15 Nov 2000 02:05:12 +0000 (02:05 +0000)
committerGreg Stein <gstein@apache.org>
Wed, 15 Nov 2000 02:05:12 +0000 (02:05 +0000)
mod_dav. The patch applied and compiled cleanly, so I'm committing. Any
necessary changes from a review will come later, so that we can easily track
what needed to change (and can be back-ported to mod_dav 1.1).

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

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

modules/dav/fs/repos.c
modules/dav/main/mod_dav.c
modules/dav/main/mod_dav.h
modules/dav/main/props.c
modules/dav/main/util.c

index c6d198fb4e978d0f551014bb65121ac6736ad788..5248a5f91f7cb21c672a8fbb3972a021314c7bf5 100644 (file)
@@ -591,7 +591,6 @@ static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
 static dav_resource * dav_fs_get_resource(
     request_rec *r,
     const char *root_dir,
-    const char *workspace,
     const char *target,
     int is_label)
 {
index a61a7f85b5b68368d71941aa5d785bb55aa6f85f..a9092c4311b5653adaacf19c29b51551e6918b71 100644 (file)
@@ -258,6 +258,11 @@ const dav_hooks_vsn *dav_get_vsn_hooks(request_rec *r)
     return dav_get_provider(r)->vsn;
 }
 
+const dav_hooks_binding *dav_get_binding_hooks(request_rec *r)
+{
+    return dav_get_provider(r)->binding;
+}
+
 /*
  * Command handler for the DAV directive, which is TAKE1.
  */
@@ -618,7 +623,6 @@ static int dav_get_resource(request_rec *r, int target_allowed,
 {
     void *data;
     dav_dir_conf *conf;
-    const char *workspace = NULL;
     const char *target_selector = NULL;
     int is_label = 0;
     int result;
@@ -630,10 +634,6 @@ static int dav_get_resource(request_rec *r, int target_allowed,
         return OK;
     }
 
-    /* get any workspace header */
-    if ((result = dav_get_workspace(r, &workspace)) != OK)
-        return result;
-
     /* if the request target can be overridden, get any target selector */
     if (target_allowed) {
         if ((result = dav_get_target_selector(r, target,
@@ -646,7 +646,7 @@ static int dav_get_resource(request_rec *r, int target_allowed,
     /* assert: conf->provider != NULL */
 
     /* resolve the resource */
-    *res_p = (*conf->provider->repos->get_resource)(r, conf->dir, workspace,
+    *res_p = (*conf->provider->repos->get_resource)(r, conf->dir,
                                                     target_selector, is_label);
     if (*res_p == NULL) {
         /* Apache will supply a default error for this. */
@@ -710,46 +710,6 @@ static int dav_parse_range(request_rec *r,
     return 1;
 }
 
-static int available_report(request_rec *r, const dav_resource *resource)
-{
-    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
-    dav_error *err;
-    const dav_report_elem *reports;
-
-    if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) {
-       err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
-                            apr_psprintf(r->pool,
-                                          "Could not fetch a list of the "
-                                          "available reports."),
-                            err);
-        return dav_handle_err(r, err, NULL);
-    }
-
-    /* set the correct status and Content-Type */
-    r->status = 200;
-    r->content_type = DAV_XML_CONTENT_TYPE;
-
-    /* send all the headers now */
-    ap_send_http_header(r);
-
-    /* send the response... */
-    ap_rputs(DAV_XML_HEADER DEBUG_CR
-             "<D:report-set xmlns:D=\"DAV:\">" DEBUG_CR
-             "<D:available-report/>" DEBUG_CR,
-             r);
-
-    for (; reports->nmspace != NULL; ++reports) {
-        /* Note: we presume reports->namespace is propertly XML/URL quoted */
-        ap_rprintf(r, "<%s xmlns=\"%s\"/>" DEBUG_CR,
-                   reports->name, reports->nmspace);
-    }
-
-    ap_rputs("</D:report-set>" DEBUG_CR, r);
-
-    /* the response has been sent. */
-    return DONE;
-}
-
 /* handle the GET method */
 static int dav_method_get(request_rec *r)
 {
@@ -1170,7 +1130,7 @@ static int dav_method_put(request_rec *r)
     /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
 
     /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
-    return dav_created(r, NULL, "Resource", !av_info.resource_created);
+    return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
 }
 
 /* ### move this to dav_util? */
@@ -1307,6 +1267,7 @@ static int dav_method_options(request_rec *r)
 {
     const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+    const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
     dav_resource *resource;
     const char *options;
     const char *dav_level;
@@ -1335,8 +1296,10 @@ static int dav_method_options(request_rec *r)
     if (locks_hooks != NULL) {
         dav_level = "1,2";
     }
-    if (vsn_hooks != NULL) {
-        vsn_level = (*vsn_hooks->get_vsn_header)();
+
+    if (vsn_hooks != NULL
+        && (vsn_level = (*vsn_hooks->get_vsn_header)()) != NULL) {
+       dav_level = apr_pstrcat(r->pool, dav_level, ",", vsn_level, NULL);
     }
 
     /* gather property set URIs from all the liveprop providers */
@@ -1407,17 +1370,29 @@ static int dav_method_options(request_rec *r)
     if (vsn_hooks != NULL) {
         const char *vsn_options = NULL;
 
-        /* ### take into account resource type */
         if (!resource->exists) {
-            if ((*vsn_hooks->versionable)(resource))
-                vsn_options = ", MKRESOURCE";
+            int vsn_control = (*vsn_hooks->versionable)(resource);
+            int mkworkspace = vsn_hooks->can_be_workspace != NULL
+                              && (*vsn_hooks->can_be_workspace)(resource);
+
+            if (vsn_control && mkworkspace) {
+                vsn_options = ", VERSION-CONTROL, MKWORKSPACE";
+            }
+            else if (vsn_control)
+                vsn_options = ", VERSION-CONTROL";
+            else if (mkworkspace) {
+                vsn_options = ", MKWORKSPACE";
+            }
         }
         else if (!resource->versioned) {
-            if ((*vsn_hooks->versionable)(resource))
-                vsn_options = ", CHECKIN";
+            if ((*vsn_hooks->versionable)(resource)) {
+                vsn_options = ", VERSION-CONTROL";
+            }
         }
         else if (resource->working)
             vsn_options = ", CHECKIN, UNCHECKOUT";
+        else if (vsn_hooks->add_label != NULL)
+            vsn_options = ", CHECKOUT, LABEL";
         else
             vsn_options = ", CHECKOUT";
 
@@ -1425,12 +1400,16 @@ static int dav_method_options(request_rec *r)
             options = apr_pstrcat(r->pool, options, vsn_options, NULL);
     }
 
+    /* If there is a bindings provider, see if resource is bindable */
+    if (binding_hooks != NULL) {
+       dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
+        if ((*binding_hooks->is_bindable)(resource))
+            options = apr_pstrcat(r->pool, options, ", BIND", NULL);
+    }
+
     apr_table_setn(r->headers_out, "Allow", options);
     apr_table_setn(r->headers_out, "DAV", dav_level);
 
-    if (vsn_level != NULL)
-        apr_table_setn(r->headers_out, "Versioning", vsn_level);
-
     /* ### this will send a Content-Type. the default OPTIONS does not. */
     ap_send_http_header(r);
 
@@ -2730,8 +2709,185 @@ static int dav_method_unlock(request_rec *r)
 
 static int dav_method_vsn_control(request_rec *r)
 {
-    /* ### */
-    return HTTP_METHOD_NOT_ALLOWED;
+    dav_resource *resource;
+    int resource_state;
+    dav_auto_version_info av_info;
+    const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+    dav_error *err;
+    ap_xml_doc *doc;
+    ap_xml_elem *child;
+    const char *target = NULL;
+    int result;
+
+    /* if no versioning provider, decline the request */
+    if (vsn_hooks == NULL)
+        return DECLINED;
+
+    /* ask repository module to resolve the resource */
+    result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
+    if (result != OK)
+        return result;
+
+    /* remember the pre-creation resource state */
+    resource_state = dav_get_resource_state(r, resource);
+
+    /* parse the request body (may be a version-control element) */
+    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+       return result;
+    }
+    /* note: doc == NULL if no request body */
+
+    if (doc != NULL) {
+        if (!dav_validate_root(doc, "version-control")) {
+           ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                         "The request body does not contain "
+                         "a \"version-control\" element.");
+           return HTTP_BAD_REQUEST;
+        }
+
+        /* get the version URI */
+        if ((child = dav_find_child(doc->root, "version")) == NULL) {
+           ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                         "The \"version-control\" element does not contain "
+                         "a \"version\" element.");
+           return HTTP_BAD_REQUEST;
+        }
+
+        if ((child = dav_find_child(child, "href")) == NULL) {
+           ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                         "The \"version\" element does not contain "
+                         "an \"href\" element.");
+           return HTTP_BAD_REQUEST;
+        }
+
+        /* get version URI */
+        ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL, &target, NULL);
+        if (strlen(target) == 0) {
+           ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                         "An \"href\" element does not contain a URI.");
+           return HTTP_BAD_REQUEST;
+        }
+    }
+
+    /* Check request preconditions */
+
+    /* ### need a general mechanism for reporting precondition violations
+     * ### (should be returning XML document for 403/409 responses)
+     */
+
+    /* if not versioning existing resource, must specify version to select */
+    if (!resource->exists && target == NULL) {
+        err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
+                            "<DAV:initial-version-required/>");
+       return dav_handle_err(r, err, NULL);
+    }
+    else if (resource->exists) {
+        /* cannot add resource to existing version history */
+        if (target != NULL) {
+            err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
+                                "<DAV:cannot-add-to-existing-history/>");
+           return dav_handle_err(r, err, NULL);
+        }
+
+        /* resource must be unversioned and versionable, or version selector */
+        if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+            || (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
+            err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
+                                "<DAV:must-be-versionable/>");
+           return dav_handle_err(r, err, NULL);
+        }
+
+        /* the DeltaV spec says if resource is a version selector,
+         * then VERSION-CONTROL is a no-op
+         */
+        if (resource->versioned) {
+            /* set the Cache-Control header, per the spec */
+            apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+            /* no body */
+            ap_set_content_length(r, 0);
+            ap_send_http_header(r);
+
+            return DONE;
+        }
+    }
+
+    /* Check If-Headers and existing locks */
+    /* Note: depth == 0. Implies no need for a multistatus response. */
+    if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
+                                   resource_state == DAV_RESOURCE_NULL ?
+                                   DAV_VALIDATE_PARENT :
+                                   DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+       return dav_handle_err(r, err, NULL);
+    }
+
+    /* if in versioned collection, make sure parent is checked out */
+    if ((err = dav_ensure_resource_writable(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);
+       err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+                            apr_psprintf(r->pool,
+                                        "Could not VERSION-CONTROL resource %s.",
+                                        ap_escape_html(r->pool, r->uri)),
+                            err);
+        return dav_handle_err(r, err, NULL);
+    }
+
+    /* revert writability of parent directory */
+    err = dav_revert_resource_writability(r, resource, 0 /*undo*/, &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 "
+                            "the parent collection.",
+                            err);
+        dav_log_err(r, err, APLOG_WARNING);
+    }
+
+    /* if the resource is lockable, let lock system know of new resource */
+    if (locks_hooks != NULL
+       && (*locks_hooks->get_supportedlock)(resource) != NULL) {
+       dav_lockdb *lockdb;
+
+       if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
+           /* The resource creation was successful, but the locking failed. */
+           err = dav_push_error(r->pool, err->status, 0,
+                                "The VERSION-CONTROL was successful, but there "
+                                "was a problem opening the lock database "
+                                "which prevents inheriting locks from the "
+                                "parent resources.",
+                                err);
+           return dav_handle_err(r, err, NULL);
+       }
+
+       /* notify lock system that we have created/replaced a resource */
+       err = dav_notify_created(r, lockdb, resource, resource_state, 0);
+
+       (*locks_hooks->close_lockdb)(lockdb);
+
+       if (err != NULL) {
+           /* The dir creation was successful, but the locking failed. */
+           err = dav_push_error(r->pool, err->status, 0,
+                                "The VERSION-CONTROL was successful, but there "
+                                "was a problem updating its lock "
+                                "information.",
+                                err);
+           return dav_handle_err(r, err, NULL);
+       }
+    }
+
+    /* set the Cache-Control header, per the spec */
+    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+    /* return an appropriate response (HTTP_CREATED) */
+    return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
 }
 
 /* handle the CHECKOUT method */
@@ -2935,43 +3091,72 @@ static int dav_method_checkin(request_rec *r)
     return dav_created(r, new_version->uri, "Version", 0);
 }
 
-static int dav_method_set_target(request_rec *r)
+/* context maintained during SET-TARGET treewalk */
+typedef struct dav_set_target_walker_ctx
 {
-    /* ### */
-    return HTTP_METHOD_NOT_ALLOWED;
-}
+    /* input: */
+    dav_walk_params w;
 
-static int dav_method_label(request_rec *r)
+    /* target specifier */
+    const char *target;
+
+    /* flag for whether target is version URI or label */
+    int is_label;
+
+    /* version provider hooks */
+    const dav_hooks_vsn *vsn_hooks;
+
+} dav_set_target_walker_ctx;
+
+static dav_error * dav_set_target_walker(dav_walk_resource *wres, int calltype)
 {
-    /* ### */
-    return HTTP_METHOD_NOT_ALLOWED;
+    dav_set_target_walker_ctx *ctx = wres->walk_ctx;
+    dav_error *err = NULL;
+
+    /* Check the state of the resource: must be a checked-in version
+     * or baseline selector
+     */
+    /* ### need a general mechanism for reporting precondition violations
+     * ### (should be returning XML document for 403/409 responses)
+     */
+    if (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
+        || !wres->resource->versioned || wres->resource->working) {
+       err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
+                           "<DAV:must-be-checked-in-version-selector/>");
+    }
+    else {
+        /* do the set-target operation */
+        err = (*ctx->vsn_hooks->set_target)(wres->resource, ctx->target, ctx->is_label);
+    }
+
+    if (err != NULL) {
+        /* ### need utility routine to add response with description? */
+        dav_add_response(wres, err->status, NULL);
+        wres->response->desc = err->desc;
+    }
+
+    return NULL;
 }
 
-static int dav_method_report(request_rec *r)
+static int dav_method_set_target(request_rec *r)
 {
     dav_resource *resource;
     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
-    int result;
-    int target_allowed;
     ap_xml_doc *doc;
+    ap_xml_elem *child;
+    int depth;
+    int result;
+    int tsize;
+    dav_error *err;
+    dav_set_target_walker_ctx ctx = { { 0 } };
+    dav_response *multi_status;
 
     /* If no versioning provider, decline the request */
     if (vsn_hooks == NULL)
         return DECLINED;
 
-    if ((result = ap_xml_parse_input(r, &doc)) != OK)
-       return result;
-    if (doc == NULL) {
-        /* This supplies additional information for the default msg. */
-        ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
-                      "The request body must specify a report.");
-        return HTTP_BAD_REQUEST;
-    }
-
     /* Ask repository module to resolve the resource */
-    /* ### need to compute target_allowed based on report type */
-    target_allowed = 0;
-    result = dav_get_resource(r, target_allowed, NULL, &resource);
+    result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
     if (result != OK)
         return result;
     if (!resource->exists) {
@@ -2979,49 +3164,628 @@ static int dav_method_report(request_rec *r)
         return HTTP_NOT_FOUND;
     }
 
-    if (dav_validate_root(doc, "available-report")) {
-        return available_report(r, resource);
+    if ((depth = dav_get_depth(r, 0)) < 0) {
+       /* dav_get_depth() supplies additional information for the
+        * default message. */
+       return HTTP_BAD_REQUEST;
     }
 
-    /* ### run report hook */
-    return DECLINED;
-}
+    /* parse the request body */
+    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+       return result;
+    }
 
-static int dav_method_make_workspace(request_rec *r)
-{
-    /* ### */
-    return HTTP_METHOD_NOT_ALLOWED;
-}
+    if (doc == NULL || !dav_validate_root(doc, "set-target")) {
+       /* This supplies additional information for the default message. */
+       ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                     "The request body does not contain "
+                     "a \"set-target\" element.");
+       return HTTP_BAD_REQUEST;
+    }
 
-static int dav_method_make_activity(request_rec *r)
-{
-    /* ### */
-    return HTTP_METHOD_NOT_ALLOWED;
-}
+    /* check for label-name or version element */
+    if ((child = dav_find_child(doc->root, "label-name")) != NULL) {
+        ctx.is_label = 1;
+    }
+    else if ((child = dav_find_child(doc->root, "version")) != NULL) {
+        ctx.is_label = 0;
 
-static int dav_method_baseline_control(request_rec *r)
-{
-    /* ### */
-    return HTTP_METHOD_NOT_ALLOWED;
-}
+        /* get the href element */
+        if ((child = dav_find_child(child, "href")) == NULL) {
+           ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                         "The version element does not contain "
+                         "an \"href\" element.");
+           return HTTP_BAD_REQUEST;
+        }
+    }
+    else {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                     "The \"set-target\" element does not contain "
+                     "a \"label-name\" or \"version\" element.");
+       return HTTP_BAD_REQUEST;
+    }
 
-static int dav_method_merge(request_rec *r)
-{
-    /* ### */
-    return HTTP_METHOD_NOT_ALLOWED;
-}
+    /* get the target value (a label or a version URI */
+    ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL, &ctx.target, &tsize);
+    if (tsize == 0) {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                     "A \"label-name\" or \"href\" element does not contain "
+                     "any content.");
+       return HTTP_BAD_REQUEST;
+    }
 
+    /* do the set-target operation walk */
+    ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
+    ctx.w.func = dav_set_target_walker;
+    ctx.w.walk_ctx = &ctx;
+    ctx.w.pool = r->pool;
+    ctx.w.root = resource;
+    ctx.vsn_hooks = vsn_hooks;
 
-/*
- * Response handler for DAV resources
- */
-static int dav_handler(request_rec *r)
-{
-    dav_dir_conf *conf;
+    err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
 
-    /* quickly ignore any HTTP/0.9 requests */
-    if (r->assbackwards) {
-       return DECLINED;
+    if (err != NULL) {
+        /* some sort of error occurred which terminated the walk */
+        err = dav_push_error(r->pool, err->status, 0,
+                             "The SET-TARGET operation was terminated prematurely.",
+                             err);
+        return dav_handle_err(r, err, multi_status);
+    }
+
+    if (multi_status != NULL) {
+        /* One or more resources had errors. If depth was zero, convert
+         * response to simple error, else make sure there is an
+         * overall error to pass to dav_handle_err()
+         */
+        if (depth == 0) {
+            err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
+            multi_status = NULL;
+        }
+        else {
+            err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
+                                "Errors occurred during the SET-TARGET operation.");
+        }
+
+        return dav_handle_err(r, err, multi_status);
+    }
+
+    /* set the Cache-Control header, per the spec */
+    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+    /* no body */
+    ap_set_content_length(r, 0);
+    ap_send_http_header(r);
+
+    return DONE;
+}
+
+/* context maintained during LABEL treewalk */
+typedef struct dav_label_walker_ctx
+{
+    /* input: */
+    dav_walk_params w;
+
+    /* label being manipulated */
+    const char *label;
+
+    /* label operation */
+    int label_op;
+#define DAV_LABEL_ADD           1
+#define DAV_LABEL_SET           2
+#define DAV_LABEL_REMOVE        3
+
+    /* version provider hooks */
+    const dav_hooks_vsn *vsn_hooks;
+
+} dav_label_walker_ctx;
+
+static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
+{
+    dav_label_walker_ctx *ctx = wres->walk_ctx;
+    dav_error *err = NULL;
+
+    /* Check the state of the resource: must be a version or
+     * non-checkedout version selector
+     */
+    /* ### need a general mechanism for reporting precondition violations
+     * ### (should be returning XML document for 403/409 responses)
+     */
+    if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
+        (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
+         || !wres->resource->versioned)) {
+       err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
+                           "<DAV:must-be-version-or-version-selector/>");
+    }
+    else if (wres->resource->working) {
+       err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
+                           "<DAV:must-not-be-checked-out/>");
+    }
+    else {
+        /* do the label operation */
+        if (ctx->label_op == DAV_LABEL_REMOVE)
+           err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
+        else
+           err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
+                                               ctx->label_op == DAV_LABEL_SET);
+    }
+
+    if (err != NULL) {
+        /* ### need utility routine to add response with description? */
+        dav_add_response(wres, err->status, NULL);
+        wres->response->desc = err->desc;
+    }
+
+    return NULL;
+}
+
+static int dav_method_label(request_rec *r)
+{
+    dav_resource *resource;
+    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+    ap_xml_doc *doc;
+    ap_xml_elem *child;
+    int depth;
+    int result;
+    int tsize;
+    dav_error *err;
+    dav_label_walker_ctx ctx = { { 0 } };
+    dav_response *multi_status;
+
+    /* If no versioning provider, or the provider doesn't support
+     * labels, decline the request */
+    if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
+        return DECLINED;
+
+    /* Ask repository module to resolve the resource */
+    result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
+    if (result != OK)
+        return result;
+    if (!resource->exists) {
+        /* Apache will supply a default error for this. */
+        return HTTP_NOT_FOUND;
+    }
+
+    if ((depth = dav_get_depth(r, 0)) < 0) {
+       /* dav_get_depth() supplies additional information for the
+        * default message. */
+       return HTTP_BAD_REQUEST;
+    }
+
+    /* parse the request body */
+    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+       return result;
+    }
+
+    if (doc == NULL || !dav_validate_root(doc, "label")) {
+       /* This supplies additional information for the default message. */
+       ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                     "The request body does not contain "
+                     "a \"label\" element.");
+       return HTTP_BAD_REQUEST;
+    }
+
+    /* check for add, set, or remove element */
+    if ((child = dav_find_child(doc->root, "add")) != NULL) {
+        ctx.label_op = DAV_LABEL_ADD;
+    }
+    else if ((child = dav_find_child(doc->root, "set")) != NULL) {
+        ctx.label_op = DAV_LABEL_SET;
+    }
+    else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
+        ctx.label_op = DAV_LABEL_REMOVE;
+    }
+    else {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                     "The \"label\" element does not contain "
+                     "an \"add\", \"set\", or \"remove\" element.");
+       return HTTP_BAD_REQUEST;
+    }
+
+    /* get the label string */
+    if ((child = dav_find_child(child, "label-name")) == NULL) {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                     "The label command element does not contain "
+                     "a \"label-name\" element.");
+       return HTTP_BAD_REQUEST;
+    }
+
+    ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL, &ctx.label, &tsize);
+    if (tsize == 0) {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                     "A \"label-name\" element does not contain "
+                     "a label name.");
+       return HTTP_BAD_REQUEST;
+    }
+
+    /* do the label operation walk */
+    ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
+    ctx.w.func = dav_label_walker;
+    ctx.w.walk_ctx = &ctx;
+    ctx.w.pool = r->pool;
+    ctx.w.root = resource;
+    ctx.vsn_hooks = vsn_hooks;
+
+    err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
+
+    if (err != NULL) {
+        /* some sort of error occurred which terminated the walk */
+        err = dav_push_error(r->pool, err->status, 0,
+                             "The LABEL operation was terminated prematurely.",
+                             err);
+        return dav_handle_err(r, err, multi_status);
+    }
+
+    if (multi_status != NULL) {
+        /* One or more resources had errors. If depth was zero, convert
+         * response to simple error, else make sure there is an
+         * overall error to pass to dav_handle_err()
+         */
+        if (depth == 0) {
+            err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
+            multi_status = NULL;
+        }
+        else {
+            err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
+                                "Errors occurred during the LABEL operation.");
+        }
+
+        return dav_handle_err(r, err, multi_status);
+    }
+
+    /* set the Cache-Control header, per the spec */
+    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+    /* no body */
+    ap_set_content_length(r, 0);
+    ap_send_http_header(r);
+
+    return DONE;
+}
+
+static int dav_method_report(request_rec *r)
+{
+    dav_resource *resource;
+    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+    int result;
+    int target_allowed;
+    ap_xml_doc *doc;
+    ap_text_header hdr = { 0 };
+    ap_text *t;
+    dav_error *err;
+
+    /* If no versioning provider, decline the request */
+    if (vsn_hooks == NULL)
+        return DECLINED;
+
+    if ((result = ap_xml_parse_input(r, &doc)) != OK)
+       return result;
+    if (doc == NULL) {
+        /* This supplies additional information for the default msg. */
+        ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                      "The request body must specify a report.");
+        return HTTP_BAD_REQUEST;
+    }
+
+    /* Ask repository module to resolve the resource.
+     * First determine whether a Target-Selector header is allowed
+     * for this report.
+     */
+    target_allowed = (*vsn_hooks->report_target_selector_allowed)(doc);
+    result = dav_get_resource(r, target_allowed, NULL, &resource);
+    if (result != OK)
+        return result;
+    if (!resource->exists) {
+        /* Apache will supply a default error for this. */
+        return HTTP_NOT_FOUND;
+    }
+
+    /* run report hook */
+    /* ### writing large reports to memory could be bad...
+     * ### but if provider generated output directly, it would
+     * ### have to handle error responses as well.
+     */
+    if ((err = (*vsn_hooks->get_report)(r, resource, doc, &hdr)) != NULL)
+       return dav_handle_err(r, err, NULL);
+
+    /* send the report response */
+    r->status = HTTP_OK;
+    r->content_type = DAV_XML_CONTENT_TYPE;
+
+    /* send the headers and start a timeout */
+    ap_send_http_header(r);
+
+    /* send the response body */
+    ap_rputs(DAV_XML_HEADER DEBUG_CR, r);
+
+    for (t = hdr.first; t != NULL; t = t->next)
+        ap_rputs(t->text, r);
+
+    return DONE;
+}
+
+static int dav_method_make_workspace(request_rec *r)
+{
+    dav_resource *resource;
+    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
+    dav_error *err;
+    ap_xml_doc *doc;
+    int result;
+
+    /* if no versioning provider, or the provider does not support workspaces,
+     * decline the request
+     */
+    if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
+        return DECLINED;
+
+    /* ask repository module to resolve the resource */
+    result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
+    if (result != OK)
+        return result;
+
+    /* parse the request body (must be a mkworkspace element) */
+    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
+       return result;
+    }
+
+    if (doc == NULL
+        || !dav_validate_root(doc, "mkworkspace")) {
+       ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                     "The request body does not contain "
+                     "a \"mkworkspace\" element.");
+       return HTTP_BAD_REQUEST;
+    }
+
+    /* Check request preconditions */
+
+    /* ### need a general mechanism for reporting precondition violations
+     * ### (should be returning XML document for 403/409 responses)
+     */
+
+    /* resource must not already exist */
+    if (resource->exists) {
+        err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
+                            "<DAV:resource-must-be-null/>");
+       return dav_handle_err(r, err, NULL);
+    }
+
+    /* ### what about locking? */
+
+    /* attempt to create the workspace */
+    if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
+       err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
+                            apr_psprintf(r->pool,
+                                        "Could not create workspace %s.",
+                                        ap_escape_html(r->pool, r->uri)),
+                            err);
+        return dav_handle_err(r, err, NULL);
+    }
+
+    /* set the Cache-Control header, per the spec */
+    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
+
+    /* return an appropriate response (HTTP_CREATED) */
+    return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
+}
+
+static int dav_method_make_activity(request_rec *r)
+{
+    /* ### */
+    return HTTP_METHOD_NOT_ALLOWED;
+}
+
+static int dav_method_baseline_control(request_rec *r)
+{
+    /* ### */
+    return HTTP_METHOD_NOT_ALLOWED;
+}
+
+static int dav_method_merge(request_rec *r)
+{
+    /* ### */
+    return HTTP_METHOD_NOT_ALLOWED;
+}
+
+static int dav_method_bind(request_rec *r)
+{
+    dav_resource *resource;
+    dav_resource *binding;
+    dav_auto_version_info av_info;
+    const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
+    const char *dest;
+    dav_error *err;
+    dav_error *err2;
+    dav_response *multi_response = NULL;
+    dav_lookup_result lookup;
+    int overwrite;
+    int result;
+
+    /* If no bindings provider, decline the request */
+    if (binding_hooks == NULL)
+        return DECLINED;
+
+    /* Ask repository module to resolve the resource */
+    result = dav_get_resource(r, 0 /*!target_allowed*/, NULL, &resource);
+    if (result != OK)
+        return result;
+    if (!resource->exists) {
+       /* Apache will supply a default error for this. */
+       return HTTP_NOT_FOUND;
+    }
+
+    /* get the destination URI */
+    dest = apr_table_get(r->headers_in, "Destination");
+    if (dest == NULL) {
+       /* This supplies additional information for the default message. */
+       ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                     "The request is missing a Destination header.");
+       return HTTP_BAD_REQUEST;
+    }
+
+    lookup = dav_lookup_uri(dest, r);
+    if (lookup.rnew == NULL) {
+       if (lookup.err.status == HTTP_BAD_REQUEST) {
+           /* This supplies additional information for the default message. */
+           ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
+                         lookup.err.desc);
+           return HTTP_BAD_REQUEST;
+       }
+        else if (lookup.err.status == HTTP_BAD_GATEWAY) {
+            /* ### Bindings protocol draft 02 says to return 507
+             * ### (Cross Server Binding Forbidden); Apache already defines 507
+             * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
+             * ### HTTP_FORBIDDEN
+             */
+             return dav_error_response(r, HTTP_FORBIDDEN,
+                                       "Cross server bindings are not allowed by this server.");
+        }
+
+       /* ### this assumes that dav_lookup_uri() only generates a status
+        * ### that Apache can provide a status line for!! */
+
+       return dav_error_response(r, lookup.err.status, lookup.err.desc);
+    }
+    if (lookup.rnew->status != HTTP_OK) {
+       /* ### how best to report this... */
+       return dav_error_response(r, lookup.rnew->status,
+                                 "Destination URI had an error.");
+    }
+
+    /* resolve binding resource */
+    result = dav_get_resource(lookup.rnew, 0 /*!target_allowed*/, NULL, &binding);
+    if (result != OK)
+        return result;
+
+    /* are the two resources handled by the same repository? */
+    if (resource->hooks != binding->hooks) {
+       /* ### this message exposes some backend config, but screw it... */
+       return dav_error_response(r, HTTP_BAD_GATEWAY,
+                                 "Destination URI is handled by a "
+                                 "different repository than the source URI. "
+                                 "BIND between repositories is not possible.");
+    }
+
+    /* get and parse the overwrite header value */
+    if ((overwrite = dav_get_overwrite(r)) < 0) {
+       /* dav_get_overwrite() supplies additional information for the
+        * default message. */
+       return HTTP_BAD_REQUEST;
+    }
+
+    /* quick failure test: if dest exists and overwrite is false. */
+    if (binding->exists && !overwrite) {
+       return dav_error_response(r, HTTP_PRECONDITION_FAILED,
+                                 "Destination is not empty and "
+                                 "Overwrite is not \"T\"");
+    }
+
+    /* are the source and destination the same? */
+    if ((*resource->hooks->is_same_resource)(resource, binding)) {
+       return dav_error_response(r, HTTP_FORBIDDEN,
+                                 "Source and Destination URIs are the same.");
+    }
+
+    /*
+    ** Check If-Headers and existing locks for destination. Note that we
+    ** use depth==infinity since the target (hierarchy) will be deleted
+    ** before the move/copy is completed.
+    **
+    ** Note that we are overwriting the target, which implies a DELETE, so
+    ** we are subject to the error/response rules as a DELETE. Namely, we
+    ** will return a 424 error if any of the validations fail.
+    ** (see dav_method_delete() for more information)
+    */
+    if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
+                                   &multi_response,
+                                   DAV_VALIDATE_PARENT
+                                    | DAV_VALIDATE_USE_424, NULL)) != NULL) {
+       err = dav_push_error(r->pool, err->status, 0,
+                            apr_psprintf(r->pool,
+                                        "Could not BIND %s due to a "
+                                        "failed precondition on the "
+                                        "destination (e.g. locks).",
+                                        ap_escape_html(r->pool, r->uri)),
+                            err);
+       return dav_handle_err(r, err, multi_response);
+    }
+
+    /* guard against creating circular bindings */
+    if (resource->collection
+       && (*resource->hooks->is_parent_resource)(resource, binding)) {
+       return dav_error_response(r, HTTP_FORBIDDEN,
+                                 "Source collection contains the Destination.");
+    }
+    if (resource->collection
+       && (*resource->hooks->is_parent_resource)(binding, resource)) {
+       /* The destination must exist (since it contains the source), and
+        * a condition above implies Overwrite==T. Obviously, we cannot
+        * delete the Destination before the BIND, as that would
+        * delete the Source.
+        */
+
+       return dav_error_response(r, HTTP_FORBIDDEN,
+                                 "Destination collection contains the Source and "
+                                 "Overwrite has been specified.");
+    }
+
+    /* prepare the destination collection for modification */
+    if ((err = dav_ensure_resource_writable(r, binding, 1 /* parent_only */,
+                                           &av_info)) != NULL) {
+        /* could not make destination writable */
+       return dav_handle_err(r, err, NULL);
+    }
+
+    /* If target exists, remove it first (we know Ovewrite must be TRUE).
+     * Then try to bind to the resource.
+     */
+    if (binding->exists)
+       err = (*resource->hooks->remove_resource)(binding, &multi_response);
+
+    if (err == NULL) {
+       err = (*binding_hooks->bind_resource)(resource, binding);
+    }
+
+    /* restore parent collection states */
+    err2 = dav_revert_resource_writability(r, NULL,
+                                          err != NULL /* undo if error */,
+                                          &av_info);
+
+    /* check for error from remove/bind operations */
+    if (err != NULL) {
+       err = dav_push_error(r->pool, err->status, 0,
+                            apr_psprintf(r->pool,
+                                        "Could not BIND %s.",
+                                        ap_escape_html(r->pool, r->uri)),
+                            err);
+       return dav_handle_err(r, err, multi_response);
+    }
+
+    /* check for errors from reverting writability */
+    if (err2 != NULL) {
+       /* 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 "
+                            "source parent collection.",
+                            err2);
+       dav_log_err(r, err, APLOG_WARNING);
+    }
+
+    /* return an appropriate response (HTTP_CREATED) */
+    /* ### spec doesn't say what happens when destination was replaced */
+    return dav_created(r, lookup.rnew->uri, "Binding", 0);
+}
+
+
+/*
+ * Response handler for DAV resources
+ */
+static int dav_handler(request_rec *r)
+{
+    dav_dir_conf *conf;
+
+    /* quickly ignore any HTTP/0.9 requests */
+    if (r->assbackwards) {
+       return DECLINED;
     }
 
     /* ### do we need to do anything with r->proxyreq ?? */
@@ -3174,6 +3938,10 @@ static int dav_handler(request_rec *r)
        return dav_method_merge(r);
     }
 
+    if (!strcmp(r->method, "BIND")) {
+       return dav_method_bind(r);
+    }
+
     /* ### add'l methods for Advanced Collections, ACLs, DASL */
 
     return DECLINED;
index ae0968c1249cdf68dbea3ec8b19af81a00363787..e7385c9e86bf467ed3a8515ca9aec8782020c537 100644 (file)
@@ -252,6 +252,7 @@ typedef struct dav_hooks_locks dav_hooks_locks;
 typedef struct dav_hooks_vsn dav_hooks_vsn;
 typedef struct dav_hooks_repository dav_hooks_repository;
 typedef struct dav_hooks_liveprop dav_hooks_liveprop;
+typedef struct dav_hooks_binding dav_hooks_binding;
 
 /* ### deprecated name */
 typedef dav_hooks_propdb dav_hooks_db;
@@ -278,11 +279,12 @@ typedef enum {
     DAV_RESOURCE_TYPE_UNKNOWN,
 
     DAV_RESOURCE_TYPE_REGULAR,          /* file or collection; could be
-                                           unversioned or version selector */
+                                         * unversioned, or version selector,
+                                         * or baseline selector */
 
-    DAV_RESOURCE_TYPE_VERSION,          /* version URL */
+    DAV_RESOURCE_TYPE_VERSION,          /* version or baseline URL */
 
-    DAV_RESOURCE_TYPE_HISTORY,          /* version history URL */
+    DAV_RESOURCE_TYPE_HISTORY,          /* version or baseline history URL */
 
     DAV_RESOURCE_TYPE_WORKING,          /* working resource URL */
 
@@ -290,8 +292,6 @@ typedef enum {
 
     DAV_RESOURCE_TYPE_ACTIVITY,         /* activity URL */
 
-    DAV_RESOURCE_TYPE_BASELINE,         /* baseline URL */
-
     DAV_RESOURCE_TYPE_PRIVATE           /* repository-private type */
 
 } dav_resource_type;
@@ -317,11 +317,14 @@ typedef struct dav_resource {
 
     int versioned;     /* 0 => unversioned; can be 1 for
                          * REGULAR and WORKSPACE resources,
-                         * and is always 1 for VERSION, WORKING,
-                         * and BASELINE */
+                         * and is always 1 for VERSION and WORKING */
+
+    int baselined;      /* 0 => not baselined; can be 1 for
+                         * REGULAR and VERSION resources;
+                         * versioned == 1 when baselined == 1 */
 
     int working;       /* 0 => not checked out; can be 1 for
-                         * REGULAR, WORKSPACE, and BASELINE,
+                         * REGULAR and WORKSPACE resources,
                          * and is always 1 for WORKING */
 
     const char *uri;   /* the URI for this resource */
@@ -456,6 +459,7 @@ typedef struct {
     const dav_hooks_locks *locks;
     const dav_hooks_liveprop *liveprop;
     const dav_hooks_vsn *vsn;
+    const dav_hooks_binding *binding;
 
 } dav_provider;
 
@@ -474,6 +478,7 @@ AP_DECLARE_EXTERNAL_HOOK(DAV, void, insert_all_liveprops,
 const dav_hooks_locks *dav_get_lock_hooks(request_rec *r);
 const dav_hooks_propdb *dav_get_propdb_hooks(request_rec *r);
 const dav_hooks_vsn *dav_get_vsn_hooks(request_rec *r);
+const dav_hooks_binding *dav_get_binding_hooks(request_rec *r);
 
 DAV_DECLARE(void) dav_register_provider(apr_pool_t *p, const char *name,
                                         const dav_provider *hooks);
@@ -490,6 +495,7 @@ void dav_add_all_liveprop_xmlns(apr_pool_t *p, ap_text_header *phdr);
 #define DAV_GET_HOOKS_PROPDB(r)         dav_get_propdb_hooks(r)
 #define DAV_GET_HOOKS_LOCKS(r)          dav_get_lock_hooks(r)
 #define DAV_GET_HOOKS_VSN(r)            dav_get_vsn_hooks(r)
+#define DAV_GET_HOOKS_BINDING(r)        dav_get_binding_hooks(r)
 
 
 /* --------------------------------------------------------------------
@@ -1341,8 +1347,6 @@ struct dav_hooks_repository
      *
      * The root_dir is the root of the directory for which this repository
      * is configured.
-     * The workspace is the value of any Workspace header, or NULL
-     * if there is none.
      * The target is either a label, or a version URI, or NULL. If there
      * is a target, then is_label specifies whether the target is a label
      * or a URI.
@@ -1353,7 +1357,6 @@ struct dav_hooks_repository
     dav_resource * (*get_resource)(
         request_rec *r,
         const char *root_dir,
-        const char *workspace,
        const char *target,
         int is_label
     );
@@ -1549,13 +1552,6 @@ struct dav_hooks_repository
 */
 
 
-/* dav_get_workspace:
- *
- * Returns the value of any Workspace header in a request
- * (used by versioning clients)
- */
-int dav_get_workspace(request_rec *r, const char **workspace);
-
 /*
  * dav_get_target_selector
  *
@@ -1647,15 +1643,32 @@ typedef struct {
 /* Versioning provider hooks */
 struct dav_hooks_vsn
 {
+    /*
+    ** MANDATORY HOOKS
+    ** The following hooks are mandatory for all versioning providers;
+    ** they define the functionality needed to implement "core" versioning.
+    */
+
     /* Return supported versioning level
      * for the Versioning header
      */
     const char * (*get_vsn_header)(void);
 
-    /* Create a new (empty) resource. If successful,
-     * the resource object state is updated appropriately.
+    /* 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
+     * "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).
      */
-    dav_error * (*mkresource)(dav_resource *resource);
+    dav_error * (*vsn_control)(dav_resource *resource,
+                               const char *target);
 
     /* Checkout a resource. If successful, the resource
      * object state is updated appropriately.
@@ -1684,6 +1697,17 @@ struct dav_hooks_vsn
     dav_error * (*checkin)(dav_resource *resource,
                            dav_resource **version_resource);
 
+    /*
+    ** Set the default target of a version selector or
+    ** baseline selector resource.
+    ** The target argument specifies the new target, which
+    ** can be a label, if is_label != 0, or a version URI,
+    ** if is_label == 0.
+    */
+    dav_error * (*set_target)(const dav_resource *resource,
+                             const char *target,
+                              int is_label);
+
     /* Determine whether a non-versioned (or non-existent) resource
      * is versionable. Returns != 0 if resource can be versioned.
      */
@@ -1699,14 +1723,124 @@ struct dav_hooks_vsn
     ** Return the set of reports available at this resource.
     **
     ** An array of report elements should be returned, with an end-marker
-    ** element containing namespace==NULL. The report response will be
-    ** constructed and returned.
-    **
-    ** DAV:available-report should not be returned; the mod_dav core will
-    ** handle that.
+    ** element containing namespace==NULL. The value of the
+    ** DAV:supported-report-set property will be constructed and
+    ** returned.
     */
     dav_error * (*avail_reports)(const dav_resource *resource,
                                  const dav_report_elem **reports);
+
+    /*
+    ** Determine whether a Target-Selector 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.
+    */
+    int (*report_target_selector_allowed)(const ap_xml_doc *doc);
+
+    /*
+    ** Generate a report on a resource. Since a provider is free
+    ** to define its own reports, and the value of request headers
+    ** may affect the interpretation of a report, the request record
+    ** must be passed to this routine.
+    **
+    ** The dav_xml_doc structure contains the parsed report request
+    ** body. The report response is generated into the dav_text_header
+    ** structure.
+    **
+    ** ### shouldn't generate large responses to memory ###
+    */
+    dav_error * (*get_report)(request_rec *r,
+                              const dav_resource *resource,
+                              const ap_xml_doc *doc,
+                              ap_text_header *report);
+
+    /*
+    ** OPTIONAL HOOKS
+    ** The following hooks are optional; if not defined, then the
+    ** corresponding protocol methods will be unsupported.
+    */
+
+    /*
+    ** Add a label to a version. The resource is either a specific
+    ** version, or a version selector, in which case the label should
+    ** be added to the current target of the version selector. The
+    ** version selector cannot be checked out.
+    **
+    ** If replace != 0, any existing label by the same name is
+    ** effectively deleted first. Otherwise, it is an error to
+    ** attempt to add a label which already exists on some version
+    ** of the same history resource.
+    **
+    ** This hook is optional; if not defined, then the LABEL method
+    ** will not be supported. If it is defined, then the remove_label
+    ** hook must be defined also.
+    */
+    dav_error * (*add_label)(const dav_resource *resource,
+                             const char *label,
+                             int replace);
+
+    /*
+    ** Remove a label from a version. The resource is either a specific
+    ** version, or a version selector, in which case the label should
+    ** be added to the current target of the version selector. The
+    ** version selector cannot be checked out.
+    **
+    ** It is an error if no such label exists on the specified version.
+    **
+    ** This hook is optional, but if defined, the add_label hook
+    ** must be defined also.
+    */
+    dav_error * (*remove_label)(const dav_resource *resource,
+                                const char *label);
+
+    /*
+    ** Determine whether a null resource can be created as a workspace.
+    ** The provider may restrict workspaces to certain locations.
+    ** Returns 0 if the resource cannot be a workspace.
+    **
+    ** This hook is optional; if the provider does not support workspaces,
+    ** it should be set to NULL.
+    */
+    int (*can_be_workspace)(const dav_resource *resource);
+
+    /*
+    ** Create a workspace resource. The resource must not already
+    ** exist. Any <DAV:mkworkspace> element is passed to the provider
+    ** in the "doc" structure; it may be empty.
+    **
+    ** If workspace creation is succesful, the state of the resource
+    ** object is updated appropriately.
+    **
+    ** This hook is optional; if the provider does not support workspaces,
+    ** it should be set to NULL.
+    */
+    dav_error * (*make_workspace)(dav_resource *resource,
+                                  ap_xml_doc *doc);
+};
+
+
+/* --------------------------------------------------------------------
+**
+** BINDING FUNCTIONS
+*/
+
+/* binding provider hooks */
+struct dav_hooks_binding {
+
+    /* Determine whether a resource can be the target of a binding.
+     * Returns 0 if the resource cannot be a binding target.
+     */
+    int (*is_bindable)(const dav_resource *resource);
+
+    /* Create a binding to a resource.
+     * The resource argument is the target of the binding;
+     * the binding argument must be a resource which does not already
+     * exist.
+     */
+    dav_error * (*bind_resource)(const dav_resource *resource,
+                                dav_resource *binding);
+
 };
 
 
index 5bca277071428be15d858176f49e5ce40646e41d..ed86b84a46a5aa39de1a8a3988d2f36bd229198b 100644 (file)
@@ -279,6 +279,9 @@ static const char * const dav_core_props[] =
     "lockdiscovery",
     "resourcetype",
     "supportedlock",
+    "supported-method-set",
+    "supported-live-property-set",
+    "supported-report-set",
 
     NULL       /* sentinel */
 };
@@ -288,6 +291,9 @@ enum {
     DAV_PROPID_CORE_lockdiscovery,
     DAV_PROPID_CORE_resourcetype,
     DAV_PROPID_CORE_supportedlock,
+    DAV_PROPID_CORE_supported_method_set,
+    DAV_PROPID_CORE_supported_live_property_set,
+    DAV_PROPID_CORE_supported_report_set,
 
     DAV_PROPID_CORE_UNKNOWN
 };
@@ -372,7 +378,10 @@ static int dav_rw_liveprop(dav_propdb *propdb, dav_elem_private *priv)
        || propid == DAV_PROPID_CORE_getcontenttype
        || propid == DAV_PROPID_CORE_getcontentlanguage
 #endif
-       || propid == DAV_PROPID_CORE_supportedlock) {
+       || propid == DAV_PROPID_CORE_supportedlock
+       || propid == DAV_PROPID_CORE_supported_method_set
+       || propid == DAV_PROPID_CORE_supported_live_property_set
+       || propid == DAV_PROPID_CORE_supported_report_set) {
 
        return 0;
     }
@@ -419,6 +428,7 @@ static dav_error * dav_insert_coreprop(dav_propdb *propdb,
                                       int *inserted)
 {
     const char *value = NULL;
+    dav_error *err;
 
     *inserted = 0;
 
@@ -430,8 +440,13 @@ static dav_error * dav_insert_coreprop(dav_propdb *propdb,
 
     case DAV_PROPID_CORE_resourcetype:
         switch (propdb->resource->type) {
-        case DAV_RESOURCE_TYPE_REGULAR:
         case DAV_RESOURCE_TYPE_VERSION:
+            if (propdb->resource->baselined) {
+               value = "<D:baseline/>";
+                break;
+            }
+            /* fall through */
+        case DAV_RESOURCE_TYPE_REGULAR:
         case DAV_RESOURCE_TYPE_WORKING:
             if (propdb->resource->collection) {
                value = "<D:collection/>";
@@ -451,9 +466,6 @@ static dav_error * dav_insert_coreprop(dav_propdb *propdb,
         case DAV_RESOURCE_TYPE_ACTIVITY:
            value = "<D:activity/>";
             break;
-        case DAV_RESOURCE_TYPE_BASELINE:
-           value = "<D:baseline/>";
-            break;
 
         default:
            /* ### bad juju */
@@ -463,7 +475,6 @@ static dav_error * dav_insert_coreprop(dav_propdb *propdb,
 
     case DAV_PROPID_CORE_lockdiscovery:
         if (propdb->lockdb != NULL) {
-           dav_error *err;
            dav_lock *locks;
 
            if ((err = dav_lock_query(propdb->lockdb, propdb->resource,
@@ -499,6 +510,43 @@ static dav_error * dav_insert_coreprop(dav_propdb *propdb,
         }
        break;
 
+    case DAV_PROPID_CORE_supported_method_set:
+        /* ### leverage code from dav_method_options ### */
+        break;
+
+    case DAV_PROPID_CORE_supported_live_property_set:
+        /* ### insert all live property names ### */
+        break;
+
+    case DAV_PROPID_CORE_supported_report_set:
+        if (propdb->vsn_hooks != NULL) {
+            const dav_report_elem *reports;
+
+            if ((err = (*propdb->vsn_hooks->avail_reports)(propdb->resource, &reports)) != NULL) {
+               return dav_push_error(propdb->r->pool, err->status, 0,
+                                     "DAV:supported-report-set could not be determined "
+                                      "due to a problem fetching the available reports "
+                                      "for this resource.",
+                                     err);
+            }
+
+            if (reports == NULL)
+                break;
+
+            value = "";
+
+            for (; reports->nmspace != NULL; ++reports) {
+                /* Note: we presume reports->namespace is properly XML/URL quoted */
+                char *v = apr_psprintf(propdb->p, "<%s xmlns=\"%s\"/>" DEBUG_CR,
+                                       reports->name, reports->nmspace);
+                /* This isn't very memory-efficient, but there should only be a small
+                 * number of reports
+                 */
+                value = apr_pstrcat(propdb->p, value, v, NULL);
+            }
+        }
+        break;
+
     case DAV_PROPID_CORE_getcontenttype:
        if (propdb->subreq == NULL) {
            dav_do_prop_subreq(propdb);
@@ -1129,6 +1177,15 @@ dav_get_props_result dav_get_allprops(dav_propdb *propdb, int getvals)
     (void)dav_insert_coreprop(propdb,
                              DAV_PROPID_CORE_lockdiscovery, "lockdiscovery",
                              getvals, &hdr, &unused_inserted);
+    (void)dav_insert_coreprop(propdb,
+                             DAV_PROPID_CORE_supported_method_set, "supported-method-set",
+                             getvals, &hdr, &unused_inserted);
+    (void)dav_insert_coreprop(propdb,
+                             DAV_PROPID_CORE_supported_live_property_set, "supported-live-property-set",
+                             getvals, &hdr, &unused_inserted);
+    (void)dav_insert_coreprop(propdb,
+                             DAV_PROPID_CORE_supported_report_set, "supported-report-set",
+                             getvals, &hdr, &unused_inserted);
 
     /* if the resourcetype wasn't stored, then prepare one */
     if (!found_resourcetype) {
index 5af802f7273556ba1d0e736e038ff5c7cfa5564d..c845a6422ee988c9b4059dd550a2c6c3f10661fe 100644 (file)
@@ -1511,39 +1511,8 @@ static const char *strip_white(const char *s, apr_pool_t *pool)
     return s;
 }
 
-#define DAV_WORKSPACE_HDR "Workspace"
 #define DAV_TARGET_SELECTOR_HDR "Target-Selector"
 
-/* see mod_dav.h for docco */
-int dav_get_workspace(request_rec *r, const char **workspace)
-{
-    const char *ws_uri;
-
-    *workspace = NULL;
-    ws_uri = apr_table_get(r->headers_in, DAV_WORKSPACE_HDR);
-
-    if (ws_uri != NULL) {
-       dav_lookup_result lookup;
-
-       /* make the URI server-relative */
-       lookup = dav_lookup_uri(ws_uri, r);
-       if (lookup.rnew == NULL) {
-           if (lookup.err.status == HTTP_BAD_REQUEST) {
-               /* This supplies additional information for the default message. */
-               ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
-                             lookup.err.desc);
-               return HTTP_BAD_REQUEST;
-           }
-
-           return lookup.err.status;
-       }
-
-       *workspace = lookup.rnew->uri;
-    }
-
-    return OK;
-}
-
 /* see mod_dav.h for docco */
 int dav_get_target_selector(request_rec *r,
                             const ap_xml_elem *version,
@@ -1603,28 +1572,18 @@ void dav_add_vary_header(request_rec *in_req,
     /* Only versioning headers require a Vary response header,
      * so only do this check if there is a versioning provider */
     if (vsn_hooks != NULL) {
-       const char *workspace = apr_table_get(in_req->headers_in, DAV_WORKSPACE_HDR);
        const char *target = apr_table_get(in_req->headers_in, DAV_TARGET_SELECTOR_HDR);
        const char *vary = apr_table_get(out_req->headers_out, "Vary");
 
-        /* If Workspace header specified, add it to Vary header */
-       if (workspace != NULL) {
-           if (vary == NULL)
-               vary = DAV_WORKSPACE_HDR;
-           else
-               vary = apr_pstrcat(out_req->pool, vary, "," DAV_WORKSPACE_HDR, NULL);
-       }
-
         /* If Target-Selector specified, add it to the Vary header */
        if (target != NULL) {
            if (vary == NULL)
                vary = DAV_TARGET_SELECTOR_HDR;
            else
                vary = apr_pstrcat(out_req->pool, vary, "," DAV_TARGET_SELECTOR_HDR, NULL);
-       }
 
-       if (workspace != NULL || target != NULL)
            apr_table_setn(out_req->headers_out, "Vary", vary);
+       }
     }
 }
 
@@ -1641,10 +1600,6 @@ dav_error *dav_ensure_resource_writable(request_rec *r,
     /* Initialize results */
     memset(av_info, 0, sizeof(*av_info));
 
-    if (!parent_only) {
-        av_info->resource_created = !resource->exists;
-    }
-
     /* check parent resource if requested or if resource must be created */
     if (!resource->exists || parent_only) {
        dav_resource *parent = (*resource->hooks->get_parent_resource)(resource);
@@ -1702,7 +1657,7 @@ dav_error *dav_ensure_resource_writable(request_rec *r,
 
        /* if not just checking parent, create new child resource */
         if (!parent_only) {
-           if ((err = (*vsn_hooks->mkresource)(resource)) != NULL) {
+           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));