1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
56 ** DAV repository-independent lock functions
61 #include "http_config.h"
62 #include "http_protocol.h"
63 #include "http_core.h"
64 #include "apr_strings.h"
68 /* ---------------------------------------------------------------
70 ** Property-related lock functions
75 ** dav_lock_get_activelock: Returns a <lockdiscovery> containing
76 ** an activelock element for every item in the lock_discovery tree
78 const char *dav_lock_get_activelock(request_rec *r, dav_lock *lock,
82 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
84 dav_buffer work_buf = { 0 };
85 apr_pool_t *p = r->pool;
87 /* If no locks or no lock provider, there are no locks */
88 if (lock == NULL || hooks == NULL) {
90 ** Since resourcediscovery is defined with (activelock)*,
91 ** <D:activelock/> shouldn't be necessary for an empty lock.
97 ** Note: it could be interesting to sum the lengths of the owners
98 ** and locktokens during this loop. However, the buffer
99 ** mechanism provides some rough padding so that we don't
100 ** really need to have an exact size. Further, constructing
101 ** locktoken strings could be relatively expensive.
103 for (lock_scan = lock; lock_scan != NULL; lock_scan = lock_scan->next)
106 /* if a buffer was not provided, then use an internal buffer */
110 /* reset the length before we start appending stuff */
113 /* prep the buffer with a "good" size */
114 dav_check_bufsize(p, pbuf, count * 300);
116 for (; lock != NULL; lock = lock->next) {
120 if (lock->rectype == DAV_LOCKREC_INDIRECT_PARTIAL) {
121 /* ### crap. design error */
122 dav_buffer_append(p, pbuf,
123 "DESIGN ERROR: attempted to product an "
124 "activelock element from a partial, indirect "
125 "lock record. Creating an XML parsing error "
126 "to ease detection of this situation: <");
130 dav_buffer_append(p, pbuf, "<D:activelock>" DEBUG_CR "<D:locktype>");
131 switch (lock->type) {
132 case DAV_LOCKTYPE_WRITE:
133 dav_buffer_append(p, pbuf, "<D:write/>");
136 /* ### internal error. log something? */
139 dav_buffer_append(p, pbuf, "</D:locktype>" DEBUG_CR "<D:lockscope>");
140 switch (lock->scope) {
141 case DAV_LOCKSCOPE_EXCLUSIVE:
142 dav_buffer_append(p, pbuf, "<D:exclusive/>");
144 case DAV_LOCKSCOPE_SHARED:
145 dav_buffer_append(p, pbuf, "<D:shared/>");
148 /* ### internal error. log something? */
151 dav_buffer_append(p, pbuf, "</D:lockscope>" DEBUG_CR);
152 sprintf(tmp, "<D:depth>%s</D:depth>" DEBUG_CR,
153 lock->depth == DAV_INFINITY ? "infinity" : "0");
154 dav_buffer_append(p, pbuf, tmp);
158 ** This contains a complete, self-contained <DAV:owner> element,
159 ** with namespace declarations and xml:lang handling. Just drop
162 dav_buffer_append(p, pbuf, lock->owner);
165 dav_buffer_append(p, pbuf, "<D:timeout>");
166 if (lock->timeout == DAV_TIMEOUT_INFINITE) {
167 dav_buffer_append(p, pbuf, "Infinite");
170 time_t now = time(NULL);
171 sprintf(tmp, "Second-%lu", lock->timeout - now);
172 dav_buffer_append(p, pbuf, tmp);
175 dav_buffer_append(p, pbuf,
176 "</D:timeout>" DEBUG_CR
177 "<D:locktoken>" DEBUG_CR
179 dav_buffer_append(p, pbuf,
180 (*hooks->format_locktoken)(p, lock->locktoken));
181 dav_buffer_append(p, pbuf,
183 "</D:locktoken>" DEBUG_CR
184 "</D:activelock>" DEBUG_CR);
191 ** dav_lock_parse_lockinfo: Validates the given xml_doc to contain a
192 ** lockinfo XML element, then populates a dav_lock structure
193 ** with its contents.
195 dav_error * dav_lock_parse_lockinfo(request_rec *r,
196 const dav_resource *resource,
198 const ap_xml_doc *doc,
199 dav_lock **lock_request)
201 apr_pool_t *p = r->pool;
206 if (!dav_validate_root(doc, "lockinfo")) {
207 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
208 "The request body contains an unexpected "
209 "XML root element.");
212 if ((err = (*lockdb->hooks->create_lock)(lockdb, resource,
214 return dav_push_error(p, err->status, 0,
215 "Could not parse the lockinfo due to an "
216 "internal problem creating a lock structure.",
220 lock->depth = dav_get_depth(r, DAV_INFINITY);
221 if (lock->depth == -1) {
222 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
223 "An invalid Depth header was specified.");
225 lock->timeout = dav_get_timeout(r);
227 /* Parse elements in the XML body */
228 for (child = doc->root->first_child; child; child = child->next) {
229 if (strcmp(child->name, "locktype") == 0
230 && child->first_child
231 && lock->type == DAV_LOCKTYPE_UNKNOWN) {
232 if (strcmp(child->first_child->name, "write") == 0) {
233 lock->type = DAV_LOCKTYPE_WRITE;
237 if (strcmp(child->name, "lockscope") == 0
238 && child->first_child
239 && lock->scope == DAV_LOCKSCOPE_UNKNOWN) {
240 if (strcmp(child->first_child->name, "exclusive") == 0)
241 lock->scope = DAV_LOCKSCOPE_EXCLUSIVE;
242 else if (strcmp(child->first_child->name, "shared") == 0)
243 lock->scope = DAV_LOCKSCOPE_SHARED;
244 if (lock->scope != DAV_LOCKSCOPE_UNKNOWN)
248 if (strcmp(child->name, "owner") == 0 && lock->owner == NULL) {
251 /* quote all the values in the <DAV:owner> element */
252 ap_xml_quote_elem(p, child);
255 ** Store a full <DAV:owner> element with namespace definitions
256 ** and an xml:lang definition, if applicable.
258 ap_xml_to_text(p, child, AP_XML_X2T_FULL_NS_LANG, doc->namespaces,
265 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
267 "The server cannot satisfy the "
268 "LOCK request due to an unknown XML "
269 "element (\"%s\") within the "
270 "DAV:lockinfo element.",
274 *lock_request = lock;
278 /* ---------------------------------------------------------------
280 ** General lock functions
284 /* dav_lock_walker: Walker callback function to record indirect locks */
285 static dav_error * dav_lock_walker(dav_walker_ctx *ctx, int calltype)
289 /* We don't want to set indirects on the target */
290 if ((*ctx->resource->hooks->is_same_resource)(ctx->resource, ctx->root))
293 if ((err = (*ctx->lockdb->hooks->append_locks)(ctx->lockdb, ctx->resource,
295 ctx->lock)) != NULL) {
296 if (ap_is_HTTP_SERVER_ERROR(err->status)) {
297 /* ### add a higher-level description? */
301 /* add to the multistatus response */
302 dav_add_response(ctx, ctx->resource->uri, err->status, NULL);
305 ** ### actually, this is probably wrong: we want to fail the whole
306 ** ### LOCK process if something goes bad. maybe the caller should
307 ** ### do a dav_unlock() (e.g. a rollback) if any errors occurred.
315 ** dav_add_lock: Add a direct lock for resource, and indirect locks for
316 ** all children, bounded by depth.
317 ** ### assume request only contains one lock
319 dav_error * dav_add_lock(request_rec *r, const dav_resource *resource,
320 dav_lockdb *lockdb, dav_lock *lock,
321 dav_response **response)
324 int depth = lock->depth;
328 /* Requested lock can be:
329 * Depth: 0 for null resource, existing resource, or existing collection
330 * Depth: Inf for existing collection
334 ** 2518 9.2 says to ignore depth if target is not a collection (it has
335 ** no internal children); pretend the client gave the correct depth.
337 if (!resource->collection) {
341 /* In all cases, first add direct entry in lockdb */
344 ** Append the new (direct) lock to the resource's existing locks.
346 ** Note: this also handles locknull resources
348 if ((err = (*lockdb->hooks->append_locks)(lockdb, resource, 0,
350 /* ### maybe add a higher-level description */
355 /* Walk existing collection and set indirect locks */
356 dav_walker_ctx ctx = { 0 };
358 ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_AUTH;
360 ctx.func = dav_lock_walker;
363 ctx.resource = resource;
367 dav_buffer_init(r->pool, &ctx.uri, resource->uri);
369 err = (*resource->hooks->walk)(&ctx, DAV_INFINITY);
371 /* implies a 5xx status code occurred. screw the multistatus */
375 if (ctx.response != NULL) {
376 /* manufacture a 207 error for the multistatus response */
377 *response = ctx.response;
378 return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
379 "Error(s) occurred on resources during the "
380 "addition of a depth lock.");
388 ** dav_lock_query: Opens the lock database. Returns a linked list of
389 ** dav_lock structures for all direct locks on path.
391 dav_error * dav_lock_query(dav_lockdb *lockdb, const dav_resource *resource,
394 /* If no lock database, return empty result */
395 if (lockdb == NULL) {
400 /* ### insert a higher-level description? */
401 return (*lockdb->hooks->get_locks)(lockdb, resource,
402 DAV_GETLOCKS_RESOLVED,
406 /* dav_unlock_walker: Walker callback function to remove indirect locks */
407 static dav_error * dav_unlock_walker(dav_walker_ctx *ctx, int calltype)
411 if ((err = (*ctx->lockdb->hooks->remove_lock)(ctx->lockdb, ctx->resource,
412 ctx->locktoken)) != NULL) {
413 /* ### should we stop or return a multistatus? looks like STOP */
414 /* ### add a higher-level description? */
422 ** dav_get_direct_resource:
424 ** Find a lock on the specified resource, then return the resource the
425 ** lock was applied to (in other words, given a (possibly) indirect lock,
426 ** return the direct lock's corresponding resource).
428 ** If the lock is an indirect lock, this usually means traversing up the
429 ** namespace [repository] hierarchy. Note that some lock providers may be
430 ** able to return this information with a traversal.
432 static dav_error * dav_get_direct_resource(apr_pool_t *p,
434 const dav_locktoken *locktoken,
435 const dav_resource *resource,
436 const dav_resource **direct_resource)
438 if (lockdb->hooks->lookup_resource != NULL) {
439 return (*lockdb->hooks->lookup_resource)(lockdb, locktoken,
440 resource, direct_resource);
443 *direct_resource = NULL;
445 /* Find the top of this lock-
446 * If r->filename's direct locks include locktoken, use r->filename.
447 * If r->filename's indirect locks include locktoken, retry r->filename/..
450 while (resource != NULL) {
455 ** Find the lock specified by <locktoken> on <resource>. If it is
456 ** an indirect lock, then partial results are okay. We're just
457 ** trying to find the thing and know whether it is a direct or
460 if ((err = (*lockdb->hooks->find_lock)(lockdb, resource, locktoken,
461 1, &lock)) != NULL) {
462 /* ### add a higher-level desc? */
466 /* not found! that's an error. */
468 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
469 "The specified locktoken does not correspond "
470 "to an existing lock on this resource.");
473 if (lock->rectype == DAV_LOCKREC_DIRECT) {
474 /* we found the direct lock. return this resource. */
476 *direct_resource = resource;
480 /* the lock was indirect. move up a level in the URL namespace */
481 resource = (*resource->hooks->get_parent_resource)(resource);
484 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
485 "The lock database is corrupt. A direct lock could "
486 "not be found for the corresponding indirect lock "
487 "on this resource.");
491 ** dav_unlock: Removes all direct and indirect locks for r->filename,
492 ** with given locktoken. If locktoken == null_locktoken, all locks
493 ** are removed. If r->filename represents an indirect lock,
494 ** we must unlock the appropriate direct lock.
495 ** Returns OK or appropriate HTTP_* response and logs any errors.
497 ** ### We've already crawled the tree to ensure everything was locked
498 ** by us; there should be no need to incorporate a rollback.
500 int dav_unlock(request_rec *r, const dav_resource *resource,
501 const dav_locktoken *locktoken)
505 const dav_resource *lock_resource = resource;
506 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
507 const dav_hooks_repository *repos_hooks = resource->hooks;
510 /* If no locks provider, we shouldn't have been called */
512 /* ### map result to something nice; log an error */
513 return HTTP_INTERNAL_SERVER_ERROR;
516 /* 2518 requires the entire lock to be removed if resource/locktoken
517 * point to an indirect lock. We need resource of the _direct_
518 * lock in order to walk down the tree and remove the locks. So,
519 * If locktoken != null_locktoken,
520 * Walk up the resource hierarchy until we see a direct lock.
521 * Or, we could get the direct lock's db/key, pick out the URL
522 * and do a subrequest. I think walking up is faster and will work
525 * Just start removing all locks at and below resource.
528 if ((err = (*hooks->open_lockdb)(r, 0, 1, &lockdb)) != NULL) {
529 /* ### return err! maybe add a higher-level desc */
530 /* ### map result to something nice; log an error */
531 return HTTP_INTERNAL_SERVER_ERROR;
534 if (locktoken != NULL
535 && (err = dav_get_direct_resource(r->pool, lockdb,
537 &lock_resource)) != NULL) {
538 /* ### add a higher-level desc? */
539 /* ### should return err! */
543 /* At this point, lock_resource/locktoken refers to a direct lock (key), ie
544 * the root of a depth > 0 lock, or locktoken is null.
546 if ((err = (*hooks->remove_lock)(lockdb, lock_resource,
547 locktoken)) != NULL) {
548 /* ### add a higher-level desc? */
549 /* ### return err! */
550 return HTTP_INTERNAL_SERVER_ERROR;
553 if (lock_resource->collection) {
554 dav_walker_ctx ctx = { 0 };
556 ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_LOCKNULL;
558 ctx.func = dav_unlock_walker;
560 ctx.resource = lock_resource;
563 ctx.locktoken = locktoken;
565 dav_buffer_init(r->pool, &ctx.uri, lock_resource->uri);
567 err = (*repos_hooks->walk)(&ctx, DAV_INFINITY);
570 result = err == NULL ? OK : err->status;
575 (*hooks->close_lockdb)(lockdb);
580 /* dav_inherit_walker: Walker callback function to inherit locks */
581 static dav_error * dav_inherit_walker(dav_walker_ctx *ctx, int calltype)
584 && (*ctx->resource->hooks->is_same_resource)(ctx->resource,
589 /* ### maybe add a higher-level desc */
590 return (*ctx->lockdb->hooks->append_locks)(ctx->lockdb, ctx->resource, 1,
595 ** dav_inherit_locks: When a resource or collection is added to a collection,
596 ** locks on the collection should be inherited to the resource/collection.
597 ** (MOVE, MKCOL, etc) Here we propagate any direct or indirect locks from
598 ** parent of resource to resource and below.
600 static dav_error * dav_inherit_locks(request_rec *r, dav_lockdb *lockdb,
601 const dav_resource *resource,
605 const dav_resource *which_resource;
609 dav_walker_ctx ctx = { 0 };
610 const dav_hooks_repository *repos_hooks = resource->hooks;
613 which_resource = (*repos_hooks->get_parent_resource)(resource);
614 if (which_resource == NULL) {
615 /* ### map result to something nice; log an error */
616 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
617 "Could not fetch parent resource. Unable to "
618 "inherit locks from the parent and apply "
619 "them to this resource.");
623 which_resource = resource;
626 if ((err = (*lockdb->hooks->get_locks)(lockdb, which_resource,
627 DAV_GETLOCKS_PARTIAL,
629 /* ### maybe add a higher-level desc */
634 /* No locks to propagate, just return */
639 ** (1) Copy all indirect locks from our parent;
640 ** (2) Create indirect locks for the depth infinity, direct locks
643 ** The append_locks call in the walker callback will do the indirect
644 ** conversion, but we need to remove any direct locks that are NOT
647 for (scan = locks, prev = NULL;
649 prev = scan, scan = scan->next) {
651 if (scan->rectype == DAV_LOCKREC_DIRECT
652 && scan->depth != DAV_INFINITY) {
657 prev->next = scan->next;
661 /* <locks> has all our new locks. Walk down and propagate them. */
663 ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_LOCKNULL;
665 ctx.func = dav_inherit_walker;
667 ctx.resource = resource;
671 ctx.skip_root = !use_parent;
673 dav_buffer_init(r->pool, &ctx.uri, resource->uri);
675 return (*repos_hooks->walk)(&ctx, DAV_INFINITY);
678 /* ---------------------------------------------------------------
680 ** Functions dealing with lock-null resources
685 ** dav_get_resource_state: Returns the state of the resource
686 ** r->filename: DAV_RESOURCE_NULL, DAV_RESOURCE_LOCK_NULL,
687 ** or DAV_RESOURCE_EXIST.
689 ** Returns DAV_RESOURCE_ERROR if an error occurs.
691 int dav_get_resource_state(request_rec *r, const dav_resource *resource)
693 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
695 if (resource->exists)
696 return DAV_RESOURCE_EXISTS;
704 ** A locknull resource has the form:
706 ** known-dir "/" locknull-file
708 ** It would be nice to look into <resource> to verify this form,
709 ** but it does not have enough information for us. Instead, we
710 ** can look at the path_info. If the form does not match, then
711 ** there is no way we could have a locknull resource -- it must
712 ** be a plain, null resource.
714 ** Apache sets r->filename to known-dir/unknown-file and r->path_info
715 ** to "" for the "proper" case. If anything is in path_info, then
716 ** it can't be a locknull resource.
718 ** ### I bet this path_info hack doesn't work for repositories.
719 ** ### Need input from repository implementors! What kind of
720 ** ### restructure do we need? New provider APIs?
722 if (r->path_info != NULL && *r->path_info != '\0') {
723 return DAV_RESOURCE_NULL;
726 if ((err = (*hooks->open_lockdb)(r, 1, 1, &lockdb)) == NULL) {
727 /* note that we might see some expired locks... *shrug* */
728 err = (*hooks->has_locks)(lockdb, resource, &locks_present);
729 (*hooks->close_lockdb)(lockdb);
733 /* ### don't log an error. return err. add higher-level desc. */
735 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
736 "Failed to query lock-null status for %s",
739 return DAV_RESOURCE_ERROR;
743 return DAV_RESOURCE_LOCK_NULL;
746 return DAV_RESOURCE_NULL;
749 dav_error * dav_notify_created(request_rec *r,
751 const dav_resource *resource,
757 if (resource_state == DAV_RESOURCE_LOCK_NULL) {
760 ** The resource is no longer a locknull resource. This will remove
761 ** the special marker.
763 ** Note that a locknull resource has already inherited all of the
764 ** locks from the parent. We do not need to call dav_inherit_locks.
766 ** NOTE: some lock providers record locks for locknull resources using
767 ** a different key than for regular resources. this will shift
768 ** the lock information between the two key types.
770 (void)(*lockdb->hooks->remove_locknull_state)(lockdb, resource);
773 ** There are resources under this one, which are new. We must
774 ** propagate the locks down to the new resources.
777 (err = dav_inherit_locks(r, lockdb, resource, 0)) != NULL) {
778 /* ### add a higher level desc? */
782 else if (resource_state == DAV_RESOURCE_NULL) {
784 /* ### should pass depth to dav_inherit_locks so that it can
785 ** ### optimize for the depth==0 case.
788 /* this resource should inherit locks from its parent */
789 if ((err = dav_inherit_locks(r, lockdb, resource, 1)) != NULL) {
791 err = dav_push_error(r->pool, err->status, 0,
792 "The resource was created successfully, but "
793 "there was a problem inheriting locks from "
794 "the parent resource.",
799 /* else the resource already exists and its locks are correct. */