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