| /* ==================================================================== |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2000-2002 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Apache" and "Apache Software Foundation" must |
| * not be used to endorse or promote products derived from this |
| * software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * nor may "Apache" appear in their name, without prior written |
| * permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| /* |
| ** DAV repository-independent lock functions |
| */ |
| |
| #include "apr.h" |
| #include "apr_strings.h" |
| |
| #if APR_HAVE_STDIO_H |
| #include <stdio.h> /* 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 <lockdiscovery> containing |
| ** an activelock element for every item in the lock_discovery tree |
| */ |
| 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)*, |
| ** <D: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, "<D:activelock>" DEBUG_CR "<D:locktype>"); |
| switch (lock->type) { |
| case DAV_LOCKTYPE_WRITE: |
| dav_buffer_append(p, pbuf, "<D:write/>"); |
| break; |
| default: |
| /* ### internal error. log something? */ |
| break; |
| } |
| dav_buffer_append(p, pbuf, "</D:locktype>" DEBUG_CR "<D:lockscope>"); |
| switch (lock->scope) { |
| case DAV_LOCKSCOPE_EXCLUSIVE: |
| dav_buffer_append(p, pbuf, "<D:exclusive/>"); |
| break; |
| case DAV_LOCKSCOPE_SHARED: |
| dav_buffer_append(p, pbuf, "<D:shared/>"); |
| break; |
| default: |
| /* ### internal error. log something? */ |
| break; |
| } |
| dav_buffer_append(p, pbuf, "</D:lockscope>" DEBUG_CR); |
| sprintf(tmp, "<D:depth>%s</D:depth>" DEBUG_CR, |
| lock->depth == DAV_INFINITY ? "infinity" : "0"); |
| dav_buffer_append(p, pbuf, tmp); |
| |
| if (lock->owner) { |
| /* |
| ** This contains a complete, self-contained <DAV:owner> element, |
| ** with namespace declarations and xml:lang handling. Just drop |
| ** it in. |
| */ |
| dav_buffer_append(p, pbuf, lock->owner); |
| } |
| |
| dav_buffer_append(p, pbuf, "<D:timeout>"); |
| 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, |
| "</D:timeout>" DEBUG_CR |
| "<D:locktoken>" DEBUG_CR |
| "<D:href>"); |
| dav_buffer_append(p, pbuf, |
| (*hooks->format_locktoken)(p, lock->locktoken)); |
| dav_buffer_append(p, pbuf, |
| "</D:href>" DEBUG_CR |
| "</D:locktoken>" DEBUG_CR |
| "</D:activelock>" 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_error * dav_lock_parse_lockinfo(request_rec *r, |
| const dav_resource *resource, |
| dav_lockdb *lockdb, |
| const ap_xml_doc *doc, |
| dav_lock **lock_request) |
| { |
| apr_pool_t *p = r->pool; |
| dav_error *err; |
| ap_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 <DAV:owner> element */ |
| ap_xml_quote_elem(p, child); |
| |
| /* |
| ** Store a full <DAV:owner> element with namespace definitions |
| ** and an xml:lang definition, if applicable. |
| */ |
| ap_xml_to_text(p, child, AP_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_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 <locktoken> on <resource>. 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. |
| */ |
| 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; |
| } |
| } |
| |
| /* <locks> 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. |
| */ |
| 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 <resource> 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 | APLOG_NOERRNO, 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_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; |
| } |