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