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