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
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17/*
18** DAV repository-independent lock functions
19*/
20
21#include "apr.h"
22#include "apr_strings.h"
23
24#include "mod_dav.h"
25#include "http_log.h"
26#include "http_config.h"
27#include "http_protocol.h"
28#include "http_core.h"
29
30APLOG_USE_MODULE(dav);
31
32/* ---------------------------------------------------------------
33**
34** Property-related lock functions
35**
36*/
37
38/*
39** dav_lock_get_activelock:  Returns a <lockdiscovery> containing
40**    an activelock element for every item in the lock_discovery tree
41*/
42DAV_DECLARE(const char *) dav_lock_get_activelock(request_rec *r,
43                                                  dav_lock *lock,
44                                                  dav_buffer *pbuf)
45{
46    dav_lock *lock_scan;
47    const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
48    int count = 0;
49    dav_buffer work_buf = { 0 };
50    apr_pool_t *p = r->pool;
51
52    /* If no locks or no lock provider, there are no locks */
53    if (lock == NULL || hooks == NULL) {
54        /*
55        ** Since resourcediscovery is defined with (activelock)*,
56        ** <D:activelock/> shouldn't be necessary for an empty lock.
57        */
58        return "";
59    }
60
61    /*
62    ** Note: it could be interesting to sum the lengths of the owners
63    **       and locktokens during this loop. However, the buffer
64    **       mechanism provides some rough padding so that we don't
65    **       really need to have an exact size. Further, constructing
66    **       locktoken strings could be relatively expensive.
67    */
68    for (lock_scan = lock; lock_scan != NULL; lock_scan = lock_scan->next)
69        count++;
70
71    /* if a buffer was not provided, then use an internal buffer */
72    if (pbuf == NULL)
73        pbuf = &work_buf;
74
75    /* reset the length before we start appending stuff */
76    pbuf->cur_len = 0;
77
78    /* prep the buffer with a "good" size */
79    dav_check_bufsize(p, pbuf, count * 300);
80
81    for (; lock != NULL; lock = lock->next) {
82        char tmp[100];
83
84#if DAV_DEBUG
85        if (lock->rectype == DAV_LOCKREC_INDIRECT_PARTIAL) {
86            /* ### crap. design error */
87            dav_buffer_append(p, pbuf,
88                              "DESIGN ERROR: attempted to product an "
89                              "activelock element from a partial, indirect "
90                              "lock record. Creating an XML parsing error "
91                              "to ease detection of this situation: <");
92        }
93#endif
94
95        dav_buffer_append(p, pbuf, "<D:activelock>" DEBUG_CR "<D:locktype>");
96        switch (lock->type) {
97        case DAV_LOCKTYPE_WRITE:
98            dav_buffer_append(p, pbuf, "<D:write/>");
99            break;
100        default:
101            /* ### internal error. log something? */
102            break;
103        }
104        dav_buffer_append(p, pbuf, "</D:locktype>" DEBUG_CR "<D:lockscope>");
105        switch (lock->scope) {
106        case DAV_LOCKSCOPE_EXCLUSIVE:
107            dav_buffer_append(p, pbuf, "<D:exclusive/>");
108            break;
109        case DAV_LOCKSCOPE_SHARED:
110            dav_buffer_append(p, pbuf, "<D:shared/>");
111            break;
112        default:
113            /* ### internal error. log something? */
114            break;
115        }
116        dav_buffer_append(p, pbuf, "</D:lockscope>" DEBUG_CR);
117        apr_snprintf(tmp, sizeof(tmp), "<D:depth>%s</D:depth>" DEBUG_CR,
118                     lock->depth == DAV_INFINITY ? "infinity" : "0");
119        dav_buffer_append(p, pbuf, tmp);
120
121        if (lock->owner) {
122            /*
123            ** This contains a complete, self-contained <DAV:owner> element,
124            ** with namespace declarations and xml:lang handling. Just drop
125            ** it in.
126            */
127            dav_buffer_append(p, pbuf, lock->owner);
128        }
129
130        dav_buffer_append(p, pbuf, "<D:timeout>");
131        if (lock->timeout == DAV_TIMEOUT_INFINITE) {
132            dav_buffer_append(p, pbuf, "Infinite");
133        }
134        else {
135            time_t now = time(NULL);
136            apr_snprintf(tmp, sizeof(tmp), "Second-%lu", (long unsigned int)(lock->timeout - now));
137            dav_buffer_append(p, pbuf, tmp);
138        }
139
140        dav_buffer_append(p, pbuf,
141                          "</D:timeout>" DEBUG_CR
142                          "<D:locktoken>" DEBUG_CR
143                          "<D:href>");
144        dav_buffer_append(p, pbuf,
145                          (*hooks->format_locktoken)(p, lock->locktoken));
146        dav_buffer_append(p, pbuf,
147                          "</D:href>" DEBUG_CR
148                          "</D:locktoken>" DEBUG_CR
149                          "</D:activelock>" DEBUG_CR);
150    }
151
152    return pbuf->buf;
153}
154
155/*
156** dav_lock_parse_lockinfo:  Validates the given xml_doc to contain a
157**    lockinfo XML element, then populates a dav_lock structure
158**    with its contents.
159*/
160DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r,
161                                                 const dav_resource *resource,
162                                                 dav_lockdb *lockdb,
163                                                 const apr_xml_doc *doc,
164                                                 dav_lock **lock_request)
165{
166    apr_pool_t *p = r->pool;
167    dav_error *err;
168    apr_xml_elem *child;
169    dav_lock *lock;
170
171    if (!dav_validate_root(doc, "lockinfo")) {
172        return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
173                             "The request body contains an unexpected "
174                             "XML root element.");
175    }
176
177    if ((err = (*lockdb->hooks->create_lock)(lockdb, resource,
178                                             &lock)) != NULL) {
179        return dav_push_error(p, err->status, 0,
180                              "Could not parse the lockinfo due to an "
181                              "internal problem creating a lock structure.",
182                              err);
183    }
184
185    lock->depth = dav_get_depth(r, DAV_INFINITY);
186    if (lock->depth == -1) {
187        return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
188                             "An invalid Depth header was specified.");
189    }
190    lock->timeout = dav_get_timeout(r);
191
192    /* Parse elements in the XML body */
193    for (child = doc->root->first_child; child; child = child->next) {
194        if (strcmp(child->name, "locktype") == 0
195            && child->first_child
196            && lock->type == DAV_LOCKTYPE_UNKNOWN) {
197            if (strcmp(child->first_child->name, "write") == 0) {
198                lock->type = DAV_LOCKTYPE_WRITE;
199                continue;
200            }
201        }
202        if (strcmp(child->name, "lockscope") == 0
203            && child->first_child
204            && lock->scope == DAV_LOCKSCOPE_UNKNOWN) {
205            if (strcmp(child->first_child->name, "exclusive") == 0)
206                lock->scope = DAV_LOCKSCOPE_EXCLUSIVE;
207            else if (strcmp(child->first_child->name, "shared") == 0)
208                lock->scope = DAV_LOCKSCOPE_SHARED;
209            if (lock->scope != DAV_LOCKSCOPE_UNKNOWN)
210                continue;
211        }
212
213        if (strcmp(child->name, "owner") == 0 && lock->owner == NULL) {
214            const char *text;
215
216            /* quote all the values in the <DAV:owner> element */
217            apr_xml_quote_elem(p, child);
218
219            /*
220            ** Store a full <DAV:owner> element with namespace definitions
221            ** and an xml:lang definition, if applicable.
222            */
223            apr_xml_to_text(p, child, APR_XML_X2T_FULL_NS_LANG, doc->namespaces,
224                            NULL, &text, NULL);
225            lock->owner = text;
226
227            continue;
228        }
229
230        return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 0,
231                             apr_psprintf(p,
232                                         "The server cannot satisfy the "
233                                         "LOCK request due to an unknown XML "
234                                         "element (\"%s\") within the "
235                                         "DAV:lockinfo element.",
236                                         child->name));
237    }
238
239    *lock_request = lock;
240    return NULL;
241}
242
243/* ---------------------------------------------------------------
244**
245** General lock functions
246**
247*/
248
249/* dav_lock_walker:  Walker callback function to record indirect locks */
250static dav_error * dav_lock_walker(dav_walk_resource *wres, int calltype)
251{
252    dav_walker_ctx *ctx = wres->walk_ctx;
253    dav_error *err;
254
255    /* We don't want to set indirects on the target */
256    if ((*wres->resource->hooks->is_same_resource)(wres->resource,
257                                                   ctx->w.root))
258        return NULL;
259
260    if ((err = (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
261                                                     wres->resource, 1,
262                                                     ctx->lock)) != NULL) {
263        if (ap_is_HTTP_SERVER_ERROR(err->status)) {
264            /* ### add a higher-level description? */
265            return err;
266        }
267
268        /* add to the multistatus response */
269        dav_add_response(wres, err->status, NULL);
270
271        /*
272        ** ### actually, this is probably wrong: we want to fail the whole
273        ** ### LOCK process if something goes bad. maybe the caller should
274        ** ### do a dav_unlock() (e.g. a rollback) if any errors occurred.
275        */
276    }
277
278    return NULL;
279}
280
281/*
282** dav_add_lock:  Add a direct lock for resource, and indirect locks for
283**    all children, bounded by depth.
284**    ### assume request only contains one lock
285*/
286DAV_DECLARE(dav_error *) dav_add_lock(request_rec *r,
287                                      const dav_resource *resource,
288                                      dav_lockdb *lockdb, dav_lock *lock,
289                                      dav_response **response)
290{
291    dav_error *err;
292    int depth = lock->depth;
293
294    *response = NULL;
295
296    /* Requested lock can be:
297     *   Depth: 0   for null resource, existing resource, or existing collection
298     *   Depth: Inf for existing collection
299     */
300
301    /*
302    ** 2518 9.2 says to ignore depth if target is not a collection (it has
303    **   no internal children); pretend the client gave the correct depth.
304    */
305    if (!resource->collection) {
306        depth = 0;
307    }
308
309    /* In all cases, first add direct entry in lockdb */
310
311    /*
312    ** Append the new (direct) lock to the resource's existing locks.
313    **
314    ** Note: this also handles locknull resources
315    */
316    if ((err = (*lockdb->hooks->append_locks)(lockdb, resource, 0,
317                                              lock)) != NULL) {
318        /* ### maybe add a higher-level description */
319        return err;
320    }
321
322    if (depth > 0) {
323        /* Walk existing collection and set indirect locks */
324        dav_walker_ctx ctx = { { 0 } };
325        dav_response *multi_status;
326
327        ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
328        ctx.w.func = dav_lock_walker;
329        ctx.w.walk_ctx = &ctx;
330        ctx.w.pool = r->pool;
331        ctx.w.root = resource;
332        ctx.w.lockdb = lockdb;
333
334        ctx.r = r;
335        ctx.lock = lock;
336
337        err = (*resource->hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
338        if (err != NULL) {
339            /* implies a 5xx status code occurred. screw the multistatus */
340            return err;
341        }
342
343        if (multi_status != NULL) {
344            /* manufacture a 207 error for the multistatus response */
345            *response = multi_status;
346            return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, 0,
347                                 "Error(s) occurred on resources during the "
348                                 "addition of a depth lock.");
349        }
350    }
351
352    return NULL;
353}
354
355/*
356** dav_lock_query:  Opens the lock database. Returns a linked list of
357**    dav_lock structures for all direct locks on path.
358*/
359DAV_DECLARE(dav_error*) dav_lock_query(dav_lockdb *lockdb,
360                                       const dav_resource *resource,
361                                       dav_lock **locks)
362{
363    /* If no lock database, return empty result */
364    if (lockdb == NULL) {
365        *locks = NULL;
366        return NULL;
367    }
368
369    /* ### insert a higher-level description? */
370    return (*lockdb->hooks->get_locks)(lockdb, resource,
371                                       DAV_GETLOCKS_RESOLVED,
372                                       locks);
373}
374
375/* dav_unlock_walker:  Walker callback function to remove indirect locks */
376static dav_error * dav_unlock_walker(dav_walk_resource *wres, int calltype)
377{
378    dav_walker_ctx *ctx = wres->walk_ctx;
379    dav_error *err;
380
381    /* Before removing the lock, do any auto-checkin required */
382    if (wres->resource->working) {
383        /* ### get rid of this typecast */
384        if ((err = dav_auto_checkin(ctx->r, (dav_resource *) wres->resource,
385                                    0 /*undo*/, 1 /*unlock*/, NULL))
386            != NULL) {
387            return err;
388        }
389    }
390
391    if ((err = (*ctx->w.lockdb->hooks->remove_lock)(ctx->w.lockdb,
392                                                    wres->resource,
393                                                    ctx->locktoken)) != NULL) {
394        /* ### should we stop or return a multistatus? looks like STOP */
395        /* ### add a higher-level description? */
396        return err;
397    }
398
399    return NULL;
400}
401
402/*
403** dav_get_direct_resource:
404**
405** Find a lock on the specified resource, then return the resource the
406** lock was applied to (in other words, given a (possibly) indirect lock,
407** return the direct lock's corresponding resource).
408**
409** If the lock is an indirect lock, this usually means traversing up the
410** namespace [repository] hierarchy. Note that some lock providers may be
411** able to return this information with a traversal.
412*/
413static dav_error * dav_get_direct_resource(apr_pool_t *p,
414                                           dav_lockdb *lockdb,
415                                           const dav_locktoken *locktoken,
416                                           const dav_resource *resource,
417                                           const dav_resource **direct_resource)
418{
419    if (lockdb->hooks->lookup_resource != NULL) {
420        return (*lockdb->hooks->lookup_resource)(lockdb, locktoken,
421                                                 resource, direct_resource);
422    }
423
424    *direct_resource = NULL;
425
426    /* Find the top of this lock-
427     * If r->filename's direct   locks include locktoken, use r->filename.
428     * If r->filename's indirect locks include locktoken, retry r->filename/..
429     * Else fail.
430     */
431    while (resource != NULL) {
432        dav_error *err;
433        dav_lock *lock;
434        dav_resource *parent;
435
436        /*
437        ** Find the lock specified by <locktoken> on <resource>. If it is
438        ** an indirect lock, then partial results are okay. We're just
439        ** trying to find the thing and know whether it is a direct or
440        ** an indirect lock.
441        */
442        if ((err = (*lockdb->hooks->find_lock)(lockdb, resource, locktoken,
443                                               1, &lock)) != NULL) {
444            /* ### add a higher-level desc? */
445            return err;
446        }
447
448        /* not found! that's an error. */
449        if (lock == NULL) {
450            return dav_new_error(p, HTTP_BAD_REQUEST, 0, 0,
451                                 "The specified locktoken does not correspond "
452                                 "to an existing lock on this resource.");
453        }
454
455        if (lock->rectype == DAV_LOCKREC_DIRECT) {
456            /* we found the direct lock. return this resource. */
457
458            *direct_resource = resource;
459            return NULL;
460        }
461
462        /* the lock was indirect. move up a level in the URL namespace */
463        if ((err = (*resource->hooks->get_parent_resource)(resource,
464                                                           &parent)) != NULL) {
465            /* ### add a higher-level desc? */
466            return err;
467        }
468        resource = parent;
469    }
470
471    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
472                         "The lock database is corrupt. A direct lock could "
473                         "not be found for the corresponding indirect lock "
474                         "on this resource.");
475}
476
477/*
478** dav_unlock:  Removes all direct and indirect locks for r->filename,
479**    with given locktoken.  If locktoken == null_locktoken, all locks
480**    are removed.  If r->filename represents an indirect lock,
481**    we must unlock the appropriate direct lock.
482**    Returns OK or appropriate HTTP_* response and logs any errors.
483**
484** ### We've already crawled the tree to ensure everything was locked
485**     by us; there should be no need to incorporate a rollback.
486*/
487DAV_DECLARE(int) dav_unlock(request_rec *r, const dav_resource *resource,
488                            const dav_locktoken *locktoken)
489{
490    int result;
491    dav_lockdb *lockdb;
492    const dav_resource *lock_resource = resource;
493    const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
494    const dav_hooks_repository *repos_hooks = resource->hooks;
495    dav_walker_ctx ctx = { { 0 } };
496    dav_response *multi_status;
497    dav_error *err;
498
499    /* If no locks provider, then there is nothing to unlock. */
500    if (hooks == NULL) {
501        return OK;
502    }
503
504    /* 2518 requires the entire lock to be removed if resource/locktoken
505     * point to an indirect lock.  We need resource of the _direct_
506     * lock in order to walk down the tree and remove the locks.  So,
507     * If locktoken != null_locktoken,
508     *    Walk up the resource hierarchy until we see a direct lock.
509     *    Or, we could get the direct lock's db/key, pick out the URL
510     *    and do a subrequest.  I think walking up is faster and will work
511     *    all the time.
512     * Else
513     *    Just start removing all locks at and below resource.
514     */
515
516    if ((err = (*hooks->open_lockdb)(r, 0, 1, &lockdb)) != NULL) {
517        /* ### return err! maybe add a higher-level desc */
518        /* ### map result to something nice; log an error */
519        return HTTP_INTERNAL_SERVER_ERROR;
520    }
521
522    if (locktoken != NULL
523        && (err = dav_get_direct_resource(r->pool, lockdb,
524                                          locktoken, resource,
525                                          &lock_resource)) != NULL) {
526        /* ### add a higher-level desc? */
527        /* ### should return err! */
528        return err->status;
529    }
530
531    /* At this point, lock_resource/locktoken refers to a direct lock (key), ie
532     * the root of a depth > 0 lock, or locktoken is null.
533     */
534    ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
535    ctx.w.func = dav_unlock_walker;
536    ctx.w.walk_ctx = &ctx;
537    ctx.w.pool = r->pool;
538    ctx.w.root = lock_resource;
539    ctx.w.lockdb = lockdb;
540
541    ctx.r = r;
542    ctx.locktoken = locktoken;
543
544    err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
545
546    /* ### fix this! */
547    /* ### do something with multi_status */
548    result = err == NULL ? OK : err->status;
549
550    (*hooks->close_lockdb)(lockdb);
551
552    return result;
553}
554
555/* dav_inherit_walker:  Walker callback function to inherit locks */
556static dav_error * dav_inherit_walker(dav_walk_resource *wres, int calltype)
557{
558    dav_walker_ctx *ctx = wres->walk_ctx;
559
560    if (ctx->skip_root
561        && (*wres->resource->hooks->is_same_resource)(wres->resource,
562                                                      ctx->w.root)) {
563        return NULL;
564    }
565
566    /* ### maybe add a higher-level desc */
567    return (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
568                                                 wres->resource, 1,
569                                                 ctx->lock);
570}
571
572/*
573** dav_inherit_locks:  When a resource or collection is added to a collection,
574**    locks on the collection should be inherited to the resource/collection.
575**    (MOVE, MKCOL, etc) Here we propagate any direct or indirect locks from
576**    parent of resource to resource and below.
577*/
578static dav_error * dav_inherit_locks(request_rec *r, dav_lockdb *lockdb,
579                                     const dav_resource *resource,
580                                     int use_parent)
581{
582    dav_error *err;
583    const dav_resource *which_resource;
584    dav_lock *locks;
585    dav_lock *scan;
586    dav_lock *prev;
587    dav_walker_ctx ctx = { { 0 } };
588    const dav_hooks_repository *repos_hooks = resource->hooks;
589    dav_response *multi_status;
590
591    if (use_parent) {
592        dav_resource *parent;
593        if ((err = (*repos_hooks->get_parent_resource)(resource,
594                                                       &parent)) != NULL) {
595            /* ### add a higher-level desc? */
596            return err;
597        }
598        if (parent == NULL) {
599            /* ### map result to something nice; log an error */
600            return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
601                                 "Could not fetch parent resource. Unable to "
602                                 "inherit locks from the parent and apply "
603                                 "them to this resource.");
604        }
605        which_resource = parent;
606    }
607    else {
608        which_resource = resource;
609    }
610
611    if ((err = (*lockdb->hooks->get_locks)(lockdb, which_resource,
612                                           DAV_GETLOCKS_PARTIAL,
613                                           &locks)) != NULL) {
614        /* ### maybe add a higher-level desc */
615        return err;
616    }
617
618    if (locks == NULL) {
619        /* No locks to propagate, just return */
620        return NULL;
621    }
622
623    /*
624    ** (1) Copy all indirect locks from our parent;
625    ** (2) Create indirect locks for the depth infinity, direct locks
626    **     in our parent.
627    **
628    ** The append_locks call in the walker callback will do the indirect
629    ** conversion, but we need to remove any direct locks that are NOT
630    ** depth "infinity".
631    */
632    for (scan = locks, prev = NULL;
633         scan != NULL;
634         prev = scan, scan = scan->next) {
635
636        if (scan->rectype == DAV_LOCKREC_DIRECT
637            && scan->depth != DAV_INFINITY) {
638
639            if (prev == NULL)
640                locks = scan->next;
641            else
642                prev->next = scan->next;
643        }
644    }
645
646    /* <locks> has all our new locks.  Walk down and propagate them. */
647
648    ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
649    ctx.w.func = dav_inherit_walker;
650    ctx.w.walk_ctx = &ctx;
651    ctx.w.pool = r->pool;
652    ctx.w.root = resource;
653    ctx.w.lockdb = lockdb;
654
655    ctx.r = r;
656    ctx.lock = locks;
657    ctx.skip_root = !use_parent;
658
659    /* ### do something with multi_status */
660    return (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
661}
662
663/* ---------------------------------------------------------------
664**
665** Functions dealing with lock-null resources
666**
667*/
668
669/*
670** dav_get_resource_state:  Returns the state of the resource
671**    r->filename:  DAV_RESOURCE_NULL, DAV_RESOURCE_LOCK_NULL,
672**    or DAV_RESOURCE_EXIST.
673**
674**    Returns DAV_RESOURCE_ERROR if an error occurs.
675*/
676DAV_DECLARE(int) dav_get_resource_state(request_rec *r,
677                                        const dav_resource *resource)
678{
679    const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
680
681    if (resource->exists)
682        return DAV_RESOURCE_EXISTS;
683
684    if (hooks != NULL) {
685        dav_error *err;
686        dav_lockdb *lockdb;
687        int locks_present;
688
689        /*
690        ** A locknull resource has the form:
691        **
692        **   known-dir "/" locknull-file
693        **
694        ** It would be nice to look into <resource> to verify this form,
695        ** but it does not have enough information for us. Instead, we
696        ** can look at the path_info. If the form does not match, then
697        ** there is no way we could have a locknull resource -- it must
698        ** be a plain, null resource.
699        **
700        ** Apache sets r->filename to known-dir/unknown-file and r->path_info
701        ** to "" for the "proper" case. If anything is in path_info, then
702        ** it can't be a locknull resource.
703        **
704        ** ### I bet this path_info hack doesn't work for repositories.
705        ** ### Need input from repository implementors! What kind of
706        ** ### restructure do we need? New provider APIs?
707        */
708        if (r->path_info != NULL && *r->path_info != '\0') {
709            return DAV_RESOURCE_NULL;
710        }
711
712        if ((err = (*hooks->open_lockdb)(r, 1, 1, &lockdb)) == NULL) {
713            /* note that we might see some expired locks... *shrug* */
714            err = (*hooks->has_locks)(lockdb, resource, &locks_present);
715            (*hooks->close_lockdb)(lockdb);
716        }
717
718        if (err != NULL) {
719            /* ### don't log an error. return err. add higher-level desc. */
720
721            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00623)
722                          "Failed to query lock-null status for %s",
723                          r->filename);
724
725            return DAV_RESOURCE_ERROR;
726        }
727
728        if (locks_present)
729            return DAV_RESOURCE_LOCK_NULL;
730    }
731
732    return DAV_RESOURCE_NULL;
733}
734
735DAV_DECLARE(dav_error *) dav_notify_created(request_rec *r,
736                                            dav_lockdb *lockdb,
737                                            const dav_resource *resource,
738                                            int resource_state,
739                                            int depth)
740{
741    dav_error *err;
742
743    if (resource_state == DAV_RESOURCE_LOCK_NULL) {
744
745        /*
746        ** The resource is no longer a locknull resource. This will remove
747        ** the special marker.
748        **
749        ** Note that a locknull resource has already inherited all of the
750        ** locks from the parent. We do not need to call dav_inherit_locks.
751        **
752        ** NOTE: some lock providers record locks for locknull resources using
753        **       a different key than for regular resources. this will shift
754        **       the lock information between the two key types.
755        */
756        (void)(*lockdb->hooks->remove_locknull_state)(lockdb, resource);
757
758        /*
759        ** There are resources under this one, which are new. We must
760        ** propagate the locks down to the new resources.
761        */
762        if (depth > 0 &&
763            (err = dav_inherit_locks(r, lockdb, resource, 0)) != NULL) {
764            /* ### add a higher level desc? */
765            return err;
766        }
767    }
768    else if (resource_state == DAV_RESOURCE_NULL) {
769
770        /* ### should pass depth to dav_inherit_locks so that it can
771        ** ### optimize for the depth==0 case.
772        */
773
774        /* this resource should inherit locks from its parent */
775        if ((err = dav_inherit_locks(r, lockdb, resource, 1)) != NULL) {
776
777            err = dav_push_error(r->pool, err->status, 0,
778                                 "The resource was created successfully, but "
779                                 "there was a problem inheriting locks from "
780                                 "the parent resource.",
781                                 err);
782            return err;
783        }
784    }
785    /* else the resource already exists and its locks are correct. */
786
787    return NULL;
788}
789