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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17/* 18 * DAV extension module for Apache 2.0.* 19 * 20 * This module is repository-independent. It depends on hooks provided by a 21 * repository implementation. 22 * 23 * APACHE ISSUES: 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 31 * status codes 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 34 * into r->status. 35 * - http_vhost functions should apply "const" to their params 36 * 37 * DESIGN NOTES: 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. 43 */ 44 45#include "apr_strings.h" 46#include "apr_lib.h" /* for apr_is* */ 47 48#define APR_WANT_STRFUNC 49#include "apr_want.h" 50 51#include "httpd.h" 52#include "http_config.h" 53#include "http_core.h" 54#include "http_log.h" 55#include "http_main.h" 56#include "http_protocol.h" 57#include "http_request.h" 58#include "util_script.h" 59 60#include "mod_dav.h" 61 62#include "ap_provider.h" 63 64 65/* ### what is the best way to set this? */ 66#define DAV_DEFAULT_PROVIDER "filesystem" 67 68/* used to denote that mod_dav will be handling this request */ 69#define DAV_HANDLER_NAME "dav-handler" 70 71APLOG_USE_MODULE(dav); 72 73enum { 74 DAV_ENABLED_UNSET = 0, 75 DAV_ENABLED_OFF, 76 DAV_ENABLED_ON 77}; 78 79/* per-dir configuration */ 80typedef struct { 81 const char *provider_name; 82 const dav_provider *provider; 83 const char *dir; 84 int locktimeout; 85 int allow_depthinfinity; 86 87} dav_dir_conf; 88 89/* per-server configuration */ 90typedef struct { 91 int unused; 92 93} dav_server_conf; 94 95#define DAV_INHERIT_VALUE(parent, child, field) \ 96 ((child)->field ? (child)->field : (parent)->field) 97 98 99/* forward-declare for use in configuration lookup */ 100extern module DAV_DECLARE_DATA dav_module; 101 102/* DAV methods */ 103enum { 104 DAV_M_BIND = 0, 105 DAV_M_SEARCH, 106 DAV_M_LAST 107}; 108static int dav_methods[DAV_M_LAST]; 109 110 111static int dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, 112 server_rec *s) 113{ 114 /* DBG0("dav_init_handler"); */ 115 116 /* Register DAV methods */ 117 dav_methods[DAV_M_BIND] = ap_method_register(p, "BIND"); 118 dav_methods[DAV_M_SEARCH] = ap_method_register(p, "SEARCH"); 119 120 return OK; 121} 122 123static void *dav_create_server_config(apr_pool_t *p, server_rec *s) 124{ 125 dav_server_conf *newconf; 126 127 newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf)); 128 129 /* ### this isn't used at the moment... */ 130 131 return newconf; 132} 133 134static void *dav_merge_server_config(apr_pool_t *p, void *base, void *overrides) 135{ 136#if 0 137 dav_server_conf *child = overrides; 138#endif 139 dav_server_conf *newconf; 140 141 newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf)); 142 143 /* ### nothing to merge right now... */ 144 145 return newconf; 146} 147 148static void *dav_create_dir_config(apr_pool_t *p, char *dir) 149{ 150 /* NOTE: dir==NULL creates the default per-dir config */ 151 152 dav_dir_conf *conf; 153 154 conf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*conf)); 155 156 /* clean up the directory to remove any trailing slash */ 157 if (dir != NULL) { 158 char *d; 159 apr_size_t l; 160 161 l = strlen(dir); 162 d = apr_pstrmemdup(p, dir, l); 163 if (l > 1 && d[l - 1] == '/') 164 d[l - 1] = '\0'; 165 conf->dir = d; 166 } 167 168 return conf; 169} 170 171static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides) 172{ 173 dav_dir_conf *parent = base; 174 dav_dir_conf *child = overrides; 175 dav_dir_conf *newconf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*newconf)); 176 177 /* DBG3("dav_merge_dir_config: new=%08lx base=%08lx overrides=%08lx", 178 (long)newconf, (long)base, (long)overrides); */ 179 180 newconf->provider_name = DAV_INHERIT_VALUE(parent, child, provider_name); 181 newconf->provider = DAV_INHERIT_VALUE(parent, child, provider); 182 if (parent->provider_name != NULL) { 183 if (child->provider_name == NULL) { 184 ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00578) 185 "\"DAV Off\" cannot be used to turn off a subtree " 186 "of a DAV-enabled location."); 187 } 188 else if (strcasecmp(child->provider_name, 189 parent->provider_name) != 0) { 190 ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00579) 191 "A subtree cannot specify a different DAV provider " 192 "than its parent."); 193 } 194 } 195 196 newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout); 197 newconf->dir = DAV_INHERIT_VALUE(parent, child, dir); 198 newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child, 199 allow_depthinfinity); 200 201 return newconf; 202} 203 204static const dav_provider *dav_get_provider(request_rec *r) 205{ 206 dav_dir_conf *conf; 207 208 conf = ap_get_module_config(r->per_dir_config, &dav_module); 209 /* assert: conf->provider_name != NULL 210 (otherwise, DAV is disabled, and we wouldn't be here) */ 211 212 /* assert: conf->provider != NULL 213 (checked when conf->provider_name is set) */ 214 return conf->provider; 215} 216 217DAV_DECLARE(const dav_hooks_locks *) dav_get_lock_hooks(request_rec *r) 218{ 219 return dav_get_provider(r)->locks; 220} 221 222DAV_DECLARE(const dav_hooks_propdb *) dav_get_propdb_hooks(request_rec *r) 223{ 224 return dav_get_provider(r)->propdb; 225} 226 227DAV_DECLARE(const dav_hooks_vsn *) dav_get_vsn_hooks(request_rec *r) 228{ 229 return dav_get_provider(r)->vsn; 230} 231 232DAV_DECLARE(const dav_hooks_binding *) dav_get_binding_hooks(request_rec *r) 233{ 234 return dav_get_provider(r)->binding; 235} 236 237DAV_DECLARE(const dav_hooks_search *) dav_get_search_hooks(request_rec *r) 238{ 239 return dav_get_provider(r)->search; 240} 241 242/* 243 * Command handler for the DAV directive, which is TAKE1. 244 */ 245static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1) 246{ 247 dav_dir_conf *conf = (dav_dir_conf *)config; 248 249 if (strcasecmp(arg1, "on") == 0) { 250 conf->provider_name = DAV_DEFAULT_PROVIDER; 251 } 252 else if (strcasecmp(arg1, "off") == 0) { 253 conf->provider_name = NULL; 254 conf->provider = NULL; 255 } 256 else { 257 conf->provider_name = apr_pstrdup(cmd->pool, arg1); 258 } 259 260 if (conf->provider_name != NULL) { 261 /* lookup and cache the actual provider now */ 262 conf->provider = dav_lookup_provider(conf->provider_name); 263 264 if (conf->provider == NULL) { 265 /* by the time they use it, the provider should be loaded and 266 registered with us. */ 267 return apr_psprintf(cmd->pool, 268 "Unknown DAV provider: %s", 269 conf->provider_name); 270 } 271 } 272 273 return NULL; 274} 275 276/* 277 * Command handler for the DAVDepthInfinity directive, which is FLAG. 278 */ 279static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config, 280 int arg) 281{ 282 dav_dir_conf *conf = (dav_dir_conf *)config; 283 284 if (arg) 285 conf->allow_depthinfinity = DAV_ENABLED_ON; 286 else 287 conf->allow_depthinfinity = DAV_ENABLED_OFF; 288 return NULL; 289} 290 291/* 292 * Command handler for DAVMinTimeout directive, which is TAKE1 293 */ 294static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config, 295 const char *arg1) 296{ 297 dav_dir_conf *conf = (dav_dir_conf *)config; 298 299 conf->locktimeout = atoi(arg1); 300 if (conf->locktimeout < 0) 301 return "DAVMinTimeout requires a non-negative integer."; 302 303 return NULL; 304} 305 306/* 307** dav_error_response() 308** 309** Send a nice response back to the user. In most cases, Apache doesn't 310** allow us to provide details in the body about what happened. This 311** function allows us to completely specify the response body. 312** 313** ### this function is not logging any errors! (e.g. the body) 314*/ 315static int dav_error_response(request_rec *r, int status, const char *body) 316{ 317 r->status = status; 318 319 ap_set_content_type(r, "text/html; charset=ISO-8859-1"); 320 321 /* begin the response now... */ 322 ap_rvputs(r, 323 DAV_RESPONSE_BODY_1, 324 r->status_line, 325 DAV_RESPONSE_BODY_2, 326 &r->status_line[4], 327 DAV_RESPONSE_BODY_3, 328 body, 329 DAV_RESPONSE_BODY_4, 330 ap_psignature("<hr />\n", r), 331 DAV_RESPONSE_BODY_5, 332 NULL); 333 334 /* the response has been sent. */ 335 /* 336 * ### Use of DONE obviates logging..! 337 */ 338 return DONE; 339} 340 341 342/* 343 * Send a "standardized" error response based on the error's namespace & tag 344 */ 345static int dav_error_response_tag(request_rec *r, 346 dav_error *err) 347{ 348 r->status = err->status; 349 350 ap_set_content_type(r, DAV_XML_CONTENT_TYPE); 351 352 ap_rputs(DAV_XML_HEADER DEBUG_CR 353 "<D:error xmlns:D=\"DAV:\"", r); 354 355 if (err->desc != NULL) { 356 /* ### should move this namespace somewhere (with the others!) */ 357 ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r); 358 } 359 360 if (err->namespace != NULL) { 361 ap_rprintf(r, 362 " xmlns:C=\"%s\">" DEBUG_CR 363 "<C:%s/>" DEBUG_CR, 364 err->namespace, err->tagname); 365 } 366 else { 367 ap_rprintf(r, 368 ">" DEBUG_CR 369 "<D:%s/>" DEBUG_CR, err->tagname); 370 } 371 372 /* here's our mod_dav specific tag: */ 373 if (err->desc != NULL) { 374 ap_rprintf(r, 375 "<m:human-readable errcode=\"%d\">" DEBUG_CR 376 "%s" DEBUG_CR 377 "</m:human-readable>" DEBUG_CR, 378 err->error_id, 379 apr_xml_quote_string(r->pool, err->desc, 0)); 380 } 381 382 ap_rputs("</D:error>" DEBUG_CR, r); 383 384 /* the response has been sent. */ 385 /* 386 * ### Use of DONE obviates logging..! 387 */ 388 return DONE; 389} 390 391 392/* 393 * Apache's URI escaping does not replace '&' since that is a valid character 394 * in a URI (to form a query section). We must explicitly handle it so that 395 * we can embed the URI into an XML document. 396 */ 397static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri) 398{ 399 /* check the easy case... */ 400 if (ap_strchr_c(uri, '&') == NULL) 401 return uri; 402 403 /* there was a '&', so more work is needed... sigh. */ 404 405 /* 406 * Note: this is a teeny bit of overkill since we know there are no 407 * '<' or '>' characters, but who cares. 408 */ 409 return apr_xml_quote_string(p, uri, 0); 410} 411 412 413/* Write a complete RESPONSE object out as a <DAV:repsonse> xml 414 element. Data is sent into brigade BB, which is auto-flushed into 415 OUTPUT filter stack. Use POOL for any temporary allocations. 416 417 [Presumably the <multistatus> tag has already been written; this 418 routine is shared by dav_send_multistatus and dav_stream_response.] 419*/ 420static void dav_send_one_response(dav_response *response, 421 apr_bucket_brigade *bb, 422 ap_filter_t *output, 423 apr_pool_t *pool) 424{ 425 apr_text *t = NULL; 426 427 if (response->propresult.xmlns == NULL) { 428 ap_fputs(output, bb, "<D:response>"); 429 } 430 else { 431 ap_fputs(output, bb, "<D:response"); 432 for (t = response->propresult.xmlns; t; t = t->next) { 433 ap_fputs(output, bb, t->text); 434 } 435 ap_fputc(output, bb, '>'); 436 } 437 438 ap_fputstrs(output, bb, 439 DEBUG_CR "<D:href>", 440 dav_xml_escape_uri(pool, response->href), 441 "</D:href>" DEBUG_CR, 442 NULL); 443 444 if (response->propresult.propstats == NULL) { 445 /* use the Status-Line text from Apache. Note, this will 446 * default to 500 Internal Server Error if first->status 447 * is not a known (or valid) status code. 448 */ 449 ap_fputstrs(output, bb, 450 "<D:status>HTTP/1.1 ", 451 ap_get_status_line(response->status), 452 "</D:status>" DEBUG_CR, 453 NULL); 454 } 455 else { 456 /* assume this includes <propstat> and is quoted properly */ 457 for (t = response->propresult.propstats; t; t = t->next) { 458 ap_fputs(output, bb, t->text); 459 } 460 } 461 462 if (response->desc != NULL) { 463 /* 464 * We supply the description, so we know it doesn't have to 465 * have any escaping/encoding applied to it. 466 */ 467 ap_fputstrs(output, bb, 468 "<D:responsedescription>", 469 response->desc, 470 "</D:responsedescription>" DEBUG_CR, 471 NULL); 472 } 473 474 ap_fputs(output, bb, "</D:response>" DEBUG_CR); 475} 476 477 478/* Factorized helper function: prep request_rec R for a multistatus 479 response and write <multistatus> tag into BB, destined for 480 R->output_filters. Use xml NAMESPACES in initial tag, if 481 non-NULL. */ 482static void dav_begin_multistatus(apr_bucket_brigade *bb, 483 request_rec *r, int status, 484 apr_array_header_t *namespaces) 485{ 486 /* Set the correct status and Content-Type */ 487 r->status = status; 488 ap_set_content_type(r, DAV_XML_CONTENT_TYPE); 489 490 /* Send the headers and actual multistatus response now... */ 491 ap_fputs(r->output_filters, bb, DAV_XML_HEADER DEBUG_CR 492 "<D:multistatus xmlns:D=\"DAV:\""); 493 494 if (namespaces != NULL) { 495 int i; 496 497 for (i = namespaces->nelts; i--; ) { 498 ap_fprintf(r->output_filters, bb, " xmlns:ns%d=\"%s\"", i, 499 APR_XML_GET_URI_ITEM(namespaces, i)); 500 } 501 } 502 503 ap_fputs(r->output_filters, bb, ">" DEBUG_CR); 504} 505 506/* Finish a multistatus response started by dav_begin_multistatus: */ 507static apr_status_t dav_finish_multistatus(request_rec *r, 508 apr_bucket_brigade *bb) 509{ 510 apr_bucket *b; 511 512 ap_fputs(r->output_filters, bb, "</D:multistatus>" DEBUG_CR); 513 514 /* indicate the end of the response body */ 515 b = apr_bucket_eos_create(r->connection->bucket_alloc); 516 APR_BRIGADE_INSERT_TAIL(bb, b); 517 518 /* deliver whatever might be remaining in the brigade */ 519 return ap_pass_brigade(r->output_filters, bb); 520} 521 522static void dav_send_multistatus(request_rec *r, int status, 523 dav_response *first, 524 apr_array_header_t *namespaces) 525{ 526 apr_pool_t *subpool; 527 apr_bucket_brigade *bb = apr_brigade_create(r->pool, 528 r->connection->bucket_alloc); 529 530 dav_begin_multistatus(bb, r, status, namespaces); 531 532 apr_pool_create(&subpool, r->pool); 533 534 for (; first != NULL; first = first->next) { 535 apr_pool_clear(subpool); 536 dav_send_one_response(first, bb, r->output_filters, subpool); 537 } 538 apr_pool_destroy(subpool); 539 540 dav_finish_multistatus(r, bb); 541} 542 543/* 544 * dav_log_err() 545 * 546 * Write error information to the log. 547 */ 548static void dav_log_err(request_rec *r, dav_error *err, int level) 549{ 550 dav_error *errscan; 551 552 /* Log the errors */ 553 /* ### should have a directive to log the first or all */ 554 for (errscan = err; errscan != NULL; errscan = errscan->prev) { 555 if (errscan->desc == NULL) 556 continue; 557 558 ap_log_rerror(APLOG_MARK, level, errscan->aprerr, r, "%s [%d, #%d]", 559 errscan->desc, errscan->status, errscan->error_id); 560 } 561} 562 563/* 564 * dav_handle_err() 565 * 566 * Handle the standard error processing. <err> must be non-NULL. 567 * 568 * <response> is set by the following: 569 * - dav_validate_request() 570 * - dav_add_lock() 571 * - repos_hooks->remove_resource 572 * - repos_hooks->move_resource 573 * - repos_hooks->copy_resource 574 * - vsn_hooks->update 575 */ 576static int dav_handle_err(request_rec *r, dav_error *err, 577 dav_response *response) 578{ 579 /* log the errors */ 580 dav_log_err(r, err, APLOG_ERR); 581 582 if (response == NULL) { 583 dav_error *stackerr = err; 584 585 /* our error messages are safe; tell Apache this */ 586 apr_table_setn(r->notes, "verbose-error-to", "*"); 587 588 /* Didn't get a multistatus response passed in, but we still 589 might be able to generate a standard <D:error> response. 590 Search the error stack for an errortag. */ 591 while (stackerr != NULL && stackerr->tagname == NULL) 592 stackerr = stackerr->prev; 593 594 if (stackerr != NULL && stackerr->tagname != NULL) 595 return dav_error_response_tag(r, stackerr); 596 597 return err->status; 598 } 599 600 /* send the multistatus and tell Apache the request/response is DONE. */ 601 dav_send_multistatus(r, err->status, response, NULL); 602 return DONE; 603} 604 605/* handy function for return values of methods that (may) create things. 606 * locn if provided is assumed to be escaped. */ 607static int dav_created(request_rec *r, const char *locn, const char *what, 608 int replaced) 609{ 610 const char *body; 611 612 if (locn == NULL) { 613 locn = r->unparsed_uri; 614 } 615 616 /* did the target resource already exist? */ 617 if (replaced) { 618 /* Apache will supply a default message */ 619 return HTTP_NO_CONTENT; 620 } 621 622 /* Per HTTP/1.1, S10.2.2: add a Location header to contain the 623 * URI that was created. */ 624 625 /* Convert locn to an absolute URI, and return in Location header */ 626 apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, locn, r)); 627 628 /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */ 629 630 /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so 631 * we must manufacture the entire response. */ 632 body = apr_psprintf(r->pool, "%s %s has been created.", 633 what, ap_escape_html(r->pool, locn)); 634 return dav_error_response(r, HTTP_CREATED, body); 635} 636 637/* ### move to dav_util? */ 638DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth) 639{ 640 const char *depth = apr_table_get(r->headers_in, "Depth"); 641 642 if (depth == NULL) { 643 return def_depth; 644 } 645 646 if (strcasecmp(depth, "infinity") == 0) { 647 return DAV_INFINITY; 648 } 649 else if (strcmp(depth, "0") == 0) { 650 return 0; 651 } 652 else if (strcmp(depth, "1") == 0) { 653 return 1; 654 } 655 656 /* The caller will return an HTTP_BAD_REQUEST. This will augment the 657 * default message that Apache provides. */ 658 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00580) 659 "An invalid Depth header was specified."); 660 return -1; 661} 662 663static int dav_get_overwrite(request_rec *r) 664{ 665 const char *overwrite = apr_table_get(r->headers_in, "Overwrite"); 666 667 if (overwrite == NULL) { 668 return 1; /* default is "T" */ 669 } 670 671 if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') { 672 return 0; 673 } 674 675 if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') { 676 return 1; 677 } 678 679 /* The caller will return an HTTP_BAD_REQUEST. This will augment the 680 * default message that Apache provides. */ 681 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00581) 682 "An invalid Overwrite header was specified."); 683 return -1; 684} 685 686/* resolve a request URI to a resource descriptor. 687 * 688 * If label_allowed != 0, then allow the request target to be altered by 689 * a Label: header. 690 * 691 * If use_checked_in is true, then the repository provider should return 692 * the resource identified by the DAV:checked-in property of the resource 693 * identified by the Request-URI. 694 */ 695static dav_error *dav_get_resource(request_rec *r, int label_allowed, 696 int use_checked_in, dav_resource **res_p) 697{ 698 dav_dir_conf *conf; 699 const char *label = NULL; 700 dav_error *err; 701 702 /* if the request target can be overridden, get any target selector */ 703 if (label_allowed) { 704 label = apr_table_get(r->headers_in, "label"); 705 } 706 707 conf = ap_get_module_config(r->per_dir_config, &dav_module); 708 /* assert: conf->provider != NULL */ 709 if (conf->provider == NULL) { 710 return dav_new_error(r->pool, HTTP_METHOD_NOT_ALLOWED, 0, 0, 711 apr_psprintf(r->pool, 712 "DAV not enabled for %s", 713 ap_escape_html(r->pool, r->uri))); 714 } 715 716 /* resolve the resource */ 717 err = (*conf->provider->repos->get_resource)(r, conf->dir, 718 label, use_checked_in, 719 res_p); 720 if (err != NULL) { 721 err = dav_push_error(r->pool, err->status, 0, 722 "Could not fetch resource information.", err); 723 return err; 724 } 725 726 /* Note: this shouldn't happen, but just be sure... */ 727 if (*res_p == NULL) { 728 /* ### maybe use HTTP_INTERNAL_SERVER_ERROR */ 729 return dav_new_error(r->pool, HTTP_NOT_FOUND, 0, 0, 730 apr_psprintf(r->pool, 731 "The provider did not define a " 732 "resource for %s.", 733 ap_escape_html(r->pool, r->uri))); 734 } 735 736 /* ### hmm. this doesn't feel like the right place or thing to do */ 737 /* if there were any input headers requiring a Vary header in the response, 738 * add it now */ 739 dav_add_vary_header(r, r, *res_p); 740 741 return NULL; 742} 743 744static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb) 745{ 746 const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r); 747 748 if (hooks == NULL) { 749 *lockdb = NULL; 750 return NULL; 751 } 752 753 /* open the thing lazily */ 754 return (*hooks->open_lockdb)(r, ro, 0, lockdb); 755} 756 757/** 758 * @return 1 if valid content-range, 759 * 0 if no content-range, 760 * -1 if malformed content-range 761 */ 762static int dav_parse_range(request_rec *r, 763 apr_off_t *range_start, apr_off_t *range_end) 764{ 765 const char *range_c; 766 char *range; 767 char *dash; 768 char *slash; 769 char *errp; 770 771 range_c = apr_table_get(r->headers_in, "content-range"); 772 if (range_c == NULL) 773 return 0; 774 775 range = apr_pstrdup(r->pool, range_c); 776 if (strncasecmp(range, "bytes ", 6) != 0 777 || (dash = ap_strchr(range, '-')) == NULL 778 || (slash = ap_strchr(range, '/')) == NULL) { 779 /* malformed header */ 780 return -1; 781 } 782 783 *dash++ = *slash++ = '\0'; 784 785 /* detect invalid ranges */ 786 if (apr_strtoff(range_start, range + 6, &errp, 10) 787 || *errp || *range_start < 0) { 788 return -1; 789 } 790 if (apr_strtoff(range_end, dash, &errp, 10) 791 || *errp || *range_end < 0 || *range_end < *range_start) { 792 return -1; 793 } 794 795 if (*slash != '*') { 796 apr_off_t dummy; 797 798 if (apr_strtoff(&dummy, slash, &errp, 10) 799 || *errp || dummy <= *range_end) { 800 return -1; 801 } 802 } 803 804 /* we now have a valid range */ 805 return 1; 806} 807 808/* handle the GET method */ 809static int dav_method_get(request_rec *r) 810{ 811 dav_resource *resource; 812 dav_error *err; 813 int status; 814 815 /* This method should only be called when the resource is not 816 * visible to Apache. We will fetch the resource from the repository, 817 * then create a subrequest for Apache to handle. 818 */ 819 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */, 820 &resource); 821 if (err != NULL) 822 return dav_handle_err(r, err, NULL); 823 824 if (!resource->exists) { 825 /* Apache will supply a default error for this. */ 826 return HTTP_NOT_FOUND; 827 } 828 829 /* set up the HTTP headers for the response */ 830 if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) { 831 err = dav_push_error(r->pool, err->status, 0, 832 "Unable to set up HTTP headers.", 833 err); 834 return dav_handle_err(r, err, NULL); 835 } 836 837 /* Handle conditional requests */ 838 status = ap_meets_conditions(r); 839 if (status) { 840 return status; 841 } 842 843 if (r->header_only) { 844 return DONE; 845 } 846 847 /* okay... time to deliver the content */ 848 if ((err = (*resource->hooks->deliver)(resource, 849 r->output_filters)) != NULL) { 850 err = dav_push_error(r->pool, err->status, 0, 851 "Unable to deliver content.", 852 err); 853 return dav_handle_err(r, err, NULL); 854 } 855 856 return DONE; 857} 858 859/* validate resource/locks on POST, then pass to the default handler */ 860static int dav_method_post(request_rec *r) 861{ 862 dav_resource *resource; 863 dav_error *err; 864 865 /* Ask repository module to resolve the resource */ 866 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 867 &resource); 868 if (err != NULL) 869 return dav_handle_err(r, err, NULL); 870 871 /* Note: depth == 0. Implies no need for a multistatus response. */ 872 if ((err = dav_validate_request(r, resource, 0, NULL, NULL, 873 DAV_VALIDATE_RESOURCE, NULL)) != NULL) { 874 /* ### add a higher-level description? */ 875 return dav_handle_err(r, err, NULL); 876 } 877 878 return DECLINED; 879} 880 881/* handle the PUT method */ 882static int dav_method_put(request_rec *r) 883{ 884 dav_resource *resource; 885 int resource_state; 886 dav_auto_version_info av_info; 887 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); 888 const char *body; 889 dav_error *err; 890 dav_error *err2; 891 dav_stream_mode mode; 892 dav_stream *stream; 893 dav_response *multi_response; 894 int has_range; 895 apr_off_t range_start; 896 apr_off_t range_end; 897 898 /* Ask repository module to resolve the resource */ 899 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 900 &resource); 901 if (err != NULL) 902 return dav_handle_err(r, err, NULL); 903 904 /* If not a file or collection resource, PUT not allowed */ 905 if (resource->type != DAV_RESOURCE_TYPE_REGULAR 906 && resource->type != DAV_RESOURCE_TYPE_WORKING) { 907 body = apr_psprintf(r->pool, 908 "Cannot create resource %s with PUT.", 909 ap_escape_html(r->pool, r->uri)); 910 return dav_error_response(r, HTTP_CONFLICT, body); 911 } 912 913 /* Cannot PUT a collection */ 914 if (resource->collection) { 915 return dav_error_response(r, HTTP_CONFLICT, 916 "Cannot PUT to a collection."); 917 918 } 919 920 resource_state = dav_get_resource_state(r, resource); 921 922 /* 923 * Note: depth == 0 normally requires no multistatus response. However, 924 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI 925 * other than the Request-URI, thereby requiring a multistatus. 926 * 927 * If the resource does not exist (DAV_RESOURCE_NULL), then we must 928 * check the resource *and* its parent. If the resource exists or is 929 * a locknull resource, then we check only the resource. 930 */ 931 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response, 932 resource_state == DAV_RESOURCE_NULL ? 933 DAV_VALIDATE_PARENT : 934 DAV_VALIDATE_RESOURCE, NULL)) != NULL) { 935 /* ### add a higher-level description? */ 936 return dav_handle_err(r, err, multi_response); 937 } 938 939 has_range = dav_parse_range(r, &range_start, &range_end); 940 if (has_range < 0) { 941 /* RFC 2616 14.16: If we receive an invalid Content-Range we must 942 * not use the content. 943 */ 944 body = apr_psprintf(r->pool, 945 "Malformed Content-Range header for PUT %s.", 946 ap_escape_html(r->pool, r->uri)); 947 return dav_error_response(r, HTTP_BAD_REQUEST, body); 948 } else if (has_range) { 949 mode = DAV_MODE_WRITE_SEEKABLE; 950 } 951 else { 952 mode = DAV_MODE_WRITE_TRUNC; 953 } 954 955 /* make sure the resource can be modified (if versioning repository) */ 956 if ((err = dav_auto_checkout(r, resource, 957 0 /* not parent_only */, 958 &av_info)) != NULL) { 959 /* ### add a higher-level description? */ 960 return dav_handle_err(r, err, NULL); 961 } 962 963 /* Create the new file in the repository */ 964 if ((err = (*resource->hooks->open_stream)(resource, mode, 965 &stream)) != NULL) { 966 /* ### assuming FORBIDDEN is probably not quite right... */ 967 err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0, 968 apr_psprintf(r->pool, 969 "Unable to PUT new contents for %s.", 970 ap_escape_html(r->pool, r->uri)), 971 err); 972 } 973 974 if (err == NULL && has_range) { 975 /* a range was provided. seek to the start */ 976 err = (*resource->hooks->seek_stream)(stream, range_start); 977 } 978 979 if (err == NULL) { 980 apr_bucket_brigade *bb; 981 apr_bucket *b; 982 int seen_eos = 0; 983 984 bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); 985 986 do { 987 apr_status_t rc; 988 989 rc = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, 990 APR_BLOCK_READ, DAV_READ_BLOCKSIZE); 991 992 if (rc != APR_SUCCESS) { 993 int http_err; 994 char *msg = ap_escape_html(r->pool, r->uri); 995 if (APR_STATUS_IS_TIMEUP(rc)) { 996 http_err = HTTP_REQUEST_TIME_OUT; 997 msg = apr_psprintf(r->pool, "Timeout reading the body " 998 "(URI: %s)", msg); 999 } 1000 else { 1001 /* XXX: should this actually be HTTP_BAD_REQUEST? */ 1002 http_err = HTTP_INTERNAL_SERVER_ERROR; 1003 msg = apr_psprintf(r->pool, "An error occurred while reading" 1004 " the request body (URI: %s)", msg); 1005 } 1006 err = dav_new_error(r->pool, http_err, 0, rc, msg); 1007 break; 1008 } 1009 1010 for (b = APR_BRIGADE_FIRST(bb); 1011 b != APR_BRIGADE_SENTINEL(bb); 1012 b = APR_BUCKET_NEXT(b)) 1013 { 1014 const char *data; 1015 apr_size_t len; 1016 1017 if (APR_BUCKET_IS_EOS(b)) { 1018 seen_eos = 1; 1019 break; 1020 } 1021 1022 if (APR_BUCKET_IS_METADATA(b)) { 1023 continue; 1024 } 1025 1026 if (err == NULL) { 1027 /* write whatever we read, until we see an error */ 1028 rc = apr_bucket_read(b, &data, &len, APR_BLOCK_READ); 1029 if (rc != APR_SUCCESS) { 1030 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, rc, 1031 apr_psprintf(r->pool, 1032 "An error occurred while" 1033 " reading the request body" 1034 " from the bucket (URI: %s)", 1035 ap_escape_html(r->pool, r->uri))); 1036 break; 1037 } 1038 1039 err = (*resource->hooks->write_stream)(stream, data, len); 1040 } 1041 } 1042 1043 apr_brigade_cleanup(bb); 1044 } while (!seen_eos); 1045 1046 apr_brigade_destroy(bb); 1047 1048 err2 = (*resource->hooks->close_stream)(stream, 1049 err == NULL /* commit */); 1050 err = dav_join_error(err, err2); 1051 } 1052 1053 /* 1054 * Ensure that we think the resource exists now. 1055 * ### eek. if an error occurred during the write and we did not commit, 1056 * ### then the resource might NOT exist (e.g. dav_fs_repos.c) 1057 */ 1058 if (err == NULL) { 1059 resource->exists = 1; 1060 } 1061 1062 /* restore modifiability of resources back to what they were */ 1063 err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */, 1064 0 /*unlock*/, &av_info); 1065 1066 /* check for errors now */ 1067 if (err != NULL) { 1068 err = dav_join_error(err, err2); /* don't forget err2 */ 1069 return dav_handle_err(r, err, NULL); 1070 } 1071 1072 if (err2 != NULL) { 1073 /* just log a warning */ 1074 err2 = dav_push_error(r->pool, err2->status, 0, 1075 "The PUT was successful, but there " 1076 "was a problem automatically checking in " 1077 "the resource or its parent collection.", 1078 err2); 1079 dav_log_err(r, err2, APLOG_WARNING); 1080 } 1081 1082 /* ### place the Content-Type and Content-Language into the propdb */ 1083 1084 if (locks_hooks != NULL) { 1085 dav_lockdb *lockdb; 1086 1087 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) { 1088 /* The file creation was successful, but the locking failed. */ 1089 err = dav_push_error(r->pool, err->status, 0, 1090 "The file was PUT successfully, but there " 1091 "was a problem opening the lock database " 1092 "which prevents inheriting locks from the " 1093 "parent resources.", 1094 err); 1095 return dav_handle_err(r, err, NULL); 1096 } 1097 1098 /* notify lock system that we have created/replaced a resource */ 1099 err = dav_notify_created(r, lockdb, resource, resource_state, 0); 1100 1101 (*locks_hooks->close_lockdb)(lockdb); 1102 1103 if (err != NULL) { 1104 /* The file creation was successful, but the locking failed. */ 1105 err = dav_push_error(r->pool, err->status, 0, 1106 "The file was PUT successfully, but there " 1107 "was a problem updating its lock " 1108 "information.", 1109 err); 1110 return dav_handle_err(r, err, NULL); 1111 } 1112 } 1113 1114 /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */ 1115 1116 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */ 1117 return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS); 1118} 1119 1120 1121/* Use POOL to temporarily construct a dav_response object (from WRES 1122 STATUS, and PROPSTATS) and stream it via WRES's ctx->brigade. */ 1123static void dav_stream_response(dav_walk_resource *wres, 1124 int status, 1125 dav_get_props_result *propstats, 1126 apr_pool_t *pool) 1127{ 1128 dav_response resp = { 0 }; 1129 dav_walker_ctx *ctx = wres->walk_ctx; 1130 1131 resp.href = wres->resource->uri; 1132 resp.status = status; 1133 if (propstats) { 1134 resp.propresult = *propstats; 1135 } 1136 1137 dav_send_one_response(&resp, ctx->bb, ctx->r->output_filters, pool); 1138} 1139 1140 1141/* ### move this to dav_util? */ 1142DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres, 1143 int status, dav_get_props_result *propstats) 1144{ 1145 dav_response *resp; 1146 1147 /* just drop some data into an dav_response */ 1148 resp = apr_pcalloc(wres->pool, sizeof(*resp)); 1149 resp->href = apr_pstrdup(wres->pool, wres->resource->uri); 1150 resp->status = status; 1151 if (propstats) { 1152 resp->propresult = *propstats; 1153 } 1154 1155 resp->next = wres->response; 1156 wres->response = resp; 1157} 1158 1159 1160/* handle the DELETE method */ 1161static int dav_method_delete(request_rec *r) 1162{ 1163 dav_resource *resource; 1164 dav_auto_version_info av_info; 1165 dav_error *err; 1166 dav_error *err2; 1167 dav_response *multi_response; 1168 int result; 1169 int depth; 1170 1171 /* We don't use the request body right now, so torch it. */ 1172 if ((result = ap_discard_request_body(r)) != OK) { 1173 return result; 1174 } 1175 1176 /* Ask repository module to resolve the resource */ 1177 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 1178 &resource); 1179 if (err != NULL) 1180 return dav_handle_err(r, err, NULL); 1181 if (!resource->exists) { 1182 /* Apache will supply a default error for this. */ 1183 return HTTP_NOT_FOUND; 1184 } 1185 1186 /* 2518 says that depth must be infinity only for collections. 1187 * For non-collections, depth is ignored, unless it is an illegal value (1). 1188 */ 1189 depth = dav_get_depth(r, DAV_INFINITY); 1190 1191 if (resource->collection && depth != DAV_INFINITY) { 1192 /* This supplies additional information for the default message. */ 1193 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00582) 1194 "Depth must be \"infinity\" for DELETE of a collection."); 1195 return HTTP_BAD_REQUEST; 1196 } 1197 1198 if (!resource->collection && depth == 1) { 1199 /* This supplies additional information for the default message. */ 1200 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00583) 1201 "Depth of \"1\" is not allowed for DELETE."); 1202 return HTTP_BAD_REQUEST; 1203 } 1204 1205 /* 1206 ** If any resources fail the lock/If: conditions, then we must fail 1207 ** the delete. Each of the failing resources will be listed within 1208 ** a DAV:multistatus body, wrapped into a 424 response. 1209 ** 1210 ** Note that a failure on the resource itself does not generate a 1211 ** multistatus response -- only internal members/collections. 1212 */ 1213 if ((err = dav_validate_request(r, resource, depth, NULL, 1214 &multi_response, 1215 DAV_VALIDATE_PARENT 1216 | DAV_VALIDATE_USE_424, NULL)) != NULL) { 1217 err = dav_push_error(r->pool, err->status, 0, 1218 apr_psprintf(r->pool, 1219 "Could not DELETE %s due to a failed " 1220 "precondition (e.g. locks).", 1221 ap_escape_html(r->pool, r->uri)), 1222 err); 1223 return dav_handle_err(r, err, multi_response); 1224 } 1225 1226 /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those 1227 * locked by the token(s) in the if_header. 1228 */ 1229 if ((result = dav_unlock(r, resource, NULL)) != OK) { 1230 return result; 1231 } 1232 1233 /* if versioned resource, make sure parent is checked out */ 1234 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */, 1235 &av_info)) != NULL) { 1236 /* ### add a higher-level description? */ 1237 return dav_handle_err(r, err, NULL); 1238 } 1239 1240 /* try to remove the resource */ 1241 err = (*resource->hooks->remove_resource)(resource, &multi_response); 1242 1243 /* restore writability of parent back to what it was */ 1244 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */, 1245 0 /*unlock*/, &av_info); 1246 1247 /* check for errors now */ 1248 if (err != NULL) { 1249 err = dav_push_error(r->pool, err->status, 0, 1250 apr_psprintf(r->pool, 1251 "Could not DELETE %s.", 1252 ap_escape_html(r->pool, r->uri)), 1253 err); 1254 return dav_handle_err(r, err, multi_response); 1255 } 1256 if (err2 != NULL) { 1257 /* just log a warning */ 1258 err = dav_push_error(r->pool, err2->status, 0, 1259 "The DELETE was successful, but there " 1260 "was a problem automatically checking in " 1261 "the parent collection.", 1262 err2); 1263 dav_log_err(r, err, APLOG_WARNING); 1264 } 1265 1266 /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */ 1267 1268 /* Apache will supply a default error for this. */ 1269 return HTTP_NO_CONTENT; 1270} 1271 1272/* generate DAV:supported-method-set OPTIONS response */ 1273static dav_error *dav_gen_supported_methods(request_rec *r, 1274 const apr_xml_elem *elem, 1275 const apr_table_t *methods, 1276 apr_text_header *body) 1277{ 1278 const apr_array_header_t *arr; 1279 const apr_table_entry_t *elts; 1280 apr_xml_elem *child; 1281 apr_xml_attr *attr; 1282 char *s; 1283 int i; 1284 1285 apr_text_append(r->pool, body, "<D:supported-method-set>" DEBUG_CR); 1286 1287 if (elem->first_child == NULL) { 1288 /* show all supported methods */ 1289 arr = apr_table_elts(methods); 1290 elts = (const apr_table_entry_t *)arr->elts; 1291 1292 for (i = 0; i < arr->nelts; ++i) { 1293 if (elts[i].key == NULL) 1294 continue; 1295 1296 s = apr_psprintf(r->pool, 1297 "<D:supported-method D:name=\"%s\"/>" 1298 DEBUG_CR, 1299 elts[i].key); 1300 apr_text_append(r->pool, body, s); 1301 } 1302 } 1303 else { 1304 /* check for support of specific methods */ 1305 for (child = elem->first_child; child != NULL; child = child->next) { 1306 if (child->ns == APR_XML_NS_DAV_ID 1307 && strcmp(child->name, "supported-method") == 0) { 1308 const char *name = NULL; 1309 1310 /* go through attributes to find method name */ 1311 for (attr = child->attr; attr != NULL; attr = attr->next) { 1312 if (attr->ns == APR_XML_NS_DAV_ID 1313 && strcmp(attr->name, "name") == 0) 1314 name = attr->value; 1315 } 1316 1317 if (name == NULL) { 1318 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0, 1319 "A DAV:supported-method element " 1320 "does not have a \"name\" attribute"); 1321 } 1322 1323 /* see if method is supported */ 1324 if (apr_table_get(methods, name) != NULL) { 1325 s = apr_psprintf(r->pool, 1326 "<D:supported-method D:name=\"%s\"/>" 1327 DEBUG_CR, 1328 name); 1329 apr_text_append(r->pool, body, s); 1330 } 1331 } 1332 } 1333 } 1334 1335 apr_text_append(r->pool, body, "</D:supported-method-set>" DEBUG_CR); 1336 return NULL; 1337} 1338 1339/* generate DAV:supported-live-property-set OPTIONS response */ 1340static dav_error *dav_gen_supported_live_props(request_rec *r, 1341 const dav_resource *resource, 1342 const apr_xml_elem *elem, 1343 apr_text_header *body) 1344{ 1345 dav_lockdb *lockdb; 1346 dav_propdb *propdb; 1347 apr_xml_elem *child; 1348 apr_xml_attr *attr; 1349 dav_error *err; 1350 1351 /* open lock database, to report on supported lock properties */ 1352 /* ### should open read-only */ 1353 if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) { 1354 return dav_push_error(r->pool, err->status, 0, 1355 "The lock database could not be opened, " 1356 "preventing the reporting of supported lock " 1357 "properties.", 1358 err); 1359 } 1360 1361 /* open the property database (readonly) for the resource */ 1362 if ((err = dav_open_propdb(r, lockdb, resource, 1, NULL, 1363 &propdb)) != NULL) { 1364 if (lockdb != NULL) 1365 (*lockdb->hooks->close_lockdb)(lockdb); 1366 1367 return dav_push_error(r->pool, err->status, 0, 1368 "The property database could not be opened, " 1369 "preventing report of supported properties.", 1370 err); 1371 } 1372 1373 apr_text_append(r->pool, body, "<D:supported-live-property-set>" DEBUG_CR); 1374 1375 if (elem->first_child == NULL) { 1376 /* show all supported live properties */ 1377 dav_get_props_result props = dav_get_allprops(propdb, DAV_PROP_INSERT_SUPPORTED); 1378 body->last->next = props.propstats; 1379 while (body->last->next != NULL) 1380 body->last = body->last->next; 1381 } 1382 else { 1383 /* check for support of specific live property */ 1384 for (child = elem->first_child; child != NULL; child = child->next) { 1385 if (child->ns == APR_XML_NS_DAV_ID 1386 && strcmp(child->name, "supported-live-property") == 0) { 1387 const char *name = NULL; 1388 const char *nmspace = NULL; 1389 1390 /* go through attributes to find name and namespace */ 1391 for (attr = child->attr; attr != NULL; attr = attr->next) { 1392 if (attr->ns == APR_XML_NS_DAV_ID) { 1393 if (strcmp(attr->name, "name") == 0) 1394 name = attr->value; 1395 else if (strcmp(attr->name, "namespace") == 0) 1396 nmspace = attr->value; 1397 } 1398 } 1399 1400 if (name == NULL) { 1401 err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0, 1402 "A DAV:supported-live-property " 1403 "element does not have a \"name\" " 1404 "attribute"); 1405 break; 1406 } 1407 1408 /* default namespace to DAV: */ 1409 if (nmspace == NULL) 1410 nmspace = "DAV:"; 1411 1412 /* check for support of property */ 1413 dav_get_liveprop_supported(propdb, nmspace, name, body); 1414 } 1415 } 1416 } 1417 1418 apr_text_append(r->pool, body, "</D:supported-live-property-set>" DEBUG_CR); 1419 1420 dav_close_propdb(propdb); 1421 1422 if (lockdb != NULL) 1423 (*lockdb->hooks->close_lockdb)(lockdb); 1424 1425 return err; 1426} 1427 1428/* generate DAV:supported-report-set OPTIONS response */ 1429static dav_error *dav_gen_supported_reports(request_rec *r, 1430 const dav_resource *resource, 1431 const apr_xml_elem *elem, 1432 const dav_hooks_vsn *vsn_hooks, 1433 apr_text_header *body) 1434{ 1435 apr_xml_elem *child; 1436 apr_xml_attr *attr; 1437 dav_error *err; 1438 char *s; 1439 1440 apr_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR); 1441 1442 if (vsn_hooks != NULL) { 1443 const dav_report_elem *reports; 1444 const dav_report_elem *rp; 1445 1446 if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) { 1447 return dav_push_error(r->pool, err->status, 0, 1448 "DAV:supported-report-set could not be " 1449 "determined due to a problem fetching the " 1450 "available reports for this resource.", 1451 err); 1452 } 1453 1454 if (reports != NULL) { 1455 if (elem->first_child == NULL) { 1456 /* show all supported reports */ 1457 for (rp = reports; rp->nmspace != NULL; ++rp) { 1458 /* Note: we presume reports->namespace is 1459 * properly XML/URL quoted */ 1460 s = apr_psprintf(r->pool, 1461 "<D:supported-report D:name=\"%s\" " 1462 "D:namespace=\"%s\"/>" DEBUG_CR, 1463 rp->name, rp->nmspace); 1464 apr_text_append(r->pool, body, s); 1465 } 1466 } 1467 else { 1468 /* check for support of specific report */ 1469 for (child = elem->first_child; child != NULL; child = child->next) { 1470 if (child->ns == APR_XML_NS_DAV_ID 1471 && strcmp(child->name, "supported-report") == 0) { 1472 const char *name = NULL; 1473 const char *nmspace = NULL; 1474 1475 /* go through attributes to find name and namespace */ 1476 for (attr = child->attr; attr != NULL; attr = attr->next) { 1477 if (attr->ns == APR_XML_NS_DAV_ID) { 1478 if (strcmp(attr->name, "name") == 0) 1479 name = attr->value; 1480 else if (strcmp(attr->name, "namespace") == 0) 1481 nmspace = attr->value; 1482 } 1483 } 1484 1485 if (name == NULL) { 1486 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0, 1487 "A DAV:supported-report element " 1488 "does not have a \"name\" attribute"); 1489 } 1490 1491 /* default namespace to DAV: */ 1492 if (nmspace == NULL) 1493 nmspace = "DAV:"; 1494 1495 for (rp = reports; rp->nmspace != NULL; ++rp) { 1496 if (strcmp(name, rp->name) == 0 1497 && strcmp(nmspace, rp->nmspace) == 0) { 1498 /* Note: we presume reports->nmspace is 1499 * properly XML/URL quoted 1500 */ 1501 s = apr_psprintf(r->pool, 1502 "<D:supported-report " 1503 "D:name=\"%s\" " 1504 "D:namespace=\"%s\"/>" 1505 DEBUG_CR, 1506 rp->name, rp->nmspace); 1507 apr_text_append(r->pool, body, s); 1508 break; 1509 } 1510 } 1511 } 1512 } 1513 } 1514 } 1515 } 1516 1517 apr_text_append(r->pool, body, "</D:supported-report-set>" DEBUG_CR); 1518 return NULL; 1519} 1520 1521 1522/* handle the SEARCH method */ 1523static int dav_method_search(request_rec *r) 1524{ 1525 const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r); 1526 dav_resource *resource; 1527 dav_error *err; 1528 dav_response *multi_status; 1529 1530 /* If no search provider, decline the request */ 1531 if (search_hooks == NULL) 1532 return DECLINED; 1533 1534 /* This method should only be called when the resource is not 1535 * visible to Apache. We will fetch the resource from the repository, 1536 * then create a subrequest for Apache to handle. 1537 */ 1538 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */, 1539 &resource); 1540 if (err != NULL) 1541 return dav_handle_err(r, err, NULL); 1542 1543 if (!resource->exists) { 1544 /* Apache will supply a default error for this. */ 1545 return HTTP_NOT_FOUND; 1546 } 1547 1548 /* set up the HTTP headers for the response */ 1549 if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) { 1550 err = dav_push_error(r->pool, err->status, 0, 1551 "Unable to set up HTTP headers.", 1552 err); 1553 return dav_handle_err(r, err, NULL); 1554 } 1555 1556 if (r->header_only) { 1557 return DONE; 1558 } 1559 1560 /* okay... time to search the content */ 1561 /* Let's validate XML and process walk function 1562 * in the hook function 1563 */ 1564 if ((err = (*search_hooks->search_resource)(r, &multi_status)) != NULL) { 1565 /* ### add a higher-level description? */ 1566 return dav_handle_err(r, err, NULL); 1567 } 1568 1569 /* We have results in multi_status */ 1570 /* Should I pass namespace?? */ 1571 dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL); 1572 1573 return DONE; 1574} 1575 1576 1577/* handle the OPTIONS method */ 1578static int dav_method_options(request_rec *r) 1579{ 1580 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); 1581 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 1582 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r); 1583 const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r); 1584 dav_resource *resource; 1585 const char *dav_level; 1586 char *allow; 1587 char *s; 1588 const apr_array_header_t *arr; 1589 const apr_table_entry_t *elts; 1590 apr_table_t *methods = apr_table_make(r->pool, 12); 1591 apr_text_header vsn_options = { 0 }; 1592 apr_text_header body = { 0 }; 1593 apr_text *t; 1594 int text_size; 1595 int result; 1596 int i; 1597 apr_array_header_t *uri_ary; 1598 apr_xml_doc *doc; 1599 const apr_xml_elem *elem; 1600 dav_error *err; 1601 1602 apr_array_header_t *extensions; 1603 ap_list_provider_names_t *entry; 1604 1605 /* resolve the resource */ 1606 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 1607 &resource); 1608 if (err != NULL) 1609 return dav_handle_err(r, err, NULL); 1610 1611 /* parse any request body */ 1612 if ((result = ap_xml_parse_input(r, &doc)) != OK) { 1613 return result; 1614 } 1615 /* note: doc == NULL if no request body */ 1616 1617 if (doc && !dav_validate_root(doc, "options")) { 1618 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00584) 1619 "The \"options\" element was not found."); 1620 return HTTP_BAD_REQUEST; 1621 } 1622 1623 /* determine which providers are available */ 1624 dav_level = "1"; 1625 1626 if (locks_hooks != NULL) { 1627 dav_level = "1,2"; 1628 } 1629 1630 if (binding_hooks != NULL) 1631 dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL); 1632 1633 /* DAV header additions registered by external modules */ 1634 extensions = ap_list_provider_names(r->pool, DAV_OPTIONS_EXTENSION_GROUP, "0"); 1635 entry = (ap_list_provider_names_t *)extensions->elts; 1636 1637 for (i = 0; i < extensions->nelts; i++, entry++) { 1638 const dav_options_provider *options = 1639 dav_get_options_providers(entry->provider_name); 1640 1641 if (options && options->dav_header) { 1642 apr_text_header hoptions = { 0 }; 1643 1644 options->dav_header(r, resource, &hoptions); 1645 for (t = hoptions.first; t && t->text; t = t->next) 1646 dav_level = apr_pstrcat(r->pool, dav_level, ",", t->text, NULL); 1647 } 1648 } 1649 1650 /* ### 1651 * MSFT Web Folders chokes if length of DAV header value > 63 characters! 1652 * To workaround that, we use separate DAV headers for versioning and 1653 * live prop provider namespace URIs. 1654 * ### 1655 */ 1656 apr_table_setn(r->headers_out, "DAV", dav_level); 1657 1658 /* 1659 * If there is a versioning provider, generate DAV headers 1660 * for versioning options. 1661 */ 1662 if (vsn_hooks != NULL) { 1663 (*vsn_hooks->get_vsn_options)(r->pool, &vsn_options); 1664 1665 for (t = vsn_options.first; t != NULL; t = t->next) 1666 apr_table_addn(r->headers_out, "DAV", t->text); 1667 } 1668 1669 /* 1670 * Gather property set URIs from all the liveprop providers, 1671 * and generate a separate DAV header for each URI, to avoid 1672 * problems with long header lengths. 1673 */ 1674 uri_ary = apr_array_make(r->pool, 5, sizeof(const char *)); 1675 dav_run_gather_propsets(uri_ary); 1676 for (i = 0; i < uri_ary->nelts; ++i) { 1677 if (((char **)uri_ary->elts)[i] != NULL) 1678 apr_table_addn(r->headers_out, "DAV", ((char **)uri_ary->elts)[i]); 1679 } 1680 1681 /* this tells MSFT products to skip looking for FrontPage extensions */ 1682 apr_table_setn(r->headers_out, "MS-Author-Via", "DAV"); 1683 1684 /* 1685 * Determine which methods are allowed on the resource. 1686 * Three cases: resource is null (3), is lock-null (7.4), or exists. 1687 * 1688 * All cases support OPTIONS, and if there is a lock provider, LOCK. 1689 * (Lock-) null resources also support MKCOL and PUT. 1690 * Lock-null supports PROPFIND and UNLOCK. 1691 * Existing resources support lots of stuff. 1692 */ 1693 1694 apr_table_addn(methods, "OPTIONS", ""); 1695 1696 /* ### take into account resource type */ 1697 switch (dav_get_resource_state(r, resource)) 1698 { 1699 case DAV_RESOURCE_EXISTS: 1700 /* resource exists */ 1701 apr_table_addn(methods, "GET", ""); 1702 apr_table_addn(methods, "HEAD", ""); 1703 apr_table_addn(methods, "POST", ""); 1704 apr_table_addn(methods, "DELETE", ""); 1705 apr_table_addn(methods, "TRACE", ""); 1706 apr_table_addn(methods, "PROPFIND", ""); 1707 apr_table_addn(methods, "PROPPATCH", ""); 1708 apr_table_addn(methods, "COPY", ""); 1709 apr_table_addn(methods, "MOVE", ""); 1710 1711 if (!resource->collection) 1712 apr_table_addn(methods, "PUT", ""); 1713 1714 if (locks_hooks != NULL) { 1715 apr_table_addn(methods, "LOCK", ""); 1716 apr_table_addn(methods, "UNLOCK", ""); 1717 } 1718 1719 break; 1720 1721 case DAV_RESOURCE_LOCK_NULL: 1722 /* resource is lock-null. */ 1723 apr_table_addn(methods, "MKCOL", ""); 1724 apr_table_addn(methods, "PROPFIND", ""); 1725 apr_table_addn(methods, "PUT", ""); 1726 1727 if (locks_hooks != NULL) { 1728 apr_table_addn(methods, "LOCK", ""); 1729 apr_table_addn(methods, "UNLOCK", ""); 1730 } 1731 1732 break; 1733 1734 case DAV_RESOURCE_NULL: 1735 /* resource is null. */ 1736 apr_table_addn(methods, "MKCOL", ""); 1737 apr_table_addn(methods, "PUT", ""); 1738 1739 if (locks_hooks != NULL) 1740 apr_table_addn(methods, "LOCK", ""); 1741 1742 break; 1743 1744 default: 1745 /* ### internal error! */ 1746 break; 1747 } 1748 1749 /* If there is a versioning provider, add versioning methods */ 1750 if (vsn_hooks != NULL) { 1751 if (!resource->exists) { 1752 if ((*vsn_hooks->versionable)(resource)) 1753 apr_table_addn(methods, "VERSION-CONTROL", ""); 1754 1755 if (vsn_hooks->can_be_workspace != NULL 1756 && (*vsn_hooks->can_be_workspace)(resource)) 1757 apr_table_addn(methods, "MKWORKSPACE", ""); 1758 1759 if (vsn_hooks->can_be_activity != NULL 1760 && (*vsn_hooks->can_be_activity)(resource)) 1761 apr_table_addn(methods, "MKACTIVITY", ""); 1762 } 1763 else if (!resource->versioned) { 1764 if ((*vsn_hooks->versionable)(resource)) 1765 apr_table_addn(methods, "VERSION-CONTROL", ""); 1766 } 1767 else if (resource->working) { 1768 apr_table_addn(methods, "CHECKIN", ""); 1769 1770 /* ### we might not support this DeltaV option */ 1771 apr_table_addn(methods, "UNCHECKOUT", ""); 1772 } 1773 else if (vsn_hooks->add_label != NULL) { 1774 apr_table_addn(methods, "CHECKOUT", ""); 1775 apr_table_addn(methods, "LABEL", ""); 1776 } 1777 else { 1778 apr_table_addn(methods, "CHECKOUT", ""); 1779 } 1780 } 1781 1782 /* If there is a bindings provider, see if resource is bindable */ 1783 if (binding_hooks != NULL 1784 && (*binding_hooks->is_bindable)(resource)) { 1785 apr_table_addn(methods, "BIND", ""); 1786 } 1787 1788 /* If there is a search provider, set SEARCH in option */ 1789 if (search_hooks != NULL) { 1790 apr_table_addn(methods, "SEARCH", ""); 1791 } 1792 1793 /* additional methods registered by external modules */ 1794 extensions = ap_list_provider_names(r->pool, DAV_OPTIONS_EXTENSION_GROUP, "0"); 1795 entry = (ap_list_provider_names_t *)extensions->elts; 1796 1797 for (i = 0; i < extensions->nelts; i++, entry++) { 1798 const dav_options_provider *options = 1799 dav_get_options_providers(entry->provider_name); 1800 1801 if (options && options->dav_method) { 1802 apr_text_header hoptions = { 0 }; 1803 1804 options->dav_method(r, resource, &hoptions); 1805 for (t = hoptions.first; t && t->text; t = t->next) 1806 apr_table_addn(methods, t->text, ""); 1807 } 1808 } 1809 1810 /* Generate the Allow header */ 1811 arr = apr_table_elts(methods); 1812 elts = (const apr_table_entry_t *)arr->elts; 1813 text_size = 0; 1814 1815 /* first, compute total length */ 1816 for (i = 0; i < arr->nelts; ++i) { 1817 if (elts[i].key == NULL) 1818 continue; 1819 1820 /* add 1 for comma or null */ 1821 text_size += strlen(elts[i].key) + 1; 1822 } 1823 1824 s = allow = apr_palloc(r->pool, text_size); 1825 1826 for (i = 0; i < arr->nelts; ++i) { 1827 if (elts[i].key == NULL) 1828 continue; 1829 1830 if (s != allow) 1831 *s++ = ','; 1832 1833 strcpy(s, elts[i].key); 1834 s += strlen(s); 1835 } 1836 1837 apr_table_setn(r->headers_out, "Allow", allow); 1838 1839 1840 /* If there is search set_option_head function, set head */ 1841 /* DASL: <DAV:basicsearch> 1842 * DASL: <http://foo.bar.com/syntax1> 1843 * DASL: <http://akuma.com/syntax2> 1844 */ 1845 if (search_hooks != NULL 1846 && *search_hooks->set_option_head != NULL) { 1847 if ((err = (*search_hooks->set_option_head)(r)) != NULL) { 1848 return dav_handle_err(r, err, NULL); 1849 } 1850 } 1851 1852 /* if there was no request body, then there is no response body */ 1853 if (doc == NULL) { 1854 ap_set_content_length(r, 0); 1855 1856 /* ### this sends a Content-Type. the default OPTIONS does not. */ 1857 1858 /* ### the default (ap_send_http_options) returns OK, but I believe 1859 * ### that is because it is the default handler and nothing else 1860 * ### will run after the thing. */ 1861 return DONE; 1862 } 1863 1864 /* handle each options request */ 1865 for (elem = doc->root->first_child; elem != NULL; elem = elem->next) { 1866 /* check for something we recognize first */ 1867 int core_option = 0; 1868 dav_error *err = NULL; 1869 1870 if (elem->ns == APR_XML_NS_DAV_ID) { 1871 if (strcmp(elem->name, "supported-method-set") == 0) { 1872 err = dav_gen_supported_methods(r, elem, methods, &body); 1873 core_option = 1; 1874 } 1875 else if (strcmp(elem->name, "supported-live-property-set") == 0) { 1876 err = dav_gen_supported_live_props(r, resource, elem, &body); 1877 core_option = 1; 1878 } 1879 else if (strcmp(elem->name, "supported-report-set") == 0) { 1880 err = dav_gen_supported_reports(r, resource, elem, vsn_hooks, &body); 1881 core_option = 1; 1882 } 1883 } 1884 1885 if (err != NULL) 1886 return dav_handle_err(r, err, NULL); 1887 1888 /* if unrecognized option, pass to versioning provider */ 1889 if (!core_option && vsn_hooks != NULL) { 1890 if ((err = (*vsn_hooks->get_option)(resource, elem, &body)) 1891 != NULL) { 1892 return dav_handle_err(r, err, NULL); 1893 } 1894 } 1895 } 1896 1897 /* send the options response */ 1898 r->status = HTTP_OK; 1899 ap_set_content_type(r, DAV_XML_CONTENT_TYPE); 1900 1901 /* send the headers and response body */ 1902 ap_rputs(DAV_XML_HEADER DEBUG_CR 1903 "<D:options-response xmlns:D=\"DAV:\">" DEBUG_CR, r); 1904 1905 for (t = body.first; t != NULL; t = t->next) 1906 ap_rputs(t->text, r); 1907 1908 ap_rputs("</D:options-response>" DEBUG_CR, r); 1909 1910 /* we've sent everything necessary to the client. */ 1911 return DONE; 1912} 1913 1914static void dav_cache_badprops(dav_walker_ctx *ctx) 1915{ 1916 const apr_xml_elem *elem; 1917 apr_text_header hdr = { 0 }; 1918 1919 /* just return if we built the thing already */ 1920 if (ctx->propstat_404 != NULL) { 1921 return; 1922 } 1923 1924 apr_text_append(ctx->w.pool, &hdr, 1925 "<D:propstat>" DEBUG_CR 1926 "<D:prop>" DEBUG_CR); 1927 1928 elem = dav_find_child(ctx->doc->root, "prop"); 1929 for (elem = elem->first_child; elem; elem = elem->next) { 1930 apr_text_append(ctx->w.pool, &hdr, 1931 apr_xml_empty_elem(ctx->w.pool, elem)); 1932 } 1933 1934 apr_text_append(ctx->w.pool, &hdr, 1935 "</D:prop>" DEBUG_CR 1936 "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR 1937 "</D:propstat>" DEBUG_CR); 1938 1939 ctx->propstat_404 = hdr.first; 1940} 1941 1942static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype) 1943{ 1944 dav_walker_ctx *ctx = wres->walk_ctx; 1945 dav_error *err; 1946 dav_propdb *propdb; 1947 dav_get_props_result propstats = { 0 }; 1948 1949 /* 1950 ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since 1951 ** dav_get_allprops() does not need to do namespace translation, 1952 ** we're okay. 1953 ** 1954 ** Note: we cast to lose the "const". The propdb won't try to change 1955 ** the resource, however, since we are opening readonly. 1956 */ 1957 err = dav_open_propdb(ctx->r, ctx->w.lockdb, wres->resource, 1, 1958 ctx->doc ? ctx->doc->namespaces : NULL, &propdb); 1959 if (err != NULL) { 1960 /* ### do something with err! */ 1961 1962 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) { 1963 dav_get_props_result badprops = { 0 }; 1964 1965 /* some props were expected on this collection/resource */ 1966 dav_cache_badprops(ctx); 1967 badprops.propstats = ctx->propstat_404; 1968 dav_stream_response(wres, 0, &badprops, ctx->scratchpool); 1969 } 1970 else { 1971 /* no props on this collection/resource */ 1972 dav_stream_response(wres, HTTP_OK, NULL, ctx->scratchpool); 1973 } 1974 1975 apr_pool_clear(ctx->scratchpool); 1976 return NULL; 1977 } 1978 /* ### what to do about closing the propdb on server failure? */ 1979 1980 if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) { 1981 propstats = dav_get_props(propdb, ctx->doc); 1982 } 1983 else { 1984 dav_prop_insert what = ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP 1985 ? DAV_PROP_INSERT_VALUE 1986 : DAV_PROP_INSERT_NAME; 1987 propstats = dav_get_allprops(propdb, what); 1988 } 1989 dav_close_propdb(propdb); 1990 1991 dav_stream_response(wres, 0, &propstats, ctx->scratchpool); 1992 1993 /* at this point, ctx->scratchpool has been used to stream a 1994 single response. this function fully controls the pool, and 1995 thus has the right to clear it for the next iteration of this 1996 callback. */ 1997 apr_pool_clear(ctx->scratchpool); 1998 1999 return NULL; 2000} 2001 2002/* handle the PROPFIND method */ 2003static int dav_method_propfind(request_rec *r) 2004{ 2005 dav_resource *resource; 2006 int depth; 2007 dav_error *err; 2008 int result; 2009 apr_xml_doc *doc; 2010 dav_walker_ctx ctx = { { 0 } }; 2011 dav_response *multi_status; 2012 2013 /* Ask repository module to resolve the resource */ 2014 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */, 2015 &resource); 2016 if (err != NULL) 2017 return dav_handle_err(r, err, NULL); 2018 2019 if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) { 2020 /* Apache will supply a default error for this. */ 2021 return HTTP_NOT_FOUND; 2022 } 2023 2024 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) { 2025 /* dav_get_depth() supplies additional information for the 2026 * default message. */ 2027 return HTTP_BAD_REQUEST; 2028 } 2029 2030 if (depth == DAV_INFINITY && resource->collection) { 2031 dav_dir_conf *conf; 2032 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config, 2033 &dav_module); 2034 /* default is to DISALLOW these requests */ 2035 if (conf->allow_depthinfinity != DAV_ENABLED_ON) { 2036 return dav_error_response(r, HTTP_FORBIDDEN, 2037 apr_psprintf(r->pool, 2038 "PROPFIND requests with a " 2039 "Depth of \"infinity\" are " 2040 "not allowed for %s.", 2041 ap_escape_html(r->pool, 2042 r->uri))); 2043 } 2044 } 2045 2046 if ((result = ap_xml_parse_input(r, &doc)) != OK) { 2047 return result; 2048 } 2049 /* note: doc == NULL if no request body */ 2050 2051 if (doc && !dav_validate_root(doc, "propfind")) { 2052 /* This supplies additional information for the default message. */ 2053 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00585) 2054 "The \"propfind\" element was not found."); 2055 return HTTP_BAD_REQUEST; 2056 } 2057 2058 /* ### validate that only one of these three elements is present */ 2059 2060 if (doc == NULL || dav_find_child(doc->root, "allprop") != NULL) { 2061 /* note: no request body implies allprop */ 2062 ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP; 2063 } 2064 else if (dav_find_child(doc->root, "propname") != NULL) { 2065 ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME; 2066 } 2067 else if (dav_find_child(doc->root, "prop") != NULL) { 2068 ctx.propfind_type = DAV_PROPFIND_IS_PROP; 2069 } 2070 else { 2071 /* "propfind" element must have one of the above three children */ 2072 2073 /* This supplies additional information for the default message. */ 2074 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00586) 2075 "The \"propfind\" element does not contain one of " 2076 "the required child elements (the specific command)."); 2077 return HTTP_BAD_REQUEST; 2078 } 2079 2080 ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH; 2081 ctx.w.func = dav_propfind_walker; 2082 ctx.w.walk_ctx = &ctx; 2083 ctx.w.pool = r->pool; 2084 ctx.w.root = resource; 2085 2086 ctx.doc = doc; 2087 ctx.r = r; 2088 ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); 2089 apr_pool_create(&ctx.scratchpool, r->pool); 2090 2091 /* ### should open read-only */ 2092 if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) { 2093 err = dav_push_error(r->pool, err->status, 0, 2094 "The lock database could not be opened, " 2095 "preventing access to the various lock " 2096 "properties for the PROPFIND.", 2097 err); 2098 return dav_handle_err(r, err, NULL); 2099 } 2100 if (ctx.w.lockdb != NULL) { 2101 /* if we have a lock database, then we can walk locknull resources */ 2102 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL; 2103 } 2104 2105 /* send <multistatus> tag, with all doc->namespaces attached. */ 2106 2107 /* NOTE: we *cannot* leave out the doc's namespaces from the 2108 initial <multistatus> tag. if a 404 was generated for an HREF, 2109 then we need to spit out the doc's namespaces for use by the 2110 404. Note that <response> elements will override these ns0, 2111 ns1, etc, but NOT within the <response> scope for the 2112 badprops. */ 2113 dav_begin_multistatus(ctx.bb, r, HTTP_MULTI_STATUS, 2114 doc ? doc->namespaces : NULL); 2115 2116 /* Have the provider walk the resource. */ 2117 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status); 2118 2119 if (ctx.w.lockdb != NULL) { 2120 (*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb); 2121 } 2122 2123 if (err != NULL) { 2124 /* If an error occurred during the resource walk, there's 2125 basically nothing we can do but abort the connection and 2126 log an error. This is one of the limitations of HTTP; it 2127 needs to "know" the entire status of the response before 2128 generating it, which is just impossible in these streamy 2129 response situations. */ 2130 err = dav_push_error(r->pool, err->status, 0, 2131 "Provider encountered an error while streaming" 2132 " a multistatus PROPFIND response.", err); 2133 dav_log_err(r, err, APLOG_ERR); 2134 r->connection->aborted = 1; 2135 return DONE; 2136 } 2137 2138 dav_finish_multistatus(r, ctx.bb); 2139 2140 /* the response has been sent. */ 2141 return DONE; 2142} 2143 2144static apr_text * dav_failed_proppatch(apr_pool_t *p, 2145 apr_array_header_t *prop_ctx) 2146{ 2147 apr_text_header hdr = { 0 }; 2148 int i = prop_ctx->nelts; 2149 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts; 2150 dav_error *err424_set = NULL; 2151 dav_error *err424_delete = NULL; 2152 const char *s; 2153 2154 /* ### might be nice to sort by status code and description */ 2155 2156 for ( ; i-- > 0; ++ctx ) { 2157 apr_text_append(p, &hdr, 2158 "<D:propstat>" DEBUG_CR 2159 "<D:prop>"); 2160 apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop)); 2161 apr_text_append(p, &hdr, "</D:prop>" DEBUG_CR); 2162 2163 if (ctx->err == NULL) { 2164 /* nothing was assigned here yet, so make it a 424 */ 2165 2166 if (ctx->operation == DAV_PROP_OP_SET) { 2167 if (err424_set == NULL) 2168 err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0, 0, 2169 "Attempted DAV:set operation " 2170 "could not be completed due " 2171 "to other errors."); 2172 ctx->err = err424_set; 2173 } 2174 else if (ctx->operation == DAV_PROP_OP_DELETE) { 2175 if (err424_delete == NULL) 2176 err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0, 0, 2177 "Attempted DAV:remove " 2178 "operation could not be " 2179 "completed due to other " 2180 "errors."); 2181 ctx->err = err424_delete; 2182 } 2183 } 2184 2185 s = apr_psprintf(p, 2186 "<D:status>" 2187 "HTTP/1.1 %d (status)" 2188 "</D:status>" DEBUG_CR, 2189 ctx->err->status); 2190 apr_text_append(p, &hdr, s); 2191 2192 /* ### we should use compute_desc if necessary... */ 2193 if (ctx->err->desc != NULL) { 2194 apr_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR); 2195 apr_text_append(p, &hdr, ctx->err->desc); 2196 apr_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR); 2197 } 2198 2199 apr_text_append(p, &hdr, "</D:propstat>" DEBUG_CR); 2200 } 2201 2202 return hdr.first; 2203} 2204 2205static apr_text * dav_success_proppatch(apr_pool_t *p, apr_array_header_t *prop_ctx) 2206{ 2207 apr_text_header hdr = { 0 }; 2208 int i = prop_ctx->nelts; 2209 dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts; 2210 2211 /* 2212 * ### we probably need to revise the way we assemble the response... 2213 * ### this code assumes everything will return status==200. 2214 */ 2215 2216 apr_text_append(p, &hdr, 2217 "<D:propstat>" DEBUG_CR 2218 "<D:prop>" DEBUG_CR); 2219 2220 for ( ; i-- > 0; ++ctx ) { 2221 apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop)); 2222 } 2223 2224 apr_text_append(p, &hdr, 2225 "</D:prop>" DEBUG_CR 2226 "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR 2227 "</D:propstat>" DEBUG_CR); 2228 2229 return hdr.first; 2230} 2231 2232static void dav_prop_log_errors(dav_prop_ctx *ctx) 2233{ 2234 dav_log_err(ctx->r, ctx->err, APLOG_ERR); 2235} 2236 2237/* 2238 * Call <func> for each context. This can stop when an error occurs, or 2239 * simply iterate through the whole list. 2240 * 2241 * Returns 1 if an error occurs (and the iteration is aborted). Returns 0 2242 * if all elements are processed. 2243 * 2244 * If <reverse> is true (non-zero), then the list is traversed in 2245 * reverse order. 2246 */ 2247static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx), 2248 apr_array_header_t *ctx_list, int stop_on_error, 2249 int reverse) 2250{ 2251 int i = ctx_list->nelts; 2252 dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts; 2253 2254 if (reverse) 2255 ctx += i; 2256 2257 while (i--) { 2258 if (reverse) 2259 --ctx; 2260 2261 (*func)(ctx); 2262 if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) { 2263 return 1; 2264 } 2265 2266 if (!reverse) 2267 ++ctx; 2268 } 2269 2270 return 0; 2271} 2272 2273/* handle the PROPPATCH method */ 2274static int dav_method_proppatch(request_rec *r) 2275{ 2276 dav_error *err; 2277 dav_resource *resource; 2278 int result; 2279 apr_xml_doc *doc; 2280 apr_xml_elem *child; 2281 dav_propdb *propdb; 2282 int failure = 0; 2283 dav_response resp = { 0 }; 2284 apr_text *propstat_text; 2285 apr_array_header_t *ctx_list; 2286 dav_prop_ctx *ctx; 2287 dav_auto_version_info av_info; 2288 2289 /* Ask repository module to resolve the resource */ 2290 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 2291 &resource); 2292 if (err != NULL) 2293 return dav_handle_err(r, err, NULL); 2294 if (!resource->exists) { 2295 /* Apache will supply a default error for this. */ 2296 return HTTP_NOT_FOUND; 2297 } 2298 2299 if ((result = ap_xml_parse_input(r, &doc)) != OK) { 2300 return result; 2301 } 2302 /* note: doc == NULL if no request body */ 2303 2304 if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) { 2305 /* This supplies additional information for the default message. */ 2306 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00587) 2307 "The request body does not contain " 2308 "a \"propertyupdate\" element."); 2309 return HTTP_BAD_REQUEST; 2310 } 2311 2312 /* Check If-Headers and existing locks */ 2313 /* Note: depth == 0. Implies no need for a multistatus response. */ 2314 if ((err = dav_validate_request(r, resource, 0, NULL, NULL, 2315 DAV_VALIDATE_RESOURCE, NULL)) != NULL) { 2316 /* ### add a higher-level description? */ 2317 return dav_handle_err(r, err, NULL); 2318 } 2319 2320 /* make sure the resource can be modified (if versioning repository) */ 2321 if ((err = dav_auto_checkout(r, resource, 2322 0 /* not parent_only */, 2323 &av_info)) != NULL) { 2324 /* ### add a higher-level description? */ 2325 return dav_handle_err(r, err, NULL); 2326 } 2327 2328 if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces, 2329 &propdb)) != NULL) { 2330 /* undo any auto-checkout */ 2331 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info); 2332 2333 err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 2334 apr_psprintf(r->pool, 2335 "Could not open the property " 2336 "database for %s.", 2337 ap_escape_html(r->pool, r->uri)), 2338 err); 2339 return dav_handle_err(r, err, NULL); 2340 } 2341 /* ### what to do about closing the propdb on server failure? */ 2342 2343 /* ### validate "live" properties */ 2344 2345 /* set up an array to hold property operation contexts */ 2346 ctx_list = apr_array_make(r->pool, 10, sizeof(dav_prop_ctx)); 2347 2348 /* do a first pass to ensure that all "remove" properties exist */ 2349 for (child = doc->root->first_child; child; child = child->next) { 2350 int is_remove; 2351 apr_xml_elem *prop_group; 2352 apr_xml_elem *one_prop; 2353 2354 /* Ignore children that are not set/remove */ 2355 if (child->ns != APR_XML_NS_DAV_ID 2356 || (!(is_remove = (strcmp(child->name, "remove") == 0)) 2357 && strcmp(child->name, "set") != 0)) { 2358 continue; 2359 } 2360 2361 /* make sure that a "prop" child exists for set/remove */ 2362 if ((prop_group = dav_find_child(child, "prop")) == NULL) { 2363 dav_close_propdb(propdb); 2364 2365 /* undo any auto-checkout */ 2366 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info); 2367 2368 /* This supplies additional information for the default message. */ 2369 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00588) 2370 "A \"prop\" element is missing inside " 2371 "the propertyupdate command."); 2372 return HTTP_BAD_REQUEST; 2373 } 2374 2375 for (one_prop = prop_group->first_child; one_prop; 2376 one_prop = one_prop->next) { 2377 2378 ctx = (dav_prop_ctx *)apr_array_push(ctx_list); 2379 ctx->propdb = propdb; 2380 ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET; 2381 ctx->prop = one_prop; 2382 2383 ctx->r = r; /* for later use by dav_prop_log_errors() */ 2384 2385 dav_prop_validate(ctx); 2386 2387 if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) { 2388 failure = 1; 2389 } 2390 } 2391 } 2392 2393 /* ### should test that we found at least one set/remove */ 2394 2395 /* execute all of the operations */ 2396 if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) { 2397 failure = 1; 2398 } 2399 2400 /* generate a failure/success response */ 2401 if (failure) { 2402 (void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1); 2403 propstat_text = dav_failed_proppatch(r->pool, ctx_list); 2404 } 2405 else { 2406 (void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0); 2407 propstat_text = dav_success_proppatch(r->pool, ctx_list); 2408 } 2409 2410 /* make sure this gets closed! */ 2411 dav_close_propdb(propdb); 2412 2413 /* complete any auto-versioning */ 2414 dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info); 2415 2416 /* log any errors that occurred */ 2417 (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0); 2418 2419 resp.href = resource->uri; 2420 2421 /* ### should probably use something new to pass along this text... */ 2422 resp.propresult.propstats = propstat_text; 2423 2424 dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces); 2425 2426 /* the response has been sent. */ 2427 return DONE; 2428} 2429 2430static int process_mkcol_body(request_rec *r) 2431{ 2432 /* This is snarfed from ap_setup_client_block(). We could get pretty 2433 * close to this behavior by passing REQUEST_NO_BODY, but we need to 2434 * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block 2435 * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */ 2436 2437 const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); 2438 const char *lenp = apr_table_get(r->headers_in, "Content-Length"); 2439 2440 /* make sure to set the Apache request fields properly. */ 2441 r->read_body = REQUEST_NO_BODY; 2442 r->read_chunked = 0; 2443 r->remaining = 0; 2444 2445 if (tenc) { 2446 if (strcasecmp(tenc, "chunked")) { 2447 /* Use this instead of Apache's default error string */ 2448 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00589) 2449 "Unknown Transfer-Encoding %s", tenc); 2450 return HTTP_NOT_IMPLEMENTED; 2451 } 2452 2453 r->read_chunked = 1; 2454 } 2455 else if (lenp) { 2456 const char *pos = lenp; 2457 2458 while (apr_isdigit(*pos) || apr_isspace(*pos)) { 2459 ++pos; 2460 } 2461 2462 if (*pos != '\0') { 2463 /* This supplies additional information for the default message. */ 2464 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00590) 2465 "Invalid Content-Length %s", lenp); 2466 return HTTP_BAD_REQUEST; 2467 } 2468 2469 r->remaining = apr_atoi64(lenp); 2470 } 2471 2472 if (r->read_chunked || r->remaining > 0) { 2473 /* ### log something? */ 2474 2475 /* Apache will supply a default error for this. */ 2476 return HTTP_UNSUPPORTED_MEDIA_TYPE; 2477 } 2478 2479 /* 2480 * Get rid of the body. this will call ap_setup_client_block(), but 2481 * our copy above has already verified its work. 2482 */ 2483 return ap_discard_request_body(r); 2484} 2485 2486/* handle the MKCOL method */ 2487static int dav_method_mkcol(request_rec *r) 2488{ 2489 dav_resource *resource; 2490 int resource_state; 2491 dav_auto_version_info av_info; 2492 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); 2493 dav_error *err; 2494 dav_error *err2; 2495 int result; 2496 dav_response *multi_status; 2497 2498 /* handle the request body */ 2499 /* ### this may move lower once we start processing bodies */ 2500 if ((result = process_mkcol_body(r)) != OK) { 2501 return result; 2502 } 2503 2504 /* Ask repository module to resolve the resource */ 2505 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 2506 &resource); 2507 if (err != NULL) 2508 return dav_handle_err(r, err, NULL); 2509 2510 if (resource->exists) { 2511 /* oops. something was already there! */ 2512 2513 /* Apache will supply a default error for this. */ 2514 /* ### we should provide a specific error message! */ 2515 return HTTP_METHOD_NOT_ALLOWED; 2516 } 2517 2518 resource_state = dav_get_resource_state(r, resource); 2519 2520 /* 2521 * Check If-Headers and existing locks. 2522 * 2523 * Note: depth == 0 normally requires no multistatus response. However, 2524 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI 2525 * other than the Request-URI, thereby requiring a multistatus. 2526 * 2527 * If the resource does not exist (DAV_RESOURCE_NULL), then we must 2528 * check the resource *and* its parent. If the resource exists or is 2529 * a locknull resource, then we check only the resource. 2530 */ 2531 if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status, 2532 resource_state == DAV_RESOURCE_NULL ? 2533 DAV_VALIDATE_PARENT : 2534 DAV_VALIDATE_RESOURCE, NULL)) != NULL) { 2535 /* ### add a higher-level description? */ 2536 return dav_handle_err(r, err, multi_status); 2537 } 2538 2539 /* if versioned resource, make sure parent is checked out */ 2540 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */, 2541 &av_info)) != NULL) { 2542 /* ### add a higher-level description? */ 2543 return dav_handle_err(r, err, NULL); 2544 } 2545 2546 /* try to create the collection */ 2547 resource->collection = 1; 2548 err = (*resource->hooks->create_collection)(resource); 2549 2550 /* restore modifiability of parent back to what it was */ 2551 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */, 2552 0 /*unlock*/, &av_info); 2553 2554 /* check for errors now */ 2555 if (err != NULL) { 2556 return dav_handle_err(r, err, NULL); 2557 } 2558 if (err2 != NULL) { 2559 /* just log a warning */ 2560 err = dav_push_error(r->pool, err2->status, 0, 2561 "The MKCOL was successful, but there " 2562 "was a problem automatically checking in " 2563 "the parent collection.", 2564 err2); 2565 dav_log_err(r, err, APLOG_WARNING); 2566 } 2567 2568 if (locks_hooks != NULL) { 2569 dav_lockdb *lockdb; 2570 2571 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) { 2572 /* The directory creation was successful, but the locking failed. */ 2573 err = dav_push_error(r->pool, err->status, 0, 2574 "The MKCOL was successful, but there " 2575 "was a problem opening the lock database " 2576 "which prevents inheriting locks from the " 2577 "parent resources.", 2578 err); 2579 return dav_handle_err(r, err, NULL); 2580 } 2581 2582 /* notify lock system that we have created/replaced a resource */ 2583 err = dav_notify_created(r, lockdb, resource, resource_state, 0); 2584 2585 (*locks_hooks->close_lockdb)(lockdb); 2586 2587 if (err != NULL) { 2588 /* The dir creation was successful, but the locking failed. */ 2589 err = dav_push_error(r->pool, err->status, 0, 2590 "The MKCOL was successful, but there " 2591 "was a problem updating its lock " 2592 "information.", 2593 err); 2594 return dav_handle_err(r, err, NULL); 2595 } 2596 } 2597 2598 /* return an appropriate response (HTTP_CREATED) */ 2599 return dav_created(r, NULL, "Collection", 0); 2600} 2601 2602/* handle the COPY and MOVE methods */ 2603static int dav_method_copymove(request_rec *r, int is_move) 2604{ 2605 dav_resource *resource; 2606 dav_resource *resnew; 2607 dav_auto_version_info src_av_info = { 0 }; 2608 dav_auto_version_info dst_av_info = { 0 }; 2609 const char *body; 2610 const char *dest; 2611 dav_error *err; 2612 dav_error *err2; 2613 dav_error *err3; 2614 dav_response *multi_response; 2615 dav_lookup_result lookup; 2616 int is_dir; 2617 int overwrite; 2618 int depth; 2619 int result; 2620 dav_lockdb *lockdb; 2621 int replace_dest; 2622 int resnew_state; 2623 2624 /* Ask repository module to resolve the resource */ 2625 err = dav_get_resource(r, !is_move /* label_allowed */, 2626 0 /* use_checked_in */, &resource); 2627 if (err != NULL) 2628 return dav_handle_err(r, err, NULL); 2629 2630 if (!resource->exists) { 2631 /* Apache will supply a default error for this. */ 2632 return HTTP_NOT_FOUND; 2633 } 2634 2635 /* If not a file or collection resource, COPY/MOVE not allowed */ 2636 /* ### allow COPY/MOVE of DeltaV resource types */ 2637 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) { 2638 body = apr_psprintf(r->pool, 2639 "Cannot COPY/MOVE resource %s.", 2640 ap_escape_html(r->pool, r->uri)); 2641 return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body); 2642 } 2643 2644 /* get the destination URI */ 2645 dest = apr_table_get(r->headers_in, "Destination"); 2646 if (dest == NULL) { 2647 /* Look in headers provided by Netscape's Roaming Profiles */ 2648 const char *nscp_host = apr_table_get(r->headers_in, "Host"); 2649 const char *nscp_path = apr_table_get(r->headers_in, "New-uri"); 2650 2651 if (nscp_host != NULL && nscp_path != NULL) 2652 dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path); 2653 } 2654 if (dest == NULL) { 2655 /* This supplies additional information for the default message. */ 2656 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00591) 2657 "The request is missing a Destination header."); 2658 return HTTP_BAD_REQUEST; 2659 } 2660 2661 lookup = dav_lookup_uri(dest, r, 1 /* must_be_absolute */); 2662 if (lookup.rnew == NULL) { 2663 if (lookup.err.status == HTTP_BAD_REQUEST) { 2664 /* This supplies additional information for the default message. */ 2665 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00592) 2666 "%s", lookup.err.desc); 2667 return HTTP_BAD_REQUEST; 2668 } 2669 2670 /* ### this assumes that dav_lookup_uri() only generates a status 2671 * ### that Apache can provide a status line for!! */ 2672 2673 return dav_error_response(r, lookup.err.status, lookup.err.desc); 2674 } 2675 if (lookup.rnew->status != HTTP_OK) { 2676 const char *auth = apr_table_get(lookup.rnew->err_headers_out, 2677 "WWW-Authenticate"); 2678 if (lookup.rnew->status == HTTP_UNAUTHORIZED && auth != NULL) { 2679 /* propagate the WWW-Authorization header up from the 2680 * subreq so the client sees it. */ 2681 apr_table_setn(r->err_headers_out, "WWW-Authenticate", 2682 apr_pstrdup(r->pool, auth)); 2683 } 2684 2685 /* ### how best to report this... */ 2686 return dav_error_response(r, lookup.rnew->status, 2687 "Destination URI had an error."); 2688 } 2689 2690 /* Resolve destination resource */ 2691 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */, 2692 0 /* use_checked_in */, &resnew); 2693 if (err != NULL) 2694 return dav_handle_err(r, err, NULL); 2695 2696 /* are the two resources handled by the same repository? */ 2697 if (resource->hooks != resnew->hooks) { 2698 /* ### this message exposes some backend config, but screw it... */ 2699 return dav_error_response(r, HTTP_BAD_GATEWAY, 2700 "Destination URI is handled by a " 2701 "different repository than the source URI. " 2702 "MOVE or COPY between repositories is " 2703 "not possible."); 2704 } 2705 2706 /* get and parse the overwrite header value */ 2707 if ((overwrite = dav_get_overwrite(r)) < 0) { 2708 /* dav_get_overwrite() supplies additional information for the 2709 * default message. */ 2710 return HTTP_BAD_REQUEST; 2711 } 2712 2713 /* quick failure test: if dest exists and overwrite is false. */ 2714 if (resnew->exists && !overwrite) { 2715 /* Supply some text for the error response body. */ 2716 return dav_error_response(r, HTTP_PRECONDITION_FAILED, 2717 "Destination is not empty and " 2718 "Overwrite is not \"T\""); 2719 } 2720 2721 /* are the source and destination the same? */ 2722 if ((*resource->hooks->is_same_resource)(resource, resnew)) { 2723 /* Supply some text for the error response body. */ 2724 return dav_error_response(r, HTTP_FORBIDDEN, 2725 "Source and Destination URIs are the same."); 2726 2727 } 2728 2729 is_dir = resource->collection; 2730 2731 /* get and parse the Depth header value. "0" and "infinity" are legal. */ 2732 if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) { 2733 /* dav_get_depth() supplies additional information for the 2734 * default message. */ 2735 return HTTP_BAD_REQUEST; 2736 } 2737 if (depth == 1) { 2738 /* This supplies additional information for the default message. */ 2739 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00593) 2740 "Depth must be \"0\" or \"infinity\" for COPY or MOVE."); 2741 return HTTP_BAD_REQUEST; 2742 } 2743 if (is_move && is_dir && depth != DAV_INFINITY) { 2744 /* This supplies additional information for the default message. */ 2745 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00594) 2746 "Depth must be \"infinity\" when moving a collection."); 2747 return HTTP_BAD_REQUEST; 2748 } 2749 2750 /* 2751 * Check If-Headers and existing locks for each resource in the source. 2752 * We will return a 424 response with a DAV:multistatus body. 2753 * The multistatus responses will contain the information about any 2754 * resource that fails the validation. 2755 * 2756 * We check the parent resource, too, if this is a MOVE. Moving the 2757 * resource effectively removes it from the parent collection, so we 2758 * must ensure that we have met the appropriate conditions. 2759 * 2760 * If a problem occurs with the Request-URI itself, then a plain error 2761 * (rather than a multistatus) will be returned. 2762 */ 2763 if ((err = dav_validate_request(r, resource, depth, NULL, 2764 &multi_response, 2765 (is_move ? DAV_VALIDATE_PARENT 2766 : DAV_VALIDATE_RESOURCE 2767 | DAV_VALIDATE_NO_MODIFY) 2768 | DAV_VALIDATE_USE_424, 2769 NULL)) != NULL) { 2770 err = dav_push_error(r->pool, err->status, 0, 2771 apr_psprintf(r->pool, 2772 "Could not %s %s due to a failed " 2773 "precondition on the source " 2774 "(e.g. locks).", 2775 is_move ? "MOVE" : "COPY", 2776 ap_escape_html(r->pool, r->uri)), 2777 err); 2778 return dav_handle_err(r, err, multi_response); 2779 } 2780 2781 /* 2782 * Check If-Headers and existing locks for destination. Note that we 2783 * use depth==infinity since the target (hierarchy) will be deleted 2784 * before the move/copy is completed. 2785 * 2786 * Note that we are overwriting the target, which implies a DELETE, so 2787 * we are subject to the error/response rules as a DELETE. Namely, we 2788 * will return a 424 error if any of the validations fail. 2789 * (see dav_method_delete() for more information) 2790 */ 2791 if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL, 2792 &multi_response, 2793 DAV_VALIDATE_PARENT 2794 | DAV_VALIDATE_USE_424, NULL)) != NULL) { 2795 err = dav_push_error(r->pool, err->status, 0, 2796 apr_psprintf(r->pool, 2797 "Could not MOVE/COPY %s due to a " 2798 "failed precondition on the " 2799 "destination (e.g. locks).", 2800 ap_escape_html(r->pool, r->uri)), 2801 err); 2802 return dav_handle_err(r, err, multi_response); 2803 } 2804 2805 if (is_dir 2806 && depth == DAV_INFINITY 2807 && (*resource->hooks->is_parent_resource)(resource, resnew)) { 2808 /* Supply some text for the error response body. */ 2809 return dav_error_response(r, HTTP_FORBIDDEN, 2810 "Source collection contains the " 2811 "Destination."); 2812 2813 } 2814 if (is_dir 2815 && (*resnew->hooks->is_parent_resource)(resnew, resource)) { 2816 /* The destination must exist (since it contains the source), and 2817 * a condition above implies Overwrite==T. Obviously, we cannot 2818 * delete the Destination before the MOVE/COPY, as that would 2819 * delete the Source. 2820 */ 2821 2822 /* Supply some text for the error response body. */ 2823 return dav_error_response(r, HTTP_FORBIDDEN, 2824 "Destination collection contains the Source " 2825 "and Overwrite has been specified."); 2826 } 2827 2828 /* ### for now, we don't need anything in the body */ 2829 if ((result = ap_discard_request_body(r)) != OK) { 2830 return result; 2831 } 2832 2833 if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) { 2834 /* ### add a higher-level description? */ 2835 return dav_handle_err(r, err, NULL); 2836 } 2837 2838 /* remove any locks from the old resources */ 2839 /* 2840 * ### this is Yet Another Traversal. if we do a rename(), then we 2841 * ### really don't have to do this in some cases since the inode 2842 * ### values will remain constant across the move. but we can't 2843 * ### know that fact from outside the provider :-( 2844 * 2845 * ### note that we now have a problem atomicity in the move/copy 2846 * ### since a failure after this would have removed locks (technically, 2847 * ### this is okay to do, but really...) 2848 */ 2849 if (is_move && lockdb != NULL) { 2850 /* ### this is wrong! it blasts direct locks on parent resources */ 2851 /* ### pass lockdb! */ 2852 (void)dav_unlock(r, resource, NULL); 2853 } 2854 2855 /* if this is a move, then the source parent collection will be modified */ 2856 if (is_move) { 2857 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */, 2858 &src_av_info)) != NULL) { 2859 if (lockdb != NULL) 2860 (*lockdb->hooks->close_lockdb)(lockdb); 2861 2862 /* ### add a higher-level description? */ 2863 return dav_handle_err(r, err, NULL); 2864 } 2865 } 2866 2867 /* 2868 * Remember the initial state of the destination, so the lock system 2869 * can be notified as to how it changed. 2870 */ 2871 resnew_state = dav_get_resource_state(lookup.rnew, resnew); 2872 2873 /* In a MOVE operation, the destination is replaced by the source. 2874 * In a COPY operation, if the destination exists, is under version 2875 * control, and is the same resource type as the source, 2876 * then it should not be replaced, but modified to be a copy of 2877 * the source. 2878 */ 2879 if (!resnew->exists) 2880 replace_dest = 0; 2881 else if (is_move || !resource->versioned) 2882 replace_dest = 1; 2883 else if (resource->type != resnew->type) 2884 replace_dest = 1; 2885 else if ((resource->collection == 0) != (resnew->collection == 0)) 2886 replace_dest = 1; 2887 else 2888 replace_dest = 0; 2889 2890 /* If the destination must be created or replaced, 2891 * make sure the parent collection is writable 2892 */ 2893 if (!resnew->exists || replace_dest) { 2894 if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/, 2895 &dst_av_info)) != NULL) { 2896 /* could not make destination writable: 2897 * if move, restore state of source parent 2898 */ 2899 if (is_move) { 2900 (void)dav_auto_checkin(r, NULL, 1 /* undo */, 2901 0 /*unlock*/, &src_av_info); 2902 } 2903 2904 if (lockdb != NULL) 2905 (*lockdb->hooks->close_lockdb)(lockdb); 2906 2907 /* ### add a higher-level description? */ 2908 return dav_handle_err(r, err, NULL); 2909 } 2910 } 2911 2912 /* If source and destination parents are the same, then 2913 * use the same resource object, so status updates to one are reflected 2914 * in the other, when doing auto-versioning. Otherwise, 2915 * we may try to checkin the parent twice. 2916 */ 2917 if (src_av_info.parent_resource != NULL 2918 && dst_av_info.parent_resource != NULL 2919 && (*src_av_info.parent_resource->hooks->is_same_resource) 2920 (src_av_info.parent_resource, dst_av_info.parent_resource)) { 2921 2922 dst_av_info.parent_resource = src_av_info.parent_resource; 2923 } 2924 2925 /* If destination is being replaced, remove it first 2926 * (we know Ovewrite must be TRUE). Then try to copy/move the resource. 2927 */ 2928 if (replace_dest) 2929 err = (*resnew->hooks->remove_resource)(resnew, &multi_response); 2930 2931 if (err == NULL) { 2932 if (is_move) 2933 err = (*resource->hooks->move_resource)(resource, resnew, 2934 &multi_response); 2935 else 2936 err = (*resource->hooks->copy_resource)(resource, resnew, depth, 2937 &multi_response); 2938 } 2939 2940 /* perform any auto-versioning cleanup */ 2941 err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */, 2942 0 /*unlock*/, &dst_av_info); 2943 2944 if (is_move) { 2945 err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */, 2946 0 /*unlock*/, &src_av_info); 2947 } 2948 else 2949 err3 = NULL; 2950 2951 /* check for error from remove/copy/move operations */ 2952 if (err != NULL) { 2953 if (lockdb != NULL) 2954 (*lockdb->hooks->close_lockdb)(lockdb); 2955 2956 err = dav_push_error(r->pool, err->status, 0, 2957 apr_psprintf(r->pool, 2958 "Could not MOVE/COPY %s.", 2959 ap_escape_html(r->pool, r->uri)), 2960 err); 2961 return dav_handle_err(r, err, multi_response); 2962 } 2963 2964 /* check for errors from auto-versioning */ 2965 if (err2 != NULL) { 2966 /* just log a warning */ 2967 err = dav_push_error(r->pool, err2->status, 0, 2968 "The MOVE/COPY was successful, but there was a " 2969 "problem automatically checking in the " 2970 "source parent collection.", 2971 err2); 2972 dav_log_err(r, err, APLOG_WARNING); 2973 } 2974 if (err3 != NULL) { 2975 /* just log a warning */ 2976 err = dav_push_error(r->pool, err3->status, 0, 2977 "The MOVE/COPY was successful, but there was a " 2978 "problem automatically checking in the " 2979 "destination or its parent collection.", 2980 err3); 2981 dav_log_err(r, err, APLOG_WARNING); 2982 } 2983 2984 /* propagate any indirect locks at the target */ 2985 if (lockdb != NULL) { 2986 2987 /* notify lock system that we have created/replaced a resource */ 2988 err = dav_notify_created(r, lockdb, resnew, resnew_state, depth); 2989 2990 (*lockdb->hooks->close_lockdb)(lockdb); 2991 2992 if (err != NULL) { 2993 /* The move/copy was successful, but the locking failed. */ 2994 err = dav_push_error(r->pool, err->status, 0, 2995 "The MOVE/COPY was successful, but there " 2996 "was a problem updating the lock " 2997 "information.", 2998 err); 2999 return dav_handle_err(r, err, NULL); 3000 } 3001 } 3002 3003 /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */ 3004 return dav_created(r, lookup.rnew->unparsed_uri, "Destination", 3005 resnew_state == DAV_RESOURCE_EXISTS); 3006} 3007 3008/* dav_method_lock: Handler to implement the DAV LOCK method 3009 * Returns appropriate HTTP_* response. 3010 */ 3011static int dav_method_lock(request_rec *r) 3012{ 3013 dav_error *err; 3014 dav_resource *resource; 3015 dav_resource *parent; 3016 const dav_hooks_locks *locks_hooks; 3017 int result; 3018 int depth; 3019 int new_lock_request = 0; 3020 apr_xml_doc *doc; 3021 dav_lock *lock; 3022 dav_response *multi_response = NULL; 3023 dav_lockdb *lockdb; 3024 int resource_state; 3025 3026 /* If no locks provider, decline the request */ 3027 locks_hooks = DAV_GET_HOOKS_LOCKS(r); 3028 if (locks_hooks == NULL) 3029 return DECLINED; 3030 3031 if ((result = ap_xml_parse_input(r, &doc)) != OK) 3032 return result; 3033 3034 depth = dav_get_depth(r, DAV_INFINITY); 3035 if (depth != 0 && depth != DAV_INFINITY) { 3036 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00595) 3037 "Depth must be 0 or \"infinity\" for LOCK."); 3038 return HTTP_BAD_REQUEST; 3039 } 3040 3041 /* Ask repository module to resolve the resource */ 3042 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 3043 &resource); 3044 if (err != NULL) 3045 return dav_handle_err(r, err, NULL); 3046 3047 /* Check if parent collection exists */ 3048 if ((err = resource->hooks->get_parent_resource(resource, &parent)) != NULL) { 3049 /* ### add a higher-level description? */ 3050 return dav_handle_err(r, err, NULL); 3051 } 3052 if (parent && (!parent->exists || parent->collection != 1)) { 3053 err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0, 3054 apr_psprintf(r->pool, 3055 "The parent resource of %s does not " 3056 "exist or is not a collection.", 3057 ap_escape_html(r->pool, r->uri))); 3058 return dav_handle_err(r, err, NULL); 3059 } 3060 3061 /* 3062 * Open writable. Unless an error occurs, we'll be 3063 * writing into the database. 3064 */ 3065 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) { 3066 /* ### add a higher-level description? */ 3067 return dav_handle_err(r, err, NULL); 3068 } 3069 3070 if (doc != NULL) { 3071 if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc, 3072 &lock)) != NULL) { 3073 /* ### add a higher-level description to err? */ 3074 goto error; 3075 } 3076 new_lock_request = 1; 3077 3078 lock->auth_user = apr_pstrdup(r->pool, r->user); 3079 } 3080 3081 resource_state = dav_get_resource_state(r, resource); 3082 3083 /* 3084 * Check If-Headers and existing locks. 3085 * 3086 * If this will create a locknull resource, then the LOCK will affect 3087 * the parent collection (much like a PUT/MKCOL). For that case, we must 3088 * validate the parent resource's conditions. 3089 */ 3090 if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response, 3091 (resource_state == DAV_RESOURCE_NULL 3092 ? DAV_VALIDATE_PARENT 3093 : DAV_VALIDATE_RESOURCE) 3094 | (new_lock_request ? lock->scope : 0) 3095 | DAV_VALIDATE_ADD_LD, 3096 lockdb)) != OK) { 3097 err = dav_push_error(r->pool, err->status, 0, 3098 apr_psprintf(r->pool, 3099 "Could not LOCK %s due to a failed " 3100 "precondition (e.g. other locks).", 3101 ap_escape_html(r->pool, r->uri)), 3102 err); 3103 goto error; 3104 } 3105 3106 if (new_lock_request == 0) { 3107 dav_locktoken_list *ltl; 3108 3109 /* 3110 * Refresh request 3111 * ### Assumption: We can renew multiple locks on the same resource 3112 * ### at once. First harvest all the positive lock-tokens given in 3113 * ### the If header. Then modify the lock entries for this resource 3114 * ### with the new Timeout val. 3115 */ 3116 3117 if ((err = dav_get_locktoken_list(r, <l)) != NULL) { 3118 err = dav_push_error(r->pool, err->status, 0, 3119 apr_psprintf(r->pool, 3120 "The lock refresh for %s failed " 3121 "because no lock tokens were " 3122 "specified in an \"If:\" " 3123 "header.", 3124 ap_escape_html(r->pool, r->uri)), 3125 err); 3126 goto error; 3127 } 3128 3129 if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl, 3130 dav_get_timeout(r), 3131 &lock)) != NULL) { 3132 /* ### add a higher-level description to err? */ 3133 goto error; 3134 } 3135 } else { 3136 /* New lock request */ 3137 char *locktoken_txt; 3138 dav_dir_conf *conf; 3139 3140 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config, 3141 &dav_module); 3142 3143 /* apply lower bound (if any) from DAVMinTimeout directive */ 3144 if (lock->timeout != DAV_TIMEOUT_INFINITE 3145 && lock->timeout < time(NULL) + conf->locktimeout) 3146 lock->timeout = time(NULL) + conf->locktimeout; 3147 3148 err = dav_add_lock(r, resource, lockdb, lock, &multi_response); 3149 if (err != NULL) { 3150 /* ### add a higher-level description to err? */ 3151 goto error; 3152 } 3153 3154 locktoken_txt = apr_pstrcat(r->pool, "<", 3155 (*locks_hooks->format_locktoken)(r->pool, 3156 lock->locktoken), 3157 ">", NULL); 3158 3159 apr_table_setn(r->headers_out, "Lock-Token", locktoken_txt); 3160 } 3161 3162 (*locks_hooks->close_lockdb)(lockdb); 3163 3164 r->status = HTTP_OK; 3165 ap_set_content_type(r, DAV_XML_CONTENT_TYPE); 3166 3167 ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r); 3168 if (lock == NULL) 3169 ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r); 3170 else { 3171 ap_rprintf(r, 3172 "<D:lockdiscovery>" DEBUG_CR 3173 "%s" DEBUG_CR 3174 "</D:lockdiscovery>" DEBUG_CR, 3175 dav_lock_get_activelock(r, lock, NULL)); 3176 } 3177 ap_rputs("</D:prop>", r); 3178 3179 /* the response has been sent. */ 3180 return DONE; 3181 3182 error: 3183 (*locks_hooks->close_lockdb)(lockdb); 3184 return dav_handle_err(r, err, multi_response); 3185} 3186 3187/* dav_method_unlock: Handler to implement the DAV UNLOCK method 3188 * Returns appropriate HTTP_* response. 3189 */ 3190static int dav_method_unlock(request_rec *r) 3191{ 3192 dav_error *err; 3193 dav_resource *resource; 3194 const dav_hooks_locks *locks_hooks; 3195 int result; 3196 const char *const_locktoken_txt; 3197 char *locktoken_txt; 3198 dav_locktoken *locktoken = NULL; 3199 int resource_state; 3200 dav_response *multi_response; 3201 3202 /* If no locks provider, decline the request */ 3203 locks_hooks = DAV_GET_HOOKS_LOCKS(r); 3204 if (locks_hooks == NULL) 3205 return DECLINED; 3206 3207 if ((const_locktoken_txt = apr_table_get(r->headers_in, 3208 "Lock-Token")) == NULL) { 3209 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00596) 3210 "Unlock failed (%s): " 3211 "No Lock-Token specified in header", r->filename); 3212 return HTTP_BAD_REQUEST; 3213 } 3214 3215 locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt); 3216 if (locktoken_txt[0] != '<') { 3217 /* ### should provide more specifics... */ 3218 return HTTP_BAD_REQUEST; 3219 } 3220 locktoken_txt++; 3221 3222 if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') { 3223 /* ### should provide more specifics... */ 3224 return HTTP_BAD_REQUEST; 3225 } 3226 locktoken_txt[strlen(locktoken_txt) - 1] = '\0'; 3227 3228 if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt, 3229 &locktoken)) != NULL) { 3230 err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0, 3231 apr_psprintf(r->pool, 3232 "The UNLOCK on %s failed -- an " 3233 "invalid lock token was specified " 3234 "in the \"If:\" header.", 3235 ap_escape_html(r->pool, r->uri)), 3236 err); 3237 return dav_handle_err(r, err, NULL); 3238 } 3239 3240 /* Ask repository module to resolve the resource */ 3241 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 3242 &resource); 3243 if (err != NULL) 3244 return dav_handle_err(r, err, NULL); 3245 3246 resource_state = dav_get_resource_state(r, resource); 3247 3248 /* 3249 * Check If-Headers and existing locks. 3250 * 3251 * Note: depth == 0 normally requires no multistatus response. However, 3252 * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI 3253 * other than the Request-URI, thereby requiring a multistatus. 3254 * 3255 * If the resource is a locknull resource, then the UNLOCK will affect 3256 * the parent collection (much like a delete). For that case, we must 3257 * validate the parent resource's conditions. 3258 */ 3259 if ((err = dav_validate_request(r, resource, 0, locktoken, 3260 &multi_response, 3261 resource_state == DAV_RESOURCE_LOCK_NULL 3262 ? DAV_VALIDATE_PARENT 3263 : DAV_VALIDATE_RESOURCE, NULL)) != NULL) { 3264 /* ### add a higher-level description? */ 3265 return dav_handle_err(r, err, multi_response); 3266 } 3267 3268 /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken, 3269 * _all_ resources locked by locktoken are released. It does not say 3270 * resource has to be the root of an infinte lock. Thus, an UNLOCK 3271 * on any part of an infinte lock will remove the lock on all resources. 3272 * 3273 * For us, if r->filename represents an indirect lock (part of an infinity lock), 3274 * we must actually perform an UNLOCK on the direct lock for this resource. 3275 */ 3276 if ((result = dav_unlock(r, resource, locktoken)) != OK) { 3277 return result; 3278 } 3279 3280 return HTTP_NO_CONTENT; 3281} 3282 3283static int dav_method_vsn_control(request_rec *r) 3284{ 3285 dav_resource *resource; 3286 int resource_state; 3287 dav_auto_version_info av_info; 3288 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); 3289 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 3290 dav_error *err; 3291 apr_xml_doc *doc; 3292 const char *target = NULL; 3293 int result; 3294 3295 /* if no versioning provider, decline the request */ 3296 if (vsn_hooks == NULL) 3297 return DECLINED; 3298 3299 /* ask repository module to resolve the resource */ 3300 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 3301 &resource); 3302 if (err != NULL) 3303 return dav_handle_err(r, err, NULL); 3304 3305 /* remember the pre-creation resource state */ 3306 resource_state = dav_get_resource_state(r, resource); 3307 3308 /* parse the request body (may be a version-control element) */ 3309 if ((result = ap_xml_parse_input(r, &doc)) != OK) { 3310 return result; 3311 } 3312 /* note: doc == NULL if no request body */ 3313 3314 if (doc != NULL) { 3315 const apr_xml_elem *child; 3316 apr_size_t tsize; 3317 3318 if (!dav_validate_root(doc, "version-control")) { 3319 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00597) 3320 "The request body does not contain " 3321 "a \"version-control\" element."); 3322 return HTTP_BAD_REQUEST; 3323 } 3324 3325 /* get the version URI */ 3326 if ((child = dav_find_child(doc->root, "version")) == NULL) { 3327 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00598) 3328 "The \"version-control\" element does not contain " 3329 "a \"version\" element."); 3330 return HTTP_BAD_REQUEST; 3331 } 3332 3333 if ((child = dav_find_child(child, "href")) == NULL) { 3334 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00599) 3335 "The \"version\" element does not contain " 3336 "an \"href\" element."); 3337 return HTTP_BAD_REQUEST; 3338 } 3339 3340 /* get version URI */ 3341 apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL, 3342 &target, &tsize); 3343 if (tsize == 0) { 3344 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00600) 3345 "An \"href\" element does not contain a URI."); 3346 return HTTP_BAD_REQUEST; 3347 } 3348 } 3349 3350 /* Check request preconditions */ 3351 3352 /* ### need a general mechanism for reporting precondition violations 3353 * ### (should be returning XML document for 403/409 responses) 3354 */ 3355 3356 /* if not versioning existing resource, must specify version to select */ 3357 if (!resource->exists && target == NULL) { 3358 err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0, 3359 "<DAV:initial-version-required/>"); 3360 return dav_handle_err(r, err, NULL); 3361 } 3362 else if (resource->exists) { 3363 /* cannot add resource to existing version history */ 3364 if (target != NULL) { 3365 err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0, 3366 "<DAV:cannot-add-to-existing-history/>"); 3367 return dav_handle_err(r, err, NULL); 3368 } 3369 3370 /* resource must be unversioned and versionable, or version selector */ 3371 if (resource->type != DAV_RESOURCE_TYPE_REGULAR 3372 || (!resource->versioned && !(vsn_hooks->versionable)(resource))) { 3373 err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0, 3374 "<DAV:must-be-versionable/>"); 3375 return dav_handle_err(r, err, NULL); 3376 } 3377 3378 /* the DeltaV spec says if resource is a version selector, 3379 * then VERSION-CONTROL is a no-op 3380 */ 3381 if (resource->versioned) { 3382 /* set the Cache-Control header, per the spec */ 3383 apr_table_setn(r->headers_out, "Cache-Control", "no-cache"); 3384 3385 /* no body */ 3386 ap_set_content_length(r, 0); 3387 3388 return DONE; 3389 } 3390 } 3391 3392 /* Check If-Headers and existing locks */ 3393 /* Note: depth == 0. Implies no need for a multistatus response. */ 3394 if ((err = dav_validate_request(r, resource, 0, NULL, NULL, 3395 resource_state == DAV_RESOURCE_NULL ? 3396 DAV_VALIDATE_PARENT : 3397 DAV_VALIDATE_RESOURCE, NULL)) != NULL) { 3398 return dav_handle_err(r, err, NULL); 3399 } 3400 3401 /* if in versioned collection, make sure parent is checked out */ 3402 if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */, 3403 &av_info)) != NULL) { 3404 return dav_handle_err(r, err, NULL); 3405 } 3406 3407 /* attempt to version-control the resource */ 3408 if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) { 3409 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info); 3410 err = dav_push_error(r->pool, HTTP_CONFLICT, 0, 3411 apr_psprintf(r->pool, 3412 "Could not VERSION-CONTROL resource %s.", 3413 ap_escape_html(r->pool, r->uri)), 3414 err); 3415 return dav_handle_err(r, err, NULL); 3416 } 3417 3418 /* revert writability of parent directory */ 3419 err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info); 3420 if (err != NULL) { 3421 /* just log a warning */ 3422 err = dav_push_error(r->pool, err->status, 0, 3423 "The VERSION-CONTROL was successful, but there " 3424 "was a problem automatically checking in " 3425 "the parent collection.", 3426 err); 3427 dav_log_err(r, err, APLOG_WARNING); 3428 } 3429 3430 /* if the resource is lockable, let lock system know of new resource */ 3431 if (locks_hooks != NULL 3432 && (*locks_hooks->get_supportedlock)(resource) != NULL) { 3433 dav_lockdb *lockdb; 3434 3435 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) { 3436 /* The resource creation was successful, but the locking failed. */ 3437 err = dav_push_error(r->pool, err->status, 0, 3438 "The VERSION-CONTROL was successful, but there " 3439 "was a problem opening the lock database " 3440 "which prevents inheriting locks from the " 3441 "parent resources.", 3442 err); 3443 return dav_handle_err(r, err, NULL); 3444 } 3445 3446 /* notify lock system that we have created/replaced a resource */ 3447 err = dav_notify_created(r, lockdb, resource, resource_state, 0); 3448 3449 (*locks_hooks->close_lockdb)(lockdb); 3450 3451 if (err != NULL) { 3452 /* The dir creation was successful, but the locking failed. */ 3453 err = dav_push_error(r->pool, err->status, 0, 3454 "The VERSION-CONTROL was successful, but there " 3455 "was a problem updating its lock " 3456 "information.", 3457 err); 3458 return dav_handle_err(r, err, NULL); 3459 } 3460 } 3461 3462 /* set the Cache-Control header, per the spec */ 3463 apr_table_setn(r->headers_out, "Cache-Control", "no-cache"); 3464 3465 /* return an appropriate response (HTTP_CREATED) */ 3466 return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/); 3467} 3468 3469/* handle the CHECKOUT method */ 3470static int dav_method_checkout(request_rec *r) 3471{ 3472 dav_resource *resource; 3473 dav_resource *working_resource; 3474 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 3475 dav_error *err; 3476 int result; 3477 apr_xml_doc *doc; 3478 int apply_to_vsn = 0; 3479 int is_unreserved = 0; 3480 int is_fork_ok = 0; 3481 int create_activity = 0; 3482 apr_array_header_t *activities = NULL; 3483 3484 /* If no versioning provider, decline the request */ 3485 if (vsn_hooks == NULL) 3486 return DECLINED; 3487 3488 if ((result = ap_xml_parse_input(r, &doc)) != OK) 3489 return result; 3490 3491 if (doc != NULL) { 3492 const apr_xml_elem *aset; 3493 3494 if (!dav_validate_root(doc, "checkout")) { 3495 /* This supplies additional information for the default msg. */ 3496 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00601) 3497 "The request body, if present, must be a " 3498 "DAV:checkout element."); 3499 return HTTP_BAD_REQUEST; 3500 } 3501 3502 if (dav_find_child(doc->root, "apply-to-version") != NULL) { 3503 if (apr_table_get(r->headers_in, "label") != NULL) { 3504 /* ### we want generic 403/409 XML reporting here */ 3505 /* ### DAV:must-not-have-label-and-apply-to-version */ 3506 return dav_error_response(r, HTTP_CONFLICT, 3507 "DAV:apply-to-version cannot be " 3508 "used in conjunction with a " 3509 "Label header."); 3510 } 3511 apply_to_vsn = 1; 3512 } 3513 3514 is_unreserved = dav_find_child(doc->root, "unreserved") != NULL; 3515 is_fork_ok = dav_find_child(doc->root, "fork-ok") != NULL; 3516 3517 if ((aset = dav_find_child(doc->root, "activity-set")) != NULL) { 3518 if (dav_find_child(aset, "new") != NULL) { 3519 create_activity = 1; 3520 } 3521 else { 3522 const apr_xml_elem *child = aset->first_child; 3523 3524 activities = apr_array_make(r->pool, 1, sizeof(const char *)); 3525 3526 for (; child != NULL; child = child->next) { 3527 if (child->ns == APR_XML_NS_DAV_ID 3528 && strcmp(child->name, "href") == 0) { 3529 const char *href; 3530 3531 href = dav_xml_get_cdata(child, r->pool, 3532 1 /* strip_white */); 3533 *(const char **)apr_array_push(activities) = href; 3534 } 3535 } 3536 3537 if (activities->nelts == 0) { 3538 /* no href's is a DTD violation: 3539 <!ELEMENT activity-set (href+ | new)> 3540 */ 3541 3542 /* This supplies additional info for the default msg. */ 3543 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00602) 3544 "Within the DAV:activity-set element, the " 3545 "DAV:new element must be used, or at least " 3546 "one DAV:href must be specified."); 3547 return HTTP_BAD_REQUEST; 3548 } 3549 } 3550 } 3551 } 3552 3553 /* Ask repository module to resolve the resource */ 3554 err = dav_get_resource(r, 1 /*label_allowed*/, apply_to_vsn, &resource); 3555 if (err != NULL) 3556 return dav_handle_err(r, err, NULL); 3557 3558 if (!resource->exists) { 3559 /* Apache will supply a default error for this. */ 3560 return HTTP_NOT_FOUND; 3561 } 3562 3563 /* Check the state of the resource: must be a file or collection, 3564 * must be versioned, and must not already be checked out. 3565 */ 3566 if (resource->type != DAV_RESOURCE_TYPE_REGULAR 3567 && resource->type != DAV_RESOURCE_TYPE_VERSION) { 3568 return dav_error_response(r, HTTP_CONFLICT, 3569 "Cannot checkout this type of resource."); 3570 } 3571 3572 if (!resource->versioned) { 3573 return dav_error_response(r, HTTP_CONFLICT, 3574 "Cannot checkout unversioned resource."); 3575 } 3576 3577 if (resource->working) { 3578 return dav_error_response(r, HTTP_CONFLICT, 3579 "The resource is already checked out to the workspace."); 3580 } 3581 3582 /* ### do lock checks, once behavior is defined */ 3583 3584 /* Do the checkout */ 3585 if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/, 3586 is_unreserved, is_fork_ok, 3587 create_activity, activities, 3588 &working_resource)) != NULL) { 3589 err = dav_push_error(r->pool, HTTP_CONFLICT, 0, 3590 apr_psprintf(r->pool, 3591 "Could not CHECKOUT resource %s.", 3592 ap_escape_html(r->pool, r->uri)), 3593 err); 3594 return dav_handle_err(r, err, NULL); 3595 } 3596 3597 /* set the Cache-Control header, per the spec */ 3598 apr_table_setn(r->headers_out, "Cache-Control", "no-cache"); 3599 3600 /* if no working resource created, return OK, 3601 * else return CREATED with working resource URL in Location header 3602 */ 3603 if (working_resource == NULL) { 3604 /* no body */ 3605 ap_set_content_length(r, 0); 3606 return DONE; 3607 } 3608 3609 return dav_created(r, working_resource->uri, "Checked-out resource", 0); 3610} 3611 3612/* handle the UNCHECKOUT method */ 3613static int dav_method_uncheckout(request_rec *r) 3614{ 3615 dav_resource *resource; 3616 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 3617 dav_error *err; 3618 int result; 3619 3620 /* If no versioning provider, decline the request */ 3621 if (vsn_hooks == NULL) 3622 return DECLINED; 3623 3624 if ((result = ap_discard_request_body(r)) != OK) { 3625 return result; 3626 } 3627 3628 /* Ask repository module to resolve the resource */ 3629 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 3630 &resource); 3631 if (err != NULL) 3632 return dav_handle_err(r, err, NULL); 3633 3634 if (!resource->exists) { 3635 /* Apache will supply a default error for this. */ 3636 return HTTP_NOT_FOUND; 3637 } 3638 3639 /* Check the state of the resource: must be a file or collection, 3640 * must be versioned, and must be checked out. 3641 */ 3642 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) { 3643 return dav_error_response(r, HTTP_CONFLICT, 3644 "Cannot uncheckout this type of resource."); 3645 } 3646 3647 if (!resource->versioned) { 3648 return dav_error_response(r, HTTP_CONFLICT, 3649 "Cannot uncheckout unversioned resource."); 3650 } 3651 3652 if (!resource->working) { 3653 return dav_error_response(r, HTTP_CONFLICT, 3654 "The resource is not checked out to the workspace."); 3655 } 3656 3657 /* ### do lock checks, once behavior is defined */ 3658 3659 /* Do the uncheckout */ 3660 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) { 3661 err = dav_push_error(r->pool, HTTP_CONFLICT, 0, 3662 apr_psprintf(r->pool, 3663 "Could not UNCHECKOUT resource %s.", 3664 ap_escape_html(r->pool, r->uri)), 3665 err); 3666 return dav_handle_err(r, err, NULL); 3667 } 3668 3669 /* no body */ 3670 ap_set_content_length(r, 0); 3671 3672 return DONE; 3673} 3674 3675/* handle the CHECKIN method */ 3676static int dav_method_checkin(request_rec *r) 3677{ 3678 dav_resource *resource; 3679 dav_resource *new_version; 3680 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 3681 dav_error *err; 3682 int result; 3683 apr_xml_doc *doc; 3684 int keep_checked_out = 0; 3685 3686 /* If no versioning provider, decline the request */ 3687 if (vsn_hooks == NULL) 3688 return DECLINED; 3689 3690 if ((result = ap_xml_parse_input(r, &doc)) != OK) 3691 return result; 3692 3693 if (doc != NULL) { 3694 if (!dav_validate_root(doc, "checkin")) { 3695 /* This supplies additional information for the default msg. */ 3696 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00603) 3697 "The request body, if present, must be a " 3698 "DAV:checkin element."); 3699 return HTTP_BAD_REQUEST; 3700 } 3701 3702 keep_checked_out = dav_find_child(doc->root, "keep-checked-out") != NULL; 3703 } 3704 3705 /* Ask repository module to resolve the resource */ 3706 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 3707 &resource); 3708 if (err != NULL) 3709 return dav_handle_err(r, err, NULL); 3710 3711 if (!resource->exists) { 3712 /* Apache will supply a default error for this. */ 3713 return HTTP_NOT_FOUND; 3714 } 3715 3716 /* Check the state of the resource: must be a file or collection, 3717 * must be versioned, and must be checked out. 3718 */ 3719 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) { 3720 return dav_error_response(r, HTTP_CONFLICT, 3721 "Cannot checkin this type of resource."); 3722 } 3723 3724 if (!resource->versioned) { 3725 return dav_error_response(r, HTTP_CONFLICT, 3726 "Cannot checkin unversioned resource."); 3727 } 3728 3729 if (!resource->working) { 3730 return dav_error_response(r, HTTP_CONFLICT, 3731 "The resource is not checked out."); 3732 } 3733 3734 /* ### do lock checks, once behavior is defined */ 3735 3736 /* Do the checkin */ 3737 if ((err = (*vsn_hooks->checkin)(resource, keep_checked_out, &new_version)) 3738 != NULL) { 3739 err = dav_push_error(r->pool, HTTP_CONFLICT, 0, 3740 apr_psprintf(r->pool, 3741 "Could not CHECKIN resource %s.", 3742 ap_escape_html(r->pool, r->uri)), 3743 err); 3744 return dav_handle_err(r, err, NULL); 3745 } 3746 3747 return dav_created(r, new_version->uri, "Version", 0); 3748} 3749 3750static int dav_method_update(request_rec *r) 3751{ 3752 dav_resource *resource; 3753 dav_resource *version = NULL; 3754 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 3755 apr_xml_doc *doc; 3756 apr_xml_elem *child; 3757 int is_label = 0; 3758 int depth; 3759 int result; 3760 apr_size_t tsize; 3761 const char *target; 3762 dav_response *multi_response; 3763 dav_error *err; 3764 dav_lookup_result lookup; 3765 3766 /* If no versioning provider, or UPDATE not supported, 3767 * decline the request */ 3768 if (vsn_hooks == NULL || vsn_hooks->update == NULL) 3769 return DECLINED; 3770 3771 if ((depth = dav_get_depth(r, 0)) < 0) { 3772 /* dav_get_depth() supplies additional information for the 3773 * default message. */ 3774 return HTTP_BAD_REQUEST; 3775 } 3776 3777 /* parse the request body */ 3778 if ((result = ap_xml_parse_input(r, &doc)) != OK) { 3779 return result; 3780 } 3781 3782 if (doc == NULL || !dav_validate_root(doc, "update")) { 3783 /* This supplies additional information for the default message. */ 3784 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00604) 3785 "The request body does not contain " 3786 "an \"update\" element."); 3787 return HTTP_BAD_REQUEST; 3788 } 3789 3790 /* check for label-name or version element, but not both */ 3791 if ((child = dav_find_child(doc->root, "label-name")) != NULL) 3792 is_label = 1; 3793 else if ((child = dav_find_child(doc->root, "version")) != NULL) { 3794 /* get the href element */ 3795 if ((child = dav_find_child(child, "href")) == NULL) { 3796 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00605) 3797 "The version element does not contain " 3798 "an \"href\" element."); 3799 return HTTP_BAD_REQUEST; 3800 } 3801 } 3802 else { 3803 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00606) 3804 "The \"update\" element does not contain " 3805 "a \"label-name\" or \"version\" element."); 3806 return HTTP_BAD_REQUEST; 3807 } 3808 3809 /* a depth greater than zero is only allowed for a label */ 3810 if (!is_label && depth != 0) { 3811 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00607) 3812 "Depth must be zero for UPDATE with a version"); 3813 return HTTP_BAD_REQUEST; 3814 } 3815 3816 /* get the target value (a label or a version URI) */ 3817 apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL, 3818 &target, &tsize); 3819 if (tsize == 0) { 3820 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00608) 3821 "A \"label-name\" or \"href\" element does not contain " 3822 "any content."); 3823 return HTTP_BAD_REQUEST; 3824 } 3825 3826 /* Ask repository module to resolve the resource */ 3827 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 3828 &resource); 3829 if (err != NULL) 3830 return dav_handle_err(r, err, NULL); 3831 3832 if (!resource->exists) { 3833 /* Apache will supply a default error for this. */ 3834 return HTTP_NOT_FOUND; 3835 } 3836 3837 /* ### need a general mechanism for reporting precondition violations 3838 * ### (should be returning XML document for 403/409 responses) 3839 */ 3840 if (resource->type != DAV_RESOURCE_TYPE_REGULAR 3841 || !resource->versioned || resource->working) { 3842 return dav_error_response(r, HTTP_CONFLICT, 3843 "<DAV:must-be-checked-in-version-controlled-resource>"); 3844 } 3845 3846 /* if target is a version, resolve the version resource */ 3847 /* ### dav_lookup_uri only allows absolute URIs; is that OK? */ 3848 if (!is_label) { 3849 lookup = dav_lookup_uri(target, r, 0 /* must_be_absolute */); 3850 if (lookup.rnew == NULL) { 3851 if (lookup.err.status == HTTP_BAD_REQUEST) { 3852 /* This supplies additional information for the default message. */ 3853 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00609) 3854 "%s", lookup.err.desc); 3855 return HTTP_BAD_REQUEST; 3856 } 3857 3858 /* ### this assumes that dav_lookup_uri() only generates a status 3859 * ### that Apache can provide a status line for!! */ 3860 3861 return dav_error_response(r, lookup.err.status, lookup.err.desc); 3862 } 3863 if (lookup.rnew->status != HTTP_OK) { 3864 /* ### how best to report this... */ 3865 return dav_error_response(r, lookup.rnew->status, 3866 "Version URI had an error."); 3867 } 3868 3869 /* resolve version resource */ 3870 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */, 3871 0 /* use_checked_in */, &version); 3872 if (err != NULL) 3873 return dav_handle_err(r, err, NULL); 3874 3875 /* NULL out target, since we're using a version resource */ 3876 target = NULL; 3877 } 3878 3879 /* do the UPDATE operation */ 3880 err = (*vsn_hooks->update)(resource, version, target, depth, &multi_response); 3881 3882 if (err != NULL) { 3883 err = dav_push_error(r->pool, err->status, 0, 3884 apr_psprintf(r->pool, 3885 "Could not UPDATE %s.", 3886 ap_escape_html(r->pool, r->uri)), 3887 err); 3888 return dav_handle_err(r, err, multi_response); 3889 } 3890 3891 /* set the Cache-Control header, per the spec */ 3892 apr_table_setn(r->headers_out, "Cache-Control", "no-cache"); 3893 3894 /* no body */ 3895 ap_set_content_length(r, 0); 3896 3897 return DONE; 3898} 3899 3900/* context maintained during LABEL treewalk */ 3901typedef struct dav_label_walker_ctx 3902{ 3903 /* input: */ 3904 dav_walk_params w; 3905 3906 /* label being manipulated */ 3907 const char *label; 3908 3909 /* label operation */ 3910 int label_op; 3911#define DAV_LABEL_ADD 1 3912#define DAV_LABEL_SET 2 3913#define DAV_LABEL_REMOVE 3 3914 3915 /* version provider hooks */ 3916 const dav_hooks_vsn *vsn_hooks; 3917 3918} dav_label_walker_ctx; 3919 3920static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype) 3921{ 3922 dav_label_walker_ctx *ctx = wres->walk_ctx; 3923 dav_error *err = NULL; 3924 3925 /* Check the state of the resource: must be a version or 3926 * non-checkedout version selector 3927 */ 3928 /* ### need a general mechanism for reporting precondition violations 3929 * ### (should be returning XML document for 403/409 responses) 3930 */ 3931 if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION && 3932 (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR 3933 || !wres->resource->versioned)) { 3934 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0, 0, 3935 "<DAV:must-be-version-or-version-selector/>"); 3936 } 3937 else if (wres->resource->working) { 3938 err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0, 0, 3939 "<DAV:must-not-be-checked-out/>"); 3940 } 3941 else { 3942 /* do the label operation */ 3943 if (ctx->label_op == DAV_LABEL_REMOVE) 3944 err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label); 3945 else 3946 err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label, 3947 ctx->label_op == DAV_LABEL_SET); 3948 } 3949 3950 if (err != NULL) { 3951 /* ### need utility routine to add response with description? */ 3952 dav_add_response(wres, err->status, NULL); 3953 wres->response->desc = err->desc; 3954 } 3955 3956 return NULL; 3957} 3958 3959static int dav_method_label(request_rec *r) 3960{ 3961 dav_resource *resource; 3962 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 3963 apr_xml_doc *doc; 3964 apr_xml_elem *child; 3965 int depth; 3966 int result; 3967 apr_size_t tsize; 3968 dav_error *err; 3969 dav_label_walker_ctx ctx = { { 0 } }; 3970 dav_response *multi_status; 3971 3972 /* If no versioning provider, or the provider doesn't support 3973 * labels, decline the request */ 3974 if (vsn_hooks == NULL || vsn_hooks->add_label == NULL) 3975 return DECLINED; 3976 3977 /* Ask repository module to resolve the resource */ 3978 err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */, 3979 &resource); 3980 if (err != NULL) 3981 return dav_handle_err(r, err, NULL); 3982 if (!resource->exists) { 3983 /* Apache will supply a default error for this. */ 3984 return HTTP_NOT_FOUND; 3985 } 3986 3987 if ((depth = dav_get_depth(r, 0)) < 0) { 3988 /* dav_get_depth() supplies additional information for the 3989 * default message. */ 3990 return HTTP_BAD_REQUEST; 3991 } 3992 3993 /* parse the request body */ 3994 if ((result = ap_xml_parse_input(r, &doc)) != OK) { 3995 return result; 3996 } 3997 3998 if (doc == NULL || !dav_validate_root(doc, "label")) { 3999 /* This supplies additional information for the default message. */ 4000 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00610) 4001 "The request body does not contain " 4002 "a \"label\" element."); 4003 return HTTP_BAD_REQUEST; 4004 } 4005 4006 /* check for add, set, or remove element */ 4007 if ((child = dav_find_child(doc->root, "add")) != NULL) { 4008 ctx.label_op = DAV_LABEL_ADD; 4009 } 4010 else if ((child = dav_find_child(doc->root, "set")) != NULL) { 4011 ctx.label_op = DAV_LABEL_SET; 4012 } 4013 else if ((child = dav_find_child(doc->root, "remove")) != NULL) { 4014 ctx.label_op = DAV_LABEL_REMOVE; 4015 } 4016 else { 4017 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00611) 4018 "The \"label\" element does not contain " 4019 "an \"add\", \"set\", or \"remove\" element."); 4020 return HTTP_BAD_REQUEST; 4021 } 4022 4023 /* get the label string */ 4024 if ((child = dav_find_child(child, "label-name")) == NULL) { 4025 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00612) 4026 "The label command element does not contain " 4027 "a \"label-name\" element."); 4028 return HTTP_BAD_REQUEST; 4029 } 4030 4031 apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL, 4032 &ctx.label, &tsize); 4033 if (tsize == 0) { 4034 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00613) 4035 "A \"label-name\" element does not contain " 4036 "a label name."); 4037 return HTTP_BAD_REQUEST; 4038 } 4039 4040 /* do the label operation walk */ 4041 ctx.w.walk_type = DAV_WALKTYPE_NORMAL; 4042 ctx.w.func = dav_label_walker; 4043 ctx.w.walk_ctx = &ctx; 4044 ctx.w.pool = r->pool; 4045 ctx.w.root = resource; 4046 ctx.vsn_hooks = vsn_hooks; 4047 4048 err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status); 4049 4050 if (err != NULL) { 4051 /* some sort of error occurred which terminated the walk */ 4052 err = dav_push_error(r->pool, err->status, 0, 4053 "The LABEL operation was terminated prematurely.", 4054 err); 4055 return dav_handle_err(r, err, multi_status); 4056 } 4057 4058 if (multi_status != NULL) { 4059 /* One or more resources had errors. If depth was zero, convert 4060 * response to simple error, else make sure there is an 4061 * overall error to pass to dav_handle_err() 4062 */ 4063 if (depth == 0) { 4064 err = dav_new_error(r->pool, multi_status->status, 0, 0, 4065 multi_status->desc); 4066 multi_status = NULL; 4067 } 4068 else { 4069 err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, 0, 4070 "Errors occurred during the LABEL operation."); 4071 } 4072 4073 return dav_handle_err(r, err, multi_status); 4074 } 4075 4076 /* set the Cache-Control header, per the spec */ 4077 apr_table_setn(r->headers_out, "Cache-Control", "no-cache"); 4078 4079 /* no body */ 4080 ap_set_content_length(r, 0); 4081 4082 return DONE; 4083} 4084 4085static int dav_method_report(request_rec *r) 4086{ 4087 dav_resource *resource; 4088 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 4089 int result; 4090 int label_allowed; 4091 apr_xml_doc *doc; 4092 dav_error *err; 4093 4094 /* If no versioning provider, decline the request */ 4095 if (vsn_hooks == NULL) 4096 return DECLINED; 4097 4098 if ((result = ap_xml_parse_input(r, &doc)) != OK) 4099 return result; 4100 if (doc == NULL) { 4101 /* This supplies additional information for the default msg. */ 4102 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00614) 4103 "The request body must specify a report."); 4104 return HTTP_BAD_REQUEST; 4105 } 4106 4107 /* Ask repository module to resolve the resource. 4108 * First determine whether a Target-Selector header is allowed 4109 * for this report. 4110 */ 4111 label_allowed = (*vsn_hooks->report_label_header_allowed)(doc); 4112 err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */, 4113 &resource); 4114 if (err != NULL) 4115 return dav_handle_err(r, err, NULL); 4116 4117 if (!resource->exists) { 4118 /* Apache will supply a default error for this. */ 4119 return HTTP_NOT_FOUND; 4120 } 4121 4122 /* set up defaults for the report response */ 4123 r->status = HTTP_OK; 4124 ap_set_content_type(r, DAV_XML_CONTENT_TYPE); 4125 4126 /* run report hook */ 4127 if ((err = (*vsn_hooks->deliver_report)(r, resource, doc, 4128 r->output_filters)) != NULL) { 4129 if (! r->sent_bodyct) 4130 /* No data has been sent to client yet; throw normal error. */ 4131 return dav_handle_err(r, err, NULL); 4132 4133 /* If an error occurred during the report delivery, there's 4134 basically nothing we can do but abort the connection and 4135 log an error. This is one of the limitations of HTTP; it 4136 needs to "know" the entire status of the response before 4137 generating it, which is just impossible in these streamy 4138 response situations. */ 4139 err = dav_push_error(r->pool, err->status, 0, 4140 "Provider encountered an error while streaming" 4141 " a REPORT response.", err); 4142 dav_log_err(r, err, APLOG_ERR); 4143 r->connection->aborted = 1; 4144 return DONE; 4145 } 4146 4147 return DONE; 4148} 4149 4150static int dav_method_make_workspace(request_rec *r) 4151{ 4152 dav_resource *resource; 4153 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 4154 dav_error *err; 4155 apr_xml_doc *doc; 4156 int result; 4157 4158 /* if no versioning provider, or the provider does not support workspaces, 4159 * decline the request 4160 */ 4161 if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL) 4162 return DECLINED; 4163 4164 /* ask repository module to resolve the resource */ 4165 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 4166 &resource); 4167 if (err != NULL) 4168 return dav_handle_err(r, err, NULL); 4169 4170 /* parse the request body (must be a mkworkspace element) */ 4171 if ((result = ap_xml_parse_input(r, &doc)) != OK) { 4172 return result; 4173 } 4174 4175 if (doc == NULL 4176 || !dav_validate_root(doc, "mkworkspace")) { 4177 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00615) 4178 "The request body does not contain " 4179 "a \"mkworkspace\" element."); 4180 return HTTP_BAD_REQUEST; 4181 } 4182 4183 /* Check request preconditions */ 4184 4185 /* ### need a general mechanism for reporting precondition violations 4186 * ### (should be returning XML document for 403/409 responses) 4187 */ 4188 4189 /* resource must not already exist */ 4190 if (resource->exists) { 4191 err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0, 4192 "<DAV:resource-must-be-null/>"); 4193 return dav_handle_err(r, err, NULL); 4194 } 4195 4196 /* ### what about locking? */ 4197 4198 /* attempt to create the workspace */ 4199 if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) { 4200 err = dav_push_error(r->pool, err->status, 0, 4201 apr_psprintf(r->pool, 4202 "Could not create workspace %s.", 4203 ap_escape_html(r->pool, r->uri)), 4204 err); 4205 return dav_handle_err(r, err, NULL); 4206 } 4207 4208 /* set the Cache-Control header, per the spec */ 4209 apr_table_setn(r->headers_out, "Cache-Control", "no-cache"); 4210 4211 /* return an appropriate response (HTTP_CREATED) */ 4212 return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/); 4213} 4214 4215static int dav_method_make_activity(request_rec *r) 4216{ 4217 dav_resource *resource; 4218 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 4219 dav_error *err; 4220 int result; 4221 4222 /* if no versioning provider, or the provider does not support activities, 4223 * decline the request 4224 */ 4225 if (vsn_hooks == NULL || vsn_hooks->make_activity == NULL) 4226 return DECLINED; 4227 4228 /* ask repository module to resolve the resource */ 4229 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 4230 &resource); 4231 if (err != NULL) 4232 return dav_handle_err(r, err, NULL); 4233 4234 /* MKACTIVITY does not have a defined request body. */ 4235 if ((result = ap_discard_request_body(r)) != OK) { 4236 return result; 4237 } 4238 4239 /* Check request preconditions */ 4240 4241 /* ### need a general mechanism for reporting precondition violations 4242 * ### (should be returning XML document for 403/409 responses) 4243 */ 4244 4245 /* resource must not already exist */ 4246 if (resource->exists) { 4247 err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 0, 4248 "<DAV:resource-must-be-null/>"); 4249 return dav_handle_err(r, err, NULL); 4250 } 4251 4252 /* the provider must say whether the resource can be created as 4253 an activity, i.e. whether the location is ok. */ 4254 if (vsn_hooks->can_be_activity != NULL 4255 && !(*vsn_hooks->can_be_activity)(resource)) { 4256 err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0, 0, 4257 "<DAV:activity-location-ok/>"); 4258 return dav_handle_err(r, err, NULL); 4259 } 4260 4261 /* ### what about locking? */ 4262 4263 /* attempt to create the activity */ 4264 if ((err = (*vsn_hooks->make_activity)(resource)) != NULL) { 4265 err = dav_push_error(r->pool, err->status, 0, 4266 apr_psprintf(r->pool, 4267 "Could not create activity %s.", 4268 ap_escape_html(r->pool, r->uri)), 4269 err); 4270 return dav_handle_err(r, err, NULL); 4271 } 4272 4273 /* set the Cache-Control header, per the spec */ 4274 apr_table_setn(r->headers_out, "Cache-Control", "no-cache"); 4275 4276 /* return an appropriate response (HTTP_CREATED) */ 4277 return dav_created(r, resource->uri, "Activity", 0 /*replaced*/); 4278} 4279 4280static int dav_method_baseline_control(request_rec *r) 4281{ 4282 /* ### */ 4283 return HTTP_METHOD_NOT_ALLOWED; 4284} 4285 4286static int dav_method_merge(request_rec *r) 4287{ 4288 dav_resource *resource; 4289 dav_resource *source_resource; 4290 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 4291 dav_error *err; 4292 int result; 4293 apr_xml_doc *doc; 4294 apr_xml_elem *source_elem; 4295 apr_xml_elem *href_elem; 4296 apr_xml_elem *prop_elem; 4297 const char *source; 4298 int no_auto_merge; 4299 int no_checkout; 4300 dav_lookup_result lookup; 4301 4302 /* If no versioning provider, decline the request */ 4303 if (vsn_hooks == NULL) 4304 return DECLINED; 4305 4306 if ((result = ap_xml_parse_input(r, &doc)) != OK) 4307 return result; 4308 4309 if (doc == NULL || !dav_validate_root(doc, "merge")) { 4310 /* This supplies additional information for the default msg. */ 4311 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00616) 4312 "The request body must be present and must be a " 4313 "DAV:merge element."); 4314 return HTTP_BAD_REQUEST; 4315 } 4316 4317 if ((source_elem = dav_find_child(doc->root, "source")) == NULL) { 4318 /* This supplies additional information for the default msg. */ 4319 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00617) 4320 "The DAV:merge element must contain a DAV:source " 4321 "element."); 4322 return HTTP_BAD_REQUEST; 4323 } 4324 if ((href_elem = dav_find_child(source_elem, "href")) == NULL) { 4325 /* This supplies additional information for the default msg. */ 4326 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00618) 4327 "The DAV:source element must contain a DAV:href " 4328 "element."); 4329 return HTTP_BAD_REQUEST; 4330 } 4331 source = dav_xml_get_cdata(href_elem, r->pool, 1 /* strip_white */); 4332 4333 /* get a subrequest for the source, so that we can get a dav_resource 4334 for that source. */ 4335 lookup = dav_lookup_uri(source, r, 0 /* must_be_absolute */); 4336 if (lookup.rnew == NULL) { 4337 if (lookup.err.status == HTTP_BAD_REQUEST) { 4338 /* This supplies additional information for the default message. */ 4339 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00619) 4340 "%s", lookup.err.desc); 4341 return HTTP_BAD_REQUEST; 4342 } 4343 4344 /* ### this assumes that dav_lookup_uri() only generates a status 4345 * ### that Apache can provide a status line for!! */ 4346 4347 return dav_error_response(r, lookup.err.status, lookup.err.desc); 4348 } 4349 if (lookup.rnew->status != HTTP_OK) { 4350 /* ### how best to report this... */ 4351 return dav_error_response(r, lookup.rnew->status, 4352 "Merge source URI had an error."); 4353 } 4354 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */, 4355 0 /* use_checked_in */, &source_resource); 4356 if (err != NULL) 4357 return dav_handle_err(r, err, NULL); 4358 4359 no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL; 4360 no_checkout = dav_find_child(doc->root, "no-checkout") != NULL; 4361 4362 prop_elem = dav_find_child(doc->root, "prop"); 4363 4364 /* ### check RFC. I believe the DAV:merge element may contain any 4365 ### element also allowed within DAV:checkout. need to extract them 4366 ### here, and pass them along. 4367 ### if so, then refactor the CHECKOUT method handling so we can reuse 4368 ### the code. maybe create a structure to hold CHECKOUT parameters 4369 ### which can be passed to the checkout() and merge() hooks. */ 4370 4371 /* Ask repository module to resolve the resource */ 4372 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 4373 &resource); 4374 if (err != NULL) 4375 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; 4379 } 4380 4381 /* ### check the source and target resources flags/types */ 4382 4383 /* ### do lock checks, once behavior is defined */ 4384 4385 /* set the Cache-Control header, per the spec */ 4386 /* ### correct? */ 4387 apr_table_setn(r->headers_out, "Cache-Control", "no-cache"); 4388 4389 /* Initialize these values for a standard MERGE response. If the MERGE 4390 is going to do something different (i.e. an error), then it must 4391 return a dav_error, and we'll reset these values properly. */ 4392 r->status = HTTP_OK; 4393 ap_set_content_type(r, "text/xml"); 4394 4395 /* ### should we do any preliminary response generation? probably not, 4396 ### because we may have an error, thus demanding something else in 4397 ### the response body. */ 4398 4399 /* Do the merge, including any response generation. */ 4400 if ((err = (*vsn_hooks->merge)(resource, source_resource, 4401 no_auto_merge, no_checkout, 4402 prop_elem, 4403 r->output_filters)) != NULL) { 4404 /* ### is err->status the right error here? */ 4405 err = dav_push_error(r->pool, err->status, 0, 4406 apr_psprintf(r->pool, 4407 "Could not MERGE resource \"%s\" " 4408 "into \"%s\".", 4409 ap_escape_html(r->pool, source), 4410 ap_escape_html(r->pool, r->uri)), 4411 err); 4412 return dav_handle_err(r, err, NULL); 4413 } 4414 4415 /* the response was fully generated by the merge() hook. */ 4416 /* ### urk. does this prevent logging? need to check... */ 4417 return DONE; 4418} 4419 4420static int dav_method_bind(request_rec *r) 4421{ 4422 dav_resource *resource; 4423 dav_resource *binding; 4424 dav_auto_version_info av_info; 4425 const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r); 4426 const char *dest; 4427 dav_error *err; 4428 dav_error *err2; 4429 dav_response *multi_response = NULL; 4430 dav_lookup_result lookup; 4431 int overwrite; 4432 4433 /* If no bindings provider, decline the request */ 4434 if (binding_hooks == NULL) 4435 return DECLINED; 4436 4437 /* Ask repository module to resolve the resource */ 4438 err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */, 4439 &resource); 4440 if (err != NULL) 4441 return dav_handle_err(r, err, NULL); 4442 4443 if (!resource->exists) { 4444 /* Apache will supply a default error for this. */ 4445 return HTTP_NOT_FOUND; 4446 } 4447 4448 /* get the destination URI */ 4449 dest = apr_table_get(r->headers_in, "Destination"); 4450 if (dest == NULL) { 4451 /* This supplies additional information for the default message. */ 4452 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00620) 4453 "The request is missing a Destination header."); 4454 return HTTP_BAD_REQUEST; 4455 } 4456 4457 lookup = dav_lookup_uri(dest, r, 0 /* must_be_absolute */); 4458 if (lookup.rnew == NULL) { 4459 if (lookup.err.status == HTTP_BAD_REQUEST) { 4460 /* This supplies additional information for the default message. */ 4461 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00621) 4462 "%s", lookup.err.desc); 4463 return HTTP_BAD_REQUEST; 4464 } 4465 else if (lookup.err.status == HTTP_BAD_GATEWAY) { 4466 /* ### Bindings protocol draft 02 says to return 507 4467 * ### (Cross Server Binding Forbidden); Apache already defines 507 4468 * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return 4469 * ### HTTP_FORBIDDEN 4470 */ 4471 return dav_error_response(r, HTTP_FORBIDDEN, 4472 "Cross server bindings are not " 4473 "allowed by this server."); 4474 } 4475 4476 /* ### this assumes that dav_lookup_uri() only generates a status 4477 * ### that Apache can provide a status line for!! */ 4478 4479 return dav_error_response(r, lookup.err.status, lookup.err.desc); 4480 } 4481 if (lookup.rnew->status != HTTP_OK) { 4482 /* ### how best to report this... */ 4483 return dav_error_response(r, lookup.rnew->status, 4484 "Destination URI had an error."); 4485 } 4486 4487 /* resolve binding resource */ 4488 err = dav_get_resource(lookup.rnew, 0 /* label_allowed */, 4489 0 /* use_checked_in */, &binding); 4490 if (err != NULL) 4491 return dav_handle_err(r, err, NULL); 4492 4493 /* are the two resources handled by the same repository? */ 4494 if (resource->hooks != binding->hooks) { 4495 /* ### this message exposes some backend config, but screw it... */ 4496 return dav_error_response(r, HTTP_BAD_GATEWAY, 4497 "Destination URI is handled by a " 4498 "different repository than the source URI. " 4499 "BIND between repositories is not possible."); 4500 } 4501 4502 /* get and parse the overwrite header value */ 4503 if ((overwrite = dav_get_overwrite(r)) < 0) { 4504 /* dav_get_overwrite() supplies additional information for the 4505 * default message. */ 4506 return HTTP_BAD_REQUEST; 4507 } 4508 4509 /* quick failure test: if dest exists and overwrite is false. */ 4510 if (binding->exists && !overwrite) { 4511 return dav_error_response(r, HTTP_PRECONDITION_FAILED, 4512 "Destination is not empty and " 4513 "Overwrite is not \"T\""); 4514 } 4515 4516 /* are the source and destination the same? */ 4517 if ((*resource->hooks->is_same_resource)(resource, binding)) { 4518 return dav_error_response(r, HTTP_FORBIDDEN, 4519 "Source and Destination URIs are the same."); 4520 } 4521 4522 /* 4523 * Check If-Headers and existing locks for destination. Note that we 4524 * use depth==infinity since the target (hierarchy) will be deleted 4525 * before the move/copy is completed. 4526 * 4527 * Note that we are overwriting the target, which implies a DELETE, so 4528 * we are subject to the error/response rules as a DELETE. Namely, we 4529 * will return a 424 error if any of the validations fail. 4530 * (see dav_method_delete() for more information) 4531 */ 4532 if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL, 4533 &multi_response, 4534 DAV_VALIDATE_PARENT 4535 | DAV_VALIDATE_USE_424, NULL)) != NULL) { 4536 err = dav_push_error(r->pool, err->status, 0, 4537 apr_psprintf(r->pool, 4538 "Could not BIND %s due to a " 4539 "failed precondition on the " 4540 "destination (e.g. locks).", 4541 ap_escape_html(r->pool, r->uri)), 4542 err); 4543 return dav_handle_err(r, err, multi_response); 4544 } 4545 4546 /* guard against creating circular bindings */ 4547 if (resource->collection 4548 && (*resource->hooks->is_parent_resource)(resource, binding)) { 4549 return dav_error_response(r, HTTP_FORBIDDEN, 4550 "Source collection contains the Destination."); 4551 } 4552 if (resource->collection 4553 && (*resource->hooks->is_parent_resource)(binding, resource)) { 4554 /* The destination must exist (since it contains the source), and 4555 * a condition above implies Overwrite==T. Obviously, we cannot 4556 * delete the Destination before the BIND, as that would 4557 * delete the Source. 4558 */ 4559 4560 return dav_error_response(r, HTTP_FORBIDDEN, 4561 "Destination collection contains the Source and " 4562 "Overwrite has been specified."); 4563 } 4564 4565 /* prepare the destination collection for modification */ 4566 if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */, 4567 &av_info)) != NULL) { 4568 /* could not make destination writable */ 4569 return dav_handle_err(r, err, NULL); 4570 } 4571 4572 /* If target exists, remove it first (we know Ovewrite must be TRUE). 4573 * Then try to bind to the resource. 4574 */ 4575 if (binding->exists) 4576 err = (*resource->hooks->remove_resource)(binding, &multi_response); 4577 4578 if (err == NULL) { 4579 err = (*binding_hooks->bind_resource)(resource, binding); 4580 } 4581 4582 /* restore parent collection states */ 4583 err2 = dav_auto_checkin(r, NULL, 4584 err != NULL /* undo if error */, 4585 0 /* unlock */, &av_info); 4586 4587 /* check for error from remove/bind operations */ 4588 if (err != NULL) { 4589 err = dav_push_error(r->pool, err->status, 0, 4590 apr_psprintf(r->pool, 4591 "Could not BIND %s.", 4592 ap_escape_html(r->pool, r->uri)), 4593 err); 4594 return dav_handle_err(r, err, multi_response); 4595 } 4596 4597 /* check for errors from reverting writability */ 4598 if (err2 != NULL) { 4599 /* just log a warning */ 4600 err = dav_push_error(r->pool, err2->status, 0, 4601 "The BIND was successful, but there was a " 4602 "problem automatically checking in the " 4603 "source parent collection.", 4604 err2); 4605 dav_log_err(r, err, APLOG_WARNING); 4606 } 4607 4608 /* return an appropriate response (HTTP_CREATED) */ 4609 /* ### spec doesn't say what happens when destination was replaced */ 4610 return dav_created(r, lookup.rnew->unparsed_uri, "Binding", 0); 4611} 4612 4613 4614/* 4615 * Response handler for DAV resources 4616 */ 4617static int dav_handler(request_rec *r) 4618{ 4619 if (strcmp(r->handler, DAV_HANDLER_NAME) != 0) 4620 return DECLINED; 4621 4622 /* Reject requests with an unescaped hash character, as these may 4623 * be more destructive than the user intended. */ 4624 if (r->parsed_uri.fragment != NULL) { 4625 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00622) 4626 "buggy client used un-escaped hash in Request-URI"); 4627 return dav_error_response(r, HTTP_BAD_REQUEST, 4628 "The request was invalid: the URI included " 4629 "an un-escaped hash character"); 4630 } 4631 4632 /* ### do we need to do anything with r->proxyreq ?? */ 4633 4634 /* 4635 * ### anything else to do here? could another module and/or 4636 * ### config option "take over" the handler here? i.e. how do 4637 * ### we lock down this hierarchy so that we are the ultimate 4638 * ### arbiter? (or do we simply depend on the administrator 4639 * ### to avoid conflicting configurations?) 4640 */ 4641 4642 /* 4643 * Set up the methods mask, since that's one of the reasons this handler 4644 * gets called, and lower-level things may need the info. 4645 * 4646 * First, set the mask to the methods we handle directly. Since by 4647 * definition we own our managed space, we unconditionally set 4648 * the r->allowed field rather than ORing our values with anything 4649 * any other module may have put in there. 4650 * 4651 * These are the HTTP-defined methods that we handle directly. 4652 */ 4653 r->allowed = 0 4654 | (AP_METHOD_BIT << M_GET) 4655 | (AP_METHOD_BIT << M_PUT) 4656 | (AP_METHOD_BIT << M_DELETE) 4657 | (AP_METHOD_BIT << M_OPTIONS) 4658 | (AP_METHOD_BIT << M_INVALID); 4659 4660 /* 4661 * These are the DAV methods we handle. 4662 */ 4663 r->allowed |= 0 4664 | (AP_METHOD_BIT << M_COPY) 4665 | (AP_METHOD_BIT << M_LOCK) 4666 | (AP_METHOD_BIT << M_UNLOCK) 4667 | (AP_METHOD_BIT << M_MKCOL) 4668 | (AP_METHOD_BIT << M_MOVE) 4669 | (AP_METHOD_BIT << M_PROPFIND) 4670 | (AP_METHOD_BIT << M_PROPPATCH); 4671 4672 /* 4673 * These are methods that we don't handle directly, but let the 4674 * server's default handler do for us as our agent. 4675 */ 4676 r->allowed |= 0 4677 | (AP_METHOD_BIT << M_POST); 4678 4679 /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header 4680 * ### is sent; it will need the other allowed states; since the default 4681 * ### handler is not called on error, then it doesn't add the other 4682 * ### allowed states, so we must 4683 */ 4684 4685 /* ### we might need to refine this for just where we return the error. 4686 * ### also, there is the issue with other methods (see ISSUES) 4687 */ 4688 4689 /* dispatch the appropriate method handler */ 4690 if (r->method_number == M_GET) { 4691 return dav_method_get(r); 4692 } 4693 4694 if (r->method_number == M_PUT) { 4695 return dav_method_put(r); 4696 } 4697 4698 if (r->method_number == M_POST) { 4699 return dav_method_post(r); 4700 } 4701 4702 if (r->method_number == M_DELETE) { 4703 return dav_method_delete(r); 4704 } 4705 4706 if (r->method_number == M_OPTIONS) { 4707 return dav_method_options(r); 4708 } 4709 4710 if (r->method_number == M_PROPFIND) { 4711 return dav_method_propfind(r); 4712 } 4713 4714 if (r->method_number == M_PROPPATCH) { 4715 return dav_method_proppatch(r); 4716 } 4717 4718 if (r->method_number == M_MKCOL) { 4719 return dav_method_mkcol(r); 4720 } 4721 4722 if (r->method_number == M_COPY) { 4723 return dav_method_copymove(r, DAV_DO_COPY); 4724 } 4725 4726 if (r->method_number == M_MOVE) { 4727 return dav_method_copymove(r, DAV_DO_MOVE); 4728 } 4729 4730 if (r->method_number == M_LOCK) { 4731 return dav_method_lock(r); 4732 } 4733 4734 if (r->method_number == M_UNLOCK) { 4735 return dav_method_unlock(r); 4736 } 4737 4738 if (r->method_number == M_VERSION_CONTROL) { 4739 return dav_method_vsn_control(r); 4740 } 4741 4742 if (r->method_number == M_CHECKOUT) { 4743 return dav_method_checkout(r); 4744 } 4745 4746 if (r->method_number == M_UNCHECKOUT) { 4747 return dav_method_uncheckout(r); 4748 } 4749 4750 if (r->method_number == M_CHECKIN) { 4751 return dav_method_checkin(r); 4752 } 4753 4754 if (r->method_number == M_UPDATE) { 4755 return dav_method_update(r); 4756 } 4757 4758 if (r->method_number == M_LABEL) { 4759 return dav_method_label(r); 4760 } 4761 4762 if (r->method_number == M_REPORT) { 4763 return dav_method_report(r); 4764 } 4765 4766 if (r->method_number == M_MKWORKSPACE) { 4767 return dav_method_make_workspace(r); 4768 } 4769 4770 if (r->method_number == M_MKACTIVITY) { 4771 return dav_method_make_activity(r); 4772 } 4773 4774 if (r->method_number == M_BASELINE_CONTROL) { 4775 return dav_method_baseline_control(r); 4776 } 4777 4778 if (r->method_number == M_MERGE) { 4779 return dav_method_merge(r); 4780 } 4781 4782 /* BIND method */ 4783 if (r->method_number == dav_methods[DAV_M_BIND]) { 4784 return dav_method_bind(r); 4785 } 4786 4787 /* DASL method */ 4788 if (r->method_number == dav_methods[DAV_M_SEARCH]) { 4789 return dav_method_search(r); 4790 } 4791 4792 /* ### add'l methods for Advanced Collections, ACLs */ 4793 4794 return DECLINED; 4795} 4796 4797static int dav_fixups(request_rec *r) 4798{ 4799 dav_dir_conf *conf; 4800 4801 /* quickly ignore any HTTP/0.9 requests which aren't subreqs. */ 4802 if (r->assbackwards && !r->main) { 4803 return DECLINED; 4804 } 4805 4806 conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config, 4807 &dav_module); 4808 4809 /* if DAV is not enabled, then we've got nothing to do */ 4810 if (conf->provider == NULL) { 4811 return DECLINED; 4812 } 4813 4814 /* We are going to handle almost every request. In certain cases, 4815 the provider maps to the filesystem (thus, handle_get is 4816 FALSE), and core Apache will handle it. a For that case, we 4817 just return right away. */ 4818 if (r->method_number == M_GET) { 4819 /* 4820 * ### need some work to pull Content-Type and Content-Language 4821 * ### from the property database. 4822 */ 4823 4824 /* 4825 * If the repository hasn't indicated that it will handle the 4826 * GET method, then just punt. 4827 * 4828 * ### this isn't quite right... taking over the response can break 4829 * ### things like mod_negotiation. need to look into this some more. 4830 */ 4831 if (!conf->provider->repos->handle_get) { 4832 return DECLINED; 4833 } 4834 } 4835 4836 /* ### this is wrong. We should only be setting the r->handler for the 4837 * requests that mod_dav knows about. If we set the handler for M_POST 4838 * requests, then CGI scripts that use POST will return the source for the 4839 * script. However, mod_dav DOES handle POST, so something else needs 4840 * to be fixed. 4841 */ 4842 if (r->method_number != M_POST) { 4843 4844 /* We are going to be handling the response for this resource. */ 4845 r->handler = DAV_HANDLER_NAME; 4846 return OK; 4847 } 4848 4849 return DECLINED; 4850} 4851 4852static void register_hooks(apr_pool_t *p) 4853{ 4854 ap_hook_handler(dav_handler, NULL, NULL, APR_HOOK_MIDDLE); 4855 ap_hook_post_config(dav_init_handler, NULL, NULL, APR_HOOK_MIDDLE); 4856 ap_hook_fixups(dav_fixups, NULL, NULL, APR_HOOK_MIDDLE); 4857 4858 dav_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, APR_HOOK_LAST); 4859 dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops, 4860 NULL, NULL, APR_HOOK_MIDDLE); 4861 4862 dav_core_register_uris(p); 4863} 4864 4865/*--------------------------------------------------------------------------- 4866 * 4867 * Configuration info for the module 4868 */ 4869 4870static const command_rec dav_cmds[] = 4871{ 4872 /* per directory/location */ 4873 AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF, 4874 "specify the DAV provider for a directory or location"), 4875 4876 /* per directory/location, or per server */ 4877 AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL, 4878 ACCESS_CONF|RSRC_CONF, 4879 "specify minimum allowed timeout"), 4880 4881 /* per directory/location, or per server */ 4882 AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL, 4883 ACCESS_CONF|RSRC_CONF, 4884 "allow Depth infinity PROPFIND requests"), 4885 4886 { NULL } 4887}; 4888 4889module DAV_DECLARE_DATA dav_module = 4890{ 4891 STANDARD20_MODULE_STUFF, 4892 dav_create_dir_config, /* dir config creater */ 4893 dav_merge_dir_config, /* dir merger --- default is to override */ 4894 dav_create_server_config, /* server config */ 4895 dav_merge_server_config, /* merge server config */ 4896 dav_cmds, /* command table */ 4897 register_hooks, /* register hooks */ 4898}; 4899 4900APR_HOOK_STRUCT( 4901 APR_HOOK_LINK(gather_propsets) 4902 APR_HOOK_LINK(find_liveprop) 4903 APR_HOOK_LINK(insert_all_liveprops) 4904 ) 4905 4906APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets, 4907 (apr_array_header_t *uris), 4908 (uris)) 4909 4910APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, find_liveprop, 4911 (const dav_resource *resource, 4912 const char *ns_uri, const char *name, 4913 const dav_hooks_liveprop **hooks), 4914 (resource, ns_uri, name, hooks), 0) 4915 4916APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops, 4917 (request_rec *r, const dav_resource *resource, 4918 dav_prop_insert what, apr_text_header *phdr), 4919 (r, resource, what, phdr)) 4920