Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / lib / e2k-result.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2001-2004 Novell, Inc.
4  *
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.
8  *
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.
13  *
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.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
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"
29
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include <libxml/parser.h>
34 #include <libxml/tree.h>
35 #include <libxml/xmlmemory.h>
36
37 static void
38 prop_get_binary_array (E2kResult *result, const char *propname, xmlNode *node)
39 {
40         GPtrArray *array;
41
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));
46                 else
47                         g_ptr_array_add (array, g_byte_array_new ());
48         }
49
50         e2k_properties_set_binary_array (result->props, propname, array);
51 }
52
53 static void
54 prop_get_string_array (E2kResult *result, const char *propname,
55                        E2kPropType real_type, xmlNode *node)
56 {
57         GPtrArray *array;
58
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));
63                 else
64                         g_ptr_array_add (array, g_strdup (""));
65         }
66
67         e2k_properties_set_type_as_string_array (result->props, propname,
68                                                  real_type, array);
69 }
70
71 static void
72 prop_get_binary (E2kResult *result, const char *propname, xmlNode *node)
73 {
74         GByteArray *data;
75
76         if (node->xmlChildrenNode && node->xmlChildrenNode->content)
77                 data = e2k_base64_decode (node->xmlChildrenNode->content);
78         else
79                 data = g_byte_array_new ();
80
81         e2k_properties_set_binary (result->props, propname, data);
82 }
83
84 static void
85 prop_get_string (E2kResult *result, const char *propname,
86                  E2kPropType real_type, xmlNode *node)
87 {
88         char *content;
89
90         if (node->xmlChildrenNode && node->xmlChildrenNode->content)
91                 content = g_strdup (node->xmlChildrenNode->content);
92         else
93                 content = g_strdup ("");
94
95         e2k_properties_set_type_as_string (result->props, propname,
96                                            real_type, content);
97 }
98
99 static void
100 prop_get_xml (E2kResult *result, const char *propname, xmlNode *node)
101 {
102         e2k_properties_set_xml (result->props, propname,
103                                 xmlCopyNode (node, TRUE));
104 }
105
106 static void
107 prop_parse (xmlNode *node, E2kResult *result)
108 {
109         char *name, *type;
110
111         g_return_if_fail (node->ns != NULL);
112
113         if (!result->props)
114                 result->props = e2k_properties_new ();
115
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
120                  * the '0'.)
121                  */
122                 name = g_strdup_printf ("%s0%s", node->ns->href, node->name);
123         } else
124                 name = g_strdup_printf ("%s%s", node->ns->href, node->name);
125
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);
146         else
147                 prop_get_xml (result, name, node);
148
149         if (type)
150                 xmlFree (type);
151         g_free (name);
152 }
153
154 static void
155 propstat_parse (xmlNode *node, E2kResult *result)
156 {
157         node = node->xmlChildrenNode;
158         if (!E2K_IS_NODE (node, "DAV:", "status"))
159                 return;
160         result->status = e2k_http_parse_status (node->xmlChildrenNode->content);
161         if (result->status != E2K_HTTP_OK)
162                 return;
163
164         node = node->next;
165         if (!E2K_IS_NODE (node, "DAV:", "prop"))
166                 return;
167
168         for (node = node->xmlChildrenNode; node; node = node->next) {
169                 if (node->type == XML_ELEMENT_NODE)
170                         prop_parse (node, result);
171         }
172 }
173
174 static void
175 e2k_result_clear (E2kResult *result)
176 {
177         xmlFree (result->href);
178         result->href = NULL;
179         if (result->props) {
180                 e2k_properties_free (result->props);
181                 result->props = NULL;
182         }
183 }
184
185 /**
186  * e2k_results_array_new:
187  *
188  * Creates a new results array
189  *
190  * Return value: the array
191  **/
192 GArray *
193 e2k_results_array_new (void)
194 {
195         return g_array_new (FALSE, FALSE, sizeof (E2kResult));
196 }
197
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.
201  */
202 static char *
203 sanitize_bad_multistatus (const char *buf, int len)
204 {
205         GString *body;
206         const char *p;
207         int start, end;
208         char ns, badprop[7], *ret;
209
210         /* If there are no "mapi/id/{...}" namespace declarations, then
211          * we don't need any cleanup.
212          */
213         if (!memchr (buf, '{', len))
214                 return NULL;
215
216         body = g_string_new_len (buf, len);
217
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;
222
223         p = strchr (p, '>');
224         g_return_val_if_fail (p != NULL, NULL);
225         end = p - body->str;
226
227         while (1) {
228                 if (strncmp (body->str + start, "xmlns:", 6) != 0)
229                         break;
230                 if (strncmp (body->str + start + 7, "=\"", 2) != 0)
231                         break;
232                 if (strncmp (body->str + start + 9, E2K_NS_MAPI_ID, E2K_NS_MAPI_ID_LEN) != 0)
233                         goto next;
234
235                 ns = body->str[start + 6];
236
237                 /* Find properties in this namespace and strip the
238                  * initial '0' from their names to make them valid
239                  * XML NCNames.
240                  */
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);
247
248         next:
249                 p = strchr (body->str + start, '"');
250                 if (!p)
251                         break;
252                 p = strchr (p + 1, '"');
253                 if (!p)
254                         break;
255                 if (p[1] != ' ')
256                         break;
257
258                 start = p + 2 - body->str;
259         }
260
261         ret = body->str;
262         g_string_free (body, FALSE);
263         return ret;
264 }
265
266 /**
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
270  *
271  * Constructs an #E2kResult for each response in @msg and appends them
272  * to @results_array.
273  **/
274 void
275 e2k_results_array_add_from_multistatus (GArray *results_array,
276                                         SoupMessage *msg)
277 {
278         xmlDoc *doc;
279         xmlNode *node, *rnode;
280         E2kResult result;
281         char *body;
282
283         g_return_if_fail (msg->status_code == E2K_HTTP_MULTI_STATUS);
284
285         body = sanitize_bad_multistatus (msg->response.body, msg->response.length);
286         if (body) {
287                 doc = e2k_parse_xml (body, -1);
288                 g_free (body);
289         } else
290                 doc = e2k_parse_xml (msg->response.body, msg->response.length);
291         if (!doc)
292                 return;
293         node = doc->xmlRootNode;
294         if (!node || !E2K_IS_NODE (node, "DAV:", "multistatus")) {
295                 xmlFree (doc);
296                 return;
297         }
298
299         for (node = node->xmlChildrenNode; node; node = node->next) {
300                 if (!E2K_IS_NODE (node, "DAV:", "response") ||
301                     !node->xmlChildrenNode)
302                         continue;
303
304                 memset (&result, 0, sizeof (result));
305                 result.status = E2K_HTTP_OK; /* sometimes omitted if Brief */
306
307                 for (rnode = node->xmlChildrenNode; rnode; rnode = rnode->next) {
308                         if (rnode->type != XML_ELEMENT_NODE)
309                                 continue;
310
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);
318                         else
319                                 prop_parse (rnode, &result);
320                 }
321
322                 if (result.href) {
323                         if (E2K_HTTP_STATUS_IS_SUCCESSFUL (result.status) &&
324                             !result.props)
325                                 result.props = e2k_properties_new ();
326                         g_array_append_val (results_array, result);
327                 } else
328                         e2k_result_clear (&result);
329         }
330
331         xmlFreeDoc (doc);
332 }
333
334 /**
335  * e2k_results_array_free:
336  * @results_array: the array
337  * @free_results: whether or not to also free the contents of the array
338  *
339  * Frees @results_array, and optionally its contents
340  **/
341 void
342 e2k_results_array_free (GArray *results_array, gboolean free_results)
343 {
344         if (free_results) {
345                 e2k_results_free ((E2kResult *)results_array->data,
346                                   results_array->len);
347         }
348         g_array_free (results_array, FALSE);
349 }
350
351
352 /**
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
357  *
358  * Parses @msg and puts the results in *@results and *@nresults.
359  * The caller should free the data with e2k_results_free()
360  **/
361 void
362 e2k_results_from_multistatus (SoupMessage *msg,
363                               E2kResult **results, int *nresults)
364 {
365         GArray *results_array;
366
367         results_array = e2k_results_array_new ();
368         e2k_results_array_add_from_multistatus (results_array, msg);
369
370         *results = (E2kResult *)results_array->data;
371         *nresults = results_array->len;
372         e2k_results_array_free (results_array, FALSE);
373 }
374
375 /**
376  * e2k_results_copy:
377  * @results: a results array returned from e2k_results_from_multistatus()
378  * @nresults: the length of @results
379  *
380  * Performs a deep copy of @results
381  *
382  * Return value: a copy of @results.
383  **/
384 E2kResult *
385 e2k_results_copy (E2kResult *results, int nresults)
386 {
387         GArray *results_array = NULL;
388         E2kResult result, *new_results;
389         int i;
390
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);
396
397                 g_array_append_val (results_array, result);
398         }
399
400         new_results = (E2kResult *) (results_array->data);
401         g_array_free (results_array, FALSE);
402         return new_results;
403 }
404
405 /**
406  * e2k_results_free:
407  * @results: a results array
408  * @nresults: the length of @results
409  *
410  * Frees the data in @results.
411  **/
412 void
413 e2k_results_free (E2kResult *results, int nresults)
414 {
415         int i;
416
417         for (i = 0; i < nresults; i++)
418                 e2k_result_clear (&results[i]);
419         g_free (results);
420 }
421
422
423 /* Iterators */
424 struct E2kResultIter {
425         E2kContext *ctx;
426         E2kOperation *op;
427         E2kHTTPStatus status;
428
429         E2kResult *results;
430         int nresults, next;
431         int first, total;
432         gboolean ascending;
433
434         E2kResultIterFetchFunc fetch_func;
435         E2kResultIterFreeFunc free_func;
436         gpointer user_data;
437 };
438
439 static void
440 iter_fetch (E2kResultIter *iter)
441 {
442         if (iter->nresults) {
443                 if (iter->ascending)
444                         iter->first += iter->nresults;
445                 else
446                         iter->first -= iter->nresults;
447                 e2k_results_free (iter->results, iter->nresults);
448                 iter->nresults = 0;
449         }
450
451         iter->status = iter->fetch_func (iter, iter->ctx, iter->op,
452                                          &iter->results,
453                                          &iter->nresults,
454                                          &iter->first,
455                                          &iter->total,
456                                          iter->user_data);
457         iter->next = 0;
458 }
459
460 /**
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
467  * if not yet known
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
471  *
472  * Creates a object that can be used to return the results of
473  * a Multi-Status query on @ctx.
474  *
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.
480  *
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.
485  *
486  * Return value: the new iterator
487  **/
488 E2kResultIter *
489 e2k_result_iter_new (E2kContext *ctx, E2kOperation *op,
490                      gboolean ascending, int total,
491                      E2kResultIterFetchFunc fetch_func,
492                      E2kResultIterFreeFunc free_func,
493                      gpointer user_data)
494 {
495         E2kResultIter *iter;
496
497         iter = g_new0 (E2kResultIter, 1);
498         iter->ctx = g_object_ref (ctx);
499         iter->op = op;
500         iter->ascending = ascending;
501         iter->total = total;
502         iter->fetch_func = fetch_func;
503         iter->free_func = free_func;
504         iter->user_data = user_data;
505
506         iter_fetch (iter);
507
508         return iter;
509 }
510
511 /**
512  * e2k_result_iter_next:
513  * @iter: an #E2kResultIter
514  *
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
518  * these two cases.)
519  *
520  * Return value: the result, or %NULL
521  **/
522 E2kResult *
523 e2k_result_iter_next (E2kResultIter *iter)
524 {
525         g_return_val_if_fail (iter != NULL, NULL);
526
527         if (iter->nresults == 0)
528                 return NULL;
529
530         if (iter->next >= iter->nresults) {
531                 iter_fetch (iter);
532                 if (iter->nresults == 0)
533                         return NULL;
534                 if (iter->total <= 0)
535                         iter->status = E2K_HTTP_MALFORMED;
536                 if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (iter->status))
537                         return NULL;
538         }
539
540         return iter->ascending ?
541                 &iter->results[iter->next++] :
542                 &iter->results[iter->nresults - ++iter->next];
543 }
544
545 /**
546  * e2k_result_iter_get_index:
547  * @iter: an #E2kResultIter
548  *
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.
552  *
553  * Return value: the index of the current result
554  **/
555 int
556 e2k_result_iter_get_index (E2kResultIter *iter)
557 {
558         g_return_val_if_fail (iter != NULL, -1);
559
560         return iter->ascending ?
561                 iter->first + iter->next - 1 :
562                 iter->first + (iter->nresults - iter->next);
563 }
564
565 /**
566  * e2k_result_iter_get_total:
567  * @iter: an #E2kResultIter
568  *
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
572  * folder).
573  *
574  * Return value: the total number of results expected
575  **/
576 int
577 e2k_result_iter_get_total (E2kResultIter *iter)
578 {
579         g_return_val_if_fail (iter != NULL, -1);
580
581         return iter->total;
582 }
583
584 /**
585  * e2k_result_iter_free:
586  * @iter: an #E2kResultIter
587  *
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.)
591  *
592  * Return value: the final status
593  **/
594 E2kHTTPStatus
595 e2k_result_iter_free (E2kResultIter *iter)
596 {
597         E2kHTTPStatus status;
598
599         g_return_val_if_fail (iter != NULL, E2K_HTTP_MALFORMED);
600
601         status = iter->status;
602         if (iter->nresults)
603                 e2k_results_free (iter->results, iter->nresults);
604         iter->free_func (iter, iter->user_data);
605         g_object_unref (iter->ctx);
606         g_free (iter);
607
608         return status;
609 }