summaryrefslogtreecommitdiff
path: root/src/ne_props.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ne_props.c')
-rw-r--r--src/ne_props.c620
1 files changed, 620 insertions, 0 deletions
diff --git a/src/ne_props.c b/src/ne_props.c
new file mode 100644
index 0000000..9b7da0f
--- /dev/null
+++ b/src/ne_props.c
@@ -0,0 +1,620 @@
+/*
+ WebDAV property manipulation
+ Copyright (C) 2000-2003, Joe Orton <joe@manyfish.co.uk>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+
+*/
+
+#include "config.h"
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "ne_alloc.h"
+#include "ne_xml.h"
+#include "ne_props.h"
+#include "ne_basic.h"
+#include "ne_locks.h"
+
+/* don't store flat props with a value > 10K */
+#define MAX_FLATPROP_LEN (102400)
+
+struct ne_propfind_handler_s {
+ ne_session *sess;
+ ne_request *request;
+
+ int has_props; /* whether we've already written some
+ * props to the body. */
+ ne_buffer *body;
+
+ ne_207_parser *parser207;
+ ne_xml_parser *parser;
+
+ /* Callback to create the private structure. */
+ ne_props_create_complex private_creator;
+ void *private_userdata;
+
+ /* Current propset, or NULL if none being processed. */
+ ne_prop_result_set *current;
+
+ ne_buffer *value; /* current flat property value */
+ int depth; /* nesting depth within a flat property */
+
+ ne_props_result callback;
+ void *userdata;
+};
+
+#define ELM_flatprop (NE_207_STATE_TOP - 1)
+
+/* We build up the results of one 'response' element in memory. */
+struct prop {
+ char *name, *nspace, *value, *lang;
+ /* Store a ne_propname here too, for convienience. pname.name =
+ * name, pname.nspace = nspace, but they are const'ed in pname. */
+ ne_propname pname;
+};
+
+#define NSPACE(x) ((x) ? (x) : "")
+
+struct propstat {
+ struct prop *props;
+ int numprops;
+ ne_status status;
+};
+
+/* Results set. */
+struct ne_prop_result_set_s {
+ struct propstat *pstats;
+ int numpstats;
+ void *private;
+ char *href;
+};
+
+
+static int
+startelm(void *userdata, int state, const char *name, const char *nspace,
+ const char **atts);
+static int
+endelm(void *userdata, int state, const char *name, const char *nspace);
+
+/* Handle character data; flat property value. */
+static int chardata(void *userdata, int state, const char *data, size_t len)
+{
+ ne_propfind_handler *hdl = userdata;
+
+ if (state == ELM_flatprop && hdl->value->length < MAX_FLATPROP_LEN)
+ ne_buffer_append(hdl->value, data, len);
+
+ return 0;
+}
+
+ne_xml_parser *ne_propfind_get_parser(ne_propfind_handler *handler)
+{
+ return handler->parser;
+}
+
+ne_request *ne_propfind_get_request(ne_propfind_handler *handler)
+{
+ return handler->request;
+}
+
+static int propfind(ne_propfind_handler *handler,
+ ne_props_result results, void *userdata)
+{
+ int ret;
+ ne_request *req = handler->request;
+
+ /* Register the flat property handler to catch any properties
+ * which the user isn't handling as 'complex'. */
+ ne_xml_push_handler(handler->parser, startelm, chardata, endelm, handler);
+
+ handler->callback = results;
+ handler->userdata = userdata;
+
+ ne_set_request_body_buffer(req, handler->body->data,
+ ne_buffer_size(handler->body));
+
+ ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
+
+ ne_add_response_body_reader(req, ne_accept_207, ne_xml_parse_v,
+ handler->parser);
+
+ ret = ne_request_dispatch(req);
+
+ if (ret == NE_OK && ne_get_status(req)->klass != 2) {
+ ret = NE_ERROR;
+ } else if (!ne_xml_valid(handler->parser)) {
+ ne_set_error(handler->sess, ne_xml_get_error(handler->parser));
+ ret = NE_ERROR;
+ }
+
+ return ret;
+}
+
+static void set_body(ne_propfind_handler *hdl, const ne_propname *names)
+{
+ ne_buffer *body = hdl->body;
+ int n;
+
+ if (!hdl->has_props) {
+ ne_buffer_zappend(body, "<prop>" EOL);
+ hdl->has_props = 1;
+ }
+
+ for (n = 0; names[n].name != NULL; n++) {
+ ne_buffer_concat(body, "<", names[n].name, " xmlns=\"",
+ NSPACE(names[n].nspace), "\"/>" EOL, NULL);
+ }
+
+}
+
+int ne_propfind_allprop(ne_propfind_handler *handler,
+ ne_props_result results, void *userdata)
+{
+ ne_buffer_zappend(handler->body, "<allprop/></propfind>" EOL);
+ return propfind(handler, results, userdata);
+}
+
+int ne_propfind_named(ne_propfind_handler *handler, const ne_propname *props,
+ ne_props_result results, void *userdata)
+{
+ set_body(handler, props);
+ ne_buffer_zappend(handler->body, "</prop></propfind>" EOL);
+ return propfind(handler, results, userdata);
+}
+
+
+/* The easy one... PROPPATCH */
+int ne_proppatch(ne_session *sess, const char *uri,
+ const ne_proppatch_operation *items)
+{
+ ne_request *req = ne_request_create(sess, "PROPPATCH", uri);
+ ne_buffer *body = ne_buffer_create();
+ int n, ret;
+
+ /* Create the request body */
+ ne_buffer_zappend(body, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" EOL
+ "<D:propertyupdate xmlns:D=\"DAV:\">");
+
+ for (n = 0; items[n].name != NULL; n++) {
+ const char *elm = (items[n].type == ne_propset) ? "set" : "remove";
+
+ /* <set><prop><prop-name>value</prop-name></prop></set> */
+ ne_buffer_concat(body, "<D:", elm, "><D:prop>"
+ "<", items[n].name->name, NULL);
+
+ if (items[n].name->nspace) {
+ ne_buffer_concat(body, " xmlns=\"", items[n].name->nspace, "\"", NULL);
+ }
+
+ if (items[n].type == ne_propset) {
+ ne_buffer_concat(body, ">", items[n].value, NULL);
+ } else {
+ ne_buffer_append(body, ">", 1);
+ }
+
+ ne_buffer_concat(body, "</", items[n].name->name, "></D:prop></D:", elm, ">"
+ EOL, NULL);
+ }
+
+ ne_buffer_zappend(body, "</D:propertyupdate>" EOL);
+
+ ne_set_request_body_buffer(req, body->data, ne_buffer_size(body));
+ ne_add_request_header(req, "Content-Type", NE_XML_MEDIA_TYPE);
+
+#ifdef USE_DAV_LOCKS
+ ne_lock_using_resource(req, uri, NE_DEPTH_ZERO);
+#endif
+
+ ret = ne_simple_request(sess, req);
+
+ ne_buffer_destroy(body);
+
+ return ret;
+}
+
+/* Compare two property names. */
+static int pnamecmp(const ne_propname *pn1, const ne_propname *pn2)
+{
+ if (pn1->nspace == NULL && pn2->nspace != NULL) {
+ return 1;
+ } else if (pn1->nspace != NULL && pn2->nspace == NULL) {
+ return -1;
+ } else if (pn1->nspace == NULL) {
+ return strcmp(pn1->name, pn2->name);
+ } else {
+ return (strcmp(pn1->nspace, pn2->nspace) ||
+ strcmp(pn1->name, pn2->name));
+ }
+}
+
+/* Find property in 'set' with name 'pname'. If found, set pstat_ret
+ * to the containing propstat, likewise prop_ret, and returns zero.
+ * If not found, returns non-zero. */
+static int findprop(const ne_prop_result_set *set, const ne_propname *pname,
+ struct propstat **pstat_ret, struct prop **prop_ret)
+{
+
+ int ps, p;
+
+ for (ps = 0; ps < set->numpstats; ps++) {
+ for (p = 0; p < set->pstats[ps].numprops; p++) {
+ struct prop *prop = &set->pstats[ps].props[p];
+
+ if (pnamecmp(&prop->pname, pname) == 0) {
+ if (pstat_ret != NULL)
+ *pstat_ret = &set->pstats[ps];
+ if (prop_ret != NULL)
+ *prop_ret = prop;
+ return 0;
+ }
+ }
+ }
+
+ return -1;
+}
+
+const char *ne_propset_value(const ne_prop_result_set *set,
+ const ne_propname *pname)
+{
+ struct prop *prop;
+
+ if (findprop(set, pname, NULL, &prop)) {
+ return NULL;
+ } else {
+ return prop->value;
+ }
+}
+
+const char *ne_propset_lang(const ne_prop_result_set *set,
+ const ne_propname *pname)
+{
+ struct prop *prop;
+
+ if (findprop(set, pname, NULL, &prop)) {
+ return NULL;
+ } else {
+ return prop->lang;
+ }
+}
+
+void *ne_propfind_current_private(ne_propfind_handler *handler)
+{
+ return handler->current ? handler->current->private : NULL;
+}
+
+void *ne_propset_private(const ne_prop_result_set *set)
+{
+ return set->private;
+}
+
+int ne_propset_iterate(const ne_prop_result_set *set,
+ ne_propset_iterator iterator, void *userdata)
+{
+ int ps, p;
+
+ for (ps = 0; ps < set->numpstats; ps++) {
+ for (p = 0; p < set->pstats[ps].numprops; p++) {
+ struct prop *prop = &set->pstats[ps].props[p];
+ int ret = iterator(userdata, &prop->pname, prop->value,
+ &set->pstats[ps].status);
+ if (ret)
+ return ret;
+
+ }
+ }
+
+ return 0;
+}
+
+const ne_status *ne_propset_status(const ne_prop_result_set *set,
+ const ne_propname *pname)
+{
+ struct propstat *pstat;
+
+ if (findprop(set, pname, &pstat, NULL)) {
+ /* TODO: it is tempting to return a dummy status object here
+ * rather than NULL, which says "Property result was not given
+ * by server." but I'm not sure if this is best left to the
+ * client. */
+ return NULL;
+ } else {
+ return &pstat->status;
+ }
+}
+
+static void *start_response(void *userdata, const char *href)
+{
+ ne_prop_result_set *set = ne_calloc(sizeof(*set));
+ ne_propfind_handler *hdl = userdata;
+
+ set->href = ne_strdup(href);
+
+ if (hdl->private_creator != NULL) {
+ set->private = hdl->private_creator(hdl->private_userdata, href);
+ }
+
+ hdl->current = set;
+
+ return set;
+}
+
+static void *start_propstat(void *userdata, void *response)
+{
+ ne_prop_result_set *set = response;
+ int n;
+ struct propstat *pstat;
+
+ n = set->numpstats;
+ set->pstats = ne_realloc(set->pstats, sizeof(struct propstat) * (n+1));
+ set->numpstats = n+1;
+
+ pstat = &set->pstats[n];
+ memset(pstat, 0, sizeof(*pstat));
+
+ /* And return this as the new pstat. */
+ return &set->pstats[n];
+}
+
+static int startelm(void *userdata, int parent,
+ const char *nspace, const char *name, const char **atts)
+{
+ ne_propfind_handler *hdl = userdata;
+ struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207);
+ struct prop *prop;
+ int n;
+ const char *lang;
+
+ /* Just handle all children of propstat and their descendants. */
+ if ((parent != NE_207_STATE_PROP && parent != ELM_flatprop)
+ || pstat == NULL)
+ return NE_XML_DECLINE;
+
+ if (parent == ELM_flatprop) {
+ /* collecting the flatprop value. */
+ hdl->depth++;
+ if (hdl->value->used < MAX_FLATPROP_LEN)
+ ne_buffer_concat(hdl->value, "<", name, ">", NULL);
+ return ELM_flatprop;
+ }
+
+ /* Add a property to this propstat */
+ n = pstat->numprops;
+
+ pstat->props = ne_realloc(pstat->props, sizeof(struct prop) * (n + 1));
+ pstat->numprops = n+1;
+
+ /* Fill in the new property. */
+ prop = &pstat->props[n];
+
+ prop->pname.name = prop->name = ne_strdup(name);
+ if (nspace[0] == '\0') {
+ prop->pname.nspace = prop->nspace = NULL;
+ } else {
+ prop->pname.nspace = prop->nspace = ne_strdup(nspace);
+ }
+ prop->value = NULL;
+
+ NE_DEBUG(NE_DBG_XML, "Got property #%d: {%s}%s.\n", n,
+ NSPACE(prop->nspace), prop->name);
+
+ /* This is under discussion at time of writing (April '01), and it
+ * looks like we need to retrieve the xml:lang property from any
+ * element here or above.
+ *
+ * Also, I think we might need attribute namespace handling here. */
+ lang = ne_xml_get_attr(hdl->parser, atts, NULL, "xml:lang");
+ if (lang != NULL) {
+ prop->lang = ne_strdup(lang);
+ NE_DEBUG(NE_DBG_XML, "Property language is %s\n", prop->lang);
+ } else {
+ prop->lang = NULL;
+ }
+
+ hdl->depth = 0;
+
+ return ELM_flatprop;
+}
+
+static int endelm(void *userdata, int state,
+ const char *nspace, const char *name)
+{
+ ne_propfind_handler *hdl = userdata;
+ struct propstat *pstat = ne_207_get_current_propstat(hdl->parser207);
+ int n;
+
+ if (hdl->depth > 0) {
+ /* nested. */
+ if (hdl->value->used < MAX_FLATPROP_LEN)
+ ne_buffer_concat(hdl->value, "</", name, ">", NULL);
+ hdl->depth--;
+ } else {
+ /* end of the current property value */
+ n = pstat->numprops - 1;
+ pstat->props[n].value = ne_buffer_finish(hdl->value);
+ hdl->value = ne_buffer_create();
+ }
+ return 0;
+}
+
+static void end_propstat(void *userdata, void *pstat_v,
+ const ne_status *status,
+ const char *description)
+{
+ struct propstat *pstat = pstat_v;
+
+ /* Nothing to do if no status was given. */
+ if (!status) return;
+
+ /* If we get a non-2xx response back here, we wipe the value for
+ * each of the properties in this propstat, so the caller knows to
+ * look at the status instead. It's annoying, since for each prop
+ * we will have done an unnecessary strdup("") above, but there is
+ * no easy way round that given the fact that we don't know
+ * whether we've got an error or not till after we get the
+ * property element.
+ *
+ * Interestingly IIS breaks the 2518 DTD and puts the status
+ * element first in the propstat. This is useful since then we
+ * *do* know whether each subsequent empty prop element means, but
+ * we can't rely on that here. */
+ if (status->klass != 2) {
+ int n;
+
+ for (n = 0; n < pstat->numprops; n++) {
+ ne_free(pstat->props[n].value);
+ pstat->props[n].value = NULL;
+ }
+ }
+
+ /* copy the status structure, and dup the reason phrase. */
+ pstat->status = *status;
+ pstat->status.reason_phrase = ne_strdup(status->reason_phrase);
+}
+
+/* Frees up a results set */
+static void free_propset(ne_prop_result_set *set)
+{
+ int n;
+
+ for (n = 0; n < set->numpstats; n++) {
+ int m;
+ struct propstat *p = &set->pstats[n];
+
+ for (m = 0; m < p->numprops; m++) {
+ NE_FREE(p->props[m].nspace);
+ ne_free(p->props[m].name);
+ NE_FREE(p->props[m].lang);
+ NE_FREE(p->props[m].value);
+ }
+
+ if (p->status.reason_phrase)
+ ne_free(p->status.reason_phrase);
+ if (p->props)
+ ne_free(p->props);
+ }
+
+ if (set->pstats)
+ ne_free(set->pstats);
+ ne_free(set->href);
+ ne_free(set);
+}
+
+static void end_response(void *userdata, void *resource,
+ const ne_status *status,
+ const char *description)
+{
+ ne_propfind_handler *handler = userdata;
+ ne_prop_result_set *set = resource;
+
+ /* Pass back the results for this resource. */
+ if (handler->callback && set->numpstats > 0)
+ handler->callback(handler->userdata, set->href, set);
+
+ /* Clean up the propset tree we've just built. */
+ free_propset(set);
+ handler->current = NULL;
+}
+
+ne_propfind_handler *
+ne_propfind_create(ne_session *sess, const char *uri, int depth)
+{
+ ne_propfind_handler *ret = ne_calloc(sizeof(ne_propfind_handler));
+
+ ret->parser = ne_xml_create();
+ ret->parser207 = ne_207_create(ret->parser, ret);
+ ret->sess = sess;
+ ret->body = ne_buffer_create();
+ ret->request = ne_request_create(sess, "PROPFIND", uri);
+ ret->value = ne_buffer_create();
+
+ ne_add_depth_header(ret->request, depth);
+
+ ne_207_set_response_handlers(ret->parser207,
+ start_response, end_response);
+
+ ne_207_set_propstat_handlers(ret->parser207, start_propstat,
+ end_propstat);
+
+ /* The start of the request body is fixed: */
+ ne_buffer_concat(ret->body,
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>" EOL
+ "<propfind xmlns=\"DAV:\">", NULL);
+
+ return ret;
+}
+
+/* Destroy a propfind handler */
+void ne_propfind_destroy(ne_propfind_handler *handler)
+{
+ ne_buffer_destroy(handler->value);
+ if (handler->current)
+ free_propset(handler->current);
+ ne_207_destroy(handler->parser207);
+ ne_xml_destroy(handler->parser);
+ ne_buffer_destroy(handler->body);
+ ne_request_destroy(handler->request);
+ ne_free(handler);
+}
+
+int ne_simple_propfind(ne_session *sess, const char *href, int depth,
+ const ne_propname *props,
+ ne_props_result results, void *userdata)
+{
+ ne_propfind_handler *hdl;
+ int ret;
+
+ hdl = ne_propfind_create(sess, href, depth);
+ if (props != NULL) {
+ ret = ne_propfind_named(hdl, props, results, userdata);
+ } else {
+ ret = ne_propfind_allprop(hdl, results, userdata);
+ }
+
+ ne_propfind_destroy(hdl);
+
+ return ret;
+}
+
+int ne_propnames(ne_session *sess, const char *href, int depth,
+ ne_props_result results, void *userdata)
+{
+ ne_propfind_handler *hdl;
+ int ret;
+
+ hdl = ne_propfind_create(sess, href, depth);
+
+ ne_buffer_zappend(hdl->body, "<propname/></propfind>");
+
+ ret = propfind(hdl, results, userdata);
+
+ ne_propfind_destroy(hdl);
+
+ return ret;
+}
+
+void ne_propfind_set_private(ne_propfind_handler *hdl,
+ ne_props_create_complex creator,
+ void *userdata)
+{
+ hdl->private_creator = creator;
+ hdl->private_userdata = userdata;
+}