Back to index

avfs  1.0.1
dav_props.c
Go to the documentation of this file.
00001 /* 
00002    WebDAV Properties manipulation
00003    Copyright (C) 2000-2001, Joe Orton <joe@light.plus.com>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009    
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public
00016    License along with this library; if not, write to the Free
00017    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
00018    MA 02111-1307, USA
00019 
00020 */
00021 
00022 #include "config.h"
00023 
00024 #ifdef HAVE_STDLIB_H
00025 #include <stdlib.h>
00026 #endif
00027 #ifdef HAVE_STRING_H
00028 #include <string.h>
00029 #endif
00030 
00031 #include "ne_alloc.h"
00032 #include "dav_props.h"
00033 #include "dav_basic.h"
00034 #include "hip_xml.h"
00035 
00036 struct dav_propfind_handler_s {
00037     http_session *sess;
00038     const char *uri;
00039     int depth;
00040 
00041     int has_props; /* whether we've already written some
00042                   * props to the body. */
00043     sbuffer body;
00044     
00045     dav_207_parser *parser207;
00046     hip_xml_parser *parser;
00047     struct hip_xml_elm *elms;
00048 
00049     /* Callback to create the private structure. */
00050     dav_props_create_complex private_creator;
00051     void *private_userdata;
00052     
00053     /* Current propset. */
00054     dav_prop_result_set *current;
00055 
00056     dav_props_result callback;
00057     void *userdata;
00058 };
00059 
00060 #define ELM_namedprop (DAV_ELM_207_UNUSED)
00061 
00062 /* We build up the results of one 'response' element in memory. */
00063 struct prop {
00064     char *name, *nspace, *value;
00065     /* Store a dav_propname here too, for convienience.  pname.name =
00066      * name, pname.nspace = nspace, but they are const'ed in pname. */
00067     dav_propname pname;
00068 };
00069 
00070 struct propstat {
00071     struct prop *props;
00072     int numprops;
00073     http_status status;
00074 } propstat;
00075 
00076 /* Results set. */
00077 struct dav_prop_result_set_s {
00078     struct propstat *pstats;
00079     int numpstats;
00080     void *private;
00081     char *href;
00082 };
00083 
00084 hip_xml_parser *dav_propfind_get_parser(dav_propfind_handler *handler)
00085 {
00086     return handler->parser;
00087 }
00088 
00089 static int propfind(dav_propfind_handler *handler, 
00090                   dav_props_result results, void *userdata)
00091 {
00092     int ret;
00093     http_req *req;
00094 
00095     /* Register the catch-all handler to ignore any cruft the
00096      * server returns. */
00097     dav_207_ignore_unknown(handler->parser207);
00098     
00099     req = http_request_create(handler->sess, "PROPFIND", handler->uri);
00100 
00101     handler->callback = results;
00102     handler->userdata = userdata;
00103 
00104     http_set_request_body_buffer(req, sbuffer_data(handler->body));
00105 
00106     http_add_request_header(req, "Content-Type", "text/xml"); /* TODO: UTF-8? */
00107     dav_add_depth_header(req, handler->depth);
00108     
00109     http_add_response_body_reader(req, dav_accept_207, hip_xml_parse_v, 
00110                               handler->parser);
00111 
00112     ret = http_request_dispatch(req);
00113 
00114     if (ret == HTTP_OK && http_get_status(req)->klass != 2) {
00115        ret = HTTP_ERROR;
00116     } else if (!hip_xml_valid(handler->parser)) {
00117        http_set_error(handler->sess, hip_xml_get_error(handler->parser));
00118        ret = HTTP_ERROR;
00119     }
00120 
00121     http_request_destroy(req);
00122 
00123     return ret;
00124 }
00125 
00126 static void set_body(dav_propfind_handler *hdl, const dav_propname *names)
00127 {
00128     sbuffer body = hdl->body;
00129     int n;
00130     
00131     if (!hdl->has_props) {
00132        sbuffer_zappend(body, "<prop>" EOL);
00133        hdl->has_props = 1;
00134     }
00135 
00136     for (n = 0; names[n].name != NULL; n++) {
00137        char *name, *nspace;
00138        
00139        /* TODO:
00140         * In retrospect it is probably Wrong to do UTF-8 encoding
00141         * here.  More likely, it should be an API requirement that
00142         * property names/values are UTF-8 encoded strings.
00143         * 
00144         * I'm not up on the issues and problems that need to be
00145         * solved in this arena, so someone who is please shout
00146         * at me and tell me I'm stupid: neon@webdav.org */
00147 
00148        name = ne_utf8_encode(names[n].name);
00149        nspace = ne_utf8_encode(names[n].nspace);
00150 
00151        sbuffer_concat(body, "<", names[n].name, " xmlns=\"", 
00152                      names[n].nspace, "\"/>" EOL, NULL);
00153 
00154        free(name);
00155        free(nspace);
00156     }
00157 
00158 }
00159 
00160 int dav_propfind_allprop(dav_propfind_handler *handler, 
00161                       dav_props_result results, void *userdata)
00162 {
00163     sbuffer_zappend(handler->body, "<allprop/></propfind>" EOL);
00164     return propfind(handler, results, userdata);
00165 }
00166 
00167 int dav_propfind_named(dav_propfind_handler *handler,
00168                      dav_props_result results, void *userdata)
00169 {
00170     sbuffer_zappend(handler->body, "</prop></propfind>" EOL);
00171     return propfind(handler, results, userdata);
00172 }
00173 
00174 
00175 /* The easy one... PROPPATCH */
00176 int dav_proppatch(http_session *sess, const char *uri, 
00177                 const dav_proppatch_operation *items)
00178 {
00179     http_req *req = http_request_create(sess, "PROPPATCH", uri);
00180     sbuffer body = sbuffer_create();
00181     char *utf8body;
00182     int n, ret;
00183     
00184     /* Create the request body */
00185     sbuffer_zappend(body, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" EOL
00186                    "<propertyupdate xmlns=\"DAV:\">");
00187 
00188     for (n = 0; items[n].name != NULL; n++) {
00189        switch (items[n].type) {
00190        case dav_propset:
00191            /* <set><prop><prop-name>value</prop-name></prop></set> */
00192            sbuffer_concat(body, "<set><prop>"
00193                         "<", items[n].name->name, " xmlns=\"",
00194                         items[n].name->nspace, "\">", items[n].value,
00195                         "</", items[n].name->name, "></prop></set>" EOL, 
00196                         NULL);
00197            break;
00198 
00199        case dav_propremove:
00200            /* <remove><prop><prop-name/></prop></remove> */
00201            sbuffer_concat(body, 
00202                         "<remove><prop><", items[n].name->name, " xmlns=\"",
00203                         items[n].name->nspace, "\"/></prop></remove>" EOL, 
00204                         NULL);
00205            break;
00206        }
00207     }  
00208 
00209     sbuffer_zappend(body, "</propertyupdate>" EOL);
00210     
00211     utf8body = ne_utf8_encode(sbuffer_data(body));
00212 
00213     http_set_request_body_buffer(req, utf8body);
00214     http_add_request_header(req, "Content-Type", "text/xml"); /* TODO: UTF-8? */
00215     
00216     ret = dav_simple_request(sess, req);
00217     
00218     sbuffer_destroy(body);
00219     free(utf8body);
00220 
00221     return ret;
00222 }
00223 
00224 /* Compare two property names. */
00225 static int pnamecmp(const dav_propname *pn1, const dav_propname *pn2)
00226 {
00227     return (strcasecmp(pn1->nspace, pn2->nspace) ||
00228            strcasecmp(pn1->name, pn2->name));
00229 }
00230 
00231 /* Find property in 'set' with name 'pname'.  If found, set pstat_ret
00232  * to the containing propstat, likewise prop_ret, and returns zero.
00233  * If not found, returns non-zero.  */
00234 static int findprop(const dav_prop_result_set *set, const dav_propname *pname,
00235                   struct propstat **pstat_ret, struct prop **prop_ret)
00236 {
00237     
00238     int ps, p;
00239 
00240     for (ps = 0; ps < set->numpstats; ps++) {
00241        for (p = 0; p < set->pstats[ps].numprops; p++) {
00242            struct prop *prop = &set->pstats[ps].props[p];
00243 
00244            if (pnamecmp(&prop->pname, pname) == 0) {
00245               if (pstat_ret != NULL)
00246                   *pstat_ret = &set->pstats[ps];
00247               if (prop_ret != NULL)
00248                   *prop_ret = prop;
00249               return 0;
00250            }
00251        }
00252     }
00253 
00254     return -1;
00255 }
00256 
00257 const char *dav_propset_value(const dav_prop_result_set *set,
00258                            const dav_propname *pname)
00259 {
00260     struct prop *prop;
00261     
00262     if (findprop(set, pname, NULL, &prop)) {
00263        return NULL;
00264     } else {
00265        return prop->value;
00266     }
00267 }
00268 
00269 void *dav_propfind_current_private(dav_propfind_handler *handler)
00270 {
00271     return handler->current->private;
00272 }
00273 
00274 void *dav_propset_private(const dav_prop_result_set *set)
00275 {
00276     return set->private;
00277 }
00278 
00279 int dav_propset_iterate(const dav_prop_result_set *set,
00280                      dav_propset_iterator iterator, void *userdata)
00281 {
00282     int ps, p;
00283 
00284     for (ps = 0; ps < set->numpstats; ps++) {
00285        for (p = 0; p < set->pstats[ps].numprops; p++) {
00286            struct prop *prop = &set->pstats[ps].props[p];
00287            int ret = iterator(userdata, &prop->pname, prop->value, 
00288                             &set->pstats[ps].status);
00289            if (ret)
00290               return ret;
00291 
00292        }
00293     }
00294 
00295     return 0;
00296 }
00297 
00298 const http_status *dav_propset_status(const dav_prop_result_set *set,
00299                                   const dav_propname *pname)
00300 {
00301     struct propstat *pstat;
00302     
00303     if (findprop(set, pname, &pstat, NULL)) {
00304        /* TODO: it is tempting to return a dummy status object here
00305         * rather than NULL, which says "Property result was not given
00306         * by server."  but I'm not sure if this is best left to the
00307         * client.  */
00308        return NULL;
00309     } else {
00310        return &pstat->status;
00311     }
00312 }
00313 
00314 static int check_context(hip_xml_elmid parent, hip_xml_elmid child)
00315 {
00316     if (child == ELM_namedprop && parent == DAV_ELM_prop)
00317        return HIP_XML_VALID;
00318 
00319     if (child == HIP_ELM_unknown && parent == DAV_ELM_prop)
00320        return HIP_XML_VALID;
00321 
00322     return HIP_XML_DECLINE;
00323 }
00324 
00325 static void *start_response(void *userdata, const char *href)
00326 {
00327     dav_prop_result_set *set = ne_calloc(sizeof(*set));
00328     dav_propfind_handler *hdl = userdata;
00329 
00330     set->href = ne_strdup(href);
00331 
00332     if (hdl->private_creator != NULL) {
00333        set->private = hdl->private_creator(hdl->private_userdata, href);
00334     }
00335 
00336     hdl->current = set;
00337 
00338     return set;
00339 }
00340 
00341 static void *start_propstat(void *userdata, void *response)
00342 {
00343     dav_prop_result_set *set = response;
00344     int n;
00345     struct propstat *pstat;
00346 
00347     n = set->numpstats;
00348     set->pstats = realloc(set->pstats, sizeof(struct propstat) * (n+1));
00349     set->numpstats = n+1;
00350 
00351     pstat = &set->pstats[n];
00352     memset(pstat, 0, sizeof(*pstat));
00353     
00354     /* And return this as the new pstat. */
00355     return &set->pstats[n];
00356 }
00357 
00358 static int 
00359 startelm(void *userdata, const struct hip_xml_elm *elm, 
00360         const char **atts)
00361 {
00362     dav_propfind_handler *hdl = userdata;
00363     struct propstat *pstat = dav_207_get_current_propstat(hdl->parser207);
00364     struct prop *prop;
00365     int n;
00366 
00367     /* Paranoia */
00368     if (pstat == NULL) {
00369        DEBUG(DEBUG_XML, "gp_startelm: No propstat found, or not my element.");
00370        return -1;
00371     }
00372 
00373     /* Add a property to this propstat */
00374     n = pstat->numprops;
00375 
00376     pstat->props = realloc(pstat->props, sizeof(struct prop) * (n + 1));
00377     pstat->numprops = n+1;
00378 
00379     /* Fill in the new property. */
00380     prop = &pstat->props[n];
00381 
00382     prop->pname.name = prop->name = ne_strdup(elm->name);
00383     prop->pname.nspace = prop->nspace = ne_strdup(elm->nspace);
00384     prop->value = NULL;
00385 
00386     DEBUG(DEBUG_XML, "Got property #%d: %s@@%s.\n", n, 
00387          prop->nspace, prop->name);
00388 
00389     return 0;
00390 }
00391 
00392 static int 
00393 endelm(void *userdata, const struct hip_xml_elm *elm, const char *cdata)
00394 {
00395     dav_propfind_handler *hdl = userdata;
00396     struct propstat *pstat = dav_207_get_current_propstat(hdl->parser207);
00397     int n;
00398 
00399     if (pstat == NULL) {
00400        DEBUG(DEBUG_XML, "gp_endelm: No propstat found, or not my element.");
00401        return -1;
00402     }
00403 
00404     n = pstat->numprops - 1;
00405 
00406     DEBUG(DEBUG_XML, "Value of property #%d is %s\n", n, cdata);
00407     
00408     pstat->props[n].value = ne_strdup(cdata);
00409 
00410     return 0;
00411 }
00412 
00413 static void end_propstat(void *userdata, void *pstat_v, 
00414                       const char *status_line, const http_status *status,
00415                       const char *description)
00416 {
00417     struct propstat *pstat = pstat_v;
00418 
00419     /* If we get a non-2xx response back here, we wipe the value for
00420      * each of the properties in this propstat, so the caller knows to
00421      * look at the status instead. It's annoying, since for each prop
00422      * we will have done an unnecessary strdup("") above, but there is
00423      * no easy way round that given the fact that we don't know
00424      * whether we've got an error or not till after we get the
00425      * property element. */
00426     if (status->klass != 2) {
00427        int n;
00428        
00429        for (n = 0; n < pstat->numprops; n++) {
00430            free(pstat->props[n].value);
00431            pstat->props[n].value = NULL;
00432        }
00433     }
00434 
00435     pstat->status = *status;
00436 }
00437 
00438 /* Frees up a results set */
00439 static void free_propset(dav_prop_result_set *set)
00440 {
00441     int n;
00442     
00443     for (n = 0; n < set->numpstats; n++) {
00444        int m;
00445        struct propstat *p = &set->pstats[n];
00446 
00447        for (m = 0; m < p->numprops; m++) {
00448            free(p->props[m].nspace);
00449            free(p->props[m].name);
00450            HTTP_FREE(p->props[m].value);
00451        }
00452 
00453        free(set->pstats[n].props);
00454     }
00455 
00456     free(set->pstats);
00457     free(set);        
00458 }
00459 
00460 static void end_response(void *userdata, void *resource,
00461                       const char *status_line,
00462                       const http_status *status,
00463                       const char *description)
00464 {
00465     dav_propfind_handler *handler = userdata;
00466     dav_prop_result_set *set = resource;
00467     
00468     /* TODO: Handle status here too? The status element is mandatory
00469      * inside each propstat, so, not much point probably. */
00470 
00471     /* Pass back the results for this resource. */
00472     if (handler->callback != NULL) {
00473        handler->callback(handler->userdata, set->href, set);
00474     }
00475 
00476     free(set->href);
00477 
00478     /* Clean up the propset tree we've just built. */
00479     free_propset(set);
00480 }
00481 
00482 dav_propfind_handler *
00483 dav_propfind_create(http_session *sess, const char *uri, int depth)
00484 {
00485     dav_propfind_handler *ret = ne_calloc(sizeof(dav_propfind_handler));
00486 
00487     ret->parser = hip_xml_create();
00488     ret->parser207 = dav_207_create(ret->parser, ret);
00489     ret->uri = uri;
00490     ret->depth = depth;
00491     ret->sess = sess;
00492     ret->body = sbuffer_create();
00493 
00494     dav_207_set_response_handlers(ret->parser207, 
00495                               start_response, end_response);
00496 
00497     dav_207_set_propstat_handlers(ret->parser207, start_propstat,
00498                               end_propstat);
00499 
00500     /* The start of the request body is fixed: */
00501     sbuffer_concat(ret->body, 
00502                   "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL 
00503                   "<propfind xmlns=\"DAV:\">", NULL);
00504 
00505     return ret;
00506 }
00507 
00508 static struct hip_xml_elm *make_elms(const dav_propname *props)
00509 {
00510     int n;
00511     struct hip_xml_elm *elms;
00512 
00513     if (props == NULL) {
00514        /* Just collect unknown.  Note this is a collect, so they get
00515         * a noddy text representation of the XML back, which is
00516         * probably never actually useful. */
00517 
00518        DEBUG(DEBUG_XML, "using UNKNOWN element handler.\n");
00519        elms = ne_calloc(sizeof(*elms) * 2);
00520        
00521        elms[0].id = HIP_ELM_unknown;
00522        elms[0].flags = HIP_XML_COLLECT | HIP_XML_UTF8DECODE;
00523 
00524        return elms;
00525 
00526     } else {
00527        /* Count the properties */
00528        for (n = 0; props[n].name != NULL; n++) /* noop */;
00529        
00530        /* Allocate the array, enough for each */
00531        elms = ne_calloc(sizeof(*elms) * (n+1));
00532        
00533        /* Fill it in. Note that the elements all have the SAME
00534         * element ID, and we COLLECT inside them, since these
00535         * are flat properties. */
00536        for (n = 0; props[n].name != NULL; n++) {
00537            elms[n].nspace = props[n].nspace;
00538            elms[n].name = props[n].name;
00539            elms[n].id = ELM_namedprop;
00540            elms[n].flags = HIP_XML_COLLECT | HIP_XML_UTF8DECODE;
00541        }
00542     }
00543     
00544     return elms;
00545 }
00546 
00547 static void free_elms(struct hip_xml_elm *elms)
00548 {
00549     free(elms);
00550 }
00551 
00552 /* Destroy a propfind handler */
00553 void dav_propfind_destroy(dav_propfind_handler *handler)
00554 {
00555     dav_207_destroy(handler->parser207);
00556     hip_xml_destroy(handler->parser);
00557     if (handler->elms != NULL)
00558        free_elms(handler->elms);
00559     sbuffer_destroy(handler->body);
00560     free(handler);    
00561 }
00562 
00563 int dav_simple_propfind(http_session *sess, const char *href, int depth,
00564                      const dav_propname *props,
00565                      dav_props_result results, void *userdata)
00566 {
00567     dav_propfind_handler *hdl;
00568     int ret;
00569 
00570     hdl = dav_propfind_create(sess, href, depth);
00571     if (props != NULL) {
00572        /* Named. */
00573        dav_propfind_set_flat(hdl, props);
00574        ret = dav_propfind_named(hdl, results, userdata);
00575     } else {
00576        /* Allprop: register the catch-all-props handler. */
00577        hdl->elms = make_elms(NULL);
00578        hip_xml_push_handler(hdl->parser, hdl->elms, 
00579                           check_context, startelm, endelm, hdl);
00580        ret = dav_propfind_allprop(hdl, results, userdata);
00581     }
00582        
00583     dav_propfind_destroy(hdl);
00584     
00585     return ret;
00586 }
00587 
00588 void dav_propfind_set_flat(dav_propfind_handler *hdl, 
00589                         const dav_propname *props)
00590 {
00591     set_body(hdl, props);
00592 
00593     /* Register our special flat-property handler, which
00594      * is used for every flat property that they just passed.
00595      */
00596     
00597     hdl->elms = make_elms(props);
00598     hip_xml_push_handler(hdl->parser, hdl->elms, 
00599                       check_context, startelm, endelm, hdl);
00600 }
00601 
00602 void dav_propfind_set_complex(dav_propfind_handler *hdl,
00603                            const dav_propname *props,
00604                            dav_props_create_complex creator,
00605                            void *userdata)
00606 {
00607     set_body(hdl, props);
00608     hdl->private_creator = creator;
00609     hdl->private_userdata = userdata;
00610 }