1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2001 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
56 ** DAV extension module for Apache 2.0.*
57 ** - Property database handling (repository-independent)
63 ** This version assumes that there is a per-resource database provider
64 ** to record properties. The database provider decides how and where to
65 ** store these databases.
67 ** The DBM keys for the properties have the following form:
69 ** namespace ":" propname
71 ** For example: 5:author
73 ** The namespace provides an integer index into the namespace table
74 ** (see below). propname is simply the property name, without a namespace
77 ** A special case exists for properties that had a prefix starting with
78 ** "xml". The XML Specification reserves these for future use. mod_dav
79 ** stores and retrieves them unchanged. The keys for these properties
84 ** The propname will contain the prefix and the property name. For
85 ** example, a key might be ":xmlfoo:name"
87 ** The ":name" style will also be used for properties that do not
88 ** exist within a namespace.
90 ** The DBM values consist of two null-terminated strings, appended
91 ** together (the null-terms are retained and stored in the database).
92 ** The first string is the xml:lang value for the property. An empty
93 ** string signifies that a lang value was not in context for the value.
94 ** The second string is the property value itself.
99 ** The namespace table is an array that lists each of the namespaces
100 ** that are in use by the properties in the given propdb. Each entry
101 ** in the array is a simple URI.
103 ** For example: http://www.foo.bar/standards/props/
105 ** The prefix used for the property is stripped and the URI for it
106 ** is entered into the namespace table. Also, any namespaces used
107 ** within the property value will be entered into the table (and
108 ** stripped from the child elements).
110 ** The namespaces are stored in the DBM database under the "METADATA" key.
113 ** STRIPPING NAMESPACES
115 ** Within the property values, the namespace declarations (xmlns...)
116 ** are stripped. Each element and attribute will have its prefix removed
117 ** and a new prefix inserted.
119 ** This must be done so that we can return multiple properties in a
120 ** PROPFIND which may have (originally) used conflicting prefixes. For
121 ** that case, we must bind all property value elements to new namespace
124 ** This implies that clients must NOT be sensitive to the namespace
125 ** prefix used for their properties. It WILL change when the properties
126 ** are returned (we return them as "ns<index>", e.g. "ns5"). Also, the
127 ** property value can contain ONLY XML elements and CDATA. PI and comment
128 ** elements will be stripped. CDATA whitespace will be preserved, but
129 ** whitespace within element tags will be altered. Attribute ordering
130 ** may be altered. Element and CDATA ordering will be preserved.
133 ** ATTRIBUTES ON PROPERTY NAME ELEMENTS
135 ** When getting/setting properties, the XML used looks like:
138 ** <propname1>value</propname1>
139 ** <propname2>value</propname1>
142 ** This implementation (mod_dav) DOES NOT save any attributes that are
143 ** associated with the <propname1> element. The property value is deemed
144 ** to be only the contents ("value" in the above example).
146 ** We do store the xml:lang value (if any) that applies to the context
147 ** of the <propname1> element. Whether the xml:lang attribute is on
148 ** <propname1> itself, or from a higher level element, we will store it
149 ** with the property value.
154 ** The DBM db contains a key named "METADATA" that holds database-level
155 ** information, such as the namespace table. The record also contains the
156 ** db's version number as the very first 16-bit value. This first number
157 ** is actually stored as two single bytes: the first byte is a "major"
158 ** version number. The second byte is a "minor" number.
160 ** If the major number is not what mod_dav expects, then the db is closed
161 ** immediately and an error is returned. A minor number change is
162 ** acceptable -- it is presumed that old/new dav_props.c can deal with
163 ** the database format. For example, a newer dav_props might update the
164 ** minor value and append information to the end of the metadata record
165 ** (which would be ignored by previous versions).
170 ** At the moment, for the dav_get_allprops() and dav_get_props() functions,
171 ** we must return a set of xmlns: declarations for ALL known namespaces
172 ** in the file. There isn't a way to filter this because we don't know
173 ** which are going to be used or not. Examining property names is not
174 ** sufficient because the property values could use entirely different
177 ** ==> we must devise a scheme where we can "garbage collect" the namespace
178 ** entries from the property database.
182 #include "apr_strings.h"
184 #define APR_WANT_STDIO
185 #define APR_WANT_BYTEFUNC
186 #include "apr_want.h"
190 #include "http_log.h"
191 #include "http_request.h"
194 ** There is some rough support for writable DAV:getcontenttype and
195 ** DAV:getcontentlanguage properties. If this #define is (1), then
196 ** this support is disabled.
198 ** We are disabling it because of a lack of support in GET and PUT
199 ** operations. For GET, it would be "expensive" to look for a propdb,
200 ** open it, and attempt to extract the Content-Type and Content-Language
201 ** values for the response.
202 ** (Handling the PUT would not be difficult, though)
204 #define DAV_DISABLE_WRITABLE_PROPS 1
206 #define DAV_GDBM_NS_KEY "METADATA"
207 #define DAV_GDBM_NS_KEY_LEN 8
209 #define DAV_EMPTY_VALUE "\0" /* TWO null terms */
211 /* the namespace URI was not found; no ID is available */
212 #define AP_XML_NS_ERROR_NOT_FOUND (AP_XML_NS_ERROR_BASE)
216 #define DAV_DBVSN_MAJOR 4
219 ** Prior versions could have keys or values with invalid
220 ** namespace prefixes as a result of the xmlns="" form not
221 ** resetting the default namespace to be "no namespace". The
222 ** namespace would be set to "" which is invalid; it should
223 ** be set to "no namespace".
226 ** Prior versions could have values with invalid namespace
227 ** prefixes due to an incorrect mapping of input to propdb
228 ** namespace indices. Version bumped to obsolete the old
232 ** This introduced the xml:lang value into the property value's
233 ** record in the propdb.
241 #define DAV_DBVSN_MINOR 0
245 } dav_propdb_metadata;
248 int version; /* *minor* version of this db */
250 apr_pool_t *p; /* the pool we should use */
251 request_rec *r; /* the request record */
253 const dav_resource *resource; /* the target resource */
255 int deferred; /* open of db has been deferred */
256 dav_db *db; /* underlying database containing props */
258 dav_buffer ns_table; /* table of namespace URIs */
259 short ns_count; /* number of entries in table */
260 int ns_table_dirty; /* ns_table was modified */
262 apr_array_header_t *ns_xlate; /* translation of an elem->ns to URI */
263 int *ns_map; /* map elem->ns to propdb ns values */
264 int incomplete_map; /* some mappings do not exist */
266 dav_lockdb *lockdb; /* the lock database */
268 dav_buffer wb_key; /* work buffer for dav_gdbm_key */
269 dav_buffer wb_lock; /* work buffer for lockdiscovery property */
271 /* if we ever run a GET subreq, it will be stored here */
274 /* hooks we should use for processing (based on the target resource) */
275 const dav_hooks_db *db_hooks;
279 /* NOTE: dav_core_props[] and the following enum must stay in sync. */
280 /* ### move these into a "core" liveprop provider? */
281 static const char * const dav_core_props[] =
284 "getcontentlanguage",
291 DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE,
292 DAV_PROPID_CORE_getcontentlanguage,
293 DAV_PROPID_CORE_lockdiscovery,
294 DAV_PROPID_CORE_supportedlock,
296 DAV_PROPID_CORE_UNKNOWN
300 ** This structure is used to track information needed for a rollback.
301 ** If a SET was performed and no prior value existed, then value.dptr
304 typedef struct dav_rollback_item {
305 dav_datum key; /* key for the item being saved */
306 dav_datum value; /* value before set/replace/delete */
308 /* or use the following (choice selected by dav_prop_ctx.is_liveprop) */
309 struct dav_liveprop_rollback *liveprop; /* liveprop rollback ctx */
316 static const char *dav_get_ns_table_uri(dav_propdb *propdb, int ns)
318 const char *p = propdb->ns_table.buf + sizeof(dav_propdb_metadata);
327 static int dav_find_liveprop_provider(dav_propdb *propdb,
329 const char *propname,
330 const dav_hooks_liveprop **provider)
336 if (ns_uri == NULL) {
337 /* policy: liveprop providers cannot define no-namespace properties */
338 return DAV_PROPID_CORE_UNKNOWN;
340 else if (strcmp(ns_uri, "DAV:") == 0) {
341 const char * const *p = dav_core_props;
343 for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid)
344 if (strcmp(propname, *p) == 0) {
348 /* didn't find it. fall thru. a provider can define DAV: props */
351 /* is there a liveprop provider for this property? */
352 propid = dav_run_find_liveprop(propdb->resource, ns_uri, propname,
358 /* no provider for this property */
359 return DAV_PROPID_CORE_UNKNOWN;
362 static void dav_find_liveprop(dav_propdb *propdb, ap_xml_elem *elem)
365 dav_elem_private *priv = elem->private;
366 const dav_hooks_liveprop *hooks;
369 if (elem->ns == AP_XML_NS_NONE)
371 else if (elem->ns == AP_XML_NS_DAV_ID)
374 ns_uri = AP_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
376 priv->propid = dav_find_liveprop_provider(propdb, ns_uri, elem->name,
379 /* ### this test seems redundant... */
380 if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
381 priv->provider = hooks;
385 /* is the live property read/write? */
386 static int dav_rw_liveprop(dav_propdb *propdb, dav_elem_private *priv)
388 int propid = priv->propid;
391 ** Check the liveprop provider (if this is a provider-defined prop)
393 if (priv->provider != NULL) {
394 return (*priv->provider->is_writable)(propdb->resource, propid);
397 /* these are defined as read-only */
398 if (propid == DAV_PROPID_CORE_lockdiscovery
399 #if DAV_DISABLE_WRITABLE_PROPS
400 || propid == DAV_PROPID_CORE_getcontenttype
401 || propid == DAV_PROPID_CORE_getcontentlanguage
403 || propid == DAV_PROPID_CORE_supportedlock
409 /* these are defined as read/write */
410 if (propid == DAV_PROPID_CORE_getcontenttype
411 || propid == DAV_PROPID_CORE_getcontentlanguage
412 || propid == DAV_PROPID_CORE_UNKNOWN) {
418 ** We don't recognize the property, so it must be dead (and writable)
423 /* do a sub-request to fetch properties for the target resource's URI. */
424 static void dav_do_prop_subreq(dav_propdb *propdb)
426 /* perform a "GET" on the resource's URI (note that the resource
427 may not correspond to the current request!). */
428 propdb->subreq = ap_sub_req_lookup_uri(propdb->resource->uri, propdb->r,
432 static dav_error * dav_insert_coreprop(dav_propdb *propdb,
433 int propid, const char *name,
434 dav_prop_insert what,
435 ap_text_header *phdr,
436 dav_prop_insert *inserted)
438 const char *value = NULL;
441 *inserted = DAV_PROP_INSERT_NOTDEF;
443 /* fast-path the common case */
444 if (propid == DAV_PROPID_CORE_UNKNOWN)
449 case DAV_PROPID_CORE_lockdiscovery:
450 if (propdb->lockdb != NULL) {
453 if ((err = dav_lock_query(propdb->lockdb, propdb->resource,
455 return dav_push_error(propdb->p, err->status, 0,
456 "DAV:lockdiscovery could not be "
457 "determined due to a problem fetching "
458 "the locks for this resource.",
462 /* fast-path the no-locks case */
468 ** This may modify the buffer. value may point to
469 ** wb_lock.pbuf or a string constant.
471 value = dav_lock_get_activelock(propdb->r, locks,
474 /* make a copy to isolate it from changes to wb_lock */
475 value = apr_pstrdup(propdb->p, propdb->wb_lock.buf);
480 case DAV_PROPID_CORE_supportedlock:
481 if (propdb->lockdb != NULL) {
482 value = (*propdb->lockdb->hooks->get_supportedlock)(propdb->resource);
486 case DAV_PROPID_CORE_getcontenttype:
487 if (propdb->subreq == NULL) {
488 dav_do_prop_subreq(propdb);
490 if (propdb->subreq->content_type != NULL) {
491 value = propdb->subreq->content_type;
495 case DAV_PROPID_CORE_getcontentlanguage:
499 if (propdb->subreq == NULL) {
500 dav_do_prop_subreq(propdb);
502 if ((lang = apr_table_get(propdb->subreq->headers_out,
503 "Content-Language")) != NULL) {
510 /* fall through to interpret as a dead property */
514 /* if something was supplied, then insert it */
518 if (what == DAV_PROP_INSERT_SUPPORTED) {
519 /* use D: prefix to refer to the DAV: namespace URI,
520 * and let the namespace attribute default to "DAV:"
522 s = apr_psprintf(propdb->p,
523 "<D:supported-live-property D:name=\"%s\"/>" DEBUG_CR,
526 else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
527 /* use D: prefix to refer to the DAV: namespace URI */
528 s = apr_psprintf(propdb->p, "<D:%s>%s</D:%s>" DEBUG_CR,
532 /* use D: prefix to refer to the DAV: namespace URI */
533 s = apr_psprintf(propdb->p, "<D:%s/>" DEBUG_CR, name);
535 ap_text_append(propdb->p, phdr, s);
543 static dav_error * dav_insert_liveprop(dav_propdb *propdb,
544 const ap_xml_elem *elem,
545 dav_prop_insert what,
546 ap_text_header *phdr,
547 dav_prop_insert *inserted)
549 dav_elem_private *priv = elem->private;
551 *inserted = DAV_PROP_INSERT_NOTDEF;
553 if (priv->provider == NULL) {
554 /* this is a "core" property that we define */
555 return dav_insert_coreprop(propdb, priv->propid, elem->name,
556 what, phdr, inserted);
559 /* ask the provider (that defined this prop) to insert the prop */
560 *inserted = (*priv->provider->insert_prop)(propdb->resource, priv->propid,
566 static void dav_append_prop(dav_propdb *propdb,
567 const char *name, const char *value,
568 ap_text_header *phdr)
571 const char *lang = value;
573 /* skip past the xml:lang value */
574 value += strlen(lang) + 1;
576 if (*value == '\0') {
577 /* the property is an empty value */
579 /* "no namespace" case */
580 s = apr_psprintf(propdb->p, "<%s/>" DEBUG_CR, name+1);
583 s = apr_psprintf(propdb->p, "<ns%s/>" DEBUG_CR, name);
586 else if (*lang != '\0') {
588 /* "no namespace" case */
589 s = apr_psprintf(propdb->p, "<%s xml:lang=\"%s\">%s</%s>" DEBUG_CR,
590 name+1, lang, value, name+1);
593 s = apr_psprintf(propdb->p, "<ns%s xml:lang=\"%s\">%s</ns%s>" DEBUG_CR,
594 name, lang, value, name);
597 else if (*name == ':') {
598 /* "no namespace" case */
599 s = apr_psprintf(propdb->p, "<%s>%s</%s>" DEBUG_CR, name+1, value, name+1);
602 s = apr_psprintf(propdb->p, "<ns%s>%s</ns%s>" DEBUG_CR, name, value, name);
604 ap_text_append(propdb->p, phdr, s);
608 ** Prepare the ns_map variable in the propdb structure. This entails copying
609 ** all URIs from the "input" namespace list (in propdb->ns_xlate) into the
610 ** propdb's list of namespaces. As each URI is copied (or pre-existing
611 ** URI looked up), the index mapping is stored into the ns_map variable.
613 ** Note: we must copy all declared namespaces because we cannot easily
614 ** determine which input namespaces were actually used within the property
615 ** values that are being stored within the propdb. Theoretically, we can
616 ** determine this at the point where we serialize the property values
617 ** back into strings. This would require a bit more work, and will be
618 ** left to future optimizations.
620 ** ### we should always initialize the propdb namespace array with "DAV:"
621 ** ### since we know it will be entered anyhow (by virtue of it always
622 ** ### occurring in the ns_xlate array). That will allow us to use
623 ** ### AP_XML_NS_DAV_ID for propdb ns values, too.
625 static void dav_prep_ns_map(dav_propdb *propdb, int add_ns)
629 const int orig_count = propdb->ns_count;
631 int updating = 0; /* are we updating an existing ns_map? */
633 if (propdb->ns_map) {
634 if (add_ns && propdb->incomplete_map) {
635 /* we must revisit the map and insert new entries */
637 propdb->incomplete_map = 0;
640 /* nothing to do: we have a proper ns_map */
645 propdb->ns_map = apr_palloc(propdb->p, propdb->ns_xlate->nelts * sizeof(*propdb->ns_map));
648 pmap = propdb->ns_map;
650 /* ### stupid O(n * orig_count) algorithm */
651 for (i = propdb->ns_xlate->nelts, puri = (const char **)propdb->ns_xlate->elts;
655 const char *uri = *puri;
656 const size_t uri_len = strlen(uri);
659 /* updating an existing mapping... we can skip a lot of stuff */
661 if (*pmap != AP_XML_NS_ERROR_NOT_FOUND) {
662 /* This entry has been filled in, so we can skip it */
672 ** GIVEN: uri (a namespace URI from the request input)
674 ** FIND: an equivalent URI in the propdb namespace table
677 /* only scan original entries (we may have added some in here) */
678 for (p = propdb->ns_table.buf + sizeof(dav_propdb_metadata),
687 if (memcmp(uri, p, len) == 0) {
694 *pmap = AP_XML_NS_ERROR_NOT_FOUND;
697 ** This flag indicates that we have an ns_map with missing
698 ** entries. If dav_prep_ns_map() is called with add_ns==1 AND
699 ** this flag is set, then we zip thru the array and add those
700 ** URIs (effectively updating the ns_map as if add_ns=1 was
701 ** passed when the initial prep was called).
703 propdb->incomplete_map = 1;
710 ** The input URI was not found in the propdb namespace table, and
711 ** we are supposed to add it. Append it to the table and store
712 ** the index into the ns_map.
714 dav_check_bufsize(propdb->p, &propdb->ns_table, uri_len + 1);
715 memcpy(propdb->ns_table.buf + propdb->ns_table.cur_len, uri, uri_len + 1);
716 propdb->ns_table.cur_len += uri_len + 1;
718 propdb->ns_table_dirty = 1;
720 *pmap = propdb->ns_count++;
727 /* find the "DAV:" namespace in our table and return its ID. */
728 static int dav_find_dav_id(dav_propdb *propdb)
730 const char *p = propdb->ns_table.buf + sizeof(dav_propdb_metadata);
733 for (ns = 0; ns < propdb->ns_count; ++ns) {
734 size_t len = strlen(p);
736 if (len == 4 && memcmp(p, "DAV:", 5) == 0)
741 /* the "DAV:" namespace is not present */
745 static void dav_insert_xmlns(apr_pool_t *p, const char *pre_prefix, int ns,
746 const char *ns_uri, ap_text_header *phdr)
750 s = apr_psprintf(p, " xmlns:%s%d=\"%s\"", pre_prefix, ns, ns_uri);
751 ap_text_append(p, phdr, s);
754 /* return all known namespaces (in this propdb) */
755 static void dav_get_propdb_xmlns(dav_propdb *propdb, ap_text_header *phdr)
758 const char *p = propdb->ns_table.buf + sizeof(dav_propdb_metadata);
761 /* note: ns_count == 0 when we have no propdb file */
762 for (i = 0; i < propdb->ns_count; ++i, p += len + 1) {
766 dav_insert_xmlns(propdb->p, "ns", i, p, phdr);
770 /* add a namespace decl from one of the namespace tables */
771 static void dav_add_marked_xmlns(dav_propdb *propdb, char *marks, int ns,
772 apr_array_header_t *ns_table,
773 const char *pre_prefix,
774 ap_text_header *phdr)
780 dav_insert_xmlns(propdb->p,
781 pre_prefix, ns, AP_XML_GET_URI_ITEM(ns_table, ns),
786 ** Internal function to build a key
788 ** WARNING: returns a pointer to a "static" buffer holding the key. The
789 ** value must be copied or no longer used if this function is
792 static dav_datum dav_gdbm_key(dav_propdb *propdb, const ap_xml_elem *elem)
797 size_t l_name = strlen(elem->name);
798 dav_datum key = { 0 };
801 * Convert namespace ID to a string. "no namespace" is an empty string,
802 * so the keys will have the form ":name". Otherwise, the keys will
803 * have the form "#:name".
805 if (elem->ns == AP_XML_NS_NONE) {
810 if (propdb->ns_map == NULL) {
812 * Note that we prep the map and do NOT add namespaces. If that
813 * is required, then the caller should have called prep
814 * beforehand, passing the correct values.
816 dav_prep_ns_map(propdb, 0);
819 ns = propdb->ns_map[elem->ns];
820 if (AP_XML_NS_IS_ERROR(ns))
821 return key; /* zeroed */
823 l_ns = sprintf(nsbuf, "%d", ns);
826 /* assemble: #:name */
827 dav_set_bufsize(propdb->p, &propdb->wb_key, l_ns + 1 + l_name + 1);
828 memcpy(propdb->wb_key.buf, nsbuf, l_ns);
829 propdb->wb_key.buf[l_ns] = ':';
830 memcpy(&propdb->wb_key.buf[l_ns + 1], elem->name, l_name + 1);
832 /* build the database key */
833 key.dsize = l_ns + 1 + l_name + 1;
834 key.dptr = propdb->wb_key.buf;
839 static dav_error *dav_really_open_db(dav_propdb *propdb, int ro)
843 dav_datum value = { 0 };
845 /* we're trying to open the db; turn off the 'deferred' flag */
846 propdb->deferred = 0;
848 /* ask the DB provider to open the thing */
849 err = (*propdb->db_hooks->open)(propdb->p, propdb->resource, ro,
852 return dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
853 DAV_ERR_PROP_OPENING,
854 "Could not open the property database.",
859 ** NOTE: propdb->db could be NULL if we attempted to open a readonly
860 ** database that doesn't exist. If we require read/write
861 ** access, then a database was created and opened.
864 if (propdb->db != NULL) {
865 key.dptr = DAV_GDBM_NS_KEY;
866 key.dsize = DAV_GDBM_NS_KEY_LEN;
867 if ((err = (*propdb->db_hooks->fetch)(propdb->db, key,
869 /* ### push a higher-level description? */
873 if (value.dptr == NULL) {
874 dav_propdb_metadata m = {
875 DAV_DBVSN_MAJOR, DAV_DBVSN_MINOR, 0
878 if (propdb->db != NULL) {
880 * If there is no METADATA key, then the database may be
881 * from versions 0.9.0 .. 0.9.4 (which would be incompatible).
882 * These can be identified by the presence of an NS_TABLE entry.
884 key.dptr = "NS_TABLE";
886 if ((*propdb->db_hooks->exists)(propdb->db, key)) {
887 (*propdb->db_hooks->close)(propdb->db);
889 /* call it a major version error */
890 return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
891 DAV_ERR_PROP_BAD_MAJOR,
892 "Prop database has the wrong major "
893 "version number and cannot be used.");
897 /* initialize a new metadata structure */
898 dav_set_bufsize(propdb->p, &propdb->ns_table, sizeof(m));
899 memcpy(propdb->ns_table.buf, &m, sizeof(m));
902 dav_propdb_metadata m;
904 dav_set_bufsize(propdb->p, &propdb->ns_table, value.dsize);
905 memcpy(propdb->ns_table.buf, value.dptr, value.dsize);
907 memcpy(&m, value.dptr, sizeof(m));
908 if (m.major != DAV_DBVSN_MAJOR) {
909 (*propdb->db_hooks->close)(propdb->db);
911 return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
912 DAV_ERR_PROP_BAD_MAJOR,
913 "Prop database has the wrong major "
914 "version number and cannot be used.");
916 propdb->version = m.minor;
917 propdb->ns_count = ntohs(m.ns_count);
919 (*propdb->db_hooks->freedatum)(propdb->db, value);
925 dav_error *dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
926 const dav_resource *resource,
928 apr_array_header_t * ns_xlate,
929 dav_propdb **p_propdb)
931 dav_propdb *propdb = apr_pcalloc(r->pool, sizeof(*propdb));
936 if (resource->uri == NULL) {
937 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
938 "INTERNAL DESIGN ERROR: resource must define "
943 propdb->version = DAV_DBVSN_MINOR;
945 propdb->p = r->pool; /* ### get rid of this */
946 propdb->resource = resource;
947 propdb->ns_xlate = ns_xlate;
949 propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r);
951 propdb->lockdb = lockdb;
953 /* always defer actual open, to avoid expense of accessing db
954 * when only live properties are involved
956 propdb->deferred = 1;
958 /* ### what to do about closing the propdb on server failure? */
964 void dav_close_propdb(dav_propdb *propdb)
966 if (propdb->db == NULL)
969 if (propdb->ns_table_dirty) {
970 dav_propdb_metadata m;
975 key.dptr = DAV_GDBM_NS_KEY;
976 key.dsize = DAV_GDBM_NS_KEY_LEN;
978 value.dptr = propdb->ns_table.buf;
979 value.dsize = propdb->ns_table.cur_len;
981 /* fill in the metadata that we store into the prop db. */
982 m.major = DAV_DBVSN_MAJOR;
983 m.minor = propdb->version; /* ### keep current minor version? */
984 m.ns_count = htons(propdb->ns_count);
986 memcpy(propdb->ns_table.buf, &m, sizeof(m));
988 err = (*propdb->db_hooks->store)(propdb->db, key, value);
989 /* ### what to do with the error? */
992 (*propdb->db_hooks->close)(propdb->db);
995 dav_get_props_result dav_get_allprops(dav_propdb *propdb, dav_prop_insert what)
997 const dav_hooks_db *db_hooks = propdb->db_hooks;
998 ap_text_header hdr = { 0 };
999 ap_text_header hdr_ns = { 0 };
1000 dav_get_props_result result = { 0 };
1001 int found_contenttype = 0;
1002 int found_contentlang = 0;
1003 dav_prop_insert unused_inserted;
1005 /* if not just getting supported live properties,
1006 * scan all properties in the dead prop database
1008 if (what != DAV_PROP_INSERT_SUPPORTED) {
1009 if (propdb->deferred) {
1010 /* ### what to do with db open error? */
1011 (void) dav_really_open_db(propdb, 1 /*ro*/);
1014 /* generate all the namespaces that are in the propdb */
1015 dav_get_propdb_xmlns(propdb, &hdr_ns);
1017 /* initialize the result with some start tags... */
1018 ap_text_append(propdb->p, &hdr,
1019 "<D:propstat>" DEBUG_CR
1020 "<D:prop>" DEBUG_CR);
1022 /* if there ARE properties, then scan them */
1023 if (propdb->db != NULL) {
1025 int dav_id = dav_find_dav_id(propdb);
1027 (void) (*db_hooks->firstkey)(propdb->db, &key);
1031 /* any keys with leading capital letters should be skipped
1032 (real keys start with a number or a colon) */
1033 if (*key.dptr >= 'A' && *key.dptr <= 'Z')
1037 ** We also look for <DAV:getcontenttype> and
1038 ** <DAV:getcontentlanguage>. If they are not stored as dead
1039 ** properties, then we need to perform a subrequest to get
1040 ** their values (if any).
1044 && dav_id == atoi(key.dptr)) {
1048 /* find the colon */
1049 if ( key.dptr[1] == ':' ) {
1050 colon = key.dptr + 1;
1053 colon = strchr(key.dptr + 2, ':');
1056 if (colon[1] == 'g') {
1057 if (strcmp(colon + 1, "getcontenttype") == 0) {
1058 found_contenttype = 1;
1060 else if (strcmp(colon + 1, "getcontentlanguage") == 0) {
1061 found_contentlang = 1;
1066 if (what == DAV_PROP_INSERT_VALUE) {
1069 (void) (*db_hooks->fetch)(propdb->db, key, &value);
1070 if (value.dptr == NULL) {
1071 /* ### anything better to do? */
1072 /* ### probably should enter a 500 error */
1076 /* put the prop name and value into the result */
1077 dav_append_prop(propdb, key.dptr, value.dptr, &hdr);
1079 (*db_hooks->freedatum)(propdb->db, value);
1082 /* simple, empty element if a value isn't needed */
1083 dav_append_prop(propdb, key.dptr, DAV_EMPTY_VALUE, &hdr);
1088 (void) (*db_hooks->nextkey)(propdb->db, &key);
1089 (*db_hooks->freedatum)(propdb->db, prevkey);
1093 /* add namespaces for all the liveprop providers */
1094 dav_add_all_liveprop_xmlns(propdb->p, &hdr_ns);
1097 /* ask the liveprop providers to insert their properties */
1098 dav_run_insert_all_liveprops(propdb->r, propdb->resource, what, &hdr);
1100 /* insert the standard properties */
1101 /* ### should be handling the return errors here */
1102 (void)dav_insert_coreprop(propdb,
1103 DAV_PROPID_CORE_supportedlock, "supportedlock",
1104 what, &hdr, &unused_inserted);
1105 (void)dav_insert_coreprop(propdb,
1106 DAV_PROPID_CORE_lockdiscovery, "lockdiscovery",
1107 what, &hdr, &unused_inserted);
1109 /* if we didn't find these, then do the whole subreq thing. */
1110 if (!found_contenttype) {
1111 /* ### should be handling the return error here */
1112 (void)dav_insert_coreprop(propdb,
1113 DAV_PROPID_CORE_getcontenttype,
1115 what, &hdr, &unused_inserted);
1117 if (!found_contentlang) {
1118 /* ### should be handling the return error here */
1119 (void)dav_insert_coreprop(propdb,
1120 DAV_PROPID_CORE_getcontentlanguage,
1121 "getcontentlanguage",
1122 what, &hdr, &unused_inserted);
1125 /* if not just reporting on supported live props,
1126 * terminate the result */
1127 if (what != DAV_PROP_INSERT_SUPPORTED) {
1128 ap_text_append(propdb->p, &hdr,
1129 "</D:prop>" DEBUG_CR
1130 "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
1131 "</D:propstat>" DEBUG_CR);
1134 result.propstats = hdr.first;
1135 result.xmlns = hdr_ns.first;
1139 dav_get_props_result dav_get_props(dav_propdb *propdb, ap_xml_doc *doc)
1141 const dav_hooks_db *db_hooks = propdb->db_hooks;
1142 ap_xml_elem *elem = dav_find_child(doc->root, "prop");
1143 ap_text_header hdr_good = { 0 };
1144 ap_text_header hdr_bad = { 0 };
1145 ap_text_header hdr_ns = { 0 };
1147 int propdb_xmlns_done = 0;
1148 dav_get_props_result result = { 0 };
1150 char *marks_liveprop;
1152 /* ### NOTE: we should pass in TWO buffers -- one for keys, one for
1155 /* we will ALWAYS provide a "good" result, even if it is EMPTY */
1156 ap_text_append(propdb->p, &hdr_good,
1157 "<D:propstat>" DEBUG_CR
1158 "<D:prop>" DEBUG_CR);
1160 /* ### the marks should be in a buffer! */
1161 /* allocate zeroed-memory for the marks. These marks indicate which
1162 input namespaces we've generated into the output xmlns buffer */
1163 marks_input = apr_pcalloc(propdb->p, propdb->ns_xlate->nelts);
1165 /* same for the liveprops */
1166 marks_liveprop = apr_pcalloc(propdb->p, dav_get_liveprop_ns_count() + 1);
1168 for (elem = elem->first_child; elem; elem = elem->next) {
1169 dav_datum key = { 0 };
1170 dav_datum value = { 0 };
1171 dav_elem_private *priv;
1173 dav_prop_insert inserted;
1174 int is_liveprop = 0;
1177 ** First try live property providers; if they don't handle
1178 ** the property, then try looking it up in the propdb.
1181 if (elem->private == NULL) {
1182 elem->private = apr_pcalloc(propdb->p, sizeof(*priv));
1184 priv = elem->private;
1186 /* cache the propid; dav_get_props() could be called many times */
1187 if (priv->propid == 0)
1188 dav_find_liveprop(propdb, elem);
1190 if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
1193 /* insert the property. returns 1 if an insertion was done. */
1194 if ((err = dav_insert_liveprop(propdb, elem, DAV_PROP_INSERT_VALUE,
1195 &hdr_good, &inserted)) != NULL) {
1196 /* ### need to propagate the error to the caller... */
1197 /* ### skip it for now, as if nothing was inserted */
1199 if (inserted == DAV_PROP_INSERT_VALUE) {
1203 ** Add the liveprop's namespace URIs. Note that provider==NULL
1204 ** for core properties.
1206 if (priv->provider != NULL) {
1207 const char * const * scan_ns_uri;
1209 for (scan_ns_uri = priv->provider->namespace_uris;
1210 *scan_ns_uri != NULL;
1214 ns = dav_get_liveprop_ns_index(*scan_ns_uri);
1215 if (marks_liveprop[ns])
1217 marks_liveprop[ns] = 1;
1219 dav_insert_xmlns(propdb->p, "lp", ns, *scan_ns_uri,
1226 else if (inserted == DAV_PROP_INSERT_NOTDEF) {
1227 /* allow property to be handled as a dead property */
1233 ** If not handled as a live property, look in the dead property
1237 /* make sure propdb is really open */
1238 if (propdb->deferred) {
1239 /* ### what to do with db open error? */
1240 (void) dav_really_open_db(propdb, 1 /*ro*/);
1244 * generate all the namespaces that are in the propdb
1246 if (!propdb_xmlns_done) {
1247 dav_get_propdb_xmlns(propdb, &hdr_ns);
1248 propdb_xmlns_done = 1;
1252 ** Note: the key may be NULL if we have no properties that are in
1253 ** a namespace that matches the requested prop's namespace.
1255 key = dav_gdbm_key(propdb, elem);
1257 /* fetch IF we have a db and a key. otherwise, value is NULL */
1258 if (propdb->db != NULL && key.dptr != NULL) {
1259 (void) (*db_hooks->fetch)(propdb->db, key, &value);
1263 if (value.dptr == NULL) {
1264 /* not found. add a record to the "bad" propstats */
1266 /* make sure we've started our "bad" propstat */
1267 if (hdr_bad.first == NULL) {
1268 ap_text_append(propdb->p, &hdr_bad,
1269 "<D:propstat>" DEBUG_CR
1270 "<D:prop>" DEBUG_CR);
1273 /* note: key.dptr may be NULL if the propdb doesn't have an
1274 equivalent namespace stored */
1275 if (key.dptr == NULL) {
1278 if (elem->ns == AP_XML_NS_NONE) {
1280 * elem has a prefix already (xml...:name) or the elem
1281 * simply has no namespace.
1283 s = apr_psprintf(propdb->p, "<%s/>" DEBUG_CR, elem->name);
1286 /* ensure that an xmlns is generated for the
1288 dav_add_marked_xmlns(propdb, marks_input, elem->ns,
1289 propdb->ns_xlate, "i", &hdr_ns);
1290 s = apr_psprintf(propdb->p, "<i%d:%s/>" DEBUG_CR,
1291 elem->ns, elem->name);
1293 ap_text_append(propdb->p, &hdr_bad, s);
1296 /* add in the bad prop using our namespace decl */
1297 dav_append_prop(propdb, key.dptr, DAV_EMPTY_VALUE, &hdr_bad);
1301 /* found it. put the value into the "good" propstats */
1305 dav_append_prop(propdb, key.dptr, value.dptr, &hdr_good);
1307 (*db_hooks->freedatum)(propdb->db, value);
1311 ap_text_append(propdb->p, &hdr_good,
1312 "</D:prop>" DEBUG_CR
1313 "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
1314 "</D:propstat>" DEBUG_CR);
1316 /* default to start with the good */
1317 result.propstats = hdr_good.first;
1319 /* we may not have any "bad" results */
1320 if (hdr_bad.first != NULL) {
1321 ap_text_append(propdb->p, &hdr_bad,
1322 "</D:prop>" DEBUG_CR
1323 "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
1324 "</D:propstat>" DEBUG_CR);
1326 /* if there are no good props, then just return the bad */
1328 result.propstats = hdr_bad.first;
1331 /* hook the bad propstat to the end of the good one */
1332 hdr_good.last->next = hdr_bad.first;
1336 result.xmlns = hdr_ns.first;
1340 void dav_get_liveprop_supported(dav_propdb *propdb,
1342 const char *propname,
1343 ap_text_header *body)
1346 const dav_hooks_liveprop *hooks;
1348 propid = dav_find_liveprop_provider(propdb, ns_uri, propname, &hooks);
1350 if (propid != DAV_PROPID_CORE_UNKNOWN) {
1351 if (hooks == NULL) {
1352 /* this is a "core" property that we define */
1353 dav_prop_insert unused_inserted;
1354 dav_insert_coreprop(propdb, propid, propname,
1355 DAV_PROP_INSERT_SUPPORTED, body, &unused_inserted);
1358 (*hooks->insert_prop)(propdb->resource, propid,
1359 DAV_PROP_INSERT_SUPPORTED, body);
1364 void dav_prop_validate(dav_prop_ctx *ctx)
1366 dav_propdb *propdb = ctx->propdb;
1367 ap_xml_elem *prop = ctx->prop;
1368 dav_elem_private *priv;
1370 priv = ctx->prop->private = apr_pcalloc(propdb->p, sizeof(*priv));
1373 ** Check to see if this is a live property, and fill the fields
1374 ** in the XML elem, as appropriate.
1376 ** Verify that the property is read/write. If not, then it cannot
1377 ** be SET or DELETEd.
1379 if (priv->propid == 0) {
1380 dav_find_liveprop(propdb, prop);
1382 /* it's a liveprop if a provider was found */
1383 /* ### actually the "core" props should really be liveprops, but
1384 ### there is no "provider" for those and the r/w props are
1385 ### treated as dead props anyhow */
1386 ctx->is_liveprop = priv->provider != NULL;
1389 if (!dav_rw_liveprop(propdb, priv)) {
1390 ctx->err = dav_new_error(propdb->p, HTTP_CONFLICT,
1391 DAV_ERR_PROP_READONLY,
1392 "Property is read-only.");
1396 if (ctx->is_liveprop) {
1397 int defer_to_dead = 0;
1399 ctx->err = (*priv->provider->patch_validate)(propdb->resource,
1400 prop, ctx->operation,
1403 if (ctx->err != NULL || !defer_to_dead)
1406 /* clear is_liveprop -- act as a dead prop now */
1407 ctx->is_liveprop = 0;
1411 ** The property is supposed to be stored into the dead-property
1412 ** database. Make sure the thing is truly open (and writable).
1414 if (propdb->deferred
1415 && (ctx->err = dav_really_open_db(propdb, 0 /* ro */)) != NULL) {
1420 ** There should be an open, writable database in here!
1422 ** Note: the database would be NULL if it was opened readonly and it
1425 if (propdb->db == NULL) {
1426 ctx->err = dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
1427 DAV_ERR_PROP_NO_DATABASE,
1428 "Attempted to set/remove a property "
1429 "without a valid, open, read/write "
1430 "property database.");
1434 if (ctx->operation == DAV_PROP_OP_SET) {
1436 ** Prep the element => propdb namespace index mapping, inserting
1437 ** namespace URIs into the propdb that don't exist.
1439 dav_prep_ns_map(propdb, 1);
1441 else if (ctx->operation == DAV_PROP_OP_DELETE) {
1443 ** There are no checks to perform here. If a property exists, then
1444 ** we will delete it. If it does not exist, then it does not matter
1447 ** Note that if a property does not exist, that does not rule out
1448 ** that a SET will occur during this PROPPATCH (thusly creating it).
1453 void dav_prop_exec(dav_prop_ctx *ctx)
1455 dav_propdb *propdb = ctx->propdb;
1456 dav_error *err = NULL;
1457 dav_rollback_item *rollback;
1458 dav_elem_private *priv = ctx->prop->private;
1460 rollback = apr_pcalloc(propdb->p, sizeof(*rollback));
1461 ctx->rollback = rollback;
1463 if (ctx->is_liveprop) {
1464 err = (*priv->provider->patch_exec)(propdb->resource,
1465 ctx->prop, ctx->operation,
1467 &ctx->rollback->liveprop);
1472 /* we're going to need the key for all operations */
1473 key = dav_gdbm_key(propdb, ctx->prop);
1475 /* save the old value so that we can do a rollback. */
1476 rollback->key = key;
1477 if ((err = (*propdb->db_hooks->fetch)(propdb->db, key,
1478 &rollback->value)) != NULL)
1481 if (ctx->operation == DAV_PROP_OP_SET) {
1485 /* Note: propdb->ns_map was set in dav_prop_validate() */
1487 /* quote all the values in the element */
1488 ap_xml_quote_elem(propdb->p, ctx->prop);
1490 /* generate a text blob for the xml:lang plus the contents */
1491 ap_xml_to_text(propdb->p, ctx->prop, AP_XML_X2T_LANG_INNER, NULL,
1493 (const char **)&value.dptr, &value.dsize);
1495 err = (*propdb->db_hooks->store)(propdb->db, key, value);
1498 ** If an error occurred, then assume that we didn't change the
1499 ** value. Remove the rollback item so that we don't try to set
1500 ** its value during the rollback.
1503 else if (ctx->operation == DAV_PROP_OP_DELETE) {
1506 ** Delete the property. Ignore errors -- the property is there, or
1507 ** we are deleting it for a second time.
1509 /* ### but what about other errors? */
1510 (void) (*propdb->db_hooks->remove)(propdb->db, key);
1515 /* push a more specific error here */
1518 ** Use HTTP_INTERNAL_SERVER_ERROR because we shouldn't have seen
1519 ** any errors at this point.
1521 ctx->err = dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
1523 "Could not execute PROPPATCH.", err);
1527 void dav_prop_commit(dav_prop_ctx *ctx)
1529 dav_elem_private *priv = ctx->prop->private;
1532 ** Note that a commit implies ctx->err is NULL. The caller should assume
1533 ** a status of HTTP_OK for this case.
1536 if (ctx->is_liveprop) {
1537 (*priv->provider->patch_commit)(ctx->propdb->resource,
1540 ctx->rollback->liveprop);
1544 void dav_prop_rollback(dav_prop_ctx *ctx)
1546 dav_error *err = NULL;
1547 dav_elem_private *priv = ctx->prop->private;
1549 /* do nothing if there is no rollback information. */
1550 if (ctx->rollback == NULL)
1554 ** ### if we have an error, and a rollback occurs, then the namespace
1555 ** ### mods should not happen at all. Basically, the namespace management
1556 ** ### is simply a bitch.
1559 if (ctx->is_liveprop) {
1560 err = (*priv->provider->patch_rollback)(ctx->propdb->resource,
1563 ctx->rollback->liveprop);
1565 else if (ctx->rollback->value.dptr == NULL) {
1566 /* don't fail if the thing isn't really there */
1567 /* ### but what about other errors? */
1568 (void) (*ctx->propdb->db_hooks->remove)(ctx->propdb->db,
1569 ctx->rollback->key);
1572 err = (*ctx->propdb->db_hooks->store)(ctx->propdb->db,
1574 ctx->rollback->value);
1578 if (ctx->err == NULL)
1581 dav_error *scan = err;
1583 /* hook previous errors at the end of the rollback error */
1584 while (scan->prev != NULL)
1586 scan->prev = ctx->err;