props.c revision 26250b0077972bf21b6d8a8d23772a4cf53f9477
2818N/A/* ====================================================================
2818N/A * The Apache Software License, Version 1.1
2818N/A *
2818N/A * Copyright (c) 2000 The Apache Software Foundation. All rights
2818N/A * reserved.
2818N/A *
2818N/A * Redistribution and use in source and binary forms, with or without
2818N/A * modification, are permitted provided that the following conditions
2818N/A * are met:
2818N/A *
2818N/A * 1. Redistributions of source code must retain the above copyright
2818N/A * notice, this list of conditions and the following disclaimer.
2818N/A *
2818N/A * 2. Redistributions in binary form must reproduce the above copyright
2818N/A * notice, this list of conditions and the following disclaimer in
2818N/A * the documentation and/or other materials provided with the
2818N/A * distribution.
2818N/A *
2818N/A * 3. The end-user documentation included with the redistribution,
2818N/A * if any, must include the following acknowledgment:
2818N/A * "This product includes software developed by the
2818N/A * Apache Software Foundation (http://www.apache.org/)."
5466N/A * Alternately, this acknowledgment may appear in the software itself,
2818N/A * if and wherever such third-party acknowledgments normally appear.
2818N/A *
2818N/A * 4. The names "Apache" and "Apache Software Foundation" must
2818N/A * not be used to endorse or promote products derived from this
6444N/A * software without prior written permission. For written
2818N/A * permission, please contact apache@apache.org.
2818N/A *
2818N/A * 5. Products derived from this software may not be called "Apache",
2818N/A * nor may "Apache" appear in their name, without prior written
6444N/A * permission of the Apache Software Foundation.
2818N/A *
3869N/A * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
2818N/A * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2818N/A * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
6444N/A * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
2899N/A * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3817N/A * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3817N/A * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
3817N/A * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3817N/A * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2818N/A * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
2818N/A * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2818N/A * SUCH DAMAGE.
2818N/A * ====================================================================
2818N/A *
2818N/A * This software consists of voluntary contributions made by many
2818N/A * individuals on behalf of the Apache Software Foundation. For more
4976N/A * information on the Apache Software Foundation, please see
2818N/A * <http://www.apache.org/>.
2818N/A */
2818N/A
2818N/A/*
2818N/A** DAV extension module for Apache 2.0.*
2818N/A** - Property database handling (repository-independent)
2818N/A**
2818N/A** NOTES:
2818N/A**
6901N/A** PROPERTY DATABASE
6901N/A**
6901N/A** This version assumes that there is a per-resource database provider
2818N/A** to record properties. The database provider decides how and where to
2818N/A** store these databases.
2818N/A**
2818N/A** The DBM keys for the properties have the following form:
2818N/A**
2818N/A** namespace ":" propname
2818N/A**
2818N/A** For example: 5:author
2818N/A**
2818N/A** The namespace provides an integer index into the namespace table
2818N/A** (see below). propname is simply the property name, without a namespace
2818N/A** prefix.
2818N/A**
2818N/A** A special case exists for properties that had a prefix starting with
2818N/A** "xml". The XML Specification reserves these for future use. mod_dav
2818N/A** stores and retrieves them unchanged. The keys for these properties
4302N/A** have the form:
4302N/A**
5934N/A** ":" propname
5934N/A**
5934N/A** The propname will contain the prefix and the property name. For
5934N/A** example, a key might be ":xmlfoo:name"
4302N/A**
4302N/A** The ":name" style will also be used for properties that do not
3633N/A** exist within a namespace.
3633N/A**
3633N/A** The DBM values consist of two null-terminated strings, appended
2818N/A** together (the null-terms are retained and stored in the database).
2818N/A** The first string is the xml:lang value for the property. An empty
2818N/A** string signifies that a lang value was not in context for the value.
2818N/A** The second string is the property value itself.
2818N/A**
2818N/A**
2818N/A** NAMESPACE TABLE
2818N/A**
2818N/A** The namespace table is an array that lists each of the namespaces
2818N/A** that are in use by the properties in the given propdb. Each entry
2818N/A** in the array is a simple URI.
2818N/A**
2818N/A** For example: http://www.foo.bar/standards/props/
2818N/A**
2818N/A** The prefix used for the property is stripped and the URI for it
3693N/A** is entered into the namespace table. Also, any namespaces used
3693N/A** within the property value will be entered into the table (and
3693N/A** stripped from the child elements).
3693N/A**
3693N/A** The namespaces are stored in the DBM database under the "METADATA" key.
3693N/A**
3693N/A**
2818N/A** STRIPPING NAMESPACES
2818N/A**
2818N/A** Within the property values, the namespace declarations (xmlns...)
2818N/A** are stripped. Each element and attribute will have its prefix removed
2818N/A** and a new prefix inserted.
2818N/A**
2818N/A** This must be done so that we can return multiple properties in a
2818N/A** PROPFIND which may have (originally) used conflicting prefixes. For
3251N/A** that case, we must bind all property value elements to new namespace
2818N/A** values.
2818N/A**
2818N/A** This implies that clients must NOT be sensitive to the namespace
2818N/A** prefix used for their properties. It WILL change when the properties
2818N/A** are returned (we return them as "ns<index>", e.g. "ns5"). Also, the
2818N/A** property value can contain ONLY XML elements and CDATA. PI and comment
2818N/A** elements will be stripped. CDATA whitespace will be preserved, but
2818N/A** whitespace within element tags will be altered. Attribute ordering
3693N/A** may be altered. Element and CDATA ordering will be preserved.
2818N/A**
2818N/A**
2818N/A** ATTRIBUTES ON PROPERTY NAME ELEMENTS
2818N/A**
2818N/A** When getting/setting properties, the XML used looks like:
2818N/A**
2818N/A** <prop>
2818N/A** <propname1>value</propname1>
3497N/A** <propname2>value</propname1>
3497N/A** </prop>
3497N/A**
3497N/A** This implementation (mod_dav) DOES NOT save any attributes that are
3497N/A** associated with the <propname1> element. The property value is deemed
3497N/A** to be only the contents ("value" in the above example).
3497N/A**
3497N/A** We do store the xml:lang value (if any) that applies to the context
3497N/A** of the <propname1> element. Whether the xml:lang attribute is on
3497N/A** <propname1> itself, or from a higher level element, we will store it
3497N/A** with the property value.
3497N/A**
3497N/A**
3497N/A** VERSIONING
3693N/A**
3497N/A** The DBM db contains a key named "METADATA" that holds database-level
3497N/A** information, such as the namespace table. The record also contains the
4828N/A** db's version number as the very first 16-bit value. This first number
4828N/A** is actually stored as two single bytes: the first byte is a "major"
3497N/A** version number. The second byte is a "minor" number.
3497N/A**
3497N/A** If the major number is not what mod_dav expects, then the db is closed
3497N/A** immediately and an error is returned. A minor number change is
4828N/A** acceptable -- it is presumed that old/new dav_props.c can deal with
4828N/A** the database format. For example, a newer dav_props might update the
3497N/A** minor value and append information to the end of the metadata record
2818N/A** (which would be ignored by previous versions).
3477N/A**
2818N/A**
2818N/A** ISSUES:
2818N/A**
2818N/A** At the moment, for the dav_get_allprops() and dav_get_props() functions,
2818N/A** we must return a set of xmlns: declarations for ALL known namespaces
2818N/A** in the file. There isn't a way to filter this because we don't know
2818N/A** which are going to be used or not. Examining property names is not
2818N/A** sufficient because the property values could use entirely different
2818N/A** namespaces.
2818N/A**
2818N/A** ==> we must devise a scheme where we can "garbage collect" the namespace
2818N/A** entries from the property database.
2818N/A*/
3722N/A
3722N/A#include "mod_dav.h"
2818N/A
2818N/A#include "http_log.h"
4089N/A#include "http_request.h"
4089N/A
4089N/A/*
4089N/A** There is some rough support for writeable DAV:getcontenttype and
4089N/A** DAV:getcontentlanguage properties. If this #define is (1), then
2818N/A** this support is disabled.
3747N/A**
3747N/A** We are disabling it because of a lack of support in GET and PUT
3747N/A** operations. For GET, it would be "expensive" to look for a propdb,
3747N/A** open it, and attempt to extract the Content-Type and Content-Language
3747N/A** values for the response.
3747N/A** (Handling the PUT would not be difficult, though)
3747N/A*/
2818N/A#define DAV_DISABLE_WRITEABLE_PROPS 1
4089N/A
2818N/A#define DAV_GDBM_NS_KEY "METADATA"
3817N/A#define DAV_GDBM_NS_KEY_LEN 8
3817N/A
3817N/A#define DAV_EMPTY_VALUE "\0" /* TWO null terms */
3817N/A
3817N/A/* the namespace URI was not found; no ID is available */
3817N/A#define DAV_NS_ERROR_NOT_FOUND (DAV_NS_ERROR_BASE)
3817N/A
3817N/Atypedef struct {
3817N/A unsigned char major;
3817N/A#define DAV_DBVSN_MAJOR 4
3817N/A /*
3817N/A ** V4 -- 0.9.9 ..
3817N/A ** Prior versions could have keys or values with invalid
3817N/A ** namespace prefixes as a result of the xmlns="" form not
** resetting the default namespace to be "no namespace". The
** namespace would be set to "" which is invalid; it should
** be set to "no namespace".
**
** V3 -- 0.9.8
** Prior versions could have values with invalid namespace
** prefixes due to an incorrect mapping of input to propdb
** namespace indices. Version bumped to obsolete the old
** values.
**
** V2 -- 0.9.7
** This introduced the xml:lang value into the property value's
** record in the propdb.
**
** V1 -- .. 0.9.6
** Initial version.
*/
unsigned char minor;
#define DAV_DBVSN_MINOR 0
short ns_count;
} dav_propdb_metadata;
struct dav_propdb {
int version; /* *minor* version of this db */
ap_pool_t *p; /* the pool we should use */
request_rec *r; /* the request record */
dav_resource *resource; /* the target resource */
int deferred; /* open of db has been deferred */
dav_db *db; /* underlying database containing props */
dav_buffer ns_table; /* table of namespace URIs */
short ns_count; /* number of entries in table */
int ns_table_dirty; /* ns_table was modified */
ap_array_header_t *ns_xlate; /* translation of an elem->ns to URI */
int *ns_map; /* map elem->ns to propdb ns values */
int incomplete_map; /* some mappings do not exist */
dav_lockdb *lockdb; /* the lock database */
dav_buffer wb_key; /* work buffer for dav_gdbm_key */
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;
const dav_hooks_vsn *vsn_hooks;
const dav_dyn_hooks *liveprop; /* head of list */
};
/* ### move these into a "core" liveprop provider? */
static const char * const dav_core_props[] =
{
"getcontenttype",
"getcontentlanguage",
"lockdiscovery",
"resourcetype",
"supportedlock",
NULL /* sentinel */
};
enum {
DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE,
DAV_PROPID_CORE_getcontentlanguage,
DAV_PROPID_CORE_lockdiscovery,
DAV_PROPID_CORE_resourcetype,
DAV_PROPID_CORE_supportedlock,
DAV_PROPID_CORE_UNKNOWN
};
#define DAV_IS_CORE_PROP(propid) ((propid) >= DAV_PROPID_CORE && \
(propid) <= DAV_PROPID_CORE_UNKNOWN)
/*
** This structure is used to track information needed for a rollback.
** If a SET was performed and no prior value existed, then value.dptr
** will be NULL.
*/
typedef struct dav_rollback_item {
dav_datum key; /* key for the item being saved */
dav_datum value; /* value before set/replace/delete */
/* or use the following (choice selected by dav_prop_ctx.is_liveprop) */
struct dav_liveprop_rollback *liveprop; /* liveprop rollback ctx */
} dav_rollback_item;
#if 0
/* ### unused */
static const char *dav_get_ns_table_uri(dav_propdb *propdb, int ns)
{
const char *p = propdb->ns_table.buf + sizeof(dav_propdb_metadata);
while (ns--)
p += strlen(p) + 1;
return p;
}
#endif
static void dav_find_liveprop(dav_propdb *propdb, dav_xml_elem *elem)
{
int propid;
const char *ns_uri;
const dav_dyn_hooks *ddh;
if (elem->ns == DAV_NS_DAV_ID) {
const char * const *p = dav_core_props;
for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid)
if (strcmp(elem->name, *p) == 0) {
elem->propid = propid;
return;
}
/* didn't find it. fall thru. a provider can define DAV: props */
}
else if (elem->ns == DAV_NS_NONE) {
/* policy: liveprop providers cannot define no-namespace properties */
elem->propid = DAV_PROPID_CORE_UNKNOWN;
return;
}
ns_uri = DAV_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
for (ddh = propdb->liveprop; ddh != NULL; ddh = ddh->next) {
propid = (*DAV_AS_HOOKS_LIVEPROP(ddh)->find_prop)(ns_uri, elem->name);
if (propid != 0) {
elem->propid = propid;
elem->provider = DAV_AS_HOOKS_LIVEPROP(ddh);
elem->ns_map = ddh->ctx.ns_map;
return;
}
}
elem->propid = DAV_PROPID_CORE_UNKNOWN;
}
/* is the live property read/write? */
static int dav_rw_liveprop(dav_propdb *propdb, int propid)
{
dav_prop_rw rw;
const dav_dyn_hooks *ddh;
/* these are defined as read-only */
if (propid == DAV_PROPID_CORE_lockdiscovery
|| propid == DAV_PROPID_CORE_resourcetype
#if DAV_DISABLE_WRITEABLE_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;
}
/*
** Check the liveprop providers
*/
for (ddh = propdb->liveprop; ddh != NULL; ddh = ddh->next) {
rw = (*DAV_AS_HOOKS_LIVEPROP(ddh)->is_writeable)(propdb->resource,
propid);
if (rw == DAV_PROP_RW_YES)
return 1;
if (rw == DAV_PROP_RW_NO)
return 0;
}
/*
** No provider recognized 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)
{
/* 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(propdb->resource->uri, propdb->r);
}
static dav_error * dav_insert_coreprop(dav_propdb *propdb,
int propid, const char *name,
int getvals,
dav_text_header *phdr,
int *inserted)
{
const char *value = NULL;
*inserted = 0;
/* fast-path the common case */
if (propid == DAV_PROPID_CORE_UNKNOWN)
return NULL;
switch (propid) {
case DAV_PROPID_CORE_resourcetype:
switch (propdb->resource->type) {
case DAV_RESOURCE_TYPE_REGULAR:
if (propdb->resource->collection) {
value = "<D:collection/>";
}
else {
/* ### should we denote lock-null resources? */
value = ""; /* becomes: <D:resourcetype/> */
}
break;
case DAV_RESOURCE_TYPE_HISTORY:
value = "<D:history/>";
break;
case DAV_RESOURCE_TYPE_WORKSPACE:
value = "<D:workspace/>";
break;
case DAV_RESOURCE_TYPE_ACTIVITY:
value = "<D:activity/>";
break;
case DAV_RESOURCE_TYPE_CONFIGURATION:
value = "<D:configuration/>";
break;
case DAV_RESOURCE_TYPE_REVISION:
value = "<D:revision/>";
break;
default:
/* ### bad juju */
break;
}
break;
case DAV_PROPID_CORE_lockdiscovery:
if (propdb->lockdb != NULL) {
dav_error *err;
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 = ap_pstrdup(propdb->p, propdb->wb_lock.buf);
}
}
break;
case DAV_PROPID_CORE_supportedlock:
if (propdb->lockdb != NULL) {
value = (*propdb->lockdb->hooks->get_supportedlock)();
}
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 = ap_table_get(propdb->subreq->headers_out,
"Content-Language")) != NULL) {
value = lang;
}
break;
}
case DAV_PROPID_CORE_UNKNOWN:
default:
/* fall through to interpret as a dead property */
break;
}
/* if something was supplied, then insert it */
if (value != NULL) {
const char *s;
if (getvals && *value != '\0') {
/* use D: prefix to refer to the DAV: namespace URI */
s = ap_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 = ap_psprintf(propdb->p, "<D:%s/>" DEBUG_CR, name);
}
dav_text_append(propdb->p, phdr, s);
*inserted = 1;
}
return NULL;
}
static dav_error * dav_insert_liveprop(dav_propdb *propdb,
const dav_xml_elem *elem,
int getvals,
dav_text_header *phdr,
int *inserted)
{
dav_prop_insert pi;
*inserted = 0;
if (DAV_IS_CORE_PROP(elem->propid))
return dav_insert_coreprop(propdb, elem->propid, elem->name,
getvals, phdr, inserted);
/* ask the provider (that defined this prop) to insert the prop */
pi = (*elem->provider->insert_prop)(propdb->resource, elem->propid,
getvals, elem->ns_map, phdr);
#if DAV_DEBUG
if (pi == DAV_PROP_INSERT_NOTME) {
/* ### the provider should have returned NOTDEF, at least */
return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 0,
"DESIGN ERROR: a liveprop provider defined "
"a property, but did not respond to the "
"insert_prop hook for it.");
}
#endif
if (pi != DAV_PROP_INSERT_NOTDEF)
*inserted = 1;
return NULL;
}
static void dav_append_prop(dav_propdb *propdb,
const char *name, const char *value,
dav_text_header *phdr)
{
const char *s;
const char *lang = value;
/* skip past the xml:lang value */
value += strlen(lang) + 1;
if (*value == '\0') {
/* the property is an empty value */
if (*name == ':') {
/* "no namespace" case */
s = ap_psprintf(propdb->p, "<%s/>" DEBUG_CR, name+1);
}
else {
s = ap_psprintf(propdb->p, "<ns%s/>" DEBUG_CR, name);
}
}
else if (*lang != '\0') {
if (*name == ':') {
/* "no namespace" case */
s = ap_psprintf(propdb->p, "<%s xml:lang=\"%s\">%s</%s>" DEBUG_CR,
name+1, lang, value, name+1);
}
else {
s = ap_psprintf(propdb->p, "<ns%s xml:lang=\"%s\">%s</ns%s>" DEBUG_CR,
name, lang, value, name);
}
}
else if (*name == ':') {
/* "no namespace" case */
s = ap_psprintf(propdb->p, "<%s>%s</%s>" DEBUG_CR, name+1, value, name+1);
}
else {
s = ap_psprintf(propdb->p, "<ns%s>%s</ns%s>" DEBUG_CR, name, value, name);
}
dav_text_append(propdb->p, phdr, s);
}
/*
** Prepare the ns_map variable in the propdb structure. This entails copying
** all URIs from the "input" namespace list (in propdb->ns_xlate) into the
** propdb's list of namespaces. As each URI is copied (or pre-existing
** URI looked up), the index mapping is stored into the ns_map variable.
**
** Note: we must copy all declared namespaces because we cannot easily
** determine which input namespaces were actually used within the property
** values that are being stored within the propdb. Theoretically, we can
** determine this at the point where we serialize the property values
** back into strings. This would require a bit more work, and will be
** left to future optimizations.
**
** ### we should always initialize the propdb namespace array with "DAV:"
** ### since we know it will be entered anyhow (by virtue of it always
** ### occurring in the ns_xlate array). That will allow us to use
** ### DAV_NS_DAV_ID for propdb ns values, too.
*/
static void dav_prep_ns_map(dav_propdb *propdb, int add_ns)
{
int i;
const char **puri;
const int orig_count = propdb->ns_count;
int *pmap;
int updating = 0; /* are we updating an existing ns_map? */
if (propdb->ns_map) {
if (add_ns && propdb->incomplete_map) {
/* we must revisit the map and insert new entries */
updating = 1;
propdb->incomplete_map = 0;
}
else {
/* nothing to do: we have a proper ns_map */
return;
}
}
else {
propdb->ns_map = ap_palloc(propdb->p, propdb->ns_xlate->nelts * sizeof(*propdb->ns_map));
}
pmap = propdb->ns_map;
/* ### stupid O(n * orig_count) algorithm */
for (i = propdb->ns_xlate->nelts, puri = (const char **)propdb->ns_xlate->elts;
i-- > 0;
++puri, ++pmap) {
const char *uri = *puri;
const size_t uri_len = strlen(uri);
if (updating) {
/* updating an existing mapping... we can skip a lot of stuff */
if (*pmap != DAV_NS_ERROR_NOT_FOUND) {
/* This entry has been filled in, so we can skip it */
continue;
}
}
else {
int j;
size_t len;
const char *p;
/*
** GIVEN: uri (a namespace URI from the request input)
**
** FIND: an equivalent URI in the propdb namespace table
*/
/* only scan original entries (we may have added some in here) */
for (p = propdb->ns_table.buf + sizeof(dav_propdb_metadata),
j = 0;
j < orig_count;
++j, p += len + 1) {
len = strlen(p);
if (uri_len != len)
continue;
if (memcmp(uri, p, len) == 0) {
*pmap = j;
goto next_input_uri;
}
}
if (!add_ns) {
*pmap = DAV_NS_ERROR_NOT_FOUND;
/*
** This flag indicates that we have an ns_map with missing
** entries. If dav_prep_ns_map() is called with add_ns==1 AND
** this flag is set, then we zip thru the array and add those
** URIs (effectively updating the ns_map as if add_ns=1 was
** passed when the initial prep was called).
*/
propdb->incomplete_map = 1;
continue;
}
}
/*
** The input URI was not found in the propdb namespace table, and
** we are supposed to add it. Append it to the table and store
** the index into the ns_map.
*/
dav_check_bufsize(propdb->p, &propdb->ns_table, uri_len + 1);
memcpy(propdb->ns_table.buf + propdb->ns_table.cur_len, uri, uri_len + 1);
propdb->ns_table.cur_len += uri_len + 1;
propdb->ns_table_dirty = 1;
*pmap = propdb->ns_count++;
next_input_uri:
;
}
}
/* find the "DAV:" namespace in our table and return its ID. */
static int dav_find_dav_id(dav_propdb *propdb)
{
const char *p = propdb->ns_table.buf + sizeof(dav_propdb_metadata);
int ns;
for (ns = 0; ns < propdb->ns_count; ++ns) {
size_t len = strlen(p);
if (len == 4 && memcmp(p, "DAV:", 5) == 0)
return ns;
p += len + 1;
}
/* the "DAV:" namespace is not present */
return -1;
}
static void dav_insert_xmlns(ap_pool_t *p, const char *pre_prefix, int ns,
const char *ns_uri, dav_text_header *phdr)
{
const char *s;
s = ap_psprintf(p, " xmlns:%s%d=\"%s\"", pre_prefix, ns, ns_uri);
dav_text_append(p, phdr, s);
}
/* return all known namespaces (in this propdb) */
static void dav_get_propdb_xmlns(dav_propdb *propdb, dav_text_header *phdr)
{
int i;
const char *p = propdb->ns_table.buf + sizeof(dav_propdb_metadata);
size_t len;
/* note: ns_count == 0 when we have no propdb file */
for (i = 0; i < propdb->ns_count; ++i, p += len + 1) {
len = strlen(p);
dav_insert_xmlns(propdb->p, "ns", i, p, phdr);
}
}
/* add a namespace decl from one of the namespace tables */
static void dav_add_marked_xmlns(dav_propdb *propdb, char *marks, int ns,
ap_array_header_t *ns_table,
const char *pre_prefix,
dav_text_header *phdr)
{
if (marks[ns])
return;
marks[ns] = 1;
dav_insert_xmlns(propdb->p,
pre_prefix, ns, DAV_GET_URI_ITEM(ns_table, ns),
phdr);
}
/*
** Internal function to build a key
**
** WARNING: returns a pointer to a "static" buffer holding the key. The
** value must be copied or no longer used if this function is
** called again.
*/
static dav_datum dav_gdbm_key(dav_propdb *propdb, const dav_xml_elem *elem)
{
int ns;
char nsbuf[20];
size_t l_ns;
size_t l_name = strlen(elem->name);
dav_datum key = { 0 };
/*
* Convert namespace ID to a string. "no namespace" is an empty string,
* so the keys will have the form ":name". Otherwise, the keys will
* have the form "#:name".
*/
if (elem->ns == DAV_NS_NONE) {
nsbuf[0] = '\0';
l_ns = 0;
}
else {
if (propdb->ns_map == NULL) {
/*
* Note that we prep the map and do NOT add namespaces. If that
* is required, then the caller should have called prep
* beforehand, passing the correct values.
*/
dav_prep_ns_map(propdb, 0);
}
ns = propdb->ns_map[elem->ns];
if (DAV_NS_IS_ERROR(ns))
return key; /* zeroed */
l_ns = sprintf(nsbuf, "%d", ns);
}
/* assemble: #:name */
dav_set_bufsize(propdb->p, &propdb->wb_key, l_ns + 1 + l_name + 1);
memcpy(propdb->wb_key.buf, nsbuf, l_ns);
propdb->wb_key.buf[l_ns] = ':';
memcpy(&propdb->wb_key.buf[l_ns + 1], elem->name, l_name + 1);
/* build the database key */
key.dsize = l_ns + 1 + l_name + 1;
key.dptr = propdb->wb_key.buf;
return key;
}
static dav_error *dav_really_open_db(dav_propdb *propdb, int ro)
{
dav_error *err;
dav_datum key;
dav_datum value = { 0 };
/* 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.
*/
if (propdb->db != NULL) {
key.dptr = DAV_GDBM_NS_KEY;
key.dsize = DAV_GDBM_NS_KEY_LEN;
if ((err = (*propdb->db_hooks->fetch)(propdb->db, key,
&value)) != NULL) {
/* ### push a higher-level description? */
return err;
}
}
if (value.dptr == NULL) {
dav_propdb_metadata m = {
DAV_DBVSN_MAJOR, DAV_DBVSN_MINOR, 0
};
if (propdb->db != NULL) {
/*
* If there is no METADATA key, then the database may be
* from versions 0.9.0 .. 0.9.4 (which would be incompatible).
* These can be identified by the presence of an NS_TABLE entry.
*/
key.dptr = "NS_TABLE";
key.dsize = 8;
if ((*propdb->db_hooks->exists)(propdb->db, key)) {
(*propdb->db_hooks->close)(propdb->db);
/* call it a major version error */
return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
DAV_ERR_PROP_BAD_MAJOR,
"Prop database has the wrong major "
"version number and cannot be used.");
}
}
/* initialize a new metadata structure */
dav_set_bufsize(propdb->p, &propdb->ns_table, sizeof(m));
memcpy(propdb->ns_table.buf, &m, sizeof(m));
}
else {
dav_propdb_metadata m;
dav_set_bufsize(propdb->p, &propdb->ns_table, value.dsize);
memcpy(propdb->ns_table.buf, value.dptr, value.dsize);
memcpy(&m, value.dptr, sizeof(m));
if (m.major != DAV_DBVSN_MAJOR) {
(*propdb->db_hooks->close)(propdb->db);
return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
DAV_ERR_PROP_BAD_MAJOR,
"Prop database has the wrong major "
"version number and cannot be used.");
}
propdb->version = m.minor;
propdb->ns_count = ntohs(m.ns_count);
(*propdb->db_hooks->freedatum)(propdb->db, value);
}
return NULL;
}
dav_error *dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
dav_resource *resource,
int ro,
ap_array_header_t * ns_xlate,
dav_propdb **p_propdb)
{
dav_propdb *propdb = ap_pcalloc(r->pool, sizeof(*propdb));
dav_error *err;
*p_propdb = NULL;
#if DAV_DEBUG
if (resource->uri == NULL) {
return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
"INTERNAL DESIGN ERROR: resource must define "
"its URI.");
}
#endif
propdb->version = DAV_DBVSN_MINOR;
propdb->r = r;
propdb->p = r->pool; /* ### get rid of this */
propdb->resource = resource;
propdb->ns_xlate = ns_xlate;
propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r);
propdb->vsn_hooks = DAV_GET_HOOKS_VSN(r);
propdb->liveprop = dav_get_provider_hooks(r, DAV_DYN_TYPE_LIVEPROP);
propdb->lockdb = lockdb;
if (!ro) {
propdb->deferred = 1;
}
else if ((err = dav_really_open_db(propdb, 1 /* ro */)) != NULL) {
return err;
}
/* ### what to do about closing the propdb on server failure? */
*p_propdb = propdb;
return NULL;
}
void dav_close_propdb(dav_propdb *propdb)
{
if (propdb->db == NULL)
return;
if (propdb->ns_table_dirty) {
dav_propdb_metadata m;
dav_datum key;
dav_datum value;
dav_error *err;
key.dptr = DAV_GDBM_NS_KEY;
key.dsize = DAV_GDBM_NS_KEY_LEN;
value.dptr = propdb->ns_table.buf;
value.dsize = propdb->ns_table.cur_len;
/* fill in the metadata that we store into the prop db. */
m.major = DAV_DBVSN_MAJOR;
m.minor = propdb->version; /* ### keep current minor version? */
m.ns_count = htons(propdb->ns_count);
memcpy(propdb->ns_table.buf, &m, sizeof(m));
err = (*propdb->db_hooks->store)(propdb->db, key, value);
/* ### what to do with the error? */
}
(*propdb->db_hooks->close)(propdb->db);
}
dav_get_props_result dav_get_allprops(dav_propdb *propdb, int getvals)
{
const dav_hooks_db *db_hooks = propdb->db_hooks;
dav_text_header hdr = { 0 };
dav_text_header hdr_ns = { 0 };
dav_get_props_result result = { 0 };
int found_resourcetype = 0;
int found_contenttype = 0;
int found_contentlang = 0;
int unused_inserted;
int i;
const char * const * scan_uri;
const dav_dyn_hooks *ddh;
/* generate all the namespaces that are in the propdb */
dav_get_propdb_xmlns(propdb, &hdr_ns);
/* initialize the result with some start tags... */
dav_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_datum key;
int dav_id = dav_find_dav_id(propdb);
(void) (*db_hooks->firstkey)(propdb->db, &key);
while (key.dptr) {
dav_datum prevkey;
/* any keys with leading capital letters should be skipped
(real keys start with a number or a colon) */
if (*key.dptr >= 'A' && *key.dptr <= 'Z')
goto next_key;
/*
** See if this is the <DAV:resourcetype> property. We need to
** know whether it was found (and therefore, whether to supply
** a default later).
**
** 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 (dav_id != -1
&& *key.dptr != ':'
&& dav_id == atoi(key.dptr)) {
const char *colon;
/* find the colon */
if ( key.dptr[1] == ':' ) {
colon = key.dptr + 1;
}
else {
colon = strchr(key.dptr + 2, ':');
}
if (colon[1] == 'r'
&& strcmp(colon + 1, "resourcetype") == 0) {
found_resourcetype = 1;
}
else if (colon[1] == 'g') {
if (strcmp(colon + 1, "getcontenttype") == 0) {
found_contenttype = 1;
}
else if (strcmp(colon + 1, "getcontentlanguage") == 0) {
found_contentlang = 1;
}
}
}
if (getvals) {
dav_datum value;
(void) (*db_hooks->fetch)(propdb->db, key, &value);
if (value.dptr == NULL) {
/* ### anything better to do? */
/* ### probably should enter a 500 error */
goto next_key;
}
/* put the prop name and value into the result */
dav_append_prop(propdb, key.dptr, value.dptr, &hdr);
(*db_hooks->freedatum)(propdb->db, value);
}
else {
/* simple, empty element if a value isn't needed */
dav_append_prop(propdb, key.dptr, DAV_EMPTY_VALUE, &hdr);
}
next_key:
prevkey = key;
(void) (*db_hooks->nextkey)(propdb->db, &key);
(*db_hooks->freedatum)(propdb->db, prevkey);
}
}
/* add namespaces for all the liveprop providers */
for (i = 0, scan_uri = (const char * const *)dav_liveprop_uris->elts;
i < dav_liveprop_uris->nelts;
++i, ++scan_uri)
dav_insert_xmlns(propdb->p, "lp", i, *scan_uri, &hdr_ns);
/* ask the liveprop providers to insert their properties */
for (ddh = propdb->liveprop; ddh != NULL; ddh = ddh->next) {
(*DAV_AS_HOOKS_LIVEPROP(ddh)->insert_all)(propdb->resource, getvals,
ddh->ctx.ns_map,
&hdr);
}
/* insert the standard properties */
/* ### should be handling the return errors here */
(void)dav_insert_coreprop(propdb,
DAV_PROPID_CORE_supportedlock, "supportedlock",
getvals, &hdr, &unused_inserted);
(void)dav_insert_coreprop(propdb,
DAV_PROPID_CORE_lockdiscovery, "lockdiscovery",
getvals, &hdr, &unused_inserted);
/* if the resourcetype wasn't stored, then prepare one */
if (!found_resourcetype) {
/* ### should be handling the return error here */
(void)dav_insert_coreprop(propdb,
DAV_PROPID_CORE_resourcetype, "resourcetype",
getvals, &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",
getvals, &hdr, &unused_inserted);
}
if (!found_contentlang) {
/* ### should be handling the return error here */
(void)dav_insert_coreprop(propdb,
DAV_PROPID_CORE_getcontentlanguage,
"getcontentlanguage",
getvals, &hdr, &unused_inserted);
}
/* terminate the result */
dav_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_get_props_result dav_get_props(dav_propdb *propdb, dav_xml_doc *doc)
{
const dav_hooks_db *db_hooks = propdb->db_hooks;
dav_xml_elem *elem = dav_find_child(doc->root, "prop");
dav_text_header hdr_good = { 0 };
dav_text_header hdr_bad = { 0 };
dav_text_header hdr_ns = { 0 };
int have_good = 0;
dav_get_props_result result = { 0 };
char *marks_input;
char *marks_liveprop;
/* ### 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 */
dav_text_append(propdb->p, &hdr_good,
"<D:propstat>" DEBUG_CR
"<D:prop>" DEBUG_CR);
/* generate all the namespaces that are in the propdb */
dav_get_propdb_xmlns(propdb, &hdr_ns);
/* ### the marks should be in a buffer! */
/* allocate zeroed-memory for the marks. These marks indicate which
input namespaces we've generated into the output xmlns buffer */
marks_input = ap_pcalloc(propdb->p, propdb->ns_xlate->nelts);
/* same for the liveprops */
marks_liveprop = ap_pcalloc(propdb->p, dav_liveprop_uris->nelts);
for (elem = elem->first_child; elem; elem = elem->next) {
dav_datum key;
dav_datum value = { 0 };
/*
** Note: the key may be NULL if we have no properties that are in
** a namespace that matches the requested prop's namespace.
*/
key = dav_gdbm_key(propdb, elem);
/* fetch IF we have a db and a key. otherwise, value is NULL */
if (propdb->db != NULL && key.dptr != NULL) {
(void) (*db_hooks->fetch)(propdb->db, key, &value);
}
/*
** If we did not find the property in the database, then it may
** be a liveprop that we can handle specially.
*/
if (value.dptr == NULL) {
dav_error *err;
int inserted;
/* cache the propid; dav_get_props() could be called many times */
if (elem->propid == 0)
dav_find_liveprop(propdb, elem);
/* insert the property. returns 1 if an insertion was done. */
if ((err = dav_insert_liveprop(propdb, elem, 1, &hdr_good,
&inserted)) != NULL) {
/* ### need to propagate the error to the caller... */
/* ### skip it for now, as if nothing was inserted */
}
if (inserted) {
have_good = 1;
/*
** Add the liveprop's namespace URIs. Note that provider==NULL
** for core properties.
*/
if (elem->provider != NULL) {
const char * const * scan_ns_uri;
const int * scan_ns;
for (scan_ns_uri = elem->provider->namespace_uris,
scan_ns = elem->ns_map;
*scan_ns_uri != NULL;
++scan_ns_uri, ++scan_ns) {
dav_add_marked_xmlns(propdb, marks_liveprop, *scan_ns,
dav_liveprop_uris, "lp", &hdr_ns);
}
}
continue;
}
}
if (value.dptr == NULL) {
/* not found. add a record to the "bad" propstats */
/* make sure we've started our "bad" propstat */
if (hdr_bad.first == NULL) {
dav_text_append(propdb->p, &hdr_bad,
"<D:propstat>" DEBUG_CR
"<D:prop>" DEBUG_CR);
}
/* note: key.dptr may be NULL if the propdb doesn't have an
equivalent namespace stored */
if (key.dptr == NULL) {
const char *s;
if (elem->ns == DAV_NS_NONE) {
/*
* elem has a prefix already (xml...:name) or the elem
* simply has no namespace.
*/
s = ap_psprintf(propdb->p, "<%s/>" DEBUG_CR, elem->name);
}
else {
/* ensure that an xmlns is generated for the
input namespace */
dav_add_marked_xmlns(propdb, marks_input, elem->ns,
propdb->ns_xlate, "i", &hdr_ns);
s = ap_psprintf(propdb->p, "<i%d:%s/>" DEBUG_CR,
elem->ns, elem->name);
}
dav_text_append(propdb->p, &hdr_bad, s);
}
else {
/* add in the bad prop using our namespace decl */
dav_append_prop(propdb, key.dptr, DAV_EMPTY_VALUE, &hdr_bad);
}
}
else {
/* found it. put the value into the "good" propstats */
have_good = 1;
dav_append_prop(propdb, key.dptr, value.dptr, &hdr_good);
(*db_hooks->freedatum)(propdb->db, value);
}
}
dav_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) {
dav_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;
}
}
result.xmlns = hdr_ns.first;
return result;
}
void dav_prop_validate(dav_prop_ctx *ctx)
{
dav_propdb *propdb = ctx->propdb;
dav_xml_elem *prop = ctx->prop;
/*
** 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 (prop->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 = prop->provider != NULL;
}
if (!dav_rw_liveprop(propdb, prop->propid)) {
ctx->err = dav_new_error(propdb->p, HTTP_CONFLICT,
DAV_ERR_PROP_READONLY,
"Property is read-only.");
return;
}
if (ctx->is_liveprop) {
int defer_to_dead = 0;
ctx->err = (*prop->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 writeable).
*/
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,
"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.
*/
dav_prep_ns_map(propdb, 1);
}
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).
*/
}
}
void dav_prop_exec(dav_prop_ctx *ctx)
{
dav_propdb *propdb = ctx->propdb;
dav_error *err = NULL;
dav_rollback_item *rollback;
rollback = ap_pcalloc(propdb->p, sizeof(*rollback));
ctx->rollback = rollback;
if (ctx->is_liveprop) {
err = (*ctx->prop->provider->patch_exec)(propdb->resource,
ctx->prop, ctx->operation,
ctx->liveprop_ctx,
&ctx->rollback->liveprop);
}
else {
dav_datum key;
/* we're going to need the key for all operations */
key = dav_gdbm_key(propdb, ctx->prop);
/* save the old value so that we can do a rollback. */
rollback->key = key;
if ((err = (*propdb->db_hooks->fetch)(propdb->db, key,
&rollback->value)) != NULL)
goto error;
if (ctx->operation == DAV_PROP_OP_SET) {
dav_datum value;
/* Note: propdb->ns_map was set in dav_prop_validate() */
/* quote all the values in the element */
dav_quote_xml_elem(propdb->p, ctx->prop);
/* generate a text blob for the xml:lang plus the contents */
dav_xml2text(propdb->p, ctx->prop, DAV_X2T_LANG_INNER, NULL,
propdb->ns_map,
(const char **)&value.dptr, &value.dsize);
err = (*propdb->db_hooks->store)(propdb->db, key, value);
/*
** 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.
*/
}
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, key);
}
}
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);
}
}
void dav_prop_commit(dav_prop_ctx *ctx)
{
/*
** 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) {
(*ctx->prop->provider->patch_commit)(ctx->propdb->resource,
ctx->operation,
ctx->liveprop_ctx,
ctx->rollback->liveprop);
}
}
void dav_prop_rollback(dav_prop_ctx *ctx)
{
dav_error *err = NULL;
/* 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 = (*ctx->prop->provider->patch_rollback)(ctx->propdb->resource,
ctx->operation,
ctx->liveprop_ctx,
ctx->rollback->liveprop);
}
else if (ctx->rollback->value.dptr == NULL) {
/* don't fail if the thing isn't really there */
/* ### but what about other errors? */
(void) (*ctx->propdb->db_hooks->remove)(ctx->propdb->db,
ctx->rollback->key);
}
else {
err = (*ctx->propdb->db_hooks->store)(ctx->propdb->db,
ctx->rollback->key,
ctx->rollback->value);
}
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;
}
}
}