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