1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /* Copyright (C) 2001-2004 Novell, Inc.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of version 2 of the GNU Lesser General Public
7 * License as published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
24 #include "e2k-result.h"
25 #include "e2k-encoding-utils.h"
26 #include "e2k-http-utils.h"
27 #include "e2k-propnames.h"
28 #include "e2k-xml-utils.h"
33 #include <libxml/parser.h>
34 #include <libxml/tree.h>
35 #include <libxml/xmlmemory.h>
38 prop_get_binary_array (E2kResult *result, const char *propname, xmlNode *node)
42 array = g_ptr_array_new ();
43 for (node = node->xmlChildrenNode; node; node = node->next) {
44 if (node->xmlChildrenNode && node->xmlChildrenNode->content)
45 g_ptr_array_add (array, e2k_base64_decode (node->xmlChildrenNode->content));
47 g_ptr_array_add (array, g_byte_array_new ());
50 e2k_properties_set_binary_array (result->props, propname, array);
54 prop_get_string_array (E2kResult *result, const char *propname,
55 E2kPropType real_type, xmlNode *node)
59 array = g_ptr_array_new ();
60 for (node = node->xmlChildrenNode; node; node = node->next) {
61 if (node->xmlChildrenNode && node->xmlChildrenNode->content)
62 g_ptr_array_add (array, g_strdup (node->xmlChildrenNode->content));
64 g_ptr_array_add (array, g_strdup (""));
67 e2k_properties_set_type_as_string_array (result->props, propname,
72 prop_get_binary (E2kResult *result, const char *propname, xmlNode *node)
76 if (node->xmlChildrenNode && node->xmlChildrenNode->content)
77 data = e2k_base64_decode (node->xmlChildrenNode->content);
79 data = g_byte_array_new ();
81 e2k_properties_set_binary (result->props, propname, data);
85 prop_get_string (E2kResult *result, const char *propname,
86 E2kPropType real_type, xmlNode *node)
90 if (node->xmlChildrenNode && node->xmlChildrenNode->content)
91 content = g_strdup (node->xmlChildrenNode->content);
93 content = g_strdup ("");
95 e2k_properties_set_type_as_string (result->props, propname,
100 prop_get_xml (E2kResult *result, const char *propname, xmlNode *node)
102 e2k_properties_set_xml (result->props, propname,
103 xmlCopyNode (node, TRUE));
107 prop_parse (xmlNode *node, E2kResult *result)
111 g_return_if_fail (node->ns != NULL);
114 result->props = e2k_properties_new ();
116 if (!strncmp (node->ns->href, E2K_NS_MAPI_ID, E2K_NS_MAPI_ID_LEN)) {
117 /* Reinsert the illegal initial '0' that was stripped out
118 * by sanitize_bad_multistatus. (This also covers us in
119 * the cases where the server returns the property without
122 name = g_strdup_printf ("%s0%s", node->ns->href, node->name);
124 name = g_strdup_printf ("%s%s", node->ns->href, node->name);
126 type = xmlGetNsProp (node, "dt", E2K_NS_TYPE);
127 if (type && !strcmp (type, "mv.bin.base64"))
128 prop_get_binary_array (result, name, node);
129 else if (type && !strcmp (type, "mv.int"))
130 prop_get_string_array (result, name, E2K_PROP_TYPE_INT_ARRAY, node);
131 else if (type && !strncmp (type, "mv.", 3))
132 prop_get_string_array (result, name, E2K_PROP_TYPE_STRING_ARRAY, node);
133 else if (type && !strcmp (type, "bin.base64"))
134 prop_get_binary (result, name, node);
135 else if (type && !strcmp (type, "int"))
136 prop_get_string (result, name, E2K_PROP_TYPE_INT, node);
137 else if (type && !strcmp (type, "boolean"))
138 prop_get_string (result, name, E2K_PROP_TYPE_BOOL, node);
139 else if (type && !strcmp (type, "float"))
140 prop_get_string (result, name, E2K_PROP_TYPE_FLOAT, node);
141 else if (type && !strcmp (type, "dateTime.tz"))
142 prop_get_string (result, name, E2K_PROP_TYPE_DATE, node);
143 else if (!node->xmlChildrenNode ||
144 !node->xmlChildrenNode->xmlChildrenNode)
145 prop_get_string (result, name, E2K_PROP_TYPE_STRING, node);
147 prop_get_xml (result, name, node);
155 propstat_parse (xmlNode *node, E2kResult *result)
157 node = node->xmlChildrenNode;
158 if (!E2K_IS_NODE (node, "DAV:", "status"))
160 result->status = e2k_http_parse_status (node->xmlChildrenNode->content);
161 if (result->status != E2K_HTTP_OK)
165 if (!E2K_IS_NODE (node, "DAV:", "prop"))
168 for (node = node->xmlChildrenNode; node; node = node->next) {
169 if (node->type == XML_ELEMENT_NODE)
170 prop_parse (node, result);
175 e2k_result_clear (E2kResult *result)
177 xmlFree (result->href);
180 e2k_properties_free (result->props);
181 result->props = NULL;
186 * e2k_results_array_new:
188 * Creates a new results array
190 * Return value: the array
193 e2k_results_array_new (void)
195 return g_array_new (FALSE, FALSE, sizeof (E2kResult));
198 /* Properties in the /mapi/id/{...} namespaces are usually (though not
199 * always) returned with names that start with '0', which is illegal
200 * and makes libxml choke. So we preprocess them to fix that.
203 sanitize_bad_multistatus (const char *buf, int len)
208 char ns, badprop[7], *ret;
210 /* If there are no "mapi/id/{...}" namespace declarations, then
211 * we don't need any cleanup.
213 if (!memchr (buf, '{', len))
216 body = g_string_new_len (buf, len);
218 /* Find the start and end of namespace declarations */
219 p = strstr (body->str, " xmlns:");
220 g_return_val_if_fail (p != NULL, NULL);
221 start = p + 1 - body->str;
224 g_return_val_if_fail (p != NULL, NULL);
228 if (strncmp (body->str + start, "xmlns:", 6) != 0)
230 if (strncmp (body->str + start + 7, "=\"", 2) != 0)
232 if (strncmp (body->str + start + 9, E2K_NS_MAPI_ID, E2K_NS_MAPI_ID_LEN) != 0)
235 ns = body->str[start + 6];
237 /* Find properties in this namespace and strip the
238 * initial '0' from their names to make them valid
241 snprintf (badprop, 6, "<%c:0x", ns);
242 while ((p = strstr (body->str, badprop)))
243 g_string_erase (body, p + 3 - body->str, 1);
244 snprintf (badprop, 7, "</%c:0x", ns);
245 while ((p = strstr (body->str, badprop)))
246 g_string_erase (body, p + 4 - body->str, 1);
249 p = strchr (body->str + start, '"');
252 p = strchr (p + 1, '"');
258 start = p + 2 - body->str;
262 g_string_free (body, FALSE);
267 * e2k_results_array_add_from_multistatus:
268 * @results_array: a results array, created by e2k_results_array_new()
269 * @msg: a 207 Multi-Status response
271 * Constructs an #E2kResult for each response in @msg and appends them
275 e2k_results_array_add_from_multistatus (GArray *results_array,
279 xmlNode *node, *rnode;
283 g_return_if_fail (msg->status_code == E2K_HTTP_MULTI_STATUS);
285 body = sanitize_bad_multistatus (msg->response.body, msg->response.length);
287 doc = e2k_parse_xml (body, -1);
290 doc = e2k_parse_xml (msg->response.body, msg->response.length);
293 node = doc->xmlRootNode;
294 if (!node || !E2K_IS_NODE (node, "DAV:", "multistatus")) {
299 for (node = node->xmlChildrenNode; node; node = node->next) {
300 if (!E2K_IS_NODE (node, "DAV:", "response") ||
301 !node->xmlChildrenNode)
304 memset (&result, 0, sizeof (result));
305 result.status = E2K_HTTP_OK; /* sometimes omitted if Brief */
307 for (rnode = node->xmlChildrenNode; rnode; rnode = rnode->next) {
308 if (rnode->type != XML_ELEMENT_NODE)
311 if (E2K_IS_NODE (rnode, "DAV:", "href"))
312 result.href = xmlNodeGetContent (rnode);
313 else if (E2K_IS_NODE (rnode, "DAV:", "status")) {
314 result.status = e2k_http_parse_status (
315 rnode->xmlChildrenNode->content);
316 } else if (E2K_IS_NODE (rnode, "DAV:", "propstat"))
317 propstat_parse (rnode, &result);
319 prop_parse (rnode, &result);
323 if (E2K_HTTP_STATUS_IS_SUCCESSFUL (result.status) &&
325 result.props = e2k_properties_new ();
326 g_array_append_val (results_array, result);
328 e2k_result_clear (&result);
335 * e2k_results_array_free:
336 * @results_array: the array
337 * @free_results: whether or not to also free the contents of the array
339 * Frees @results_array, and optionally its contents
342 e2k_results_array_free (GArray *results_array, gboolean free_results)
345 e2k_results_free ((E2kResult *)results_array->data,
348 g_array_free (results_array, FALSE);
353 * e2k_results_from_multistatus:
354 * @msg: a 207 Multi-Status response
355 * @results: pointer to a variable to store an array of E2kResult in
356 * @nresults: pointer to a variable to store the length of *@results in
358 * Parses @msg and puts the results in *@results and *@nresults.
359 * The caller should free the data with e2k_results_free()
362 e2k_results_from_multistatus (SoupMessage *msg,
363 E2kResult **results, int *nresults)
365 GArray *results_array;
367 results_array = e2k_results_array_new ();
368 e2k_results_array_add_from_multistatus (results_array, msg);
370 *results = (E2kResult *)results_array->data;
371 *nresults = results_array->len;
372 e2k_results_array_free (results_array, FALSE);
377 * @results: a results array returned from e2k_results_from_multistatus()
378 * @nresults: the length of @results
380 * Performs a deep copy of @results
382 * Return value: a copy of @results.
385 e2k_results_copy (E2kResult *results, int nresults)
387 GArray *results_array = NULL;
388 E2kResult result, *new_results;
391 results_array = g_array_new (TRUE, FALSE, sizeof (E2kResult));
392 for (i = 0; i < nresults; i++) {
393 result.href = xmlMemStrdup (results[i].href);
394 result.status = results[i].status;
395 result.props = e2k_properties_copy (results[i].props);
397 g_array_append_val (results_array, result);
400 new_results = (E2kResult *) (results_array->data);
401 g_array_free (results_array, FALSE);
407 * @results: a results array
408 * @nresults: the length of @results
410 * Frees the data in @results.
413 e2k_results_free (E2kResult *results, int nresults)
417 for (i = 0; i < nresults; i++)
418 e2k_result_clear (&results[i]);
424 struct E2kResultIter {
427 E2kHTTPStatus status;
434 E2kResultIterFetchFunc fetch_func;
435 E2kResultIterFreeFunc free_func;
440 iter_fetch (E2kResultIter *iter)
442 if (iter->nresults) {
444 iter->first += iter->nresults;
446 iter->first -= iter->nresults;
447 e2k_results_free (iter->results, iter->nresults);
451 iter->status = iter->fetch_func (iter, iter->ctx, iter->op,
461 * e2k_result_iter_new:
462 * @ctx: an #E2kContext
463 * @op: an #E2kOperation, to use for cancellation
464 * @ascending: %TRUE if results should be returned in ascending
465 * order, %FALSE if they should be returned in descending order
466 * @total: the total number of results that will be returned, or -1
468 * @fetch_func: function to call to fetch more results
469 * @free_func: function to call when the iterator is freed
470 * @user_data: data to pass to @fetch_func and @free_func
472 * Creates a object that can be used to return the results of
473 * a Multi-Status query on @ctx.
475 * @fetch_func will be called to fetch results, and it may update the
476 * #first and #total fields if necessary. If @ascending is %TRUE, then
477 * e2k_result_iter_next() will first return the first result, then the
478 * second result, etc. If @ascending is %FALSE, it will return the
479 * last result, then the second-to-last result, etc.
481 * When all of the results returned by the first @fetch_func call have
482 * been returned to the caller, @fetch_func will be called again to
483 * get more results. This will continue until @fetch_func returns 0
484 * results, or returns an error code.
486 * Return value: the new iterator
489 e2k_result_iter_new (E2kContext *ctx, E2kOperation *op,
490 gboolean ascending, int total,
491 E2kResultIterFetchFunc fetch_func,
492 E2kResultIterFreeFunc free_func,
497 iter = g_new0 (E2kResultIter, 1);
498 iter->ctx = g_object_ref (ctx);
500 iter->ascending = ascending;
502 iter->fetch_func = fetch_func;
503 iter->free_func = free_func;
504 iter->user_data = user_data;
512 * e2k_result_iter_next:
513 * @iter: an #E2kResultIter
515 * Returns the next result in the operation being iterated by @iter.
516 * If there are no more results, or if an error occurs, it will return
517 * %NULL. (The return value of e2k_result_iter_free() distinguishes
520 * Return value: the result, or %NULL
523 e2k_result_iter_next (E2kResultIter *iter)
525 g_return_val_if_fail (iter != NULL, NULL);
527 if (iter->nresults == 0)
530 if (iter->next >= iter->nresults) {
532 if (iter->nresults == 0)
534 if (iter->total <= 0)
535 iter->status = E2K_HTTP_MALFORMED;
536 if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (iter->status))
540 return iter->ascending ?
541 &iter->results[iter->next++] :
542 &iter->results[iter->nresults - ++iter->next];
546 * e2k_result_iter_get_index:
547 * @iter: an #E2kResultIter
549 * Returns the index of the current result in the complete list of
550 * results. Note that for a descending search, %index will start at
551 * %total - 1 and count backwards to 0.
553 * Return value: the index of the current result
556 e2k_result_iter_get_index (E2kResultIter *iter)
558 g_return_val_if_fail (iter != NULL, -1);
560 return iter->ascending ?
561 iter->first + iter->next - 1 :
562 iter->first + (iter->nresults - iter->next);
566 * e2k_result_iter_get_total:
567 * @iter: an #E2kResultIter
569 * Returns the total number of results expected for @iter. Note that
570 * in some cases, this may change while the results are being iterated
571 * (if objects that match the query are added to or removed from the
574 * Return value: the total number of results expected
577 e2k_result_iter_get_total (E2kResultIter *iter)
579 g_return_val_if_fail (iter != NULL, -1);
585 * e2k_result_iter_free:
586 * @iter: an #E2kResultIter
588 * Frees @iter and all associated memory, and returns a status code
589 * indicating whether it ended successfully or not. (Note that the
590 * status may be %E2K_HTTP_OK rather than %E2K_HTTP_MULTI_STATUS.)
592 * Return value: the final status
595 e2k_result_iter_free (E2kResultIter *iter)
597 E2kHTTPStatus status;
599 g_return_val_if_fail (iter != NULL, E2K_HTTP_MALFORMED);
601 status = iter->status;
603 e2k_results_free (iter->results, iter->nresults);
604 iter->free_func (iter, iter->user_data);
605 g_object_unref (iter->ctx);