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** - various utilities, repository-independent 20*/ 21 22#include "apr_strings.h" 23#include "apr_lib.h" 24 25#define APR_WANT_STRFUNC 26#include "apr_want.h" 27 28#include "mod_dav.h" 29 30#include "http_request.h" 31#include "http_config.h" 32#include "http_vhost.h" 33#include "http_log.h" 34#include "http_protocol.h" 35 36DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status, 37 int error_id, const char *desc) 38{ 39 int save_errno = errno; 40 dav_error *err = apr_pcalloc(p, sizeof(*err)); 41 42 /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */ 43 44 err->status = status; 45 err->error_id = error_id; 46 err->desc = desc; 47 err->save_errno = save_errno; 48 49 return err; 50} 51 52DAV_DECLARE(dav_error*) dav_new_error_tag(apr_pool_t *p, int status, 53 int error_id, const char *desc, 54 const char *namespace, 55 const char *tagname) 56{ 57 dav_error *err = dav_new_error(p, status, error_id, desc); 58 59 err->tagname = tagname; 60 err->namespace = namespace; 61 62 return err; 63} 64 65 66DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status, 67 int error_id, const char *desc, 68 dav_error *prev) 69{ 70 dav_error *err = apr_pcalloc(p, sizeof(*err)); 71 72 err->status = status; 73 err->error_id = error_id; 74 err->desc = desc; 75 err->prev = prev; 76 77 return err; 78} 79 80DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf, 81 apr_size_t extra_needed) 82{ 83 /* grow the buffer if necessary */ 84 if (pbuf->cur_len + extra_needed > pbuf->alloc_len) { 85 char *newbuf; 86 87 pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD; 88 newbuf = apr_palloc(p, pbuf->alloc_len); 89 memcpy(newbuf, pbuf->buf, pbuf->cur_len); 90 pbuf->buf = newbuf; 91 } 92} 93 94DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf, 95 apr_size_t size) 96{ 97 /* NOTE: this does not retain prior contents */ 98 99 /* NOTE: this function is used to init the first pointer, too, since 100 the PAD will be larger than alloc_len (0) for zeroed structures */ 101 102 /* grow if we don't have enough for the requested size plus padding */ 103 if (size + DAV_BUFFER_PAD > pbuf->alloc_len) { 104 /* set the new length; min of MINSIZE */ 105 pbuf->alloc_len = size + DAV_BUFFER_PAD; 106 if (pbuf->alloc_len < DAV_BUFFER_MINSIZE) 107 pbuf->alloc_len = DAV_BUFFER_MINSIZE; 108 109 pbuf->buf = apr_palloc(p, pbuf->alloc_len); 110 } 111 pbuf->cur_len = size; 112} 113 114 115/* initialize a buffer and copy the specified (null-term'd) string into it */ 116DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf, 117 const char *str) 118{ 119 dav_set_bufsize(p, pbuf, strlen(str)); 120 memcpy(pbuf->buf, str, pbuf->cur_len + 1); 121} 122 123/* append a string to the end of the buffer, adjust length */ 124DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf, 125 const char *str) 126{ 127 apr_size_t len = strlen(str); 128 129 dav_check_bufsize(p, pbuf, len + 1); 130 memcpy(pbuf->buf + pbuf->cur_len, str, len + 1); 131 pbuf->cur_len += len; 132} 133 134/* place a string on the end of the buffer, do NOT adjust length */ 135DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf, 136 const char *str) 137{ 138 apr_size_t len = strlen(str); 139 140 dav_check_bufsize(p, pbuf, len + 1); 141 memcpy(pbuf->buf + pbuf->cur_len, str, len + 1); 142} 143 144/* place some memory on the end of a buffer; do NOT adjust length */ 145DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf, 146 const void *mem, apr_size_t amt, 147 apr_size_t pad) 148{ 149 dav_check_bufsize(p, pbuf, amt + pad); 150 memcpy(pbuf->buf + pbuf->cur_len, mem, amt); 151} 152 153/* 154** dav_lookup_uri() 155** 156** Extension for ap_sub_req_lookup_uri() which can't handle absolute 157** URIs properly. 158** 159** If NULL is returned, then an error occurred with parsing the URI or 160** the URI does not match the current server. 161*/ 162DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri, 163 request_rec * r, 164 int must_be_absolute) 165{ 166 dav_lookup_result result = { 0 }; 167 const char *scheme; 168 apr_port_t port; 169 apr_uri_t comp; 170 char *new_file; 171 const char *domain; 172 173 /* first thing to do is parse the URI into various components */ 174 if (apr_uri_parse(r->pool, uri, &comp) != APR_SUCCESS) { 175 result.err.status = HTTP_BAD_REQUEST; 176 result.err.desc = "Invalid syntax in Destination URI."; 177 return result; 178 } 179 180 /* the URI must be an absoluteURI (WEBDAV S9.3) */ 181 if (comp.scheme == NULL && must_be_absolute) { 182 result.err.status = HTTP_BAD_REQUEST; 183 result.err.desc = "Destination URI must be an absolute URI."; 184 return result; 185 } 186 187 /* the URI must not have a query (args) or a fragment */ 188 if (comp.query != NULL || comp.fragment != NULL) { 189 result.err.status = HTTP_BAD_REQUEST; 190 result.err.desc = 191 "Destination URI contains invalid components " 192 "(a query or a fragment)."; 193 return result; 194 } 195 196 /* If the scheme or port was provided, then make sure that it matches 197 the scheme/port of this request. If the request must be absolute, 198 then require the (explicit/implicit) scheme/port be matching. 199 200 ### hmm. if a port wasn't provided (does the parse return port==0?), 201 ### but we're on a non-standard port, then we won't detect that the 202 ### URI's port implies the wrong one. 203 */ 204 if (comp.scheme != NULL || comp.port != 0 || must_be_absolute) 205 { 206 /* ### not sure this works if the current request came in via https: */ 207 scheme = r->parsed_uri.scheme; 208 if (scheme == NULL) 209 scheme = ap_http_scheme(r); 210 211 /* insert a port if the URI did not contain one */ 212 if (comp.port == 0) 213 comp.port = apr_uri_port_of_scheme(comp.scheme); 214 215 /* now, verify that the URI uses the same scheme as the current. 216 request. the port must match our port. 217 */ 218 port = r->connection->local_addr->port; 219 if (strcasecmp(comp.scheme, scheme) != 0 220#ifdef APACHE_PORT_HANDLING_IS_BUSTED 221 || comp.port != port 222#endif 223 ) { 224 result.err.status = HTTP_BAD_GATEWAY; 225 result.err.desc = apr_psprintf(r->pool, 226 "Destination URI refers to " 227 "different scheme or port " 228 "(%s://hostname:%d)" APR_EOL_STR 229 "(want: %s://hostname:%d)", 230 comp.scheme ? comp.scheme : scheme, 231 comp.port ? comp.port : port, 232 scheme, port); 233 return result; 234 } 235 } 236 237 /* we have verified the scheme, port, and general structure */ 238 239 /* 240 ** Hrm. IE5 will pass unqualified hostnames for both the 241 ** Host: and Destination: headers. This breaks the 242 ** http_vhost.c::matches_aliases function. 243 ** 244 ** For now, qualify unqualified comp.hostnames with 245 ** r->server->server_hostname. 246 ** 247 ** ### this is a big hack. Apache should provide a better way. 248 ** ### maybe the admin should list the unqualified hosts in a 249 ** ### <ServerAlias> block? 250 */ 251 if (comp.hostname != NULL 252 && strrchr(comp.hostname, '.') == NULL 253 && (domain = strchr(r->server->server_hostname, '.')) != NULL) { 254 comp.hostname = apr_pstrcat(r->pool, comp.hostname, domain, NULL); 255 } 256 257 /* now, if a hostname was provided, then verify that it represents the 258 same server as the current connection. note that we just use our 259 port, since we've verified the URI matches ours */ 260#ifdef APACHE_PORT_HANDLING_IS_BUSTED 261 if (comp.hostname != NULL && 262 !ap_matches_request_vhost(r, comp.hostname, port)) { 263 result.err.status = HTTP_BAD_GATEWAY; 264 result.err.desc = "Destination URI refers to a different server."; 265 return result; 266 } 267#endif 268 269 /* we have verified that the requested URI denotes the same server as 270 the current request. Therefore, we can use ap_sub_req_lookup_uri() */ 271 272 /* reconstruct a URI as just the path */ 273 new_file = apr_uri_unparse(r->pool, &comp, APR_URI_UNP_OMITSITEPART); 274 275 /* 276 * Lookup the URI and return the sub-request. Note that we use the 277 * same HTTP method on the destination. This allows the destination 278 * to apply appropriate restrictions (e.g. readonly). 279 */ 280 result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL); 281 282 return result; 283} 284 285/* --------------------------------------------------------------- 286** 287** XML UTILITY FUNCTIONS 288*/ 289 290/* validate that the root element uses a given DAV: tagname (TRUE==valid) */ 291DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc, 292 const char *tagname) 293{ 294 return doc->root && 295 doc->root->ns == APR_XML_NS_DAV_ID && 296 strcmp(doc->root->name, tagname) == 0; 297} 298 299/* find and return the (unique) child with a given DAV: tagname */ 300DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem, 301 const char *tagname) 302{ 303 apr_xml_elem *child = elem->first_child; 304 305 for (; child; child = child->next) 306 if (child->ns == APR_XML_NS_DAV_ID && !strcmp(child->name, tagname)) 307 return child; 308 return NULL; 309} 310 311/* gather up all the CDATA into a single string */ 312DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool, 313 int strip_white) 314{ 315 apr_size_t len = 0; 316 apr_text *scan; 317 const apr_xml_elem *child; 318 char *cdata; 319 char *s; 320 apr_size_t tlen; 321 const char *found_text = NULL; /* initialize to avoid gcc warning */ 322 int found_count = 0; 323 324 for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) { 325 found_text = scan->text; 326 ++found_count; 327 len += strlen(found_text); 328 } 329 330 for (child = elem->first_child; child != NULL; child = child->next) { 331 for (scan = child->following_cdata.first; 332 scan != NULL; 333 scan = scan->next) { 334 found_text = scan->text; 335 ++found_count; 336 len += strlen(found_text); 337 } 338 } 339 340 /* some fast-path cases: 341 * 1) zero-length cdata 342 * 2) a single piece of cdata with no whitespace to strip 343 */ 344 if (len == 0) 345 return ""; 346 if (found_count == 1) { 347 if (!strip_white 348 || (!apr_isspace(*found_text) 349 && !apr_isspace(found_text[len - 1]))) 350 return found_text; 351 } 352 353 cdata = s = apr_palloc(pool, len + 1); 354 355 for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) { 356 tlen = strlen(scan->text); 357 memcpy(s, scan->text, tlen); 358 s += tlen; 359 } 360 361 for (child = elem->first_child; child != NULL; child = child->next) { 362 for (scan = child->following_cdata.first; 363 scan != NULL; 364 scan = scan->next) { 365 tlen = strlen(scan->text); 366 memcpy(s, scan->text, tlen); 367 s += tlen; 368 } 369 } 370 371 *s = '\0'; 372 373 if (strip_white) { 374 /* trim leading whitespace */ 375 while (apr_isspace(*cdata)) /* assume: return false for '\0' */ 376 ++cdata; 377 378 /* trim trailing whitespace */ 379 while (len-- > 0 && apr_isspace(cdata[len])) 380 continue; 381 cdata[len + 1] = '\0'; 382 } 383 384 return cdata; 385} 386 387DAV_DECLARE(dav_xmlns_info *) dav_xmlns_create(apr_pool_t *pool) 388{ 389 dav_xmlns_info *xi = apr_pcalloc(pool, sizeof(*xi)); 390 391 xi->pool = pool; 392 xi->uri_prefix = apr_hash_make(pool); 393 xi->prefix_uri = apr_hash_make(pool); 394 395 return xi; 396} 397 398DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi, 399 const char *prefix, const char *uri) 400{ 401 /* this "should" not overwrite a prefix mapping */ 402 apr_hash_set(xi->prefix_uri, prefix, APR_HASH_KEY_STRING, uri); 403 404 /* note: this may overwrite an existing URI->prefix mapping, but it 405 doesn't matter -- any prefix is usuable to specify the URI. */ 406 apr_hash_set(xi->uri_prefix, uri, APR_HASH_KEY_STRING, prefix); 407} 408 409DAV_DECLARE(const char *) dav_xmlns_add_uri(dav_xmlns_info *xi, 410 const char *uri) 411{ 412 const char *prefix; 413 414 if ((prefix = apr_hash_get(xi->uri_prefix, uri, 415 APR_HASH_KEY_STRING)) != NULL) 416 return prefix; 417 418 prefix = apr_psprintf(xi->pool, "g%d", xi->count++); 419 dav_xmlns_add(xi, prefix, uri); 420 return prefix; 421} 422 423DAV_DECLARE(const char *) dav_xmlns_get_uri(dav_xmlns_info *xi, 424 const char *prefix) 425{ 426 return apr_hash_get(xi->prefix_uri, prefix, APR_HASH_KEY_STRING); 427} 428 429DAV_DECLARE(const char *) dav_xmlns_get_prefix(dav_xmlns_info *xi, 430 const char *uri) 431{ 432 return apr_hash_get(xi->uri_prefix, uri, APR_HASH_KEY_STRING); 433} 434 435DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi, 436 apr_text_header *phdr) 437{ 438 apr_hash_index_t *hi = apr_hash_first(xi->pool, xi->prefix_uri); 439 440 for (; hi != NULL; hi = apr_hash_next(hi)) { 441 const void *prefix; 442 void *uri; 443 const char *s; 444 445 apr_hash_this(hi, &prefix, NULL, &uri); 446 447 s = apr_psprintf(xi->pool, " xmlns:%s=\"%s\"", 448 (const char *)prefix, (const char *)uri); 449 apr_text_append(xi->pool, phdr, s); 450 } 451} 452 453/* --------------------------------------------------------------- 454** 455** Timeout header processing 456** 457*/ 458 459/* dav_get_timeout: If the Timeout: header exists, return a time_t 460 * when this lock is expected to expire. Otherwise, return 461 * a time_t of DAV_TIMEOUT_INFINITE. 462 * 463 * It's unclear if DAV clients are required to understand 464 * Seconds-xxx and Infinity time values. We assume that they do. 465 * In addition, for now, that's all we understand, too. 466 */ 467DAV_DECLARE(time_t) dav_get_timeout(request_rec *r) 468{ 469 time_t now, expires = DAV_TIMEOUT_INFINITE; 470 471 const char *timeout_const = apr_table_get(r->headers_in, "Timeout"); 472 const char *timeout = apr_pstrdup(r->pool, timeout_const), *val; 473 474 if (timeout == NULL) 475 return DAV_TIMEOUT_INFINITE; 476 477 /* Use the first thing we understand, or infinity if 478 * we don't understand anything. 479 */ 480 481 while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) { 482 if (!strncmp(val, "Infinite", 8)) { 483 return DAV_TIMEOUT_INFINITE; 484 } 485 486 if (!strncmp(val, "Second-", 7)) { 487 val += 7; 488 /* ### We need to handle overflow better: 489 * ### timeout will be <= 2^32 - 1 490 */ 491 expires = atol(val); 492 now = time(NULL); 493 return now + expires; 494 } 495 } 496 497 return DAV_TIMEOUT_INFINITE; 498} 499 500/* --------------------------------------------------------------- 501** 502** If Header processing 503** 504*/ 505 506/* add_if_resource returns a new if_header, linking it to next_ih. 507 */ 508static dav_if_header *dav_add_if_resource(apr_pool_t *p, dav_if_header *next_ih, 509 const char *uri, apr_size_t uri_len) 510{ 511 dav_if_header *ih; 512 513 if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL) 514 return NULL; 515 516 ih->uri = uri; 517 ih->uri_len = uri_len; 518 ih->next = next_ih; 519 520 return ih; 521} 522 523/* add_if_state adds a condition to an if_header. 524 */ 525static dav_error * dav_add_if_state(apr_pool_t *p, dav_if_header *ih, 526 const char *state_token, 527 dav_if_state_type t, int condition, 528 const dav_hooks_locks *locks_hooks) 529{ 530 dav_if_state_list *new_sl; 531 532 new_sl = apr_pcalloc(p, sizeof(*new_sl)); 533 534 new_sl->condition = condition; 535 new_sl->type = t; 536 537 if (t == dav_if_opaquelock) { 538 dav_error *err; 539 540 if ((err = (*locks_hooks->parse_locktoken)(p, state_token, 541 &new_sl->locktoken)) != NULL) { 542 /* If the state token cannot be parsed, treat it as an 543 * unknown state; this will evaluate to "false" later 544 * during If header validation. */ 545 if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) { 546 new_sl->type = dav_if_unknown; 547 } 548 else { 549 /* ### maybe add a higher-level description */ 550 return err; 551 } 552 } 553 } 554 else 555 new_sl->etag = state_token; 556 557 new_sl->next = ih->state; 558 ih->state = new_sl; 559 560 return NULL; 561} 562 563/* fetch_next_token returns the substring from str+1 564 * to the next occurence of char term, or \0, whichever 565 * occurs first. Leading whitespace is ignored. 566 */ 567static char *dav_fetch_next_token(char **str, char term) 568{ 569 char *sp; 570 char *token; 571 572 token = *str + 1; 573 574 while (*token && (*token == ' ' || *token == '\t')) 575 token++; 576 577 if ((sp = strchr(token, term)) == NULL) 578 return NULL; 579 580 *sp = '\0'; 581 *str = sp; 582 return token; 583} 584 585/* dav_process_if_header: 586 * 587 * If NULL (no error) is returned, then **if_header points to the 588 * "If" productions structure (or NULL if "If" is not present). 589 * 590 * ### this part is bogus: 591 * If an error is encountered, the error is logged. Parent should 592 * return err->status. 593 */ 594static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih) 595{ 596 dav_error *err; 597 char *str; 598 char *list; 599 const char *state_token; 600 const char *uri = NULL; /* scope of current production; NULL=no-tag */ 601 apr_size_t uri_len = 0; 602 dav_if_header *ih = NULL; 603 apr_uri_t parsed_uri; 604 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); 605 enum {no_tagged, tagged, unknown} list_type = unknown; 606 int condition; 607 608 *p_ih = NULL; 609 610 if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL) 611 return NULL; 612 613 while (*str) { 614 switch(*str) { 615 case '<': 616 /* Tagged-list production - following states apply to this uri */ 617 if (list_type == no_tagged 618 || ((uri = dav_fetch_next_token(&str, '>')) == NULL)) { 619 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 620 DAV_ERR_IF_TAGGED, 621 "Invalid If-header: unclosed \"<\" or " 622 "unexpected tagged-list production."); 623 } 624 625 /* 2518 specifies this must be an absolute URI; just take the 626 * relative part for later comparison against r->uri */ 627 if (apr_uri_parse(r->pool, uri, &parsed_uri) != APR_SUCCESS 628 || !parsed_uri.path) { 629 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 630 DAV_ERR_IF_TAGGED, 631 "Invalid URI in tagged If-header."); 632 } 633 /* note that parsed_uri.path is allocated; we can trash it */ 634 635 /* clean up the URI a bit */ 636 ap_getparents(parsed_uri.path); 637 638 /* the resources we will compare to have unencoded paths */ 639 if (ap_unescape_url(parsed_uri.path) != OK) { 640 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 641 DAV_ERR_IF_TAGGED, 642 "Invalid percent encoded URI in " 643 "tagged If-header."); 644 } 645 646 uri_len = strlen(parsed_uri.path); 647 if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/') { 648 parsed_uri.path[--uri_len] = '\0'; 649 } 650 651 uri = parsed_uri.path; 652 list_type = tagged; 653 break; 654 655 case '(': 656 /* List production */ 657 658 /* If a uri has not been encountered, this is a No-Tagged-List */ 659 if (list_type == unknown) 660 list_type = no_tagged; 661 662 if ((list = dav_fetch_next_token(&str, ')')) == NULL) { 663 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 664 DAV_ERR_IF_UNCLOSED_PAREN, 665 "Invalid If-header: unclosed \"(\"."); 666 } 667 668 if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) { 669 /* ### dav_add_if_resource() should return an error for us! */ 670 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 671 DAV_ERR_IF_PARSE, 672 "Internal server error parsing \"If:\" " 673 "header."); 674 } 675 676 condition = DAV_IF_COND_NORMAL; 677 678 while (*list) { 679 /* List is the entire production (in a uri scope) */ 680 681 switch (*list) { 682 case '<': 683 if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) { 684 /* ### add a description to this error */ 685 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 686 DAV_ERR_IF_PARSE, NULL); 687 } 688 689 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock, 690 condition, locks_hooks)) != NULL) { 691 /* ### maybe add a higher level description */ 692 return err; 693 } 694 condition = DAV_IF_COND_NORMAL; 695 break; 696 697 case '[': 698 if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) { 699 /* ### add a description to this error */ 700 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 701 DAV_ERR_IF_PARSE, NULL); 702 } 703 704 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag, 705 condition, locks_hooks)) != NULL) { 706 /* ### maybe add a higher level description */ 707 return err; 708 } 709 condition = DAV_IF_COND_NORMAL; 710 break; 711 712 case 'N': 713 if (list[1] == 'o' && list[2] == 't') { 714 if (condition != DAV_IF_COND_NORMAL) { 715 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 716 DAV_ERR_IF_MULTIPLE_NOT, 717 "Invalid \"If:\" header: " 718 "Multiple \"not\" entries " 719 "for the same state."); 720 } 721 condition = DAV_IF_COND_NOT; 722 } 723 list += 2; 724 break; 725 726 case ' ': 727 case '\t': 728 break; 729 730 default: 731 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 732 DAV_ERR_IF_UNK_CHAR, 733 apr_psprintf(r->pool, 734 "Invalid \"If:\" " 735 "header: Unexpected " 736 "character encountered " 737 "(0x%02x, '%c').", 738 *list, *list)); 739 } 740 741 list++; 742 } 743 break; 744 745 case ' ': 746 case '\t': 747 break; 748 749 default: 750 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 751 DAV_ERR_IF_UNK_CHAR, 752 apr_psprintf(r->pool, 753 "Invalid \"If:\" header: " 754 "Unexpected character " 755 "encountered (0x%02x, '%c').", 756 *str, *str)); 757 } 758 759 str++; 760 } 761 762 *p_ih = ih; 763 return NULL; 764} 765 766static int dav_find_submitted_locktoken(const dav_if_header *if_header, 767 const dav_lock *lock_list, 768 const dav_hooks_locks *locks_hooks) 769{ 770 for (; if_header != NULL; if_header = if_header->next) { 771 const dav_if_state_list *state_list; 772 773 for (state_list = if_header->state; 774 state_list != NULL; 775 state_list = state_list->next) { 776 777 if (state_list->type == dav_if_opaquelock) { 778 const dav_lock *lock; 779 780 /* given state_list->locktoken, match it */ 781 782 /* 783 ** The resource will have one or more lock tokens. We only 784 ** need to match one of them against any token in the 785 ** If: header. 786 ** 787 ** One token case: It is an exclusive or shared lock. Either 788 ** way, we must find it. 789 ** 790 ** N token case: They are shared locks. By policy, we need 791 ** to match only one. The resource's other 792 ** tokens may belong to somebody else (so we 793 ** shouldn't see them in the If: header anyway) 794 */ 795 for (lock = lock_list; lock != NULL; lock = lock->next) { 796 797 if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) { 798 return 1; 799 } 800 } 801 } 802 } 803 } 804 805 return 0; 806} 807 808/* dav_validate_resource_state: 809 * Returns NULL if path/uri meets if-header and lock requirements 810 */ 811static dav_error * dav_validate_resource_state(apr_pool_t *p, 812 const dav_resource *resource, 813 dav_lockdb *lockdb, 814 const dav_if_header *if_header, 815 int flags, 816 dav_buffer *pbuf, 817 request_rec *r) 818{ 819 dav_error *err; 820 const char *uri; 821 const char *etag; 822 const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL); 823 const dav_if_header *ifhdr_scan; 824 dav_if_state_list *state_list; 825 dav_lock *lock_list; 826 dav_lock *lock; 827 int num_matched; 828 int num_that_apply; 829 int seen_locktoken; 830 apr_size_t uri_len; 831 const char *reason = NULL; 832 833 /* DBG1("validate: <%s>", resource->uri); */ 834 835 /* 836 ** The resource will have one of three states: 837 ** 838 ** 1) No locks. We have no special requirements that the user supply 839 ** specific locktokens. One of the state lists must match, and 840 ** we're done. 841 ** 842 ** 2) One exclusive lock. The locktoken must appear *anywhere* in the 843 ** If: header. Of course, asserting the token in a "Not" term will 844 ** quickly fail that state list :-). If the locktoken appears in 845 ** one of the state lists *and* one state list matches, then we're 846 ** done. 847 ** 848 ** 3) One or more shared locks. One of the locktokens must appear 849 ** *anywhere* in the If: header. If one of the locktokens appears, 850 ** and we match one state list, then we are done. 851 ** 852 ** The <seen_locktoken> variable determines whether we have seen one 853 ** of this resource's locktokens in the If: header. 854 */ 855 856 /* 857 ** If this is a new lock request, <flags> will contain the requested 858 ** lock scope. Three rules apply: 859 ** 860 ** 1) Do not require a (shared) locktoken to be seen (when we are 861 ** applying another shared lock) 862 ** 2) If the scope is exclusive and we see any locks, fail. 863 ** 3) If the scope is shared and we see an exclusive lock, fail. 864 */ 865 866 if (lockdb == NULL) { 867 /* we're in State 1. no locks. */ 868 lock_list = NULL; 869 } 870 else { 871 /* 872 ** ### hrm... we don't need to have these fully 873 ** ### resolved since we're only looking at the 874 ** ### locktokens... 875 ** 876 ** ### use get_locks w/ calltype=PARTIAL 877 */ 878 if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) { 879 return dav_push_error(p, 880 HTTP_INTERNAL_SERVER_ERROR, 0, 881 "The locks could not be queried for " 882 "verification against a possible \"If:\" " 883 "header.", 884 err); 885 } 886 887 /* lock_list now determines whether we're in State 1, 2, or 3. */ 888 } 889 890 /* 891 ** For a new, exclusive lock: if any locks exist, fail. 892 ** For a new, shared lock: if an exclusive lock exists, fail. 893 ** else, do not require a token to be seen. 894 */ 895 if (flags & DAV_LOCKSCOPE_EXCLUSIVE) { 896 if (lock_list != NULL) { 897 return dav_new_error(p, HTTP_LOCKED, 0, 898 "Existing lock(s) on the requested resource " 899 "prevent an exclusive lock."); 900 } 901 902 /* 903 ** There are no locks, so we can pretend that we've already met 904 ** any requirement to find the resource's locks in an If: header. 905 */ 906 seen_locktoken = 1; 907 } 908 else if (flags & DAV_LOCKSCOPE_SHARED) { 909 /* 910 ** Strictly speaking, we don't need this loop. Either the first 911 ** (and only) lock will be EXCLUSIVE, or none of them will be. 912 */ 913 for (lock = lock_list; lock != NULL; lock = lock->next) { 914 if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) { 915 return dav_new_error(p, HTTP_LOCKED, 0, 916 "The requested resource is already " 917 "locked exclusively."); 918 } 919 } 920 921 /* 922 ** The locks on the resource (if any) are all shared. Set the 923 ** <seen_locktoken> flag to indicate that we do not need to find 924 ** the locks in an If: header. 925 */ 926 seen_locktoken = 1; 927 } 928 else { 929 /* 930 ** For methods other than LOCK: 931 ** 932 ** If we have no locks or if the resource is not being modified 933 ** (per RFC 4918 the lock token is not required on resources 934 ** we are not changing), then <seen_locktoken> can be set to true -- 935 ** pretending that we've already met the requirement of seeing one 936 ** of the resource's locks in the If: header. 937 ** 938 ** Otherwise, it must be cleared and we'll look for one. 939 */ 940 seen_locktoken = (lock_list == NULL 941 || flags & DAV_VALIDATE_NO_MODIFY); 942 } 943 944 /* 945 ** If there is no If: header, then we can shortcut some logic: 946 ** 947 ** 1) if we do not need to find a locktoken in the (non-existent) If: 948 ** header, then we are successful. 949 ** 950 ** 2) if we must find a locktoken in the (non-existent) If: header, then 951 ** we fail. 952 */ 953 if (if_header == NULL) { 954 if (seen_locktoken) 955 return NULL; 956 957 return dav_new_error(p, HTTP_LOCKED, 0, 958 "This resource is locked and an \"If:\" header " 959 "was not supplied to allow access to the " 960 "resource."); 961 } 962 /* the If: header is present */ 963 964 /* 965 ** If a dummy header is present (because of a Lock-Token: header), then 966 ** we are required to find that token in this resource's set of locks. 967 ** If we have no locks, then we immediately fail. 968 ** 969 ** This is a 400 (Bad Request) since they should only submit a locktoken 970 ** that actually exists. 971 ** 972 ** Don't issue this response if we're talking about the parent resource. 973 ** It is okay for that resource to NOT have this locktoken. 974 ** (in fact, it certainly will not: a dummy_header only occurs for the 975 ** UNLOCK method, the parent is checked only for locknull resources, 976 ** and the parent certainly does not have the (locknull's) locktoken) 977 */ 978 if (lock_list == NULL && if_header->dummy_header) { 979 if (flags & DAV_VALIDATE_IS_PARENT) 980 return NULL; 981 return dav_new_error(p, HTTP_BAD_REQUEST, 0, 982 "The locktoken specified in the \"Lock-Token:\" " 983 "header is invalid because this resource has no " 984 "outstanding locks."); 985 } 986 987 /* 988 ** Prepare the input URI. We want the URI to never have a trailing slash. 989 ** 990 ** When URIs are placed into the dav_if_header structure, they are 991 ** guaranteed to never have a trailing slash. If the URIs are equivalent, 992 ** then it doesn't matter if they both lack a trailing slash -- they're 993 ** still equivalent. 994 ** 995 ** Note: we could also ensure that a trailing slash is present on both 996 ** URIs, but the majority of URIs provided to us via a resource walk 997 ** will not contain that trailing slash. 998 */ 999 uri = resource->uri; 1000 uri_len = strlen(uri); 1001 if (uri[uri_len - 1] == '/') { 1002 dav_set_bufsize(p, pbuf, uri_len); 1003 memcpy(pbuf->buf, uri, uri_len); 1004 pbuf->buf[--uri_len] = '\0'; 1005 uri = pbuf->buf; 1006 } 1007 1008 /* get the resource's etag; we may need it during the checks */ 1009 etag = (*resource->hooks->getetag)(resource); 1010 1011 /* how many state_lists apply to this URI? */ 1012 num_that_apply = 0; 1013 1014 /* If there are if-headers, fail if this resource 1015 * does not match at least one state_list. 1016 */ 1017 for (ifhdr_scan = if_header; 1018 ifhdr_scan != NULL; 1019 ifhdr_scan = ifhdr_scan->next) { 1020 1021 /* DBG2("uri=<%s> if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */ 1022 1023 if (ifhdr_scan->uri != NULL 1024 && (uri_len != ifhdr_scan->uri_len 1025 || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) { 1026 /* 1027 ** A tagged-list's URI doesn't match this resource's URI. 1028 ** Skip to the next state_list to see if it will match. 1029 */ 1030 continue; 1031 } 1032 1033 /* this state_list applies to this resource */ 1034 1035 /* 1036 ** ### only one state_list should ever apply! a no-tag, or a tagged 1037 ** ### where S9.4.2 states only one can match. 1038 ** 1039 ** ### revamp this code to loop thru ifhdr_scan until we find the 1040 ** ### matching state_list. process it. stop. 1041 */ 1042 ++num_that_apply; 1043 1044 /* To succeed, resource must match *all* of the states 1045 * specified in the state_list. 1046 */ 1047 for (state_list = ifhdr_scan->state; 1048 state_list != NULL; 1049 state_list = state_list->next) { 1050 1051 switch(state_list->type) { 1052 case dav_if_etag: 1053 { 1054 const char *given_etag, *current_etag; 1055 int mismatch; 1056 1057 /* Do a weak entity comparison function as defined in 1058 * RFC 2616 13.3.3. 1059 */ 1060 if (state_list->etag[0] == 'W' && 1061 state_list->etag[1] == '/') { 1062 given_etag = state_list->etag + 2; 1063 } 1064 else { 1065 given_etag = state_list->etag; 1066 } 1067 if (etag[0] == 'W' && 1068 etag[1] == '/') { 1069 current_etag = etag + 2; 1070 } 1071 else { 1072 current_etag = etag; 1073 } 1074 1075 mismatch = strcmp(given_etag, current_etag); 1076 1077 if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) { 1078 /* 1079 ** The specified entity-tag does not match the 1080 ** entity-tag on the resource. This state_list is 1081 ** not going to match. Bust outta here. 1082 */ 1083 reason = 1084 "an entity-tag was specified, but the resource's " 1085 "actual ETag does not match."; 1086 goto state_list_failed; 1087 } 1088 else if (state_list->condition == DAV_IF_COND_NOT 1089 && !mismatch) { 1090 /* 1091 ** The specified entity-tag DOES match the 1092 ** entity-tag on the resource. This state_list is 1093 ** not going to match. Bust outta here. 1094 */ 1095 reason = 1096 "an entity-tag was specified using the \"Not\" form, " 1097 "but the resource's actual ETag matches the provided " 1098 "entity-tag."; 1099 goto state_list_failed; 1100 } 1101 break; 1102 } 1103 1104 case dav_if_opaquelock: 1105 if (lockdb == NULL) { 1106 if (state_list->condition == DAV_IF_COND_NOT) { 1107 /* the locktoken is definitely not there! (success) */ 1108 continue; 1109 } 1110 1111 /* condition == DAV_IF_COND_NORMAL */ 1112 1113 /* 1114 ** If no lockdb is provided, then validation fails for 1115 ** this state_list (NORMAL means we were supposed to 1116 ** find the token, which we obviously cannot do without 1117 ** a lock database). 1118 ** 1119 ** Go and try the next state list. 1120 */ 1121 reason = 1122 "a State-token was supplied, but a lock database " 1123 "is not available for to provide the required lock."; 1124 goto state_list_failed; 1125 } 1126 1127 /* Resource validation 'fails' if: 1128 * ANY of the lock->locktokens match 1129 * a NOT state_list->locktoken, 1130 * OR 1131 * NONE of the lock->locktokens match 1132 * a NORMAL state_list->locktoken. 1133 */ 1134 num_matched = 0; 1135 for (lock = lock_list; lock != NULL; lock = lock->next) { 1136 1137 /* 1138 DBG2("compare: rsrc=%s ifhdr=%s", 1139 (*locks_hooks->format_locktoken)(p, lock->locktoken), 1140 (*locks_hooks->format_locktoken)(p, state_list->locktoken)); 1141 */ 1142 1143 /* nothing to do if the locktokens do not match. */ 1144 if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) { 1145 continue; 1146 } 1147 1148 /* 1149 ** We have now matched up one of the resource's locktokens 1150 ** to a locktoken in a State-token in the If: header. 1151 ** Note this fact, so that we can pass the overall 1152 ** requirement of seeing at least one of the resource's 1153 ** locktokens. 1154 */ 1155 seen_locktoken = 1; 1156 1157 if (state_list->condition == DAV_IF_COND_NOT) { 1158 /* 1159 ** This state requires that the specified locktoken 1160 ** is NOT present on the resource. But we just found 1161 ** it. There is no way this state-list can now 1162 ** succeed, so go try another one. 1163 */ 1164 reason = 1165 "a State-token was supplied, which used a " 1166 "\"Not\" condition. The State-token was found " 1167 "in the locks on this resource"; 1168 goto state_list_failed; 1169 } 1170 1171 /* condition == DAV_IF_COND_NORMAL */ 1172 1173 /* Validate auth_user: If an authenticated user created 1174 ** the lock, only the same user may submit that locktoken 1175 ** to manipulate a resource. 1176 */ 1177 if (lock->auth_user && 1178 (!r->user || 1179 strcmp(lock->auth_user, r->user))) { 1180 const char *errmsg; 1181 1182 errmsg = apr_pstrcat(p, "User \"", 1183 r->user, 1184 "\" submitted a locktoken created " 1185 "by user \"", 1186 lock->auth_user, "\".", NULL); 1187 return dav_new_error(p, HTTP_FORBIDDEN, 0, errmsg); 1188 } 1189 1190 /* 1191 ** We just matched a specified State-Token to one of the 1192 ** resource's locktokens. 1193 ** 1194 ** Break out of the lock scan -- we only needed to find 1195 ** one match (actually, there shouldn't be any other 1196 ** matches in the lock list). 1197 */ 1198 num_matched = 1; 1199 break; 1200 } 1201 1202 if (num_matched == 0 1203 && state_list->condition == DAV_IF_COND_NORMAL) { 1204 /* 1205 ** We had a NORMAL state, meaning that we should have 1206 ** found the State-Token within the locks on this 1207 ** resource. We didn't, so this state_list must fail. 1208 */ 1209 reason = 1210 "a State-token was supplied, but it was not found " 1211 "in the locks on this resource."; 1212 goto state_list_failed; 1213 } 1214 1215 break; 1216 1217 case dav_if_unknown: 1218 /* Request is predicated on some unknown state token, 1219 * which must be presumed to *not* match, so fail 1220 * unless this is a Not condition. */ 1221 1222 if (state_list->condition == DAV_IF_COND_NORMAL) { 1223 reason = 1224 "an unknown state token was supplied"; 1225 goto state_list_failed; 1226 } 1227 break; 1228 1229 } /* switch */ 1230 } /* foreach ( state_list ) */ 1231 1232 /* 1233 ** We've checked every state in this state_list and none of them 1234 ** have failed. Since all of them succeeded, then we have a matching 1235 ** state list and we may be done. 1236 ** 1237 ** The next requirement is that we have seen one of the resource's 1238 ** locktokens (if any). If we have, then we can just exit. If we 1239 ** haven't, then we need to keep looking. 1240 */ 1241 if (seen_locktoken) { 1242 /* woo hoo! */ 1243 return NULL; 1244 } 1245 1246 /* 1247 ** Haven't seen one. Let's break out of the search and just look 1248 ** for a matching locktoken. 1249 */ 1250 break; 1251 1252 /* 1253 ** This label is used when we detect that a state_list is not 1254 ** going to match this resource. We bust out and try the next 1255 ** state_list. 1256 */ 1257 state_list_failed: 1258 ; 1259 1260 } /* foreach ( ifhdr_scan ) */ 1261 1262 /* 1263 ** The above loop exits for one of two reasons: 1264 ** 1) a state_list matched and seen_locktoken is false. 1265 ** 2) all if_header structures were scanned, without (1) occurring 1266 */ 1267 1268 if (ifhdr_scan == NULL) { 1269 /* 1270 ** We finished the loop without finding any matching state lists. 1271 */ 1272 1273 /* 1274 ** If none of the state_lists apply to this resource, then we 1275 ** may have succeeded. Note that this scenario implies a 1276 ** tagged-list with no matching state_lists. If the If: header 1277 ** was a no-tag-list, then it would have applied to this resource. 1278 ** 1279 ** S9.4.2 states that when no state_lists apply, then the header 1280 ** should be ignored. 1281 ** 1282 ** If we saw one of the resource's locktokens, then we're done. 1283 ** If we did not see a locktoken, then we fail. 1284 */ 1285 if (num_that_apply == 0) { 1286 if (seen_locktoken) 1287 return NULL; 1288 1289 /* 1290 ** We may have aborted the scan before seeing the locktoken. 1291 ** Rescan the If: header to see if we can find the locktoken 1292 ** somewhere. 1293 ** 1294 ** Note that seen_locktoken == 0 implies lock_list != NULL 1295 ** which implies locks_hooks != NULL. 1296 */ 1297 if (dav_find_submitted_locktoken(if_header, lock_list, 1298 locks_hooks)) { 1299 /* 1300 ** We found a match! We're set... none of the If: header 1301 ** assertions apply (implicit success), and the If: header 1302 ** specified the locktoken somewhere. We're done. 1303 */ 1304 return NULL; 1305 } 1306 1307 return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */, 1308 "This resource is locked and the \"If:\" " 1309 "header did not specify one of the " 1310 "locktokens for this resource's lock(s)."); 1311 } 1312 /* else: one or more state_lists were applicable, but failed. */ 1313 1314 /* 1315 ** If the dummy_header did not match, then they specified an 1316 ** incorrect token in the Lock-Token header. Forget whether the 1317 ** If: statement matched or not... we'll tell them about the 1318 ** bad Lock-Token first. That is considered a 400 (Bad Request). 1319 */ 1320 if (if_header->dummy_header) { 1321 return dav_new_error(p, HTTP_BAD_REQUEST, 0, 1322 "The locktoken specified in the " 1323 "\"Lock-Token:\" header did not specify one " 1324 "of this resource's locktoken(s)."); 1325 } 1326 1327 if (reason == NULL) { 1328 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 1329 "The preconditions specified by the \"If:\" " 1330 "header did not match this resource."); 1331 } 1332 1333 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 1334 apr_psprintf(p, 1335 "The precondition(s) specified by " 1336 "the \"If:\" header did not match " 1337 "this resource. At least one " 1338 "failure is because: %s", reason)); 1339 } 1340 1341 /* assert seen_locktoken == 0 */ 1342 1343 /* 1344 ** ifhdr_scan != NULL implies we found a matching state_list. 1345 ** 1346 ** Since we're still here, it also means that we have not yet found 1347 ** one the resource's locktokens in the If: header. 1348 ** 1349 ** Scan all the if_headers and states looking for one of this 1350 ** resource's locktokens. Note that we need to go back and scan them 1351 ** all -- we may have aborted a scan with a failure before we saw a 1352 ** matching token. 1353 ** 1354 ** Note that seen_locktoken == 0 implies lock_list != NULL which implies 1355 ** locks_hooks != NULL. 1356 */ 1357 if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) { 1358 /* 1359 ** We found a match! We're set... we have a matching state list, 1360 ** and the If: header specified the locktoken somewhere. We're done. 1361 */ 1362 return NULL; 1363 } 1364 1365 /* 1366 ** We had a matching state list, but the user agent did not specify one 1367 ** of this resource's locktokens. Tell them so. 1368 ** 1369 ** Note that we need to special-case the message on whether a "dummy" 1370 ** header exists. If it exists, yet we didn't see a needed locktoken, 1371 ** then that implies the dummy header (Lock-Token header) did NOT 1372 ** specify one of this resource's locktokens. (this implies something 1373 ** in the real If: header matched) 1374 ** 1375 ** We want to note the 400 (Bad Request) in favor of a 423 (Locked). 1376 */ 1377 if (if_header->dummy_header) { 1378 return dav_new_error(p, HTTP_BAD_REQUEST, 0, 1379 "The locktoken specified in the " 1380 "\"Lock-Token:\" header did not specify one " 1381 "of this resource's locktoken(s)."); 1382 } 1383 1384 return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */, 1385 "This resource is locked and the \"If:\" header " 1386 "did not specify one of the " 1387 "locktokens for this resource's lock(s)."); 1388} 1389 1390/* dav_validate_walker: Walker callback function to validate resource state */ 1391static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype) 1392{ 1393 dav_walker_ctx *ctx = wres->walk_ctx; 1394 dav_error *err; 1395 1396 if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource, 1397 ctx->w.lockdb, 1398 ctx->if_header, ctx->flags, 1399 &ctx->work_buf, ctx->r)) == NULL) { 1400 /* There was no error, so just bug out. */ 1401 return NULL; 1402 } 1403 1404 /* 1405 ** If we have a serious server error, or if the request itself failed, 1406 ** then just return error (not a multistatus). 1407 */ 1408 if (ap_is_HTTP_SERVER_ERROR(err->status) 1409 || (*wres->resource->hooks->is_same_resource)(wres->resource, 1410 ctx->w.root)) { 1411 /* ### maybe push a higher-level description? */ 1412 return err; 1413 } 1414 1415 /* associate the error with the current URI */ 1416 dav_add_response(wres, err->status, NULL); 1417 1418 return NULL; 1419} 1420 1421/* If-* header checking */ 1422static int dav_meets_conditions(request_rec *r, int resource_state) 1423{ 1424 const char *if_match, *if_none_match; 1425 int retVal; 1426 1427 /* If-Match '*' fix. Resource existence not checked by ap_meets_conditions. 1428 * If-Match '*' request should succeed only if the resource exists. */ 1429 if ((if_match = apr_table_get(r->headers_in, "If-Match")) != NULL) { 1430 if (if_match[0] == '*' && resource_state != DAV_RESOURCE_EXISTS) 1431 return HTTP_PRECONDITION_FAILED; 1432 } 1433 1434 retVal = ap_meets_conditions(r); 1435 1436 /* If-None-Match '*' fix. If-None-Match '*' request should succeed 1437 * if the resource does not exist. */ 1438 if (retVal == HTTP_PRECONDITION_FAILED) { 1439 /* Note. If if_none_match != NULL, if_none_match is the culprit. 1440 * Since, in presence of If-None-Match, 1441 * other If-* headers are undefined. */ 1442 if ((if_none_match = 1443 apr_table_get(r->headers_in, "If-None-Match")) != NULL) { 1444 if (if_none_match[0] == '*' 1445 && resource_state != DAV_RESOURCE_EXISTS) { 1446 return OK; 1447 } 1448 } 1449 } 1450 1451 return retVal; 1452} 1453 1454/* 1455** dav_validate_request: Validate if-headers (and check for locks) on: 1456** (1) r->filename @ depth; 1457** (2) Parent of r->filename if check_parent == 1 1458** 1459** The check of parent should be done when it is necessary to verify that 1460** the parent collection will accept a new member (ie current resource 1461** state is null). 1462** 1463** Return OK on successful validation. 1464** On error, return appropriate HTTP_* code, and log error. If a multi-stat 1465** error is necessary, response will point to it, else NULL. 1466*/ 1467DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r, 1468 dav_resource *resource, 1469 int depth, 1470 dav_locktoken *locktoken, 1471 dav_response **response, 1472 int flags, 1473 dav_lockdb *lockdb) 1474{ 1475 dav_error *err; 1476 int result; 1477 dav_if_header *if_header; 1478 int lock_db_opened_locally = 0; 1479 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); 1480 const dav_hooks_repository *repos_hooks = resource->hooks; 1481 dav_buffer work_buf = { 0 }; 1482 dav_response *new_response; 1483 int resource_state; 1484 const char *etag; 1485 int set_etag = 0; 1486 1487#if DAV_DEBUG 1488 if (depth && response == NULL) { 1489 /* 1490 ** ### bleck. we can't return errors for other URIs unless we have 1491 ** ### a "response" ptr. 1492 */ 1493 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 1494 "DESIGN ERROR: dav_validate_request called " 1495 "with depth>0, but no response ptr."); 1496 } 1497#endif 1498 1499 if (response != NULL) 1500 *response = NULL; 1501 1502 /* Set the ETag header required by dav_meets_conditions() */ 1503 etag = apr_table_get(r->headers_out, "ETag"); 1504 if (!etag) { 1505 etag = (*resource->hooks->getetag)(resource); 1506 if (etag && *etag) { 1507 apr_table_set(r->headers_out, "ETag", etag); 1508 set_etag = 1; 1509 } 1510 } 1511 /* Do the standard checks for conditional requests using 1512 * If-..-Since, If-Match etc */ 1513 resource_state = dav_get_resource_state(r, resource); 1514 result = dav_meets_conditions(r, resource_state); 1515 if (set_etag) { 1516 /* 1517 * If we have set an ETag to headers out above for 1518 * dav_meets_conditions() revert this here as we do not want to set 1519 * the ETag in responses to requests with methods where this might not 1520 * be desired. 1521 */ 1522 apr_table_unset(r->headers_out, "ETag"); 1523 } 1524 if (result != OK) { 1525 return dav_new_error(r->pool, result, 0, NULL); 1526 } 1527 1528 /* always parse (and later process) the If: header */ 1529 if ((err = dav_process_if_header(r, &if_header)) != NULL) { 1530 /* ### maybe add higher-level description */ 1531 return err; 1532 } 1533 1534 /* If a locktoken was specified, create a dummy if_header with which 1535 * to validate resources. In the interim, figure out why DAV uses 1536 * locktokens in an if-header without a Lock-Token header to refresh 1537 * locks, but a Lock-Token header without an if-header to remove them. 1538 */ 1539 if (locktoken != NULL) { 1540 dav_if_header *ifhdr_new; 1541 1542 ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new)); 1543 ifhdr_new->uri = resource->uri; 1544 ifhdr_new->uri_len = strlen(resource->uri); 1545 ifhdr_new->dummy_header = 1; 1546 1547 ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state)); 1548 ifhdr_new->state->type = dav_if_opaquelock; 1549 ifhdr_new->state->condition = DAV_IF_COND_NORMAL; 1550 ifhdr_new->state->locktoken = locktoken; 1551 1552 ifhdr_new->next = if_header; 1553 if_header = ifhdr_new; 1554 } 1555 1556 /* 1557 ** If necessary, open the lock database (read-only, lazily); 1558 ** the validation process may need to retrieve or update lock info. 1559 ** Otherwise, assume provided lockdb is valid and opened rw. 1560 */ 1561 if (lockdb == NULL) { 1562 if (locks_hooks != NULL) { 1563 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) { 1564 /* ### maybe insert higher-level comment */ 1565 return err; 1566 } 1567 lock_db_opened_locally = 1; 1568 } 1569 } 1570 1571 /* (1) Validate the specified resource, at the specified depth */ 1572 if (resource->exists && depth > 0) { 1573 dav_walker_ctx ctx = { { 0 } }; 1574 dav_response *multi_status; 1575 1576 ctx.w.walk_type = DAV_WALKTYPE_NORMAL; 1577 ctx.w.func = dav_validate_walker; 1578 ctx.w.walk_ctx = &ctx; 1579 ctx.w.pool = r->pool; 1580 ctx.w.root = resource; 1581 1582 ctx.if_header = if_header; 1583 ctx.r = r; 1584 ctx.flags = flags; 1585 1586 if (lockdb != NULL) { 1587 ctx.w.lockdb = lockdb; 1588 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL; 1589 } 1590 1591 err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status); 1592 if (err == NULL) { 1593 *response = multi_status;; 1594 } 1595 /* else: implies a 5xx status code occurred. */ 1596 } 1597 else { 1598 err = dav_validate_resource_state(r->pool, resource, lockdb, 1599 if_header, flags, &work_buf, r); 1600 } 1601 1602 /* (2) Validate the parent resource if requested */ 1603 if (err == NULL && (flags & DAV_VALIDATE_PARENT)) { 1604 dav_resource *parent_resource; 1605 1606 err = (*repos_hooks->get_parent_resource)(resource, &parent_resource); 1607 1608 if (err == NULL && parent_resource == NULL) { 1609 err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0, 1610 "Cannot access parent of repository root."); 1611 } 1612 else if (err == NULL) { 1613 err = dav_validate_resource_state(r->pool, parent_resource, lockdb, 1614 if_header, 1615 flags | DAV_VALIDATE_IS_PARENT, 1616 &work_buf, r); 1617 1618 /* 1619 ** This error occurred on the parent resource. This implies that 1620 ** we have to create a multistatus response (to report the error 1621 ** against a URI other than the Request-URI). "Convert" this error 1622 ** into a multistatus response. 1623 */ 1624 if (err != NULL) { 1625 new_response = apr_pcalloc(r->pool, sizeof(*new_response)); 1626 1627 new_response->href = parent_resource->uri; 1628 new_response->status = err->status; 1629 new_response->desc = 1630 "A validation error has occurred on the parent resource, " 1631 "preventing the operation on the resource specified by " 1632 "the Request-URI."; 1633 if (err->desc != NULL) { 1634 new_response->desc = apr_pstrcat(r->pool, 1635 new_response->desc, 1636 " The error was: ", 1637 err->desc, NULL); 1638 } 1639 1640 /* assert: DAV_VALIDATE_PARENT implies response != NULL */ 1641 new_response->next = *response; 1642 *response = new_response; 1643 1644 err = NULL; 1645 } 1646 } 1647 } 1648 1649 if (lock_db_opened_locally) 1650 (*locks_hooks->close_lockdb)(lockdb); 1651 1652 /* 1653 ** If we don't have a (serious) error, and we have multistatus responses, 1654 ** then we need to construct an "error". This error will be the overall 1655 ** status returned, and the multistatus responses will go into its body. 1656 ** 1657 ** For certain methods, the overall error will be a 424. The default is 1658 ** to construct a standard 207 response. 1659 */ 1660 if (err == NULL && response != NULL && *response != NULL) { 1661 apr_text *propstat = NULL; 1662 1663 if ((flags & DAV_VALIDATE_USE_424) != 0) { 1664 /* manufacture a 424 error to hold the multistatus response(s) */ 1665 return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0, 1666 "An error occurred on another resource, " 1667 "preventing the requested operation on " 1668 "this resource."); 1669 } 1670 1671 /* 1672 ** Whatever caused the error, the Request-URI should have a 424 1673 ** associated with it since we cannot complete the method. 1674 ** 1675 ** For a LOCK operation, insert an empty DAV:lockdiscovery property. 1676 ** For other methods, return a simple 424. 1677 */ 1678 if ((flags & DAV_VALIDATE_ADD_LD) != 0) { 1679 propstat = apr_pcalloc(r->pool, sizeof(*propstat)); 1680 propstat->text = 1681 "<D:propstat>" DEBUG_CR 1682 "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR 1683 "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR 1684 "</D:propstat>" DEBUG_CR; 1685 } 1686 1687 /* create the 424 response */ 1688 new_response = apr_pcalloc(r->pool, sizeof(*new_response)); 1689 new_response->href = resource->uri; 1690 new_response->status = HTTP_FAILED_DEPENDENCY; 1691 new_response->propresult.propstats = propstat; 1692 new_response->desc = 1693 "An error occurred on another resource, preventing the " 1694 "requested operation on this resource."; 1695 1696 new_response->next = *response; 1697 *response = new_response; 1698 1699 /* manufacture a 207 error for the multistatus response(s) */ 1700 return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, 1701 "Error(s) occurred on resources during the " 1702 "validation process."); 1703 } 1704 1705 return err; 1706} 1707 1708/* dav_get_locktoken_list: 1709 * 1710 * Sets ltl to a locktoken_list of all positive locktokens in header, 1711 * else NULL if no If-header, or no positive locktokens. 1712 */ 1713DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r, 1714 dav_locktoken_list **ltl) 1715{ 1716 dav_error *err; 1717 dav_if_header *if_header; 1718 dav_if_state_list *if_state; 1719 dav_locktoken_list *lock_token = NULL; 1720 1721 *ltl = NULL; 1722 1723 if ((err = dav_process_if_header(r, &if_header)) != NULL) { 1724 /* ### add a higher-level description? */ 1725 return err; 1726 } 1727 1728 while (if_header != NULL) { 1729 if_state = if_header->state; /* Begining of the if_state linked list */ 1730 while (if_state != NULL) { 1731 if (if_state->condition == DAV_IF_COND_NORMAL 1732 && if_state->type == dav_if_opaquelock) { 1733 lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list)); 1734 lock_token->locktoken = if_state->locktoken; 1735 lock_token->next = *ltl; 1736 *ltl = lock_token; 1737 } 1738 if_state = if_state->next; 1739 } 1740 if_header = if_header->next; 1741 } 1742 if (*ltl == NULL) { 1743 /* No nodes added */ 1744 return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT, 1745 "No locktokens were specified in the \"If:\" " 1746 "header, so the refresh could not be performed."); 1747 } 1748 1749 return NULL; 1750} 1751 1752#if 0 /* not needed right now... */ 1753 1754static const char *strip_white(const char *s, apr_pool_t *pool) 1755{ 1756 apr_size_t idx; 1757 1758 /* trim leading whitespace */ 1759 while (apr_isspace(*s)) /* assume: return false for '\0' */ 1760 ++s; 1761 1762 /* trim trailing whitespace */ 1763 idx = strlen(s) - 1; 1764 if (apr_isspace(s[idx])) { 1765 char *s2 = apr_pstrdup(pool, s); 1766 1767 while (apr_isspace(s2[idx]) && idx > 0) 1768 --idx; 1769 s2[idx + 1] = '\0'; 1770 return s2; 1771 } 1772 1773 return s; 1774} 1775#endif 1776 1777#define DAV_LABEL_HDR "Label" 1778 1779/* dav_add_vary_header 1780 * 1781 * If there were any headers in the request which require a Vary header 1782 * in the response, add it. 1783 */ 1784DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req, 1785 request_rec *out_req, 1786 const dav_resource *resource) 1787{ 1788 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req); 1789 1790 /* ### this is probably all wrong... I think there is a function in 1791 ### the Apache API to add things to the Vary header. need to check */ 1792 1793 /* Only versioning headers require a Vary response header, 1794 * so only do this check if there is a versioning provider */ 1795 if (vsn_hooks != NULL) { 1796 const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR); 1797 const char *vary = apr_table_get(out_req->headers_out, "Vary"); 1798 1799 /* If Target-Selector specified, add it to the Vary header */ 1800 if (target != NULL) { 1801 if (vary == NULL) 1802 vary = DAV_LABEL_HDR; 1803 else 1804 vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR, 1805 NULL); 1806 1807 apr_table_setn(out_req->headers_out, "Vary", vary); 1808 } 1809 } 1810} 1811 1812/* dav_can_auto_checkout 1813 * 1814 * Determine whether auto-checkout is enabled for a resource. 1815 * r - the request_rec 1816 * resource - the resource 1817 * auto_version - the value of the auto_versionable hook for the resource 1818 * lockdb - pointer to lock database (opened if necessary) 1819 * auto_checkout - set to 1 if auto-checkout enabled 1820 */ 1821static dav_error * dav_can_auto_checkout( 1822 request_rec *r, 1823 dav_resource *resource, 1824 dav_auto_version auto_version, 1825 dav_lockdb **lockdb, 1826 int *auto_checkout) 1827{ 1828 dav_error *err; 1829 dav_lock *lock_list; 1830 1831 *auto_checkout = 0; 1832 1833 if (auto_version == DAV_AUTO_VERSION_ALWAYS) { 1834 *auto_checkout = 1; 1835 } 1836 else if (auto_version == DAV_AUTO_VERSION_LOCKED) { 1837 if (*lockdb == NULL) { 1838 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); 1839 1840 if (locks_hooks == NULL) { 1841 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 1842 "Auto-checkout is only enabled for locked resources, " 1843 "but there is no lock provider."); 1844 } 1845 1846 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) { 1847 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 1848 "Cannot open lock database to determine " 1849 "auto-versioning behavior.", 1850 err); 1851 } 1852 } 1853 1854 if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) { 1855 return dav_push_error(r->pool, 1856 HTTP_INTERNAL_SERVER_ERROR, 0, 1857 "The locks could not be queried for " 1858 "determining auto-versioning behavior.", 1859 err); 1860 } 1861 1862 if (lock_list != NULL) 1863 *auto_checkout = 1; 1864 } 1865 1866 return NULL; 1867} 1868 1869/* see mod_dav.h for docco */ 1870DAV_DECLARE(dav_error *) dav_auto_checkout( 1871 request_rec *r, 1872 dav_resource *resource, 1873 int parent_only, 1874 dav_auto_version_info *av_info) 1875{ 1876 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 1877 dav_lockdb *lockdb = NULL; 1878 dav_error *err = NULL; 1879 1880 /* Initialize results */ 1881 memset(av_info, 0, sizeof(*av_info)); 1882 1883 /* if no versioning provider, just return */ 1884 if (vsn_hooks == NULL) 1885 return NULL; 1886 1887 /* check parent resource if requested or if resource must be created */ 1888 if (!resource->exists || parent_only) { 1889 dav_resource *parent; 1890 1891 if ((err = (*resource->hooks->get_parent_resource)(resource, 1892 &parent)) != NULL) 1893 goto done; 1894 1895 if (parent == NULL || !parent->exists) { 1896 err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 1897 apr_psprintf(r->pool, 1898 "Missing one or more intermediate " 1899 "collections. Cannot create resource %s.", 1900 ap_escape_html(r->pool, resource->uri))); 1901 goto done; 1902 } 1903 1904 av_info->parent_resource = parent; 1905 1906 /* if parent versioned and not checked out, see if it can be */ 1907 if (parent->versioned && !parent->working) { 1908 int checkout_parent; 1909 1910 if ((err = dav_can_auto_checkout(r, parent, 1911 (*vsn_hooks->auto_versionable)(parent), 1912 &lockdb, &checkout_parent)) 1913 != NULL) { 1914 goto done; 1915 } 1916 1917 if (!checkout_parent) { 1918 err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 1919 "<DAV:cannot-modify-checked-in-parent>"); 1920 goto done; 1921 } 1922 1923 /* Try to checkout the parent collection. 1924 * Note that auto-versioning can only be applied to a version selector, 1925 * so no separate working resource will be created. 1926 */ 1927 if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/, 1928 0, 0, 0, NULL, NULL)) 1929 != NULL) 1930 { 1931 err = dav_push_error(r->pool, HTTP_CONFLICT, 0, 1932 apr_psprintf(r->pool, 1933 "Unable to auto-checkout parent collection. " 1934 "Cannot create resource %s.", 1935 ap_escape_html(r->pool, resource->uri)), 1936 err); 1937 goto done; 1938 } 1939 1940 /* remember that parent was checked out */ 1941 av_info->parent_checkedout = 1; 1942 } 1943 } 1944 1945 /* if only checking parent, we're done */ 1946 if (parent_only) 1947 goto done; 1948 1949 /* if creating a new resource, see if it should be version-controlled */ 1950 if (!resource->exists 1951 && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) { 1952 1953 if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) { 1954 err = dav_push_error(r->pool, HTTP_CONFLICT, 0, 1955 apr_psprintf(r->pool, 1956 "Unable to create versioned resource %s.", 1957 ap_escape_html(r->pool, resource->uri)), 1958 err); 1959 goto done; 1960 } 1961 1962 /* remember that resource was created */ 1963 av_info->resource_versioned = 1; 1964 } 1965 1966 /* if resource is versioned, make sure it is checked out */ 1967 if (resource->versioned && !resource->working) { 1968 int checkout_resource; 1969 1970 if ((err = dav_can_auto_checkout(r, resource, 1971 (*vsn_hooks->auto_versionable)(resource), 1972 &lockdb, &checkout_resource)) != NULL) { 1973 goto done; 1974 } 1975 1976 if (!checkout_resource) { 1977 err = dav_new_error(r->pool, HTTP_CONFLICT, 0, 1978 "<DAV:cannot-modify-version-controlled-content>"); 1979 goto done; 1980 } 1981 1982 /* Auto-versioning can only be applied to version selectors, so 1983 * no separate working resource will be created. */ 1984 if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/, 1985 0, 0, 0, NULL, NULL)) 1986 != NULL) 1987 { 1988 err = dav_push_error(r->pool, HTTP_CONFLICT, 0, 1989 apr_psprintf(r->pool, 1990 "Unable to checkout resource %s.", 1991 ap_escape_html(r->pool, resource->uri)), 1992 err); 1993 goto done; 1994 } 1995 1996 /* remember that resource was checked out */ 1997 av_info->resource_checkedout = 1; 1998 } 1999 2000done: 2001 2002 /* make sure lock database is closed */ 2003 if (lockdb != NULL) 2004 (*lockdb->hooks->close_lockdb)(lockdb); 2005 2006 /* if an error occurred, undo any auto-versioning operations already done */ 2007 if (err != NULL) { 2008 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info); 2009 return err; 2010 } 2011 2012 return NULL; 2013} 2014 2015/* see mod_dav.h for docco */ 2016DAV_DECLARE(dav_error *) dav_auto_checkin( 2017 request_rec *r, 2018 dav_resource *resource, 2019 int undo, 2020 int unlock, 2021 dav_auto_version_info *av_info) 2022{ 2023 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); 2024 dav_error *err = NULL; 2025 dav_auto_version auto_version; 2026 2027 /* If no versioning provider, this is a no-op */ 2028 if (vsn_hooks == NULL) 2029 return NULL; 2030 2031 /* If undoing auto-checkouts, then do uncheckouts */ 2032 if (undo) { 2033 if (resource != NULL) { 2034 if (av_info->resource_checkedout) { 2035 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) { 2036 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 2037 apr_psprintf(r->pool, 2038 "Unable to undo auto-checkout " 2039 "of resource %s.", 2040 ap_escape_html(r->pool, resource->uri)), 2041 err); 2042 } 2043 } 2044 2045 if (av_info->resource_versioned) { 2046 dav_response *response; 2047 2048 /* ### should we do anything with the response? */ 2049 if ((err = (*resource->hooks->remove_resource)(resource, 2050 &response)) != NULL) { 2051 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 2052 apr_psprintf(r->pool, 2053 "Unable to undo auto-version-control " 2054 "of resource %s.", 2055 ap_escape_html(r->pool, resource->uri)), 2056 err); 2057 } 2058 } 2059 } 2060 2061 if (av_info->parent_resource != NULL && av_info->parent_checkedout) { 2062 if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) { 2063 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 2064 apr_psprintf(r->pool, 2065 "Unable to undo auto-checkout " 2066 "of parent collection %s.", 2067 ap_escape_html(r->pool, av_info->parent_resource->uri)), 2068 err); 2069 } 2070 } 2071 2072 return NULL; 2073 } 2074 2075 /* If the resource was checked out, and auto-checkin is enabled, 2076 * then check it in. 2077 */ 2078 if (resource != NULL && resource->working 2079 && (unlock || av_info->resource_checkedout)) { 2080 2081 auto_version = (*vsn_hooks->auto_versionable)(resource); 2082 2083 if (auto_version == DAV_AUTO_VERSION_ALWAYS || 2084 (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) { 2085 2086 if ((err = (*vsn_hooks->checkin)(resource, 2087 0 /*keep_checked_out*/, NULL)) 2088 != NULL) { 2089 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 2090 apr_psprintf(r->pool, 2091 "Unable to auto-checkin resource %s.", 2092 ap_escape_html(r->pool, resource->uri)), 2093 err); 2094 } 2095 } 2096 } 2097 2098 /* If parent resource was checked out, and auto-checkin is enabled, 2099 * then check it in. 2100 */ 2101 if (!unlock 2102 && av_info->parent_checkedout 2103 && av_info->parent_resource != NULL 2104 && av_info->parent_resource->working) { 2105 2106 auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource); 2107 2108 if (auto_version == DAV_AUTO_VERSION_ALWAYS) { 2109 if ((err = (*vsn_hooks->checkin)(av_info->parent_resource, 2110 0 /*keep_checked_out*/, NULL)) 2111 != NULL) { 2112 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 2113 apr_psprintf(r->pool, 2114 "Unable to auto-checkin parent collection %s.", 2115 ap_escape_html(r->pool, av_info->parent_resource->uri)), 2116 err); 2117 } 2118 } 2119 } 2120 2121 return NULL; 2122} 2123