1180 lines
40 KiB
C
1180 lines
40 KiB
C
/* 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 */
|
|
|
|
#define DAV_PROP_ELEMENT "mod_dav-element"
|
|
|
|
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 */
|
|
|
|
int flags; /* ro, disable lock discovery */
|
|
|
|
/* 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->p,
|
|
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->flags & DAV_PROPDB_DISABLE_LOCKDISCOVERY) {
|
|
value = "";
|
|
break;
|
|
}
|
|
|
|
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_pstrcat(propdb->p,
|
|
"<D:supported-live-property D:name=\"",
|
|
name, "\"/>" DEBUG_CR, NULL);
|
|
}
|
|
else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
|
|
/* use D: prefix to refer to the DAV: namespace URI */
|
|
s = apr_pstrcat(propdb->p, "<D:", name, ">", value, "</D:", name,
|
|
">" DEBUG_CR, NULL);
|
|
}
|
|
else {
|
|
/* use D: prefix to refer to the DAV: namespace URI */
|
|
s = apr_pstrcat(propdb->p, "<D:", name, "/>" DEBUG_CR, NULL);
|
|
}
|
|
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_pstrcat(pool, "<", name->name, "/>" DEBUG_CR, NULL);
|
|
else {
|
|
const char *prefix = dav_xmlns_add_uri(xi, name->ns);
|
|
|
|
s = apr_pstrcat(pool, "<", prefix, ":", name->name, "/>" DEBUG_CR, NULL);
|
|
}
|
|
|
|
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 flags,
|
|
apr_array_header_t * ns_xlate,
|
|
dav_propdb **p_propdb)
|
|
{
|
|
return dav_popen_propdb(r->pool, r, lockdb, resource,
|
|
flags, ns_xlate, p_propdb);
|
|
}
|
|
|
|
DAV_DECLARE(dav_error *)dav_popen_propdb(apr_pool_t *p,
|
|
request_rec *r, dav_lockdb *lockdb,
|
|
const dav_resource *resource,
|
|
int flags,
|
|
apr_array_header_t * ns_xlate,
|
|
dav_propdb **p_propdb)
|
|
{
|
|
dav_propdb *propdb = NULL;
|
|
|
|
propdb = apr_pcalloc(p, sizeof(*propdb));
|
|
propdb->p = p;
|
|
|
|
*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;
|
|
propdb->resource = resource;
|
|
propdb->ns_xlate = ns_xlate;
|
|
|
|
propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r);
|
|
|
|
propdb->lockdb = lockdb;
|
|
|
|
propdb->flags = flags;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
if (propdb->subreq) {
|
|
ap_destroy_sub_req(propdb->subreq);
|
|
propdb->subreq = NULL;
|
|
}
|
|
}
|
|
|
|
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 };
|
|
dav_liveprop_elem *element;
|
|
char *marks_liveprop;
|
|
dav_xmlns_info *xi;
|
|
int xi_filled = 0;
|
|
|
|
/* we lose both the document and the element when calling (insert_prop),
|
|
* make these available in the pool.
|
|
*/
|
|
element = dav_get_liveprop_element(propdb->resource);
|
|
if (!element) {
|
|
element = apr_pcalloc(propdb->resource->pool, sizeof(dav_liveprop_elem));
|
|
apr_pool_userdata_setn(element, DAV_PROP_ELEMENT, NULL, propdb->resource->pool);
|
|
}
|
|
else {
|
|
memset(element, 0, sizeof(dav_liveprop_elem));
|
|
}
|
|
element->doc = doc;
|
|
|
|
/* ### 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;
|
|
|
|
element->elem = elem;
|
|
|
|
/*
|
|
** 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 outlives propdb->p. Hence use the request pool */
|
|
elem->priv = apr_pcalloc(propdb->r->pool, 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(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource *resource)
|
|
{
|
|
dav_liveprop_elem *element;
|
|
|
|
apr_pool_userdata_get((void **)&element, DAV_PROP_ELEMENT, resource->pool);
|
|
|
|
return element;
|
|
}
|
|
|
|
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.
|
|
**
|
|
** http://tools.ietf.org/html/rfc4918#section-14.23 says
|
|
** "Specifying the removal of a property that does not exist is
|
|
** not an error"
|
|
*/
|
|
/* ### 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;
|
|
}
|
|
}
|
|
}
|