/* 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.
*/
/* ** 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? */ staticconstchar * const dav_core_props[] =
{ "getcontenttype", "getcontentlanguage", "lockdiscovery", "supportedlock",
/* ** This structure is used to track information needed for a rollback.
*/ typedefstruct 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;
/* ### this test seems redundant... */ if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
priv->provider = hooks;
}
}
/* is the live property read/write? */ staticint 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. */ staticvoid 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. */ constchar *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);
}
/* 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:
{ constchar *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) { constchar *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, "",
name, "\"/>" DEBUG_CR, NULL);
} elseif (what == DAV_PROP_INSERT_VALUE && *value != '\0') { /* use D: prefix to refer to the DAV: namespace URI */
s = apr_pstrcat(propdb->p, ", name, ">", value, ", name, ">" DEBUG_CR, NULL);
} else { /* use D: prefix to refer to the DAV: namespace URI */
s = apr_pstrcat(propdb->p, ", name, "/>" DEBUG_CR, NULL);
}
apr_text_append(propdb->p, phdr, s);
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);
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.
*/
/* 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, "" DEBUG_CR "" 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;
} elseif (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);
}
/* 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, "" DEBUG_CR "HTTP/1.1 200 OK" DEBUG_CR "" DEBUG_CR);
}
/* 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, "" DEBUG_CR "" 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);
/* ** 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) { constchar * 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;
/* property added. move on to the next property. */ continue;
} elseif (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*/);
}
/* 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, "" DEBUG_CR "" DEBUG_CR);
}
/* output this property's name (into the bad propstats) */
dav_output_prop_name(propdb->p, &name, xi, &hdr_bad);
}
/* 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, "" DEBUG_CR "HTTP/1.1 404 Not Found" DEBUG_CR "" 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;
/* ** 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;
}
/* 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);
} elseif (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).
*/
}
}
/* 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? */
} elseif (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);
}
}
/* 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.
*/
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.