1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000 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.
84 #include "http_config.h"
85 #include "http_core.h"
87 #include "http_main.h"
88 #include "http_protocol.h"
89 #include "http_request.h"
90 #include "util_script.h"
91 #include "apr_strings.h"
96 /* ### what is the best way to set this? */
97 #define DAV_DEFAULT_PROVIDER "filesystem"
100 DAV_ENABLED_UNSET = 0,
105 /* per-dir configuration */
107 const char *provider_name;
108 const dav_provider *provider;
111 int allow_depthinfinity;
113 apr_table_t *d_params; /* per-directory DAV config parameters */
117 /* per-server configuration */
123 #define DAV_INHERIT_VALUE(parent, child, field) \
124 ((child)->field ? (child)->field : (parent)->field)
127 /* forward-declare for use in configuration lookup */
128 extern module DAV_DECLARE_DATA dav_module;
130 static void dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
133 /* DBG0("dav_init_handler"); */
135 ap_add_version_component(p, "DAV/2");
138 static void *dav_create_server_config(apr_pool_t *p, server_rec *s)
140 dav_server_conf *newconf;
142 newconf = (dav_server_conf *) apr_pcalloc(p, sizeof(*newconf));
144 /* ### this isn't used at the moment... */
149 static void *dav_merge_server_config(apr_pool_t *p, void *base, void *overrides)
152 dav_server_conf *child = overrides;
154 dav_server_conf *newconf;
156 newconf = (dav_server_conf *) apr_pcalloc(p, sizeof(*newconf));
158 /* ### nothing to merge right now... */
163 static void *dav_create_dir_config(apr_pool_t *p, char *dir)
165 /* NOTE: dir==NULL creates the default per-dir config */
169 conf = (dav_dir_conf *) apr_pcalloc(p, sizeof(*conf));
171 /* clean up the directory to remove any trailing slash */
176 d = apr_pstrdup(p, dir);
178 if (l > 1 && d[l - 1] == '/')
183 conf->d_params = apr_make_table(p, 1);
188 static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
190 dav_dir_conf *parent = base;
191 dav_dir_conf *child = overrides;
192 dav_dir_conf *newconf = (dav_dir_conf *) apr_pcalloc(p, sizeof(*newconf));
194 /* DBG3("dav_merge_dir_config: new=%08lx base=%08lx overrides=%08lx",
195 (long)newconf, (long)base, (long)overrides); */
197 newconf->provider_name = DAV_INHERIT_VALUE(parent, child, provider_name);
198 newconf->provider = DAV_INHERIT_VALUE(parent, child, provider);
199 if (parent->provider_name != NULL) {
200 if (child->provider_name == NULL) {
201 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, NULL,
202 "\"DAV Off\" cannot be used to turn off a subtree "
203 "of a DAV-enabled location.");
205 else if (strcasecmp(child->provider_name,
206 parent->provider_name) != 0) {
207 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, NULL,
208 "A subtree cannot specify a different DAV provider "
213 newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
214 newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
215 newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
216 allow_depthinfinity);
218 newconf->d_params = apr_copy_table(p, parent->d_params);
219 apr_overlap_tables(newconf->d_params, child->d_params,
220 APR_OVERLAP_TABLES_SET);
225 apr_table_t *dav_get_dir_params(const request_rec *r)
229 conf = ap_get_module_config(r->per_dir_config, &dav_module);
230 return conf->d_params;
233 static const dav_provider * dav_get_provider(request_rec *r)
237 conf = ap_get_module_config(r->per_dir_config, &dav_module);
238 /* assert: conf->provider_name != NULL
239 (otherwise, DAV is disabled, and we wouldn't be here) */
241 /* assert: conf->provider != NULL
242 (checked when conf->provider_name is set) */
243 return conf->provider;
246 const dav_hooks_locks *dav_get_lock_hooks(request_rec *r)
248 return dav_get_provider(r)->locks;
251 const dav_hooks_propdb *dav_get_propdb_hooks(request_rec *r)
253 return dav_get_provider(r)->propdb;
256 const dav_hooks_vsn *dav_get_vsn_hooks(request_rec *r)
258 return dav_get_provider(r)->vsn;
261 const dav_hooks_binding *dav_get_binding_hooks(request_rec *r)
263 return dav_get_provider(r)->binding;
267 * Command handler for the DAV directive, which is TAKE1.
269 static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
271 dav_dir_conf *conf = (dav_dir_conf *) config;
273 if (strcasecmp(arg1, "on") == 0) {
274 conf->provider_name = DAV_DEFAULT_PROVIDER;
276 else if (strcasecmp(arg1, "off") == 0) {
277 conf->provider_name = NULL;
278 conf->provider = NULL;
281 conf->provider_name = apr_pstrdup(cmd->pool, arg1);
284 if (conf->provider_name != NULL) {
285 /* lookup and cache the actual provider now */
286 conf->provider = dav_lookup_provider(conf->provider_name);
288 if (conf->provider == NULL) {
289 /* by the time they use it, the provider should be loaded and
290 registered with us. */
291 return apr_psprintf(cmd->pool,
292 "Unknown DAV provider: %s",
293 conf->provider_name);
301 * Command handler for the DAVDepthInfinity directive, which is FLAG.
303 static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
306 dav_dir_conf *conf = (dav_dir_conf *) config;
309 conf->allow_depthinfinity = DAV_ENABLED_ON;
311 conf->allow_depthinfinity = DAV_ENABLED_OFF;
316 * Command handler for DAVMinTimeout directive, which is TAKE1
318 static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config,
321 dav_dir_conf *conf = (dav_dir_conf *) config;
323 conf->locktimeout = atoi(arg1);
324 if (conf->locktimeout < 0)
325 return "DAVMinTimeout requires a non-negative integer.";
331 * Command handler for DAVParam directive, which is TAKE2
333 static const char *dav_cmd_davparam(cmd_parms *cmd, void *config,
334 const char *arg1, const char *arg2)
336 dav_dir_conf *conf = (dav_dir_conf *) config;
338 apr_table_set(conf->d_params, arg1, arg2);
344 ** dav_error_response()
346 ** Send a nice response back to the user. In most cases, Apache doesn't
347 ** allow us to provide details in the body about what happened. This
348 ** function allows us to completely specify the response body.
350 static int dav_error_response(request_rec *r, int status, const char *body)
353 r->content_type = "text/html";
355 /* since we're returning DONE, ensure the request body is consumed. */
356 (void) ap_discard_request_body(r);
358 /* begin the response now... */
359 ap_send_http_header(r);
371 ap_rputs(ap_psignature("\n<P><HR>\n", r), r);
372 ap_rputs(DAV_RESPONSE_BODY_4, r);
374 /* the response has been sent. */
376 * ### Use of DONE obviates logging..!
382 ** Apache's URI escaping does not replace '&' since that is a valid character
383 ** in a URI (to form a query section). We must explicitly handle it so that
384 ** we can embed the URI into an XML document.
386 static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri)
388 const char *e_uri = ap_escape_uri(p, uri);
390 /* check the easy case... */
391 if (ap_strchr_c(e_uri, '&') == NULL)
394 /* more work needed... sigh. */
397 ** Note: this is a teeny bit of overkill since we know there are no
398 ** '<' or '>' characters, but who cares.
400 return ap_xml_quote_string(p, e_uri, 0);
403 static void dav_send_multistatus(request_rec *r, int status,
405 apr_array_header_t *namespaces)
407 /* Set the correct status and Content-Type */
409 r->content_type = DAV_XML_CONTENT_TYPE;
411 /* Send all of the headers now */
412 ap_send_http_header(r);
414 /* Send the actual multistatus response now... */
415 ap_rputs(DAV_XML_HEADER DEBUG_CR
416 "<D:multistatus xmlns:D=\"DAV:\"", r);
418 if (namespaces != NULL) {
421 for (i = namespaces->nelts; i--; ) {
422 ap_rprintf(r, " xmlns:ns%d=\"%s\"", i,
423 AP_XML_GET_URI_ITEM(namespaces, i));
427 /* ap_rputc('>', r); */
428 ap_rputs(">" DEBUG_CR, r);
430 for (; first != NULL; first = first->next) {
433 if (first->propresult.xmlns == NULL) {
434 ap_rputs("<D:response>", r);
437 ap_rputs("<D:response", r);
438 for (t = first->propresult.xmlns; t; t = t->next) {
439 ap_rputs(t->text, r);
444 ap_rputs(DEBUG_CR "<D:href>", r);
445 ap_rputs(dav_xml_escape_uri(r->pool, first->href), r);
446 ap_rputs("</D:href>" DEBUG_CR, r);
448 if (first->propresult.propstats == NULL) {
449 /* use the Status-Line text from Apache. Note, this will
450 * default to 500 Internal Server Error if first->status
451 * is not a known (or valid) status code. */
453 "<D:status>HTTP/1.1 %s</D:status>" DEBUG_CR,
454 ap_get_status_line(first->status));
457 /* assume this includes <propstat> and is quoted properly */
458 for (t = first->propresult.propstats; t; t = t->next) {
459 ap_rputs(t->text, r);
463 if (first->desc != NULL) {
465 ** We supply the description, so we know it doesn't have to
466 ** have any escaping/encoding applied to it.
468 ap_rputs("<D:responsedescription>", r);
469 ap_rputs(first->desc, r);
470 ap_rputs("</D:responsedescription>" DEBUG_CR, r);
473 ap_rputs("</D:response>" DEBUG_CR, r);
476 ap_rputs("</D:multistatus>" DEBUG_CR, r);
482 ** Write error information to the log.
484 static void dav_log_err(request_rec *r, dav_error *err, int level)
489 /* ### should have a directive to log the first or all */
490 for (errscan = err; errscan != NULL; errscan = errscan->prev) {
491 if (errscan->desc == NULL)
493 if (errscan->save_errno != 0) {
494 errno = errscan->save_errno;
495 ap_log_rerror(APLOG_MARK, level, errno, r, "%s [%d, #%d]",
496 errscan->desc, errscan->status, errscan->error_id);
499 ap_log_rerror(APLOG_MARK, level | APLOG_NOERRNO, 0, r,
501 errscan->desc, errscan->status, errscan->error_id);
509 ** Handle the standard error processing. <err> must be non-NULL.
511 ** <response> is set by the following:
512 ** - dav_validate_request()
514 ** - repos_hooks->remove_resource
515 ** - repos_hooks->move_resource
516 ** - repos_hooks->copy_resource
518 static int dav_handle_err(request_rec *r, dav_error *err,
519 dav_response *response)
522 dav_log_err(r, err, APLOG_ERR);
524 if (response == NULL) {
525 /* our error messages are safe; tell Apache this */
526 apr_table_setn(r->notes, "verbose-error-to", "*");
530 /* since we're returning DONE, ensure the request body is consumed. */
531 (void) ap_discard_request_body(r);
533 /* send the multistatus and tell Apache the request/response is DONE. */
534 dav_send_multistatus(r, err->status, response, NULL);
538 /* handy function for return values of methods that (may) create things */
539 static int dav_created(request_rec *r, const char *locn, const char *what,
548 /* did the target resource already exist? */
550 /* Apache will supply a default message */
551 return HTTP_NO_CONTENT;
554 /* Per HTTP/1.1, S10.2.2: add a Location header to contain the
555 * URI that was created. */
557 /* Convert locn to an absolute URI, and return in Location header */
558 apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, locn, r));
560 /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */
562 /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
563 * we must manufacture the entire response. */
564 body = apr_psprintf(r->pool, "%s %s has been created.",
565 what, ap_escape_html(r->pool, locn));
566 return dav_error_response(r, HTTP_CREATED, body);
569 /* ### move to dav_util? */
570 int dav_get_depth(request_rec *r, int def_depth)
572 const char *depth = apr_table_get(r->headers_in, "Depth");
577 if (strcasecmp(depth, "infinity") == 0) {
580 else if (strcmp(depth, "0") == 0) {
583 else if (strcmp(depth, "1") == 0) {
587 /* The caller will return an HTTP_BAD_REQUEST. This will augment the
588 * default message that Apache provides. */
589 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
590 "An invalid Depth header was specified.");
594 static int dav_get_overwrite(request_rec *r)
596 const char *overwrite = apr_table_get(r->headers_in, "Overwrite");
598 if (overwrite == NULL) {
599 return 1; /* default is "T" */
602 if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') {
605 if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') {
609 /* The caller will return an HTTP_BAD_REQUEST. This will augment the
610 * default message that Apache provides. */
611 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
612 "An invalid Overwrite header was specified.");
616 /* resolve a request URI to a resource descriptor.
617 * If target_allowed != 0, then allow the request target to be overridden
618 * by either a DAV:version or DAV:label-name element (passed as
619 * the target argument), or any Target-Selector header in the request.
621 static int dav_get_resource(request_rec *r, int target_allowed,
622 ap_xml_elem *target, dav_resource **res_p)
626 const char *target_selector = NULL;
630 /* go look for the resource if it isn't already present */
631 (void) apr_get_userdata(&data, DAV_KEY_RESOURCE, r->pool);
637 /* if the request target can be overridden, get any target selector */
638 if (target_allowed) {
639 if ((result = dav_get_target_selector(r, target,
645 conf = ap_get_module_config(r->per_dir_config, &dav_module);
646 /* assert: conf->provider != NULL */
648 /* resolve the resource */
649 *res_p = (*conf->provider->repos->get_resource)(r, conf->dir,
650 target_selector, is_label);
651 if (*res_p == NULL) {
652 /* Apache will supply a default error for this. */
653 return HTTP_NOT_FOUND;
656 (void) apr_set_userdata(*res_p, DAV_KEY_RESOURCE, apr_null_cleanup,
659 /* ### hmm. this doesn't feel like the right place or thing to do */
660 /* if there were any input headers requiring a Vary header in the response,
662 dav_add_vary_header(r, r, *res_p);
667 static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
669 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
676 /* open the thing lazily */
677 return (*hooks->open_lockdb)(r, ro, 0, lockdb);
680 static int dav_parse_range(request_rec *r,
681 off_t *range_start, off_t *range_end)
688 range_c = apr_table_get(r->headers_in, "content-range");
692 range = apr_pstrdup(r->pool, range_c);
693 if (strncasecmp(range, "bytes ", 6) != 0
694 || (dash = ap_strchr(range, '-')) == NULL
695 || (slash = ap_strchr(range, '/')) == NULL) {
696 /* malformed header. ignore it (per S14.16 of RFC2616) */
700 *dash = *slash = '\0';
701 *range_start = atol(range + 6);
702 *range_end = atol(dash + 1);
703 if (*range_end < *range_start
704 || (slash[1] != '*' && atol(slash + 1) <= *range_end)) {
705 /* invalid range. ignore it (per S14.16 of RFC2616) */
709 /* we now have a valid range */
713 /* handle the GET method */
714 static int dav_method_get(request_rec *r)
716 dav_resource *resource;
719 /* This method should only be called when the resource is not
720 * visible to Apache. We will fetch the resource from the repository,
721 * then create a subrequest for Apache to handle.
723 result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
726 if (!resource->exists) {
727 /* Apache will supply a default error for this. */
728 return HTTP_NOT_FOUND;
731 /* Check resource type */
732 if (resource->type != DAV_RESOURCE_TYPE_REGULAR &&
733 resource->type != DAV_RESOURCE_TYPE_VERSION &&
734 resource->type != DAV_RESOURCE_TYPE_WORKING)
736 return dav_error_response(r, HTTP_CONFLICT,
737 "Cannot GET this type of resource.");
740 /* Cannot handle GET of a collection from a repository */
741 if (resource->collection) {
742 return dav_error_response(r, HTTP_CONFLICT,
743 "No default response to GET for a "
748 ** We can use two different approaches for a GET.
750 ** 1) get_pathname will return a pathname to a file which should be
751 ** sent to the client. If the repository provides this, then we
754 ** This is the best alternative since it allows us to do a sub-
755 ** request on the file, which gives the Apache framework a chance
756 ** to deal with negotiation, MIME types, or whatever.
758 ** 2) open_stream and read_stream.
760 if (resource->hooks->get_pathname != NULL) {
761 const char *pathname;
763 request_rec *new_req;
765 /* Ask repository for copy of file */
766 pathname = (*resource->hooks->get_pathname)(resource, &fhandle);
767 if (pathname == NULL) {
768 return HTTP_NOT_FOUND;
771 /* Convert to canonical filename, so Apache detects component
772 * separators (on Windows, it only looks for '/', not '\')
774 pathname = ap_os_case_canonical_filename(r->pool, pathname);
776 /* Create a sub-request with the new filename */
777 new_req = ap_sub_req_lookup_file(pathname, r, NULL);
778 if (new_req == NULL) {
779 (*resource->hooks->free_file)(fhandle);
780 return HTTP_INTERNAL_SERVER_ERROR;
783 /* This may be a HEAD request */
784 new_req->header_only = r->header_only;
786 /* ### this enables header generation */
787 new_req->assbackwards = 0;
789 /* Run the sub-request */
790 result = ap_run_sub_req(new_req);
791 ap_destroy_sub_req(new_req);
794 (*resource->hooks->free_file)(fhandle);
799 dav_stream_mode mode;
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 /* use plain READ mode unless we see a Content-Range */
816 mode = DAV_MODE_READ;
818 /* process the Content-Range header (if present) */
819 has_range = dav_parse_range(r, &range_start, &range_end);
821 /* use a read mode which is seekable */
822 mode = DAV_MODE_READ_SEEKABLE;
824 /* prep the output */
825 r->status = HTTP_PARTIAL_CONTENT;
826 apr_table_setn(r->headers_out,
828 apr_psprintf(r->pool, "bytes %ld-%ld/*",
829 range_start, range_end));
830 ap_set_content_length(r, range_end - range_start + 1);
833 if (r->header_only) {
834 ap_send_http_header(r);
838 if ((err = (*resource->hooks->open_stream)(resource, mode,
840 /* ### assuming FORBIDDEN is probably not quite right... */
841 err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
842 apr_psprintf(r->pool,
843 "Unable to GET contents for %s.",
844 ap_escape_html(r->pool, r->uri)),
846 return dav_handle_err(r, err, NULL);
850 && (err = (*resource->hooks->seek_stream)(stream,
851 range_start)) != NULL) {
852 err = dav_push_error(r->pool, err->status, 0,
853 "Could not seek to beginning of the "
854 "specified Content-Range.", err);
855 return dav_handle_err(r, err, NULL);
858 /* all set. send the headers now. */
859 ap_send_http_header(r);
861 buffer = apr_palloc(r->pool, DAV_READ_BLOCKSIZE);
866 amt = DAV_READ_BLOCKSIZE;
867 else if ((range_end - range_start + 1) > DAV_READ_BLOCKSIZE)
868 amt = DAV_READ_BLOCKSIZE;
870 /* note: range_end - range_start is an ssize_t */
871 amt = (size_t)(range_end - range_start + 1);
874 if ((err = (*resource->hooks->read_stream)(stream, buffer,
879 /* no more content */
882 if (ap_rwrite(buffer, amt, r) < 0) {
883 /* ### what to do with this error? */
889 if (range_start > range_end)
895 return dav_handle_err(r, err, NULL);
898 ** ### range_start should equal range_end+1. if it doesn't, then
899 ** ### we did not send enough data to the client. the client will
900 ** ### hang (and timeout) waiting for the data.
902 ** ### what to do? abort the connection?
910 /* validate resource on POST, then pass it off to the default handler */
911 static int dav_method_post(request_rec *r)
913 dav_resource *resource;
917 /* Ask repository module to resolve the resource */
918 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
923 /* Note: depth == 0. Implies no need for a multistatus response. */
924 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
925 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
926 /* ### add a higher-level description? */
927 return dav_handle_err(r, err, NULL);
933 /* handle the PUT method */
934 static int dav_method_put(request_rec *r)
936 dav_resource *resource;
938 dav_auto_version_info av_info;
939 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
944 dav_stream_mode mode;
946 dav_response *multi_response;
951 if ((result = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != OK) {
955 /* Ask repository module to resolve the resource */
956 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
961 /* If not a file or collection resource, PUT not allowed */
962 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
963 body = apr_psprintf(r->pool,
964 "Cannot create resource %s with PUT.",
965 ap_escape_html(r->pool, r->uri));
966 return dav_error_response(r, HTTP_CONFLICT, body);
969 /* Cannot PUT a collection */
970 if (resource->collection) {
971 return dav_error_response(r, HTTP_CONFLICT,
972 "Cannot PUT to a collection.");
976 resource_state = dav_get_resource_state(r, resource);
979 ** Note: depth == 0 normally requires no multistatus response. However,
980 ** if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
981 ** other than the Request-URI, thereby requiring a multistatus.
983 ** If the resource does not exist (DAV_RESOURCE_NULL), then we must
984 ** check the resource *and* its parent. If the resource exists or is
985 ** a locknull resource, then we check only the resource.
987 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
988 resource_state == DAV_RESOURCE_NULL ?
989 DAV_VALIDATE_PARENT :
990 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
991 /* ### add a higher-level description? */
992 return dav_handle_err(r, err, multi_response);
995 /* make sure the resource can be modified (if versioning repository) */
996 if ((err = dav_ensure_resource_writable(r, resource,
997 0 /* not parent_only */,
998 &av_info)) != NULL) {
999 /* ### add a higher-level description? */
1000 return dav_handle_err(r, err, NULL);
1003 /* truncate and rewrite the file unless we see a Content-Range */
1004 mode = DAV_MODE_WRITE_TRUNC;
1006 has_range = dav_parse_range(r, &range_start, &range_end);
1008 mode = DAV_MODE_WRITE_SEEKABLE;
1011 /* Create the new file in the repository */
1012 if ((err = (*resource->hooks->open_stream)(resource, mode,
1013 &stream)) != NULL) {
1014 /* ### assuming FORBIDDEN is probably not quite right... */
1015 err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
1016 apr_psprintf(r->pool,
1017 "Unable to PUT new contents for %s.",
1018 ap_escape_html(r->pool, r->uri)),
1022 if (err == NULL && has_range) {
1023 /* a range was provided. seek to the start */
1024 err = (*resource->hooks->seek_stream)(stream, range_start);
1028 if (ap_should_client_block(r)) {
1029 char *buffer = apr_palloc(r->pool, DAV_READ_BLOCKSIZE);
1033 ** Once we start reading the request, then we must read the
1034 ** whole darn thing. ap_discard_request_body() won't do anything
1035 ** for a partially-read request.
1038 while ((len = ap_get_client_block(r, buffer,
1039 DAV_READ_BLOCKSIZE)) > 0) {
1041 /* write whatever we read, until we see an error */
1042 err = (*resource->hooks->write_stream)(stream,
1048 ** ### what happens if we read more/less than the amount
1049 ** ### specified in the Content-Range? eek...
1054 ** Error reading request body. This has precedence over
1057 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1058 "An error occurred while reading the "
1063 err2 = (*resource->hooks->close_stream)(stream,
1064 err == NULL /* commit */);
1065 if (err2 != NULL && err == NULL) {
1066 /* no error during the write, but we hit one at close. use it. */
1072 ** Ensure that we think the resource exists now.
1073 ** ### eek. if an error occurred during the write and we did not commit,
1074 ** ### then the resource might NOT exist (e.g. dav_fs_repos.c)
1077 resource->exists = 1;
1080 /* restore modifiability of resources back to what they were */
1081 err2 = dav_revert_resource_writability(r, resource, err != NULL /* undo if error */,
1084 /* check for errors now */
1086 return dav_handle_err(r, err, NULL);
1089 /* just log a warning */
1090 err2 = dav_push_error(r->pool, err->status, 0,
1091 "The PUT was successful, but there "
1092 "was a problem reverting the writability of "
1093 "the resource or its parent collection.",
1095 dav_log_err(r, err2, APLOG_WARNING);
1098 /* ### place the Content-Type and Content-Language into the propdb */
1100 if (locks_hooks != NULL) {
1103 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1104 /* The file creation was successful, but the locking failed. */
1105 err = dav_push_error(r->pool, err->status, 0,
1106 "The file was PUT successfully, but there "
1107 "was a problem opening the lock database "
1108 "which prevents inheriting locks from the "
1109 "parent resources.",
1111 return dav_handle_err(r, err, NULL);
1114 /* notify lock system that we have created/replaced a resource */
1115 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
1117 (*locks_hooks->close_lockdb)(lockdb);
1120 /* The file creation was successful, but the locking failed. */
1121 err = dav_push_error(r->pool, err->status, 0,
1122 "The file was PUT successfully, but there "
1123 "was a problem updating its lock "
1126 return dav_handle_err(r, err, NULL);
1130 /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
1132 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
1133 return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
1136 /* ### move this to dav_util? */
1137 DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
1138 int status, dav_get_props_result *propstats)
1140 dav_walker_ctx *ctx = wres->walk_ctx;
1143 /* just drop some data into an dav_response */
1144 resp = apr_pcalloc(ctx->w.pool, sizeof(*resp));
1145 resp->href = apr_pstrdup(ctx->w.pool, wres->resource->uri);
1146 resp->status = status;
1148 resp->propresult = *propstats;
1151 resp->next = wres->response;
1152 wres->response = resp;
1155 /* handle the DELETE method */
1156 static int dav_method_delete(request_rec *r)
1158 dav_resource *resource;
1159 dav_auto_version_info av_info;
1162 dav_response *multi_response;
1166 /* We don't use the request body right now, so torch it. */
1167 if ((result = ap_discard_request_body(r)) != OK) {
1171 /* Ask repository module to resolve the resource */
1172 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
1175 if (!resource->exists) {
1176 /* Apache will supply a default error for this. */
1177 return HTTP_NOT_FOUND;
1180 /* 2518 says that depth must be infinity only for collections.
1181 * For non-collections, depth is ignored, unless it is an illegal value (1).
1183 depth = dav_get_depth(r, DAV_INFINITY);
1185 if (resource->collection && depth != DAV_INFINITY) {
1186 /* This supplies additional information for the default message. */
1187 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1188 "Depth must be \"infinity\" for DELETE of a collection.");
1189 return HTTP_BAD_REQUEST;
1191 if (!resource->collection && depth == 1) {
1192 /* This supplies additional information for the default message. */
1193 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1194 "Depth of \"1\" is not allowed for DELETE.");
1195 return HTTP_BAD_REQUEST;
1199 ** If any resources fail the lock/If: conditions, then we must fail
1200 ** the delete. Each of the failing resources will be listed within
1201 ** a DAV:multistatus body, wrapped into a 424 response.
1203 ** Note that a failure on the resource itself does not generate a
1204 ** multistatus response -- only internal members/collections.
1206 if ((err = dav_validate_request(r, resource, depth, NULL,
1209 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
1210 err = dav_push_error(r->pool, err->status, 0,
1211 apr_psprintf(r->pool,
1212 "Could not DELETE %s due to a failed "
1213 "precondition (e.g. locks).",
1214 ap_escape_html(r->pool, r->uri)),
1216 return dav_handle_err(r, err, multi_response);
1219 /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
1220 * locked by the token(s) in the if_header.
1222 if ((result = dav_unlock(r, resource, NULL)) != OK) {
1226 /* if versioned resource, make sure parent is checked out */
1227 if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
1228 &av_info)) != NULL) {
1229 /* ### add a higher-level description? */
1230 return dav_handle_err(r, err, NULL);
1233 /* try to remove the resource */
1234 err = (*resource->hooks->remove_resource)(resource, &multi_response);
1236 /* restore writability of parent back to what it was */
1237 err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
1240 /* check for errors now */
1242 err = dav_push_error(r->pool, err->status, 0,
1243 apr_psprintf(r->pool,
1244 "Could not DELETE %s.",
1245 ap_escape_html(r->pool, r->uri)),
1247 return dav_handle_err(r, err, multi_response);
1250 /* just log a warning */
1251 err = dav_push_error(r->pool, err2->status, 0,
1252 "The DELETE was successful, but there "
1253 "was a problem reverting the writability of "
1254 "its parent collection.",
1256 dav_log_err(r, err, APLOG_WARNING);
1259 /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
1261 /* Apache will supply a default error for this. */
1262 return HTTP_NO_CONTENT;
1265 /* handle the OPTIONS method */
1266 static int dav_method_options(request_rec *r)
1268 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1269 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1270 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
1271 dav_resource *resource;
1272 const char *options;
1273 const char *dav_level;
1274 const char *vsn_level;
1276 apr_array_header_t *uri_ary;
1279 /* per HTTP/1.1 S9.2, we can discard this body */
1280 if ((result = ap_discard_request_body(r)) != OK) {
1285 ap_set_content_length(r, 0);
1287 /* resolve the resource */
1288 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
1292 /* determine which providers are available */
1296 if (locks_hooks != NULL) {
1300 if (vsn_hooks != NULL
1301 && (vsn_level = (*vsn_hooks->get_vsn_header)()) != NULL) {
1302 dav_level = apr_pstrcat(r->pool, dav_level, ",", vsn_level, NULL);
1305 /* gather property set URIs from all the liveprop providers */
1306 uri_ary = apr_make_array(r->pool, 5, sizeof(const char *));
1307 ap_run_gather_propsets(uri_ary);
1308 uris = apr_array_pstrcat(r->pool, uri_ary, ',');
1310 dav_level = apr_pstrcat(r->pool, dav_level, ",", uris, NULL);
1313 /* this tells MSFT products to skip looking for FrontPage extensions */
1314 apr_table_setn(r->headers_out, "MS-Author-Via", "DAV");
1317 ** Three cases: resource is null (3), is lock-null (7.4), or exists.
1319 ** All cases support OPTIONS and LOCK.
1320 ** (Lock-) null resources also support MKCOL and PUT.
1321 ** Lock-null support PROPFIND and UNLOCK.
1322 ** Existing resources support lots of stuff.
1325 /* ### take into account resource type */
1326 switch (dav_get_resource_state(r, resource))
1328 case DAV_RESOURCE_EXISTS:
1329 /* resource exists */
1330 if (resource->collection) {
1331 options = apr_pstrcat(r->pool,
1333 "GET, HEAD, POST, DELETE, TRACE, "
1334 "PROPFIND, PROPPATCH, COPY, MOVE",
1335 locks_hooks != NULL ? ", LOCK, UNLOCK" : "",
1339 /* files also support PUT */
1340 options = apr_pstrcat(r->pool,
1342 "GET, HEAD, POST, DELETE, TRACE, "
1343 "PROPFIND, PROPPATCH, COPY, MOVE, PUT",
1344 locks_hooks != NULL ? ", LOCK, UNLOCK" : "",
1349 case DAV_RESOURCE_LOCK_NULL:
1350 /* resource is lock-null. */
1351 options = apr_pstrcat(r->pool, "OPTIONS, MKCOL, PUT, PROPFIND",
1352 locks_hooks != NULL ? ", LOCK, UNLOCK" : "",
1356 case DAV_RESOURCE_NULL:
1357 /* resource is null. */
1358 options = apr_pstrcat(r->pool, "OPTIONS, MKCOL, PUT",
1359 locks_hooks != NULL ? ", LOCK" : "",
1364 /* ### internal error! */
1365 options = "OPTIONS";
1369 /* If there is a versioning provider, add versioning options */
1370 if (vsn_hooks != NULL) {
1371 const char *vsn_options = NULL;
1373 if (!resource->exists) {
1374 int vsn_control = (*vsn_hooks->versionable)(resource);
1375 int mkworkspace = vsn_hooks->can_be_workspace != NULL
1376 && (*vsn_hooks->can_be_workspace)(resource);
1378 if (vsn_control && mkworkspace) {
1379 vsn_options = ", VERSION-CONTROL, MKWORKSPACE";
1381 else if (vsn_control)
1382 vsn_options = ", VERSION-CONTROL";
1383 else if (mkworkspace) {
1384 vsn_options = ", MKWORKSPACE";
1387 else if (!resource->versioned) {
1388 if ((*vsn_hooks->versionable)(resource)) {
1389 vsn_options = ", VERSION-CONTROL";
1392 else if (resource->working)
1393 vsn_options = ", CHECKIN, UNCHECKOUT";
1394 else if (vsn_hooks->add_label != NULL)
1395 vsn_options = ", CHECKOUT, LABEL";
1397 vsn_options = ", CHECKOUT";
1399 if (vsn_options != NULL)
1400 options = apr_pstrcat(r->pool, options, vsn_options, NULL);
1403 /* If there is a bindings provider, see if resource is bindable */
1404 if (binding_hooks != NULL) {
1405 dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
1406 if ((*binding_hooks->is_bindable)(resource))
1407 options = apr_pstrcat(r->pool, options, ", BIND", NULL);
1410 apr_table_setn(r->headers_out, "Allow", options);
1411 apr_table_setn(r->headers_out, "DAV", dav_level);
1413 /* ### this will send a Content-Type. the default OPTIONS does not. */
1414 ap_send_http_header(r);
1416 /* ### the default (ap_send_http_options) returns OK, but I believe
1417 * ### that is because it is the default handler and nothing else
1418 * ### will run after the thing. */
1420 /* we've sent everything necessary to the client. */
1424 static void dav_cache_badprops(dav_walker_ctx *ctx)
1426 const ap_xml_elem *elem;
1427 ap_text_header hdr = { 0 };
1429 /* just return if we built the thing already */
1430 if (ctx->propstat_404 != NULL) {
1434 ap_text_append(ctx->w.pool, &hdr,
1435 "<D:propstat>" DEBUG_CR
1436 "<D:prop>" DEBUG_CR);
1438 elem = dav_find_child(ctx->doc->root, "prop");
1439 for (elem = elem->first_child; elem; elem = elem->next) {
1440 ap_text_append(ctx->w.pool, &hdr,
1441 ap_xml_empty_elem(ctx->w.pool, elem));
1444 ap_text_append(ctx->w.pool, &hdr,
1445 "</D:prop>" DEBUG_CR
1446 "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
1447 "</D:propstat>" DEBUG_CR);
1449 ctx->propstat_404 = hdr.first;
1452 static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
1454 dav_walker_ctx *ctx = wres->walk_ctx;
1457 dav_get_props_result propstats = { 0 };
1460 ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
1461 ** dav_get_allprops() does not need to do namespace translation,
1464 ** Note: we cast to lose the "const". The propdb won't try to change
1465 ** the resource, however, since we are opening readonly.
1467 err = dav_open_propdb(ctx->r, ctx->w.lockdb,
1468 (dav_resource *)wres->resource, 1,
1469 ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
1471 /* ### do something with err! */
1473 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1474 dav_get_props_result badprops = { 0 };
1476 /* some props were expected on this collection/resource */
1477 dav_cache_badprops(ctx);
1478 badprops.propstats = ctx->propstat_404;
1479 dav_add_response(wres, 0, &badprops);
1482 /* no props on this collection/resource */
1483 dav_add_response(wres, HTTP_OK, NULL);
1487 /* ### what to do about closing the propdb on server failure? */
1489 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1490 propstats = dav_get_props(propdb, ctx->doc);
1493 propstats = dav_get_allprops(propdb,
1494 ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP);
1496 dav_close_propdb(propdb);
1498 dav_add_response(wres, 0, &propstats);
1503 /* handle the PROPFIND method */
1504 static int dav_method_propfind(request_rec *r)
1506 dav_resource *resource;
1511 const ap_xml_elem *child;
1512 dav_walker_ctx ctx = { { 0 } };
1513 dav_response *multi_status;
1515 /* Ask repository module to resolve the resource */
1516 result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
1520 if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
1521 /* Apache will supply a default error for this. */
1522 return HTTP_NOT_FOUND;
1525 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
1526 /* dav_get_depth() supplies additional information for the
1527 * default message. */
1528 return HTTP_BAD_REQUEST;
1531 if (depth == DAV_INFINITY) {
1533 conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
1535 /* default is to DISALLOW these requests */
1536 if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
1537 return dav_error_response(r, HTTP_FORBIDDEN,
1538 apr_psprintf(r->pool,
1539 "PROPFIND requests with a "
1540 "Depth of \"infinity\" are "
1541 "not allowed for %s.",
1542 ap_escape_html(r->pool,
1547 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1550 /* note: doc == NULL if no request body */
1552 if (doc && !dav_validate_root(doc, "propfind")) {
1553 /* This supplies additional information for the default message. */
1554 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1555 "The \"propfind\" element was not found.");
1556 return HTTP_BAD_REQUEST;
1559 /* ### validate that only one of these three elements is present */
1562 || (child = dav_find_child(doc->root, "allprop")) != NULL) {
1563 /* note: no request body implies allprop */
1564 ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
1566 else if ((child = dav_find_child(doc->root, "propname")) != NULL) {
1567 ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
1569 else if ((child = dav_find_child(doc->root, "prop")) != NULL) {
1570 ctx.propfind_type = DAV_PROPFIND_IS_PROP;
1573 /* "propfind" element must have one of the above three children */
1575 /* This supplies additional information for the default message. */
1576 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1577 "The \"propfind\" element does not contain one of "
1578 "the required child elements (the specific command).");
1579 return HTTP_BAD_REQUEST;
1582 ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
1583 ctx.w.func = dav_propfind_walker;
1584 ctx.w.walk_ctx = &ctx;
1585 ctx.w.pool = r->pool;
1586 ctx.w.root = resource;
1591 /* ### should open read-only */
1592 if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
1593 err = dav_push_error(r->pool, err->status, 0,
1594 "The lock database could not be opened, "
1595 "preventing access to the various lock "
1596 "properties for the PROPFIND.",
1598 return dav_handle_err(r, err, NULL);
1600 if (ctx.w.lockdb != NULL) {
1601 /* if we have a lock database, then we can walk locknull resources */
1602 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1605 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
1607 if (ctx.w.lockdb != NULL) {
1608 (*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb);
1612 /* ### add a higher-level description? */
1613 return dav_handle_err(r, err, NULL);
1616 /* return a 207 (Multi-Status) response now. */
1618 /* if a 404 was generated for an HREF, then we need to spit out the
1619 * doc's namespaces for use by the 404. Note that <response> elements
1620 * will override these ns0, ns1, etc, but NOT within the <response>
1621 * scope for the badprops. */
1622 /* NOTE: propstat_404 != NULL implies doc != NULL */
1623 if (ctx.propstat_404 != NULL) {
1624 dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status,
1628 dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
1631 /* the response has been sent. */
1635 static ap_text * dav_failed_proppatch(apr_pool_t *p,
1636 apr_array_header_t *prop_ctx)
1638 ap_text_header hdr = { 0 };
1639 int i = prop_ctx->nelts;
1640 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
1641 dav_error *err424_set = NULL;
1642 dav_error *err424_delete = NULL;
1645 /* ### might be nice to sort by status code and description */
1647 for ( ; i-- > 0; ++ctx ) {
1648 ap_text_append(p, &hdr,
1649 "<D:propstat>" DEBUG_CR
1651 ap_text_append(p, &hdr, ap_xml_empty_elem(p, ctx->prop));
1652 ap_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
1654 if (ctx->err == NULL) {
1655 /* nothing was assigned here yet, so make it a 424 */
1657 if (ctx->operation == DAV_PROP_OP_SET) {
1658 if (err424_set == NULL)
1659 err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
1660 "Attempted DAV:set operation "
1661 "could not be completed due "
1662 "to other errors.");
1663 ctx->err = err424_set;
1665 else if (ctx->operation == DAV_PROP_OP_DELETE) {
1666 if (err424_delete == NULL)
1667 err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
1668 "Attempted DAV:remove "
1669 "operation could not be "
1670 "completed due to other "
1672 ctx->err = err424_delete;
1678 "HTTP/1.1 %d (status)"
1679 "</D:status>" DEBUG_CR,
1681 ap_text_append(p, &hdr, s);
1683 /* ### we should use compute_desc if necessary... */
1684 if (ctx->err->desc != NULL) {
1685 ap_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
1686 ap_text_append(p, &hdr, ctx->err->desc);
1687 ap_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
1690 ap_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
1696 static ap_text * dav_success_proppatch(apr_pool_t *p, apr_array_header_t *prop_ctx)
1698 ap_text_header hdr = { 0 };
1699 int i = prop_ctx->nelts;
1700 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
1703 ** ### we probably need to revise the way we assemble the response...
1704 ** ### this code assumes everything will return status==200.
1707 ap_text_append(p, &hdr,
1708 "<D:propstat>" DEBUG_CR
1709 "<D:prop>" DEBUG_CR);
1711 for ( ; i-- > 0; ++ctx ) {
1712 ap_text_append(p, &hdr, ap_xml_empty_elem(p, ctx->prop));
1715 ap_text_append(p, &hdr,
1716 "</D:prop>" DEBUG_CR
1717 "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
1718 "</D:propstat>" DEBUG_CR);
1723 static void dav_prop_log_errors(dav_prop_ctx *ctx)
1725 dav_log_err(ctx->r, ctx->err, APLOG_ERR);
1729 ** Call <func> for each context. This can stop when an error occurs, or
1730 ** simply iterate through the whole list.
1732 ** Returns 1 if an error occurs (and the iteration is aborted). Returns 0
1733 ** if all elements are processed.
1735 ** If <reverse> is true (non-zero), then the list is traversed in
1738 static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
1739 apr_array_header_t *ctx_list, int stop_on_error,
1742 int i = ctx_list->nelts;
1743 dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
1753 if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
1764 /* handle the PROPPATCH method */
1765 static int dav_method_proppatch(request_rec *r)
1768 dav_resource *resource;
1774 dav_response resp = { 0 };
1775 ap_text *propstat_text;
1776 apr_array_header_t *ctx_list;
1779 /* Ask repository module to resolve the resource */
1780 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
1783 if (!resource->exists) {
1784 /* Apache will supply a default error for this. */
1785 return HTTP_NOT_FOUND;
1788 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1791 /* note: doc == NULL if no request body */
1793 if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
1794 /* This supplies additional information for the default message. */
1795 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1796 "The request body does not contain "
1797 "a \"propertyupdate\" element.");
1798 return HTTP_BAD_REQUEST;
1801 /* Check If-Headers and existing locks */
1802 /* Note: depth == 0. Implies no need for a multistatus response. */
1803 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
1804 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
1805 /* ### add a higher-level description? */
1806 return dav_handle_err(r, err, NULL);
1809 if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
1810 &propdb)) != NULL) {
1811 err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1812 apr_psprintf(r->pool,
1813 "Could not open the property "
1815 ap_escape_html(r->pool, r->uri)),
1817 return dav_handle_err(r, err, NULL);
1819 /* ### what to do about closing the propdb on server failure? */
1821 /* ### validate "live" properties */
1823 /* set up an array to hold property operation contexts */
1824 ctx_list = apr_make_array(r->pool, 10, sizeof(dav_prop_ctx));
1826 /* do a first pass to ensure that all "remove" properties exist */
1827 for (child = doc->root->first_child; child; child = child->next) {
1829 ap_xml_elem *prop_group;
1830 ap_xml_elem *one_prop;
1832 /* Ignore children that are not set/remove */
1833 if (child->ns != AP_XML_NS_DAV_ID
1834 || (!(is_remove = strcmp(child->name, "remove") == 0)
1835 && strcmp(child->name, "set") != 0)) {
1839 /* make sure that a "prop" child exists for set/remove */
1840 if ((prop_group = dav_find_child(child, "prop")) == NULL) {
1841 dav_close_propdb(propdb);
1843 /* This supplies additional information for the default message. */
1844 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1845 "A \"prop\" element is missing inside "
1846 "the propertyupdate command.");
1847 return HTTP_BAD_REQUEST;
1850 for (one_prop = prop_group->first_child; one_prop;
1851 one_prop = one_prop->next) {
1853 ctx = (dav_prop_ctx *)apr_push_array(ctx_list);
1854 ctx->propdb = propdb;
1855 ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
1856 ctx->prop = one_prop;
1858 ctx->r = r; /* for later use by dav_prop_log_errors() */
1860 dav_prop_validate(ctx);
1862 if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
1868 /* ### should test that we found at least one set/remove */
1870 /* execute all of the operations */
1871 if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
1875 /* generate a failure/success response */
1877 (void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
1878 propstat_text = dav_failed_proppatch(r->pool, ctx_list);
1881 (void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
1882 propstat_text = dav_success_proppatch(r->pool, ctx_list);
1885 /* make sure this gets closed! */
1886 dav_close_propdb(propdb);
1888 /* log any errors that occurred */
1889 (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
1891 resp.href = resource->uri;
1893 /* ### should probably use something new to pass along this text... */
1894 resp.propresult.propstats = propstat_text;
1896 dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
1898 /* the response has been sent. */
1902 static int process_mkcol_body(request_rec *r)
1904 /* This is snarfed from ap_setup_client_block(). We could get pretty
1905 * close to this behavior by passing REQUEST_NO_BODY, but we need to
1906 * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
1907 * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
1909 const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
1910 const char *lenp = apr_table_get(r->headers_in, "Content-Length");
1912 /* make sure to set the Apache request fields properly. */
1913 r->read_body = REQUEST_NO_BODY;
1914 r->read_chunked = 0;
1918 if (strcasecmp(tenc, "chunked")) {
1919 /* Use this instead of Apache's default error string */
1920 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1921 "Unknown Transfer-Encoding %s", tenc);
1922 return HTTP_NOT_IMPLEMENTED;
1925 r->read_chunked = 1;
1928 const char *pos = lenp;
1930 while (apr_isdigit(*pos) || apr_isspace(*pos)) {
1934 /* This supplies additional information for the default message. */
1935 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
1936 "Invalid Content-Length %s", lenp);
1937 return HTTP_BAD_REQUEST;
1940 r->remaining = atol(lenp);
1943 if (r->read_chunked || r->remaining > 0) {
1944 /* ### log something? */
1946 /* Apache will supply a default error for this. */
1947 return HTTP_UNSUPPORTED_MEDIA_TYPE;
1951 ** Get rid of the body. this will call ap_setup_client_block(), but
1952 ** our copy above has already verified its work.
1954 return ap_discard_request_body(r);
1957 /* handle the MKCOL method */
1958 static int dav_method_mkcol(request_rec *r)
1960 dav_resource *resource;
1962 dav_auto_version_info av_info;
1963 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1968 dav_response *multi_status;
1970 /* handle the request body */
1971 /* ### this may move lower once we start processing bodies */
1972 if ((result = process_mkcol_body(r)) != OK) {
1976 conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
1979 /* Ask repository module to resolve the resource */
1980 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
1984 if (resource->exists) {
1985 /* oops. something was already there! */
1987 /* Apache will supply a default error for this. */
1988 /* ### we should provide a specific error message! */
1989 return HTTP_METHOD_NOT_ALLOWED;
1992 resource_state = dav_get_resource_state(r, resource);
1995 ** Check If-Headers and existing locks.
1997 ** Note: depth == 0 normally requires no multistatus response. However,
1998 ** if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
1999 ** other than the Request-URI, thereby requiring a multistatus.
2001 ** If the resource does not exist (DAV_RESOURCE_NULL), then we must
2002 ** check the resource *and* its parent. If the resource exists or is
2003 ** a locknull resource, then we check only the resource.
2005 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
2006 resource_state == DAV_RESOURCE_NULL ?
2007 DAV_VALIDATE_PARENT :
2008 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2009 /* ### add a higher-level description? */
2010 return dav_handle_err(r, err, multi_status);
2013 /* if versioned resource, make sure parent is checked out */
2014 if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
2015 &av_info)) != NULL) {
2016 /* ### add a higher-level description? */
2017 return dav_handle_err(r, err, NULL);
2020 /* try to create the collection */
2021 resource->collection = 1;
2022 err = (*resource->hooks->create_collection)(resource);
2024 /* restore modifiability of parent back to what it was */
2025 err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
2028 /* check for errors now */
2030 return dav_handle_err(r, err, NULL);
2033 /* just log a warning */
2034 err = dav_push_error(r->pool, err->status, 0,
2035 "The MKCOL was successful, but there "
2036 "was a problem reverting the writability of "
2037 "its parent collection.",
2039 dav_log_err(r, err, APLOG_WARNING);
2042 if (locks_hooks != NULL) {
2045 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2046 /* The directory creation was successful, but the locking failed. */
2047 err = dav_push_error(r->pool, err->status, 0,
2048 "The MKCOL was successful, but there "
2049 "was a problem opening the lock database "
2050 "which prevents inheriting locks from the "
2051 "parent resources.",
2053 return dav_handle_err(r, err, NULL);
2056 /* notify lock system that we have created/replaced a resource */
2057 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
2059 (*locks_hooks->close_lockdb)(lockdb);
2062 /* The dir creation was successful, but the locking failed. */
2063 err = dav_push_error(r->pool, err->status, 0,
2064 "The MKCOL was successful, but there "
2065 "was a problem updating its lock "
2068 return dav_handle_err(r, err, NULL);
2072 /* return an appropriate response (HTTP_CREATED) */
2073 return dav_created(r, NULL, "Collection", 0);
2076 /* handle the COPY and MOVE methods */
2077 static int dav_method_copymove(request_rec *r, int is_move)
2079 dav_resource *resource;
2080 dav_auto_version_info src_av_info = { 0 };
2081 dav_resource *resnew;
2082 dav_auto_version_info dst_av_info;
2088 dav_response *multi_response;
2089 dav_lookup_result lookup;
2098 /* Ask repository module to resolve the resource */
2099 result = dav_get_resource(r, !is_move /*target_allowed*/, NULL, &resource);
2102 if (!resource->exists) {
2103 /* Apache will supply a default error for this. */
2104 return HTTP_NOT_FOUND;
2107 /* If not a file or collection resource, COPY/MOVE not allowed */
2108 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
2109 body = apr_psprintf(r->pool,
2110 "Cannot COPY/MOVE resource %s.",
2111 ap_escape_html(r->pool, r->uri));
2112 return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
2115 /* get the destination URI */
2116 dest = apr_table_get(r->headers_in, "Destination");
2118 /* Look in headers provided by Netscape's Roaming Profiles */
2119 const char *nscp_host = apr_table_get(r->headers_in, "Host");
2120 const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
2122 if (nscp_host != NULL && nscp_path != NULL)
2123 dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
2126 /* This supplies additional information for the default message. */
2127 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2128 "The request is missing a Destination header.");
2129 return HTTP_BAD_REQUEST;
2132 lookup = dav_lookup_uri(dest, r);
2133 if (lookup.rnew == NULL) {
2134 if (lookup.err.status == HTTP_BAD_REQUEST) {
2135 /* This supplies additional information for the default message. */
2136 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2138 return HTTP_BAD_REQUEST;
2141 /* ### this assumes that dav_lookup_uri() only generates a status
2142 * ### that Apache can provide a status line for!! */
2144 return dav_error_response(r, lookup.err.status, lookup.err.desc);
2146 if (lookup.rnew->status != HTTP_OK) {
2147 /* ### how best to report this... */
2148 return dav_error_response(r, lookup.rnew->status,
2149 "Destination URI had an error.");
2152 /* Resolve destination resource */
2153 result = dav_get_resource(lookup.rnew, 0 /*target_allowed*/, NULL, &resnew);
2157 /* are the two resources handled by the same repository? */
2158 if (resource->hooks != resnew->hooks) {
2159 /* ### this message exposes some backend config, but screw it... */
2160 return dav_error_response(r, HTTP_BAD_GATEWAY,
2161 "Destination URI is handled by a "
2162 "different repository than the source URI. "
2163 "MOVE or COPY between repositories is "
2167 /* get and parse the overwrite header value */
2168 if ((overwrite = dav_get_overwrite(r)) < 0) {
2169 /* dav_get_overwrite() supplies additional information for the
2170 * default message. */
2171 return HTTP_BAD_REQUEST;
2174 /* quick failure test: if dest exists and overwrite is false. */
2175 if (resnew->exists && !overwrite) {
2176 /* Supply some text for the error response body. */
2177 return dav_error_response(r, HTTP_PRECONDITION_FAILED,
2178 "Destination is not empty and "
2179 "Overwrite is not \"T\"");
2182 /* are the source and destination the same? */
2183 if ((*resource->hooks->is_same_resource)(resource, resnew)) {
2184 /* Supply some text for the error response body. */
2185 return dav_error_response(r, HTTP_FORBIDDEN,
2186 "Source and Destination URIs are the same.");
2190 is_dir = resource->collection;
2192 /* get and parse the Depth header value. "0" and "infinity" are legal. */
2193 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
2194 /* dav_get_depth() supplies additional information for the
2195 * default message. */
2196 return HTTP_BAD_REQUEST;
2199 /* This supplies additional information for the default message. */
2200 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2201 "Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
2202 return HTTP_BAD_REQUEST;
2204 if (is_move && is_dir && depth != DAV_INFINITY) {
2205 /* This supplies additional information for the default message. */
2206 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2207 "Depth must be \"infinity\" when moving a collection.");
2208 return HTTP_BAD_REQUEST;
2212 ** Check If-Headers and existing locks for each resource in the source
2213 ** if we are performing a MOVE. We will return a 424 response with a
2214 ** DAV:multistatus body. The multistatus responses will contain the
2215 ** information about any resource that fails the validation.
2217 ** We check the parent resource, too, since this is a MOVE. Moving the
2218 ** resource effectively removes it from the parent collection, so we
2219 ** must ensure that we have met the appropriate conditions.
2221 ** If a problem occurs with the Request-URI itself, then a plain error
2222 ** (rather than a multistatus) will be returned.
2225 && (err = dav_validate_request(r, resource, depth, NULL,
2228 | DAV_VALIDATE_USE_424,
2230 err = dav_push_error(r->pool, err->status, 0,
2231 apr_psprintf(r->pool,
2232 "Could not MOVE %s due to a failed "
2233 "precondition on the source "
2235 ap_escape_html(r->pool, r->uri)),
2237 return dav_handle_err(r, err, multi_response);
2241 ** Check If-Headers and existing locks for destination. Note that we
2242 ** use depth==infinity since the target (hierarchy) will be deleted
2243 ** before the move/copy is completed.
2245 ** Note that we are overwriting the target, which implies a DELETE, so
2246 ** we are subject to the error/response rules as a DELETE. Namely, we
2247 ** will return a 424 error if any of the validations fail.
2248 ** (see dav_method_delete() for more information)
2250 if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
2253 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
2254 err = dav_push_error(r->pool, err->status, 0,
2255 apr_psprintf(r->pool,
2256 "Could not MOVE/COPY %s due to a "
2257 "failed precondition on the "
2258 "destination (e.g. locks).",
2259 ap_escape_html(r->pool, r->uri)),
2261 return dav_handle_err(r, err, multi_response);
2265 && depth == DAV_INFINITY
2266 && (*resource->hooks->is_parent_resource)(resource, resnew)) {
2267 /* Supply some text for the error response body. */
2268 return dav_error_response(r, HTTP_FORBIDDEN,
2269 "Source collection contains the "
2274 && (*resnew->hooks->is_parent_resource)(resnew, resource)) {
2275 /* The destination must exist (since it contains the source), and
2276 * a condition above implies Overwrite==T. Obviously, we cannot
2277 * delete the Destination before the MOVE/COPY, as that would
2278 * delete the Source.
2281 /* Supply some text for the error response body. */
2282 return dav_error_response(r, HTTP_FORBIDDEN,
2283 "Destination collection contains the Source "
2284 "and Overwrite has been specified.");
2287 /* ### for now, we don't need anything in the body */
2288 if ((result = ap_discard_request_body(r)) != OK) {
2292 if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
2293 /* ### add a higher-level description? */
2294 return dav_handle_err(r, err, NULL);
2297 /* remove any locks from the old resources */
2299 ** ### this is Yet Another Traversal. if we do a rename(), then we
2300 ** ### really don't have to do this in some cases since the inode
2301 ** ### values will remain constant across the move. but we can't
2302 ** ### know that fact from outside the provider :-(
2304 ** ### note that we now have a problem atomicity in the move/copy
2305 ** ### since a failure after this would have removed locks (technically,
2306 ** ### this is okay to do, but really...)
2308 if (is_move && lockdb != NULL) {
2309 /* ### this is wrong! it blasts direct locks on parent resources */
2310 /* ### pass lockdb! */
2311 (void)dav_unlock(r, resource, NULL);
2314 /* remember whether target resource existed */
2315 replaced = resnew->exists;
2317 /* if this is a move, then the source parent collection will be modified */
2319 if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
2320 &src_av_info)) != NULL) {
2322 (*lockdb->hooks->close_lockdb)(lockdb);
2324 /* ### add a higher-level description? */
2325 return dav_handle_err(r, err, NULL);
2329 /* prepare the destination collection for modification */
2330 if ((err = dav_ensure_resource_writable(r, resnew, 1 /* parent_only */,
2331 &dst_av_info)) != NULL) {
2332 /* could not make destination writable:
2333 * if move, restore state of source parent
2336 (void) dav_revert_resource_writability(r, NULL, 1 /* undo */,
2341 (*lockdb->hooks->close_lockdb)(lockdb);
2343 /* ### add a higher-level description? */
2344 return dav_handle_err(r, err, NULL);
2347 /* If source and destination parents are the same, then
2348 * use the same object, so status updates to one are reflected
2349 * in the other, when reverting their writable states.
2351 if (src_av_info.parent_resource != NULL
2352 && (*src_av_info.parent_resource->hooks->is_same_resource)
2353 (src_av_info.parent_resource, dst_av_info.parent_resource)) {
2355 dst_av_info.parent_resource = src_av_info.parent_resource;
2358 /* New resource will be same kind as source */
2359 resnew->collection = resource->collection;
2361 resource_state = dav_get_resource_state(lookup.rnew, resnew);
2363 /* If target exists, remove it first (we know Ovewrite must be TRUE).
2364 * Then try to copy/move the resource.
2367 err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
2371 err = (*resource->hooks->move_resource)(resource, resnew,
2374 err = (*resource->hooks->copy_resource)(resource, resnew, depth,
2378 /* restore parent collection states */
2379 err2 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
2383 err3 = dav_revert_resource_writability(r, NULL, err != NULL /* undo if error */,
2389 /* check for error from remove/copy/move operations */
2392 (*lockdb->hooks->close_lockdb)(lockdb);
2394 err = dav_push_error(r->pool, err->status, 0,
2395 apr_psprintf(r->pool,
2396 "Could not MOVE/COPY %s.",
2397 ap_escape_html(r->pool, r->uri)),
2399 return dav_handle_err(r, err, multi_response);
2402 /* check for errors from reverting writability */
2404 /* just log a warning */
2405 err = dav_push_error(r->pool, err2->status, 0,
2406 "The MOVE/COPY was successful, but there was a "
2407 "problem reverting the writability of the "
2408 "source parent collection.",
2410 dav_log_err(r, err, APLOG_WARNING);
2413 /* just log a warning */
2414 err = dav_push_error(r->pool, err3->status, 0,
2415 "The MOVE/COPY was successful, but there was a "
2416 "problem reverting the writability of the "
2417 "destination parent collection.",
2419 dav_log_err(r, err, APLOG_WARNING);
2422 /* propagate any indirect locks at the target */
2423 if (lockdb != NULL) {
2425 /* notify lock system that we have created/replaced a resource */
2426 err = dav_notify_created(r, lockdb, resnew, resource_state, depth);
2428 (*lockdb->hooks->close_lockdb)(lockdb);
2431 /* The move/copy was successful, but the locking failed. */
2432 err = dav_push_error(r->pool, err->status, 0,
2433 "The MOVE/COPY was successful, but there "
2434 "was a problem updating the lock "
2437 return dav_handle_err(r, err, NULL);
2441 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
2442 return dav_created(r, lookup.rnew->uri, "Destination", replaced);
2445 /* dav_method_lock: Handler to implement the DAV LOCK method
2446 ** Returns appropriate HTTP_* response.
2448 static int dav_method_lock(request_rec *r)
2451 dav_resource *resource;
2452 const dav_hooks_locks *locks_hooks;
2455 int new_lock_request = 0;
2458 dav_response *multi_response = NULL;
2462 /* If no locks provider, decline the request */
2463 locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2464 if (locks_hooks == NULL)
2467 if ((result = ap_xml_parse_input(r, &doc)) != OK)
2470 depth = dav_get_depth(r, DAV_INFINITY);
2471 if (depth != 0 && depth != DAV_INFINITY) {
2472 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2473 "Depth must be 0 or \"infinity\" for LOCK.");
2474 return HTTP_BAD_REQUEST;
2477 /* Ask repository module to resolve the resource.
2478 * DeltaV says result of target selector is undefined,
2479 * so allow it, and let provider reject the lock attempt
2480 * on a version if it wants to.
2482 result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
2487 ** Open writable. Unless an error occurs, we'll be
2488 ** writing into the database.
2490 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2491 /* ### add a higher-level description? */
2492 return dav_handle_err(r, err, NULL);
2496 if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
2498 /* ### add a higher-level description to err? */
2501 new_lock_request = 1;
2503 lock->auth_user = apr_pstrdup(r->pool, r->user);
2506 resource_state = dav_get_resource_state(r, resource);
2509 ** Check If-Headers and existing locks.
2511 ** If this will create a locknull resource, then the LOCK will affect
2512 ** the parent collection (much like a PUT/MKCOL). For that case, we must
2513 ** validate the parent resource's conditions.
2515 if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
2516 (resource_state == DAV_RESOURCE_NULL
2517 ? DAV_VALIDATE_PARENT
2518 : DAV_VALIDATE_RESOURCE)
2519 | (new_lock_request ? lock->scope : 0)
2520 | DAV_VALIDATE_ADD_LD,
2522 err = dav_push_error(r->pool, err->status, 0,
2523 apr_psprintf(r->pool,
2524 "Could not LOCK %s due to a failed "
2525 "precondition (e.g. other locks).",
2526 ap_escape_html(r->pool, r->uri)),
2531 if (new_lock_request == 0) {
2532 dav_locktoken_list *ltl;
2536 ** ### Assumption: We can renew multiple locks on the same resource
2537 ** ### at once. First harvest all the positive lock-tokens given in
2538 ** ### the If header. Then modify the lock entries for this resource
2539 ** ### with the new Timeout val.
2542 if ((err = dav_get_locktoken_list(r, <l)) != NULL) {
2543 err = dav_push_error(r->pool, err->status, 0,
2544 apr_psprintf(r->pool,
2545 "The lock refresh for %s failed "
2546 "because no lock tokens were "
2547 "specified in an \"If:\" "
2549 ap_escape_html(r->pool, r->uri)),
2554 if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
2557 /* ### add a higher-level description to err? */
2561 /* New lock request */
2562 char *locktoken_txt;
2565 conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
2568 /* apply lower bound (if any) from DAVMinTimeout directive */
2569 if (lock->timeout != DAV_TIMEOUT_INFINITE
2570 && lock->timeout < time(NULL) + conf->locktimeout)
2571 lock->timeout = time(NULL) + conf->locktimeout;
2573 err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
2575 /* ### add a higher-level description to err? */
2579 locktoken_txt = apr_pstrcat(r->pool, "<",
2580 (*locks_hooks->format_locktoken)(r->pool, lock->locktoken),
2583 apr_table_set(r->headers_out, "Lock-Token", locktoken_txt);
2586 (*locks_hooks->close_lockdb)(lockdb);
2588 r->status = HTTP_OK;
2589 r->content_type = DAV_XML_CONTENT_TYPE;
2591 ap_send_http_header(r);
2593 ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
2595 ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
2598 "<D:lockdiscovery>" DEBUG_CR
2600 "</D:lockdiscovery>" DEBUG_CR,
2601 dav_lock_get_activelock(r, lock, NULL));
2603 ap_rputs("</D:prop>", r);
2605 /* the response has been sent. */
2609 (*locks_hooks->close_lockdb)(lockdb);
2610 return dav_handle_err(r, err, multi_response);
2613 /* dav_method_unlock: Handler to implement the DAV UNLOCK method
2614 * Returns appropriate HTTP_* response.
2616 static int dav_method_unlock(request_rec *r)
2619 dav_resource *resource;
2620 const dav_hooks_locks *locks_hooks;
2622 const char *const_locktoken_txt;
2623 char *locktoken_txt;
2624 dav_locktoken *locktoken = NULL;
2626 dav_response *multi_response;
2628 /* If no locks provider, decline the request */
2629 locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2630 if (locks_hooks == NULL)
2633 if ((const_locktoken_txt = apr_table_get(r->headers_in, "Lock-Token")) == NULL) {
2634 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2635 "Unlock failed (%s): No Lock-Token specified in header", r->filename);
2636 return HTTP_BAD_REQUEST;
2639 locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt);
2640 if (locktoken_txt[0] != '<') {
2641 /* ### should provide more specifics... */
2642 return HTTP_BAD_REQUEST;
2646 if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
2647 /* ### should provide more specifics... */
2648 return HTTP_BAD_REQUEST;
2650 locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
2652 if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
2653 &locktoken)) != NULL) {
2654 err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
2655 apr_psprintf(r->pool,
2656 "The UNLOCK on %s failed -- an "
2657 "invalid lock token was specified "
2658 "in the \"If:\" header.",
2659 ap_escape_html(r->pool, r->uri)),
2661 return dav_handle_err(r, err, NULL);
2664 /* Ask repository module to resolve the resource.
2665 * DeltaV says result of target selector is undefined,
2666 * so allow it, and let provider reject the unlock attempt
2667 * on a version if it wants to.
2669 result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
2673 resource_state = dav_get_resource_state(r, resource);
2676 ** Check If-Headers and existing locks.
2678 ** Note: depth == 0 normally requires no multistatus response. However,
2679 ** if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
2680 ** other than the Request-URI, thereby requiring a multistatus.
2682 ** If the resource is a locknull resource, then the UNLOCK will affect
2683 ** the parent collection (much like a delete). For that case, we must
2684 ** validate the parent resource's conditions.
2686 if ((err = dav_validate_request(r, resource, 0, locktoken,
2688 resource_state == DAV_RESOURCE_LOCK_NULL
2689 ? DAV_VALIDATE_PARENT
2690 : DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2691 /* ### add a higher-level description? */
2692 return dav_handle_err(r, err, multi_response);
2695 /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
2696 * _all_ resources locked by locktoken are released. It does not say
2697 * resource has to be the root of an infinte lock. Thus, an UNLOCK
2698 * on any part of an infinte lock will remove the lock on all resources.
2700 * For us, if r->filename represents an indirect lock (part of an infinity lock),
2701 * we must actually perform an UNLOCK on the direct lock for this resource.
2703 if ((result = dav_unlock(r, resource, locktoken)) != OK) {
2707 return HTTP_NO_CONTENT;
2710 static int dav_method_vsn_control(request_rec *r)
2712 dav_resource *resource;
2714 dav_auto_version_info av_info;
2715 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2716 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
2719 const char *target = NULL;
2722 /* if no versioning provider, decline the request */
2723 if (vsn_hooks == NULL)
2726 /* ask repository module to resolve the resource */
2727 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
2731 /* remember the pre-creation resource state */
2732 resource_state = dav_get_resource_state(r, resource);
2734 /* parse the request body (may be a version-control element) */
2735 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
2738 /* note: doc == NULL if no request body */
2741 const ap_xml_elem *child;
2744 if (!dav_validate_root(doc, "version-control")) {
2745 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2746 "The request body does not contain "
2747 "a \"version-control\" element.");
2748 return HTTP_BAD_REQUEST;
2751 /* get the version URI */
2752 if ((child = dav_find_child(doc->root, "version")) == NULL) {
2753 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2754 "The \"version-control\" element does not contain "
2755 "a \"version\" element.");
2756 return HTTP_BAD_REQUEST;
2759 if ((child = dav_find_child(child, "href")) == NULL) {
2760 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2761 "The \"version\" element does not contain "
2762 "an \"href\" element.");
2763 return HTTP_BAD_REQUEST;
2766 /* get version URI */
2767 ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
2770 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2771 "An \"href\" element does not contain a URI.");
2772 return HTTP_BAD_REQUEST;
2776 /* Check request preconditions */
2778 /* ### need a general mechanism for reporting precondition violations
2779 * ### (should be returning XML document for 403/409 responses)
2782 /* if not versioning existing resource, must specify version to select */
2783 if (!resource->exists && target == NULL) {
2784 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
2785 "<DAV:initial-version-required/>");
2786 return dav_handle_err(r, err, NULL);
2788 else if (resource->exists) {
2789 /* cannot add resource to existing version history */
2790 if (target != NULL) {
2791 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
2792 "<DAV:cannot-add-to-existing-history/>");
2793 return dav_handle_err(r, err, NULL);
2796 /* resource must be unversioned and versionable, or version selector */
2797 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
2798 || (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
2799 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
2800 "<DAV:must-be-versionable/>");
2801 return dav_handle_err(r, err, NULL);
2804 /* the DeltaV spec says if resource is a version selector,
2805 * then VERSION-CONTROL is a no-op
2807 if (resource->versioned) {
2808 /* set the Cache-Control header, per the spec */
2809 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
2812 ap_set_content_length(r, 0);
2813 ap_send_http_header(r);
2819 /* Check If-Headers and existing locks */
2820 /* Note: depth == 0. Implies no need for a multistatus response. */
2821 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
2822 resource_state == DAV_RESOURCE_NULL ?
2823 DAV_VALIDATE_PARENT :
2824 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2825 return dav_handle_err(r, err, NULL);
2828 /* if in versioned collection, make sure parent is checked out */
2829 if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
2830 &av_info)) != NULL) {
2831 return dav_handle_err(r, err, NULL);
2834 /* attempt to version-control the resource */
2835 if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
2836 dav_revert_resource_writability(r, resource, 1 /*undo*/, &av_info);
2837 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
2838 apr_psprintf(r->pool,
2839 "Could not VERSION-CONTROL resource %s.",
2840 ap_escape_html(r->pool, r->uri)),
2842 return dav_handle_err(r, err, NULL);
2845 /* revert writability of parent directory */
2846 err = dav_revert_resource_writability(r, resource, 0 /*undo*/, &av_info);
2848 /* just log a warning */
2849 err = dav_push_error(r->pool, err->status, 0,
2850 "The VERSION-CONTROL was successful, but there "
2851 "was a problem reverting the writability of "
2852 "the parent collection.",
2854 dav_log_err(r, err, APLOG_WARNING);
2857 /* if the resource is lockable, let lock system know of new resource */
2858 if (locks_hooks != NULL
2859 && (*locks_hooks->get_supportedlock)(resource) != NULL) {
2862 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2863 /* The resource creation was successful, but the locking failed. */
2864 err = dav_push_error(r->pool, err->status, 0,
2865 "The VERSION-CONTROL was successful, but there "
2866 "was a problem opening the lock database "
2867 "which prevents inheriting locks from the "
2868 "parent resources.",
2870 return dav_handle_err(r, err, NULL);
2873 /* notify lock system that we have created/replaced a resource */
2874 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
2876 (*locks_hooks->close_lockdb)(lockdb);
2879 /* The dir creation was successful, but the locking failed. */
2880 err = dav_push_error(r->pool, err->status, 0,
2881 "The VERSION-CONTROL was successful, but there "
2882 "was a problem updating its lock "
2885 return dav_handle_err(r, err, NULL);
2889 /* set the Cache-Control header, per the spec */
2890 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
2892 /* return an appropriate response (HTTP_CREATED) */
2893 return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
2896 /* handle the CHECKOUT method */
2897 static int dav_method_checkout(request_rec *r)
2899 dav_resource *resource;
2900 dav_resource *working_resource;
2901 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
2905 ap_xml_elem *target = NULL;
2907 /* If no versioning provider, decline the request */
2908 if (vsn_hooks == NULL)
2911 if ((result = ap_xml_parse_input(r, &doc)) != OK)
2915 if (!dav_validate_root(doc, "checkout")) {
2916 /* This supplies additional information for the default msg. */
2917 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
2918 "The request body, if present, must be a "
2919 "DAV:checkout element.");
2920 return HTTP_BAD_REQUEST;
2923 if ((target = dav_find_child(doc->root, "version")) == NULL)
2924 target = dav_find_child(doc->root, "label-name");
2927 /* Ask repository module to resolve the resource */
2928 result = dav_get_resource(r, 1 /*target_allowed*/, target, &resource);
2931 if (!resource->exists) {
2932 /* Apache will supply a default error for this. */
2933 return HTTP_NOT_FOUND;
2936 /* Check the state of the resource: must be a file or collection,
2937 * must be versioned, and must not already be checked out.
2939 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
2940 return dav_error_response(r, HTTP_CONFLICT,
2941 "Cannot checkout this type of resource.");
2944 if (!resource->versioned) {
2945 return dav_error_response(r, HTTP_CONFLICT,
2946 "Cannot checkout unversioned resource.");
2949 if (resource->working) {
2950 return dav_error_response(r, HTTP_CONFLICT,
2951 "The resource is already checked out to the workspace.");
2954 /* ### do lock checks, once behavior is defined */
2956 /* Do the checkout */
2957 if ((err = (*vsn_hooks->checkout)(resource, &working_resource)) != NULL) {
2958 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
2959 apr_psprintf(r->pool,
2960 "Could not CHECKOUT resource %s.",
2961 ap_escape_html(r->pool, r->uri)),
2963 return dav_handle_err(r, err, NULL);
2966 /* set the Cache-Control header, per the spec */
2967 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
2969 /* use appropriate URI for Location header */
2970 if (working_resource == NULL)
2971 working_resource = resource;
2973 return dav_created(r, working_resource->uri, "Checked-out resource", 0);
2976 /* handle the UNCHECKOUT method */
2977 static int dav_method_uncheckout(request_rec *r)
2979 dav_resource *resource;
2980 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
2984 /* If no versioning provider, decline the request */
2985 if (vsn_hooks == NULL)
2988 if ((result = ap_discard_request_body(r)) != OK) {
2992 /* Ask repository module to resolve the resource */
2993 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
2996 if (!resource->exists) {
2997 /* Apache will supply a default error for this. */
2998 return HTTP_NOT_FOUND;
3001 /* Check the state of the resource: must be a file or collection,
3002 * must be versioned, and must be checked out.
3004 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3005 return dav_error_response(r, HTTP_CONFLICT,
3006 "Cannot uncheckout this type of resource.");
3009 if (!resource->versioned) {
3010 return dav_error_response(r, HTTP_CONFLICT,
3011 "Cannot uncheckout unversioned resource.");
3014 if (!resource->working) {
3015 return dav_error_response(r, HTTP_CONFLICT,
3016 "The resource is not checked out to the workspace.");
3019 /* ### do lock checks, once behavior is defined */
3021 /* Do the uncheckout */
3022 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
3023 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3024 apr_psprintf(r->pool,
3025 "Could not UNCHECKOUT resource %s.",
3026 ap_escape_html(r->pool, r->uri)),
3028 return dav_handle_err(r, err, NULL);
3032 ap_set_content_length(r, 0);
3033 ap_send_http_header(r);
3038 /* handle the CHECKIN method */
3039 static int dav_method_checkin(request_rec *r)
3041 dav_resource *resource;
3042 dav_resource *new_version;
3043 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3047 /* If no versioning provider, decline the request */
3048 if (vsn_hooks == NULL)
3051 if ((result = ap_discard_request_body(r)) != OK) {
3055 /* Ask repository module to resolve the resource */
3056 result = dav_get_resource(r, 0 /* target_allowed */, NULL, &resource);
3059 if (!resource->exists) {
3060 /* Apache will supply a default error for this. */
3061 return HTTP_NOT_FOUND;
3064 /* Check the state of the resource: must be a file or collection,
3065 * must be versioned, and must be checked out.
3067 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3068 return dav_error_response(r, HTTP_CONFLICT,
3069 "Cannot checkin this type of resource.");
3072 if (!resource->versioned) {
3073 return dav_error_response(r, HTTP_CONFLICT,
3074 "Cannot checkin unversioned resource.");
3077 if (!resource->working) {
3078 return dav_error_response(r, HTTP_CONFLICT,
3079 "The resource is not checked out to the workspace.");
3082 /* ### do lock checks, once behavior is defined */
3084 /* Do the checkin */
3085 if ((err = (*vsn_hooks->checkin)(resource, &new_version)) != NULL) {
3086 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3087 apr_psprintf(r->pool,
3088 "Could not CHECKIN resource %s.",
3089 ap_escape_html(r->pool, r->uri)),
3091 return dav_handle_err(r, err, NULL);
3094 return dav_created(r, new_version->uri, "Version", 0);
3097 /* context maintained during SET-TARGET treewalk */
3098 typedef struct dav_set_target_walker_ctx
3103 /* target specifier */
3106 /* flag for whether target is version URI or label */
3109 /* version provider hooks */
3110 const dav_hooks_vsn *vsn_hooks;
3112 } dav_set_target_walker_ctx;
3114 static dav_error * dav_set_target_walker(dav_walk_resource *wres, int calltype)
3116 dav_set_target_walker_ctx *ctx = wres->walk_ctx;
3117 dav_error *err = NULL;
3119 /* Check the state of the resource: must be a checked-in version
3120 * or baseline selector
3122 /* ### need a general mechanism for reporting precondition violations
3123 * ### (should be returning XML document for 403/409 responses)
3125 if (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
3126 || !wres->resource->versioned || wres->resource->working) {
3127 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3128 "<DAV:must-be-checked-in-version-selector/>");
3131 /* do the set-target operation */
3132 err = (*ctx->vsn_hooks->set_target)(wres->resource, ctx->target, ctx->is_label);
3136 /* ### need utility routine to add response with description? */
3137 dav_add_response(wres, err->status, NULL);
3138 wres->response->desc = err->desc;
3144 static int dav_method_set_target(request_rec *r)
3146 dav_resource *resource;
3147 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3154 dav_set_target_walker_ctx ctx = { { 0 } };
3155 dav_response *multi_status;
3157 /* If no versioning provider, decline the request */
3158 if (vsn_hooks == NULL)
3161 /* Ask repository module to resolve the resource */
3162 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
3165 if (!resource->exists) {
3166 /* Apache will supply a default error for this. */
3167 return HTTP_NOT_FOUND;
3170 if ((depth = dav_get_depth(r, 0)) < 0) {
3171 /* dav_get_depth() supplies additional information for the
3172 * default message. */
3173 return HTTP_BAD_REQUEST;
3176 /* parse the request body */
3177 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3181 if (doc == NULL || !dav_validate_root(doc, "set-target")) {
3182 /* This supplies additional information for the default message. */
3183 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3184 "The request body does not contain "
3185 "a \"set-target\" element.");
3186 return HTTP_BAD_REQUEST;
3189 /* check for label-name or version element */
3190 if ((child = dav_find_child(doc->root, "label-name")) != NULL) {
3193 else if ((child = dav_find_child(doc->root, "version")) != NULL) {
3196 /* get the href element */
3197 if ((child = dav_find_child(child, "href")) == NULL) {
3198 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3199 "The version element does not contain "
3200 "an \"href\" element.");
3201 return HTTP_BAD_REQUEST;
3205 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3206 "The \"set-target\" element does not contain "
3207 "a \"label-name\" or \"version\" element.");
3208 return HTTP_BAD_REQUEST;
3211 /* get the target value (a label or a version URI */
3212 ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3213 &ctx.target, &tsize);
3215 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3216 "A \"label-name\" or \"href\" element does not contain "
3218 return HTTP_BAD_REQUEST;
3221 /* do the set-target operation walk */
3222 ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
3223 ctx.w.func = dav_set_target_walker;
3224 ctx.w.walk_ctx = &ctx;
3225 ctx.w.pool = r->pool;
3226 ctx.w.root = resource;
3227 ctx.vsn_hooks = vsn_hooks;
3229 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
3232 /* some sort of error occurred which terminated the walk */
3233 err = dav_push_error(r->pool, err->status, 0,
3234 "The SET-TARGET operation was terminated prematurely.",
3236 return dav_handle_err(r, err, multi_status);
3239 if (multi_status != NULL) {
3240 /* One or more resources had errors. If depth was zero, convert
3241 * response to simple error, else make sure there is an
3242 * overall error to pass to dav_handle_err()
3245 err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
3246 multi_status = NULL;
3249 err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
3250 "Errors occurred during the SET-TARGET operation.");
3253 return dav_handle_err(r, err, multi_status);
3256 /* set the Cache-Control header, per the spec */
3257 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3260 ap_set_content_length(r, 0);
3261 ap_send_http_header(r);
3266 /* context maintained during LABEL treewalk */
3267 typedef struct dav_label_walker_ctx
3272 /* label being manipulated */
3275 /* label operation */
3277 #define DAV_LABEL_ADD 1
3278 #define DAV_LABEL_SET 2
3279 #define DAV_LABEL_REMOVE 3
3281 /* version provider hooks */
3282 const dav_hooks_vsn *vsn_hooks;
3284 } dav_label_walker_ctx;
3286 static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
3288 dav_label_walker_ctx *ctx = wres->walk_ctx;
3289 dav_error *err = NULL;
3291 /* Check the state of the resource: must be a version or
3292 * non-checkedout version selector
3294 /* ### need a general mechanism for reporting precondition violations
3295 * ### (should be returning XML document for 403/409 responses)
3297 if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
3298 (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
3299 || !wres->resource->versioned)) {
3300 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3301 "<DAV:must-be-version-or-version-selector/>");
3303 else if (wres->resource->working) {
3304 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3305 "<DAV:must-not-be-checked-out/>");
3308 /* do the label operation */
3309 if (ctx->label_op == DAV_LABEL_REMOVE)
3310 err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
3312 err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
3313 ctx->label_op == DAV_LABEL_SET);
3317 /* ### need utility routine to add response with description? */
3318 dav_add_response(wres, err->status, NULL);
3319 wres->response->desc = err->desc;
3325 static int dav_method_label(request_rec *r)
3327 dav_resource *resource;
3328 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3335 dav_label_walker_ctx ctx = { { 0 } };
3336 dav_response *multi_status;
3338 /* If no versioning provider, or the provider doesn't support
3339 * labels, decline the request */
3340 if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
3343 /* Ask repository module to resolve the resource */
3344 result = dav_get_resource(r, 1 /*target_allowed*/, NULL, &resource);
3347 if (!resource->exists) {
3348 /* Apache will supply a default error for this. */
3349 return HTTP_NOT_FOUND;
3352 if ((depth = dav_get_depth(r, 0)) < 0) {
3353 /* dav_get_depth() supplies additional information for the
3354 * default message. */
3355 return HTTP_BAD_REQUEST;
3358 /* parse the request body */
3359 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3363 if (doc == NULL || !dav_validate_root(doc, "label")) {
3364 /* This supplies additional information for the default message. */
3365 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3366 "The request body does not contain "
3367 "a \"label\" element.");
3368 return HTTP_BAD_REQUEST;
3371 /* check for add, set, or remove element */
3372 if ((child = dav_find_child(doc->root, "add")) != NULL) {
3373 ctx.label_op = DAV_LABEL_ADD;
3375 else if ((child = dav_find_child(doc->root, "set")) != NULL) {
3376 ctx.label_op = DAV_LABEL_SET;
3378 else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
3379 ctx.label_op = DAV_LABEL_REMOVE;
3382 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3383 "The \"label\" element does not contain "
3384 "an \"add\", \"set\", or \"remove\" element.");
3385 return HTTP_BAD_REQUEST;
3388 /* get the label string */
3389 if ((child = dav_find_child(child, "label-name")) == NULL) {
3390 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3391 "The label command element does not contain "
3392 "a \"label-name\" element.");
3393 return HTTP_BAD_REQUEST;
3396 ap_xml_to_text(r->pool, child, AP_XML_X2T_INNER, NULL, NULL,
3397 &ctx.label, &tsize);
3399 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3400 "A \"label-name\" element does not contain "
3402 return HTTP_BAD_REQUEST;
3405 /* do the label operation walk */
3406 ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
3407 ctx.w.func = dav_label_walker;
3408 ctx.w.walk_ctx = &ctx;
3409 ctx.w.pool = r->pool;
3410 ctx.w.root = resource;
3411 ctx.vsn_hooks = vsn_hooks;
3413 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
3416 /* some sort of error occurred which terminated the walk */
3417 err = dav_push_error(r->pool, err->status, 0,
3418 "The LABEL operation was terminated prematurely.",
3420 return dav_handle_err(r, err, multi_status);
3423 if (multi_status != NULL) {
3424 /* One or more resources had errors. If depth was zero, convert
3425 * response to simple error, else make sure there is an
3426 * overall error to pass to dav_handle_err()
3429 err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
3430 multi_status = NULL;
3433 err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
3434 "Errors occurred during the LABEL operation.");
3437 return dav_handle_err(r, err, multi_status);
3440 /* set the Cache-Control header, per the spec */
3441 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3444 ap_set_content_length(r, 0);
3445 ap_send_http_header(r);
3450 static int dav_method_report(request_rec *r)
3452 dav_resource *resource;
3453 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3457 ap_text_header hdr = { 0 };
3461 /* If no versioning provider, decline the request */
3462 if (vsn_hooks == NULL)
3465 if ((result = ap_xml_parse_input(r, &doc)) != OK)
3468 /* This supplies additional information for the default msg. */
3469 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3470 "The request body must specify a report.");
3471 return HTTP_BAD_REQUEST;
3474 /* Ask repository module to resolve the resource.
3475 * First determine whether a Target-Selector header is allowed
3478 target_allowed = (*vsn_hooks->report_target_selector_allowed)(doc);
3479 result = dav_get_resource(r, target_allowed, NULL, &resource);
3482 if (!resource->exists) {
3483 /* Apache will supply a default error for this. */
3484 return HTTP_NOT_FOUND;
3487 /* run report hook */
3488 /* ### writing large reports to memory could be bad...
3489 * ### but if provider generated output directly, it would
3490 * ### have to handle error responses as well.
3492 if ((err = (*vsn_hooks->get_report)(r, resource, doc, &hdr)) != NULL)
3493 return dav_handle_err(r, err, NULL);
3495 /* send the report response */
3496 r->status = HTTP_OK;
3497 r->content_type = DAV_XML_CONTENT_TYPE;
3499 /* send the headers and start a timeout */
3500 ap_send_http_header(r);
3502 /* send the response body */
3503 ap_rputs(DAV_XML_HEADER DEBUG_CR, r);
3505 for (t = hdr.first; t != NULL; t = t->next)
3506 ap_rputs(t->text, r);
3511 static int dav_method_make_workspace(request_rec *r)
3513 dav_resource *resource;
3514 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3519 /* if no versioning provider, or the provider does not support workspaces,
3520 * decline the request
3522 if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
3525 /* ask repository module to resolve the resource */
3526 result = dav_get_resource(r, 0 /*target_allowed*/, NULL, &resource);
3530 /* parse the request body (must be a mkworkspace element) */
3531 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3536 || !dav_validate_root(doc, "mkworkspace")) {
3537 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3538 "The request body does not contain "
3539 "a \"mkworkspace\" element.");
3540 return HTTP_BAD_REQUEST;
3543 /* Check request preconditions */
3545 /* ### need a general mechanism for reporting precondition violations
3546 * ### (should be returning XML document for 403/409 responses)
3549 /* resource must not already exist */
3550 if (resource->exists) {
3551 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3552 "<DAV:resource-must-be-null/>");
3553 return dav_handle_err(r, err, NULL);
3556 /* ### what about locking? */
3558 /* attempt to create the workspace */
3559 if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
3560 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3561 apr_psprintf(r->pool,
3562 "Could not create workspace %s.",
3563 ap_escape_html(r->pool, r->uri)),
3565 return dav_handle_err(r, err, NULL);
3568 /* set the Cache-Control header, per the spec */
3569 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3571 /* return an appropriate response (HTTP_CREATED) */
3572 return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
3575 static int dav_method_make_activity(request_rec *r)
3578 return HTTP_METHOD_NOT_ALLOWED;
3581 static int dav_method_baseline_control(request_rec *r)
3584 return HTTP_METHOD_NOT_ALLOWED;
3587 static int dav_method_merge(request_rec *r)
3590 return HTTP_METHOD_NOT_ALLOWED;
3593 static int dav_method_bind(request_rec *r)
3595 dav_resource *resource;
3596 dav_resource *binding;
3597 dav_auto_version_info av_info;
3598 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
3602 dav_response *multi_response = NULL;
3603 dav_lookup_result lookup;
3607 /* If no bindings provider, decline the request */
3608 if (binding_hooks == NULL)
3611 /* Ask repository module to resolve the resource */
3612 result = dav_get_resource(r, 0 /*!target_allowed*/, NULL, &resource);
3615 if (!resource->exists) {
3616 /* Apache will supply a default error for this. */
3617 return HTTP_NOT_FOUND;
3620 /* get the destination URI */
3621 dest = apr_table_get(r->headers_in, "Destination");
3623 /* This supplies additional information for the default message. */
3624 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3625 "The request is missing a Destination header.");
3626 return HTTP_BAD_REQUEST;
3629 lookup = dav_lookup_uri(dest, r);
3630 if (lookup.rnew == NULL) {
3631 if (lookup.err.status == HTTP_BAD_REQUEST) {
3632 /* This supplies additional information for the default message. */
3633 ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r,
3635 return HTTP_BAD_REQUEST;
3637 else if (lookup.err.status == HTTP_BAD_GATEWAY) {
3638 /* ### Bindings protocol draft 02 says to return 507
3639 * ### (Cross Server Binding Forbidden); Apache already defines 507
3640 * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
3641 * ### HTTP_FORBIDDEN
3643 return dav_error_response(r, HTTP_FORBIDDEN,
3644 "Cross server bindings are not allowed by this server.");
3647 /* ### this assumes that dav_lookup_uri() only generates a status
3648 * ### that Apache can provide a status line for!! */
3650 return dav_error_response(r, lookup.err.status, lookup.err.desc);
3652 if (lookup.rnew->status != HTTP_OK) {
3653 /* ### how best to report this... */
3654 return dav_error_response(r, lookup.rnew->status,
3655 "Destination URI had an error.");
3658 /* resolve binding resource */
3659 result = dav_get_resource(lookup.rnew, 0 /*!target_allowed*/, NULL, &binding);
3663 /* are the two resources handled by the same repository? */
3664 if (resource->hooks != binding->hooks) {
3665 /* ### this message exposes some backend config, but screw it... */
3666 return dav_error_response(r, HTTP_BAD_GATEWAY,
3667 "Destination URI is handled by a "
3668 "different repository than the source URI. "
3669 "BIND between repositories is not possible.");
3672 /* get and parse the overwrite header value */
3673 if ((overwrite = dav_get_overwrite(r)) < 0) {
3674 /* dav_get_overwrite() supplies additional information for the
3675 * default message. */
3676 return HTTP_BAD_REQUEST;
3679 /* quick failure test: if dest exists and overwrite is false. */
3680 if (binding->exists && !overwrite) {
3681 return dav_error_response(r, HTTP_PRECONDITION_FAILED,
3682 "Destination is not empty and "
3683 "Overwrite is not \"T\"");
3686 /* are the source and destination the same? */
3687 if ((*resource->hooks->is_same_resource)(resource, binding)) {
3688 return dav_error_response(r, HTTP_FORBIDDEN,
3689 "Source and Destination URIs are the same.");
3693 ** Check If-Headers and existing locks for destination. Note that we
3694 ** use depth==infinity since the target (hierarchy) will be deleted
3695 ** before the move/copy is completed.
3697 ** Note that we are overwriting the target, which implies a DELETE, so
3698 ** we are subject to the error/response rules as a DELETE. Namely, we
3699 ** will return a 424 error if any of the validations fail.
3700 ** (see dav_method_delete() for more information)
3702 if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
3705 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
3706 err = dav_push_error(r->pool, err->status, 0,
3707 apr_psprintf(r->pool,
3708 "Could not BIND %s due to a "
3709 "failed precondition on the "
3710 "destination (e.g. locks).",
3711 ap_escape_html(r->pool, r->uri)),
3713 return dav_handle_err(r, err, multi_response);
3716 /* guard against creating circular bindings */
3717 if (resource->collection
3718 && (*resource->hooks->is_parent_resource)(resource, binding)) {
3719 return dav_error_response(r, HTTP_FORBIDDEN,
3720 "Source collection contains the Destination.");
3722 if (resource->collection
3723 && (*resource->hooks->is_parent_resource)(binding, resource)) {
3724 /* The destination must exist (since it contains the source), and
3725 * a condition above implies Overwrite==T. Obviously, we cannot
3726 * delete the Destination before the BIND, as that would
3727 * delete the Source.
3730 return dav_error_response(r, HTTP_FORBIDDEN,
3731 "Destination collection contains the Source and "
3732 "Overwrite has been specified.");
3735 /* prepare the destination collection for modification */
3736 if ((err = dav_ensure_resource_writable(r, binding, 1 /* parent_only */,
3737 &av_info)) != NULL) {
3738 /* could not make destination writable */
3739 return dav_handle_err(r, err, NULL);
3742 /* If target exists, remove it first (we know Ovewrite must be TRUE).
3743 * Then try to bind to the resource.
3745 if (binding->exists)
3746 err = (*resource->hooks->remove_resource)(binding, &multi_response);
3749 err = (*binding_hooks->bind_resource)(resource, binding);
3752 /* restore parent collection states */
3753 err2 = dav_revert_resource_writability(r, NULL,
3754 err != NULL /* undo if error */,
3757 /* check for error from remove/bind operations */
3759 err = dav_push_error(r->pool, err->status, 0,
3760 apr_psprintf(r->pool,
3761 "Could not BIND %s.",
3762 ap_escape_html(r->pool, r->uri)),
3764 return dav_handle_err(r, err, multi_response);
3767 /* check for errors from reverting writability */
3769 /* just log a warning */
3770 err = dav_push_error(r->pool, err2->status, 0,
3771 "The BIND was successful, but there was a "
3772 "problem reverting the writability of the "
3773 "source parent collection.",
3775 dav_log_err(r, err, APLOG_WARNING);
3778 /* return an appropriate response (HTTP_CREATED) */
3779 /* ### spec doesn't say what happens when destination was replaced */
3780 return dav_created(r, lookup.rnew->uri, "Binding", 0);
3785 * Response handler for DAV resources
3787 static int dav_handler(request_rec *r)
3791 /* quickly ignore any HTTP/0.9 requests */
3792 if (r->assbackwards) {
3796 /* ### do we need to do anything with r->proxyreq ?? */
3798 conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
3802 * Set up the methods mask, since that's one of the reasons this handler
3803 * gets called, and lower-level things may need the info.
3805 * First, set the mask to the methods we handle directly. Since by
3806 * definition we own our managed space, we unconditionally set
3807 * the r->allowed field rather than ORing our values with anything
3808 * any other module may have put in there.
3810 * These are the HTTP-defined methods that we handle directly.
3819 * These are the DAV methods we handle.
3828 | (1 << M_PROPPATCH);
3830 * These are methods that we don't handle directly, but let the
3831 * server's default handler do for us as our agent.
3836 /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
3837 * ### is sent; it will need the other allowed states; since the default
3838 * ### handler is not called on error, then it doesn't add the other
3839 * ### allowed states, so we must */
3840 /* ### we might need to refine this for just where we return the error.
3841 * ### also, there is the issue with other methods (see ISSUES) */
3842 /* ### more work necessary, now that we have M_foo for DAV methods */
3844 /* dispatch the appropriate method handler */
3845 if (r->method_number == M_GET) {
3846 return dav_method_get(r);
3849 if (r->method_number == M_PUT) {
3850 return dav_method_put(r);
3853 if (r->method_number == M_POST) {
3854 return dav_method_post(r);
3857 if (r->method_number == M_DELETE) {
3858 return dav_method_delete(r);
3861 if (r->method_number == M_OPTIONS) {
3862 return dav_method_options(r);
3865 if (r->method_number == M_PROPFIND) {
3866 return dav_method_propfind(r);
3869 if (r->method_number == M_PROPPATCH) {
3870 return dav_method_proppatch(r);
3873 if (r->method_number == M_MKCOL) {
3874 return dav_method_mkcol(r);
3877 if (r->method_number == M_COPY) {
3878 return dav_method_copymove(r, DAV_DO_COPY);
3881 if (r->method_number == M_MOVE) {
3882 return dav_method_copymove(r, DAV_DO_MOVE);
3885 if (r->method_number == M_LOCK) {
3886 return dav_method_lock(r);
3889 if (r->method_number == M_UNLOCK) {
3890 return dav_method_unlock(r);
3894 * NOTE: When Apache moves creates defines for the add'l DAV methods,
3895 * then it will no longer use M_INVALID. This code must be
3896 * updated each time Apache adds method defines.
3898 if (r->method_number != M_INVALID) {
3902 if (!strcmp(r->method, "VERSION-CONTROL")) {
3903 return dav_method_vsn_control(r);
3906 if (!strcmp(r->method, "CHECKOUT")) {
3907 return dav_method_checkout(r);
3910 if (!strcmp(r->method, "UNCHECKOUT")) {
3911 return dav_method_uncheckout(r);
3914 if (!strcmp(r->method, "CHECKIN")) {
3915 return dav_method_checkin(r);
3918 if (!strcmp(r->method, "SET-TARGET")) {
3919 return dav_method_set_target(r);
3922 if (!strcmp(r->method, "LABEL")) {
3923 return dav_method_label(r);
3926 if (!strcmp(r->method, "REPORT")) {
3927 return dav_method_report(r);
3930 if (!strcmp(r->method, "MKWORKSPACE")) {
3931 return dav_method_make_workspace(r);
3934 if (!strcmp(r->method, "MKACTIVITY")) {
3935 return dav_method_make_activity(r);
3938 if (!strcmp(r->method, "BASELINE-CONTROL")) {
3939 return dav_method_baseline_control(r);
3942 if (!strcmp(r->method, "MERGE")) {
3943 return dav_method_merge(r);
3946 if (!strcmp(r->method, "BIND")) {
3947 return dav_method_bind(r);
3950 /* ### add'l methods for Advanced Collections, ACLs, DASL */
3955 static int dav_type_checker(request_rec *r)
3959 conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
3962 /* if DAV is not enabled, then we've got nothing to do */
3963 if (conf->provider == NULL) {
3967 if (r->method_number == M_GET) {
3969 ** ### need some work to pull Content-Type and Content-Language
3970 ** ### from the property database.
3974 ** If the repository hasn't indicated that it will handle the
3975 ** GET method, then just punt.
3977 ** ### this isn't quite right... taking over the response can break
3978 ** ### things like mod_negotiation. need to look into this some more.
3980 if (!conf->provider->repos->handle_get) {
3985 /* ### we should (instead) trap the ones that we DO understand */
3986 /* ### the handler DOES handle POST, so we need to fix one of these */
3987 if (r->method_number != M_POST) {
3990 ** ### anything else to do here? could another module and/or
3991 ** ### config option "take over" the handler here? i.e. how do
3992 ** ### we lock down this hierarchy so that we are the ultimate
3993 ** ### arbiter? (or do we simply depend on the administrator
3994 ** ### to avoid conflicting configurations?)
3996 ** ### I think the OK stops running type-checkers. need to look.
3998 r->handler = "dav-handler";
4005 static void register_hooks(void)
4007 ap_hook_post_config(dav_init_handler, NULL, NULL, AP_HOOK_MIDDLE);
4008 ap_hook_type_checker(dav_type_checker, NULL, NULL, AP_HOOK_FIRST);
4010 ap_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, AP_HOOK_LAST);
4011 ap_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
4012 NULL, NULL, AP_HOOK_MIDDLE);
4014 /* ### damn. need a pool. */
4015 dav_core_register_uris(NULL);
4018 /*---------------------------------------------------------------------------
4020 ** Configuration info for the module
4023 static const command_rec dav_cmds[] =
4025 /* per directory/location */
4026 AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
4027 "specify the DAV provider for a directory or location"),
4029 /* per directory/location, or per server */
4030 AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
4031 ACCESS_CONF|RSRC_CONF,
4032 "specify minimum allowed timeout"),
4034 /* per directory/location, or per server */
4035 AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL,
4036 ACCESS_CONF|RSRC_CONF,
4037 "allow Depth infinity PROPFIND requests"),
4039 /* per directory/location, or per server */
4040 AP_INIT_TAKE2("DAVParam", dav_cmd_davparam, NULL,
4041 ACCESS_CONF|RSRC_CONF,
4042 "DAVParam <parameter name> <parameter value>"),
4047 static const handler_rec dav_handlers[] =
4049 {"dav-handler", dav_handler},
4053 module DAV_DECLARE_DATA dav_module =
4055 STANDARD20_MODULE_STUFF,
4056 dav_create_dir_config, /* dir config creater */
4057 dav_merge_dir_config, /* dir merger --- default is to override */
4058 dav_create_server_config, /* server config */
4059 dav_merge_server_config, /* merge server config */
4060 dav_cmds, /* command table */
4061 dav_handlers, /* handlers */
4062 register_hooks, /* register hooks */
4066 AP_HOOK_LINK(gather_propsets)
4067 AP_HOOK_LINK(find_liveprop)
4068 AP_HOOK_LINK(insert_all_liveprops)
4070 AP_IMPLEMENT_EXTERNAL_HOOK_VOID(DAV, gather_propsets,
4071 (apr_array_header_t *uris),
4073 AP_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(DAV, int, find_liveprop,
4074 (const dav_resource *resource,
4075 const char *ns_uri, const char *name,
4076 const dav_hooks_liveprop **hooks),
4077 (resource, ns_uri, name, hooks), 0);
4078 AP_IMPLEMENT_EXTERNAL_HOOK_VOID(DAV, insert_all_liveprops,
4079 (request_rec *r, const dav_resource *resource,
4080 int insvalue, ap_text_header *phdr),
4081 (r, resource, insvalue, phdr));