]> granicus.if.org Git - apache/blob - modules/dav/main/util.c
Fix handling of unknown state tokens in If headers:
[apache] / modules / dav / main / util.c
1 /* Copyright 2000-2005 The Apache Software Foundation or its licensors, as
2  * applicable.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * 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_scheme(r);
210
211         /* insert a port if the URI did not contain one */
212         if (comp.port == 0)
213             comp.port = apr_uri_port_of_scheme(comp.scheme);
214
215         /* now, verify that the URI uses the same scheme as the current.
216            request. the port must match our port.
217         */
218         port = r->connection->local_addr->port;
219         if (strcasecmp(comp.scheme, scheme) != 0
220 #ifdef APACHE_PORT_HANDLING_IS_BUSTED
221             || comp.port != port
222 #endif
223             ) {
224             result.err.status = HTTP_BAD_GATEWAY;
225             result.err.desc = apr_psprintf(r->pool,
226                                            "Destination URI refers to "
227                                            "different scheme or port "
228                                            "(%s://hostname:%d)" APR_EOL_STR
229                                            "(want: %s://hostname:%d)",
230                                            comp.scheme ? comp.scheme : scheme,
231                                            comp.port ? comp.port : port,
232                                            scheme, port);
233             return result;
234         }
235     }
236
237     /* we have verified the scheme, port, and general structure */
238
239     /*
240     ** Hrm.  IE5 will pass unqualified hostnames for both the 
241     ** Host: and Destination: headers.  This breaks the
242     ** http_vhost.c::matches_aliases function.
243     **
244     ** For now, qualify unqualified comp.hostnames with
245     ** r->server->server_hostname.
246     **
247     ** ### this is a big hack. Apache should provide a better way.
248     ** ### maybe the admin should list the unqualified hosts in a
249     ** ### <ServerAlias> block?
250     */
251     if (comp.hostname != NULL
252         && strrchr(comp.hostname, '.') == NULL
253         && (domain = strchr(r->server->server_hostname, '.')) != NULL) {
254         comp.hostname = apr_pstrcat(r->pool, comp.hostname, domain, NULL);
255     }
256
257     /* now, if a hostname was provided, then verify that it represents the
258        same server as the current connection. note that we just use our
259        port, since we've verified the URI matches ours */
260 #ifdef APACHE_PORT_HANDLING_IS_BUSTED
261     if (comp.hostname != NULL &&
262         !ap_matches_request_vhost(r, comp.hostname, port)) {
263         result.err.status = HTTP_BAD_GATEWAY;
264         result.err.desc = "Destination URI refers to a different server.";
265         return result;
266     }
267 #endif
268
269     /* we have verified that the requested URI denotes the same server as
270        the current request. Therefore, we can use ap_sub_req_lookup_uri() */
271
272     /* reconstruct a URI as just the path */
273     new_file = apr_uri_unparse(r->pool, &comp, APR_URI_UNP_OMITSITEPART);
274
275     /*
276      * Lookup the URI and return the sub-request. Note that we use the
277      * same HTTP method on the destination. This allows the destination
278      * to apply appropriate restrictions (e.g. readonly).
279      */
280     result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
281
282     return result;
283 }
284
285 /* ---------------------------------------------------------------
286 **
287 ** XML UTILITY FUNCTIONS
288 */
289
290 /* validate that the root element uses a given DAV: tagname (TRUE==valid) */
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             /* If the state token cannot be parsed, treat it as an
543              * unknown state; this will evaluate to "false" later
544              * during If header validation. */
545             if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) {
546                 new_sl->type = dav_if_unknown;
547             }
548             else {
549                 /* ### maybe add a higher-level description */
550                 return err;
551             }
552         }
553     }
554     else
555         new_sl->etag = state_token;
556
557     new_sl->next = ih->state;
558     ih->state = new_sl;
559
560     return NULL;
561 }
562
563 /* fetch_next_token returns the substring from str+1
564  * to the next occurence of char term, or \0, whichever
565  * occurs first.  Leading whitespace is ignored.
566  */
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                 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
629                                      DAV_ERR_IF_TAGGED,
630                                      "Invalid URI in tagged If-header.");
631             }
632             /* note that parsed_uri.path is allocated; we can trash it */
633
634             /* clean up the URI a bit */
635             ap_getparents(parsed_uri.path);
636             uri_len = strlen(parsed_uri.path);
637             if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/')
638                 parsed_uri.path[--uri_len] = '\0';
639
640             uri = parsed_uri.path;
641             list_type = tagged;
642             break;
643
644         case '(':
645             /* List production */
646
647             /* If a uri has not been encountered, this is a No-Tagged-List */
648             if (list_type == unknown)
649                 list_type = no_tagged;
650
651             if ((list = dav_fetch_next_token(&str, ')')) == NULL) {
652                 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
653                                      DAV_ERR_IF_UNCLOSED_PAREN,
654                                      "Invalid If-header: unclosed \"(\".");
655             }
656
657             if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) {
658                 /* ### dav_add_if_resource() should return an error for us! */
659                 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
660                                      DAV_ERR_IF_PARSE,
661                                      "Internal server error parsing \"If:\" "
662                                      "header.");
663             }
664
665             condition = DAV_IF_COND_NORMAL;
666
667             while (*list) {
668                 /* List is the entire production (in a uri scope) */
669
670                 switch (*list) {
671                 case '<':
672                     if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) {
673                         /* ### add a description to this error */
674                         return dav_new_error(r->pool, HTTP_BAD_REQUEST,
675                                              DAV_ERR_IF_PARSE, NULL);
676                     }
677
678                     if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock,
679                                                 condition, locks_hooks)) != NULL) {
680                         /* ### maybe add a higher level description */
681                         return err;
682                     }
683                     condition = DAV_IF_COND_NORMAL;
684                     break;
685
686                 case '[':
687                     if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) {
688                         /* ### add a description to this error */
689                         return dav_new_error(r->pool, HTTP_BAD_REQUEST,
690                                              DAV_ERR_IF_PARSE, NULL);
691                     }
692
693                     if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag,
694                                                 condition, locks_hooks)) != NULL) {
695                         /* ### maybe add a higher level description */
696                         return err;
697                     }
698                     condition = DAV_IF_COND_NORMAL;
699                     break;
700
701                 case 'N':
702                     if (list[1] == 'o' && list[2] == 't') {
703                         if (condition != DAV_IF_COND_NORMAL) {
704                             return dav_new_error(r->pool, HTTP_BAD_REQUEST,
705                                                  DAV_ERR_IF_MULTIPLE_NOT,
706                                                  "Invalid \"If:\" header: "
707                                                  "Multiple \"not\" entries "
708                                                  "for the same state.");
709                         }
710                         condition = DAV_IF_COND_NOT;
711                     }
712                     list += 2;
713                     break;
714
715                 case ' ':
716                 case '\t':
717                     break;
718
719                 default:
720                     return dav_new_error(r->pool, HTTP_BAD_REQUEST,
721                                          DAV_ERR_IF_UNK_CHAR,
722                                          apr_psprintf(r->pool,
723                                                      "Invalid \"If:\" "
724                                                      "header: Unexpected "
725                                                      "character encountered "
726                                                      "(0x%02x, '%c').",
727                                                      *list, *list));
728                 }
729
730                 list++;
731             }
732             break;
733
734         case ' ':
735         case '\t':
736             break;
737
738         default:
739             return dav_new_error(r->pool, HTTP_BAD_REQUEST,
740                                  DAV_ERR_IF_UNK_CHAR,
741                                  apr_psprintf(r->pool,
742                                              "Invalid \"If:\" header: "
743                                              "Unexpected character "
744                                              "encountered (0x%02x, '%c').",
745                                              *str, *str));
746         }
747
748         str++;
749     }
750
751     *p_ih = ih;
752     return NULL;
753 }
754
755 static int dav_find_submitted_locktoken(const dav_if_header *if_header,
756                                         const dav_lock *lock_list,
757                                         const dav_hooks_locks *locks_hooks)
758 {
759     for (; if_header != NULL; if_header = if_header->next) {
760         const dav_if_state_list *state_list;
761
762         for (state_list = if_header->state;
763              state_list != NULL;
764              state_list = state_list->next) {
765
766             if (state_list->type == dav_if_opaquelock) {
767                 const dav_lock *lock;
768
769                 /* given state_list->locktoken, match it */
770
771                 /*
772                 ** The resource will have one or more lock tokens. We only
773                 ** need to match one of them against any token in the
774                 ** If: header.
775                 **
776                 ** One token case: It is an exclusive or shared lock. Either
777                 **                 way, we must find it.
778                 **
779                 ** N token case: They are shared locks. By policy, we need
780                 **               to match only one. The resource's other
781                 **               tokens may belong to somebody else (so we
782                 **               shouldn't see them in the If: header anyway)
783                 */
784                 for (lock = lock_list; lock != NULL; lock = lock->next) {
785
786                     if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
787                         return 1;
788                     }
789                 }
790             }
791         }
792     }
793
794     return 0;
795 }
796
797 /* dav_validate_resource_state:
798  *    Returns NULL if path/uri meets if-header and lock requirements
799  */
800 static dav_error * dav_validate_resource_state(apr_pool_t *p,
801                                                const dav_resource *resource,
802                                                dav_lockdb *lockdb,
803                                                const dav_if_header *if_header,
804                                                int flags,
805                                                dav_buffer *pbuf,
806                                                request_rec *r)
807 {
808     dav_error *err;
809     const char *uri;
810     const char *etag;
811     const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL);
812     const dav_if_header *ifhdr_scan;
813     dav_if_state_list *state_list;
814     dav_lock *lock_list;
815     dav_lock *lock;
816     int num_matched;
817     int num_that_apply;
818     int seen_locktoken;
819     apr_size_t uri_len;
820     const char *reason = NULL;
821
822     /* DBG1("validate: <%s>", resource->uri); */
823
824     /*
825     ** The resource will have one of three states:
826     **
827     ** 1) No locks. We have no special requirements that the user supply
828     **    specific locktokens. One of the state lists must match, and
829     **    we're done.
830     **
831     ** 2) One exclusive lock. The locktoken must appear *anywhere* in the
832     **    If: header. Of course, asserting the token in a "Not" term will
833     **    quickly fail that state list :-). If the locktoken appears in
834     **    one of the state lists *and* one state list matches, then we're
835     **    done.
836     **
837     ** 3) One or more shared locks. One of the locktokens must appear
838     **    *anywhere* in the If: header. If one of the locktokens appears,
839     **    and we match one state list, then we are done.
840     **
841     ** The <seen_locktoken> variable determines whether we have seen one
842     ** of this resource's locktokens in the If: header.
843     */
844
845     /*
846     ** If this is a new lock request, <flags> will contain the requested
847     ** lock scope.  Three rules apply:
848     **
849     ** 1) Do not require a (shared) locktoken to be seen (when we are
850     **    applying another shared lock)
851     ** 2) If the scope is exclusive and we see any locks, fail.
852     ** 3) If the scope is shared and we see an exclusive lock, fail.
853     */
854
855     if (lockdb == NULL) {
856         /* we're in State 1. no locks. */
857         lock_list = NULL;
858     }
859     else {
860         /*
861         ** ### hrm... we don't need to have these fully
862         ** ### resolved since we're only looking at the
863         ** ### locktokens...
864         **
865         ** ### use get_locks w/ calltype=PARTIAL
866         */
867         if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) {
868             return dav_push_error(p,
869                                   HTTP_INTERNAL_SERVER_ERROR, 0,
870                                   "The locks could not be queried for "
871                                   "verification against a possible \"If:\" "
872                                   "header.",
873                                   err);
874         }
875
876         /* lock_list now determines whether we're in State 1, 2, or 3. */
877     }
878
879     /* 
880     ** For a new, exclusive lock: if any locks exist, fail.
881     ** For a new, shared lock:    if an exclusive lock exists, fail.
882     **                            else, do not require a token to be seen.
883     */
884     if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
885         if (lock_list != NULL) {
886             return dav_new_error(p, HTTP_LOCKED, 0, 
887                                  "Existing lock(s) on the requested resource "
888                                  "prevent an exclusive lock.");
889         }
890
891         /*
892         ** There are no locks, so we can pretend that we've already met
893         ** any requirement to find the resource's locks in an If: header.
894         */
895         seen_locktoken = 1;
896     }
897     else if (flags & DAV_LOCKSCOPE_SHARED) {
898         /*
899         ** Strictly speaking, we don't need this loop. Either the first
900         ** (and only) lock will be EXCLUSIVE, or none of them will be.
901         */
902         for (lock = lock_list; lock != NULL; lock = lock->next) {
903             if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) {
904                 return dav_new_error(p, HTTP_LOCKED, 0,
905                                      "The requested resource is already "
906                                      "locked exclusively.");
907             }
908         }
909
910         /*
911         ** The locks on the resource (if any) are all shared. Set the
912         ** <seen_locktoken> flag to indicate that we do not need to find
913         ** the locks in an If: header.
914         */
915         seen_locktoken = 1;
916     }
917     else {
918         /*
919         ** For methods other than LOCK:
920         **
921         ** If we have no locks, then <seen_locktoken> can be set to true --
922         ** pretending that we've already met the requirement of seeing one
923         ** of the resource's locks in the If: header.
924         **
925         ** Otherwise, it must be cleared and we'll look for one.
926         */
927         seen_locktoken = (lock_list == NULL);
928     }
929
930     /*
931     ** If there is no If: header, then we can shortcut some logic:
932     **
933     ** 1) if we do not need to find a locktoken in the (non-existent) If:
934     **    header, then we are successful.
935     **
936     ** 2) if we must find a locktoken in the (non-existent) If: header, then
937     **    we fail.
938     */
939     if (if_header == NULL) {
940         if (seen_locktoken)
941             return NULL;
942
943         return dav_new_error(p, HTTP_LOCKED, 0,
944                              "This resource is locked and an \"If:\" header "
945                              "was not supplied to allow access to the "
946                              "resource.");
947     }
948     /* the If: header is present */
949
950     /*
951     ** If a dummy header is present (because of a Lock-Token: header), then
952     ** we are required to find that token in this resource's set of locks.
953     ** If we have no locks, then we immediately fail.
954     **
955     ** This is a 400 (Bad Request) since they should only submit a locktoken
956     ** that actually exists.
957     **
958     ** Don't issue this response if we're talking about the parent resource.
959     ** It is okay for that resource to NOT have this locktoken.
960     ** (in fact, it certainly will not: a dummy_header only occurs for the
961     **  UNLOCK method, the parent is checked only for locknull resources,
962     **  and the parent certainly does not have the (locknull's) locktoken)
963     */
964     if (lock_list == NULL && if_header->dummy_header) {
965         if (flags & DAV_VALIDATE_IS_PARENT)
966             return NULL;
967         return dav_new_error(p, HTTP_BAD_REQUEST, 0,
968                              "The locktoken specified in the \"Lock-Token:\" "
969                              "header is invalid because this resource has no "
970                              "outstanding locks.");
971     }
972
973     /*
974     ** Prepare the input URI. We want the URI to never have a trailing slash.
975     **
976     ** When URIs are placed into the dav_if_header structure, they are
977     ** guaranteed to never have a trailing slash. If the URIs are equivalent,
978     ** then it doesn't matter if they both lack a trailing slash -- they're
979     ** still equivalent.
980     **
981     ** Note: we could also ensure that a trailing slash is present on both
982     ** URIs, but the majority of URIs provided to us via a resource walk
983     ** will not contain that trailing slash.
984     */
985     uri = resource->uri;
986     uri_len = strlen(uri);
987     if (uri[uri_len - 1] == '/') {
988         dav_set_bufsize(p, pbuf, uri_len);
989         memcpy(pbuf->buf, uri, uri_len);
990         pbuf->buf[--uri_len] = '\0';
991         uri = pbuf->buf;
992     }
993
994     /* get the resource's etag; we may need it during the checks */
995     etag = (*resource->hooks->getetag)(resource);
996
997     /* how many state_lists apply to this URI? */
998     num_that_apply = 0;
999
1000     /* If there are if-headers, fail if this resource
1001      * does not match at least one state_list.
1002      */
1003     for (ifhdr_scan = if_header;
1004          ifhdr_scan != NULL;
1005          ifhdr_scan = ifhdr_scan->next) {
1006
1007         /* DBG2("uri=<%s>  if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
1008
1009         if (ifhdr_scan->uri != NULL
1010             && (uri_len != ifhdr_scan->uri_len
1011                 || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
1012             /*
1013             ** A tagged-list's URI doesn't match this resource's URI.
1014             ** Skip to the next state_list to see if it will match.
1015             */
1016             continue;
1017         }
1018
1019         /* this state_list applies to this resource */
1020
1021         /*
1022         ** ### only one state_list should ever apply! a no-tag, or a tagged
1023         ** ### where S9.4.2 states only one can match.
1024         **
1025         ** ### revamp this code to loop thru ifhdr_scan until we find the
1026         ** ### matching state_list. process it. stop.
1027         */
1028         ++num_that_apply;
1029
1030         /* To succeed, resource must match *all* of the states
1031          * specified in the state_list.
1032          */
1033         for (state_list = ifhdr_scan->state;
1034              state_list != NULL;
1035              state_list = state_list->next) {
1036
1037             switch(state_list->type) {
1038             case dav_if_etag:
1039             {
1040                 const char *given_etag, *current_etag;
1041                 int mismatch;
1042
1043                 /* Do a weak entity comparison function as defined in
1044                  * RFC 2616 13.3.3.
1045                  */
1046                 if (state_list->etag[0] == 'W' &&
1047                     state_list->etag[1] == '/') {
1048                     given_etag = state_list->etag + 2;
1049                 }
1050                 else {
1051                     given_etag = state_list->etag;
1052                 }
1053                 if (etag[0] == 'W' &&
1054                     etag[1] == '/') {
1055                     current_etag = etag + 2;
1056                 }
1057                 else {
1058                     current_etag = etag;
1059                 }
1060
1061                 mismatch = strcmp(given_etag, current_etag);
1062
1063                 if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
1064                     /*
1065                     ** The specified entity-tag does not match the
1066                     ** entity-tag on the resource. This state_list is
1067                     ** not going to match. Bust outta here.
1068                     */
1069                     reason =
1070                         "an entity-tag was specified, but the resource's "
1071                         "actual ETag does not match.";
1072                     goto state_list_failed;
1073                 }
1074                 else if (state_list->condition == DAV_IF_COND_NOT
1075                          && !mismatch) {
1076                     /*
1077                     ** The specified entity-tag DOES match the
1078                     ** entity-tag on the resource. This state_list is
1079                     ** not going to match. Bust outta here.
1080                     */
1081                     reason =
1082                         "an entity-tag was specified using the \"Not\" form, "
1083                         "but the resource's actual ETag matches the provided "
1084                         "entity-tag.";
1085                     goto state_list_failed;
1086                 }
1087                 break;
1088             }
1089
1090             case dav_if_opaquelock:
1091                 if (lockdb == NULL) {
1092                     if (state_list->condition == DAV_IF_COND_NOT) {
1093                         /* the locktoken is definitely not there! (success) */
1094                         continue;
1095                     }
1096
1097                     /* condition == DAV_IF_COND_NORMAL */
1098
1099                     /*
1100                     ** If no lockdb is provided, then validation fails for
1101                     ** this state_list (NORMAL means we were supposed to
1102                     ** find the token, which we obviously cannot do without
1103                     ** a lock database).
1104                     **
1105                     ** Go and try the next state list.
1106                     */
1107                     reason =
1108                         "a State-token was supplied, but a lock database "
1109                         "is not available for to provide the required lock.";
1110                     goto state_list_failed;
1111                 }
1112
1113                 /* Resource validation 'fails' if:
1114                  *    ANY  of the lock->locktokens match
1115                  *         a NOT state_list->locktoken,
1116                  * OR
1117                  *    NONE of the lock->locktokens match
1118                  *         a NORMAL state_list->locktoken.
1119                  */
1120                 num_matched = 0;
1121                 for (lock = lock_list; lock != NULL; lock = lock->next) {
1122
1123                     /*
1124                     DBG2("compare: rsrc=%s  ifhdr=%s",
1125                          (*locks_hooks->format_locktoken)(p, lock->locktoken),
1126                          (*locks_hooks->format_locktoken)(p, state_list->locktoken));
1127                     */
1128
1129                     /* nothing to do if the locktokens do not match. */
1130                     if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
1131                         continue;
1132                     }
1133
1134                     /*
1135                     ** We have now matched up one of the resource's locktokens
1136                     ** to a locktoken in a State-token in the If: header.
1137                     ** Note this fact, so that we can pass the overall
1138                     ** requirement of seeing at least one of the resource's
1139                     ** locktokens.
1140                     */
1141                     seen_locktoken = 1;
1142
1143                     if (state_list->condition == DAV_IF_COND_NOT) {
1144                         /*
1145                         ** This state requires that the specified locktoken
1146                         ** is NOT present on the resource. But we just found
1147                         ** it. There is no way this state-list can now
1148                         ** succeed, so go try another one.
1149                         */
1150                         reason =
1151                             "a State-token was supplied, which used a "
1152                             "\"Not\" condition. The State-token was found "
1153                             "in the locks on this resource";
1154                         goto state_list_failed;
1155                     }
1156
1157                     /* condition == DAV_IF_COND_NORMAL */
1158
1159                     /* Validate auth_user:  If an authenticated user created
1160                     ** the lock, only the same user may submit that locktoken
1161                     ** to manipulate a resource.
1162                     */
1163                     if (lock->auth_user && 
1164                         (!r->user ||
1165                          strcmp(lock->auth_user, r->user))) {
1166                         const char *errmsg;
1167
1168                         errmsg = apr_pstrcat(p, "User \"",
1169                                             r->user, 
1170                                             "\" submitted a locktoken created "
1171                                             "by user \"",
1172                                             lock->auth_user, "\".", NULL);
1173                         return dav_new_error(p, HTTP_FORBIDDEN, 0, errmsg);
1174                     }
1175
1176                     /*
1177                     ** We just matched a specified State-Token to one of the
1178                     ** resource's locktokens.
1179                     **
1180                     ** Break out of the lock scan -- we only needed to find
1181                     ** one match (actually, there shouldn't be any other
1182                     ** matches in the lock list).
1183                     */
1184                     num_matched = 1;
1185                     break;
1186                 }
1187
1188                 if (num_matched == 0
1189                     && state_list->condition == DAV_IF_COND_NORMAL) {
1190                     /*
1191                     ** We had a NORMAL state, meaning that we should have
1192                     ** found the State-Token within the locks on this
1193                     ** resource. We didn't, so this state_list must fail.
1194                     */
1195                     reason =
1196                         "a State-token was supplied, but it was not found "
1197                         "in the locks on this resource.";
1198                     goto state_list_failed;
1199                 }
1200
1201                 break;
1202
1203             case dav_if_unknown:
1204                 /* Request is predicated on some unknown state token,
1205                  * which must be presumed to *not* match, so fail
1206                  * unless this is a Not condition. */
1207                 
1208                 if (state_list->condition == DAV_IF_COND_NORMAL) {
1209                     reason = 
1210                         "an unknown state token was supplied";
1211                     goto state_list_failed;
1212                 }
1213                 break;
1214
1215             } /* switch */
1216         } /* foreach ( state_list ) */
1217
1218         /*
1219         ** We've checked every state in this state_list and none of them
1220         ** have failed. Since all of them succeeded, then we have a matching
1221         ** state list and we may be done.
1222         **
1223         ** The next requirement is that we have seen one of the resource's
1224         ** locktokens (if any). If we have, then we can just exit. If we
1225         ** haven't, then we need to keep looking.
1226         */
1227         if (seen_locktoken) {
1228             /* woo hoo! */
1229             return NULL;
1230         }
1231
1232         /*
1233         ** Haven't seen one. Let's break out of the search and just look
1234         ** for a matching locktoken.
1235         */
1236         break;
1237
1238         /*
1239         ** This label is used when we detect that a state_list is not
1240         ** going to match this resource. We bust out and try the next
1241         ** state_list.
1242         */
1243       state_list_failed:
1244         ;
1245
1246     } /* foreach ( ifhdr_scan ) */
1247
1248     /*
1249     ** The above loop exits for one of two reasons:
1250     **   1) a state_list matched and seen_locktoken is false.
1251     **   2) all if_header structures were scanned, without (1) occurring
1252     */
1253
1254     if (ifhdr_scan == NULL) {
1255         /*
1256         ** We finished the loop without finding any matching state lists.
1257         */
1258
1259         /*
1260         ** If none of the state_lists apply to this resource, then we
1261         ** may have succeeded. Note that this scenario implies a
1262         ** tagged-list with no matching state_lists. If the If: header
1263         ** was a no-tag-list, then it would have applied to this resource.
1264         **
1265         ** S9.4.2 states that when no state_lists apply, then the header
1266         ** should be ignored.
1267         **
1268         ** If we saw one of the resource's locktokens, then we're done.
1269         ** If we did not see a locktoken, then we fail.
1270         */
1271         if (num_that_apply == 0) {
1272             if (seen_locktoken)
1273                 return NULL;
1274
1275             /*
1276             ** We may have aborted the scan before seeing the locktoken.
1277             ** Rescan the If: header to see if we can find the locktoken
1278             ** somewhere.
1279             **
1280             ** Note that seen_locktoken == 0 implies lock_list != NULL
1281             ** which implies locks_hooks != NULL.
1282             */
1283             if (dav_find_submitted_locktoken(if_header, lock_list,
1284                                              locks_hooks)) {
1285                 /*
1286                 ** We found a match! We're set... none of the If: header
1287                 ** assertions apply (implicit success), and the If: header
1288                 ** specified the locktoken somewhere. We're done.
1289                 */
1290                 return NULL;
1291             }
1292
1293             return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */,
1294                                  "This resource is locked and the \"If:\" "
1295                                  "header did not specify one of the "
1296                                  "locktokens for this resource's lock(s).");
1297         }
1298         /* else: one or more state_lists were applicable, but failed. */
1299
1300         /*
1301         ** If the dummy_header did not match, then they specified an
1302         ** incorrect token in the Lock-Token header. Forget whether the
1303         ** If: statement matched or not... we'll tell them about the
1304         ** bad Lock-Token first. That is considered a 400 (Bad Request).
1305         */
1306         if (if_header->dummy_header) {
1307             return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1308                                  "The locktoken specified in the "
1309                                  "\"Lock-Token:\" header did not specify one "
1310                                  "of this resource's locktoken(s).");
1311         }
1312
1313         if (reason == NULL) {
1314             return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1315                                  "The preconditions specified by the \"If:\" "
1316                                  "header did not match this resource.");
1317         }
1318
1319         return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1320                              apr_psprintf(p,
1321                                          "The precondition(s) specified by "
1322                                          "the \"If:\" header did not match "
1323                                          "this resource. At least one "
1324                                          "failure is because: %s", reason));
1325     }
1326
1327     /* assert seen_locktoken == 0 */
1328
1329     /*
1330     ** ifhdr_scan != NULL implies we found a matching state_list.
1331     **
1332     ** Since we're still here, it also means that we have not yet found
1333     ** one the resource's locktokens in the If: header.
1334     **
1335     ** Scan all the if_headers and states looking for one of this
1336     ** resource's locktokens. Note that we need to go back and scan them
1337     ** all -- we may have aborted a scan with a failure before we saw a
1338     ** matching token.
1339     **
1340     ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
1341     ** locks_hooks != NULL.
1342     */
1343     if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
1344         /*
1345         ** We found a match! We're set... we have a matching state list,
1346         ** and the If: header specified the locktoken somewhere. We're done.
1347         */
1348         return NULL;
1349     }
1350
1351     /*
1352     ** We had a matching state list, but the user agent did not specify one
1353     ** of this resource's locktokens. Tell them so.
1354     **
1355     ** Note that we need to special-case the message on whether a "dummy"
1356     ** header exists. If it exists, yet we didn't see a needed locktoken,
1357     ** then that implies the dummy header (Lock-Token header) did NOT
1358     ** specify one of this resource's locktokens. (this implies something
1359     ** in the real If: header matched)
1360     **
1361     ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
1362     */
1363     if (if_header->dummy_header) {
1364         return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1365                              "The locktoken specified in the "
1366                              "\"Lock-Token:\" header did not specify one "
1367                              "of this resource's locktoken(s).");
1368     }
1369
1370     return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */,
1371                          "This resource is locked and the \"If:\" header "
1372                          "did not specify one of the "
1373                          "locktokens for this resource's lock(s).");
1374 }
1375
1376 /* dav_validate_walker:  Walker callback function to validate resource state */
1377 static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
1378 {
1379     dav_walker_ctx *ctx = wres->walk_ctx;
1380     dav_error *err;
1381
1382     if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
1383                                            ctx->w.lockdb,
1384                                            ctx->if_header, ctx->flags,
1385                                            &ctx->work_buf, ctx->r)) == NULL) {
1386         /* There was no error, so just bug out. */
1387         return NULL;
1388     }
1389
1390     /*
1391     ** If we have a serious server error, or if the request itself failed,
1392     ** then just return error (not a multistatus).
1393     */
1394     if (ap_is_HTTP_SERVER_ERROR(err->status)
1395         || (*wres->resource->hooks->is_same_resource)(wres->resource,
1396                                                       ctx->w.root)) {
1397         /* ### maybe push a higher-level description? */
1398         return err;
1399     }
1400
1401     /* associate the error with the current URI */
1402     dav_add_response(wres, err->status, NULL);
1403
1404     return NULL;
1405 }
1406
1407 /*
1408 ** dav_validate_request:  Validate if-headers (and check for locks) on:
1409 **    (1) r->filename @ depth;
1410 **    (2) Parent of r->filename if check_parent == 1
1411 **
1412 ** The check of parent should be done when it is necessary to verify that
1413 ** the parent collection will accept a new member (ie current resource
1414 ** state is null).
1415 **
1416 ** Return OK on successful validation.
1417 ** On error, return appropriate HTTP_* code, and log error. If a multi-stat
1418 ** error is necessary, response will point to it, else NULL.
1419 */
1420 DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r, 
1421                                               dav_resource *resource,
1422                                               int depth,
1423                                               dav_locktoken *locktoken,
1424                                               dav_response **response,
1425                                               int flags,
1426                                               dav_lockdb *lockdb)
1427 {
1428     dav_error *err;
1429     int result;
1430     dav_if_header *if_header;
1431     int lock_db_opened_locally = 0;
1432     const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1433     const dav_hooks_repository *repos_hooks = resource->hooks;
1434     dav_buffer work_buf = { 0 };
1435     dav_response *new_response;
1436
1437 #if DAV_DEBUG
1438     if (depth && response == NULL) {
1439         /*
1440         ** ### bleck. we can't return errors for other URIs unless we have
1441         ** ### a "response" ptr.
1442         */
1443         return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1444                              "DESIGN ERROR: dav_validate_request called "
1445                              "with depth>0, but no response ptr.");
1446     }
1447 #endif
1448
1449     if (response != NULL)
1450         *response = NULL;
1451
1452     /* Do the standard checks for conditional requests using 
1453      * If-..-Since, If-Match etc */
1454     if ((result = ap_meets_conditions(r)) != OK) {
1455         /* ### fix this up... how? */
1456         return dav_new_error(r->pool, result, 0, NULL);
1457     }
1458
1459     /* always parse (and later process) the If: header */
1460     if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1461         /* ### maybe add higher-level description */
1462         return err;
1463     }
1464
1465     /* If a locktoken was specified, create a dummy if_header with which
1466      * to validate resources.  In the interim, figure out why DAV uses
1467      * locktokens in an if-header without a Lock-Token header to refresh
1468      * locks, but a Lock-Token header without an if-header to remove them.
1469      */
1470     if (locktoken != NULL) {
1471         dav_if_header *ifhdr_new;
1472
1473         ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new));
1474         ifhdr_new->uri = resource->uri;
1475         ifhdr_new->uri_len = strlen(resource->uri);
1476         ifhdr_new->dummy_header = 1;
1477
1478         ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state));
1479         ifhdr_new->state->type = dav_if_opaquelock;
1480         ifhdr_new->state->condition = DAV_IF_COND_NORMAL;
1481         ifhdr_new->state->locktoken = locktoken;
1482
1483         ifhdr_new->next = if_header;
1484         if_header = ifhdr_new;
1485     }
1486
1487     /*
1488     ** If necessary, open the lock database (read-only, lazily);
1489     ** the validation process may need to retrieve or update lock info.
1490     ** Otherwise, assume provided lockdb is valid and opened rw.
1491     */
1492     if (lockdb == NULL) {
1493         if (locks_hooks != NULL) {
1494             if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1495                 /* ### maybe insert higher-level comment */
1496                 return err;
1497             }
1498             lock_db_opened_locally = 1;
1499         }
1500     }
1501
1502     /* (1) Validate the specified resource, at the specified depth */
1503     if (resource->exists && depth > 0) {
1504         dav_walker_ctx ctx = { { 0 } };
1505         dav_response *multi_status;
1506
1507         ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
1508         ctx.w.func = dav_validate_walker;
1509         ctx.w.walk_ctx = &ctx;
1510         ctx.w.pool = r->pool;
1511         ctx.w.root = resource;
1512
1513         ctx.if_header = if_header;
1514         ctx.r = r;
1515         ctx.flags = flags;
1516
1517         if (lockdb != NULL) {
1518             ctx.w.lockdb = lockdb;
1519             ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1520         }
1521
1522         err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
1523         if (err == NULL) {
1524             *response = multi_status;;
1525         }
1526         /* else: implies a 5xx status code occurred. */
1527     }
1528     else {
1529         err = dav_validate_resource_state(r->pool, resource, lockdb,
1530                                           if_header, flags, &work_buf, r);
1531     }
1532
1533     /* (2) Validate the parent resource if requested */
1534     if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
1535         dav_resource *parent_resource;
1536
1537         err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
1538
1539         if (err == NULL && parent_resource == NULL) {
1540             err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
1541                                 "Cannot access parent of repository root.");
1542         }
1543         else if (err == NULL) {
1544             err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
1545                                               if_header,
1546                                               flags | DAV_VALIDATE_IS_PARENT,
1547                                               &work_buf, r);
1548             
1549             /*
1550             ** This error occurred on the parent resource. This implies that
1551             ** we have to create a multistatus response (to report the error
1552             ** against a URI other than the Request-URI). "Convert" this error
1553             ** into a multistatus response.
1554             */
1555             if (err != NULL) {
1556                 new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1557                 
1558                 new_response->href = parent_resource->uri;
1559                 new_response->status = err->status;
1560                 new_response->desc =
1561                     "A validation error has occurred on the parent resource, "
1562                     "preventing the operation on the resource specified by "
1563                     "the Request-URI.";
1564                 if (err->desc != NULL) {
1565                     new_response->desc = apr_pstrcat(r->pool,
1566                                                     new_response->desc,
1567                                                     " The error was: ",
1568                                                     err->desc, NULL);
1569                 }
1570                 
1571                 /* assert: DAV_VALIDATE_PARENT implies response != NULL */
1572                 new_response->next = *response;
1573                 *response = new_response;
1574                 
1575                 err = NULL;
1576             }
1577         }
1578     }
1579
1580     if (lock_db_opened_locally)
1581         (*locks_hooks->close_lockdb)(lockdb);
1582
1583     /*
1584     ** If we don't have a (serious) error, and we have multistatus responses,
1585     ** then we need to construct an "error". This error will be the overall
1586     ** status returned, and the multistatus responses will go into its body.
1587     **
1588     ** For certain methods, the overall error will be a 424. The default is
1589     ** to construct a standard 207 response.
1590     */
1591     if (err == NULL && response != NULL && *response != NULL) {
1592         apr_text *propstat = NULL;
1593
1594         if ((flags & DAV_VALIDATE_USE_424) != 0) {
1595             /* manufacture a 424 error to hold the multistatus response(s) */
1596             return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0,
1597                                  "An error occurred on another resource, "
1598                                  "preventing the requested operation on "
1599                                  "this resource.");
1600         }
1601
1602         /*
1603         ** Whatever caused the error, the Request-URI should have a 424
1604         ** associated with it since we cannot complete the method.
1605         **
1606         ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
1607         ** For other methods, return a simple 424.
1608         */
1609         if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
1610             propstat = apr_pcalloc(r->pool, sizeof(*propstat));
1611             propstat->text =
1612                 "<D:propstat>" DEBUG_CR
1613                 "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
1614                 "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
1615                 "</D:propstat>" DEBUG_CR;
1616         }
1617
1618         /* create the 424 response */
1619         new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1620         new_response->href = resource->uri;
1621         new_response->status = HTTP_FAILED_DEPENDENCY;
1622         new_response->propresult.propstats = propstat;
1623         new_response->desc =
1624             "An error occurred on another resource, preventing the "
1625             "requested operation on this resource.";
1626
1627         new_response->next = *response;
1628         *response = new_response;
1629
1630         /* manufacture a 207 error for the multistatus response(s) */
1631         return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
1632                              "Error(s) occurred on resources during the "
1633                              "validation process.");
1634     }
1635
1636     return err;
1637 }
1638
1639 /* dav_get_locktoken_list:
1640  *
1641  * Sets ltl to a locktoken_list of all positive locktokens in header,
1642  * else NULL if no If-header, or no positive locktokens.
1643  */
1644 DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r,
1645                                                 dav_locktoken_list **ltl) 
1646 {
1647     dav_error *err;
1648     dav_if_header *if_header;
1649     dav_if_state_list *if_state;
1650     dav_locktoken_list *lock_token = NULL;                
1651         
1652     *ltl = NULL;
1653
1654     if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1655         /* ### add a higher-level description? */
1656         return err;
1657     }
1658                          
1659     while (if_header != NULL) {
1660         if_state = if_header->state;        /* Begining of the if_state linked list */
1661         while (if_state != NULL)        {
1662             if (if_state->condition == DAV_IF_COND_NORMAL
1663                 && if_state->type == dav_if_opaquelock) {
1664                 lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list));
1665                 lock_token->locktoken = if_state->locktoken;
1666                 lock_token->next = *ltl;
1667                 *ltl = lock_token;
1668             }
1669             if_state = if_state->next; 
1670         }
1671         if_header = if_header->next;
1672     }
1673     if (*ltl == NULL) {
1674         /* No nodes added */
1675         return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT,
1676                              "No locktokens were specified in the \"If:\" "
1677                              "header, so the refresh could not be performed.");
1678     }
1679
1680     return NULL;
1681 }
1682
1683 #if 0 /* not needed right now... */
1684
1685 static const char *strip_white(const char *s, apr_pool_t *pool)
1686 {
1687     apr_size_t idx;
1688
1689     /* trim leading whitespace */
1690     while (apr_isspace(*s))     /* assume: return false for '\0' */
1691         ++s;
1692
1693     /* trim trailing whitespace */
1694     idx = strlen(s) - 1;
1695     if (apr_isspace(s[idx])) {
1696         char *s2 = apr_pstrdup(pool, s);
1697
1698         while (apr_isspace(s2[idx]) && idx > 0)
1699             --idx;
1700         s2[idx + 1] = '\0';
1701         return s2;
1702     }
1703
1704     return s;
1705 }
1706 #endif
1707
1708 #define DAV_LABEL_HDR "Label"
1709
1710 /* dav_add_vary_header
1711  *
1712  * If there were any headers in the request which require a Vary header
1713  * in the response, add it.
1714  */
1715 DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req,
1716                                       request_rec *out_req,
1717                                       const dav_resource *resource)
1718 {
1719     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
1720
1721     /* ### this is probably all wrong... I think there is a function in
1722        ### the Apache API to add things to the Vary header. need to check */
1723
1724     /* Only versioning headers require a Vary response header,
1725      * so only do this check if there is a versioning provider */
1726     if (vsn_hooks != NULL) {
1727         const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR);
1728         const char *vary = apr_table_get(out_req->headers_out, "Vary");
1729
1730         /* If Target-Selector specified, add it to the Vary header */
1731         if (target != NULL) {
1732             if (vary == NULL)
1733                 vary = DAV_LABEL_HDR;
1734             else
1735                 vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
1736                                    NULL);
1737
1738             apr_table_setn(out_req->headers_out, "Vary", vary);
1739         }
1740     }
1741 }
1742
1743 /* dav_can_auto_checkout
1744  *
1745  * Determine whether auto-checkout is enabled for a resource.
1746  * r - the request_rec
1747  * resource - the resource
1748  * auto_version - the value of the auto_versionable hook for the resource
1749  * lockdb - pointer to lock database (opened if necessary)
1750  * auto_checkout - set to 1 if auto-checkout enabled
1751  */
1752 static dav_error * dav_can_auto_checkout(
1753     request_rec *r,                                         
1754     dav_resource *resource,
1755     dav_auto_version auto_version,
1756     dav_lockdb **lockdb,
1757     int *auto_checkout)
1758 {
1759     dav_error *err;
1760     dav_lock *lock_list;
1761
1762     *auto_checkout = 0;
1763
1764     if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
1765         *auto_checkout = 1;
1766     }
1767     else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
1768         if (*lockdb == NULL) {
1769             const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1770
1771             if (locks_hooks == NULL) {
1772                 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1773                                      "Auto-checkout is only enabled for locked resources, "
1774                                      "but there is no lock provider.");
1775             }
1776
1777             if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) {
1778                 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1779                                       "Cannot open lock database to determine "
1780                                       "auto-versioning behavior.",
1781                                       err);
1782             }
1783         }
1784
1785         if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) {
1786             return dav_push_error(r->pool,
1787                                   HTTP_INTERNAL_SERVER_ERROR, 0,
1788                                   "The locks could not be queried for "
1789                                   "determining auto-versioning behavior.",
1790                                   err);
1791         }
1792
1793         if (lock_list != NULL)
1794             *auto_checkout = 1;
1795     }
1796
1797     return NULL;
1798 }
1799
1800 /* see mod_dav.h for docco */
1801 DAV_DECLARE(dav_error *) dav_auto_checkout(
1802     request_rec *r,
1803     dav_resource *resource,
1804     int parent_only,
1805     dav_auto_version_info *av_info)
1806 {
1807     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1808     dav_lockdb *lockdb = NULL;
1809     dav_error *err = NULL;
1810
1811     /* Initialize results */
1812     memset(av_info, 0, sizeof(*av_info));
1813
1814     /* if no versioning provider, just return */
1815     if (vsn_hooks == NULL)
1816         return NULL;
1817
1818     /* check parent resource if requested or if resource must be created */
1819     if (!resource->exists || parent_only) {
1820         dav_resource *parent;
1821
1822         if ((err = (*resource->hooks->get_parent_resource)(resource,
1823                                                            &parent)) != NULL)
1824             goto done;
1825
1826         if (parent == NULL || !parent->exists) {
1827             err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1828                                 apr_psprintf(r->pool,
1829                                             "Missing one or more intermediate "
1830                                             "collections. Cannot create resource %s.",
1831                                             ap_escape_html(r->pool, resource->uri)));
1832             goto done;
1833         }
1834
1835         av_info->parent_resource = parent;
1836
1837         /* if parent versioned and not checked out, see if it can be */
1838         if (parent->versioned && !parent->working) {
1839             int checkout_parent;
1840
1841             if ((err = dav_can_auto_checkout(r, parent,
1842                                              (*vsn_hooks->auto_versionable)(parent),
1843                                              &lockdb, &checkout_parent))
1844                 != NULL) {
1845                 goto done;
1846             }
1847
1848             if (!checkout_parent) {
1849                 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1850                                     "<DAV:cannot-modify-checked-in-parent>");
1851                 goto done;
1852             }
1853
1854             /* Try to checkout the parent collection.
1855              * Note that auto-versioning can only be applied to a version selector,
1856              * so no separate working resource will be created.
1857              */
1858             if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
1859                                               0, 0, 0, NULL, NULL))
1860                 != NULL)
1861             {
1862                 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1863                                      apr_psprintf(r->pool,
1864                                                  "Unable to auto-checkout parent collection. "
1865                                                  "Cannot create resource %s.",
1866                                                  ap_escape_html(r->pool, resource->uri)),
1867                                      err);
1868                 goto done;
1869             }
1870
1871             /* remember that parent was checked out */
1872             av_info->parent_checkedout = 1;
1873         }
1874     }
1875
1876     /* if only checking parent, we're done */
1877     if (parent_only)
1878         goto done;
1879
1880     /* if creating a new resource, see if it should be version-controlled */
1881     if (!resource->exists
1882         && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) {
1883
1884         if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
1885             err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1886                                  apr_psprintf(r->pool,
1887                                              "Unable to create versioned resource %s.",
1888                                              ap_escape_html(r->pool, resource->uri)),
1889                                  err);
1890             goto done;
1891         }
1892
1893         /* remember that resource was created */
1894         av_info->resource_versioned = 1;
1895     }
1896
1897     /* if resource is versioned, make sure it is checked out */
1898     if (resource->versioned && !resource->working) {
1899         int checkout_resource;
1900
1901         if ((err = dav_can_auto_checkout(r, resource,
1902                                          (*vsn_hooks->auto_versionable)(resource),
1903                                          &lockdb, &checkout_resource)) != NULL) {
1904             goto done;
1905         }
1906
1907         if (!checkout_resource) {
1908             err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1909                                 "<DAV:cannot-modify-version-controlled-content>");
1910             goto done;
1911         }
1912
1913         /* Auto-versioning can only be applied to version selectors, so
1914          * no separate working resource will be created. */
1915         if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/,
1916                                           0, 0, 0, NULL, NULL))
1917             != NULL)
1918         {
1919             err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1920                                  apr_psprintf(r->pool,
1921                                              "Unable to checkout resource %s.",
1922                                              ap_escape_html(r->pool, resource->uri)),
1923                                  err);
1924             goto done;
1925         }
1926
1927         /* remember that resource was checked out */
1928         av_info->resource_checkedout = 1;
1929     }
1930
1931 done:
1932
1933     /* make sure lock database is closed */
1934     if (lockdb != NULL)
1935         (*lockdb->hooks->close_lockdb)(lockdb);
1936
1937     /* if an error occurred, undo any auto-versioning operations already done */
1938     if (err != NULL) {
1939         dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
1940         return err;
1941     }
1942
1943     return NULL;
1944 }
1945
1946 /* see mod_dav.h for docco */
1947 DAV_DECLARE(dav_error *) dav_auto_checkin(
1948     request_rec *r,
1949     dav_resource *resource,
1950     int undo,
1951     int unlock,
1952     dav_auto_version_info *av_info)
1953 {
1954     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1955     dav_error *err = NULL;
1956     dav_auto_version auto_version;
1957
1958     /* If no versioning provider, this is a no-op */
1959     if (vsn_hooks == NULL)
1960         return NULL;
1961
1962     /* If undoing auto-checkouts, then do uncheckouts */
1963     if (undo) {
1964         if (resource != NULL) {
1965             if (av_info->resource_checkedout) {
1966                 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
1967                     return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1968                                           apr_psprintf(r->pool,
1969                                                       "Unable to undo auto-checkout "
1970                                                       "of resource %s.",
1971                                                       ap_escape_html(r->pool, resource->uri)),
1972                                           err);
1973                 }
1974             }
1975
1976             if (av_info->resource_versioned) {
1977                 dav_response *response;
1978
1979                 /* ### should we do anything with the response? */
1980                 if ((err = (*resource->hooks->remove_resource)(resource,
1981                                                                &response)) != NULL) {
1982                     return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1983                                           apr_psprintf(r->pool,
1984                                                       "Unable to undo auto-version-control "
1985                                                       "of resource %s.",
1986                                                       ap_escape_html(r->pool, resource->uri)),
1987                                           err);
1988                 }
1989             }
1990         }
1991
1992         if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
1993             if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) {
1994                 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1995                                       apr_psprintf(r->pool,
1996                                                   "Unable to undo auto-checkout "
1997                                                   "of parent collection %s.",
1998                                                   ap_escape_html(r->pool, av_info->parent_resource->uri)),
1999                                       err);
2000             }
2001         }
2002
2003         return NULL;
2004     }
2005
2006     /* If the resource was checked out, and auto-checkin is enabled,
2007      * then check it in.
2008      */
2009     if (resource != NULL && resource->working
2010         && (unlock || av_info->resource_checkedout)) {
2011
2012         auto_version = (*vsn_hooks->auto_versionable)(resource);
2013
2014         if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
2015             (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
2016
2017             if ((err = (*vsn_hooks->checkin)(resource,
2018                                              0 /*keep_checked_out*/, NULL))
2019                 != NULL) {
2020                 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2021                                       apr_psprintf(r->pool,
2022                                                   "Unable to auto-checkin resource %s.",
2023                                                   ap_escape_html(r->pool, resource->uri)),
2024                                       err);
2025             }
2026         }
2027     }
2028
2029     /* If parent resource was checked out, and auto-checkin is enabled,
2030      * then check it in.
2031      */
2032     if (!unlock
2033         && av_info->parent_checkedout
2034         && av_info->parent_resource != NULL
2035         && av_info->parent_resource->working) {
2036
2037         auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
2038
2039         if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
2040             if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
2041                                              0 /*keep_checked_out*/, NULL))
2042                 != NULL) {
2043                 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2044                                       apr_psprintf(r->pool,
2045                                                   "Unable to auto-checkin parent collection %s.",
2046                                                   ap_escape_html(r->pool, av_info->parent_resource->uri)),
2047                                                   err);
2048             }
2049         }
2050     }
2051
2052     return NULL;
2053 }