1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 ** DAV repository-independent lock functions
22 #include "apr_strings.h"
25 #include <stdio.h> /* for sprintf() */
30 #include "http_config.h"
31 #include "http_protocol.h"
32 #include "http_core.h"
35 /* ---------------------------------------------------------------
37 ** Property-related lock functions
42 ** dav_lock_get_activelock: Returns a <lockdiscovery> containing
43 ** an activelock element for every item in the lock_discovery tree
45 DAV_DECLARE(const char *) dav_lock_get_activelock(request_rec *r,
50 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
52 dav_buffer work_buf = { 0 };
53 apr_pool_t *p = r->pool;
55 /* If no locks or no lock provider, there are no locks */
56 if (lock == NULL || hooks == NULL) {
58 ** Since resourcediscovery is defined with (activelock)*,
59 ** <D:activelock/> shouldn't be necessary for an empty lock.
65 ** Note: it could be interesting to sum the lengths of the owners
66 ** and locktokens during this loop. However, the buffer
67 ** mechanism provides some rough padding so that we don't
68 ** really need to have an exact size. Further, constructing
69 ** locktoken strings could be relatively expensive.
71 for (lock_scan = lock; lock_scan != NULL; lock_scan = lock_scan->next)
74 /* if a buffer was not provided, then use an internal buffer */
78 /* reset the length before we start appending stuff */
81 /* prep the buffer with a "good" size */
82 dav_check_bufsize(p, pbuf, count * 300);
84 for (; lock != NULL; lock = lock->next) {
88 if (lock->rectype == DAV_LOCKREC_INDIRECT_PARTIAL) {
89 /* ### crap. design error */
90 dav_buffer_append(p, pbuf,
91 "DESIGN ERROR: attempted to product an "
92 "activelock element from a partial, indirect "
93 "lock record. Creating an XML parsing error "
94 "to ease detection of this situation: <");
98 dav_buffer_append(p, pbuf, "<D:activelock>" DEBUG_CR "<D:locktype>");
100 case DAV_LOCKTYPE_WRITE:
101 dav_buffer_append(p, pbuf, "<D:write/>");
104 /* ### internal error. log something? */
107 dav_buffer_append(p, pbuf, "</D:locktype>" DEBUG_CR "<D:lockscope>");
108 switch (lock->scope) {
109 case DAV_LOCKSCOPE_EXCLUSIVE:
110 dav_buffer_append(p, pbuf, "<D:exclusive/>");
112 case DAV_LOCKSCOPE_SHARED:
113 dav_buffer_append(p, pbuf, "<D:shared/>");
116 /* ### internal error. log something? */
119 dav_buffer_append(p, pbuf, "</D:lockscope>" DEBUG_CR);
120 sprintf(tmp, "<D:depth>%s</D:depth>" DEBUG_CR,
121 lock->depth == DAV_INFINITY ? "infinity" : "0");
122 dav_buffer_append(p, pbuf, tmp);
126 ** This contains a complete, self-contained <DAV:owner> element,
127 ** with namespace declarations and xml:lang handling. Just drop
130 dav_buffer_append(p, pbuf, lock->owner);
133 dav_buffer_append(p, pbuf, "<D:timeout>");
134 if (lock->timeout == DAV_TIMEOUT_INFINITE) {
135 dav_buffer_append(p, pbuf, "Infinite");
138 time_t now = time(NULL);
139 sprintf(tmp, "Second-%lu", (long unsigned int)(lock->timeout - now));
140 dav_buffer_append(p, pbuf, tmp);
143 dav_buffer_append(p, pbuf,
144 "</D:timeout>" DEBUG_CR
145 "<D:locktoken>" DEBUG_CR
147 dav_buffer_append(p, pbuf,
148 (*hooks->format_locktoken)(p, lock->locktoken));
149 dav_buffer_append(p, pbuf,
151 "</D:locktoken>" DEBUG_CR
152 "</D:activelock>" DEBUG_CR);
159 ** dav_lock_parse_lockinfo: Validates the given xml_doc to contain a
160 ** lockinfo XML element, then populates a dav_lock structure
161 ** with its contents.
163 DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r,
164 const dav_resource *resource,
166 const apr_xml_doc *doc,
167 dav_lock **lock_request)
169 apr_pool_t *p = r->pool;
174 if (!dav_validate_root(doc, "lockinfo")) {
175 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
176 "The request body contains an unexpected "
177 "XML root element.");
180 if ((err = (*lockdb->hooks->create_lock)(lockdb, resource,
182 return dav_push_error(p, err->status, 0,
183 "Could not parse the lockinfo due to an "
184 "internal problem creating a lock structure.",
188 lock->depth = dav_get_depth(r, DAV_INFINITY);
189 if (lock->depth == -1) {
190 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
191 "An invalid Depth header was specified.");
193 lock->timeout = dav_get_timeout(r);
195 /* Parse elements in the XML body */
196 for (child = doc->root->first_child; child; child = child->next) {
197 if (strcmp(child->name, "locktype") == 0
198 && child->first_child
199 && lock->type == DAV_LOCKTYPE_UNKNOWN) {
200 if (strcmp(child->first_child->name, "write") == 0) {
201 lock->type = DAV_LOCKTYPE_WRITE;
205 if (strcmp(child->name, "lockscope") == 0
206 && child->first_child
207 && lock->scope == DAV_LOCKSCOPE_UNKNOWN) {
208 if (strcmp(child->first_child->name, "exclusive") == 0)
209 lock->scope = DAV_LOCKSCOPE_EXCLUSIVE;
210 else if (strcmp(child->first_child->name, "shared") == 0)
211 lock->scope = DAV_LOCKSCOPE_SHARED;
212 if (lock->scope != DAV_LOCKSCOPE_UNKNOWN)
216 if (strcmp(child->name, "owner") == 0 && lock->owner == NULL) {
219 /* quote all the values in the <DAV:owner> element */
220 apr_xml_quote_elem(p, child);
223 ** Store a full <DAV:owner> element with namespace definitions
224 ** and an xml:lang definition, if applicable.
226 apr_xml_to_text(p, child, APR_XML_X2T_FULL_NS_LANG, doc->namespaces,
233 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
235 "The server cannot satisfy the "
236 "LOCK request due to an unknown XML "
237 "element (\"%s\") within the "
238 "DAV:lockinfo element.",
242 *lock_request = lock;
246 /* ---------------------------------------------------------------
248 ** General lock functions
252 /* dav_lock_walker: Walker callback function to record indirect locks */
253 static dav_error * dav_lock_walker(dav_walk_resource *wres, int calltype)
255 dav_walker_ctx *ctx = wres->walk_ctx;
258 /* We don't want to set indirects on the target */
259 if ((*wres->resource->hooks->is_same_resource)(wres->resource,
263 if ((err = (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
265 ctx->lock)) != NULL) {
266 if (ap_is_HTTP_SERVER_ERROR(err->status)) {
267 /* ### add a higher-level description? */
271 /* add to the multistatus response */
272 dav_add_response(wres, err->status, NULL);
275 ** ### actually, this is probably wrong: we want to fail the whole
276 ** ### LOCK process if something goes bad. maybe the caller should
277 ** ### do a dav_unlock() (e.g. a rollback) if any errors occurred.
285 ** dav_add_lock: Add a direct lock for resource, and indirect locks for
286 ** all children, bounded by depth.
287 ** ### assume request only contains one lock
289 DAV_DECLARE(dav_error *) dav_add_lock(request_rec *r,
290 const dav_resource *resource,
291 dav_lockdb *lockdb, dav_lock *lock,
292 dav_response **response)
295 int depth = lock->depth;
299 /* Requested lock can be:
300 * Depth: 0 for null resource, existing resource, or existing collection
301 * Depth: Inf for existing collection
305 ** 2518 9.2 says to ignore depth if target is not a collection (it has
306 ** no internal children); pretend the client gave the correct depth.
308 if (!resource->collection) {
312 /* In all cases, first add direct entry in lockdb */
315 ** Append the new (direct) lock to the resource's existing locks.
317 ** Note: this also handles locknull resources
319 if ((err = (*lockdb->hooks->append_locks)(lockdb, resource, 0,
321 /* ### maybe add a higher-level description */
326 /* Walk existing collection and set indirect locks */
327 dav_walker_ctx ctx = { { 0 } };
328 dav_response *multi_status;
330 ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
331 ctx.w.func = dav_lock_walker;
332 ctx.w.walk_ctx = &ctx;
333 ctx.w.pool = r->pool;
334 ctx.w.root = resource;
335 ctx.w.lockdb = lockdb;
340 err = (*resource->hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
342 /* implies a 5xx status code occurred. screw the multistatus */
346 if (multi_status != NULL) {
347 /* manufacture a 207 error for the multistatus response */
348 *response = multi_status;
349 return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
350 "Error(s) occurred on resources during the "
351 "addition of a depth lock.");
359 ** dav_lock_query: Opens the lock database. Returns a linked list of
360 ** dav_lock structures for all direct locks on path.
362 DAV_DECLARE(dav_error*) dav_lock_query(dav_lockdb *lockdb,
363 const dav_resource *resource,
366 /* If no lock database, return empty result */
367 if (lockdb == NULL) {
372 /* ### insert a higher-level description? */
373 return (*lockdb->hooks->get_locks)(lockdb, resource,
374 DAV_GETLOCKS_RESOLVED,
378 /* dav_unlock_walker: Walker callback function to remove indirect locks */
379 static dav_error * dav_unlock_walker(dav_walk_resource *wres, int calltype)
381 dav_walker_ctx *ctx = wres->walk_ctx;
384 /* Before removing the lock, do any auto-checkin required */
385 if (wres->resource->working) {
386 /* ### get rid of this typecast */
387 if ((err = dav_auto_checkin(ctx->r, (dav_resource *) wres->resource,
388 0 /*undo*/, 1 /*unlock*/, NULL))
394 if ((err = (*ctx->w.lockdb->hooks->remove_lock)(ctx->w.lockdb,
396 ctx->locktoken)) != NULL) {
397 /* ### should we stop or return a multistatus? looks like STOP */
398 /* ### add a higher-level description? */
406 ** dav_get_direct_resource:
408 ** Find a lock on the specified resource, then return the resource the
409 ** lock was applied to (in other words, given a (possibly) indirect lock,
410 ** return the direct lock's corresponding resource).
412 ** If the lock is an indirect lock, this usually means traversing up the
413 ** namespace [repository] hierarchy. Note that some lock providers may be
414 ** able to return this information with a traversal.
416 static dav_error * dav_get_direct_resource(apr_pool_t *p,
418 const dav_locktoken *locktoken,
419 const dav_resource *resource,
420 const dav_resource **direct_resource)
422 if (lockdb->hooks->lookup_resource != NULL) {
423 return (*lockdb->hooks->lookup_resource)(lockdb, locktoken,
424 resource, direct_resource);
427 *direct_resource = NULL;
429 /* Find the top of this lock-
430 * If r->filename's direct locks include locktoken, use r->filename.
431 * If r->filename's indirect locks include locktoken, retry r->filename/..
434 while (resource != NULL) {
437 dav_resource *parent;
440 ** Find the lock specified by <locktoken> on <resource>. If it is
441 ** an indirect lock, then partial results are okay. We're just
442 ** trying to find the thing and know whether it is a direct or
445 if ((err = (*lockdb->hooks->find_lock)(lockdb, resource, locktoken,
446 1, &lock)) != NULL) {
447 /* ### add a higher-level desc? */
451 /* not found! that's an error. */
453 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
454 "The specified locktoken does not correspond "
455 "to an existing lock on this resource.");
458 if (lock->rectype == DAV_LOCKREC_DIRECT) {
459 /* we found the direct lock. return this resource. */
461 *direct_resource = resource;
465 /* the lock was indirect. move up a level in the URL namespace */
466 if ((err = (*resource->hooks->get_parent_resource)(resource,
468 /* ### add a higher-level desc? */
474 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
475 "The lock database is corrupt. A direct lock could "
476 "not be found for the corresponding indirect lock "
477 "on this resource.");
481 ** dav_unlock: Removes all direct and indirect locks for r->filename,
482 ** with given locktoken. If locktoken == null_locktoken, all locks
483 ** are removed. If r->filename represents an indirect lock,
484 ** we must unlock the appropriate direct lock.
485 ** Returns OK or appropriate HTTP_* response and logs any errors.
487 ** ### We've already crawled the tree to ensure everything was locked
488 ** by us; there should be no need to incorporate a rollback.
490 DAV_DECLARE(int) dav_unlock(request_rec *r, const dav_resource *resource,
491 const dav_locktoken *locktoken)
495 const dav_resource *lock_resource = resource;
496 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
497 const dav_hooks_repository *repos_hooks = resource->hooks;
498 dav_walker_ctx ctx = { { 0 } };
499 dav_response *multi_status;
502 /* If no locks provider, then there is nothing to unlock. */
507 /* 2518 requires the entire lock to be removed if resource/locktoken
508 * point to an indirect lock. We need resource of the _direct_
509 * lock in order to walk down the tree and remove the locks. So,
510 * If locktoken != null_locktoken,
511 * Walk up the resource hierarchy until we see a direct lock.
512 * Or, we could get the direct lock's db/key, pick out the URL
513 * and do a subrequest. I think walking up is faster and will work
516 * Just start removing all locks at and below resource.
519 if ((err = (*hooks->open_lockdb)(r, 0, 1, &lockdb)) != NULL) {
520 /* ### return err! maybe add a higher-level desc */
521 /* ### map result to something nice; log an error */
522 return HTTP_INTERNAL_SERVER_ERROR;
525 if (locktoken != NULL
526 && (err = dav_get_direct_resource(r->pool, lockdb,
528 &lock_resource)) != NULL) {
529 /* ### add a higher-level desc? */
530 /* ### should return err! */
534 /* At this point, lock_resource/locktoken refers to a direct lock (key), ie
535 * the root of a depth > 0 lock, or locktoken is null.
537 ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
538 ctx.w.func = dav_unlock_walker;
539 ctx.w.walk_ctx = &ctx;
540 ctx.w.pool = r->pool;
541 ctx.w.root = lock_resource;
542 ctx.w.lockdb = lockdb;
545 ctx.locktoken = locktoken;
547 err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
550 /* ### do something with multi_status */
551 result = err == NULL ? OK : err->status;
553 (*hooks->close_lockdb)(lockdb);
558 /* dav_inherit_walker: Walker callback function to inherit locks */
559 static dav_error * dav_inherit_walker(dav_walk_resource *wres, int calltype)
561 dav_walker_ctx *ctx = wres->walk_ctx;
564 && (*wres->resource->hooks->is_same_resource)(wres->resource,
569 /* ### maybe add a higher-level desc */
570 return (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
576 ** dav_inherit_locks: When a resource or collection is added to a collection,
577 ** locks on the collection should be inherited to the resource/collection.
578 ** (MOVE, MKCOL, etc) Here we propagate any direct or indirect locks from
579 ** parent of resource to resource and below.
581 static dav_error * dav_inherit_locks(request_rec *r, dav_lockdb *lockdb,
582 const dav_resource *resource,
586 const dav_resource *which_resource;
590 dav_walker_ctx ctx = { { 0 } };
591 const dav_hooks_repository *repos_hooks = resource->hooks;
592 dav_response *multi_status;
595 dav_resource *parent;
596 if ((err = (*repos_hooks->get_parent_resource)(resource,
598 /* ### add a higher-level desc? */
601 if (parent == NULL) {
602 /* ### map result to something nice; log an error */
603 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
604 "Could not fetch parent resource. Unable to "
605 "inherit locks from the parent and apply "
606 "them to this resource.");
608 which_resource = parent;
611 which_resource = resource;
614 if ((err = (*lockdb->hooks->get_locks)(lockdb, which_resource,
615 DAV_GETLOCKS_PARTIAL,
617 /* ### maybe add a higher-level desc */
622 /* No locks to propagate, just return */
627 ** (1) Copy all indirect locks from our parent;
628 ** (2) Create indirect locks for the depth infinity, direct locks
631 ** The append_locks call in the walker callback will do the indirect
632 ** conversion, but we need to remove any direct locks that are NOT
635 for (scan = locks, prev = NULL;
637 prev = scan, scan = scan->next) {
639 if (scan->rectype == DAV_LOCKREC_DIRECT
640 && scan->depth != DAV_INFINITY) {
645 prev->next = scan->next;
649 /* <locks> has all our new locks. Walk down and propagate them. */
651 ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
652 ctx.w.func = dav_inherit_walker;
653 ctx.w.walk_ctx = &ctx;
654 ctx.w.pool = r->pool;
655 ctx.w.root = resource;
656 ctx.w.lockdb = lockdb;
660 ctx.skip_root = !use_parent;
662 /* ### do something with multi_status */
663 return (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
666 /* ---------------------------------------------------------------
668 ** Functions dealing with lock-null resources
673 ** dav_get_resource_state: Returns the state of the resource
674 ** r->filename: DAV_RESOURCE_NULL, DAV_RESOURCE_LOCK_NULL,
675 ** or DAV_RESOURCE_EXIST.
677 ** Returns DAV_RESOURCE_ERROR if an error occurs.
679 DAV_DECLARE(int) dav_get_resource_state(request_rec *r,
680 const dav_resource *resource)
682 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
684 if (resource->exists)
685 return DAV_RESOURCE_EXISTS;
693 ** A locknull resource has the form:
695 ** known-dir "/" locknull-file
697 ** It would be nice to look into <resource> to verify this form,
698 ** but it does not have enough information for us. Instead, we
699 ** can look at the path_info. If the form does not match, then
700 ** there is no way we could have a locknull resource -- it must
701 ** be a plain, null resource.
703 ** Apache sets r->filename to known-dir/unknown-file and r->path_info
704 ** to "" for the "proper" case. If anything is in path_info, then
705 ** it can't be a locknull resource.
707 ** ### I bet this path_info hack doesn't work for repositories.
708 ** ### Need input from repository implementors! What kind of
709 ** ### restructure do we need? New provider APIs?
711 if (r->path_info != NULL && *r->path_info != '\0') {
712 return DAV_RESOURCE_NULL;
715 if ((err = (*hooks->open_lockdb)(r, 1, 1, &lockdb)) == NULL) {
716 /* note that we might see some expired locks... *shrug* */
717 err = (*hooks->has_locks)(lockdb, resource, &locks_present);
718 (*hooks->close_lockdb)(lockdb);
722 /* ### don't log an error. return err. add higher-level desc. */
724 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
725 "Failed to query lock-null status for %s",
728 return DAV_RESOURCE_ERROR;
732 return DAV_RESOURCE_LOCK_NULL;
735 return DAV_RESOURCE_NULL;
738 DAV_DECLARE(dav_error *) dav_notify_created(request_rec *r,
740 const dav_resource *resource,
746 if (resource_state == DAV_RESOURCE_LOCK_NULL) {
749 ** The resource is no longer a locknull resource. This will remove
750 ** the special marker.
752 ** Note that a locknull resource has already inherited all of the
753 ** locks from the parent. We do not need to call dav_inherit_locks.
755 ** NOTE: some lock providers record locks for locknull resources using
756 ** a different key than for regular resources. this will shift
757 ** the lock information between the two key types.
759 (void)(*lockdb->hooks->remove_locknull_state)(lockdb, resource);
762 ** There are resources under this one, which are new. We must
763 ** propagate the locks down to the new resources.
766 (err = dav_inherit_locks(r, lockdb, resource, 0)) != NULL) {
767 /* ### add a higher level desc? */
771 else if (resource_state == DAV_RESOURCE_NULL) {
773 /* ### should pass depth to dav_inherit_locks so that it can
774 ** ### optimize for the depth==0 case.
777 /* this resource should inherit locks from its parent */
778 if ((err = dav_inherit_locks(r, lockdb, resource, 1)) != NULL) {
780 err = dav_push_error(r->pool, err->status, 0,
781 "The resource was created successfully, but "
782 "there was a problem inheriting locks from "
783 "the parent resource.",
788 /* else the resource already exists and its locks are correct. */