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. */