]> granicus.if.org Git - apache/blob - modules/dav/main/mod_dav.c
360b66f4f914b8461b73e91f6f8f277f5648bb45
[apache] / modules / dav / main / mod_dav.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  */
54
55 /*
56 ** DAV extension module for Apache 2.0.*
57 **
58 ** This module is repository-independent. It depends on hooks provided by a
59 ** repository implementation.
60 **
61 ** APACHE ISSUES:
62 **   - within a DAV hierarchy, if an unknown method is used and we default
63 **     to Apache's implementation, it sends back an OPTIONS with the wrong
64 **     set of methods -- there is NO HOOK for us.
65 **     therefore: we need to manually handle the HTTP_METHOD_NOT_ALLOWED
66 **       and HTTP_NOT_IMPLEMENTED responses (not ap_send_error_response).
67 **   - process_mkcol_body() had to dup code from ap_setup_client_block().
68 **   - it would be nice to get status lines from Apache for arbitrary
69 **     status codes
70 **   - it would be nice to be able to extend Apache's set of response
71 **     codes so that it doesn't return 500 when an unknown code is placed
72 **     into r->status.
73 **   - http_vhost functions should apply "const" to their params
74 **
75 ** DESIGN NOTES:
76 **   - For PROPFIND, we batch up the entire response in memory before
77 **     sending it. We may want to reorganize around sending the information
78 **     as we suck it in from the propdb. Alternatively, we should at least
79 **     generate a total Content-Length if we're going to buffer in memory
80 **     so that we can keep the connection open.
81 */
82
83 #include "httpd.h"
84 #include "http_config.h"
85 #include "http_core.h"
86 #include "http_log.h"
87 #include "http_main.h"
88 #include "http_protocol.h"
89 #include "http_request.h"
90 #include "util_script.h"
91 #include "apr_strings.h"
92
93 #include "mod_dav.h"
94
95
96 /* ### what is the best way to set this? */
97 #define DAV_DEFAULT_PROVIDER    "filesystem"
98
99 enum {
100     DAV_ENABLED_UNSET = 0,
101     DAV_ENABLED_OFF,
102     DAV_ENABLED_ON
103 };
104
105 /* per-dir configuration */
106 typedef struct {
107     const char *provider_name;
108     const dav_provider *provider;
109     const char *dir;
110     int locktimeout;
111     int allow_depthinfinity;
112
113     apr_table_t *d_params;      /* per-directory DAV config parameters */
114
115 } dav_dir_conf;
116
117 /* per-server configuration */
118 typedef struct {
119     int unused;
120
121 } dav_server_conf;
122
123 #define DAV_INHERIT_VALUE(parent, child, field) \
124                 ((child)->field ? (child)->field : (parent)->field)
125
126
127 /* forward-declare for use in configuration lookup */
128 extern module DAV_DECLARE_DATA dav_module;
129
130 static void dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
131                              server_rec *s)
132 {
133     /* DBG0("dav_init_handler"); */
134
135     ap_add_version_component(p, "DAV/2");
136 }
137
138 static void *dav_create_server_config(apr_pool_t *p, server_rec *s)
139 {
140     dav_server_conf *newconf;
141
142     newconf = (dav_server_conf *) apr_pcalloc(p, sizeof(*newconf));
143
144     /* ### this isn't used at the moment... */
145
146     return newconf;
147 }
148
149 static void *dav_merge_server_config(apr_pool_t *p, void *base, void *overrides)
150 {
151 #if 0
152     dav_server_conf *child = overrides;
153 #endif
154     dav_server_conf *newconf;
155
156     newconf = (dav_server_conf *) apr_pcalloc(p, sizeof(*newconf));
157
158     /* ### nothing to merge right now... */
159
160     return newconf;
161 }
162
163 static void *dav_create_dir_config(apr_pool_t *p, char *dir)
164 {
165     /* NOTE: dir==NULL creates the default per-dir config */
166
167     dav_dir_conf *conf;
168
169     conf = (dav_dir_conf *) apr_pcalloc(p, sizeof(*conf));
170
171     /* clean up the directory to remove any trailing slash */
172     if (dir != NULL) {
173         char *d;
174         apr_size_t l;
175
176         d = apr_pstrdup(p, dir);
177         l = strlen(d);
178         if (l > 1 && d[l - 1] == '/')
179             d[l - 1] = '\0';
180         conf->dir = d;
181     }
182
183     conf->d_params = apr_make_table(p, 1);
184
185     return conf;
186 }
187
188 static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
189 {
190     dav_dir_conf *parent = base;
191     dav_dir_conf *child = overrides;
192     dav_dir_conf *newconf = (dav_dir_conf *) apr_pcalloc(p, sizeof(*newconf));
193
194     /* DBG3("dav_merge_dir_config: new=%08lx  base=%08lx  overrides=%08lx",
195        (long)newconf, (long)base, (long)overrides); */
196
197     newconf->provider_name = DAV_INHERIT_VALUE(parent, child, provider_name);
198     newconf->provider = DAV_INHERIT_VALUE(parent, child, provider);
199     if (parent->provider_name != NULL) {
200         if (child->provider_name == NULL) {
201             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, NULL,
202                          "\"DAV Off\" cannot be used to turn off a subtree "
203                          "of a DAV-enabled location.");
204         }
205         else if (strcasecmp(child->provider_name,
206                             parent->provider_name) != 0) {
207             ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, NULL,
208                          "A subtree cannot specify a different DAV provider "
209                          "than its parent.");
210         }
211     }
212
213     newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
214     newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
215     newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
216                                                      allow_depthinfinity);
217
218     newconf->d_params = apr_copy_table(p, parent->d_params);
219     apr_overlap_tables(newconf->d_params, child->d_params,
220                       APR_OVERLAP_TABLES_SET);
221
222     return newconf;
223 }
224
225 apr_table_t *dav_get_dir_params(const request_rec *r)
226 {
227     dav_dir_conf *conf;
228
229     conf = ap_get_module_config(r->per_dir_config, &dav_module);
230     return conf->d_params;
231 }
232
233 static const dav_provider * dav_get_provider(request_rec *r)
234 {
235     dav_dir_conf *conf;
236
237     conf = ap_get_module_config(r->per_dir_config, &dav_module);
238     /* assert: conf->provider_name != NULL
239        (otherwise, DAV is disabled, and we wouldn't be here) */
240
241     /* assert: conf->provider != NULL
242        (checked when conf->provider_name is set) */
243     return conf->provider;
244 }
245
246 const dav_hooks_locks *dav_get_lock_hooks(request_rec *r)
247 {
248     return dav_get_provider(r)->locks;
249 }
250
251 const dav_hooks_propdb *dav_get_propdb_hooks(request_rec *r)
252 {
253     return dav_get_provider(r)->propdb;
254 }
255
256 const dav_hooks_vsn *dav_get_vsn_hooks(request_rec *r)
257 {
258     return dav_get_provider(r)->vsn;
259 }
260
261 const dav_hooks_binding *dav_get_binding_hooks(request_rec *r)
262 {
263     return dav_get_provider(r)->binding;
264 }
265
266 /*
267  * Command handler for the DAV directive, which is TAKE1.
268  */
269 static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
270 {
271     dav_dir_conf *conf = (dav_dir_conf *) config;
272
273     if (strcasecmp(arg1, "on") == 0) {
274         conf->provider_name = DAV_DEFAULT_PROVIDER;
275     }
276     else if (strcasecmp(arg1, "off") == 0) {
277         conf->provider_name = NULL;
278         conf->provider = NULL;
279     }
280     else {
281         conf->provider_name = apr_pstrdup(cmd->pool, arg1);
282     }
283
284     if (conf->provider_name != NULL) {
285         /* lookup and cache the actual provider now */
286         conf->provider = dav_lookup_provider(conf->provider_name);
287
288         if (conf->provider == NULL) {
289             /* by the time they use it, the provider should be loaded and
290                registered with us. */
291             return apr_psprintf(cmd->pool,
292                                 "Unknown DAV provider: %s",
293                                 conf->provider_name);
294         }
295     }
296
297     return NULL;
298 }
299
300 /*
301  * Command handler for the DAVDepthInfinity directive, which is FLAG.
302  */
303 static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
304                                             int arg)
305 {
306     dav_dir_conf *conf = (dav_dir_conf *) config;
307
308     if (arg)
309         conf->allow_depthinfinity = DAV_ENABLED_ON;
310     else
311         conf->allow_depthinfinity = DAV_ENABLED_OFF;
312     return NULL;
313 }
314
315 /*
316  * Command handler for DAVMinTimeout directive, which is TAKE1
317  */
318 static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config,
319                                          const char *arg1)
320 {
321     dav_dir_conf *conf = (dav_dir_conf *) config;
322
323     conf->locktimeout = atoi(arg1);
324     if (conf->locktimeout < 0)
325         return "DAVMinTimeout requires a non-negative integer.";
326
327     return NULL;
328 }
329
330 /*
331  * Command handler for DAVParam directive, which is TAKE2
332  */
333 static const char *dav_cmd_davparam(cmd_parms *cmd, void *config,
334                                     const char *arg1, const char *arg2)
335 {
336     dav_dir_conf *conf = (dav_dir_conf *) config;
337
338     apr_table_set(conf->d_params, arg1, arg2);
339
340     return NULL;
341 }
342
343 /*
344 ** dav_error_response()
345 **
346 ** Send a nice response back to the user. In most cases, Apache doesn't
347 ** allow us to provide details in the body about what happened. This
348 ** function allows us to completely specify the response body.
349 */
350 static int dav_error_response(request_rec *r, int status, const char *body)
351 {
352     r->status = status;
353     r->content_type = "text/html";
354
355     /* since we're returning DONE, ensure the request body is consumed. */
356     (void) ap_discard_request_body(r);
357
358     /* begin the response now... */
359     ap_send_http_header(r);
360
361     ap_rvputs(r,
362               DAV_RESPONSE_BODY_1,
363               r->status_line,
364               DAV_RESPONSE_BODY_2,
365               &r->status_line[4],
366               DAV_RESPONSE_BODY_3,
367               NULL);
368
369     ap_rputs(body, r);
370
371     ap_rputs(ap_psignature("\n<P><HR>\n", r), r);
372     ap_rputs(DAV_RESPONSE_BODY_4, r);
373
374     /* the response has been sent. */
375     /*
376      * ### Use of DONE obviates logging..!
377      */
378     return DONE;
379 }
380
381 /*
382 ** Apache's URI escaping does not replace '&' since that is a valid character
383 ** in a URI (to form a query section). We must explicitly handle it so that
384 ** we can embed the URI into an XML document.
385 */
386 static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri)
387 {
388     const char *e_uri = ap_escape_uri(p, uri);
389
390     /* check the easy case... */
391     if (ap_strchr_c(e_uri, '&') == NULL)
392         return e_uri;
393
394     /* more work needed... sigh. */
395
396     /*
397     ** Note: this is a teeny bit of overkill since we know there are no
398     ** '<' or '>' characters, but who cares.
399     */
400     return ap_xml_quote_string(p, e_uri, 0);
401 }
402
403 static void dav_send_multistatus(request_rec *r, int status,
404                                  dav_response *first,
405                                  apr_array_header_t *namespaces)
406 {
407     /* Set the correct status and Content-Type */
408     r->status = status;
409     r->content_type = DAV_XML_CONTENT_TYPE;
410
411     /* Send all of the headers now */
412     ap_send_http_header(r);
413
414     /* Send the actual multistatus response now... */
415     ap_rputs(DAV_XML_HEADER DEBUG_CR
416              "<D:multistatus xmlns:D=\"DAV:\"", r);
417
418     if (namespaces != NULL) {
419         int i;
420
421         for (i = namespaces->nelts; i--; ) {
422             ap_rprintf(r, " xmlns:ns%d=\"%s\"", i,
423                        AP_XML_GET_URI_ITEM(namespaces, i));
424         }
425     }
426
427     /* ap_rputc('>', r); */
428     ap_rputs(">" DEBUG_CR, r);
429
430     for (; first != NULL; first = first->next) {
431         ap_text *t;
432
433         if (first->propresult.xmlns == NULL) {
434             ap_rputs("<D:response>", r);
435         }
436         else {
437             ap_rputs("<D:response", r);
438             for (t = first->propresult.xmlns; t; t = t->next) {
439                 ap_rputs(t->text, r);
440             }
441             ap_rputc('>', r);
442         }
443
444         ap_rputs(DEBUG_CR "<D:href>", r);
445         ap_rputs(dav_xml_escape_uri(r->pool, first->href), r);
446         ap_rputs("</D:href>" DEBUG_CR, r);
447
448         if (first->propresult.propstats == NULL) {
449             /* use the Status-Line text from Apache.  Note, this will
450              * default to 500 Internal Server Error if first->status
451              * is not a known (or valid) status code. */
452             ap_rprintf(r,
453                        "<D:status>HTTP/1.1 %s</D:status>" DEBUG_CR, 
454                        ap_get_status_line(first->status));
455         }
456         else {
457             /* assume this includes <propstat> and is quoted properly */
458             for (t = first->propresult.propstats; t; t = t->next) {
459                 ap_rputs(t->text, r);
460             }
461         }
462
463         if (first->desc != NULL) {
464             /*
465             ** We supply the description, so we know it doesn't have to
466             ** have any escaping/encoding applied to it.
467             */
468             ap_rputs("<D:responsedescription>", r);
469             ap_rputs(first->desc, r);
470             ap_rputs("</D:responsedescription>" DEBUG_CR, r);
471         }
472
473         ap_rputs("</D:response>" DEBUG_CR, r);
474     }
475
476     ap_rputs("</D:multistatus>" DEBUG_CR, r);
477 }
478
479 /*
480 ** dav_log_err()
481 **
482 ** Write error information to the log.
483 */
484 static void dav_log_err(request_rec *r, dav_error *err, int level)
485 {
486     dav_error *errscan;
487
488     /* Log the errors */
489     /* ### should have a directive to log the first or all */
490     for (errscan = err; errscan != NULL; errscan = errscan->prev) {
491         if (errscan->desc == NULL)
492             continue;
493         if (errscan->save_errno != 0) {
494             errno = errscan->save_errno;
495             ap_log_rerror(APLOG_MARK, level, errno, r, "%s  [%d, #%d]",
496                           errscan->desc, errscan->status, errscan->error_id);
497         }
498         else {
499             ap_log_rerror(APLOG_MARK, level | APLOG_NOERRNO, 0, r,
500                           "%s  [%d, #%d]",
501                           errscan->desc, errscan->status, errscan->error_id);
502         }
503     }
504 }
505
506 /*
507 ** dav_handle_err()
508 **
509 ** Handle the standard error processing. <err> must be non-NULL.
510 **
511 ** <response> is set by the following:
512 **   - dav_validate_request()
513 **   - dav_add_lock()
514 **   - repos_hooks->remove_resource
515 **   - repos_hooks->move_resource
516 **   - repos_hooks->copy_resource
517 */
518 static int dav_handle_err(request_rec *r, dav_error *err,
519                           dav_response *response)
520 {
521     /* log the errors */
522     dav_log_err(r, err, APLOG_ERR);
523
524     if (response == NULL) {
525         /* our error messages are safe; tell Apache this */
526         apr_table_setn(r->notes, "verbose-error-to", "*");
527         return err->status;
528     }
529
530     /* since we're returning DONE, ensure the request body is consumed. */
531     (void) ap_discard_request_body(r);
532
533     /* send the multistatus and tell Apache the request/response is DONE. */
534     dav_send_multistatus(r, err->status, response, NULL);
535     return DONE;
536 }
537
538 /* handy function for return values of methods that (may) create things */
539 static int dav_created(request_rec *r, const char *locn, const char *what,
540                        int replaced)
541 {
542     const char *body;
543
544     if (locn == NULL) {
545         locn = r->uri;
546     }
547
548     /* did the target resource already exist? */
549     if (replaced) {
550         /* Apache will supply a default message */
551         return HTTP_NO_CONTENT;
552     }
553
554     /* Per HTTP/1.1, S10.2.2: add a Location header to contain the
555      * URI that was created. */
556
557     /* Convert locn to an absolute URI, and return in Location header */
558     apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, locn, r));
559
560     /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */
561
562     /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
563      * we must manufacture the entire response. */
564     body = apr_psprintf(r->pool, "%s %s has been created.",
565                         what, ap_escape_html(r->pool, locn));
566     return dav_error_response(r, HTTP_CREATED, body);
567 }
568
569 /* ### move to dav_util? */
570 int dav_get_depth(request_rec *r, int def_depth)
571 {
572     const char *depth = apr_table_get(r->headers_in, "Depth");
573
574     if (depth == NULL) {
575         return def_depth;
576     }
577     if (strcasecmp(depth, "infinity") == 0) {
578         return DAV_INFINITY;
579     }
580     else if (strcmp(depth, "0") == 0) {
581         return 0;
582     }
583     else if (strcmp(depth, "1") == 0) {
584         return 1;
585     }
586
587     /* The caller will return an HTTP_BAD_REQUEST. This will augment the
588      * default message that Apache provides. */
589     ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
590                   "An invalid Depth header was specified.");
591     return -1;
592 }
593
594 static int dav_get_overwrite(request_rec *r)
595 {
596     const char *overwrite = apr_table_get(r->headers_in, "Overwrite");
597
598     if (overwrite == NULL) {
599         return 1;               /* default is "T" */
600     }
601
602     if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') {
603         return 0;
604     }
605     if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') {
606         return 1;
607     }
608
609     /* The caller will return an HTTP_BAD_REQUEST. This will augment the
610      * default message that Apache provides. */
611     ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
612                   "An invalid Overwrite header was specified.");
613     return -1;
614 }
615
616 /* resolve a request URI to a resource descriptor.
617  * If target_allowed != 0, then allow the request target to be overridden
618  * by either a DAV:version or DAV:label-name element (passed as
619  * the target argument), or any Target-Selector header in the request.
620  */
621 static int dav_get_resource(request_rec *r, int target_allowed,
622                             ap_xml_elem *target, dav_resource **res_p)
623 {
624     void *data;
625     dav_dir_conf *conf;
626     const char *target_selector = NULL;
627     int is_label = 0;
628     int result;
629
630     /* go look for the resource if it isn't already present */
631     (void) apr_get_userdata(&data, DAV_KEY_RESOURCE, r->pool);
632     if (data != NULL) {
633         *res_p = data;
634         return OK;
635     }
636
637     /* if the request target can be overridden, get any target selector */
638     if (target_allowed) {
639         if ((result = dav_get_target_selector(r, target,
640                                               &target_selector,
641                                               &is_label)) != OK)
642             return result;
643     }
644
645     conf = ap_get_module_config(r->per_dir_config, &dav_module);
646     /* assert: conf->provider != NULL */
647
648     /* resolve the resource */
649     *res_p = (*conf->provider->repos->get_resource)(r, conf->dir,
650                                                     target_selector, is_label);
651     if (*res_p == NULL) {
652         /* Apache will supply a default error for this. */
653         return HTTP_NOT_FOUND;
654     }
655
656     (void) apr_set_userdata(*res_p, DAV_KEY_RESOURCE, apr_null_cleanup,
657                             r->pool);
658
659     /* ### hmm. this doesn't feel like the right place or thing to do */
660     /* if there were any input headers requiring a Vary header in the response,
661      * add it now */
662     dav_add_vary_header(r, r, *res_p);
663
664     return OK;
665 }
666
667 static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
668 {
669     const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
670
671     if (hooks == NULL) {
672         *lockdb = NULL;
673         return NULL;
674     }
675
676     /* open the thing lazily */
677     return (*hooks->open_lockdb)(r, ro, 0, lockdb);
678 }
679
680 static int dav_parse_range(request_rec *r,
681                            off_t *range_start, off_t *range_end)
682 {
683     const char *range_c;
684     char *range;
685     char *dash;
686     char *slash;
687
688     range_c = apr_table_get(r->headers_in, "content-range");
689     if (range_c == NULL)
690         return 0;
691
692     range = apr_pstrdup(r->pool, range_c);
693     if (strncasecmp(range, "bytes ", 6) != 0
694         || (dash = ap_strchr(range, '-')) == NULL
695         || (slash = ap_strchr(range, '/')) == NULL) {
696         /* malformed header. ignore it (per S14.16 of RFC2616) */
697         return 0;
698     }
699
700     *dash = *slash = '\0';
701     *range_start = atol(range + 6);
702     *range_end = atol(dash + 1);
703     if (*range_end < *range_start
704         || (slash[1] != '*' && atol(slash + 1) <= *range_end)) {
705         /* invalid range. ignore it (per S14.16 of RFC2616) */
706         return 0;
707     }
708
709     /* we now have a valid range */
710     return 1;
711 }
712
713 /* handle the GET method */
714 static int dav_method_get(request_rec *r)
715 {
716     dav_resource *resource;
717     int result;
718
719     /* This method should only be called when the resource is not
720      * visible to Apache. We will fetch the resource from the repository,
721      * then create a subrequest for Apache to handle.
722      */
723     result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
724     if (result != OK)
725         return result;
726     if (!resource->exists) {
727         /* Apache will supply a default error for this. */
728         return HTTP_NOT_FOUND;
729     }
730
731     /* Check resource type */
732     if (resource->type != DAV_RESOURCE_TYPE_REGULAR &&
733         resource->type != DAV_RESOURCE_TYPE_VERSION &&
734         resource->type != DAV_RESOURCE_TYPE_WORKING)
735     {
736         return dav_error_response(r, HTTP_CONFLICT,
737                                   "Cannot GET this type of resource.");
738     }
739
740     /* Cannot handle GET of a collection from a repository */
741     if (resource->collection) {
742         return dav_error_response(r, HTTP_CONFLICT, 
743                                   "No default response to GET for a "
744                                   "collection.");
745     }
746
747     /*
748     ** We can use two different approaches for a GET.
749     **
750     ** 1) get_pathname will return a pathname to a file which should be
751     **    sent to the client. If the repository provides this, then we
752     **    use it.
753     **
754     **    This is the best alternative since it allows us to do a sub-
755     **    request on the file, which gives the Apache framework a chance
756     **    to deal with negotiation, MIME types, or whatever.
757     **
758     ** 2) open_stream and read_stream.
759     */
760     if (resource->hooks->get_pathname != NULL) {
761         const char *pathname;
762         void *fhandle;
763         request_rec *new_req;
764         
765         /* Ask repository for copy of file */
766         pathname = (*resource->hooks->get_pathname)(resource, &fhandle);
767         if (pathname == NULL) {
768             return HTTP_NOT_FOUND;
769         }
770
771         /* Convert to canonical filename, so Apache detects component
772          * separators (on Windows, it only looks for '/', not '\')
773          */
774         pathname = ap_os_case_canonical_filename(r->pool, pathname);
775
776         /* Create a sub-request with the new filename */
777         new_req = ap_sub_req_lookup_file(pathname, r, NULL);
778         if (new_req == NULL) {
779             (*resource->hooks->free_file)(fhandle);
780             return HTTP_INTERNAL_SERVER_ERROR;
781         }
782
783         /* This may be a HEAD request */
784         new_req->header_only = r->header_only;
785
786         /* ### this enables header generation */
787         new_req->assbackwards = 0;
788
789         /* Run the sub-request */
790         result = ap_run_sub_req(new_req);
791         ap_destroy_sub_req(new_req);
792
793         /* Free resources */
794         (*resource->hooks->free_file)(fhandle);
795
796         return result;
797     }
798     else {
799         dav_stream_mode mode;
800         dav_stream *stream;
801         dav_error *err;
802         void *buffer;
803         int has_range;
804         off_t range_start;
805         off_t range_end;
806
807         /* set up the HTTP headers for the response */
808         if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
809             err = dav_push_error(r->pool, err->status, 0,
810                                  "Unable to set up HTTP headers.",
811                                  err);
812             return dav_handle_err(r, err, NULL);
813         }
814
815         /* use plain READ mode unless we see a Content-Range */
816         mode = DAV_MODE_READ;
817
818         /* process the Content-Range header (if present) */
819         has_range = dav_parse_range(r, &range_start, &range_end);
820         if (has_range) {
821             /* use a read mode which is seekable */
822             mode = DAV_MODE_READ_SEEKABLE;
823
824             /* prep the output */
825             r->status = HTTP_PARTIAL_CONTENT;
826             apr_table_setn(r->headers_out,
827                           "Content-Range",
828                           apr_psprintf(r->pool, "bytes %ld-%ld/*",
829                                       range_start, range_end));
830             ap_set_content_length(r, range_end - range_start + 1);
831         }
832
833         if (r->header_only) {
834             ap_send_http_header(r);
835             return DONE;
836         }
837
838         if ((err = (*resource->hooks->open_stream)(resource, mode,
839                                                    &stream)) != NULL) {
840             /* ### assuming FORBIDDEN is probably not quite right... */
841             err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
842                                  apr_psprintf(r->pool,
843                                              "Unable to GET contents for %s.",
844                                              ap_escape_html(r->pool, r->uri)),
845                                  err);
846             return dav_handle_err(r, err, NULL);
847         }
848
849         if (has_range
850             && (err = (*resource->hooks->seek_stream)(stream,
851                                                       range_start)) != NULL) {
852             err = dav_push_error(r->pool, err->status, 0,
853                                  "Could not seek to beginning of the "
854                                  "specified Content-Range.", err);
855             return dav_handle_err(r, err, NULL);
856         }
857
858         /* all set. send the headers now. */
859         ap_send_http_header(r);
860
861         buffer = apr_palloc(r->pool, DAV_READ_BLOCKSIZE);
862         while (1) {
863             size_t amt;
864
865             if (!has_range)
866                 amt = DAV_READ_BLOCKSIZE;
867             else if ((range_end - range_start + 1) > DAV_READ_BLOCKSIZE)
868                 amt = DAV_READ_BLOCKSIZE;
869             else {
870                 /* note: range_end - range_start is an ssize_t */
871                 amt = (size_t)(range_end - range_start + 1);
872             }
873
874             if ((err = (*resource->hooks->read_stream)(stream, buffer,
875                                                        &amt)) != NULL) {
876                 break;
877             }
878             if (amt == 0) {
879                 /* no more content */
880                 break;
881             }
882             if (ap_rwrite(buffer, amt, r) < 0) {
883                 /* ### what to do with this error? */
884                 break;
885             }
886
887             if (has_range) {
888                 range_start += amt;
889                 if (range_start > range_end)
890                     break;
891             }
892         }
893
894         if (err != NULL)
895             return dav_handle_err(r, err, NULL);
896
897         /*
898         ** ### range_start should equal range_end+1. if it doesn't, then
899         ** ### we did not send enough data to the client. the client will
900         ** ### hang (and timeout) waiting for the data.
901         **
902         ** ### what to do? abort the connection?
903         */
904         return DONE;
905     }
906
907     /* NOTREACHED */
908 }
909
910 /* validate resource on POST, then pass it off to the default handler */
911 static int dav_method_post(request_rec *r)
912 {
913     dav_resource *resource;
914     dav_error *err;
915     int result;
916
917     /* Ask repository module to resolve the resource */
918     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
919     if (result != OK) {
920         return result;
921     }
922
923     /* Note: depth == 0. Implies no need for a multistatus response. */
924     if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
925                                     DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
926         /* ### add a higher-level description? */
927         return dav_handle_err(r, err, NULL);
928     }
929
930     return DECLINED;
931 }
932
933 /* handle the PUT method */
934 static int dav_method_put(request_rec *r)
935 {
936     dav_resource *resource;
937     int resource_state;
938     dav_auto_version_info av_info;
939     const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
940     const char *body;
941     dav_error *err;
942     dav_error *err2;
943     int result;
944     dav_stream_mode mode;
945     dav_stream *stream;
946     dav_response *multi_response;
947     int has_range;
948     off_t range_start;
949     off_t range_end;
950
951     if ((result = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != OK) {
952         return result;
953     }
954
955     /* Ask repository module to resolve the resource */
956     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
957     if (result != OK) {
958         return result;
959     }
960
961     /* If not a file or collection resource, PUT not allowed */
962     if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
963         body = apr_psprintf(r->pool,
964                            "Cannot create resource %s with PUT.",
965                            ap_escape_html(r->pool, r->uri));
966         return dav_error_response(r, HTTP_CONFLICT, body);
967     }
968
969     /* Cannot PUT a collection */
970     if (resource->collection) {
971         return dav_error_response(r, HTTP_CONFLICT,
972                                   "Cannot PUT to a collection.");
973
974     }
975
976     resource_state = dav_get_resource_state(r, resource);
977
978     /*
979     ** Note: depth == 0 normally requires no multistatus response. However,
980     ** if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
981     ** other than the Request-URI, thereby requiring a multistatus.
982     **
983     ** If the resource does not exist (DAV_RESOURCE_NULL), then we must
984     ** check the resource *and* its parent. If the resource exists or is
985     ** a locknull resource, then we check only the resource.
986     */
987     if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
988                                     resource_state == DAV_RESOURCE_NULL ?
989                                     DAV_VALIDATE_PARENT :
990                                     DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
991         /* ### add a higher-level description? */
992         return dav_handle_err(r, err, multi_response);
993     }
994
995     /* make sure the resource can be modified (if versioning repository) */
996     if ((err = dav_ensure_resource_writable(r, resource,
997                                             0 /* not parent_only */,
998                                             &av_info)) != NULL) {
999         /* ### add a higher-level description? */
1000         return dav_handle_err(r, err, NULL);
1001     }
1002
1003     /* truncate and rewrite the file unless we see a Content-Range */
1004     mode = DAV_MODE_WRITE_TRUNC;
1005
1006     has_range = dav_parse_range(r, &range_start, &range_end);
1007     if (has_range) {
1008         mode = DAV_MODE_WRITE_SEEKABLE;
1009     }
1010
1011     /* Create the new file in the repository */
1012     if ((err = (*resource->hooks->open_stream)(resource, mode,
1013                                                &stream)) != NULL) {
1014         /* ### assuming FORBIDDEN is probably not quite right... */
1015         err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
1016                              apr_psprintf(r->pool,
1017                                          "Unable to PUT new contents for %s.",
1018                                          ap_escape_html(r->pool, r->uri)),
1019                              err);
1020     }
1021
1022     if (err == NULL && has_range) {
1023         /* a range was provided. seek to the start */
1024         err = (*resource->hooks->seek_stream)(stream, range_start);
1025     }
1026
1027     if (err == NULL) {
1028         if (ap_should_client_block(r)) {
1029             char *buffer = apr_palloc(r->pool, DAV_READ_BLOCKSIZE);
1030             long len;
1031
1032             /*
1033             ** Once we start reading the request, then we must read the
1034             ** whole darn thing. ap_discard_request_body() won't do anything
1035             ** for a partially-read request.
1036             */
1037
1038             while ((len = ap_get_client_block(r, buffer,
1039                                               DAV_READ_BLOCKSIZE)) > 0) {
1040                 if (err == NULL) {
1041                     /* write whatever we read, until we see an error */
1042                     err = (*resource->hooks->write_stream)(stream,
1043                                                            buffer, len);
1044                 }
1045             }
1046
1047             /*
1048             ** ### what happens if we read more/less than the amount
1049             ** ### specified in the Content-Range? eek...
1050             */
1051
1052             if (len == -1) {
1053                 /*
1054                 ** Error reading request body. This has precedence over
1055                 ** prior errors.
1056                 */
1057                 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1058                                     "An error occurred while reading the "
1059                                     "request body.");
1060             }
1061         }
1062
1063         err2 = (*resource->hooks->close_stream)(stream,
1064                                                 err == NULL /* commit */);
1065         if (err2 != NULL && err == NULL) {
1066             /* no error during the write, but we hit one at close. use it. */
1067             err = err2;
1068         }
1069     }
1070
1071     /*
1072     ** Ensure that we think the resource exists now.
1073     ** ### eek. if an error occurred during the write and we did not commit,
1074     ** ### then the resource might NOT exist (e.g. dav_fs_repos.c)
1075     */
1076     if (err == NULL) {
1077         resource->exists = 1;
1078     }
1079
1080     /* restore modifiability of resources back to what they were */
1081     err2 = dav_revert_resource_writability(r, resource, err != NULL /* undo if error */,
1082                                            &av_info);
1083
1084     /* check for errors now */
1085     if (err != NULL) {
1086         return dav_handle_err(r, err, NULL);
1087     }
1088     if (err2 != NULL) {
1089         /* just log a warning */
1090         err2 = dav_push_error(r->pool, err->status, 0,
1091                               "The PUT was successful, but there "
1092                               "was a problem reverting the writability of "
1093                               "the resource or its parent collection.",
1094                               err2);
1095         dav_log_err(r, err2, APLOG_WARNING);
1096     }
1097
1098     /* ### place the Content-Type and Content-Language into the propdb */
1099
1100     if (locks_hooks != NULL) {
1101         dav_lockdb *lockdb;
1102
1103         if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1104             /* The file creation was successful, but the locking failed. */
1105             err = dav_push_error(r->pool, err->status, 0,
1106                                  "The file was PUT successfully, but there "
1107                                  "was a problem opening the lock database "
1108                                  "which prevents inheriting locks from the "
1109                                  "parent resources.",
1110                                  err);
1111             return dav_handle_err(r, err, NULL);
1112         }
1113
1114         /* notify lock system that we have created/replaced a resource */
1115         err = dav_notify_created(r, lockdb, resource, resource_state, 0);
1116
1117         (*locks_hooks->close_lockdb)(lockdb);
1118
1119         if (err != NULL) {
1120             /* The file creation was successful, but the locking failed. */
1121             err = dav_push_error(r->pool, err->status, 0,
1122                                  "The file was PUT successfully, but there "
1123                                  "was a problem updating its lock "
1124                                  "information.",
1125                                  err);
1126             return dav_handle_err(r, err, NULL);
1127         }
1128     }
1129
1130     /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
1131
1132     /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
1133     return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
1134 }
1135
1136 /* ### move this to dav_util? */
1137 DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
1138                                    int status, dav_get_props_result *propstats)
1139 {
1140     dav_walker_ctx *ctx = wres->walk_ctx;
1141     dav_response *resp;
1142
1143     /* just drop some data into an dav_response */
1144     resp = apr_pcalloc(ctx->w.pool, sizeof(*resp));
1145     resp->href = apr_pstrdup(ctx->w.pool, wres->resource->uri);
1146     resp->status = status;
1147     if (propstats) {
1148         resp->propresult = *propstats;
1149     }
1150
1151     resp->next = wres->response;
1152     wres->response = resp;
1153 }
1154
1155 /* handle the DELETE method */
1156 static int dav_method_delete(request_rec *r)
1157 {
1158     dav_resource *resource;
1159     dav_auto_version_info av_info;
1160     dav_error *err;
1161     dav_error *err2;
1162     dav_response *multi_response;
1163     int result;
1164     int depth;
1165
1166     /* We don't use the request body right now, so torch it. */
1167     if ((result = ap_discard_request_body(r)) != OK) {
1168         return result;
1169     }
1170
1171     /* Ask repository module to resolve the resource */
1172     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
1173     if (result != OK)
1174         return result;
1175     if (!resource->exists) {
1176         /* Apache will supply a default error for this. */
1177         return HTTP_NOT_FOUND;
1178     }
1179
1180     /* 2518 says that depth must be infinity only for collections.
1181      * For non-collections, depth is ignored, unless it is an illegal value (1).
1182      */
1183     depth = dav_get_depth(r, DAV_INFINITY);
1184
1185     if (resource->collection && depth != DAV_INFINITY) {
1186         /* This supplies additional information for the default message. */
1187         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1188                       "Depth must be \"infinity\" for DELETE of a collection.");
1189         return HTTP_BAD_REQUEST;
1190     }
1191     if (!resource->collection && depth == 1) {
1192         /* This supplies additional information for the default message. */
1193         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1194                       "Depth of \"1\" is not allowed for DELETE.");
1195         return HTTP_BAD_REQUEST;
1196     }
1197
1198     /*
1199     ** If any resources fail the lock/If: conditions, then we must fail
1200     ** the delete. Each of the failing resources will be listed within
1201     ** a DAV:multistatus body, wrapped into a 424 response.
1202     **
1203     ** Note that a failure on the resource itself does not generate a
1204     ** multistatus response -- only internal members/collections.
1205     */
1206     if ((err = dav_validate_request(r, resource, depth, NULL,
1207                                     &multi_response,
1208                                     DAV_VALIDATE_PARENT
1209                                     | DAV_VALIDATE_USE_424, NULL)) != NULL) {
1210         err = dav_push_error(r->pool, err->status, 0,
1211                              apr_psprintf(r->pool,
1212                                          "Could not DELETE %s due to a failed "
1213                                          "precondition (e.g. locks).",
1214                                          ap_escape_html(r->pool, r->uri)),
1215                              err);
1216         return dav_handle_err(r, err, multi_response);
1217     }
1218
1219     /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
1220      *     locked by the token(s) in the if_header.
1221      */
1222     if ((result = dav_unlock(r, resource, NULL)) != OK) {
1223         return result;
1224     }
1225
1226     /* if versioned resource, make sure parent is checked out */
1227     if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
1228                                             &av_info)) != NULL) {
1229         /* ### add a higher-level description? */
1230         return dav_handle_err(r, err, NULL);
1231     }
1232
1233     /* try to remove the resource */
1234     err = (*resource->hooks->remove_resource)(resource, &multi_response);
1235
1236     /* restore writability of parent back to what it was */
1237     err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
1238                                            &av_info);
1239
1240     /* check for errors now */
1241     if (err != NULL) {
1242         err = dav_push_error(r->pool, err->status, 0,
1243                              apr_psprintf(r->pool,
1244                                          "Could not DELETE %s.",
1245                                          ap_escape_html(r->pool, r->uri)),
1246                              err);
1247         return dav_handle_err(r, err, multi_response);
1248     }
1249     if (err2 != NULL) {
1250         /* just log a warning */
1251         err = dav_push_error(r->pool, err2->status, 0,
1252                              "The DELETE was successful, but there "
1253                              "was a problem reverting the writability of "
1254                              "its parent collection.",
1255                              err2);
1256         dav_log_err(r, err, APLOG_WARNING);
1257     }
1258
1259     /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
1260
1261     /* Apache will supply a default error for this. */
1262     return HTTP_NO_CONTENT;
1263 }
1264
1265 /* handle the OPTIONS method */
1266 static int dav_method_options(request_rec *r)
1267 {
1268     const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1269     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1270     const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
1271     dav_resource *resource;
1272     const char *options;
1273     const char *dav_level;
1274     const char *vsn_level;
1275     int result;
1276     apr_array_header_t *uri_ary;
1277     const char *uris;
1278
1279     /* per HTTP/1.1 S9.2, we can discard this body */
1280     if ((result = ap_discard_request_body(r)) != OK) {
1281         return result;
1282     }
1283
1284     /* no body */
1285     ap_set_content_length(r, 0);
1286
1287     /* resolve the resource */
1288     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
1289     if (result != OK)
1290         return result;
1291
1292     /* determine which providers are available */
1293     dav_level = "1";
1294     vsn_level = NULL;
1295
1296     if (locks_hooks != NULL) {
1297         dav_level = "1,2";
1298     }
1299
1300     if (vsn_hooks != NULL
1301         && (vsn_level = (*vsn_hooks->get_vsn_header)()) != NULL) {
1302         dav_level = apr_pstrcat(r->pool, dav_level, ",", vsn_level, NULL);
1303     }
1304
1305     /* gather property set URIs from all the liveprop providers */
1306     uri_ary = apr_make_array(r->pool, 5, sizeof(const char *));
1307     ap_run_gather_propsets(uri_ary);
1308     uris = apr_array_pstrcat(r->pool, uri_ary, ',');
1309     if (*uris) {
1310         dav_level = apr_pstrcat(r->pool, dav_level, ",", uris, NULL);
1311     }
1312
1313     /* this tells MSFT products to skip looking for FrontPage extensions */
1314     apr_table_setn(r->headers_out, "MS-Author-Via", "DAV");
1315
1316     /*
1317     ** Three cases:  resource is null (3), is lock-null (7.4), or exists.
1318     **
1319     ** All cases support OPTIONS and LOCK.
1320     ** (Lock-) null resources also support MKCOL and PUT.
1321     ** Lock-null support PROPFIND and UNLOCK.
1322     ** Existing resources support lots of stuff.
1323     */
1324
1325     /* ### take into account resource type */
1326     switch (dav_get_resource_state(r, resource))
1327     {
1328     case DAV_RESOURCE_EXISTS:
1329         /* resource exists */
1330         if (resource->collection) {
1331             options = apr_pstrcat(r->pool,
1332                 "OPTIONS, "
1333                 "GET, HEAD, POST, DELETE, TRACE, "
1334                 "PROPFIND, PROPPATCH, COPY, MOVE",
1335                 locks_hooks != NULL ? ", LOCK, UNLOCK" : "",
1336                 NULL);
1337         }
1338         else {
1339             /* files also support PUT */
1340             options = apr_pstrcat(r->pool,
1341                 "OPTIONS, "
1342                 "GET, HEAD, POST, DELETE, TRACE, "
1343                 "PROPFIND, PROPPATCH, COPY, MOVE, PUT",
1344                 locks_hooks != NULL ? ", LOCK, UNLOCK" : "",
1345                 NULL);
1346         }
1347         break;
1348
1349     case DAV_RESOURCE_LOCK_NULL:
1350         /* resource is lock-null. */
1351         options = apr_pstrcat(r->pool, "OPTIONS, MKCOL, PUT, PROPFIND",
1352                              locks_hooks != NULL ? ", LOCK, UNLOCK" : "",
1353                              NULL);
1354         break;
1355
1356     case DAV_RESOURCE_NULL:
1357         /* resource is null. */
1358         options = apr_pstrcat(r->pool, "OPTIONS, MKCOL, PUT",
1359                              locks_hooks != NULL ? ", LOCK" : "",
1360                              NULL);
1361         break;
1362
1363     default:
1364         /* ### internal error! */
1365         options = "OPTIONS";
1366         break;
1367     }
1368
1369     /* If there is a versioning provider, add versioning options */
1370     if (vsn_hooks != NULL) {
1371         const char *vsn_options = NULL;
1372
1373         if (!resource->exists) {
1374             int vsn_control = (*vsn_hooks->versionable)(resource);
1375             int mkworkspace = vsn_hooks->can_be_workspace != NULL
1376                               && (*vsn_hooks->can_be_workspace)(resource);
1377
1378             if (vsn_control && mkworkspace) {
1379                 vsn_options = ", VERSION-CONTROL, MKWORKSPACE";
1380             }
1381             else if (vsn_control)
1382                 vsn_options = ", VERSION-CONTROL";
1383             else if (mkworkspace) {
1384                 vsn_options = ", MKWORKSPACE";
1385             }
1386         }
1387         else if (!resource->versioned) {
1388             if ((*vsn_hooks->versionable)(resource)) {
1389                 vsn_options = ", VERSION-CONTROL";
1390             }
1391         }
1392         else if (resource->working)
1393             vsn_options = ", CHECKIN, UNCHECKOUT";
1394         else if (vsn_hooks->add_label != NULL)
1395             vsn_options = ", CHECKOUT, LABEL";
1396         else
1397             vsn_options = ", CHECKOUT";
1398
1399         if (vsn_options != NULL)
1400             options = apr_pstrcat(r->pool, options, vsn_options, NULL);
1401     }
1402
1403     /* If there is a bindings provider, see if resource is bindable */
1404     if (binding_hooks != NULL) {
1405         dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
1406         if ((*binding_hooks->is_bindable)(resource))
1407             options = apr_pstrcat(r->pool, options, ", BIND", NULL);
1408     }
1409
1410     apr_table_setn(r->headers_out, "Allow", options);
1411     apr_table_setn(r->headers_out, "DAV", dav_level);
1412
1413     /* ### this will send a Content-Type. the default OPTIONS does not. */
1414     ap_send_http_header(r);
1415
1416     /* ### the default (ap_send_http_options) returns OK, but I believe
1417      * ### that is because it is the default handler and nothing else
1418      * ### will run after the thing. */
1419
1420     /* we've sent everything necessary to the client. */
1421     return DONE;
1422 }
1423
1424 static void dav_cache_badprops(dav_walker_ctx *ctx)
1425 {
1426     const ap_xml_elem *elem;
1427     ap_text_header hdr = { 0 };
1428
1429     /* just return if we built the thing already */
1430     if (ctx->propstat_404 != NULL) {
1431         return;
1432     }
1433
1434     ap_text_append(ctx->w.pool, &hdr,
1435                    "<D:propstat>" DEBUG_CR
1436                    "<D:prop>" DEBUG_CR);
1437
1438     elem = dav_find_child(ctx->doc->root, "prop");
1439     for (elem = elem->first_child; elem; elem = elem->next) {
1440         ap_text_append(ctx->w.pool, &hdr,
1441                        ap_xml_empty_elem(ctx->w.pool, elem));
1442     }
1443
1444     ap_text_append(ctx->w.pool, &hdr,
1445                    "</D:prop>" DEBUG_CR
1446                    "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
1447                    "</D:propstat>" DEBUG_CR);
1448
1449     ctx->propstat_404 = hdr.first;
1450 }
1451
1452 static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
1453 {
1454     dav_walker_ctx *ctx = wres->walk_ctx;
1455     dav_error *err;
1456     dav_propdb *propdb;
1457     dav_get_props_result propstats = { 0 };
1458
1459     /*
1460     ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
1461     ** dav_get_allprops() does not need to do namespace translation,
1462     ** we're okay.
1463     **
1464     ** Note: we cast to lose the "const". The propdb won't try to change
1465     ** the resource, however, since we are opening readonly.
1466     */
1467     err = dav_open_propdb(ctx->r, ctx->w.lockdb,
1468                           (dav_resource *)wres->resource, 1,
1469                           ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
1470     if (err != NULL) {
1471         /* ### do something with err! */
1472
1473         if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1474             dav_get_props_result badprops = { 0 };
1475
1476             /* some props were expected on this collection/resource */
1477             dav_cache_badprops(ctx);
1478             badprops.propstats = ctx->propstat_404;
1479             dav_add_response(wres, 0, &badprops);
1480         }
1481         else {
1482             /* no props on this collection/resource */
1483             dav_add_response(wres, HTTP_OK, NULL);
1484         }
1485         return NULL;
1486     }
1487     /* ### what to do about closing the propdb on server failure? */
1488
1489     if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1490         propstats = dav_get_props(propdb, ctx->doc);
1491     }
1492     else {
1493         propstats = dav_get_allprops(propdb,
1494                              ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP);
1495     }
1496     dav_close_propdb(propdb);
1497
1498     dav_add_response(wres, 0, &propstats);
1499
1500     return NULL;
1501 }
1502
1503 /* handle the PROPFIND method */
1504 static int dav_method_propfind(request_rec *r)
1505 {
1506     dav_resource *resource;
1507     int depth;
1508     dav_error *err;
1509     int result;
1510     ap_xml_doc *doc;
1511     const ap_xml_elem *child;
1512     dav_walker_ctx ctx = { { 0 } };
1513     dav_response *multi_status;
1514
1515     /* Ask repository module to resolve the resource */
1516     result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
1517     if (result != OK)
1518         return result;
1519
1520     if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
1521         /* Apache will supply a default error for this. */
1522         return HTTP_NOT_FOUND;
1523     }
1524
1525     if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
1526         /* dav_get_depth() supplies additional information for the
1527          * default message. */
1528         return HTTP_BAD_REQUEST;
1529     }
1530
1531     if (depth == DAV_INFINITY) {
1532         dav_dir_conf *conf;
1533         conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
1534                                                      &dav_module);
1535         /* default is to DISALLOW these requests */
1536         if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
1537             return dav_error_response(r, HTTP_FORBIDDEN,
1538                                       apr_psprintf(r->pool,
1539                                                   "PROPFIND requests with a "
1540                                                   "Depth of \"infinity\" are "
1541                                                   "not allowed for %s.",
1542                                                   ap_escape_html(r->pool,
1543                                                                  r->uri)));
1544         }
1545     }
1546
1547     if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1548         return result;
1549     }
1550     /* note: doc == NULL if no request body */
1551
1552     if (doc && !dav_validate_root(doc, "propfind")) {
1553         /* This supplies additional information for the default message. */
1554         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1555                       "The \"propfind\" element was not found.");
1556         return HTTP_BAD_REQUEST;
1557     }
1558
1559     /* ### validate that only one of these three elements is present */
1560
1561     if (doc == NULL
1562         || (child = dav_find_child(doc->root, "allprop")) != NULL) {
1563         /* note: no request body implies allprop */
1564         ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
1565     }
1566     else if ((child = dav_find_child(doc->root, "propname")) != NULL) {
1567         ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
1568     }
1569     else if ((child = dav_find_child(doc->root, "prop")) != NULL) {
1570         ctx.propfind_type = DAV_PROPFIND_IS_PROP;
1571     }
1572     else {
1573         /* "propfind" element must have one of the above three children */
1574
1575         /* This supplies additional information for the default message. */
1576         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1577                       "The \"propfind\" element does not contain one of "
1578                       "the required child elements (the specific command).");
1579         return HTTP_BAD_REQUEST;
1580     }
1581
1582     ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
1583     ctx.w.func = dav_propfind_walker;
1584     ctx.w.walk_ctx = &ctx;
1585     ctx.w.pool = r->pool;
1586     ctx.w.root = resource;
1587
1588     ctx.doc = doc;
1589     ctx.r = r;
1590
1591     /* ### should open read-only */
1592     if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
1593         err = dav_push_error(r->pool, err->status, 0,
1594                              "The lock database could not be opened, "
1595                              "preventing access to the various lock "
1596                              "properties for the PROPFIND.",
1597                              err);
1598         return dav_handle_err(r, err, NULL);
1599     }
1600     if (ctx.w.lockdb != NULL) {
1601         /* if we have a lock database, then we can walk locknull resources */
1602         ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1603     }
1604
1605     err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
1606
1607     if (ctx.w.lockdb != NULL) {
1608         (*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb);
1609     }
1610
1611     if (err != NULL) {
1612         /* ### add a higher-level description? */
1613         return dav_handle_err(r, err, NULL);
1614     }
1615
1616     /* return a 207 (Multi-Status) response now. */
1617
1618     /* if a 404 was generated for an HREF, then we need to spit out the
1619      * doc's namespaces for use by the 404. Note that <response> elements
1620      * will override these ns0, ns1, etc, but NOT within the <response>
1621      * scope for the badprops. */
1622     /* NOTE: propstat_404 != NULL implies doc != NULL */
1623     if (ctx.propstat_404 != NULL) {
1624         dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status,
1625                              doc->namespaces);
1626     }
1627     else {
1628         dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
1629     }
1630
1631     /* the response has been sent. */
1632     return DONE;
1633 }
1634
1635 static ap_text * dav_failed_proppatch(apr_pool_t *p,
1636                                        apr_array_header_t *prop_ctx)
1637 {
1638     ap_text_header hdr = { 0 };
1639     int i = prop_ctx->nelts;
1640     dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
1641     dav_error *err424_set = NULL;
1642     dav_error *err424_delete = NULL;
1643     const char *s;
1644
1645     /* ### might be nice to sort by status code and description */
1646
1647     for ( ; i-- > 0; ++ctx ) {
1648         ap_text_append(p, &hdr,
1649                        "<D:propstat>" DEBUG_CR
1650                        "<D:prop>");
1651         ap_text_append(p, &hdr, ap_xml_empty_elem(p, ctx->prop));
1652         ap_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
1653
1654         if (ctx->err == NULL) {
1655             /* nothing was assigned here yet, so make it a 424 */
1656
1657             if (ctx->operation == DAV_PROP_OP_SET) {
1658                 if (err424_set == NULL)
1659                     err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
1660                                                "Attempted DAV:set operation "
1661                                                "could not be completed due "
1662                                                "to other errors.");
1663                 ctx->err = err424_set;
1664             }
1665             else if (ctx->operation == DAV_PROP_OP_DELETE) {
1666                 if (err424_delete == NULL)
1667                     err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
1668                                                   "Attempted DAV:remove "
1669                                                   "operation could not be "
1670                                                   "completed due to other "
1671                                                   "errors.");
1672                 ctx->err = err424_delete;
1673             }
1674         }
1675
1676         s = apr_psprintf(p,
1677                         "<D:status>"
1678                         "HTTP/1.1 %d (status)"
1679                         "</D:status>" DEBUG_CR,
1680                         ctx->err->status);
1681         ap_text_append(p, &hdr, s);
1682
1683         /* ### we should use compute_desc if necessary... */
1684         if (ctx->err->desc != NULL) {
1685             ap_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
1686             ap_text_append(p, &hdr, ctx->err->desc);
1687             ap_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
1688         }
1689
1690         ap_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
1691     }
1692
1693     return hdr.first;
1694 }
1695
1696 static ap_text * dav_success_proppatch(apr_pool_t *p, apr_array_header_t *prop_ctx)
1697 {
1698     ap_text_header hdr = { 0 };
1699     int i = prop_ctx->nelts;
1700     dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
1701
1702     /*
1703     ** ### we probably need to revise the way we assemble the response...
1704     ** ### this code assumes everything will return status==200.
1705     */
1706
1707     ap_text_append(p, &hdr,
1708                    "<D:propstat>" DEBUG_CR
1709                    "<D:prop>" DEBUG_CR);
1710
1711     for ( ; i-- > 0; ++ctx ) {
1712         ap_text_append(p, &hdr, ap_xml_empty_elem(p, ctx->prop));
1713     }
1714
1715     ap_text_append(p, &hdr,
1716                     "</D:prop>" DEBUG_CR
1717                     "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
1718                     "</D:propstat>" DEBUG_CR);
1719
1720     return hdr.first;
1721 }
1722
1723 static void dav_prop_log_errors(dav_prop_ctx *ctx)
1724 {
1725     dav_log_err(ctx->r, ctx->err, APLOG_ERR);
1726 }
1727
1728 /*
1729 ** Call <func> for each context. This can stop when an error occurs, or
1730 ** simply iterate through the whole list.
1731 **
1732 ** Returns 1 if an error occurs (and the iteration is aborted). Returns 0
1733 ** if all elements are processed.
1734 **
1735 ** If <reverse> is true (non-zero), then the list is traversed in
1736 ** reverse order.
1737 */
1738 static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
1739                                 apr_array_header_t *ctx_list, int stop_on_error,
1740                                 int reverse)
1741 {
1742     int i = ctx_list->nelts;
1743     dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
1744
1745     if (reverse)
1746         ctx += i;
1747
1748     while (i--) {
1749         if (reverse)
1750             --ctx;
1751
1752         (*func)(ctx);
1753         if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
1754             return 1;
1755         }
1756
1757         if (!reverse)
1758             ++ctx;
1759     }
1760
1761     return 0;
1762 }
1763
1764 /* handle the PROPPATCH method */
1765 static int dav_method_proppatch(request_rec *r)
1766 {
1767     dav_error *err;
1768     dav_resource *resource;
1769     int result;
1770     ap_xml_doc *doc;
1771     ap_xml_elem *child;
1772     dav_propdb *propdb;
1773     int failure = 0;
1774     dav_response resp = { 0 };
1775     ap_text *propstat_text;
1776     apr_array_header_t *ctx_list;
1777     dav_prop_ctx *ctx;
1778
1779     /* Ask repository module to resolve the resource */
1780     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
1781     if (result != OK)
1782         return result;
1783     if (!resource->exists) {
1784         /* Apache will supply a default error for this. */
1785         return HTTP_NOT_FOUND;
1786     }
1787
1788     if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1789         return result;
1790     }
1791     /* note: doc == NULL if no request body */
1792
1793     if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
1794         /* This supplies additional information for the default message. */
1795         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1796                       "The request body does not contain "
1797                       "a \"propertyupdate\" element.");
1798         return HTTP_BAD_REQUEST;
1799     }
1800
1801     /* Check If-Headers and existing locks */
1802     /* Note: depth == 0. Implies no need for a multistatus response. */
1803     if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
1804                                     DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
1805         /* ### add a higher-level description? */
1806         return dav_handle_err(r, err, NULL);
1807     }
1808
1809     if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
1810                                &propdb)) != NULL) {
1811         err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1812                              apr_psprintf(r->pool,
1813                                          "Could not open the property "
1814                                          "database for %s.",
1815                                          ap_escape_html(r->pool, r->uri)),
1816                              err);
1817         return dav_handle_err(r, err, NULL);
1818     }
1819     /* ### what to do about closing the propdb on server failure? */
1820
1821     /* ### validate "live" properties */
1822
1823     /* set up an array to hold property operation contexts */
1824     ctx_list = apr_make_array(r->pool, 10, sizeof(dav_prop_ctx));
1825
1826     /* do a first pass to ensure that all "remove" properties exist */
1827     for (child = doc->root->first_child; child; child = child->next) {
1828         int is_remove;
1829         ap_xml_elem *prop_group;
1830         ap_xml_elem *one_prop;
1831
1832         /* Ignore children that are not set/remove */
1833         if (child->ns != AP_XML_NS_DAV_ID
1834             || (!(is_remove = strcmp(child->name, "remove") == 0)
1835                 && strcmp(child->name, "set") != 0)) {
1836             continue;
1837         }
1838
1839         /* make sure that a "prop" child exists for set/remove */
1840         if ((prop_group = dav_find_child(child, "prop")) == NULL) {
1841             dav_close_propdb(propdb);
1842
1843             /* This supplies additional information for the default message. */
1844             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1845                           "A \"prop\" element is missing inside "
1846                           "the propertyupdate command.");
1847             return HTTP_BAD_REQUEST;
1848         }
1849
1850         for (one_prop = prop_group->first_child; one_prop;
1851              one_prop = one_prop->next) {
1852
1853             ctx = (dav_prop_ctx *)apr_push_array(ctx_list);
1854             ctx->propdb = propdb;
1855             ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
1856             ctx->prop = one_prop;
1857
1858             ctx->r = r;         /* for later use by dav_prop_log_errors() */
1859
1860             dav_prop_validate(ctx);
1861
1862             if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
1863                 failure = 1;
1864             }
1865         }
1866     }
1867
1868     /* ### should test that we found at least one set/remove */
1869
1870     /* execute all of the operations */
1871     if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
1872         failure = 1;
1873     }
1874
1875     /* generate a failure/success response */
1876     if (failure) {
1877         (void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
1878         propstat_text = dav_failed_proppatch(r->pool, ctx_list);
1879     }
1880     else {
1881         (void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
1882         propstat_text = dav_success_proppatch(r->pool, ctx_list);
1883     }
1884
1885     /* make sure this gets closed! */
1886     dav_close_propdb(propdb);
1887
1888     /* log any errors that occurred */
1889     (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
1890
1891     resp.href = resource->uri;
1892
1893     /* ### should probably use something new to pass along this text... */
1894     resp.propresult.propstats = propstat_text;
1895
1896     dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
1897
1898     /* the response has been sent. */
1899     return DONE;
1900 }
1901
1902 static int process_mkcol_body(request_rec *r)
1903 {
1904     /* This is snarfed from ap_setup_client_block(). We could get pretty
1905      * close to this behavior by passing REQUEST_NO_BODY, but we need to
1906      * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
1907      * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
1908
1909     const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
1910     const char *lenp = apr_table_get(r->headers_in, "Content-Length");
1911
1912     /* make sure to set the Apache request fields properly. */
1913     r->read_body = REQUEST_NO_BODY;
1914     r->read_chunked = 0;
1915     r->remaining = 0;
1916
1917     if (tenc) {
1918         if (strcasecmp(tenc, "chunked")) {
1919             /* Use this instead of Apache's default error string */
1920             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1921                           "Unknown Transfer-Encoding %s", tenc);
1922             return HTTP_NOT_IMPLEMENTED;
1923         }
1924
1925         r->read_chunked = 1;
1926     }
1927     else if (lenp) {
1928         const char *pos = lenp;
1929
1930         while (apr_isdigit(*pos) || apr_isspace(*pos)) {
1931             ++pos;
1932         }
1933         if (*pos != '\0') {
1934             /* This supplies additional information for the default message. */
1935             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1936                           "Invalid Content-Length %s", lenp);
1937             return HTTP_BAD_REQUEST;
1938         }
1939
1940         r->remaining = atol(lenp);
1941     }
1942
1943     if (r->read_chunked || r->remaining > 0) {
1944         /* ### log something? */
1945
1946         /* Apache will supply a default error for this. */
1947         return HTTP_UNSUPPORTED_MEDIA_TYPE;
1948     }
1949
1950     /*
1951     ** Get rid of the body. this will call ap_setup_client_block(), but
1952     ** our copy above has already verified its work.
1953     */
1954     return ap_discard_request_body(r);
1955 }
1956
1957 /* handle the MKCOL method */
1958 static int dav_method_mkcol(request_rec *r)
1959 {
1960     dav_resource *resource;
1961     int resource_state;
1962     dav_auto_version_info av_info;
1963     const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1964     dav_error *err;
1965     dav_error *err2;
1966     int result;
1967     dav_dir_conf *conf;
1968     dav_response *multi_status;
1969
1970     /* handle the request body */
1971     /* ### this may move lower once we start processing bodies */
1972     if ((result = process_mkcol_body(r)) != OK) {
1973         return result;
1974     }
1975
1976     conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
1977                                                  &dav_module);
1978
1979     /* Ask repository module to resolve the resource */
1980     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
1981     if (result != OK)
1982         return result;
1983
1984     if (resource->exists) {
1985         /* oops. something was already there! */
1986
1987         /* Apache will supply a default error for this. */
1988         /* ### we should provide a specific error message! */
1989         return HTTP_METHOD_NOT_ALLOWED;
1990     }
1991
1992     resource_state = dav_get_resource_state(r, resource);
1993
1994     /*
1995     ** Check If-Headers and existing locks.
1996     **
1997     ** Note: depth == 0 normally requires no multistatus response. However,
1998     ** if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
1999     ** other than the Request-URI, thereby requiring a multistatus.
2000     **
2001     ** If the resource does not exist (DAV_RESOURCE_NULL), then we must
2002     ** check the resource *and* its parent. If the resource exists or is
2003     ** a locknull resource, then we check only the resource.
2004     */
2005     if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
2006                                     resource_state == DAV_RESOURCE_NULL ?
2007                                     DAV_VALIDATE_PARENT :
2008                                     DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2009         /* ### add a higher-level description? */
2010         return dav_handle_err(r, err, multi_status);
2011     }
2012
2013     /* if versioned resource, make sure parent is checked out */
2014     if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
2015                                             &av_info)) != NULL) {
2016         /* ### add a higher-level description? */
2017         return dav_handle_err(r, err, NULL);
2018     }
2019
2020     /* try to create the collection */
2021     resource->collection = 1;
2022     err = (*resource->hooks->create_collection)(resource);
2023
2024     /* restore modifiability of parent back to what it was */
2025     err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
2026                                            &av_info);
2027
2028     /* check for errors now */
2029     if (err != NULL) {
2030         return dav_handle_err(r, err, NULL);
2031     }
2032     if (err2 != NULL) {
2033         /* just log a warning */
2034         err = dav_push_error(r->pool, err->status, 0,
2035                              "The MKCOL was successful, but there "
2036                              "was a problem reverting the writability of "
2037                              "its parent collection.",
2038                              err2);
2039         dav_log_err(r, err, APLOG_WARNING);
2040     }
2041
2042     if (locks_hooks != NULL) {
2043         dav_lockdb *lockdb;
2044
2045         if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2046             /* The directory creation was successful, but the locking failed. */
2047             err = dav_push_error(r->pool, err->status, 0,
2048                                  "The MKCOL was successful, but there "
2049                                  "was a problem opening the lock database "
2050                                  "which prevents inheriting locks from the "
2051                                  "parent resources.",
2052                                  err);
2053             return dav_handle_err(r, err, NULL);
2054         }
2055
2056         /* notify lock system that we have created/replaced a resource */
2057         err = dav_notify_created(r, lockdb, resource, resource_state, 0);
2058
2059         (*locks_hooks->close_lockdb)(lockdb);
2060
2061         if (err != NULL) {
2062             /* The dir creation was successful, but the locking failed. */
2063             err = dav_push_error(r->pool, err->status, 0,
2064                                  "The MKCOL was successful, but there "
2065                                  "was a problem updating its lock "
2066                                  "information.",
2067                                  err);
2068             return dav_handle_err(r, err, NULL);
2069         }
2070     }
2071
2072     /* return an appropriate response (HTTP_CREATED) */
2073     return dav_created(r, NULL, "Collection", 0);
2074 }
2075
2076 /* handle the COPY and MOVE methods */
2077 static int dav_method_copymove(request_rec *r, int is_move)
2078 {
2079     dav_resource *resource;
2080     dav_auto_version_info src_av_info = { 0 };
2081     dav_resource *resnew;
2082     dav_auto_version_info dst_av_info;
2083     const char *body;
2084     const char *dest;
2085     dav_error *err;
2086     dav_error *err2;
2087     dav_error *err3;
2088     dav_response *multi_response;
2089     dav_lookup_result lookup;
2090     int is_dir;
2091     int overwrite;
2092     int depth;
2093     int result;
2094     dav_lockdb *lockdb;
2095     int replaced;
2096     int resource_state;
2097
2098     /* Ask repository module to resolve the resource */
2099     result = dav_get_resource(r, !is_move /*target_allowed*/, NULL, &resource);
2100     if (result != OK)
2101         return result;
2102     if (!resource->exists) {
2103         /* Apache will supply a default error for this. */
2104         return HTTP_NOT_FOUND;
2105     }
2106
2107     /* If not a file or collection resource, COPY/MOVE not allowed */
2108     if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
2109         body = apr_psprintf(r->pool,
2110                            "Cannot COPY/MOVE resource %s.",
2111                            ap_escape_html(r->pool, r->uri));
2112         return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
2113     }
2114
2115     /* get the destination URI */
2116     dest = apr_table_get(r->headers_in, "Destination");
2117     if (dest == NULL) {
2118         /* Look in headers provided by Netscape's Roaming Profiles */
2119         const char *nscp_host = apr_table_get(r->headers_in, "Host");
2120         const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
2121
2122         if (nscp_host != NULL && nscp_path != NULL)
2123             dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
2124     }
2125     if (dest == NULL) {
2126         /* This supplies additional information for the default message. */
2127         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2128                       "The request is missing a Destination header.");
2129         return HTTP_BAD_REQUEST;
2130     }
2131
2132     lookup = dav_lookup_uri(dest, r);
2133     if (lookup.rnew == NULL) {
2134         if (lookup.err.status == HTTP_BAD_REQUEST) {
2135             /* This supplies additional information for the default message. */
2136             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2137                           lookup.err.desc);
2138             return HTTP_BAD_REQUEST;
2139         }
2140
2141         /* ### this assumes that dav_lookup_uri() only generates a status
2142          * ### that Apache can provide a status line for!! */
2143
2144         return dav_error_response(r, lookup.err.status, lookup.err.desc);
2145     }
2146     if (lookup.rnew->status != HTTP_OK) {
2147         /* ### how best to report this... */
2148         return dav_error_response(r, lookup.rnew->status,
2149                                   "Destination URI had an error.");
2150     }
2151
2152     /* Resolve destination resource */
2153     result = dav_get_resource(lookup.rnew, 0 /*target_allowed*/, NULL, &resnew);
2154     if (result != OK)
2155         return result;
2156
2157     /* are the two resources handled by the same repository? */
2158     if (resource->hooks != resnew->hooks) {
2159         /* ### this message exposes some backend config, but screw it... */
2160         return dav_error_response(r, HTTP_BAD_GATEWAY,
2161                                   "Destination URI is handled by a "
2162                                   "different repository than the source URI. "
2163                                   "MOVE or COPY between repositories is "
2164                                   "not possible.");
2165     }
2166
2167     /* get and parse the overwrite header value */
2168     if ((overwrite = dav_get_overwrite(r)) < 0) {
2169         /* dav_get_overwrite() supplies additional information for the
2170          * default message. */
2171         return HTTP_BAD_REQUEST;
2172     }
2173
2174     /* quick failure test: if dest exists and overwrite is false. */
2175     if (resnew->exists && !overwrite) {
2176         /* Supply some text for the error response body. */
2177         return dav_error_response(r, HTTP_PRECONDITION_FAILED,
2178                                   "Destination is not empty and "
2179                                   "Overwrite is not \"T\"");
2180     }
2181
2182     /* are the source and destination the same? */
2183     if ((*resource->hooks->is_same_resource)(resource, resnew)) {
2184         /* Supply some text for the error response body. */
2185         return dav_error_response(r, HTTP_FORBIDDEN,
2186                                   "Source and Destination URIs are the same.");
2187
2188     }
2189
2190     is_dir = resource->collection;
2191
2192     /* get and parse the Depth header value. "0" and "infinity" are legal. */
2193     if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
2194         /* dav_get_depth() supplies additional information for the
2195          * default message. */
2196         return HTTP_BAD_REQUEST;
2197     }
2198     if (depth == 1) {
2199         /* This supplies additional information for the default message. */
2200         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2201                    "Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
2202         return HTTP_BAD_REQUEST;
2203     }
2204     if (is_move && is_dir && depth != DAV_INFINITY) {
2205         /* This supplies additional information for the default message. */
2206         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2207                     "Depth must be \"infinity\" when moving a collection.");
2208         return HTTP_BAD_REQUEST;
2209     }
2210
2211     /*
2212     ** Check If-Headers and existing locks for each resource in the source
2213     ** if we are performing a MOVE. We will return a 424 response with a
2214     ** DAV:multistatus body. The multistatus responses will contain the
2215     ** information about any resource that fails the validation.
2216     **
2217     ** We check the parent resource, too, since this is a MOVE. Moving the
2218     ** resource effectively removes it from the parent collection, so we
2219     ** must ensure that we have met the appropriate conditions.
2220     **
2221     ** If a problem occurs with the Request-URI itself, then a plain error
2222     ** (rather than a multistatus) will be returned.
2223     */
2224     if (is_move
2225         && (err = dav_validate_request(r, resource, depth, NULL,
2226                                        &multi_response,
2227                                        DAV_VALIDATE_PARENT
2228                                        | DAV_VALIDATE_USE_424,
2229                                        NULL)) != NULL) {
2230         err = dav_push_error(r->pool, err->status, 0,
2231                              apr_psprintf(r->pool,
2232                                          "Could not MOVE %s due to a failed "
2233                                          "precondition on the source "
2234                                          "(e.g. locks).",
2235                                          ap_escape_html(r->pool, r->uri)),
2236                              err);
2237         return dav_handle_err(r, err, multi_response);
2238     }
2239
2240     /*
2241     ** Check If-Headers and existing locks for destination. Note that we
2242     ** use depth==infinity since the target (hierarchy) will be deleted
2243     ** before the move/copy is completed.
2244     **
2245     ** Note that we are overwriting the target, which implies a DELETE, so
2246     ** we are subject to the error/response rules as a DELETE. Namely, we
2247     ** will return a 424 error if any of the validations fail.
2248     ** (see dav_method_delete() for more information)
2249     */
2250     if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
2251                                     &multi_response,
2252                                     DAV_VALIDATE_PARENT
2253                                     | DAV_VALIDATE_USE_424, NULL)) != NULL) {
2254         err = dav_push_error(r->pool, err->status, 0,
2255                              apr_psprintf(r->pool,
2256                                          "Could not MOVE/COPY %s due to a "
2257                                          "failed precondition on the "
2258                                          "destination (e.g. locks).",
2259                                          ap_escape_html(r->pool, r->uri)),
2260                              err);
2261         return dav_handle_err(r, err, multi_response);
2262     }
2263
2264     if (is_dir
2265         && depth == DAV_INFINITY
2266         && (*resource->hooks->is_parent_resource)(resource, resnew)) {
2267         /* Supply some text for the error response body. */
2268         return dav_error_response(r, HTTP_FORBIDDEN,
2269                                   "Source collection contains the "
2270                                   "Destination.");
2271
2272     }
2273     if (is_dir
2274         && (*resnew->hooks->is_parent_resource)(resnew, resource)) {
2275         /* The destination must exist (since it contains the source), and
2276          * a condition above implies Overwrite==T. Obviously, we cannot
2277          * delete the Destination before the MOVE/COPY, as that would
2278          * delete the Source.
2279          */
2280
2281         /* Supply some text for the error response body. */
2282         return dav_error_response(r, HTTP_FORBIDDEN,
2283                                   "Destination collection contains the Source "
2284                                   "and Overwrite has been specified.");
2285     }
2286
2287     /* ### for now, we don't need anything in the body */
2288     if ((result = ap_discard_request_body(r)) != OK) {
2289         return result;
2290     }
2291
2292     if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
2293         /* ### add a higher-level description? */
2294         return dav_handle_err(r, err, NULL);
2295     }
2296
2297     /* remove any locks from the old resources */
2298     /*
2299     ** ### this is Yet Another Traversal. if we do a rename(), then we
2300     ** ### really don't have to do this in some cases since the inode
2301     ** ### values will remain constant across the move. but we can't
2302     ** ### know that fact from outside the provider :-(
2303     **
2304     ** ### note that we now have a problem atomicity in the move/copy
2305     ** ### since a failure after this would have removed locks (technically,
2306     ** ### this is okay to do, but really...)
2307     */
2308     if (is_move && lockdb != NULL) {
2309         /* ### this is wrong! it blasts direct locks on parent resources */
2310         /* ### pass lockdb! */
2311         (void)dav_unlock(r, resource, NULL);
2312     }
2313
2314     /* remember whether target resource existed */
2315     replaced = resnew->exists;
2316
2317     /* if this is a move, then the source parent collection will be modified */
2318     if (is_move) {
2319         if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
2320                                                 &src_av_info)) != NULL) {
2321             if (lockdb != NULL)
2322                 (*lockdb->hooks->close_lockdb)(lockdb);
2323
2324             /* ### add a higher-level description? */
2325             return dav_handle_err(r, err, NULL);
2326         }
2327     }
2328
2329     /* prepare the destination collection for modification */
2330     if ((err = dav_ensure_resource_writable(r, resnew, 1 /* parent_only */,
2331                                             &dst_av_info)) != NULL) {
2332         /* could not make destination writable:
2333          * if move, restore state of source parent
2334          */
2335         if (is_move) {
2336             (void) dav_revert_resource_writability(r, NULL, 1 /* undo */,
2337                                                    &src_av_info);
2338         }
2339
2340         if (lockdb != NULL)
2341             (*lockdb->hooks->close_lockdb)(lockdb);
2342
2343         /* ### add a higher-level description? */
2344         return dav_handle_err(r, err, NULL);
2345     }
2346
2347     /* If source and destination parents are the same, then
2348      * use the same object, so status updates to one are reflected
2349      * in the other, when reverting their writable states.
2350      */
2351     if (src_av_info.parent_resource != NULL
2352         && (*src_av_info.parent_resource->hooks->is_same_resource)
2353             (src_av_info.parent_resource, dst_av_info.parent_resource)) {
2354
2355         dst_av_info.parent_resource = src_av_info.parent_resource;
2356     }
2357
2358     /* New resource will be same kind as source */
2359     resnew->collection = resource->collection;
2360
2361     resource_state = dav_get_resource_state(lookup.rnew, resnew);
2362
2363     /* If target exists, remove it first (we know Ovewrite must be TRUE).
2364      * Then try to copy/move the resource.
2365      */
2366     if (resnew->exists)
2367         err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
2368
2369     if (err == NULL) {
2370         if (is_move)
2371             err = (*resource->hooks->move_resource)(resource, resnew,
2372                                                     &multi_response);
2373         else
2374             err = (*resource->hooks->copy_resource)(resource, resnew, depth,
2375                                                     &multi_response);
2376     }
2377
2378     /* restore parent collection states */
2379     err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
2380                                            &dst_av_info);
2381
2382     if (is_move) {
2383         err3 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
2384                                                &src_av_info);
2385     }
2386     else
2387         err3 = NULL;
2388
2389     /* check for error from remove/copy/move operations */
2390     if (err != NULL) {
2391         if (lockdb != NULL)
2392             (*lockdb->hooks->close_lockdb)(lockdb);
2393
2394         err = dav_push_error(r->pool, err->status, 0,
2395                              apr_psprintf(r->pool,
2396                                          "Could not MOVE/COPY %s.",
2397                                          ap_escape_html(r->pool, r->uri)),
2398                              err);
2399         return dav_handle_err(r, err, multi_response);
2400     }
2401
2402     /* check for errors from reverting writability */
2403     if (err2 != NULL) {
2404         /* just log a warning */
2405         err = dav_push_error(r->pool, err2->status, 0,
2406                              "The MOVE/COPY was successful, but there was a "
2407                              "problem reverting the writability of the "
2408                              "source parent collection.",
2409                              err2);
2410         dav_log_err(r, err, APLOG_WARNING);
2411     }
2412     if (err3 != NULL) {
2413         /* just log a warning */
2414         err = dav_push_error(r->pool, err3->status, 0,
2415                              "The MOVE/COPY was successful, but there was a "
2416                              "problem reverting the writability of the "
2417                              "destination parent collection.",
2418                              err3);
2419         dav_log_err(r, err, APLOG_WARNING);
2420     }
2421
2422     /* propagate any indirect locks at the target */
2423     if (lockdb != NULL) {
2424
2425         /* notify lock system that we have created/replaced a resource */
2426         err = dav_notify_created(r, lockdb, resnew, resource_state, depth);
2427
2428         (*lockdb->hooks->close_lockdb)(lockdb);
2429
2430         if (err != NULL) {
2431             /* The move/copy was successful, but the locking failed. */
2432             err = dav_push_error(r->pool, err->status, 0,
2433                                  "The MOVE/COPY was successful, but there "
2434                                  "was a problem updating the lock "
2435                                  "information.",
2436                                  err);
2437             return dav_handle_err(r, err, NULL);
2438         }
2439     }
2440
2441     /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
2442     return dav_created(r, lookup.rnew->uri, "Destination", replaced);
2443 }
2444
2445 /* dav_method_lock:  Handler to implement the DAV LOCK method
2446 **    Returns appropriate HTTP_* response.
2447 */
2448 static int dav_method_lock(request_rec *r)
2449 {
2450     dav_error *err;
2451     dav_resource *resource;
2452     const dav_hooks_locks *locks_hooks;
2453     int result;
2454     int depth;
2455     int new_lock_request = 0;
2456     ap_xml_doc *doc;
2457     dav_lock *lock;
2458     dav_response *multi_response = NULL;
2459     dav_lockdb *lockdb;
2460     int resource_state;
2461
2462     /* If no locks provider, decline the request */
2463     locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2464     if (locks_hooks == NULL)
2465         return DECLINED;
2466
2467     if ((result = ap_xml_parse_input(r, &doc)) != OK)
2468         return result;
2469
2470     depth = dav_get_depth(r, DAV_INFINITY);
2471     if (depth != 0 && depth != DAV_INFINITY) {
2472         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2473                       "Depth must be 0 or \"infinity\" for LOCK.");
2474         return HTTP_BAD_REQUEST;
2475     }
2476
2477     /* Ask repository module to resolve the resource.
2478      * DeltaV says result of target selector is undefined,
2479      * so allow it, and let provider reject the lock attempt
2480      * on a version if it wants to.
2481      */
2482     result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
2483     if (result != OK)
2484         return result;
2485
2486     /*
2487     ** Open writable. Unless an error occurs, we'll be
2488     ** writing into the database.
2489     */
2490     if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2491         /* ### add a higher-level description? */
2492         return dav_handle_err(r, err, NULL);
2493     }
2494
2495     if (doc != NULL) {
2496         if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
2497                                            &lock)) != NULL) {
2498             /* ### add a higher-level description to err? */
2499             goto error;
2500         }
2501         new_lock_request = 1;
2502
2503         lock->auth_user = apr_pstrdup(r->pool, r->user);
2504     }
2505
2506     resource_state = dav_get_resource_state(r, resource);
2507
2508     /*
2509     ** Check If-Headers and existing locks.
2510     **
2511     ** If this will create a locknull resource, then the LOCK will affect
2512     ** the parent collection (much like a PUT/MKCOL). For that case, we must
2513     ** validate the parent resource's conditions.
2514     */
2515     if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
2516                                     (resource_state == DAV_RESOURCE_NULL
2517                                      ? DAV_VALIDATE_PARENT
2518                                      : DAV_VALIDATE_RESOURCE)
2519                                     | (new_lock_request ? lock->scope : 0)
2520                                     | DAV_VALIDATE_ADD_LD,
2521                                     lockdb)) != OK) {
2522         err = dav_push_error(r->pool, err->status, 0,
2523                              apr_psprintf(r->pool,
2524                                          "Could not LOCK %s due to a failed "
2525                                          "precondition (e.g. other locks).",
2526                                          ap_escape_html(r->pool, r->uri)),
2527                              err);
2528         goto error;
2529     }
2530
2531     if (new_lock_request == 0) {
2532         dav_locktoken_list *ltl;
2533                 
2534         /*
2535         ** Refresh request 
2536         ** ### Assumption:  We can renew multiple locks on the same resource
2537         ** ### at once. First harvest all the positive lock-tokens given in
2538         ** ### the If header. Then modify the lock entries for this resource
2539         ** ### with the new Timeout val.
2540         */
2541
2542         if ((err = dav_get_locktoken_list(r, &ltl)) != NULL) {
2543             err = dav_push_error(r->pool, err->status, 0,
2544                                  apr_psprintf(r->pool,
2545                                              "The lock refresh for %s failed "
2546                                              "because no lock tokens were "
2547                                              "specified in an \"If:\" "
2548                                              "header.",
2549                                              ap_escape_html(r->pool, r->uri)),
2550                                  err);
2551             goto error;
2552         }
2553
2554         if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
2555                                                  dav_get_timeout(r),
2556                                                  &lock)) != NULL) {
2557             /* ### add a higher-level description to err? */
2558             goto error;
2559         }
2560     } else {
2561         /* New lock request */
2562         char *locktoken_txt;
2563         dav_dir_conf *conf;
2564
2565         conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
2566                                                      &dav_module);
2567
2568         /* apply lower bound (if any) from DAVMinTimeout directive */
2569         if (lock->timeout != DAV_TIMEOUT_INFINITE
2570             && lock->timeout < time(NULL) + conf->locktimeout)
2571             lock->timeout = time(NULL) + conf->locktimeout;
2572
2573         err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
2574         if (err != NULL) {
2575             /* ### add a higher-level description to err? */
2576             goto error;
2577         }
2578
2579         locktoken_txt = apr_pstrcat(r->pool, "<",
2580                                    (*locks_hooks->format_locktoken)(r->pool, lock->locktoken),
2581                                    ">", NULL);
2582
2583         apr_table_set(r->headers_out, "Lock-Token", locktoken_txt);
2584     }
2585
2586     (*locks_hooks->close_lockdb)(lockdb);
2587
2588     r->status = HTTP_OK;
2589     r->content_type = DAV_XML_CONTENT_TYPE;
2590
2591     ap_send_http_header(r);
2592
2593     ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
2594     if (lock == NULL)
2595         ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
2596     else {
2597         ap_rprintf(r,
2598                    "<D:lockdiscovery>" DEBUG_CR
2599                    "%s" DEBUG_CR
2600                    "</D:lockdiscovery>" DEBUG_CR,
2601                    dav_lock_get_activelock(r, lock, NULL));
2602     }
2603     ap_rputs("</D:prop>", r);
2604
2605     /* the response has been sent. */
2606     return DONE;
2607
2608   error:
2609     (*locks_hooks->close_lockdb)(lockdb);
2610     return dav_handle_err(r, err, multi_response);
2611 }
2612
2613 /* dav_method_unlock:  Handler to implement the DAV UNLOCK method
2614  *    Returns appropriate HTTP_* response.
2615  */
2616 static int dav_method_unlock(request_rec *r)
2617 {
2618     dav_error *err;
2619     dav_resource *resource;
2620     const dav_hooks_locks *locks_hooks;
2621     int result;
2622     const char *const_locktoken_txt;
2623     char *locktoken_txt;
2624     dav_locktoken *locktoken = NULL;
2625     int resource_state;
2626     dav_response *multi_response;
2627
2628     /* If no locks provider, decline the request */
2629     locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2630     if (locks_hooks == NULL)
2631         return DECLINED;
2632
2633     if ((const_locktoken_txt = apr_table_get(r->headers_in, "Lock-Token")) == NULL) {
2634         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2635                       "Unlock failed (%s):  No Lock-Token specified in header", r->filename);
2636         return HTTP_BAD_REQUEST;
2637     }
2638
2639     locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt);
2640     if (locktoken_txt[0] != '<') {
2641         /* ### should provide more specifics... */
2642         return HTTP_BAD_REQUEST;
2643     }
2644     locktoken_txt++;
2645
2646     if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
2647         /* ### should provide more specifics... */
2648         return HTTP_BAD_REQUEST;
2649     }
2650     locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
2651                 
2652     if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
2653                                                &locktoken)) != NULL) {
2654         err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
2655                              apr_psprintf(r->pool,
2656                                          "The UNLOCK on %s failed -- an "
2657                                          "invalid lock token was specified "
2658                                          "in the \"If:\" header.",
2659                                          ap_escape_html(r->pool, r->uri)),
2660                              err);
2661         return dav_handle_err(r, err, NULL);
2662     }
2663
2664     /* Ask repository module to resolve the resource.
2665      * DeltaV says result of target selector is undefined,
2666      * so allow it, and let provider reject the unlock attempt
2667      * on a version if it wants to.
2668      */
2669     result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
2670     if (result != OK)
2671         return result;
2672
2673     resource_state = dav_get_resource_state(r, resource);
2674
2675     /*
2676     ** Check If-Headers and existing locks.
2677     **
2678     ** Note: depth == 0 normally requires no multistatus response. However,
2679     ** if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
2680     ** other than the Request-URI, thereby requiring a multistatus.
2681     **
2682     ** If the resource is a locknull resource, then the UNLOCK will affect
2683     ** the parent collection (much like a delete). For that case, we must
2684     ** validate the parent resource's conditions.
2685     */
2686     if ((err = dav_validate_request(r, resource, 0, locktoken,
2687                                     &multi_response,
2688                                     resource_state == DAV_RESOURCE_LOCK_NULL
2689                                     ? DAV_VALIDATE_PARENT
2690                                     : DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2691         /* ### add a higher-level description? */
2692         return dav_handle_err(r, err, multi_response);
2693     }
2694
2695     /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
2696      *     _all_ resources locked by locktoken are released.  It does not say
2697      *     resource has to be the root of an infinte lock.  Thus, an UNLOCK
2698      *     on any part of an infinte lock will remove the lock on all resources.
2699      *     
2700      *     For us, if r->filename represents an indirect lock (part of an infinity lock),
2701      *     we must actually perform an UNLOCK on the direct lock for this resource.
2702      */     
2703     if ((result = dav_unlock(r, resource, locktoken)) != OK) {
2704         return result;
2705     }
2706
2707     return HTTP_NO_CONTENT;
2708 }
2709
2710 static int dav_method_vsn_control(request_rec *r)
2711 {
2712     dav_resource *resource;
2713     int resource_state;
2714     dav_auto_version_info av_info;
2715     const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2716     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
2717     dav_error *err;
2718     ap_xml_doc *doc;
2719     const char *target = NULL;
2720     int result;
2721
2722     /* if no versioning provider, decline the request */
2723     if (vsn_hooks == NULL)
2724         return DECLINED;
2725
2726     /* ask repository module to resolve the resource */
2727     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
2728     if (result != OK)
2729         return result;
2730
2731     /* remember the pre-creation resource state */
2732     resource_state = dav_get_resource_state(r, resource);
2733
2734     /* parse the request body (may be a version-control element) */
2735     if ((result = ap_xml_parse_input(r, &doc)) != OK) {
2736         return result;
2737     }
2738     /* note: doc == NULL if no request body */
2739
2740     if (doc != NULL) {
2741         const ap_xml_elem *child;
2742         apr_size_t tsize;
2743
2744         if (!dav_validate_root(doc, "version-control")) {
2745             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2746                           "The request body does not contain "
2747                           "a \"version-control\" element.");
2748             return HTTP_BAD_REQUEST;
2749         }
2750
2751         /* get the version URI */
2752         if ((child = dav_find_child(doc->root, "version")) == NULL) {
2753             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2754                           "The \"version-control\" element does not contain "
2755                           "a \"version\" element.");
2756             return HTTP_BAD_REQUEST;
2757         }
2758
2759         if ((child = dav_find_child(child, "href")) == NULL) {
2760             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2761                           "The \"version\" element does not contain "
2762                           "an \"href\" element.");
2763             return HTTP_BAD_REQUEST;
2764         }
2765
2766         /* get version URI */
2767         ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
2768                        &target, &tsize);
2769         if (tsize == 0) {
2770             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2771                           "An \"href\" element does not contain a URI.");
2772             return HTTP_BAD_REQUEST;
2773         }
2774     }
2775
2776     /* Check request preconditions */
2777
2778     /* ### need a general mechanism for reporting precondition violations
2779      * ### (should be returning XML document for 403/409 responses)
2780      */
2781
2782     /* if not versioning existing resource, must specify version to select */
2783     if (!resource->exists && target == NULL) {
2784         err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
2785                             "<DAV:initial-version-required/>");
2786         return dav_handle_err(r, err, NULL);
2787     }
2788     else if (resource->exists) {
2789         /* cannot add resource to existing version history */
2790         if (target != NULL) {
2791             err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
2792                                 "<DAV:cannot-add-to-existing-history/>");
2793             return dav_handle_err(r, err, NULL);
2794         }
2795
2796         /* resource must be unversioned and versionable, or version selector */
2797         if (resource->type != DAV_RESOURCE_TYPE_REGULAR
2798             || (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
2799             err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
2800                                 "<DAV:must-be-versionable/>");
2801             return dav_handle_err(r, err, NULL);
2802         }
2803
2804         /* the DeltaV spec says if resource is a version selector,
2805          * then VERSION-CONTROL is a no-op
2806          */
2807         if (resource->versioned) {
2808             /* set the Cache-Control header, per the spec */
2809             apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
2810
2811             /* no body */
2812             ap_set_content_length(r, 0);
2813             ap_send_http_header(r);
2814
2815             return DONE;
2816         }
2817     }
2818
2819     /* Check If-Headers and existing locks */
2820     /* Note: depth == 0. Implies no need for a multistatus response. */
2821     if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
2822                                     resource_state == DAV_RESOURCE_NULL ?
2823                                     DAV_VALIDATE_PARENT :
2824                                     DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2825         return dav_handle_err(r, err, NULL);
2826     }
2827
2828     /* if in versioned collection, make sure parent is checked out */
2829     if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
2830                                             &av_info)) != NULL) {
2831         return dav_handle_err(r, err, NULL);
2832     }
2833
2834     /* attempt to version-control the resource */
2835     if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
2836         dav_revert_resource_writability(r, resource, 1 /*undo*/, &av_info);
2837         err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
2838                              apr_psprintf(r->pool,
2839                                          "Could not VERSION-CONTROL resource %s.",
2840                                          ap_escape_html(r->pool, r->uri)),
2841                              err);
2842         return dav_handle_err(r, err, NULL);
2843     }
2844
2845     /* revert writability of parent directory */
2846     err = dav_revert_resource_writability(r, resource, 0 /*undo*/, &av_info);
2847     if (err != NULL) {
2848         /* just log a warning */
2849         err = dav_push_error(r->pool, err->status, 0,
2850                              "The VERSION-CONTROL was successful, but there "
2851                              "was a problem reverting the writability of "
2852                              "the parent collection.",
2853                              err);
2854         dav_log_err(r, err, APLOG_WARNING);
2855     }
2856
2857     /* if the resource is lockable, let lock system know of new resource */
2858     if (locks_hooks != NULL
2859         && (*locks_hooks->get_supportedlock)(resource) != NULL) {
2860         dav_lockdb *lockdb;
2861
2862         if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2863             /* The resource creation was successful, but the locking failed. */
2864             err = dav_push_error(r->pool, err->status, 0,
2865                                  "The VERSION-CONTROL was successful, but there "
2866                                  "was a problem opening the lock database "
2867                                  "which prevents inheriting locks from the "
2868                                  "parent resources.",
2869                                  err);
2870             return dav_handle_err(r, err, NULL);
2871         }
2872
2873         /* notify lock system that we have created/replaced a resource */
2874         err = dav_notify_created(r, lockdb, resource, resource_state, 0);
2875
2876         (*locks_hooks->close_lockdb)(lockdb);
2877
2878         if (err != NULL) {
2879             /* The dir creation was successful, but the locking failed. */
2880             err = dav_push_error(r->pool, err->status, 0,
2881                                  "The VERSION-CONTROL was successful, but there "
2882                                  "was a problem updating its lock "
2883                                  "information.",
2884                                  err);
2885             return dav_handle_err(r, err, NULL);
2886         }
2887     }
2888
2889     /* set the Cache-Control header, per the spec */
2890     apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
2891
2892     /* return an appropriate response (HTTP_CREATED) */
2893     return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
2894 }
2895
2896 /* handle the CHECKOUT method */
2897 static int dav_method_checkout(request_rec *r)
2898 {
2899     dav_resource *resource;
2900     dav_resource *working_resource;
2901     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
2902     dav_error *err;
2903     int result;
2904     ap_xml_doc *doc;
2905     ap_xml_elem *target = NULL;
2906
2907     /* If no versioning provider, decline the request */
2908     if (vsn_hooks == NULL)
2909         return DECLINED;
2910
2911     if ((result = ap_xml_parse_input(r, &doc)) != OK)
2912         return result;
2913
2914     if (doc != NULL) {
2915         if (!dav_validate_root(doc, "checkout")) {
2916             /* This supplies additional information for the default msg. */
2917             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2918                           "The request body, if present, must be a "
2919                           "DAV:checkout element.");
2920             return HTTP_BAD_REQUEST;
2921         }
2922
2923         if ((target = dav_find_child(doc->root, "version")) == NULL)
2924             target = dav_find_child(doc->root, "label-name");
2925     }
2926
2927     /* Ask repository module to resolve the resource */
2928     result = dav_get_resource(r, 1 /*target_allowed*/, target, &resource);
2929     if (result != OK)
2930         return result;
2931     if (!resource->exists) {
2932         /* Apache will supply a default error for this. */
2933         return HTTP_NOT_FOUND;
2934     }
2935
2936     /* Check the state of the resource: must be a file or collection,
2937      * must be versioned, and must not already be checked out.
2938      */
2939     if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
2940         return dav_error_response(r, HTTP_CONFLICT,
2941                                   "Cannot checkout this type of resource.");
2942     }
2943
2944     if (!resource->versioned) {
2945         return dav_error_response(r, HTTP_CONFLICT,
2946                                   "Cannot checkout unversioned resource.");
2947     }
2948
2949     if (resource->working) {
2950         return dav_error_response(r, HTTP_CONFLICT,
2951                                   "The resource is already checked out to the workspace.");
2952     }
2953
2954     /* ### do lock checks, once behavior is defined */
2955
2956     /* Do the checkout */
2957     if ((err = (*vsn_hooks->checkout)(resource, &working_resource)) != NULL) {
2958         err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
2959                              apr_psprintf(r->pool,
2960                                          "Could not CHECKOUT resource %s.",
2961                                          ap_escape_html(r->pool, r->uri)),
2962                              err);
2963         return dav_handle_err(r, err, NULL);
2964     }
2965
2966     /* set the Cache-Control header, per the spec */
2967     apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
2968
2969     /* use appropriate URI for Location header */
2970     if (working_resource == NULL)
2971         working_resource = resource;
2972
2973     return dav_created(r, working_resource->uri, "Checked-out resource", 0);
2974 }
2975
2976 /* handle the UNCHECKOUT method */
2977 static int dav_method_uncheckout(request_rec *r)
2978 {
2979     dav_resource *resource;
2980     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
2981     dav_error *err;
2982     int result;
2983
2984     /* If no versioning provider, decline the request */
2985     if (vsn_hooks == NULL)
2986         return DECLINED;
2987
2988     if ((result = ap_discard_request_body(r)) != OK) {
2989         return result;
2990     }
2991
2992     /* Ask repository module to resolve the resource */
2993     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
2994     if (result != OK)
2995         return result;
2996     if (!resource->exists) {
2997         /* Apache will supply a default error for this. */
2998         return HTTP_NOT_FOUND;
2999     }
3000
3001     /* Check the state of the resource: must be a file or collection,
3002      * must be versioned, and must be checked out.
3003      */
3004     if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3005         return dav_error_response(r, HTTP_CONFLICT,
3006                                   "Cannot uncheckout this type of resource.");
3007     }
3008
3009     if (!resource->versioned) {
3010         return dav_error_response(r, HTTP_CONFLICT,
3011                                   "Cannot uncheckout unversioned resource.");
3012     }
3013
3014     if (!resource->working) {
3015         return dav_error_response(r, HTTP_CONFLICT,
3016                                   "The resource is not checked out to the workspace.");
3017     }
3018
3019     /* ### do lock checks, once behavior is defined */
3020
3021     /* Do the uncheckout */
3022     if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
3023         err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3024                              apr_psprintf(r->pool,
3025                                          "Could not UNCHECKOUT resource %s.",
3026                                          ap_escape_html(r->pool, r->uri)),
3027                              err);
3028         return dav_handle_err(r, err, NULL);
3029     }
3030
3031     /* no body */
3032     ap_set_content_length(r, 0);
3033     ap_send_http_header(r);
3034
3035     return DONE;
3036 }
3037
3038 /* handle the CHECKIN method */
3039 static int dav_method_checkin(request_rec *r)
3040 {
3041     dav_resource *resource;
3042     dav_resource *new_version;
3043     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3044     dav_error *err;
3045     int result;
3046
3047     /* If no versioning provider, decline the request */
3048     if (vsn_hooks == NULL)
3049         return DECLINED;
3050
3051     if ((result = ap_discard_request_body(r)) != OK) {
3052         return result;
3053     }
3054
3055     /* Ask repository module to resolve the resource */
3056     result = dav_get_resource(r, 0 /* target_allowed */, NULL, &resource);
3057     if (result != OK)
3058         return result;
3059     if (!resource->exists) {
3060         /* Apache will supply a default error for this. */
3061         return HTTP_NOT_FOUND;
3062     }
3063
3064     /* Check the state of the resource: must be a file or collection,
3065      * must be versioned, and must be checked out.
3066      */
3067     if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3068         return dav_error_response(r, HTTP_CONFLICT,
3069                                   "Cannot checkin this type of resource.");
3070     }
3071
3072     if (!resource->versioned) {
3073         return dav_error_response(r, HTTP_CONFLICT,
3074                                   "Cannot checkin unversioned resource.");
3075     }
3076
3077     if (!resource->working) {
3078         return dav_error_response(r, HTTP_CONFLICT,
3079                                   "The resource is not checked out to the workspace.");
3080     }
3081
3082     /* ### do lock checks, once behavior is defined */
3083
3084     /* Do the checkin */
3085     if ((err = (*vsn_hooks->checkin)(resource, &new_version)) != NULL) {
3086         err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3087                              apr_psprintf(r->pool,
3088                                          "Could not CHECKIN resource %s.",
3089                                          ap_escape_html(r->pool, r->uri)),
3090                              err);
3091         return dav_handle_err(r, err, NULL);
3092     }
3093
3094     return dav_created(r, new_version->uri, "Version", 0);
3095 }
3096
3097 /* context maintained during SET-TARGET treewalk */
3098 typedef struct dav_set_target_walker_ctx
3099 {
3100     /* input: */
3101     dav_walk_params w;
3102
3103     /* target specifier */
3104     const char *target;
3105
3106     /* flag for whether target is version URI or label */
3107     int is_label;
3108
3109     /* version provider hooks */
3110     const dav_hooks_vsn *vsn_hooks;
3111
3112 } dav_set_target_walker_ctx;
3113
3114 static dav_error * dav_set_target_walker(dav_walk_resource *wres, int calltype)
3115 {
3116     dav_set_target_walker_ctx *ctx = wres->walk_ctx;
3117     dav_error *err = NULL;
3118
3119     /* Check the state of the resource: must be a checked-in version
3120      * or baseline selector
3121      */
3122     /* ### need a general mechanism for reporting precondition violations
3123      * ### (should be returning XML document for 403/409 responses)
3124      */
3125     if (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
3126         || !wres->resource->versioned || wres->resource->working) {
3127         err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3128                             "<DAV:must-be-checked-in-version-selector/>");
3129     }
3130     else {
3131         /* do the set-target operation */
3132         err = (*ctx->vsn_hooks->set_target)(wres->resource, ctx->target, ctx->is_label);
3133     }
3134
3135     if (err != NULL) {
3136         /* ### need utility routine to add response with description? */
3137         dav_add_response(wres, err->status, NULL);
3138         wres->response->desc = err->desc;
3139     }
3140
3141     return NULL;
3142 }
3143
3144 static int dav_method_set_target(request_rec *r)
3145 {
3146     dav_resource *resource;
3147     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3148     ap_xml_doc *doc;
3149     ap_xml_elem *child;
3150     int depth;
3151     int result;
3152     apr_size_t tsize;
3153     dav_error *err;
3154     dav_set_target_walker_ctx ctx = { { 0 } };
3155     dav_response *multi_status;
3156
3157     /* If no versioning provider, decline the request */
3158     if (vsn_hooks == NULL)
3159         return DECLINED;
3160
3161     /* Ask repository module to resolve the resource */
3162     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
3163     if (result != OK)
3164         return result;
3165     if (!resource->exists) {
3166         /* Apache will supply a default error for this. */
3167         return HTTP_NOT_FOUND;
3168     }
3169
3170     if ((depth = dav_get_depth(r, 0)) < 0) {
3171         /* dav_get_depth() supplies additional information for the
3172          * default message. */
3173         return HTTP_BAD_REQUEST;
3174     }
3175
3176     /* parse the request body */
3177     if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3178         return result;
3179     }
3180
3181     if (doc == NULL || !dav_validate_root(doc, "set-target")) {
3182         /* This supplies additional information for the default message. */
3183         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3184                       "The request body does not contain "
3185                       "a \"set-target\" element.");
3186         return HTTP_BAD_REQUEST;
3187     }
3188
3189     /* check for label-name or version element */
3190     if ((child = dav_find_child(doc->root, "label-name")) != NULL) {
3191         ctx.is_label = 1;
3192     }
3193     else if ((child = dav_find_child(doc->root, "version")) != NULL) {
3194         ctx.is_label = 0;
3195
3196         /* get the href element */
3197         if ((child = dav_find_child(child, "href")) == NULL) {
3198             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3199                           "The version element does not contain "
3200                           "an \"href\" element.");
3201             return HTTP_BAD_REQUEST;
3202         }
3203     }
3204     else {
3205         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3206                       "The \"set-target\" element does not contain "
3207                       "a \"label-name\" or \"version\" element.");
3208         return HTTP_BAD_REQUEST;
3209     }
3210
3211     /* get the target value (a label or a version URI */
3212     ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3213                    &ctx.target, &tsize);
3214     if (tsize == 0) {
3215         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3216                       "A \"label-name\" or \"href\" element does not contain "
3217                       "any content.");
3218         return HTTP_BAD_REQUEST;
3219     }
3220
3221     /* do the set-target operation walk */
3222     ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
3223     ctx.w.func = dav_set_target_walker;
3224     ctx.w.walk_ctx = &ctx;
3225     ctx.w.pool = r->pool;
3226     ctx.w.root = resource;
3227     ctx.vsn_hooks = vsn_hooks;
3228
3229     err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
3230
3231     if (err != NULL) {
3232         /* some sort of error occurred which terminated the walk */
3233         err = dav_push_error(r->pool, err->status, 0,
3234                              "The SET-TARGET operation was terminated prematurely.",
3235                              err);
3236         return dav_handle_err(r, err, multi_status);
3237     }
3238
3239     if (multi_status != NULL) {
3240         /* One or more resources had errors. If depth was zero, convert
3241          * response to simple error, else make sure there is an
3242          * overall error to pass to dav_handle_err()
3243          */
3244         if (depth == 0) {
3245             err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
3246             multi_status = NULL;
3247         }
3248         else {
3249             err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
3250                                 "Errors occurred during the SET-TARGET operation.");
3251         }
3252
3253         return dav_handle_err(r, err, multi_status);
3254     }
3255
3256     /* set the Cache-Control header, per the spec */
3257     apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3258
3259     /* no body */
3260     ap_set_content_length(r, 0);
3261     ap_send_http_header(r);
3262
3263     return DONE;
3264 }
3265
3266 /* context maintained during LABEL treewalk */
3267 typedef struct dav_label_walker_ctx
3268 {
3269     /* input: */
3270     dav_walk_params w;
3271
3272     /* label being manipulated */
3273     const char *label;
3274
3275     /* label operation */
3276     int label_op;
3277 #define DAV_LABEL_ADD           1
3278 #define DAV_LABEL_SET           2
3279 #define DAV_LABEL_REMOVE        3
3280
3281     /* version provider hooks */
3282     const dav_hooks_vsn *vsn_hooks;
3283
3284 } dav_label_walker_ctx;
3285
3286 static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
3287 {
3288     dav_label_walker_ctx *ctx = wres->walk_ctx;
3289     dav_error *err = NULL;
3290
3291     /* Check the state of the resource: must be a version or
3292      * non-checkedout version selector
3293      */
3294     /* ### need a general mechanism for reporting precondition violations
3295      * ### (should be returning XML document for 403/409 responses)
3296      */
3297     if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
3298         (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
3299          || !wres->resource->versioned)) {
3300         err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3301                             "<DAV:must-be-version-or-version-selector/>");
3302     }
3303     else if (wres->resource->working) {
3304         err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3305                             "<DAV:must-not-be-checked-out/>");
3306     }
3307     else {
3308         /* do the label operation */
3309         if (ctx->label_op == DAV_LABEL_REMOVE)
3310             err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
3311         else
3312             err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
3313                                                ctx->label_op == DAV_LABEL_SET);
3314     }
3315
3316     if (err != NULL) {
3317         /* ### need utility routine to add response with description? */
3318         dav_add_response(wres, err->status, NULL);
3319         wres->response->desc = err->desc;
3320     }
3321
3322     return NULL;
3323 }
3324
3325 static int dav_method_label(request_rec *r)
3326 {
3327     dav_resource *resource;
3328     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3329     ap_xml_doc *doc;
3330     ap_xml_elem *child;
3331     int depth;
3332     int result;
3333     apr_size_t tsize;
3334     dav_error *err;
3335     dav_label_walker_ctx ctx = { { 0 } };
3336     dav_response *multi_status;
3337
3338     /* If no versioning provider, or the provider doesn't support
3339      * labels, decline the request */
3340     if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
3341         return DECLINED;
3342
3343     /* Ask repository module to resolve the resource */
3344     result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
3345     if (result != OK)
3346         return result;
3347     if (!resource->exists) {
3348         /* Apache will supply a default error for this. */
3349         return HTTP_NOT_FOUND;
3350     }
3351
3352     if ((depth = dav_get_depth(r, 0)) < 0) {
3353         /* dav_get_depth() supplies additional information for the
3354          * default message. */
3355         return HTTP_BAD_REQUEST;
3356     }
3357
3358     /* parse the request body */
3359     if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3360         return result;
3361     }
3362
3363     if (doc == NULL || !dav_validate_root(doc, "label")) {
3364         /* This supplies additional information for the default message. */
3365         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3366                       "The request body does not contain "
3367                       "a \"label\" element.");
3368         return HTTP_BAD_REQUEST;
3369     }
3370
3371     /* check for add, set, or remove element */
3372     if ((child = dav_find_child(doc->root, "add")) != NULL) {
3373         ctx.label_op = DAV_LABEL_ADD;
3374     }
3375     else if ((child = dav_find_child(doc->root, "set")) != NULL) {
3376         ctx.label_op = DAV_LABEL_SET;
3377     }
3378     else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
3379         ctx.label_op = DAV_LABEL_REMOVE;
3380     }
3381     else {
3382         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3383                       "The \"label\" element does not contain "
3384                       "an \"add\", \"set\", or \"remove\" element.");
3385         return HTTP_BAD_REQUEST;
3386     }
3387
3388     /* get the label string */
3389     if ((child = dav_find_child(child, "label-name")) == NULL) {
3390         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3391                       "The label command element does not contain "
3392                       "a \"label-name\" element.");
3393         return HTTP_BAD_REQUEST;
3394     }
3395
3396     ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3397                    &ctx.label, &tsize);
3398     if (tsize == 0) {
3399         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3400                       "A \"label-name\" element does not contain "
3401                       "a label name.");
3402         return HTTP_BAD_REQUEST;
3403     }
3404
3405     /* do the label operation walk */
3406     ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
3407     ctx.w.func = dav_label_walker;
3408     ctx.w.walk_ctx = &ctx;
3409     ctx.w.pool = r->pool;
3410     ctx.w.root = resource;
3411     ctx.vsn_hooks = vsn_hooks;
3412
3413     err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
3414
3415     if (err != NULL) {
3416         /* some sort of error occurred which terminated the walk */
3417         err = dav_push_error(r->pool, err->status, 0,
3418                              "The LABEL operation was terminated prematurely.",
3419                              err);
3420         return dav_handle_err(r, err, multi_status);
3421     }
3422
3423     if (multi_status != NULL) {
3424         /* One or more resources had errors. If depth was zero, convert
3425          * response to simple error, else make sure there is an
3426          * overall error to pass to dav_handle_err()
3427          */
3428         if (depth == 0) {
3429             err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
3430             multi_status = NULL;
3431         }
3432         else {
3433             err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
3434                                 "Errors occurred during the LABEL operation.");
3435         }
3436
3437         return dav_handle_err(r, err, multi_status);
3438     }
3439
3440     /* set the Cache-Control header, per the spec */
3441     apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3442
3443     /* no body */
3444     ap_set_content_length(r, 0);
3445     ap_send_http_header(r);
3446
3447     return DONE;
3448 }
3449
3450 static int dav_method_report(request_rec *r)
3451 {
3452     dav_resource *resource;
3453     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3454     int result;
3455     int target_allowed;
3456     ap_xml_doc *doc;
3457     ap_text_header hdr = { 0 };
3458     ap_text *t;
3459     dav_error *err;
3460
3461     /* If no versioning provider, decline the request */
3462     if (vsn_hooks == NULL)
3463         return DECLINED;
3464
3465     if ((result = ap_xml_parse_input(r, &doc)) != OK)
3466         return result;
3467     if (doc == NULL) {
3468         /* This supplies additional information for the default msg. */
3469         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3470                       "The request body must specify a report.");
3471         return HTTP_BAD_REQUEST;
3472     }
3473
3474     /* Ask repository module to resolve the resource.
3475      * First determine whether a Target-Selector header is allowed
3476      * for this report.
3477      */
3478     target_allowed = (*vsn_hooks->report_target_selector_allowed)(doc);
3479     result = dav_get_resource(r, target_allowed, NULL, &resource);
3480     if (result != OK)
3481         return result;
3482     if (!resource->exists) {
3483         /* Apache will supply a default error for this. */
3484         return HTTP_NOT_FOUND;
3485     }
3486
3487     /* run report hook */
3488     /* ### writing large reports to memory could be bad...
3489      * ### but if provider generated output directly, it would
3490      * ### have to handle error responses as well.
3491      */
3492     if ((err = (*vsn_hooks->get_report)(r, resource, doc, &hdr)) != NULL)
3493         return dav_handle_err(r, err, NULL);
3494
3495     /* send the report response */
3496     r->status = HTTP_OK;
3497     r->content_type = DAV_XML_CONTENT_TYPE;
3498
3499     /* send the headers and start a timeout */
3500     ap_send_http_header(r);
3501
3502     /* send the response body */
3503     ap_rputs(DAV_XML_HEADER DEBUG_CR, r);
3504
3505     for (t = hdr.first; t != NULL; t = t->next)
3506         ap_rputs(t->text, r);
3507
3508     return DONE;
3509 }
3510
3511 static int dav_method_make_workspace(request_rec *r)
3512 {
3513     dav_resource *resource;
3514     const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3515     dav_error *err;
3516     ap_xml_doc *doc;
3517     int result;
3518
3519     /* if no versioning provider, or the provider does not support workspaces,
3520      * decline the request
3521      */
3522     if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
3523         return DECLINED;
3524
3525     /* ask repository module to resolve the resource */
3526     result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
3527     if (result != OK)
3528         return result;
3529
3530     /* parse the request body (must be a mkworkspace element) */
3531     if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3532         return result;
3533     }
3534
3535     if (doc == NULL
3536         || !dav_validate_root(doc, "mkworkspace")) {
3537         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3538                       "The request body does not contain "
3539                       "a \"mkworkspace\" element.");
3540         return HTTP_BAD_REQUEST;
3541     }
3542
3543     /* Check request preconditions */
3544
3545     /* ### need a general mechanism for reporting precondition violations
3546      * ### (should be returning XML document for 403/409 responses)
3547      */
3548
3549     /* resource must not already exist */
3550     if (resource->exists) {
3551         err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3552                             "<DAV:resource-must-be-null/>");
3553         return dav_handle_err(r, err, NULL);
3554     }
3555
3556     /* ### what about locking? */
3557
3558     /* attempt to create the workspace */
3559     if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
3560         err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3561                              apr_psprintf(r->pool,
3562                                          "Could not create workspace %s.",
3563                                          ap_escape_html(r->pool, r->uri)),
3564                              err);
3565         return dav_handle_err(r, err, NULL);
3566     }
3567
3568     /* set the Cache-Control header, per the spec */
3569     apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3570
3571     /* return an appropriate response (HTTP_CREATED) */
3572     return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
3573 }
3574
3575 static int dav_method_make_activity(request_rec *r)
3576 {
3577     /* ### */
3578     return HTTP_METHOD_NOT_ALLOWED;
3579 }
3580
3581 static int dav_method_baseline_control(request_rec *r)
3582 {
3583     /* ### */
3584     return HTTP_METHOD_NOT_ALLOWED;
3585 }
3586
3587 static int dav_method_merge(request_rec *r)
3588 {
3589     /* ### */
3590     return HTTP_METHOD_NOT_ALLOWED;
3591 }
3592
3593 static int dav_method_bind(request_rec *r)
3594 {
3595     dav_resource *resource;
3596     dav_resource *binding;
3597     dav_auto_version_info av_info;
3598     const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
3599     const char *dest;
3600     dav_error *err;
3601     dav_error *err2;
3602     dav_response *multi_response = NULL;
3603     dav_lookup_result lookup;
3604     int overwrite;
3605     int result;
3606
3607     /* If no bindings provider, decline the request */
3608     if (binding_hooks == NULL)
3609         return DECLINED;
3610
3611     /* Ask repository module to resolve the resource */
3612     result = dav_get_resource(r, 0 /*!target_allowed*/, NULL, &resource);
3613     if (result != OK)
3614         return result;
3615     if (!resource->exists) {
3616         /* Apache will supply a default error for this. */
3617         return HTTP_NOT_FOUND;
3618     }
3619
3620     /* get the destination URI */
3621     dest = apr_table_get(r->headers_in, "Destination");
3622     if (dest == NULL) {
3623         /* This supplies additional information for the default message. */
3624         ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3625                       "The request is missing a Destination header.");
3626         return HTTP_BAD_REQUEST;
3627     }
3628
3629     lookup = dav_lookup_uri(dest, r);
3630     if (lookup.rnew == NULL) {
3631         if (lookup.err.status == HTTP_BAD_REQUEST) {
3632             /* This supplies additional information for the default message. */
3633             ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3634                           lookup.err.desc);
3635             return HTTP_BAD_REQUEST;
3636         }
3637         else if (lookup.err.status == HTTP_BAD_GATEWAY) {
3638             /* ### Bindings protocol draft 02 says to return 507
3639              * ### (Cross Server Binding Forbidden); Apache already defines 507
3640              * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
3641              * ### HTTP_FORBIDDEN
3642              */
3643              return dav_error_response(r, HTTP_FORBIDDEN,
3644                                        "Cross server bindings are not allowed by this server.");
3645         }
3646
3647         /* ### this assumes that dav_lookup_uri() only generates a status
3648          * ### that Apache can provide a status line for!! */
3649
3650         return dav_error_response(r, lookup.err.status, lookup.err.desc);
3651     }
3652     if (lookup.rnew->status != HTTP_OK) {
3653         /* ### how best to report this... */
3654         return dav_error_response(r, lookup.rnew->status,
3655                                   "Destination URI had an error.");
3656     }
3657
3658     /* resolve binding resource */
3659     result = dav_get_resource(lookup.rnew, 0 /*!target_allowed*/, NULL, &binding);
3660     if (result != OK)
3661         return result;
3662
3663     /* are the two resources handled by the same repository? */
3664     if (resource->hooks != binding->hooks) {
3665         /* ### this message exposes some backend config, but screw it... */
3666         return dav_error_response(r, HTTP_BAD_GATEWAY,
3667                                   "Destination URI is handled by a "
3668                                   "different repository than the source URI. "
3669                                   "BIND between repositories is not possible.");
3670     }
3671
3672     /* get and parse the overwrite header value */
3673     if ((overwrite = dav_get_overwrite(r)) < 0) {
3674         /* dav_get_overwrite() supplies additional information for the
3675          * default message. */
3676         return HTTP_BAD_REQUEST;
3677     }
3678
3679     /* quick failure test: if dest exists and overwrite is false. */
3680     if (binding->exists && !overwrite) {
3681         return dav_error_response(r, HTTP_PRECONDITION_FAILED,
3682                                   "Destination is not empty and "
3683                                   "Overwrite is not \"T\"");
3684     }
3685
3686     /* are the source and destination the same? */
3687     if ((*resource->hooks->is_same_resource)(resource, binding)) {
3688         return dav_error_response(r, HTTP_FORBIDDEN,
3689                                   "Source and Destination URIs are the same.");
3690     }
3691
3692     /*
3693     ** Check If-Headers and existing locks for destination. Note that we
3694     ** use depth==infinity since the target (hierarchy) will be deleted
3695     ** before the move/copy is completed.
3696     **
3697     ** Note that we are overwriting the target, which implies a DELETE, so
3698     ** we are subject to the error/response rules as a DELETE. Namely, we
3699     ** will return a 424 error if any of the validations fail.
3700     ** (see dav_method_delete() for more information)
3701     */
3702     if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
3703                                     &multi_response,
3704                                     DAV_VALIDATE_PARENT
3705                                     | DAV_VALIDATE_USE_424, NULL)) != NULL) {
3706         err = dav_push_error(r->pool, err->status, 0,
3707                              apr_psprintf(r->pool,
3708                                          "Could not BIND %s due to a "
3709                                          "failed precondition on the "
3710                                          "destination (e.g. locks).",
3711                                          ap_escape_html(r->pool, r->uri)),
3712                              err);
3713         return dav_handle_err(r, err, multi_response);
3714     }
3715
3716     /* guard against creating circular bindings */
3717     if (resource->collection
3718         && (*resource->hooks->is_parent_resource)(resource, binding)) {
3719         return dav_error_response(r, HTTP_FORBIDDEN,
3720                                   "Source collection contains the Destination.");
3721     }
3722     if (resource->collection
3723         && (*resource->hooks->is_parent_resource)(binding, resource)) {
3724         /* The destination must exist (since it contains the source), and
3725          * a condition above implies Overwrite==T. Obviously, we cannot
3726          * delete the Destination before the BIND, as that would
3727          * delete the Source.
3728          */
3729
3730         return dav_error_response(r, HTTP_FORBIDDEN,
3731                                   "Destination collection contains the Source and "
3732                                   "Overwrite has been specified.");
3733     }
3734
3735     /* prepare the destination collection for modification */
3736     if ((err = dav_ensure_resource_writable(r, binding, 1 /* parent_only */,
3737                                             &av_info)) != NULL) {
3738         /* could not make destination writable */
3739         return dav_handle_err(r, err, NULL);
3740     }
3741
3742     /* If target exists, remove it first (we know Ovewrite must be TRUE).
3743      * Then try to bind to the resource.
3744      */
3745     if (binding->exists)
3746         err = (*resource->hooks->remove_resource)(binding, &multi_response);
3747
3748     if (err == NULL) {
3749         err = (*binding_hooks->bind_resource)(resource, binding);
3750     }
3751
3752     /* restore parent collection states */
3753     err2 = dav_revert_resource_writability(r, NULL,
3754                                            err != NULL /* undo if error */,
3755                                            &av_info);
3756
3757     /* check for error from remove/bind operations */
3758     if (err != NULL) {
3759         err = dav_push_error(r->pool, err->status, 0,
3760                              apr_psprintf(r->pool,
3761                                          "Could not BIND %s.",
3762                                          ap_escape_html(r->pool, r->uri)),
3763                              err);
3764         return dav_handle_err(r, err, multi_response);
3765     }
3766
3767     /* check for errors from reverting writability */
3768     if (err2 != NULL) {
3769         /* just log a warning */
3770         err = dav_push_error(r->pool, err2->status, 0,
3771                              "The BIND was successful, but there was a "
3772                              "problem reverting the writability of the "
3773                              "source parent collection.",
3774                              err2);
3775         dav_log_err(r, err, APLOG_WARNING);
3776     }
3777
3778     /* return an appropriate response (HTTP_CREATED) */
3779     /* ### spec doesn't say what happens when destination was replaced */
3780     return dav_created(r, lookup.rnew->uri, "Binding", 0);
3781 }
3782
3783
3784 /*
3785  * Response handler for DAV resources
3786  */
3787 static int dav_handler(request_rec *r)
3788 {
3789     dav_dir_conf *conf;
3790
3791     /* quickly ignore any HTTP/0.9 requests */
3792     if (r->assbackwards) {
3793         return DECLINED;
3794     }
3795
3796     /* ### do we need to do anything with r->proxyreq ?? */
3797
3798     conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
3799                                                  &dav_module);
3800
3801     /*
3802      * Set up the methods mask, since that's one of the reasons this handler
3803      * gets called, and lower-level things may need the info.
3804      *
3805      * First, set the mask to the methods we handle directly.  Since by
3806      * definition we own our managed space, we unconditionally set
3807      * the r->allowed field rather than ORing our values with anything
3808      * any other module may have put in there.
3809      *
3810      * These are the HTTP-defined methods that we handle directly.
3811      */
3812     r->allowed = 0
3813         | (1 << M_GET)
3814         | (1 << M_PUT)
3815         | (1 << M_DELETE)
3816         | (1 << M_OPTIONS)
3817         | (1 << M_INVALID);
3818     /*
3819      * These are the DAV methods we handle.
3820      */
3821     r->allowed |= 0
3822         | (1 << M_COPY)
3823         | (1 << M_LOCK)
3824         | (1 << M_UNLOCK)
3825         | (1 << M_MKCOL)
3826         | (1 << M_MOVE)
3827         | (1 << M_PROPFIND)
3828         | (1 << M_PROPPATCH);
3829     /*
3830      * These are methods that we don't handle directly, but let the
3831      * server's default handler do for us as our agent.
3832      */
3833     r->allowed |= 0
3834         | (1 << M_POST);
3835  
3836     /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
3837      * ### is sent; it will need the other allowed states; since the default
3838      * ### handler is not called on error, then it doesn't add the other
3839      * ### allowed states, so we must */
3840     /* ### we might need to refine this for just where we return the error.
3841      * ### also, there is the issue with other methods (see ISSUES) */
3842     /* ### more work necessary, now that we have M_foo for DAV methods */
3843
3844     /* dispatch the appropriate method handler */
3845     if (r->method_number == M_GET) {
3846         return dav_method_get(r);
3847     }
3848
3849     if (r->method_number == M_PUT) {
3850         return dav_method_put(r);
3851     }
3852
3853     if (r->method_number == M_POST) {
3854         return dav_method_post(r);
3855     }
3856
3857     if (r->method_number == M_DELETE) {
3858         return dav_method_delete(r);
3859     }
3860
3861     if (r->method_number == M_OPTIONS) {
3862         return dav_method_options(r);
3863     }
3864
3865     if (r->method_number == M_PROPFIND) {
3866         return dav_method_propfind(r);
3867     }
3868
3869     if (r->method_number == M_PROPPATCH) {
3870         return dav_method_proppatch(r);
3871     }
3872
3873     if (r->method_number == M_MKCOL) {
3874         return dav_method_mkcol(r);
3875     }
3876
3877     if (r->method_number == M_COPY) {
3878         return dav_method_copymove(r, DAV_DO_COPY);
3879     }
3880
3881     if (r->method_number == M_MOVE) {
3882         return dav_method_copymove(r, DAV_DO_MOVE);
3883     }
3884
3885     if (r->method_number == M_LOCK) {
3886         return dav_method_lock(r);
3887     }
3888
3889     if (r->method_number == M_UNLOCK) {
3890         return dav_method_unlock(r);
3891     }
3892
3893     /*
3894      * NOTE: When Apache moves creates defines for the add'l DAV methods,
3895      *       then it will no longer use M_INVALID. This code must be
3896      *       updated each time Apache adds method defines.
3897      */
3898     if (r->method_number != M_INVALID) {
3899         return DECLINED;
3900     }
3901
3902     if (!strcmp(r->method, "VERSION-CONTROL")) {
3903         return dav_method_vsn_control(r);
3904     }
3905
3906     if (!strcmp(r->method, "CHECKOUT")) {
3907         return dav_method_checkout(r);
3908     }
3909
3910     if (!strcmp(r->method, "UNCHECKOUT")) {
3911         return dav_method_uncheckout(r);
3912     }
3913
3914     if (!strcmp(r->method, "CHECKIN")) {
3915         return dav_method_checkin(r);
3916     }
3917
3918     if (!strcmp(r->method, "SET-TARGET")) {
3919         return dav_method_set_target(r);
3920     }
3921
3922     if (!strcmp(r->method, "LABEL")) {
3923         return dav_method_label(r);
3924     }
3925
3926     if (!strcmp(r->method, "REPORT")) {
3927         return dav_method_report(r);
3928     }
3929
3930     if (!strcmp(r->method, "MKWORKSPACE")) {
3931         return dav_method_make_workspace(r);
3932     }
3933
3934     if (!strcmp(r->method, "MKACTIVITY")) {
3935         return dav_method_make_activity(r);
3936     }
3937
3938     if (!strcmp(r->method, "BASELINE-CONTROL")) {
3939         return dav_method_baseline_control(r);
3940     }
3941
3942     if (!strcmp(r->method, "MERGE")) {
3943         return dav_method_merge(r);
3944     }
3945
3946     if (!strcmp(r->method, "BIND")) {
3947         return dav_method_bind(r);
3948     }
3949
3950     /* ### add'l methods for Advanced Collections, ACLs, DASL */
3951
3952     return DECLINED;
3953 }
3954
3955 static int dav_type_checker(request_rec *r)
3956 {
3957     dav_dir_conf *conf;
3958
3959     conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
3960                                                  &dav_module);
3961
3962     /* if DAV is not enabled, then we've got nothing to do */
3963     if (conf->provider == NULL) {
3964         return DECLINED;
3965     }
3966
3967     if (r->method_number == M_GET) {
3968         /*
3969         ** ### need some work to pull Content-Type and Content-Language
3970         ** ### from the property database.
3971         */
3972             
3973         /*
3974         ** If the repository hasn't indicated that it will handle the
3975         ** GET method, then just punt.
3976         **
3977         ** ### this isn't quite right... taking over the response can break
3978         ** ### things like mod_negotiation. need to look into this some more.
3979         */
3980         if (!conf->provider->repos->handle_get) {
3981             return DECLINED;
3982         }
3983     }
3984
3985     /* ### we should (instead) trap the ones that we DO understand */
3986     /* ### the handler DOES handle POST, so we need to fix one of these */
3987     if (r->method_number != M_POST) {
3988
3989         /*
3990         ** ### anything else to do here? could another module and/or
3991         ** ### config option "take over" the handler here? i.e. how do
3992         ** ### we lock down this hierarchy so that we are the ultimate
3993         ** ### arbiter? (or do we simply depend on the administrator
3994         ** ### to avoid conflicting configurations?)
3995         **
3996         ** ### I think the OK stops running type-checkers. need to look.
3997         */
3998         r->handler = "dav-handler";
3999         return OK;
4000     }
4001
4002     return DECLINED;
4003 }
4004
4005 static void register_hooks(void)
4006 {
4007     ap_hook_post_config(dav_init_handler, NULL, NULL, AP_HOOK_MIDDLE);
4008     ap_hook_type_checker(dav_type_checker, NULL, NULL, AP_HOOK_FIRST);
4009
4010     ap_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, AP_HOOK_LAST);
4011     ap_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
4012                                  NULL, NULL, AP_HOOK_MIDDLE);
4013
4014     /* ### damn. need a pool. */
4015     dav_core_register_uris(NULL);
4016 }
4017
4018 /*---------------------------------------------------------------------------
4019 **
4020 ** Configuration info for the module
4021 */
4022
4023 static const command_rec dav_cmds[] =
4024 {
4025     /* per directory/location */
4026     AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
4027                   "specify the DAV provider for a directory or location"),
4028
4029     /* per directory/location, or per server */
4030     AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
4031                   ACCESS_CONF|RSRC_CONF,
4032                   "specify minimum allowed timeout"),
4033
4034     /* per directory/location, or per server */
4035     AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL,
4036                  ACCESS_CONF|RSRC_CONF,
4037                  "allow Depth infinity PROPFIND requests"),
4038
4039     /* per directory/location, or per server */
4040     AP_INIT_TAKE2("DAVParam", dav_cmd_davparam, NULL,
4041                   ACCESS_CONF|RSRC_CONF,
4042                   "DAVParam <parameter name> <parameter value>"),
4043
4044     { NULL }
4045 };
4046
4047 static const handler_rec dav_handlers[] =
4048 {
4049     {"dav-handler", dav_handler},
4050     { NULL }
4051 };
4052
4053 module DAV_DECLARE_DATA dav_module =
4054 {
4055     STANDARD20_MODULE_STUFF,
4056     dav_create_dir_config,      /* dir config creater */
4057     dav_merge_dir_config,       /* dir merger --- default is to override */
4058     dav_create_server_config,   /* server config */
4059     dav_merge_server_config,    /* merge server config */
4060     dav_cmds,                   /* command table */
4061     dav_handlers,               /* handlers */
4062     register_hooks,             /* register hooks */
4063 };
4064
4065 AP_HOOK_STRUCT(
4066     AP_HOOK_LINK(gather_propsets)
4067     AP_HOOK_LINK(find_liveprop)
4068     AP_HOOK_LINK(insert_all_liveprops)
4069     )
4070 AP_IMPLEMENT_EXTERNAL_HOOK_VOID(DAV, gather_propsets,
4071                                 (apr_array_header_t *uris),
4072                                 (uris))
4073 AP_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(DAV, int, find_liveprop,
4074                                      (const dav_resource *resource,
4075                                       const char *ns_uri, const char *name,
4076                                       const dav_hooks_liveprop **hooks),
4077                                      (resource, ns_uri, name, hooks), 0);
4078 AP_IMPLEMENT_EXTERNAL_HOOK_VOID(DAV, insert_all_liveprops,
4079                                 (request_rec *r, const dav_resource *resource,
4080                                  int insvalue, ap_text_header *phdr),
4081                                 (r, resource, insvalue, phdr));