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;
139 static int dav_methods[DAV_M_LAST];
142 static int dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
145 /* DBG0("dav_init_handler"); */
147 /* Register DAV methods */
148 dav_methods[DAV_M_BIND] = ap_method_register(p, "BIND");
149 dav_methods[DAV_M_SEARCH] = ap_method_register(p, "SEARCH");
151 ap_add_version_component(p, "DAV/2");
156 static void *dav_create_server_config(apr_pool_t *p, server_rec *s)
158 dav_server_conf *newconf;
160 newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
162 /* ### this isn't used at the moment... */
167 static void *dav_merge_server_config(apr_pool_t *p, void *base, void *overrides)
170 dav_server_conf *child = overrides;
172 dav_server_conf *newconf;
174 newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
176 /* ### nothing to merge right now... */
181 static void *dav_create_dir_config(apr_pool_t *p, char *dir)
183 /* NOTE: dir==NULL creates the default per-dir config */
187 conf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*conf));
189 /* clean up the directory to remove any trailing slash */
194 d = apr_pstrdup(p, dir);
196 if (l > 1 && d[l - 1] == '/')
204 static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
206 dav_dir_conf *parent = base;
207 dav_dir_conf *child = overrides;
208 dav_dir_conf *newconf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*newconf));
210 /* DBG3("dav_merge_dir_config: new=%08lx base=%08lx overrides=%08lx",
211 (long)newconf, (long)base, (long)overrides); */
213 newconf->provider_name = DAV_INHERIT_VALUE(parent, child, provider_name);
214 newconf->provider = DAV_INHERIT_VALUE(parent, child, provider);
215 if (parent->provider_name != NULL) {
216 if (child->provider_name == NULL) {
217 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, NULL,
218 "\"DAV Off\" cannot be used to turn off a subtree "
219 "of a DAV-enabled location.");
221 else if (strcasecmp(child->provider_name,
222 parent->provider_name) != 0) {
223 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, NULL,
224 "A subtree cannot specify a different DAV provider "
229 newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
230 newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
231 newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
232 allow_depthinfinity);
237 static const dav_provider *dav_get_provider(request_rec *r)
241 conf = ap_get_module_config(r->per_dir_config, &dav_module);
242 /* assert: conf->provider_name != NULL
243 (otherwise, DAV is disabled, and we wouldn't be here) */
245 /* assert: conf->provider != NULL
246 (checked when conf->provider_name is set) */
247 return conf->provider;
250 const dav_hooks_locks *dav_get_lock_hooks(request_rec *r)
252 return dav_get_provider(r)->locks;
255 const dav_hooks_propdb *dav_get_propdb_hooks(request_rec *r)
257 return dav_get_provider(r)->propdb;
260 const dav_hooks_vsn *dav_get_vsn_hooks(request_rec *r)
262 return dav_get_provider(r)->vsn;
265 const dav_hooks_binding *dav_get_binding_hooks(request_rec *r)
267 return dav_get_provider(r)->binding;
270 const dav_hooks_search *dav_get_search_hooks(request_rec *r)
272 return dav_get_provider(r)->search;
276 * Command handler for the DAV directive, which is TAKE1.
278 static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
280 dav_dir_conf *conf = (dav_dir_conf *)config;
282 if (strcasecmp(arg1, "on") == 0) {
283 conf->provider_name = DAV_DEFAULT_PROVIDER;
285 else if (strcasecmp(arg1, "off") == 0) {
286 conf->provider_name = NULL;
287 conf->provider = NULL;
290 conf->provider_name = apr_pstrdup(cmd->pool, arg1);
293 if (conf->provider_name != NULL) {
294 /* lookup and cache the actual provider now */
295 conf->provider = dav_lookup_provider(conf->provider_name);
297 if (conf->provider == NULL) {
298 /* by the time they use it, the provider should be loaded and
299 registered with us. */
300 return apr_psprintf(cmd->pool,
301 "Unknown DAV provider: %s",
302 conf->provider_name);
310 * Command handler for the DAVDepthInfinity directive, which is FLAG.
312 static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
315 dav_dir_conf *conf = (dav_dir_conf *)config;
318 conf->allow_depthinfinity = DAV_ENABLED_ON;
320 conf->allow_depthinfinity = DAV_ENABLED_OFF;
325 * Command handler for DAVMinTimeout directive, which is TAKE1
327 static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config,
330 dav_dir_conf *conf = (dav_dir_conf *)config;
332 conf->locktimeout = atoi(arg1);
333 if (conf->locktimeout < 0)
334 return "DAVMinTimeout requires a non-negative integer.";
340 ** dav_error_response()
342 ** Send a nice response back to the user. In most cases, Apache doesn't
343 ** allow us to provide details in the body about what happened. This
344 ** function allows us to completely specify the response body.
346 ** ### this function is not logging any errors! (e.g. the body)
348 static int dav_error_response(request_rec *r, int status, const char *body)
352 /* ### I really don't think this is needed; gotta test */
353 r->status_line = ap_get_status_line(status);
355 ap_set_content_type(r, "text/html");
357 /* since we're returning DONE, ensure the request body is consumed. */
358 (void) ap_discard_request_body(r);
360 /* begin the response now... */
369 ap_psignature("<hr />\n", r),
373 /* the response has been sent. */
375 * ### Use of DONE obviates logging..!
382 * Send a "standardized" error response based on the error's namespace & tag
384 static int dav_error_response_tag(request_rec *r,
387 r->status = err->status;
389 /* ### I really don't think this is needed; gotta test */
390 r->status_line = ap_get_status_line(err->status);
392 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
394 /* since we're returning DONE, ensure the request body is consumed. */
395 (void) ap_discard_request_body(r);
397 ap_rputs(DAV_XML_HEADER DEBUG_CR
398 "<D:error xmlns:D=\"DAV:\"", r);
400 if (err->desc != NULL) {
401 /* ### should move this namespace somewhere (with the others!) */
402 ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r);
405 if (err->namespace != NULL) {
407 " xmlns:C=\"%s\">" DEBUG_CR
409 err->namespace, err->tagname);
414 "<D:%s/>" DEBUG_CR, err->tagname);
417 /* here's our mod_dav specific tag: */
418 if (err->desc != NULL) {
420 "<m:human-readable errcode=\"%d\">" DEBUG_CR
422 "</m:human-readable>" DEBUG_CR,
424 apr_xml_quote_string(r->pool, err->desc, 0));
427 ap_rputs("</D:error>" DEBUG_CR, r);
429 /* the response has been sent. */
431 * ### Use of DONE obviates logging..!
438 * Apache's URI escaping does not replace '&' since that is a valid character
439 * in a URI (to form a query section). We must explicitly handle it so that
440 * we can embed the URI into an XML document.
442 static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri)
444 const char *e_uri = ap_escape_uri(p, uri);
446 /* check the easy case... */
447 if (ap_strchr_c(e_uri, '&') == NULL)
450 /* there was a '&', so more work is needed... sigh. */
453 * Note: this is a teeny bit of overkill since we know there are no
454 * '<' or '>' characters, but who cares.
456 return ap_xml_quote_string(p, e_uri, 0);
459 static void dav_send_multistatus(request_rec *r, int status,
461 apr_array_header_t *namespaces)
463 /* Set the correct status and Content-Type */
465 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
467 /* Send the headers and actual multistatus response now... */
468 ap_rputs(DAV_XML_HEADER DEBUG_CR
469 "<D:multistatus xmlns:D=\"DAV:\"", r);
471 if (namespaces != NULL) {
474 for (i = namespaces->nelts; i--; ) {
475 ap_rprintf(r, " xmlns:ns%d=\"%s\"", i,
476 AP_XML_GET_URI_ITEM(namespaces, i));
480 /* ap_rputc('>', r); */
481 ap_rputs(">" DEBUG_CR, r);
483 for (; first != NULL; first = first->next) {
486 if (first->propresult.xmlns == NULL) {
487 ap_rputs("<D:response>", r);
490 ap_rputs("<D:response", r);
491 for (t = first->propresult.xmlns; t; t = t->next) {
492 ap_rputs(t->text, r);
497 ap_rputs(DEBUG_CR "<D:href>", r);
498 ap_rputs(dav_xml_escape_uri(r->pool, first->href), r);
499 ap_rputs("</D:href>" DEBUG_CR, r);
501 if (first->propresult.propstats == NULL) {
502 /* use the Status-Line text from Apache. Note, this will
503 * default to 500 Internal Server Error if first->status
504 * is not a known (or valid) status code.
507 "<D:status>HTTP/1.1 %s</D:status>" DEBUG_CR,
508 ap_get_status_line(first->status));
511 /* assume this includes <propstat> and is quoted properly */
512 for (t = first->propresult.propstats; t; t = t->next) {
513 ap_rputs(t->text, r);
517 if (first->desc != NULL) {
519 * We supply the description, so we know it doesn't have to
520 * have any escaping/encoding applied to it.
522 ap_rputs("<D:responsedescription>", r);
523 ap_rputs(first->desc, r);
524 ap_rputs("</D:responsedescription>" DEBUG_CR, r);
527 ap_rputs("</D:response>" DEBUG_CR, r);
530 ap_rputs("</D:multistatus>" DEBUG_CR, r);
536 * Write error information to the log.
538 static void dav_log_err(request_rec *r, dav_error *err, int level)
543 /* ### should have a directive to log the first or all */
544 for (errscan = err; errscan != NULL; errscan = errscan->prev) {
545 if (errscan->desc == NULL)
548 if (errscan->save_errno != 0) {
549 errno = errscan->save_errno;
550 ap_log_rerror(APLOG_MARK, level, errno, r, "%s [%d, #%d]",
551 errscan->desc, errscan->status, errscan->error_id);
554 ap_log_rerror(APLOG_MARK, level | APLOG_NOERRNO, 0, r,
556 errscan->desc, errscan->status, errscan->error_id);
564 * Handle the standard error processing. <err> must be non-NULL.
566 * <response> is set by the following:
567 * - dav_validate_request()
569 * - repos_hooks->remove_resource
570 * - repos_hooks->move_resource
571 * - repos_hooks->copy_resource
572 * - vsn_hooks->update
574 static int dav_handle_err(request_rec *r, dav_error *err,
575 dav_response *response)
578 dav_log_err(r, err, APLOG_ERR);
580 if (response == NULL) {
581 dav_error *stackerr = err;
583 /* our error messages are safe; tell Apache this */
584 apr_table_setn(r->notes, "verbose-error-to", "*");
586 /* Didn't get a multistatus response passed in, but we still
587 might be able to generate a standard <D:error> response.
588 Search the error stack for an errortag. */
589 while (stackerr != NULL && stackerr->tagname == NULL)
590 stackerr = stackerr->prev;
592 if (stackerr != NULL && stackerr->tagname != NULL)
593 return dav_error_response_tag(r, stackerr);
598 /* since we're returning DONE, ensure the request body is consumed. */
599 (void) ap_discard_request_body(r);
601 /* send the multistatus and tell Apache the request/response is DONE. */
602 dav_send_multistatus(r, err->status, response, NULL);
606 /* handy function for return values of methods that (may) create things */
607 static int dav_created(request_rec *r, const char *locn, const char *what,
616 /* did the target resource already exist? */
618 /* Apache will supply a default message */
619 return HTTP_NO_CONTENT;
622 /* Per HTTP/1.1, S10.2.2: add a Location header to contain the
623 * URI that was created. */
625 /* Convert locn to an absolute URI, and return in Location header */
626 apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, locn, r));
628 /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */
630 /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
631 * we must manufacture the entire response. */
632 body = apr_psprintf(r->pool, "%s %s has been created.",
633 what, ap_escape_html(r->pool, locn));
634 return dav_error_response(r, HTTP_CREATED, body);
637 /* ### move to dav_util? */
638 int dav_get_depth(request_rec *r, int def_depth)
640 const char *depth = apr_table_get(r->headers_in, "Depth");
646 if (strcasecmp(depth, "infinity") == 0) {
649 else if (strcmp(depth, "0") == 0) {
652 else if (strcmp(depth, "1") == 0) {
656 /* The caller will return an HTTP_BAD_REQUEST. This will augment the
657 * default message that Apache provides. */
658 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
659 "An invalid Depth header was specified.");
663 static int dav_get_overwrite(request_rec *r)
665 const char *overwrite = apr_table_get(r->headers_in, "Overwrite");
667 if (overwrite == NULL) {
668 return 1; /* default is "T" */
671 if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') {
675 if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') {
679 /* The caller will return an HTTP_BAD_REQUEST. This will augment the
680 * default message that Apache provides. */
681 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
682 "An invalid Overwrite header was specified.");
686 /* resolve a request URI to a resource descriptor.
688 * If label_allowed != 0, then allow the request target to be altered by
691 * If use_checked_in is true, then the repository provider should return
692 * the resource identified by the DAV:checked-in property of the resource
693 * identified by the Request-URI.
695 static dav_error *dav_get_resource(request_rec *r, int label_allowed,
696 int use_checked_in, dav_resource **res_p)
699 const char *label = NULL;
702 /* if the request target can be overridden, get any target selector */
704 label = apr_table_get(r->headers_in, "label");
707 conf = ap_get_module_config(r->per_dir_config, &dav_module);
708 /* assert: conf->provider != NULL */
710 /* resolve the resource */
711 err = (*conf->provider->repos->get_resource)(r, conf->dir,
712 label, use_checked_in,
715 err = dav_push_error(r->pool, err->status, 0,
716 "Could not fetch resource information.", err);
720 /* Note: this shouldn't happen, but just be sure... */
721 if (*res_p == NULL) {
722 /* ### maybe use HTTP_INTERNAL_SERVER_ERROR */
723 return dav_new_error(r->pool, HTTP_NOT_FOUND, 0,
724 apr_psprintf(r->pool,
725 "The provider did not define a "
727 ap_escape_html(r->pool, r->uri)));
730 /* ### hmm. this doesn't feel like the right place or thing to do */
731 /* if there were any input headers requiring a Vary header in the response,
733 dav_add_vary_header(r, r, *res_p);
738 static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
740 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
747 /* open the thing lazily */
748 return (*hooks->open_lockdb)(r, ro, 0, lockdb);
751 static int dav_parse_range(request_rec *r,
752 apr_off_t *range_start, apr_off_t *range_end)
759 range_c = apr_table_get(r->headers_in, "content-range");
763 range = apr_pstrdup(r->pool, range_c);
764 if (strncasecmp(range, "bytes ", 6) != 0
765 || (dash = ap_strchr(range, '-')) == NULL
766 || (slash = ap_strchr(range, '/')) == NULL) {
767 /* malformed header. ignore it (per S14.16 of RFC2616) */
771 *dash = *slash = '\0';
773 /* ### atol may not be large enough for the apr_off_t */
774 *range_start = atol(range + 6);
775 *range_end = atol(dash + 1);
777 if (*range_end < *range_start
778 || (slash[1] != '*' && atol(slash + 1) <= *range_end)) {
779 /* invalid range. ignore it (per S14.16 of RFC2616) */
783 /* we now have a valid range */
787 /* handle the GET method */
788 static int dav_method_get(request_rec *r)
790 dav_resource *resource;
793 /* This method should only be called when the resource is not
794 * visible to Apache. We will fetch the resource from the repository,
795 * then create a subrequest for Apache to handle.
797 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
800 return dav_handle_err(r, err, NULL);
802 if (!resource->exists) {
803 /* Apache will supply a default error for this. */
804 return HTTP_NOT_FOUND;
807 /* set up the HTTP headers for the response */
808 if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
809 err = dav_push_error(r->pool, err->status, 0,
810 "Unable to set up HTTP headers.",
812 return dav_handle_err(r, err, NULL);
815 if (r->header_only) {
819 /* okay... time to deliver the content */
820 if ((err = (*resource->hooks->deliver)(resource,
821 r->output_filters)) != NULL) {
822 err = dav_push_error(r->pool, err->status, 0,
823 "Unable to deliver content.",
825 return dav_handle_err(r, err, NULL);
831 /* validate resource on POST, then pass it off to the default handler */
832 static int dav_method_post(request_rec *r)
834 dav_resource *resource;
837 /* Ask repository module to resolve the resource */
838 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
841 return dav_handle_err(r, err, NULL);
843 /* Note: depth == 0. Implies no need for a multistatus response. */
844 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
845 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
846 /* ### add a higher-level description? */
847 return dav_handle_err(r, err, NULL);
853 /* handle the PUT method */
854 static int dav_method_put(request_rec *r)
856 dav_resource *resource;
858 dav_auto_version_info av_info;
859 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
864 dav_stream_mode mode;
866 dav_response *multi_response;
868 apr_off_t range_start;
871 if ((result = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != OK) {
875 /* Ask repository module to resolve the resource */
876 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
879 return dav_handle_err(r, err, NULL);
881 /* If not a file or collection resource, PUT not allowed */
882 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
883 && resource->type != DAV_RESOURCE_TYPE_WORKING) {
884 body = apr_psprintf(r->pool,
885 "Cannot create resource %s with PUT.",
886 ap_escape_html(r->pool, r->uri));
887 return dav_error_response(r, HTTP_CONFLICT, body);
890 /* Cannot PUT a collection */
891 if (resource->collection) {
892 return dav_error_response(r, HTTP_CONFLICT,
893 "Cannot PUT to a collection.");
897 resource_state = dav_get_resource_state(r, resource);
900 * Note: depth == 0 normally requires no multistatus response. However,
901 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
902 * other than the Request-URI, thereby requiring a multistatus.
904 * If the resource does not exist (DAV_RESOURCE_NULL), then we must
905 * check the resource *and* its parent. If the resource exists or is
906 * a locknull resource, then we check only the resource.
908 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
909 resource_state == DAV_RESOURCE_NULL ?
910 DAV_VALIDATE_PARENT :
911 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
912 /* ### add a higher-level description? */
913 return dav_handle_err(r, err, multi_response);
916 /* make sure the resource can be modified (if versioning repository) */
917 if ((err = dav_auto_checkout(r, resource,
918 0 /* not parent_only */,
919 &av_info)) != NULL) {
920 /* ### add a higher-level description? */
921 return dav_handle_err(r, err, NULL);
924 /* truncate and rewrite the file unless we see a Content-Range */
925 mode = DAV_MODE_WRITE_TRUNC;
927 has_range = dav_parse_range(r, &range_start, &range_end);
929 mode = DAV_MODE_WRITE_SEEKABLE;
932 /* Create the new file in the repository */
933 if ((err = (*resource->hooks->open_stream)(resource, mode,
935 /* ### assuming FORBIDDEN is probably not quite right... */
936 err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
937 apr_psprintf(r->pool,
938 "Unable to PUT new contents for %s.",
939 ap_escape_html(r->pool, r->uri)),
943 if (err == NULL && has_range) {
944 /* a range was provided. seek to the start */
945 err = (*resource->hooks->seek_stream)(stream, range_start);
949 if (ap_should_client_block(r)) {
950 char *buffer = apr_palloc(r->pool, DAV_READ_BLOCKSIZE);
954 * Once we start reading the request, then we must read the
955 * whole darn thing. ap_discard_request_body() won't do anything
956 * for a partially-read request.
959 while ((len = ap_get_client_block(r, buffer,
960 DAV_READ_BLOCKSIZE)) > 0) {
962 /* write whatever we read, until we see an error */
963 err = (*resource->hooks->write_stream)(stream,
969 * ### what happens if we read more/less than the amount
970 * ### specified in the Content-Range? eek...
975 * Error reading request body. This has precedence over
978 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
979 "An error occurred while reading the "
984 err2 = (*resource->hooks->close_stream)(stream,
985 err == NULL /* commit */);
986 if (err2 != NULL && err == NULL) {
987 /* no error during the write, but we hit one at close. use it. */
993 * Ensure that we think the resource exists now.
994 * ### eek. if an error occurred during the write and we did not commit,
995 * ### then the resource might NOT exist (e.g. dav_fs_repos.c)
998 resource->exists = 1;
1001 /* restore modifiability of resources back to what they were */
1002 err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */,
1003 0 /*unlock*/, &av_info);
1005 /* check for errors now */
1007 return dav_handle_err(r, err, NULL);
1011 /* just log a warning */
1012 err2 = dav_push_error(r->pool, err->status, 0,
1013 "The PUT was successful, but there "
1014 "was a problem automatically checking in "
1015 "the resource or its parent collection.",
1017 dav_log_err(r, err2, APLOG_WARNING);
1020 /* ### place the Content-Type and Content-Language into the propdb */
1022 if (locks_hooks != NULL) {
1025 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1026 /* The file creation was successful, but the locking failed. */
1027 err = dav_push_error(r->pool, err->status, 0,
1028 "The file was PUT successfully, but there "
1029 "was a problem opening the lock database "
1030 "which prevents inheriting locks from the "
1031 "parent resources.",
1033 return dav_handle_err(r, err, NULL);
1036 /* notify lock system that we have created/replaced a resource */
1037 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
1039 (*locks_hooks->close_lockdb)(lockdb);
1042 /* The file creation was successful, but the locking failed. */
1043 err = dav_push_error(r->pool, err->status, 0,
1044 "The file was PUT successfully, but there "
1045 "was a problem updating its lock "
1048 return dav_handle_err(r, err, NULL);
1052 /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
1054 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
1055 return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
1058 /* ### move this to dav_util? */
1059 DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
1060 int status, dav_get_props_result *propstats)
1064 /* just drop some data into an dav_response */
1065 resp = apr_pcalloc(wres->pool, sizeof(*resp));
1066 resp->href = apr_pstrdup(wres->pool, wres->resource->uri);
1067 resp->status = status;
1069 resp->propresult = *propstats;
1072 resp->next = wres->response;
1073 wres->response = resp;
1076 /* handle the DELETE method */
1077 static int dav_method_delete(request_rec *r)
1079 dav_resource *resource;
1080 dav_auto_version_info av_info;
1083 dav_response *multi_response;
1087 /* We don't use the request body right now, so torch it. */
1088 if ((result = ap_discard_request_body(r)) != OK) {
1092 /* Ask repository module to resolve the resource */
1093 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
1096 return dav_handle_err(r, err, NULL);
1097 if (!resource->exists) {
1098 /* Apache will supply a default error for this. */
1099 return HTTP_NOT_FOUND;
1102 /* 2518 says that depth must be infinity only for collections.
1103 * For non-collections, depth is ignored, unless it is an illegal value (1).
1105 depth = dav_get_depth(r, DAV_INFINITY);
1107 if (resource->collection && depth != DAV_INFINITY) {
1108 /* This supplies additional information for the default message. */
1109 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1110 "Depth must be \"infinity\" for DELETE of a collection.");
1111 return HTTP_BAD_REQUEST;
1114 if (!resource->collection && depth == 1) {
1115 /* This supplies additional information for the default message. */
1116 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1117 "Depth of \"1\" is not allowed for DELETE.");
1118 return HTTP_BAD_REQUEST;
1122 ** If any resources fail the lock/If: conditions, then we must fail
1123 ** the delete. Each of the failing resources will be listed within
1124 ** a DAV:multistatus body, wrapped into a 424 response.
1126 ** Note that a failure on the resource itself does not generate a
1127 ** multistatus response -- only internal members/collections.
1129 if ((err = dav_validate_request(r, resource, depth, NULL,
1132 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
1133 err = dav_push_error(r->pool, err->status, 0,
1134 apr_psprintf(r->pool,
1135 "Could not DELETE %s due to a failed "
1136 "precondition (e.g. locks).",
1137 ap_escape_html(r->pool, r->uri)),
1139 return dav_handle_err(r, err, multi_response);
1142 /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
1143 * locked by the token(s) in the if_header.
1145 if ((result = dav_unlock(r, resource, NULL)) != OK) {
1149 /* if versioned resource, make sure parent is checked out */
1150 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
1151 &av_info)) != NULL) {
1152 /* ### add a higher-level description? */
1153 return dav_handle_err(r, err, NULL);
1156 /* try to remove the resource */
1157 err = (*resource->hooks->remove_resource)(resource, &multi_response);
1159 /* restore writability of parent back to what it was */
1160 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
1161 0 /*unlock*/, &av_info);
1163 /* check for errors now */
1165 err = dav_push_error(r->pool, err->status, 0,
1166 apr_psprintf(r->pool,
1167 "Could not DELETE %s.",
1168 ap_escape_html(r->pool, r->uri)),
1170 return dav_handle_err(r, err, multi_response);
1173 /* just log a warning */
1174 err = dav_push_error(r->pool, err2->status, 0,
1175 "The DELETE was successful, but there "
1176 "was a problem automatically checking in "
1177 "the parent collection.",
1179 dav_log_err(r, err, APLOG_WARNING);
1182 /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
1184 /* Apache will supply a default error for this. */
1185 return HTTP_NO_CONTENT;
1188 /* generate DAV:supported-method-set OPTIONS response */
1189 static dav_error *dav_gen_supported_methods(request_rec *r,
1190 const ap_xml_elem *elem,
1191 const apr_table_t *methods,
1192 ap_text_header *body)
1194 const apr_array_header_t *arr;
1195 const apr_table_entry_t *elts;
1201 ap_text_append(r->pool, body, "<D:supported-method-set>" DEBUG_CR);
1203 if (elem->first_child == NULL) {
1204 /* show all supported methods */
1205 arr = apr_table_elts(methods);
1206 elts = (const apr_table_entry_t *)arr->elts;
1208 for (i = 0; i < arr->nelts; ++i) {
1209 if (elts[i].key == NULL)
1212 s = apr_psprintf(r->pool,
1213 "<D:supported-method D:name=\"%s\"/>"
1216 ap_text_append(r->pool, body, s);
1220 /* check for support of specific methods */
1221 for (child = elem->first_child; child != NULL; child = child->next) {
1222 if (child->ns == AP_XML_NS_DAV_ID
1223 && strcmp(child->name, "supported-method") == 0) {
1224 const char *name = NULL;
1226 /* go through attributes to find method name */
1227 for (attr = child->attr; attr != NULL; attr = attr->next) {
1228 if (attr->ns == AP_XML_NS_DAV_ID
1229 && strcmp(attr->name, "name") == 0)
1234 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1235 "A DAV:supported-method element "
1236 "does not have a \"name\" attribute");
1239 /* see if method is supported */
1240 if (apr_table_get(methods, name) != NULL) {
1241 s = apr_psprintf(r->pool,
1242 "<D:supported-method D:name=\"%s\"/>"
1245 ap_text_append(r->pool, body, s);
1251 ap_text_append(r->pool, body, "</D:supported-method-set>" DEBUG_CR);
1255 /* generate DAV:supported-live-property-set OPTIONS response */
1256 static dav_error *dav_gen_supported_live_props(request_rec *r,
1257 const dav_resource *resource,
1258 const ap_xml_elem *elem,
1259 ap_text_header *body)
1267 /* open lock database, to report on supported lock properties */
1268 /* ### should open read-only */
1269 if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
1270 return dav_push_error(r->pool, err->status, 0,
1271 "The lock database could not be opened, "
1272 "preventing the reporting of supported lock "
1277 /* open the property database (readonly) for the resource */
1278 if ((err = dav_open_propdb(r, lockdb, resource, 1, NULL,
1279 &propdb)) != NULL) {
1281 (*lockdb->hooks->close_lockdb)(lockdb);
1283 return dav_push_error(r->pool, err->status, 0,
1284 "The property database could not be opened, "
1285 "preventing report of supported properties.",
1289 ap_text_append(r->pool, body, "<D:supported-live-property-set>" DEBUG_CR);
1291 if (elem->first_child == NULL) {
1292 /* show all supported live properties */
1293 dav_get_props_result props = dav_get_allprops(propdb, DAV_PROP_INSERT_SUPPORTED);
1294 body->last->next = props.propstats;
1295 while (body->last->next != NULL)
1296 body->last = body->last->next;
1299 /* check for support of specific live property */
1300 for (child = elem->first_child; child != NULL; child = child->next) {
1301 if (child->ns == AP_XML_NS_DAV_ID
1302 && strcmp(child->name, "supported-live-property") == 0) {
1303 const char *name = NULL;
1304 const char *nmspace = NULL;
1306 /* go through attributes to find name and namespace */
1307 for (attr = child->attr; attr != NULL; attr = attr->next) {
1308 if (attr->ns == AP_XML_NS_DAV_ID) {
1309 if (strcmp(attr->name, "name") == 0)
1311 else if (strcmp(attr->name, "namespace") == 0)
1312 nmspace = attr->value;
1317 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1318 "A DAV:supported-live-property "
1319 "element does not have a \"name\" "
1324 /* default namespace to DAV: */
1325 if (nmspace == NULL)
1328 /* check for support of property */
1329 dav_get_liveprop_supported(propdb, nmspace, name, body);
1334 ap_text_append(r->pool, body, "</D:supported-live-property-set>" DEBUG_CR);
1336 dav_close_propdb(propdb);
1339 (*lockdb->hooks->close_lockdb)(lockdb);
1344 /* generate DAV:supported-report-set OPTIONS response */
1345 static dav_error *dav_gen_supported_reports(request_rec *r,
1346 const dav_resource *resource,
1347 const ap_xml_elem *elem,
1348 const dav_hooks_vsn *vsn_hooks,
1349 ap_text_header *body)
1356 ap_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR);
1358 if (vsn_hooks != NULL) {
1359 const dav_report_elem *reports;
1360 const dav_report_elem *rp;
1362 if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) {
1363 return dav_push_error(r->pool, err->status, 0,
1364 "DAV:supported-report-set could not be "
1365 "determined due to a problem fetching the "
1366 "available reports for this resource.",
1370 if (reports != NULL) {
1371 if (elem->first_child == NULL) {
1372 /* show all supported reports */
1373 for (rp = reports; rp->nmspace != NULL; ++rp) {
1374 /* Note: we presume reports->namespace is
1375 * properly XML/URL quoted */
1376 s = apr_psprintf(r->pool,
1377 "<D:supported-report D:name=\"%s\" "
1378 "D:namespace=\"%s\"/>" DEBUG_CR,
1379 rp->name, rp->nmspace);
1380 ap_text_append(r->pool, body, s);
1384 /* check for support of specific report */
1385 for (child = elem->first_child; child != NULL; child = child->next) {
1386 if (child->ns == AP_XML_NS_DAV_ID
1387 && strcmp(child->name, "supported-report") == 0) {
1388 const char *name = NULL;
1389 const char *nmspace = NULL;
1391 /* go through attributes to find name and namespace */
1392 for (attr = child->attr; attr != NULL; attr = attr->next) {
1393 if (attr->ns == AP_XML_NS_DAV_ID) {
1394 if (strcmp(attr->name, "name") == 0)
1396 else if (strcmp(attr->name, "namespace") == 0)
1397 nmspace = attr->value;
1402 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1403 "A DAV:supported-report element "
1404 "does not have a \"name\" attribute");
1407 /* default namespace to DAV: */
1408 if (nmspace == NULL)
1411 for (rp = reports; rp->nmspace != NULL; ++rp) {
1412 if (strcmp(name, rp->name) == 0
1413 && strcmp(nmspace, rp->nmspace) == 0) {
1414 /* Note: we presume reports->nmspace is
1415 * properly XML/URL quoted
1417 s = apr_psprintf(r->pool,
1418 "<D:supported-report "
1420 "D:namespace=\"%s\"/>"
1422 rp->name, rp->nmspace);
1423 ap_text_append(r->pool, body, s);
1433 ap_text_append(r->pool, body, "</D:supported-report-set>" DEBUG_CR);
1438 /* handle the SEARCH method */
1439 static int dav_method_search(request_rec *r)
1441 const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
1442 dav_resource *resource;
1444 dav_response *multi_status;
1446 /* If no search provider, decline the request */
1447 if (search_hooks == NULL)
1450 /* This method should only be called when the resource is not
1451 * visible to Apache. We will fetch the resource from the repository,
1452 * then create a subrequest for Apache to handle.
1454 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
1457 return dav_handle_err(r, err, NULL);
1459 if (!resource->exists) {
1460 /* Apache will supply a default error for this. */
1461 return HTTP_NOT_FOUND;
1464 /* set up the HTTP headers for the response */
1465 if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
1466 err = dav_push_error(r->pool, err->status, 0,
1467 "Unable to set up HTTP headers.",
1469 return dav_handle_err(r, err, NULL);
1472 if (r->header_only) {
1476 /* okay... time to search the content */
1477 /* Let's validate XML and process walk function
1478 * in the hook function
1480 if ((err = (*search_hooks->search_resource)(r, &multi_status)) != NULL) {
1481 /* ### add a higher-level description? */
1482 return dav_handle_err(r, err, NULL);
1485 /* We have results in multi_status */
1486 /* Should I pass namespace?? */
1487 dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
1493 /* handle the OPTIONS method */
1494 static int dav_method_options(request_rec *r)
1496 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1497 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1498 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
1499 const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
1500 dav_resource *resource;
1501 const char *dav_level;
1504 const apr_array_header_t *arr;
1505 const apr_table_entry_t *elts;
1506 apr_table_t *methods = apr_table_make(r->pool, 12);
1507 ap_text_header vsn_options = { 0 };
1508 ap_text_header body = { 0 };
1513 apr_array_header_t *uri_ary;
1515 const ap_xml_elem *elem;
1518 /* resolve the resource */
1519 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
1522 return dav_handle_err(r, err, NULL);
1524 /* parse any request body */
1525 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1528 /* note: doc == NULL if no request body */
1530 if (doc && !dav_validate_root(doc, "options")) {
1531 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1532 "The \"options\" element was not found.");
1533 return HTTP_BAD_REQUEST;
1536 /* determine which providers are available */
1539 if (locks_hooks != NULL) {
1543 if (binding_hooks != NULL)
1544 dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
1547 * MSFT Web Folders chokes if length of DAV header value > 63 characters!
1548 * To workaround that, we use separate DAV headers for versioning and
1549 * live prop provider namespace URIs.
1552 apr_table_setn(r->headers_out, "DAV", dav_level);
1555 * If there is a versioning provider, generate DAV headers
1556 * for versioning options.
1558 if (vsn_hooks != NULL) {
1559 (*vsn_hooks->get_vsn_options)(r->pool, &vsn_options);
1561 for (t = vsn_options.first; t != NULL; t = t->next)
1562 apr_table_addn(r->headers_out, "DAV", t->text);
1566 * Gather property set URIs from all the liveprop providers,
1567 * and generate a separate DAV header for each URI, to avoid
1568 * problems with long header lengths.
1570 uri_ary = apr_array_make(r->pool, 5, sizeof(const char *));
1571 dav_run_gather_propsets(uri_ary);
1572 for (i = 0; i < uri_ary->nelts; ++i) {
1573 if (((char **)uri_ary->elts)[i] != NULL)
1574 apr_table_addn(r->headers_out, "DAV", ((char **)uri_ary->elts)[i]);
1577 /* this tells MSFT products to skip looking for FrontPage extensions */
1578 apr_table_setn(r->headers_out, "MS-Author-Via", "DAV");
1581 * Determine which methods are allowed on the resource.
1582 * Three cases: resource is null (3), is lock-null (7.4), or exists.
1584 * All cases support OPTIONS, and if there is a lock provider, LOCK.
1585 * (Lock-) null resources also support MKCOL and PUT.
1586 * Lock-null supports PROPFIND and UNLOCK.
1587 * Existing resources support lots of stuff.
1590 apr_table_addn(methods, "OPTIONS", "");
1592 /* ### take into account resource type */
1593 switch (dav_get_resource_state(r, resource))
1595 case DAV_RESOURCE_EXISTS:
1596 /* resource exists */
1597 apr_table_addn(methods, "GET", "");
1598 apr_table_addn(methods, "HEAD", "");
1599 apr_table_addn(methods, "POST", "");
1600 apr_table_addn(methods, "DELETE", "");
1601 apr_table_addn(methods, "TRACE", "");
1602 apr_table_addn(methods, "PROPFIND", "");
1603 apr_table_addn(methods, "PROPPATCH", "");
1604 apr_table_addn(methods, "COPY", "");
1605 apr_table_addn(methods, "MOVE", "");
1607 if (!resource->collection)
1608 apr_table_addn(methods, "PUT", "");
1610 if (locks_hooks != NULL) {
1611 apr_table_addn(methods, "LOCK", "");
1612 apr_table_addn(methods, "UNLOCK", "");
1617 case DAV_RESOURCE_LOCK_NULL:
1618 /* resource is lock-null. */
1619 apr_table_addn(methods, "MKCOL", "");
1620 apr_table_addn(methods, "PROPFIND", "");
1621 apr_table_addn(methods, "PUT", "");
1623 if (locks_hooks != NULL) {
1624 apr_table_addn(methods, "LOCK", "");
1625 apr_table_addn(methods, "UNLOCK", "");
1630 case DAV_RESOURCE_NULL:
1631 /* resource is null. */
1632 apr_table_addn(methods, "MKCOL", "");
1633 apr_table_addn(methods, "PUT", "");
1635 if (locks_hooks != NULL)
1636 apr_table_addn(methods, "LOCK", "");
1641 /* ### internal error! */
1645 /* If there is a versioning provider, add versioning methods */
1646 if (vsn_hooks != NULL) {
1647 if (!resource->exists) {
1648 if ((*vsn_hooks->versionable)(resource))
1649 apr_table_addn(methods, "VERSION-CONTROL", "");
1651 if (vsn_hooks->can_be_workspace != NULL
1652 && (*vsn_hooks->can_be_workspace)(resource))
1653 apr_table_addn(methods, "MKWORKSPACE", "");
1655 if (vsn_hooks->can_be_activity != NULL
1656 && (*vsn_hooks->can_be_activity)(resource))
1657 apr_table_addn(methods, "MKACTIVITY", "");
1659 else if (!resource->versioned) {
1660 if ((*vsn_hooks->versionable)(resource))
1661 apr_table_addn(methods, "VERSION-CONTROL", "");
1663 else if (resource->working) {
1664 apr_table_addn(methods, "CHECKIN", "");
1666 /* ### we might not support this DeltaV option */
1667 apr_table_addn(methods, "UNCHECKOUT", "");
1669 else if (vsn_hooks->add_label != NULL) {
1670 apr_table_addn(methods, "CHECKOUT", "");
1671 apr_table_addn(methods, "LABEL", "");
1674 apr_table_addn(methods, "CHECKOUT", "");
1678 /* If there is a bindings provider, see if resource is bindable */
1679 if (binding_hooks != NULL
1680 && (*binding_hooks->is_bindable)(resource)) {
1681 apr_table_addn(methods, "BIND", "");
1684 /* If there is a search provider, set SEARCH in option */
1685 if (search_hooks != NULL) {
1686 apr_table_addn(methods, "SEARCH", "");
1689 /* Generate the Allow header */
1690 arr = apr_table_elts(methods);
1691 elts = (const apr_table_entry_t *)arr->elts;
1694 /* first, compute total length */
1695 for (i = 0; i < arr->nelts; ++i) {
1696 if (elts[i].key == NULL)
1699 /* add 1 for comma or null */
1700 text_size += strlen(elts[i].key) + 1;
1703 s = allow = apr_palloc(r->pool, text_size);
1705 for (i = 0; i < arr->nelts; ++i) {
1706 if (elts[i].key == NULL)
1712 strcpy(s, elts[i].key);
1716 apr_table_setn(r->headers_out, "Allow", allow);
1719 /* If there is search set_option_head function, set head */
1720 /* DASL: <DAV:basicsearch>
1721 * DASL: <http://foo.bar.com/syntax1>
1722 * DASL: <http://akuma.com/syntax2>
1724 if (search_hooks != NULL
1725 && *search_hooks->set_option_head != NULL) {
1726 if ((err = (*search_hooks->set_option_head)(r)) != NULL) {
1727 return dav_handle_err(r, err, NULL);
1731 /* if there was no request body, then there is no response body */
1733 ap_set_content_length(r, 0);
1735 /* ### this sends a Content-Type. the default OPTIONS does not. */
1737 /* ### the default (ap_send_http_options) returns OK, but I believe
1738 * ### that is because it is the default handler and nothing else
1739 * ### will run after the thing. */
1743 /* handle each options request */
1744 for (elem = doc->root->first_child; elem != NULL; elem = elem->next) {
1745 /* check for something we recognize first */
1746 int core_option = 0;
1747 dav_error *err = NULL;
1749 if (elem->ns == AP_XML_NS_DAV_ID) {
1750 if (strcmp(elem->name, "supported-method-set") == 0) {
1751 err = dav_gen_supported_methods(r, elem, methods, &body);
1754 else if (strcmp(elem->name, "supported-live-property-set") == 0) {
1755 err = dav_gen_supported_live_props(r, resource, elem, &body);
1758 else if (strcmp(elem->name, "supported-report-set") == 0) {
1759 err = dav_gen_supported_reports(r, resource, elem, vsn_hooks, &body);
1765 return dav_handle_err(r, err, NULL);
1767 /* if unrecognized option, pass to versioning provider */
1769 if ((err = (*vsn_hooks->get_option)(resource, elem, &body))
1771 return dav_handle_err(r, err, NULL);
1776 /* send the options response */
1777 r->status = HTTP_OK;
1778 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
1780 /* send the headers and response body */
1781 ap_rputs(DAV_XML_HEADER DEBUG_CR
1782 "<D:options-response xmlns:D=\"DAV:\">" DEBUG_CR, r);
1784 for (t = body.first; t != NULL; t = t->next)
1785 ap_rputs(t->text, r);
1787 ap_rputs("</D:options-response>" DEBUG_CR, r);
1789 /* we've sent everything necessary to the client. */
1793 static void dav_cache_badprops(dav_walker_ctx *ctx)
1795 const ap_xml_elem *elem;
1796 ap_text_header hdr = { 0 };
1798 /* just return if we built the thing already */
1799 if (ctx->propstat_404 != NULL) {
1803 ap_text_append(ctx->w.pool, &hdr,
1804 "<D:propstat>" DEBUG_CR
1805 "<D:prop>" DEBUG_CR);
1807 elem = dav_find_child(ctx->doc->root, "prop");
1808 for (elem = elem->first_child; elem; elem = elem->next) {
1809 ap_text_append(ctx->w.pool, &hdr,
1810 ap_xml_empty_elem(ctx->w.pool, elem));
1813 ap_text_append(ctx->w.pool, &hdr,
1814 "</D:prop>" DEBUG_CR
1815 "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
1816 "</D:propstat>" DEBUG_CR);
1818 ctx->propstat_404 = hdr.first;
1821 static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
1823 dav_walker_ctx *ctx = wres->walk_ctx;
1826 dav_get_props_result propstats = { 0 };
1829 ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
1830 ** dav_get_allprops() does not need to do namespace translation,
1833 ** Note: we cast to lose the "const". The propdb won't try to change
1834 ** the resource, however, since we are opening readonly.
1836 err = dav_open_propdb(ctx->r, ctx->w.lockdb, wres->resource, 1,
1837 ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
1839 /* ### do something with err! */
1841 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1842 dav_get_props_result badprops = { 0 };
1844 /* some props were expected on this collection/resource */
1845 dav_cache_badprops(ctx);
1846 badprops.propstats = ctx->propstat_404;
1847 dav_add_response(wres, 0, &badprops);
1850 /* no props on this collection/resource */
1851 dav_add_response(wres, HTTP_OK, NULL);
1855 /* ### what to do about closing the propdb on server failure? */
1857 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1858 propstats = dav_get_props(propdb, ctx->doc);
1861 dav_prop_insert what = ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP
1862 ? DAV_PROP_INSERT_VALUE
1863 : DAV_PROP_INSERT_NAME;
1864 propstats = dav_get_allprops(propdb, what);
1866 dav_close_propdb(propdb);
1868 dav_add_response(wres, 0, &propstats);
1873 /* handle the PROPFIND method */
1874 static int dav_method_propfind(request_rec *r)
1876 dav_resource *resource;
1881 const ap_xml_elem *child;
1882 dav_walker_ctx ctx = { { 0 } };
1883 dav_response *multi_status;
1885 /* Ask repository module to resolve the resource */
1886 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
1889 return dav_handle_err(r, err, NULL);
1891 if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
1892 /* Apache will supply a default error for this. */
1893 return HTTP_NOT_FOUND;
1896 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
1897 /* dav_get_depth() supplies additional information for the
1898 * default message. */
1899 return HTTP_BAD_REQUEST;
1902 if (depth == DAV_INFINITY && resource->collection) {
1904 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
1906 /* default is to DISALLOW these requests */
1907 if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
1908 return dav_error_response(r, HTTP_FORBIDDEN,
1909 apr_psprintf(r->pool,
1910 "PROPFIND requests with a "
1911 "Depth of \"infinity\" are "
1912 "not allowed for %s.",
1913 ap_escape_html(r->pool,
1918 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1921 /* note: doc == NULL if no request body */
1923 if (doc && !dav_validate_root(doc, "propfind")) {
1924 /* This supplies additional information for the default message. */
1925 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1926 "The \"propfind\" element was not found.");
1927 return HTTP_BAD_REQUEST;
1930 /* ### validate that only one of these three elements is present */
1933 || (child = dav_find_child(doc->root, "allprop")) != NULL) {
1934 /* note: no request body implies allprop */
1935 ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
1937 else if ((child = dav_find_child(doc->root, "propname")) != NULL) {
1938 ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
1940 else if ((child = dav_find_child(doc->root, "prop")) != NULL) {
1941 ctx.propfind_type = DAV_PROPFIND_IS_PROP;
1944 /* "propfind" element must have one of the above three children */
1946 /* This supplies additional information for the default message. */
1947 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1948 "The \"propfind\" element does not contain one of "
1949 "the required child elements (the specific command).");
1950 return HTTP_BAD_REQUEST;
1953 ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
1954 ctx.w.func = dav_propfind_walker;
1955 ctx.w.walk_ctx = &ctx;
1956 ctx.w.pool = r->pool;
1957 ctx.w.root = resource;
1962 /* ### should open read-only */
1963 if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
1964 err = dav_push_error(r->pool, err->status, 0,
1965 "The lock database could not be opened, "
1966 "preventing access to the various lock "
1967 "properties for the PROPFIND.",
1969 return dav_handle_err(r, err, NULL);
1971 if (ctx.w.lockdb != NULL) {
1972 /* if we have a lock database, then we can walk locknull resources */
1973 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1976 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
1978 if (ctx.w.lockdb != NULL) {
1979 (*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb);
1983 /* ### add a higher-level description? */
1984 return dav_handle_err(r, err, NULL);
1987 /* return a 207 (Multi-Status) response now. */
1989 /* if a 404 was generated for an HREF, then we need to spit out the
1990 * doc's namespaces for use by the 404. Note that <response> elements
1991 * will override these ns0, ns1, etc, but NOT within the <response>
1992 * scope for the badprops. */
1993 /* NOTE: propstat_404 != NULL implies doc != NULL */
1994 if (ctx.propstat_404 != NULL) {
1995 dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status,
1999 dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
2002 /* the response has been sent. */
2006 static ap_text * dav_failed_proppatch(apr_pool_t *p,
2007 apr_array_header_t *prop_ctx)
2009 ap_text_header hdr = { 0 };
2010 int i = prop_ctx->nelts;
2011 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
2012 dav_error *err424_set = NULL;
2013 dav_error *err424_delete = NULL;
2016 /* ### might be nice to sort by status code and description */
2018 for ( ; i-- > 0; ++ctx ) {
2019 ap_text_append(p, &hdr,
2020 "<D:propstat>" DEBUG_CR
2022 ap_text_append(p, &hdr, ap_xml_empty_elem(p, ctx->prop));
2023 ap_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
2025 if (ctx->err == NULL) {
2026 /* nothing was assigned here yet, so make it a 424 */
2028 if (ctx->operation == DAV_PROP_OP_SET) {
2029 if (err424_set == NULL)
2030 err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
2031 "Attempted DAV:set operation "
2032 "could not be completed due "
2033 "to other errors.");
2034 ctx->err = err424_set;
2036 else if (ctx->operation == DAV_PROP_OP_DELETE) {
2037 if (err424_delete == NULL)
2038 err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
2039 "Attempted DAV:remove "
2040 "operation could not be "
2041 "completed due to other "
2043 ctx->err = err424_delete;
2049 "HTTP/1.1 %d (status)"
2050 "</D:status>" DEBUG_CR,
2052 ap_text_append(p, &hdr, s);
2054 /* ### we should use compute_desc if necessary... */
2055 if (ctx->err->desc != NULL) {
2056 ap_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
2057 ap_text_append(p, &hdr, ctx->err->desc);
2058 ap_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
2061 ap_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
2067 static ap_text * dav_success_proppatch(apr_pool_t *p, apr_array_header_t *prop_ctx)
2069 ap_text_header hdr = { 0 };
2070 int i = prop_ctx->nelts;
2071 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
2074 * ### we probably need to revise the way we assemble the response...
2075 * ### this code assumes everything will return status==200.
2078 ap_text_append(p, &hdr,
2079 "<D:propstat>" DEBUG_CR
2080 "<D:prop>" DEBUG_CR);
2082 for ( ; i-- > 0; ++ctx ) {
2083 ap_text_append(p, &hdr, ap_xml_empty_elem(p, ctx->prop));
2086 ap_text_append(p, &hdr,
2087 "</D:prop>" DEBUG_CR
2088 "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
2089 "</D:propstat>" DEBUG_CR);
2094 static void dav_prop_log_errors(dav_prop_ctx *ctx)
2096 dav_log_err(ctx->r, ctx->err, APLOG_ERR);
2100 * Call <func> for each context. This can stop when an error occurs, or
2101 * simply iterate through the whole list.
2103 * Returns 1 if an error occurs (and the iteration is aborted). Returns 0
2104 * if all elements are processed.
2106 * If <reverse> is true (non-zero), then the list is traversed in
2109 static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
2110 apr_array_header_t *ctx_list, int stop_on_error,
2113 int i = ctx_list->nelts;
2114 dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
2124 if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
2135 /* handle the PROPPATCH method */
2136 static int dav_method_proppatch(request_rec *r)
2139 dav_resource *resource;
2145 dav_response resp = { 0 };
2146 ap_text *propstat_text;
2147 apr_array_header_t *ctx_list;
2149 dav_auto_version_info av_info;
2151 /* Ask repository module to resolve the resource */
2152 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2155 return dav_handle_err(r, err, NULL);
2156 if (!resource->exists) {
2157 /* Apache will supply a default error for this. */
2158 return HTTP_NOT_FOUND;
2161 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
2164 /* note: doc == NULL if no request body */
2166 if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
2167 /* This supplies additional information for the default message. */
2168 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2169 "The request body does not contain "
2170 "a \"propertyupdate\" element.");
2171 return HTTP_BAD_REQUEST;
2174 /* Check If-Headers and existing locks */
2175 /* Note: depth == 0. Implies no need for a multistatus response. */
2176 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
2177 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2178 /* ### add a higher-level description? */
2179 return dav_handle_err(r, err, NULL);
2182 /* make sure the resource can be modified (if versioning repository) */
2183 if ((err = dav_auto_checkout(r, resource,
2184 0 /* not parent_only */,
2185 &av_info)) != NULL) {
2186 /* ### add a higher-level description? */
2187 return dav_handle_err(r, err, NULL);
2190 if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
2191 &propdb)) != NULL) {
2192 /* undo any auto-checkout */
2193 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
2195 err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2196 apr_psprintf(r->pool,
2197 "Could not open the property "
2199 ap_escape_html(r->pool, r->uri)),
2201 return dav_handle_err(r, err, NULL);
2203 /* ### what to do about closing the propdb on server failure? */
2205 /* ### validate "live" properties */
2207 /* set up an array to hold property operation contexts */
2208 ctx_list = apr_array_make(r->pool, 10, sizeof(dav_prop_ctx));
2210 /* do a first pass to ensure that all "remove" properties exist */
2211 for (child = doc->root->first_child; child; child = child->next) {
2213 ap_xml_elem *prop_group;
2214 ap_xml_elem *one_prop;
2216 /* Ignore children that are not set/remove */
2217 if (child->ns != AP_XML_NS_DAV_ID
2218 || (!(is_remove = strcmp(child->name, "remove") == 0)
2219 && strcmp(child->name, "set") != 0)) {
2223 /* make sure that a "prop" child exists for set/remove */
2224 if ((prop_group = dav_find_child(child, "prop")) == NULL) {
2225 dav_close_propdb(propdb);
2227 /* undo any auto-checkout */
2228 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
2230 /* This supplies additional information for the default message. */
2231 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2232 "A \"prop\" element is missing inside "
2233 "the propertyupdate command.");
2234 return HTTP_BAD_REQUEST;
2237 for (one_prop = prop_group->first_child; one_prop;
2238 one_prop = one_prop->next) {
2240 ctx = (dav_prop_ctx *)apr_array_push(ctx_list);
2241 ctx->propdb = propdb;
2242 ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
2243 ctx->prop = one_prop;
2245 ctx->r = r; /* for later use by dav_prop_log_errors() */
2247 dav_prop_validate(ctx);
2249 if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
2255 /* ### should test that we found at least one set/remove */
2257 /* execute all of the operations */
2258 if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
2262 /* generate a failure/success response */
2264 (void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
2265 propstat_text = dav_failed_proppatch(r->pool, ctx_list);
2268 (void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
2269 propstat_text = dav_success_proppatch(r->pool, ctx_list);
2272 /* make sure this gets closed! */
2273 dav_close_propdb(propdb);
2275 /* complete any auto-versioning */
2276 dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info);
2278 /* log any errors that occurred */
2279 (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
2281 resp.href = resource->uri;
2283 /* ### should probably use something new to pass along this text... */
2284 resp.propresult.propstats = propstat_text;
2286 dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
2288 /* the response has been sent. */
2292 static int process_mkcol_body(request_rec *r)
2294 /* This is snarfed from ap_setup_client_block(). We could get pretty
2295 * close to this behavior by passing REQUEST_NO_BODY, but we need to
2296 * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
2297 * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
2299 const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
2300 const char *lenp = apr_table_get(r->headers_in, "Content-Length");
2302 /* make sure to set the Apache request fields properly. */
2303 r->read_body = REQUEST_NO_BODY;
2304 r->read_chunked = 0;
2308 if (strcasecmp(tenc, "chunked")) {
2309 /* Use this instead of Apache's default error string */
2310 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2311 "Unknown Transfer-Encoding %s", tenc);
2312 return HTTP_NOT_IMPLEMENTED;
2315 r->read_chunked = 1;
2318 const char *pos = lenp;
2320 while (apr_isdigit(*pos) || apr_isspace(*pos)) {
2325 /* This supplies additional information for the default message. */
2326 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2327 "Invalid Content-Length %s", lenp);
2328 return HTTP_BAD_REQUEST;
2331 r->remaining = atol(lenp);
2334 if (r->read_chunked || r->remaining > 0) {
2335 /* ### log something? */
2337 /* Apache will supply a default error for this. */
2338 return HTTP_UNSUPPORTED_MEDIA_TYPE;
2342 * Get rid of the body. this will call ap_setup_client_block(), but
2343 * our copy above has already verified its work.
2345 return ap_discard_request_body(r);
2348 /* handle the MKCOL method */
2349 static int dav_method_mkcol(request_rec *r)
2351 dav_resource *resource;
2353 dav_auto_version_info av_info;
2354 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2359 dav_response *multi_status;
2361 /* handle the request body */
2362 /* ### this may move lower once we start processing bodies */
2363 if ((result = process_mkcol_body(r)) != OK) {
2367 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
2370 /* Ask repository module to resolve the resource */
2371 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2374 return dav_handle_err(r, err, NULL);
2376 if (resource->exists) {
2377 /* oops. something was already there! */
2379 /* Apache will supply a default error for this. */
2380 /* ### we should provide a specific error message! */
2381 return HTTP_METHOD_NOT_ALLOWED;
2384 resource_state = dav_get_resource_state(r, resource);
2387 * Check If-Headers and existing locks.
2389 * Note: depth == 0 normally requires no multistatus response. However,
2390 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
2391 * other than the Request-URI, thereby requiring a multistatus.
2393 * If the resource does not exist (DAV_RESOURCE_NULL), then we must
2394 * check the resource *and* its parent. If the resource exists or is
2395 * a locknull resource, then we check only the resource.
2397 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
2398 resource_state == DAV_RESOURCE_NULL ?
2399 DAV_VALIDATE_PARENT :
2400 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2401 /* ### add a higher-level description? */
2402 return dav_handle_err(r, err, multi_status);
2405 /* if versioned resource, make sure parent is checked out */
2406 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
2407 &av_info)) != NULL) {
2408 /* ### add a higher-level description? */
2409 return dav_handle_err(r, err, NULL);
2412 /* try to create the collection */
2413 resource->collection = 1;
2414 err = (*resource->hooks->create_collection)(resource);
2416 /* restore modifiability of parent back to what it was */
2417 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2418 0 /*unlock*/, &av_info);
2420 /* check for errors now */
2422 return dav_handle_err(r, err, NULL);
2425 /* just log a warning */
2426 err = dav_push_error(r->pool, err->status, 0,
2427 "The MKCOL was successful, but there "
2428 "was a problem automatically checking in "
2429 "the parent collection.",
2431 dav_log_err(r, err, APLOG_WARNING);
2434 if (locks_hooks != NULL) {
2437 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2438 /* The directory creation was successful, but the locking failed. */
2439 err = dav_push_error(r->pool, err->status, 0,
2440 "The MKCOL was successful, but there "
2441 "was a problem opening the lock database "
2442 "which prevents inheriting locks from the "
2443 "parent resources.",
2445 return dav_handle_err(r, err, NULL);
2448 /* notify lock system that we have created/replaced a resource */
2449 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
2451 (*locks_hooks->close_lockdb)(lockdb);
2454 /* The dir creation was successful, but the locking failed. */
2455 err = dav_push_error(r->pool, err->status, 0,
2456 "The MKCOL was successful, but there "
2457 "was a problem updating its lock "
2460 return dav_handle_err(r, err, NULL);
2464 /* return an appropriate response (HTTP_CREATED) */
2465 return dav_created(r, NULL, "Collection", 0);
2468 /* handle the COPY and MOVE methods */
2469 static int dav_method_copymove(request_rec *r, int is_move)
2471 dav_resource *resource;
2472 dav_resource *resnew;
2473 dav_auto_version_info src_av_info = { 0 };
2474 dav_auto_version_info dst_av_info = { 0 };
2480 dav_response *multi_response;
2481 dav_lookup_result lookup;
2490 /* Ask repository module to resolve the resource */
2491 err = dav_get_resource(r, !is_move /* label_allowed */,
2492 0 /* use_checked_in */, &resource);
2494 return dav_handle_err(r, err, NULL);
2496 if (!resource->exists) {
2497 /* Apache will supply a default error for this. */
2498 return HTTP_NOT_FOUND;
2501 /* If not a file or collection resource, COPY/MOVE not allowed */
2502 /* ### allow COPY/MOVE of DeltaV resource types */
2503 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
2504 body = apr_psprintf(r->pool,
2505 "Cannot COPY/MOVE resource %s.",
2506 ap_escape_html(r->pool, r->uri));
2507 return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
2510 /* get the destination URI */
2511 dest = apr_table_get(r->headers_in, "Destination");
2513 /* Look in headers provided by Netscape's Roaming Profiles */
2514 const char *nscp_host = apr_table_get(r->headers_in, "Host");
2515 const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
2517 if (nscp_host != NULL && nscp_path != NULL)
2518 dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
2521 /* This supplies additional information for the default message. */
2522 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2523 "The request is missing a Destination header.");
2524 return HTTP_BAD_REQUEST;
2527 lookup = dav_lookup_uri(dest, r, 1 /* must_be_absolute */);
2528 if (lookup.rnew == NULL) {
2529 if (lookup.err.status == HTTP_BAD_REQUEST) {
2530 /* This supplies additional information for the default message. */
2531 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2533 return HTTP_BAD_REQUEST;
2536 /* ### this assumes that dav_lookup_uri() only generates a status
2537 * ### that Apache can provide a status line for!! */
2539 return dav_error_response(r, lookup.err.status, lookup.err.desc);
2541 if (lookup.rnew->status != HTTP_OK) {
2542 /* ### how best to report this... */
2543 return dav_error_response(r, lookup.rnew->status,
2544 "Destination URI had an error.");
2547 /* Resolve destination resource */
2548 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
2549 0 /* use_checked_in */, &resnew);
2551 return dav_handle_err(r, err, NULL);
2553 /* are the two resources handled by the same repository? */
2554 if (resource->hooks != resnew->hooks) {
2555 /* ### this message exposes some backend config, but screw it... */
2556 return dav_error_response(r, HTTP_BAD_GATEWAY,
2557 "Destination URI is handled by a "
2558 "different repository than the source URI. "
2559 "MOVE or COPY between repositories is "
2563 /* get and parse the overwrite header value */
2564 if ((overwrite = dav_get_overwrite(r)) < 0) {
2565 /* dav_get_overwrite() supplies additional information for the
2566 * default message. */
2567 return HTTP_BAD_REQUEST;
2570 /* quick failure test: if dest exists and overwrite is false. */
2571 if (resnew->exists && !overwrite) {
2572 /* Supply some text for the error response body. */
2573 return dav_error_response(r, HTTP_PRECONDITION_FAILED,
2574 "Destination is not empty and "
2575 "Overwrite is not \"T\"");
2578 /* are the source and destination the same? */
2579 if ((*resource->hooks->is_same_resource)(resource, resnew)) {
2580 /* Supply some text for the error response body. */
2581 return dav_error_response(r, HTTP_FORBIDDEN,
2582 "Source and Destination URIs are the same.");
2586 is_dir = resource->collection;
2588 /* get and parse the Depth header value. "0" and "infinity" are legal. */
2589 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
2590 /* dav_get_depth() supplies additional information for the
2591 * default message. */
2592 return HTTP_BAD_REQUEST;
2595 /* This supplies additional information for the default message. */
2596 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2597 "Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
2598 return HTTP_BAD_REQUEST;
2600 if (is_move && is_dir && depth != DAV_INFINITY) {
2601 /* This supplies additional information for the default message. */
2602 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2603 "Depth must be \"infinity\" when moving a collection.");
2604 return HTTP_BAD_REQUEST;
2608 * Check If-Headers and existing locks for each resource in the source
2609 * if we are performing a MOVE. We will return a 424 response with a
2610 * DAV:multistatus body. The multistatus responses will contain the
2611 * information about any resource that fails the validation.
2613 * We check the parent resource, too, since this is a MOVE. Moving the
2614 * resource effectively removes it from the parent collection, so we
2615 * must ensure that we have met the appropriate conditions.
2617 * If a problem occurs with the Request-URI itself, then a plain error
2618 * (rather than a multistatus) will be returned.
2621 && (err = dav_validate_request(r, resource, depth, NULL,
2624 | DAV_VALIDATE_USE_424,
2626 err = dav_push_error(r->pool, err->status, 0,
2627 apr_psprintf(r->pool,
2628 "Could not MOVE %s due to a failed "
2629 "precondition on the source "
2631 ap_escape_html(r->pool, r->uri)),
2633 return dav_handle_err(r, err, multi_response);
2637 * Check If-Headers and existing locks for destination. Note that we
2638 * use depth==infinity since the target (hierarchy) will be deleted
2639 * before the move/copy is completed.
2641 * Note that we are overwriting the target, which implies a DELETE, so
2642 * we are subject to the error/response rules as a DELETE. Namely, we
2643 * will return a 424 error if any of the validations fail.
2644 * (see dav_method_delete() for more information)
2646 if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
2649 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
2650 err = dav_push_error(r->pool, err->status, 0,
2651 apr_psprintf(r->pool,
2652 "Could not MOVE/COPY %s due to a "
2653 "failed precondition on the "
2654 "destination (e.g. locks).",
2655 ap_escape_html(r->pool, r->uri)),
2657 return dav_handle_err(r, err, multi_response);
2661 && depth == DAV_INFINITY
2662 && (*resource->hooks->is_parent_resource)(resource, resnew)) {
2663 /* Supply some text for the error response body. */
2664 return dav_error_response(r, HTTP_FORBIDDEN,
2665 "Source collection contains the "
2670 && (*resnew->hooks->is_parent_resource)(resnew, resource)) {
2671 /* The destination must exist (since it contains the source), and
2672 * a condition above implies Overwrite==T. Obviously, we cannot
2673 * delete the Destination before the MOVE/COPY, as that would
2674 * delete the Source.
2677 /* Supply some text for the error response body. */
2678 return dav_error_response(r, HTTP_FORBIDDEN,
2679 "Destination collection contains the Source "
2680 "and Overwrite has been specified.");
2683 /* ### for now, we don't need anything in the body */
2684 if ((result = ap_discard_request_body(r)) != OK) {
2688 if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
2689 /* ### add a higher-level description? */
2690 return dav_handle_err(r, err, NULL);
2693 /* remove any locks from the old resources */
2695 * ### this is Yet Another Traversal. if we do a rename(), then we
2696 * ### really don't have to do this in some cases since the inode
2697 * ### values will remain constant across the move. but we can't
2698 * ### know that fact from outside the provider :-(
2700 * ### note that we now have a problem atomicity in the move/copy
2701 * ### since a failure after this would have removed locks (technically,
2702 * ### this is okay to do, but really...)
2704 if (is_move && lockdb != NULL) {
2705 /* ### this is wrong! it blasts direct locks on parent resources */
2706 /* ### pass lockdb! */
2707 (void)dav_unlock(r, resource, NULL);
2710 /* if this is a move, then the source parent collection will be modified */
2712 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
2713 &src_av_info)) != NULL) {
2715 (*lockdb->hooks->close_lockdb)(lockdb);
2717 /* ### add a higher-level description? */
2718 return dav_handle_err(r, err, NULL);
2723 * Remember the initial state of the destination, so the lock system
2724 * can be notified as to how it changed.
2726 resnew_state = dav_get_resource_state(lookup.rnew, resnew);
2728 /* In a MOVE operation, the destination is replaced by the source.
2729 * In a COPY operation, if the destination exists, is under version
2730 * control, and is the same resource type as the source,
2731 * then it should not be replaced, but modified to be a copy of
2734 if (!resnew->exists)
2736 else if (is_move || !resource->versioned)
2738 else if (resource->type != resnew->type)
2740 else if ((resource->collection == 0) != (resnew->collection == 0))
2745 /* If the destination must be created or replaced,
2746 * make sure the parent collection is writable
2748 if (!resnew->exists || replace_dest) {
2749 if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/,
2750 &dst_av_info)) != NULL) {
2751 /* could not make destination writable:
2752 * if move, restore state of source parent
2755 (void)dav_auto_checkin(r, NULL, 1 /* undo */,
2756 0 /*unlock*/, &src_av_info);
2760 (*lockdb->hooks->close_lockdb)(lockdb);
2762 /* ### add a higher-level description? */
2763 return dav_handle_err(r, err, NULL);
2767 /* If source and destination parents are the same, then
2768 * use the same resource object, so status updates to one are reflected
2769 * in the other, when doing auto-versioning. Otherwise,
2770 * we may try to checkin the parent twice.
2772 if (src_av_info.parent_resource != NULL
2773 && dst_av_info.parent_resource != NULL
2774 && (*src_av_info.parent_resource->hooks->is_same_resource)
2775 (src_av_info.parent_resource, dst_av_info.parent_resource)) {
2777 dst_av_info.parent_resource = src_av_info.parent_resource;
2780 /* If destination is being replaced, remove it first
2781 * (we know Ovewrite must be TRUE). Then try to copy/move the resource.
2784 err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
2788 err = (*resource->hooks->move_resource)(resource, resnew,
2791 err = (*resource->hooks->copy_resource)(resource, resnew, depth,
2795 /* perform any auto-versioning cleanup */
2796 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2797 0 /*unlock*/, &dst_av_info);
2800 err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2801 0 /*unlock*/, &src_av_info);
2806 /* check for error from remove/copy/move operations */
2809 (*lockdb->hooks->close_lockdb)(lockdb);
2811 err = dav_push_error(r->pool, err->status, 0,
2812 apr_psprintf(r->pool,
2813 "Could not MOVE/COPY %s.",
2814 ap_escape_html(r->pool, r->uri)),
2816 return dav_handle_err(r, err, multi_response);
2819 /* check for errors from auto-versioning */
2821 /* just log a warning */
2822 err = dav_push_error(r->pool, err2->status, 0,
2823 "The MOVE/COPY was successful, but there was a "
2824 "problem automatically checking in the "
2825 "source parent collection.",
2827 dav_log_err(r, err, APLOG_WARNING);
2830 /* just log a warning */
2831 err = dav_push_error(r->pool, err3->status, 0,
2832 "The MOVE/COPY was successful, but there was a "
2833 "problem automatically checking in the "
2834 "destination or its parent collection.",
2836 dav_log_err(r, err, APLOG_WARNING);
2839 /* propagate any indirect locks at the target */
2840 if (lockdb != NULL) {
2842 /* notify lock system that we have created/replaced a resource */
2843 err = dav_notify_created(r, lockdb, resnew, resnew_state, depth);
2845 (*lockdb->hooks->close_lockdb)(lockdb);
2848 /* The move/copy was successful, but the locking failed. */
2849 err = dav_push_error(r->pool, err->status, 0,
2850 "The MOVE/COPY was successful, but there "
2851 "was a problem updating the lock "
2854 return dav_handle_err(r, err, NULL);
2858 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
2859 return dav_created(r, lookup.rnew->uri, "Destination",
2860 resnew_state == DAV_RESOURCE_EXISTS);
2863 /* dav_method_lock: Handler to implement the DAV LOCK method
2864 * Returns appropriate HTTP_* response.
2866 static int dav_method_lock(request_rec *r)
2869 dav_resource *resource;
2870 const dav_hooks_locks *locks_hooks;
2873 int new_lock_request = 0;
2876 dav_response *multi_response = NULL;
2880 /* If no locks provider, decline the request */
2881 locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2882 if (locks_hooks == NULL)
2885 if ((result = ap_xml_parse_input(r, &doc)) != OK)
2888 depth = dav_get_depth(r, DAV_INFINITY);
2889 if (depth != 0 && depth != DAV_INFINITY) {
2890 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2891 "Depth must be 0 or \"infinity\" for LOCK.");
2892 return HTTP_BAD_REQUEST;
2895 /* Ask repository module to resolve the resource */
2896 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2899 return dav_handle_err(r, err, NULL);
2902 * Open writable. Unless an error occurs, we'll be
2903 * writing into the database.
2905 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2906 /* ### add a higher-level description? */
2907 return dav_handle_err(r, err, NULL);
2911 if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
2913 /* ### add a higher-level description to err? */
2916 new_lock_request = 1;
2918 lock->auth_user = apr_pstrdup(r->pool, r->user);
2921 resource_state = dav_get_resource_state(r, resource);
2924 * Check If-Headers and existing locks.
2926 * If this will create a locknull resource, then the LOCK will affect
2927 * the parent collection (much like a PUT/MKCOL). For that case, we must
2928 * validate the parent resource's conditions.
2930 if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
2931 (resource_state == DAV_RESOURCE_NULL
2932 ? DAV_VALIDATE_PARENT
2933 : DAV_VALIDATE_RESOURCE)
2934 | (new_lock_request ? lock->scope : 0)
2935 | DAV_VALIDATE_ADD_LD,
2937 err = dav_push_error(r->pool, err->status, 0,
2938 apr_psprintf(r->pool,
2939 "Could not LOCK %s due to a failed "
2940 "precondition (e.g. other locks).",
2941 ap_escape_html(r->pool, r->uri)),
2946 if (new_lock_request == 0) {
2947 dav_locktoken_list *ltl;
2951 * ### Assumption: We can renew multiple locks on the same resource
2952 * ### at once. First harvest all the positive lock-tokens given in
2953 * ### the If header. Then modify the lock entries for this resource
2954 * ### with the new Timeout val.
2957 if ((err = dav_get_locktoken_list(r, <l)) != NULL) {
2958 err = dav_push_error(r->pool, err->status, 0,
2959 apr_psprintf(r->pool,
2960 "The lock refresh for %s failed "
2961 "because no lock tokens were "
2962 "specified in an \"If:\" "
2964 ap_escape_html(r->pool, r->uri)),
2969 if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
2972 /* ### add a higher-level description to err? */
2976 /* New lock request */
2977 char *locktoken_txt;
2980 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
2983 /* apply lower bound (if any) from DAVMinTimeout directive */
2984 if (lock->timeout != DAV_TIMEOUT_INFINITE
2985 && lock->timeout < time(NULL) + conf->locktimeout)
2986 lock->timeout = time(NULL) + conf->locktimeout;
2988 err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
2990 /* ### add a higher-level description to err? */
2994 locktoken_txt = apr_pstrcat(r->pool, "<",
2995 (*locks_hooks->format_locktoken)(r->pool,
2999 apr_table_set(r->headers_out, "Lock-Token", locktoken_txt);
3002 (*locks_hooks->close_lockdb)(lockdb);
3004 r->status = HTTP_OK;
3005 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
3007 ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
3009 ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
3012 "<D:lockdiscovery>" DEBUG_CR
3014 "</D:lockdiscovery>" DEBUG_CR,
3015 dav_lock_get_activelock(r, lock, NULL));
3017 ap_rputs("</D:prop>", r);
3019 /* the response has been sent. */
3023 (*locks_hooks->close_lockdb)(lockdb);
3024 return dav_handle_err(r, err, multi_response);
3027 /* dav_method_unlock: Handler to implement the DAV UNLOCK method
3028 * Returns appropriate HTTP_* response.
3030 static int dav_method_unlock(request_rec *r)
3033 dav_resource *resource;
3034 const dav_hooks_locks *locks_hooks;
3036 const char *const_locktoken_txt;
3037 char *locktoken_txt;
3038 dav_locktoken *locktoken = NULL;
3040 dav_response *multi_response;
3042 /* If no locks provider, decline the request */
3043 locks_hooks = DAV_GET_HOOKS_LOCKS(r);
3044 if (locks_hooks == NULL)
3047 if ((const_locktoken_txt = apr_table_get(r->headers_in,
3048 "Lock-Token")) == NULL) {
3049 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3050 "Unlock failed (%s): "
3051 "No Lock-Token specified in header", r->filename);
3052 return HTTP_BAD_REQUEST;
3055 locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt);
3056 if (locktoken_txt[0] != '<') {
3057 /* ### should provide more specifics... */
3058 return HTTP_BAD_REQUEST;
3062 if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
3063 /* ### should provide more specifics... */
3064 return HTTP_BAD_REQUEST;
3066 locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
3068 if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
3069 &locktoken)) != NULL) {
3070 err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
3071 apr_psprintf(r->pool,
3072 "The UNLOCK on %s failed -- an "
3073 "invalid lock token was specified "
3074 "in the \"If:\" header.",
3075 ap_escape_html(r->pool, r->uri)),
3077 return dav_handle_err(r, err, NULL);
3080 /* Ask repository module to resolve the resource */
3081 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3084 return dav_handle_err(r, err, NULL);
3086 resource_state = dav_get_resource_state(r, resource);
3089 * Check If-Headers and existing locks.
3091 * Note: depth == 0 normally requires no multistatus response. However,
3092 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
3093 * other than the Request-URI, thereby requiring a multistatus.
3095 * If the resource is a locknull resource, then the UNLOCK will affect
3096 * the parent collection (much like a delete). For that case, we must
3097 * validate the parent resource's conditions.
3099 if ((err = dav_validate_request(r, resource, 0, locktoken,
3101 resource_state == DAV_RESOURCE_LOCK_NULL
3102 ? DAV_VALIDATE_PARENT
3103 : DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
3104 /* ### add a higher-level description? */
3105 return dav_handle_err(r, err, multi_response);
3108 /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
3109 * _all_ resources locked by locktoken are released. It does not say
3110 * resource has to be the root of an infinte lock. Thus, an UNLOCK
3111 * on any part of an infinte lock will remove the lock on all resources.
3113 * For us, if r->filename represents an indirect lock (part of an infinity lock),
3114 * we must actually perform an UNLOCK on the direct lock for this resource.
3116 if ((result = dav_unlock(r, resource, locktoken)) != OK) {
3120 return HTTP_NO_CONTENT;
3123 static int dav_method_vsn_control(request_rec *r)
3125 dav_resource *resource;
3127 dav_auto_version_info av_info;
3128 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
3129 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3132 const char *target = NULL;
3135 /* if no versioning provider, decline the request */
3136 if (vsn_hooks == NULL)
3139 /* ask repository module to resolve the resource */
3140 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3143 return dav_handle_err(r, err, NULL);
3145 /* remember the pre-creation resource state */
3146 resource_state = dav_get_resource_state(r, resource);
3148 /* parse the request body (may be a version-control element) */
3149 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3152 /* note: doc == NULL if no request body */
3155 const ap_xml_elem *child;
3158 if (!dav_validate_root(doc, "version-control")) {
3159 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3160 "The request body does not contain "
3161 "a \"version-control\" element.");
3162 return HTTP_BAD_REQUEST;
3165 /* get the version URI */
3166 if ((child = dav_find_child(doc->root, "version")) == NULL) {
3167 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3168 "The \"version-control\" element does not contain "
3169 "a \"version\" element.");
3170 return HTTP_BAD_REQUEST;
3173 if ((child = dav_find_child(child, "href")) == NULL) {
3174 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3175 "The \"version\" element does not contain "
3176 "an \"href\" element.");
3177 return HTTP_BAD_REQUEST;
3180 /* get version URI */
3181 ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3184 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3185 "An \"href\" element does not contain a URI.");
3186 return HTTP_BAD_REQUEST;
3190 /* Check request preconditions */
3192 /* ### need a general mechanism for reporting precondition violations
3193 * ### (should be returning XML document for 403/409 responses)
3196 /* if not versioning existing resource, must specify version to select */
3197 if (!resource->exists && target == NULL) {
3198 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3199 "<DAV:initial-version-required/>");
3200 return dav_handle_err(r, err, NULL);
3202 else if (resource->exists) {
3203 /* cannot add resource to existing version history */
3204 if (target != NULL) {
3205 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3206 "<DAV:cannot-add-to-existing-history/>");
3207 return dav_handle_err(r, err, NULL);
3210 /* resource must be unversioned and versionable, or version selector */
3211 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3212 || (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
3213 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3214 "<DAV:must-be-versionable/>");
3215 return dav_handle_err(r, err, NULL);
3218 /* the DeltaV spec says if resource is a version selector,
3219 * then VERSION-CONTROL is a no-op
3221 if (resource->versioned) {
3222 /* set the Cache-Control header, per the spec */
3223 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3226 ap_set_content_length(r, 0);
3232 /* Check If-Headers and existing locks */
3233 /* Note: depth == 0. Implies no need for a multistatus response. */
3234 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
3235 resource_state == DAV_RESOURCE_NULL ?
3236 DAV_VALIDATE_PARENT :
3237 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
3238 return dav_handle_err(r, err, NULL);
3241 /* if in versioned collection, make sure parent is checked out */
3242 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
3243 &av_info)) != NULL) {
3244 return dav_handle_err(r, err, NULL);
3247 /* attempt to version-control the resource */
3248 if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
3249 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
3250 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3251 apr_psprintf(r->pool,
3252 "Could not VERSION-CONTROL resource %s.",
3253 ap_escape_html(r->pool, r->uri)),
3255 return dav_handle_err(r, err, NULL);
3258 /* revert writability of parent directory */
3259 err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info);
3261 /* just log a warning */
3262 err = dav_push_error(r->pool, err->status, 0,
3263 "The VERSION-CONTROL was successful, but there "
3264 "was a problem automatically checking in "
3265 "the parent collection.",
3267 dav_log_err(r, err, APLOG_WARNING);
3270 /* if the resource is lockable, let lock system know of new resource */
3271 if (locks_hooks != NULL
3272 && (*locks_hooks->get_supportedlock)(resource) != NULL) {
3275 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
3276 /* The resource creation was successful, but the locking failed. */
3277 err = dav_push_error(r->pool, err->status, 0,
3278 "The VERSION-CONTROL was successful, but there "
3279 "was a problem opening the lock database "
3280 "which prevents inheriting locks from the "
3281 "parent resources.",
3283 return dav_handle_err(r, err, NULL);
3286 /* notify lock system that we have created/replaced a resource */
3287 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
3289 (*locks_hooks->close_lockdb)(lockdb);
3292 /* The dir creation was successful, but the locking failed. */
3293 err = dav_push_error(r->pool, err->status, 0,
3294 "The VERSION-CONTROL was successful, but there "
3295 "was a problem updating its lock "
3298 return dav_handle_err(r, err, NULL);
3302 /* set the Cache-Control header, per the spec */
3303 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3305 /* return an appropriate response (HTTP_CREATED) */
3306 return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
3309 /* handle the CHECKOUT method */
3310 static int dav_method_checkout(request_rec *r)
3312 dav_resource *resource;
3313 dav_resource *working_resource;
3314 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3318 int apply_to_vsn = 0;
3319 int is_unreserved = 0;
3321 int create_activity = 0;
3322 apr_array_header_t *activities = NULL;
3324 /* If no versioning provider, decline the request */
3325 if (vsn_hooks == NULL)
3328 if ((result = ap_xml_parse_input(r, &doc)) != OK)
3332 const ap_xml_elem *aset;
3334 if (!dav_validate_root(doc, "checkout")) {
3335 /* This supplies additional information for the default msg. */
3336 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3337 "The request body, if present, must be a "
3338 "DAV:checkout element.");
3339 return HTTP_BAD_REQUEST;
3342 if (dav_find_child(doc->root, "apply-to-version") != NULL) {
3343 if (apr_table_get(r->headers_in, "label") != NULL) {
3344 /* ### we want generic 403/409 XML reporting here */
3345 /* ### DAV:must-not-have-label-and-apply-to-version */
3346 return dav_error_response(r, HTTP_CONFLICT,
3347 "DAV:apply-to-version cannot be "
3348 "used in conjunction with a "
3354 is_unreserved = dav_find_child(doc->root, "unreserved") != NULL;
3355 is_fork_ok = dav_find_child(doc->root, "fork-ok") != NULL;
3357 if ((aset = dav_find_child(doc->root, "activity-set")) != NULL) {
3358 if (dav_find_child(aset, "new") != NULL) {
3359 create_activity = 1;
3362 const ap_xml_elem *child = aset->first_child;
3364 activities = apr_array_make(r->pool, 1, sizeof(const char *));
3366 for (; child != NULL; child = child->next) {
3367 if (child->ns == AP_XML_NS_DAV_ID
3368 && strcmp(child->name, "href") == 0) {
3371 href = dav_xml_get_cdata(child, r->pool,
3372 1 /* strip_white */);
3373 *(const char **)apr_array_push(activities) = href;
3377 if (activities->nelts == 0) {
3378 /* no href's is a DTD violation:
3379 <!ELEMENT activity-set (href+ | new)>
3382 /* This supplies additional info for the default msg. */
3383 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3384 "Within the DAV:activity-set element, the "
3385 "DAV:new element must be used, or at least "
3386 "one DAV:href must be specified.");
3387 return HTTP_BAD_REQUEST;
3393 /* Ask repository module to resolve the resource */
3394 err = dav_get_resource(r, 1 /*label_allowed*/, apply_to_vsn, &resource);
3396 return dav_handle_err(r, err, NULL);
3398 if (!resource->exists) {
3399 /* Apache will supply a default error for this. */
3400 return HTTP_NOT_FOUND;
3403 /* Check the state of the resource: must be a file or collection,
3404 * must be versioned, and must not already be checked out.
3406 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3407 && resource->type != DAV_RESOURCE_TYPE_VERSION) {
3408 return dav_error_response(r, HTTP_CONFLICT,
3409 "Cannot checkout this type of resource.");
3412 if (!resource->versioned) {
3413 return dav_error_response(r, HTTP_CONFLICT,
3414 "Cannot checkout unversioned resource.");
3417 if (resource->working) {
3418 return dav_error_response(r, HTTP_CONFLICT,
3419 "The resource is already checked out to the workspace.");
3422 /* ### do lock checks, once behavior is defined */
3424 /* Do the checkout */
3425 if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/,
3426 is_unreserved, is_fork_ok,
3427 create_activity, activities,
3428 &working_resource)) != NULL) {
3429 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3430 apr_psprintf(r->pool,
3431 "Could not CHECKOUT resource %s.",
3432 ap_escape_html(r->pool, r->uri)),
3434 return dav_handle_err(r, err, NULL);
3437 /* set the Cache-Control header, per the spec */
3438 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3440 /* if no working resource created, return OK,
3441 * else return CREATED with working resource URL in Location header
3443 if (working_resource == NULL) {
3445 ap_set_content_length(r, 0);
3449 return dav_created(r, working_resource->uri, "Checked-out resource", 0);
3452 /* handle the UNCHECKOUT method */
3453 static int dav_method_uncheckout(request_rec *r)
3455 dav_resource *resource;
3456 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3460 /* If no versioning provider, decline the request */
3461 if (vsn_hooks == NULL)
3464 if ((result = ap_discard_request_body(r)) != OK) {
3468 /* Ask repository module to resolve the resource */
3469 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3472 return dav_handle_err(r, err, NULL);
3474 if (!resource->exists) {
3475 /* Apache will supply a default error for this. */
3476 return HTTP_NOT_FOUND;
3479 /* Check the state of the resource: must be a file or collection,
3480 * must be versioned, and must be checked out.
3482 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3483 return dav_error_response(r, HTTP_CONFLICT,
3484 "Cannot uncheckout this type of resource.");
3487 if (!resource->versioned) {
3488 return dav_error_response(r, HTTP_CONFLICT,
3489 "Cannot uncheckout unversioned resource.");
3492 if (!resource->working) {
3493 return dav_error_response(r, HTTP_CONFLICT,
3494 "The resource is not checked out to the workspace.");
3497 /* ### do lock checks, once behavior is defined */
3499 /* Do the uncheckout */
3500 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
3501 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3502 apr_psprintf(r->pool,
3503 "Could not UNCHECKOUT resource %s.",
3504 ap_escape_html(r->pool, r->uri)),
3506 return dav_handle_err(r, err, NULL);
3510 ap_set_content_length(r, 0);
3515 /* handle the CHECKIN method */
3516 static int dav_method_checkin(request_rec *r)
3518 dav_resource *resource;
3519 dav_resource *new_version;
3520 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3524 int keep_checked_out = 0;
3526 /* If no versioning provider, decline the request */
3527 if (vsn_hooks == NULL)
3530 if ((result = ap_xml_parse_input(r, &doc)) != OK)
3534 if (!dav_validate_root(doc, "checkin")) {
3535 /* This supplies additional information for the default msg. */
3536 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3537 "The request body, if present, must be a "
3538 "DAV:checkin element.");
3539 return HTTP_BAD_REQUEST;
3542 keep_checked_out = dav_find_child(doc->root, "keep-checked-out") != NULL;
3545 /* Ask repository module to resolve the resource */
3546 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3549 return dav_handle_err(r, err, NULL);
3551 if (!resource->exists) {
3552 /* Apache will supply a default error for this. */
3553 return HTTP_NOT_FOUND;
3556 /* Check the state of the resource: must be a file or collection,
3557 * must be versioned, and must be checked out.
3559 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3560 return dav_error_response(r, HTTP_CONFLICT,
3561 "Cannot checkin this type of resource.");
3564 if (!resource->versioned) {
3565 return dav_error_response(r, HTTP_CONFLICT,
3566 "Cannot checkin unversioned resource.");
3569 if (!resource->working) {
3570 return dav_error_response(r, HTTP_CONFLICT,
3571 "The resource is not checked out.");
3574 /* ### do lock checks, once behavior is defined */
3576 /* Do the checkin */
3577 if ((err = (*vsn_hooks->checkin)(resource, keep_checked_out, &new_version))
3579 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3580 apr_psprintf(r->pool,
3581 "Could not CHECKIN resource %s.",
3582 ap_escape_html(r->pool, r->uri)),
3584 return dav_handle_err(r, err, NULL);
3587 return dav_created(r, new_version->uri, "Version", 0);
3590 static int dav_method_update(request_rec *r)
3592 dav_resource *resource;
3593 dav_resource *version = NULL;
3594 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3602 dav_response *multi_response;
3604 dav_lookup_result lookup;
3606 /* If no versioning provider, or UPDATE not supported,
3607 * decline the request */
3608 if (vsn_hooks == NULL || vsn_hooks->update == NULL)
3611 if ((depth = dav_get_depth(r, 0)) < 0) {
3612 /* dav_get_depth() supplies additional information for the
3613 * default message. */
3614 return HTTP_BAD_REQUEST;
3617 /* parse the request body */
3618 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3622 if (doc == NULL || !dav_validate_root(doc, "update")) {
3623 /* This supplies additional information for the default message. */
3624 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3625 "The request body does not contain "
3626 "an \"update\" element.");
3627 return HTTP_BAD_REQUEST;
3630 /* check for label-name or version element, but not both */
3631 if ((child = dav_find_child(doc->root, "label-name")) != NULL)
3633 else if ((child = dav_find_child(doc->root, "version")) != NULL) {
3634 /* get the href element */
3635 if ((child = dav_find_child(child, "href")) == NULL) {
3636 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3637 "The version element does not contain "
3638 "an \"href\" element.");
3639 return HTTP_BAD_REQUEST;
3643 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3644 "The \"update\" element does not contain "
3645 "a \"label-name\" or \"version\" element.");
3646 return HTTP_BAD_REQUEST;
3649 /* a depth greater than zero is only allowed for a label */
3650 if (!is_label && depth != 0) {
3651 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3652 "Depth must be zero for UPDATE with a version");
3653 return HTTP_BAD_REQUEST;
3656 /* get the target value (a label or a version URI) */
3657 ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3660 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3661 "A \"label-name\" or \"href\" element does not contain "
3663 return HTTP_BAD_REQUEST;
3666 /* Ask repository module to resolve the resource */
3667 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3670 return dav_handle_err(r, err, NULL);
3672 if (!resource->exists) {
3673 /* Apache will supply a default error for this. */
3674 return HTTP_NOT_FOUND;
3677 /* ### need a general mechanism for reporting precondition violations
3678 * ### (should be returning XML document for 403/409 responses)
3680 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3681 || !resource->versioned || resource->working) {
3682 return dav_error_response(r, HTTP_CONFLICT,
3683 "<DAV:must-be-checked-in-version-controlled-resource>");
3686 /* if target is a version, resolve the version resource */
3687 /* ### dav_lookup_uri only allows absolute URIs; is that OK? */
3689 lookup = dav_lookup_uri(target, r, 0 /* must_be_absolute */);
3690 if (lookup.rnew == NULL) {
3691 if (lookup.err.status == HTTP_BAD_REQUEST) {
3692 /* This supplies additional information for the default message. */
3693 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3695 return HTTP_BAD_REQUEST;
3698 /* ### this assumes that dav_lookup_uri() only generates a status
3699 * ### that Apache can provide a status line for!! */
3701 return dav_error_response(r, lookup.err.status, lookup.err.desc);
3703 if (lookup.rnew->status != HTTP_OK) {
3704 /* ### how best to report this... */
3705 return dav_error_response(r, lookup.rnew->status,
3706 "Version URI had an error.");
3709 /* resolve version resource */
3710 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
3711 0 /* use_checked_in */, &version);
3713 return dav_handle_err(r, err, NULL);
3715 /* NULL out target, since we're using a version resource */
3719 /* do the UPDATE operation */
3720 err = (*vsn_hooks->update)(resource, version, target, depth, &multi_response);
3723 err = dav_push_error(r->pool, err->status, 0,
3724 ap_psprintf(r->pool,
3725 "Could not UPDATE %s.",
3726 ap_escape_html(r->pool, r->uri)),
3728 return dav_handle_err(r, err, multi_response);
3731 /* set the Cache-Control header, per the spec */
3732 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3735 ap_set_content_length(r, 0);
3740 /* context maintained during LABEL treewalk */
3741 typedef struct dav_label_walker_ctx
3746 /* label being manipulated */
3749 /* label operation */
3751 #define DAV_LABEL_ADD 1
3752 #define DAV_LABEL_SET 2
3753 #define DAV_LABEL_REMOVE 3
3755 /* version provider hooks */
3756 const dav_hooks_vsn *vsn_hooks;
3758 } dav_label_walker_ctx;
3760 static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
3762 dav_label_walker_ctx *ctx = wres->walk_ctx;
3763 dav_error *err = NULL;
3765 /* Check the state of the resource: must be a version or
3766 * non-checkedout version selector
3768 /* ### need a general mechanism for reporting precondition violations
3769 * ### (should be returning XML document for 403/409 responses)
3771 if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
3772 (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
3773 || !wres->resource->versioned)) {
3774 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3775 "<DAV:must-be-version-or-version-selector/>");
3777 else if (wres->resource->working) {
3778 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3779 "<DAV:must-not-be-checked-out/>");
3782 /* do the label operation */
3783 if (ctx->label_op == DAV_LABEL_REMOVE)
3784 err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
3786 err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
3787 ctx->label_op == DAV_LABEL_SET);
3791 /* ### need utility routine to add response with description? */
3792 dav_add_response(wres, err->status, NULL);
3793 wres->response->desc = err->desc;
3799 static int dav_method_label(request_rec *r)
3801 dav_resource *resource;
3802 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3809 dav_label_walker_ctx ctx = { { 0 } };
3810 dav_response *multi_status;
3812 /* If no versioning provider, or the provider doesn't support
3813 * labels, decline the request */
3814 if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
3817 /* Ask repository module to resolve the resource */
3818 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
3821 return dav_handle_err(r, err, NULL);
3822 if (!resource->exists) {
3823 /* Apache will supply a default error for this. */
3824 return HTTP_NOT_FOUND;
3827 if ((depth = dav_get_depth(r, 0)) < 0) {
3828 /* dav_get_depth() supplies additional information for the
3829 * default message. */
3830 return HTTP_BAD_REQUEST;
3833 /* parse the request body */
3834 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3838 if (doc == NULL || !dav_validate_root(doc, "label")) {
3839 /* This supplies additional information for the default message. */
3840 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3841 "The request body does not contain "
3842 "a \"label\" element.");
3843 return HTTP_BAD_REQUEST;
3846 /* check for add, set, or remove element */
3847 if ((child = dav_find_child(doc->root, "add")) != NULL) {
3848 ctx.label_op = DAV_LABEL_ADD;
3850 else if ((child = dav_find_child(doc->root, "set")) != NULL) {
3851 ctx.label_op = DAV_LABEL_SET;
3853 else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
3854 ctx.label_op = DAV_LABEL_REMOVE;
3857 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3858 "The \"label\" element does not contain "
3859 "an \"add\", \"set\", or \"remove\" element.");
3860 return HTTP_BAD_REQUEST;
3863 /* get the label string */
3864 if ((child = dav_find_child(child, "label-name")) == NULL) {
3865 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3866 "The label command element does not contain "
3867 "a \"label-name\" element.");
3868 return HTTP_BAD_REQUEST;
3871 ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3872 &ctx.label, &tsize);
3874 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3875 "A \"label-name\" element does not contain "
3877 return HTTP_BAD_REQUEST;
3880 /* do the label operation walk */
3881 ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
3882 ctx.w.func = dav_label_walker;
3883 ctx.w.walk_ctx = &ctx;
3884 ctx.w.pool = r->pool;
3885 ctx.w.root = resource;
3886 ctx.vsn_hooks = vsn_hooks;
3888 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
3891 /* some sort of error occurred which terminated the walk */
3892 err = dav_push_error(r->pool, err->status, 0,
3893 "The LABEL operation was terminated prematurely.",
3895 return dav_handle_err(r, err, multi_status);
3898 if (multi_status != NULL) {
3899 /* One or more resources had errors. If depth was zero, convert
3900 * response to simple error, else make sure there is an
3901 * overall error to pass to dav_handle_err()
3904 err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
3905 multi_status = NULL;
3908 err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
3909 "Errors occurred during the LABEL operation.");
3912 return dav_handle_err(r, err, multi_status);
3915 /* set the Cache-Control header, per the spec */
3916 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3919 ap_set_content_length(r, 0);
3924 static int dav_method_report(request_rec *r)
3926 dav_resource *resource;
3927 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3933 /* If no versioning provider, decline the request */
3934 if (vsn_hooks == NULL)
3937 if ((result = ap_xml_parse_input(r, &doc)) != OK)
3940 /* This supplies additional information for the default msg. */
3941 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3942 "The request body must specify a report.");
3943 return HTTP_BAD_REQUEST;
3946 /* Ask repository module to resolve the resource.
3947 * First determine whether a Target-Selector header is allowed
3950 label_allowed = (*vsn_hooks->report_label_header_allowed)(doc);
3951 err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */,
3954 return dav_handle_err(r, err, NULL);
3956 if (!resource->exists) {
3957 /* Apache will supply a default error for this. */
3958 return HTTP_NOT_FOUND;
3961 /* set up defaults for the report response */
3962 r->status = HTTP_OK;
3963 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
3965 /* run report hook */
3966 if ((err = (*vsn_hooks->deliver_report)(r, resource, doc,
3967 r->output_filters)) != NULL) {
3968 /* NOTE: we're assuming that the provider has not generated any
3970 return dav_handle_err(r, err, NULL);
3976 static int dav_method_make_workspace(request_rec *r)
3978 dav_resource *resource;
3979 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3984 /* if no versioning provider, or the provider does not support workspaces,
3985 * decline the request
3987 if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
3990 /* ask repository module to resolve the resource */
3991 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3994 return dav_handle_err(r, err, NULL);
3996 /* parse the request body (must be a mkworkspace element) */
3997 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
4002 || !dav_validate_root(doc, "mkworkspace")) {
4003 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4004 "The request body does not contain "
4005 "a \"mkworkspace\" element.");
4006 return HTTP_BAD_REQUEST;
4009 /* Check request preconditions */
4011 /* ### need a general mechanism for reporting precondition violations
4012 * ### (should be returning XML document for 403/409 responses)
4015 /* resource must not already exist */
4016 if (resource->exists) {
4017 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
4018 "<DAV:resource-must-be-null/>");
4019 return dav_handle_err(r, err, NULL);
4022 /* ### what about locking? */
4024 /* attempt to create the workspace */
4025 if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
4026 err = dav_push_error(r->pool, err->status, 0,
4027 apr_psprintf(r->pool,
4028 "Could not create workspace %s.",
4029 ap_escape_html(r->pool, r->uri)),
4031 return dav_handle_err(r, err, NULL);
4034 /* set the Cache-Control header, per the spec */
4035 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4037 /* return an appropriate response (HTTP_CREATED) */
4038 return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
4041 static int dav_method_make_activity(request_rec *r)
4043 dav_resource *resource;
4044 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4048 /* if no versioning provider, or the provider does not support activities,
4049 * decline the request
4051 if (vsn_hooks == NULL || vsn_hooks->make_activity == NULL)
4054 /* ask repository module to resolve the resource */
4055 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4058 return dav_handle_err(r, err, NULL);
4060 /* MKACTIVITY does not have a defined request body. */
4061 if ((result = ap_discard_request_body(r)) != OK) {
4065 /* Check request preconditions */
4067 /* ### need a general mechanism for reporting precondition violations
4068 * ### (should be returning XML document for 403/409 responses)
4071 /* resource must not already exist */
4072 if (resource->exists) {
4073 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
4074 "<DAV:resource-must-be-null/>");
4075 return dav_handle_err(r, err, NULL);
4078 /* ### what about locking? */
4080 /* attempt to create the activity */
4081 if ((err = (*vsn_hooks->make_activity)(resource)) != NULL) {
4082 err = dav_push_error(r->pool, err->status, 0,
4083 apr_psprintf(r->pool,
4084 "Could not create activity %s.",
4085 ap_escape_html(r->pool, r->uri)),
4087 return dav_handle_err(r, err, NULL);
4090 /* set the Cache-Control header, per the spec */
4091 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4093 /* return an appropriate response (HTTP_CREATED) */
4094 return dav_created(r, resource->uri, "Activity", 0 /*replaced*/);
4097 static int dav_method_baseline_control(request_rec *r)
4100 return HTTP_METHOD_NOT_ALLOWED;
4103 static int dav_method_merge(request_rec *r)
4105 dav_resource *resource;
4106 dav_resource *source_resource;
4107 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4111 ap_xml_elem *source_elem;
4112 ap_xml_elem *href_elem;
4113 ap_xml_elem *prop_elem;
4117 dav_lookup_result lookup;
4119 /* If no versioning provider, decline the request */
4120 if (vsn_hooks == NULL)
4123 if ((result = ap_xml_parse_input(r, &doc)) != OK)
4126 if (doc == NULL || !dav_validate_root(doc, "merge")) {
4127 /* This supplies additional information for the default msg. */
4128 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4129 "The request body must be present and must be a "
4130 "DAV:merge element.");
4131 return HTTP_BAD_REQUEST;
4134 if ((source_elem = dav_find_child(doc->root, "source")) == NULL) {
4135 /* This supplies additional information for the default msg. */
4136 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4137 "The DAV:merge element must contain a DAV:source "
4139 return HTTP_BAD_REQUEST;
4141 if ((href_elem = dav_find_child(source_elem, "href")) == NULL) {
4142 /* This supplies additional information for the default msg. */
4143 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4144 "The DAV:source element must contain a DAV:href "
4146 return HTTP_BAD_REQUEST;
4148 source = dav_xml_get_cdata(href_elem, r->pool, 1 /* strip_white */);
4150 /* get a subrequest for the source, so that we can get a dav_resource
4152 lookup = dav_lookup_uri(source, r, 0 /* must_be_absolute */);
4153 if (lookup.rnew == NULL) {
4154 if (lookup.err.status == HTTP_BAD_REQUEST) {
4155 /* This supplies additional information for the default message. */
4156 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4158 return HTTP_BAD_REQUEST;
4161 /* ### this assumes that dav_lookup_uri() only generates a status
4162 * ### that Apache can provide a status line for!! */
4164 return dav_error_response(r, lookup.err.status, lookup.err.desc);
4166 if (lookup.rnew->status != HTTP_OK) {
4167 /* ### how best to report this... */
4168 return dav_error_response(r, lookup.rnew->status,
4169 "Merge source URI had an error.");
4171 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
4172 0 /* use_checked_in */, &source_resource);
4174 return dav_handle_err(r, err, NULL);
4176 no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL;
4177 no_checkout = dav_find_child(doc->root, "no-checkout") != NULL;
4179 prop_elem = dav_find_child(doc->root, "prop");
4181 /* ### check RFC. I believe the DAV:merge element may contain any
4182 ### element also allowed within DAV:checkout. need to extract them
4183 ### here, and pass them along.
4184 ### if so, then refactor the CHECKOUT method handling so we can reuse
4185 ### the code. maybe create a structure to hold CHECKOUT parameters
4186 ### which can be passed to the checkout() and merge() hooks. */
4188 /* Ask repository module to resolve the resource */
4189 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4192 return dav_handle_err(r, err, NULL);
4193 if (!resource->exists) {
4194 /* Apache will supply a default error for this. */
4195 return HTTP_NOT_FOUND;
4198 /* ### check the source and target resources flags/types */
4200 /* ### do lock checks, once behavior is defined */
4202 /* set the Cache-Control header, per the spec */
4204 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4206 /* Initialize these values for a standard MERGE response. If the MERGE
4207 is going to do something different (i.e. an error), then it must
4208 return a dav_error, and we'll reset these values properly. */
4209 r->status = HTTP_OK;
4210 ap_set_content_type(r, "text/xml");
4212 /* ### should we do any preliminary response generation? probably not,
4213 ### because we may have an error, thus demanding something else in
4214 ### the response body. */
4216 /* Do the merge, including any response generation. */
4217 if ((err = (*vsn_hooks->merge)(resource, source_resource,
4218 no_auto_merge, no_checkout,
4220 r->output_filters)) != NULL) {
4221 /* ### is err->status the right error here? */
4222 err = dav_push_error(r->pool, err->status, 0,
4223 apr_psprintf(r->pool,
4224 "Could not MERGE resource \"%s\" "
4226 ap_escape_html(r->pool, source),
4227 ap_escape_html(r->pool, r->uri)),
4229 return dav_handle_err(r, err, NULL);
4232 /* the response was fully generated by the merge() hook. */
4233 /* ### urk. does this prevent logging? need to check... */
4237 static int dav_method_bind(request_rec *r)
4239 dav_resource *resource;
4240 dav_resource *binding;
4241 dav_auto_version_info av_info;
4242 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
4246 dav_response *multi_response = NULL;
4247 dav_lookup_result lookup;
4250 /* If no bindings provider, decline the request */
4251 if (binding_hooks == NULL)
4254 /* Ask repository module to resolve the resource */
4255 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4258 return dav_handle_err(r, err, NULL);
4260 if (!resource->exists) {
4261 /* Apache will supply a default error for this. */
4262 return HTTP_NOT_FOUND;
4265 /* get the destination URI */
4266 dest = apr_table_get(r->headers_in, "Destination");
4268 /* This supplies additional information for the default message. */
4269 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4270 "The request is missing a Destination header.");
4271 return HTTP_BAD_REQUEST;
4274 lookup = dav_lookup_uri(dest, r, 0 /* must_be_absolute */);
4275 if (lookup.rnew == NULL) {
4276 if (lookup.err.status == HTTP_BAD_REQUEST) {
4277 /* This supplies additional information for the default message. */
4278 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
4280 return HTTP_BAD_REQUEST;
4282 else if (lookup.err.status == HTTP_BAD_GATEWAY) {
4283 /* ### Bindings protocol draft 02 says to return 507
4284 * ### (Cross Server Binding Forbidden); Apache already defines 507
4285 * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
4286 * ### HTTP_FORBIDDEN
4288 return dav_error_response(r, HTTP_FORBIDDEN,
4289 "Cross server bindings are not "
4290 "allowed by this server.");
4293 /* ### this assumes that dav_lookup_uri() only generates a status
4294 * ### that Apache can provide a status line for!! */
4296 return dav_error_response(r, lookup.err.status, lookup.err.desc);
4298 if (lookup.rnew->status != HTTP_OK) {
4299 /* ### how best to report this... */
4300 return dav_error_response(r, lookup.rnew->status,
4301 "Destination URI had an error.");
4304 /* resolve binding resource */
4305 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
4306 0 /* use_checked_in */, &binding);
4308 return dav_handle_err(r, err, NULL);
4310 /* are the two resources handled by the same repository? */
4311 if (resource->hooks != binding->hooks) {
4312 /* ### this message exposes some backend config, but screw it... */
4313 return dav_error_response(r, HTTP_BAD_GATEWAY,
4314 "Destination URI is handled by a "
4315 "different repository than the source URI. "
4316 "BIND between repositories is not possible.");
4319 /* get and parse the overwrite header value */
4320 if ((overwrite = dav_get_overwrite(r)) < 0) {
4321 /* dav_get_overwrite() supplies additional information for the
4322 * default message. */
4323 return HTTP_BAD_REQUEST;
4326 /* quick failure test: if dest exists and overwrite is false. */
4327 if (binding->exists && !overwrite) {
4328 return dav_error_response(r, HTTP_PRECONDITION_FAILED,
4329 "Destination is not empty and "
4330 "Overwrite is not \"T\"");
4333 /* are the source and destination the same? */
4334 if ((*resource->hooks->is_same_resource)(resource, binding)) {
4335 return dav_error_response(r, HTTP_FORBIDDEN,
4336 "Source and Destination URIs are the same.");
4340 * Check If-Headers and existing locks for destination. Note that we
4341 * use depth==infinity since the target (hierarchy) will be deleted
4342 * before the move/copy is completed.
4344 * Note that we are overwriting the target, which implies a DELETE, so
4345 * we are subject to the error/response rules as a DELETE. Namely, we
4346 * will return a 424 error if any of the validations fail.
4347 * (see dav_method_delete() for more information)
4349 if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
4352 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
4353 err = dav_push_error(r->pool, err->status, 0,
4354 apr_psprintf(r->pool,
4355 "Could not BIND %s due to a "
4356 "failed precondition on the "
4357 "destination (e.g. locks).",
4358 ap_escape_html(r->pool, r->uri)),
4360 return dav_handle_err(r, err, multi_response);
4363 /* guard against creating circular bindings */
4364 if (resource->collection
4365 && (*resource->hooks->is_parent_resource)(resource, binding)) {
4366 return dav_error_response(r, HTTP_FORBIDDEN,
4367 "Source collection contains the Destination.");
4369 if (resource->collection
4370 && (*resource->hooks->is_parent_resource)(binding, resource)) {
4371 /* The destination must exist (since it contains the source), and
4372 * a condition above implies Overwrite==T. Obviously, we cannot
4373 * delete the Destination before the BIND, as that would
4374 * delete the Source.
4377 return dav_error_response(r, HTTP_FORBIDDEN,
4378 "Destination collection contains the Source and "
4379 "Overwrite has been specified.");
4382 /* prepare the destination collection for modification */
4383 if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */,
4384 &av_info)) != NULL) {
4385 /* could not make destination writable */
4386 return dav_handle_err(r, err, NULL);
4389 /* If target exists, remove it first (we know Ovewrite must be TRUE).
4390 * Then try to bind to the resource.
4392 if (binding->exists)
4393 err = (*resource->hooks->remove_resource)(binding, &multi_response);
4396 err = (*binding_hooks->bind_resource)(resource, binding);
4399 /* restore parent collection states */
4400 err2 = dav_auto_checkin(r, NULL,
4401 err != NULL /* undo if error */,
4402 0 /* unlock */, &av_info);
4404 /* check for error from remove/bind operations */
4406 err = dav_push_error(r->pool, err->status, 0,
4407 apr_psprintf(r->pool,
4408 "Could not BIND %s.",
4409 ap_escape_html(r->pool, r->uri)),
4411 return dav_handle_err(r, err, multi_response);
4414 /* check for errors from reverting writability */
4416 /* just log a warning */
4417 err = dav_push_error(r->pool, err2->status, 0,
4418 "The BIND was successful, but there was a "
4419 "problem automatically checking in the "
4420 "source parent collection.",
4422 dav_log_err(r, err, APLOG_WARNING);
4425 /* return an appropriate response (HTTP_CREATED) */
4426 /* ### spec doesn't say what happens when destination was replaced */
4427 return dav_created(r, lookup.rnew->uri, "Binding", 0);
4432 * Response handler for DAV resources
4434 static int dav_handler(request_rec *r)
4438 if (strcmp(r->handler, "dav-handler")) {
4442 /* quickly ignore any HTTP/0.9 requests */
4443 if (r->assbackwards) {
4447 /* ### do we need to do anything with r->proxyreq ?? */
4449 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
4453 * Set up the methods mask, since that's one of the reasons this handler
4454 * gets called, and lower-level things may need the info.
4456 * First, set the mask to the methods we handle directly. Since by
4457 * definition we own our managed space, we unconditionally set
4458 * the r->allowed field rather than ORing our values with anything
4459 * any other module may have put in there.
4461 * These are the HTTP-defined methods that we handle directly.
4464 | (AP_METHOD_BIT << M_GET)
4465 | (AP_METHOD_BIT << M_PUT)
4466 | (AP_METHOD_BIT << M_DELETE)
4467 | (AP_METHOD_BIT << M_OPTIONS)
4468 | (AP_METHOD_BIT << M_INVALID);
4471 * These are the DAV methods we handle.
4474 | (AP_METHOD_BIT << M_COPY)
4475 | (AP_METHOD_BIT << M_LOCK)
4476 | (AP_METHOD_BIT << M_UNLOCK)
4477 | (AP_METHOD_BIT << M_MKCOL)
4478 | (AP_METHOD_BIT << M_MOVE)
4479 | (AP_METHOD_BIT << M_PROPFIND)
4480 | (AP_METHOD_BIT << M_PROPPATCH);
4483 * These are methods that we don't handle directly, but let the
4484 * server's default handler do for us as our agent.
4487 | (AP_METHOD_BIT << M_POST);
4489 /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
4490 * ### is sent; it will need the other allowed states; since the default
4491 * ### handler is not called on error, then it doesn't add the other
4492 * ### allowed states, so we must
4495 /* ### we might need to refine this for just where we return the error.
4496 * ### also, there is the issue with other methods (see ISSUES)
4499 /* ### more work necessary, now that we have M_foo for DAV methods */
4501 /* dispatch the appropriate method handler */
4502 if (r->method_number == M_GET) {
4503 return dav_method_get(r);
4506 if (r->method_number == M_PUT) {
4507 return dav_method_put(r);
4510 if (r->method_number == M_POST) {
4511 return dav_method_post(r);
4514 if (r->method_number == M_DELETE) {
4515 return dav_method_delete(r);
4518 if (r->method_number == M_OPTIONS) {
4519 return dav_method_options(r);
4522 if (r->method_number == M_PROPFIND) {
4523 return dav_method_propfind(r);
4526 if (r->method_number == M_PROPPATCH) {
4527 return dav_method_proppatch(r);
4530 if (r->method_number == M_MKCOL) {
4531 return dav_method_mkcol(r);
4534 if (r->method_number == M_COPY) {
4535 return dav_method_copymove(r, DAV_DO_COPY);
4538 if (r->method_number == M_MOVE) {
4539 return dav_method_copymove(r, DAV_DO_MOVE);
4542 if (r->method_number == M_LOCK) {
4543 return dav_method_lock(r);
4546 if (r->method_number == M_UNLOCK) {
4547 return dav_method_unlock(r);
4550 if (r->method_number == M_VERSION_CONTROL) {
4551 return dav_method_vsn_control(r);
4554 if (r->method_number == M_CHECKOUT) {
4555 return dav_method_checkout(r);
4558 if (r->method_number == M_UNCHECKOUT) {
4559 return dav_method_uncheckout(r);
4562 if (r->method_number == M_CHECKIN) {
4563 return dav_method_checkin(r);
4566 if (r->method_number == M_UPDATE) {
4567 return dav_method_update(r);
4570 if (r->method_number == M_LABEL) {
4571 return dav_method_label(r);
4574 if (r->method_number == M_REPORT) {
4575 return dav_method_report(r);
4578 if (r->method_number == M_MKWORKSPACE) {
4579 return dav_method_make_workspace(r);
4582 if (r->method_number == M_MKACTIVITY) {
4583 return dav_method_make_activity(r);
4586 if (r->method_number == M_BASELINE_CONTROL) {
4587 return dav_method_baseline_control(r);
4590 if (r->method_number == M_MERGE) {
4591 return dav_method_merge(r);
4594 if (r->method_number == dav_methods[DAV_M_BIND]) {
4595 return dav_method_bind(r);
4599 if (r->method_number == dav_methods[DAV_M_SEARCH]) {
4600 return dav_method_search(r);
4603 /* ### add'l methods for Advanced Collections, ACLs */
4608 static int dav_type_checker(request_rec *r)
4612 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
4615 /* if DAV is not enabled, then we've got nothing to do */
4616 if (conf->provider == NULL) {
4620 if (r->method_number == M_GET) {
4622 * ### need some work to pull Content-Type and Content-Language
4623 * ### from the property database.
4627 * If the repository hasn't indicated that it will handle the
4628 * GET method, then just punt.
4630 * ### this isn't quite right... taking over the response can break
4631 * ### things like mod_negotiation. need to look into this some more.
4633 if (!conf->provider->repos->handle_get) {
4638 /* ### we should (instead) trap the ones that we DO understand */
4639 /* ### the handler DOES handle POST, so we need to fix one of these */
4640 if (r->method_number != M_POST) {
4643 * ### anything else to do here? could another module and/or
4644 * ### config option "take over" the handler here? i.e. how do
4645 * ### we lock down this hierarchy so that we are the ultimate
4646 * ### arbiter? (or do we simply depend on the administrator
4647 * ### to avoid conflicting configurations?)
4649 * ### I think the OK stops running type-checkers. need to look.
4651 r->handler = "dav-handler";
4658 static void register_hooks(apr_pool_t *p)
4660 ap_hook_handler(dav_handler, NULL, NULL, APR_HOOK_MIDDLE);
4661 ap_hook_post_config(dav_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
4662 ap_hook_type_checker(dav_type_checker, NULL, NULL, APR_HOOK_FIRST);
4664 dav_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, APR_HOOK_LAST);
4665 dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
4666 NULL, NULL, APR_HOOK_MIDDLE);
4668 dav_core_register_uris(p);
4671 /*---------------------------------------------------------------------------
4673 * Configuration info for the module
4676 static const command_rec dav_cmds[] =
4678 /* per directory/location */
4679 AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
4680 "specify the DAV provider for a directory or location"),
4682 /* per directory/location, or per server */
4683 AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
4684 ACCESS_CONF|RSRC_CONF,
4685 "specify minimum allowed timeout"),
4687 /* per directory/location, or per server */
4688 AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL,
4689 ACCESS_CONF|RSRC_CONF,
4690 "allow Depth infinity PROPFIND requests"),
4695 module DAV_DECLARE_DATA dav_module =
4697 STANDARD20_MODULE_STUFF,
4698 dav_create_dir_config, /* dir config creater */
4699 dav_merge_dir_config, /* dir merger --- default is to override */
4700 dav_create_server_config, /* server config */
4701 dav_merge_server_config, /* merge server config */
4702 dav_cmds, /* command table */
4703 register_hooks, /* register hooks */
4707 APR_HOOK_LINK(gather_propsets)
4708 APR_HOOK_LINK(find_liveprop)
4709 APR_HOOK_LINK(insert_all_liveprops)
4712 APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets,
4713 (apr_array_header_t *uris),
4716 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, find_liveprop,
4717 (const dav_resource *resource,
4718 const char *ns_uri, const char *name,
4719 const dav_hooks_liveprop **hooks),
4720 (resource, ns_uri, name, hooks), 0)
4722 APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops,
4723 (request_rec *r, const dav_resource *resource,
4724 dav_prop_insert what, ap_text_header *phdr),
4725 (r, resource, what, phdr))