1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
56 * DAV extension module for Apache 2.0.*
58 * This module is repository-independent. It depends on hooks provided by a
59 * repository implementation.
62 * - within a DAV hierarchy, if an unknown method is used and we default
63 * to Apache's implementation, it sends back an OPTIONS with the wrong
64 * set of methods -- there is NO HOOK for us.
65 * therefore: we need to manually handle the HTTP_METHOD_NOT_ALLOWED
66 * and HTTP_NOT_IMPLEMENTED responses (not ap_send_error_response).
67 * - process_mkcol_body() had to dup code from ap_setup_client_block().
68 * - it would be nice to get status lines from Apache for arbitrary
70 * - it would be nice to be able to extend Apache's set of response
71 * codes so that it doesn't return 500 when an unknown code is placed
73 * - http_vhost functions should apply "const" to their params
76 * - For PROPFIND, we batch up the entire response in memory before
77 * sending it. We may want to reorganize around sending the information
78 * as we suck it in from the propdb. Alternatively, we should at least
79 * generate a total Content-Length if we're going to buffer in memory
80 * so that we can keep the connection open.
83 #include "apr_strings.h"
84 #include "apr_lib.h" /* for apr_is* */
86 #define APR_WANT_STRFUNC
90 #include "http_config.h"
91 #include "http_core.h"
93 #include "http_main.h"
94 #include "http_protocol.h"
95 #include "http_request.h"
96 #include "util_script.h"
101 /* ### what is the best way to set this? */
102 #define DAV_DEFAULT_PROVIDER "filesystem"
105 DAV_ENABLED_UNSET = 0,
110 /* per-dir configuration */
112 const char *provider_name;
113 const dav_provider *provider;
116 int allow_depthinfinity;
120 /* per-server configuration */
126 #define DAV_INHERIT_VALUE(parent, child, field) \
127 ((child)->field ? (child)->field : (parent)->field)
130 /* forward-declare for use in configuration lookup */
131 extern module DAV_DECLARE_DATA dav_module;
138 static int dav_methods[DAV_M_LAST];
141 static int dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
144 /* DBG0("dav_init_handler"); */
146 /* Register DAV methods */
147 dav_methods[DAV_M_BIND] = ap_method_register(p, "BIND");
149 ap_add_version_component(p, "DAV/2");
154 static void *dav_create_server_config(apr_pool_t *p, server_rec *s)
156 dav_server_conf *newconf;
158 newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
160 /* ### this isn't used at the moment... */
165 static void *dav_merge_server_config(apr_pool_t *p, void *base, void *overrides)
168 dav_server_conf *child = overrides;
170 dav_server_conf *newconf;
172 newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
174 /* ### nothing to merge right now... */
179 static void *dav_create_dir_config(apr_pool_t *p, char *dir)
181 /* NOTE: dir==NULL creates the default per-dir config */
185 conf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*conf));
187 /* clean up the directory to remove any trailing slash */
192 d = apr_pstrdup(p, dir);
194 if (l > 1 && d[l - 1] == '/')
202 static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
204 dav_dir_conf *parent = base;
205 dav_dir_conf *child = overrides;
206 dav_dir_conf *newconf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*newconf));
208 /* DBG3("dav_merge_dir_config: new=%08lx base=%08lx overrides=%08lx",
209 (long)newconf, (long)base, (long)overrides); */
211 newconf->provider_name = DAV_INHERIT_VALUE(parent, child, provider_name);
212 newconf->provider = DAV_INHERIT_VALUE(parent, child, provider);
213 if (parent->provider_name != NULL) {
214 if (child->provider_name == NULL) {
215 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, NULL,
216 "\"DAV Off\" cannot be used to turn off a subtree "
217 "of a DAV-enabled location.");
219 else if (strcasecmp(child->provider_name,
220 parent->provider_name) != 0) {
221 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, NULL,
222 "A subtree cannot specify a different DAV provider "
227 newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
228 newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
229 newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
230 allow_depthinfinity);
235 static const dav_provider *dav_get_provider(request_rec *r)
239 conf = ap_get_module_config(r->per_dir_config, &dav_module);
240 /* assert: conf->provider_name != NULL
241 (otherwise, DAV is disabled, and we wouldn't be here) */
243 /* assert: conf->provider != NULL
244 (checked when conf->provider_name is set) */
245 return conf->provider;
248 const dav_hooks_locks *dav_get_lock_hooks(request_rec *r)
250 return dav_get_provider(r)->locks;
253 const dav_hooks_propdb *dav_get_propdb_hooks(request_rec *r)
255 return dav_get_provider(r)->propdb;
258 const dav_hooks_vsn *dav_get_vsn_hooks(request_rec *r)
260 return dav_get_provider(r)->vsn;
263 const dav_hooks_binding *dav_get_binding_hooks(request_rec *r)
265 return dav_get_provider(r)->binding;
269 * Command handler for the DAV directive, which is TAKE1.
271 static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
273 dav_dir_conf *conf = (dav_dir_conf *)config;
275 if (strcasecmp(arg1, "on") == 0) {
276 conf->provider_name = DAV_DEFAULT_PROVIDER;
278 else if (strcasecmp(arg1, "off") == 0) {
279 conf->provider_name = NULL;
280 conf->provider = NULL;
283 conf->provider_name = apr_pstrdup(cmd->pool, arg1);
286 if (conf->provider_name != NULL) {
287 /* lookup and cache the actual provider now */
288 conf->provider = dav_lookup_provider(conf->provider_name);
290 if (conf->provider == NULL) {
291 /* by the time they use it, the provider should be loaded and
292 registered with us. */
293 return apr_psprintf(cmd->pool,
294 "Unknown DAV provider: %s",
295 conf->provider_name);
303 * Command handler for the DAVDepthInfinity directive, which is FLAG.
305 static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
308 dav_dir_conf *conf = (dav_dir_conf *)config;
311 conf->allow_depthinfinity = DAV_ENABLED_ON;
313 conf->allow_depthinfinity = DAV_ENABLED_OFF;
318 * Command handler for DAVMinTimeout directive, which is TAKE1
320 static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config,
323 dav_dir_conf *conf = (dav_dir_conf *)config;
325 conf->locktimeout = atoi(arg1);
326 if (conf->locktimeout < 0)
327 return "DAVMinTimeout requires a non-negative integer.";
333 ** dav_error_response()
335 ** Send a nice response back to the user. In most cases, Apache doesn't
336 ** allow us to provide details in the body about what happened. This
337 ** function allows us to completely specify the response body.
339 ** ### this function is not logging any errors! (e.g. the body)
341 static int dav_error_response(request_rec *r, int status, const char *body)
345 /* ### I really don't think this is needed; gotta test */
346 r->status_line = ap_get_status_line(status);
348 ap_set_content_type(r, "text/html");
350 /* since we're returning DONE, ensure the request body is consumed. */
351 (void) ap_discard_request_body(r);
353 /* begin the response now... */
362 ap_psignature("<hr />\n", r),
366 /* the response has been sent. */
368 * ### Use of DONE obviates logging..!
375 * Send a "standardized" error response based on the error's namespace & tag
377 static int dav_error_response_tag(request_rec *r,
380 r->status = err->status;
382 /* ### I really don't think this is needed; gotta test */
383 r->status_line = ap_get_status_line(err->status);
385 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
387 /* since we're returning DONE, ensure the request body is consumed. */
388 (void) ap_discard_request_body(r);
390 ap_rputs(DAV_XML_HEADER DEBUG_CR
391 "<D:error xmlns:D=\"DAV:\"", r);
393 if (err->desc != NULL) {
394 /* ### should move this namespace somewhere (with the others!) */
395 ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r);
398 if (err->namespace != NULL) {
400 " xmlns:C=\"%s\">" DEBUG_CR
402 err->namespace, err->tagname);
407 "<D:%s/>" DEBUG_CR, err->tagname);
410 /* here's our mod_dav specific tag: */
411 if (err->desc != NULL) {
413 "<m:human-readable errcode=\"%d\">" DEBUG_CR
415 "</m:human-readable>" DEBUG_CR,
417 apr_xml_quote_string(r->pool, err->desc, 0));
420 ap_rputs("</D:error>" DEBUG_CR, r);
422 /* the response has been sent. */
424 * ### Use of DONE obviates logging..!
431 * Apache's URI escaping does not replace '&' since that is a valid character
432 * in a URI (to form a query section). We must explicitly handle it so that
433 * we can embed the URI into an XML document.
435 static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri)
437 const char *e_uri = ap_escape_uri(p, uri);
439 /* check the easy case... */
440 if (ap_strchr_c(e_uri, '&') == NULL)
443 /* there was a '&', so more work is needed... sigh. */
446 * Note: this is a teeny bit of overkill since we know there are no
447 * '<' or '>' characters, but who cares.
449 return ap_xml_quote_string(p, e_uri, 0);
452 static void dav_send_multistatus(request_rec *r, int status,
454 apr_array_header_t *namespaces)
456 /* Set the correct status and Content-Type */
458 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
460 /* Send the headers and actual multistatus response now... */
461 ap_rputs(DAV_XML_HEADER DEBUG_CR
462 "<D:multistatus xmlns:D=\"DAV:\"", r);
464 if (namespaces != NULL) {
467 for (i = namespaces->nelts; i--; ) {
468 ap_rprintf(r, " xmlns:ns%d=\"%s\"", i,
469 AP_XML_GET_URI_ITEM(namespaces, i));
473 /* ap_rputc('>', r); */
474 ap_rputs(">" DEBUG_CR, r);
476 for (; first != NULL; first = first->next) {
479 if (first->propresult.xmlns == NULL) {
480 ap_rputs("<D:response>", r);
483 ap_rputs("<D:response", r);
484 for (t = first->propresult.xmlns; t; t = t->next) {
485 ap_rputs(t->text, r);
490 ap_rputs(DEBUG_CR "<D:href>", r);
491 ap_rputs(dav_xml_escape_uri(r->pool, first->href), r);
492 ap_rputs("</D:href>" DEBUG_CR, r);
494 if (first->propresult.propstats == NULL) {
495 /* use the Status-Line text from Apache. Note, this will
496 * default to 500 Internal Server Error if first->status
497 * is not a known (or valid) status code.
500 "<D:status>HTTP/1.1 %s</D:status>" DEBUG_CR,
501 ap_get_status_line(first->status));
504 /* assume this includes <propstat> and is quoted properly */
505 for (t = first->propresult.propstats; t; t = t->next) {
506 ap_rputs(t->text, r);
510 if (first->desc != NULL) {
512 * We supply the description, so we know it doesn't have to
513 * have any escaping/encoding applied to it.
515 ap_rputs("<D:responsedescription>", r);
516 ap_rputs(first->desc, r);
517 ap_rputs("</D:responsedescription>" DEBUG_CR, r);
520 ap_rputs("</D:response>" DEBUG_CR, r);
523 ap_rputs("</D:multistatus>" DEBUG_CR, r);
529 * Write error information to the log.
531 static void dav_log_err(request_rec *r, dav_error *err, int level)
536 /* ### should have a directive to log the first or all */
537 for (errscan = err; errscan != NULL; errscan = errscan->prev) {
538 if (errscan->desc == NULL)
541 if (errscan->save_errno != 0) {
542 errno = errscan->save_errno;
543 ap_log_rerror(APLOG_MARK, level, errno, r, "%s [%d, #%d]",
544 errscan->desc, errscan->status, errscan->error_id);
547 ap_log_rerror(APLOG_MARK, level | APLOG_NOERRNO, 0, r,
549 errscan->desc, errscan->status, errscan->error_id);
557 * Handle the standard error processing. <err> must be non-NULL.
559 * <response> is set by the following:
560 * - dav_validate_request()
562 * - repos_hooks->remove_resource
563 * - repos_hooks->move_resource
564 * - repos_hooks->copy_resource
565 * - vsn_hooks->update
567 static int dav_handle_err(request_rec *r, dav_error *err,
568 dav_response *response)
571 dav_log_err(r, err, APLOG_ERR);
573 if (response == NULL) {
574 dav_error *stackerr = err;
576 /* our error messages are safe; tell Apache this */
577 apr_table_setn(r->notes, "verbose-error-to", "*");
579 /* Didn't get a multistatus response passed in, but we still
580 might be able to generate a standard <D:error> response.
581 Search the error stack for an errortag. */
582 while (stackerr != NULL && stackerr->tagname == NULL)
583 stackerr = stackerr->prev;
585 if (stackerr != NULL && stackerr->tagname != NULL)
586 return dav_error_response_tag(r, stackerr);
591 /* since we're returning DONE, ensure the request body is consumed. */
592 (void) ap_discard_request_body(r);
594 /* send the multistatus and tell Apache the request/response is DONE. */
595 dav_send_multistatus(r, err->status, response, NULL);
599 /* handy function for return values of methods that (may) create things */
600 static int dav_created(request_rec *r, const char *locn, const char *what,
609 /* did the target resource already exist? */
611 /* Apache will supply a default message */
612 return HTTP_NO_CONTENT;
615 /* Per HTTP/1.1, S10.2.2: add a Location header to contain the
616 * URI that was created. */
618 /* Convert locn to an absolute URI, and return in Location header */
619 apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, locn, r));
621 /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */
623 /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
624 * we must manufacture the entire response. */
625 body = apr_psprintf(r->pool, "%s %s has been created.",
626 what, ap_escape_html(r->pool, locn));
627 return dav_error_response(r, HTTP_CREATED, body);
630 /* ### move to dav_util? */
631 int dav_get_depth(request_rec *r, int def_depth)
633 const char *depth = apr_table_get(r->headers_in, "Depth");
639 if (strcasecmp(depth, "infinity") == 0) {
642 else if (strcmp(depth, "0") == 0) {
645 else if (strcmp(depth, "1") == 0) {
649 /* The caller will return an HTTP_BAD_REQUEST. This will augment the
650 * default message that Apache provides. */
651 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
652 "An invalid Depth header was specified.");
656 static int dav_get_overwrite(request_rec *r)
658 const char *overwrite = apr_table_get(r->headers_in, "Overwrite");
660 if (overwrite == NULL) {
661 return 1; /* default is "T" */
664 if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') {
668 if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') {
672 /* The caller will return an HTTP_BAD_REQUEST. This will augment the
673 * default message that Apache provides. */
674 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
675 "An invalid Overwrite header was specified.");
679 /* resolve a request URI to a resource descriptor.
681 * If label_allowed != 0, then allow the request target to be altered by
684 * If use_checked_in is true, then the repository provider should return
685 * the resource identified by the DAV:checked-in property of the resource
686 * identified by the Request-URI.
688 static dav_error *dav_get_resource(request_rec *r, int label_allowed,
689 int use_checked_in, dav_resource **res_p)
692 const char *label = NULL;
695 /* if the request target can be overridden, get any target selector */
697 label = apr_table_get(r->headers_in, "label");
700 conf = ap_get_module_config(r->per_dir_config, &dav_module);
701 /* assert: conf->provider != NULL */
703 /* resolve the resource */
704 err = (*conf->provider->repos->get_resource)(r, conf->dir,
705 label, use_checked_in,
708 err = dav_push_error(r->pool, err->status, 0,
709 "Could not fetch resource information.", err);
713 /* Note: this shouldn't happen, but just be sure... */
714 if (*res_p == NULL) {
715 /* ### maybe use HTTP_INTERNAL_SERVER_ERROR */
716 return dav_new_error(r->pool, HTTP_NOT_FOUND, 0,
717 apr_psprintf(r->pool,
718 "The provider did not define a "
720 ap_escape_html(r->pool, r->uri)));
723 /* ### hmm. this doesn't feel like the right place or thing to do */
724 /* if there were any input headers requiring a Vary header in the response,
726 dav_add_vary_header(r, r, *res_p);
731 static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
733 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
740 /* open the thing lazily */
741 return (*hooks->open_lockdb)(r, ro, 0, lockdb);
744 static int dav_parse_range(request_rec *r,
745 apr_off_t *range_start, apr_off_t *range_end)
752 range_c = apr_table_get(r->headers_in, "content-range");
756 range = apr_pstrdup(r->pool, range_c);
757 if (strncasecmp(range, "bytes ", 6) != 0
758 || (dash = ap_strchr(range, '-')) == NULL
759 || (slash = ap_strchr(range, '/')) == NULL) {
760 /* malformed header. ignore it (per S14.16 of RFC2616) */
764 *dash = *slash = '\0';
766 /* ### atol may not be large enough for the apr_off_t */
767 *range_start = atol(range + 6);
768 *range_end = atol(dash + 1);
770 if (*range_end < *range_start
771 || (slash[1] != '*' && atol(slash + 1) <= *range_end)) {
772 /* invalid range. ignore it (per S14.16 of RFC2616) */
776 /* we now have a valid range */
780 /* handle the GET method */
781 static int dav_method_get(request_rec *r)
783 dav_resource *resource;
786 /* This method should only be called when the resource is not
787 * visible to Apache. We will fetch the resource from the repository,
788 * then create a subrequest for Apache to handle.
790 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
793 return dav_handle_err(r, err, NULL);
795 if (!resource->exists) {
796 /* Apache will supply a default error for this. */
797 return HTTP_NOT_FOUND;
800 /* set up the HTTP headers for the response */
801 if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
802 err = dav_push_error(r->pool, err->status, 0,
803 "Unable to set up HTTP headers.",
805 return dav_handle_err(r, err, NULL);
808 if (r->header_only) {
812 /* okay... time to deliver the content */
813 if ((err = (*resource->hooks->deliver)(resource,
814 r->output_filters)) != NULL) {
815 err = dav_push_error(r->pool, err->status, 0,
816 "Unable to deliver content.",
818 return dav_handle_err(r, err, NULL);
824 /* validate resource on POST, then pass it off to the default handler */
825 static int dav_method_post(request_rec *r)
827 dav_resource *resource;
830 /* Ask repository module to resolve the resource */
831 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
834 return dav_handle_err(r, err, NULL);
836 /* Note: depth == 0. Implies no need for a multistatus response. */
837 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
838 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
839 /* ### add a higher-level description? */
840 return dav_handle_err(r, err, NULL);
846 /* handle the PUT method */
847 static int dav_method_put(request_rec *r)
849 dav_resource *resource;
851 dav_auto_version_info av_info;
852 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
857 dav_stream_mode mode;
859 dav_response *multi_response;
861 apr_off_t range_start;
864 if ((result = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != OK) {
868 /* Ask repository module to resolve the resource */
869 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
872 return dav_handle_err(r, err, NULL);
874 /* If not a file or collection resource, PUT not allowed */
875 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
876 && resource->type != DAV_RESOURCE_TYPE_WORKING) {
877 body = apr_psprintf(r->pool,
878 "Cannot create resource %s with PUT.",
879 ap_escape_html(r->pool, r->uri));
880 return dav_error_response(r, HTTP_CONFLICT, body);
883 /* Cannot PUT a collection */
884 if (resource->collection) {
885 return dav_error_response(r, HTTP_CONFLICT,
886 "Cannot PUT to a collection.");
890 resource_state = dav_get_resource_state(r, resource);
893 * Note: depth == 0 normally requires no multistatus response. However,
894 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
895 * other than the Request-URI, thereby requiring a multistatus.
897 * If the resource does not exist (DAV_RESOURCE_NULL), then we must
898 * check the resource *and* its parent. If the resource exists or is
899 * a locknull resource, then we check only the resource.
901 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
902 resource_state == DAV_RESOURCE_NULL ?
903 DAV_VALIDATE_PARENT :
904 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
905 /* ### add a higher-level description? */
906 return dav_handle_err(r, err, multi_response);
909 /* make sure the resource can be modified (if versioning repository) */
910 if ((err = dav_auto_checkout(r, resource,
911 0 /* not parent_only */,
912 &av_info)) != NULL) {
913 /* ### add a higher-level description? */
914 return dav_handle_err(r, err, NULL);
917 /* truncate and rewrite the file unless we see a Content-Range */
918 mode = DAV_MODE_WRITE_TRUNC;
920 has_range = dav_parse_range(r, &range_start, &range_end);
922 mode = DAV_MODE_WRITE_SEEKABLE;
925 /* Create the new file in the repository */
926 if ((err = (*resource->hooks->open_stream)(resource, mode,
928 /* ### assuming FORBIDDEN is probably not quite right... */
929 err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
930 apr_psprintf(r->pool,
931 "Unable to PUT new contents for %s.",
932 ap_escape_html(r->pool, r->uri)),
936 if (err == NULL && has_range) {
937 /* a range was provided. seek to the start */
938 err = (*resource->hooks->seek_stream)(stream, range_start);
942 if (ap_should_client_block(r)) {
943 char *buffer = apr_palloc(r->pool, DAV_READ_BLOCKSIZE);
947 * Once we start reading the request, then we must read the
948 * whole darn thing. ap_discard_request_body() won't do anything
949 * for a partially-read request.
952 while ((len = ap_get_client_block(r, buffer,
953 DAV_READ_BLOCKSIZE)) > 0) {
955 /* write whatever we read, until we see an error */
956 err = (*resource->hooks->write_stream)(stream,
962 * ### what happens if we read more/less than the amount
963 * ### specified in the Content-Range? eek...
968 * Error reading request body. This has precedence over
971 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
972 "An error occurred while reading the "
977 err2 = (*resource->hooks->close_stream)(stream,
978 err == NULL /* commit */);
979 if (err2 != NULL && err == NULL) {
980 /* no error during the write, but we hit one at close. use it. */
986 * Ensure that we think the resource exists now.
987 * ### eek. if an error occurred during the write and we did not commit,
988 * ### then the resource might NOT exist (e.g. dav_fs_repos.c)
991 resource->exists = 1;
994 /* restore modifiability of resources back to what they were */
995 err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */,
996 0 /*unlock*/, &av_info);
998 /* check for errors now */
1000 return dav_handle_err(r, err, NULL);
1004 /* just log a warning */
1005 err2 = dav_push_error(r->pool, err->status, 0,
1006 "The PUT was successful, but there "
1007 "was a problem automatically checking in "
1008 "the resource or its parent collection.",
1010 dav_log_err(r, err2, APLOG_WARNING);
1013 /* ### place the Content-Type and Content-Language into the propdb */
1015 if (locks_hooks != NULL) {
1018 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1019 /* The file creation was successful, but the locking failed. */
1020 err = dav_push_error(r->pool, err->status, 0,
1021 "The file was PUT successfully, but there "
1022 "was a problem opening the lock database "
1023 "which prevents inheriting locks from the "
1024 "parent resources.",
1026 return dav_handle_err(r, err, NULL);
1029 /* notify lock system that we have created/replaced a resource */
1030 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
1032 (*locks_hooks->close_lockdb)(lockdb);
1035 /* The file creation was successful, but the locking failed. */
1036 err = dav_push_error(r->pool, err->status, 0,
1037 "The file was PUT successfully, but there "
1038 "was a problem updating its lock "
1041 return dav_handle_err(r, err, NULL);
1045 /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
1047 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
1048 return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
1051 /* ### move this to dav_util? */
1052 DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
1053 int status, dav_get_props_result *propstats)
1057 /* just drop some data into an dav_response */
1058 resp = apr_pcalloc(wres->pool, sizeof(*resp));
1059 resp->href = apr_pstrdup(wres->pool, wres->resource->uri);
1060 resp->status = status;
1062 resp->propresult = *propstats;
1065 resp->next = wres->response;
1066 wres->response = resp;
1069 /* handle the DELETE method */
1070 static int dav_method_delete(request_rec *r)
1072 dav_resource *resource;
1073 dav_auto_version_info av_info;
1076 dav_response *multi_response;
1080 /* We don't use the request body right now, so torch it. */
1081 if ((result = ap_discard_request_body(r)) != OK) {
1085 /* Ask repository module to resolve the resource */
1086 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
1089 return dav_handle_err(r, err, NULL);
1090 if (!resource->exists) {
1091 /* Apache will supply a default error for this. */
1092 return HTTP_NOT_FOUND;
1095 /* 2518 says that depth must be infinity only for collections.
1096 * For non-collections, depth is ignored, unless it is an illegal value (1).
1098 depth = dav_get_depth(r, DAV_INFINITY);
1100 if (resource->collection && depth != DAV_INFINITY) {
1101 /* This supplies additional information for the default message. */
1102 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1103 "Depth must be \"infinity\" for DELETE of a collection.");
1104 return HTTP_BAD_REQUEST;
1107 if (!resource->collection && depth == 1) {
1108 /* This supplies additional information for the default message. */
1109 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1110 "Depth of \"1\" is not allowed for DELETE.");
1111 return HTTP_BAD_REQUEST;
1115 ** If any resources fail the lock/If: conditions, then we must fail
1116 ** the delete. Each of the failing resources will be listed within
1117 ** a DAV:multistatus body, wrapped into a 424 response.
1119 ** Note that a failure on the resource itself does not generate a
1120 ** multistatus response -- only internal members/collections.
1122 if ((err = dav_validate_request(r, resource, depth, NULL,
1125 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
1126 err = dav_push_error(r->pool, err->status, 0,
1127 apr_psprintf(r->pool,
1128 "Could not DELETE %s due to a failed "
1129 "precondition (e.g. locks).",
1130 ap_escape_html(r->pool, r->uri)),
1132 return dav_handle_err(r, err, multi_response);
1135 /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
1136 * locked by the token(s) in the if_header.
1138 if ((result = dav_unlock(r, resource, NULL)) != OK) {
1142 /* if versioned resource, make sure parent is checked out */
1143 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
1144 &av_info)) != NULL) {
1145 /* ### add a higher-level description? */
1146 return dav_handle_err(r, err, NULL);
1149 /* try to remove the resource */
1150 err = (*resource->hooks->remove_resource)(resource, &multi_response);
1152 /* restore writability of parent back to what it was */
1153 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
1154 0 /*unlock*/, &av_info);
1156 /* check for errors now */
1158 err = dav_push_error(r->pool, err->status, 0,
1159 apr_psprintf(r->pool,
1160 "Could not DELETE %s.",
1161 ap_escape_html(r->pool, r->uri)),
1163 return dav_handle_err(r, err, multi_response);
1166 /* just log a warning */
1167 err = dav_push_error(r->pool, err2->status, 0,
1168 "The DELETE was successful, but there "
1169 "was a problem automatically checking in "
1170 "the parent collection.",
1172 dav_log_err(r, err, APLOG_WARNING);
1175 /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
1177 /* Apache will supply a default error for this. */
1178 return HTTP_NO_CONTENT;
1181 /* generate DAV:supported-method-set OPTIONS response */
1182 static dav_error *dav_gen_supported_methods(request_rec *r,
1183 const ap_xml_elem *elem,
1184 const apr_table_t *methods,
1185 ap_text_header *body)
1187 const apr_array_header_t *arr;
1188 const apr_table_entry_t *elts;
1194 ap_text_append(r->pool, body, "<D:supported-method-set>" DEBUG_CR);
1196 if (elem->first_child == NULL) {
1197 /* show all supported methods */
1198 arr = apr_table_elts(methods);
1199 elts = (const apr_table_entry_t *)arr->elts;
1201 for (i = 0; i < arr->nelts; ++i) {
1202 if (elts[i].key == NULL)
1205 s = apr_psprintf(r->pool,
1206 "<D:supported-method D:name=\"%s\"/>"
1209 ap_text_append(r->pool, body, s);
1213 /* check for support of specific methods */
1214 for (child = elem->first_child; child != NULL; child = child->next) {
1215 if (child->ns == AP_XML_NS_DAV_ID
1216 && strcmp(child->name, "supported-method") == 0) {
1217 const char *name = NULL;
1219 /* go through attributes to find method name */
1220 for (attr = child->attr; attr != NULL; attr = attr->next) {
1221 if (attr->ns == AP_XML_NS_DAV_ID
1222 && strcmp(attr->name, "name") == 0)
1227 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1228 "A DAV:supported-method element "
1229 "does not have a \"name\" attribute");
1232 /* see if method is supported */
1233 if (apr_table_get(methods, name) != NULL) {
1234 s = apr_psprintf(r->pool,
1235 "<D:supported-method D:name=\"%s\"/>"
1238 ap_text_append(r->pool, body, s);
1244 ap_text_append(r->pool, body, "</D:supported-method-set>" DEBUG_CR);
1248 /* generate DAV:supported-live-property-set OPTIONS response */
1249 static dav_error *dav_gen_supported_live_props(request_rec *r,
1250 const dav_resource *resource,
1251 const ap_xml_elem *elem,
1252 ap_text_header *body)
1260 /* open lock database, to report on supported lock properties */
1261 /* ### should open read-only */
1262 if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
1263 return dav_push_error(r->pool, err->status, 0,
1264 "The lock database could not be opened, "
1265 "preventing the reporting of supported lock "
1270 /* open the property database (readonly) for the resource */
1271 if ((err = dav_open_propdb(r, lockdb, resource, 1, NULL,
1272 &propdb)) != NULL) {
1274 (*lockdb->hooks->close_lockdb)(lockdb);
1276 return dav_push_error(r->pool, err->status, 0,
1277 "The property database could not be opened, "
1278 "preventing report of supported properties.",
1282 ap_text_append(r->pool, body, "<D:supported-live-property-set>" DEBUG_CR);
1284 if (elem->first_child == NULL) {
1285 /* show all supported live properties */
1286 dav_get_props_result props = dav_get_allprops(propdb, DAV_PROP_INSERT_SUPPORTED);
1287 body->last->next = props.propstats;
1288 while (body->last->next != NULL)
1289 body->last = body->last->next;
1292 /* check for support of specific live property */
1293 for (child = elem->first_child; child != NULL; child = child->next) {
1294 if (child->ns == AP_XML_NS_DAV_ID
1295 && strcmp(child->name, "supported-live-property") == 0) {
1296 const char *name = NULL;
1297 const char *nmspace = NULL;
1299 /* go through attributes to find name and namespace */
1300 for (attr = child->attr; attr != NULL; attr = attr->next) {
1301 if (attr->ns == AP_XML_NS_DAV_ID) {
1302 if (strcmp(attr->name, "name") == 0)
1304 else if (strcmp(attr->name, "namespace") == 0)
1305 nmspace = attr->value;
1310 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1311 "A DAV:supported-live-property "
1312 "element does not have a \"name\" "
1317 /* default namespace to DAV: */
1318 if (nmspace == NULL)
1321 /* check for support of property */
1322 dav_get_liveprop_supported(propdb, nmspace, name, body);
1327 ap_text_append(r->pool, body, "</D:supported-live-property-set>" DEBUG_CR);
1329 dav_close_propdb(propdb);
1332 (*lockdb->hooks->close_lockdb)(lockdb);
1337 /* generate DAV:supported-report-set OPTIONS response */
1338 static dav_error *dav_gen_supported_reports(request_rec *r,
1339 const dav_resource *resource,
1340 const ap_xml_elem *elem,
1341 const dav_hooks_vsn *vsn_hooks,
1342 ap_text_header *body)
1349 ap_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR);
1351 if (vsn_hooks != NULL) {
1352 const dav_report_elem *reports;
1353 const dav_report_elem *rp;
1355 if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) {
1356 return dav_push_error(r->pool, err->status, 0,
1357 "DAV:supported-report-set could not be "
1358 "determined due to a problem fetching the "
1359 "available reports for this resource.",
1363 if (reports != NULL) {
1364 if (elem->first_child == NULL) {
1365 /* show all supported reports */
1366 for (rp = reports; rp->nmspace != NULL; ++rp) {
1367 /* Note: we presume reports->namespace is
1368 * properly XML/URL quoted */
1369 s = apr_psprintf(r->pool,
1370 "<D:supported-report D:name=\"%s\" "
1371 "D:namespace=\"%s\"/>" DEBUG_CR,
1372 rp->name, rp->nmspace);
1373 ap_text_append(r->pool, body, s);
1377 /* check for support of specific report */
1378 for (child = elem->first_child; child != NULL; child = child->next) {
1379 if (child->ns == AP_XML_NS_DAV_ID
1380 && strcmp(child->name, "supported-report") == 0) {
1381 const char *name = NULL;
1382 const char *nmspace = NULL;
1384 /* go through attributes to find name and namespace */
1385 for (attr = child->attr; attr != NULL; attr = attr->next) {
1386 if (attr->ns == AP_XML_NS_DAV_ID) {
1387 if (strcmp(attr->name, "name") == 0)
1389 else if (strcmp(attr->name, "namespace") == 0)
1390 nmspace = attr->value;
1395 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1396 "A DAV:supported-report element "
1397 "does not have a \"name\" attribute");
1400 /* default namespace to DAV: */
1401 if (nmspace == NULL)
1404 for (rp = reports; rp->nmspace != NULL; ++rp) {
1405 if (strcmp(name, rp->name) == 0
1406 && strcmp(nmspace, rp->nmspace) == 0) {
1407 /* Note: we presume reports->nmspace is
1408 * properly XML/URL quoted
1410 s = apr_psprintf(r->pool,
1411 "<D:supported-report "
1413 "D:namespace=\"%s\"/>"
1415 rp->name, rp->nmspace);
1416 ap_text_append(r->pool, body, s);
1426 ap_text_append(r->pool, body, "</D:supported-report-set>" DEBUG_CR);
1430 /* handle the OPTIONS method */
1431 static int dav_method_options(request_rec *r)
1433 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1434 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1435 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
1436 dav_resource *resource;
1437 const char *dav_level;
1440 const apr_array_header_t *arr;
1441 const apr_table_entry_t *elts;
1442 apr_table_t *methods = apr_table_make(r->pool, 12);
1443 ap_text_header vsn_options = { 0 };
1444 ap_text_header body = { 0 };
1449 apr_array_header_t *uri_ary;
1451 const ap_xml_elem *elem;
1454 /* resolve the resource */
1455 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
1458 return dav_handle_err(r, err, NULL);
1460 /* parse any request body */
1461 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1464 /* note: doc == NULL if no request body */
1466 if (doc && !dav_validate_root(doc, "options")) {
1467 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1468 "The \"options\" element was not found.");
1469 return HTTP_BAD_REQUEST;
1472 /* determine which providers are available */
1475 if (locks_hooks != NULL) {
1479 if (binding_hooks != NULL)
1480 dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
1483 * MSFT Web Folders chokes if length of DAV header value > 63 characters!
1484 * To workaround that, we use separate DAV headers for versioning and
1485 * live prop provider namespace URIs.
1488 apr_table_setn(r->headers_out, "DAV", dav_level);
1491 * If there is a versioning provider, generate DAV headers
1492 * for versioning options.
1494 if (vsn_hooks != NULL) {
1495 (*vsn_hooks->get_vsn_options)(r->pool, &vsn_options);
1497 for (t = vsn_options.first; t != NULL; t = t->next)
1498 apr_table_addn(r->headers_out, "DAV", t->text);
1502 * Gather property set URIs from all the liveprop providers,
1503 * and generate a separate DAV header for each URI, to avoid
1504 * problems with long header lengths.
1506 uri_ary = apr_array_make(r->pool, 5, sizeof(const char *));
1507 dav_run_gather_propsets(uri_ary);
1508 for (i = 0; i < uri_ary->nelts; ++i) {
1509 if (((char **)uri_ary->elts)[i] != NULL)
1510 apr_table_addn(r->headers_out, "DAV", ((char **)uri_ary->elts)[i]);
1513 /* this tells MSFT products to skip looking for FrontPage extensions */
1514 apr_table_setn(r->headers_out, "MS-Author-Via", "DAV");
1517 * Determine which methods are allowed on the resource.
1518 * Three cases: resource is null (3), is lock-null (7.4), or exists.
1520 * All cases support OPTIONS, and if there is a lock provider, LOCK.
1521 * (Lock-) null resources also support MKCOL and PUT.
1522 * Lock-null supports PROPFIND and UNLOCK.
1523 * Existing resources support lots of stuff.
1526 apr_table_addn(methods, "OPTIONS", "");
1528 /* ### take into account resource type */
1529 switch (dav_get_resource_state(r, resource))
1531 case DAV_RESOURCE_EXISTS:
1532 /* resource exists */
1533 apr_table_addn(methods, "GET", "");
1534 apr_table_addn(methods, "HEAD", "");
1535 apr_table_addn(methods, "POST", "");
1536 apr_table_addn(methods, "DELETE", "");
1537 apr_table_addn(methods, "TRACE", "");
1538 apr_table_addn(methods, "PROPFIND", "");
1539 apr_table_addn(methods, "PROPPATCH", "");
1540 apr_table_addn(methods, "COPY", "");
1541 apr_table_addn(methods, "MOVE", "");
1543 if (!resource->collection)
1544 apr_table_addn(methods, "PUT", "");
1546 if (locks_hooks != NULL) {
1547 apr_table_addn(methods, "LOCK", "");
1548 apr_table_addn(methods, "UNLOCK", "");
1553 case DAV_RESOURCE_LOCK_NULL:
1554 /* resource is lock-null. */
1555 apr_table_addn(methods, "MKCOL", "");
1556 apr_table_addn(methods, "PROPFIND", "");
1557 apr_table_addn(methods, "PUT", "");
1559 if (locks_hooks != NULL) {
1560 apr_table_addn(methods, "LOCK", "");
1561 apr_table_addn(methods, "UNLOCK", "");
1566 case DAV_RESOURCE_NULL:
1567 /* resource is null. */
1568 apr_table_addn(methods, "MKCOL", "");
1569 apr_table_addn(methods, "PUT", "");
1571 if (locks_hooks != NULL)
1572 apr_table_addn(methods, "LOCK", "");
1577 /* ### internal error! */
1581 /* If there is a versioning provider, add versioning methods */
1582 if (vsn_hooks != NULL) {
1583 if (!resource->exists) {
1584 if ((*vsn_hooks->versionable)(resource))
1585 apr_table_addn(methods, "VERSION-CONTROL", "");
1587 if (vsn_hooks->can_be_workspace != NULL
1588 && (*vsn_hooks->can_be_workspace)(resource))
1589 apr_table_addn(methods, "MKWORKSPACE", "");
1591 if (vsn_hooks->can_be_activity != NULL
1592 && (*vsn_hooks->can_be_activity)(resource))
1593 apr_table_addn(methods, "MKACTIVITY", "");
1595 else if (!resource->versioned) {
1596 if ((*vsn_hooks->versionable)(resource))
1597 apr_table_addn(methods, "VERSION-CONTROL", "");
1599 else if (resource->working) {
1600 apr_table_addn(methods, "CHECKIN", "");
1602 /* ### we might not support this DeltaV option */
1603 apr_table_addn(methods, "UNCHECKOUT", "");
1605 else if (vsn_hooks->add_label != NULL) {
1606 apr_table_addn(methods, "CHECKOUT", "");
1607 apr_table_addn(methods, "LABEL", "");
1610 apr_table_addn(methods, "CHECKOUT", "");
1614 /* If there is a bindings provider, see if resource is bindable */
1615 if (binding_hooks != NULL
1616 && (*binding_hooks->is_bindable)(resource)) {
1617 apr_table_addn(methods, "BIND", "");
1620 /* Generate the Allow header */
1621 arr = apr_table_elts(methods);
1622 elts = (const apr_table_entry_t *)arr->elts;
1625 /* first, compute total length */
1626 for (i = 0; i < arr->nelts; ++i) {
1627 if (elts[i].key == NULL)
1630 /* add 1 for comma or null */
1631 text_size += strlen(elts[i].key) + 1;
1634 s = allow = apr_palloc(r->pool, text_size);
1636 for (i = 0; i < arr->nelts; ++i) {
1637 if (elts[i].key == NULL)
1643 strcpy(s, elts[i].key);
1647 apr_table_setn(r->headers_out, "Allow", allow);
1649 /* if there was no request body, then there is no response body */
1651 ap_set_content_length(r, 0);
1653 /* ### this sends a Content-Type. the default OPTIONS does not. */
1655 /* ### the default (ap_send_http_options) returns OK, but I believe
1656 * ### that is because it is the default handler and nothing else
1657 * ### will run after the thing. */
1661 /* handle each options request */
1662 for (elem = doc->root->first_child; elem != NULL; elem = elem->next) {
1663 /* check for something we recognize first */
1664 int core_option = 0;
1665 dav_error *err = NULL;
1667 if (elem->ns == AP_XML_NS_DAV_ID) {
1668 if (strcmp(elem->name, "supported-method-set") == 0) {
1669 err = dav_gen_supported_methods(r, elem, methods, &body);
1672 else if (strcmp(elem->name, "supported-live-property-set") == 0) {
1673 err = dav_gen_supported_live_props(r, resource, elem, &body);
1676 else if (strcmp(elem->name, "supported-report-set") == 0) {
1677 err = dav_gen_supported_reports(r, resource, elem, vsn_hooks, &body);
1683 return dav_handle_err(r, err, NULL);
1685 /* if unrecognized option, pass to versioning provider */
1687 if ((err = (*vsn_hooks->get_option)(resource, elem, &body))
1689 return dav_handle_err(r, err, NULL);
1694 /* send the options response */
1695 r->status = HTTP_OK;
1696 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
1698 /* send the headers and response body */
1699 ap_rputs(DAV_XML_HEADER DEBUG_CR
1700 "<D:options-response xmlns:D=\"DAV:\">" DEBUG_CR, r);
1702 for (t = body.first; t != NULL; t = t->next)
1703 ap_rputs(t->text, r);
1705 ap_rputs("</D:options-response>" DEBUG_CR, r);
1707 /* we've sent everything necessary to the client. */
1711 static void dav_cache_badprops(dav_walker_ctx *ctx)
1713 const ap_xml_elem *elem;
1714 ap_text_header hdr = { 0 };
1716 /* just return if we built the thing already */
1717 if (ctx->propstat_404 != NULL) {
1721 ap_text_append(ctx->w.pool, &hdr,
1722 "<D:propstat>" DEBUG_CR
1723 "<D:prop>" DEBUG_CR);
1725 elem = dav_find_child(ctx->doc->root, "prop");
1726 for (elem = elem->first_child; elem; elem = elem->next) {
1727 ap_text_append(ctx->w.pool, &hdr,
1728 ap_xml_empty_elem(ctx->w.pool, elem));
1731 ap_text_append(ctx->w.pool, &hdr,
1732 "</D:prop>" DEBUG_CR
1733 "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
1734 "</D:propstat>" DEBUG_CR);
1736 ctx->propstat_404 = hdr.first;
1739 static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
1741 dav_walker_ctx *ctx = wres->walk_ctx;
1744 dav_get_props_result propstats = { 0 };
1747 ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
1748 ** dav_get_allprops() does not need to do namespace translation,
1751 ** Note: we cast to lose the "const". The propdb won't try to change
1752 ** the resource, however, since we are opening readonly.
1754 err = dav_open_propdb(ctx->r, ctx->w.lockdb, wres->resource, 1,
1755 ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
1757 /* ### do something with err! */
1759 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1760 dav_get_props_result badprops = { 0 };
1762 /* some props were expected on this collection/resource */
1763 dav_cache_badprops(ctx);
1764 badprops.propstats = ctx->propstat_404;
1765 dav_add_response(wres, 0, &badprops);
1768 /* no props on this collection/resource */
1769 dav_add_response(wres, HTTP_OK, NULL);
1773 /* ### what to do about closing the propdb on server failure? */
1775 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1776 propstats = dav_get_props(propdb, ctx->doc);
1779 dav_prop_insert what = ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP
1780 ? DAV_PROP_INSERT_VALUE
1781 : DAV_PROP_INSERT_NAME;
1782 propstats = dav_get_allprops(propdb, what);
1784 dav_close_propdb(propdb);
1786 dav_add_response(wres, 0, &propstats);
1791 /* handle the PROPFIND method */
1792 static int dav_method_propfind(request_rec *r)
1794 dav_resource *resource;
1799 const ap_xml_elem *child;
1800 dav_walker_ctx ctx = { { 0 } };
1801 dav_response *multi_status;
1803 /* Ask repository module to resolve the resource */
1804 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
1807 return dav_handle_err(r, err, NULL);
1809 if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
1810 /* Apache will supply a default error for this. */
1811 return HTTP_NOT_FOUND;
1814 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
1815 /* dav_get_depth() supplies additional information for the
1816 * default message. */
1817 return HTTP_BAD_REQUEST;
1820 if (depth == DAV_INFINITY && resource->collection) {
1822 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
1824 /* default is to DISALLOW these requests */
1825 if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
1826 return dav_error_response(r, HTTP_FORBIDDEN,
1827 apr_psprintf(r->pool,
1828 "PROPFIND requests with a "
1829 "Depth of \"infinity\" are "
1830 "not allowed for %s.",
1831 ap_escape_html(r->pool,
1836 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1839 /* note: doc == NULL if no request body */
1841 if (doc && !dav_validate_root(doc, "propfind")) {
1842 /* This supplies additional information for the default message. */
1843 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1844 "The \"propfind\" element was not found.");
1845 return HTTP_BAD_REQUEST;
1848 /* ### validate that only one of these three elements is present */
1851 || (child = dav_find_child(doc->root, "allprop")) != NULL) {
1852 /* note: no request body implies allprop */
1853 ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
1855 else if ((child = dav_find_child(doc->root, "propname")) != NULL) {
1856 ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
1858 else if ((child = dav_find_child(doc->root, "prop")) != NULL) {
1859 ctx.propfind_type = DAV_PROPFIND_IS_PROP;
1862 /* "propfind" element must have one of the above three children */
1864 /* This supplies additional information for the default message. */
1865 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1866 "The \"propfind\" element does not contain one of "
1867 "the required child elements (the specific command).");
1868 return HTTP_BAD_REQUEST;
1871 ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
1872 ctx.w.func = dav_propfind_walker;
1873 ctx.w.walk_ctx = &ctx;
1874 ctx.w.pool = r->pool;
1875 ctx.w.root = resource;
1880 /* ### should open read-only */
1881 if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
1882 err = dav_push_error(r->pool, err->status, 0,
1883 "The lock database could not be opened, "
1884 "preventing access to the various lock "
1885 "properties for the PROPFIND.",
1887 return dav_handle_err(r, err, NULL);
1889 if (ctx.w.lockdb != NULL) {
1890 /* if we have a lock database, then we can walk locknull resources */
1891 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1894 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
1896 if (ctx.w.lockdb != NULL) {
1897 (*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb);
1901 /* ### add a higher-level description? */
1902 return dav_handle_err(r, err, NULL);
1905 /* return a 207 (Multi-Status) response now. */
1907 /* if a 404 was generated for an HREF, then we need to spit out the
1908 * doc's namespaces for use by the 404. Note that <response> elements
1909 * will override these ns0, ns1, etc, but NOT within the <response>
1910 * scope for the badprops. */
1911 /* NOTE: propstat_404 != NULL implies doc != NULL */
1912 if (ctx.propstat_404 != NULL) {
1913 dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status,
1917 dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
1920 /* the response has been sent. */
1924 static ap_text * dav_failed_proppatch(apr_pool_t *p,
1925 apr_array_header_t *prop_ctx)
1927 ap_text_header hdr = { 0 };
1928 int i = prop_ctx->nelts;
1929 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
1930 dav_error *err424_set = NULL;
1931 dav_error *err424_delete = NULL;
1934 /* ### might be nice to sort by status code and description */
1936 for ( ; i-- > 0; ++ctx ) {
1937 ap_text_append(p, &hdr,
1938 "<D:propstat>" DEBUG_CR
1940 ap_text_append(p, &hdr, ap_xml_empty_elem(p, ctx->prop));
1941 ap_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
1943 if (ctx->err == NULL) {
1944 /* nothing was assigned here yet, so make it a 424 */
1946 if (ctx->operation == DAV_PROP_OP_SET) {
1947 if (err424_set == NULL)
1948 err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
1949 "Attempted DAV:set operation "
1950 "could not be completed due "
1951 "to other errors.");
1952 ctx->err = err424_set;
1954 else if (ctx->operation == DAV_PROP_OP_DELETE) {
1955 if (err424_delete == NULL)
1956 err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
1957 "Attempted DAV:remove "
1958 "operation could not be "
1959 "completed due to other "
1961 ctx->err = err424_delete;
1967 "HTTP/1.1 %d (status)"
1968 "</D:status>" DEBUG_CR,
1970 ap_text_append(p, &hdr, s);
1972 /* ### we should use compute_desc if necessary... */
1973 if (ctx->err->desc != NULL) {
1974 ap_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
1975 ap_text_append(p, &hdr, ctx->err->desc);
1976 ap_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
1979 ap_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
1985 static ap_text * dav_success_proppatch(apr_pool_t *p, apr_array_header_t *prop_ctx)
1987 ap_text_header hdr = { 0 };
1988 int i = prop_ctx->nelts;
1989 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
1992 * ### we probably need to revise the way we assemble the response...
1993 * ### this code assumes everything will return status==200.
1996 ap_text_append(p, &hdr,
1997 "<D:propstat>" DEBUG_CR
1998 "<D:prop>" DEBUG_CR);
2000 for ( ; i-- > 0; ++ctx ) {
2001 ap_text_append(p, &hdr, ap_xml_empty_elem(p, ctx->prop));
2004 ap_text_append(p, &hdr,
2005 "</D:prop>" DEBUG_CR
2006 "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
2007 "</D:propstat>" DEBUG_CR);
2012 static void dav_prop_log_errors(dav_prop_ctx *ctx)
2014 dav_log_err(ctx->r, ctx->err, APLOG_ERR);
2018 * Call <func> for each context. This can stop when an error occurs, or
2019 * simply iterate through the whole list.
2021 * Returns 1 if an error occurs (and the iteration is aborted). Returns 0
2022 * if all elements are processed.
2024 * If <reverse> is true (non-zero), then the list is traversed in
2027 static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
2028 apr_array_header_t *ctx_list, int stop_on_error,
2031 int i = ctx_list->nelts;
2032 dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
2042 if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
2053 /* handle the PROPPATCH method */
2054 static int dav_method_proppatch(request_rec *r)
2057 dav_resource *resource;
2063 dav_response resp = { 0 };
2064 ap_text *propstat_text;
2065 apr_array_header_t *ctx_list;
2067 dav_auto_version_info av_info;
2069 /* Ask repository module to resolve the resource */
2070 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2073 return dav_handle_err(r, err, NULL);
2074 if (!resource->exists) {
2075 /* Apache will supply a default error for this. */
2076 return HTTP_NOT_FOUND;
2079 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
2082 /* note: doc == NULL if no request body */
2084 if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
2085 /* This supplies additional information for the default message. */
2086 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2087 "The request body does not contain "
2088 "a \"propertyupdate\" element.");
2089 return HTTP_BAD_REQUEST;
2092 /* Check If-Headers and existing locks */
2093 /* Note: depth == 0. Implies no need for a multistatus response. */
2094 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
2095 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2096 /* ### add a higher-level description? */
2097 return dav_handle_err(r, err, NULL);
2100 /* make sure the resource can be modified (if versioning repository) */
2101 if ((err = dav_auto_checkout(r, resource,
2102 0 /* not parent_only */,
2103 &av_info)) != NULL) {
2104 /* ### add a higher-level description? */
2105 return dav_handle_err(r, err, NULL);
2108 if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
2109 &propdb)) != NULL) {
2110 /* undo any auto-checkout */
2111 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
2113 err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2114 apr_psprintf(r->pool,
2115 "Could not open the property "
2117 ap_escape_html(r->pool, r->uri)),
2119 return dav_handle_err(r, err, NULL);
2121 /* ### what to do about closing the propdb on server failure? */
2123 /* ### validate "live" properties */
2125 /* set up an array to hold property operation contexts */
2126 ctx_list = apr_array_make(r->pool, 10, sizeof(dav_prop_ctx));
2128 /* do a first pass to ensure that all "remove" properties exist */
2129 for (child = doc->root->first_child; child; child = child->next) {
2131 ap_xml_elem *prop_group;
2132 ap_xml_elem *one_prop;
2134 /* Ignore children that are not set/remove */
2135 if (child->ns != AP_XML_NS_DAV_ID
2136 || (!(is_remove = strcmp(child->name, "remove") == 0)
2137 && strcmp(child->name, "set") != 0)) {
2141 /* make sure that a "prop" child exists for set/remove */
2142 if ((prop_group = dav_find_child(child, "prop")) == NULL) {
2143 dav_close_propdb(propdb);
2145 /* undo any auto-checkout */
2146 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
2148 /* This supplies additional information for the default message. */
2149 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2150 "A \"prop\" element is missing inside "
2151 "the propertyupdate command.");
2152 return HTTP_BAD_REQUEST;
2155 for (one_prop = prop_group->first_child; one_prop;
2156 one_prop = one_prop->next) {
2158 ctx = (dav_prop_ctx *)apr_array_push(ctx_list);
2159 ctx->propdb = propdb;
2160 ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
2161 ctx->prop = one_prop;
2163 ctx->r = r; /* for later use by dav_prop_log_errors() */
2165 dav_prop_validate(ctx);
2167 if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
2173 /* ### should test that we found at least one set/remove */
2175 /* execute all of the operations */
2176 if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
2180 /* generate a failure/success response */
2182 (void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
2183 propstat_text = dav_failed_proppatch(r->pool, ctx_list);
2186 (void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
2187 propstat_text = dav_success_proppatch(r->pool, ctx_list);
2190 /* make sure this gets closed! */
2191 dav_close_propdb(propdb);
2193 /* complete any auto-versioning */
2194 dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info);
2196 /* log any errors that occurred */
2197 (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
2199 resp.href = resource->uri;
2201 /* ### should probably use something new to pass along this text... */
2202 resp.propresult.propstats = propstat_text;
2204 dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
2206 /* the response has been sent. */
2210 static int process_mkcol_body(request_rec *r)
2212 /* This is snarfed from ap_setup_client_block(). We could get pretty
2213 * close to this behavior by passing REQUEST_NO_BODY, but we need to
2214 * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
2215 * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
2217 const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
2218 const char *lenp = apr_table_get(r->headers_in, "Content-Length");
2220 /* make sure to set the Apache request fields properly. */
2221 r->read_body = REQUEST_NO_BODY;
2222 r->read_chunked = 0;
2226 if (strcasecmp(tenc, "chunked")) {
2227 /* Use this instead of Apache's default error string */
2228 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2229 "Unknown Transfer-Encoding %s", tenc);
2230 return HTTP_NOT_IMPLEMENTED;
2233 r->read_chunked = 1;
2236 const char *pos = lenp;
2238 while (apr_isdigit(*pos) || apr_isspace(*pos)) {
2243 /* This supplies additional information for the default message. */
2244 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2245 "Invalid Content-Length %s", lenp);
2246 return HTTP_BAD_REQUEST;
2249 r->remaining = atol(lenp);
2252 if (r->read_chunked || r->remaining > 0) {
2253 /* ### log something? */
2255 /* Apache will supply a default error for this. */
2256 return HTTP_UNSUPPORTED_MEDIA_TYPE;
2260 * Get rid of the body. this will call ap_setup_client_block(), but
2261 * our copy above has already verified its work.
2263 return ap_discard_request_body(r);
2266 /* handle the MKCOL method */
2267 static int dav_method_mkcol(request_rec *r)
2269 dav_resource *resource;
2271 dav_auto_version_info av_info;
2272 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2277 dav_response *multi_status;
2279 /* handle the request body */
2280 /* ### this may move lower once we start processing bodies */
2281 if ((result = process_mkcol_body(r)) != OK) {
2285 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
2288 /* Ask repository module to resolve the resource */
2289 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2292 return dav_handle_err(r, err, NULL);
2294 if (resource->exists) {
2295 /* oops. something was already there! */
2297 /* Apache will supply a default error for this. */
2298 /* ### we should provide a specific error message! */
2299 return HTTP_METHOD_NOT_ALLOWED;
2302 resource_state = dav_get_resource_state(r, resource);
2305 * Check If-Headers and existing locks.
2307 * Note: depth == 0 normally requires no multistatus response. However,
2308 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
2309 * other than the Request-URI, thereby requiring a multistatus.
2311 * If the resource does not exist (DAV_RESOURCE_NULL), then we must
2312 * check the resource *and* its parent. If the resource exists or is
2313 * a locknull resource, then we check only the resource.
2315 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
2316 resource_state == DAV_RESOURCE_NULL ?
2317 DAV_VALIDATE_PARENT :
2318 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2319 /* ### add a higher-level description? */
2320 return dav_handle_err(r, err, multi_status);
2323 /* if versioned resource, make sure parent is checked out */
2324 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
2325 &av_info)) != NULL) {
2326 /* ### add a higher-level description? */
2327 return dav_handle_err(r, err, NULL);
2330 /* try to create the collection */
2331 resource->collection = 1;
2332 err = (*resource->hooks->create_collection)(resource);
2334 /* restore modifiability of parent back to what it was */
2335 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2336 0 /*unlock*/, &av_info);
2338 /* check for errors now */
2340 return dav_handle_err(r, err, NULL);
2343 /* just log a warning */
2344 err = dav_push_error(r->pool, err->status, 0,
2345 "The MKCOL was successful, but there "
2346 "was a problem automatically checking in "
2347 "the parent collection.",
2349 dav_log_err(r, err, APLOG_WARNING);
2352 if (locks_hooks != NULL) {
2355 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2356 /* The directory creation was successful, but the locking failed. */
2357 err = dav_push_error(r->pool, err->status, 0,
2358 "The MKCOL was successful, but there "
2359 "was a problem opening the lock database "
2360 "which prevents inheriting locks from the "
2361 "parent resources.",
2363 return dav_handle_err(r, err, NULL);
2366 /* notify lock system that we have created/replaced a resource */
2367 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
2369 (*locks_hooks->close_lockdb)(lockdb);
2372 /* The dir creation was successful, but the locking failed. */
2373 err = dav_push_error(r->pool, err->status, 0,
2374 "The MKCOL was successful, but there "
2375 "was a problem updating its lock "
2378 return dav_handle_err(r, err, NULL);
2382 /* return an appropriate response (HTTP_CREATED) */
2383 return dav_created(r, NULL, "Collection", 0);
2386 /* handle the COPY and MOVE methods */
2387 static int dav_method_copymove(request_rec *r, int is_move)
2389 dav_resource *resource;
2390 dav_resource *resnew;
2391 dav_auto_version_info src_av_info = { 0 };
2392 dav_auto_version_info dst_av_info = { 0 };
2398 dav_response *multi_response;
2399 dav_lookup_result lookup;
2408 /* Ask repository module to resolve the resource */
2409 err = dav_get_resource(r, !is_move /* label_allowed */,
2410 0 /* use_checked_in */, &resource);
2412 return dav_handle_err(r, err, NULL);
2414 if (!resource->exists) {
2415 /* Apache will supply a default error for this. */
2416 return HTTP_NOT_FOUND;
2419 /* If not a file or collection resource, COPY/MOVE not allowed */
2420 /* ### allow COPY/MOVE of DeltaV resource types */
2421 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
2422 body = apr_psprintf(r->pool,
2423 "Cannot COPY/MOVE resource %s.",
2424 ap_escape_html(r->pool, r->uri));
2425 return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
2428 /* get the destination URI */
2429 dest = apr_table_get(r->headers_in, "Destination");
2431 /* Look in headers provided by Netscape's Roaming Profiles */
2432 const char *nscp_host = apr_table_get(r->headers_in, "Host");
2433 const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
2435 if (nscp_host != NULL && nscp_path != NULL)
2436 dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
2439 /* This supplies additional information for the default message. */
2440 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2441 "The request is missing a Destination header.");
2442 return HTTP_BAD_REQUEST;
2445 lookup = dav_lookup_uri(dest, r, 1 /* must_be_absolute */);
2446 if (lookup.rnew == NULL) {
2447 if (lookup.err.status == HTTP_BAD_REQUEST) {
2448 /* This supplies additional information for the default message. */
2449 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2451 return HTTP_BAD_REQUEST;
2454 /* ### this assumes that dav_lookup_uri() only generates a status
2455 * ### that Apache can provide a status line for!! */
2457 return dav_error_response(r, lookup.err.status, lookup.err.desc);
2459 if (lookup.rnew->status != HTTP_OK) {
2460 /* ### how best to report this... */
2461 return dav_error_response(r, lookup.rnew->status,
2462 "Destination URI had an error.");
2465 /* Resolve destination resource */
2466 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
2467 0 /* use_checked_in */, &resnew);
2469 return dav_handle_err(r, err, NULL);
2471 /* are the two resources handled by the same repository? */
2472 if (resource->hooks != resnew->hooks) {
2473 /* ### this message exposes some backend config, but screw it... */
2474 return dav_error_response(r, HTTP_BAD_GATEWAY,
2475 "Destination URI is handled by a "
2476 "different repository than the source URI. "
2477 "MOVE or COPY between repositories is "
2481 /* get and parse the overwrite header value */
2482 if ((overwrite = dav_get_overwrite(r)) < 0) {
2483 /* dav_get_overwrite() supplies additional information for the
2484 * default message. */
2485 return HTTP_BAD_REQUEST;
2488 /* quick failure test: if dest exists and overwrite is false. */
2489 if (resnew->exists && !overwrite) {
2490 /* Supply some text for the error response body. */
2491 return dav_error_response(r, HTTP_PRECONDITION_FAILED,
2492 "Destination is not empty and "
2493 "Overwrite is not \"T\"");
2496 /* are the source and destination the same? */
2497 if ((*resource->hooks->is_same_resource)(resource, resnew)) {
2498 /* Supply some text for the error response body. */
2499 return dav_error_response(r, HTTP_FORBIDDEN,
2500 "Source and Destination URIs are the same.");
2504 is_dir = resource->collection;
2506 /* get and parse the Depth header value. "0" and "infinity" are legal. */
2507 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
2508 /* dav_get_depth() supplies additional information for the
2509 * default message. */
2510 return HTTP_BAD_REQUEST;
2513 /* This supplies additional information for the default message. */
2514 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2515 "Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
2516 return HTTP_BAD_REQUEST;
2518 if (is_move && is_dir && depth != DAV_INFINITY) {
2519 /* This supplies additional information for the default message. */
2520 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2521 "Depth must be \"infinity\" when moving a collection.");
2522 return HTTP_BAD_REQUEST;
2526 * Check If-Headers and existing locks for each resource in the source
2527 * if we are performing a MOVE. We will return a 424 response with a
2528 * DAV:multistatus body. The multistatus responses will contain the
2529 * information about any resource that fails the validation.
2531 * We check the parent resource, too, since this is a MOVE. Moving the
2532 * resource effectively removes it from the parent collection, so we
2533 * must ensure that we have met the appropriate conditions.
2535 * If a problem occurs with the Request-URI itself, then a plain error
2536 * (rather than a multistatus) will be returned.
2539 && (err = dav_validate_request(r, resource, depth, NULL,
2542 | DAV_VALIDATE_USE_424,
2544 err = dav_push_error(r->pool, err->status, 0,
2545 apr_psprintf(r->pool,
2546 "Could not MOVE %s due to a failed "
2547 "precondition on the source "
2549 ap_escape_html(r->pool, r->uri)),
2551 return dav_handle_err(r, err, multi_response);
2555 * Check If-Headers and existing locks for destination. Note that we
2556 * use depth==infinity since the target (hierarchy) will be deleted
2557 * before the move/copy is completed.
2559 * Note that we are overwriting the target, which implies a DELETE, so
2560 * we are subject to the error/response rules as a DELETE. Namely, we
2561 * will return a 424 error if any of the validations fail.
2562 * (see dav_method_delete() for more information)
2564 if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
2567 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
2568 err = dav_push_error(r->pool, err->status, 0,
2569 apr_psprintf(r->pool,
2570 "Could not MOVE/COPY %s due to a "
2571 "failed precondition on the "
2572 "destination (e.g. locks).",
2573 ap_escape_html(r->pool, r->uri)),
2575 return dav_handle_err(r, err, multi_response);
2579 && depth == DAV_INFINITY
2580 && (*resource->hooks->is_parent_resource)(resource, resnew)) {
2581 /* Supply some text for the error response body. */
2582 return dav_error_response(r, HTTP_FORBIDDEN,
2583 "Source collection contains the "
2588 && (*resnew->hooks->is_parent_resource)(resnew, resource)) {
2589 /* The destination must exist (since it contains the source), and
2590 * a condition above implies Overwrite==T. Obviously, we cannot
2591 * delete the Destination before the MOVE/COPY, as that would
2592 * delete the Source.
2595 /* Supply some text for the error response body. */
2596 return dav_error_response(r, HTTP_FORBIDDEN,
2597 "Destination collection contains the Source "
2598 "and Overwrite has been specified.");
2601 /* ### for now, we don't need anything in the body */
2602 if ((result = ap_discard_request_body(r)) != OK) {
2606 if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
2607 /* ### add a higher-level description? */
2608 return dav_handle_err(r, err, NULL);
2611 /* remove any locks from the old resources */
2613 * ### this is Yet Another Traversal. if we do a rename(), then we
2614 * ### really don't have to do this in some cases since the inode
2615 * ### values will remain constant across the move. but we can't
2616 * ### know that fact from outside the provider :-(
2618 * ### note that we now have a problem atomicity in the move/copy
2619 * ### since a failure after this would have removed locks (technically,
2620 * ### this is okay to do, but really...)
2622 if (is_move && lockdb != NULL) {
2623 /* ### this is wrong! it blasts direct locks on parent resources */
2624 /* ### pass lockdb! */
2625 (void)dav_unlock(r, resource, NULL);
2628 /* if this is a move, then the source parent collection will be modified */
2630 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
2631 &src_av_info)) != NULL) {
2633 (*lockdb->hooks->close_lockdb)(lockdb);
2635 /* ### add a higher-level description? */
2636 return dav_handle_err(r, err, NULL);
2641 * Remember the initial state of the destination, so the lock system
2642 * can be notified as to how it changed.
2644 resnew_state = dav_get_resource_state(lookup.rnew, resnew);
2646 /* In a MOVE operation, the destination is replaced by the source.
2647 * In a COPY operation, if the destination exists, is under version
2648 * control, and is the same resource type as the source,
2649 * then it should not be replaced, but modified to be a copy of
2652 if (!resnew->exists)
2654 else if (is_move || !resource->versioned)
2656 else if (resource->type != resnew->type)
2658 else if ((resource->collection == 0) != (resnew->collection == 0))
2663 /* If the destination must be created or replaced,
2664 * make sure the parent collection is writable
2666 if (!resnew->exists || replace_dest) {
2667 if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/,
2668 &dst_av_info)) != NULL) {
2669 /* could not make destination writable:
2670 * if move, restore state of source parent
2673 (void)dav_auto_checkin(r, NULL, 1 /* undo */,
2674 0 /*unlock*/, &src_av_info);
2678 (*lockdb->hooks->close_lockdb)(lockdb);
2680 /* ### add a higher-level description? */
2681 return dav_handle_err(r, err, NULL);
2685 /* If source and destination parents are the same, then
2686 * use the same resource object, so status updates to one are reflected
2687 * in the other, when doing auto-versioning. Otherwise,
2688 * we may try to checkin the parent twice.
2690 if (src_av_info.parent_resource != NULL
2691 && dst_av_info.parent_resource != NULL
2692 && (*src_av_info.parent_resource->hooks->is_same_resource)
2693 (src_av_info.parent_resource, dst_av_info.parent_resource)) {
2695 dst_av_info.parent_resource = src_av_info.parent_resource;
2698 /* If destination is being replaced, remove it first
2699 * (we know Ovewrite must be TRUE). Then try to copy/move the resource.
2702 err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
2706 err = (*resource->hooks->move_resource)(resource, resnew,
2709 err = (*resource->hooks->copy_resource)(resource, resnew, depth,
2713 /* perform any auto-versioning cleanup */
2714 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2715 0 /*unlock*/, &dst_av_info);
2718 err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2719 0 /*unlock*/, &src_av_info);
2724 /* check for error from remove/copy/move operations */
2727 (*lockdb->hooks->close_lockdb)(lockdb);
2729 err = dav_push_error(r->pool, err->status, 0,
2730 apr_psprintf(r->pool,
2731 "Could not MOVE/COPY %s.",
2732 ap_escape_html(r->pool, r->uri)),
2734 return dav_handle_err(r, err, multi_response);
2737 /* check for errors from auto-versioning */
2739 /* just log a warning */
2740 err = dav_push_error(r->pool, err2->status, 0,
2741 "The MOVE/COPY was successful, but there was a "
2742 "problem automatically checking in the "
2743 "source parent collection.",
2745 dav_log_err(r, err, APLOG_WARNING);
2748 /* just log a warning */
2749 err = dav_push_error(r->pool, err3->status, 0,
2750 "The MOVE/COPY was successful, but there was a "
2751 "problem automatically checking in the "
2752 "destination or its parent collection.",
2754 dav_log_err(r, err, APLOG_WARNING);
2757 /* propagate any indirect locks at the target */
2758 if (lockdb != NULL) {
2760 /* notify lock system that we have created/replaced a resource */
2761 err = dav_notify_created(r, lockdb, resnew, resnew_state, depth);
2763 (*lockdb->hooks->close_lockdb)(lockdb);
2766 /* The move/copy was successful, but the locking failed. */
2767 err = dav_push_error(r->pool, err->status, 0,
2768 "The MOVE/COPY was successful, but there "
2769 "was a problem updating the lock "
2772 return dav_handle_err(r, err, NULL);
2776 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
2777 return dav_created(r, lookup.rnew->uri, "Destination",
2778 resnew_state == DAV_RESOURCE_EXISTS);
2781 /* dav_method_lock: Handler to implement the DAV LOCK method
2782 * Returns appropriate HTTP_* response.
2784 static int dav_method_lock(request_rec *r)
2787 dav_resource *resource;
2788 const dav_hooks_locks *locks_hooks;
2791 int new_lock_request = 0;
2794 dav_response *multi_response = NULL;
2798 /* If no locks provider, decline the request */
2799 locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2800 if (locks_hooks == NULL)
2803 if ((result = ap_xml_parse_input(r, &doc)) != OK)
2806 depth = dav_get_depth(r, DAV_INFINITY);
2807 if (depth != 0 && depth != DAV_INFINITY) {
2808 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2809 "Depth must be 0 or \"infinity\" for LOCK.");
2810 return HTTP_BAD_REQUEST;
2813 /* Ask repository module to resolve the resource */
2814 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2817 return dav_handle_err(r, err, NULL);
2820 * Open writable. Unless an error occurs, we'll be
2821 * writing into the database.
2823 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2824 /* ### add a higher-level description? */
2825 return dav_handle_err(r, err, NULL);
2829 if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
2831 /* ### add a higher-level description to err? */
2834 new_lock_request = 1;
2836 lock->auth_user = apr_pstrdup(r->pool, r->user);
2839 resource_state = dav_get_resource_state(r, resource);
2842 * Check If-Headers and existing locks.
2844 * If this will create a locknull resource, then the LOCK will affect
2845 * the parent collection (much like a PUT/MKCOL). For that case, we must
2846 * validate the parent resource's conditions.
2848 if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
2849 (resource_state == DAV_RESOURCE_NULL
2850 ? DAV_VALIDATE_PARENT
2851 : DAV_VALIDATE_RESOURCE)
2852 | (new_lock_request ? lock->scope : 0)
2853 | DAV_VALIDATE_ADD_LD,
2855 err = dav_push_error(r->pool, err->status, 0,
2856 apr_psprintf(r->pool,
2857 "Could not LOCK %s due to a failed "
2858 "precondition (e.g. other locks).",
2859 ap_escape_html(r->pool, r->uri)),
2864 if (new_lock_request == 0) {
2865 dav_locktoken_list *ltl;
2869 * ### Assumption: We can renew multiple locks on the same resource
2870 * ### at once. First harvest all the positive lock-tokens given in
2871 * ### the If header. Then modify the lock entries for this resource
2872 * ### with the new Timeout val.
2875 if ((err = dav_get_locktoken_list(r, <l)) != NULL) {
2876 err = dav_push_error(r->pool, err->status, 0,
2877 apr_psprintf(r->pool,
2878 "The lock refresh for %s failed "
2879 "because no lock tokens were "
2880 "specified in an \"If:\" "
2882 ap_escape_html(r->pool, r->uri)),
2887 if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
2890 /* ### add a higher-level description to err? */
2894 /* New lock request */
2895 char *locktoken_txt;
2898 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
2901 /* apply lower bound (if any) from DAVMinTimeout directive */
2902 if (lock->timeout != DAV_TIMEOUT_INFINITE
2903 && lock->timeout < time(NULL) + conf->locktimeout)
2904 lock->timeout = time(NULL) + conf->locktimeout;
2906 err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
2908 /* ### add a higher-level description to err? */
2912 locktoken_txt = apr_pstrcat(r->pool, "<",
2913 (*locks_hooks->format_locktoken)(r->pool,
2917 apr_table_set(r->headers_out, "Lock-Token", locktoken_txt);
2920 (*locks_hooks->close_lockdb)(lockdb);
2922 r->status = HTTP_OK;
2923 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
2925 ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
2927 ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
2930 "<D:lockdiscovery>" DEBUG_CR
2932 "</D:lockdiscovery>" DEBUG_CR,
2933 dav_lock_get_activelock(r, lock, NULL));
2935 ap_rputs("</D:prop>", r);
2937 /* the response has been sent. */
2941 (*locks_hooks->close_lockdb)(lockdb);
2942 return dav_handle_err(r, err, multi_response);
2945 /* dav_method_unlock: Handler to implement the DAV UNLOCK method
2946 * Returns appropriate HTTP_* response.
2948 static int dav_method_unlock(request_rec *r)
2951 dav_resource *resource;
2952 const dav_hooks_locks *locks_hooks;
2954 const char *const_locktoken_txt;
2955 char *locktoken_txt;
2956 dav_locktoken *locktoken = NULL;
2958 dav_response *multi_response;
2960 /* If no locks provider, decline the request */
2961 locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2962 if (locks_hooks == NULL)
2965 if ((const_locktoken_txt = apr_table_get(r->headers_in,
2966 "Lock-Token")) == NULL) {
2967 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2968 "Unlock failed (%s): "
2969 "No Lock-Token specified in header", r->filename);
2970 return HTTP_BAD_REQUEST;
2973 locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt);
2974 if (locktoken_txt[0] != '<') {
2975 /* ### should provide more specifics... */
2976 return HTTP_BAD_REQUEST;
2980 if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
2981 /* ### should provide more specifics... */
2982 return HTTP_BAD_REQUEST;
2984 locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
2986 if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
2987 &locktoken)) != NULL) {
2988 err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
2989 apr_psprintf(r->pool,
2990 "The UNLOCK on %s failed -- an "
2991 "invalid lock token was specified "
2992 "in the \"If:\" header.",
2993 ap_escape_html(r->pool, r->uri)),
2995 return dav_handle_err(r, err, NULL);
2998 /* Ask repository module to resolve the resource */
2999 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3002 return dav_handle_err(r, err, NULL);
3004 resource_state = dav_get_resource_state(r, resource);
3007 * Check If-Headers and existing locks.
3009 * Note: depth == 0 normally requires no multistatus response. However,
3010 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
3011 * other than the Request-URI, thereby requiring a multistatus.
3013 * If the resource is a locknull resource, then the UNLOCK will affect
3014 * the parent collection (much like a delete). For that case, we must
3015 * validate the parent resource's conditions.
3017 if ((err = dav_validate_request(r, resource, 0, locktoken,
3019 resource_state == DAV_RESOURCE_LOCK_NULL
3020 ? DAV_VALIDATE_PARENT
3021 : DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
3022 /* ### add a higher-level description? */
3023 return dav_handle_err(r, err, multi_response);
3026 /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
3027 * _all_ resources locked by locktoken are released. It does not say
3028 * resource has to be the root of an infinte lock. Thus, an UNLOCK
3029 * on any part of an infinte lock will remove the lock on all resources.
3031 * For us, if r->filename represents an indirect lock (part of an infinity lock),
3032 * we must actually perform an UNLOCK on the direct lock for this resource.
3034 if ((result = dav_unlock(r, resource, locktoken)) != OK) {
3038 return HTTP_NO_CONTENT;
3041 static int dav_method_vsn_control(request_rec *r)
3043 dav_resource *resource;
3045 dav_auto_version_info av_info;
3046 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
3047 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3050 const char *target = NULL;
3053 /* if no versioning provider, decline the request */
3054 if (vsn_hooks == NULL)
3057 /* ask repository module to resolve the resource */
3058 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3061 return dav_handle_err(r, err, NULL);
3063 /* remember the pre-creation resource state */
3064 resource_state = dav_get_resource_state(r, resource);
3066 /* parse the request body (may be a version-control element) */
3067 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3070 /* note: doc == NULL if no request body */
3073 const ap_xml_elem *child;
3076 if (!dav_validate_root(doc, "version-control")) {
3077 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3078 "The request body does not contain "
3079 "a \"version-control\" element.");
3080 return HTTP_BAD_REQUEST;
3083 /* get the version URI */
3084 if ((child = dav_find_child(doc->root, "version")) == NULL) {
3085 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3086 "The \"version-control\" element does not contain "
3087 "a \"version\" element.");
3088 return HTTP_BAD_REQUEST;
3091 if ((child = dav_find_child(child, "href")) == NULL) {
3092 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3093 "The \"version\" element does not contain "
3094 "an \"href\" element.");
3095 return HTTP_BAD_REQUEST;
3098 /* get version URI */
3099 ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3102 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3103 "An \"href\" element does not contain a URI.");
3104 return HTTP_BAD_REQUEST;
3108 /* Check request preconditions */
3110 /* ### need a general mechanism for reporting precondition violations
3111 * ### (should be returning XML document for 403/409 responses)
3114 /* if not versioning existing resource, must specify version to select */
3115 if (!resource->exists && target == NULL) {
3116 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3117 "<DAV:initial-version-required/>");
3118 return dav_handle_err(r, err, NULL);
3120 else if (resource->exists) {
3121 /* cannot add resource to existing version history */
3122 if (target != NULL) {
3123 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3124 "<DAV:cannot-add-to-existing-history/>");
3125 return dav_handle_err(r, err, NULL);
3128 /* resource must be unversioned and versionable, or version selector */
3129 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3130 || (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
3131 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3132 "<DAV:must-be-versionable/>");
3133 return dav_handle_err(r, err, NULL);
3136 /* the DeltaV spec says if resource is a version selector,
3137 * then VERSION-CONTROL is a no-op
3139 if (resource->versioned) {
3140 /* set the Cache-Control header, per the spec */
3141 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3144 ap_set_content_length(r, 0);
3150 /* Check If-Headers and existing locks */
3151 /* Note: depth == 0. Implies no need for a multistatus response. */
3152 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
3153 resource_state == DAV_RESOURCE_NULL ?
3154 DAV_VALIDATE_PARENT :
3155 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
3156 return dav_handle_err(r, err, NULL);
3159 /* if in versioned collection, make sure parent is checked out */
3160 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
3161 &av_info)) != NULL) {
3162 return dav_handle_err(r, err, NULL);
3165 /* attempt to version-control the resource */
3166 if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
3167 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
3168 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3169 apr_psprintf(r->pool,
3170 "Could not VERSION-CONTROL resource %s.",
3171 ap_escape_html(r->pool, r->uri)),
3173 return dav_handle_err(r, err, NULL);
3176 /* revert writability of parent directory */
3177 err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info);
3179 /* just log a warning */
3180 err = dav_push_error(r->pool, err->status, 0,
3181 "The VERSION-CONTROL was successful, but there "
3182 "was a problem automatically checking in "
3183 "the parent collection.",
3185 dav_log_err(r, err, APLOG_WARNING);
3188 /* if the resource is lockable, let lock system know of new resource */
3189 if (locks_hooks != NULL
3190 && (*locks_hooks->get_supportedlock)(resource) != NULL) {
3193 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
3194 /* The resource creation was successful, but the locking failed. */
3195 err = dav_push_error(r->pool, err->status, 0,
3196 "The VERSION-CONTROL was successful, but there "
3197 "was a problem opening the lock database "
3198 "which prevents inheriting locks from the "
3199 "parent resources.",
3201 return dav_handle_err(r, err, NULL);
3204 /* notify lock system that we have created/replaced a resource */
3205 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
3207 (*locks_hooks->close_lockdb)(lockdb);
3210 /* The dir creation was successful, but the locking failed. */
3211 err = dav_push_error(r->pool, err->status, 0,
3212 "The VERSION-CONTROL was successful, but there "
3213 "was a problem updating its lock "
3216 return dav_handle_err(r, err, NULL);
3220 /* set the Cache-Control header, per the spec */
3221 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3223 /* return an appropriate response (HTTP_CREATED) */
3224 return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
3227 /* handle the CHECKOUT method */
3228 static int dav_method_checkout(request_rec *r)
3230 dav_resource *resource;
3231 dav_resource *working_resource;
3232 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3236 int apply_to_vsn = 0;
3237 int is_unreserved = 0;
3239 int create_activity = 0;
3240 apr_array_header_t *activities = NULL;
3242 /* If no versioning provider, decline the request */
3243 if (vsn_hooks == NULL)
3246 if ((result = ap_xml_parse_input(r, &doc)) != OK)
3250 const ap_xml_elem *aset;
3252 if (!dav_validate_root(doc, "checkout")) {
3253 /* This supplies additional information for the default msg. */
3254 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3255 "The request body, if present, must be a "
3256 "DAV:checkout element.");
3257 return HTTP_BAD_REQUEST;
3260 if (dav_find_child(doc->root, "apply-to-version") != NULL) {
3261 if (apr_table_get(r->headers_in, "label") != NULL) {
3262 /* ### we want generic 403/409 XML reporting here */
3263 /* ### DAV:must-not-have-label-and-apply-to-version */
3264 return dav_error_response(r, HTTP_CONFLICT,
3265 "DAV:apply-to-version cannot be "
3266 "used in conjunction with a "
3272 is_unreserved = dav_find_child(doc->root, "unreserved") != NULL;
3273 is_fork_ok = dav_find_child(doc->root, "fork-ok") != NULL;
3275 if ((aset = dav_find_child(doc->root, "activity-set")) != NULL) {
3276 if (dav_find_child(aset, "new") != NULL) {
3277 create_activity = 1;
3280 const ap_xml_elem *child = aset->first_child;
3282 activities = apr_array_make(r->pool, 1, sizeof(const char *));
3284 for (; child != NULL; child = child->next) {
3285 if (child->ns == AP_XML_NS_DAV_ID
3286 && strcmp(child->name, "href") == 0) {
3289 href = dav_xml_get_cdata(child, r->pool,
3290 1 /* strip_white */);
3291 *(const char **)apr_array_push(activities) = href;
3295 if (activities->nelts == 0) {
3296 /* no href's is a DTD violation:
3297 <!ELEMENT activity-set (href+ | new)>
3300 /* This supplies additional info for the default msg. */
3301 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3302 "Within the DAV:activity-set element, the "
3303 "DAV:new element must be used, or at least "
3304 "one DAV:href must be specified.");
3305 return HTTP_BAD_REQUEST;
3311 /* Ask repository module to resolve the resource */
3312 err = dav_get_resource(r, 1 /*label_allowed*/, apply_to_vsn, &resource);
3314 return dav_handle_err(r, err, NULL);
3316 if (!resource->exists) {
3317 /* Apache will supply a default error for this. */
3318 return HTTP_NOT_FOUND;
3321 /* Check the state of the resource: must be a file or collection,
3322 * must be versioned, and must not already be checked out.
3324 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3325 && resource->type != DAV_RESOURCE_TYPE_VERSION) {
3326 return dav_error_response(r, HTTP_CONFLICT,
3327 "Cannot checkout this type of resource.");
3330 if (!resource->versioned) {
3331 return dav_error_response(r, HTTP_CONFLICT,
3332 "Cannot checkout unversioned resource.");
3335 if (resource->working) {
3336 return dav_error_response(r, HTTP_CONFLICT,
3337 "The resource is already checked out to the workspace.");
3340 /* ### do lock checks, once behavior is defined */
3342 /* Do the checkout */
3343 if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/,
3344 is_unreserved, is_fork_ok,
3345 create_activity, activities,
3346 &working_resource)) != NULL) {
3347 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3348 apr_psprintf(r->pool,
3349 "Could not CHECKOUT resource %s.",
3350 ap_escape_html(r->pool, r->uri)),
3352 return dav_handle_err(r, err, NULL);
3355 /* set the Cache-Control header, per the spec */
3356 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3358 /* if no working resource created, return OK,
3359 * else return CREATED with working resource URL in Location header
3361 if (working_resource == NULL) {
3363 ap_set_content_length(r, 0);
3367 return dav_created(r, working_resource->uri, "Checked-out resource", 0);
3370 /* handle the UNCHECKOUT method */
3371 static int dav_method_uncheckout(request_rec *r)
3373 dav_resource *resource;
3374 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3378 /* If no versioning provider, decline the request */
3379 if (vsn_hooks == NULL)
3382 if ((result = ap_discard_request_body(r)) != OK) {
3386 /* Ask repository module to resolve the resource */
3387 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3390 return dav_handle_err(r, err, NULL);
3392 if (!resource->exists) {
3393 /* Apache will supply a default error for this. */
3394 return HTTP_NOT_FOUND;
3397 /* Check the state of the resource: must be a file or collection,
3398 * must be versioned, and must be checked out.
3400 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3401 return dav_error_response(r, HTTP_CONFLICT,
3402 "Cannot uncheckout this type of resource.");
3405 if (!resource->versioned) {
3406 return dav_error_response(r, HTTP_CONFLICT,
3407 "Cannot uncheckout unversioned resource.");
3410 if (!resource->working) {
3411 return dav_error_response(r, HTTP_CONFLICT,
3412 "The resource is not checked out to the workspace.");
3415 /* ### do lock checks, once behavior is defined */
3417 /* Do the uncheckout */
3418 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
3419 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3420 apr_psprintf(r->pool,
3421 "Could not UNCHECKOUT resource %s.",
3422 ap_escape_html(r->pool, r->uri)),
3424 return dav_handle_err(r, err, NULL);
3428 ap_set_content_length(r, 0);
3433 /* handle the CHECKIN method */
3434 static int dav_method_checkin(request_rec *r)
3436 dav_resource *resource;
3437 dav_resource *new_version;
3438 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3442 int keep_checked_out = 0;
3444 /* If no versioning provider, decline the request */
3445 if (vsn_hooks == NULL)
3448 if ((result = ap_xml_parse_input(r, &doc)) != OK)
3452 if (!dav_validate_root(doc, "checkin")) {
3453 /* This supplies additional information for the default msg. */
3454 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3455 "The request body, if present, must be a "
3456 "DAV:checkin element.");
3457 return HTTP_BAD_REQUEST;
3460 keep_checked_out = dav_find_child(doc->root, "keep-checked-out") != NULL;
3463 /* Ask repository module to resolve the resource */
3464 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3467 return dav_handle_err(r, err, NULL);
3469 if (!resource->exists) {
3470 /* Apache will supply a default error for this. */
3471 return HTTP_NOT_FOUND;
3474 /* Check the state of the resource: must be a file or collection,
3475 * must be versioned, and must be checked out.
3477 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3478 return dav_error_response(r, HTTP_CONFLICT,
3479 "Cannot checkin this type of resource.");
3482 if (!resource->versioned) {
3483 return dav_error_response(r, HTTP_CONFLICT,
3484 "Cannot checkin unversioned resource.");
3487 if (!resource->working) {
3488 return dav_error_response(r, HTTP_CONFLICT,
3489 "The resource is not checked out.");
3492 /* ### do lock checks, once behavior is defined */
3494 /* Do the checkin */
3495 if ((err = (*vsn_hooks->checkin)(resource, keep_checked_out, &new_version))
3497 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3498 apr_psprintf(r->pool,
3499 "Could not CHECKIN resource %s.",
3500 ap_escape_html(r->pool, r->uri)),
3502 return dav_handle_err(r, err, NULL);
3505 return dav_created(r, new_version->uri, "Version", 0);
3508 static int dav_method_update(request_rec *r)
3510 dav_resource *resource;
3511 dav_resource *version = NULL;
3512 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3520 dav_response *multi_response;
3522 dav_lookup_result lookup;
3524 /* If no versioning provider, or UPDATE not supported,
3525 * decline the request */
3526 if (vsn_hooks == NULL || vsn_hooks->update == NULL)
3529 if ((depth = dav_get_depth(r, 0)) < 0) {
3530 /* dav_get_depth() supplies additional information for the
3531 * default message. */
3532 return HTTP_BAD_REQUEST;
3535 /* parse the request body */
3536 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3540 if (doc == NULL || !dav_validate_root(doc, "update")) {
3541 /* This supplies additional information for the default message. */
3542 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3543 "The request body does not contain "
3544 "an \"update\" element.");
3545 return HTTP_BAD_REQUEST;
3548 /* check for label-name or version element, but not both */
3549 if ((child = dav_find_child(doc->root, "label-name")) != NULL)
3551 else if ((child = dav_find_child(doc->root, "version")) != NULL) {
3552 /* get the href element */
3553 if ((child = dav_find_child(child, "href")) == NULL) {
3554 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3555 "The version element does not contain "
3556 "an \"href\" element.");
3557 return HTTP_BAD_REQUEST;
3561 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3562 "The \"update\" element does not contain "
3563 "a \"label-name\" or \"version\" element.");
3564 return HTTP_BAD_REQUEST;
3567 /* a depth greater than zero is only allowed for a label */
3568 if (!is_label && depth != 0) {
3569 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3570 "Depth must be zero for UPDATE with a version");
3571 return HTTP_BAD_REQUEST;
3574 /* get the target value (a label or a version URI) */
3575 ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3578 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3579 "A \"label-name\" or \"href\" element does not contain "
3581 return HTTP_BAD_REQUEST;
3584 /* Ask repository module to resolve the resource */
3585 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3588 return dav_handle_err(r, err, NULL);
3590 if (!resource->exists) {
3591 /* Apache will supply a default error for this. */
3592 return HTTP_NOT_FOUND;
3595 /* ### need a general mechanism for reporting precondition violations
3596 * ### (should be returning XML document for 403/409 responses)
3598 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3599 || !resource->versioned || resource->working) {
3600 return dav_error_response(r, HTTP_CONFLICT,
3601 "<DAV:must-be-checked-in-version-controlled-resource>");
3604 /* if target is a version, resolve the version resource */
3605 /* ### dav_lookup_uri only allows absolute URIs; is that OK? */
3607 lookup = dav_lookup_uri(target, r, 0 /* must_be_absolute */);
3608 if (lookup.rnew == NULL) {
3609 if (lookup.err.status == HTTP_BAD_REQUEST) {
3610 /* This supplies additional information for the default message. */
3611 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3613 return HTTP_BAD_REQUEST;
3616 /* ### this assumes that dav_lookup_uri() only generates a status
3617 * ### that Apache can provide a status line for!! */
3619 return dav_error_response(r, lookup.err.status, lookup.err.desc);
3621 if (lookup.rnew->status != HTTP_OK) {
3622 /* ### how best to report this... */
3623 return dav_error_response(r, lookup.rnew->status,
3624 "Version URI had an error.");
3627 /* resolve version resource */
3628 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
3629 0 /* use_checked_in */, &version);
3631 return dav_handle_err(r, err, NULL);
3633 /* NULL out target, since we're using a version resource */
3637 /* do the UPDATE operation */
3638 err = (*vsn_hooks->update)(resource, version, target, depth, &multi_response);
3641 err = dav_push_error(r->pool, err->status, 0,
3642 ap_psprintf(r->pool,
3643 "Could not UPDATE %s.",
3644 ap_escape_html(r->pool, r->uri)),
3646 return dav_handle_err(r, err, multi_response);
3649 /* set the Cache-Control header, per the spec */
3650 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3653 ap_set_content_length(r, 0);
3658 /* context maintained during LABEL treewalk */
3659 typedef struct dav_label_walker_ctx
3664 /* label being manipulated */
3667 /* label operation */
3669 #define DAV_LABEL_ADD 1
3670 #define DAV_LABEL_SET 2
3671 #define DAV_LABEL_REMOVE 3
3673 /* version provider hooks */
3674 const dav_hooks_vsn *vsn_hooks;
3676 } dav_label_walker_ctx;
3678 static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
3680 dav_label_walker_ctx *ctx = wres->walk_ctx;
3681 dav_error *err = NULL;
3683 /* Check the state of the resource: must be a version or
3684 * non-checkedout version selector
3686 /* ### need a general mechanism for reporting precondition violations
3687 * ### (should be returning XML document for 403/409 responses)
3689 if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
3690 (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
3691 || !wres->resource->versioned)) {
3692 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3693 "<DAV:must-be-version-or-version-selector/>");
3695 else if (wres->resource->working) {
3696 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3697 "<DAV:must-not-be-checked-out/>");
3700 /* do the label operation */
3701 if (ctx->label_op == DAV_LABEL_REMOVE)
3702 err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
3704 err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
3705 ctx->label_op == DAV_LABEL_SET);
3709 /* ### need utility routine to add response with description? */
3710 dav_add_response(wres, err->status, NULL);
3711 wres->response->desc = err->desc;
3717 static int dav_method_label(request_rec *r)
3719 dav_resource *resource;
3720 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3727 dav_label_walker_ctx ctx = { { 0 } };
3728 dav_response *multi_status;
3730 /* If no versioning provider, or the provider doesn't support
3731 * labels, decline the request */
3732 if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
3735 /* Ask repository module to resolve the resource */
3736 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
3739 return dav_handle_err(r, err, NULL);
3740 if (!resource->exists) {
3741 /* Apache will supply a default error for this. */
3742 return HTTP_NOT_FOUND;
3745 if ((depth = dav_get_depth(r, 0)) < 0) {
3746 /* dav_get_depth() supplies additional information for the
3747 * default message. */
3748 return HTTP_BAD_REQUEST;
3751 /* parse the request body */
3752 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3756 if (doc == NULL || !dav_validate_root(doc, "label")) {
3757 /* This supplies additional information for the default message. */
3758 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3759 "The request body does not contain "
3760 "a \"label\" element.");
3761 return HTTP_BAD_REQUEST;
3764 /* check for add, set, or remove element */
3765 if ((child = dav_find_child(doc->root, "add")) != NULL) {
3766 ctx.label_op = DAV_LABEL_ADD;
3768 else if ((child = dav_find_child(doc->root, "set")) != NULL) {
3769 ctx.label_op = DAV_LABEL_SET;
3771 else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
3772 ctx.label_op = DAV_LABEL_REMOVE;
3775 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3776 "The \"label\" element does not contain "
3777 "an \"add\", \"set\", or \"remove\" element.");
3778 return HTTP_BAD_REQUEST;
3781 /* get the label string */
3782 if ((child = dav_find_child(child, "label-name")) == NULL) {
3783 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3784 "The label command element does not contain "
3785 "a \"label-name\" element.");
3786 return HTTP_BAD_REQUEST;
3789 ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3790 &ctx.label, &tsize);
3792 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3793 "A \"label-name\" element does not contain "
3795 return HTTP_BAD_REQUEST;
3798 /* do the label operation walk */
3799 ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
3800 ctx.w.func = dav_label_walker;
3801 ctx.w.walk_ctx = &ctx;
3802 ctx.w.pool = r->pool;
3803 ctx.w.root = resource;
3804 ctx.vsn_hooks = vsn_hooks;
3806 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
3809 /* some sort of error occurred which terminated the walk */
3810 err = dav_push_error(r->pool, err->status, 0,
3811 "The LABEL operation was terminated prematurely.",
3813 return dav_handle_err(r, err, multi_status);
3816 if (multi_status != NULL) {
3817 /* One or more resources had errors. If depth was zero, convert
3818 * response to simple error, else make sure there is an
3819 * overall error to pass to dav_handle_err()
3822 err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
3823 multi_status = NULL;
3826 err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
3827 "Errors occurred during the LABEL operation.");
3830 return dav_handle_err(r, err, multi_status);
3833 /* set the Cache-Control header, per the spec */
3834 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3837 ap_set_content_length(r, 0);
3842 static int dav_method_report(request_rec *r)
3844 dav_resource *resource;
3845 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3851 /* If no versioning provider, decline the request */
3852 if (vsn_hooks == NULL)
3855 if ((result = ap_xml_parse_input(r, &doc)) != OK)
3858 /* This supplies additional information for the default msg. */
3859 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3860 "The request body must specify a report.");
3861 return HTTP_BAD_REQUEST;
3864 /* Ask repository module to resolve the resource.
3865 * First determine whether a Target-Selector header is allowed
3868 label_allowed = (*vsn_hooks->report_label_header_allowed)(doc);
3869 err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */,
3872 return dav_handle_err(r, err, NULL);
3874 if (!resource->exists) {
3875 /* Apache will supply a default error for this. */
3876 return HTTP_NOT_FOUND;
3879 /* set up defaults for the report response */
3880 r->status = HTTP_OK;
3881 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
3883 /* run report hook */
3884 if ((err = (*vsn_hooks->deliver_report)(r, resource, doc,
3885 r->output_filters)) != NULL) {
3886 /* NOTE: we're assuming that the provider has not generated any
3888 return dav_handle_err(r, err, NULL);
3894 static int dav_method_make_workspace(request_rec *r)
3896 dav_resource *resource;
3897 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3902 /* if no versioning provider, or the provider does not support workspaces,
3903 * decline the request
3905 if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
3908 /* ask repository module to resolve the resource */
3909 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3912 return dav_handle_err(r, err, NULL);
3914 /* parse the request body (must be a mkworkspace element) */
3915 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3920 || !dav_validate_root(doc, "mkworkspace")) {
3921 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3922 "The request body does not contain "
3923 "a \"mkworkspace\" element.");
3924 return HTTP_BAD_REQUEST;
3927 /* Check request preconditions */
3929 /* ### need a general mechanism for reporting precondition violations
3930 * ### (should be returning XML document for 403/409 responses)
3933 /* resource must not already exist */
3934 if (resource->exists) {
3935 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3936 "<DAV:resource-must-be-null/>");
3937 return dav_handle_err(r, err, NULL);
3940 /* ### what about locking? */
3942 /* attempt to create the workspace */
3943 if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
3944 err = dav_push_error(r->pool, err->status, 0,
3945 apr_psprintf(r->pool,
3946 "Could not create workspace %s.",
3947 ap_escape_html(r->pool, r->uri)),
3949 return dav_handle_err(r, err, NULL);
3952 /* set the Cache-Control header, per the spec */
3953 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3955 /* return an appropriate response (HTTP_CREATED) */
3956 return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
3959 static int dav_method_make_activity(request_rec *r)
3961 dav_resource *resource;
3962 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3966 /* if no versioning provider, or the provider does not support activities,
3967 * decline the request
3969 if (vsn_hooks == NULL || vsn_hooks->make_activity == NULL)
3972 /* ask repository module to resolve the resource */
3973 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3976 return dav_handle_err(r, err, NULL);
3978 /* MKACTIVITY does not have a defined request body. */
3979 if ((result = ap_discard_request_body(r)) != OK) {
3983 /* Check request preconditions */
3985 /* ### need a general mechanism for reporting precondition violations
3986 * ### (should be returning XML document for 403/409 responses)
3989 /* resource must not already exist */
3990 if (resource->exists) {
3991 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3992 "<DAV:resource-must-be-null/>");
3993 return dav_handle_err(r, err, NULL);
3996 /* ### what about locking? */
3998 /* attempt to create the activity */
3999 if ((err = (*vsn_hooks->make_activity)(resource)) != NULL) {
4000 err = dav_push_error(r->pool, err->status, 0,
4001 apr_psprintf(r->pool,
4002 "Could not create activity %s.",
4003 ap_escape_html(r->pool, r->uri)),
4005 return dav_handle_err(r, err, NULL);
4008 /* set the Cache-Control header, per the spec */
4009 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4011 /* return an appropriate response (HTTP_CREATED) */
4012 return dav_created(r, resource->uri, "Activity", 0 /*replaced*/);
4015 static int dav_method_baseline_control(request_rec *r)
4018 return HTTP_METHOD_NOT_ALLOWED;
4021 static int dav_method_merge(request_rec *r)
4023 dav_resource *resource;
4024 dav_resource *source_resource;
4025 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4029 ap_xml_elem *source_elem;
4030 ap_xml_elem *href_elem;
4031 ap_xml_elem *prop_elem;
4035 dav_lookup_result lookup;
4037 /* If no versioning provider, decline the request */
4038 if (vsn_hooks == NULL)
4041 if ((result = ap_xml_parse_input(r, &doc)) != OK)
4044 if (doc == NULL || !dav_validate_root(doc, "merge")) {
4045 /* This supplies additional information for the default msg. */
4046 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4047 "The request body must be present and must be a "
4048 "DAV:merge element.");
4049 return HTTP_BAD_REQUEST;
4052 if ((source_elem = dav_find_child(doc->root, "source")) == NULL) {
4053 /* This supplies additional information for the default msg. */
4054 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4055 "The DAV:merge element must contain a DAV:source "
4057 return HTTP_BAD_REQUEST;
4059 if ((href_elem = dav_find_child(source_elem, "href")) == NULL) {
4060 /* This supplies additional information for the default msg. */
4061 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4062 "The DAV:source element must contain a DAV:href "
4064 return HTTP_BAD_REQUEST;
4066 source = dav_xml_get_cdata(href_elem, r->pool, 1 /* strip_white */);
4068 /* get a subrequest for the source, so that we can get a dav_resource
4070 lookup = dav_lookup_uri(source, r, 0 /* must_be_absolute */);
4071 if (lookup.rnew == NULL) {
4072 if (lookup.err.status == HTTP_BAD_REQUEST) {
4073 /* This supplies additional information for the default message. */
4074 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4076 return HTTP_BAD_REQUEST;
4079 /* ### this assumes that dav_lookup_uri() only generates a status
4080 * ### that Apache can provide a status line for!! */
4082 return dav_error_response(r, lookup.err.status, lookup.err.desc);
4084 if (lookup.rnew->status != HTTP_OK) {
4085 /* ### how best to report this... */
4086 return dav_error_response(r, lookup.rnew->status,
4087 "Merge source URI had an error.");
4089 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
4090 0 /* use_checked_in */, &source_resource);
4092 return dav_handle_err(r, err, NULL);
4094 no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL;
4095 no_checkout = dav_find_child(doc->root, "no-checkout") != NULL;
4097 prop_elem = dav_find_child(doc->root, "prop");
4099 /* ### check RFC. I believe the DAV:merge element may contain any
4100 ### element also allowed within DAV:checkout. need to extract them
4101 ### here, and pass them along.
4102 ### if so, then refactor the CHECKOUT method handling so we can reuse
4103 ### the code. maybe create a structure to hold CHECKOUT parameters
4104 ### which can be passed to the checkout() and merge() hooks. */
4106 /* Ask repository module to resolve the resource */
4107 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4110 return dav_handle_err(r, err, NULL);
4111 if (!resource->exists) {
4112 /* Apache will supply a default error for this. */
4113 return HTTP_NOT_FOUND;
4116 /* ### check the source and target resources flags/types */
4118 /* ### do lock checks, once behavior is defined */
4120 /* set the Cache-Control header, per the spec */
4122 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4124 /* Initialize these values for a standard MERGE response. If the MERGE
4125 is going to do something different (i.e. an error), then it must
4126 return a dav_error, and we'll reset these values properly. */
4127 r->status = HTTP_OK;
4128 ap_set_content_type(r, "text/xml");
4130 /* ### should we do any preliminary response generation? probably not,
4131 ### because we may have an error, thus demanding something else in
4132 ### the response body. */
4134 /* Do the merge, including any response generation. */
4135 if ((err = (*vsn_hooks->merge)(resource, source_resource,
4136 no_auto_merge, no_checkout,
4138 r->output_filters)) != NULL) {
4139 /* ### is err->status the right error here? */
4140 err = dav_push_error(r->pool, err->status, 0,
4141 apr_psprintf(r->pool,
4142 "Could not MERGE resource \"%s\" "
4144 ap_escape_html(r->pool, source),
4145 ap_escape_html(r->pool, r->uri)),
4147 return dav_handle_err(r, err, NULL);
4150 /* the response was fully generated by the merge() hook. */
4151 /* ### urk. does this prevent logging? need to check... */
4155 static int dav_method_bind(request_rec *r)
4157 dav_resource *resource;
4158 dav_resource *binding;
4159 dav_auto_version_info av_info;
4160 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
4164 dav_response *multi_response = NULL;
4165 dav_lookup_result lookup;
4168 /* If no bindings provider, decline the request */
4169 if (binding_hooks == NULL)
4172 /* Ask repository module to resolve the resource */
4173 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4176 return dav_handle_err(r, err, NULL);
4178 if (!resource->exists) {
4179 /* Apache will supply a default error for this. */
4180 return HTTP_NOT_FOUND;
4183 /* get the destination URI */
4184 dest = apr_table_get(r->headers_in, "Destination");
4186 /* This supplies additional information for the default message. */
4187 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4188 "The request is missing a Destination header.");
4189 return HTTP_BAD_REQUEST;
4192 lookup = dav_lookup_uri(dest, r, 0 /* must_be_absolute */);
4193 if (lookup.rnew == NULL) {
4194 if (lookup.err.status == HTTP_BAD_REQUEST) {
4195 /* This supplies additional information for the default message. */
4196 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4198 return HTTP_BAD_REQUEST;
4200 else if (lookup.err.status == HTTP_BAD_GATEWAY) {
4201 /* ### Bindings protocol draft 02 says to return 507
4202 * ### (Cross Server Binding Forbidden); Apache already defines 507
4203 * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
4204 * ### HTTP_FORBIDDEN
4206 return dav_error_response(r, HTTP_FORBIDDEN,
4207 "Cross server bindings are not "
4208 "allowed by this server.");
4211 /* ### this assumes that dav_lookup_uri() only generates a status
4212 * ### that Apache can provide a status line for!! */
4214 return dav_error_response(r, lookup.err.status, lookup.err.desc);
4216 if (lookup.rnew->status != HTTP_OK) {
4217 /* ### how best to report this... */
4218 return dav_error_response(r, lookup.rnew->status,
4219 "Destination URI had an error.");
4222 /* resolve binding resource */
4223 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
4224 0 /* use_checked_in */, &binding);
4226 return dav_handle_err(r, err, NULL);
4228 /* are the two resources handled by the same repository? */
4229 if (resource->hooks != binding->hooks) {
4230 /* ### this message exposes some backend config, but screw it... */
4231 return dav_error_response(r, HTTP_BAD_GATEWAY,
4232 "Destination URI is handled by a "
4233 "different repository than the source URI. "
4234 "BIND between repositories is not possible.");
4237 /* get and parse the overwrite header value */
4238 if ((overwrite = dav_get_overwrite(r)) < 0) {
4239 /* dav_get_overwrite() supplies additional information for the
4240 * default message. */
4241 return HTTP_BAD_REQUEST;
4244 /* quick failure test: if dest exists and overwrite is false. */
4245 if (binding->exists && !overwrite) {
4246 return dav_error_response(r, HTTP_PRECONDITION_FAILED,
4247 "Destination is not empty and "
4248 "Overwrite is not \"T\"");
4251 /* are the source and destination the same? */
4252 if ((*resource->hooks->is_same_resource)(resource, binding)) {
4253 return dav_error_response(r, HTTP_FORBIDDEN,
4254 "Source and Destination URIs are the same.");
4258 * Check If-Headers and existing locks for destination. Note that we
4259 * use depth==infinity since the target (hierarchy) will be deleted
4260 * before the move/copy is completed.
4262 * Note that we are overwriting the target, which implies a DELETE, so
4263 * we are subject to the error/response rules as a DELETE. Namely, we
4264 * will return a 424 error if any of the validations fail.
4265 * (see dav_method_delete() for more information)
4267 if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
4270 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
4271 err = dav_push_error(r->pool, err->status, 0,
4272 apr_psprintf(r->pool,
4273 "Could not BIND %s due to a "
4274 "failed precondition on the "
4275 "destination (e.g. locks).",
4276 ap_escape_html(r->pool, r->uri)),
4278 return dav_handle_err(r, err, multi_response);
4281 /* guard against creating circular bindings */
4282 if (resource->collection
4283 && (*resource->hooks->is_parent_resource)(resource, binding)) {
4284 return dav_error_response(r, HTTP_FORBIDDEN,
4285 "Source collection contains the Destination.");
4287 if (resource->collection
4288 && (*resource->hooks->is_parent_resource)(binding, resource)) {
4289 /* The destination must exist (since it contains the source), and
4290 * a condition above implies Overwrite==T. Obviously, we cannot
4291 * delete the Destination before the BIND, as that would
4292 * delete the Source.
4295 return dav_error_response(r, HTTP_FORBIDDEN,
4296 "Destination collection contains the Source and "
4297 "Overwrite has been specified.");
4300 /* prepare the destination collection for modification */
4301 if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */,
4302 &av_info)) != NULL) {
4303 /* could not make destination writable */
4304 return dav_handle_err(r, err, NULL);
4307 /* If target exists, remove it first (we know Ovewrite must be TRUE).
4308 * Then try to bind to the resource.
4310 if (binding->exists)
4311 err = (*resource->hooks->remove_resource)(binding, &multi_response);
4314 err = (*binding_hooks->bind_resource)(resource, binding);
4317 /* restore parent collection states */
4318 err2 = dav_auto_checkin(r, NULL,
4319 err != NULL /* undo if error */,
4320 0 /* unlock */, &av_info);
4322 /* check for error from remove/bind operations */
4324 err = dav_push_error(r->pool, err->status, 0,
4325 apr_psprintf(r->pool,
4326 "Could not BIND %s.",
4327 ap_escape_html(r->pool, r->uri)),
4329 return dav_handle_err(r, err, multi_response);
4332 /* check for errors from reverting writability */
4334 /* just log a warning */
4335 err = dav_push_error(r->pool, err2->status, 0,
4336 "The BIND was successful, but there was a "
4337 "problem automatically checking in the "
4338 "source parent collection.",
4340 dav_log_err(r, err, APLOG_WARNING);
4343 /* return an appropriate response (HTTP_CREATED) */
4344 /* ### spec doesn't say what happens when destination was replaced */
4345 return dav_created(r, lookup.rnew->uri, "Binding", 0);
4350 * Response handler for DAV resources
4352 static int dav_handler(request_rec *r)
4356 if (strcmp(r->handler, "dav-handler")) {
4360 /* quickly ignore any HTTP/0.9 requests */
4361 if (r->assbackwards) {
4365 /* ### do we need to do anything with r->proxyreq ?? */
4367 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
4371 * Set up the methods mask, since that's one of the reasons this handler
4372 * gets called, and lower-level things may need the info.
4374 * First, set the mask to the methods we handle directly. Since by
4375 * definition we own our managed space, we unconditionally set
4376 * the r->allowed field rather than ORing our values with anything
4377 * any other module may have put in there.
4379 * These are the HTTP-defined methods that we handle directly.
4382 | (AP_METHOD_BIT << M_GET)
4383 | (AP_METHOD_BIT << M_PUT)
4384 | (AP_METHOD_BIT << M_DELETE)
4385 | (AP_METHOD_BIT << M_OPTIONS)
4386 | (AP_METHOD_BIT << M_INVALID);
4389 * These are the DAV methods we handle.
4392 | (AP_METHOD_BIT << M_COPY)
4393 | (AP_METHOD_BIT << M_LOCK)
4394 | (AP_METHOD_BIT << M_UNLOCK)
4395 | (AP_METHOD_BIT << M_MKCOL)
4396 | (AP_METHOD_BIT << M_MOVE)
4397 | (AP_METHOD_BIT << M_PROPFIND)
4398 | (AP_METHOD_BIT << M_PROPPATCH);
4401 * These are methods that we don't handle directly, but let the
4402 * server's default handler do for us as our agent.
4405 | (AP_METHOD_BIT << M_POST);
4407 /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
4408 * ### is sent; it will need the other allowed states; since the default
4409 * ### handler is not called on error, then it doesn't add the other
4410 * ### allowed states, so we must
4413 /* ### we might need to refine this for just where we return the error.
4414 * ### also, there is the issue with other methods (see ISSUES)
4417 /* ### more work necessary, now that we have M_foo for DAV methods */
4419 /* dispatch the appropriate method handler */
4420 if (r->method_number == M_GET) {
4421 return dav_method_get(r);
4424 if (r->method_number == M_PUT) {
4425 return dav_method_put(r);
4428 if (r->method_number == M_POST) {
4429 return dav_method_post(r);
4432 if (r->method_number == M_DELETE) {
4433 return dav_method_delete(r);
4436 if (r->method_number == M_OPTIONS) {
4437 return dav_method_options(r);
4440 if (r->method_number == M_PROPFIND) {
4441 return dav_method_propfind(r);
4444 if (r->method_number == M_PROPPATCH) {
4445 return dav_method_proppatch(r);
4448 if (r->method_number == M_MKCOL) {
4449 return dav_method_mkcol(r);
4452 if (r->method_number == M_COPY) {
4453 return dav_method_copymove(r, DAV_DO_COPY);
4456 if (r->method_number == M_MOVE) {
4457 return dav_method_copymove(r, DAV_DO_MOVE);
4460 if (r->method_number == M_LOCK) {
4461 return dav_method_lock(r);
4464 if (r->method_number == M_UNLOCK) {
4465 return dav_method_unlock(r);
4468 if (r->method_number == M_VERSION_CONTROL) {
4469 return dav_method_vsn_control(r);
4472 if (r->method_number == M_CHECKOUT) {
4473 return dav_method_checkout(r);
4476 if (r->method_number == M_UNCHECKOUT) {
4477 return dav_method_uncheckout(r);
4480 if (r->method_number == M_CHECKIN) {
4481 return dav_method_checkin(r);
4484 if (r->method_number == M_UPDATE) {
4485 return dav_method_update(r);
4488 if (r->method_number == M_LABEL) {
4489 return dav_method_label(r);
4492 if (r->method_number == M_REPORT) {
4493 return dav_method_report(r);
4496 if (r->method_number == M_MKWORKSPACE) {
4497 return dav_method_make_workspace(r);
4500 if (r->method_number == M_MKACTIVITY) {
4501 return dav_method_make_activity(r);
4504 if (r->method_number == M_BASELINE_CONTROL) {
4505 return dav_method_baseline_control(r);
4508 if (r->method_number == M_MERGE) {
4509 return dav_method_merge(r);
4512 if (r->method_number == dav_methods[DAV_M_BIND]) {
4513 return dav_method_bind(r);
4516 /* ### add'l methods for Advanced Collections, ACLs, DASL */
4521 static int dav_type_checker(request_rec *r)
4525 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
4528 /* if DAV is not enabled, then we've got nothing to do */
4529 if (conf->provider == NULL) {
4533 if (r->method_number == M_GET) {
4535 * ### need some work to pull Content-Type and Content-Language
4536 * ### from the property database.
4540 * If the repository hasn't indicated that it will handle the
4541 * GET method, then just punt.
4543 * ### this isn't quite right... taking over the response can break
4544 * ### things like mod_negotiation. need to look into this some more.
4546 if (!conf->provider->repos->handle_get) {
4551 /* ### we should (instead) trap the ones that we DO understand */
4552 /* ### the handler DOES handle POST, so we need to fix one of these */
4553 if (r->method_number != M_POST) {
4556 * ### anything else to do here? could another module and/or
4557 * ### config option "take over" the handler here? i.e. how do
4558 * ### we lock down this hierarchy so that we are the ultimate
4559 * ### arbiter? (or do we simply depend on the administrator
4560 * ### to avoid conflicting configurations?)
4562 * ### I think the OK stops running type-checkers. need to look.
4564 r->handler = "dav-handler";
4571 static void register_hooks(apr_pool_t *p)
4573 ap_hook_handler(dav_handler, NULL, NULL, APR_HOOK_MIDDLE);
4574 ap_hook_post_config(dav_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
4575 ap_hook_type_checker(dav_type_checker, NULL, NULL, APR_HOOK_FIRST);
4577 dav_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, APR_HOOK_LAST);
4578 dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
4579 NULL, NULL, APR_HOOK_MIDDLE);
4581 dav_core_register_uris(p);
4584 /*---------------------------------------------------------------------------
4586 * Configuration info for the module
4589 static const command_rec dav_cmds[] =
4591 /* per directory/location */
4592 AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
4593 "specify the DAV provider for a directory or location"),
4595 /* per directory/location, or per server */
4596 AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
4597 ACCESS_CONF|RSRC_CONF,
4598 "specify minimum allowed timeout"),
4600 /* per directory/location, or per server */
4601 AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL,
4602 ACCESS_CONF|RSRC_CONF,
4603 "allow Depth infinity PROPFIND requests"),
4608 module DAV_DECLARE_DATA dav_module =
4610 STANDARD20_MODULE_STUFF,
4611 dav_create_dir_config, /* dir config creater */
4612 dav_merge_dir_config, /* dir merger --- default is to override */
4613 dav_create_server_config, /* server config */
4614 dav_merge_server_config, /* merge server config */
4615 dav_cmds, /* command table */
4616 register_hooks, /* register hooks */
4620 APR_HOOK_LINK(gather_propsets)
4621 APR_HOOK_LINK(find_liveprop)
4622 APR_HOOK_LINK(insert_all_liveprops)
4625 APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets,
4626 (apr_array_header_t *uris),
4629 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, find_liveprop,
4630 (const dav_resource *resource,
4631 const char *ns_uri, const char *name,
4632 const dav_hooks_liveprop **hooks),
4633 (resource, ns_uri, name, hooks), 0)
4635 APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops,
4636 (request_rec *r, const dav_resource *resource,
4637 dav_prop_insert what, ap_text_header *phdr),
4638 (r, resource, what, phdr))