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
170struct dav_propdb {
171    apr_pool_t *p;                /* the pool we should use */
172    request_rec *r;               /* the request record */
173
174    const dav_resource *resource; /* the target resource */
175
176    int deferred;                 /* open of db has been deferred */
177    dav_db *db;                   /* underlying database containing props */
178
179    apr_array_header_t *ns_xlate; /* translation of an elem->ns to URI */
180    dav_namespace_map *mapping;   /* namespace mapping */
181
182    dav_lockdb *lockdb;           /* the lock database */
183
184    dav_buffer wb_lock;           /* work buffer for lockdiscovery property */
185
186    /* if we ever run a GET subreq, it will be stored here */
187    request_rec *subreq;
188
189    /* hooks we should use for processing (based on the target resource) */
190    const dav_hooks_db *db_hooks;
191};
192
193/* NOTE: dav_core_props[] and the following enum must stay in sync. */
194/* ### move these into a "core" liveprop provider? */
195static const char * const dav_core_props[] =
196{
197    "getcontenttype",
198    "getcontentlanguage",
199    "lockdiscovery",
200    "supportedlock",
201
202    NULL        /* sentinel */
203};
204enum {
205    DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE,
206    DAV_PROPID_CORE_getcontentlanguage,
207    DAV_PROPID_CORE_lockdiscovery,
208    DAV_PROPID_CORE_supportedlock,
209
210    DAV_PROPID_CORE_UNKNOWN
211};
212
213/*
214** This structure is used to track information needed for a rollback.
215*/
216typedef struct dav_rollback_item {
217    /* select one of the two rollback context structures based on the
218       value of dav_prop_ctx.is_liveprop */
219    dav_deadprop_rollback *deadprop;
220    dav_liveprop_rollback *liveprop;
221
222} dav_rollback_item;
223
224
225static int dav_find_liveprop_provider(dav_propdb *propdb,
226                                      const char *ns_uri,
227                                      const char *propname,
228                                      const dav_hooks_liveprop **provider)
229{
230    int propid;
231
232    *provider = NULL;
233
234    if (ns_uri == NULL) {
235        /* policy: liveprop providers cannot define no-namespace properties */
236        return DAV_PROPID_CORE_UNKNOWN;
237    }
238
239    /* check liveprop providers first, so they can define core properties */
240    propid = dav_run_find_liveprop(propdb->resource, ns_uri, propname,
241                                   provider);
242    if (propid != 0) {
243        return propid;
244    }
245
246    /* check for core property */
247    if (strcmp(ns_uri, "DAV:") == 0) {
248        const char * const *p = dav_core_props;
249
250        for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid)
251            if (strcmp(propname, *p) == 0) {
252                return propid;
253            }
254    }
255
256    /* no provider for this property */
257    return DAV_PROPID_CORE_UNKNOWN;
258}
259
260static void dav_find_liveprop(dav_propdb *propdb, apr_xml_elem *elem)
261{
262    const char *ns_uri;
263    dav_elem_private *priv = elem->priv;
264    const dav_hooks_liveprop *hooks;
265
266
267    if (elem->ns == APR_XML_NS_NONE)
268        ns_uri = NULL;
269    else if (elem->ns == APR_XML_NS_DAV_ID)
270        ns_uri = "DAV:";
271    else
272        ns_uri = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
273
274    priv->propid = dav_find_liveprop_provider(propdb, ns_uri, elem->name,
275                                              &hooks);
276
277    /* ### this test seems redundant... */
278    if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
279        priv->provider = hooks;
280    }
281}
282
283/* is the live property read/write? */
284static int dav_rw_liveprop(dav_propdb *propdb, dav_elem_private *priv)
285{
286    int propid = priv->propid;
287
288    /*
289    ** Check the liveprop provider (if this is a provider-defined prop)
290    */
291    if (priv->provider != NULL) {
292        return (*priv->provider->is_writable)(propdb->resource, propid);
293    }
294
295    /* these are defined as read-only */
296    if (propid == DAV_PROPID_CORE_lockdiscovery
297#if DAV_DISABLE_WRITABLE_PROPS
298        || propid == DAV_PROPID_CORE_getcontenttype
299        || propid == DAV_PROPID_CORE_getcontentlanguage
300#endif
301        || propid == DAV_PROPID_CORE_supportedlock
302        ) {
303
304        return 0;
305    }
306
307    /* these are defined as read/write */
308    if (propid == DAV_PROPID_CORE_getcontenttype
309        || propid == DAV_PROPID_CORE_getcontentlanguage
310        || propid == DAV_PROPID_CORE_UNKNOWN) {
311
312        return 1;
313    }
314
315    /*
316    ** We don't recognize the property, so it must be dead (and writable)
317    */
318    return 1;
319}
320
321/* do a sub-request to fetch properties for the target resource's URI. */
322static void dav_do_prop_subreq(dav_propdb *propdb)
323{
324    /* perform a "GET" on the resource's URI (note that the resource
325       may not correspond to the current request!). */
326    propdb->subreq = ap_sub_req_lookup_uri(propdb->resource->uri, propdb->r,
327                                           NULL);
328}
329
330static dav_error * dav_insert_coreprop(dav_propdb *propdb,
331                                       int propid, const char *name,
332                                       dav_prop_insert what,
333                                       apr_text_header *phdr,
334                                       dav_prop_insert *inserted)
335{
336    const char *value = NULL;
337    dav_error *err;
338
339    *inserted = DAV_PROP_INSERT_NOTDEF;
340
341    /* fast-path the common case */
342    if (propid == DAV_PROPID_CORE_UNKNOWN)
343        return NULL;
344
345    switch (propid) {
346
347    case DAV_PROPID_CORE_lockdiscovery:
348        if (propdb->lockdb != NULL) {
349            dav_lock *locks;
350
351            if ((err = dav_lock_query(propdb->lockdb, propdb->resource,
352                                      &locks)) != NULL) {
353                return dav_push_error(propdb->p, err->status, 0,
354                                      "DAV:lockdiscovery could not be "
355                                      "determined due to a problem fetching "
356                                      "the locks for this resource.",
357                                      err);
358            }
359
360            /* fast-path the no-locks case */
361            if (locks == NULL) {
362                value = "";
363            }
364            else {
365                /*
366                ** This may modify the buffer. value may point to
367                ** wb_lock.pbuf or a string constant.
368                */
369                value = dav_lock_get_activelock(propdb->r, locks,
370                                                &propdb->wb_lock);
371
372                /* make a copy to isolate it from changes to wb_lock */
373                value = apr_pstrdup(propdb->p, propdb->wb_lock.buf);
374            }
375        }
376        break;
377
378    case DAV_PROPID_CORE_supportedlock:
379        if (propdb->lockdb != NULL) {
380            value = (*propdb->lockdb->hooks->get_supportedlock)(propdb->resource);
381        }
382        break;
383
384    case DAV_PROPID_CORE_getcontenttype:
385        if (propdb->subreq == NULL) {
386            dav_do_prop_subreq(propdb);
387        }
388        if (propdb->subreq->content_type != NULL) {
389            value = propdb->subreq->content_type;
390        }
391        break;
392
393    case DAV_PROPID_CORE_getcontentlanguage:
394    {
395        const char *lang;
396
397        if (propdb->subreq == NULL) {
398            dav_do_prop_subreq(propdb);
399        }
400        if ((lang = apr_table_get(propdb->subreq->headers_out,
401                                 "Content-Language")) != NULL) {
402            value = lang;
403        }
404        break;
405    }
406
407    default:
408        /* fall through to interpret as a dead property */
409        break;
410    }
411
412    /* if something was supplied, then insert it */
413    if (value != NULL) {
414        const char *s;
415
416        if (what == DAV_PROP_INSERT_SUPPORTED) {
417            /* use D: prefix to refer to the DAV: namespace URI,
418             * and let the namespace attribute default to "DAV:"
419             */
420            s = apr_psprintf(propdb->p,
421                            "<D:supported-live-property D:name=\"%s\"/>" DEBUG_CR,
422                            name);
423        }
424        else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') {
425            /* use D: prefix to refer to the DAV: namespace URI */
426            s = apr_psprintf(propdb->p, "<D:%s>%s</D:%s>" DEBUG_CR,
427                            name, value, name);
428        }
429        else {
430            /* use D: prefix to refer to the DAV: namespace URI */
431            s = apr_psprintf(propdb->p, "<D:%s/>" DEBUG_CR, name);
432        }
433        apr_text_append(propdb->p, phdr, s);
434
435        *inserted = what;
436    }
437
438    return NULL;
439}
440
441static dav_error * dav_insert_liveprop(dav_propdb *propdb,
442                                       const apr_xml_elem *elem,
443                                       dav_prop_insert what,
444                                       apr_text_header *phdr,
445                                       dav_prop_insert *inserted)
446{
447    dav_elem_private *priv = elem->priv;
448
449    *inserted = DAV_PROP_INSERT_NOTDEF;
450
451    if (priv->provider == NULL) {
452        /* this is a "core" property that we define */
453        return dav_insert_coreprop(propdb, priv->propid, elem->name,
454                                   what, phdr, inserted);
455    }
456
457    /* ask the provider (that defined this prop) to insert the prop */
458    *inserted = (*priv->provider->insert_prop)(propdb->resource, priv->propid,
459                                               what, phdr);
460
461    return NULL;
462}
463
464static void dav_output_prop_name(apr_pool_t *pool,
465                                 const dav_prop_name *name,
466                                 dav_xmlns_info *xi,
467                                 apr_text_header *phdr)
468{
469    const char *s;
470
471    if (*name->ns == '\0')
472        s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name->name);
473    else {
474        const char *prefix = dav_xmlns_add_uri(xi, name->ns);
475
476        s = apr_psprintf(pool, "<%s:%s/>" DEBUG_CR, prefix, name->name);
477    }
478
479    apr_text_append(pool, phdr, s);
480}
481
482static void dav_insert_xmlns(apr_pool_t *p, const char *pre_prefix, long ns,
483                             const char *ns_uri, apr_text_header *phdr)
484{
485    const char *s;
486
487    s = apr_psprintf(p, " xmlns:%s%ld=\"%s\"", pre_prefix, ns, ns_uri);
488    apr_text_append(p, phdr, s);
489}
490
491static dav_error *dav_really_open_db(dav_propdb *propdb, int ro)
492{
493    dav_error *err;
494
495    /* we're trying to open the db; turn off the 'deferred' flag */
496    propdb->deferred = 0;
497
498    /* ask the DB provider to open the thing */
499    err = (*propdb->db_hooks->open)(propdb->p, propdb->resource, ro,
500                                    &propdb->db);
501    if (err != NULL) {
502        return dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
503                              DAV_ERR_PROP_OPENING,
504                              "Could not open the property database.",
505                              err);
506    }
507
508    /*
509    ** NOTE: propdb->db could be NULL if we attempted to open a readonly
510    **       database that doesn't exist. If we require read/write
511    **       access, then a database was created and opened.
512    */
513
514    return NULL;
515}
516
517DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
518                                        const dav_resource *resource,
519                                        int ro,
520                                        apr_array_header_t * ns_xlate,
521                                        dav_propdb **p_propdb)
522{
523    dav_propdb *propdb = apr_pcalloc(r->pool, sizeof(*propdb));
524
525    *p_propdb = NULL;
526
527#if DAV_DEBUG
528    if (resource->uri == NULL) {
529        return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
530                             "INTERNAL DESIGN ERROR: resource must define "
531                             "its URI.");
532    }
533#endif
534
535    propdb->r = r;
536    apr_pool_create(&propdb->p, r->pool);
537    propdb->resource = resource;
538    propdb->ns_xlate = ns_xlate;
539
540    propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r);
541
542    propdb->lockdb = lockdb;
543
544    /* always defer actual open, to avoid expense of accessing db
545     * when only live properties are involved
546     */
547    propdb->deferred = 1;
548
549    /* ### what to do about closing the propdb on server failure? */
550
551    *p_propdb = propdb;
552    return NULL;
553}
554
555DAV_DECLARE(void) dav_close_propdb(dav_propdb *propdb)
556{
557    if (propdb->db != NULL) {
558        (*propdb->db_hooks->close)(propdb->db);
559    }
560
561    /* Currently, mod_dav's pool usage doesn't allow clearing this pool. */
562#if 0
563    apr_pool_destroy(propdb->p);
564#endif
565    return;
566}
567
568DAV_DECLARE(dav_get_props_result) dav_get_allprops(dav_propdb *propdb,
569                                                   dav_prop_insert what)
570{
571    const dav_hooks_db *db_hooks = propdb->db_hooks;
572    apr_text_header hdr = { 0 };
573    apr_text_header hdr_ns = { 0 };
574    dav_get_props_result result = { 0 };
575    int found_contenttype = 0;
576    int found_contentlang = 0;
577    dav_prop_insert unused_inserted;
578
579    /* if not just getting supported live properties,
580     * scan all properties in the dead prop database
581     */
582    if (what != DAV_PROP_INSERT_SUPPORTED) {
583        if (propdb->deferred) {
584            /* ### what to do with db open error? */
585            (void) dav_really_open_db(propdb, 1 /*ro*/);
586        }
587
588        /* initialize the result with some start tags... */
589        apr_text_append(propdb->p, &hdr,
590                        "<D:propstat>" DEBUG_CR
591                        "<D:prop>" DEBUG_CR);
592
593        /* if there ARE properties, then scan them */
594        if (propdb->db != NULL) {
595            dav_xmlns_info *xi = dav_xmlns_create(propdb->p);
596            dav_prop_name name;
597            dav_error *err;
598
599            /* define (up front) any namespaces the db might need */
600            (void) (*db_hooks->define_namespaces)(propdb->db, xi);
601
602            /* get the first property name, beginning the scan */
603            err = (*db_hooks->first_name)(propdb->db, &name);
604            while (!err && name.ns) {
605
606                /*
607                ** We also look for <DAV:getcontenttype> and
608                ** <DAV:getcontentlanguage>. If they are not stored as dead
609                ** properties, then we need to perform a subrequest to get
610                ** their values (if any).
611                */
612                if (*name.ns == 'D' && strcmp(name.ns, "DAV:") == 0
613                    && *name.name == 'g') {
614                    if (strcmp(name.name, "getcontenttype") == 0) {
615                        found_contenttype = 1;
616                    }
617                    else if (strcmp(name.name, "getcontentlanguage") == 0) {
618                        found_contentlang = 1;
619                    }
620                }
621
622                if (what == DAV_PROP_INSERT_VALUE) {
623                    int found;
624
625                    if ((err = (*db_hooks->output_value)(propdb->db, &name,
626                                                         xi, &hdr,
627                                                         &found)) != NULL) {
628                        /* ### anything better to do? */
629                        /* ### probably should enter a 500 error */
630                        goto next_key;
631                    }
632                    /* assert: found == 1 */
633                }
634                else {
635                    /* the value was not requested, so just add an empty
636                       tag specifying the property name. */
637                    dav_output_prop_name(propdb->p, &name, xi, &hdr);
638                }
639
640              next_key:
641                err = (*db_hooks->next_name)(propdb->db, &name);
642            }
643
644            /* all namespaces have been entered into xi. generate them into
645               the output now. */
646            dav_xmlns_generate(xi, &hdr_ns);
647
648        } /* propdb->db != NULL */
649
650        /* add namespaces for all the liveprop providers */
651        dav_add_all_liveprop_xmlns(propdb->p, &hdr_ns);
652    }
653
654    /* ask the liveprop providers to insert their properties */
655    dav_run_insert_all_liveprops(propdb->r, propdb->resource, what, &hdr);
656
657    /* insert the standard properties */
658    /* ### should be handling the return errors here */
659    (void)dav_insert_coreprop(propdb,
660                              DAV_PROPID_CORE_supportedlock, "supportedlock",
661                              what, &hdr, &unused_inserted);
662    (void)dav_insert_coreprop(propdb,
663                              DAV_PROPID_CORE_lockdiscovery, "lockdiscovery",
664                              what, &hdr, &unused_inserted);
665
666    /* if we didn't find these, then do the whole subreq thing. */
667    if (!found_contenttype) {
668        /* ### should be handling the return error here */
669        (void)dav_insert_coreprop(propdb,
670                                  DAV_PROPID_CORE_getcontenttype,
671                                  "getcontenttype",
672                                  what, &hdr, &unused_inserted);
673    }
674    if (!found_contentlang) {
675        /* ### should be handling the return error here */
676        (void)dav_insert_coreprop(propdb,
677                                  DAV_PROPID_CORE_getcontentlanguage,
678                                  "getcontentlanguage",
679                                  what, &hdr, &unused_inserted);
680    }
681
682    /* if not just reporting on supported live props,
683     * terminate the result */
684    if (what != DAV_PROP_INSERT_SUPPORTED) {
685        apr_text_append(propdb->p, &hdr,
686                        "</D:prop>" DEBUG_CR
687                        "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
688                        "</D:propstat>" DEBUG_CR);
689    }
690
691    result.propstats = hdr.first;
692    result.xmlns = hdr_ns.first;
693    return result;
694}
695
696DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb,
697                                                apr_xml_doc *doc)
698{
699    const dav_hooks_db *db_hooks = propdb->db_hooks;
700    apr_xml_elem *elem = dav_find_child(doc->root, "prop");
701    apr_text_header hdr_good = { 0 };
702    apr_text_header hdr_bad = { 0 };
703    apr_text_header hdr_ns = { 0 };
704    int have_good = 0;
705    dav_get_props_result result = { 0 };
706    char *marks_liveprop;
707    dav_xmlns_info *xi;
708    int xi_filled = 0;
709
710    /* ### NOTE: we should pass in TWO buffers -- one for keys, one for
711       the marks */
712
713    /* we will ALWAYS provide a "good" result, even if it is EMPTY */
714    apr_text_append(propdb->p, &hdr_good,
715                   "<D:propstat>" DEBUG_CR
716                   "<D:prop>" DEBUG_CR);
717
718    /* ### the marks should be in a buffer! */
719    /* allocate zeroed-memory for the marks. These marks indicate which
720       liveprop namespaces we've generated into the output xmlns buffer */
721
722    /* same for the liveprops */
723    marks_liveprop = apr_pcalloc(propdb->p, dav_get_liveprop_ns_count() + 1);
724
725    xi = dav_xmlns_create(propdb->p);
726
727    for (elem = elem->first_child; elem; elem = elem->next) {
728        dav_elem_private *priv;
729        dav_error *err;
730        dav_prop_insert inserted;
731        dav_prop_name name;
732
733        /*
734        ** First try live property providers; if they don't handle
735        ** the property, then try looking it up in the propdb.
736        */
737
738        if (elem->priv == NULL) {
739            elem->priv = apr_pcalloc(propdb->p, sizeof(*priv));
740        }
741        priv = elem->priv;
742
743        /* cache the propid; dav_get_props() could be called many times */
744        if (priv->propid == 0)
745            dav_find_liveprop(propdb, elem);
746
747        if (priv->propid != DAV_PROPID_CORE_UNKNOWN) {
748
749            /* insert the property. returns 1 if an insertion was done. */
750            if ((err = dav_insert_liveprop(propdb, elem, DAV_PROP_INSERT_VALUE,
751                                           &hdr_good, &inserted)) != NULL) {
752                /* ### need to propagate the error to the caller... */
753                /* ### skip it for now, as if nothing was inserted */
754            }
755            if (inserted == DAV_PROP_INSERT_VALUE) {
756                have_good = 1;
757
758                /*
759                ** Add the liveprop's namespace URIs. Note that provider==NULL
760                ** for core properties.
761                */
762                if (priv->provider != NULL) {
763                    const char * const * scan_ns_uri;
764
765                    for (scan_ns_uri = priv->provider->namespace_uris;
766                         *scan_ns_uri != NULL;
767                         ++scan_ns_uri) {
768                        long ns;
769
770                        ns = dav_get_liveprop_ns_index(*scan_ns_uri);
771                        if (marks_liveprop[ns])
772                            continue;
773                        marks_liveprop[ns] = 1;
774
775                        dav_insert_xmlns(propdb->p, "lp", ns, *scan_ns_uri,
776                                         &hdr_ns);
777                    }
778                }
779
780                /* property added. move on to the next property. */
781                continue;
782            }
783            else if (inserted == DAV_PROP_INSERT_NOTDEF) {
784                /* nothing to do. fall thru to allow property to be handled
785                   as a dead property */
786            }
787#if DAV_DEBUG
788            else {
789#if 0
790                /* ### need to change signature to return an error */
791                return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 0,
792                                     "INTERNAL DESIGN ERROR: insert_liveprop "
793                                     "did not insert what was asked for.");
794#endif
795            }
796#endif
797        }
798
799        /* The property wasn't a live property, so look in the dead property
800           database. */
801
802        /* make sure propdb is really open */
803        if (propdb->deferred) {
804            /* ### what to do with db open error? */
805            (void) dav_really_open_db(propdb, 1 /*ro*/);
806        }
807
808        if (elem->ns == APR_XML_NS_NONE)
809            name.ns = "";
810        else
811            name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
812        name.name = elem->name;
813
814        /* only bother to look if a database exists */
815        if (propdb->db != NULL) {
816            int found;
817
818            if ((err = (*db_hooks->output_value)(propdb->db, &name,
819                                                 xi, &hdr_good,
820                                                 &found)) != NULL) {
821                /* ### what to do? continue doesn't seem right... */
822                continue;
823            }
824
825            if (found) {
826                have_good = 1;
827
828                /* if we haven't added the db's namespaces, then do so... */
829                if (!xi_filled) {
830                    (void) (*db_hooks->define_namespaces)(propdb->db, xi);
831                    xi_filled = 1;
832                }
833                continue;
834            }
835        }
836
837        /* not found as a live OR dead property. add a record to the "bad"
838           propstats */
839
840        /* make sure we've started our "bad" propstat */
841        if (hdr_bad.first == NULL) {
842            apr_text_append(propdb->p, &hdr_bad,
843                            "<D:propstat>" DEBUG_CR
844                            "<D:prop>" DEBUG_CR);
845        }
846
847        /* output this property's name (into the bad propstats) */
848        dav_output_prop_name(propdb->p, &name, xi, &hdr_bad);
849    }
850
851    apr_text_append(propdb->p, &hdr_good,
852                    "</D:prop>" DEBUG_CR
853                    "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
854                    "</D:propstat>" DEBUG_CR);
855
856    /* default to start with the good */
857    result.propstats = hdr_good.first;
858
859    /* we may not have any "bad" results */
860    if (hdr_bad.first != NULL) {
861        /* "close" the bad propstat */
862        apr_text_append(propdb->p, &hdr_bad,
863                        "</D:prop>" DEBUG_CR
864                        "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
865                        "</D:propstat>" DEBUG_CR);
866
867        /* if there are no good props, then just return the bad */
868        if (!have_good) {
869            result.propstats = hdr_bad.first;
870        }
871        else {
872            /* hook the bad propstat to the end of the good one */
873            hdr_good.last->next = hdr_bad.first;
874        }
875    }
876
877    /* add in all the various namespaces, and return them */
878    dav_xmlns_generate(xi, &hdr_ns);
879    result.xmlns = hdr_ns.first;
880
881    return result;
882}
883
884DAV_DECLARE(void) dav_get_liveprop_supported(dav_propdb *propdb,
885                                             const char *ns_uri,
886                                             const char *propname,
887                                             apr_text_header *body)
888{
889    int propid;
890    const dav_hooks_liveprop *hooks;
891
892    propid = dav_find_liveprop_provider(propdb, ns_uri, propname, &hooks);
893
894    if (propid != DAV_PROPID_CORE_UNKNOWN) {
895        if (hooks == NULL) {
896            /* this is a "core" property that we define */
897            dav_prop_insert unused_inserted;
898            dav_insert_coreprop(propdb, propid, propname,
899                                DAV_PROP_INSERT_SUPPORTED, body, &unused_inserted);
900        }
901        else {
902            (*hooks->insert_prop)(propdb->resource, propid,
903                                  DAV_PROP_INSERT_SUPPORTED, body);
904        }
905    }
906}
907
908DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx)
909{
910    dav_propdb *propdb = ctx->propdb;
911    apr_xml_elem *prop = ctx->prop;
912    dav_elem_private *priv;
913
914    priv = ctx->prop->priv = apr_pcalloc(propdb->p, sizeof(*priv));
915
916    /*
917    ** Check to see if this is a live property, and fill the fields
918    ** in the XML elem, as appropriate.
919    **
920    ** Verify that the property is read/write. If not, then it cannot
921    ** be SET or DELETEd.
922    */
923    if (priv->propid == 0) {
924        dav_find_liveprop(propdb, prop);
925
926        /* it's a liveprop if a provider was found */
927        /* ### actually the "core" props should really be liveprops, but
928           ### there is no "provider" for those and the r/w props are
929           ### treated as dead props anyhow */
930        ctx->is_liveprop = priv->provider != NULL;
931    }
932
933    if (!dav_rw_liveprop(propdb, priv)) {
934        ctx->err = dav_new_error(propdb->p, HTTP_CONFLICT,
935                                 DAV_ERR_PROP_READONLY,
936                                 "Property is read-only.");
937        return;
938    }
939
940    if (ctx->is_liveprop) {
941        int defer_to_dead = 0;
942
943        ctx->err = (*priv->provider->patch_validate)(propdb->resource,
944                                                     prop, ctx->operation,
945                                                     &ctx->liveprop_ctx,
946                                                     &defer_to_dead);
947        if (ctx->err != NULL || !defer_to_dead)
948            return;
949
950        /* clear is_liveprop -- act as a dead prop now */
951        ctx->is_liveprop = 0;
952    }
953
954    /*
955    ** The property is supposed to be stored into the dead-property
956    ** database. Make sure the thing is truly open (and writable).
957    */
958    if (propdb->deferred
959        && (ctx->err = dav_really_open_db(propdb, 0 /* ro */)) != NULL) {
960        return;
961    }
962
963    /*
964    ** There should be an open, writable database in here!
965    **
966    ** Note: the database would be NULL if it was opened readonly and it
967    **       did not exist.
968    */
969    if (propdb->db == NULL) {
970        ctx->err = dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
971                                 DAV_ERR_PROP_NO_DATABASE,
972                                 "Attempted to set/remove a property "
973                                 "without a valid, open, read/write "
974                                 "property database.");
975        return;
976    }
977
978    if (ctx->operation == DAV_PROP_OP_SET) {
979        /*
980        ** Prep the element => propdb namespace index mapping, inserting
981        ** namespace URIs into the propdb that don't exist.
982        */
983        (void) (*propdb->db_hooks->map_namespaces)(propdb->db,
984                                                   propdb->ns_xlate,
985                                                   &propdb->mapping);
986    }
987    else if (ctx->operation == DAV_PROP_OP_DELETE) {
988        /*
989        ** There are no checks to perform here. If a property exists, then
990        ** we will delete it. If it does not exist, then it does not matter
991        ** (see S12.13.1).
992        **
993        ** Note that if a property does not exist, that does not rule out
994        ** that a SET will occur during this PROPPATCH (thusly creating it).
995        */
996    }
997}
998
999DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx)
1000{
1001    dav_propdb *propdb = ctx->propdb;
1002    dav_error *err = NULL;
1003    dav_elem_private *priv = ctx->prop->priv;
1004
1005    ctx->rollback = apr_pcalloc(propdb->p, sizeof(*ctx->rollback));
1006
1007    if (ctx->is_liveprop) {
1008        err = (*priv->provider->patch_exec)(propdb->resource,
1009                                            ctx->prop, ctx->operation,
1010                                            ctx->liveprop_ctx,
1011                                            &ctx->rollback->liveprop);
1012    }
1013    else {
1014        dav_prop_name name;
1015
1016        if (ctx->prop->ns == APR_XML_NS_NONE)
1017            name.ns = "";
1018        else
1019            name.ns = APR_XML_GET_URI_ITEM(propdb->ns_xlate, ctx->prop->ns);
1020        name.name = ctx->prop->name;
1021
1022        /* save the old value so that we can do a rollback. */
1023        if ((err = (*propdb->db_hooks
1024                    ->get_rollback)(propdb->db, &name,
1025                                    &ctx->rollback->deadprop)) != NULL)
1026            goto error;
1027
1028        if (ctx->operation == DAV_PROP_OP_SET) {
1029
1030            /* Note: propdb->mapping was set in dav_prop_validate() */
1031            err = (*propdb->db_hooks->store)(propdb->db, &name, ctx->prop,
1032                                             propdb->mapping);
1033
1034            /*
1035            ** If an error occurred, then assume that we didn't change the
1036            ** value. Remove the rollback item so that we don't try to set
1037            ** its value during the rollback.
1038            */
1039            /* ### euh... where is the removal? */
1040        }
1041        else if (ctx->operation == DAV_PROP_OP_DELETE) {
1042
1043            /*
1044            ** Delete the property. Ignore errors -- the property is there, or
1045            ** we are deleting it for a second time.
1046            */
1047            /* ### but what about other errors? */
1048            (void) (*propdb->db_hooks->remove)(propdb->db, &name);
1049        }
1050    }
1051
1052  error:
1053    /* push a more specific error here */
1054    if (err != NULL) {
1055        /*
1056        ** Use HTTP_INTERNAL_SERVER_ERROR because we shouldn't have seen
1057        ** any errors at this point.
1058        */
1059        ctx->err = dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
1060                                  DAV_ERR_PROP_EXEC,
1061                                  "Could not execute PROPPATCH.", err);
1062    }
1063}
1064
1065DAV_DECLARE_NONSTD(void) dav_prop_commit(dav_prop_ctx *ctx)
1066{
1067    dav_elem_private *priv = ctx->prop->priv;
1068
1069    /*
1070    ** Note that a commit implies ctx->err is NULL. The caller should assume
1071    ** a status of HTTP_OK for this case.
1072    */
1073
1074    if (ctx->is_liveprop) {
1075        (*priv->provider->patch_commit)(ctx->propdb->resource,
1076                                        ctx->operation,
1077                                        ctx->liveprop_ctx,
1078                                        ctx->rollback->liveprop);
1079    }
1080}
1081
1082DAV_DECLARE_NONSTD(void) dav_prop_rollback(dav_prop_ctx *ctx)
1083{
1084    dav_error *err = NULL;
1085    dav_elem_private *priv = ctx->prop->priv;
1086
1087    /* do nothing if there is no rollback information. */
1088    if (ctx->rollback == NULL)
1089        return;
1090
1091    /*
1092    ** ### if we have an error, and a rollback occurs, then the namespace
1093    ** ### mods should not happen at all. Basically, the namespace management
1094    ** ### is simply a bitch.
1095    */
1096
1097    if (ctx->is_liveprop) {
1098        err = (*priv->provider->patch_rollback)(ctx->propdb->resource,
1099                                                ctx->operation,
1100                                                ctx->liveprop_ctx,
1101                                                ctx->rollback->liveprop);
1102    }
1103    else {
1104        err = (*ctx->propdb->db_hooks
1105               ->apply_rollback)(ctx->propdb->db, ctx->rollback->deadprop);
1106    }
1107
1108    if (err != NULL) {
1109        if (ctx->err == NULL)
1110            ctx->err = err;
1111        else {
1112            dav_error *scan = err;
1113
1114            /* hook previous errors at the end of the rollback error */
1115            while (scan->prev != NULL)
1116                scan = scan->prev;
1117            scan->prev = ctx->err;
1118            ctx->err = err;
1119        }
1120    }
1121}
1122