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