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