in modules/dav/main/mod_dav.c [2855:3282]
static int dav_method_copymove(request_rec *r, int is_move)
{
dav_resource *resource;
dav_resource *resnew;
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;
dav_error *err2;
dav_error *err3;
dav_response *multi_response;
dav_lookup_result lookup;
int is_dir;
int overwrite;
int depth;
int result;
dav_lockdb *lockdb;
int replace_dest;
int resnew_state;
int rc;
/* Ask repository module to resolve the resource */
err = dav_get_resource(r, !is_move /* label_allowed */,
0 /* use_checked_in */, &resource);
if (err != NULL)
return dav_handle_err(r, err, NULL);
/* check for any method preconditions */
if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED
&& err) {
return dav_handle_err(r, err, NULL);
}
if (!resource->exists) {
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
/* 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.",
ap_escape_html(r->pool, r->uri));
return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
}
/* get the destination URI */
dest = apr_table_get(r->headers_in, "Destination");
if (dest == NULL) {
/* Look in headers provided by Netscape's Roaming Profiles */
const char *nscp_host = apr_table_get(r->headers_in, "Host");
const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
if (nscp_host != NULL && nscp_path != NULL)
dest = apr_pstrcat(r->pool, "http://", nscp_host, nscp_path, NULL);
}
if (dest == NULL) {
/* This supplies additional information for the default message. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00591)
"The request is missing a Destination header.");
return HTTP_BAD_REQUEST;
}
lookup = dav_lookup_uri(dest, r, 1 /* must_be_absolute */);
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, 0, r, APLOGNO(00592)
"%s", lookup.err.desc);
return HTTP_BAD_REQUEST;
}
/* ### 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) {
const char *auth = apr_table_get(lookup.rnew->err_headers_out,
"WWW-Authenticate");
if (lookup.rnew->status == HTTP_UNAUTHORIZED && auth != NULL) {
/* propagate the WWW-Authorization header up from the
* subreq so the client sees it. */
apr_table_setn(r->err_headers_out, "WWW-Authenticate",
apr_pstrdup(r->pool, auth));
}
/* ### how best to report this... */
return dav_error_response(r, lookup.rnew->status,
"Destination URI had an error.");
}
/* Resolve destination resource */
err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
0 /* use_checked_in */, &resnew);
if (err != NULL)
return dav_handle_err(r, err, NULL);
/* check for any method preconditions */
if (dav_run_method_precondition(r, resource, resnew, NULL, &err) != DECLINED
&& err) {
return dav_handle_err(r, err, NULL);
}
/* are the two resources handled by the same repository? */
if (resource->hooks != resnew->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. "
"MOVE or COPY 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 (resnew->exists && !overwrite) {
/* Supply some text for the error response body. */
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, resnew)) {
/* Supply some text for the error response body. */
return dav_error_response(r, HTTP_FORBIDDEN,
"Source and Destination URIs are the same.");
}
is_dir = resource->collection;
/* get and parse the Depth header value. "0" and "infinity" are legal. */
if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
/* dav_get_depth() supplies additional information for the
* default message. */
return HTTP_BAD_REQUEST;
}
if (depth == 1) {
/* This supplies additional information for the default message. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00593)
"Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
return HTTP_BAD_REQUEST;
}
if (is_move && is_dir && depth != DAV_INFINITY) {
/* This supplies additional information for the default message. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00594)
"Depth must be \"infinity\" when moving a collection.");
return HTTP_BAD_REQUEST;
}
/*
* Check If-Headers and existing locks for each resource in the source.
* We will return a 424 response with a DAV:multistatus body.
* The multistatus responses will contain the information about any
* resource that fails the validation.
*
* We check the parent resource, too, if this is a MOVE. Moving the
* resource effectively removes it from the parent collection, so we
* must ensure that we have met the appropriate conditions.
*
* If a problem occurs with the Request-URI itself, then a plain error
* (rather than a multistatus) will be returned.
*/
if ((err = dav_validate_request(r, resource, depth, NULL,
&multi_response,
(is_move ? DAV_VALIDATE_PARENT
: DAV_VALIDATE_RESOURCE
| DAV_VALIDATE_NO_MODIFY)
| DAV_VALIDATE_USE_424,
NULL)) != NULL) {
err = dav_push_error(r->pool, err->status, 0,
apr_psprintf(r->pool,
"Could not %s %s due to a failed "
"precondition on the source "
"(e.g. locks).",
is_move ? "MOVE" : "COPY",
ap_escape_html(r->pool, r->uri)),
err);
return dav_handle_err(r, err, multi_response);
}
/*
* 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, resnew, 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 MOVE/COPY %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);
}
if (is_dir
&& depth == DAV_INFINITY
&& (*resource->hooks->is_parent_resource)(resource, resnew)) {
/* Supply some text for the error response body. */
return dav_error_response(r, HTTP_FORBIDDEN,
"Source collection contains the "
"Destination.");
}
if (is_dir
&& (*resnew->hooks->is_parent_resource)(resnew, 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 MOVE/COPY, as that would
* delete the Source.
*/
/* Supply some text for the error response body. */
return dav_error_response(r, HTTP_FORBIDDEN,
"Destination collection contains the Source "
"and Overwrite has been specified.");
}
/* ### for now, we don't need anything in the body */
if ((result = ap_discard_request_body(r)) != OK) {
return result;
}
if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
/* ### add a higher-level description? */
return dav_handle_err(r, err, NULL);
}
/* remove any locks from the old resources */
/*
* ### this is Yet Another Traversal. if we do a rename(), then we
* ### really don't have to do this in some cases since the inode
* ### values will remain constant across the move. but we can't
* ### know that fact from outside the provider :-(
*
* ### note that we now have a problem atomicity in the move/copy
* ### since a failure after this would have removed locks (technically,
* ### this is okay to do, but really...)
*/
if (is_move && lockdb != NULL) {
/* ### this is wrong! it blasts direct locks on parent resources */
/* ### pass lockdb! */
(void)dav_unlock(r, resource, NULL);
}
/* if this is a move, then the source parent collection will be modified */
if (is_move) {
if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
&src_av_info)) != NULL) {
if (lockdb != NULL)
(*lockdb->hooks->close_lockdb)(lockdb);
/* ### add a higher-level description? */
return dav_handle_err(r, err, NULL);
}
}
/*
* 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);
/* 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 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;
}
/* If destination is being replaced, remove it first
* (we know Ovewrite must be TRUE). Then try to copy/move the resource.
*/
if (replace_dest)
err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
if (err == NULL) {
if (is_move)
err = (*resource->hooks->move_resource)(resource, resnew,
&multi_response);
else
err = (*resource->hooks->copy_resource)(resource, resnew, depth,
&multi_response);
}
/* 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_auto_checkin(r, NULL, err != NULL /* undo if error */,
0 /*unlock*/, &src_av_info);
}
else
err3 = NULL;
/* check for error from remove/copy/move operations */
if (err != NULL) {
if (lockdb != NULL)
(*lockdb->hooks->close_lockdb)(lockdb);
err = dav_push_error(r->pool, err->status, 0,
apr_psprintf(r->pool,
"Could not MOVE/COPY %s.",
ap_escape_html(r->pool, r->uri)),
err);
return dav_handle_err(r, err, multi_response);
}
/* 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 automatically checking in the "
"source parent collection.",
err2);
dav_log_err(r, err, APLOG_WARNING);
}
if (err3 != NULL) {
/* just log a warning */
err = dav_push_error(r->pool, err3->status, 0,
"The MOVE/COPY was successful, but there was a "
"problem automatically checking in the "
"destination or its parent collection.",
err3);
dav_log_err(r, err, APLOG_WARNING);
}
/* propagate any indirect locks at the target */
if (lockdb != NULL) {
/* notify lock system that we have created/replaced a resource */
err = dav_notify_created(r, lockdb, resnew, resnew_state, depth);
(*lockdb->hooks->close_lockdb)(lockdb);
if (err != NULL) {
/* The move/copy was successful, but the locking failed. */
err = dav_push_error(r->pool, err->status, 0,
"The MOVE/COPY was successful, but there "
"was a problem updating the lock "
"information.",
err);
return dav_handle_err(r, err, NULL);
}
}
/* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
rc = dav_created(r, lookup.rnew->uri, "Destination",
resnew_state == DAV_RESOURCE_EXISTS);
#ifdef APR_XML_X2T_PARSED
if (resource->acls) {
resource->acls->acl_post_processing(r, resource, FALSE);
resource->acls->acl_post_processing(r, resnew,
r->status == HTTP_CREATED);
}
#endif
return rc;
}