1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18 ** DAV extension module for Apache 2.0.*
19 **  - Property database handling (repository-independent)
20 **
21 ** NOTES:
22 **
23 **   PROPERTY DATABASE
24 **
25 **   This version assumes that there is a per-resource database provider
26 **   to record properties. The database provider decides how and where to
27 **   store these databases.
28 **
29 **   The DBM keys for the properties have the following form:
30 **
31 **     namespace ":" propname
32 **
33 **   For example: 5:author
34 **
35 **   The namespace provides an integer index into the namespace table
36 **   (see below). propname is simply the property name, without a namespace
37 **   prefix.
38 **
39 **   A special case exists for properties that had a prefix starting with
40 **   "xml". The XML Specification reserves these for future use. mod_dav
41 **   stores and retrieves them unchanged. The keys for these properties
42 **   have the form:
43 **
44 **     ":" propname
45 **
46 **   The propname will contain the prefix and the property name. For
47 **   example, a key might be ":xmlfoo:name"
48 **
49 **   The ":name" style will also be used for properties that do not
50 **   exist within a namespace.
51 **
52 **   The DBM values consist of two null-terminated strings, appended
53 **   together (the null-terms are retained and stored in the database).
54 **   The first string is the xml:lang value for the property. An empty
55 **   string signifies that a lang value was not in context for the value.
56 **   The second string is the property value itself.
57 **
58 **
59 **   NAMESPACE TABLE
60 **
61 **   The namespace table is an array that lists each of the namespaces
62 **   that are in use by the properties in the given propdb. Each entry
63 **   in the array is a simple URI.
64 **
65 **   For example: http://www.foo.bar/standards/props/
66 **
67 **   The prefix used for the property is stripped and the URI for it
68 **   is entered into the namespace table. Also, any namespaces used
69 **   within the property value will be entered into the table (and
70 **   stripped from the child elements).
71 **
72 **   The namespaces are stored in the DBM database under the "METADATA" key.
73 **
74 **
75 **   STRIPPING NAMESPACES
76 **
77 **   Within the property values, the namespace declarations (xmlns...)
78 **   are stripped. Each element and attribute will have its prefix removed
79 **   and a new prefix inserted.
80 **
81 **   This must be done so that we can return multiple properties in a
82 **   PROPFIND which may have (originally) used conflicting prefixes. For
83 **   that case, we must bind all property value elements to new namespace
84 **   values.
85 **
86 **   This implies that clients must NOT be sensitive to the namespace
87 **   prefix used for their properties. It WILL change when the properties
88 **   are returned (we return them as "ns<index>", e.g. "ns5"). Also, the
89 **   property value can contain ONLY XML elements and CDATA. PI and comment
90 **   elements will be stripped. CDATA whitespace will be preserved, but
91 **   whitespace within element tags will be altered. Attribute ordering
92 **   may be altered. Element and CDATA ordering will be preserved.
93 **
94 **
95 **   ATTRIBUTES ON PROPERTY NAME ELEMENTS
96 **
97 **   When getting/setting properties, the XML used looks like:
98 **
99 **     <prop>
100 **       <propname1>value</propname1>
101 **       <propname2>value</propname1>
102 **     </prop>
103 **
104 **   This implementation (mod_dav) DOES NOT save any attributes that are
105 **   associated with the <propname1> element. The property value is deemed
106 **   to be only the contents ("value" in the above example).
107 **
108 **   We do store the xml:lang value (if any) that applies to the context
109 **   of the <propname1> element. Whether the xml:lang attribute is on
110 **   <propname1> itself, or from a higher level element, we will store it
111 **   with the property value.
112 **
113 **
114 **   VERSIONING
115 **
116 **   The DBM db contains a key named "METADATA" that holds database-level
117 **   information, such as the namespace table. The record also contains the
118 **   db's version number as the very first 16-bit value. This first number
119 **   is actually stored as two single bytes: the first byte is a "major"
120 **   version number. The second byte is a "minor" number.
121 **
122 **   If the major number is not what mod_dav expects, then the db is closed
123 **   immediately and an error is returned. A minor number change is
124 **   acceptable -- it is presumed that old/new dav_props.c can deal with
125 **   the database format. For example, a newer dav_props might update the
126 **   minor value and append information to the end of the metadata record
127 **   (which would be ignored by previous versions).
128 **
129 **
130 ** ISSUES:
131 **
132 **   At the moment, for the dav_get_allprops() and dav_get_props() functions,
133 **   we must return a set of xmlns: declarations for ALL known namespaces
134 **   in the file. There isn't a way to filter this because we don't know
135 **   which are going to be used or not. Examining property names is not
136 **   sufficient because the property values could use entirely different
137 **   namespaces.
138 **
139 **   ==> we must devise a scheme where we can "garbage collect" the namespace
140 **       entries from the property database.
141 */
142 
143 #include "apr.h"
144 #include "apr_strings.h"
145 
146 #define APR_WANT_STDIO
147 #define APR_WANT_BYTEFUNC
148 #include "apr_want.h"
149 
150 #include "mod_dav.h"
151 
152 #include "http_log.h"
153 #include "http_request.h"
154 
155 /*
156 ** There is some rough support for writable DAV:getcontenttype and
157 ** DAV:getcontentlanguage properties. If this #define is (1), then
158 ** this support is disabled.
159 **
160 ** We are disabling it because of a lack of support in GET and PUT
161 ** operations. For GET, it would be "expensive" to look for a propdb,
162 ** open it, and attempt to extract the Content-Type and Content-Language
163 ** values for the response.
164 ** (Handling the PUT would not be difficult, though)
165 */
166 #define DAV_DISABLE_WRITABLE_PROPS     1
167 
168 #define DAV_EMPTY_VALUE                "\0"    /* TWO null terms */
169 
170 #define DAV_PROP_ELEMENT "mod_dav-element"
171 
172 struct dav_propdb {
173     apr_pool_t *p;                /* the pool we should use */
174     request_rec *r;               /* the request record */
175 
176     const dav_resource *resource; /* the target resource */
177 
178     int deferred;                 /* open of db has been deferred */
179     dav_db *db;                   /* underlying database containing props */
180 
181     apr_array_header_t *ns_xlate; /* translation of an elem->ns to URI */
182     dav_namespace_map *mapping;   /* namespace mapping */
183 
184     dav_lockdb *lockdb;           /* the lock database */
185 
186     dav_buffer wb_lock;           /* work buffer for lockdiscovery property */
187 
188     /* if we ever run a GET subreq, it will be stored here */
189     request_rec *subreq;
190 
191     /* hooks we should use for processing (based on the target resource) */
192     const dav_hooks_db *db_hooks;
193 };
194 
195 /* NOTE: dav_core_props[] and the following enum must stay in sync. */
196 /* ### move these into a "core" liveprop provider? */
197 static const char * const dav_core_props[] =
198 {
199     "getcontenttype",
200     "getcontentlanguage",
201     "lockdiscovery",
202     "supportedlock",
203 
204     NULL        /* sentinel */
205 };
206 enum {
207     DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE,
208     DAV_PROPID_CORE_getcontentlanguage,
209     DAV_PROPID_CORE_lockdiscovery,
210     DAV_PROPID_CORE_supportedlock,
211 
212     DAV_PROPID_CORE_UNKNOWN
213 };
214 
215 /*
216 ** This structure is used to track information needed for a rollback.
217 */
218 typedef struct dav_rollback_item {
219     /* select one of the two rollback context structures based on the
220        value of dav_prop_ctx.is_liveprop */
221     dav_deadprop_rollback *deadprop;
222     dav_liveprop_rollback *liveprop;
223 
224 } dav_rollback_item;
225 
226 
dav_find_liveprop_provider(dav_propdb * propdb,const char * ns_uri,const char * propname,const dav_hooks_liveprop ** provider)227 static int dav_find_liveprop_provider(dav_propdb *propdb,
228                                       const char *ns_uri,
229                                       const char *propname,
230                                       const dav_hooks_liveprop **provider)
231 {
232     int propid;
233 
234     *provider = NULL;
235 
236     if (ns_uri == NULL) {
237         /* policy: liveprop providers cannot define no-namespace properties */
238         return DAV_PROPID_CORE_UNKNOWN;
239     }
240 
241     /* check liveprop providers first, so they can define core properties */
242     propid = dav_run_find_liveprop(propdb->resource, ns_uri, propname,
243                                    provider);
244     if (propid != 0) {
245         return propid;
246     }
247 
248     /* check for core property */
249     if (strcmp(ns_uri, "DAV:") == 0) {
250         const char * const *p = dav_core_props;
251 
252         for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid)
253             if (strcmp(propname, *p) == 0) {
254                 return propid;
255             }
256     }
257 
258     /* no provider for this property */
259     return DAV_PROPID_CORE_UNKNOWN;
260 }
261 
dav_find_liveprop(dav_propdb * propdb,apr_xml_elem * elem)262 static void dav_find_liveprop(dav_propdb *propdb, apr_xml_elem *elem)
263 {
264     const char *ns_uri;
265     dav_elem_private *priv = elem->priv;
266     const dav_hooks_liveprop *hooks;
267 
268 
269     if (elem->ns == APR_XML_NS_NONE)
270         ns_uri = NULL;
271     else if (elem->ns == APR_XML_NS_DAV_ID)
272         ns_uri = "DAV:";
273     else
274         ns_uri = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
275 
276     priv->propid = dav_find_liveprop_provider(propdb, ns_uri, elem->name,
277                                               &hooks);
278 
279     /* ### this test seems redundant... */
280     if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
281         priv->provider = hooks;
282     }
283 }
284 
285 /* is the live property read/write? */
dav_rw_liveprop(dav_propdb * propdb,dav_elem_private * priv)286 static int dav_rw_liveprop(dav_propdb *propdb, dav_elem_private *priv)
287 {
288     int propid = priv->propid;
289 
290     /*
291     ** Check the liveprop provider (if this is a provider-defined prop)
292     */
293     if (priv->provider != NULL) {
294         return (*priv->provider->is_writable)(propdb->resource, propid);
295     }
296 
297     /* these are defined as read-only */
298     if (propid == DAV_PROPID_CORE_lockdiscovery
299 #if DAV_DISABLE_WRITABLE_PROPS
300         || propid == DAV_PROPID_CORE_getcontenttype
301         || propid == DAV_PROPID_CORE_getcontentlanguage
302 #endif
303         || propid == DAV_PROPID_CORE_supportedlock
304         ) {
305 
306         return 0;
307     }
308 
309     /* these are defined as read/write */
310     if (propid == DAV_PROPID_CORE_getcontenttype
311         || propid == DAV_PROPID_CORE_getcontentlanguage
312         || propid == DAV_PROPID_CORE_UNKNOWN) {
313 
314         return 1;
315     }
316 
317     /*
318     ** We don't recognize the property, so it must be dead (and writable)
319     */
320     return 1;
321 }
322 
323 /* do a sub-request to fetch properties for the target resource's URI. */
dav_do_prop_subreq(dav_propdb * propdb)324 static void dav_do_prop_subreq(dav_propdb *propdb)
325 {
326     /* need to escape the uri that's in the resource struct because during
327      * the property walker it's not encoded. */
328     const char *e_uri = ap_escape_uri(propdb->p,
329                                       propdb->resource->uri);
330 
331     /* perform a "GET" on the resource's URI (note that the resource
332        may not correspond to the current request!). */
333     propdb->subreq = ap_sub_req_lookup_uri(e_uri, propdb->r, NULL);
334 }
335 
dav_insert_coreprop(dav_propdb * propdb,int propid,const char * name,dav_prop_insert what,apr_text_header * phdr,dav_prop_insert * inserted)336 static dav_error * dav_insert_coreprop(dav_propdb *propdb,
337                                        int propid, const char *name,
338                                        dav_prop_insert what,
339                                        apr_text_header *phdr,
340                                        dav_prop_insert *inserted)
341 {
342     const char *value = NULL;
343     dav_error *err;
344 
345     *inserted = DAV_PROP_INSERT_NOTDEF;
346 
347     /* fast-path the common case */
348     if (propid == DAV_PROPID_CORE_UNKNOWN)
349         return NULL;
350 
351     switch (propid) {
352 
353     case DAV_PROPID_CORE_lockdiscovery:
354         if (propdb->lockdb != NULL) {
355             dav_lock *locks;
356 
357             if ((err = dav_lock_query(propdb->lockdb, propdb->resource,
358                                       &locks)) != NULL) {
359                 return dav_push_error(propdb->p, err->status, 0,
360                                       "DAV:lockdiscovery could not be "
361                                       "determined due to a problem fetching "
362                                       "the locks for this resource.",
363                                       err);
364             }
365 
366             /* fast-path the no-locks case */
367             if (locks == NULL) {
368                 value = "";
369             }
370             else {
371                 /*
372                 ** This may modify the buffer. value may point to
373                 ** wb_lock.pbuf or a string constant.
374                 */
375                 value = dav_lock_get_activelock(propdb->r, locks,
376                                                 &propdb->wb_lock);
377 
378                 /* make a copy to isolate it from changes to wb_lock */
379                 value = apr_pstrdup(propdb->p, propdb->wb_lock.buf);
380             }
381         }
382         break;
383 
384     case DAV_PROPID_CORE_supportedlock:
385         if (propdb->lockdb != NULL) {
386             value = (*propdb->lockdb->hooks->get_supportedlock)(propdb->resource);
387         }
388         break;
389 
390     case DAV_PROPID_CORE_getcontenttype:
391         if (propdb->subreq == NULL) {
392             dav_do_prop_subreq(propdb);
393         }
394         if (propdb->subreq->content_type != NULL) {
395             value = propdb->subreq->content_type;
396         }
397         break;
398 
399     case DAV_PROPID_CORE_getcontentlanguage:
400     {
401         const char *lang;
402 
403         if (propdb->subreq == NULL) {
404             dav_do_prop_subreq(propdb);
405         }
406         if ((lang = apr_table_get(propdb->subreq->headers_out,
407                                  "Content-Language")) != NULL) {
408             value = lang;
409         }
410         break;
411     }
412 
413     default:
414         /* fall through to interpret as a dead property */
415         break;
416     }
417 
418     /* if something was supplied, then insert it */
419     if (value != NULL) {
420         const char *s;
421 
422         if (what == DAV_PROP_INSERT_SUPPORTED) {
423             /* use D: prefix to refer to the DAV: namespace URI,
424              * and let the namespace attribute default to "DAV:"
425              */
426             s = apr_pstrcat(propdb->p,
427                             "<D:supported-live-property D:name=\"",
428                             name, "\"/>" DEBUG_CR, NULL);
429         }
430         else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
431             /* use D: prefix to refer to the DAV: namespace URI */
432             s = apr_pstrcat(propdb->p, "<D:", name, ">", value, "</D:", name,
433                             ">" DEBUG_CR, NULL);
434         }
435         else {
436             /* use D: prefix to refer to the DAV: namespace URI */
437             s = apr_pstrcat(propdb->p, "<D:", name, "/>" DEBUG_CR, NULL);
438         }
439         apr_text_append(propdb->p, phdr, s);
440 
441         *inserted = what;
442     }
443 
444     return NULL;
445 }
446 
dav_insert_liveprop(dav_propdb * propdb,const apr_xml_elem * elem,dav_prop_insert what,apr_text_header * phdr,dav_prop_insert * inserted)447 static dav_error * dav_insert_liveprop(dav_propdb *propdb,
448                                        const apr_xml_elem *elem,
449                                        dav_prop_insert what,
450                                        apr_text_header *phdr,
451                                        dav_prop_insert *inserted)
452 {
453     dav_elem_private *priv = elem->priv;
454 
455     *inserted = DAV_PROP_INSERT_NOTDEF;
456 
457     if (priv->provider == NULL) {
458         /* this is a "core" property that we define */
459         return dav_insert_coreprop(propdb, priv->propid, elem->name,
460                                    what, phdr, inserted);
461     }
462 
463     /* ask the provider (that defined this prop) to insert the prop */
464     *inserted = (*priv->provider->insert_prop)(propdb->resource, priv->propid,
465                                                what, phdr);
466 
467     return NULL;
468 }
469 
dav_output_prop_name(apr_pool_t * pool,const dav_prop_name * name,dav_xmlns_info * xi,apr_text_header * phdr)470 static void dav_output_prop_name(apr_pool_t *pool,
471                                  const dav_prop_name *name,
472                                  dav_xmlns_info *xi,
473                                  apr_text_header *phdr)
474 {
475     const char *s;
476 
477     if (*name->ns == '\0')
478         s = apr_pstrcat(pool, "<", name->name, "/>" DEBUG_CR, NULL);
479     else {
480         const char *prefix = dav_xmlns_add_uri(xi, name->ns);
481 
482         s = apr_pstrcat(pool, "<", prefix, ":", name->name, "/>" DEBUG_CR, NULL);
483     }
484 
485     apr_text_append(pool, phdr, s);
486 }
487 
dav_insert_xmlns(apr_pool_t * p,const char * pre_prefix,long ns,const char * ns_uri,apr_text_header * phdr)488 static void dav_insert_xmlns(apr_pool_t *p, const char *pre_prefix, long ns,
489                              const char *ns_uri, apr_text_header *phdr)
490 {
491     const char *s;
492 
493     s = apr_psprintf(p, " xmlns:%s%ld=\"%s\"", pre_prefix, ns, ns_uri);
494     apr_text_append(p, phdr, s);
495 }
496 
dav_really_open_db(dav_propdb * propdb,int ro)497 static dav_error *dav_really_open_db(dav_propdb *propdb, int ro)
498 {
499     dav_error *err;
500 
501     /* we're trying to open the db; turn off the 'deferred' flag */
502     propdb->deferred = 0;
503 
504     /* ask the DB provider to open the thing */
505     err = (*propdb->db_hooks->open)(propdb->p, propdb->resource, ro,
506                                     &propdb->db);
507     if (err != NULL) {
508         return dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
509                               DAV_ERR_PROP_OPENING,
510                               "Could not open the property database.",
511                               err);
512     }
513 
514     /*
515     ** NOTE: propdb->db could be NULL if we attempted to open a readonly
516     **       database that doesn't exist. If we require read/write
517     **       access, then a database was created and opened.
518     */
519 
520     return NULL;
521 }
522 
dav_open_propdb(request_rec * r,dav_lockdb * lockdb,const dav_resource * resource,int ro,apr_array_header_t * ns_xlate,dav_propdb ** p_propdb)523 DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
524                                         const dav_resource *resource,
525                                         int ro,
526                                         apr_array_header_t * ns_xlate,
527                                         dav_propdb **p_propdb)
528 {
529     return dav_popen_propdb(r->pool, r, lockdb, resource, ro, ns_xlate, p_propdb);
530 }
531 
dav_popen_propdb(apr_pool_t * p,request_rec * r,dav_lockdb * lockdb,const dav_resource * resource,int ro,apr_array_header_t * ns_xlate,dav_propdb ** p_propdb)532 DAV_DECLARE(dav_error *)dav_popen_propdb(apr_pool_t *p,
533                                          request_rec *r, dav_lockdb *lockdb,
534                                          const dav_resource *resource,
535                                          int ro,
536                                          apr_array_header_t * ns_xlate,
537                                          dav_propdb **p_propdb)
538 {
539     dav_propdb *propdb = NULL;
540 
541     propdb = apr_pcalloc(p, sizeof(*propdb));
542     propdb->p = p;
543 
544     *p_propdb = NULL;
545 
546 #if DAV_DEBUG
547     if (resource->uri == NULL) {
548         return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
549                              "INTERNAL DESIGN ERROR: resource must define "
550                              "its URI.");
551     }
552 #endif
553 
554     propdb->r = r;
555     propdb->resource = resource;
556     propdb->ns_xlate = ns_xlate;
557 
558     propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r);
559 
560     propdb->lockdb = lockdb;
561 
562     /* always defer actual open, to avoid expense of accessing db
563      * when only live properties are involved
564      */
565     propdb->deferred = 1;
566 
567     /* ### what to do about closing the propdb on server failure? */
568 
569     *p_propdb = propdb;
570     return NULL;
571 }
572 
dav_close_propdb(dav_propdb * propdb)573 DAV_DECLARE(void) dav_close_propdb(dav_propdb *propdb)
574 {
575     if (propdb->db != NULL) {
576         (*propdb->db_hooks->close)(propdb->db);
577     }
578 
579     if (propdb->subreq) {
580         ap_destroy_sub_req(propdb->subreq);
581         propdb->subreq = NULL;
582     }
583 }
584 
dav_get_allprops(dav_propdb * propdb,dav_prop_insert what)585 DAV_DECLARE(dav_get_props_result) dav_get_allprops(dav_propdb *propdb,
586                                                    dav_prop_insert what)
587 {
588     const dav_hooks_db *db_hooks = propdb->db_hooks;
589     apr_text_header hdr = { 0 };
590     apr_text_header hdr_ns = { 0 };
591     dav_get_props_result result = { 0 };
592     int found_contenttype = 0;
593     int found_contentlang = 0;
594     dav_prop_insert unused_inserted;
595 
596     /* if not just getting supported live properties,
597      * scan all properties in the dead prop database
598      */
599     if (what != DAV_PROP_INSERT_SUPPORTED) {
600         if (propdb->deferred) {
601             /* ### what to do with db open error? */
602             (void) dav_really_open_db(propdb, 1 /*ro*/);
603         }
604 
605         /* initialize the result with some start tags... */
606         apr_text_append(propdb->p, &hdr,
607                         "<D:propstat>" DEBUG_CR
608                         "<D:prop>" DEBUG_CR);
609 
610         /* if there ARE properties, then scan them */
611         if (propdb->db != NULL) {
612             dav_xmlns_info *xi = dav_xmlns_create(propdb->p);
613             dav_prop_name name;
614             dav_error *err;
615 
616             /* define (up front) any namespaces the db might need */
617             (void) (*db_hooks->define_namespaces)(propdb->db, xi);
618 
619             /* get the first property name, beginning the scan */
620             err = (*db_hooks->first_name)(propdb->db, &name);
621             while (!err && name.ns) {
622 
623                 /*
624                 ** We also look for <DAV:getcontenttype> and
625                 ** <DAV:getcontentlanguage>. If they are not stored as dead
626                 ** properties, then we need to perform a subrequest to get
627                 ** their values (if any).
628                 */
629                 if (*name.ns == 'D' && strcmp(name.ns, "DAV:") == 0
630                     && *name.name == 'g') {
631                     if (strcmp(name.name, "getcontenttype") == 0) {
632                         found_contenttype = 1;
633                     }
634                     else if (strcmp(name.name, "getcontentlanguage") == 0) {
635                         found_contentlang = 1;
636                     }
637                 }
638 
639                 if (what == DAV_PROP_INSERT_VALUE) {
640                     int found;
641 
642                     if ((err = (*db_hooks->output_value)(propdb->db, &name,
643                                                          xi, &hdr,
644                                                          &found)) != NULL) {
645                         /* ### anything better to do? */
646                         /* ### probably should enter a 500 error */
647                         goto next_key;
648                     }
649                     /* assert: found == 1 */
650                 }
651                 else {
652                     /* the value was not requested, so just add an empty
653                        tag specifying the property name. */
654                     dav_output_prop_name(propdb->p, &name, xi, &hdr);
655                 }
656 
657               next_key:
658                 err = (*db_hooks->next_name)(propdb->db, &name);
659             }
660 
661             /* all namespaces have been entered into xi. generate them into
662                the output now. */
663             dav_xmlns_generate(xi, &hdr_ns);
664 
665         } /* propdb->db != NULL */
666 
667         /* add namespaces for all the liveprop providers */
668         dav_add_all_liveprop_xmlns(propdb->p, &hdr_ns);
669     }
670 
671     /* ask the liveprop providers to insert their properties */
672     dav_run_insert_all_liveprops(propdb->r, propdb->resource, what, &hdr);
673 
674     /* insert the standard properties */
675     /* ### should be handling the return errors here */
676     (void)dav_insert_coreprop(propdb,
677                               DAV_PROPID_CORE_supportedlock, "supportedlock",
678                               what, &hdr, &unused_inserted);
679     (void)dav_insert_coreprop(propdb,
680                               DAV_PROPID_CORE_lockdiscovery, "lockdiscovery",
681                               what, &hdr, &unused_inserted);
682 
683     /* if we didn't find these, then do the whole subreq thing. */
684     if (!found_contenttype) {
685         /* ### should be handling the return error here */
686         (void)dav_insert_coreprop(propdb,
687                                   DAV_PROPID_CORE_getcontenttype,
688                                   "getcontenttype",
689                                   what, &hdr, &unused_inserted);
690     }
691     if (!found_contentlang) {
692         /* ### should be handling the return error here */
693         (void)dav_insert_coreprop(propdb,
694                                   DAV_PROPID_CORE_getcontentlanguage,
695                                   "getcontentlanguage",
696                                   what, &hdr, &unused_inserted);
697     }
698 
699     /* if not just reporting on supported live props,
700      * terminate the result */
701     if (what != DAV_PROP_INSERT_SUPPORTED) {
702         apr_text_append(propdb->p, &hdr,
703                         "</D:prop>" DEBUG_CR
704                         "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
705                         "</D:propstat>" DEBUG_CR);
706     }
707 
708     result.propstats = hdr.first;
709     result.xmlns = hdr_ns.first;
710     return result;
711 }
712 
dav_get_props(dav_propdb * propdb,apr_xml_doc * doc)713 DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb,
714                                                 apr_xml_doc *doc)
715 {
716     const dav_hooks_db *db_hooks = propdb->db_hooks;
717     apr_xml_elem *elem = dav_find_child(doc->root, "prop");
718     apr_text_header hdr_good = { 0 };
719     apr_text_header hdr_bad = { 0 };
720     apr_text_header hdr_ns = { 0 };
721     int have_good = 0;
722     dav_get_props_result result = { 0 };
723     dav_liveprop_elem *element;
724     char *marks_liveprop;
725     dav_xmlns_info *xi;
726     int xi_filled = 0;
727 
728     /* we lose both the document and the element when calling (insert_prop),
729      * make these available in the pool.
730      */
731     element = dav_get_liveprop_element(propdb->resource);
732     if (!element) {
733         element = apr_pcalloc(propdb->resource->pool, sizeof(dav_liveprop_elem));
734         apr_pool_userdata_setn(element, DAV_PROP_ELEMENT, NULL, propdb->resource->pool);
735     }
736     else {
737         memset(element, 0, sizeof(dav_liveprop_elem));
738     }
739     element->doc = doc;
740 
741     /* ### NOTE: we should pass in TWO buffers -- one for keys, one for
742        the marks */
743 
744     /* we will ALWAYS provide a "good" result, even if it is EMPTY */
745     apr_text_append(propdb->p, &hdr_good,
746                    "<D:propstat>" DEBUG_CR
747                    "<D:prop>" DEBUG_CR);
748 
749     /* ### the marks should be in a buffer! */
750     /* allocate zeroed-memory for the marks. These marks indicate which
751        liveprop namespaces we've generated into the output xmlns buffer */
752 
753     /* same for the liveprops */
754     marks_liveprop = apr_pcalloc(propdb->p, dav_get_liveprop_ns_count() + 1);
755 
756     xi = dav_xmlns_create(propdb->p);
757 
758     for (elem = elem->first_child; elem; elem = elem->next) {
759         dav_elem_private *priv;
760         dav_error *err;
761         dav_prop_insert inserted;
762         dav_prop_name name;
763 
764         element->elem = elem;
765 
766         /*
767         ** First try live property providers; if they don't handle
768         ** the property, then try looking it up in the propdb.
769         */
770 
771         if (elem->priv == NULL) {
772             /* elem->priv outlives propdb->p. Hence use the request pool */
773             elem->priv = apr_pcalloc(propdb->r->pool, sizeof(*priv));
774         }
775         priv = elem->priv;
776 
777         /* cache the propid; dav_get_props() could be called many times */
778         if (priv->propid == 0)
779             dav_find_liveprop(propdb, elem);
780 
781         if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
782 
783             /* insert the property. returns 1 if an insertion was done. */
784             if ((err = dav_insert_liveprop(propdb, elem, DAV_PROP_INSERT_VALUE,
785                                            &hdr_good, &inserted)) != NULL) {
786                 /* ### need to propagate the error to the caller... */
787                 /* ### skip it for now, as if nothing was inserted */
788             }
789             if (inserted == DAV_PROP_INSERT_VALUE) {
790                 have_good = 1;
791 
792                 /*
793                 ** Add the liveprop's namespace URIs. Note that provider==NULL
794                 ** for core properties.
795                 */
796                 if (priv->provider != NULL) {
797                     const char * const * scan_ns_uri;
798 
799                     for (scan_ns_uri = priv->provider->namespace_uris;
800                          *scan_ns_uri != NULL;
801                          ++scan_ns_uri) {
802                         long ns;
803 
804                         ns = dav_get_liveprop_ns_index(*scan_ns_uri);
805                         if (marks_liveprop[ns])
806                             continue;
807                         marks_liveprop[ns] = 1;
808 
809                         dav_insert_xmlns(propdb->p, "lp", ns, *scan_ns_uri,
810                                          &hdr_ns);
811                     }
812                 }
813 
814                 /* property added. move on to the next property. */
815                 continue;
816             }
817             else if (inserted == DAV_PROP_INSERT_NOTDEF) {
818                 /* nothing to do. fall thru to allow property to be handled
819                    as a dead property */
820             }
821 #if DAV_DEBUG
822             else {
823 #if 0
824                 /* ### need to change signature to return an error */
825                 return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 0,
826                                      0,
827                                      "INTERNAL DESIGN ERROR: insert_liveprop "
828                                      "did not insert what was asked for.");
829 #endif
830             }
831 #endif
832         }
833 
834         /* The property wasn't a live property, so look in the dead property
835            database. */
836 
837         /* make sure propdb is really open */
838         if (propdb->deferred) {
839             /* ### what to do with db open error? */
840             (void) dav_really_open_db(propdb, 1 /*ro*/);
841         }
842 
843         if (elem->ns == APR_XML_NS_NONE)
844             name.ns = "";
845         else
846             name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
847         name.name = elem->name;
848 
849         /* only bother to look if a database exists */
850         if (propdb->db != NULL) {
851             int found;
852 
853             if ((err = (*db_hooks->output_value)(propdb->db, &name,
854                                                  xi, &hdr_good,
855                                                  &found)) != NULL) {
856                 /* ### what to do? continue doesn't seem right... */
857                 continue;
858             }
859 
860             if (found) {
861                 have_good = 1;
862 
863                 /* if we haven't added the db's namespaces, then do so... */
864                 if (!xi_filled) {
865                     (void) (*db_hooks->define_namespaces)(propdb->db, xi);
866                     xi_filled = 1;
867                 }
868                 continue;
869             }
870         }
871 
872         /* not found as a live OR dead property. add a record to the "bad"
873            propstats */
874 
875         /* make sure we've started our "bad" propstat */
876         if (hdr_bad.first == NULL) {
877             apr_text_append(propdb->p, &hdr_bad,
878                             "<D:propstat>" DEBUG_CR
879                             "<D:prop>" DEBUG_CR);
880         }
881 
882         /* output this property's name (into the bad propstats) */
883         dav_output_prop_name(propdb->p, &name, xi, &hdr_bad);
884     }
885 
886     apr_text_append(propdb->p, &hdr_good,
887                     "</D:prop>" DEBUG_CR
888                     "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
889                     "</D:propstat>" DEBUG_CR);
890 
891     /* default to start with the good */
892     result.propstats = hdr_good.first;
893 
894     /* we may not have any "bad" results */
895     if (hdr_bad.first != NULL) {
896         /* "close" the bad propstat */
897         apr_text_append(propdb->p, &hdr_bad,
898                         "</D:prop>" DEBUG_CR
899                         "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
900                         "</D:propstat>" DEBUG_CR);
901 
902         /* if there are no good props, then just return the bad */
903         if (!have_good) {
904             result.propstats = hdr_bad.first;
905         }
906         else {
907             /* hook the bad propstat to the end of the good one */
908             hdr_good.last->next = hdr_bad.first;
909         }
910     }
911 
912     /* add in all the various namespaces, and return them */
913     dav_xmlns_generate(xi, &hdr_ns);
914     result.xmlns = hdr_ns.first;
915 
916     return result;
917 }
918 
dav_get_liveprop_supported(dav_propdb * propdb,const char * ns_uri,const char * propname,apr_text_header * body)919 DAV_DECLARE(void) dav_get_liveprop_supported(dav_propdb *propdb,
920                                              const char *ns_uri,
921                                              const char *propname,
922                                              apr_text_header *body)
923 {
924     int propid;
925     const dav_hooks_liveprop *hooks;
926 
927     propid = dav_find_liveprop_provider(propdb, ns_uri, propname, &hooks);
928 
929     if (propid != DAV_PROPID_CORE_UNKNOWN) {
930         if (hooks == NULL) {
931             /* this is a "core" property that we define */
932             dav_prop_insert unused_inserted;
933             dav_insert_coreprop(propdb, propid, propname,
934                                 DAV_PROP_INSERT_SUPPORTED, body, &unused_inserted);
935         }
936         else {
937             (*hooks->insert_prop)(propdb->resource, propid,
938                                   DAV_PROP_INSERT_SUPPORTED, body);
939         }
940     }
941 }
942 
dav_get_liveprop_element(const dav_resource * resource)943 DAV_DECLARE(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource *resource)
944 {
945     dav_liveprop_elem *element;
946 
947     apr_pool_userdata_get((void **)&element, DAV_PROP_ELEMENT, resource->pool);
948 
949     return element;
950 }
951 
dav_prop_validate(dav_prop_ctx * ctx)952 DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx)
953 {
954     dav_propdb *propdb = ctx->propdb;
955     apr_xml_elem *prop = ctx->prop;
956     dav_elem_private *priv;
957 
958     priv = ctx->prop->priv = apr_pcalloc(propdb->p, sizeof(*priv));
959 
960     /*
961     ** Check to see if this is a live property, and fill the fields
962     ** in the XML elem, as appropriate.
963     **
964     ** Verify that the property is read/write. If not, then it cannot
965     ** be SET or DELETEd.
966     */
967     if (priv->propid == 0) {
968         dav_find_liveprop(propdb, prop);
969 
970         /* it's a liveprop if a provider was found */
971         /* ### actually the "core" props should really be liveprops, but
972            ### there is no "provider" for those and the r/w props are
973            ### treated as dead props anyhow */
974         ctx->is_liveprop = priv->provider != NULL;
975     }
976 
977     if (!dav_rw_liveprop(propdb, priv)) {
978         ctx->err = dav_new_error(propdb->p, HTTP_CONFLICT,
979                                  DAV_ERR_PROP_READONLY, 0,
980                                  "Property is read-only.");
981         return;
982     }
983 
984     if (ctx->is_liveprop) {
985         int defer_to_dead = 0;
986 
987         ctx->err = (*priv->provider->patch_validate)(propdb->resource,
988                                                      prop, ctx->operation,
989                                                      &ctx->liveprop_ctx,
990                                                      &defer_to_dead);
991         if (ctx->err != NULL || !defer_to_dead)
992             return;
993 
994         /* clear is_liveprop -- act as a dead prop now */
995         ctx->is_liveprop = 0;
996     }
997 
998     /*
999     ** The property is supposed to be stored into the dead-property
1000     ** database. Make sure the thing is truly open (and writable).
1001     */
1002     if (propdb->deferred
1003         && (ctx->err = dav_really_open_db(propdb, 0 /* ro */)) != NULL) {
1004         return;
1005     }
1006 
1007     /*
1008     ** There should be an open, writable database in here!
1009     **
1010     ** Note: the database would be NULL if it was opened readonly and it
1011     **       did not exist.
1012     */
1013     if (propdb->db == NULL) {
1014         ctx->err = dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
1015                                  DAV_ERR_PROP_NO_DATABASE, 0,
1016                                  "Attempted to set/remove a property "
1017                                  "without a valid, open, read/write "
1018                                  "property database.");
1019         return;
1020     }
1021 
1022     if (ctx->operation == DAV_PROP_OP_SET) {
1023         /*
1024         ** Prep the element => propdb namespace index mapping, inserting
1025         ** namespace URIs into the propdb that don't exist.
1026         */
1027         (void) (*propdb->db_hooks->map_namespaces)(propdb->db,
1028                                                    propdb->ns_xlate,
1029                                                    &propdb->mapping);
1030     }
1031     else if (ctx->operation == DAV_PROP_OP_DELETE) {
1032         /*
1033         ** There are no checks to perform here. If a property exists, then
1034         ** we will delete it. If it does not exist, then it does not matter
1035         ** (see S12.13.1).
1036         **
1037         ** Note that if a property does not exist, that does not rule out
1038         ** that a SET will occur during this PROPPATCH (thusly creating it).
1039         */
1040     }
1041 }
1042 
dav_prop_exec(dav_prop_ctx * ctx)1043 DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx)
1044 {
1045     dav_propdb *propdb = ctx->propdb;
1046     dav_error *err = NULL;
1047     dav_elem_private *priv = ctx->prop->priv;
1048 
1049     ctx->rollback = apr_pcalloc(propdb->p, sizeof(*ctx->rollback));
1050 
1051     if (ctx->is_liveprop) {
1052         err = (*priv->provider->patch_exec)(propdb->resource,
1053                                             ctx->prop, ctx->operation,
1054                                             ctx->liveprop_ctx,
1055                                             &ctx->rollback->liveprop);
1056     }
1057     else {
1058         dav_prop_name name;
1059 
1060         if (ctx->prop->ns == APR_XML_NS_NONE)
1061             name.ns = "";
1062         else
1063             name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, ctx->prop->ns);
1064         name.name = ctx->prop->name;
1065 
1066         /* save the old value so that we can do a rollback. */
1067         if ((err = (*propdb->db_hooks
1068                     ->get_rollback)(propdb->db, &name,
1069                                     &ctx->rollback->deadprop)) != NULL)
1070             goto error;
1071 
1072         if (ctx->operation == DAV_PROP_OP_SET) {
1073 
1074             /* Note: propdb->mapping was set in dav_prop_validate() */
1075             err = (*propdb->db_hooks->store)(propdb->db, &name, ctx->prop,
1076                                              propdb->mapping);
1077 
1078             /*
1079             ** If an error occurred, then assume that we didn't change the
1080             ** value. Remove the rollback item so that we don't try to set
1081             ** its value during the rollback.
1082             */
1083             /* ### euh... where is the removal? */
1084         }
1085         else if (ctx->operation == DAV_PROP_OP_DELETE) {
1086 
1087             /*
1088             ** Delete the property. Ignore errors -- the property is there, or
1089             ** we are deleting it for a second time.
1090             **
1091             ** http://tools.ietf.org/html/rfc4918#section-14.23 says
1092             ** "Specifying the removal of a property that does not exist is
1093             ** not an error"
1094             */
1095             /* ### but what about other errors? */
1096             (void) (*propdb->db_hooks->remove)(propdb->db, &name);
1097         }
1098     }
1099 
1100   error:
1101     /* push a more specific error here */
1102     if (err != NULL) {
1103         /*
1104         ** Use HTTP_INTERNAL_SERVER_ERROR because we shouldn't have seen
1105         ** any errors at this point.
1106         */
1107         ctx->err = dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
1108                                   DAV_ERR_PROP_EXEC,
1109                                   "Could not execute PROPPATCH.", err);
1110     }
1111 }
1112 
dav_prop_commit(dav_prop_ctx * ctx)1113 DAV_DECLARE_NONSTD(void) dav_prop_commit(dav_prop_ctx *ctx)
1114 {
1115     dav_elem_private *priv = ctx->prop->priv;
1116 
1117     /*
1118     ** Note that a commit implies ctx->err is NULL. The caller should assume
1119     ** a status of HTTP_OK for this case.
1120     */
1121 
1122     if (ctx->is_liveprop) {
1123         (*priv->provider->patch_commit)(ctx->propdb->resource,
1124                                         ctx->operation,
1125                                         ctx->liveprop_ctx,
1126                                         ctx->rollback->liveprop);
1127     }
1128 }
1129 
dav_prop_rollback(dav_prop_ctx * ctx)1130 DAV_DECLARE_NONSTD(void) dav_prop_rollback(dav_prop_ctx *ctx)
1131 {
1132     dav_error *err = NULL;
1133     dav_elem_private *priv = ctx->prop->priv;
1134 
1135     /* do nothing if there is no rollback information. */
1136     if (ctx->rollback == NULL)
1137         return;
1138 
1139     /*
1140     ** ### if we have an error, and a rollback occurs, then the namespace
1141     ** ### mods should not happen at all. Basically, the namespace management
1142     ** ### is simply a bitch.
1143     */
1144 
1145     if (ctx->is_liveprop) {
1146         err = (*priv->provider->patch_rollback)(ctx->propdb->resource,
1147                                                 ctx->operation,
1148                                                 ctx->liveprop_ctx,
1149                                                 ctx->rollback->liveprop);
1150     }
1151     else {
1152         err = (*ctx->propdb->db_hooks
1153                ->apply_rollback)(ctx->propdb->db, ctx->rollback->deadprop);
1154     }
1155 
1156     if (err != NULL) {
1157         if (ctx->err == NULL)
1158             ctx->err = err;
1159         else {
1160             dav_error *scan = err;
1161 
1162             /* hook previous errors at the end of the rollback error */
1163             while (scan->prev != NULL)
1164                 scan = scan->prev;
1165             scan->prev = ctx->err;
1166             ctx->err = err;
1167         }
1168     }
1169 }
1170