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 filesystem-based repository provider 19*/ 20 21#include "apr.h" 22#include "apr_file_io.h" 23#include "apr_strings.h" 24#include "apr_buckets.h" 25 26#if APR_HAVE_UNISTD_H 27#include <unistd.h> /* for getpid() */ 28#endif 29 30#include "httpd.h" 31#include "http_log.h" 32#include "http_protocol.h" /* for ap_set_* (in dav_fs_set_headers) */ 33#include "http_request.h" /* for ap_update_mtime() */ 34 35#include "mod_dav.h" 36#include "repos.h" 37 38 39/* to assist in debugging mod_dav's GET handling */ 40#define DEBUG_GET_HANDLER 0 41 42#define DAV_FS_COPY_BLOCKSIZE 16384 /* copy 16k at a time */ 43 44/* context needed to identify a resource */ 45struct dav_resource_private { 46 apr_pool_t *pool; /* memory storage pool associated with request */ 47 const char *pathname; /* full pathname to resource */ 48 apr_finfo_t finfo; /* filesystem info */ 49 request_rec *r; 50}; 51 52/* private context for doing a filesystem walk */ 53typedef struct { 54 /* the input walk parameters */ 55 const dav_walk_params *params; 56 57 /* reused as we walk */ 58 dav_walk_resource wres; 59 60 dav_resource res1; 61 dav_resource_private info1; 62 dav_buffer path1; 63 dav_buffer uri_buf; 64 65 /* MOVE/COPY need a secondary path */ 66 dav_resource res2; 67 dav_resource_private info2; 68 dav_buffer path2; 69 70 dav_buffer locknull_buf; 71 72} dav_fs_walker_context; 73 74typedef struct { 75 int is_move; /* is this a MOVE? */ 76 dav_buffer work_buf; /* handy buffer for copymove_file() */ 77 78 /* CALLBACK: this is a secondary resource managed specially for us */ 79 const dav_resource *res_dst; 80 81 /* copied from dav_walk_params (they are invariant across the walk) */ 82 const dav_resource *root; 83 apr_pool_t *pool; 84 85} dav_fs_copymove_walk_ctx; 86 87/* an internal WALKTYPE to walk hidden files (the .DAV directory) */ 88#define DAV_WALKTYPE_HIDDEN 0x4000 89 90/* an internal WALKTYPE to call collections (again) after their contents */ 91#define DAV_WALKTYPE_POSTFIX 0x8000 92 93#define DAV_CALLTYPE_POSTFIX 1000 /* a private call type */ 94 95 96/* pull this in from the other source file */ 97extern const dav_hooks_locks dav_hooks_locks_fs; 98 99/* forward-declare the hook structures */ 100static const dav_hooks_repository dav_hooks_repository_fs; 101static const dav_hooks_liveprop dav_hooks_liveprop_fs; 102 103/* 104** The namespace URIs that we use. This list and the enumeration must 105** stay in sync. 106*/ 107static const char * const dav_fs_namespace_uris[] = 108{ 109 "DAV:", 110 "http://apache.org/dav/props/", 111 112 NULL /* sentinel */ 113}; 114enum { 115 DAV_FS_URI_DAV, /* the DAV: namespace URI */ 116 DAV_FS_URI_MYPROPS /* the namespace URI for our custom props */ 117}; 118 119/* 120** Does this platform support an executable flag? 121** 122** ### need a way to portably abstract this query 123** 124** DAV_FINFO_MASK gives the appropriate mask to use for the stat call 125** used to get file attributes. 126*/ 127#ifndef WIN32 128#define DAV_FS_HAS_EXECUTABLE 129#define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \ 130 APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME | \ 131 APR_FINFO_PROT) 132#else 133/* as above, but without APR_FINFO_PROT */ 134#define DAV_FINFO_MASK (APR_FINFO_LINK | APR_FINFO_TYPE | APR_FINFO_INODE | \ 135 APR_FINFO_SIZE | APR_FINFO_CTIME | APR_FINFO_MTIME) 136#endif 137 138/* 139** The single property that we define (in the DAV_FS_URI_MYPROPS namespace) 140*/ 141#define DAV_PROPID_FS_executable 1 142 143/* 144 * prefix for temporary files 145 */ 146#define DAV_FS_TMP_PREFIX ".davfs.tmp" 147 148static const dav_liveprop_spec dav_fs_props[] = 149{ 150 /* standard DAV properties */ 151 { 152 DAV_FS_URI_DAV, 153 "creationdate", 154 DAV_PROPID_creationdate, 155 0 156 }, 157 { 158 DAV_FS_URI_DAV, 159 "getcontentlength", 160 DAV_PROPID_getcontentlength, 161 0 162 }, 163 { 164 DAV_FS_URI_DAV, 165 "getetag", 166 DAV_PROPID_getetag, 167 0 168 }, 169 { 170 DAV_FS_URI_DAV, 171 "getlastmodified", 172 DAV_PROPID_getlastmodified, 173 0 174 }, 175 176 /* our custom properties */ 177 { 178 DAV_FS_URI_MYPROPS, 179 "executable", 180 DAV_PROPID_FS_executable, 181 0 /* handled special in dav_fs_is_writable */ 182 }, 183 184 { 0 } /* sentinel */ 185}; 186 187static const dav_liveprop_group dav_fs_liveprop_group = 188{ 189 dav_fs_props, 190 dav_fs_namespace_uris, 191 &dav_hooks_liveprop_fs 192}; 193 194 195/* define the dav_stream structure for our use */ 196struct dav_stream { 197 apr_pool_t *p; 198 apr_file_t *f; 199 const char *pathname; /* we may need to remove it at close time */ 200 char *temppath; 201 int unlink_on_error; 202}; 203 204/* returns an appropriate HTTP status code given an APR status code for a 205 * failed I/O operation. ### use something besides 500? */ 206#define MAP_IO2HTTP(e) (APR_STATUS_IS_ENOSPC(e) ? HTTP_INSUFFICIENT_STORAGE : \ 207 APR_STATUS_IS_ENOENT(e) ? HTTP_CONFLICT : \ 208 HTTP_INTERNAL_SERVER_ERROR) 209 210/* forward declaration for internal treewalkers */ 211static dav_error * dav_fs_walk(const dav_walk_params *params, int depth, 212 dav_response **response); 213static dav_error * dav_fs_internal_walk(const dav_walk_params *params, 214 int depth, int is_move, 215 const dav_resource *root_dst, 216 dav_response **response); 217 218/* -------------------------------------------------------------------- 219** 220** PRIVATE REPOSITORY FUNCTIONS 221*/ 222static request_rec *dav_fs_get_request_rec(const dav_resource *resource) 223{ 224 return resource->info->r; 225} 226 227apr_pool_t *dav_fs_pool(const dav_resource *resource) 228{ 229 return resource->info->pool; 230} 231 232const char *dav_fs_pathname(const dav_resource *resource) 233{ 234 return resource->info->pathname; 235} 236 237dav_error * dav_fs_dir_file_name( 238 const dav_resource *resource, 239 const char **dirpath_p, 240 const char **fname_p) 241{ 242 dav_resource_private *ctx = resource->info; 243 244 if (resource->collection) { 245 *dirpath_p = ctx->pathname; 246 if (fname_p != NULL) 247 *fname_p = NULL; 248 } 249 else { 250 const char *testpath, *rootpath; 251 char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname); 252 apr_size_t dirlen = strlen(dirpath); 253 apr_status_t rv = APR_SUCCESS; 254 255 testpath = dirpath; 256 if (dirlen > 0) { 257 rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool); 258 } 259 260 /* remove trailing slash from dirpath, unless it's a root path 261 */ 262 if ((rv == APR_SUCCESS && testpath && *testpath) 263 || rv == APR_ERELATIVE) { 264 if (dirpath[dirlen - 1] == '/') { 265 dirpath[dirlen - 1] = '\0'; 266 } 267 } 268 269 /* ###: Looks like a response could be appropriate 270 * 271 * APR_SUCCESS here tells us the dir is a root 272 * APR_ERELATIVE told us we had no root (ok) 273 * APR_EINCOMPLETE an incomplete testpath told us 274 * there was no -file- name here! 275 * APR_EBADPATH or other errors tell us this file 276 * path is undecipherable 277 */ 278 279 if (rv == APR_SUCCESS || rv == APR_ERELATIVE) { 280 *dirpath_p = dirpath; 281 if (fname_p != NULL) 282 *fname_p = ctx->pathname + dirlen; 283 } 284 else { 285 return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 286 "An incomplete/bad path was found in " 287 "dav_fs_dir_file_name."); 288 } 289 } 290 291 return NULL; 292} 293 294/* Note: picked up from ap_gm_timestr_822() */ 295/* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */ 296static void dav_format_time(int style, apr_time_t sec, char *buf, apr_size_t buflen) 297{ 298 apr_time_exp_t tms; 299 300 /* ### what to do if fails? */ 301 (void) apr_time_exp_gmt(&tms, sec); 302 303 if (style == DAV_STYLE_ISO8601) { 304 /* ### should we use "-00:00" instead of "Z" ?? */ 305 306 /* 20 chars plus null term */ 307 apr_snprintf(buf, buflen, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ", 308 tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday, 309 tms.tm_hour, tms.tm_min, tms.tm_sec); 310 return; 311 } 312 313 /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */ 314 315 /* 29 chars plus null term */ 316 apr_snprintf(buf, buflen, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", 317 apr_day_snames[tms.tm_wday], 318 tms.tm_mday, apr_month_snames[tms.tm_mon], 319 tms.tm_year + 1900, 320 tms.tm_hour, tms.tm_min, tms.tm_sec); 321} 322 323/* Copy or move src to dst; src_finfo is used to propagate permissions 324 * bits across if non-NULL; dst_finfo must be non-NULL iff dst already 325 * exists. */ 326static dav_error * dav_fs_copymove_file( 327 int is_move, 328 apr_pool_t * p, 329 const char *src, 330 const char *dst, 331 const apr_finfo_t *src_finfo, 332 const apr_finfo_t *dst_finfo, 333 dav_buffer *pbuf) 334{ 335 dav_buffer work_buf = { 0 }; 336 apr_file_t *inf = NULL; 337 apr_file_t *outf = NULL; 338 apr_status_t status; 339 apr_fileperms_t perms; 340 341 if (pbuf == NULL) 342 pbuf = &work_buf; 343 344 /* Determine permissions to use for destination */ 345 if (src_finfo && src_finfo->valid & APR_FINFO_PROT 346 && src_finfo->protection & APR_UEXECUTE) { 347 perms = src_finfo->protection; 348 349 if (dst_finfo != NULL) { 350 /* chmod it if it already exist */ 351 if ((status = apr_file_perms_set(dst, perms)) != APR_SUCCESS) { 352 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status, 353 "Could not set permissions on destination"); 354 } 355 } 356 } 357 else { 358 perms = APR_OS_DEFAULT; 359 } 360 361 dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE); 362 363 if ((status = apr_file_open(&inf, src, APR_READ | APR_BINARY, 364 APR_OS_DEFAULT, p)) != APR_SUCCESS) { 365 /* ### use something besides 500? */ 366 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status, 367 "Could not open file for reading"); 368 } 369 370 /* ### do we need to deal with the umask? */ 371 status = apr_file_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE 372 | APR_BINARY, perms, p); 373 if (status != APR_SUCCESS) { 374 apr_file_close(inf); 375 376 return dav_new_error(p, MAP_IO2HTTP(status), 0, status, 377 "Could not open file for writing"); 378 } 379 380 while (1) { 381 apr_size_t len = DAV_FS_COPY_BLOCKSIZE; 382 383 status = apr_file_read(inf, pbuf->buf, &len); 384 if (status != APR_SUCCESS && status != APR_EOF) { 385 apr_status_t lcl_status; 386 387 apr_file_close(inf); 388 apr_file_close(outf); 389 390 if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) { 391 /* ### ACK! Inconsistent state... */ 392 393 /* ### use something besides 500? */ 394 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 395 lcl_status, 396 "Could not delete output after read " 397 "failure. Server is now in an " 398 "inconsistent state."); 399 } 400 401 /* ### use something besides 500? */ 402 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status, 403 "Could not read input file"); 404 } 405 406 if (status == APR_EOF) 407 break; 408 409 /* write any bytes that were read */ 410 status = apr_file_write_full(outf, pbuf->buf, len, NULL); 411 if (status != APR_SUCCESS) { 412 apr_status_t lcl_status; 413 414 apr_file_close(inf); 415 apr_file_close(outf); 416 417 if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) { 418 /* ### ACK! Inconsistent state... */ 419 420 /* ### use something besides 500? */ 421 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 422 lcl_status, 423 "Could not delete output after write " 424 "failure. Server is now in an " 425 "inconsistent state."); 426 } 427 428 return dav_new_error(p, MAP_IO2HTTP(status), 0, status, 429 "Could not write output file"); 430 } 431 } 432 433 apr_file_close(inf); 434 apr_file_close(outf); 435 436 if (is_move && (status = apr_file_remove(src, p)) != APR_SUCCESS) { 437 dav_error *err; 438 apr_status_t lcl_status; 439 440 if (APR_STATUS_IS_ENOENT(status)) { 441 /* 442 * Something is wrong here but the result is what we wanted. 443 * We definitely should not remove the destination file. 444 */ 445 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status, 446 apr_psprintf(p, "Could not remove source " 447 "file %s after move to %s. The " 448 "server may be in an " 449 "inconsistent state.", src, dst)); 450 return err; 451 } 452 else if ((lcl_status = apr_file_remove(dst, p)) != APR_SUCCESS) { 453 /* ### ACK. this creates an inconsistency. do more!? */ 454 455 /* ### use something besides 500? */ 456 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, lcl_status, 457 "Could not remove source or destination " 458 "file. Server is now in an inconsistent " 459 "state."); 460 } 461 462 /* ### use something besides 500? */ 463 err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status, 464 "Could not remove source file after move. " 465 "Destination was removed to ensure consistency."); 466 return err; 467 } 468 469 return NULL; 470} 471 472/* copy/move a file from within a state dir to another state dir */ 473/* ### need more buffers to replace the pool argument */ 474static dav_error * dav_fs_copymove_state( 475 int is_move, 476 apr_pool_t * p, 477 const char *src_dir, const char *src_file, 478 const char *dst_dir, const char *dst_file, 479 dav_buffer *pbuf) 480{ 481 apr_finfo_t src_finfo; /* finfo for source file */ 482 apr_finfo_t dst_state_finfo; /* finfo for STATE directory */ 483 apr_status_t rv; 484 const char *src; 485 const char *dst; 486 487 /* build the propset pathname for the source file */ 488 src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL); 489 490 /* the source file doesn't exist */ 491 rv = apr_stat(&src_finfo, src, APR_FINFO_NORM, p); 492 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) { 493 return NULL; 494 } 495 496 /* build the pathname for the destination state dir */ 497 dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL); 498 499 /* ### do we need to deal with the umask? */ 500 501 /* ensure that it exists */ 502 rv = apr_dir_make(dst, APR_OS_DEFAULT, p); 503 if (rv != APR_SUCCESS) { 504 if (!APR_STATUS_IS_EEXIST(rv)) { 505 /* ### use something besides 500? */ 506 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 507 "Could not create internal state directory"); 508 } 509 } 510 511 /* get info about the state directory */ 512 rv = apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p); 513 if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) { 514 /* Ack! Where'd it go? */ 515 /* ### use something besides 500? */ 516 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 517 "State directory disappeared"); 518 } 519 520 /* The mkdir() may have failed because a *file* exists there already */ 521 if (dst_state_finfo.filetype != APR_DIR) { 522 /* ### try to recover by deleting this file? (and mkdir again) */ 523 /* ### use something besides 500? */ 524 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0, 525 "State directory is actually a file"); 526 } 527 528 /* append the target file to the state directory pathname */ 529 dst = apr_pstrcat(p, dst, "/", dst_file, NULL); 530 531 /* copy/move the file now */ 532 if (is_move) { 533 /* try simple rename first */ 534 rv = apr_file_rename(src, dst, p); 535 if (APR_STATUS_IS_EXDEV(rv)) { 536 return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf); 537 } 538 if (rv != APR_SUCCESS) { 539 /* ### use something besides 500? */ 540 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 541 "Could not move state file."); 542 } 543 } 544 else 545 { 546 /* gotta copy (and delete) */ 547 return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf); 548 } 549 550 return NULL; 551} 552 553static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p, 554 const dav_resource *src, 555 const dav_resource *dst, 556 dav_buffer *pbuf) 557{ 558 const char *src_dir; 559 const char *src_file; 560 const char *src_state1; 561 const char *src_state2; 562 const char *dst_dir; 563 const char *dst_file; 564 const char *dst_state1; 565 const char *dst_state2; 566 dav_error *err; 567 568 /* Get directory and filename for resources */ 569 /* ### should test these result values... */ 570 (void) dav_fs_dir_file_name(src, &src_dir, &src_file); 571 (void) dav_fs_dir_file_name(dst, &dst_dir, &dst_file); 572 573 /* Get the corresponding state files for each resource */ 574 dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2); 575 dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2); 576#if DAV_DEBUG 577 if ((src_state2 != NULL && dst_state2 == NULL) || 578 (src_state2 == NULL && dst_state2 != NULL)) { 579 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, 0, 580 "DESIGN ERROR: dav_dbm_get_statefiles() " 581 "returned inconsistent results."); 582 } 583#endif 584 585 err = dav_fs_copymove_state(is_move, p, 586 src_dir, src_state1, 587 dst_dir, dst_state1, 588 pbuf); 589 590 if (err == NULL && src_state2 != NULL) { 591 err = dav_fs_copymove_state(is_move, p, 592 src_dir, src_state2, 593 dst_dir, dst_state2, 594 pbuf); 595 596 if (err != NULL) { 597 /* ### CRAP. inconsistency. */ 598 /* ### should perform some cleanup at the target if we still 599 ### have the original files */ 600 601 /* Change the error to reflect the bad server state. */ 602 err->status = HTTP_INTERNAL_SERVER_ERROR; 603 err->desc = 604 "Could not fully copy/move the properties. " 605 "The server is now in an inconsistent state."; 606 } 607 } 608 609 return err; 610} 611 612static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource) 613{ 614 const char *dirpath; 615 const char *fname; 616 const char *state1; 617 const char *state2; 618 const char *pathname; 619 apr_status_t status; 620 621 /* Get directory, filename, and state-file names for the resource */ 622 /* ### should test this result value... */ 623 (void) dav_fs_dir_file_name(resource, &dirpath, &fname); 624 dav_dbm_get_statefiles(p, fname, &state1, &state2); 625 626 /* build the propset pathname for the file */ 627 pathname = apr_pstrcat(p, 628 dirpath, 629 "/" DAV_FS_STATE_DIR "/", 630 state1, 631 NULL); 632 633 /* note: we may get ENOENT if the state dir is not present */ 634 if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS 635 && !APR_STATUS_IS_ENOENT(status)) { 636 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status, 637 "Could not remove properties."); 638 } 639 640 if (state2 != NULL) { 641 /* build the propset pathname for the file */ 642 pathname = apr_pstrcat(p, 643 dirpath, 644 "/" DAV_FS_STATE_DIR "/", 645 state2, 646 NULL); 647 648 if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS 649 && !APR_STATUS_IS_ENOENT(status)) { 650 /* ### CRAP. only removed half. */ 651 return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, status, 652 "Could not fully remove properties. " 653 "The server is now in an inconsistent " 654 "state."); 655 } 656 } 657 658 return NULL; 659} 660 661/* -------------------------------------------------------------------- 662** 663** REPOSITORY HOOK FUNCTIONS 664*/ 665 666static dav_error * dav_fs_get_resource( 667 request_rec *r, 668 const char *root_dir, 669 const char *label, 670 int use_checked_in, 671 dav_resource **result_resource) 672{ 673 dav_resource_private *ctx; 674 dav_resource *resource; 675 char *s; 676 char *filename; 677 apr_size_t len; 678 679 /* ### optimize this into a single allocation! */ 680 681 /* Create private resource context descriptor */ 682 ctx = apr_pcalloc(r->pool, sizeof(*ctx)); 683 ctx->finfo = r->finfo; 684 ctx->r = r; 685 686 /* ### this should go away */ 687 ctx->pool = r->pool; 688 689 /* Preserve case on OSes which fold canonical filenames */ 690#if 0 691 /* ### not available in Apache 2.0 yet */ 692 filename = r->case_preserved_filename; 693#else 694 filename = r->filename; 695#endif 696 697 /* 698 ** If there is anything in the path_info, then this indicates that the 699 ** entire path was not used to specify the file/dir. We want to append 700 ** it onto the filename so that we get a "valid" pathname for null 701 ** resources. 702 */ 703 s = apr_pstrcat(r->pool, filename, r->path_info, NULL); 704 705 /* make sure the pathname does not have a trailing "/" */ 706 len = strlen(s); 707 if (len > 1 && s[len - 1] == '/') { 708 s[len - 1] = '\0'; 709 } 710 ctx->pathname = s; 711 712 /* Create resource descriptor */ 713 resource = apr_pcalloc(r->pool, sizeof(*resource)); 714 resource->type = DAV_RESOURCE_TYPE_REGULAR; 715 resource->info = ctx; 716 resource->hooks = &dav_hooks_repository_fs; 717 resource->pool = r->pool; 718 719 /* make sure the URI does not have a trailing "/" */ 720 len = strlen(r->unparsed_uri); 721 if (len > 1 && r->unparsed_uri[len - 1] == '/') { 722 s = apr_pstrmemdup(r->pool, r->unparsed_uri, len-1); 723 resource->uri = s; 724 } 725 else { 726 resource->uri = r->unparsed_uri; 727 } 728 729 if (r->finfo.filetype != APR_NOFILE) { 730 resource->exists = 1; 731 resource->collection = r->finfo.filetype == APR_DIR; 732 733 /* unused info in the URL will indicate a null resource */ 734 735 if (r->path_info != NULL && *r->path_info != '\0') { 736 if (resource->collection) { 737 /* only a trailing "/" is allowed */ 738 if (*r->path_info != '/' || r->path_info[1] != '\0') { 739 740 /* 741 ** This URL/filename represents a locknull resource or 742 ** possibly a destination of a MOVE/COPY 743 */ 744 resource->exists = 0; 745 resource->collection = 0; 746 } 747 } 748 else 749 { 750 /* 751 ** The base of the path refers to a file -- nothing should 752 ** be in path_info. The resource is simply an error: it 753 ** can't be a null or a locknull resource. 754 */ 755 return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0, 756 "The URL contains extraneous path " 757 "components. The resource could not " 758 "be identified."); 759 } 760 761 /* retain proper integrity across the structures */ 762 if (!resource->exists) { 763 ctx->finfo.filetype = APR_NOFILE; 764 } 765 } 766 } 767 768 *result_resource = resource; 769 return NULL; 770} 771 772static dav_error * dav_fs_get_parent_resource(const dav_resource *resource, 773 dav_resource **result_parent) 774{ 775 dav_resource_private *ctx = resource->info; 776 dav_resource_private *parent_ctx; 777 dav_resource *parent_resource; 778 apr_status_t rv; 779 char *dirpath; 780 const char *testroot; 781 const char *testpath; 782 783 /* If we're at the root of the URL space, then there is no parent. */ 784 if (strcmp(resource->uri, "/") == 0) { 785 *result_parent = NULL; 786 return NULL; 787 } 788 789 /* If given resource is root, then there is no parent. 790 * Unless we can retrieve the filepath root, this is 791 * intendend to fail. If we split the root and 792 * no path info remains, then we also fail. 793 */ 794 testpath = ctx->pathname; 795 rv = apr_filepath_root(&testroot, &testpath, 0, ctx->pool); 796 if ((rv != APR_SUCCESS && rv != APR_ERELATIVE) 797 || !testpath || !*testpath) { 798 *result_parent = NULL; 799 return NULL; 800 } 801 802 /* ### optimize this into a single allocation! */ 803 804 /* Create private resource context descriptor */ 805 parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx)); 806 807 /* ### this should go away */ 808 parent_ctx->pool = ctx->pool; 809 810 dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname); 811 if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/') 812 dirpath[strlen(dirpath) - 1] = '\0'; 813 parent_ctx->pathname = dirpath; 814 815 parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource)); 816 parent_resource->info = parent_ctx; 817 parent_resource->collection = 1; 818 parent_resource->hooks = &dav_hooks_repository_fs; 819 parent_resource->pool = resource->pool; 820 821 if (resource->uri != NULL) { 822 char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri); 823 if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/') 824 uri[strlen(uri) - 1] = '\0'; 825 parent_resource->uri = uri; 826 } 827 828 rv = apr_stat(&parent_ctx->finfo, parent_ctx->pathname, 829 APR_FINFO_NORM, ctx->pool); 830 if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) { 831 parent_resource->exists = 1; 832 } 833 834 *result_parent = parent_resource; 835 return NULL; 836} 837 838static int dav_fs_is_same_resource( 839 const dav_resource *res1, 840 const dav_resource *res2) 841{ 842 dav_resource_private *ctx1 = res1->info; 843 dav_resource_private *ctx2 = res2->info; 844 845 if (res1->hooks != res2->hooks) 846 return 0; 847 848 if ((ctx1->finfo.filetype != APR_NOFILE) && (ctx2->finfo.filetype != APR_NOFILE) 849 && (ctx1->finfo.valid & ctx2->finfo.valid & APR_FINFO_INODE)) { 850 return ctx1->finfo.inode == ctx2->finfo.inode; 851 } 852 else { 853 return strcmp(ctx1->pathname, ctx2->pathname) == 0; 854 } 855} 856 857static int dav_fs_is_parent_resource( 858 const dav_resource *res1, 859 const dav_resource *res2) 860{ 861 dav_resource_private *ctx1 = res1->info; 862 dav_resource_private *ctx2 = res2->info; 863 apr_size_t len1 = strlen(ctx1->pathname); 864 apr_size_t len2; 865 866 if (res1->hooks != res2->hooks) 867 return 0; 868 869 /* it is safe to use ctx2 now */ 870 len2 = strlen(ctx2->pathname); 871 872 return (len2 > len1 873 && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0 874 && ctx2->pathname[len1] == '/'); 875} 876 877static apr_status_t tmpfile_cleanup(void *data) { 878 dav_stream *ds = data; 879 if (ds->temppath) { 880 apr_file_remove(ds->temppath, ds->p); 881 } 882 return APR_SUCCESS; 883} 884 885/* custom mktemp that creates the file with APR_OS_DEFAULT permissions */ 886static apr_status_t dav_fs_mktemp(apr_file_t **fp, char *templ, apr_pool_t *p) 887{ 888 apr_status_t rv; 889 int num = ((getpid() << 7) + (apr_uintptr_t)templ % (1 << 16) ) % 890 ( 1 << 23 ) ; 891 char *numstr = templ + strlen(templ) - 6; 892 893 ap_assert(numstr >= templ); 894 895 do { 896 num = (num + 1) % ( 1 << 23 ); 897 apr_snprintf(numstr, 7, "%06x", num); 898 rv = apr_file_open(fp, templ, 899 APR_WRITE | APR_CREATE | APR_BINARY | APR_EXCL, 900 APR_OS_DEFAULT, p); 901 } while (APR_STATUS_IS_EEXIST(rv)); 902 903 return rv; 904} 905 906static dav_error * dav_fs_open_stream(const dav_resource *resource, 907 dav_stream_mode mode, 908 dav_stream **stream) 909{ 910 apr_pool_t *p = resource->info->pool; 911 dav_stream *ds = apr_pcalloc(p, sizeof(*ds)); 912 apr_int32_t flags; 913 apr_status_t rv; 914 915 switch (mode) { 916 default: 917 flags = APR_READ | APR_BINARY; 918 break; 919 920 case DAV_MODE_WRITE_TRUNC: 921 flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY; 922 break; 923 case DAV_MODE_WRITE_SEEKABLE: 924 flags = APR_WRITE | APR_CREATE | APR_BINARY; 925 break; 926 } 927 928 ds->p = p; 929 ds->pathname = resource->info->pathname; 930 ds->temppath = NULL; 931 ds->unlink_on_error = 0; 932 933 if (mode == DAV_MODE_WRITE_TRUNC) { 934 ds->temppath = apr_pstrcat(p, ap_make_dirstr_parent(p, ds->pathname), 935 DAV_FS_TMP_PREFIX "XXXXXX", NULL); 936 rv = dav_fs_mktemp(&ds->f, ds->temppath, ds->p); 937 apr_pool_cleanup_register(p, ds, tmpfile_cleanup, 938 apr_pool_cleanup_null); 939 } 940 else if (mode == DAV_MODE_WRITE_SEEKABLE) { 941 rv = apr_file_open(&ds->f, ds->pathname, flags | APR_FOPEN_EXCL, 942 APR_OS_DEFAULT, ds->p); 943 if (rv == APR_SUCCESS) { 944 /* we have created a new file */ 945 ds->unlink_on_error = 1; 946 } 947 else if (APR_STATUS_IS_EEXIST(rv)) { 948 rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, 949 ds->p); 950 } 951 } 952 else { 953 rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, ds->p); 954 } 955 956 if (rv != APR_SUCCESS) { 957 return dav_new_error(p, MAP_IO2HTTP(rv), 0, rv, 958 "An error occurred while opening a resource."); 959 } 960 961 /* (APR registers cleanups for the fd with the pool) */ 962 963 *stream = ds; 964 return NULL; 965} 966 967static dav_error * dav_fs_close_stream(dav_stream *stream, int commit) 968{ 969 apr_status_t rv; 970 971 apr_file_close(stream->f); 972 973 if (!commit) { 974 if (stream->temppath) { 975 apr_pool_cleanup_run(stream->p, stream, tmpfile_cleanup); 976 } 977 else if (stream->unlink_on_error) { 978 if ((rv = apr_file_remove(stream->pathname, stream->p)) 979 != APR_SUCCESS) { 980 /* ### use a better description? */ 981 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, 982 rv, 983 "There was a problem removing (rolling " 984 "back) the resource " 985 "when it was being closed."); 986 } 987 } 988 } 989 else if (stream->temppath) { 990 rv = apr_file_rename(stream->temppath, stream->pathname, stream->p); 991 if (rv) { 992 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 993 "There was a problem writing the file " 994 "atomically after writes."); 995 } 996 apr_pool_cleanup_kill(stream->p, stream, tmpfile_cleanup); 997 } 998 999 return NULL; 1000} 1001 1002static dav_error * dav_fs_write_stream(dav_stream *stream, 1003 const void *buf, apr_size_t bufsize) 1004{ 1005 apr_status_t status; 1006 1007 status = apr_file_write_full(stream->f, buf, bufsize, NULL); 1008 if (APR_STATUS_IS_ENOSPC(status)) { 1009 return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0, status, 1010 "There is not enough storage to write to " 1011 "this resource."); 1012 } 1013 else if (status != APR_SUCCESS) { 1014 /* ### use something besides 500? */ 1015 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, status, 1016 "An error occurred while writing to a " 1017 "resource."); 1018 } 1019 return NULL; 1020} 1021 1022static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos) 1023{ 1024 apr_status_t status; 1025 1026 if ((status = apr_file_seek(stream->f, APR_SET, &abs_pos)) 1027 != APR_SUCCESS) { 1028 /* ### should check whether apr_file_seek set abs_pos was set to the 1029 * correct position? */ 1030 /* ### use something besides 500? */ 1031 return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0, status, 1032 "Could not seek to specified position in the " 1033 "resource."); 1034 } 1035 return NULL; 1036} 1037 1038 1039#if DEBUG_GET_HANDLER 1040 1041/* only define set_headers() and deliver() for debug purposes */ 1042 1043 1044static dav_error * dav_fs_set_headers(request_rec *r, 1045 const dav_resource *resource) 1046{ 1047 /* ### this function isn't really used since we have a get_pathname */ 1048 if (!resource->exists) 1049 return NULL; 1050 1051 /* make sure the proper mtime is in the request record */ 1052 ap_update_mtime(r, resource->info->finfo.mtime); 1053 1054 /* ### note that these use r->filename rather than <resource> */ 1055 ap_set_last_modified(r); 1056 ap_set_etag(r); 1057 1058 /* we accept byte-ranges */ 1059 ap_set_accept_ranges(r); 1060 1061 /* set up the Content-Length header */ 1062 ap_set_content_length(r, resource->info->finfo.size); 1063 1064 /* ### how to set the content type? */ 1065 /* ### until this is resolved, the Content-Type header is busted */ 1066 1067 return NULL; 1068} 1069 1070static dav_error * dav_fs_deliver(const dav_resource *resource, 1071 ap_filter_t *output) 1072{ 1073 apr_pool_t *pool = resource->pool; 1074 apr_bucket_brigade *bb; 1075 apr_file_t *fd; 1076 apr_status_t status; 1077 apr_bucket *bkt; 1078 1079 /* Check resource type */ 1080 if (resource->type != DAV_RESOURCE_TYPE_REGULAR 1081 && resource->type != DAV_RESOURCE_TYPE_VERSION 1082 && resource->type != DAV_RESOURCE_TYPE_WORKING) { 1083 return dav_new_error(pool, HTTP_CONFLICT, 0, 0, 1084 "Cannot GET this type of resource."); 1085 } 1086 if (resource->collection) { 1087 return dav_new_error(pool, HTTP_CONFLICT, 0, 0, 1088 "There is no default response to GET for a " 1089 "collection."); 1090 } 1091 1092 if ((status = apr_file_open(&fd, resource->info->pathname, 1093 APR_READ | APR_BINARY, 0, 1094 pool)) != APR_SUCCESS) { 1095 return dav_new_error(pool, HTTP_FORBIDDEN, 0, status, 1096 "File permissions deny server access."); 1097 } 1098 1099 bb = apr_brigade_create(pool, output->c->bucket_alloc); 1100 1101 apr_brigade_insert_file(bb, fd, 0, resource->info->finfo.size, pool); 1102 1103 bkt = apr_bucket_eos_create(output->c->bucket_alloc); 1104 APR_BRIGADE_INSERT_TAIL(bb, bkt); 1105 1106 if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) { 1107 return dav_new_error(pool, HTTP_FORBIDDEN, 0, status, 1108 "Could not write contents to filter."); 1109 } 1110 1111 return NULL; 1112} 1113 1114#endif /* DEBUG_GET_HANDLER */ 1115 1116 1117static dav_error * dav_fs_create_collection(dav_resource *resource) 1118{ 1119 dav_resource_private *ctx = resource->info; 1120 apr_status_t status; 1121 1122 status = apr_dir_make(ctx->pathname, APR_OS_DEFAULT, ctx->pool); 1123 if (APR_STATUS_IS_ENOSPC(status)) { 1124 return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0, status, 1125 "There is not enough storage to create " 1126 "this collection."); 1127 } 1128 else if (APR_STATUS_IS_ENOENT(status)) { 1129 return dav_new_error(ctx->pool, HTTP_CONFLICT, 0, status, 1130 "Cannot create collection; intermediate " 1131 "collection does not exist."); 1132 } 1133 else if (status != APR_SUCCESS) { 1134 /* ### refine this error message? */ 1135 return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, status, 1136 "Unable to create collection."); 1137 } 1138 1139 /* update resource state to show it exists as a collection */ 1140 resource->exists = 1; 1141 resource->collection = 1; 1142 1143 return NULL; 1144} 1145 1146static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres, 1147 int calltype) 1148{ 1149 apr_status_t status; 1150 dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx; 1151 dav_resource_private *srcinfo = wres->resource->info; 1152 dav_resource_private *dstinfo = ctx->res_dst->info; 1153 dav_error *err = NULL; 1154 1155 if (wres->resource->collection) { 1156 if (calltype == DAV_CALLTYPE_POSTFIX) { 1157 /* Postfix call for MOVE. delete the source dir. 1158 * Note: when copying, we do not enable the postfix-traversal. 1159 */ 1160 /* ### we are ignoring any error here; what should we do? */ 1161 (void) apr_dir_remove(srcinfo->pathname, ctx->pool); 1162 } 1163 else { 1164 /* copy/move of a collection. Create the new, target collection */ 1165 if ((status = apr_dir_make(dstinfo->pathname, APR_OS_DEFAULT, 1166 ctx->pool)) != APR_SUCCESS) { 1167 /* ### assume it was a permissions problem */ 1168 /* ### need a description here */ 1169 err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, status, NULL); 1170 } 1171 } 1172 } 1173 else { 1174 err = dav_fs_copymove_file(ctx->is_move, ctx->pool, 1175 srcinfo->pathname, dstinfo->pathname, 1176 &srcinfo->finfo, 1177 ctx->res_dst->exists ? &dstinfo->finfo : NULL, 1178 &ctx->work_buf); 1179 /* ### push a higher-level description? */ 1180 } 1181 1182 /* 1183 ** If we have a "not so bad" error, then it might need to go into a 1184 ** multistatus response. 1185 ** 1186 ** For a MOVE, it will always go into the multistatus. It could be 1187 ** that everything has been moved *except* for the root. Using a 1188 ** multistatus (with no errors for the other resources) will signify 1189 ** this condition. 1190 ** 1191 ** For a COPY, we are traversing in a prefix fashion. If the root fails, 1192 ** then we can just bail out now. 1193 */ 1194 if (err != NULL 1195 && !ap_is_HTTP_SERVER_ERROR(err->status) 1196 && (ctx->is_move 1197 || !dav_fs_is_same_resource(wres->resource, ctx->root))) { 1198 /* ### use errno to generate DAV:responsedescription? */ 1199 dav_add_response(wres, err->status, NULL); 1200 1201 /* the error is in the multistatus now. do not stop the traversal. */ 1202 return NULL; 1203 } 1204 1205 return err; 1206} 1207 1208static dav_error *dav_fs_copymove_resource( 1209 int is_move, 1210 const dav_resource *src, 1211 const dav_resource *dst, 1212 int depth, 1213 dav_response **response) 1214{ 1215 dav_error *err = NULL; 1216 dav_buffer work_buf = { 0 }; 1217 1218 *response = NULL; 1219 1220 /* if a collection, recursively copy/move it and its children, 1221 * including the state dirs 1222 */ 1223 if (src->collection) { 1224 dav_walk_params params = { 0 }; 1225 dav_response *multi_status; 1226 1227 params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN; 1228 params.func = dav_fs_copymove_walker; 1229 params.pool = src->info->pool; 1230 params.root = src; 1231 1232 /* params.walk_ctx is managed by dav_fs_internal_walk() */ 1233 1234 /* postfix is needed for MOVE to delete source dirs */ 1235 if (is_move) 1236 params.walk_type |= DAV_WALKTYPE_POSTFIX; 1237 1238 /* note that we return the error OR the multistatus. never both */ 1239 1240 if ((err = dav_fs_internal_walk(¶ms, depth, is_move, dst, 1241 &multi_status)) != NULL) { 1242 /* on a "real" error, then just punt. nothing else to do. */ 1243 return err; 1244 } 1245 1246 if ((*response = multi_status) != NULL) { 1247 /* some multistatus responses exist. wrap them in a 207 */ 1248 return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0, 0, 1249 "Error(s) occurred on some resources during " 1250 "the COPY/MOVE process."); 1251 } 1252 1253 return NULL; 1254 } 1255 1256 /* not a collection */ 1257 if ((err = dav_fs_copymove_file(is_move, src->info->pool, 1258 src->info->pathname, dst->info->pathname, 1259 &src->info->finfo, 1260 dst->exists ? &dst->info->finfo : NULL, 1261 &work_buf)) != NULL) { 1262 /* ### push a higher-level description? */ 1263 return err; 1264 } 1265 1266 /* copy/move properties as well */ 1267 return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf); 1268} 1269 1270static dav_error * dav_fs_copy_resource( 1271 const dav_resource *src, 1272 dav_resource *dst, 1273 int depth, 1274 dav_response **response) 1275{ 1276 dav_error *err; 1277 1278#if DAV_DEBUG 1279 if (src->hooks != dst->hooks) { 1280 /* 1281 ** ### strictly speaking, this is a design error; we should not 1282 ** ### have reached this point. 1283 */ 1284 return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, 1285 "DESIGN ERROR: a mix of repositories " 1286 "was passed to copy_resource."); 1287 } 1288#endif 1289 1290 if ((err = dav_fs_copymove_resource(0, src, dst, depth, 1291 response)) == NULL) { 1292 1293 /* update state of destination resource to show it exists */ 1294 dst->exists = 1; 1295 dst->collection = src->collection; 1296 } 1297 1298 return err; 1299} 1300 1301static dav_error * dav_fs_move_resource( 1302 dav_resource *src, 1303 dav_resource *dst, 1304 dav_response **response) 1305{ 1306 dav_resource_private *srcinfo = src->info; 1307 dav_resource_private *dstinfo = dst->info; 1308 dav_error *err; 1309 apr_status_t rv; 1310 1311#if DAV_DEBUG 1312 if (src->hooks != dst->hooks) { 1313 /* 1314 ** ### strictly speaking, this is a design error; we should not 1315 ** ### have reached this point. 1316 */ 1317 return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, 1318 "DESIGN ERROR: a mix of repositories " 1319 "was passed to move_resource."); 1320 } 1321#endif 1322 1323 1324 /* try rename first */ 1325 rv = apr_file_rename(srcinfo->pathname, dstinfo->pathname, srcinfo->pool); 1326 1327 /* if we can't simply rename, then do it the hard way... */ 1328 if (APR_STATUS_IS_EXDEV(rv)) { 1329 if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY, 1330 response)) == NULL) { 1331 /* update resource states */ 1332 dst->exists = 1; 1333 dst->collection = src->collection; 1334 src->exists = 0; 1335 src->collection = 0; 1336 } 1337 1338 return err; 1339 } 1340 1341 /* no multistatus response */ 1342 *response = NULL; 1343 1344 if (rv != APR_SUCCESS) { 1345 /* ### should have a better error than this. */ 1346 return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0, rv, 1347 "Could not rename resource."); 1348 } 1349 1350 /* Rename did work. Update resource states and move properties as well */ 1351 dst->exists = 1; 1352 dst->collection = src->collection; 1353 src->exists = 0; 1354 src->collection = 0; 1355 1356 if ((err = dav_fs_copymoveset(1, src->info->pool, 1357 src, dst, NULL)) == NULL) { 1358 /* no error. we're done. go ahead and return now. */ 1359 return NULL; 1360 } 1361 1362 /* error occurred during properties move; try to put resource back */ 1363 if (apr_file_rename(dstinfo->pathname, srcinfo->pathname, 1364 srcinfo->pool) != APR_SUCCESS) { 1365 /* couldn't put it back! */ 1366 return dav_push_error(srcinfo->pool, 1367 HTTP_INTERNAL_SERVER_ERROR, 0, 1368 "The resource was moved, but a failure " 1369 "occurred during the move of its " 1370 "properties. The resource could not be " 1371 "restored to its original location. The " 1372 "server is now in an inconsistent state.", 1373 err); 1374 } 1375 1376 /* update resource states again */ 1377 src->exists = 1; 1378 src->collection = dst->collection; 1379 dst->exists = 0; 1380 dst->collection = 0; 1381 1382 /* resource moved back, but properties may be inconsistent */ 1383 return dav_push_error(srcinfo->pool, 1384 HTTP_INTERNAL_SERVER_ERROR, 0, 1385 "The resource was moved, but a failure " 1386 "occurred during the move of its properties. " 1387 "The resource was moved back to its original " 1388 "location, but its properties may have been " 1389 "partially moved. The server may be in an " 1390 "inconsistent state.", 1391 err); 1392} 1393 1394static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype) 1395{ 1396 dav_resource_private *info = wres->resource->info; 1397 1398 /* do not attempt to remove a null resource, 1399 * or a collection with children 1400 */ 1401 if (wres->resource->exists && 1402 (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) { 1403 /* try to remove the resource */ 1404 apr_status_t result; 1405 1406 result = wres->resource->collection 1407 ? apr_dir_remove(info->pathname, wres->pool) 1408 : apr_file_remove(info->pathname, wres->pool); 1409 1410 /* 1411 ** If an error occurred, then add it to multistatus response. 1412 ** Note that we add it for the root resource, too. It is quite 1413 ** possible to delete the whole darn tree, yet fail on the root. 1414 ** 1415 ** (also: remember we are deleting via a postfix traversal) 1416 */ 1417 if (result != APR_SUCCESS) { 1418 /* ### assume there is a permissions problem */ 1419 1420 /* ### use errno to generate DAV:responsedescription? */ 1421 dav_add_response(wres, HTTP_FORBIDDEN, NULL); 1422 } 1423 } 1424 1425 return NULL; 1426} 1427 1428static dav_error * dav_fs_remove_resource(dav_resource *resource, 1429 dav_response **response) 1430{ 1431 apr_status_t status; 1432 dav_resource_private *info = resource->info; 1433 1434 *response = NULL; 1435 1436 /* if a collection, recursively remove it and its children, 1437 * including the state dirs 1438 */ 1439 if (resource->collection) { 1440 dav_walk_params params = { 0 }; 1441 dav_error *err = NULL; 1442 dav_response *multi_status; 1443 1444 params.walk_type = (DAV_WALKTYPE_NORMAL 1445 | DAV_WALKTYPE_HIDDEN 1446 | DAV_WALKTYPE_POSTFIX); 1447 params.func = dav_fs_delete_walker; 1448 params.pool = info->pool; 1449 params.root = resource; 1450 1451 if ((err = dav_fs_walk(¶ms, DAV_INFINITY, 1452 &multi_status)) != NULL) { 1453 /* on a "real" error, then just punt. nothing else to do. */ 1454 return err; 1455 } 1456 1457 if ((*response = multi_status) != NULL) { 1458 /* some multistatus responses exist. wrap them in a 207 */ 1459 return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0, 0, 1460 "Error(s) occurred on some resources during " 1461 "the deletion process."); 1462 } 1463 1464 /* no errors... update resource state */ 1465 resource->exists = 0; 1466 resource->collection = 0; 1467 1468 return NULL; 1469 } 1470 1471 /* not a collection; remove the file and its properties */ 1472 if ((status = apr_file_remove(info->pathname, info->pool)) != APR_SUCCESS) { 1473 /* ### put a description in here */ 1474 return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, status, NULL); 1475 } 1476 1477 /* update resource state */ 1478 resource->exists = 0; 1479 resource->collection = 0; 1480 1481 /* remove properties and return its result */ 1482 return dav_fs_deleteset(info->pool, resource); 1483} 1484 1485/* Take an unescaped path component and escape it and append it onto a 1486 * dav_buffer for a URI */ 1487static apr_size_t dav_fs_append_uri(apr_pool_t *p, dav_buffer *pbuf, 1488 const char *path, apr_size_t pad) 1489{ 1490 const char *epath = ap_escape_uri(p, path); 1491 apr_size_t epath_len = strlen(epath); 1492 1493 dav_buffer_place_mem(p, pbuf, epath, epath_len + 1, pad); 1494 return epath_len; 1495} 1496 1497/* ### move this to dav_util? */ 1498/* Walk recursively down through directories, * 1499 * including lock-null resources as we go. */ 1500static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth) 1501{ 1502 const dav_walk_params *params = fsctx->params; 1503 apr_pool_t *pool = params->pool; 1504 apr_status_t status; 1505 dav_error *err = NULL; 1506 int isdir = fsctx->res1.collection; 1507 apr_finfo_t dirent; 1508 apr_dir_t *dirp; 1509 1510 /* ensure the context is prepared properly, then call the func */ 1511 err = (*params->func)(&fsctx->wres, 1512 isdir 1513 ? DAV_CALLTYPE_COLLECTION 1514 : DAV_CALLTYPE_MEMBER); 1515 if (err != NULL) { 1516 return err; 1517 } 1518 1519 if (depth == 0 || !isdir) { 1520 return NULL; 1521 } 1522 1523 /* put a trailing slash onto the directory, in preparation for appending 1524 * files to it as we discovery them within the directory */ 1525 dav_check_bufsize(pool, &fsctx->path1, DAV_BUFFER_PAD); 1526 fsctx->path1.buf[fsctx->path1.cur_len++] = '/'; 1527 fsctx->path1.buf[fsctx->path1.cur_len] = '\0'; /* in pad area */ 1528 1529 /* if a secondary path is present, then do that, too */ 1530 if (fsctx->path2.buf != NULL) { 1531 dav_check_bufsize(pool, &fsctx->path2, DAV_BUFFER_PAD); 1532 fsctx->path2.buf[fsctx->path2.cur_len++] = '/'; 1533 fsctx->path2.buf[fsctx->path2.cur_len] = '\0'; /* in pad area */ 1534 } 1535 1536 /* Note: the URI should ALREADY have a trailing "/" */ 1537 1538 /* for this first pass of files, all resources exist */ 1539 fsctx->res1.exists = 1; 1540 1541 /* a file is the default; we'll adjust if we hit a directory */ 1542 fsctx->res1.collection = 0; 1543 fsctx->res2.collection = 0; 1544 1545 /* open and scan the directory */ 1546 if ((status = apr_dir_open(&dirp, fsctx->path1.buf, pool)) != APR_SUCCESS) { 1547 /* ### need a better error */ 1548 return dav_new_error(pool, HTTP_NOT_FOUND, 0, status, NULL); 1549 } 1550 while ((apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp)) == APR_SUCCESS) { 1551 apr_size_t len; 1552 apr_size_t escaped_len; 1553 1554 len = strlen(dirent.name); 1555 1556 /* avoid recursing into our current, parent, or state directories */ 1557 if (dirent.name[0] == '.' 1558 && (len == 1 || (dirent.name[1] == '.' && len == 2))) { 1559 continue; 1560 } 1561 1562 if (params->walk_type & DAV_WALKTYPE_AUTH) { 1563 /* ### need to authorize each file */ 1564 /* ### example: .htaccess is normally configured to fail auth */ 1565 1566 /* stuff in the state directory and temp files are never authorized! */ 1567 if (!strcmp(dirent.name, DAV_FS_STATE_DIR) || 1568 !strncmp(dirent.name, DAV_FS_TMP_PREFIX, 1569 strlen(DAV_FS_TMP_PREFIX))) { 1570 continue; 1571 } 1572 } 1573 /* skip the state dir and temp files unless a HIDDEN is performed */ 1574 if (!(params->walk_type & DAV_WALKTYPE_HIDDEN) 1575 && (!strcmp(dirent.name, DAV_FS_STATE_DIR) || 1576 !strncmp(dirent.name, DAV_FS_TMP_PREFIX, 1577 strlen(DAV_FS_TMP_PREFIX)))) { 1578 continue; 1579 } 1580 1581 /* append this file onto the path buffer (copy null term) */ 1582 dav_buffer_place_mem(pool, &fsctx->path1, dirent.name, len + 1, 0); 1583 1584 status = apr_stat(&fsctx->info1.finfo, fsctx->path1.buf, 1585 DAV_FINFO_MASK, pool); 1586 if (status != APR_SUCCESS && status != APR_INCOMPLETE) { 1587 /* woah! where'd it go? */ 1588 /* ### should have a better error here */ 1589 err = dav_new_error(pool, HTTP_NOT_FOUND, 0, status, NULL); 1590 break; 1591 } 1592 1593 /* copy the file to the URI, too. NOTE: we will pad an extra byte 1594 for the trailing slash later. */ 1595 escaped_len = dav_fs_append_uri(pool, &fsctx->uri_buf, dirent.name, 1); 1596 1597 /* if there is a secondary path, then do that, too */ 1598 if (fsctx->path2.buf != NULL) { 1599 dav_buffer_place_mem(pool, &fsctx->path2, dirent.name, len + 1, 0); 1600 } 1601 1602 /* set up the (internal) pathnames for the two resources */ 1603 fsctx->info1.pathname = fsctx->path1.buf; 1604 fsctx->info2.pathname = fsctx->path2.buf; 1605 1606 /* set up the URI for the current resource */ 1607 fsctx->res1.uri = fsctx->uri_buf.buf; 1608 1609 /* ### for now, only process regular files (e.g. skip symlinks) */ 1610 if (fsctx->info1.finfo.filetype == APR_REG) { 1611 /* call the function for the specified dir + file */ 1612 if ((err = (*params->func)(&fsctx->wres, 1613 DAV_CALLTYPE_MEMBER)) != NULL) { 1614 /* ### maybe add a higher-level description? */ 1615 break; 1616 } 1617 } 1618 else if (fsctx->info1.finfo.filetype == APR_DIR) { 1619 apr_size_t save_path_len = fsctx->path1.cur_len; 1620 apr_size_t save_uri_len = fsctx->uri_buf.cur_len; 1621 apr_size_t save_path2_len = fsctx->path2.cur_len; 1622 1623 /* adjust length to incorporate the subdir name */ 1624 fsctx->path1.cur_len += len; 1625 fsctx->path2.cur_len += len; 1626 1627 /* adjust URI length to incorporate subdir and a slash */ 1628 fsctx->uri_buf.cur_len += escaped_len + 1; 1629 fsctx->uri_buf.buf[fsctx->uri_buf.cur_len - 1] = '/'; 1630 fsctx->uri_buf.buf[fsctx->uri_buf.cur_len] = '\0'; 1631 1632 /* switch over to a collection */ 1633 fsctx->res1.collection = 1; 1634 fsctx->res2.collection = 1; 1635 1636 /* recurse on the subdir */ 1637 /* ### don't always want to quit on error from single child */ 1638 if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) { 1639 /* ### maybe add a higher-level description? */ 1640 break; 1641 } 1642 1643 /* put the various information back */ 1644 fsctx->path1.cur_len = save_path_len; 1645 fsctx->path2.cur_len = save_path2_len; 1646 fsctx->uri_buf.cur_len = save_uri_len; 1647 1648 fsctx->res1.collection = 0; 1649 fsctx->res2.collection = 0; 1650 1651 /* assert: res1.exists == 1 */ 1652 } 1653 } 1654 1655 /* ### check the return value of this? */ 1656 apr_dir_close(dirp); 1657 1658 if (err != NULL) 1659 return err; 1660 1661 if (params->walk_type & DAV_WALKTYPE_LOCKNULL) { 1662 apr_size_t offset = 0; 1663 1664 /* null terminate the directory name */ 1665 fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0'; 1666 1667 /* Include any lock null resources found in this collection */ 1668 fsctx->res1.collection = 1; 1669 if ((err = dav_fs_get_locknull_members(&fsctx->res1, 1670 &fsctx->locknull_buf)) != NULL) { 1671 /* ### maybe add a higher-level description? */ 1672 return err; 1673 } 1674 1675 /* put a slash back on the end of the directory */ 1676 fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/'; 1677 1678 /* these are all non-existant (files) */ 1679 fsctx->res1.exists = 0; 1680 fsctx->res1.collection = 0; 1681 memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo)); 1682 1683 while (offset < fsctx->locknull_buf.cur_len) { 1684 apr_size_t len = strlen(fsctx->locknull_buf.buf + offset); 1685 dav_lock *locks = NULL; 1686 1687 /* 1688 ** Append the locknull file to the paths and the URI. Note that 1689 ** we don't have to pad the URI for a slash since a locknull 1690 ** resource is not a collection. 1691 */ 1692 dav_buffer_place_mem(pool, &fsctx->path1, 1693 fsctx->locknull_buf.buf + offset, len + 1, 0); 1694 dav_fs_append_uri(pool, &fsctx->uri_buf, 1695 fsctx->locknull_buf.buf + offset, 0); 1696 if (fsctx->path2.buf != NULL) { 1697 dav_buffer_place_mem(pool, &fsctx->path2, 1698 fsctx->locknull_buf.buf + offset, 1699 len + 1, 0); 1700 } 1701 1702 /* set up the (internal) pathnames for the two resources */ 1703 fsctx->info1.pathname = fsctx->path1.buf; 1704 fsctx->info2.pathname = fsctx->path2.buf; 1705 1706 /* set up the URI for the current resource */ 1707 fsctx->res1.uri = fsctx->uri_buf.buf; 1708 1709 /* 1710 ** To prevent a PROPFIND showing an expired locknull 1711 ** resource, query the lock database to force removal 1712 ** of both the lock entry and .locknull, if necessary.. 1713 ** Sure, the query in PROPFIND would do this.. after 1714 ** the locknull resource was already included in the 1715 ** return. 1716 ** 1717 ** NOTE: we assume the caller has opened the lock database 1718 ** if they have provided DAV_WALKTYPE_LOCKNULL. 1719 */ 1720 /* ### we should also look into opening it read-only and 1721 ### eliding timed-out items from the walk, yet leaving 1722 ### them in the locknull database until somebody opens 1723 ### the thing writable. 1724 */ 1725 /* ### probably ought to use has_locks. note the problem 1726 ### mentioned above, though... we would traverse this as 1727 ### a locknull, but then a PROPFIND would load the lock 1728 ### info, causing a timeout and the locks would not be 1729 ### reported. Therefore, a null resource would be returned 1730 ### in the PROPFIND. 1731 ### 1732 ### alternative: just load unresolved locks. any direct 1733 ### locks will be timed out (correct). any indirect will 1734 ### not (correct; consider if a parent timed out -- the 1735 ### timeout routines do not walk and remove indirects; 1736 ### even the resolve func would probably fail when it 1737 ### tried to find a timed-out direct lock). 1738 */ 1739 if ((err = dav_lock_query(params->lockdb, &fsctx->res1, 1740 &locks)) != NULL) { 1741 /* ### maybe add a higher-level description? */ 1742 return err; 1743 } 1744 1745 /* call the function for the specified dir + file */ 1746 if (locks != NULL && 1747 (err = (*params->func)(&fsctx->wres, 1748 DAV_CALLTYPE_LOCKNULL)) != NULL) { 1749 /* ### maybe add a higher-level description? */ 1750 return err; 1751 } 1752 1753 offset += len + 1; 1754 } 1755 1756 /* reset the exists flag */ 1757 fsctx->res1.exists = 1; 1758 } 1759 1760 if (params->walk_type & DAV_WALKTYPE_POSTFIX) { 1761 /* replace the dirs' trailing slashes with null terms */ 1762 fsctx->path1.buf[--fsctx->path1.cur_len] = '\0'; 1763 fsctx->uri_buf.buf[--fsctx->uri_buf.cur_len] = '\0'; 1764 if (fsctx->path2.buf != NULL) { 1765 fsctx->path2.buf[--fsctx->path2.cur_len] = '\0'; 1766 } 1767 1768 /* this is a collection which exists */ 1769 fsctx->res1.collection = 1; 1770 1771 return (*params->func)(&fsctx->wres, DAV_CALLTYPE_POSTFIX); 1772 } 1773 1774 return NULL; 1775} 1776 1777static dav_error * dav_fs_internal_walk(const dav_walk_params *params, 1778 int depth, int is_move, 1779 const dav_resource *root_dst, 1780 dav_response **response) 1781{ 1782 dav_fs_walker_context fsctx = { 0 }; 1783 dav_error *err; 1784 dav_fs_copymove_walk_ctx cm_ctx = { 0 }; 1785 1786#if DAV_DEBUG 1787 if ((params->walk_type & DAV_WALKTYPE_LOCKNULL) != 0 1788 && params->lockdb == NULL) { 1789 return dav_new_error(params->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, 1790 "DESIGN ERROR: walker called to walk locknull " 1791 "resources, but a lockdb was not provided."); 1792 } 1793#endif 1794 1795 fsctx.params = params; 1796 fsctx.wres.walk_ctx = params->walk_ctx; 1797 fsctx.wres.pool = params->pool; 1798 1799 /* ### zero out versioned, working, baselined? */ 1800 1801 fsctx.res1 = *params->root; 1802 fsctx.res1.pool = params->pool; 1803 1804 fsctx.res1.info = &fsctx.info1; 1805 fsctx.info1 = *params->root->info; 1806 1807 /* the pathname is stored in the path1 buffer */ 1808 dav_buffer_init(params->pool, &fsctx.path1, fsctx.info1.pathname); 1809 fsctx.info1.pathname = fsctx.path1.buf; 1810 1811 if (root_dst != NULL) { 1812 /* internal call from the COPY/MOVE code. set it up. */ 1813 1814 fsctx.wres.walk_ctx = &cm_ctx; 1815 cm_ctx.is_move = is_move; 1816 cm_ctx.res_dst = &fsctx.res2; 1817 cm_ctx.root = params->root; 1818 cm_ctx.pool = params->pool; 1819 1820 fsctx.res2 = *root_dst; 1821 fsctx.res2.exists = 0; 1822 fsctx.res2.collection = 0; 1823 fsctx.res2.uri = NULL; /* we don't track this */ 1824 fsctx.res2.pool = params->pool; 1825 1826 fsctx.res2.info = &fsctx.info2; 1827 fsctx.info2 = *root_dst->info; 1828 1829 /* res2 does not exist -- clear its finfo structure */ 1830 memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo)); 1831 1832 /* the pathname is stored in the path2 buffer */ 1833 dav_buffer_init(params->pool, &fsctx.path2, fsctx.info2.pathname); 1834 fsctx.info2.pathname = fsctx.path2.buf; 1835 } 1836 1837 /* prep the URI buffer */ 1838 dav_buffer_init(params->pool, &fsctx.uri_buf, params->root->uri); 1839 1840 /* if we have a directory, then ensure the URI has a trailing "/" */ 1841 if (fsctx.res1.collection 1842 && fsctx.uri_buf.buf[fsctx.uri_buf.cur_len - 1] != '/') { 1843 1844 /* this will fall into the pad area */ 1845 fsctx.uri_buf.buf[fsctx.uri_buf.cur_len++] = '/'; 1846 fsctx.uri_buf.buf[fsctx.uri_buf.cur_len] = '\0'; 1847 } 1848 1849 /* the current resource's URI is stored in the uri_buf buffer */ 1850 fsctx.res1.uri = fsctx.uri_buf.buf; 1851 1852 /* point the callback's resource at our structure */ 1853 fsctx.wres.resource = &fsctx.res1; 1854 1855 /* always return the error, and any/all multistatus responses */ 1856 err = dav_fs_walker(&fsctx, depth); 1857 *response = fsctx.wres.response; 1858 return err; 1859} 1860 1861static dav_error * dav_fs_walk(const dav_walk_params *params, int depth, 1862 dav_response **response) 1863{ 1864 /* always return the error, and any/all multistatus responses */ 1865 return dav_fs_internal_walk(params, depth, 0, NULL, response); 1866} 1867 1868/* dav_fs_etag: Stolen from ap_make_etag. Creates a strong etag 1869 * for file path. 1870 * ### do we need to return weak tags sometimes? 1871 */ 1872static const char *dav_fs_getetag(const dav_resource *resource) 1873{ 1874 dav_resource_private *ctx = resource->info; 1875 /* XXX: This should really honor the FileETag setting */ 1876 1877 if (!resource->exists) 1878 return apr_pstrdup(ctx->pool, ""); 1879 1880 if (ctx->finfo.filetype != APR_NOFILE) { 1881 return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "-%" 1882 APR_UINT64_T_HEX_FMT "\"", 1883 (apr_uint64_t) ctx->finfo.size, 1884 (apr_uint64_t) ctx->finfo.mtime); 1885 } 1886 1887 return apr_psprintf(ctx->pool, "\"%" APR_UINT64_T_HEX_FMT "\"", 1888 (apr_uint64_t) ctx->finfo.mtime); 1889} 1890 1891static const dav_hooks_repository dav_hooks_repository_fs = 1892{ 1893 DEBUG_GET_HANDLER, /* normally: special GET handling not required */ 1894 dav_fs_get_resource, 1895 dav_fs_get_parent_resource, 1896 dav_fs_is_same_resource, 1897 dav_fs_is_parent_resource, 1898 dav_fs_open_stream, 1899 dav_fs_close_stream, 1900 dav_fs_write_stream, 1901 dav_fs_seek_stream, 1902#if DEBUG_GET_HANDLER 1903 dav_fs_set_headers, 1904 dav_fs_deliver, 1905#else 1906 NULL, 1907 NULL, 1908#endif 1909 dav_fs_create_collection, 1910 dav_fs_copy_resource, 1911 dav_fs_move_resource, 1912 dav_fs_remove_resource, 1913 dav_fs_walk, 1914 dav_fs_getetag, 1915 NULL, 1916 dav_fs_get_request_rec, 1917 dav_fs_pathname 1918}; 1919 1920static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource, 1921 int propid, dav_prop_insert what, 1922 apr_text_header *phdr) 1923{ 1924 const char *value; 1925 const char *s; 1926 apr_pool_t *p = resource->info->pool; 1927 const dav_liveprop_spec *info; 1928 int global_ns; 1929 1930 /* an HTTP-date can be 29 chars plus a null term */ 1931 /* a 64-bit size can be 20 chars plus a null term */ 1932 char buf[DAV_TIMEBUF_SIZE]; 1933 1934 /* 1935 ** None of FS provider properties are defined if the resource does not 1936 ** exist. Just bail for this case. 1937 ** 1938 ** Even though we state that the FS properties are not defined, the 1939 ** client cannot store dead values -- we deny that thru the is_writable 1940 ** hook function. 1941 */ 1942 if (!resource->exists) 1943 return DAV_PROP_INSERT_NOTDEF; 1944 1945 switch (propid) { 1946 case DAV_PROPID_creationdate: 1947 /* 1948 ** Closest thing to a creation date. since we don't actually 1949 ** perform the operations that would modify ctime (after we 1950 ** create the file), then we should be pretty safe here. 1951 */ 1952 dav_format_time(DAV_STYLE_ISO8601, 1953 resource->info->finfo.ctime, 1954 buf, sizeof(buf)); 1955 value = buf; 1956 break; 1957 1958 case DAV_PROPID_getcontentlength: 1959 /* our property, but not defined on collection resources */ 1960 if (resource->collection) 1961 return DAV_PROP_INSERT_NOTDEF; 1962 1963 apr_snprintf(buf, sizeof(buf), "%" APR_OFF_T_FMT, resource->info->finfo.size); 1964 value = buf; 1965 break; 1966 1967 case DAV_PROPID_getetag: 1968 value = dav_fs_getetag(resource); 1969 break; 1970 1971 case DAV_PROPID_getlastmodified: 1972 dav_format_time(DAV_STYLE_RFC822, 1973 resource->info->finfo.mtime, 1974 buf, sizeof(buf)); 1975 value = buf; 1976 break; 1977 1978 case DAV_PROPID_FS_executable: 1979 /* our property, but not defined on collection resources */ 1980 if (resource->collection) 1981 return DAV_PROP_INSERT_NOTDEF; 1982 1983 /* our property, but not defined on this platform */ 1984 if (!(resource->info->finfo.valid & APR_FINFO_UPROT)) 1985 return DAV_PROP_INSERT_NOTDEF; 1986 1987 /* the files are "ours" so we only need to check owner exec privs */ 1988 if (resource->info->finfo.protection & APR_UEXECUTE) 1989 value = "T"; 1990 else 1991 value = "F"; 1992 break; 1993 1994 default: 1995 /* ### what the heck was this property? */ 1996 return DAV_PROP_INSERT_NOTDEF; 1997 } 1998 1999 /* assert: value != NULL */ 2000 2001 /* get the information and global NS index for the property */ 2002 global_ns = dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info); 2003 2004 /* assert: info != NULL && info->name != NULL */ 2005 2006 /* DBG3("FS: inserting lp%d:%s (local %d)", ns, scan->name, scan->ns); */ 2007 2008 if (what == DAV_PROP_INSERT_VALUE) { 2009 s = apr_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR, 2010 global_ns, info->name, value, global_ns, info->name); 2011 } 2012 else if (what == DAV_PROP_INSERT_NAME) { 2013 s = apr_psprintf(p, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name); 2014 } 2015 else { 2016 /* assert: what == DAV_PROP_INSERT_SUPPORTED */ 2017 s = apr_psprintf(p, 2018 "<D:supported-live-property D:name=\"%s\" " 2019 "D:namespace=\"%s\"/>" DEBUG_CR, 2020 info->name, dav_fs_namespace_uris[info->ns]); 2021 } 2022 apr_text_append(p, phdr, s); 2023 2024 /* we inserted what was asked for */ 2025 return what; 2026} 2027 2028static int dav_fs_is_writable(const dav_resource *resource, int propid) 2029{ 2030 const dav_liveprop_spec *info; 2031 2032#ifdef DAV_FS_HAS_EXECUTABLE 2033 /* if we have the executable property, and this isn't a collection, 2034 then the property is writable. */ 2035 if (propid == DAV_PROPID_FS_executable && !resource->collection) 2036 return 1; 2037#endif 2038 2039 (void) dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info); 2040 return info->is_writable; 2041} 2042 2043static dav_error *dav_fs_patch_validate(const dav_resource *resource, 2044 const apr_xml_elem *elem, 2045 int operation, 2046 void **context, 2047 int *defer_to_dead) 2048{ 2049 const apr_text *cdata; 2050 const apr_text *f_cdata; 2051 char value; 2052 dav_elem_private *priv = elem->priv; 2053 2054 if (priv->propid != DAV_PROPID_FS_executable) { 2055 *defer_to_dead = 1; 2056 return NULL; 2057 } 2058 2059 if (operation == DAV_PROP_OP_DELETE) { 2060 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0, 2061 "The 'executable' property cannot be removed."); 2062 } 2063 2064 cdata = elem->first_cdata.first; 2065 2066 /* ### hmm. this isn't actually looking at all the possible text items */ 2067 f_cdata = elem->first_child == NULL 2068 ? NULL 2069 : elem->first_child->following_cdata.first; 2070 2071 /* DBG3("name=%s cdata=%s f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */ 2072 2073 if (cdata == NULL) { 2074 if (f_cdata == NULL) { 2075 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0, 2076 "The 'executable' property expects a single " 2077 "character, valued 'T' or 'F'. There was no " 2078 "value submitted."); 2079 } 2080 cdata = f_cdata; 2081 } 2082 else if (f_cdata != NULL) 2083 goto too_long; 2084 2085 if (cdata->next != NULL || strlen(cdata->text) != 1) 2086 goto too_long; 2087 2088 value = cdata->text[0]; 2089 if (value != 'T' && value != 'F') { 2090 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0, 2091 "The 'executable' property expects a single " 2092 "character, valued 'T' or 'F'. The value " 2093 "submitted is invalid."); 2094 } 2095 2096 *context = (void *)((long)(value == 'T')); 2097 2098 return NULL; 2099 2100 too_long: 2101 return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0, 0, 2102 "The 'executable' property expects a single " 2103 "character, valued 'T' or 'F'. The value submitted " 2104 "has too many characters."); 2105 2106} 2107 2108static dav_error *dav_fs_patch_exec(const dav_resource *resource, 2109 const apr_xml_elem *elem, 2110 int operation, 2111 void *context, 2112 dav_liveprop_rollback **rollback_ctx) 2113{ 2114 long value = context != NULL; 2115 apr_fileperms_t perms = resource->info->finfo.protection; 2116 apr_status_t status; 2117 long old_value = (perms & APR_UEXECUTE) != 0; 2118 2119 /* assert: prop == executable. operation == SET. */ 2120 2121 /* don't do anything if there is no change. no rollback info either. */ 2122 /* DBG2("new value=%d (old=%d)", value, old_value); */ 2123 if (value == old_value) 2124 return NULL; 2125 2126 perms &= ~APR_UEXECUTE; 2127 if (value) 2128 perms |= APR_UEXECUTE; 2129 2130 if ((status = apr_file_perms_set(resource->info->pathname, perms)) 2131 != APR_SUCCESS) { 2132 return dav_new_error(resource->info->pool, 2133 HTTP_INTERNAL_SERVER_ERROR, 0, status, 2134 "Could not set the executable flag of the " 2135 "target resource."); 2136 } 2137 2138 /* update the resource and set up the rollback context */ 2139 resource->info->finfo.protection = perms; 2140 *rollback_ctx = (dav_liveprop_rollback *)old_value; 2141 2142 return NULL; 2143} 2144 2145static void dav_fs_patch_commit(const dav_resource *resource, 2146 int operation, 2147 void *context, 2148 dav_liveprop_rollback *rollback_ctx) 2149{ 2150 /* nothing to do */ 2151} 2152 2153static dav_error *dav_fs_patch_rollback(const dav_resource *resource, 2154 int operation, 2155 void *context, 2156 dav_liveprop_rollback *rollback_ctx) 2157{ 2158 apr_fileperms_t perms = resource->info->finfo.protection & ~APR_UEXECUTE; 2159 apr_status_t status; 2160 int value = rollback_ctx != NULL; 2161 2162 /* assert: prop == executable. operation == SET. */ 2163 2164 /* restore the executable bit */ 2165 if (value) 2166 perms |= APR_UEXECUTE; 2167 2168 if ((status = apr_file_perms_set(resource->info->pathname, perms)) 2169 != APR_SUCCESS) { 2170 return dav_new_error(resource->info->pool, 2171 HTTP_INTERNAL_SERVER_ERROR, 0, status, 2172 "After a failure occurred, the resource's " 2173 "executable flag could not be restored."); 2174 } 2175 2176 /* restore the resource's state */ 2177 resource->info->finfo.protection = perms; 2178 2179 return NULL; 2180} 2181 2182 2183static const dav_hooks_liveprop dav_hooks_liveprop_fs = 2184{ 2185 dav_fs_insert_prop, 2186 dav_fs_is_writable, 2187 dav_fs_namespace_uris, 2188 dav_fs_patch_validate, 2189 dav_fs_patch_exec, 2190 dav_fs_patch_commit, 2191 dav_fs_patch_rollback 2192}; 2193 2194static const dav_provider dav_fs_provider = 2195{ 2196 &dav_hooks_repository_fs, 2197 &dav_hooks_db_dbm, 2198 &dav_hooks_locks_fs, 2199 NULL, /* vsn */ 2200 NULL, /* binding */ 2201 NULL, /* search */ 2202 2203 NULL /* ctx */ 2204}; 2205 2206void dav_fs_gather_propsets(apr_array_header_t *uris) 2207{ 2208#ifdef DAV_FS_HAS_EXECUTABLE 2209 *(const char **)apr_array_push(uris) = 2210 "<http://apache.org/dav/propset/fs/1>"; 2211#endif 2212} 2213 2214int dav_fs_find_liveprop(const dav_resource *resource, 2215 const char *ns_uri, const char *name, 2216 const dav_hooks_liveprop **hooks) 2217{ 2218 /* don't try to find any liveprops if this isn't "our" resource */ 2219 if (resource->hooks != &dav_hooks_repository_fs) 2220 return 0; 2221 return dav_do_find_liveprop(ns_uri, name, &dav_fs_liveprop_group, hooks); 2222} 2223 2224void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource, 2225 dav_prop_insert what, apr_text_header *phdr) 2226{ 2227 /* don't insert any liveprops if this isn't "our" resource */ 2228 if (resource->hooks != &dav_hooks_repository_fs) 2229 return; 2230 2231 if (!resource->exists) { 2232 /* a lock-null resource */ 2233 /* 2234 ** ### technically, we should insert empty properties. dunno offhand 2235 ** ### what part of the spec said this, but it was essentially thus: 2236 ** ### "the properties should be defined, but may have no value". 2237 */ 2238 return; 2239 } 2240 2241 (void) dav_fs_insert_prop(resource, DAV_PROPID_creationdate, 2242 what, phdr); 2243 (void) dav_fs_insert_prop(resource, DAV_PROPID_getcontentlength, 2244 what, phdr); 2245 (void) dav_fs_insert_prop(resource, DAV_PROPID_getlastmodified, 2246 what, phdr); 2247 (void) dav_fs_insert_prop(resource, DAV_PROPID_getetag, 2248 what, phdr); 2249 2250#ifdef DAV_FS_HAS_EXECUTABLE 2251 /* Only insert this property if it is defined for this platform. */ 2252 (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable, 2253 what, phdr); 2254#endif 2255 2256 /* ### we know the others aren't defined as liveprops */ 2257} 2258 2259void dav_fs_register(apr_pool_t *p) 2260{ 2261 /* register the namespace URIs */ 2262 dav_register_liveprop_group(p, &dav_fs_liveprop_group); 2263 2264 /* register the repository provider */ 2265 dav_register_provider(p, "filesystem", &dav_fs_provider); 2266} 2267