1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * DAV extension module for Apache 2.0.*
20 * This module is repository-independent. It depends on hooks provided by a
21 * repository implementation.
24 * - within a DAV hierarchy, if an unknown method is used and we default
25 * to Apache's implementation, it sends back an OPTIONS with the wrong
26 * set of methods -- there is NO HOOK for us.
27 * therefore: we need to manually handle the HTTP_METHOD_NOT_ALLOWED
28 * and HTTP_NOT_IMPLEMENTED responses (not ap_send_error_response).
29 * - process_mkcol_body() had to dup code from ap_setup_client_block().
30 * - it would be nice to get status lines from Apache for arbitrary
32 * - it would be nice to be able to extend Apache's set of response
33 * codes so that it doesn't return 500 when an unknown code is placed
35 * - http_vhost functions should apply "const" to their params
38 * - For PROPFIND, we batch up the entire response in memory before
39 * sending it. We may want to reorganize around sending the information
40 * as we suck it in from the propdb. Alternatively, we should at least
41 * generate a total Content-Length if we're going to buffer in memory
42 * so that we can keep the connection open.
45 #include "apr_strings.h"
46 #include "apr_lib.h" /* for apr_is* */
48 #define APR_WANT_STRFUNC
52 #include "http_config.h"
53 #include "http_core.h"
55 #include "http_main.h"
56 #include "http_protocol.h"
57 #include "http_request.h"
58 #include "util_script.h"
63 /* ### what is the best way to set this? */
64 #define DAV_DEFAULT_PROVIDER "filesystem"
66 /* used to denote that mod_dav will be handling this request */
67 #define DAV_HANDLER_NAME "dav-handler"
70 DAV_ENABLED_UNSET = 0,
75 /* per-dir configuration */
77 const char *provider_name;
78 const dav_provider *provider;
81 int allow_depthinfinity;
85 /* per-server configuration */
91 #define DAV_INHERIT_VALUE(parent, child, field) \
92 ((child)->field ? (child)->field : (parent)->field)
95 /* forward-declare for use in configuration lookup */
96 extern module DAV_DECLARE_DATA dav_module;
104 static int dav_methods[DAV_M_LAST];
107 static int dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
110 /* DBG0("dav_init_handler"); */
112 /* Register DAV methods */
113 dav_methods[DAV_M_BIND] = ap_method_register(p, "BIND");
114 dav_methods[DAV_M_SEARCH] = ap_method_register(p, "SEARCH");
116 ap_add_version_component(p, "DAV/2");
121 static void *dav_create_server_config(apr_pool_t *p, server_rec *s)
123 dav_server_conf *newconf;
125 newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
127 /* ### this isn't used at the moment... */
132 static void *dav_merge_server_config(apr_pool_t *p, void *base, void *overrides)
135 dav_server_conf *child = overrides;
137 dav_server_conf *newconf;
139 newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
141 /* ### nothing to merge right now... */
146 static void *dav_create_dir_config(apr_pool_t *p, char *dir)
148 /* NOTE: dir==NULL creates the default per-dir config */
152 conf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*conf));
154 /* clean up the directory to remove any trailing slash */
159 d = apr_pstrdup(p, dir);
161 if (l > 1 && d[l - 1] == '/')
169 static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
171 dav_dir_conf *parent = base;
172 dav_dir_conf *child = overrides;
173 dav_dir_conf *newconf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*newconf));
175 /* DBG3("dav_merge_dir_config: new=%08lx base=%08lx overrides=%08lx",
176 (long)newconf, (long)base, (long)overrides); */
178 newconf->provider_name = DAV_INHERIT_VALUE(parent, child, provider_name);
179 newconf->provider = DAV_INHERIT_VALUE(parent, child, provider);
180 if (parent->provider_name != NULL) {
181 if (child->provider_name == NULL) {
182 ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
183 "\"DAV Off\" cannot be used to turn off a subtree "
184 "of a DAV-enabled location.");
186 else if (strcasecmp(child->provider_name,
187 parent->provider_name) != 0) {
188 ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
189 "A subtree cannot specify a different DAV provider "
194 newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
195 newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
196 newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
197 allow_depthinfinity);
202 static const dav_provider *dav_get_provider(request_rec *r)
206 conf = ap_get_module_config(r->per_dir_config, &dav_module);
207 /* assert: conf->provider_name != NULL
208 (otherwise, DAV is disabled, and we wouldn't be here) */
210 /* assert: conf->provider != NULL
211 (checked when conf->provider_name is set) */
212 return conf->provider;
215 DAV_DECLARE(const dav_hooks_locks *) dav_get_lock_hooks(request_rec *r)
217 return dav_get_provider(r)->locks;
220 DAV_DECLARE(const dav_hooks_propdb *) dav_get_propdb_hooks(request_rec *r)
222 return dav_get_provider(r)->propdb;
225 DAV_DECLARE(const dav_hooks_vsn *) dav_get_vsn_hooks(request_rec *r)
227 return dav_get_provider(r)->vsn;
230 DAV_DECLARE(const dav_hooks_binding *) dav_get_binding_hooks(request_rec *r)
232 return dav_get_provider(r)->binding;
235 DAV_DECLARE(const dav_hooks_search *) dav_get_search_hooks(request_rec *r)
237 return dav_get_provider(r)->search;
241 * Command handler for the DAV directive, which is TAKE1.
243 static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
245 dav_dir_conf *conf = (dav_dir_conf *)config;
247 if (strcasecmp(arg1, "on") == 0) {
248 conf->provider_name = DAV_DEFAULT_PROVIDER;
250 else if (strcasecmp(arg1, "off") == 0) {
251 conf->provider_name = NULL;
252 conf->provider = NULL;
255 conf->provider_name = apr_pstrdup(cmd->pool, arg1);
258 if (conf->provider_name != NULL) {
259 /* lookup and cache the actual provider now */
260 conf->provider = dav_lookup_provider(conf->provider_name);
262 if (conf->provider == NULL) {
263 /* by the time they use it, the provider should be loaded and
264 registered with us. */
265 return apr_psprintf(cmd->pool,
266 "Unknown DAV provider: %s",
267 conf->provider_name);
275 * Command handler for the DAVDepthInfinity directive, which is FLAG.
277 static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
280 dav_dir_conf *conf = (dav_dir_conf *)config;
283 conf->allow_depthinfinity = DAV_ENABLED_ON;
285 conf->allow_depthinfinity = DAV_ENABLED_OFF;
290 * Command handler for DAVMinTimeout directive, which is TAKE1
292 static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config,
295 dav_dir_conf *conf = (dav_dir_conf *)config;
297 conf->locktimeout = atoi(arg1);
298 if (conf->locktimeout < 0)
299 return "DAVMinTimeout requires a non-negative integer.";
305 ** dav_error_response()
307 ** Send a nice response back to the user. In most cases, Apache doesn't
308 ** allow us to provide details in the body about what happened. This
309 ** function allows us to completely specify the response body.
311 ** ### this function is not logging any errors! (e.g. the body)
313 static int dav_error_response(request_rec *r, int status, const char *body)
317 /* ### I really don't think this is needed; gotta test */
318 r->status_line = ap_get_status_line(status);
320 ap_set_content_type(r, "text/html; charset=ISO-8859-1");
322 /* begin the response now... */
331 ap_psignature("<hr />\n", r),
335 /* the response has been sent. */
337 * ### Use of DONE obviates logging..!
344 * Send a "standardized" error response based on the error's namespace & tag
346 static int dav_error_response_tag(request_rec *r,
349 r->status = err->status;
351 /* ### I really don't think this is needed; gotta test */
352 r->status_line = ap_get_status_line(err->status);
354 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
356 ap_rputs(DAV_XML_HEADER DEBUG_CR
357 "<D:error xmlns:D=\"DAV:\"", r);
359 if (err->desc != NULL) {
360 /* ### should move this namespace somewhere (with the others!) */
361 ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r);
364 if (err->namespace != NULL) {
366 " xmlns:C=\"%s\">" DEBUG_CR
368 err->namespace, err->tagname);
373 "<D:%s/>" DEBUG_CR, err->tagname);
376 /* here's our mod_dav specific tag: */
377 if (err->desc != NULL) {
379 "<m:human-readable errcode=\"%d\">" DEBUG_CR
381 "</m:human-readable>" DEBUG_CR,
383 apr_xml_quote_string(r->pool, err->desc, 0));
386 ap_rputs("</D:error>" DEBUG_CR, r);
388 /* the response has been sent. */
390 * ### Use of DONE obviates logging..!
397 * Apache's URI escaping does not replace '&' since that is a valid character
398 * in a URI (to form a query section). We must explicitly handle it so that
399 * we can embed the URI into an XML document.
401 static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri)
403 const char *e_uri = ap_escape_uri(p, uri);
405 /* check the easy case... */
406 if (ap_strchr_c(e_uri, '&') == NULL)
409 /* there was a '&', so more work is needed... sigh. */
412 * Note: this is a teeny bit of overkill since we know there are no
413 * '<' or '>' characters, but who cares.
415 return apr_xml_quote_string(p, e_uri, 0);
419 /* Write a complete RESPONSE object out as a <DAV:repsonse> xml
420 element. Data is sent into brigade BB, which is auto-flushed into
421 OUTPUT filter stack. Use POOL for any temporary allocations.
423 [Presumably the <multistatus> tag has already been written; this
424 routine is shared by dav_send_multistatus and dav_stream_response.]
426 static void dav_send_one_response(dav_response *response,
427 apr_bucket_brigade *bb,
433 if (response->propresult.xmlns == NULL) {
434 ap_fputs(output, bb, "<D:response>");
437 ap_fputs(output, bb, "<D:response");
438 for (t = response->propresult.xmlns; t; t = t->next) {
439 ap_fputs(output, bb, t->text);
441 ap_fputc(output, bb, '>');
444 ap_fputstrs(output, bb,
446 dav_xml_escape_uri(pool, response->href),
447 "</D:href>" DEBUG_CR,
450 if (response->propresult.propstats == NULL) {
451 /* use the Status-Line text from Apache. Note, this will
452 * default to 500 Internal Server Error if first->status
453 * is not a known (or valid) status code.
455 ap_fputstrs(output, bb,
456 "<D:status>HTTP/1.1 ",
457 ap_get_status_line(response->status),
458 "</D:status>" DEBUG_CR,
462 /* assume this includes <propstat> and is quoted properly */
463 for (t = response->propresult.propstats; t; t = t->next) {
464 ap_fputs(output, bb, t->text);
468 if (response->desc != NULL) {
470 * We supply the description, so we know it doesn't have to
471 * have any escaping/encoding applied to it.
473 ap_fputstrs(output, bb,
474 "<D:responsedescription>",
476 "</D:responsedescription>" DEBUG_CR,
480 ap_fputs(output, bb, "</D:response>" DEBUG_CR);
484 /* Factorized helper function: prep request_rec R for a multistatus
485 response and write <multistatus> tag into BB, destined for
486 R->output_filters. Use xml NAMESPACES in initial tag, if
488 static void dav_begin_multistatus(apr_bucket_brigade *bb,
489 request_rec *r, int status,
490 apr_array_header_t *namespaces)
492 /* Set the correct status and Content-Type */
494 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
496 /* Send the headers and actual multistatus response now... */
497 ap_fputs(r->output_filters, bb, DAV_XML_HEADER DEBUG_CR
498 "<D:multistatus xmlns:D=\"DAV:\"");
500 if (namespaces != NULL) {
503 for (i = namespaces->nelts; i--; ) {
504 ap_fprintf(r->output_filters, bb, " xmlns:ns%d=\"%s\"", i,
505 APR_XML_GET_URI_ITEM(namespaces, i));
509 ap_fputs(r->output_filters, bb, ">" DEBUG_CR);
512 /* Finish a multistatus response started by dav_begin_multistatus: */
513 static apr_status_t dav_finish_multistatus(request_rec *r,
514 apr_bucket_brigade *bb)
518 ap_fputs(r->output_filters, bb, "</D:multistatus>" DEBUG_CR);
520 /* indicate the end of the response body */
521 b = apr_bucket_eos_create(r->connection->bucket_alloc);
522 APR_BRIGADE_INSERT_TAIL(bb, b);
524 /* deliver whatever might be remaining in the brigade */
525 return ap_pass_brigade(r->output_filters, bb);
528 static void dav_send_multistatus(request_rec *r, int status,
530 apr_array_header_t *namespaces)
533 apr_bucket_brigade *bb = apr_brigade_create(r->pool,
534 r->connection->bucket_alloc);
536 dav_begin_multistatus(bb, r, status, namespaces);
538 apr_pool_create(&subpool, r->pool);
540 for (; first != NULL; first = first->next) {
541 apr_pool_clear(subpool);
542 dav_send_one_response(first, bb, r->output_filters, subpool);
544 apr_pool_destroy(subpool);
546 dav_finish_multistatus(r, bb);
552 * Write error information to the log.
554 static void dav_log_err(request_rec *r, dav_error *err, int level)
559 /* ### should have a directive to log the first or all */
560 for (errscan = err; errscan != NULL; errscan = errscan->prev) {
561 if (errscan->desc == NULL)
564 if (errscan->save_errno != 0) {
565 errno = errscan->save_errno;
566 ap_log_rerror(APLOG_MARK, level, errno, r, "%s [%d, #%d]",
567 errscan->desc, errscan->status, errscan->error_id);
570 ap_log_rerror(APLOG_MARK, level, 0, r,
572 errscan->desc, errscan->status, errscan->error_id);
580 * Handle the standard error processing. <err> must be non-NULL.
582 * <response> is set by the following:
583 * - dav_validate_request()
585 * - repos_hooks->remove_resource
586 * - repos_hooks->move_resource
587 * - repos_hooks->copy_resource
588 * - vsn_hooks->update
590 static int dav_handle_err(request_rec *r, dav_error *err,
591 dav_response *response)
594 dav_log_err(r, err, APLOG_ERR);
596 if (response == NULL) {
597 dav_error *stackerr = err;
599 /* our error messages are safe; tell Apache this */
600 apr_table_setn(r->notes, "verbose-error-to", "*");
602 /* Didn't get a multistatus response passed in, but we still
603 might be able to generate a standard <D:error> response.
604 Search the error stack for an errortag. */
605 while (stackerr != NULL && stackerr->tagname == NULL)
606 stackerr = stackerr->prev;
608 if (stackerr != NULL && stackerr->tagname != NULL)
609 return dav_error_response_tag(r, stackerr);
614 /* send the multistatus and tell Apache the request/response is DONE. */
615 dav_send_multistatus(r, err->status, response, NULL);
619 /* handy function for return values of methods that (may) create things */
620 static int dav_created(request_rec *r, const char *locn, const char *what,
629 /* did the target resource already exist? */
631 /* Apache will supply a default message */
632 return HTTP_NO_CONTENT;
635 /* Per HTTP/1.1, S10.2.2: add a Location header to contain the
636 * URI that was created. */
638 /* Convert locn to an absolute URI, and return in Location header */
639 apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, locn, r));
641 /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */
643 /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
644 * we must manufacture the entire response. */
645 body = apr_psprintf(r->pool, "%s %s has been created.",
646 what, ap_escape_html(r->pool, locn));
647 return dav_error_response(r, HTTP_CREATED, body);
650 /* ### move to dav_util? */
651 DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth)
653 const char *depth = apr_table_get(r->headers_in, "Depth");
659 if (strcasecmp(depth, "infinity") == 0) {
662 else if (strcmp(depth, "0") == 0) {
665 else if (strcmp(depth, "1") == 0) {
669 /* The caller will return an HTTP_BAD_REQUEST. This will augment the
670 * default message that Apache provides. */
671 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
672 "An invalid Depth header was specified.");
676 static int dav_get_overwrite(request_rec *r)
678 const char *overwrite = apr_table_get(r->headers_in, "Overwrite");
680 if (overwrite == NULL) {
681 return 1; /* default is "T" */
684 if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') {
688 if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') {
692 /* The caller will return an HTTP_BAD_REQUEST. This will augment the
693 * default message that Apache provides. */
694 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
695 "An invalid Overwrite header was specified.");
699 /* resolve a request URI to a resource descriptor.
701 * If label_allowed != 0, then allow the request target to be altered by
704 * If use_checked_in is true, then the repository provider should return
705 * the resource identified by the DAV:checked-in property of the resource
706 * identified by the Request-URI.
708 static dav_error *dav_get_resource(request_rec *r, int label_allowed,
709 int use_checked_in, dav_resource **res_p)
712 const char *label = NULL;
715 /* if the request target can be overridden, get any target selector */
717 label = apr_table_get(r->headers_in, "label");
720 conf = ap_get_module_config(r->per_dir_config, &dav_module);
721 /* assert: conf->provider != NULL */
723 /* resolve the resource */
724 err = (*conf->provider->repos->get_resource)(r, conf->dir,
725 label, use_checked_in,
728 err = dav_push_error(r->pool, err->status, 0,
729 "Could not fetch resource information.", err);
733 /* Note: this shouldn't happen, but just be sure... */
734 if (*res_p == NULL) {
735 /* ### maybe use HTTP_INTERNAL_SERVER_ERROR */
736 return dav_new_error(r->pool, HTTP_NOT_FOUND, 0,
737 apr_psprintf(r->pool,
738 "The provider did not define a "
740 ap_escape_html(r->pool, r->uri)));
743 /* ### hmm. this doesn't feel like the right place or thing to do */
744 /* if there were any input headers requiring a Vary header in the response,
746 dav_add_vary_header(r, r, *res_p);
751 static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
753 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
760 /* open the thing lazily */
761 return (*hooks->open_lockdb)(r, ro, 0, lockdb);
764 static int dav_parse_range(request_rec *r,
765 apr_off_t *range_start, apr_off_t *range_end)
772 range_c = apr_table_get(r->headers_in, "content-range");
776 range = apr_pstrdup(r->pool, range_c);
777 if (strncasecmp(range, "bytes ", 6) != 0
778 || (dash = ap_strchr(range, '-')) == NULL
779 || (slash = ap_strchr(range, '/')) == NULL) {
780 /* malformed header. ignore it (per S14.16 of RFC2616) */
784 *dash = *slash = '\0';
786 *range_start = apr_atoi64(range + 6);
787 *range_end = apr_atoi64(dash + 1);
789 if (*range_end < *range_start
790 || (slash[1] != '*' && apr_atoi64(slash + 1) <= *range_end)) {
791 /* invalid range. ignore it (per S14.16 of RFC2616) */
795 /* we now have a valid range */
799 /* handle the GET method */
800 static int dav_method_get(request_rec *r)
802 dav_resource *resource;
805 /* This method should only be called when the resource is not
806 * visible to Apache. We will fetch the resource from the repository,
807 * then create a subrequest for Apache to handle.
809 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
812 return dav_handle_err(r, err, NULL);
814 if (!resource->exists) {
815 /* Apache will supply a default error for this. */
816 return HTTP_NOT_FOUND;
819 /* set up the HTTP headers for the response */
820 if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
821 err = dav_push_error(r->pool, err->status, 0,
822 "Unable to set up HTTP headers.",
824 return dav_handle_err(r, err, NULL);
827 if (r->header_only) {
831 /* okay... time to deliver the content */
832 if ((err = (*resource->hooks->deliver)(resource,
833 r->output_filters)) != NULL) {
834 err = dav_push_error(r->pool, err->status, 0,
835 "Unable to deliver content.",
837 return dav_handle_err(r, err, NULL);
843 /* validate resource/locks on POST, then pass to the default handler */
844 static int dav_method_post(request_rec *r)
846 dav_resource *resource;
849 /* Ask repository module to resolve the resource */
850 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
853 return dav_handle_err(r, err, NULL);
855 /* Note: depth == 0. Implies no need for a multistatus response. */
856 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
857 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
858 /* ### add a higher-level description? */
859 return dav_handle_err(r, err, NULL);
865 /* handle the PUT method */
866 static int dav_method_put(request_rec *r)
868 dav_resource *resource;
870 dav_auto_version_info av_info;
871 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
875 dav_stream_mode mode;
877 dav_response *multi_response;
879 apr_off_t range_start;
882 /* Ask repository module to resolve the resource */
883 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
886 return dav_handle_err(r, err, NULL);
888 /* If not a file or collection resource, PUT not allowed */
889 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
890 && resource->type != DAV_RESOURCE_TYPE_WORKING) {
891 body = apr_psprintf(r->pool,
892 "Cannot create resource %s with PUT.",
893 ap_escape_html(r->pool, r->uri));
894 return dav_error_response(r, HTTP_CONFLICT, body);
897 /* Cannot PUT a collection */
898 if (resource->collection) {
899 return dav_error_response(r, HTTP_CONFLICT,
900 "Cannot PUT to a collection.");
904 resource_state = dav_get_resource_state(r, resource);
907 * Note: depth == 0 normally requires no multistatus response. However,
908 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
909 * other than the Request-URI, thereby requiring a multistatus.
911 * If the resource does not exist (DAV_RESOURCE_NULL), then we must
912 * check the resource *and* its parent. If the resource exists or is
913 * a locknull resource, then we check only the resource.
915 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
916 resource_state == DAV_RESOURCE_NULL ?
917 DAV_VALIDATE_PARENT :
918 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
919 /* ### add a higher-level description? */
920 return dav_handle_err(r, err, multi_response);
923 /* make sure the resource can be modified (if versioning repository) */
924 if ((err = dav_auto_checkout(r, resource,
925 0 /* not parent_only */,
926 &av_info)) != NULL) {
927 /* ### add a higher-level description? */
928 return dav_handle_err(r, err, NULL);
931 /* truncate and rewrite the file unless we see a Content-Range */
932 mode = DAV_MODE_WRITE_TRUNC;
934 has_range = dav_parse_range(r, &range_start, &range_end);
936 mode = DAV_MODE_WRITE_SEEKABLE;
939 /* Create the new file in the repository */
940 if ((err = (*resource->hooks->open_stream)(resource, mode,
942 /* ### assuming FORBIDDEN is probably not quite right... */
943 err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
944 apr_psprintf(r->pool,
945 "Unable to PUT new contents for %s.",
946 ap_escape_html(r->pool, r->uri)),
950 if (err == NULL && has_range) {
951 /* a range was provided. seek to the start */
952 err = (*resource->hooks->seek_stream)(stream, range_start);
956 apr_bucket_brigade *bb;
960 bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
965 rc = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
966 APR_BLOCK_READ, DAV_READ_BLOCKSIZE);
968 if (rc != APR_SUCCESS) {
969 err = dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
970 "Could not get next bucket brigade");
974 for (b = APR_BRIGADE_FIRST(bb);
975 b != APR_BRIGADE_SENTINEL(bb);
976 b = APR_BUCKET_NEXT(b))
981 if (APR_BUCKET_IS_EOS(b)) {
986 if (APR_BUCKET_IS_METADATA(b)) {
990 rc = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
991 if (rc != APR_SUCCESS) {
992 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
993 "An error occurred while reading "
994 "the request body.");
999 /* write whatever we read, until we see an error */
1000 err = (*resource->hooks->write_stream)(stream, data, len);
1004 apr_brigade_cleanup(bb);
1005 } while (!seen_eos);
1007 apr_brigade_destroy(bb);
1009 err2 = (*resource->hooks->close_stream)(stream,
1010 err == NULL /* commit */);
1011 if (err2 != NULL && err == NULL) {
1012 /* no error during the write, but we hit one at close. use it. */
1018 * Ensure that we think the resource exists now.
1019 * ### eek. if an error occurred during the write and we did not commit,
1020 * ### then the resource might NOT exist (e.g. dav_fs_repos.c)
1023 resource->exists = 1;
1026 /* restore modifiability of resources back to what they were */
1027 err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */,
1028 0 /*unlock*/, &av_info);
1030 /* check for errors now */
1032 return dav_handle_err(r, err, NULL);
1036 /* just log a warning */
1037 err2 = dav_push_error(r->pool, err2->status, 0,
1038 "The PUT was successful, but there "
1039 "was a problem automatically checking in "
1040 "the resource or its parent collection.",
1042 dav_log_err(r, err2, APLOG_WARNING);
1045 /* ### place the Content-Type and Content-Language into the propdb */
1047 if (locks_hooks != NULL) {
1050 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1051 /* The file creation was successful, but the locking failed. */
1052 err = dav_push_error(r->pool, err->status, 0,
1053 "The file was PUT successfully, but there "
1054 "was a problem opening the lock database "
1055 "which prevents inheriting locks from the "
1056 "parent resources.",
1058 return dav_handle_err(r, err, NULL);
1061 /* notify lock system that we have created/replaced a resource */
1062 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
1064 (*locks_hooks->close_lockdb)(lockdb);
1067 /* The file creation was successful, but the locking failed. */
1068 err = dav_push_error(r->pool, err->status, 0,
1069 "The file was PUT successfully, but there "
1070 "was a problem updating its lock "
1073 return dav_handle_err(r, err, NULL);
1077 /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
1079 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
1080 return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
1084 /* Use POOL to temporarily construct a dav_response object (from WRES
1085 STATUS, and PROPSTATS) and stream it via WRES's ctx->brigade. */
1086 static void dav_stream_response(dav_walk_resource *wres,
1088 dav_get_props_result *propstats,
1091 dav_response resp = { 0 };
1092 dav_walker_ctx *ctx = wres->walk_ctx;
1094 resp.href = wres->resource->uri;
1095 resp.status = status;
1097 resp.propresult = *propstats;
1100 dav_send_one_response(&resp, ctx->bb, ctx->r->output_filters, pool);
1104 /* ### move this to dav_util? */
1105 DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
1106 int status, dav_get_props_result *propstats)
1110 /* just drop some data into an dav_response */
1111 resp = apr_pcalloc(wres->pool, sizeof(*resp));
1112 resp->href = apr_pstrdup(wres->pool, wres->resource->uri);
1113 resp->status = status;
1115 resp->propresult = *propstats;
1118 resp->next = wres->response;
1119 wres->response = resp;
1123 /* handle the DELETE method */
1124 static int dav_method_delete(request_rec *r)
1126 dav_resource *resource;
1127 dav_auto_version_info av_info;
1130 dav_response *multi_response;
1134 /* We don't use the request body right now, so torch it. */
1135 if ((result = ap_discard_request_body(r)) != OK) {
1139 /* Ask repository module to resolve the resource */
1140 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
1143 return dav_handle_err(r, err, NULL);
1144 if (!resource->exists) {
1145 /* Apache will supply a default error for this. */
1146 return HTTP_NOT_FOUND;
1149 /* 2518 says that depth must be infinity only for collections.
1150 * For non-collections, depth is ignored, unless it is an illegal value (1).
1152 depth = dav_get_depth(r, DAV_INFINITY);
1154 if (resource->collection && depth != DAV_INFINITY) {
1155 /* This supplies additional information for the default message. */
1156 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1157 "Depth must be \"infinity\" for DELETE of a collection.");
1158 return HTTP_BAD_REQUEST;
1161 if (!resource->collection && depth == 1) {
1162 /* This supplies additional information for the default message. */
1163 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1164 "Depth of \"1\" is not allowed for DELETE.");
1165 return HTTP_BAD_REQUEST;
1169 ** If any resources fail the lock/If: conditions, then we must fail
1170 ** the delete. Each of the failing resources will be listed within
1171 ** a DAV:multistatus body, wrapped into a 424 response.
1173 ** Note that a failure on the resource itself does not generate a
1174 ** multistatus response -- only internal members/collections.
1176 if ((err = dav_validate_request(r, resource, depth, NULL,
1179 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
1180 err = dav_push_error(r->pool, err->status, 0,
1181 apr_psprintf(r->pool,
1182 "Could not DELETE %s due to a failed "
1183 "precondition (e.g. locks).",
1184 ap_escape_html(r->pool, r->uri)),
1186 return dav_handle_err(r, err, multi_response);
1189 /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
1190 * locked by the token(s) in the if_header.
1192 if ((result = dav_unlock(r, resource, NULL)) != OK) {
1196 /* if versioned resource, make sure parent is checked out */
1197 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
1198 &av_info)) != NULL) {
1199 /* ### add a higher-level description? */
1200 return dav_handle_err(r, err, NULL);
1203 /* try to remove the resource */
1204 err = (*resource->hooks->remove_resource)(resource, &multi_response);
1206 /* restore writability of parent back to what it was */
1207 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
1208 0 /*unlock*/, &av_info);
1210 /* check for errors now */
1212 err = dav_push_error(r->pool, err->status, 0,
1213 apr_psprintf(r->pool,
1214 "Could not DELETE %s.",
1215 ap_escape_html(r->pool, r->uri)),
1217 return dav_handle_err(r, err, multi_response);
1220 /* just log a warning */
1221 err = dav_push_error(r->pool, err2->status, 0,
1222 "The DELETE was successful, but there "
1223 "was a problem automatically checking in "
1224 "the parent collection.",
1226 dav_log_err(r, err, APLOG_WARNING);
1229 /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
1231 /* Apache will supply a default error for this. */
1232 return HTTP_NO_CONTENT;
1235 /* generate DAV:supported-method-set OPTIONS response */
1236 static dav_error *dav_gen_supported_methods(request_rec *r,
1237 const apr_xml_elem *elem,
1238 const apr_table_t *methods,
1239 apr_text_header *body)
1241 const apr_array_header_t *arr;
1242 const apr_table_entry_t *elts;
1243 apr_xml_elem *child;
1248 apr_text_append(r->pool, body, "<D:supported-method-set>" DEBUG_CR);
1250 if (elem->first_child == NULL) {
1251 /* show all supported methods */
1252 arr = apr_table_elts(methods);
1253 elts = (const apr_table_entry_t *)arr->elts;
1255 for (i = 0; i < arr->nelts; ++i) {
1256 if (elts[i].key == NULL)
1259 s = apr_psprintf(r->pool,
1260 "<D:supported-method D:name=\"%s\"/>"
1263 apr_text_append(r->pool, body, s);
1267 /* check for support of specific methods */
1268 for (child = elem->first_child; child != NULL; child = child->next) {
1269 if (child->ns == APR_XML_NS_DAV_ID
1270 && strcmp(child->name, "supported-method") == 0) {
1271 const char *name = NULL;
1273 /* go through attributes to find method name */
1274 for (attr = child->attr; attr != NULL; attr = attr->next) {
1275 if (attr->ns == APR_XML_NS_DAV_ID
1276 && strcmp(attr->name, "name") == 0)
1281 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1282 "A DAV:supported-method element "
1283 "does not have a \"name\" attribute");
1286 /* see if method is supported */
1287 if (apr_table_get(methods, name) != NULL) {
1288 s = apr_psprintf(r->pool,
1289 "<D:supported-method D:name=\"%s\"/>"
1292 apr_text_append(r->pool, body, s);
1298 apr_text_append(r->pool, body, "</D:supported-method-set>" DEBUG_CR);
1302 /* generate DAV:supported-live-property-set OPTIONS response */
1303 static dav_error *dav_gen_supported_live_props(request_rec *r,
1304 const dav_resource *resource,
1305 const apr_xml_elem *elem,
1306 apr_text_header *body)
1310 apr_xml_elem *child;
1314 /* open lock database, to report on supported lock properties */
1315 /* ### should open read-only */
1316 if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
1317 return dav_push_error(r->pool, err->status, 0,
1318 "The lock database could not be opened, "
1319 "preventing the reporting of supported lock "
1324 /* open the property database (readonly) for the resource */
1325 if ((err = dav_open_propdb(r, lockdb, resource, 1, NULL,
1326 &propdb)) != NULL) {
1328 (*lockdb->hooks->close_lockdb)(lockdb);
1330 return dav_push_error(r->pool, err->status, 0,
1331 "The property database could not be opened, "
1332 "preventing report of supported properties.",
1336 apr_text_append(r->pool, body, "<D:supported-live-property-set>" DEBUG_CR);
1338 if (elem->first_child == NULL) {
1339 /* show all supported live properties */
1340 dav_get_props_result props = dav_get_allprops(propdb, DAV_PROP_INSERT_SUPPORTED);
1341 body->last->next = props.propstats;
1342 while (body->last->next != NULL)
1343 body->last = body->last->next;
1346 /* check for support of specific live property */
1347 for (child = elem->first_child; child != NULL; child = child->next) {
1348 if (child->ns == APR_XML_NS_DAV_ID
1349 && strcmp(child->name, "supported-live-property") == 0) {
1350 const char *name = NULL;
1351 const char *nmspace = NULL;
1353 /* go through attributes to find name and namespace */
1354 for (attr = child->attr; attr != NULL; attr = attr->next) {
1355 if (attr->ns == APR_XML_NS_DAV_ID) {
1356 if (strcmp(attr->name, "name") == 0)
1358 else if (strcmp(attr->name, "namespace") == 0)
1359 nmspace = attr->value;
1364 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1365 "A DAV:supported-live-property "
1366 "element does not have a \"name\" "
1371 /* default namespace to DAV: */
1372 if (nmspace == NULL)
1375 /* check for support of property */
1376 dav_get_liveprop_supported(propdb, nmspace, name, body);
1381 apr_text_append(r->pool, body, "</D:supported-live-property-set>" DEBUG_CR);
1383 dav_close_propdb(propdb);
1386 (*lockdb->hooks->close_lockdb)(lockdb);
1391 /* generate DAV:supported-report-set OPTIONS response */
1392 static dav_error *dav_gen_supported_reports(request_rec *r,
1393 const dav_resource *resource,
1394 const apr_xml_elem *elem,
1395 const dav_hooks_vsn *vsn_hooks,
1396 apr_text_header *body)
1398 apr_xml_elem *child;
1403 apr_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR);
1405 if (vsn_hooks != NULL) {
1406 const dav_report_elem *reports;
1407 const dav_report_elem *rp;
1409 if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) {
1410 return dav_push_error(r->pool, err->status, 0,
1411 "DAV:supported-report-set could not be "
1412 "determined due to a problem fetching the "
1413 "available reports for this resource.",
1417 if (reports != NULL) {
1418 if (elem->first_child == NULL) {
1419 /* show all supported reports */
1420 for (rp = reports; rp->nmspace != NULL; ++rp) {
1421 /* Note: we presume reports->namespace is
1422 * properly XML/URL quoted */
1423 s = apr_psprintf(r->pool,
1424 "<D:supported-report D:name=\"%s\" "
1425 "D:namespace=\"%s\"/>" DEBUG_CR,
1426 rp->name, rp->nmspace);
1427 apr_text_append(r->pool, body, s);
1431 /* check for support of specific report */
1432 for (child = elem->first_child; child != NULL; child = child->next) {
1433 if (child->ns == APR_XML_NS_DAV_ID
1434 && strcmp(child->name, "supported-report") == 0) {
1435 const char *name = NULL;
1436 const char *nmspace = NULL;
1438 /* go through attributes to find name and namespace */
1439 for (attr = child->attr; attr != NULL; attr = attr->next) {
1440 if (attr->ns == APR_XML_NS_DAV_ID) {
1441 if (strcmp(attr->name, "name") == 0)
1443 else if (strcmp(attr->name, "namespace") == 0)
1444 nmspace = attr->value;
1449 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1450 "A DAV:supported-report element "
1451 "does not have a \"name\" attribute");
1454 /* default namespace to DAV: */
1455 if (nmspace == NULL)
1458 for (rp = reports; rp->nmspace != NULL; ++rp) {
1459 if (strcmp(name, rp->name) == 0
1460 && strcmp(nmspace, rp->nmspace) == 0) {
1461 /* Note: we presume reports->nmspace is
1462 * properly XML/URL quoted
1464 s = apr_psprintf(r->pool,
1465 "<D:supported-report "
1467 "D:namespace=\"%s\"/>"
1469 rp->name, rp->nmspace);
1470 apr_text_append(r->pool, body, s);
1480 apr_text_append(r->pool, body, "</D:supported-report-set>" DEBUG_CR);
1485 /* handle the SEARCH method */
1486 static int dav_method_search(request_rec *r)
1488 const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
1489 dav_resource *resource;
1491 dav_response *multi_status;
1493 /* If no search provider, decline the request */
1494 if (search_hooks == NULL)
1497 /* This method should only be called when the resource is not
1498 * visible to Apache. We will fetch the resource from the repository,
1499 * then create a subrequest for Apache to handle.
1501 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
1504 return dav_handle_err(r, err, NULL);
1506 if (!resource->exists) {
1507 /* Apache will supply a default error for this. */
1508 return HTTP_NOT_FOUND;
1511 /* set up the HTTP headers for the response */
1512 if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
1513 err = dav_push_error(r->pool, err->status, 0,
1514 "Unable to set up HTTP headers.",
1516 return dav_handle_err(r, err, NULL);
1519 if (r->header_only) {
1523 /* okay... time to search the content */
1524 /* Let's validate XML and process walk function
1525 * in the hook function
1527 if ((err = (*search_hooks->search_resource)(r, &multi_status)) != NULL) {
1528 /* ### add a higher-level description? */
1529 return dav_handle_err(r, err, NULL);
1532 /* We have results in multi_status */
1533 /* Should I pass namespace?? */
1534 dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
1540 /* handle the OPTIONS method */
1541 static int dav_method_options(request_rec *r)
1543 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1544 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1545 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
1546 const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
1547 dav_resource *resource;
1548 const char *dav_level;
1551 const apr_array_header_t *arr;
1552 const apr_table_entry_t *elts;
1553 apr_table_t *methods = apr_table_make(r->pool, 12);
1554 apr_text_header vsn_options = { 0 };
1555 apr_text_header body = { 0 };
1560 apr_array_header_t *uri_ary;
1562 const apr_xml_elem *elem;
1565 /* resolve the resource */
1566 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
1569 return dav_handle_err(r, err, NULL);
1571 /* parse any request body */
1572 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1575 /* note: doc == NULL if no request body */
1577 if (doc && !dav_validate_root(doc, "options")) {
1578 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1579 "The \"options\" element was not found.");
1580 return HTTP_BAD_REQUEST;
1583 /* determine which providers are available */
1586 if (locks_hooks != NULL) {
1590 if (binding_hooks != NULL)
1591 dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
1594 * MSFT Web Folders chokes if length of DAV header value > 63 characters!
1595 * To workaround that, we use separate DAV headers for versioning and
1596 * live prop provider namespace URIs.
1599 apr_table_setn(r->headers_out, "DAV", dav_level);
1602 * If there is a versioning provider, generate DAV headers
1603 * for versioning options.
1605 if (vsn_hooks != NULL) {
1606 (*vsn_hooks->get_vsn_options)(r->pool, &vsn_options);
1608 for (t = vsn_options.first; t != NULL; t = t->next)
1609 apr_table_addn(r->headers_out, "DAV", t->text);
1613 * Gather property set URIs from all the liveprop providers,
1614 * and generate a separate DAV header for each URI, to avoid
1615 * problems with long header lengths.
1617 uri_ary = apr_array_make(r->pool, 5, sizeof(const char *));
1618 dav_run_gather_propsets(uri_ary);
1619 for (i = 0; i < uri_ary->nelts; ++i) {
1620 if (((char **)uri_ary->elts)[i] != NULL)
1621 apr_table_addn(r->headers_out, "DAV", ((char **)uri_ary->elts)[i]);
1624 /* this tells MSFT products to skip looking for FrontPage extensions */
1625 apr_table_setn(r->headers_out, "MS-Author-Via", "DAV");
1628 * Determine which methods are allowed on the resource.
1629 * Three cases: resource is null (3), is lock-null (7.4), or exists.
1631 * All cases support OPTIONS, and if there is a lock provider, LOCK.
1632 * (Lock-) null resources also support MKCOL and PUT.
1633 * Lock-null supports PROPFIND and UNLOCK.
1634 * Existing resources support lots of stuff.
1637 apr_table_addn(methods, "OPTIONS", "");
1639 /* ### take into account resource type */
1640 switch (dav_get_resource_state(r, resource))
1642 case DAV_RESOURCE_EXISTS:
1643 /* resource exists */
1644 apr_table_addn(methods, "GET", "");
1645 apr_table_addn(methods, "HEAD", "");
1646 apr_table_addn(methods, "POST", "");
1647 apr_table_addn(methods, "DELETE", "");
1648 apr_table_addn(methods, "TRACE", "");
1649 apr_table_addn(methods, "PROPFIND", "");
1650 apr_table_addn(methods, "PROPPATCH", "");
1651 apr_table_addn(methods, "COPY", "");
1652 apr_table_addn(methods, "MOVE", "");
1654 if (!resource->collection)
1655 apr_table_addn(methods, "PUT", "");
1657 if (locks_hooks != NULL) {
1658 apr_table_addn(methods, "LOCK", "");
1659 apr_table_addn(methods, "UNLOCK", "");
1664 case DAV_RESOURCE_LOCK_NULL:
1665 /* resource is lock-null. */
1666 apr_table_addn(methods, "MKCOL", "");
1667 apr_table_addn(methods, "PROPFIND", "");
1668 apr_table_addn(methods, "PUT", "");
1670 if (locks_hooks != NULL) {
1671 apr_table_addn(methods, "LOCK", "");
1672 apr_table_addn(methods, "UNLOCK", "");
1677 case DAV_RESOURCE_NULL:
1678 /* resource is null. */
1679 apr_table_addn(methods, "MKCOL", "");
1680 apr_table_addn(methods, "PUT", "");
1682 if (locks_hooks != NULL)
1683 apr_table_addn(methods, "LOCK", "");
1688 /* ### internal error! */
1692 /* If there is a versioning provider, add versioning methods */
1693 if (vsn_hooks != NULL) {
1694 if (!resource->exists) {
1695 if ((*vsn_hooks->versionable)(resource))
1696 apr_table_addn(methods, "VERSION-CONTROL", "");
1698 if (vsn_hooks->can_be_workspace != NULL
1699 && (*vsn_hooks->can_be_workspace)(resource))
1700 apr_table_addn(methods, "MKWORKSPACE", "");
1702 if (vsn_hooks->can_be_activity != NULL
1703 && (*vsn_hooks->can_be_activity)(resource))
1704 apr_table_addn(methods, "MKACTIVITY", "");
1706 else if (!resource->versioned) {
1707 if ((*vsn_hooks->versionable)(resource))
1708 apr_table_addn(methods, "VERSION-CONTROL", "");
1710 else if (resource->working) {
1711 apr_table_addn(methods, "CHECKIN", "");
1713 /* ### we might not support this DeltaV option */
1714 apr_table_addn(methods, "UNCHECKOUT", "");
1716 else if (vsn_hooks->add_label != NULL) {
1717 apr_table_addn(methods, "CHECKOUT", "");
1718 apr_table_addn(methods, "LABEL", "");
1721 apr_table_addn(methods, "CHECKOUT", "");
1725 /* If there is a bindings provider, see if resource is bindable */
1726 if (binding_hooks != NULL
1727 && (*binding_hooks->is_bindable)(resource)) {
1728 apr_table_addn(methods, "BIND", "");
1731 /* If there is a search provider, set SEARCH in option */
1732 if (search_hooks != NULL) {
1733 apr_table_addn(methods, "SEARCH", "");
1736 /* Generate the Allow header */
1737 arr = apr_table_elts(methods);
1738 elts = (const apr_table_entry_t *)arr->elts;
1741 /* first, compute total length */
1742 for (i = 0; i < arr->nelts; ++i) {
1743 if (elts[i].key == NULL)
1746 /* add 1 for comma or null */
1747 text_size += strlen(elts[i].key) + 1;
1750 s = allow = apr_palloc(r->pool, text_size);
1752 for (i = 0; i < arr->nelts; ++i) {
1753 if (elts[i].key == NULL)
1759 strcpy(s, elts[i].key);
1763 apr_table_setn(r->headers_out, "Allow", allow);
1766 /* If there is search set_option_head function, set head */
1767 /* DASL: <DAV:basicsearch>
1768 * DASL: <http://foo.bar.com/syntax1>
1769 * DASL: <http://akuma.com/syntax2>
1771 if (search_hooks != NULL
1772 && *search_hooks->set_option_head != NULL) {
1773 if ((err = (*search_hooks->set_option_head)(r)) != NULL) {
1774 return dav_handle_err(r, err, NULL);
1778 /* if there was no request body, then there is no response body */
1780 ap_set_content_length(r, 0);
1782 /* ### this sends a Content-Type. the default OPTIONS does not. */
1784 /* ### the default (ap_send_http_options) returns OK, but I believe
1785 * ### that is because it is the default handler and nothing else
1786 * ### will run after the thing. */
1790 /* handle each options request */
1791 for (elem = doc->root->first_child; elem != NULL; elem = elem->next) {
1792 /* check for something we recognize first */
1793 int core_option = 0;
1794 dav_error *err = NULL;
1796 if (elem->ns == APR_XML_NS_DAV_ID) {
1797 if (strcmp(elem->name, "supported-method-set") == 0) {
1798 err = dav_gen_supported_methods(r, elem, methods, &body);
1801 else if (strcmp(elem->name, "supported-live-property-set") == 0) {
1802 err = dav_gen_supported_live_props(r, resource, elem, &body);
1805 else if (strcmp(elem->name, "supported-report-set") == 0) {
1806 err = dav_gen_supported_reports(r, resource, elem, vsn_hooks, &body);
1812 return dav_handle_err(r, err, NULL);
1814 /* if unrecognized option, pass to versioning provider */
1815 if (!core_option && vsn_hooks != NULL) {
1816 if ((err = (*vsn_hooks->get_option)(resource, elem, &body))
1818 return dav_handle_err(r, err, NULL);
1823 /* send the options response */
1824 r->status = HTTP_OK;
1825 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
1827 /* send the headers and response body */
1828 ap_rputs(DAV_XML_HEADER DEBUG_CR
1829 "<D:options-response xmlns:D=\"DAV:\">" DEBUG_CR, r);
1831 for (t = body.first; t != NULL; t = t->next)
1832 ap_rputs(t->text, r);
1834 ap_rputs("</D:options-response>" DEBUG_CR, r);
1836 /* we've sent everything necessary to the client. */
1840 static void dav_cache_badprops(dav_walker_ctx *ctx)
1842 const apr_xml_elem *elem;
1843 apr_text_header hdr = { 0 };
1845 /* just return if we built the thing already */
1846 if (ctx->propstat_404 != NULL) {
1850 apr_text_append(ctx->w.pool, &hdr,
1851 "<D:propstat>" DEBUG_CR
1852 "<D:prop>" DEBUG_CR);
1854 elem = dav_find_child(ctx->doc->root, "prop");
1855 for (elem = elem->first_child; elem; elem = elem->next) {
1856 apr_text_append(ctx->w.pool, &hdr,
1857 apr_xml_empty_elem(ctx->w.pool, elem));
1860 apr_text_append(ctx->w.pool, &hdr,
1861 "</D:prop>" DEBUG_CR
1862 "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
1863 "</D:propstat>" DEBUG_CR);
1865 ctx->propstat_404 = hdr.first;
1868 static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
1870 dav_walker_ctx *ctx = wres->walk_ctx;
1873 dav_get_props_result propstats = { 0 };
1876 ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
1877 ** dav_get_allprops() does not need to do namespace translation,
1880 ** Note: we cast to lose the "const". The propdb won't try to change
1881 ** the resource, however, since we are opening readonly.
1883 err = dav_open_propdb(ctx->r, ctx->w.lockdb, wres->resource, 1,
1884 ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
1886 /* ### do something with err! */
1888 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1889 dav_get_props_result badprops = { 0 };
1891 /* some props were expected on this collection/resource */
1892 dav_cache_badprops(ctx);
1893 badprops.propstats = ctx->propstat_404;
1894 dav_stream_response(wres, 0, &badprops, ctx->scratchpool);
1897 /* no props on this collection/resource */
1898 dav_stream_response(wres, HTTP_OK, NULL, ctx->scratchpool);
1901 apr_pool_clear(ctx->scratchpool);
1904 /* ### what to do about closing the propdb on server failure? */
1906 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1907 propstats = dav_get_props(propdb, ctx->doc);
1910 dav_prop_insert what = ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP
1911 ? DAV_PROP_INSERT_VALUE
1912 : DAV_PROP_INSERT_NAME;
1913 propstats = dav_get_allprops(propdb, what);
1915 dav_close_propdb(propdb);
1917 dav_stream_response(wres, 0, &propstats, ctx->scratchpool);
1919 /* at this point, ctx->scratchpool has been used to stream a
1920 single response. this function fully controls the pool, and
1921 thus has the right to clear it for the next iteration of this
1923 apr_pool_clear(ctx->scratchpool);
1928 /* handle the PROPFIND method */
1929 static int dav_method_propfind(request_rec *r)
1931 dav_resource *resource;
1936 const apr_xml_elem *child;
1937 dav_walker_ctx ctx = { { 0 } };
1938 dav_response *multi_status;
1940 /* Ask repository module to resolve the resource */
1941 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
1944 return dav_handle_err(r, err, NULL);
1946 if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
1947 /* Apache will supply a default error for this. */
1948 return HTTP_NOT_FOUND;
1951 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
1952 /* dav_get_depth() supplies additional information for the
1953 * default message. */
1954 return HTTP_BAD_REQUEST;
1957 if (depth == DAV_INFINITY && resource->collection) {
1959 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
1961 /* default is to DISALLOW these requests */
1962 if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
1963 return dav_error_response(r, HTTP_FORBIDDEN,
1964 apr_psprintf(r->pool,
1965 "PROPFIND requests with a "
1966 "Depth of \"infinity\" are "
1967 "not allowed for %s.",
1968 ap_escape_html(r->pool,
1973 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1976 /* note: doc == NULL if no request body */
1978 if (doc && !dav_validate_root(doc, "propfind")) {
1979 /* This supplies additional information for the default message. */
1980 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1981 "The \"propfind\" element was not found.");
1982 return HTTP_BAD_REQUEST;
1985 /* ### validate that only one of these three elements is present */
1988 || (child = dav_find_child(doc->root, "allprop")) != NULL) {
1989 /* note: no request body implies allprop */
1990 ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
1992 else if ((child = dav_find_child(doc->root, "propname")) != NULL) {
1993 ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
1995 else if ((child = dav_find_child(doc->root, "prop")) != NULL) {
1996 ctx.propfind_type = DAV_PROPFIND_IS_PROP;
1999 /* "propfind" element must have one of the above three children */
2001 /* This supplies additional information for the default message. */
2002 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2003 "The \"propfind\" element does not contain one of "
2004 "the required child elements (the specific command).");
2005 return HTTP_BAD_REQUEST;
2008 ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
2009 ctx.w.func = dav_propfind_walker;
2010 ctx.w.walk_ctx = &ctx;
2011 ctx.w.pool = r->pool;
2012 ctx.w.root = resource;
2016 ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
2017 apr_pool_create(&ctx.scratchpool, r->pool);
2019 /* ### should open read-only */
2020 if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
2021 err = dav_push_error(r->pool, err->status, 0,
2022 "The lock database could not be opened, "
2023 "preventing access to the various lock "
2024 "properties for the PROPFIND.",
2026 return dav_handle_err(r, err, NULL);
2028 if (ctx.w.lockdb != NULL) {
2029 /* if we have a lock database, then we can walk locknull resources */
2030 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
2033 /* send <multistatus> tag, with all doc->namespaces attached. */
2035 /* NOTE: we *cannot* leave out the doc's namespaces from the
2036 initial <multistatus> tag. if a 404 was generated for an HREF,
2037 then we need to spit out the doc's namespaces for use by the
2038 404. Note that <response> elements will override these ns0,
2039 ns1, etc, but NOT within the <response> scope for the
2041 dav_begin_multistatus(ctx.bb, r, HTTP_MULTI_STATUS,
2042 doc ? doc->namespaces : NULL);
2044 /* Have the provider walk the resource. */
2045 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
2047 if (ctx.w.lockdb != NULL) {
2048 (*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb);
2052 /* If an error occurred during the resource walk, there's
2053 basically nothing we can do but abort the connection and
2054 log an error. This is one of the limitations of HTTP; it
2055 needs to "know" the entire status of the response before
2056 generating it, which is just impossible in these streamy
2057 response situations. */
2058 err = dav_push_error(r->pool, err->status, 0,
2059 "Provider encountered an error while streaming"
2060 " a multistatus PROPFIND response.", err);
2061 dav_log_err(r, err, APLOG_ERR);
2062 r->connection->aborted = 1;
2066 dav_finish_multistatus(r, ctx.bb);
2067 ap_filter_flush(ctx.bb, r->output_filters);
2069 /* the response has been sent. */
2073 static apr_text * dav_failed_proppatch(apr_pool_t *p,
2074 apr_array_header_t *prop_ctx)
2076 apr_text_header hdr = { 0 };
2077 int i = prop_ctx->nelts;
2078 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
2079 dav_error *err424_set = NULL;
2080 dav_error *err424_delete = NULL;
2083 /* ### might be nice to sort by status code and description */
2085 for ( ; i-- > 0; ++ctx ) {
2086 apr_text_append(p, &hdr,
2087 "<D:propstat>" DEBUG_CR
2089 apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
2090 apr_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
2092 if (ctx->err == NULL) {
2093 /* nothing was assigned here yet, so make it a 424 */
2095 if (ctx->operation == DAV_PROP_OP_SET) {
2096 if (err424_set == NULL)
2097 err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
2098 "Attempted DAV:set operation "
2099 "could not be completed due "
2100 "to other errors.");
2101 ctx->err = err424_set;
2103 else if (ctx->operation == DAV_PROP_OP_DELETE) {
2104 if (err424_delete == NULL)
2105 err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
2106 "Attempted DAV:remove "
2107 "operation could not be "
2108 "completed due to other "
2110 ctx->err = err424_delete;
2116 "HTTP/1.1 %d (status)"
2117 "</D:status>" DEBUG_CR,
2119 apr_text_append(p, &hdr, s);
2121 /* ### we should use compute_desc if necessary... */
2122 if (ctx->err->desc != NULL) {
2123 apr_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
2124 apr_text_append(p, &hdr, ctx->err->desc);
2125 apr_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
2128 apr_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
2134 static apr_text * dav_success_proppatch(apr_pool_t *p, apr_array_header_t *prop_ctx)
2136 apr_text_header hdr = { 0 };
2137 int i = prop_ctx->nelts;
2138 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
2141 * ### we probably need to revise the way we assemble the response...
2142 * ### this code assumes everything will return status==200.
2145 apr_text_append(p, &hdr,
2146 "<D:propstat>" DEBUG_CR
2147 "<D:prop>" DEBUG_CR);
2149 for ( ; i-- > 0; ++ctx ) {
2150 apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
2153 apr_text_append(p, &hdr,
2154 "</D:prop>" DEBUG_CR
2155 "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
2156 "</D:propstat>" DEBUG_CR);
2161 static void dav_prop_log_errors(dav_prop_ctx *ctx)
2163 dav_log_err(ctx->r, ctx->err, APLOG_ERR);
2167 * Call <func> for each context. This can stop when an error occurs, or
2168 * simply iterate through the whole list.
2170 * Returns 1 if an error occurs (and the iteration is aborted). Returns 0
2171 * if all elements are processed.
2173 * If <reverse> is true (non-zero), then the list is traversed in
2176 static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
2177 apr_array_header_t *ctx_list, int stop_on_error,
2180 int i = ctx_list->nelts;
2181 dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
2191 if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
2202 /* handle the PROPPATCH method */
2203 static int dav_method_proppatch(request_rec *r)
2206 dav_resource *resource;
2209 apr_xml_elem *child;
2212 dav_response resp = { 0 };
2213 apr_text *propstat_text;
2214 apr_array_header_t *ctx_list;
2216 dav_auto_version_info av_info;
2218 /* Ask repository module to resolve the resource */
2219 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2222 return dav_handle_err(r, err, NULL);
2223 if (!resource->exists) {
2224 /* Apache will supply a default error for this. */
2225 return HTTP_NOT_FOUND;
2228 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
2231 /* note: doc == NULL if no request body */
2233 if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
2234 /* This supplies additional information for the default message. */
2235 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2236 "The request body does not contain "
2237 "a \"propertyupdate\" element.");
2238 return HTTP_BAD_REQUEST;
2241 /* Check If-Headers and existing locks */
2242 /* Note: depth == 0. Implies no need for a multistatus response. */
2243 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
2244 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2245 /* ### add a higher-level description? */
2246 return dav_handle_err(r, err, NULL);
2249 /* make sure the resource can be modified (if versioning repository) */
2250 if ((err = dav_auto_checkout(r, resource,
2251 0 /* not parent_only */,
2252 &av_info)) != NULL) {
2253 /* ### add a higher-level description? */
2254 return dav_handle_err(r, err, NULL);
2257 if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
2258 &propdb)) != NULL) {
2259 /* undo any auto-checkout */
2260 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
2262 err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2263 apr_psprintf(r->pool,
2264 "Could not open the property "
2266 ap_escape_html(r->pool, r->uri)),
2268 return dav_handle_err(r, err, NULL);
2270 /* ### what to do about closing the propdb on server failure? */
2272 /* ### validate "live" properties */
2274 /* set up an array to hold property operation contexts */
2275 ctx_list = apr_array_make(r->pool, 10, sizeof(dav_prop_ctx));
2277 /* do a first pass to ensure that all "remove" properties exist */
2278 for (child = doc->root->first_child; child; child = child->next) {
2280 apr_xml_elem *prop_group;
2281 apr_xml_elem *one_prop;
2283 /* Ignore children that are not set/remove */
2284 if (child->ns != APR_XML_NS_DAV_ID
2285 || (!(is_remove = strcmp(child->name, "remove") == 0)
2286 && strcmp(child->name, "set") != 0)) {
2290 /* make sure that a "prop" child exists for set/remove */
2291 if ((prop_group = dav_find_child(child, "prop")) == NULL) {
2292 dav_close_propdb(propdb);
2294 /* undo any auto-checkout */
2295 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
2297 /* This supplies additional information for the default message. */
2298 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2299 "A \"prop\" element is missing inside "
2300 "the propertyupdate command.");
2301 return HTTP_BAD_REQUEST;
2304 for (one_prop = prop_group->first_child; one_prop;
2305 one_prop = one_prop->next) {
2307 ctx = (dav_prop_ctx *)apr_array_push(ctx_list);
2308 ctx->propdb = propdb;
2309 ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
2310 ctx->prop = one_prop;
2312 ctx->r = r; /* for later use by dav_prop_log_errors() */
2314 dav_prop_validate(ctx);
2316 if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
2322 /* ### should test that we found at least one set/remove */
2324 /* execute all of the operations */
2325 if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
2329 /* generate a failure/success response */
2331 (void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
2332 propstat_text = dav_failed_proppatch(r->pool, ctx_list);
2335 (void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
2336 propstat_text = dav_success_proppatch(r->pool, ctx_list);
2339 /* make sure this gets closed! */
2340 dav_close_propdb(propdb);
2342 /* complete any auto-versioning */
2343 dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info);
2345 /* log any errors that occurred */
2346 (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
2348 resp.href = resource->uri;
2350 /* ### should probably use something new to pass along this text... */
2351 resp.propresult.propstats = propstat_text;
2353 dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
2355 /* the response has been sent. */
2359 static int process_mkcol_body(request_rec *r)
2361 /* This is snarfed from ap_setup_client_block(). We could get pretty
2362 * close to this behavior by passing REQUEST_NO_BODY, but we need to
2363 * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
2364 * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
2366 const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
2367 const char *lenp = apr_table_get(r->headers_in, "Content-Length");
2369 /* make sure to set the Apache request fields properly. */
2370 r->read_body = REQUEST_NO_BODY;
2371 r->read_chunked = 0;
2375 if (strcasecmp(tenc, "chunked")) {
2376 /* Use this instead of Apache's default error string */
2377 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2378 "Unknown Transfer-Encoding %s", tenc);
2379 return HTTP_NOT_IMPLEMENTED;
2382 r->read_chunked = 1;
2385 const char *pos = lenp;
2387 while (apr_isdigit(*pos) || apr_isspace(*pos)) {
2392 /* This supplies additional information for the default message. */
2393 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2394 "Invalid Content-Length %s", lenp);
2395 return HTTP_BAD_REQUEST;
2398 r->remaining = apr_atoi64(lenp);
2401 if (r->read_chunked || r->remaining > 0) {
2402 /* ### log something? */
2404 /* Apache will supply a default error for this. */
2405 return HTTP_UNSUPPORTED_MEDIA_TYPE;
2409 * Get rid of the body. this will call ap_setup_client_block(), but
2410 * our copy above has already verified its work.
2412 return ap_discard_request_body(r);
2415 /* handle the MKCOL method */
2416 static int dav_method_mkcol(request_rec *r)
2418 dav_resource *resource;
2420 dav_auto_version_info av_info;
2421 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2426 dav_response *multi_status;
2428 /* handle the request body */
2429 /* ### this may move lower once we start processing bodies */
2430 if ((result = process_mkcol_body(r)) != OK) {
2434 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
2437 /* Ask repository module to resolve the resource */
2438 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2441 return dav_handle_err(r, err, NULL);
2443 if (resource->exists) {
2444 /* oops. something was already there! */
2446 /* Apache will supply a default error for this. */
2447 /* ### we should provide a specific error message! */
2448 return HTTP_METHOD_NOT_ALLOWED;
2451 resource_state = dav_get_resource_state(r, resource);
2454 * Check If-Headers and existing locks.
2456 * Note: depth == 0 normally requires no multistatus response. However,
2457 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
2458 * other than the Request-URI, thereby requiring a multistatus.
2460 * If the resource does not exist (DAV_RESOURCE_NULL), then we must
2461 * check the resource *and* its parent. If the resource exists or is
2462 * a locknull resource, then we check only the resource.
2464 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
2465 resource_state == DAV_RESOURCE_NULL ?
2466 DAV_VALIDATE_PARENT :
2467 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2468 /* ### add a higher-level description? */
2469 return dav_handle_err(r, err, multi_status);
2472 /* if versioned resource, make sure parent is checked out */
2473 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
2474 &av_info)) != NULL) {
2475 /* ### add a higher-level description? */
2476 return dav_handle_err(r, err, NULL);
2479 /* try to create the collection */
2480 resource->collection = 1;
2481 err = (*resource->hooks->create_collection)(resource);
2483 /* restore modifiability of parent back to what it was */
2484 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2485 0 /*unlock*/, &av_info);
2487 /* check for errors now */
2489 return dav_handle_err(r, err, NULL);
2492 /* just log a warning */
2493 err = dav_push_error(r->pool, err2->status, 0,
2494 "The MKCOL was successful, but there "
2495 "was a problem automatically checking in "
2496 "the parent collection.",
2498 dav_log_err(r, err, APLOG_WARNING);
2501 if (locks_hooks != NULL) {
2504 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2505 /* The directory creation was successful, but the locking failed. */
2506 err = dav_push_error(r->pool, err->status, 0,
2507 "The MKCOL was successful, but there "
2508 "was a problem opening the lock database "
2509 "which prevents inheriting locks from the "
2510 "parent resources.",
2512 return dav_handle_err(r, err, NULL);
2515 /* notify lock system that we have created/replaced a resource */
2516 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
2518 (*locks_hooks->close_lockdb)(lockdb);
2521 /* The dir creation was successful, but the locking failed. */
2522 err = dav_push_error(r->pool, err->status, 0,
2523 "The MKCOL was successful, but there "
2524 "was a problem updating its lock "
2527 return dav_handle_err(r, err, NULL);
2531 /* return an appropriate response (HTTP_CREATED) */
2532 return dav_created(r, NULL, "Collection", 0);
2535 /* handle the COPY and MOVE methods */
2536 static int dav_method_copymove(request_rec *r, int is_move)
2538 dav_resource *resource;
2539 dav_resource *resnew;
2540 dav_auto_version_info src_av_info = { 0 };
2541 dav_auto_version_info dst_av_info = { 0 };
2547 dav_response *multi_response;
2548 dav_lookup_result lookup;
2557 /* Ask repository module to resolve the resource */
2558 err = dav_get_resource(r, !is_move /* label_allowed */,
2559 0 /* use_checked_in */, &resource);
2561 return dav_handle_err(r, err, NULL);
2563 if (!resource->exists) {
2564 /* Apache will supply a default error for this. */
2565 return HTTP_NOT_FOUND;
2568 /* If not a file or collection resource, COPY/MOVE not allowed */
2569 /* ### allow COPY/MOVE of DeltaV resource types */
2570 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
2571 body = apr_psprintf(r->pool,
2572 "Cannot COPY/MOVE resource %s.",
2573 ap_escape_html(r->pool, r->uri));
2574 return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
2577 /* get the destination URI */
2578 dest = apr_table_get(r->headers_in, "Destination");
2580 /* Look in headers provided by Netscape's Roaming Profiles */
2581 const char *nscp_host = apr_table_get(r->headers_in, "Host");
2582 const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
2584 if (nscp_host != NULL && nscp_path != NULL)
2585 dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
2588 /* This supplies additional information for the default message. */
2589 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2590 "The request is missing a Destination header.");
2591 return HTTP_BAD_REQUEST;
2594 lookup = dav_lookup_uri(dest, r, 1 /* must_be_absolute */);
2595 if (lookup.rnew == NULL) {
2596 if (lookup.err.status == HTTP_BAD_REQUEST) {
2597 /* This supplies additional information for the default message. */
2598 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2599 "%s", lookup.err.desc);
2600 return HTTP_BAD_REQUEST;
2603 /* ### this assumes that dav_lookup_uri() only generates a status
2604 * ### that Apache can provide a status line for!! */
2606 return dav_error_response(r, lookup.err.status, lookup.err.desc);
2608 if (lookup.rnew->status != HTTP_OK) {
2609 const char *auth = apr_table_get(lookup.rnew->err_headers_out,
2610 "WWW-Authenticate");
2611 if (lookup.rnew->status == HTTP_UNAUTHORIZED && auth != NULL) {
2612 /* propagate the WWW-Authorization header up from the
2613 * subreq so the client sees it. */
2614 apr_table_set(r->err_headers_out, "WWW-Authenticate",
2615 apr_pstrdup(r->pool, auth));
2618 /* ### how best to report this... */
2619 return dav_error_response(r, lookup.rnew->status,
2620 "Destination URI had an error.");
2623 /* Resolve destination resource */
2624 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
2625 0 /* use_checked_in */, &resnew);
2627 return dav_handle_err(r, err, NULL);
2629 /* are the two resources handled by the same repository? */
2630 if (resource->hooks != resnew->hooks) {
2631 /* ### this message exposes some backend config, but screw it... */
2632 return dav_error_response(r, HTTP_BAD_GATEWAY,
2633 "Destination URI is handled by a "
2634 "different repository than the source URI. "
2635 "MOVE or COPY between repositories is "
2639 /* get and parse the overwrite header value */
2640 if ((overwrite = dav_get_overwrite(r)) < 0) {
2641 /* dav_get_overwrite() supplies additional information for the
2642 * default message. */
2643 return HTTP_BAD_REQUEST;
2646 /* quick failure test: if dest exists and overwrite is false. */
2647 if (resnew->exists && !overwrite) {
2648 /* Supply some text for the error response body. */
2649 return dav_error_response(r, HTTP_PRECONDITION_FAILED,
2650 "Destination is not empty and "
2651 "Overwrite is not \"T\"");
2654 /* are the source and destination the same? */
2655 if ((*resource->hooks->is_same_resource)(resource, resnew)) {
2656 /* Supply some text for the error response body. */
2657 return dav_error_response(r, HTTP_FORBIDDEN,
2658 "Source and Destination URIs are the same.");
2662 is_dir = resource->collection;
2664 /* get and parse the Depth header value. "0" and "infinity" are legal. */
2665 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
2666 /* dav_get_depth() supplies additional information for the
2667 * default message. */
2668 return HTTP_BAD_REQUEST;
2671 /* This supplies additional information for the default message. */
2672 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2673 "Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
2674 return HTTP_BAD_REQUEST;
2676 if (is_move && is_dir && depth != DAV_INFINITY) {
2677 /* This supplies additional information for the default message. */
2678 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2679 "Depth must be \"infinity\" when moving a collection.");
2680 return HTTP_BAD_REQUEST;
2684 * Check If-Headers and existing locks for each resource in the source
2685 * if we are performing a MOVE. We will return a 424 response with a
2686 * DAV:multistatus body. The multistatus responses will contain the
2687 * information about any resource that fails the validation.
2689 * We check the parent resource, too, since this is a MOVE. Moving the
2690 * resource effectively removes it from the parent collection, so we
2691 * must ensure that we have met the appropriate conditions.
2693 * If a problem occurs with the Request-URI itself, then a plain error
2694 * (rather than a multistatus) will be returned.
2697 && (err = dav_validate_request(r, resource, depth, NULL,
2700 | DAV_VALIDATE_USE_424,
2702 err = dav_push_error(r->pool, err->status, 0,
2703 apr_psprintf(r->pool,
2704 "Could not MOVE %s due to a failed "
2705 "precondition on the source "
2707 ap_escape_html(r->pool, r->uri)),
2709 return dav_handle_err(r, err, multi_response);
2713 * Check If-Headers and existing locks for destination. Note that we
2714 * use depth==infinity since the target (hierarchy) will be deleted
2715 * before the move/copy is completed.
2717 * Note that we are overwriting the target, which implies a DELETE, so
2718 * we are subject to the error/response rules as a DELETE. Namely, we
2719 * will return a 424 error if any of the validations fail.
2720 * (see dav_method_delete() for more information)
2722 if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
2725 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
2726 err = dav_push_error(r->pool, err->status, 0,
2727 apr_psprintf(r->pool,
2728 "Could not MOVE/COPY %s due to a "
2729 "failed precondition on the "
2730 "destination (e.g. locks).",
2731 ap_escape_html(r->pool, r->uri)),
2733 return dav_handle_err(r, err, multi_response);
2737 && depth == DAV_INFINITY
2738 && (*resource->hooks->is_parent_resource)(resource, resnew)) {
2739 /* Supply some text for the error response body. */
2740 return dav_error_response(r, HTTP_FORBIDDEN,
2741 "Source collection contains the "
2746 && (*resnew->hooks->is_parent_resource)(resnew, resource)) {
2747 /* The destination must exist (since it contains the source), and
2748 * a condition above implies Overwrite==T. Obviously, we cannot
2749 * delete the Destination before the MOVE/COPY, as that would
2750 * delete the Source.
2753 /* Supply some text for the error response body. */
2754 return dav_error_response(r, HTTP_FORBIDDEN,
2755 "Destination collection contains the Source "
2756 "and Overwrite has been specified.");
2759 /* ### for now, we don't need anything in the body */
2760 if ((result = ap_discard_request_body(r)) != OK) {
2764 if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
2765 /* ### add a higher-level description? */
2766 return dav_handle_err(r, err, NULL);
2769 /* remove any locks from the old resources */
2771 * ### this is Yet Another Traversal. if we do a rename(), then we
2772 * ### really don't have to do this in some cases since the inode
2773 * ### values will remain constant across the move. but we can't
2774 * ### know that fact from outside the provider :-(
2776 * ### note that we now have a problem atomicity in the move/copy
2777 * ### since a failure after this would have removed locks (technically,
2778 * ### this is okay to do, but really...)
2780 if (is_move && lockdb != NULL) {
2781 /* ### this is wrong! it blasts direct locks on parent resources */
2782 /* ### pass lockdb! */
2783 (void)dav_unlock(r, resource, NULL);
2786 /* if this is a move, then the source parent collection will be modified */
2788 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
2789 &src_av_info)) != NULL) {
2791 (*lockdb->hooks->close_lockdb)(lockdb);
2793 /* ### add a higher-level description? */
2794 return dav_handle_err(r, err, NULL);
2799 * Remember the initial state of the destination, so the lock system
2800 * can be notified as to how it changed.
2802 resnew_state = dav_get_resource_state(lookup.rnew, resnew);
2804 /* In a MOVE operation, the destination is replaced by the source.
2805 * In a COPY operation, if the destination exists, is under version
2806 * control, and is the same resource type as the source,
2807 * then it should not be replaced, but modified to be a copy of
2810 if (!resnew->exists)
2812 else if (is_move || !resource->versioned)
2814 else if (resource->type != resnew->type)
2816 else if ((resource->collection == 0) != (resnew->collection == 0))
2821 /* If the destination must be created or replaced,
2822 * make sure the parent collection is writable
2824 if (!resnew->exists || replace_dest) {
2825 if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/,
2826 &dst_av_info)) != NULL) {
2827 /* could not make destination writable:
2828 * if move, restore state of source parent
2831 (void)dav_auto_checkin(r, NULL, 1 /* undo */,
2832 0 /*unlock*/, &src_av_info);
2836 (*lockdb->hooks->close_lockdb)(lockdb);
2838 /* ### add a higher-level description? */
2839 return dav_handle_err(r, err, NULL);
2843 /* If source and destination parents are the same, then
2844 * use the same resource object, so status updates to one are reflected
2845 * in the other, when doing auto-versioning. Otherwise,
2846 * we may try to checkin the parent twice.
2848 if (src_av_info.parent_resource != NULL
2849 && dst_av_info.parent_resource != NULL
2850 && (*src_av_info.parent_resource->hooks->is_same_resource)
2851 (src_av_info.parent_resource, dst_av_info.parent_resource)) {
2853 dst_av_info.parent_resource = src_av_info.parent_resource;
2856 /* If destination is being replaced, remove it first
2857 * (we know Ovewrite must be TRUE). Then try to copy/move the resource.
2860 err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
2864 err = (*resource->hooks->move_resource)(resource, resnew,
2867 err = (*resource->hooks->copy_resource)(resource, resnew, depth,
2871 /* perform any auto-versioning cleanup */
2872 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2873 0 /*unlock*/, &dst_av_info);
2876 err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2877 0 /*unlock*/, &src_av_info);
2882 /* check for error from remove/copy/move operations */
2885 (*lockdb->hooks->close_lockdb)(lockdb);
2887 err = dav_push_error(r->pool, err->status, 0,
2888 apr_psprintf(r->pool,
2889 "Could not MOVE/COPY %s.",
2890 ap_escape_html(r->pool, r->uri)),
2892 return dav_handle_err(r, err, multi_response);
2895 /* check for errors from auto-versioning */
2897 /* just log a warning */
2898 err = dav_push_error(r->pool, err2->status, 0,
2899 "The MOVE/COPY was successful, but there was a "
2900 "problem automatically checking in the "
2901 "source parent collection.",
2903 dav_log_err(r, err, APLOG_WARNING);
2906 /* just log a warning */
2907 err = dav_push_error(r->pool, err3->status, 0,
2908 "The MOVE/COPY was successful, but there was a "
2909 "problem automatically checking in the "
2910 "destination or its parent collection.",
2912 dav_log_err(r, err, APLOG_WARNING);
2915 /* propagate any indirect locks at the target */
2916 if (lockdb != NULL) {
2918 /* notify lock system that we have created/replaced a resource */
2919 err = dav_notify_created(r, lockdb, resnew, resnew_state, depth);
2921 (*lockdb->hooks->close_lockdb)(lockdb);
2924 /* The move/copy was successful, but the locking failed. */
2925 err = dav_push_error(r->pool, err->status, 0,
2926 "The MOVE/COPY was successful, but there "
2927 "was a problem updating the lock "
2930 return dav_handle_err(r, err, NULL);
2934 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
2935 return dav_created(r, lookup.rnew->uri, "Destination",
2936 resnew_state == DAV_RESOURCE_EXISTS);
2939 /* dav_method_lock: Handler to implement the DAV LOCK method
2940 * Returns appropriate HTTP_* response.
2942 static int dav_method_lock(request_rec *r)
2945 dav_resource *resource;
2946 const dav_hooks_locks *locks_hooks;
2949 int new_lock_request = 0;
2952 dav_response *multi_response = NULL;
2956 /* If no locks provider, decline the request */
2957 locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2958 if (locks_hooks == NULL)
2961 if ((result = ap_xml_parse_input(r, &doc)) != OK)
2964 depth = dav_get_depth(r, DAV_INFINITY);
2965 if (depth != 0 && depth != DAV_INFINITY) {
2966 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2967 "Depth must be 0 or \"infinity\" for LOCK.");
2968 return HTTP_BAD_REQUEST;
2971 /* Ask repository module to resolve the resource */
2972 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2975 return dav_handle_err(r, err, NULL);
2978 * Open writable. Unless an error occurs, we'll be
2979 * writing into the database.
2981 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2982 /* ### add a higher-level description? */
2983 return dav_handle_err(r, err, NULL);
2987 if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
2989 /* ### add a higher-level description to err? */
2992 new_lock_request = 1;
2994 lock->auth_user = apr_pstrdup(r->pool, r->user);
2997 resource_state = dav_get_resource_state(r, resource);
3000 * Check If-Headers and existing locks.
3002 * If this will create a locknull resource, then the LOCK will affect
3003 * the parent collection (much like a PUT/MKCOL). For that case, we must
3004 * validate the parent resource's conditions.
3006 if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
3007 (resource_state == DAV_RESOURCE_NULL
3008 ? DAV_VALIDATE_PARENT
3009 : DAV_VALIDATE_RESOURCE)
3010 | (new_lock_request ? lock->scope : 0)
3011 | DAV_VALIDATE_ADD_LD,
3013 err = dav_push_error(r->pool, err->status, 0,
3014 apr_psprintf(r->pool,
3015 "Could not LOCK %s due to a failed "
3016 "precondition (e.g. other locks).",
3017 ap_escape_html(r->pool, r->uri)),
3022 if (new_lock_request == 0) {
3023 dav_locktoken_list *ltl;
3027 * ### Assumption: We can renew multiple locks on the same resource
3028 * ### at once. First harvest all the positive lock-tokens given in
3029 * ### the If header. Then modify the lock entries for this resource
3030 * ### with the new Timeout val.
3033 if ((err = dav_get_locktoken_list(r, <l)) != NULL) {
3034 err = dav_push_error(r->pool, err->status, 0,
3035 apr_psprintf(r->pool,
3036 "The lock refresh for %s failed "
3037 "because no lock tokens were "
3038 "specified in an \"If:\" "
3040 ap_escape_html(r->pool, r->uri)),
3045 if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
3048 /* ### add a higher-level description to err? */
3052 /* New lock request */
3053 char *locktoken_txt;
3056 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
3059 /* apply lower bound (if any) from DAVMinTimeout directive */
3060 if (lock->timeout != DAV_TIMEOUT_INFINITE
3061 && lock->timeout < time(NULL) + conf->locktimeout)
3062 lock->timeout = time(NULL) + conf->locktimeout;
3064 err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
3066 /* ### add a higher-level description to err? */
3070 locktoken_txt = apr_pstrcat(r->pool, "<",
3071 (*locks_hooks->format_locktoken)(r->pool,
3075 apr_table_set(r->headers_out, "Lock-Token", locktoken_txt);
3078 (*locks_hooks->close_lockdb)(lockdb);
3080 r->status = HTTP_OK;
3081 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
3083 ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
3085 ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
3088 "<D:lockdiscovery>" DEBUG_CR
3090 "</D:lockdiscovery>" DEBUG_CR,
3091 dav_lock_get_activelock(r, lock, NULL));
3093 ap_rputs("</D:prop>", r);
3095 /* the response has been sent. */
3099 (*locks_hooks->close_lockdb)(lockdb);
3100 return dav_handle_err(r, err, multi_response);
3103 /* dav_method_unlock: Handler to implement the DAV UNLOCK method
3104 * Returns appropriate HTTP_* response.
3106 static int dav_method_unlock(request_rec *r)
3109 dav_resource *resource;
3110 const dav_hooks_locks *locks_hooks;
3112 const char *const_locktoken_txt;
3113 char *locktoken_txt;
3114 dav_locktoken *locktoken = NULL;
3116 dav_response *multi_response;
3118 /* If no locks provider, decline the request */
3119 locks_hooks = DAV_GET_HOOKS_LOCKS(r);
3120 if (locks_hooks == NULL)
3123 if ((const_locktoken_txt = apr_table_get(r->headers_in,
3124 "Lock-Token")) == NULL) {
3125 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3126 "Unlock failed (%s): "
3127 "No Lock-Token specified in header", r->filename);
3128 return HTTP_BAD_REQUEST;
3131 locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt);
3132 if (locktoken_txt[0] != '<') {
3133 /* ### should provide more specifics... */
3134 return HTTP_BAD_REQUEST;
3138 if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
3139 /* ### should provide more specifics... */
3140 return HTTP_BAD_REQUEST;
3142 locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
3144 if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
3145 &locktoken)) != NULL) {
3146 err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
3147 apr_psprintf(r->pool,
3148 "The UNLOCK on %s failed -- an "
3149 "invalid lock token was specified "
3150 "in the \"If:\" header.",
3151 ap_escape_html(r->pool, r->uri)),
3153 return dav_handle_err(r, err, NULL);
3156 /* Ask repository module to resolve the resource */
3157 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3160 return dav_handle_err(r, err, NULL);
3162 resource_state = dav_get_resource_state(r, resource);
3165 * Check If-Headers and existing locks.
3167 * Note: depth == 0 normally requires no multistatus response. However,
3168 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
3169 * other than the Request-URI, thereby requiring a multistatus.
3171 * If the resource is a locknull resource, then the UNLOCK will affect
3172 * the parent collection (much like a delete). For that case, we must
3173 * validate the parent resource's conditions.
3175 if ((err = dav_validate_request(r, resource, 0, locktoken,
3177 resource_state == DAV_RESOURCE_LOCK_NULL
3178 ? DAV_VALIDATE_PARENT
3179 : DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
3180 /* ### add a higher-level description? */
3181 return dav_handle_err(r, err, multi_response);
3184 /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
3185 * _all_ resources locked by locktoken are released. It does not say
3186 * resource has to be the root of an infinte lock. Thus, an UNLOCK
3187 * on any part of an infinte lock will remove the lock on all resources.
3189 * For us, if r->filename represents an indirect lock (part of an infinity lock),
3190 * we must actually perform an UNLOCK on the direct lock for this resource.
3192 if ((result = dav_unlock(r, resource, locktoken)) != OK) {
3196 return HTTP_NO_CONTENT;
3199 static int dav_method_vsn_control(request_rec *r)
3201 dav_resource *resource;
3203 dav_auto_version_info av_info;
3204 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
3205 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3208 const char *target = NULL;
3211 /* if no versioning provider, decline the request */
3212 if (vsn_hooks == NULL)
3215 /* ask repository module to resolve the resource */
3216 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3219 return dav_handle_err(r, err, NULL);
3221 /* remember the pre-creation resource state */
3222 resource_state = dav_get_resource_state(r, resource);
3224 /* parse the request body (may be a version-control element) */
3225 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3228 /* note: doc == NULL if no request body */
3231 const apr_xml_elem *child;
3234 if (!dav_validate_root(doc, "version-control")) {
3235 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3236 "The request body does not contain "
3237 "a \"version-control\" element.");
3238 return HTTP_BAD_REQUEST;
3241 /* get the version URI */
3242 if ((child = dav_find_child(doc->root, "version")) == NULL) {
3243 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3244 "The \"version-control\" element does not contain "
3245 "a \"version\" element.");
3246 return HTTP_BAD_REQUEST;
3249 if ((child = dav_find_child(child, "href")) == NULL) {
3250 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3251 "The \"version\" element does not contain "
3252 "an \"href\" element.");
3253 return HTTP_BAD_REQUEST;
3256 /* get version URI */
3257 apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
3260 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3261 "An \"href\" element does not contain a URI.");
3262 return HTTP_BAD_REQUEST;
3266 /* Check request preconditions */
3268 /* ### need a general mechanism for reporting precondition violations
3269 * ### (should be returning XML document for 403/409 responses)
3272 /* if not versioning existing resource, must specify version to select */
3273 if (!resource->exists && target == NULL) {
3274 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3275 "<DAV:initial-version-required/>");
3276 return dav_handle_err(r, err, NULL);
3278 else if (resource->exists) {
3279 /* cannot add resource to existing version history */
3280 if (target != NULL) {
3281 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3282 "<DAV:cannot-add-to-existing-history/>");
3283 return dav_handle_err(r, err, NULL);
3286 /* resource must be unversioned and versionable, or version selector */
3287 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3288 || (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
3289 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3290 "<DAV:must-be-versionable/>");
3291 return dav_handle_err(r, err, NULL);
3294 /* the DeltaV spec says if resource is a version selector,
3295 * then VERSION-CONTROL is a no-op
3297 if (resource->versioned) {
3298 /* set the Cache-Control header, per the spec */
3299 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3302 ap_set_content_length(r, 0);
3308 /* Check If-Headers and existing locks */
3309 /* Note: depth == 0. Implies no need for a multistatus response. */
3310 if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
3311 resource_state == DAV_RESOURCE_NULL ?
3312 DAV_VALIDATE_PARENT :
3313 DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
3314 return dav_handle_err(r, err, NULL);
3317 /* if in versioned collection, make sure parent is checked out */
3318 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
3319 &av_info)) != NULL) {
3320 return dav_handle_err(r, err, NULL);
3323 /* attempt to version-control the resource */
3324 if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
3325 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
3326 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3327 apr_psprintf(r->pool,
3328 "Could not VERSION-CONTROL resource %s.",
3329 ap_escape_html(r->pool, r->uri)),
3331 return dav_handle_err(r, err, NULL);
3334 /* revert writability of parent directory */
3335 err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info);
3337 /* just log a warning */
3338 err = dav_push_error(r->pool, err->status, 0,
3339 "The VERSION-CONTROL was successful, but there "
3340 "was a problem automatically checking in "
3341 "the parent collection.",
3343 dav_log_err(r, err, APLOG_WARNING);
3346 /* if the resource is lockable, let lock system know of new resource */
3347 if (locks_hooks != NULL
3348 && (*locks_hooks->get_supportedlock)(resource) != NULL) {
3351 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
3352 /* The resource creation was successful, but the locking failed. */
3353 err = dav_push_error(r->pool, err->status, 0,
3354 "The VERSION-CONTROL was successful, but there "
3355 "was a problem opening the lock database "
3356 "which prevents inheriting locks from the "
3357 "parent resources.",
3359 return dav_handle_err(r, err, NULL);
3362 /* notify lock system that we have created/replaced a resource */
3363 err = dav_notify_created(r, lockdb, resource, resource_state, 0);
3365 (*locks_hooks->close_lockdb)(lockdb);
3368 /* The dir creation was successful, but the locking failed. */
3369 err = dav_push_error(r->pool, err->status, 0,
3370 "The VERSION-CONTROL was successful, but there "
3371 "was a problem updating its lock "
3374 return dav_handle_err(r, err, NULL);
3378 /* set the Cache-Control header, per the spec */
3379 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3381 /* return an appropriate response (HTTP_CREATED) */
3382 return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
3385 /* handle the CHECKOUT method */
3386 static int dav_method_checkout(request_rec *r)
3388 dav_resource *resource;
3389 dav_resource *working_resource;
3390 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3394 int apply_to_vsn = 0;
3395 int is_unreserved = 0;
3397 int create_activity = 0;
3398 apr_array_header_t *activities = NULL;
3400 /* If no versioning provider, decline the request */
3401 if (vsn_hooks == NULL)
3404 if ((result = ap_xml_parse_input(r, &doc)) != OK)
3408 const apr_xml_elem *aset;
3410 if (!dav_validate_root(doc, "checkout")) {
3411 /* This supplies additional information for the default msg. */
3412 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3413 "The request body, if present, must be a "
3414 "DAV:checkout element.");
3415 return HTTP_BAD_REQUEST;
3418 if (dav_find_child(doc->root, "apply-to-version") != NULL) {
3419 if (apr_table_get(r->headers_in, "label") != NULL) {
3420 /* ### we want generic 403/409 XML reporting here */
3421 /* ### DAV:must-not-have-label-and-apply-to-version */
3422 return dav_error_response(r, HTTP_CONFLICT,
3423 "DAV:apply-to-version cannot be "
3424 "used in conjunction with a "
3430 is_unreserved = dav_find_child(doc->root, "unreserved") != NULL;
3431 is_fork_ok = dav_find_child(doc->root, "fork-ok") != NULL;
3433 if ((aset = dav_find_child(doc->root, "activity-set")) != NULL) {
3434 if (dav_find_child(aset, "new") != NULL) {
3435 create_activity = 1;
3438 const apr_xml_elem *child = aset->first_child;
3440 activities = apr_array_make(r->pool, 1, sizeof(const char *));
3442 for (; child != NULL; child = child->next) {
3443 if (child->ns == APR_XML_NS_DAV_ID
3444 && strcmp(child->name, "href") == 0) {
3447 href = dav_xml_get_cdata(child, r->pool,
3448 1 /* strip_white */);
3449 *(const char **)apr_array_push(activities) = href;
3453 if (activities->nelts == 0) {
3454 /* no href's is a DTD violation:
3455 <!ELEMENT activity-set (href+ | new)>
3458 /* This supplies additional info for the default msg. */
3459 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3460 "Within the DAV:activity-set element, the "
3461 "DAV:new element must be used, or at least "
3462 "one DAV:href must be specified.");
3463 return HTTP_BAD_REQUEST;
3469 /* Ask repository module to resolve the resource */
3470 err = dav_get_resource(r, 1 /*label_allowed*/, apply_to_vsn, &resource);
3472 return dav_handle_err(r, err, NULL);
3474 if (!resource->exists) {
3475 /* Apache will supply a default error for this. */
3476 return HTTP_NOT_FOUND;
3479 /* Check the state of the resource: must be a file or collection,
3480 * must be versioned, and must not already be checked out.
3482 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3483 && resource->type != DAV_RESOURCE_TYPE_VERSION) {
3484 return dav_error_response(r, HTTP_CONFLICT,
3485 "Cannot checkout this type of resource.");
3488 if (!resource->versioned) {
3489 return dav_error_response(r, HTTP_CONFLICT,
3490 "Cannot checkout unversioned resource.");
3493 if (resource->working) {
3494 return dav_error_response(r, HTTP_CONFLICT,
3495 "The resource is already checked out to the workspace.");
3498 /* ### do lock checks, once behavior is defined */
3500 /* Do the checkout */
3501 if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/,
3502 is_unreserved, is_fork_ok,
3503 create_activity, activities,
3504 &working_resource)) != NULL) {
3505 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3506 apr_psprintf(r->pool,
3507 "Could not CHECKOUT resource %s.",
3508 ap_escape_html(r->pool, r->uri)),
3510 return dav_handle_err(r, err, NULL);
3513 /* set the Cache-Control header, per the spec */
3514 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3516 /* if no working resource created, return OK,
3517 * else return CREATED with working resource URL in Location header
3519 if (working_resource == NULL) {
3521 ap_set_content_length(r, 0);
3525 return dav_created(r, working_resource->uri, "Checked-out resource", 0);
3528 /* handle the UNCHECKOUT method */
3529 static int dav_method_uncheckout(request_rec *r)
3531 dav_resource *resource;
3532 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3536 /* If no versioning provider, decline the request */
3537 if (vsn_hooks == NULL)
3540 if ((result = ap_discard_request_body(r)) != OK) {
3544 /* Ask repository module to resolve the resource */
3545 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3548 return dav_handle_err(r, err, NULL);
3550 if (!resource->exists) {
3551 /* Apache will supply a default error for this. */
3552 return HTTP_NOT_FOUND;
3555 /* Check the state of the resource: must be a file or collection,
3556 * must be versioned, and must be checked out.
3558 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3559 return dav_error_response(r, HTTP_CONFLICT,
3560 "Cannot uncheckout this type of resource.");
3563 if (!resource->versioned) {
3564 return dav_error_response(r, HTTP_CONFLICT,
3565 "Cannot uncheckout unversioned resource.");
3568 if (!resource->working) {
3569 return dav_error_response(r, HTTP_CONFLICT,
3570 "The resource is not checked out to the workspace.");
3573 /* ### do lock checks, once behavior is defined */
3575 /* Do the uncheckout */
3576 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
3577 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3578 apr_psprintf(r->pool,
3579 "Could not UNCHECKOUT resource %s.",
3580 ap_escape_html(r->pool, r->uri)),
3582 return dav_handle_err(r, err, NULL);
3586 ap_set_content_length(r, 0);
3591 /* handle the CHECKIN method */
3592 static int dav_method_checkin(request_rec *r)
3594 dav_resource *resource;
3595 dav_resource *new_version;
3596 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3600 int keep_checked_out = 0;
3602 /* If no versioning provider, decline the request */
3603 if (vsn_hooks == NULL)
3606 if ((result = ap_xml_parse_input(r, &doc)) != OK)
3610 if (!dav_validate_root(doc, "checkin")) {
3611 /* This supplies additional information for the default msg. */
3612 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3613 "The request body, if present, must be a "
3614 "DAV:checkin element.");
3615 return HTTP_BAD_REQUEST;
3618 keep_checked_out = dav_find_child(doc->root, "keep-checked-out") != NULL;
3621 /* Ask repository module to resolve the resource */
3622 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3625 return dav_handle_err(r, err, NULL);
3627 if (!resource->exists) {
3628 /* Apache will supply a default error for this. */
3629 return HTTP_NOT_FOUND;
3632 /* Check the state of the resource: must be a file or collection,
3633 * must be versioned, and must be checked out.
3635 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3636 return dav_error_response(r, HTTP_CONFLICT,
3637 "Cannot checkin this type of resource.");
3640 if (!resource->versioned) {
3641 return dav_error_response(r, HTTP_CONFLICT,
3642 "Cannot checkin unversioned resource.");
3645 if (!resource->working) {
3646 return dav_error_response(r, HTTP_CONFLICT,
3647 "The resource is not checked out.");
3650 /* ### do lock checks, once behavior is defined */
3652 /* Do the checkin */
3653 if ((err = (*vsn_hooks->checkin)(resource, keep_checked_out, &new_version))
3655 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3656 apr_psprintf(r->pool,
3657 "Could not CHECKIN resource %s.",
3658 ap_escape_html(r->pool, r->uri)),
3660 return dav_handle_err(r, err, NULL);
3663 return dav_created(r, new_version->uri, "Version", 0);
3666 static int dav_method_update(request_rec *r)
3668 dav_resource *resource;
3669 dav_resource *version = NULL;
3670 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3672 apr_xml_elem *child;
3678 dav_response *multi_response;
3680 dav_lookup_result lookup;
3682 /* If no versioning provider, or UPDATE not supported,
3683 * decline the request */
3684 if (vsn_hooks == NULL || vsn_hooks->update == NULL)
3687 if ((depth = dav_get_depth(r, 0)) < 0) {
3688 /* dav_get_depth() supplies additional information for the
3689 * default message. */
3690 return HTTP_BAD_REQUEST;
3693 /* parse the request body */
3694 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3698 if (doc == NULL || !dav_validate_root(doc, "update")) {
3699 /* This supplies additional information for the default message. */
3700 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3701 "The request body does not contain "
3702 "an \"update\" element.");
3703 return HTTP_BAD_REQUEST;
3706 /* check for label-name or version element, but not both */
3707 if ((child = dav_find_child(doc->root, "label-name")) != NULL)
3709 else if ((child = dav_find_child(doc->root, "version")) != NULL) {
3710 /* get the href element */
3711 if ((child = dav_find_child(child, "href")) == NULL) {
3712 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3713 "The version element does not contain "
3714 "an \"href\" element.");
3715 return HTTP_BAD_REQUEST;
3719 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3720 "The \"update\" element does not contain "
3721 "a \"label-name\" or \"version\" element.");
3722 return HTTP_BAD_REQUEST;
3725 /* a depth greater than zero is only allowed for a label */
3726 if (!is_label && depth != 0) {
3727 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3728 "Depth must be zero for UPDATE with a version");
3729 return HTTP_BAD_REQUEST;
3732 /* get the target value (a label or a version URI) */
3733 apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
3736 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3737 "A \"label-name\" or \"href\" element does not contain "
3739 return HTTP_BAD_REQUEST;
3742 /* Ask repository module to resolve the resource */
3743 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3746 return dav_handle_err(r, err, NULL);
3748 if (!resource->exists) {
3749 /* Apache will supply a default error for this. */
3750 return HTTP_NOT_FOUND;
3753 /* ### need a general mechanism for reporting precondition violations
3754 * ### (should be returning XML document for 403/409 responses)
3756 if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3757 || !resource->versioned || resource->working) {
3758 return dav_error_response(r, HTTP_CONFLICT,
3759 "<DAV:must-be-checked-in-version-controlled-resource>");
3762 /* if target is a version, resolve the version resource */
3763 /* ### dav_lookup_uri only allows absolute URIs; is that OK? */
3765 lookup = dav_lookup_uri(target, r, 0 /* must_be_absolute */);
3766 if (lookup.rnew == NULL) {
3767 if (lookup.err.status == HTTP_BAD_REQUEST) {
3768 /* This supplies additional information for the default message. */
3769 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3770 "%s", lookup.err.desc);
3771 return HTTP_BAD_REQUEST;
3774 /* ### this assumes that dav_lookup_uri() only generates a status
3775 * ### that Apache can provide a status line for!! */
3777 return dav_error_response(r, lookup.err.status, lookup.err.desc);
3779 if (lookup.rnew->status != HTTP_OK) {
3780 /* ### how best to report this... */
3781 return dav_error_response(r, lookup.rnew->status,
3782 "Version URI had an error.");
3785 /* resolve version resource */
3786 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
3787 0 /* use_checked_in */, &version);
3789 return dav_handle_err(r, err, NULL);
3791 /* NULL out target, since we're using a version resource */
3795 /* do the UPDATE operation */
3796 err = (*vsn_hooks->update)(resource, version, target, depth, &multi_response);
3799 err = dav_push_error(r->pool, err->status, 0,
3800 apr_psprintf(r->pool,
3801 "Could not UPDATE %s.",
3802 ap_escape_html(r->pool, r->uri)),
3804 return dav_handle_err(r, err, multi_response);
3807 /* set the Cache-Control header, per the spec */
3808 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3811 ap_set_content_length(r, 0);
3816 /* context maintained during LABEL treewalk */
3817 typedef struct dav_label_walker_ctx
3822 /* label being manipulated */
3825 /* label operation */
3827 #define DAV_LABEL_ADD 1
3828 #define DAV_LABEL_SET 2
3829 #define DAV_LABEL_REMOVE 3
3831 /* version provider hooks */
3832 const dav_hooks_vsn *vsn_hooks;
3834 } dav_label_walker_ctx;
3836 static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
3838 dav_label_walker_ctx *ctx = wres->walk_ctx;
3839 dav_error *err = NULL;
3841 /* Check the state of the resource: must be a version or
3842 * non-checkedout version selector
3844 /* ### need a general mechanism for reporting precondition violations
3845 * ### (should be returning XML document for 403/409 responses)
3847 if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
3848 (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
3849 || !wres->resource->versioned)) {
3850 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3851 "<DAV:must-be-version-or-version-selector/>");
3853 else if (wres->resource->working) {
3854 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3855 "<DAV:must-not-be-checked-out/>");
3858 /* do the label operation */
3859 if (ctx->label_op == DAV_LABEL_REMOVE)
3860 err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
3862 err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
3863 ctx->label_op == DAV_LABEL_SET);
3867 /* ### need utility routine to add response with description? */
3868 dav_add_response(wres, err->status, NULL);
3869 wres->response->desc = err->desc;
3875 static int dav_method_label(request_rec *r)
3877 dav_resource *resource;
3878 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3880 apr_xml_elem *child;
3885 dav_label_walker_ctx ctx = { { 0 } };
3886 dav_response *multi_status;
3888 /* If no versioning provider, or the provider doesn't support
3889 * labels, decline the request */
3890 if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
3893 /* Ask repository module to resolve the resource */
3894 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
3897 return dav_handle_err(r, err, NULL);
3898 if (!resource->exists) {
3899 /* Apache will supply a default error for this. */
3900 return HTTP_NOT_FOUND;
3903 if ((depth = dav_get_depth(r, 0)) < 0) {
3904 /* dav_get_depth() supplies additional information for the
3905 * default message. */
3906 return HTTP_BAD_REQUEST;
3909 /* parse the request body */
3910 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3914 if (doc == NULL || !dav_validate_root(doc, "label")) {
3915 /* This supplies additional information for the default message. */
3916 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3917 "The request body does not contain "
3918 "a \"label\" element.");
3919 return HTTP_BAD_REQUEST;
3922 /* check for add, set, or remove element */
3923 if ((child = dav_find_child(doc->root, "add")) != NULL) {
3924 ctx.label_op = DAV_LABEL_ADD;
3926 else if ((child = dav_find_child(doc->root, "set")) != NULL) {
3927 ctx.label_op = DAV_LABEL_SET;
3929 else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
3930 ctx.label_op = DAV_LABEL_REMOVE;
3933 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3934 "The \"label\" element does not contain "
3935 "an \"add\", \"set\", or \"remove\" element.");
3936 return HTTP_BAD_REQUEST;
3939 /* get the label string */
3940 if ((child = dav_find_child(child, "label-name")) == NULL) {
3941 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3942 "The label command element does not contain "
3943 "a \"label-name\" element.");
3944 return HTTP_BAD_REQUEST;
3947 apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
3948 &ctx.label, &tsize);
3950 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3951 "A \"label-name\" element does not contain "
3953 return HTTP_BAD_REQUEST;
3956 /* do the label operation walk */
3957 ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
3958 ctx.w.func = dav_label_walker;
3959 ctx.w.walk_ctx = &ctx;
3960 ctx.w.pool = r->pool;
3961 ctx.w.root = resource;
3962 ctx.vsn_hooks = vsn_hooks;
3964 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
3967 /* some sort of error occurred which terminated the walk */
3968 err = dav_push_error(r->pool, err->status, 0,
3969 "The LABEL operation was terminated prematurely.",
3971 return dav_handle_err(r, err, multi_status);
3974 if (multi_status != NULL) {
3975 /* One or more resources had errors. If depth was zero, convert
3976 * response to simple error, else make sure there is an
3977 * overall error to pass to dav_handle_err()
3980 err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
3981 multi_status = NULL;
3984 err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
3985 "Errors occurred during the LABEL operation.");
3988 return dav_handle_err(r, err, multi_status);
3991 /* set the Cache-Control header, per the spec */
3992 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3995 ap_set_content_length(r, 0);
4000 static int dav_method_report(request_rec *r)
4002 dav_resource *resource;
4003 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4009 /* If no versioning provider, decline the request */
4010 if (vsn_hooks == NULL)
4013 if ((result = ap_xml_parse_input(r, &doc)) != OK)
4016 /* This supplies additional information for the default msg. */
4017 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4018 "The request body must specify a report.");
4019 return HTTP_BAD_REQUEST;
4022 /* Ask repository module to resolve the resource.
4023 * First determine whether a Target-Selector header is allowed
4026 label_allowed = (*vsn_hooks->report_label_header_allowed)(doc);
4027 err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */,
4030 return dav_handle_err(r, err, NULL);
4032 if (!resource->exists) {
4033 /* Apache will supply a default error for this. */
4034 return HTTP_NOT_FOUND;
4037 /* set up defaults for the report response */
4038 r->status = HTTP_OK;
4039 ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
4041 /* run report hook */
4042 if ((err = (*vsn_hooks->deliver_report)(r, resource, doc,
4043 r->output_filters)) != NULL) {
4044 if (! r->sent_bodyct)
4045 /* No data has been sent to client yet; throw normal error. */
4046 return dav_handle_err(r, err, NULL);
4048 /* If an error occurred during the report delivery, there's
4049 basically nothing we can do but abort the connection and
4050 log an error. This is one of the limitations of HTTP; it
4051 needs to "know" the entire status of the response before
4052 generating it, which is just impossible in these streamy
4053 response situations. */
4054 err = dav_push_error(r->pool, err->status, 0,
4055 "Provider encountered an error while streaming"
4056 " a REPORT response.", err);
4057 dav_log_err(r, err, APLOG_ERR);
4058 r->connection->aborted = 1;
4065 static int dav_method_make_workspace(request_rec *r)
4067 dav_resource *resource;
4068 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4073 /* if no versioning provider, or the provider does not support workspaces,
4074 * decline the request
4076 if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
4079 /* ask repository module to resolve the resource */
4080 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4083 return dav_handle_err(r, err, NULL);
4085 /* parse the request body (must be a mkworkspace element) */
4086 if ((result = ap_xml_parse_input(r, &doc)) != OK) {
4091 || !dav_validate_root(doc, "mkworkspace")) {
4092 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4093 "The request body does not contain "
4094 "a \"mkworkspace\" element.");
4095 return HTTP_BAD_REQUEST;
4098 /* Check request preconditions */
4100 /* ### need a general mechanism for reporting precondition violations
4101 * ### (should be returning XML document for 403/409 responses)
4104 /* resource must not already exist */
4105 if (resource->exists) {
4106 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
4107 "<DAV:resource-must-be-null/>");
4108 return dav_handle_err(r, err, NULL);
4111 /* ### what about locking? */
4113 /* attempt to create the workspace */
4114 if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
4115 err = dav_push_error(r->pool, err->status, 0,
4116 apr_psprintf(r->pool,
4117 "Could not create workspace %s.",
4118 ap_escape_html(r->pool, r->uri)),
4120 return dav_handle_err(r, err, NULL);
4123 /* set the Cache-Control header, per the spec */
4124 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4126 /* return an appropriate response (HTTP_CREATED) */
4127 return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
4130 static int dav_method_make_activity(request_rec *r)
4132 dav_resource *resource;
4133 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4137 /* if no versioning provider, or the provider does not support activities,
4138 * decline the request
4140 if (vsn_hooks == NULL || vsn_hooks->make_activity == NULL)
4143 /* ask repository module to resolve the resource */
4144 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4147 return dav_handle_err(r, err, NULL);
4149 /* MKACTIVITY does not have a defined request body. */
4150 if ((result = ap_discard_request_body(r)) != OK) {
4154 /* Check request preconditions */
4156 /* ### need a general mechanism for reporting precondition violations
4157 * ### (should be returning XML document for 403/409 responses)
4160 /* resource must not already exist */
4161 if (resource->exists) {
4162 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
4163 "<DAV:resource-must-be-null/>");
4164 return dav_handle_err(r, err, NULL);
4167 /* the provider must say whether the resource can be created as
4168 an activity, i.e. whether the location is ok. */
4169 if (vsn_hooks->can_be_activity != NULL
4170 && !(*vsn_hooks->can_be_activity)(resource)) {
4171 err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
4172 "<DAV:activity-location-ok/>");
4173 return dav_handle_err(r, err, NULL);
4176 /* ### what about locking? */
4178 /* attempt to create the activity */
4179 if ((err = (*vsn_hooks->make_activity)(resource)) != NULL) {
4180 err = dav_push_error(r->pool, err->status, 0,
4181 apr_psprintf(r->pool,
4182 "Could not create activity %s.",
4183 ap_escape_html(r->pool, r->uri)),
4185 return dav_handle_err(r, err, NULL);
4188 /* set the Cache-Control header, per the spec */
4189 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4191 /* return an appropriate response (HTTP_CREATED) */
4192 return dav_created(r, resource->uri, "Activity", 0 /*replaced*/);
4195 static int dav_method_baseline_control(request_rec *r)
4198 return HTTP_METHOD_NOT_ALLOWED;
4201 static int dav_method_merge(request_rec *r)
4203 dav_resource *resource;
4204 dav_resource *source_resource;
4205 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4209 apr_xml_elem *source_elem;
4210 apr_xml_elem *href_elem;
4211 apr_xml_elem *prop_elem;
4215 dav_lookup_result lookup;
4217 /* If no versioning provider, decline the request */
4218 if (vsn_hooks == NULL)
4221 if ((result = ap_xml_parse_input(r, &doc)) != OK)
4224 if (doc == NULL || !dav_validate_root(doc, "merge")) {
4225 /* This supplies additional information for the default msg. */
4226 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4227 "The request body must be present and must be a "
4228 "DAV:merge element.");
4229 return HTTP_BAD_REQUEST;
4232 if ((source_elem = dav_find_child(doc->root, "source")) == NULL) {
4233 /* This supplies additional information for the default msg. */
4234 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4235 "The DAV:merge element must contain a DAV:source "
4237 return HTTP_BAD_REQUEST;
4239 if ((href_elem = dav_find_child(source_elem, "href")) == NULL) {
4240 /* This supplies additional information for the default msg. */
4241 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4242 "The DAV:source element must contain a DAV:href "
4244 return HTTP_BAD_REQUEST;
4246 source = dav_xml_get_cdata(href_elem, r->pool, 1 /* strip_white */);
4248 /* get a subrequest for the source, so that we can get a dav_resource
4250 lookup = dav_lookup_uri(source, r, 0 /* must_be_absolute */);
4251 if (lookup.rnew == NULL) {
4252 if (lookup.err.status == HTTP_BAD_REQUEST) {
4253 /* This supplies additional information for the default message. */
4254 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4255 "%s", lookup.err.desc);
4256 return HTTP_BAD_REQUEST;
4259 /* ### this assumes that dav_lookup_uri() only generates a status
4260 * ### that Apache can provide a status line for!! */
4262 return dav_error_response(r, lookup.err.status, lookup.err.desc);
4264 if (lookup.rnew->status != HTTP_OK) {
4265 /* ### how best to report this... */
4266 return dav_error_response(r, lookup.rnew->status,
4267 "Merge source URI had an error.");
4269 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
4270 0 /* use_checked_in */, &source_resource);
4272 return dav_handle_err(r, err, NULL);
4274 no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL;
4275 no_checkout = dav_find_child(doc->root, "no-checkout") != NULL;
4277 prop_elem = dav_find_child(doc->root, "prop");
4279 /* ### check RFC. I believe the DAV:merge element may contain any
4280 ### element also allowed within DAV:checkout. need to extract them
4281 ### here, and pass them along.
4282 ### if so, then refactor the CHECKOUT method handling so we can reuse
4283 ### the code. maybe create a structure to hold CHECKOUT parameters
4284 ### which can be passed to the checkout() and merge() hooks. */
4286 /* Ask repository module to resolve the resource */
4287 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4290 return dav_handle_err(r, err, NULL);
4291 if (!resource->exists) {
4292 /* Apache will supply a default error for this. */
4293 return HTTP_NOT_FOUND;
4296 /* ### check the source and target resources flags/types */
4298 /* ### do lock checks, once behavior is defined */
4300 /* set the Cache-Control header, per the spec */
4302 apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4304 /* Initialize these values for a standard MERGE response. If the MERGE
4305 is going to do something different (i.e. an error), then it must
4306 return a dav_error, and we'll reset these values properly. */
4307 r->status = HTTP_OK;
4308 ap_set_content_type(r, "text/xml");
4310 /* ### should we do any preliminary response generation? probably not,
4311 ### because we may have an error, thus demanding something else in
4312 ### the response body. */
4314 /* Do the merge, including any response generation. */
4315 if ((err = (*vsn_hooks->merge)(resource, source_resource,
4316 no_auto_merge, no_checkout,
4318 r->output_filters)) != NULL) {
4319 /* ### is err->status the right error here? */
4320 err = dav_push_error(r->pool, err->status, 0,
4321 apr_psprintf(r->pool,
4322 "Could not MERGE resource \"%s\" "
4324 ap_escape_html(r->pool, source),
4325 ap_escape_html(r->pool, r->uri)),
4327 return dav_handle_err(r, err, NULL);
4330 /* the response was fully generated by the merge() hook. */
4331 /* ### urk. does this prevent logging? need to check... */
4335 static int dav_method_bind(request_rec *r)
4337 dav_resource *resource;
4338 dav_resource *binding;
4339 dav_auto_version_info av_info;
4340 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
4344 dav_response *multi_response = NULL;
4345 dav_lookup_result lookup;
4348 /* If no bindings provider, decline the request */
4349 if (binding_hooks == NULL)
4352 /* Ask repository module to resolve the resource */
4353 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4356 return dav_handle_err(r, err, NULL);
4358 if (!resource->exists) {
4359 /* Apache will supply a default error for this. */
4360 return HTTP_NOT_FOUND;
4363 /* get the destination URI */
4364 dest = apr_table_get(r->headers_in, "Destination");
4366 /* This supplies additional information for the default message. */
4367 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4368 "The request is missing a Destination header.");
4369 return HTTP_BAD_REQUEST;
4372 lookup = dav_lookup_uri(dest, r, 0 /* must_be_absolute */);
4373 if (lookup.rnew == NULL) {
4374 if (lookup.err.status == HTTP_BAD_REQUEST) {
4375 /* This supplies additional information for the default message. */
4376 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4377 "%s", lookup.err.desc);
4378 return HTTP_BAD_REQUEST;
4380 else if (lookup.err.status == HTTP_BAD_GATEWAY) {
4381 /* ### Bindings protocol draft 02 says to return 507
4382 * ### (Cross Server Binding Forbidden); Apache already defines 507
4383 * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
4384 * ### HTTP_FORBIDDEN
4386 return dav_error_response(r, HTTP_FORBIDDEN,
4387 "Cross server bindings are not "
4388 "allowed by this server.");
4391 /* ### this assumes that dav_lookup_uri() only generates a status
4392 * ### that Apache can provide a status line for!! */
4394 return dav_error_response(r, lookup.err.status, lookup.err.desc);
4396 if (lookup.rnew->status != HTTP_OK) {
4397 /* ### how best to report this... */
4398 return dav_error_response(r, lookup.rnew->status,
4399 "Destination URI had an error.");
4402 /* resolve binding resource */
4403 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
4404 0 /* use_checked_in */, &binding);
4406 return dav_handle_err(r, err, NULL);
4408 /* are the two resources handled by the same repository? */
4409 if (resource->hooks != binding->hooks) {
4410 /* ### this message exposes some backend config, but screw it... */
4411 return dav_error_response(r, HTTP_BAD_GATEWAY,
4412 "Destination URI is handled by a "
4413 "different repository than the source URI. "
4414 "BIND between repositories is not possible.");
4417 /* get and parse the overwrite header value */
4418 if ((overwrite = dav_get_overwrite(r)) < 0) {
4419 /* dav_get_overwrite() supplies additional information for the
4420 * default message. */
4421 return HTTP_BAD_REQUEST;
4424 /* quick failure test: if dest exists and overwrite is false. */
4425 if (binding->exists && !overwrite) {
4426 return dav_error_response(r, HTTP_PRECONDITION_FAILED,
4427 "Destination is not empty and "
4428 "Overwrite is not \"T\"");
4431 /* are the source and destination the same? */
4432 if ((*resource->hooks->is_same_resource)(resource, binding)) {
4433 return dav_error_response(r, HTTP_FORBIDDEN,
4434 "Source and Destination URIs are the same.");
4438 * Check If-Headers and existing locks for destination. Note that we
4439 * use depth==infinity since the target (hierarchy) will be deleted
4440 * before the move/copy is completed.
4442 * Note that we are overwriting the target, which implies a DELETE, so
4443 * we are subject to the error/response rules as a DELETE. Namely, we
4444 * will return a 424 error if any of the validations fail.
4445 * (see dav_method_delete() for more information)
4447 if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
4450 | DAV_VALIDATE_USE_424, NULL)) != NULL) {
4451 err = dav_push_error(r->pool, err->status, 0,
4452 apr_psprintf(r->pool,
4453 "Could not BIND %s due to a "
4454 "failed precondition on the "
4455 "destination (e.g. locks).",
4456 ap_escape_html(r->pool, r->uri)),
4458 return dav_handle_err(r, err, multi_response);
4461 /* guard against creating circular bindings */
4462 if (resource->collection
4463 && (*resource->hooks->is_parent_resource)(resource, binding)) {
4464 return dav_error_response(r, HTTP_FORBIDDEN,
4465 "Source collection contains the Destination.");
4467 if (resource->collection
4468 && (*resource->hooks->is_parent_resource)(binding, resource)) {
4469 /* The destination must exist (since it contains the source), and
4470 * a condition above implies Overwrite==T. Obviously, we cannot
4471 * delete the Destination before the BIND, as that would
4472 * delete the Source.
4475 return dav_error_response(r, HTTP_FORBIDDEN,
4476 "Destination collection contains the Source and "
4477 "Overwrite has been specified.");
4480 /* prepare the destination collection for modification */
4481 if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */,
4482 &av_info)) != NULL) {
4483 /* could not make destination writable */
4484 return dav_handle_err(r, err, NULL);
4487 /* If target exists, remove it first (we know Ovewrite must be TRUE).
4488 * Then try to bind to the resource.
4490 if (binding->exists)
4491 err = (*resource->hooks->remove_resource)(binding, &multi_response);
4494 err = (*binding_hooks->bind_resource)(resource, binding);
4497 /* restore parent collection states */
4498 err2 = dav_auto_checkin(r, NULL,
4499 err != NULL /* undo if error */,
4500 0 /* unlock */, &av_info);
4502 /* check for error from remove/bind operations */
4504 err = dav_push_error(r->pool, err->status, 0,
4505 apr_psprintf(r->pool,
4506 "Could not BIND %s.",
4507 ap_escape_html(r->pool, r->uri)),
4509 return dav_handle_err(r, err, multi_response);
4512 /* check for errors from reverting writability */
4514 /* just log a warning */
4515 err = dav_push_error(r->pool, err2->status, 0,
4516 "The BIND was successful, but there was a "
4517 "problem automatically checking in the "
4518 "source parent collection.",
4520 dav_log_err(r, err, APLOG_WARNING);
4523 /* return an appropriate response (HTTP_CREATED) */
4524 /* ### spec doesn't say what happens when destination was replaced */
4525 return dav_created(r, lookup.rnew->uri, "Binding", 0);
4530 * Response handler for DAV resources
4532 static int dav_handler(request_rec *r)
4534 if (strcmp(r->handler, DAV_HANDLER_NAME) != 0)
4537 /* Reject requests with an unescaped hash character, as these may
4538 * be more destructive than the user intended. */
4539 if (r->parsed_uri.fragment != NULL) {
4540 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4541 "buggy client used un-escaped hash in Request-URI");
4542 return dav_error_response(r, HTTP_BAD_REQUEST,
4543 "The request was invalid: the URI included "
4544 "an un-escaped hash character");
4547 /* ### do we need to do anything with r->proxyreq ?? */
4550 * ### anything else to do here? could another module and/or
4551 * ### config option "take over" the handler here? i.e. how do
4552 * ### we lock down this hierarchy so that we are the ultimate
4553 * ### arbiter? (or do we simply depend on the administrator
4554 * ### to avoid conflicting configurations?)
4558 * Set up the methods mask, since that's one of the reasons this handler
4559 * gets called, and lower-level things may need the info.
4561 * First, set the mask to the methods we handle directly. Since by
4562 * definition we own our managed space, we unconditionally set
4563 * the r->allowed field rather than ORing our values with anything
4564 * any other module may have put in there.
4566 * These are the HTTP-defined methods that we handle directly.
4569 | (AP_METHOD_BIT << M_GET)
4570 | (AP_METHOD_BIT << M_PUT)
4571 | (AP_METHOD_BIT << M_DELETE)
4572 | (AP_METHOD_BIT << M_OPTIONS)
4573 | (AP_METHOD_BIT << M_INVALID);
4576 * These are the DAV methods we handle.
4579 | (AP_METHOD_BIT << M_COPY)
4580 | (AP_METHOD_BIT << M_LOCK)
4581 | (AP_METHOD_BIT << M_UNLOCK)
4582 | (AP_METHOD_BIT << M_MKCOL)
4583 | (AP_METHOD_BIT << M_MOVE)
4584 | (AP_METHOD_BIT << M_PROPFIND)
4585 | (AP_METHOD_BIT << M_PROPPATCH);
4588 * These are methods that we don't handle directly, but let the
4589 * server's default handler do for us as our agent.
4592 | (AP_METHOD_BIT << M_POST);
4594 /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
4595 * ### is sent; it will need the other allowed states; since the default
4596 * ### handler is not called on error, then it doesn't add the other
4597 * ### allowed states, so we must
4600 /* ### we might need to refine this for just where we return the error.
4601 * ### also, there is the issue with other methods (see ISSUES)
4604 /* dispatch the appropriate method handler */
4605 if (r->method_number == M_GET) {
4606 return dav_method_get(r);
4609 if (r->method_number == M_PUT) {
4610 return dav_method_put(r);
4613 if (r->method_number == M_POST) {
4614 return dav_method_post(r);
4617 if (r->method_number == M_DELETE) {
4618 return dav_method_delete(r);
4621 if (r->method_number == M_OPTIONS) {
4622 return dav_method_options(r);
4625 if (r->method_number == M_PROPFIND) {
4626 return dav_method_propfind(r);
4629 if (r->method_number == M_PROPPATCH) {
4630 return dav_method_proppatch(r);
4633 if (r->method_number == M_MKCOL) {
4634 return dav_method_mkcol(r);
4637 if (r->method_number == M_COPY) {
4638 return dav_method_copymove(r, DAV_DO_COPY);
4641 if (r->method_number == M_MOVE) {
4642 return dav_method_copymove(r, DAV_DO_MOVE);
4645 if (r->method_number == M_LOCK) {
4646 return dav_method_lock(r);
4649 if (r->method_number == M_UNLOCK) {
4650 return dav_method_unlock(r);
4653 if (r->method_number == M_VERSION_CONTROL) {
4654 return dav_method_vsn_control(r);
4657 if (r->method_number == M_CHECKOUT) {
4658 return dav_method_checkout(r);
4661 if (r->method_number == M_UNCHECKOUT) {
4662 return dav_method_uncheckout(r);
4665 if (r->method_number == M_CHECKIN) {
4666 return dav_method_checkin(r);
4669 if (r->method_number == M_UPDATE) {
4670 return dav_method_update(r);
4673 if (r->method_number == M_LABEL) {
4674 return dav_method_label(r);
4677 if (r->method_number == M_REPORT) {
4678 return dav_method_report(r);
4681 if (r->method_number == M_MKWORKSPACE) {
4682 return dav_method_make_workspace(r);
4685 if (r->method_number == M_MKACTIVITY) {
4686 return dav_method_make_activity(r);
4689 if (r->method_number == M_BASELINE_CONTROL) {
4690 return dav_method_baseline_control(r);
4693 if (r->method_number == M_MERGE) {
4694 return dav_method_merge(r);
4698 if (r->method_number == dav_methods[DAV_M_BIND]) {
4699 return dav_method_bind(r);
4703 if (r->method_number == dav_methods[DAV_M_SEARCH]) {
4704 return dav_method_search(r);
4707 /* ### add'l methods for Advanced Collections, ACLs */
4712 static int dav_fixups(request_rec *r)
4716 /* quickly ignore any HTTP/0.9 requests which aren't subreqs. */
4717 if (r->assbackwards && !r->main) {
4721 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
4724 /* if DAV is not enabled, then we've got nothing to do */
4725 if (conf->provider == NULL) {
4729 /* We are going to handle almost every request. In certain cases,
4730 the provider maps to the filesystem (thus, handle_get is
4731 FALSE), and core Apache will handle it. a For that case, we
4732 just return right away. */
4733 if (r->method_number == M_GET) {
4735 * ### need some work to pull Content-Type and Content-Language
4736 * ### from the property database.
4740 * If the repository hasn't indicated that it will handle the
4741 * GET method, then just punt.
4743 * ### this isn't quite right... taking over the response can break
4744 * ### things like mod_negotiation. need to look into this some more.
4746 if (!conf->provider->repos->handle_get) {
4751 /* ### this is wrong. We should only be setting the r->handler for the
4752 * requests that mod_dav knows about. If we set the handler for M_POST
4753 * requests, then CGI scripts that use POST will return the source for the
4754 * script. However, mod_dav DOES handle POST, so something else needs
4757 if (r->method_number != M_POST) {
4759 /* We are going to be handling the response for this resource. */
4760 r->handler = DAV_HANDLER_NAME;
4767 static void register_hooks(apr_pool_t *p)
4769 ap_hook_handler(dav_handler, NULL, NULL, APR_HOOK_MIDDLE);
4770 ap_hook_post_config(dav_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
4771 ap_hook_fixups(dav_fixups, NULL, NULL, APR_HOOK_MIDDLE);
4773 dav_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, APR_HOOK_LAST);
4774 dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
4775 NULL, NULL, APR_HOOK_MIDDLE);
4777 dav_core_register_uris(p);
4780 /*---------------------------------------------------------------------------
4782 * Configuration info for the module
4785 static const command_rec dav_cmds[] =
4787 /* per directory/location */
4788 AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
4789 "specify the DAV provider for a directory or location"),
4791 /* per directory/location, or per server */
4792 AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
4793 ACCESS_CONF|RSRC_CONF,
4794 "specify minimum allowed timeout"),
4796 /* per directory/location, or per server */
4797 AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL,
4798 ACCESS_CONF|RSRC_CONF,
4799 "allow Depth infinity PROPFIND requests"),
4804 module DAV_DECLARE_DATA dav_module =
4806 STANDARD20_MODULE_STUFF,
4807 dav_create_dir_config, /* dir config creater */
4808 dav_merge_dir_config, /* dir merger --- default is to override */
4809 dav_create_server_config, /* server config */
4810 dav_merge_server_config, /* merge server config */
4811 dav_cmds, /* command table */
4812 register_hooks, /* register hooks */
4816 APR_HOOK_LINK(gather_propsets)
4817 APR_HOOK_LINK(find_liveprop)
4818 APR_HOOK_LINK(insert_all_liveprops)
4821 APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets,
4822 (apr_array_header_t *uris),
4825 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, find_liveprop,
4826 (const dav_resource *resource,
4827 const char *ns_uri, const char *name,
4828 const dav_hooks_liveprop **hooks),
4829 (resource, ns_uri, name, hooks), 0)
4831 APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops,
4832 (request_rec *r, const dav_resource *resource,
4833 dav_prop_insert what, apr_text_header *phdr),
4834 (r, resource, what, phdr))