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