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** - Property database handling (repository-independent) 20** 21** NOTES: 22** 23** PROPERTY DATABASE 24** 25** This version assumes that there is a per-resource database provider 26** to record properties. The database provider decides how and where to 27** store these databases. 28** 29** The DBM keys for the properties have the following form: 30** 31** namespace ":" propname 32** 33** For example: 5:author 34** 35** The namespace provides an integer index into the namespace table 36** (see below). propname is simply the property name, without a namespace 37** prefix. 38** 39** A special case exists for properties that had a prefix starting with 40** "xml". The XML Specification reserves these for future use. mod_dav 41** stores and retrieves them unchanged. The keys for these properties 42** have the form: 43** 44** ":" propname 45** 46** The propname will contain the prefix and the property name. For 47** example, a key might be ":xmlfoo:name" 48** 49** The ":name" style will also be used for properties that do not 50** exist within a namespace. 51** 52** The DBM values consist of two null-terminated strings, appended 53** together (the null-terms are retained and stored in the database). 54** The first string is the xml:lang value for the property. An empty 55** string signifies that a lang value was not in context for the value. 56** The second string is the property value itself. 57** 58** 59** NAMESPACE TABLE 60** 61** The namespace table is an array that lists each of the namespaces 62** that are in use by the properties in the given propdb. Each entry 63** in the array is a simple URI. 64** 65** For example: http://www.foo.bar/standards/props/ 66** 67** The prefix used for the property is stripped and the URI for it 68** is entered into the namespace table. Also, any namespaces used 69** within the property value will be entered into the table (and 70** stripped from the child elements). 71** 72** The namespaces are stored in the DBM database under the "METADATA" key. 73** 74** 75** STRIPPING NAMESPACES 76** 77** Within the property values, the namespace declarations (xmlns...) 78** are stripped. Each element and attribute will have its prefix removed 79** and a new prefix inserted. 80** 81** This must be done so that we can return multiple properties in a 82** PROPFIND which may have (originally) used conflicting prefixes. For 83** that case, we must bind all property value elements to new namespace 84** values. 85** 86** This implies that clients must NOT be sensitive to the namespace 87** prefix used for their properties. It WILL change when the properties 88** are returned (we return them as "ns<index>", e.g. "ns5"). Also, the 89** property value can contain ONLY XML elements and CDATA. PI and comment 90** elements will be stripped. CDATA whitespace will be preserved, but 91** whitespace within element tags will be altered. Attribute ordering 92** may be altered. Element and CDATA ordering will be preserved. 93** 94** 95** ATTRIBUTES ON PROPERTY NAME ELEMENTS 96** 97** When getting/setting properties, the XML used looks like: 98** 99** <prop> 100** <propname1>value</propname1> 101** <propname2>value</propname1> 102** </prop> 103** 104** This implementation (mod_dav) DOES NOT save any attributes that are 105** associated with the <propname1> element. The property value is deemed 106** to be only the contents ("value" in the above example). 107** 108** We do store the xml:lang value (if any) that applies to the context 109** of the <propname1> element. Whether the xml:lang attribute is on 110** <propname1> itself, or from a higher level element, we will store it 111** with the property value. 112** 113** 114** VERSIONING 115** 116** The DBM db contains a key named "METADATA" that holds database-level 117** information, such as the namespace table. The record also contains the 118** db's version number as the very first 16-bit value. This first number 119** is actually stored as two single bytes: the first byte is a "major" 120** version number. The second byte is a "minor" number. 121** 122** If the major number is not what mod_dav expects, then the db is closed 123** immediately and an error is returned. A minor number change is 124** acceptable -- it is presumed that old/new dav_props.c can deal with 125** the database format. For example, a newer dav_props might update the 126** minor value and append information to the end of the metadata record 127** (which would be ignored by previous versions). 128** 129** 130** ISSUES: 131** 132** At the moment, for the dav_get_allprops() and dav_get_props() functions, 133** we must return a set of xmlns: declarations for ALL known namespaces 134** in the file. There isn't a way to filter this because we don't know 135** which are going to be used or not. Examining property names is not 136** sufficient because the property values could use entirely different 137** namespaces. 138** 139** ==> we must devise a scheme where we can "garbage collect" the namespace 140** entries from the property database. 141*/ 142 143#include "apr.h" 144#include "apr_strings.h" 145 146#define APR_WANT_STDIO 147#define APR_WANT_BYTEFUNC 148#include "apr_want.h" 149 150#include "mod_dav.h" 151 152#include "http_log.h" 153#include "http_request.h" 154 155/* 156** There is some rough support for writable DAV:getcontenttype and 157** DAV:getcontentlanguage properties. If this #define is (1), then 158** this support is disabled. 159** 160** We are disabling it because of a lack of support in GET and PUT 161** operations. For GET, it would be "expensive" to look for a propdb, 162** open it, and attempt to extract the Content-Type and Content-Language 163** values for the response. 164** (Handling the PUT would not be difficult, though) 165*/ 166#define DAV_DISABLE_WRITABLE_PROPS 1 167 168#define DAV_EMPTY_VALUE "\0" /* TWO null terms */ 169 170struct dav_propdb { 171 apr_pool_t *p; /* the pool we should use */ 172 request_rec *r; /* the request record */ 173 174 const dav_resource *resource; /* the target resource */ 175 176 int deferred; /* open of db has been deferred */ 177 dav_db *db; /* underlying database containing props */ 178 179 apr_array_header_t *ns_xlate; /* translation of an elem->ns to URI */ 180 dav_namespace_map *mapping; /* namespace mapping */ 181 182 dav_lockdb *lockdb; /* the lock database */ 183 184 dav_buffer wb_lock; /* work buffer for lockdiscovery property */ 185 186 /* if we ever run a GET subreq, it will be stored here */ 187 request_rec *subreq; 188 189 /* hooks we should use for processing (based on the target resource) */ 190 const dav_hooks_db *db_hooks; 191}; 192 193/* NOTE: dav_core_props[] and the following enum must stay in sync. */ 194/* ### move these into a "core" liveprop provider? */ 195static const char * const dav_core_props[] = 196{ 197 "getcontenttype", 198 "getcontentlanguage", 199 "lockdiscovery", 200 "supportedlock", 201 202 NULL /* sentinel */ 203}; 204enum { 205 DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE, 206 DAV_PROPID_CORE_getcontentlanguage, 207 DAV_PROPID_CORE_lockdiscovery, 208 DAV_PROPID_CORE_supportedlock, 209 210 DAV_PROPID_CORE_UNKNOWN 211}; 212 213/* 214** This structure is used to track information needed for a rollback. 215*/ 216typedef struct dav_rollback_item { 217 /* select one of the two rollback context structures based on the 218 value of dav_prop_ctx.is_liveprop */ 219 dav_deadprop_rollback *deadprop; 220 dav_liveprop_rollback *liveprop; 221 222} dav_rollback_item; 223 224 225static int dav_find_liveprop_provider(dav_propdb *propdb, 226 const char *ns_uri, 227 const char *propname, 228 const dav_hooks_liveprop **provider) 229{ 230 int propid; 231 232 *provider = NULL; 233 234 if (ns_uri == NULL) { 235 /* policy: liveprop providers cannot define no-namespace properties */ 236 return DAV_PROPID_CORE_UNKNOWN; 237 } 238 239 /* check liveprop providers first, so they can define core properties */ 240 propid = dav_run_find_liveprop(propdb->resource, ns_uri, propname, 241 provider); 242 if (propid != 0) { 243 return propid; 244 } 245 246 /* check for core property */ 247 if (strcmp(ns_uri, "DAV:") == 0) { 248 const char * const *p = dav_core_props; 249 250 for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid) 251 if (strcmp(propname, *p) == 0) { 252 return propid; 253 } 254 } 255 256 /* no provider for this property */ 257 return DAV_PROPID_CORE_UNKNOWN; 258} 259 260static void dav_find_liveprop(dav_propdb *propdb, apr_xml_elem *elem) 261{ 262 const char *ns_uri; 263 dav_elem_private *priv = elem->priv; 264 const dav_hooks_liveprop *hooks; 265 266 267 if (elem->ns == APR_XML_NS_NONE) 268 ns_uri = NULL; 269 else if (elem->ns == APR_XML_NS_DAV_ID) 270 ns_uri = "DAV:"; 271 else 272 ns_uri = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns); 273 274 priv->propid = dav_find_liveprop_provider(propdb, ns_uri, elem->name, 275 &hooks); 276 277 /* ### this test seems redundant... */ 278 if (priv->propid != DAV_PROPID_CORE_UNKNOWN) { 279 priv->provider = hooks; 280 } 281} 282 283/* is the live property read/write? */ 284static int dav_rw_liveprop(dav_propdb *propdb, dav_elem_private *priv) 285{ 286 int propid = priv->propid; 287 288 /* 289 ** Check the liveprop provider (if this is a provider-defined prop) 290 */ 291 if (priv->provider != NULL) { 292 return (*priv->provider->is_writable)(propdb->resource, propid); 293 } 294 295 /* these are defined as read-only */ 296 if (propid == DAV_PROPID_CORE_lockdiscovery 297#if DAV_DISABLE_WRITABLE_PROPS 298 || propid == DAV_PROPID_CORE_getcontenttype 299 || propid == DAV_PROPID_CORE_getcontentlanguage 300#endif 301 || propid == DAV_PROPID_CORE_supportedlock 302 ) { 303 304 return 0; 305 } 306 307 /* these are defined as read/write */ 308 if (propid == DAV_PROPID_CORE_getcontenttype 309 || propid == DAV_PROPID_CORE_getcontentlanguage 310 || propid == DAV_PROPID_CORE_UNKNOWN) { 311 312 return 1; 313 } 314 315 /* 316 ** We don't recognize the property, so it must be dead (and writable) 317 */ 318 return 1; 319} 320 321/* do a sub-request to fetch properties for the target resource's URI. */ 322static void dav_do_prop_subreq(dav_propdb *propdb) 323{ 324 /* perform a "GET" on the resource's URI (note that the resource 325 may not correspond to the current request!). */ 326 propdb->subreq = ap_sub_req_lookup_uri(propdb->resource->uri, propdb->r, 327 NULL); 328} 329 330static dav_error * dav_insert_coreprop(dav_propdb *propdb, 331 int propid, const char *name, 332 dav_prop_insert what, 333 apr_text_header *phdr, 334 dav_prop_insert *inserted) 335{ 336 const char *value = NULL; 337 dav_error *err; 338 339 *inserted = DAV_PROP_INSERT_NOTDEF; 340 341 /* fast-path the common case */ 342 if (propid == DAV_PROPID_CORE_UNKNOWN) 343 return NULL; 344 345 switch (propid) { 346 347 case DAV_PROPID_CORE_lockdiscovery: 348 if (propdb->lockdb != NULL) { 349 dav_lock *locks; 350 351 if ((err = dav_lock_query(propdb->lockdb, propdb->resource, 352 &locks)) != NULL) { 353 return dav_push_error(propdb->p, err->status, 0, 354 "DAV:lockdiscovery could not be " 355 "determined due to a problem fetching " 356 "the locks for this resource.", 357 err); 358 } 359 360 /* fast-path the no-locks case */ 361 if (locks == NULL) { 362 value = ""; 363 } 364 else { 365 /* 366 ** This may modify the buffer. value may point to 367 ** wb_lock.pbuf or a string constant. 368 */ 369 value = dav_lock_get_activelock(propdb->r, locks, 370 &propdb->wb_lock); 371 372 /* make a copy to isolate it from changes to wb_lock */ 373 value = apr_pstrdup(propdb->p, propdb->wb_lock.buf); 374 } 375 } 376 break; 377 378 case DAV_PROPID_CORE_supportedlock: 379 if (propdb->lockdb != NULL) { 380 value = (*propdb->lockdb->hooks->get_supportedlock)(propdb->resource); 381 } 382 break; 383 384 case DAV_PROPID_CORE_getcontenttype: 385 if (propdb->subreq == NULL) { 386 dav_do_prop_subreq(propdb); 387 } 388 if (propdb->subreq->content_type != NULL) { 389 value = propdb->subreq->content_type; 390 } 391 break; 392 393 case DAV_PROPID_CORE_getcontentlanguage: 394 { 395 const char *lang; 396 397 if (propdb->subreq == NULL) { 398 dav_do_prop_subreq(propdb); 399 } 400 if ((lang = apr_table_get(propdb->subreq->headers_out, 401 "Content-Language")) != NULL) { 402 value = lang; 403 } 404 break; 405 } 406 407 default: 408 /* fall through to interpret as a dead property */ 409 break; 410 } 411 412 /* if something was supplied, then insert it */ 413 if (value != NULL) { 414 const char *s; 415 416 if (what == DAV_PROP_INSERT_SUPPORTED) { 417 /* use D: prefix to refer to the DAV: namespace URI, 418 * and let the namespace attribute default to "DAV:" 419 */ 420 s = apr_psprintf(propdb->p, 421 "<D:supported-live-property D:name=\"%s\"/>" DEBUG_CR, 422 name); 423 } 424 else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') { 425 /* use D: prefix to refer to the DAV: namespace URI */ 426 s = apr_psprintf(propdb->p, "<D:%s>%s</D:%s>" DEBUG_CR, 427 name, value, name); 428 } 429 else { 430 /* use D: prefix to refer to the DAV: namespace URI */ 431 s = apr_psprintf(propdb->p, "<D:%s/>" DEBUG_CR, name); 432 } 433 apr_text_append(propdb->p, phdr, s); 434 435 *inserted = what; 436 } 437 438 return NULL; 439} 440 441static dav_error * dav_insert_liveprop(dav_propdb *propdb, 442 const apr_xml_elem *elem, 443 dav_prop_insert what, 444 apr_text_header *phdr, 445 dav_prop_insert *inserted) 446{ 447 dav_elem_private *priv = elem->priv; 448 449 *inserted = DAV_PROP_INSERT_NOTDEF; 450 451 if (priv->provider == NULL) { 452 /* this is a "core" property that we define */ 453 return dav_insert_coreprop(propdb, priv->propid, elem->name, 454 what, phdr, inserted); 455 } 456 457 /* ask the provider (that defined this prop) to insert the prop */ 458 *inserted = (*priv->provider->insert_prop)(propdb->resource, priv->propid, 459 what, phdr); 460 461 return NULL; 462} 463 464static void dav_output_prop_name(apr_pool_t *pool, 465 const dav_prop_name *name, 466 dav_xmlns_info *xi, 467 apr_text_header *phdr) 468{ 469 const char *s; 470 471 if (*name->ns == '\0') 472 s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name->name); 473 else { 474 const char *prefix = dav_xmlns_add_uri(xi, name->ns); 475 476 s = apr_psprintf(pool, "<%s:%s/>" DEBUG_CR, prefix, name->name); 477 } 478 479 apr_text_append(pool, phdr, s); 480} 481 482static void dav_insert_xmlns(apr_pool_t *p, const char *pre_prefix, long ns, 483 const char *ns_uri, apr_text_header *phdr) 484{ 485 const char *s; 486 487 s = apr_psprintf(p, " xmlns:%s%ld=\"%s\"", pre_prefix, ns, ns_uri); 488 apr_text_append(p, phdr, s); 489} 490 491static dav_error *dav_really_open_db(dav_propdb *propdb, int ro) 492{ 493 dav_error *err; 494 495 /* we're trying to open the db; turn off the 'deferred' flag */ 496 propdb->deferred = 0; 497 498 /* ask the DB provider to open the thing */ 499 err = (*propdb->db_hooks->open)(propdb->p, propdb->resource, ro, 500 &propdb->db); 501 if (err != NULL) { 502 return dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 503 DAV_ERR_PROP_OPENING, 504 "Could not open the property database.", 505 err); 506 } 507 508 /* 509 ** NOTE: propdb->db could be NULL if we attempted to open a readonly 510 ** database that doesn't exist. If we require read/write 511 ** access, then a database was created and opened. 512 */ 513 514 return NULL; 515} 516 517DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb, 518 const dav_resource *resource, 519 int ro, 520 apr_array_header_t * ns_xlate, 521 dav_propdb **p_propdb) 522{ 523 dav_propdb *propdb = apr_pcalloc(r->pool, sizeof(*propdb)); 524 525 *p_propdb = NULL; 526 527#if DAV_DEBUG 528 if (resource->uri == NULL) { 529 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 530 "INTERNAL DESIGN ERROR: resource must define " 531 "its URI."); 532 } 533#endif 534 535 propdb->r = r; 536 apr_pool_create(&propdb->p, r->pool); 537 propdb->resource = resource; 538 propdb->ns_xlate = ns_xlate; 539 540 propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r); 541 542 propdb->lockdb = lockdb; 543 544 /* always defer actual open, to avoid expense of accessing db 545 * when only live properties are involved 546 */ 547 propdb->deferred = 1; 548 549 /* ### what to do about closing the propdb on server failure? */ 550 551 *p_propdb = propdb; 552 return NULL; 553} 554 555DAV_DECLARE(void) dav_close_propdb(dav_propdb *propdb) 556{ 557 if (propdb->db != NULL) { 558 (*propdb->db_hooks->close)(propdb->db); 559 } 560 561 /* Currently, mod_dav's pool usage doesn't allow clearing this pool. */ 562#if 0 563 apr_pool_destroy(propdb->p); 564#endif 565 return; 566} 567 568DAV_DECLARE(dav_get_props_result) dav_get_allprops(dav_propdb *propdb, 569 dav_prop_insert what) 570{ 571 const dav_hooks_db *db_hooks = propdb->db_hooks; 572 apr_text_header hdr = { 0 }; 573 apr_text_header hdr_ns = { 0 }; 574 dav_get_props_result result = { 0 }; 575 int found_contenttype = 0; 576 int found_contentlang = 0; 577 dav_prop_insert unused_inserted; 578 579 /* if not just getting supported live properties, 580 * scan all properties in the dead prop database 581 */ 582 if (what != DAV_PROP_INSERT_SUPPORTED) { 583 if (propdb->deferred) { 584 /* ### what to do with db open error? */ 585 (void) dav_really_open_db(propdb, 1 /*ro*/); 586 } 587 588 /* initialize the result with some start tags... */ 589 apr_text_append(propdb->p, &hdr, 590 "<D:propstat>" DEBUG_CR 591 "<D:prop>" DEBUG_CR); 592 593 /* if there ARE properties, then scan them */ 594 if (propdb->db != NULL) { 595 dav_xmlns_info *xi = dav_xmlns_create(propdb->p); 596 dav_prop_name name; 597 dav_error *err; 598 599 /* define (up front) any namespaces the db might need */ 600 (void) (*db_hooks->define_namespaces)(propdb->db, xi); 601 602 /* get the first property name, beginning the scan */ 603 err = (*db_hooks->first_name)(propdb->db, &name); 604 while (!err && name.ns) { 605 606 /* 607 ** We also look for <DAV:getcontenttype> and 608 ** <DAV:getcontentlanguage>. If they are not stored as dead 609 ** properties, then we need to perform a subrequest to get 610 ** their values (if any). 611 */ 612 if (*name.ns == 'D' && strcmp(name.ns, "DAV:") == 0 613 && *name.name == 'g') { 614 if (strcmp(name.name, "getcontenttype") == 0) { 615 found_contenttype = 1; 616 } 617 else if (strcmp(name.name, "getcontentlanguage") == 0) { 618 found_contentlang = 1; 619 } 620 } 621 622 if (what == DAV_PROP_INSERT_VALUE) { 623 int found; 624 625 if ((err = (*db_hooks->output_value)(propdb->db, &name, 626 xi, &hdr, 627 &found)) != NULL) { 628 /* ### anything better to do? */ 629 /* ### probably should enter a 500 error */ 630 goto next_key; 631 } 632 /* assert: found == 1 */ 633 } 634 else { 635 /* the value was not requested, so just add an empty 636 tag specifying the property name. */ 637 dav_output_prop_name(propdb->p, &name, xi, &hdr); 638 } 639 640 next_key: 641 err = (*db_hooks->next_name)(propdb->db, &name); 642 } 643 644 /* all namespaces have been entered into xi. generate them into 645 the output now. */ 646 dav_xmlns_generate(xi, &hdr_ns); 647 648 } /* propdb->db != NULL */ 649 650 /* add namespaces for all the liveprop providers */ 651 dav_add_all_liveprop_xmlns(propdb->p, &hdr_ns); 652 } 653 654 /* ask the liveprop providers to insert their properties */ 655 dav_run_insert_all_liveprops(propdb->r, propdb->resource, what, &hdr); 656 657 /* insert the standard properties */ 658 /* ### should be handling the return errors here */ 659 (void)dav_insert_coreprop(propdb, 660 DAV_PROPID_CORE_supportedlock, "supportedlock", 661 what, &hdr, &unused_inserted); 662 (void)dav_insert_coreprop(propdb, 663 DAV_PROPID_CORE_lockdiscovery, "lockdiscovery", 664 what, &hdr, &unused_inserted); 665 666 /* if we didn't find these, then do the whole subreq thing. */ 667 if (!found_contenttype) { 668 /* ### should be handling the return error here */ 669 (void)dav_insert_coreprop(propdb, 670 DAV_PROPID_CORE_getcontenttype, 671 "getcontenttype", 672 what, &hdr, &unused_inserted); 673 } 674 if (!found_contentlang) { 675 /* ### should be handling the return error here */ 676 (void)dav_insert_coreprop(propdb, 677 DAV_PROPID_CORE_getcontentlanguage, 678 "getcontentlanguage", 679 what, &hdr, &unused_inserted); 680 } 681 682 /* if not just reporting on supported live props, 683 * terminate the result */ 684 if (what != DAV_PROP_INSERT_SUPPORTED) { 685 apr_text_append(propdb->p, &hdr, 686 "</D:prop>" DEBUG_CR 687 "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR 688 "</D:propstat>" DEBUG_CR); 689 } 690 691 result.propstats = hdr.first; 692 result.xmlns = hdr_ns.first; 693 return result; 694} 695 696DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb, 697 apr_xml_doc *doc) 698{ 699 const dav_hooks_db *db_hooks = propdb->db_hooks; 700 apr_xml_elem *elem = dav_find_child(doc->root, "prop"); 701 apr_text_header hdr_good = { 0 }; 702 apr_text_header hdr_bad = { 0 }; 703 apr_text_header hdr_ns = { 0 }; 704 int have_good = 0; 705 dav_get_props_result result = { 0 }; 706 char *marks_liveprop; 707 dav_xmlns_info *xi; 708 int xi_filled = 0; 709 710 /* ### NOTE: we should pass in TWO buffers -- one for keys, one for 711 the marks */ 712 713 /* we will ALWAYS provide a "good" result, even if it is EMPTY */ 714 apr_text_append(propdb->p, &hdr_good, 715 "<D:propstat>" DEBUG_CR 716 "<D:prop>" DEBUG_CR); 717 718 /* ### the marks should be in a buffer! */ 719 /* allocate zeroed-memory for the marks. These marks indicate which 720 liveprop namespaces we've generated into the output xmlns buffer */ 721 722 /* same for the liveprops */ 723 marks_liveprop = apr_pcalloc(propdb->p, dav_get_liveprop_ns_count() + 1); 724 725 xi = dav_xmlns_create(propdb->p); 726 727 for (elem = elem->first_child; elem; elem = elem->next) { 728 dav_elem_private *priv; 729 dav_error *err; 730 dav_prop_insert inserted; 731 dav_prop_name name; 732 733 /* 734 ** First try live property providers; if they don't handle 735 ** the property, then try looking it up in the propdb. 736 */ 737 738 if (elem->priv == NULL) { 739 elem->priv = apr_pcalloc(propdb->p, sizeof(*priv)); 740 } 741 priv = elem->priv; 742 743 /* cache the propid; dav_get_props() could be called many times */ 744 if (priv->propid == 0) 745 dav_find_liveprop(propdb, elem); 746 747 if (priv->propid != DAV_PROPID_CORE_UNKNOWN) { 748 749 /* insert the property. returns 1 if an insertion was done. */ 750 if ((err = dav_insert_liveprop(propdb, elem, DAV_PROP_INSERT_VALUE, 751 &hdr_good, &inserted)) != NULL) { 752 /* ### need to propagate the error to the caller... */ 753 /* ### skip it for now, as if nothing was inserted */ 754 } 755 if (inserted == DAV_PROP_INSERT_VALUE) { 756 have_good = 1; 757 758 /* 759 ** Add the liveprop's namespace URIs. Note that provider==NULL 760 ** for core properties. 761 */ 762 if (priv->provider != NULL) { 763 const char * const * scan_ns_uri; 764 765 for (scan_ns_uri = priv->provider->namespace_uris; 766 *scan_ns_uri != NULL; 767 ++scan_ns_uri) { 768 long ns; 769 770 ns = dav_get_liveprop_ns_index(*scan_ns_uri); 771 if (marks_liveprop[ns]) 772 continue; 773 marks_liveprop[ns] = 1; 774 775 dav_insert_xmlns(propdb->p, "lp", ns, *scan_ns_uri, 776 &hdr_ns); 777 } 778 } 779 780 /* property added. move on to the next property. */ 781 continue; 782 } 783 else if (inserted == DAV_PROP_INSERT_NOTDEF) { 784 /* nothing to do. fall thru to allow property to be handled 785 as a dead property */ 786 } 787#if DAV_DEBUG 788 else { 789#if 0 790 /* ### need to change signature to return an error */ 791 return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 0, 792 "INTERNAL DESIGN ERROR: insert_liveprop " 793 "did not insert what was asked for."); 794#endif 795 } 796#endif 797 } 798 799 /* The property wasn't a live property, so look in the dead property 800 database. */ 801 802 /* make sure propdb is really open */ 803 if (propdb->deferred) { 804 /* ### what to do with db open error? */ 805 (void) dav_really_open_db(propdb, 1 /*ro*/); 806 } 807 808 if (elem->ns == APR_XML_NS_NONE) 809 name.ns = ""; 810 else 811 name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns); 812 name.name = elem->name; 813 814 /* only bother to look if a database exists */ 815 if (propdb->db != NULL) { 816 int found; 817 818 if ((err = (*db_hooks->output_value)(propdb->db, &name, 819 xi, &hdr_good, 820 &found)) != NULL) { 821 /* ### what to do? continue doesn't seem right... */ 822 continue; 823 } 824 825 if (found) { 826 have_good = 1; 827 828 /* if we haven't added the db's namespaces, then do so... */ 829 if (!xi_filled) { 830 (void) (*db_hooks->define_namespaces)(propdb->db, xi); 831 xi_filled = 1; 832 } 833 continue; 834 } 835 } 836 837 /* not found as a live OR dead property. add a record to the "bad" 838 propstats */ 839 840 /* make sure we've started our "bad" propstat */ 841 if (hdr_bad.first == NULL) { 842 apr_text_append(propdb->p, &hdr_bad, 843 "<D:propstat>" DEBUG_CR 844 "<D:prop>" DEBUG_CR); 845 } 846 847 /* output this property's name (into the bad propstats) */ 848 dav_output_prop_name(propdb->p, &name, xi, &hdr_bad); 849 } 850 851 apr_text_append(propdb->p, &hdr_good, 852 "</D:prop>" DEBUG_CR 853 "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR 854 "</D:propstat>" DEBUG_CR); 855 856 /* default to start with the good */ 857 result.propstats = hdr_good.first; 858 859 /* we may not have any "bad" results */ 860 if (hdr_bad.first != NULL) { 861 /* "close" the bad propstat */ 862 apr_text_append(propdb->p, &hdr_bad, 863 "</D:prop>" DEBUG_CR 864 "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR 865 "</D:propstat>" DEBUG_CR); 866 867 /* if there are no good props, then just return the bad */ 868 if (!have_good) { 869 result.propstats = hdr_bad.first; 870 } 871 else { 872 /* hook the bad propstat to the end of the good one */ 873 hdr_good.last->next = hdr_bad.first; 874 } 875 } 876 877 /* add in all the various namespaces, and return them */ 878 dav_xmlns_generate(xi, &hdr_ns); 879 result.xmlns = hdr_ns.first; 880 881 return result; 882} 883 884DAV_DECLARE(void) dav_get_liveprop_supported(dav_propdb *propdb, 885 const char *ns_uri, 886 const char *propname, 887 apr_text_header *body) 888{ 889 int propid; 890 const dav_hooks_liveprop *hooks; 891 892 propid = dav_find_liveprop_provider(propdb, ns_uri, propname, &hooks); 893 894 if (propid != DAV_PROPID_CORE_UNKNOWN) { 895 if (hooks == NULL) { 896 /* this is a "core" property that we define */ 897 dav_prop_insert unused_inserted; 898 dav_insert_coreprop(propdb, propid, propname, 899 DAV_PROP_INSERT_SUPPORTED, body, &unused_inserted); 900 } 901 else { 902 (*hooks->insert_prop)(propdb->resource, propid, 903 DAV_PROP_INSERT_SUPPORTED, body); 904 } 905 } 906} 907 908DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx) 909{ 910 dav_propdb *propdb = ctx->propdb; 911 apr_xml_elem *prop = ctx->prop; 912 dav_elem_private *priv; 913 914 priv = ctx->prop->priv = apr_pcalloc(propdb->p, sizeof(*priv)); 915 916 /* 917 ** Check to see if this is a live property, and fill the fields 918 ** in the XML elem, as appropriate. 919 ** 920 ** Verify that the property is read/write. If not, then it cannot 921 ** be SET or DELETEd. 922 */ 923 if (priv->propid == 0) { 924 dav_find_liveprop(propdb, prop); 925 926 /* it's a liveprop if a provider was found */ 927 /* ### actually the "core" props should really be liveprops, but 928 ### there is no "provider" for those and the r/w props are 929 ### treated as dead props anyhow */ 930 ctx->is_liveprop = priv->provider != NULL; 931 } 932 933 if (!dav_rw_liveprop(propdb, priv)) { 934 ctx->err = dav_new_error(propdb->p, HTTP_CONFLICT, 935 DAV_ERR_PROP_READONLY, 936 "Property is read-only."); 937 return; 938 } 939 940 if (ctx->is_liveprop) { 941 int defer_to_dead = 0; 942 943 ctx->err = (*priv->provider->patch_validate)(propdb->resource, 944 prop, ctx->operation, 945 &ctx->liveprop_ctx, 946 &defer_to_dead); 947 if (ctx->err != NULL || !defer_to_dead) 948 return; 949 950 /* clear is_liveprop -- act as a dead prop now */ 951 ctx->is_liveprop = 0; 952 } 953 954 /* 955 ** The property is supposed to be stored into the dead-property 956 ** database. Make sure the thing is truly open (and writable). 957 */ 958 if (propdb->deferred 959 && (ctx->err = dav_really_open_db(propdb, 0 /* ro */)) != NULL) { 960 return; 961 } 962 963 /* 964 ** There should be an open, writable database in here! 965 ** 966 ** Note: the database would be NULL if it was opened readonly and it 967 ** did not exist. 968 */ 969 if (propdb->db == NULL) { 970 ctx->err = dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 971 DAV_ERR_PROP_NO_DATABASE, 972 "Attempted to set/remove a property " 973 "without a valid, open, read/write " 974 "property database."); 975 return; 976 } 977 978 if (ctx->operation == DAV_PROP_OP_SET) { 979 /* 980 ** Prep the element => propdb namespace index mapping, inserting 981 ** namespace URIs into the propdb that don't exist. 982 */ 983 (void) (*propdb->db_hooks->map_namespaces)(propdb->db, 984 propdb->ns_xlate, 985 &propdb->mapping); 986 } 987 else if (ctx->operation == DAV_PROP_OP_DELETE) { 988 /* 989 ** There are no checks to perform here. If a property exists, then 990 ** we will delete it. If it does not exist, then it does not matter 991 ** (see S12.13.1). 992 ** 993 ** Note that if a property does not exist, that does not rule out 994 ** that a SET will occur during this PROPPATCH (thusly creating it). 995 */ 996 } 997} 998 999DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx) 1000{ 1001 dav_propdb *propdb = ctx->propdb; 1002 dav_error *err = NULL; 1003 dav_elem_private *priv = ctx->prop->priv; 1004 1005 ctx->rollback = apr_pcalloc(propdb->p, sizeof(*ctx->rollback)); 1006 1007 if (ctx->is_liveprop) { 1008 err = (*priv->provider->patch_exec)(propdb->resource, 1009 ctx->prop, ctx->operation, 1010 ctx->liveprop_ctx, 1011 &ctx->rollback->liveprop); 1012 } 1013 else { 1014 dav_prop_name name; 1015 1016 if (ctx->prop->ns == APR_XML_NS_NONE) 1017 name.ns = ""; 1018 else 1019 name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, ctx->prop->ns); 1020 name.name = ctx->prop->name; 1021 1022 /* save the old value so that we can do a rollback. */ 1023 if ((err = (*propdb->db_hooks 1024 ->get_rollback)(propdb->db, &name, 1025 &ctx->rollback->deadprop)) != NULL) 1026 goto error; 1027 1028 if (ctx->operation == DAV_PROP_OP_SET) { 1029 1030 /* Note: propdb->mapping was set in dav_prop_validate() */ 1031 err = (*propdb->db_hooks->store)(propdb->db, &name, ctx->prop, 1032 propdb->mapping); 1033 1034 /* 1035 ** If an error occurred, then assume that we didn't change the 1036 ** value. Remove the rollback item so that we don't try to set 1037 ** its value during the rollback. 1038 */ 1039 /* ### euh... where is the removal? */ 1040 } 1041 else if (ctx->operation == DAV_PROP_OP_DELETE) { 1042 1043 /* 1044 ** Delete the property. Ignore errors -- the property is there, or 1045 ** we are deleting it for a second time. 1046 */ 1047 /* ### but what about other errors? */ 1048 (void) (*propdb->db_hooks->remove)(propdb->db, &name); 1049 } 1050 } 1051 1052 error: 1053 /* push a more specific error here */ 1054 if (err != NULL) { 1055 /* 1056 ** Use HTTP_INTERNAL_SERVER_ERROR because we shouldn't have seen 1057 ** any errors at this point. 1058 */ 1059 ctx->err = dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 1060 DAV_ERR_PROP_EXEC, 1061 "Could not execute PROPPATCH.", err); 1062 } 1063} 1064 1065DAV_DECLARE_NONSTD(void) dav_prop_commit(dav_prop_ctx *ctx) 1066{ 1067 dav_elem_private *priv = ctx->prop->priv; 1068 1069 /* 1070 ** Note that a commit implies ctx->err is NULL. The caller should assume 1071 ** a status of HTTP_OK for this case. 1072 */ 1073 1074 if (ctx->is_liveprop) { 1075 (*priv->provider->patch_commit)(ctx->propdb->resource, 1076 ctx->operation, 1077 ctx->liveprop_ctx, 1078 ctx->rollback->liveprop); 1079 } 1080} 1081 1082DAV_DECLARE_NONSTD(void) dav_prop_rollback(dav_prop_ctx *ctx) 1083{ 1084 dav_error *err = NULL; 1085 dav_elem_private *priv = ctx->prop->priv; 1086 1087 /* do nothing if there is no rollback information. */ 1088 if (ctx->rollback == NULL) 1089 return; 1090 1091 /* 1092 ** ### if we have an error, and a rollback occurs, then the namespace 1093 ** ### mods should not happen at all. Basically, the namespace management 1094 ** ### is simply a bitch. 1095 */ 1096 1097 if (ctx->is_liveprop) { 1098 err = (*priv->provider->patch_rollback)(ctx->propdb->resource, 1099 ctx->operation, 1100 ctx->liveprop_ctx, 1101 ctx->rollback->liveprop); 1102 } 1103 else { 1104 err = (*ctx->propdb->db_hooks 1105 ->apply_rollback)(ctx->propdb->db, ctx->rollback->deadprop); 1106 } 1107 1108 if (err != NULL) { 1109 if (ctx->err == NULL) 1110 ctx->err = err; 1111 else { 1112 dav_error *scan = err; 1113 1114 /* hook previous errors at the end of the rollback error */ 1115 while (scan->prev != NULL) 1116 scan = scan->prev; 1117 scan->prev = ctx->err; 1118 ctx->err = err; 1119 } 1120 } 1121} 1122