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");
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)
773
range_c = apr_table_get(r->headers_in, "content-range");
777
range = apr_pstrdup(r->pool, range_c);
778
if (strncasecmp(range, "bytes ", 6) != 0
779
|| (dash = ap_strchr(range, '-')) == NULL
780
|| (slash = ap_strchr(range, '/')) == NULL) {
781
/* malformed header. ignore it (per S14.16 of RFC2616) */
785
*dash++ = *slash++ = '\0';
787
/* ignore invalid ranges. (per S14.16 of RFC2616) */
788
if (apr_strtoff(range_start, range + 6, &errp, 10)
789
|| *errp || *range_start < 0) {
793
if (apr_strtoff(range_end, dash, &errp, 10)
794
|| *errp || *range_end < 0 || *range_end < *range_start) {
801
if (apr_strtoff(&dummy, slash, &errp, 10)
802
|| *errp || dummy <= *range_end) {
807
/* we now have a valid range */
811
/* handle the GET method */
812
static int dav_method_get(request_rec *r)
814
dav_resource *resource;
818
/* This method should only be called when the resource is not
819
* visible to Apache. We will fetch the resource from the repository,
820
* then create a subrequest for Apache to handle.
822
err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
825
return dav_handle_err(r, err, NULL);
827
if (!resource->exists) {
828
/* Apache will supply a default error for this. */
829
return HTTP_NOT_FOUND;
832
/* set up the HTTP headers for the response */
833
if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
834
err = dav_push_error(r->pool, err->status, 0,
835
"Unable to set up HTTP headers.",
837
return dav_handle_err(r, err, NULL);
840
/* Handle conditional requests */
841
status = ap_meets_conditions(r);
846
if (r->header_only) {
850
/* okay... time to deliver the content */
851
if ((err = (*resource->hooks->deliver)(resource,
852
r->output_filters)) != NULL) {
853
err = dav_push_error(r->pool, err->status, 0,
854
"Unable to deliver content.",
856
return dav_handle_err(r, err, NULL);
862
/* validate resource/locks on POST, then pass to the default handler */
863
static int dav_method_post(request_rec *r)
865
dav_resource *resource;
868
/* Ask repository module to resolve the resource */
869
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
872
return dav_handle_err(r, err, NULL);
874
/* Note: depth == 0. Implies no need for a multistatus response. */
875
if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
876
DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
877
/* ### add a higher-level description? */
878
return dav_handle_err(r, err, NULL);
884
/* handle the PUT method */
885
static int dav_method_put(request_rec *r)
887
dav_resource *resource;
889
dav_auto_version_info av_info;
890
const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
894
dav_stream_mode mode;
896
dav_response *multi_response;
898
apr_off_t range_start;
901
/* Ask repository module to resolve the resource */
902
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
905
return dav_handle_err(r, err, NULL);
907
/* If not a file or collection resource, PUT not allowed */
908
if (resource->type != DAV_RESOURCE_TYPE_REGULAR
909
&& resource->type != DAV_RESOURCE_TYPE_WORKING) {
910
body = apr_psprintf(r->pool,
911
"Cannot create resource %s with PUT.",
912
ap_escape_html(r->pool, r->uri));
913
return dav_error_response(r, HTTP_CONFLICT, body);
916
/* Cannot PUT a collection */
917
if (resource->collection) {
918
return dav_error_response(r, HTTP_CONFLICT,
919
"Cannot PUT to a collection.");
923
resource_state = dav_get_resource_state(r, resource);
926
* Note: depth == 0 normally requires no multistatus response. However,
927
* if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
928
* other than the Request-URI, thereby requiring a multistatus.
930
* If the resource does not exist (DAV_RESOURCE_NULL), then we must
931
* check the resource *and* its parent. If the resource exists or is
932
* a locknull resource, then we check only the resource.
934
if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
935
resource_state == DAV_RESOURCE_NULL ?
936
DAV_VALIDATE_PARENT :
937
DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
938
/* ### add a higher-level description? */
939
return dav_handle_err(r, err, multi_response);
942
/* make sure the resource can be modified (if versioning repository) */
943
if ((err = dav_auto_checkout(r, resource,
944
0 /* not parent_only */,
945
&av_info)) != NULL) {
946
/* ### add a higher-level description? */
947
return dav_handle_err(r, err, NULL);
950
/* truncate and rewrite the file unless we see a Content-Range */
951
mode = DAV_MODE_WRITE_TRUNC;
953
has_range = dav_parse_range(r, &range_start, &range_end);
955
mode = DAV_MODE_WRITE_SEEKABLE;
958
/* Create the new file in the repository */
959
if ((err = (*resource->hooks->open_stream)(resource, mode,
961
/* ### assuming FORBIDDEN is probably not quite right... */
962
err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
963
apr_psprintf(r->pool,
964
"Unable to PUT new contents for %s.",
965
ap_escape_html(r->pool, r->uri)),
969
if (err == NULL && has_range) {
970
/* a range was provided. seek to the start */
971
err = (*resource->hooks->seek_stream)(stream, range_start);
975
apr_bucket_brigade *bb;
979
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
984
rc = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
985
APR_BLOCK_READ, DAV_READ_BLOCKSIZE);
987
if (rc != APR_SUCCESS) {
988
err = dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
989
"Could not get next bucket brigade");
993
for (b = APR_BRIGADE_FIRST(bb);
994
b != APR_BRIGADE_SENTINEL(bb);
995
b = APR_BUCKET_NEXT(b))
1000
if (APR_BUCKET_IS_EOS(b)) {
1005
if (APR_BUCKET_IS_METADATA(b)) {
1009
rc = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
1010
if (rc != APR_SUCCESS) {
1011
err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1012
"An error occurred while reading "
1013
"the request body.");
1018
/* write whatever we read, until we see an error */
1019
err = (*resource->hooks->write_stream)(stream, data, len);
1023
apr_brigade_cleanup(bb);
1024
} while (!seen_eos);
1026
apr_brigade_destroy(bb);
1028
err2 = (*resource->hooks->close_stream)(stream,
1029
err == NULL /* commit */);
1030
if (err2 != NULL && err == NULL) {
1031
/* no error during the write, but we hit one at close. use it. */
1037
* Ensure that we think the resource exists now.
1038
* ### eek. if an error occurred during the write and we did not commit,
1039
* ### then the resource might NOT exist (e.g. dav_fs_repos.c)
1042
resource->exists = 1;
1045
/* restore modifiability of resources back to what they were */
1046
err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */,
1047
0 /*unlock*/, &av_info);
1049
/* check for errors now */
1051
return dav_handle_err(r, err, NULL);
1055
/* just log a warning */
1056
err2 = dav_push_error(r->pool, err2->status, 0,
1057
"The PUT was successful, but there "
1058
"was a problem automatically checking in "
1059
"the resource or its parent collection.",
1061
dav_log_err(r, err2, APLOG_WARNING);
1064
/* ### place the Content-Type and Content-Language into the propdb */
1066
if (locks_hooks != NULL) {
1069
if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1070
/* The file creation was successful, but the locking failed. */
1071
err = dav_push_error(r->pool, err->status, 0,
1072
"The file was PUT successfully, but there "
1073
"was a problem opening the lock database "
1074
"which prevents inheriting locks from the "
1075
"parent resources.",
1077
return dav_handle_err(r, err, NULL);
1080
/* notify lock system that we have created/replaced a resource */
1081
err = dav_notify_created(r, lockdb, resource, resource_state, 0);
1083
(*locks_hooks->close_lockdb)(lockdb);
1086
/* The file creation was successful, but the locking failed. */
1087
err = dav_push_error(r->pool, err->status, 0,
1088
"The file was PUT successfully, but there "
1089
"was a problem updating its lock "
1092
return dav_handle_err(r, err, NULL);
1096
/* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
1098
/* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
1099
return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
1103
/* Use POOL to temporarily construct a dav_response object (from WRES
1104
STATUS, and PROPSTATS) and stream it via WRES's ctx->brigade. */
1105
static void dav_stream_response(dav_walk_resource *wres,
1107
dav_get_props_result *propstats,
1110
dav_response resp = { 0 };
1111
dav_walker_ctx *ctx = wres->walk_ctx;
1113
resp.href = wres->resource->uri;
1114
resp.status = status;
1116
resp.propresult = *propstats;
1119
dav_send_one_response(&resp, ctx->bb, ctx->r->output_filters, pool);
1123
/* ### move this to dav_util? */
1124
DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
1125
int status, dav_get_props_result *propstats)
1129
/* just drop some data into an dav_response */
1130
resp = apr_pcalloc(wres->pool, sizeof(*resp));
1131
resp->href = apr_pstrdup(wres->pool, wres->resource->uri);
1132
resp->status = status;
1134
resp->propresult = *propstats;
1137
resp->next = wres->response;
1138
wres->response = resp;
1142
/* handle the DELETE method */
1143
static int dav_method_delete(request_rec *r)
1145
dav_resource *resource;
1146
dav_auto_version_info av_info;
1149
dav_response *multi_response;
1153
/* We don't use the request body right now, so torch it. */
1154
if ((result = ap_discard_request_body(r)) != OK) {
1158
/* Ask repository module to resolve the resource */
1159
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
1162
return dav_handle_err(r, err, NULL);
1163
if (!resource->exists) {
1164
/* Apache will supply a default error for this. */
1165
return HTTP_NOT_FOUND;
1168
/* 2518 says that depth must be infinity only for collections.
1169
* For non-collections, depth is ignored, unless it is an illegal value (1).
1171
depth = dav_get_depth(r, DAV_INFINITY);
1173
if (resource->collection && depth != DAV_INFINITY) {
1174
/* This supplies additional information for the default message. */
1175
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1176
"Depth must be \"infinity\" for DELETE of a collection.");
1177
return HTTP_BAD_REQUEST;
1180
if (!resource->collection && depth == 1) {
1181
/* This supplies additional information for the default message. */
1182
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1183
"Depth of \"1\" is not allowed for DELETE.");
1184
return HTTP_BAD_REQUEST;
1188
** If any resources fail the lock/If: conditions, then we must fail
1189
** the delete. Each of the failing resources will be listed within
1190
** a DAV:multistatus body, wrapped into a 424 response.
1192
** Note that a failure on the resource itself does not generate a
1193
** multistatus response -- only internal members/collections.
1195
if ((err = dav_validate_request(r, resource, depth, NULL,
1198
| DAV_VALIDATE_USE_424, NULL)) != NULL) {
1199
err = dav_push_error(r->pool, err->status, 0,
1200
apr_psprintf(r->pool,
1201
"Could not DELETE %s due to a failed "
1202
"precondition (e.g. locks).",
1203
ap_escape_html(r->pool, r->uri)),
1205
return dav_handle_err(r, err, multi_response);
1208
/* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
1209
* locked by the token(s) in the if_header.
1211
if ((result = dav_unlock(r, resource, NULL)) != OK) {
1215
/* if versioned resource, make sure parent is checked out */
1216
if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
1217
&av_info)) != NULL) {
1218
/* ### add a higher-level description? */
1219
return dav_handle_err(r, err, NULL);
1222
/* try to remove the resource */
1223
err = (*resource->hooks->remove_resource)(resource, &multi_response);
1225
/* restore writability of parent back to what it was */
1226
err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
1227
0 /*unlock*/, &av_info);
1229
/* check for errors now */
1231
err = dav_push_error(r->pool, err->status, 0,
1232
apr_psprintf(r->pool,
1233
"Could not DELETE %s.",
1234
ap_escape_html(r->pool, r->uri)),
1236
return dav_handle_err(r, err, multi_response);
1239
/* just log a warning */
1240
err = dav_push_error(r->pool, err2->status, 0,
1241
"The DELETE was successful, but there "
1242
"was a problem automatically checking in "
1243
"the parent collection.",
1245
dav_log_err(r, err, APLOG_WARNING);
1248
/* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
1250
/* Apache will supply a default error for this. */
1251
return HTTP_NO_CONTENT;
1254
/* generate DAV:supported-method-set OPTIONS response */
1255
static dav_error *dav_gen_supported_methods(request_rec *r,
1256
const apr_xml_elem *elem,
1257
const apr_table_t *methods,
1258
apr_text_header *body)
1260
const apr_array_header_t *arr;
1261
const apr_table_entry_t *elts;
1262
apr_xml_elem *child;
1267
apr_text_append(r->pool, body, "<D:supported-method-set>" DEBUG_CR);
1269
if (elem->first_child == NULL) {
1270
/* show all supported methods */
1271
arr = apr_table_elts(methods);
1272
elts = (const apr_table_entry_t *)arr->elts;
1274
for (i = 0; i < arr->nelts; ++i) {
1275
if (elts[i].key == NULL)
1278
s = apr_psprintf(r->pool,
1279
"<D:supported-method D:name=\"%s\"/>"
1282
apr_text_append(r->pool, body, s);
1286
/* check for support of specific methods */
1287
for (child = elem->first_child; child != NULL; child = child->next) {
1288
if (child->ns == APR_XML_NS_DAV_ID
1289
&& strcmp(child->name, "supported-method") == 0) {
1290
const char *name = NULL;
1292
/* go through attributes to find method name */
1293
for (attr = child->attr; attr != NULL; attr = attr->next) {
1294
if (attr->ns == APR_XML_NS_DAV_ID
1295
&& strcmp(attr->name, "name") == 0)
1300
return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1301
"A DAV:supported-method element "
1302
"does not have a \"name\" attribute");
1305
/* see if method is supported */
1306
if (apr_table_get(methods, name) != NULL) {
1307
s = apr_psprintf(r->pool,
1308
"<D:supported-method D:name=\"%s\"/>"
1311
apr_text_append(r->pool, body, s);
1317
apr_text_append(r->pool, body, "</D:supported-method-set>" DEBUG_CR);
1321
/* generate DAV:supported-live-property-set OPTIONS response */
1322
static dav_error *dav_gen_supported_live_props(request_rec *r,
1323
const dav_resource *resource,
1324
const apr_xml_elem *elem,
1325
apr_text_header *body)
1329
apr_xml_elem *child;
1333
/* open lock database, to report on supported lock properties */
1334
/* ### should open read-only */
1335
if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
1336
return dav_push_error(r->pool, err->status, 0,
1337
"The lock database could not be opened, "
1338
"preventing the reporting of supported lock "
1343
/* open the property database (readonly) for the resource */
1344
if ((err = dav_open_propdb(r, lockdb, resource, 1, NULL,
1345
&propdb)) != NULL) {
1347
(*lockdb->hooks->close_lockdb)(lockdb);
1349
return dav_push_error(r->pool, err->status, 0,
1350
"The property database could not be opened, "
1351
"preventing report of supported properties.",
1355
apr_text_append(r->pool, body, "<D:supported-live-property-set>" DEBUG_CR);
1357
if (elem->first_child == NULL) {
1358
/* show all supported live properties */
1359
dav_get_props_result props = dav_get_allprops(propdb, DAV_PROP_INSERT_SUPPORTED);
1360
body->last->next = props.propstats;
1361
while (body->last->next != NULL)
1362
body->last = body->last->next;
1365
/* check for support of specific live property */
1366
for (child = elem->first_child; child != NULL; child = child->next) {
1367
if (child->ns == APR_XML_NS_DAV_ID
1368
&& strcmp(child->name, "supported-live-property") == 0) {
1369
const char *name = NULL;
1370
const char *nmspace = NULL;
1372
/* go through attributes to find name and namespace */
1373
for (attr = child->attr; attr != NULL; attr = attr->next) {
1374
if (attr->ns == APR_XML_NS_DAV_ID) {
1375
if (strcmp(attr->name, "name") == 0)
1377
else if (strcmp(attr->name, "namespace") == 0)
1378
nmspace = attr->value;
1383
err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1384
"A DAV:supported-live-property "
1385
"element does not have a \"name\" "
1390
/* default namespace to DAV: */
1391
if (nmspace == NULL)
1394
/* check for support of property */
1395
dav_get_liveprop_supported(propdb, nmspace, name, body);
1400
apr_text_append(r->pool, body, "</D:supported-live-property-set>" DEBUG_CR);
1402
dav_close_propdb(propdb);
1405
(*lockdb->hooks->close_lockdb)(lockdb);
1410
/* generate DAV:supported-report-set OPTIONS response */
1411
static dav_error *dav_gen_supported_reports(request_rec *r,
1412
const dav_resource *resource,
1413
const apr_xml_elem *elem,
1414
const dav_hooks_vsn *vsn_hooks,
1415
apr_text_header *body)
1417
apr_xml_elem *child;
1422
apr_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR);
1424
if (vsn_hooks != NULL) {
1425
const dav_report_elem *reports;
1426
const dav_report_elem *rp;
1428
if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) {
1429
return dav_push_error(r->pool, err->status, 0,
1430
"DAV:supported-report-set could not be "
1431
"determined due to a problem fetching the "
1432
"available reports for this resource.",
1436
if (reports != NULL) {
1437
if (elem->first_child == NULL) {
1438
/* show all supported reports */
1439
for (rp = reports; rp->nmspace != NULL; ++rp) {
1440
/* Note: we presume reports->namespace is
1441
* properly XML/URL quoted */
1442
s = apr_psprintf(r->pool,
1443
"<D:supported-report D:name=\"%s\" "
1444
"D:namespace=\"%s\"/>" DEBUG_CR,
1445
rp->name, rp->nmspace);
1446
apr_text_append(r->pool, body, s);
1450
/* check for support of specific report */
1451
for (child = elem->first_child; child != NULL; child = child->next) {
1452
if (child->ns == APR_XML_NS_DAV_ID
1453
&& strcmp(child->name, "supported-report") == 0) {
1454
const char *name = NULL;
1455
const char *nmspace = NULL;
1457
/* go through attributes to find name and namespace */
1458
for (attr = child->attr; attr != NULL; attr = attr->next) {
1459
if (attr->ns == APR_XML_NS_DAV_ID) {
1460
if (strcmp(attr->name, "name") == 0)
1462
else if (strcmp(attr->name, "namespace") == 0)
1463
nmspace = attr->value;
1468
return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
1469
"A DAV:supported-report element "
1470
"does not have a \"name\" attribute");
1473
/* default namespace to DAV: */
1474
if (nmspace == NULL)
1477
for (rp = reports; rp->nmspace != NULL; ++rp) {
1478
if (strcmp(name, rp->name) == 0
1479
&& strcmp(nmspace, rp->nmspace) == 0) {
1480
/* Note: we presume reports->nmspace is
1481
* properly XML/URL quoted
1483
s = apr_psprintf(r->pool,
1484
"<D:supported-report "
1486
"D:namespace=\"%s\"/>"
1488
rp->name, rp->nmspace);
1489
apr_text_append(r->pool, body, s);
1499
apr_text_append(r->pool, body, "</D:supported-report-set>" DEBUG_CR);
1504
/* handle the SEARCH method */
1505
static int dav_method_search(request_rec *r)
1507
const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
1508
dav_resource *resource;
1510
dav_response *multi_status;
1512
/* If no search provider, decline the request */
1513
if (search_hooks == NULL)
1516
/* This method should only be called when the resource is not
1517
* visible to Apache. We will fetch the resource from the repository,
1518
* then create a subrequest for Apache to handle.
1520
err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
1523
return dav_handle_err(r, err, NULL);
1525
if (!resource->exists) {
1526
/* Apache will supply a default error for this. */
1527
return HTTP_NOT_FOUND;
1530
/* set up the HTTP headers for the response */
1531
if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
1532
err = dav_push_error(r->pool, err->status, 0,
1533
"Unable to set up HTTP headers.",
1535
return dav_handle_err(r, err, NULL);
1538
if (r->header_only) {
1542
/* okay... time to search the content */
1543
/* Let's validate XML and process walk function
1544
* in the hook function
1546
if ((err = (*search_hooks->search_resource)(r, &multi_status)) != NULL) {
1547
/* ### add a higher-level description? */
1548
return dav_handle_err(r, err, NULL);
1551
/* We have results in multi_status */
1552
/* Should I pass namespace?? */
1553
dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
1559
/* handle the OPTIONS method */
1560
static int dav_method_options(request_rec *r)
1562
const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1563
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1564
const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
1565
const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
1566
dav_resource *resource;
1567
const char *dav_level;
1570
const apr_array_header_t *arr;
1571
const apr_table_entry_t *elts;
1572
apr_table_t *methods = apr_table_make(r->pool, 12);
1573
apr_text_header vsn_options = { 0 };
1574
apr_text_header body = { 0 };
1579
apr_array_header_t *uri_ary;
1581
const apr_xml_elem *elem;
1584
/* resolve the resource */
1585
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
1588
return dav_handle_err(r, err, NULL);
1590
/* parse any request body */
1591
if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1594
/* note: doc == NULL if no request body */
1596
if (doc && !dav_validate_root(doc, "options")) {
1597
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1598
"The \"options\" element was not found.");
1599
return HTTP_BAD_REQUEST;
1602
/* determine which providers are available */
1605
if (locks_hooks != NULL) {
1609
if (binding_hooks != NULL)
1610
dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
1613
* MSFT Web Folders chokes if length of DAV header value > 63 characters!
1614
* To workaround that, we use separate DAV headers for versioning and
1615
* live prop provider namespace URIs.
1618
apr_table_setn(r->headers_out, "DAV", dav_level);
1621
* If there is a versioning provider, generate DAV headers
1622
* for versioning options.
1624
if (vsn_hooks != NULL) {
1625
(*vsn_hooks->get_vsn_options)(r->pool, &vsn_options);
1627
for (t = vsn_options.first; t != NULL; t = t->next)
1628
apr_table_addn(r->headers_out, "DAV", t->text);
1632
* Gather property set URIs from all the liveprop providers,
1633
* and generate a separate DAV header for each URI, to avoid
1634
* problems with long header lengths.
1636
uri_ary = apr_array_make(r->pool, 5, sizeof(const char *));
1637
dav_run_gather_propsets(uri_ary);
1638
for (i = 0; i < uri_ary->nelts; ++i) {
1639
if (((char **)uri_ary->elts)[i] != NULL)
1640
apr_table_addn(r->headers_out, "DAV", ((char **)uri_ary->elts)[i]);
1643
/* this tells MSFT products to skip looking for FrontPage extensions */
1644
apr_table_setn(r->headers_out, "MS-Author-Via", "DAV");
1647
* Determine which methods are allowed on the resource.
1648
* Three cases: resource is null (3), is lock-null (7.4), or exists.
1650
* All cases support OPTIONS, and if there is a lock provider, LOCK.
1651
* (Lock-) null resources also support MKCOL and PUT.
1652
* Lock-null supports PROPFIND and UNLOCK.
1653
* Existing resources support lots of stuff.
1656
apr_table_addn(methods, "OPTIONS", "");
1658
/* ### take into account resource type */
1659
switch (dav_get_resource_state(r, resource))
1661
case DAV_RESOURCE_EXISTS:
1662
/* resource exists */
1663
apr_table_addn(methods, "GET", "");
1664
apr_table_addn(methods, "HEAD", "");
1665
apr_table_addn(methods, "POST", "");
1666
apr_table_addn(methods, "DELETE", "");
1667
apr_table_addn(methods, "TRACE", "");
1668
apr_table_addn(methods, "PROPFIND", "");
1669
apr_table_addn(methods, "PROPPATCH", "");
1670
apr_table_addn(methods, "COPY", "");
1671
apr_table_addn(methods, "MOVE", "");
1673
if (!resource->collection)
1674
apr_table_addn(methods, "PUT", "");
1676
if (locks_hooks != NULL) {
1677
apr_table_addn(methods, "LOCK", "");
1678
apr_table_addn(methods, "UNLOCK", "");
1683
case DAV_RESOURCE_LOCK_NULL:
1684
/* resource is lock-null. */
1685
apr_table_addn(methods, "MKCOL", "");
1686
apr_table_addn(methods, "PROPFIND", "");
1687
apr_table_addn(methods, "PUT", "");
1689
if (locks_hooks != NULL) {
1690
apr_table_addn(methods, "LOCK", "");
1691
apr_table_addn(methods, "UNLOCK", "");
1696
case DAV_RESOURCE_NULL:
1697
/* resource is null. */
1698
apr_table_addn(methods, "MKCOL", "");
1699
apr_table_addn(methods, "PUT", "");
1701
if (locks_hooks != NULL)
1702
apr_table_addn(methods, "LOCK", "");
1707
/* ### internal error! */
1711
/* If there is a versioning provider, add versioning methods */
1712
if (vsn_hooks != NULL) {
1713
if (!resource->exists) {
1714
if ((*vsn_hooks->versionable)(resource))
1715
apr_table_addn(methods, "VERSION-CONTROL", "");
1717
if (vsn_hooks->can_be_workspace != NULL
1718
&& (*vsn_hooks->can_be_workspace)(resource))
1719
apr_table_addn(methods, "MKWORKSPACE", "");
1721
if (vsn_hooks->can_be_activity != NULL
1722
&& (*vsn_hooks->can_be_activity)(resource))
1723
apr_table_addn(methods, "MKACTIVITY", "");
1725
else if (!resource->versioned) {
1726
if ((*vsn_hooks->versionable)(resource))
1727
apr_table_addn(methods, "VERSION-CONTROL", "");
1729
else if (resource->working) {
1730
apr_table_addn(methods, "CHECKIN", "");
1732
/* ### we might not support this DeltaV option */
1733
apr_table_addn(methods, "UNCHECKOUT", "");
1735
else if (vsn_hooks->add_label != NULL) {
1736
apr_table_addn(methods, "CHECKOUT", "");
1737
apr_table_addn(methods, "LABEL", "");
1740
apr_table_addn(methods, "CHECKOUT", "");
1744
/* If there is a bindings provider, see if resource is bindable */
1745
if (binding_hooks != NULL
1746
&& (*binding_hooks->is_bindable)(resource)) {
1747
apr_table_addn(methods, "BIND", "");
1750
/* If there is a search provider, set SEARCH in option */
1751
if (search_hooks != NULL) {
1752
apr_table_addn(methods, "SEARCH", "");
1755
/* Generate the Allow header */
1756
arr = apr_table_elts(methods);
1757
elts = (const apr_table_entry_t *)arr->elts;
1760
/* first, compute total length */
1761
for (i = 0; i < arr->nelts; ++i) {
1762
if (elts[i].key == NULL)
1765
/* add 1 for comma or null */
1766
text_size += strlen(elts[i].key) + 1;
1769
s = allow = apr_palloc(r->pool, text_size);
1771
for (i = 0; i < arr->nelts; ++i) {
1772
if (elts[i].key == NULL)
1778
strcpy(s, elts[i].key);
1782
apr_table_setn(r->headers_out, "Allow", allow);
1785
/* If there is search set_option_head function, set head */
1786
/* DASL: <DAV:basicsearch>
1787
* DASL: <http://foo.bar.com/syntax1>
1788
* DASL: <http://akuma.com/syntax2>
1790
if (search_hooks != NULL
1791
&& *search_hooks->set_option_head != NULL) {
1792
if ((err = (*search_hooks->set_option_head)(r)) != NULL) {
1793
return dav_handle_err(r, err, NULL);
1797
/* if there was no request body, then there is no response body */
1799
ap_set_content_length(r, 0);
1801
/* ### this sends a Content-Type. the default OPTIONS does not. */
1803
/* ### the default (ap_send_http_options) returns OK, but I believe
1804
* ### that is because it is the default handler and nothing else
1805
* ### will run after the thing. */
1809
/* handle each options request */
1810
for (elem = doc->root->first_child; elem != NULL; elem = elem->next) {
1811
/* check for something we recognize first */
1812
int core_option = 0;
1813
dav_error *err = NULL;
1815
if (elem->ns == APR_XML_NS_DAV_ID) {
1816
if (strcmp(elem->name, "supported-method-set") == 0) {
1817
err = dav_gen_supported_methods(r, elem, methods, &body);
1820
else if (strcmp(elem->name, "supported-live-property-set") == 0) {
1821
err = dav_gen_supported_live_props(r, resource, elem, &body);
1824
else if (strcmp(elem->name, "supported-report-set") == 0) {
1825
err = dav_gen_supported_reports(r, resource, elem, vsn_hooks, &body);
1831
return dav_handle_err(r, err, NULL);
1833
/* if unrecognized option, pass to versioning provider */
1834
if (!core_option && vsn_hooks != NULL) {
1835
if ((err = (*vsn_hooks->get_option)(resource, elem, &body))
1837
return dav_handle_err(r, err, NULL);
1842
/* send the options response */
1843
r->status = HTTP_OK;
1844
ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
1846
/* send the headers and response body */
1847
ap_rputs(DAV_XML_HEADER DEBUG_CR
1848
"<D:options-response xmlns:D=\"DAV:\">" DEBUG_CR, r);
1850
for (t = body.first; t != NULL; t = t->next)
1851
ap_rputs(t->text, r);
1853
ap_rputs("</D:options-response>" DEBUG_CR, r);
1855
/* we've sent everything necessary to the client. */
1859
static void dav_cache_badprops(dav_walker_ctx *ctx)
1861
const apr_xml_elem *elem;
1862
apr_text_header hdr = { 0 };
1864
/* just return if we built the thing already */
1865
if (ctx->propstat_404 != NULL) {
1869
apr_text_append(ctx->w.pool, &hdr,
1870
"<D:propstat>" DEBUG_CR
1871
"<D:prop>" DEBUG_CR);
1873
elem = dav_find_child(ctx->doc->root, "prop");
1874
for (elem = elem->first_child; elem; elem = elem->next) {
1875
apr_text_append(ctx->w.pool, &hdr,
1876
apr_xml_empty_elem(ctx->w.pool, elem));
1879
apr_text_append(ctx->w.pool, &hdr,
1880
"</D:prop>" DEBUG_CR
1881
"<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
1882
"</D:propstat>" DEBUG_CR);
1884
ctx->propstat_404 = hdr.first;
1887
static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
1889
dav_walker_ctx *ctx = wres->walk_ctx;
1892
dav_get_props_result propstats = { 0 };
1895
** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
1896
** dav_get_allprops() does not need to do namespace translation,
1899
** Note: we cast to lose the "const". The propdb won't try to change
1900
** the resource, however, since we are opening readonly.
1902
err = dav_open_propdb(ctx->r, ctx->w.lockdb, wres->resource, 1,
1903
ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
1905
/* ### do something with err! */
1907
if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1908
dav_get_props_result badprops = { 0 };
1910
/* some props were expected on this collection/resource */
1911
dav_cache_badprops(ctx);
1912
badprops.propstats = ctx->propstat_404;
1913
dav_stream_response(wres, 0, &badprops, ctx->scratchpool);
1916
/* no props on this collection/resource */
1917
dav_stream_response(wres, HTTP_OK, NULL, ctx->scratchpool);
1920
apr_pool_clear(ctx->scratchpool);
1923
/* ### what to do about closing the propdb on server failure? */
1925
if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
1926
propstats = dav_get_props(propdb, ctx->doc);
1929
dav_prop_insert what = ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP
1930
? DAV_PROP_INSERT_VALUE
1931
: DAV_PROP_INSERT_NAME;
1932
propstats = dav_get_allprops(propdb, what);
1934
dav_close_propdb(propdb);
1936
dav_stream_response(wres, 0, &propstats, ctx->scratchpool);
1938
/* at this point, ctx->scratchpool has been used to stream a
1939
single response. this function fully controls the pool, and
1940
thus has the right to clear it for the next iteration of this
1942
apr_pool_clear(ctx->scratchpool);
1947
/* handle the PROPFIND method */
1948
static int dav_method_propfind(request_rec *r)
1950
dav_resource *resource;
1955
const apr_xml_elem *child;
1956
dav_walker_ctx ctx = { { 0 } };
1957
dav_response *multi_status;
1959
/* Ask repository module to resolve the resource */
1960
err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
1963
return dav_handle_err(r, err, NULL);
1965
if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
1966
/* Apache will supply a default error for this. */
1967
return HTTP_NOT_FOUND;
1970
if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
1971
/* dav_get_depth() supplies additional information for the
1972
* default message. */
1973
return HTTP_BAD_REQUEST;
1976
if (depth == DAV_INFINITY && resource->collection) {
1978
conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
1980
/* default is to DISALLOW these requests */
1981
if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
1982
return dav_error_response(r, HTTP_FORBIDDEN,
1983
apr_psprintf(r->pool,
1984
"PROPFIND requests with a "
1985
"Depth of \"infinity\" are "
1986
"not allowed for %s.",
1987
ap_escape_html(r->pool,
1992
if ((result = ap_xml_parse_input(r, &doc)) != OK) {
1995
/* note: doc == NULL if no request body */
1997
if (doc && !dav_validate_root(doc, "propfind")) {
1998
/* This supplies additional information for the default message. */
1999
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2000
"The \"propfind\" element was not found.");
2001
return HTTP_BAD_REQUEST;
2004
/* ### validate that only one of these three elements is present */
2007
|| (child = dav_find_child(doc->root, "allprop")) != NULL) {
2008
/* note: no request body implies allprop */
2009
ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
2011
else if ((child = dav_find_child(doc->root, "propname")) != NULL) {
2012
ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
2014
else if ((child = dav_find_child(doc->root, "prop")) != NULL) {
2015
ctx.propfind_type = DAV_PROPFIND_IS_PROP;
2018
/* "propfind" element must have one of the above three children */
2020
/* This supplies additional information for the default message. */
2021
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2022
"The \"propfind\" element does not contain one of "
2023
"the required child elements (the specific command).");
2024
return HTTP_BAD_REQUEST;
2027
ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
2028
ctx.w.func = dav_propfind_walker;
2029
ctx.w.walk_ctx = &ctx;
2030
ctx.w.pool = r->pool;
2031
ctx.w.root = resource;
2035
ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
2036
apr_pool_create(&ctx.scratchpool, r->pool);
2038
/* ### should open read-only */
2039
if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
2040
err = dav_push_error(r->pool, err->status, 0,
2041
"The lock database could not be opened, "
2042
"preventing access to the various lock "
2043
"properties for the PROPFIND.",
2045
return dav_handle_err(r, err, NULL);
2047
if (ctx.w.lockdb != NULL) {
2048
/* if we have a lock database, then we can walk locknull resources */
2049
ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
2052
/* send <multistatus> tag, with all doc->namespaces attached. */
2054
/* NOTE: we *cannot* leave out the doc's namespaces from the
2055
initial <multistatus> tag. if a 404 was generated for an HREF,
2056
then we need to spit out the doc's namespaces for use by the
2057
404. Note that <response> elements will override these ns0,
2058
ns1, etc, but NOT within the <response> scope for the
2060
dav_begin_multistatus(ctx.bb, r, HTTP_MULTI_STATUS,
2061
doc ? doc->namespaces : NULL);
2063
/* Have the provider walk the resource. */
2064
err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
2066
if (ctx.w.lockdb != NULL) {
2067
(*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb);
2071
/* If an error occurred during the resource walk, there's
2072
basically nothing we can do but abort the connection and
2073
log an error. This is one of the limitations of HTTP; it
2074
needs to "know" the entire status of the response before
2075
generating it, which is just impossible in these streamy
2076
response situations. */
2077
err = dav_push_error(r->pool, err->status, 0,
2078
"Provider encountered an error while streaming"
2079
" a multistatus PROPFIND response.", err);
2080
dav_log_err(r, err, APLOG_ERR);
2081
r->connection->aborted = 1;
2085
dav_finish_multistatus(r, ctx.bb);
2087
/* the response has been sent. */
2091
static apr_text * dav_failed_proppatch(apr_pool_t *p,
2092
apr_array_header_t *prop_ctx)
2094
apr_text_header hdr = { 0 };
2095
int i = prop_ctx->nelts;
2096
dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
2097
dav_error *err424_set = NULL;
2098
dav_error *err424_delete = NULL;
2101
/* ### might be nice to sort by status code and description */
2103
for ( ; i-- > 0; ++ctx ) {
2104
apr_text_append(p, &hdr,
2105
"<D:propstat>" DEBUG_CR
2107
apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
2108
apr_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
2110
if (ctx->err == NULL) {
2111
/* nothing was assigned here yet, so make it a 424 */
2113
if (ctx->operation == DAV_PROP_OP_SET) {
2114
if (err424_set == NULL)
2115
err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
2116
"Attempted DAV:set operation "
2117
"could not be completed due "
2118
"to other errors.");
2119
ctx->err = err424_set;
2121
else if (ctx->operation == DAV_PROP_OP_DELETE) {
2122
if (err424_delete == NULL)
2123
err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
2124
"Attempted DAV:remove "
2125
"operation could not be "
2126
"completed due to other "
2128
ctx->err = err424_delete;
2134
"HTTP/1.1 %d (status)"
2135
"</D:status>" DEBUG_CR,
2137
apr_text_append(p, &hdr, s);
2139
/* ### we should use compute_desc if necessary... */
2140
if (ctx->err->desc != NULL) {
2141
apr_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
2142
apr_text_append(p, &hdr, ctx->err->desc);
2143
apr_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
2146
apr_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
2152
static apr_text * dav_success_proppatch(apr_pool_t *p, apr_array_header_t *prop_ctx)
2154
apr_text_header hdr = { 0 };
2155
int i = prop_ctx->nelts;
2156
dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
2159
* ### we probably need to revise the way we assemble the response...
2160
* ### this code assumes everything will return status==200.
2163
apr_text_append(p, &hdr,
2164
"<D:propstat>" DEBUG_CR
2165
"<D:prop>" DEBUG_CR);
2167
for ( ; i-- > 0; ++ctx ) {
2168
apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
2171
apr_text_append(p, &hdr,
2172
"</D:prop>" DEBUG_CR
2173
"<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
2174
"</D:propstat>" DEBUG_CR);
2179
static void dav_prop_log_errors(dav_prop_ctx *ctx)
2181
dav_log_err(ctx->r, ctx->err, APLOG_ERR);
2185
* Call <func> for each context. This can stop when an error occurs, or
2186
* simply iterate through the whole list.
2188
* Returns 1 if an error occurs (and the iteration is aborted). Returns 0
2189
* if all elements are processed.
2191
* If <reverse> is true (non-zero), then the list is traversed in
2194
static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
2195
apr_array_header_t *ctx_list, int stop_on_error,
2198
int i = ctx_list->nelts;
2199
dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
2209
if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
2220
/* handle the PROPPATCH method */
2221
static int dav_method_proppatch(request_rec *r)
2224
dav_resource *resource;
2227
apr_xml_elem *child;
2230
dav_response resp = { 0 };
2231
apr_text *propstat_text;
2232
apr_array_header_t *ctx_list;
2234
dav_auto_version_info av_info;
2236
/* Ask repository module to resolve the resource */
2237
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2240
return dav_handle_err(r, err, NULL);
2241
if (!resource->exists) {
2242
/* Apache will supply a default error for this. */
2243
return HTTP_NOT_FOUND;
2246
if ((result = ap_xml_parse_input(r, &doc)) != OK) {
2249
/* note: doc == NULL if no request body */
2251
if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
2252
/* This supplies additional information for the default message. */
2253
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2254
"The request body does not contain "
2255
"a \"propertyupdate\" element.");
2256
return HTTP_BAD_REQUEST;
2259
/* Check If-Headers and existing locks */
2260
/* Note: depth == 0. Implies no need for a multistatus response. */
2261
if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
2262
DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2263
/* ### add a higher-level description? */
2264
return dav_handle_err(r, err, NULL);
2267
/* make sure the resource can be modified (if versioning repository) */
2268
if ((err = dav_auto_checkout(r, resource,
2269
0 /* not parent_only */,
2270
&av_info)) != NULL) {
2271
/* ### add a higher-level description? */
2272
return dav_handle_err(r, err, NULL);
2275
if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
2276
&propdb)) != NULL) {
2277
/* undo any auto-checkout */
2278
dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
2280
err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2281
apr_psprintf(r->pool,
2282
"Could not open the property "
2284
ap_escape_html(r->pool, r->uri)),
2286
return dav_handle_err(r, err, NULL);
2288
/* ### what to do about closing the propdb on server failure? */
2290
/* ### validate "live" properties */
2292
/* set up an array to hold property operation contexts */
2293
ctx_list = apr_array_make(r->pool, 10, sizeof(dav_prop_ctx));
2295
/* do a first pass to ensure that all "remove" properties exist */
2296
for (child = doc->root->first_child; child; child = child->next) {
2298
apr_xml_elem *prop_group;
2299
apr_xml_elem *one_prop;
2301
/* Ignore children that are not set/remove */
2302
if (child->ns != APR_XML_NS_DAV_ID
2303
|| (!(is_remove = strcmp(child->name, "remove") == 0)
2304
&& strcmp(child->name, "set") != 0)) {
2308
/* make sure that a "prop" child exists for set/remove */
2309
if ((prop_group = dav_find_child(child, "prop")) == NULL) {
2310
dav_close_propdb(propdb);
2312
/* undo any auto-checkout */
2313
dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
2315
/* This supplies additional information for the default message. */
2316
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2317
"A \"prop\" element is missing inside "
2318
"the propertyupdate command.");
2319
return HTTP_BAD_REQUEST;
2322
for (one_prop = prop_group->first_child; one_prop;
2323
one_prop = one_prop->next) {
2325
ctx = (dav_prop_ctx *)apr_array_push(ctx_list);
2326
ctx->propdb = propdb;
2327
ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
2328
ctx->prop = one_prop;
2330
ctx->r = r; /* for later use by dav_prop_log_errors() */
2332
dav_prop_validate(ctx);
2334
if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
2340
/* ### should test that we found at least one set/remove */
2342
/* execute all of the operations */
2343
if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
2347
/* generate a failure/success response */
2349
(void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
2350
propstat_text = dav_failed_proppatch(r->pool, ctx_list);
2353
(void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
2354
propstat_text = dav_success_proppatch(r->pool, ctx_list);
2357
/* make sure this gets closed! */
2358
dav_close_propdb(propdb);
2360
/* complete any auto-versioning */
2361
dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info);
2363
/* log any errors that occurred */
2364
(void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
2366
resp.href = resource->uri;
2368
/* ### should probably use something new to pass along this text... */
2369
resp.propresult.propstats = propstat_text;
2371
dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
2373
/* the response has been sent. */
2377
static int process_mkcol_body(request_rec *r)
2379
/* This is snarfed from ap_setup_client_block(). We could get pretty
2380
* close to this behavior by passing REQUEST_NO_BODY, but we need to
2381
* return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
2382
* returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
2384
const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
2385
const char *lenp = apr_table_get(r->headers_in, "Content-Length");
2387
/* make sure to set the Apache request fields properly. */
2388
r->read_body = REQUEST_NO_BODY;
2389
r->read_chunked = 0;
2393
if (strcasecmp(tenc, "chunked")) {
2394
/* Use this instead of Apache's default error string */
2395
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2396
"Unknown Transfer-Encoding %s", tenc);
2397
return HTTP_NOT_IMPLEMENTED;
2400
r->read_chunked = 1;
2403
const char *pos = lenp;
2405
while (apr_isdigit(*pos) || apr_isspace(*pos)) {
2410
/* This supplies additional information for the default message. */
2411
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2412
"Invalid Content-Length %s", lenp);
2413
return HTTP_BAD_REQUEST;
2416
r->remaining = apr_atoi64(lenp);
2419
if (r->read_chunked || r->remaining > 0) {
2420
/* ### log something? */
2422
/* Apache will supply a default error for this. */
2423
return HTTP_UNSUPPORTED_MEDIA_TYPE;
2427
* Get rid of the body. this will call ap_setup_client_block(), but
2428
* our copy above has already verified its work.
2430
return ap_discard_request_body(r);
2433
/* handle the MKCOL method */
2434
static int dav_method_mkcol(request_rec *r)
2436
dav_resource *resource;
2438
dav_auto_version_info av_info;
2439
const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2444
dav_response *multi_status;
2446
/* handle the request body */
2447
/* ### this may move lower once we start processing bodies */
2448
if ((result = process_mkcol_body(r)) != OK) {
2452
conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
2455
/* Ask repository module to resolve the resource */
2456
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2459
return dav_handle_err(r, err, NULL);
2461
if (resource->exists) {
2462
/* oops. something was already there! */
2464
/* Apache will supply a default error for this. */
2465
/* ### we should provide a specific error message! */
2466
return HTTP_METHOD_NOT_ALLOWED;
2469
resource_state = dav_get_resource_state(r, resource);
2472
* Check If-Headers and existing locks.
2474
* Note: depth == 0 normally requires no multistatus response. However,
2475
* if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
2476
* other than the Request-URI, thereby requiring a multistatus.
2478
* If the resource does not exist (DAV_RESOURCE_NULL), then we must
2479
* check the resource *and* its parent. If the resource exists or is
2480
* a locknull resource, then we check only the resource.
2482
if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
2483
resource_state == DAV_RESOURCE_NULL ?
2484
DAV_VALIDATE_PARENT :
2485
DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
2486
/* ### add a higher-level description? */
2487
return dav_handle_err(r, err, multi_status);
2490
/* if versioned resource, make sure parent is checked out */
2491
if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
2492
&av_info)) != NULL) {
2493
/* ### add a higher-level description? */
2494
return dav_handle_err(r, err, NULL);
2497
/* try to create the collection */
2498
resource->collection = 1;
2499
err = (*resource->hooks->create_collection)(resource);
2501
/* restore modifiability of parent back to what it was */
2502
err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2503
0 /*unlock*/, &av_info);
2505
/* check for errors now */
2507
return dav_handle_err(r, err, NULL);
2510
/* just log a warning */
2511
err = dav_push_error(r->pool, err2->status, 0,
2512
"The MKCOL was successful, but there "
2513
"was a problem automatically checking in "
2514
"the parent collection.",
2516
dav_log_err(r, err, APLOG_WARNING);
2519
if (locks_hooks != NULL) {
2522
if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
2523
/* The directory creation was successful, but the locking failed. */
2524
err = dav_push_error(r->pool, err->status, 0,
2525
"The MKCOL was successful, but there "
2526
"was a problem opening the lock database "
2527
"which prevents inheriting locks from the "
2528
"parent resources.",
2530
return dav_handle_err(r, err, NULL);
2533
/* notify lock system that we have created/replaced a resource */
2534
err = dav_notify_created(r, lockdb, resource, resource_state, 0);
2536
(*locks_hooks->close_lockdb)(lockdb);
2539
/* The dir creation was successful, but the locking failed. */
2540
err = dav_push_error(r->pool, err->status, 0,
2541
"The MKCOL was successful, but there "
2542
"was a problem updating its lock "
2545
return dav_handle_err(r, err, NULL);
2549
/* return an appropriate response (HTTP_CREATED) */
2550
return dav_created(r, NULL, "Collection", 0);
2553
/* handle the COPY and MOVE methods */
2554
static int dav_method_copymove(request_rec *r, int is_move)
2556
dav_resource *resource;
2557
dav_resource *resnew;
2558
dav_auto_version_info src_av_info = { 0 };
2559
dav_auto_version_info dst_av_info = { 0 };
2565
dav_response *multi_response;
2566
dav_lookup_result lookup;
2575
/* Ask repository module to resolve the resource */
2576
err = dav_get_resource(r, !is_move /* label_allowed */,
2577
0 /* use_checked_in */, &resource);
2579
return dav_handle_err(r, err, NULL);
2581
if (!resource->exists) {
2582
/* Apache will supply a default error for this. */
2583
return HTTP_NOT_FOUND;
2586
/* If not a file or collection resource, COPY/MOVE not allowed */
2587
/* ### allow COPY/MOVE of DeltaV resource types */
2588
if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
2589
body = apr_psprintf(r->pool,
2590
"Cannot COPY/MOVE resource %s.",
2591
ap_escape_html(r->pool, r->uri));
2592
return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
2595
/* get the destination URI */
2596
dest = apr_table_get(r->headers_in, "Destination");
2598
/* Look in headers provided by Netscape's Roaming Profiles */
2599
const char *nscp_host = apr_table_get(r->headers_in, "Host");
2600
const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
2602
if (nscp_host != NULL && nscp_path != NULL)
2603
dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
2606
/* This supplies additional information for the default message. */
2607
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2608
"The request is missing a Destination header.");
2609
return HTTP_BAD_REQUEST;
2612
lookup = dav_lookup_uri(dest, r, 1 /* must_be_absolute */);
2613
if (lookup.rnew == NULL) {
2614
if (lookup.err.status == HTTP_BAD_REQUEST) {
2615
/* This supplies additional information for the default message. */
2616
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2617
"%s", lookup.err.desc);
2618
return HTTP_BAD_REQUEST;
2621
/* ### this assumes that dav_lookup_uri() only generates a status
2622
* ### that Apache can provide a status line for!! */
2624
return dav_error_response(r, lookup.err.status, lookup.err.desc);
2626
if (lookup.rnew->status != HTTP_OK) {
2627
const char *auth = apr_table_get(lookup.rnew->err_headers_out,
2628
"WWW-Authenticate");
2629
if (lookup.rnew->status == HTTP_UNAUTHORIZED && auth != NULL) {
2630
/* propagate the WWW-Authorization header up from the
2631
* subreq so the client sees it. */
2632
apr_table_set(r->err_headers_out, "WWW-Authenticate",
2633
apr_pstrdup(r->pool, auth));
2636
/* ### how best to report this... */
2637
return dav_error_response(r, lookup.rnew->status,
2638
"Destination URI had an error.");
2641
/* Resolve destination resource */
2642
err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
2643
0 /* use_checked_in */, &resnew);
2645
return dav_handle_err(r, err, NULL);
2647
/* are the two resources handled by the same repository? */
2648
if (resource->hooks != resnew->hooks) {
2649
/* ### this message exposes some backend config, but screw it... */
2650
return dav_error_response(r, HTTP_BAD_GATEWAY,
2651
"Destination URI is handled by a "
2652
"different repository than the source URI. "
2653
"MOVE or COPY between repositories is "
2657
/* get and parse the overwrite header value */
2658
if ((overwrite = dav_get_overwrite(r)) < 0) {
2659
/* dav_get_overwrite() supplies additional information for the
2660
* default message. */
2661
return HTTP_BAD_REQUEST;
2664
/* quick failure test: if dest exists and overwrite is false. */
2665
if (resnew->exists && !overwrite) {
2666
/* Supply some text for the error response body. */
2667
return dav_error_response(r, HTTP_PRECONDITION_FAILED,
2668
"Destination is not empty and "
2669
"Overwrite is not \"T\"");
2672
/* are the source and destination the same? */
2673
if ((*resource->hooks->is_same_resource)(resource, resnew)) {
2674
/* Supply some text for the error response body. */
2675
return dav_error_response(r, HTTP_FORBIDDEN,
2676
"Source and Destination URIs are the same.");
2680
is_dir = resource->collection;
2682
/* get and parse the Depth header value. "0" and "infinity" are legal. */
2683
if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
2684
/* dav_get_depth() supplies additional information for the
2685
* default message. */
2686
return HTTP_BAD_REQUEST;
2689
/* This supplies additional information for the default message. */
2690
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2691
"Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
2692
return HTTP_BAD_REQUEST;
2694
if (is_move && is_dir && depth != DAV_INFINITY) {
2695
/* This supplies additional information for the default message. */
2696
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2697
"Depth must be \"infinity\" when moving a collection.");
2698
return HTTP_BAD_REQUEST;
2702
* Check If-Headers and existing locks for each resource in the source
2703
* if we are performing a MOVE. We will return a 424 response with a
2704
* DAV:multistatus body. The multistatus responses will contain the
2705
* information about any resource that fails the validation.
2707
* We check the parent resource, too, since this is a MOVE. Moving the
2708
* resource effectively removes it from the parent collection, so we
2709
* must ensure that we have met the appropriate conditions.
2711
* If a problem occurs with the Request-URI itself, then a plain error
2712
* (rather than a multistatus) will be returned.
2715
&& (err = dav_validate_request(r, resource, depth, NULL,
2718
| DAV_VALIDATE_USE_424,
2720
err = dav_push_error(r->pool, err->status, 0,
2721
apr_psprintf(r->pool,
2722
"Could not MOVE %s due to a failed "
2723
"precondition on the source "
2725
ap_escape_html(r->pool, r->uri)),
2727
return dav_handle_err(r, err, multi_response);
2731
* Check If-Headers and existing locks for destination. Note that we
2732
* use depth==infinity since the target (hierarchy) will be deleted
2733
* before the move/copy is completed.
2735
* Note that we are overwriting the target, which implies a DELETE, so
2736
* we are subject to the error/response rules as a DELETE. Namely, we
2737
* will return a 424 error if any of the validations fail.
2738
* (see dav_method_delete() for more information)
2740
if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
2743
| DAV_VALIDATE_USE_424, NULL)) != NULL) {
2744
err = dav_push_error(r->pool, err->status, 0,
2745
apr_psprintf(r->pool,
2746
"Could not MOVE/COPY %s due to a "
2747
"failed precondition on the "
2748
"destination (e.g. locks).",
2749
ap_escape_html(r->pool, r->uri)),
2751
return dav_handle_err(r, err, multi_response);
2755
&& depth == DAV_INFINITY
2756
&& (*resource->hooks->is_parent_resource)(resource, resnew)) {
2757
/* Supply some text for the error response body. */
2758
return dav_error_response(r, HTTP_FORBIDDEN,
2759
"Source collection contains the "
2764
&& (*resnew->hooks->is_parent_resource)(resnew, resource)) {
2765
/* The destination must exist (since it contains the source), and
2766
* a condition above implies Overwrite==T. Obviously, we cannot
2767
* delete the Destination before the MOVE/COPY, as that would
2768
* delete the Source.
2771
/* Supply some text for the error response body. */
2772
return dav_error_response(r, HTTP_FORBIDDEN,
2773
"Destination collection contains the Source "
2774
"and Overwrite has been specified.");
2777
/* ### for now, we don't need anything in the body */
2778
if ((result = ap_discard_request_body(r)) != OK) {
2782
if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
2783
/* ### add a higher-level description? */
2784
return dav_handle_err(r, err, NULL);
2787
/* remove any locks from the old resources */
2789
* ### this is Yet Another Traversal. if we do a rename(), then we
2790
* ### really don't have to do this in some cases since the inode
2791
* ### values will remain constant across the move. but we can't
2792
* ### know that fact from outside the provider :-(
2794
* ### note that we now have a problem atomicity in the move/copy
2795
* ### since a failure after this would have removed locks (technically,
2796
* ### this is okay to do, but really...)
2798
if (is_move && lockdb != NULL) {
2799
/* ### this is wrong! it blasts direct locks on parent resources */
2800
/* ### pass lockdb! */
2801
(void)dav_unlock(r, resource, NULL);
2804
/* if this is a move, then the source parent collection will be modified */
2806
if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
2807
&src_av_info)) != NULL) {
2809
(*lockdb->hooks->close_lockdb)(lockdb);
2811
/* ### add a higher-level description? */
2812
return dav_handle_err(r, err, NULL);
2817
* Remember the initial state of the destination, so the lock system
2818
* can be notified as to how it changed.
2820
resnew_state = dav_get_resource_state(lookup.rnew, resnew);
2822
/* In a MOVE operation, the destination is replaced by the source.
2823
* In a COPY operation, if the destination exists, is under version
2824
* control, and is the same resource type as the source,
2825
* then it should not be replaced, but modified to be a copy of
2828
if (!resnew->exists)
2830
else if (is_move || !resource->versioned)
2832
else if (resource->type != resnew->type)
2834
else if ((resource->collection == 0) != (resnew->collection == 0))
2839
/* If the destination must be created or replaced,
2840
* make sure the parent collection is writable
2842
if (!resnew->exists || replace_dest) {
2843
if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/,
2844
&dst_av_info)) != NULL) {
2845
/* could not make destination writable:
2846
* if move, restore state of source parent
2849
(void)dav_auto_checkin(r, NULL, 1 /* undo */,
2850
0 /*unlock*/, &src_av_info);
2854
(*lockdb->hooks->close_lockdb)(lockdb);
2856
/* ### add a higher-level description? */
2857
return dav_handle_err(r, err, NULL);
2861
/* If source and destination parents are the same, then
2862
* use the same resource object, so status updates to one are reflected
2863
* in the other, when doing auto-versioning. Otherwise,
2864
* we may try to checkin the parent twice.
2866
if (src_av_info.parent_resource != NULL
2867
&& dst_av_info.parent_resource != NULL
2868
&& (*src_av_info.parent_resource->hooks->is_same_resource)
2869
(src_av_info.parent_resource, dst_av_info.parent_resource)) {
2871
dst_av_info.parent_resource = src_av_info.parent_resource;
2874
/* If destination is being replaced, remove it first
2875
* (we know Ovewrite must be TRUE). Then try to copy/move the resource.
2878
err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
2882
err = (*resource->hooks->move_resource)(resource, resnew,
2885
err = (*resource->hooks->copy_resource)(resource, resnew, depth,
2889
/* perform any auto-versioning cleanup */
2890
err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2891
0 /*unlock*/, &dst_av_info);
2894
err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
2895
0 /*unlock*/, &src_av_info);
2900
/* check for error from remove/copy/move operations */
2903
(*lockdb->hooks->close_lockdb)(lockdb);
2905
err = dav_push_error(r->pool, err->status, 0,
2906
apr_psprintf(r->pool,
2907
"Could not MOVE/COPY %s.",
2908
ap_escape_html(r->pool, r->uri)),
2910
return dav_handle_err(r, err, multi_response);
2913
/* check for errors from auto-versioning */
2915
/* just log a warning */
2916
err = dav_push_error(r->pool, err2->status, 0,
2917
"The MOVE/COPY was successful, but there was a "
2918
"problem automatically checking in the "
2919
"source parent collection.",
2921
dav_log_err(r, err, APLOG_WARNING);
2924
/* just log a warning */
2925
err = dav_push_error(r->pool, err3->status, 0,
2926
"The MOVE/COPY was successful, but there was a "
2927
"problem automatically checking in the "
2928
"destination or its parent collection.",
2930
dav_log_err(r, err, APLOG_WARNING);
2933
/* propagate any indirect locks at the target */
2934
if (lockdb != NULL) {
2936
/* notify lock system that we have created/replaced a resource */
2937
err = dav_notify_created(r, lockdb, resnew, resnew_state, depth);
2939
(*lockdb->hooks->close_lockdb)(lockdb);
2942
/* The move/copy was successful, but the locking failed. */
2943
err = dav_push_error(r->pool, err->status, 0,
2944
"The MOVE/COPY was successful, but there "
2945
"was a problem updating the lock "
2948
return dav_handle_err(r, err, NULL);
2952
/* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
2953
return dav_created(r, lookup.rnew->uri, "Destination",
2954
resnew_state == DAV_RESOURCE_EXISTS);
2957
/* dav_method_lock: Handler to implement the DAV LOCK method
2958
* Returns appropriate HTTP_* response.
2960
static int dav_method_lock(request_rec *r)
2963
dav_resource *resource;
2964
const dav_hooks_locks *locks_hooks;
2967
int new_lock_request = 0;
2970
dav_response *multi_response = NULL;
2974
/* If no locks provider, decline the request */
2975
locks_hooks = DAV_GET_HOOKS_LOCKS(r);
2976
if (locks_hooks == NULL)
2979
if ((result = ap_xml_parse_input(r, &doc)) != OK)
2982
depth = dav_get_depth(r, DAV_INFINITY);
2983
if (depth != 0 && depth != DAV_INFINITY) {
2984
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2985
"Depth must be 0 or \"infinity\" for LOCK.");
2986
return HTTP_BAD_REQUEST;
2989
/* Ask repository module to resolve the resource */
2990
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
2993
return dav_handle_err(r, err, NULL);
2996
* Open writable. Unless an error occurs, we'll be
2997
* writing into the database.
2999
if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
3000
/* ### add a higher-level description? */
3001
return dav_handle_err(r, err, NULL);
3005
if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
3007
/* ### add a higher-level description to err? */
3010
new_lock_request = 1;
3012
lock->auth_user = apr_pstrdup(r->pool, r->user);
3015
resource_state = dav_get_resource_state(r, resource);
3018
* Check If-Headers and existing locks.
3020
* If this will create a locknull resource, then the LOCK will affect
3021
* the parent collection (much like a PUT/MKCOL). For that case, we must
3022
* validate the parent resource's conditions.
3024
if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
3025
(resource_state == DAV_RESOURCE_NULL
3026
? DAV_VALIDATE_PARENT
3027
: DAV_VALIDATE_RESOURCE)
3028
| (new_lock_request ? lock->scope : 0)
3029
| DAV_VALIDATE_ADD_LD,
3031
err = dav_push_error(r->pool, err->status, 0,
3032
apr_psprintf(r->pool,
3033
"Could not LOCK %s due to a failed "
3034
"precondition (e.g. other locks).",
3035
ap_escape_html(r->pool, r->uri)),
3040
if (new_lock_request == 0) {
3041
dav_locktoken_list *ltl;
3045
* ### Assumption: We can renew multiple locks on the same resource
3046
* ### at once. First harvest all the positive lock-tokens given in
3047
* ### the If header. Then modify the lock entries for this resource
3048
* ### with the new Timeout val.
3051
if ((err = dav_get_locktoken_list(r, <l)) != NULL) {
3052
err = dav_push_error(r->pool, err->status, 0,
3053
apr_psprintf(r->pool,
3054
"The lock refresh for %s failed "
3055
"because no lock tokens were "
3056
"specified in an \"If:\" "
3058
ap_escape_html(r->pool, r->uri)),
3063
if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
3066
/* ### add a higher-level description to err? */
3070
/* New lock request */
3071
char *locktoken_txt;
3074
conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
3077
/* apply lower bound (if any) from DAVMinTimeout directive */
3078
if (lock->timeout != DAV_TIMEOUT_INFINITE
3079
&& lock->timeout < time(NULL) + conf->locktimeout)
3080
lock->timeout = time(NULL) + conf->locktimeout;
3082
err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
3084
/* ### add a higher-level description to err? */
3088
locktoken_txt = apr_pstrcat(r->pool, "<",
3089
(*locks_hooks->format_locktoken)(r->pool,
3093
apr_table_set(r->headers_out, "Lock-Token", locktoken_txt);
3096
(*locks_hooks->close_lockdb)(lockdb);
3098
r->status = HTTP_OK;
3099
ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
3101
ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
3103
ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
3106
"<D:lockdiscovery>" DEBUG_CR
3108
"</D:lockdiscovery>" DEBUG_CR,
3109
dav_lock_get_activelock(r, lock, NULL));
3111
ap_rputs("</D:prop>", r);
3113
/* the response has been sent. */
3117
(*locks_hooks->close_lockdb)(lockdb);
3118
return dav_handle_err(r, err, multi_response);
3121
/* dav_method_unlock: Handler to implement the DAV UNLOCK method
3122
* Returns appropriate HTTP_* response.
3124
static int dav_method_unlock(request_rec *r)
3127
dav_resource *resource;
3128
const dav_hooks_locks *locks_hooks;
3130
const char *const_locktoken_txt;
3131
char *locktoken_txt;
3132
dav_locktoken *locktoken = NULL;
3134
dav_response *multi_response;
3136
/* If no locks provider, decline the request */
3137
locks_hooks = DAV_GET_HOOKS_LOCKS(r);
3138
if (locks_hooks == NULL)
3141
if ((const_locktoken_txt = apr_table_get(r->headers_in,
3142
"Lock-Token")) == NULL) {
3143
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3144
"Unlock failed (%s): "
3145
"No Lock-Token specified in header", r->filename);
3146
return HTTP_BAD_REQUEST;
3149
locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt);
3150
if (locktoken_txt[0] != '<') {
3151
/* ### should provide more specifics... */
3152
return HTTP_BAD_REQUEST;
3156
if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
3157
/* ### should provide more specifics... */
3158
return HTTP_BAD_REQUEST;
3160
locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
3162
if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
3163
&locktoken)) != NULL) {
3164
err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
3165
apr_psprintf(r->pool,
3166
"The UNLOCK on %s failed -- an "
3167
"invalid lock token was specified "
3168
"in the \"If:\" header.",
3169
ap_escape_html(r->pool, r->uri)),
3171
return dav_handle_err(r, err, NULL);
3174
/* Ask repository module to resolve the resource */
3175
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3178
return dav_handle_err(r, err, NULL);
3180
resource_state = dav_get_resource_state(r, resource);
3183
* Check If-Headers and existing locks.
3185
* Note: depth == 0 normally requires no multistatus response. However,
3186
* if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
3187
* other than the Request-URI, thereby requiring a multistatus.
3189
* If the resource is a locknull resource, then the UNLOCK will affect
3190
* the parent collection (much like a delete). For that case, we must
3191
* validate the parent resource's conditions.
3193
if ((err = dav_validate_request(r, resource, 0, locktoken,
3195
resource_state == DAV_RESOURCE_LOCK_NULL
3196
? DAV_VALIDATE_PARENT
3197
: DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
3198
/* ### add a higher-level description? */
3199
return dav_handle_err(r, err, multi_response);
3202
/* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
3203
* _all_ resources locked by locktoken are released. It does not say
3204
* resource has to be the root of an infinte lock. Thus, an UNLOCK
3205
* on any part of an infinte lock will remove the lock on all resources.
3207
* For us, if r->filename represents an indirect lock (part of an infinity lock),
3208
* we must actually perform an UNLOCK on the direct lock for this resource.
3210
if ((result = dav_unlock(r, resource, locktoken)) != OK) {
3214
return HTTP_NO_CONTENT;
3217
static int dav_method_vsn_control(request_rec *r)
3219
dav_resource *resource;
3221
dav_auto_version_info av_info;
3222
const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
3223
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3226
const char *target = NULL;
3229
/* if no versioning provider, decline the request */
3230
if (vsn_hooks == NULL)
3233
/* ask repository module to resolve the resource */
3234
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3237
return dav_handle_err(r, err, NULL);
3239
/* remember the pre-creation resource state */
3240
resource_state = dav_get_resource_state(r, resource);
3242
/* parse the request body (may be a version-control element) */
3243
if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3246
/* note: doc == NULL if no request body */
3249
const apr_xml_elem *child;
3252
if (!dav_validate_root(doc, "version-control")) {
3253
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3254
"The request body does not contain "
3255
"a \"version-control\" element.");
3256
return HTTP_BAD_REQUEST;
3259
/* get the version URI */
3260
if ((child = dav_find_child(doc->root, "version")) == NULL) {
3261
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3262
"The \"version-control\" element does not contain "
3263
"a \"version\" element.");
3264
return HTTP_BAD_REQUEST;
3267
if ((child = dav_find_child(child, "href")) == NULL) {
3268
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3269
"The \"version\" element does not contain "
3270
"an \"href\" element.");
3271
return HTTP_BAD_REQUEST;
3274
/* get version URI */
3275
apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
3278
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3279
"An \"href\" element does not contain a URI.");
3280
return HTTP_BAD_REQUEST;
3284
/* Check request preconditions */
3286
/* ### need a general mechanism for reporting precondition violations
3287
* ### (should be returning XML document for 403/409 responses)
3290
/* if not versioning existing resource, must specify version to select */
3291
if (!resource->exists && target == NULL) {
3292
err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3293
"<DAV:initial-version-required/>");
3294
return dav_handle_err(r, err, NULL);
3296
else if (resource->exists) {
3297
/* cannot add resource to existing version history */
3298
if (target != NULL) {
3299
err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3300
"<DAV:cannot-add-to-existing-history/>");
3301
return dav_handle_err(r, err, NULL);
3304
/* resource must be unversioned and versionable, or version selector */
3305
if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3306
|| (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
3307
err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
3308
"<DAV:must-be-versionable/>");
3309
return dav_handle_err(r, err, NULL);
3312
/* the DeltaV spec says if resource is a version selector,
3313
* then VERSION-CONTROL is a no-op
3315
if (resource->versioned) {
3316
/* set the Cache-Control header, per the spec */
3317
apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3320
ap_set_content_length(r, 0);
3326
/* Check If-Headers and existing locks */
3327
/* Note: depth == 0. Implies no need for a multistatus response. */
3328
if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
3329
resource_state == DAV_RESOURCE_NULL ?
3330
DAV_VALIDATE_PARENT :
3331
DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
3332
return dav_handle_err(r, err, NULL);
3335
/* if in versioned collection, make sure parent is checked out */
3336
if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
3337
&av_info)) != NULL) {
3338
return dav_handle_err(r, err, NULL);
3341
/* attempt to version-control the resource */
3342
if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
3343
dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
3344
err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3345
apr_psprintf(r->pool,
3346
"Could not VERSION-CONTROL resource %s.",
3347
ap_escape_html(r->pool, r->uri)),
3349
return dav_handle_err(r, err, NULL);
3352
/* revert writability of parent directory */
3353
err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info);
3355
/* just log a warning */
3356
err = dav_push_error(r->pool, err->status, 0,
3357
"The VERSION-CONTROL was successful, but there "
3358
"was a problem automatically checking in "
3359
"the parent collection.",
3361
dav_log_err(r, err, APLOG_WARNING);
3364
/* if the resource is lockable, let lock system know of new resource */
3365
if (locks_hooks != NULL
3366
&& (*locks_hooks->get_supportedlock)(resource) != NULL) {
3369
if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
3370
/* The resource creation was successful, but the locking failed. */
3371
err = dav_push_error(r->pool, err->status, 0,
3372
"The VERSION-CONTROL was successful, but there "
3373
"was a problem opening the lock database "
3374
"which prevents inheriting locks from the "
3375
"parent resources.",
3377
return dav_handle_err(r, err, NULL);
3380
/* notify lock system that we have created/replaced a resource */
3381
err = dav_notify_created(r, lockdb, resource, resource_state, 0);
3383
(*locks_hooks->close_lockdb)(lockdb);
3386
/* The dir creation was successful, but the locking failed. */
3387
err = dav_push_error(r->pool, err->status, 0,
3388
"The VERSION-CONTROL was successful, but there "
3389
"was a problem updating its lock "
3392
return dav_handle_err(r, err, NULL);
3396
/* set the Cache-Control header, per the spec */
3397
apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3399
/* return an appropriate response (HTTP_CREATED) */
3400
return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
3403
/* handle the CHECKOUT method */
3404
static int dav_method_checkout(request_rec *r)
3406
dav_resource *resource;
3407
dav_resource *working_resource;
3408
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3412
int apply_to_vsn = 0;
3413
int is_unreserved = 0;
3415
int create_activity = 0;
3416
apr_array_header_t *activities = NULL;
3418
/* If no versioning provider, decline the request */
3419
if (vsn_hooks == NULL)
3422
if ((result = ap_xml_parse_input(r, &doc)) != OK)
3426
const apr_xml_elem *aset;
3428
if (!dav_validate_root(doc, "checkout")) {
3429
/* This supplies additional information for the default msg. */
3430
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3431
"The request body, if present, must be a "
3432
"DAV:checkout element.");
3433
return HTTP_BAD_REQUEST;
3436
if (dav_find_child(doc->root, "apply-to-version") != NULL) {
3437
if (apr_table_get(r->headers_in, "label") != NULL) {
3438
/* ### we want generic 403/409 XML reporting here */
3439
/* ### DAV:must-not-have-label-and-apply-to-version */
3440
return dav_error_response(r, HTTP_CONFLICT,
3441
"DAV:apply-to-version cannot be "
3442
"used in conjunction with a "
3448
is_unreserved = dav_find_child(doc->root, "unreserved") != NULL;
3449
is_fork_ok = dav_find_child(doc->root, "fork-ok") != NULL;
3451
if ((aset = dav_find_child(doc->root, "activity-set")) != NULL) {
3452
if (dav_find_child(aset, "new") != NULL) {
3453
create_activity = 1;
3456
const apr_xml_elem *child = aset->first_child;
3458
activities = apr_array_make(r->pool, 1, sizeof(const char *));
3460
for (; child != NULL; child = child->next) {
3461
if (child->ns == APR_XML_NS_DAV_ID
3462
&& strcmp(child->name, "href") == 0) {
3465
href = dav_xml_get_cdata(child, r->pool,
3466
1 /* strip_white */);
3467
*(const char **)apr_array_push(activities) = href;
3471
if (activities->nelts == 0) {
3472
/* no href's is a DTD violation:
3473
<!ELEMENT activity-set (href+ | new)>
3476
/* This supplies additional info for the default msg. */
3477
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3478
"Within the DAV:activity-set element, the "
3479
"DAV:new element must be used, or at least "
3480
"one DAV:href must be specified.");
3481
return HTTP_BAD_REQUEST;
3487
/* Ask repository module to resolve the resource */
3488
err = dav_get_resource(r, 1 /*label_allowed*/, apply_to_vsn, &resource);
3490
return dav_handle_err(r, err, NULL);
3492
if (!resource->exists) {
3493
/* Apache will supply a default error for this. */
3494
return HTTP_NOT_FOUND;
3497
/* Check the state of the resource: must be a file or collection,
3498
* must be versioned, and must not already be checked out.
3500
if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3501
&& resource->type != DAV_RESOURCE_TYPE_VERSION) {
3502
return dav_error_response(r, HTTP_CONFLICT,
3503
"Cannot checkout this type of resource.");
3506
if (!resource->versioned) {
3507
return dav_error_response(r, HTTP_CONFLICT,
3508
"Cannot checkout unversioned resource.");
3511
if (resource->working) {
3512
return dav_error_response(r, HTTP_CONFLICT,
3513
"The resource is already checked out to the workspace.");
3516
/* ### do lock checks, once behavior is defined */
3518
/* Do the checkout */
3519
if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/,
3520
is_unreserved, is_fork_ok,
3521
create_activity, activities,
3522
&working_resource)) != NULL) {
3523
err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3524
apr_psprintf(r->pool,
3525
"Could not CHECKOUT resource %s.",
3526
ap_escape_html(r->pool, r->uri)),
3528
return dav_handle_err(r, err, NULL);
3531
/* set the Cache-Control header, per the spec */
3532
apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3534
/* if no working resource created, return OK,
3535
* else return CREATED with working resource URL in Location header
3537
if (working_resource == NULL) {
3539
ap_set_content_length(r, 0);
3543
return dav_created(r, working_resource->uri, "Checked-out resource", 0);
3546
/* handle the UNCHECKOUT method */
3547
static int dav_method_uncheckout(request_rec *r)
3549
dav_resource *resource;
3550
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3554
/* If no versioning provider, decline the request */
3555
if (vsn_hooks == NULL)
3558
if ((result = ap_discard_request_body(r)) != OK) {
3562
/* Ask repository module to resolve the resource */
3563
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3566
return dav_handle_err(r, err, NULL);
3568
if (!resource->exists) {
3569
/* Apache will supply a default error for this. */
3570
return HTTP_NOT_FOUND;
3573
/* Check the state of the resource: must be a file or collection,
3574
* must be versioned, and must be checked out.
3576
if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3577
return dav_error_response(r, HTTP_CONFLICT,
3578
"Cannot uncheckout this type of resource.");
3581
if (!resource->versioned) {
3582
return dav_error_response(r, HTTP_CONFLICT,
3583
"Cannot uncheckout unversioned resource.");
3586
if (!resource->working) {
3587
return dav_error_response(r, HTTP_CONFLICT,
3588
"The resource is not checked out to the workspace.");
3591
/* ### do lock checks, once behavior is defined */
3593
/* Do the uncheckout */
3594
if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
3595
err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3596
apr_psprintf(r->pool,
3597
"Could not UNCHECKOUT resource %s.",
3598
ap_escape_html(r->pool, r->uri)),
3600
return dav_handle_err(r, err, NULL);
3604
ap_set_content_length(r, 0);
3609
/* handle the CHECKIN method */
3610
static int dav_method_checkin(request_rec *r)
3612
dav_resource *resource;
3613
dav_resource *new_version;
3614
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3618
int keep_checked_out = 0;
3620
/* If no versioning provider, decline the request */
3621
if (vsn_hooks == NULL)
3624
if ((result = ap_xml_parse_input(r, &doc)) != OK)
3628
if (!dav_validate_root(doc, "checkin")) {
3629
/* This supplies additional information for the default msg. */
3630
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3631
"The request body, if present, must be a "
3632
"DAV:checkin element.");
3633
return HTTP_BAD_REQUEST;
3636
keep_checked_out = dav_find_child(doc->root, "keep-checked-out") != NULL;
3639
/* Ask repository module to resolve the resource */
3640
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3643
return dav_handle_err(r, err, NULL);
3645
if (!resource->exists) {
3646
/* Apache will supply a default error for this. */
3647
return HTTP_NOT_FOUND;
3650
/* Check the state of the resource: must be a file or collection,
3651
* must be versioned, and must be checked out.
3653
if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
3654
return dav_error_response(r, HTTP_CONFLICT,
3655
"Cannot checkin this type of resource.");
3658
if (!resource->versioned) {
3659
return dav_error_response(r, HTTP_CONFLICT,
3660
"Cannot checkin unversioned resource.");
3663
if (!resource->working) {
3664
return dav_error_response(r, HTTP_CONFLICT,
3665
"The resource is not checked out.");
3668
/* ### do lock checks, once behavior is defined */
3670
/* Do the checkin */
3671
if ((err = (*vsn_hooks->checkin)(resource, keep_checked_out, &new_version))
3673
err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
3674
apr_psprintf(r->pool,
3675
"Could not CHECKIN resource %s.",
3676
ap_escape_html(r->pool, r->uri)),
3678
return dav_handle_err(r, err, NULL);
3681
return dav_created(r, new_version->uri, "Version", 0);
3684
static int dav_method_update(request_rec *r)
3686
dav_resource *resource;
3687
dav_resource *version = NULL;
3688
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3690
apr_xml_elem *child;
3696
dav_response *multi_response;
3698
dav_lookup_result lookup;
3700
/* If no versioning provider, or UPDATE not supported,
3701
* decline the request */
3702
if (vsn_hooks == NULL || vsn_hooks->update == NULL)
3705
if ((depth = dav_get_depth(r, 0)) < 0) {
3706
/* dav_get_depth() supplies additional information for the
3707
* default message. */
3708
return HTTP_BAD_REQUEST;
3711
/* parse the request body */
3712
if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3716
if (doc == NULL || !dav_validate_root(doc, "update")) {
3717
/* This supplies additional information for the default message. */
3718
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3719
"The request body does not contain "
3720
"an \"update\" element.");
3721
return HTTP_BAD_REQUEST;
3724
/* check for label-name or version element, but not both */
3725
if ((child = dav_find_child(doc->root, "label-name")) != NULL)
3727
else if ((child = dav_find_child(doc->root, "version")) != NULL) {
3728
/* get the href element */
3729
if ((child = dav_find_child(child, "href")) == NULL) {
3730
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3731
"The version element does not contain "
3732
"an \"href\" element.");
3733
return HTTP_BAD_REQUEST;
3737
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3738
"The \"update\" element does not contain "
3739
"a \"label-name\" or \"version\" element.");
3740
return HTTP_BAD_REQUEST;
3743
/* a depth greater than zero is only allowed for a label */
3744
if (!is_label && depth != 0) {
3745
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3746
"Depth must be zero for UPDATE with a version");
3747
return HTTP_BAD_REQUEST;
3750
/* get the target value (a label or a version URI) */
3751
apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
3754
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3755
"A \"label-name\" or \"href\" element does not contain "
3757
return HTTP_BAD_REQUEST;
3760
/* Ask repository module to resolve the resource */
3761
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
3764
return dav_handle_err(r, err, NULL);
3766
if (!resource->exists) {
3767
/* Apache will supply a default error for this. */
3768
return HTTP_NOT_FOUND;
3771
/* ### need a general mechanism for reporting precondition violations
3772
* ### (should be returning XML document for 403/409 responses)
3774
if (resource->type != DAV_RESOURCE_TYPE_REGULAR
3775
|| !resource->versioned || resource->working) {
3776
return dav_error_response(r, HTTP_CONFLICT,
3777
"<DAV:must-be-checked-in-version-controlled-resource>");
3780
/* if target is a version, resolve the version resource */
3781
/* ### dav_lookup_uri only allows absolute URIs; is that OK? */
3783
lookup = dav_lookup_uri(target, r, 0 /* must_be_absolute */);
3784
if (lookup.rnew == NULL) {
3785
if (lookup.err.status == HTTP_BAD_REQUEST) {
3786
/* This supplies additional information for the default message. */
3787
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3788
"%s", lookup.err.desc);
3789
return HTTP_BAD_REQUEST;
3792
/* ### this assumes that dav_lookup_uri() only generates a status
3793
* ### that Apache can provide a status line for!! */
3795
return dav_error_response(r, lookup.err.status, lookup.err.desc);
3797
if (lookup.rnew->status != HTTP_OK) {
3798
/* ### how best to report this... */
3799
return dav_error_response(r, lookup.rnew->status,
3800
"Version URI had an error.");
3803
/* resolve version resource */
3804
err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
3805
0 /* use_checked_in */, &version);
3807
return dav_handle_err(r, err, NULL);
3809
/* NULL out target, since we're using a version resource */
3813
/* do the UPDATE operation */
3814
err = (*vsn_hooks->update)(resource, version, target, depth, &multi_response);
3817
err = dav_push_error(r->pool, err->status, 0,
3818
apr_psprintf(r->pool,
3819
"Could not UPDATE %s.",
3820
ap_escape_html(r->pool, r->uri)),
3822
return dav_handle_err(r, err, multi_response);
3825
/* set the Cache-Control header, per the spec */
3826
apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
3829
ap_set_content_length(r, 0);
3834
/* context maintained during LABEL treewalk */
3835
typedef struct dav_label_walker_ctx
3840
/* label being manipulated */
3843
/* label operation */
3845
#define DAV_LABEL_ADD 1
3846
#define DAV_LABEL_SET 2
3847
#define DAV_LABEL_REMOVE 3
3849
/* version provider hooks */
3850
const dav_hooks_vsn *vsn_hooks;
3852
} dav_label_walker_ctx;
3854
static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
3856
dav_label_walker_ctx *ctx = wres->walk_ctx;
3857
dav_error *err = NULL;
3859
/* Check the state of the resource: must be a version or
3860
* non-checkedout version selector
3862
/* ### need a general mechanism for reporting precondition violations
3863
* ### (should be returning XML document for 403/409 responses)
3865
if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
3866
(wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
3867
|| !wres->resource->versioned)) {
3868
err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3869
"<DAV:must-be-version-or-version-selector/>");
3871
else if (wres->resource->working) {
3872
err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
3873
"<DAV:must-not-be-checked-out/>");
3876
/* do the label operation */
3877
if (ctx->label_op == DAV_LABEL_REMOVE)
3878
err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
3880
err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
3881
ctx->label_op == DAV_LABEL_SET);
3885
/* ### need utility routine to add response with description? */
3886
dav_add_response(wres, err->status, NULL);
3887
wres->response->desc = err->desc;
3893
static int dav_method_label(request_rec *r)
3895
dav_resource *resource;
3896
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
3898
apr_xml_elem *child;
3903
dav_label_walker_ctx ctx = { { 0 } };
3904
dav_response *multi_status;
3906
/* If no versioning provider, or the provider doesn't support
3907
* labels, decline the request */
3908
if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
3911
/* Ask repository module to resolve the resource */
3912
err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
3915
return dav_handle_err(r, err, NULL);
3916
if (!resource->exists) {
3917
/* Apache will supply a default error for this. */
3918
return HTTP_NOT_FOUND;
3921
if ((depth = dav_get_depth(r, 0)) < 0) {
3922
/* dav_get_depth() supplies additional information for the
3923
* default message. */
3924
return HTTP_BAD_REQUEST;
3927
/* parse the request body */
3928
if ((result = ap_xml_parse_input(r, &doc)) != OK) {
3932
if (doc == NULL || !dav_validate_root(doc, "label")) {
3933
/* This supplies additional information for the default message. */
3934
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3935
"The request body does not contain "
3936
"a \"label\" element.");
3937
return HTTP_BAD_REQUEST;
3940
/* check for add, set, or remove element */
3941
if ((child = dav_find_child(doc->root, "add")) != NULL) {
3942
ctx.label_op = DAV_LABEL_ADD;
3944
else if ((child = dav_find_child(doc->root, "set")) != NULL) {
3945
ctx.label_op = DAV_LABEL_SET;
3947
else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
3948
ctx.label_op = DAV_LABEL_REMOVE;
3951
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3952
"The \"label\" element does not contain "
3953
"an \"add\", \"set\", or \"remove\" element.");
3954
return HTTP_BAD_REQUEST;
3957
/* get the label string */
3958
if ((child = dav_find_child(child, "label-name")) == NULL) {
3959
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3960
"The label command element does not contain "
3961
"a \"label-name\" element.");
3962
return HTTP_BAD_REQUEST;
3965
apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
3966
&ctx.label, &tsize);
3968
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3969
"A \"label-name\" element does not contain "
3971
return HTTP_BAD_REQUEST;
3974
/* do the label operation walk */
3975
ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
3976
ctx.w.func = dav_label_walker;
3977
ctx.w.walk_ctx = &ctx;
3978
ctx.w.pool = r->pool;
3979
ctx.w.root = resource;
3980
ctx.vsn_hooks = vsn_hooks;
3982
err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
3985
/* some sort of error occurred which terminated the walk */
3986
err = dav_push_error(r->pool, err->status, 0,
3987
"The LABEL operation was terminated prematurely.",
3989
return dav_handle_err(r, err, multi_status);
3992
if (multi_status != NULL) {
3993
/* One or more resources had errors. If depth was zero, convert
3994
* response to simple error, else make sure there is an
3995
* overall error to pass to dav_handle_err()
3998
err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
3999
multi_status = NULL;
4002
err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
4003
"Errors occurred during the LABEL operation.");
4006
return dav_handle_err(r, err, multi_status);
4009
/* set the Cache-Control header, per the spec */
4010
apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4013
ap_set_content_length(r, 0);
4018
static int dav_method_report(request_rec *r)
4020
dav_resource *resource;
4021
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4027
/* If no versioning provider, decline the request */
4028
if (vsn_hooks == NULL)
4031
if ((result = ap_xml_parse_input(r, &doc)) != OK)
4034
/* This supplies additional information for the default msg. */
4035
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4036
"The request body must specify a report.");
4037
return HTTP_BAD_REQUEST;
4040
/* Ask repository module to resolve the resource.
4041
* First determine whether a Target-Selector header is allowed
4044
label_allowed = (*vsn_hooks->report_label_header_allowed)(doc);
4045
err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */,
4048
return dav_handle_err(r, err, NULL);
4050
if (!resource->exists) {
4051
/* Apache will supply a default error for this. */
4052
return HTTP_NOT_FOUND;
4055
/* set up defaults for the report response */
4056
r->status = HTTP_OK;
4057
ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
4059
/* run report hook */
4060
if ((err = (*vsn_hooks->deliver_report)(r, resource, doc,
4061
r->output_filters)) != NULL) {
4062
if (! r->sent_bodyct)
4063
/* No data has been sent to client yet; throw normal error. */
4064
return dav_handle_err(r, err, NULL);
4066
/* If an error occurred during the report delivery, there's
4067
basically nothing we can do but abort the connection and
4068
log an error. This is one of the limitations of HTTP; it
4069
needs to "know" the entire status of the response before
4070
generating it, which is just impossible in these streamy
4071
response situations. */
4072
err = dav_push_error(r->pool, err->status, 0,
4073
"Provider encountered an error while streaming"
4074
" a REPORT response.", err);
4075
dav_log_err(r, err, APLOG_ERR);
4076
r->connection->aborted = 1;
4083
static int dav_method_make_workspace(request_rec *r)
4085
dav_resource *resource;
4086
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4091
/* if no versioning provider, or the provider does not support workspaces,
4092
* decline the request
4094
if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
4097
/* ask repository module to resolve the resource */
4098
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4101
return dav_handle_err(r, err, NULL);
4103
/* parse the request body (must be a mkworkspace element) */
4104
if ((result = ap_xml_parse_input(r, &doc)) != OK) {
4109
|| !dav_validate_root(doc, "mkworkspace")) {
4110
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4111
"The request body does not contain "
4112
"a \"mkworkspace\" element.");
4113
return HTTP_BAD_REQUEST;
4116
/* Check request preconditions */
4118
/* ### need a general mechanism for reporting precondition violations
4119
* ### (should be returning XML document for 403/409 responses)
4122
/* resource must not already exist */
4123
if (resource->exists) {
4124
err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
4125
"<DAV:resource-must-be-null/>");
4126
return dav_handle_err(r, err, NULL);
4129
/* ### what about locking? */
4131
/* attempt to create the workspace */
4132
if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
4133
err = dav_push_error(r->pool, err->status, 0,
4134
apr_psprintf(r->pool,
4135
"Could not create workspace %s.",
4136
ap_escape_html(r->pool, r->uri)),
4138
return dav_handle_err(r, err, NULL);
4141
/* set the Cache-Control header, per the spec */
4142
apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4144
/* return an appropriate response (HTTP_CREATED) */
4145
return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
4148
static int dav_method_make_activity(request_rec *r)
4150
dav_resource *resource;
4151
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4155
/* if no versioning provider, or the provider does not support activities,
4156
* decline the request
4158
if (vsn_hooks == NULL || vsn_hooks->make_activity == NULL)
4161
/* ask repository module to resolve the resource */
4162
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4165
return dav_handle_err(r, err, NULL);
4167
/* MKACTIVITY does not have a defined request body. */
4168
if ((result = ap_discard_request_body(r)) != OK) {
4172
/* Check request preconditions */
4174
/* ### need a general mechanism for reporting precondition violations
4175
* ### (should be returning XML document for 403/409 responses)
4178
/* resource must not already exist */
4179
if (resource->exists) {
4180
err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
4181
"<DAV:resource-must-be-null/>");
4182
return dav_handle_err(r, err, NULL);
4185
/* the provider must say whether the resource can be created as
4186
an activity, i.e. whether the location is ok. */
4187
if (vsn_hooks->can_be_activity != NULL
4188
&& !(*vsn_hooks->can_be_activity)(resource)) {
4189
err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
4190
"<DAV:activity-location-ok/>");
4191
return dav_handle_err(r, err, NULL);
4194
/* ### what about locking? */
4196
/* attempt to create the activity */
4197
if ((err = (*vsn_hooks->make_activity)(resource)) != NULL) {
4198
err = dav_push_error(r->pool, err->status, 0,
4199
apr_psprintf(r->pool,
4200
"Could not create activity %s.",
4201
ap_escape_html(r->pool, r->uri)),
4203
return dav_handle_err(r, err, NULL);
4206
/* set the Cache-Control header, per the spec */
4207
apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4209
/* return an appropriate response (HTTP_CREATED) */
4210
return dav_created(r, resource->uri, "Activity", 0 /*replaced*/);
4213
static int dav_method_baseline_control(request_rec *r)
4216
return HTTP_METHOD_NOT_ALLOWED;
4219
static int dav_method_merge(request_rec *r)
4221
dav_resource *resource;
4222
dav_resource *source_resource;
4223
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
4227
apr_xml_elem *source_elem;
4228
apr_xml_elem *href_elem;
4229
apr_xml_elem *prop_elem;
4233
dav_lookup_result lookup;
4235
/* If no versioning provider, decline the request */
4236
if (vsn_hooks == NULL)
4239
if ((result = ap_xml_parse_input(r, &doc)) != OK)
4242
if (doc == NULL || !dav_validate_root(doc, "merge")) {
4243
/* This supplies additional information for the default msg. */
4244
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4245
"The request body must be present and must be a "
4246
"DAV:merge element.");
4247
return HTTP_BAD_REQUEST;
4250
if ((source_elem = dav_find_child(doc->root, "source")) == NULL) {
4251
/* This supplies additional information for the default msg. */
4252
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4253
"The DAV:merge element must contain a DAV:source "
4255
return HTTP_BAD_REQUEST;
4257
if ((href_elem = dav_find_child(source_elem, "href")) == NULL) {
4258
/* This supplies additional information for the default msg. */
4259
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4260
"The DAV:source element must contain a DAV:href "
4262
return HTTP_BAD_REQUEST;
4264
source = dav_xml_get_cdata(href_elem, r->pool, 1 /* strip_white */);
4266
/* get a subrequest for the source, so that we can get a dav_resource
4268
lookup = dav_lookup_uri(source, r, 0 /* must_be_absolute */);
4269
if (lookup.rnew == NULL) {
4270
if (lookup.err.status == HTTP_BAD_REQUEST) {
4271
/* This supplies additional information for the default message. */
4272
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4273
"%s", lookup.err.desc);
4274
return HTTP_BAD_REQUEST;
4277
/* ### this assumes that dav_lookup_uri() only generates a status
4278
* ### that Apache can provide a status line for!! */
4280
return dav_error_response(r, lookup.err.status, lookup.err.desc);
4282
if (lookup.rnew->status != HTTP_OK) {
4283
/* ### how best to report this... */
4284
return dav_error_response(r, lookup.rnew->status,
4285
"Merge source URI had an error.");
4287
err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
4288
0 /* use_checked_in */, &source_resource);
4290
return dav_handle_err(r, err, NULL);
4292
no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL;
4293
no_checkout = dav_find_child(doc->root, "no-checkout") != NULL;
4295
prop_elem = dav_find_child(doc->root, "prop");
4297
/* ### check RFC. I believe the DAV:merge element may contain any
4298
### element also allowed within DAV:checkout. need to extract them
4299
### here, and pass them along.
4300
### if so, then refactor the CHECKOUT method handling so we can reuse
4301
### the code. maybe create a structure to hold CHECKOUT parameters
4302
### which can be passed to the checkout() and merge() hooks. */
4304
/* Ask repository module to resolve the resource */
4305
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4308
return dav_handle_err(r, err, NULL);
4309
if (!resource->exists) {
4310
/* Apache will supply a default error for this. */
4311
return HTTP_NOT_FOUND;
4314
/* ### check the source and target resources flags/types */
4316
/* ### do lock checks, once behavior is defined */
4318
/* set the Cache-Control header, per the spec */
4320
apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
4322
/* Initialize these values for a standard MERGE response. If the MERGE
4323
is going to do something different (i.e. an error), then it must
4324
return a dav_error, and we'll reset these values properly. */
4325
r->status = HTTP_OK;
4326
ap_set_content_type(r, "text/xml");
4328
/* ### should we do any preliminary response generation? probably not,
4329
### because we may have an error, thus demanding something else in
4330
### the response body. */
4332
/* Do the merge, including any response generation. */
4333
if ((err = (*vsn_hooks->merge)(resource, source_resource,
4334
no_auto_merge, no_checkout,
4336
r->output_filters)) != NULL) {
4337
/* ### is err->status the right error here? */
4338
err = dav_push_error(r->pool, err->status, 0,
4339
apr_psprintf(r->pool,
4340
"Could not MERGE resource \"%s\" "
4342
ap_escape_html(r->pool, source),
4343
ap_escape_html(r->pool, r->uri)),
4345
return dav_handle_err(r, err, NULL);
4348
/* the response was fully generated by the merge() hook. */
4349
/* ### urk. does this prevent logging? need to check... */
4353
static int dav_method_bind(request_rec *r)
4355
dav_resource *resource;
4356
dav_resource *binding;
4357
dav_auto_version_info av_info;
4358
const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
4362
dav_response *multi_response = NULL;
4363
dav_lookup_result lookup;
4366
/* If no bindings provider, decline the request */
4367
if (binding_hooks == NULL)
4370
/* Ask repository module to resolve the resource */
4371
err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
4374
return dav_handle_err(r, err, NULL);
4376
if (!resource->exists) {
4377
/* Apache will supply a default error for this. */
4378
return HTTP_NOT_FOUND;
4381
/* get the destination URI */
4382
dest = apr_table_get(r->headers_in, "Destination");
4384
/* This supplies additional information for the default message. */
4385
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4386
"The request is missing a Destination header.");
4387
return HTTP_BAD_REQUEST;
4390
lookup = dav_lookup_uri(dest, r, 0 /* must_be_absolute */);
4391
if (lookup.rnew == NULL) {
4392
if (lookup.err.status == HTTP_BAD_REQUEST) {
4393
/* This supplies additional information for the default message. */
4394
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4395
"%s", lookup.err.desc);
4396
return HTTP_BAD_REQUEST;
4398
else if (lookup.err.status == HTTP_BAD_GATEWAY) {
4399
/* ### Bindings protocol draft 02 says to return 507
4400
* ### (Cross Server Binding Forbidden); Apache already defines 507
4401
* ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
4402
* ### HTTP_FORBIDDEN
4404
return dav_error_response(r, HTTP_FORBIDDEN,
4405
"Cross server bindings are not "
4406
"allowed by this server.");
4409
/* ### this assumes that dav_lookup_uri() only generates a status
4410
* ### that Apache can provide a status line for!! */
4412
return dav_error_response(r, lookup.err.status, lookup.err.desc);
4414
if (lookup.rnew->status != HTTP_OK) {
4415
/* ### how best to report this... */
4416
return dav_error_response(r, lookup.rnew->status,
4417
"Destination URI had an error.");
4420
/* resolve binding resource */
4421
err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
4422
0 /* use_checked_in */, &binding);
4424
return dav_handle_err(r, err, NULL);
4426
/* are the two resources handled by the same repository? */
4427
if (resource->hooks != binding->hooks) {
4428
/* ### this message exposes some backend config, but screw it... */
4429
return dav_error_response(r, HTTP_BAD_GATEWAY,
4430
"Destination URI is handled by a "
4431
"different repository than the source URI. "
4432
"BIND between repositories is not possible.");
4435
/* get and parse the overwrite header value */
4436
if ((overwrite = dav_get_overwrite(r)) < 0) {
4437
/* dav_get_overwrite() supplies additional information for the
4438
* default message. */
4439
return HTTP_BAD_REQUEST;
4442
/* quick failure test: if dest exists and overwrite is false. */
4443
if (binding->exists && !overwrite) {
4444
return dav_error_response(r, HTTP_PRECONDITION_FAILED,
4445
"Destination is not empty and "
4446
"Overwrite is not \"T\"");
4449
/* are the source and destination the same? */
4450
if ((*resource->hooks->is_same_resource)(resource, binding)) {
4451
return dav_error_response(r, HTTP_FORBIDDEN,
4452
"Source and Destination URIs are the same.");
4456
* Check If-Headers and existing locks for destination. Note that we
4457
* use depth==infinity since the target (hierarchy) will be deleted
4458
* before the move/copy is completed.
4460
* Note that we are overwriting the target, which implies a DELETE, so
4461
* we are subject to the error/response rules as a DELETE. Namely, we
4462
* will return a 424 error if any of the validations fail.
4463
* (see dav_method_delete() for more information)
4465
if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
4468
| DAV_VALIDATE_USE_424, NULL)) != NULL) {
4469
err = dav_push_error(r->pool, err->status, 0,
4470
apr_psprintf(r->pool,
4471
"Could not BIND %s due to a "
4472
"failed precondition on the "
4473
"destination (e.g. locks).",
4474
ap_escape_html(r->pool, r->uri)),
4476
return dav_handle_err(r, err, multi_response);
4479
/* guard against creating circular bindings */
4480
if (resource->collection
4481
&& (*resource->hooks->is_parent_resource)(resource, binding)) {
4482
return dav_error_response(r, HTTP_FORBIDDEN,
4483
"Source collection contains the Destination.");
4485
if (resource->collection
4486
&& (*resource->hooks->is_parent_resource)(binding, resource)) {
4487
/* The destination must exist (since it contains the source), and
4488
* a condition above implies Overwrite==T. Obviously, we cannot
4489
* delete the Destination before the BIND, as that would
4490
* delete the Source.
4493
return dav_error_response(r, HTTP_FORBIDDEN,
4494
"Destination collection contains the Source and "
4495
"Overwrite has been specified.");
4498
/* prepare the destination collection for modification */
4499
if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */,
4500
&av_info)) != NULL) {
4501
/* could not make destination writable */
4502
return dav_handle_err(r, err, NULL);
4505
/* If target exists, remove it first (we know Ovewrite must be TRUE).
4506
* Then try to bind to the resource.
4508
if (binding->exists)
4509
err = (*resource->hooks->remove_resource)(binding, &multi_response);
4512
err = (*binding_hooks->bind_resource)(resource, binding);
4515
/* restore parent collection states */
4516
err2 = dav_auto_checkin(r, NULL,
4517
err != NULL /* undo if error */,
4518
0 /* unlock */, &av_info);
4520
/* check for error from remove/bind operations */
4522
err = dav_push_error(r->pool, err->status, 0,
4523
apr_psprintf(r->pool,
4524
"Could not BIND %s.",
4525
ap_escape_html(r->pool, r->uri)),
4527
return dav_handle_err(r, err, multi_response);
4530
/* check for errors from reverting writability */
4532
/* just log a warning */
4533
err = dav_push_error(r->pool, err2->status, 0,
4534
"The BIND was successful, but there was a "
4535
"problem automatically checking in the "
4536
"source parent collection.",
4538
dav_log_err(r, err, APLOG_WARNING);
4541
/* return an appropriate response (HTTP_CREATED) */
4542
/* ### spec doesn't say what happens when destination was replaced */
4543
return dav_created(r, lookup.rnew->uri, "Binding", 0);
4548
* Response handler for DAV resources
4550
static int dav_handler(request_rec *r)
4552
if (strcmp(r->handler, DAV_HANDLER_NAME) != 0)
4555
/* Reject requests with an unescaped hash character, as these may
4556
* be more destructive than the user intended. */
4557
if (r->parsed_uri.fragment != NULL) {
4558
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4559
"buggy client used un-escaped hash in Request-URI");
4560
return dav_error_response(r, HTTP_BAD_REQUEST,
4561
"The request was invalid: the URI included "
4562
"an un-escaped hash character");
4565
/* ### do we need to do anything with r->proxyreq ?? */
4568
* ### anything else to do here? could another module and/or
4569
* ### config option "take over" the handler here? i.e. how do
4570
* ### we lock down this hierarchy so that we are the ultimate
4571
* ### arbiter? (or do we simply depend on the administrator
4572
* ### to avoid conflicting configurations?)
4576
* Set up the methods mask, since that's one of the reasons this handler
4577
* gets called, and lower-level things may need the info.
4579
* First, set the mask to the methods we handle directly. Since by
4580
* definition we own our managed space, we unconditionally set
4581
* the r->allowed field rather than ORing our values with anything
4582
* any other module may have put in there.
4584
* These are the HTTP-defined methods that we handle directly.
4587
| (AP_METHOD_BIT << M_GET)
4588
| (AP_METHOD_BIT << M_PUT)
4589
| (AP_METHOD_BIT << M_DELETE)
4590
| (AP_METHOD_BIT << M_OPTIONS)
4591
| (AP_METHOD_BIT << M_INVALID);
4594
* These are the DAV methods we handle.
4597
| (AP_METHOD_BIT << M_COPY)
4598
| (AP_METHOD_BIT << M_LOCK)
4599
| (AP_METHOD_BIT << M_UNLOCK)
4600
| (AP_METHOD_BIT << M_MKCOL)
4601
| (AP_METHOD_BIT << M_MOVE)
4602
| (AP_METHOD_BIT << M_PROPFIND)
4603
| (AP_METHOD_BIT << M_PROPPATCH);
4606
* These are methods that we don't handle directly, but let the
4607
* server's default handler do for us as our agent.
4610
| (AP_METHOD_BIT << M_POST);
4612
/* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
4613
* ### is sent; it will need the other allowed states; since the default
4614
* ### handler is not called on error, then it doesn't add the other
4615
* ### allowed states, so we must
4618
/* ### we might need to refine this for just where we return the error.
4619
* ### also, there is the issue with other methods (see ISSUES)
4622
/* dispatch the appropriate method handler */
4623
if (r->method_number == M_GET) {
4624
return dav_method_get(r);
4627
if (r->method_number == M_PUT) {
4628
return dav_method_put(r);
4631
if (r->method_number == M_POST) {
4632
return dav_method_post(r);
4635
if (r->method_number == M_DELETE) {
4636
return dav_method_delete(r);
4639
if (r->method_number == M_OPTIONS) {
4640
return dav_method_options(r);
4643
if (r->method_number == M_PROPFIND) {
4644
return dav_method_propfind(r);
4647
if (r->method_number == M_PROPPATCH) {
4648
return dav_method_proppatch(r);
4651
if (r->method_number == M_MKCOL) {
4652
return dav_method_mkcol(r);
4655
if (r->method_number == M_COPY) {
4656
return dav_method_copymove(r, DAV_DO_COPY);
4659
if (r->method_number == M_MOVE) {
4660
return dav_method_copymove(r, DAV_DO_MOVE);
4663
if (r->method_number == M_LOCK) {
4664
return dav_method_lock(r);
4667
if (r->method_number == M_UNLOCK) {
4668
return dav_method_unlock(r);
4671
if (r->method_number == M_VERSION_CONTROL) {
4672
return dav_method_vsn_control(r);
4675
if (r->method_number == M_CHECKOUT) {
4676
return dav_method_checkout(r);
4679
if (r->method_number == M_UNCHECKOUT) {
4680
return dav_method_uncheckout(r);
4683
if (r->method_number == M_CHECKIN) {
4684
return dav_method_checkin(r);
4687
if (r->method_number == M_UPDATE) {
4688
return dav_method_update(r);
4691
if (r->method_number == M_LABEL) {
4692
return dav_method_label(r);
4695
if (r->method_number == M_REPORT) {
4696
return dav_method_report(r);
4699
if (r->method_number == M_MKWORKSPACE) {
4700
return dav_method_make_workspace(r);
4703
if (r->method_number == M_MKACTIVITY) {
4704
return dav_method_make_activity(r);
4707
if (r->method_number == M_BASELINE_CONTROL) {
4708
return dav_method_baseline_control(r);
4711
if (r->method_number == M_MERGE) {
4712
return dav_method_merge(r);
4716
if (r->method_number == dav_methods[DAV_M_BIND]) {
4717
return dav_method_bind(r);
4721
if (r->method_number == dav_methods[DAV_M_SEARCH]) {
4722
return dav_method_search(r);
4725
/* ### add'l methods for Advanced Collections, ACLs */
4730
static int dav_fixups(request_rec *r)
4734
/* quickly ignore any HTTP/0.9 requests which aren't subreqs. */
4735
if (r->assbackwards && !r->main) {
4739
conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
4742
/* if DAV is not enabled, then we've got nothing to do */
4743
if (conf->provider == NULL) {
4747
/* We are going to handle almost every request. In certain cases,
4748
the provider maps to the filesystem (thus, handle_get is
4749
FALSE), and core Apache will handle it. a For that case, we
4750
just return right away. */
4751
if (r->method_number == M_GET) {
4753
* ### need some work to pull Content-Type and Content-Language
4754
* ### from the property database.
4758
* If the repository hasn't indicated that it will handle the
4759
* GET method, then just punt.
4761
* ### this isn't quite right... taking over the response can break
4762
* ### things like mod_negotiation. need to look into this some more.
4764
if (!conf->provider->repos->handle_get) {
4769
/* ### this is wrong. We should only be setting the r->handler for the
4770
* requests that mod_dav knows about. If we set the handler for M_POST
4771
* requests, then CGI scripts that use POST will return the source for the
4772
* script. However, mod_dav DOES handle POST, so something else needs
4775
if (r->method_number != M_POST) {
4777
/* We are going to be handling the response for this resource. */
4778
r->handler = DAV_HANDLER_NAME;
4785
static void register_hooks(apr_pool_t *p)
4787
ap_hook_handler(dav_handler, NULL, NULL, APR_HOOK_MIDDLE);
4788
ap_hook_post_config(dav_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
4789
ap_hook_fixups(dav_fixups, NULL, NULL, APR_HOOK_MIDDLE);
4791
dav_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, APR_HOOK_LAST);
4792
dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
4793
NULL, NULL, APR_HOOK_MIDDLE);
4795
dav_core_register_uris(p);
4798
/*---------------------------------------------------------------------------
4800
* Configuration info for the module
4803
static const command_rec dav_cmds[] =
4805
/* per directory/location */
4806
AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
4807
"specify the DAV provider for a directory or location"),
4809
/* per directory/location, or per server */
4810
AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
4811
ACCESS_CONF|RSRC_CONF,
4812
"specify minimum allowed timeout"),
4814
/* per directory/location, or per server */
4815
AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL,
4816
ACCESS_CONF|RSRC_CONF,
4817
"allow Depth infinity PROPFIND requests"),
4822
module DAV_DECLARE_DATA dav_module =
4824
STANDARD20_MODULE_STUFF,
4825
dav_create_dir_config, /* dir config creater */
4826
dav_merge_dir_config, /* dir merger --- default is to override */
4827
dav_create_server_config, /* server config */
4828
dav_merge_server_config, /* merge server config */
4829
dav_cmds, /* command table */
4830
register_hooks, /* register hooks */
4834
APR_HOOK_LINK(gather_propsets)
4835
APR_HOOK_LINK(find_liveprop)
4836
APR_HOOK_LINK(insert_all_liveprops)
4839
APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets,
4840
(apr_array_header_t *uris),
4843
APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, find_liveprop,
4844
(const dav_resource *resource,
4845
const char *ns_uri, const char *name,
4846
const dav_hooks_liveprop **hooks),
4847
(resource, ns_uri, name, hooks), 0)
4849
APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops,
4850
(request_rec *r, const dav_resource *resource,
4851
dav_prop_insert what, apr_text_header *phdr),
4852
(r, resource, what, phdr))