diff options
Diffstat (limited to 'modules/dav/main/props.c')
-rw-r--r-- | modules/dav/main/props.c | 1125 |
1 files changed, 1125 insertions, 0 deletions
diff --git a/modules/dav/main/props.c b/modules/dav/main/props.c new file mode 100644 index 0000000..f64878e --- /dev/null +++ b/modules/dav/main/props.c @@ -0,0 +1,1125 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* +** DAV extension module for Apache 2.0.* +** - Property database handling (repository-independent) +** +** NOTES: +** +** PROPERTY DATABASE +** +** This version assumes that there is a per-resource database provider +** to record properties. The database provider decides how and where to +** store these databases. +** +** The DBM keys for the properties have the following form: +** +** namespace ":" propname +** +** For example: 5:author +** +** The namespace provides an integer index into the namespace table +** (see below). propname is simply the property name, without a namespace +** prefix. +** +** A special case exists for properties that had a prefix starting with +** "xml". The XML Specification reserves these for future use. mod_dav +** stores and retrieves them unchanged. The keys for these properties +** have the form: +** +** ":" propname +** +** The propname will contain the prefix and the property name. For +** example, a key might be ":xmlfoo:name" +** +** The ":name" style will also be used for properties that do not +** exist within a namespace. +** +** The DBM values consist of two null-terminated strings, appended +** together (the null-terms are retained and stored in the database). +** The first string is the xml:lang value for the property. An empty +** string signifies that a lang value was not in context for the value. +** The second string is the property value itself. +** +** +** NAMESPACE TABLE +** +** The namespace table is an array that lists each of the namespaces +** that are in use by the properties in the given propdb. Each entry +** in the array is a simple URI. +** +** For example: http://www.foo.bar/standards/props/ +** +** The prefix used for the property is stripped and the URI for it +** is entered into the namespace table. Also, any namespaces used +** within the property value will be entered into the table (and +** stripped from the child elements). +** +** The namespaces are stored in the DBM database under the "METADATA" key. +** +** +** STRIPPING NAMESPACES +** +** Within the property values, the namespace declarations (xmlns...) +** are stripped. Each element and attribute will have its prefix removed +** and a new prefix inserted. +** +** This must be done so that we can return multiple properties in a +** PROPFIND which may have (originally) used conflicting prefixes. For +** that case, we must bind all property value elements to new namespace +** values. +** +** This implies that clients must NOT be sensitive to the namespace +** prefix used for their properties. It WILL change when the properties +** are returned (we return them as "ns<index>", e.g. "ns5"). Also, the +** property value can contain ONLY XML elements and CDATA. PI and comment +** elements will be stripped. CDATA whitespace will be preserved, but +** whitespace within element tags will be altered. Attribute ordering +** may be altered. Element and CDATA ordering will be preserved. +** +** +** ATTRIBUTES ON PROPERTY NAME ELEMENTS +** +** When getting/setting properties, the XML used looks like: +** +** <prop> +** <propname1>value</propname1> +** <propname2>value</propname1> +** </prop> +** +** This implementation (mod_dav) DOES NOT save any attributes that are +** associated with the <propname1> element. The property value is deemed +** to be only the contents ("value" in the above example). +** +** We do store the xml:lang value (if any) that applies to the context +** of the <propname1> element. Whether the xml:lang attribute is on +** <propname1> itself, or from a higher level element, we will store it +** with the property value. +** +** +** VERSIONING +** +** The DBM db contains a key named "METADATA" that holds database-level +** information, such as the namespace table. The record also contains the +** db's version number as the very first 16-bit value. This first number +** is actually stored as two single bytes: the first byte is a "major" +** version number. The second byte is a "minor" number. +** +** If the major number is not what mod_dav expects, then the db is closed +** immediately and an error is returned. A minor number change is +** acceptable -- it is presumed that old/new dav_props.c can deal with +** the database format. For example, a newer dav_props might update the +** minor value and append information to the end of the metadata record +** (which would be ignored by previous versions). +** +** +** ISSUES: +** +** At the moment, for the dav_get_allprops() and dav_get_props() functions, +** we must return a set of xmlns: declarations for ALL known namespaces +** in the file. There isn't a way to filter this because we don't know +** which are going to be used or not. Examining property names is not +** sufficient because the property values could use entirely different +** namespaces. +** +** ==> we must devise a scheme where we can "garbage collect" the namespace +** entries from the property database. +*/ + +#include "apr.h" +#include "apr_strings.h" + +#define APR_WANT_STDIO +#define APR_WANT_BYTEFUNC +#include "apr_want.h" + +#include "mod_dav.h" + +#include "http_log.h" +#include "http_request.h" + +/* +** There is some rough support for writable DAV:getcontenttype and +** DAV:getcontentlanguage properties. If this #define is (1), then +** this support is disabled. +** +** We are disabling it because of a lack of support in GET and PUT +** operations. For GET, it would be "expensive" to look for a propdb, +** open it, and attempt to extract the Content-Type and Content-Language +** values for the response. +** (Handling the PUT would not be difficult, though) +*/ +#define DAV_DISABLE_WRITABLE_PROPS 1 + +#define DAV_EMPTY_VALUE "\0" /* TWO null terms */ + +struct dav_propdb { + apr_pool_t *p; /* the pool we should use */ + request_rec *r; /* the request record */ + + const dav_resource *resource; /* the target resource */ + + int deferred; /* open of db has been deferred */ + dav_db *db; /* underlying database containing props */ + + apr_array_header_t *ns_xlate; /* translation of an elem->ns to URI */ + dav_namespace_map *mapping; /* namespace mapping */ + + dav_lockdb *lockdb; /* the lock database */ + + dav_buffer wb_lock; /* work buffer for lockdiscovery property */ + + /* if we ever run a GET subreq, it will be stored here */ + request_rec *subreq; + + /* hooks we should use for processing (based on the target resource) */ + const dav_hooks_db *db_hooks; +}; + +/* NOTE: dav_core_props[] and the following enum must stay in sync. */ +/* ### move these into a "core" liveprop provider? */ +static const char * const dav_core_props[] = +{ + "getcontenttype", + "getcontentlanguage", + "lockdiscovery", + "supportedlock", + + NULL /* sentinel */ +}; +enum { + DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE, + DAV_PROPID_CORE_getcontentlanguage, + DAV_PROPID_CORE_lockdiscovery, + DAV_PROPID_CORE_supportedlock, + + DAV_PROPID_CORE_UNKNOWN +}; + +/* +** This structure is used to track information needed for a rollback. +*/ +typedef struct dav_rollback_item { + /* select one of the two rollback context structures based on the + value of dav_prop_ctx.is_liveprop */ + dav_deadprop_rollback *deadprop; + dav_liveprop_rollback *liveprop; + +} dav_rollback_item; + + +static int dav_find_liveprop_provider(dav_propdb *propdb, + const char *ns_uri, + const char *propname, + const dav_hooks_liveprop **provider) +{ + int propid; + + *provider = NULL; + + if (ns_uri == NULL) { + /* policy: liveprop providers cannot define no-namespace properties */ + return DAV_PROPID_CORE_UNKNOWN; + } + + /* check liveprop providers first, so they can define core properties */ + propid = dav_run_find_liveprop(propdb->resource, ns_uri, propname, + provider); + if (propid != 0) { + return propid; + } + + /* check for core property */ + if (strcmp(ns_uri, "DAV:") == 0) { + const char * const *p = dav_core_props; + + for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid) + if (strcmp(propname, *p) == 0) { + return propid; + } + } + + /* no provider for this property */ + return DAV_PROPID_CORE_UNKNOWN; +} + +static void dav_find_liveprop(dav_propdb *propdb, apr_xml_elem *elem) +{ + const char *ns_uri; + dav_elem_private *priv = elem->priv; + const dav_hooks_liveprop *hooks; + + + if (elem->ns == APR_XML_NS_NONE) + ns_uri = NULL; + else if (elem->ns == APR_XML_NS_DAV_ID) + ns_uri = "DAV:"; + else + ns_uri = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns); + + priv->propid = dav_find_liveprop_provider(propdb, ns_uri, elem->name, + &hooks); + + /* ### this test seems redundant... */ + if (priv->propid != DAV_PROPID_CORE_UNKNOWN) { + priv->provider = hooks; + } +} + +/* is the live property read/write? */ +static int dav_rw_liveprop(dav_propdb *propdb, dav_elem_private *priv) +{ + int propid = priv->propid; + + /* + ** Check the liveprop provider (if this is a provider-defined prop) + */ + if (priv->provider != NULL) { + return (*priv->provider->is_writable)(propdb->resource, propid); + } + + /* these are defined as read-only */ + if (propid == DAV_PROPID_CORE_lockdiscovery +#if DAV_DISABLE_WRITABLE_PROPS + || propid == DAV_PROPID_CORE_getcontenttype + || propid == DAV_PROPID_CORE_getcontentlanguage +#endif + || propid == DAV_PROPID_CORE_supportedlock + ) { + + return 0; + } + + /* these are defined as read/write */ + if (propid == DAV_PROPID_CORE_getcontenttype + || propid == DAV_PROPID_CORE_getcontentlanguage + || propid == DAV_PROPID_CORE_UNKNOWN) { + + return 1; + } + + /* + ** We don't recognize the property, so it must be dead (and writable) + */ + return 1; +} + +/* do a sub-request to fetch properties for the target resource's URI. */ +static void dav_do_prop_subreq(dav_propdb *propdb) +{ + /* need to escape the uri that's in the resource struct because during + * the property walker it's not encoded. */ + const char *e_uri = ap_escape_uri(propdb->resource->pool, + propdb->resource->uri); + + /* perform a "GET" on the resource's URI (note that the resource + may not correspond to the current request!). */ + propdb->subreq = ap_sub_req_lookup_uri(e_uri, propdb->r, NULL); +} + +static dav_error * dav_insert_coreprop(dav_propdb *propdb, + int propid, const char *name, + dav_prop_insert what, + apr_text_header *phdr, + dav_prop_insert *inserted) +{ + const char *value = NULL; + dav_error *err; + + *inserted = DAV_PROP_INSERT_NOTDEF; + + /* fast-path the common case */ + if (propid == DAV_PROPID_CORE_UNKNOWN) + return NULL; + + switch (propid) { + + case DAV_PROPID_CORE_lockdiscovery: + if (propdb->lockdb != NULL) { + dav_lock *locks; + + if ((err = dav_lock_query(propdb->lockdb, propdb->resource, + &locks)) != NULL) { + return dav_push_error(propdb->p, err->status, 0, + "DAV:lockdiscovery could not be " + "determined due to a problem fetching " + "the locks for this resource.", + err); + } + + /* fast-path the no-locks case */ + if (locks == NULL) { + value = ""; + } + else { + /* + ** This may modify the buffer. value may point to + ** wb_lock.pbuf or a string constant. + */ + value = dav_lock_get_activelock(propdb->r, locks, + &propdb->wb_lock); + + /* make a copy to isolate it from changes to wb_lock */ + value = apr_pstrdup(propdb->p, propdb->wb_lock.buf); + } + } + break; + + case DAV_PROPID_CORE_supportedlock: + if (propdb->lockdb != NULL) { + value = (*propdb->lockdb->hooks->get_supportedlock)(propdb->resource); + } + break; + + case DAV_PROPID_CORE_getcontenttype: + if (propdb->subreq == NULL) { + dav_do_prop_subreq(propdb); + } + if (propdb->subreq->content_type != NULL) { + value = propdb->subreq->content_type; + } + break; + + case DAV_PROPID_CORE_getcontentlanguage: + { + const char *lang; + + if (propdb->subreq == NULL) { + dav_do_prop_subreq(propdb); + } + if ((lang = apr_table_get(propdb->subreq->headers_out, + "Content-Language")) != NULL) { + value = lang; + } + break; + } + + default: + /* fall through to interpret as a dead property */ + break; + } + + /* if something was supplied, then insert it */ + if (value != NULL) { + const char *s; + + if (what == DAV_PROP_INSERT_SUPPORTED) { + /* use D: prefix to refer to the DAV: namespace URI, + * and let the namespace attribute default to "DAV:" + */ + s = apr_psprintf(propdb->p, + "<D:supported-live-property D:name=\"%s\"/>" DEBUG_CR, + name); + } + else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') { + /* use D: prefix to refer to the DAV: namespace URI */ + s = apr_psprintf(propdb->p, "<D:%s>%s</D:%s>" DEBUG_CR, + name, value, name); + } + else { + /* use D: prefix to refer to the DAV: namespace URI */ + s = apr_psprintf(propdb->p, "<D:%s/>" DEBUG_CR, name); + } + apr_text_append(propdb->p, phdr, s); + + *inserted = what; + } + + return NULL; +} + +static dav_error * dav_insert_liveprop(dav_propdb *propdb, + const apr_xml_elem *elem, + dav_prop_insert what, + apr_text_header *phdr, + dav_prop_insert *inserted) +{ + dav_elem_private *priv = elem->priv; + + *inserted = DAV_PROP_INSERT_NOTDEF; + + if (priv->provider == NULL) { + /* this is a "core" property that we define */ + return dav_insert_coreprop(propdb, priv->propid, elem->name, + what, phdr, inserted); + } + + /* ask the provider (that defined this prop) to insert the prop */ + *inserted = (*priv->provider->insert_prop)(propdb->resource, priv->propid, + what, phdr); + + return NULL; +} + +static void dav_output_prop_name(apr_pool_t *pool, + const dav_prop_name *name, + dav_xmlns_info *xi, + apr_text_header *phdr) +{ + const char *s; + + if (*name->ns == '\0') + s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name->name); + else { + const char *prefix = dav_xmlns_add_uri(xi, name->ns); + + s = apr_psprintf(pool, "<%s:%s/>" DEBUG_CR, prefix, name->name); + } + + apr_text_append(pool, phdr, s); +} + +static void dav_insert_xmlns(apr_pool_t *p, const char *pre_prefix, long ns, + const char *ns_uri, apr_text_header *phdr) +{ + const char *s; + + s = apr_psprintf(p, " xmlns:%s%ld=\"%s\"", pre_prefix, ns, ns_uri); + apr_text_append(p, phdr, s); +} + +static dav_error *dav_really_open_db(dav_propdb *propdb, int ro) +{ + dav_error *err; + + /* we're trying to open the db; turn off the 'deferred' flag */ + propdb->deferred = 0; + + /* ask the DB provider to open the thing */ + err = (*propdb->db_hooks->open)(propdb->p, propdb->resource, ro, + &propdb->db); + if (err != NULL) { + return dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, + DAV_ERR_PROP_OPENING, + "Could not open the property database.", + err); + } + + /* + ** NOTE: propdb->db could be NULL if we attempted to open a readonly + ** database that doesn't exist. If we require read/write + ** access, then a database was created and opened. + */ + + return NULL; +} + +DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb, + const dav_resource *resource, + int ro, + apr_array_header_t * ns_xlate, + dav_propdb **p_propdb) +{ + dav_propdb *propdb = apr_pcalloc(r->pool, sizeof(*propdb)); + + *p_propdb = NULL; + +#if DAV_DEBUG + if (resource->uri == NULL) { + return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, + "INTERNAL DESIGN ERROR: resource must define " + "its URI."); + } +#endif + + propdb->r = r; + apr_pool_create(&propdb->p, r->pool); + propdb->resource = resource; + propdb->ns_xlate = ns_xlate; + + propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r); + + propdb->lockdb = lockdb; + + /* always defer actual open, to avoid expense of accessing db + * when only live properties are involved + */ + propdb->deferred = 1; + + /* ### what to do about closing the propdb on server failure? */ + + *p_propdb = propdb; + return NULL; +} + +DAV_DECLARE(void) dav_close_propdb(dav_propdb *propdb) +{ + if (propdb->db != NULL) { + (*propdb->db_hooks->close)(propdb->db); + } + + /* Currently, mod_dav's pool usage doesn't allow clearing this pool. */ +#if 0 + apr_pool_destroy(propdb->p); +#endif +} + +DAV_DECLARE(dav_get_props_result) dav_get_allprops(dav_propdb *propdb, + dav_prop_insert what) +{ + const dav_hooks_db *db_hooks = propdb->db_hooks; + apr_text_header hdr = { 0 }; + apr_text_header hdr_ns = { 0 }; + dav_get_props_result result = { 0 }; + int found_contenttype = 0; + int found_contentlang = 0; + dav_prop_insert unused_inserted; + + /* if not just getting supported live properties, + * scan all properties in the dead prop database + */ + if (what != DAV_PROP_INSERT_SUPPORTED) { + if (propdb->deferred) { + /* ### what to do with db open error? */ + (void) dav_really_open_db(propdb, 1 /*ro*/); + } + + /* initialize the result with some start tags... */ + apr_text_append(propdb->p, &hdr, + "<D:propstat>" DEBUG_CR + "<D:prop>" DEBUG_CR); + + /* if there ARE properties, then scan them */ + if (propdb->db != NULL) { + dav_xmlns_info *xi = dav_xmlns_create(propdb->p); + dav_prop_name name; + dav_error *err; + + /* define (up front) any namespaces the db might need */ + (void) (*db_hooks->define_namespaces)(propdb->db, xi); + + /* get the first property name, beginning the scan */ + err = (*db_hooks->first_name)(propdb->db, &name); + while (!err && name.ns) { + + /* + ** We also look for <DAV:getcontenttype> and + ** <DAV:getcontentlanguage>. If they are not stored as dead + ** properties, then we need to perform a subrequest to get + ** their values (if any). + */ + if (*name.ns == 'D' && strcmp(name.ns, "DAV:") == 0 + && *name.name == 'g') { + if (strcmp(name.name, "getcontenttype") == 0) { + found_contenttype = 1; + } + else if (strcmp(name.name, "getcontentlanguage") == 0) { + found_contentlang = 1; + } + } + + if (what == DAV_PROP_INSERT_VALUE) { + int found; + + if ((err = (*db_hooks->output_value)(propdb->db, &name, + xi, &hdr, + &found)) != NULL) { + /* ### anything better to do? */ + /* ### probably should enter a 500 error */ + goto next_key; + } + /* assert: found == 1 */ + } + else { + /* the value was not requested, so just add an empty + tag specifying the property name. */ + dav_output_prop_name(propdb->p, &name, xi, &hdr); + } + + next_key: + err = (*db_hooks->next_name)(propdb->db, &name); + } + + /* all namespaces have been entered into xi. generate them into + the output now. */ + dav_xmlns_generate(xi, &hdr_ns); + + } /* propdb->db != NULL */ + + /* add namespaces for all the liveprop providers */ + dav_add_all_liveprop_xmlns(propdb->p, &hdr_ns); + } + + /* ask the liveprop providers to insert their properties */ + dav_run_insert_all_liveprops(propdb->r, propdb->resource, what, &hdr); + + /* insert the standard properties */ + /* ### should be handling the return errors here */ + (void)dav_insert_coreprop(propdb, + DAV_PROPID_CORE_supportedlock, "supportedlock", + what, &hdr, &unused_inserted); + (void)dav_insert_coreprop(propdb, + DAV_PROPID_CORE_lockdiscovery, "lockdiscovery", + what, &hdr, &unused_inserted); + + /* if we didn't find these, then do the whole subreq thing. */ + if (!found_contenttype) { + /* ### should be handling the return error here */ + (void)dav_insert_coreprop(propdb, + DAV_PROPID_CORE_getcontenttype, + "getcontenttype", + what, &hdr, &unused_inserted); + } + if (!found_contentlang) { + /* ### should be handling the return error here */ + (void)dav_insert_coreprop(propdb, + DAV_PROPID_CORE_getcontentlanguage, + "getcontentlanguage", + what, &hdr, &unused_inserted); + } + + /* if not just reporting on supported live props, + * terminate the result */ + if (what != DAV_PROP_INSERT_SUPPORTED) { + apr_text_append(propdb->p, &hdr, + "</D:prop>" DEBUG_CR + "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR + "</D:propstat>" DEBUG_CR); + } + + result.propstats = hdr.first; + result.xmlns = hdr_ns.first; + return result; +} + +DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb, + apr_xml_doc *doc) +{ + const dav_hooks_db *db_hooks = propdb->db_hooks; + apr_xml_elem *elem = dav_find_child(doc->root, "prop"); + apr_text_header hdr_good = { 0 }; + apr_text_header hdr_bad = { 0 }; + apr_text_header hdr_ns = { 0 }; + int have_good = 0; + dav_get_props_result result = { 0 }; + char *marks_liveprop; + dav_xmlns_info *xi; + int xi_filled = 0; + + /* ### NOTE: we should pass in TWO buffers -- one for keys, one for + the marks */ + + /* we will ALWAYS provide a "good" result, even if it is EMPTY */ + apr_text_append(propdb->p, &hdr_good, + "<D:propstat>" DEBUG_CR + "<D:prop>" DEBUG_CR); + + /* ### the marks should be in a buffer! */ + /* allocate zeroed-memory for the marks. These marks indicate which + liveprop namespaces we've generated into the output xmlns buffer */ + + /* same for the liveprops */ + marks_liveprop = apr_pcalloc(propdb->p, dav_get_liveprop_ns_count() + 1); + + xi = dav_xmlns_create(propdb->p); + + for (elem = elem->first_child; elem; elem = elem->next) { + dav_elem_private *priv; + dav_error *err; + dav_prop_insert inserted; + dav_prop_name name; + + /* + ** First try live property providers; if they don't handle + ** the property, then try looking it up in the propdb. + */ + + if (elem->priv == NULL) { + elem->priv = apr_pcalloc(propdb->p, sizeof(*priv)); + } + priv = elem->priv; + + /* cache the propid; dav_get_props() could be called many times */ + if (priv->propid == 0) + dav_find_liveprop(propdb, elem); + + if (priv->propid != DAV_PROPID_CORE_UNKNOWN) { + + /* insert the property. returns 1 if an insertion was done. */ + if ((err = dav_insert_liveprop(propdb, elem, DAV_PROP_INSERT_VALUE, + &hdr_good, &inserted)) != NULL) { + /* ### need to propagate the error to the caller... */ + /* ### skip it for now, as if nothing was inserted */ + } + if (inserted == DAV_PROP_INSERT_VALUE) { + have_good = 1; + + /* + ** Add the liveprop's namespace URIs. Note that provider==NULL + ** for core properties. + */ + if (priv->provider != NULL) { + const char * const * scan_ns_uri; + + for (scan_ns_uri = priv->provider->namespace_uris; + *scan_ns_uri != NULL; + ++scan_ns_uri) { + long ns; + + ns = dav_get_liveprop_ns_index(*scan_ns_uri); + if (marks_liveprop[ns]) + continue; + marks_liveprop[ns] = 1; + + dav_insert_xmlns(propdb->p, "lp", ns, *scan_ns_uri, + &hdr_ns); + } + } + + /* property added. move on to the next property. */ + continue; + } + else if (inserted == DAV_PROP_INSERT_NOTDEF) { + /* nothing to do. fall thru to allow property to be handled + as a dead property */ + } +#if DAV_DEBUG + else { +#if 0 + /* ### need to change signature to return an error */ + return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 0, + 0, + "INTERNAL DESIGN ERROR: insert_liveprop " + "did not insert what was asked for."); +#endif + } +#endif + } + + /* The property wasn't a live property, so look in the dead property + database. */ + + /* make sure propdb is really open */ + if (propdb->deferred) { + /* ### what to do with db open error? */ + (void) dav_really_open_db(propdb, 1 /*ro*/); + } + + if (elem->ns == APR_XML_NS_NONE) + name.ns = ""; + else + name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns); + name.name = elem->name; + + /* only bother to look if a database exists */ + if (propdb->db != NULL) { + int found; + + if ((err = (*db_hooks->output_value)(propdb->db, &name, + xi, &hdr_good, + &found)) != NULL) { + /* ### what to do? continue doesn't seem right... */ + continue; + } + + if (found) { + have_good = 1; + + /* if we haven't added the db's namespaces, then do so... */ + if (!xi_filled) { + (void) (*db_hooks->define_namespaces)(propdb->db, xi); + xi_filled = 1; + } + continue; + } + } + + /* not found as a live OR dead property. add a record to the "bad" + propstats */ + + /* make sure we've started our "bad" propstat */ + if (hdr_bad.first == NULL) { + apr_text_append(propdb->p, &hdr_bad, + "<D:propstat>" DEBUG_CR + "<D:prop>" DEBUG_CR); + } + + /* output this property's name (into the bad propstats) */ + dav_output_prop_name(propdb->p, &name, xi, &hdr_bad); + } + + apr_text_append(propdb->p, &hdr_good, + "</D:prop>" DEBUG_CR + "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR + "</D:propstat>" DEBUG_CR); + + /* default to start with the good */ + result.propstats = hdr_good.first; + + /* we may not have any "bad" results */ + if (hdr_bad.first != NULL) { + /* "close" the bad propstat */ + apr_text_append(propdb->p, &hdr_bad, + "</D:prop>" DEBUG_CR + "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR + "</D:propstat>" DEBUG_CR); + + /* if there are no good props, then just return the bad */ + if (!have_good) { + result.propstats = hdr_bad.first; + } + else { + /* hook the bad propstat to the end of the good one */ + hdr_good.last->next = hdr_bad.first; + } + } + + /* add in all the various namespaces, and return them */ + dav_xmlns_generate(xi, &hdr_ns); + result.xmlns = hdr_ns.first; + + return result; +} + +DAV_DECLARE(void) dav_get_liveprop_supported(dav_propdb *propdb, + const char *ns_uri, + const char *propname, + apr_text_header *body) +{ + int propid; + const dav_hooks_liveprop *hooks; + + propid = dav_find_liveprop_provider(propdb, ns_uri, propname, &hooks); + + if (propid != DAV_PROPID_CORE_UNKNOWN) { + if (hooks == NULL) { + /* this is a "core" property that we define */ + dav_prop_insert unused_inserted; + dav_insert_coreprop(propdb, propid, propname, + DAV_PROP_INSERT_SUPPORTED, body, &unused_inserted); + } + else { + (*hooks->insert_prop)(propdb->resource, propid, + DAV_PROP_INSERT_SUPPORTED, body); + } + } +} + +DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx) +{ + dav_propdb *propdb = ctx->propdb; + apr_xml_elem *prop = ctx->prop; + dav_elem_private *priv; + + priv = ctx->prop->priv = apr_pcalloc(propdb->p, sizeof(*priv)); + + /* + ** Check to see if this is a live property, and fill the fields + ** in the XML elem, as appropriate. + ** + ** Verify that the property is read/write. If not, then it cannot + ** be SET or DELETEd. + */ + if (priv->propid == 0) { + dav_find_liveprop(propdb, prop); + + /* it's a liveprop if a provider was found */ + /* ### actually the "core" props should really be liveprops, but + ### there is no "provider" for those and the r/w props are + ### treated as dead props anyhow */ + ctx->is_liveprop = priv->provider != NULL; + } + + if (!dav_rw_liveprop(propdb, priv)) { + ctx->err = dav_new_error(propdb->p, HTTP_CONFLICT, + DAV_ERR_PROP_READONLY, 0, + "Property is read-only."); + return; + } + + if (ctx->is_liveprop) { + int defer_to_dead = 0; + + ctx->err = (*priv->provider->patch_validate)(propdb->resource, + prop, ctx->operation, + &ctx->liveprop_ctx, + &defer_to_dead); + if (ctx->err != NULL || !defer_to_dead) + return; + + /* clear is_liveprop -- act as a dead prop now */ + ctx->is_liveprop = 0; + } + + /* + ** The property is supposed to be stored into the dead-property + ** database. Make sure the thing is truly open (and writable). + */ + if (propdb->deferred + && (ctx->err = dav_really_open_db(propdb, 0 /* ro */)) != NULL) { + return; + } + + /* + ** There should be an open, writable database in here! + ** + ** Note: the database would be NULL if it was opened readonly and it + ** did not exist. + */ + if (propdb->db == NULL) { + ctx->err = dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, + DAV_ERR_PROP_NO_DATABASE, 0, + "Attempted to set/remove a property " + "without a valid, open, read/write " + "property database."); + return; + } + + if (ctx->operation == DAV_PROP_OP_SET) { + /* + ** Prep the element => propdb namespace index mapping, inserting + ** namespace URIs into the propdb that don't exist. + */ + (void) (*propdb->db_hooks->map_namespaces)(propdb->db, + propdb->ns_xlate, + &propdb->mapping); + } + else if (ctx->operation == DAV_PROP_OP_DELETE) { + /* + ** There are no checks to perform here. If a property exists, then + ** we will delete it. If it does not exist, then it does not matter + ** (see S12.13.1). + ** + ** Note that if a property does not exist, that does not rule out + ** that a SET will occur during this PROPPATCH (thusly creating it). + */ + } +} + +DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx) +{ + dav_propdb *propdb = ctx->propdb; + dav_error *err = NULL; + dav_elem_private *priv = ctx->prop->priv; + + ctx->rollback = apr_pcalloc(propdb->p, sizeof(*ctx->rollback)); + + if (ctx->is_liveprop) { + err = (*priv->provider->patch_exec)(propdb->resource, + ctx->prop, ctx->operation, + ctx->liveprop_ctx, + &ctx->rollback->liveprop); + } + else { + dav_prop_name name; + + if (ctx->prop->ns == APR_XML_NS_NONE) + name.ns = ""; + else + name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, ctx->prop->ns); + name.name = ctx->prop->name; + + /* save the old value so that we can do a rollback. */ + if ((err = (*propdb->db_hooks + ->get_rollback)(propdb->db, &name, + &ctx->rollback->deadprop)) != NULL) + goto error; + + if (ctx->operation == DAV_PROP_OP_SET) { + + /* Note: propdb->mapping was set in dav_prop_validate() */ + err = (*propdb->db_hooks->store)(propdb->db, &name, ctx->prop, + propdb->mapping); + + /* + ** If an error occurred, then assume that we didn't change the + ** value. Remove the rollback item so that we don't try to set + ** its value during the rollback. + */ + /* ### euh... where is the removal? */ + } + else if (ctx->operation == DAV_PROP_OP_DELETE) { + + /* + ** Delete the property. Ignore errors -- the property is there, or + ** we are deleting it for a second time. + */ + /* ### but what about other errors? */ + (void) (*propdb->db_hooks->remove)(propdb->db, &name); + } + } + + error: + /* push a more specific error here */ + if (err != NULL) { + /* + ** Use HTTP_INTERNAL_SERVER_ERROR because we shouldn't have seen + ** any errors at this point. + */ + ctx->err = dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, + DAV_ERR_PROP_EXEC, + "Could not execute PROPPATCH.", err); + } +} + +DAV_DECLARE_NONSTD(void) dav_prop_commit(dav_prop_ctx *ctx) +{ + dav_elem_private *priv = ctx->prop->priv; + + /* + ** Note that a commit implies ctx->err is NULL. The caller should assume + ** a status of HTTP_OK for this case. + */ + + if (ctx->is_liveprop) { + (*priv->provider->patch_commit)(ctx->propdb->resource, + ctx->operation, + ctx->liveprop_ctx, + ctx->rollback->liveprop); + } +} + +DAV_DECLARE_NONSTD(void) dav_prop_rollback(dav_prop_ctx *ctx) +{ + dav_error *err = NULL; + dav_elem_private *priv = ctx->prop->priv; + + /* do nothing if there is no rollback information. */ + if (ctx->rollback == NULL) + return; + + /* + ** ### if we have an error, and a rollback occurs, then the namespace + ** ### mods should not happen at all. Basically, the namespace management + ** ### is simply a bitch. + */ + + if (ctx->is_liveprop) { + err = (*priv->provider->patch_rollback)(ctx->propdb->resource, + ctx->operation, + ctx->liveprop_ctx, + ctx->rollback->liveprop); + } + else { + err = (*ctx->propdb->db_hooks + ->apply_rollback)(ctx->propdb->db, ctx->rollback->deadprop); + } + + if (err != NULL) { + if (ctx->err == NULL) + ctx->err = err; + else { + dav_error *scan = err; + + /* hook previous errors at the end of the rollback error */ + while (scan->prev != NULL) + scan = scan->prev; + scan->prev = ctx->err; + ctx->err = err; + } + } +} |