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