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 extension module for Apache 2.0.*
19**  - various utilities, repository-independent
20*/
21
22#include "apr_strings.h"
23#include "apr_lib.h"
24
25#define APR_WANT_STRFUNC
26#include "apr_want.h"
27
28#include "mod_dav.h"
29
30#include "http_request.h"
31#include "http_config.h"
32#include "http_vhost.h"
33#include "http_log.h"
34#include "http_protocol.h"
35
36DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status,
37                                      int error_id, const char *desc)
38{
39    int save_errno = errno;
40    dav_error *err = apr_pcalloc(p, sizeof(*err));
41
42    /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */
43
44    err->status = status;
45    err->error_id = error_id;
46    err->desc = desc;
47    err->save_errno = save_errno;
48
49    return err;
50}
51
52DAV_DECLARE(dav_error*) dav_new_error_tag(apr_pool_t *p, int status,
53                                          int error_id, const char *desc,
54                                          const char *namespace,
55                                          const char *tagname)
56{
57    dav_error *err = dav_new_error(p, status, error_id, desc);
58
59    err->tagname = tagname;
60    err->namespace = namespace;
61
62    return err;
63}
64
65
66DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status,
67                                       int error_id, const char *desc,
68                                       dav_error *prev)
69{
70    dav_error *err = apr_pcalloc(p, sizeof(*err));
71
72    err->status = status;
73    err->error_id = error_id;
74    err->desc = desc;
75    err->prev = prev;
76
77    return err;
78}
79
80DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
81                                    apr_size_t extra_needed)
82{
83    /* grow the buffer if necessary */
84    if (pbuf->cur_len + extra_needed > pbuf->alloc_len) {
85        char *newbuf;
86
87        pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD;
88        newbuf = apr_palloc(p, pbuf->alloc_len);
89        memcpy(newbuf, pbuf->buf, pbuf->cur_len);
90        pbuf->buf = newbuf;
91    }
92}
93
94DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf,
95                                  apr_size_t size)
96{
97    /* NOTE: this does not retain prior contents */
98
99    /* NOTE: this function is used to init the first pointer, too, since
100       the PAD will be larger than alloc_len (0) for zeroed structures */
101
102    /* grow if we don't have enough for the requested size plus padding */
103    if (size + DAV_BUFFER_PAD > pbuf->alloc_len) {
104        /* set the new length; min of MINSIZE */
105        pbuf->alloc_len = size + DAV_BUFFER_PAD;
106        if (pbuf->alloc_len < DAV_BUFFER_MINSIZE)
107            pbuf->alloc_len = DAV_BUFFER_MINSIZE;
108
109        pbuf->buf = apr_palloc(p, pbuf->alloc_len);
110    }
111    pbuf->cur_len = size;
112}
113
114
115/* initialize a buffer and copy the specified (null-term'd) string into it */
116DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf,
117                                  const char *str)
118{
119    dav_set_bufsize(p, pbuf, strlen(str));
120    memcpy(pbuf->buf, str, pbuf->cur_len + 1);
121}
122
123/* append a string to the end of the buffer, adjust length */
124DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf,
125                                    const char *str)
126{
127    apr_size_t len = strlen(str);
128
129    dav_check_bufsize(p, pbuf, len + 1);
130    memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
131    pbuf->cur_len += len;
132}
133
134/* place a string on the end of the buffer, do NOT adjust length */
135DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf,
136                                   const char *str)
137{
138    apr_size_t len = strlen(str);
139
140    dav_check_bufsize(p, pbuf, len + 1);
141    memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
142}
143
144/* place some memory on the end of a buffer; do NOT adjust length */
145DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf,
146                                       const void *mem, apr_size_t amt,
147                                       apr_size_t pad)
148{
149    dav_check_bufsize(p, pbuf, amt + pad);
150    memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
151}
152
153/*
154** dav_lookup_uri()
155**
156** Extension for ap_sub_req_lookup_uri() which can't handle absolute
157** URIs properly.
158**
159** If NULL is returned, then an error occurred with parsing the URI or
160** the URI does not match the current server.
161*/
162DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri,
163                                              request_rec * r,
164                                              int must_be_absolute)
165{
166    dav_lookup_result result = { 0 };
167    const char *scheme;
168    apr_port_t port;
169    apr_uri_t comp;
170    char *new_file;
171    const char *domain;
172
173    /* first thing to do is parse the URI into various components */
174    if (apr_uri_parse(r->pool, uri, &comp) != APR_SUCCESS) {
175        result.err.status = HTTP_BAD_REQUEST;
176        result.err.desc = "Invalid syntax in Destination URI.";
177        return result;
178    }
179
180    /* the URI must be an absoluteURI (WEBDAV S9.3) */
181    if (comp.scheme == NULL && must_be_absolute) {
182        result.err.status = HTTP_BAD_REQUEST;
183        result.err.desc = "Destination URI must be an absolute URI.";
184        return result;
185    }
186
187    /* the URI must not have a query (args) or a fragment */
188    if (comp.query != NULL || comp.fragment != NULL) {
189        result.err.status = HTTP_BAD_REQUEST;
190        result.err.desc =
191            "Destination URI contains invalid components "
192            "(a query or a fragment).";
193        return result;
194    }
195
196    /* If the scheme or port was provided, then make sure that it matches
197       the scheme/port of this request. If the request must be absolute,
198       then require the (explicit/implicit) scheme/port be matching.
199
200       ### hmm. if a port wasn't provided (does the parse return port==0?),
201       ### but we're on a non-standard port, then we won't detect that the
202       ### URI's port implies the wrong one.
203    */
204    if (comp.scheme != NULL || comp.port != 0 || must_be_absolute)
205    {
206        /* ### not sure this works if the current request came in via https: */
207        scheme = r->parsed_uri.scheme;
208        if (scheme == NULL)
209            scheme = ap_http_scheme(r);
210
211        /* insert a port if the URI did not contain one */
212        if (comp.port == 0)
213            comp.port = apr_uri_port_of_scheme(comp.scheme);
214
215        /* now, verify that the URI uses the same scheme as the current.
216           request. the port must match our port.
217        */
218        port = r->connection->local_addr->port;
219        if (strcasecmp(comp.scheme, scheme) != 0
220#ifdef APACHE_PORT_HANDLING_IS_BUSTED
221            || comp.port != port
222#endif
223            ) {
224            result.err.status = HTTP_BAD_GATEWAY;
225            result.err.desc = apr_psprintf(r->pool,
226                                           "Destination URI refers to "
227                                           "different scheme or port "
228                                           "(%s://hostname:%d)" APR_EOL_STR
229                                           "(want: %s://hostname:%d)",
230                                           comp.scheme ? comp.scheme : scheme,
231                                           comp.port ? comp.port : port,
232                                           scheme, port);
233            return result;
234        }
235    }
236
237    /* we have verified the scheme, port, and general structure */
238
239    /*
240    ** Hrm.  IE5 will pass unqualified hostnames for both the
241    ** Host: and Destination: headers.  This breaks the
242    ** http_vhost.c::matches_aliases function.
243    **
244    ** For now, qualify unqualified comp.hostnames with
245    ** r->server->server_hostname.
246    **
247    ** ### this is a big hack. Apache should provide a better way.
248    ** ### maybe the admin should list the unqualified hosts in a
249    ** ### <ServerAlias> block?
250    */
251    if (comp.hostname != NULL
252        && strrchr(comp.hostname, '.') == NULL
253        && (domain = strchr(r->server->server_hostname, '.')) != NULL) {
254        comp.hostname = apr_pstrcat(r->pool, comp.hostname, domain, NULL);
255    }
256
257    /* now, if a hostname was provided, then verify that it represents the
258       same server as the current connection. note that we just use our
259       port, since we've verified the URI matches ours */
260#ifdef APACHE_PORT_HANDLING_IS_BUSTED
261    if (comp.hostname != NULL &&
262        !ap_matches_request_vhost(r, comp.hostname, port)) {
263        result.err.status = HTTP_BAD_GATEWAY;
264        result.err.desc = "Destination URI refers to a different server.";
265        return result;
266    }
267#endif
268
269    /* we have verified that the requested URI denotes the same server as
270       the current request. Therefore, we can use ap_sub_req_lookup_uri() */
271
272    /* reconstruct a URI as just the path */
273    new_file = apr_uri_unparse(r->pool, &comp, APR_URI_UNP_OMITSITEPART);
274
275    /*
276     * Lookup the URI and return the sub-request. Note that we use the
277     * same HTTP method on the destination. This allows the destination
278     * to apply appropriate restrictions (e.g. readonly).
279     */
280    result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
281
282    return result;
283}
284
285/* ---------------------------------------------------------------
286**
287** XML UTILITY FUNCTIONS
288*/
289
290/* validate that the root element uses a given DAV: tagname (TRUE==valid) */
291DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc,
292                                   const char *tagname)
293{
294    return doc->root &&
295        doc->root->ns == APR_XML_NS_DAV_ID &&
296        strcmp(doc->root->name, tagname) == 0;
297}
298
299/* find and return the (unique) child with a given DAV: tagname */
300DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem,
301                                           const char *tagname)
302{
303    apr_xml_elem *child = elem->first_child;
304
305    for (; child; child = child->next)
306        if (child->ns == APR_XML_NS_DAV_ID && !strcmp(child->name, tagname))
307            return child;
308    return NULL;
309}
310
311/* gather up all the CDATA into a single string */
312DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool,
313                              int strip_white)
314{
315    apr_size_t len = 0;
316    apr_text *scan;
317    const apr_xml_elem *child;
318    char *cdata;
319    char *s;
320    apr_size_t tlen;
321    const char *found_text = NULL; /* initialize to avoid gcc warning */
322    int found_count = 0;
323
324    for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
325        found_text = scan->text;
326        ++found_count;
327        len += strlen(found_text);
328    }
329
330    for (child = elem->first_child; child != NULL; child = child->next) {
331        for (scan = child->following_cdata.first;
332             scan != NULL;
333             scan = scan->next) {
334            found_text = scan->text;
335            ++found_count;
336            len += strlen(found_text);
337        }
338    }
339
340    /* some fast-path cases:
341     * 1) zero-length cdata
342     * 2) a single piece of cdata with no whitespace to strip
343     */
344    if (len == 0)
345        return "";
346    if (found_count == 1) {
347        if (!strip_white
348            || (!apr_isspace(*found_text)
349                && !apr_isspace(found_text[len - 1])))
350            return found_text;
351    }
352
353    cdata = s = apr_palloc(pool, len + 1);
354
355    for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
356        tlen = strlen(scan->text);
357        memcpy(s, scan->text, tlen);
358        s += tlen;
359    }
360
361    for (child = elem->first_child; child != NULL; child = child->next) {
362        for (scan = child->following_cdata.first;
363             scan != NULL;
364             scan = scan->next) {
365            tlen = strlen(scan->text);
366            memcpy(s, scan->text, tlen);
367            s += tlen;
368        }
369    }
370
371    *s = '\0';
372
373    if (strip_white) {
374        /* trim leading whitespace */
375        while (apr_isspace(*cdata))     /* assume: return false for '\0' */
376            ++cdata;
377
378        /* trim trailing whitespace */
379        while (len-- > 0 && apr_isspace(cdata[len]))
380            continue;
381        cdata[len + 1] = '\0';
382    }
383
384    return cdata;
385}
386
387DAV_DECLARE(dav_xmlns_info *) dav_xmlns_create(apr_pool_t *pool)
388{
389    dav_xmlns_info *xi = apr_pcalloc(pool, sizeof(*xi));
390
391    xi->pool = pool;
392    xi->uri_prefix = apr_hash_make(pool);
393    xi->prefix_uri = apr_hash_make(pool);
394
395    return xi;
396}
397
398DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi,
399                                const char *prefix, const char *uri)
400{
401    /* this "should" not overwrite a prefix mapping */
402    apr_hash_set(xi->prefix_uri, prefix, APR_HASH_KEY_STRING, uri);
403
404    /* note: this may overwrite an existing URI->prefix mapping, but it
405       doesn't matter -- any prefix is usuable to specify the URI. */
406    apr_hash_set(xi->uri_prefix, uri, APR_HASH_KEY_STRING, prefix);
407}
408
409DAV_DECLARE(const char *) dav_xmlns_add_uri(dav_xmlns_info *xi,
410                                            const char *uri)
411{
412    const char *prefix;
413
414    if ((prefix = apr_hash_get(xi->uri_prefix, uri,
415                               APR_HASH_KEY_STRING)) != NULL)
416        return prefix;
417
418    prefix = apr_psprintf(xi->pool, "g%d", xi->count++);
419    dav_xmlns_add(xi, prefix, uri);
420    return prefix;
421}
422
423DAV_DECLARE(const char *) dav_xmlns_get_uri(dav_xmlns_info *xi,
424                                            const char *prefix)
425{
426    return apr_hash_get(xi->prefix_uri, prefix, APR_HASH_KEY_STRING);
427}
428
429DAV_DECLARE(const char *) dav_xmlns_get_prefix(dav_xmlns_info *xi,
430                                               const char *uri)
431{
432    return apr_hash_get(xi->uri_prefix, uri, APR_HASH_KEY_STRING);
433}
434
435DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
436                                     apr_text_header *phdr)
437{
438    apr_hash_index_t *hi = apr_hash_first(xi->pool, xi->prefix_uri);
439
440    for (; hi != NULL; hi = apr_hash_next(hi)) {
441        const void *prefix;
442        void *uri;
443        const char *s;
444
445        apr_hash_this(hi, &prefix, NULL, &uri);
446
447        s = apr_psprintf(xi->pool, " xmlns:%s=\"%s\"",
448                         (const char *)prefix, (const char *)uri);
449        apr_text_append(xi->pool, phdr, s);
450    }
451}
452
453/* ---------------------------------------------------------------
454**
455** Timeout header processing
456**
457*/
458
459/* dav_get_timeout:  If the Timeout: header exists, return a time_t
460 *    when this lock is expected to expire.  Otherwise, return
461 *    a time_t of DAV_TIMEOUT_INFINITE.
462 *
463 *    It's unclear if DAV clients are required to understand
464 *    Seconds-xxx and Infinity time values.  We assume that they do.
465 *    In addition, for now, that's all we understand, too.
466 */
467DAV_DECLARE(time_t) dav_get_timeout(request_rec *r)
468{
469    time_t now, expires = DAV_TIMEOUT_INFINITE;
470
471    const char *timeout_const = apr_table_get(r->headers_in, "Timeout");
472    const char *timeout = apr_pstrdup(r->pool, timeout_const), *val;
473
474    if (timeout == NULL)
475        return DAV_TIMEOUT_INFINITE;
476
477    /* Use the first thing we understand, or infinity if
478     * we don't understand anything.
479     */
480
481    while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) {
482        if (!strncmp(val, "Infinite", 8)) {
483            return DAV_TIMEOUT_INFINITE;
484        }
485
486        if (!strncmp(val, "Second-", 7)) {
487            val += 7;
488            /* ### We need to handle overflow better:
489             * ### timeout will be <= 2^32 - 1
490             */
491            expires = atol(val);
492            now     = time(NULL);
493            return now + expires;
494        }
495    }
496
497    return DAV_TIMEOUT_INFINITE;
498}
499
500/* ---------------------------------------------------------------
501**
502** If Header processing
503**
504*/
505
506/* add_if_resource returns a new if_header, linking it to next_ih.
507 */
508static dav_if_header *dav_add_if_resource(apr_pool_t *p, dav_if_header *next_ih,
509                                          const char *uri, apr_size_t uri_len)
510{
511    dav_if_header *ih;
512
513    if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL)
514        return NULL;
515
516    ih->uri = uri;
517    ih->uri_len = uri_len;
518    ih->next = next_ih;
519
520    return ih;
521}
522
523/* add_if_state adds a condition to an if_header.
524 */
525static dav_error * dav_add_if_state(apr_pool_t *p, dav_if_header *ih,
526                                    const char *state_token,
527                                    dav_if_state_type t, int condition,
528                                    const dav_hooks_locks *locks_hooks)
529{
530    dav_if_state_list *new_sl;
531
532    new_sl = apr_pcalloc(p, sizeof(*new_sl));
533
534    new_sl->condition = condition;
535    new_sl->type      = t;
536
537    if (t == dav_if_opaquelock) {
538        dav_error *err;
539
540        if ((err = (*locks_hooks->parse_locktoken)(p, state_token,
541                                                   &new_sl->locktoken)) != NULL) {
542            /* If the state token cannot be parsed, treat it as an
543             * unknown state; this will evaluate to "false" later
544             * during If header validation. */
545            if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) {
546                new_sl->type = dav_if_unknown;
547            }
548            else {
549                /* ### maybe add a higher-level description */
550                return err;
551            }
552        }
553    }
554    else
555        new_sl->etag = state_token;
556
557    new_sl->next = ih->state;
558    ih->state = new_sl;
559
560    return NULL;
561}
562
563/* fetch_next_token returns the substring from str+1
564 * to the next occurence of char term, or \0, whichever
565 * occurs first.  Leading whitespace is ignored.
566 */
567static char *dav_fetch_next_token(char **str, char term)
568{
569    char *sp;
570    char *token;
571
572    token = *str + 1;
573
574    while (*token && (*token == ' ' || *token == '\t'))
575        token++;
576
577    if ((sp = strchr(token, term)) == NULL)
578        return NULL;
579
580    *sp = '\0';
581    *str = sp;
582    return token;
583}
584
585/* dav_process_if_header:
586 *
587 *   If NULL (no error) is returned, then **if_header points to the
588 *   "If" productions structure (or NULL if "If" is not present).
589 *
590 *   ### this part is bogus:
591 *   If an error is encountered, the error is logged.  Parent should
592 *   return err->status.
593 */
594static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
595{
596    dav_error *err;
597    char *str;
598    char *list;
599    const char *state_token;
600    const char *uri = NULL;        /* scope of current production; NULL=no-tag */
601    apr_size_t uri_len = 0;
602    dav_if_header *ih = NULL;
603    apr_uri_t parsed_uri;
604    const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
605    enum {no_tagged, tagged, unknown} list_type = unknown;
606    int condition;
607
608    *p_ih = NULL;
609
610    if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL)
611        return NULL;
612
613    while (*str) {
614        switch(*str) {
615        case '<':
616            /* Tagged-list production - following states apply to this uri */
617            if (list_type == no_tagged
618                || ((uri = dav_fetch_next_token(&str, '>')) == NULL)) {
619                return dav_new_error(r->pool, HTTP_BAD_REQUEST,
620                                     DAV_ERR_IF_TAGGED,
621                                     "Invalid If-header: unclosed \"<\" or "
622                                     "unexpected tagged-list production.");
623            }
624
625            /* 2518 specifies this must be an absolute URI; just take the
626             * relative part for later comparison against r->uri */
627            if (apr_uri_parse(r->pool, uri, &parsed_uri) != APR_SUCCESS
628                || !parsed_uri.path) {
629                return dav_new_error(r->pool, HTTP_BAD_REQUEST,
630                                     DAV_ERR_IF_TAGGED,
631                                     "Invalid URI in tagged If-header.");
632            }
633            /* note that parsed_uri.path is allocated; we can trash it */
634
635            /* clean up the URI a bit */
636            ap_getparents(parsed_uri.path);
637
638            /* the resources we will compare to have unencoded paths */
639            if (ap_unescape_url(parsed_uri.path) != OK) {
640                return dav_new_error(r->pool, HTTP_BAD_REQUEST,
641                                     DAV_ERR_IF_TAGGED,
642                                     "Invalid percent encoded URI in "
643                                     "tagged If-header.");
644            }
645
646            uri_len = strlen(parsed_uri.path);
647            if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/') {
648                parsed_uri.path[--uri_len] = '\0';
649            }
650
651            uri = parsed_uri.path;
652            list_type = tagged;
653            break;
654
655        case '(':
656            /* List production */
657
658            /* If a uri has not been encountered, this is a No-Tagged-List */
659            if (list_type == unknown)
660                list_type = no_tagged;
661
662            if ((list = dav_fetch_next_token(&str, ')')) == NULL) {
663                return dav_new_error(r->pool, HTTP_BAD_REQUEST,
664                                     DAV_ERR_IF_UNCLOSED_PAREN,
665                                     "Invalid If-header: unclosed \"(\".");
666            }
667
668            if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) {
669                /* ### dav_add_if_resource() should return an error for us! */
670                return dav_new_error(r->pool, HTTP_BAD_REQUEST,
671                                     DAV_ERR_IF_PARSE,
672                                     "Internal server error parsing \"If:\" "
673                                     "header.");
674            }
675
676            condition = DAV_IF_COND_NORMAL;
677
678            while (*list) {
679                /* List is the entire production (in a uri scope) */
680
681                switch (*list) {
682                case '<':
683                    if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) {
684                        /* ### add a description to this error */
685                        return dav_new_error(r->pool, HTTP_BAD_REQUEST,
686                                             DAV_ERR_IF_PARSE, NULL);
687                    }
688
689                    if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock,
690                                                condition, locks_hooks)) != NULL) {
691                        /* ### maybe add a higher level description */
692                        return err;
693                    }
694                    condition = DAV_IF_COND_NORMAL;
695                    break;
696
697                case '[':
698                    if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) {
699                        /* ### add a description to this error */
700                        return dav_new_error(r->pool, HTTP_BAD_REQUEST,
701                                             DAV_ERR_IF_PARSE, NULL);
702                    }
703
704                    if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag,
705                                                condition, locks_hooks)) != NULL) {
706                        /* ### maybe add a higher level description */
707                        return err;
708                    }
709                    condition = DAV_IF_COND_NORMAL;
710                    break;
711
712                case 'N':
713                    if (list[1] == 'o' && list[2] == 't') {
714                        if (condition != DAV_IF_COND_NORMAL) {
715                            return dav_new_error(r->pool, HTTP_BAD_REQUEST,
716                                                 DAV_ERR_IF_MULTIPLE_NOT,
717                                                 "Invalid \"If:\" header: "
718                                                 "Multiple \"not\" entries "
719                                                 "for the same state.");
720                        }
721                        condition = DAV_IF_COND_NOT;
722                    }
723                    list += 2;
724                    break;
725
726                case ' ':
727                case '\t':
728                    break;
729
730                default:
731                    return dav_new_error(r->pool, HTTP_BAD_REQUEST,
732                                         DAV_ERR_IF_UNK_CHAR,
733                                         apr_psprintf(r->pool,
734                                                     "Invalid \"If:\" "
735                                                     "header: Unexpected "
736                                                     "character encountered "
737                                                     "(0x%02x, '%c').",
738                                                     *list, *list));
739                }
740
741                list++;
742            }
743            break;
744
745        case ' ':
746        case '\t':
747            break;
748
749        default:
750            return dav_new_error(r->pool, HTTP_BAD_REQUEST,
751                                 DAV_ERR_IF_UNK_CHAR,
752                                 apr_psprintf(r->pool,
753                                             "Invalid \"If:\" header: "
754                                             "Unexpected character "
755                                             "encountered (0x%02x, '%c').",
756                                             *str, *str));
757        }
758
759        str++;
760    }
761
762    *p_ih = ih;
763    return NULL;
764}
765
766static int dav_find_submitted_locktoken(const dav_if_header *if_header,
767                                        const dav_lock *lock_list,
768                                        const dav_hooks_locks *locks_hooks)
769{
770    for (; if_header != NULL; if_header = if_header->next) {
771        const dav_if_state_list *state_list;
772
773        for (state_list = if_header->state;
774             state_list != NULL;
775             state_list = state_list->next) {
776
777            if (state_list->type == dav_if_opaquelock) {
778                const dav_lock *lock;
779
780                /* given state_list->locktoken, match it */
781
782                /*
783                ** The resource will have one or more lock tokens. We only
784                ** need to match one of them against any token in the
785                ** If: header.
786                **
787                ** One token case: It is an exclusive or shared lock. Either
788                **                 way, we must find it.
789                **
790                ** N token case: They are shared locks. By policy, we need
791                **               to match only one. The resource's other
792                **               tokens may belong to somebody else (so we
793                **               shouldn't see them in the If: header anyway)
794                */
795                for (lock = lock_list; lock != NULL; lock = lock->next) {
796
797                    if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
798                        return 1;
799                    }
800                }
801            }
802        }
803    }
804
805    return 0;
806}
807
808/* dav_validate_resource_state:
809 *    Returns NULL if path/uri meets if-header and lock requirements
810 */
811static dav_error * dav_validate_resource_state(apr_pool_t *p,
812                                               const dav_resource *resource,
813                                               dav_lockdb *lockdb,
814                                               const dav_if_header *if_header,
815                                               int flags,
816                                               dav_buffer *pbuf,
817                                               request_rec *r)
818{
819    dav_error *err;
820    const char *uri;
821    const char *etag;
822    const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL);
823    const dav_if_header *ifhdr_scan;
824    dav_if_state_list *state_list;
825    dav_lock *lock_list;
826    dav_lock *lock;
827    int num_matched;
828    int num_that_apply;
829    int seen_locktoken;
830    apr_size_t uri_len;
831    const char *reason = NULL;
832
833    /* DBG1("validate: <%s>", resource->uri); */
834
835    /*
836    ** The resource will have one of three states:
837    **
838    ** 1) No locks. We have no special requirements that the user supply
839    **    specific locktokens. One of the state lists must match, and
840    **    we're done.
841    **
842    ** 2) One exclusive lock. The locktoken must appear *anywhere* in the
843    **    If: header. Of course, asserting the token in a "Not" term will
844    **    quickly fail that state list :-). If the locktoken appears in
845    **    one of the state lists *and* one state list matches, then we're
846    **    done.
847    **
848    ** 3) One or more shared locks. One of the locktokens must appear
849    **    *anywhere* in the If: header. If one of the locktokens appears,
850    **    and we match one state list, then we are done.
851    **
852    ** The <seen_locktoken> variable determines whether we have seen one
853    ** of this resource's locktokens in the If: header.
854    */
855
856    /*
857    ** If this is a new lock request, <flags> will contain the requested
858    ** lock scope.  Three rules apply:
859    **
860    ** 1) Do not require a (shared) locktoken to be seen (when we are
861    **    applying another shared lock)
862    ** 2) If the scope is exclusive and we see any locks, fail.
863    ** 3) If the scope is shared and we see an exclusive lock, fail.
864    */
865
866    if (lockdb == NULL) {
867        /* we're in State 1. no locks. */
868        lock_list = NULL;
869    }
870    else {
871        /*
872        ** ### hrm... we don't need to have these fully
873        ** ### resolved since we're only looking at the
874        ** ### locktokens...
875        **
876        ** ### use get_locks w/ calltype=PARTIAL
877        */
878        if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) {
879            return dav_push_error(p,
880                                  HTTP_INTERNAL_SERVER_ERROR, 0,
881                                  "The locks could not be queried for "
882                                  "verification against a possible \"If:\" "
883                                  "header.",
884                                  err);
885        }
886
887        /* lock_list now determines whether we're in State 1, 2, or 3. */
888    }
889
890    /*
891    ** For a new, exclusive lock: if any locks exist, fail.
892    ** For a new, shared lock:    if an exclusive lock exists, fail.
893    **                            else, do not require a token to be seen.
894    */
895    if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
896        if (lock_list != NULL) {
897            return dav_new_error(p, HTTP_LOCKED, 0,
898                                 "Existing lock(s) on the requested resource "
899                                 "prevent an exclusive lock.");
900        }
901
902        /*
903        ** There are no locks, so we can pretend that we've already met
904        ** any requirement to find the resource's locks in an If: header.
905        */
906        seen_locktoken = 1;
907    }
908    else if (flags & DAV_LOCKSCOPE_SHARED) {
909        /*
910        ** Strictly speaking, we don't need this loop. Either the first
911        ** (and only) lock will be EXCLUSIVE, or none of them will be.
912        */
913        for (lock = lock_list; lock != NULL; lock = lock->next) {
914            if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) {
915                return dav_new_error(p, HTTP_LOCKED, 0,
916                                     "The requested resource is already "
917                                     "locked exclusively.");
918            }
919        }
920
921        /*
922        ** The locks on the resource (if any) are all shared. Set the
923        ** <seen_locktoken> flag to indicate that we do not need to find
924        ** the locks in an If: header.
925        */
926        seen_locktoken = 1;
927    }
928    else {
929        /*
930        ** For methods other than LOCK:
931        **
932        ** If we have no locks or if the resource is not being modified
933        ** (per RFC 4918 the lock token is not required on resources
934        ** we are not changing), then <seen_locktoken> can be set to true --
935        ** pretending that we've already met the requirement of seeing one
936        ** of the resource's locks in the If: header.
937        **
938        ** Otherwise, it must be cleared and we'll look for one.
939        */
940        seen_locktoken = (lock_list == NULL
941                          || flags & DAV_VALIDATE_NO_MODIFY);
942    }
943
944    /*
945    ** If there is no If: header, then we can shortcut some logic:
946    **
947    ** 1) if we do not need to find a locktoken in the (non-existent) If:
948    **    header, then we are successful.
949    **
950    ** 2) if we must find a locktoken in the (non-existent) If: header, then
951    **    we fail.
952    */
953    if (if_header == NULL) {
954        if (seen_locktoken)
955            return NULL;
956
957        return dav_new_error(p, HTTP_LOCKED, 0,
958                             "This resource is locked and an \"If:\" header "
959                             "was not supplied to allow access to the "
960                             "resource.");
961    }
962    /* the If: header is present */
963
964    /*
965    ** If a dummy header is present (because of a Lock-Token: header), then
966    ** we are required to find that token in this resource's set of locks.
967    ** If we have no locks, then we immediately fail.
968    **
969    ** This is a 400 (Bad Request) since they should only submit a locktoken
970    ** that actually exists.
971    **
972    ** Don't issue this response if we're talking about the parent resource.
973    ** It is okay for that resource to NOT have this locktoken.
974    ** (in fact, it certainly will not: a dummy_header only occurs for the
975    **  UNLOCK method, the parent is checked only for locknull resources,
976    **  and the parent certainly does not have the (locknull's) locktoken)
977    */
978    if (lock_list == NULL && if_header->dummy_header) {
979        if (flags & DAV_VALIDATE_IS_PARENT)
980            return NULL;
981        return dav_new_error(p, HTTP_BAD_REQUEST, 0,
982                             "The locktoken specified in the \"Lock-Token:\" "
983                             "header is invalid because this resource has no "
984                             "outstanding locks.");
985    }
986
987    /*
988    ** Prepare the input URI. We want the URI to never have a trailing slash.
989    **
990    ** When URIs are placed into the dav_if_header structure, they are
991    ** guaranteed to never have a trailing slash. If the URIs are equivalent,
992    ** then it doesn't matter if they both lack a trailing slash -- they're
993    ** still equivalent.
994    **
995    ** Note: we could also ensure that a trailing slash is present on both
996    ** URIs, but the majority of URIs provided to us via a resource walk
997    ** will not contain that trailing slash.
998    */
999    uri = resource->uri;
1000    uri_len = strlen(uri);
1001    if (uri[uri_len - 1] == '/') {
1002        dav_set_bufsize(p, pbuf, uri_len);
1003        memcpy(pbuf->buf, uri, uri_len);
1004        pbuf->buf[--uri_len] = '\0';
1005        uri = pbuf->buf;
1006    }
1007
1008    /* get the resource's etag; we may need it during the checks */
1009    etag = (*resource->hooks->getetag)(resource);
1010
1011    /* how many state_lists apply to this URI? */
1012    num_that_apply = 0;
1013
1014    /* If there are if-headers, fail if this resource
1015     * does not match at least one state_list.
1016     */
1017    for (ifhdr_scan = if_header;
1018         ifhdr_scan != NULL;
1019         ifhdr_scan = ifhdr_scan->next) {
1020
1021        /* DBG2("uri=<%s>  if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
1022
1023        if (ifhdr_scan->uri != NULL
1024            && (uri_len != ifhdr_scan->uri_len
1025                || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
1026            /*
1027            ** A tagged-list's URI doesn't match this resource's URI.
1028            ** Skip to the next state_list to see if it will match.
1029            */
1030            continue;
1031        }
1032
1033        /* this state_list applies to this resource */
1034
1035        /*
1036        ** ### only one state_list should ever apply! a no-tag, or a tagged
1037        ** ### where S9.4.2 states only one can match.
1038        **
1039        ** ### revamp this code to loop thru ifhdr_scan until we find the
1040        ** ### matching state_list. process it. stop.
1041        */
1042        ++num_that_apply;
1043
1044        /* To succeed, resource must match *all* of the states
1045         * specified in the state_list.
1046         */
1047        for (state_list = ifhdr_scan->state;
1048             state_list != NULL;
1049             state_list = state_list->next) {
1050
1051            switch(state_list->type) {
1052            case dav_if_etag:
1053            {
1054                const char *given_etag, *current_etag;
1055                int mismatch;
1056
1057                /* Do a weak entity comparison function as defined in
1058                 * RFC 2616 13.3.3.
1059                 */
1060                if (state_list->etag[0] == 'W' &&
1061                    state_list->etag[1] == '/') {
1062                    given_etag = state_list->etag + 2;
1063                }
1064                else {
1065                    given_etag = state_list->etag;
1066                }
1067                if (etag[0] == 'W' &&
1068                    etag[1] == '/') {
1069                    current_etag = etag + 2;
1070                }
1071                else {
1072                    current_etag = etag;
1073                }
1074
1075                mismatch = strcmp(given_etag, current_etag);
1076
1077                if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
1078                    /*
1079                    ** The specified entity-tag does not match the
1080                    ** entity-tag on the resource. This state_list is
1081                    ** not going to match. Bust outta here.
1082                    */
1083                    reason =
1084                        "an entity-tag was specified, but the resource's "
1085                        "actual ETag does not match.";
1086                    goto state_list_failed;
1087                }
1088                else if (state_list->condition == DAV_IF_COND_NOT
1089                         && !mismatch) {
1090                    /*
1091                    ** The specified entity-tag DOES match the
1092                    ** entity-tag on the resource. This state_list is
1093                    ** not going to match. Bust outta here.
1094                    */
1095                    reason =
1096                        "an entity-tag was specified using the \"Not\" form, "
1097                        "but the resource's actual ETag matches the provided "
1098                        "entity-tag.";
1099                    goto state_list_failed;
1100                }
1101                break;
1102            }
1103
1104            case dav_if_opaquelock:
1105                if (lockdb == NULL) {
1106                    if (state_list->condition == DAV_IF_COND_NOT) {
1107                        /* the locktoken is definitely not there! (success) */
1108                        continue;
1109                    }
1110
1111                    /* condition == DAV_IF_COND_NORMAL */
1112
1113                    /*
1114                    ** If no lockdb is provided, then validation fails for
1115                    ** this state_list (NORMAL means we were supposed to
1116                    ** find the token, which we obviously cannot do without
1117                    ** a lock database).
1118                    **
1119                    ** Go and try the next state list.
1120                    */
1121                    reason =
1122                        "a State-token was supplied, but a lock database "
1123                        "is not available for to provide the required lock.";
1124                    goto state_list_failed;
1125                }
1126
1127                /* Resource validation 'fails' if:
1128                 *    ANY  of the lock->locktokens match
1129                 *         a NOT state_list->locktoken,
1130                 * OR
1131                 *    NONE of the lock->locktokens match
1132                 *         a NORMAL state_list->locktoken.
1133                 */
1134                num_matched = 0;
1135                for (lock = lock_list; lock != NULL; lock = lock->next) {
1136
1137                    /*
1138                    DBG2("compare: rsrc=%s  ifhdr=%s",
1139                         (*locks_hooks->format_locktoken)(p, lock->locktoken),
1140                         (*locks_hooks->format_locktoken)(p, state_list->locktoken));
1141                    */
1142
1143                    /* nothing to do if the locktokens do not match. */
1144                    if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
1145                        continue;
1146                    }
1147
1148                    /*
1149                    ** We have now matched up one of the resource's locktokens
1150                    ** to a locktoken in a State-token in the If: header.
1151                    ** Note this fact, so that we can pass the overall
1152                    ** requirement of seeing at least one of the resource's
1153                    ** locktokens.
1154                    */
1155                    seen_locktoken = 1;
1156
1157                    if (state_list->condition == DAV_IF_COND_NOT) {
1158                        /*
1159                        ** This state requires that the specified locktoken
1160                        ** is NOT present on the resource. But we just found
1161                        ** it. There is no way this state-list can now
1162                        ** succeed, so go try another one.
1163                        */
1164                        reason =
1165                            "a State-token was supplied, which used a "
1166                            "\"Not\" condition. The State-token was found "
1167                            "in the locks on this resource";
1168                        goto state_list_failed;
1169                    }
1170
1171                    /* condition == DAV_IF_COND_NORMAL */
1172
1173                    /* Validate auth_user:  If an authenticated user created
1174                    ** the lock, only the same user may submit that locktoken
1175                    ** to manipulate a resource.
1176                    */
1177                    if (lock->auth_user &&
1178                        (!r->user ||
1179                         strcmp(lock->auth_user, r->user))) {
1180                        const char *errmsg;
1181
1182                        errmsg = apr_pstrcat(p, "User \"",
1183                                            r->user,
1184                                            "\" submitted a locktoken created "
1185                                            "by user \"",
1186                                            lock->auth_user, "\".", NULL);
1187                        return dav_new_error(p, HTTP_FORBIDDEN, 0, errmsg);
1188                    }
1189
1190                    /*
1191                    ** We just matched a specified State-Token to one of the
1192                    ** resource's locktokens.
1193                    **
1194                    ** Break out of the lock scan -- we only needed to find
1195                    ** one match (actually, there shouldn't be any other
1196                    ** matches in the lock list).
1197                    */
1198                    num_matched = 1;
1199                    break;
1200                }
1201
1202                if (num_matched == 0
1203                    && state_list->condition == DAV_IF_COND_NORMAL) {
1204                    /*
1205                    ** We had a NORMAL state, meaning that we should have
1206                    ** found the State-Token within the locks on this
1207                    ** resource. We didn't, so this state_list must fail.
1208                    */
1209                    reason =
1210                        "a State-token was supplied, but it was not found "
1211                        "in the locks on this resource.";
1212                    goto state_list_failed;
1213                }
1214
1215                break;
1216
1217            case dav_if_unknown:
1218                /* Request is predicated on some unknown state token,
1219                 * which must be presumed to *not* match, so fail
1220                 * unless this is a Not condition. */
1221
1222                if (state_list->condition == DAV_IF_COND_NORMAL) {
1223                    reason =
1224                        "an unknown state token was supplied";
1225                    goto state_list_failed;
1226                }
1227                break;
1228
1229            } /* switch */
1230        } /* foreach ( state_list ) */
1231
1232        /*
1233        ** We've checked every state in this state_list and none of them
1234        ** have failed. Since all of them succeeded, then we have a matching
1235        ** state list and we may be done.
1236        **
1237        ** The next requirement is that we have seen one of the resource's
1238        ** locktokens (if any). If we have, then we can just exit. If we
1239        ** haven't, then we need to keep looking.
1240        */
1241        if (seen_locktoken) {
1242            /* woo hoo! */
1243            return NULL;
1244        }
1245
1246        /*
1247        ** Haven't seen one. Let's break out of the search and just look
1248        ** for a matching locktoken.
1249        */
1250        break;
1251
1252        /*
1253        ** This label is used when we detect that a state_list is not
1254        ** going to match this resource. We bust out and try the next
1255        ** state_list.
1256        */
1257      state_list_failed:
1258        ;
1259
1260    } /* foreach ( ifhdr_scan ) */
1261
1262    /*
1263    ** The above loop exits for one of two reasons:
1264    **   1) a state_list matched and seen_locktoken is false.
1265    **   2) all if_header structures were scanned, without (1) occurring
1266    */
1267
1268    if (ifhdr_scan == NULL) {
1269        /*
1270        ** We finished the loop without finding any matching state lists.
1271        */
1272
1273        /*
1274        ** If none of the state_lists apply to this resource, then we
1275        ** may have succeeded. Note that this scenario implies a
1276        ** tagged-list with no matching state_lists. If the If: header
1277        ** was a no-tag-list, then it would have applied to this resource.
1278        **
1279        ** S9.4.2 states that when no state_lists apply, then the header
1280        ** should be ignored.
1281        **
1282        ** If we saw one of the resource's locktokens, then we're done.
1283        ** If we did not see a locktoken, then we fail.
1284        */
1285        if (num_that_apply == 0) {
1286            if (seen_locktoken)
1287                return NULL;
1288
1289            /*
1290            ** We may have aborted the scan before seeing the locktoken.
1291            ** Rescan the If: header to see if we can find the locktoken
1292            ** somewhere.
1293            **
1294            ** Note that seen_locktoken == 0 implies lock_list != NULL
1295            ** which implies locks_hooks != NULL.
1296            */
1297            if (dav_find_submitted_locktoken(if_header, lock_list,
1298                                             locks_hooks)) {
1299                /*
1300                ** We found a match! We're set... none of the If: header
1301                ** assertions apply (implicit success), and the If: header
1302                ** specified the locktoken somewhere. We're done.
1303                */
1304                return NULL;
1305            }
1306
1307            return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */,
1308                                 "This resource is locked and the \"If:\" "
1309                                 "header did not specify one of the "
1310                                 "locktokens for this resource's lock(s).");
1311        }
1312        /* else: one or more state_lists were applicable, but failed. */
1313
1314        /*
1315        ** If the dummy_header did not match, then they specified an
1316        ** incorrect token in the Lock-Token header. Forget whether the
1317        ** If: statement matched or not... we'll tell them about the
1318        ** bad Lock-Token first. That is considered a 400 (Bad Request).
1319        */
1320        if (if_header->dummy_header) {
1321            return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1322                                 "The locktoken specified in the "
1323                                 "\"Lock-Token:\" header did not specify one "
1324                                 "of this resource's locktoken(s).");
1325        }
1326
1327        if (reason == NULL) {
1328            return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1329                                 "The preconditions specified by the \"If:\" "
1330                                 "header did not match this resource.");
1331        }
1332
1333        return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1334                             apr_psprintf(p,
1335                                         "The precondition(s) specified by "
1336                                         "the \"If:\" header did not match "
1337                                         "this resource. At least one "
1338                                         "failure is because: %s", reason));
1339    }
1340
1341    /* assert seen_locktoken == 0 */
1342
1343    /*
1344    ** ifhdr_scan != NULL implies we found a matching state_list.
1345    **
1346    ** Since we're still here, it also means that we have not yet found
1347    ** one the resource's locktokens in the If: header.
1348    **
1349    ** Scan all the if_headers and states looking for one of this
1350    ** resource's locktokens. Note that we need to go back and scan them
1351    ** all -- we may have aborted a scan with a failure before we saw a
1352    ** matching token.
1353    **
1354    ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
1355    ** locks_hooks != NULL.
1356    */
1357    if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
1358        /*
1359        ** We found a match! We're set... we have a matching state list,
1360        ** and the If: header specified the locktoken somewhere. We're done.
1361        */
1362        return NULL;
1363    }
1364
1365    /*
1366    ** We had a matching state list, but the user agent did not specify one
1367    ** of this resource's locktokens. Tell them so.
1368    **
1369    ** Note that we need to special-case the message on whether a "dummy"
1370    ** header exists. If it exists, yet we didn't see a needed locktoken,
1371    ** then that implies the dummy header (Lock-Token header) did NOT
1372    ** specify one of this resource's locktokens. (this implies something
1373    ** in the real If: header matched)
1374    **
1375    ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
1376    */
1377    if (if_header->dummy_header) {
1378        return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1379                             "The locktoken specified in the "
1380                             "\"Lock-Token:\" header did not specify one "
1381                             "of this resource's locktoken(s).");
1382    }
1383
1384    return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */,
1385                         "This resource is locked and the \"If:\" header "
1386                         "did not specify one of the "
1387                         "locktokens for this resource's lock(s).");
1388}
1389
1390/* dav_validate_walker:  Walker callback function to validate resource state */
1391static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
1392{
1393    dav_walker_ctx *ctx = wres->walk_ctx;
1394    dav_error *err;
1395
1396    if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
1397                                           ctx->w.lockdb,
1398                                           ctx->if_header, ctx->flags,
1399                                           &ctx->work_buf, ctx->r)) == NULL) {
1400        /* There was no error, so just bug out. */
1401        return NULL;
1402    }
1403
1404    /*
1405    ** If we have a serious server error, or if the request itself failed,
1406    ** then just return error (not a multistatus).
1407    */
1408    if (ap_is_HTTP_SERVER_ERROR(err->status)
1409        || (*wres->resource->hooks->is_same_resource)(wres->resource,
1410                                                      ctx->w.root)) {
1411        /* ### maybe push a higher-level description? */
1412        return err;
1413    }
1414
1415    /* associate the error with the current URI */
1416    dav_add_response(wres, err->status, NULL);
1417
1418    return NULL;
1419}
1420
1421/* If-* header checking */
1422static int dav_meets_conditions(request_rec *r, int resource_state)
1423{
1424    const char *if_match, *if_none_match;
1425    int retVal;
1426
1427    /* If-Match '*' fix. Resource existence not checked by ap_meets_conditions.
1428     * If-Match '*' request should succeed only if the resource exists. */
1429    if ((if_match = apr_table_get(r->headers_in, "If-Match")) != NULL) {
1430        if (if_match[0] == '*' && resource_state != DAV_RESOURCE_EXISTS)
1431            return HTTP_PRECONDITION_FAILED;
1432    }
1433
1434    retVal = ap_meets_conditions(r);
1435
1436    /* If-None-Match '*' fix. If-None-Match '*' request should succeed
1437     * if the resource does not exist. */
1438    if (retVal == HTTP_PRECONDITION_FAILED) {
1439        /* Note. If if_none_match != NULL, if_none_match is the culprit.
1440         * Since, in presence of If-None-Match,
1441         * other If-* headers are undefined. */
1442        if ((if_none_match =
1443            apr_table_get(r->headers_in, "If-None-Match")) != NULL) {
1444            if (if_none_match[0] == '*'
1445                && resource_state != DAV_RESOURCE_EXISTS) {
1446                return OK;
1447            }
1448        }
1449    }
1450
1451    return retVal;
1452}
1453
1454/*
1455** dav_validate_request:  Validate if-headers (and check for locks) on:
1456**    (1) r->filename @ depth;
1457**    (2) Parent of r->filename if check_parent == 1
1458**
1459** The check of parent should be done when it is necessary to verify that
1460** the parent collection will accept a new member (ie current resource
1461** state is null).
1462**
1463** Return OK on successful validation.
1464** On error, return appropriate HTTP_* code, and log error. If a multi-stat
1465** error is necessary, response will point to it, else NULL.
1466*/
1467DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r,
1468                                              dav_resource *resource,
1469                                              int depth,
1470                                              dav_locktoken *locktoken,
1471                                              dav_response **response,
1472                                              int flags,
1473                                              dav_lockdb *lockdb)
1474{
1475    dav_error *err;
1476    int result;
1477    dav_if_header *if_header;
1478    int lock_db_opened_locally = 0;
1479    const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1480    const dav_hooks_repository *repos_hooks = resource->hooks;
1481    dav_buffer work_buf = { 0 };
1482    dav_response *new_response;
1483    int resource_state;
1484    const char *etag;
1485    int set_etag = 0;
1486
1487#if DAV_DEBUG
1488    if (depth && response == NULL) {
1489        /*
1490        ** ### bleck. we can't return errors for other URIs unless we have
1491        ** ### a "response" ptr.
1492        */
1493        return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1494                             "DESIGN ERROR: dav_validate_request called "
1495                             "with depth>0, but no response ptr.");
1496    }
1497#endif
1498
1499    if (response != NULL)
1500        *response = NULL;
1501
1502    /* Set the ETag header required by dav_meets_conditions() */
1503    etag = apr_table_get(r->headers_out, "ETag");
1504    if (!etag) {
1505        etag = (*resource->hooks->getetag)(resource);
1506        if (etag && *etag) {
1507            apr_table_set(r->headers_out, "ETag", etag);
1508            set_etag = 1;
1509        }
1510    }
1511    /* Do the standard checks for conditional requests using
1512     * If-..-Since, If-Match etc */
1513    resource_state = dav_get_resource_state(r, resource);
1514    result = dav_meets_conditions(r, resource_state);
1515    if (set_etag) {
1516        /*
1517         * If we have set an ETag to headers out above for
1518         * dav_meets_conditions() revert this here as we do not want to set
1519         * the ETag in responses to requests with methods where this might not
1520         * be desired.
1521         */
1522        apr_table_unset(r->headers_out, "ETag");
1523    }
1524    if (result != OK) {
1525        return dav_new_error(r->pool, result, 0, NULL);
1526    }
1527
1528    /* always parse (and later process) the If: header */
1529    if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1530        /* ### maybe add higher-level description */
1531        return err;
1532    }
1533
1534    /* If a locktoken was specified, create a dummy if_header with which
1535     * to validate resources.  In the interim, figure out why DAV uses
1536     * locktokens in an if-header without a Lock-Token header to refresh
1537     * locks, but a Lock-Token header without an if-header to remove them.
1538     */
1539    if (locktoken != NULL) {
1540        dav_if_header *ifhdr_new;
1541
1542        ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new));
1543        ifhdr_new->uri = resource->uri;
1544        ifhdr_new->uri_len = strlen(resource->uri);
1545        ifhdr_new->dummy_header = 1;
1546
1547        ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state));
1548        ifhdr_new->state->type = dav_if_opaquelock;
1549        ifhdr_new->state->condition = DAV_IF_COND_NORMAL;
1550        ifhdr_new->state->locktoken = locktoken;
1551
1552        ifhdr_new->next = if_header;
1553        if_header = ifhdr_new;
1554    }
1555
1556    /*
1557    ** If necessary, open the lock database (read-only, lazily);
1558    ** the validation process may need to retrieve or update lock info.
1559    ** Otherwise, assume provided lockdb is valid and opened rw.
1560    */
1561    if (lockdb == NULL) {
1562        if (locks_hooks != NULL) {
1563            if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1564                /* ### maybe insert higher-level comment */
1565                return err;
1566            }
1567            lock_db_opened_locally = 1;
1568        }
1569    }
1570
1571    /* (1) Validate the specified resource, at the specified depth */
1572    if (resource->exists && depth > 0) {
1573        dav_walker_ctx ctx = { { 0 } };
1574        dav_response *multi_status;
1575
1576        ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
1577        ctx.w.func = dav_validate_walker;
1578        ctx.w.walk_ctx = &ctx;
1579        ctx.w.pool = r->pool;
1580        ctx.w.root = resource;
1581
1582        ctx.if_header = if_header;
1583        ctx.r = r;
1584        ctx.flags = flags;
1585
1586        if (lockdb != NULL) {
1587            ctx.w.lockdb = lockdb;
1588            ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1589        }
1590
1591        err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
1592        if (err == NULL) {
1593            *response = multi_status;;
1594        }
1595        /* else: implies a 5xx status code occurred. */
1596    }
1597    else {
1598        err = dav_validate_resource_state(r->pool, resource, lockdb,
1599                                          if_header, flags, &work_buf, r);
1600    }
1601
1602    /* (2) Validate the parent resource if requested */
1603    if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
1604        dav_resource *parent_resource;
1605
1606        err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
1607
1608        if (err == NULL && parent_resource == NULL) {
1609            err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
1610                                "Cannot access parent of repository root.");
1611        }
1612        else if (err == NULL) {
1613            err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
1614                                              if_header,
1615                                              flags | DAV_VALIDATE_IS_PARENT,
1616                                              &work_buf, r);
1617
1618            /*
1619            ** This error occurred on the parent resource. This implies that
1620            ** we have to create a multistatus response (to report the error
1621            ** against a URI other than the Request-URI). "Convert" this error
1622            ** into a multistatus response.
1623            */
1624            if (err != NULL) {
1625                new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1626
1627                new_response->href = parent_resource->uri;
1628                new_response->status = err->status;
1629                new_response->desc =
1630                    "A validation error has occurred on the parent resource, "
1631                    "preventing the operation on the resource specified by "
1632                    "the Request-URI.";
1633                if (err->desc != NULL) {
1634                    new_response->desc = apr_pstrcat(r->pool,
1635                                                    new_response->desc,
1636                                                    " The error was: ",
1637                                                    err->desc, NULL);
1638                }
1639
1640                /* assert: DAV_VALIDATE_PARENT implies response != NULL */
1641                new_response->next = *response;
1642                *response = new_response;
1643
1644                err = NULL;
1645            }
1646        }
1647    }
1648
1649    if (lock_db_opened_locally)
1650        (*locks_hooks->close_lockdb)(lockdb);
1651
1652    /*
1653    ** If we don't have a (serious) error, and we have multistatus responses,
1654    ** then we need to construct an "error". This error will be the overall
1655    ** status returned, and the multistatus responses will go into its body.
1656    **
1657    ** For certain methods, the overall error will be a 424. The default is
1658    ** to construct a standard 207 response.
1659    */
1660    if (err == NULL && response != NULL && *response != NULL) {
1661        apr_text *propstat = NULL;
1662
1663        if ((flags & DAV_VALIDATE_USE_424) != 0) {
1664            /* manufacture a 424 error to hold the multistatus response(s) */
1665            return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0,
1666                                 "An error occurred on another resource, "
1667                                 "preventing the requested operation on "
1668                                 "this resource.");
1669        }
1670
1671        /*
1672        ** Whatever caused the error, the Request-URI should have a 424
1673        ** associated with it since we cannot complete the method.
1674        **
1675        ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
1676        ** For other methods, return a simple 424.
1677        */
1678        if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
1679            propstat = apr_pcalloc(r->pool, sizeof(*propstat));
1680            propstat->text =
1681                "<D:propstat>" DEBUG_CR
1682                "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
1683                "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
1684                "</D:propstat>" DEBUG_CR;
1685        }
1686
1687        /* create the 424 response */
1688        new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1689        new_response->href = resource->uri;
1690        new_response->status = HTTP_FAILED_DEPENDENCY;
1691        new_response->propresult.propstats = propstat;
1692        new_response->desc =
1693            "An error occurred on another resource, preventing the "
1694            "requested operation on this resource.";
1695
1696        new_response->next = *response;
1697        *response = new_response;
1698
1699        /* manufacture a 207 error for the multistatus response(s) */
1700        return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
1701                             "Error(s) occurred on resources during the "
1702                             "validation process.");
1703    }
1704
1705    return err;
1706}
1707
1708/* dav_get_locktoken_list:
1709 *
1710 * Sets ltl to a locktoken_list of all positive locktokens in header,
1711 * else NULL if no If-header, or no positive locktokens.
1712 */
1713DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r,
1714                                                dav_locktoken_list **ltl)
1715{
1716    dav_error *err;
1717    dav_if_header *if_header;
1718    dav_if_state_list *if_state;
1719    dav_locktoken_list *lock_token = NULL;
1720
1721    *ltl = NULL;
1722
1723    if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1724        /* ### add a higher-level description? */
1725        return err;
1726    }
1727
1728    while (if_header != NULL) {
1729        if_state = if_header->state;        /* Begining of the if_state linked list */
1730        while (if_state != NULL)        {
1731            if (if_state->condition == DAV_IF_COND_NORMAL
1732                && if_state->type == dav_if_opaquelock) {
1733                lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list));
1734                lock_token->locktoken = if_state->locktoken;
1735                lock_token->next = *ltl;
1736                *ltl = lock_token;
1737            }
1738            if_state = if_state->next;
1739        }
1740        if_header = if_header->next;
1741    }
1742    if (*ltl == NULL) {
1743        /* No nodes added */
1744        return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT,
1745                             "No locktokens were specified in the \"If:\" "
1746                             "header, so the refresh could not be performed.");
1747    }
1748
1749    return NULL;
1750}
1751
1752#if 0 /* not needed right now... */
1753
1754static const char *strip_white(const char *s, apr_pool_t *pool)
1755{
1756    apr_size_t idx;
1757
1758    /* trim leading whitespace */
1759    while (apr_isspace(*s))     /* assume: return false for '\0' */
1760        ++s;
1761
1762    /* trim trailing whitespace */
1763    idx = strlen(s) - 1;
1764    if (apr_isspace(s[idx])) {
1765        char *s2 = apr_pstrdup(pool, s);
1766
1767        while (apr_isspace(s2[idx]) && idx > 0)
1768            --idx;
1769        s2[idx + 1] = '\0';
1770        return s2;
1771    }
1772
1773    return s;
1774}
1775#endif
1776
1777#define DAV_LABEL_HDR "Label"
1778
1779/* dav_add_vary_header
1780 *
1781 * If there were any headers in the request which require a Vary header
1782 * in the response, add it.
1783 */
1784DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req,
1785                                      request_rec *out_req,
1786                                      const dav_resource *resource)
1787{
1788    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
1789
1790    /* ### this is probably all wrong... I think there is a function in
1791       ### the Apache API to add things to the Vary header. need to check */
1792
1793    /* Only versioning headers require a Vary response header,
1794     * so only do this check if there is a versioning provider */
1795    if (vsn_hooks != NULL) {
1796        const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR);
1797        const char *vary = apr_table_get(out_req->headers_out, "Vary");
1798
1799        /* If Target-Selector specified, add it to the Vary header */
1800        if (target != NULL) {
1801            if (vary == NULL)
1802                vary = DAV_LABEL_HDR;
1803            else
1804                vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
1805                                   NULL);
1806
1807            apr_table_setn(out_req->headers_out, "Vary", vary);
1808        }
1809    }
1810}
1811
1812/* dav_can_auto_checkout
1813 *
1814 * Determine whether auto-checkout is enabled for a resource.
1815 * r - the request_rec
1816 * resource - the resource
1817 * auto_version - the value of the auto_versionable hook for the resource
1818 * lockdb - pointer to lock database (opened if necessary)
1819 * auto_checkout - set to 1 if auto-checkout enabled
1820 */
1821static dav_error * dav_can_auto_checkout(
1822    request_rec *r,
1823    dav_resource *resource,
1824    dav_auto_version auto_version,
1825    dav_lockdb **lockdb,
1826    int *auto_checkout)
1827{
1828    dav_error *err;
1829    dav_lock *lock_list;
1830
1831    *auto_checkout = 0;
1832
1833    if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
1834        *auto_checkout = 1;
1835    }
1836    else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
1837        if (*lockdb == NULL) {
1838            const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1839
1840            if (locks_hooks == NULL) {
1841                return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1842                                     "Auto-checkout is only enabled for locked resources, "
1843                                     "but there is no lock provider.");
1844            }
1845
1846            if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) {
1847                return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1848                                      "Cannot open lock database to determine "
1849                                      "auto-versioning behavior.",
1850                                      err);
1851            }
1852        }
1853
1854        if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) {
1855            return dav_push_error(r->pool,
1856                                  HTTP_INTERNAL_SERVER_ERROR, 0,
1857                                  "The locks could not be queried for "
1858                                  "determining auto-versioning behavior.",
1859                                  err);
1860        }
1861
1862        if (lock_list != NULL)
1863            *auto_checkout = 1;
1864    }
1865
1866    return NULL;
1867}
1868
1869/* see mod_dav.h for docco */
1870DAV_DECLARE(dav_error *) dav_auto_checkout(
1871    request_rec *r,
1872    dav_resource *resource,
1873    int parent_only,
1874    dav_auto_version_info *av_info)
1875{
1876    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1877    dav_lockdb *lockdb = NULL;
1878    dav_error *err = NULL;
1879
1880    /* Initialize results */
1881    memset(av_info, 0, sizeof(*av_info));
1882
1883    /* if no versioning provider, just return */
1884    if (vsn_hooks == NULL)
1885        return NULL;
1886
1887    /* check parent resource if requested or if resource must be created */
1888    if (!resource->exists || parent_only) {
1889        dav_resource *parent;
1890
1891        if ((err = (*resource->hooks->get_parent_resource)(resource,
1892                                                           &parent)) != NULL)
1893            goto done;
1894
1895        if (parent == NULL || !parent->exists) {
1896            err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1897                                apr_psprintf(r->pool,
1898                                            "Missing one or more intermediate "
1899                                            "collections. Cannot create resource %s.",
1900                                            ap_escape_html(r->pool, resource->uri)));
1901            goto done;
1902        }
1903
1904        av_info->parent_resource = parent;
1905
1906        /* if parent versioned and not checked out, see if it can be */
1907        if (parent->versioned && !parent->working) {
1908            int checkout_parent;
1909
1910            if ((err = dav_can_auto_checkout(r, parent,
1911                                             (*vsn_hooks->auto_versionable)(parent),
1912                                             &lockdb, &checkout_parent))
1913                != NULL) {
1914                goto done;
1915            }
1916
1917            if (!checkout_parent) {
1918                err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1919                                    "<DAV:cannot-modify-checked-in-parent>");
1920                goto done;
1921            }
1922
1923            /* Try to checkout the parent collection.
1924             * Note that auto-versioning can only be applied to a version selector,
1925             * so no separate working resource will be created.
1926             */
1927            if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
1928                                              0, 0, 0, NULL, NULL))
1929                != NULL)
1930            {
1931                err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1932                                     apr_psprintf(r->pool,
1933                                                 "Unable to auto-checkout parent collection. "
1934                                                 "Cannot create resource %s.",
1935                                                 ap_escape_html(r->pool, resource->uri)),
1936                                     err);
1937                goto done;
1938            }
1939
1940            /* remember that parent was checked out */
1941            av_info->parent_checkedout = 1;
1942        }
1943    }
1944
1945    /* if only checking parent, we're done */
1946    if (parent_only)
1947        goto done;
1948
1949    /* if creating a new resource, see if it should be version-controlled */
1950    if (!resource->exists
1951        && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) {
1952
1953        if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
1954            err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1955                                 apr_psprintf(r->pool,
1956                                             "Unable to create versioned resource %s.",
1957                                             ap_escape_html(r->pool, resource->uri)),
1958                                 err);
1959            goto done;
1960        }
1961
1962        /* remember that resource was created */
1963        av_info->resource_versioned = 1;
1964    }
1965
1966    /* if resource is versioned, make sure it is checked out */
1967    if (resource->versioned && !resource->working) {
1968        int checkout_resource;
1969
1970        if ((err = dav_can_auto_checkout(r, resource,
1971                                         (*vsn_hooks->auto_versionable)(resource),
1972                                         &lockdb, &checkout_resource)) != NULL) {
1973            goto done;
1974        }
1975
1976        if (!checkout_resource) {
1977            err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1978                                "<DAV:cannot-modify-version-controlled-content>");
1979            goto done;
1980        }
1981
1982        /* Auto-versioning can only be applied to version selectors, so
1983         * no separate working resource will be created. */
1984        if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/,
1985                                          0, 0, 0, NULL, NULL))
1986            != NULL)
1987        {
1988            err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1989                                 apr_psprintf(r->pool,
1990                                             "Unable to checkout resource %s.",
1991                                             ap_escape_html(r->pool, resource->uri)),
1992                                 err);
1993            goto done;
1994        }
1995
1996        /* remember that resource was checked out */
1997        av_info->resource_checkedout = 1;
1998    }
1999
2000done:
2001
2002    /* make sure lock database is closed */
2003    if (lockdb != NULL)
2004        (*lockdb->hooks->close_lockdb)(lockdb);
2005
2006    /* if an error occurred, undo any auto-versioning operations already done */
2007    if (err != NULL) {
2008        dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
2009        return err;
2010    }
2011
2012    return NULL;
2013}
2014
2015/* see mod_dav.h for docco */
2016DAV_DECLARE(dav_error *) dav_auto_checkin(
2017    request_rec *r,
2018    dav_resource *resource,
2019    int undo,
2020    int unlock,
2021    dav_auto_version_info *av_info)
2022{
2023    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
2024    dav_error *err = NULL;
2025    dav_auto_version auto_version;
2026
2027    /* If no versioning provider, this is a no-op */
2028    if (vsn_hooks == NULL)
2029        return NULL;
2030
2031    /* If undoing auto-checkouts, then do uncheckouts */
2032    if (undo) {
2033        if (resource != NULL) {
2034            if (av_info->resource_checkedout) {
2035                if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
2036                    return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2037                                          apr_psprintf(r->pool,
2038                                                      "Unable to undo auto-checkout "
2039                                                      "of resource %s.",
2040                                                      ap_escape_html(r->pool, resource->uri)),
2041                                          err);
2042                }
2043            }
2044
2045            if (av_info->resource_versioned) {
2046                dav_response *response;
2047
2048                /* ### should we do anything with the response? */
2049                if ((err = (*resource->hooks->remove_resource)(resource,
2050                                                               &response)) != NULL) {
2051                    return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2052                                          apr_psprintf(r->pool,
2053                                                      "Unable to undo auto-version-control "
2054                                                      "of resource %s.",
2055                                                      ap_escape_html(r->pool, resource->uri)),
2056                                          err);
2057                }
2058            }
2059        }
2060
2061        if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
2062            if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) {
2063                return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2064                                      apr_psprintf(r->pool,
2065                                                  "Unable to undo auto-checkout "
2066                                                  "of parent collection %s.",
2067                                                  ap_escape_html(r->pool, av_info->parent_resource->uri)),
2068                                      err);
2069            }
2070        }
2071
2072        return NULL;
2073    }
2074
2075    /* If the resource was checked out, and auto-checkin is enabled,
2076     * then check it in.
2077     */
2078    if (resource != NULL && resource->working
2079        && (unlock || av_info->resource_checkedout)) {
2080
2081        auto_version = (*vsn_hooks->auto_versionable)(resource);
2082
2083        if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
2084            (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
2085
2086            if ((err = (*vsn_hooks->checkin)(resource,
2087                                             0 /*keep_checked_out*/, NULL))
2088                != NULL) {
2089                return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2090                                      apr_psprintf(r->pool,
2091                                                  "Unable to auto-checkin resource %s.",
2092                                                  ap_escape_html(r->pool, resource->uri)),
2093                                      err);
2094            }
2095        }
2096    }
2097
2098    /* If parent resource was checked out, and auto-checkin is enabled,
2099     * then check it in.
2100     */
2101    if (!unlock
2102        && av_info->parent_checkedout
2103        && av_info->parent_resource != NULL
2104        && av_info->parent_resource->working) {
2105
2106        auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
2107
2108        if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
2109            if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
2110                                             0 /*keep_checked_out*/, NULL))
2111                != NULL) {
2112                return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2113                                      apr_psprintf(r->pool,
2114                                                  "Unable to auto-checkin parent collection %s.",
2115                                                  ap_escape_html(r->pool, av_info->parent_resource->uri)),
2116                                                  err);
2117            }
2118        }
2119    }
2120
2121    return NULL;
2122}
2123