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