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