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