/*
** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
**
** By using this file, you agree to the terms and conditions set forth in
** the LICENSE.html file which can be found at the top level of the mod_dav
** distribution or at http://www.webdav.org/mod_dav/license-1.html.
**
** Contact information:
**   Greg Stein, PO Box 760, Palo Alto, CA, 94302
**   gstein@lyra.org, http://www.webdav.org/mod_dav/
*/

/*
** DAV extension module for Apache 1.3.*
**  - Property database handling (repository-independent)
**
** Written by Greg Stein, gstein@lyra.org, http://www.lyra.org/
**
** 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 "mod_dav.h"

#include "http_log.h"
#include "http_request.h"

/*
** There is some rough support for writeable 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_WRITEABLE_PROPS	1

#define DAV_GDBM_NS_KEY		"METADATA"
#define DAV_GDBM_NS_KEY_LEN	8

#define DAV_EMPTY_VALUE		"\0"	/* TWO null terms */

/* the namespace URI was not found; no ID is available */
#define DAV_NS_ERROR_NOT_FOUND	(DAV_NS_ERROR_BASE)

typedef struct {
    unsigned char major;
#define DAV_DBVSN_MAJOR		4
    /*
    ** V4 -- 0.9.9 ..
    **       Prior versions could have keys or values with invalid
    **       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 */

    pool *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 */

    array_header *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(pool *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);

        /* ### temporary/transitory fix. some old prop databases may have
           ### namespaces with empty URIs in them. they should not be placed
           ### into the output, so let's just skip them. new prop database
           ### will not have them. */
        if (len)
            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,
				 array_header *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;
}

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,
			   array_header * 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->subreq != NULL)
	ap_destroy_sub_req(propdb->subreq);

    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;
	}
    }
}
