/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* ** DAV repository-independent lock functions */ #include "apr.h" #include "apr_strings.h" #if APR_HAVE_STDIO_H #include /* for sprintf() */ #endif #include "mod_dav.h" #include "http_log.h" #include "http_config.h" #include "http_protocol.h" #include "http_core.h" /* --------------------------------------------------------------- ** ** Property-related lock functions ** */ /* ** dav_lock_get_activelock: Returns a containing ** an activelock element for every item in the lock_discovery tree */ DAV_DECLARE(const char *) dav_lock_get_activelock(request_rec *r, dav_lock *lock, dav_buffer *pbuf) { dav_lock *lock_scan; const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r); int count = 0; dav_buffer work_buf = { 0 }; apr_pool_t *p = r->pool; /* If no locks or no lock provider, there are no locks */ if (lock == NULL || hooks == NULL) { /* ** Since resourcediscovery is defined with (activelock)*, ** shouldn't be necessary for an empty lock. */ return ""; } /* ** Note: it could be interesting to sum the lengths of the owners ** and locktokens during this loop. However, the buffer ** mechanism provides some rough padding so that we don't ** really need to have an exact size. Further, constructing ** locktoken strings could be relatively expensive. */ for (lock_scan = lock; lock_scan != NULL; lock_scan = lock_scan->next) count++; /* if a buffer was not provided, then use an internal buffer */ if (pbuf == NULL) pbuf = &work_buf; /* reset the length before we start appending stuff */ pbuf->cur_len = 0; /* prep the buffer with a "good" size */ dav_check_bufsize(p, pbuf, count * 300); for (; lock != NULL; lock = lock->next) { char tmp[100]; #if DAV_DEBUG if (lock->rectype == DAV_LOCKREC_INDIRECT_PARTIAL) { /* ### crap. design error */ dav_buffer_append(p, pbuf, "DESIGN ERROR: attempted to product an " "activelock element from a partial, indirect " "lock record. Creating an XML parsing error " "to ease detection of this situation: <"); } #endif dav_buffer_append(p, pbuf, "" DEBUG_CR ""); switch (lock->type) { case DAV_LOCKTYPE_WRITE: dav_buffer_append(p, pbuf, ""); break; default: /* ### internal error. log something? */ break; } dav_buffer_append(p, pbuf, "" DEBUG_CR ""); switch (lock->scope) { case DAV_LOCKSCOPE_EXCLUSIVE: dav_buffer_append(p, pbuf, ""); break; case DAV_LOCKSCOPE_SHARED: dav_buffer_append(p, pbuf, ""); break; default: /* ### internal error. log something? */ break; } dav_buffer_append(p, pbuf, "" DEBUG_CR); sprintf(tmp, "%s" DEBUG_CR, lock->depth == DAV_INFINITY ? "infinity" : "0"); dav_buffer_append(p, pbuf, tmp); if (lock->owner) { /* ** This contains a complete, self-contained element, ** with namespace declarations and xml:lang handling. Just drop ** it in. */ dav_buffer_append(p, pbuf, lock->owner); } dav_buffer_append(p, pbuf, ""); if (lock->timeout == DAV_TIMEOUT_INFINITE) { dav_buffer_append(p, pbuf, "Infinite"); } else { time_t now = time(NULL); sprintf(tmp, "Second-%lu", (long unsigned int)(lock->timeout - now)); dav_buffer_append(p, pbuf, tmp); } dav_buffer_append(p, pbuf, "" DEBUG_CR "" DEBUG_CR ""); dav_buffer_append(p, pbuf, (*hooks->format_locktoken)(p, lock->locktoken)); dav_buffer_append(p, pbuf, "" DEBUG_CR "" DEBUG_CR "" DEBUG_CR); } return pbuf->buf; } /* ** dav_lock_parse_lockinfo: Validates the given xml_doc to contain a ** lockinfo XML element, then populates a dav_lock structure ** with its contents. */ DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r, const dav_resource *resource, dav_lockdb *lockdb, const apr_xml_doc *doc, dav_lock **lock_request) { apr_pool_t *p = r->pool; dav_error *err; apr_xml_elem *child; dav_lock *lock; if (!dav_validate_root(doc, "lockinfo")) { return dav_new_error(p, HTTP_BAD_REQUEST, 0, "The request body contains an unexpected " "XML root element."); } if ((err = (*lockdb->hooks->create_lock)(lockdb, resource, &lock)) != NULL) { return dav_push_error(p, err->status, 0, "Could not parse the lockinfo due to an " "internal problem creating a lock structure.", err); } lock->depth = dav_get_depth(r, DAV_INFINITY); if (lock->depth == -1) { return dav_new_error(p, HTTP_BAD_REQUEST, 0, "An invalid Depth header was specified."); } lock->timeout = dav_get_timeout(r); /* Parse elements in the XML body */ for (child = doc->root->first_child; child; child = child->next) { if (strcmp(child->name, "locktype") == 0 && child->first_child && lock->type == DAV_LOCKTYPE_UNKNOWN) { if (strcmp(child->first_child->name, "write") == 0) { lock->type = DAV_LOCKTYPE_WRITE; continue; } } if (strcmp(child->name, "lockscope") == 0 && child->first_child && lock->scope == DAV_LOCKSCOPE_UNKNOWN) { if (strcmp(child->first_child->name, "exclusive") == 0) lock->scope = DAV_LOCKSCOPE_EXCLUSIVE; else if (strcmp(child->first_child->name, "shared") == 0) lock->scope = DAV_LOCKSCOPE_SHARED; if (lock->scope != DAV_LOCKSCOPE_UNKNOWN) continue; } if (strcmp(child->name, "owner") == 0 && lock->owner == NULL) { const char *text; /* quote all the values in the element */ apr_xml_quote_elem(p, child); /* ** Store a full element with namespace definitions ** and an xml:lang definition, if applicable. */ apr_xml_to_text(p, child, APR_XML_X2T_FULL_NS_LANG, doc->namespaces, NULL, &text, NULL); lock->owner = text; continue; } return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, apr_psprintf(p, "The server cannot satisfy the " "LOCK request due to an unknown XML " "element (\"%s\") within the " "DAV:lockinfo element.", child->name)); } *lock_request = lock; return NULL; } /* --------------------------------------------------------------- ** ** General lock functions ** */ /* dav_lock_walker: Walker callback function to record indirect locks */ static dav_error * dav_lock_walker(dav_walk_resource *wres, int calltype) { dav_walker_ctx *ctx = wres->walk_ctx; dav_error *err; /* We don't want to set indirects on the target */ if ((*wres->resource->hooks->is_same_resource)(wres->resource, ctx->w.root)) return NULL; if ((err = (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb, wres->resource, 1, ctx->lock)) != NULL) { if (ap_is_HTTP_SERVER_ERROR(err->status)) { /* ### add a higher-level description? */ return err; } /* add to the multistatus response */ dav_add_response(wres, err->status, NULL); /* ** ### actually, this is probably wrong: we want to fail the whole ** ### LOCK process if something goes bad. maybe the caller should ** ### do a dav_unlock() (e.g. a rollback) if any errors occurred. */ } return NULL; } /* ** dav_add_lock: Add a direct lock for resource, and indirect locks for ** all children, bounded by depth. ** ### assume request only contains one lock */ DAV_DECLARE(dav_error *) dav_add_lock(request_rec *r, const dav_resource *resource, dav_lockdb *lockdb, dav_lock *lock, dav_response **response) { dav_error *err; int depth = lock->depth; *response = NULL; /* Requested lock can be: * Depth: 0 for null resource, existing resource, or existing collection * Depth: Inf for existing collection */ /* ** 2518 9.2 says to ignore depth if target is not a collection (it has ** no internal children); pretend the client gave the correct depth. */ if (!resource->collection) { depth = 0; } /* In all cases, first add direct entry in lockdb */ /* ** Append the new (direct) lock to the resource's existing locks. ** ** Note: this also handles locknull resources */ if ((err = (*lockdb->hooks->append_locks)(lockdb, resource, 0, lock)) != NULL) { /* ### maybe add a higher-level description */ return err; } if (depth > 0) { /* Walk existing collection and set indirect locks */ dav_walker_ctx ctx = { { 0 } }; dav_response *multi_status; ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH; ctx.w.func = dav_lock_walker; ctx.w.walk_ctx = &ctx; ctx.w.pool = r->pool; ctx.w.root = resource; ctx.w.lockdb = lockdb; ctx.r = r; ctx.lock = lock; err = (*resource->hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status); if (err != NULL) { /* implies a 5xx status code occurred. screw the multistatus */ return err; } if (multi_status != NULL) { /* manufacture a 207 error for the multistatus response */ *response = multi_status; return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, "Error(s) occurred on resources during the " "addition of a depth lock."); } } return NULL; } /* ** dav_lock_query: Opens the lock database. Returns a linked list of ** dav_lock structures for all direct locks on path. */ DAV_DECLARE(dav_error*) dav_lock_query(dav_lockdb *lockdb, const dav_resource *resource, dav_lock **locks) { /* If no lock database, return empty result */ if (lockdb == NULL) { *locks = NULL; return NULL; } /* ### insert a higher-level description? */ return (*lockdb->hooks->get_locks)(lockdb, resource, DAV_GETLOCKS_RESOLVED, locks); } /* dav_unlock_walker: Walker callback function to remove indirect locks */ 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) { /* ### should we stop or return a multistatus? looks like STOP */ /* ### add a higher-level description? */ return err; } return NULL; } /* ** dav_get_direct_resource: ** ** Find a lock on the specified resource, then return the resource the ** lock was applied to (in other words, given a (possibly) indirect lock, ** return the direct lock's corresponding resource). ** ** If the lock is an indirect lock, this usually means traversing up the ** namespace [repository] hierarchy. Note that some lock providers may be ** able to return this information with a traversal. */ static dav_error * dav_get_direct_resource(apr_pool_t *p, dav_lockdb *lockdb, const dav_locktoken *locktoken, const dav_resource *resource, const dav_resource **direct_resource) { if (lockdb->hooks->lookup_resource != NULL) { return (*lockdb->hooks->lookup_resource)(lockdb, locktoken, resource, direct_resource); } *direct_resource = NULL; /* Find the top of this lock- * If r->filename's direct locks include locktoken, use r->filename. * If r->filename's indirect locks include locktoken, retry r->filename/.. * Else fail. */ while (resource != NULL) { dav_error *err; dav_lock *lock; dav_resource *parent; /* ** Find the lock specified by on . If it is ** an indirect lock, then partial results are okay. We're just ** trying to find the thing and know whether it is a direct or ** an indirect lock. */ if ((err = (*lockdb->hooks->find_lock)(lockdb, resource, locktoken, 1, &lock)) != NULL) { /* ### add a higher-level desc? */ return err; } /* not found! that's an error. */ if (lock == NULL) { return dav_new_error(p, HTTP_BAD_REQUEST, 0, "The specified locktoken does not correspond " "to an existing lock on this resource."); } if (lock->rectype == DAV_LOCKREC_DIRECT) { /* we found the direct lock. return this resource. */ *direct_resource = resource; return NULL; } /* the lock was indirect. move up a level in the URL namespace */ if ((err = (*resource->hooks->get_parent_resource)(resource, &parent)) != NULL) { /* ### add a higher-level desc? */ return err; } resource = parent; } return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "The lock database is corrupt. A direct lock could " "not be found for the corresponding indirect lock " "on this resource."); } /* ** dav_unlock: Removes all direct and indirect locks for r->filename, ** with given locktoken. If locktoken == null_locktoken, all locks ** are removed. If r->filename represents an indirect lock, ** we must unlock the appropriate direct lock. ** Returns OK or appropriate HTTP_* response and logs any errors. ** ** ### We've already crawled the tree to ensure everything was locked ** by us; there should be no need to incorporate a rollback. */ DAV_DECLARE(int) dav_unlock(request_rec *r, const dav_resource *resource, const dav_locktoken *locktoken) { int result; dav_lockdb *lockdb; 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. */ if (hooks == NULL) { return OK; } /* 2518 requires the entire lock to be removed if resource/locktoken * point to an indirect lock. We need resource of the _direct_ * lock in order to walk down the tree and remove the locks. So, * If locktoken != null_locktoken, * Walk up the resource hierarchy until we see a direct lock. * Or, we could get the direct lock's db/key, pick out the URL * and do a subrequest. I think walking up is faster and will work * all the time. * Else * Just start removing all locks at and below resource. */ if ((err = (*hooks->open_lockdb)(r, 0, 1, &lockdb)) != NULL) { /* ### return err! maybe add a higher-level desc */ /* ### map result to something nice; log an error */ return HTTP_INTERNAL_SERVER_ERROR; } if (locktoken != NULL && (err = dav_get_direct_resource(r->pool, lockdb, locktoken, resource, &lock_resource)) != NULL) { /* ### add a higher-level desc? */ /* ### should return err! */ return err->status; } /* At this point, lock_resource/locktoken refers to a direct lock (key), ie * the root of a depth > 0 lock, or locktoken is null. */ 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; err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status); /* ### fix this! */ /* ### do something with multi_status */ result = err == NULL ? OK : err->status; (*hooks->close_lockdb)(lockdb); return result; } /* dav_inherit_walker: Walker callback function to inherit locks */ static dav_error * dav_inherit_walker(dav_walk_resource *wres, int calltype) { dav_walker_ctx *ctx = wres->walk_ctx; if (ctx->skip_root && (*wres->resource->hooks->is_same_resource)(wres->resource, ctx->w.root)) { return NULL; } /* ### maybe add a higher-level desc */ return (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb, wres->resource, 1, ctx->lock); } /* ** dav_inherit_locks: When a resource or collection is added to a collection, ** locks on the collection should be inherited to the resource/collection. ** (MOVE, MKCOL, etc) Here we propagate any direct or indirect locks from ** parent of resource to resource and below. */ static dav_error * dav_inherit_locks(request_rec *r, dav_lockdb *lockdb, const dav_resource *resource, int use_parent) { dav_error *err; const dav_resource *which_resource; dav_lock *locks; dav_lock *scan; dav_lock *prev; dav_walker_ctx ctx = { { 0 } }; const dav_hooks_repository *repos_hooks = resource->hooks; dav_response *multi_status; if (use_parent) { dav_resource *parent; if ((err = (*repos_hooks->get_parent_resource)(resource, &parent)) != NULL) { /* ### add a higher-level desc? */ return err; } if (parent == NULL) { /* ### map result to something nice; log an error */ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "Could not fetch parent resource. Unable to " "inherit locks from the parent and apply " "them to this resource."); } which_resource = parent; } else { which_resource = resource; } if ((err = (*lockdb->hooks->get_locks)(lockdb, which_resource, DAV_GETLOCKS_PARTIAL, &locks)) != NULL) { /* ### maybe add a higher-level desc */ return err; } if (locks == NULL) { /* No locks to propagate, just return */ return NULL; } /* ** (1) Copy all indirect locks from our parent; ** (2) Create indirect locks for the depth infinity, direct locks ** in our parent. ** ** The append_locks call in the walker callback will do the indirect ** conversion, but we need to remove any direct locks that are NOT ** depth "infinity". */ for (scan = locks, prev = NULL; scan != NULL; prev = scan, scan = scan->next) { if (scan->rectype == DAV_LOCKREC_DIRECT && scan->depth != DAV_INFINITY) { if (prev == NULL) locks = scan->next; else prev->next = scan->next; } } /* has all our new locks. Walk down and propagate them. */ ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL; ctx.w.func = dav_inherit_walker; ctx.w.walk_ctx = &ctx; ctx.w.pool = r->pool; ctx.w.root = resource; ctx.w.lockdb = lockdb; ctx.r = r; ctx.lock = locks; ctx.skip_root = !use_parent; /* ### do something with multi_status */ return (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status); } /* --------------------------------------------------------------- ** ** Functions dealing with lock-null resources ** */ /* ** dav_get_resource_state: Returns the state of the resource ** r->filename: DAV_RESOURCE_NULL, DAV_RESOURCE_LOCK_NULL, ** or DAV_RESOURCE_EXIST. ** ** Returns DAV_RESOURCE_ERROR if an error occurs. */ DAV_DECLARE(int) dav_get_resource_state(request_rec *r, const dav_resource *resource) { const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r); if (resource->exists) return DAV_RESOURCE_EXISTS; if (hooks != NULL) { dav_error *err; dav_lockdb *lockdb; int locks_present; /* ** A locknull resource has the form: ** ** known-dir "/" locknull-file ** ** It would be nice to look into to verify this form, ** but it does not have enough information for us. Instead, we ** can look at the path_info. If the form does not match, then ** there is no way we could have a locknull resource -- it must ** be a plain, null resource. ** ** Apache sets r->filename to known-dir/unknown-file and r->path_info ** to "" for the "proper" case. If anything is in path_info, then ** it can't be a locknull resource. ** ** ### I bet this path_info hack doesn't work for repositories. ** ### Need input from repository implementors! What kind of ** ### restructure do we need? New provider APIs? */ if (r->path_info != NULL && *r->path_info != '\0') { return DAV_RESOURCE_NULL; } if ((err = (*hooks->open_lockdb)(r, 1, 1, &lockdb)) == NULL) { /* note that we might see some expired locks... *shrug* */ err = (*hooks->has_locks)(lockdb, resource, &locks_present); (*hooks->close_lockdb)(lockdb); } if (err != NULL) { /* ### don't log an error. return err. add higher-level desc. */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to query lock-null status for %s", r->filename); return DAV_RESOURCE_ERROR; } if (locks_present) return DAV_RESOURCE_LOCK_NULL; } return DAV_RESOURCE_NULL; } DAV_DECLARE(dav_error *) dav_notify_created(request_rec *r, dav_lockdb *lockdb, const dav_resource *resource, int resource_state, int depth) { dav_error *err; if (resource_state == DAV_RESOURCE_LOCK_NULL) { /* ** The resource is no longer a locknull resource. This will remove ** the special marker. ** ** Note that a locknull resource has already inherited all of the ** locks from the parent. We do not need to call dav_inherit_locks. ** ** NOTE: some lock providers record locks for locknull resources using ** a different key than for regular resources. this will shift ** the lock information between the two key types. */ (void)(*lockdb->hooks->remove_locknull_state)(lockdb, resource); /* ** There are resources under this one, which are new. We must ** propagate the locks down to the new resources. */ if (depth > 0 && (err = dav_inherit_locks(r, lockdb, resource, 0)) != NULL) { /* ### add a higher level desc? */ return err; } } else if (resource_state == DAV_RESOURCE_NULL) { /* ### should pass depth to dav_inherit_locks so that it can ** ### optimize for the depth==0 case. */ /* this resource should inherit locks from its parent */ if ((err = dav_inherit_locks(r, lockdb, resource, 1)) != NULL) { err = dav_push_error(r->pool, err->status, 0, "The resource was created successfully, but " "there was a problem inheriting locks from " "the parent resource.", err); return err; } } /* else the resource already exists and its locks are correct. */ return NULL; }