Bug #666757 - [CalDAV] Manage authentication requests even after open
[platform/upstream/evolution-data-server.git] / calendar / backends / caldav / e-cal-backend-caldav.c
1 /*
2  * Evolution calendar - caldav backend
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) version 3.
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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
16  *
17  *
18  * Authors:
19  *              Christian Kellner <gicmo@gnome.org>
20  *
21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22  *
23  */
24
25 #include <config.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <gconf/gconf-client.h>
29 #include <glib/gstdio.h>
30 #include <glib/gi18n-lib.h>
31 #include <libedataserver/e-data-server-util.h>
32 #include <libedataserver/e-xml-hash-utils.h>
33 #include <libedataserver/e-proxy.h>
34 #include <libecal/e-cal-recur.h>
35 #include <libecal/e-cal-util.h>
36 #include <libecal/e-cal-time-util.h>
37 #include <libedata-cal/e-cal-backend-cache.h>
38 #include <libedata-cal/e-cal-backend-file-store.h>
39 #include <libedata-cal/e-cal-backend-util.h>
40 #include <libedata-cal/e-cal-backend-sexp.h>
41
42 /* LibXML2 includes */
43 #include <libxml/parser.h>
44 #include <libxml/tree.h>
45 #include <libxml/xpath.h>
46 #include <libxml/xpathInternals.h>
47
48 /* LibSoup includes */
49 #include <libsoup/soup.h>
50
51 #include "e-cal-backend-caldav.h"
52
53 #define d(x)
54
55 #define CALDAV_CTAG_KEY "CALDAV_CTAG"
56 #define CALDAV_MAX_MULTIGET_AMOUNT 100 /* what's the maximum count of items to fetch within a multiget request */
57 #define LOCAL_PREFIX "file://"
58
59 /* in seconds */
60 #define DEFAULT_REFRESH_TIME 60
61
62 #define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
63 #define EDC_ERROR_EX(_code, _msg) e_data_cal_create_error (_code, _msg)
64
65 typedef enum {
66
67         SLAVE_SHOULD_SLEEP,
68         SLAVE_SHOULD_WORK,
69         SLAVE_SHOULD_DIE
70
71 } SlaveCommand;
72
73 /* Private part of the ECalBackendHttp structure */
74 struct _ECalBackendCalDAVPrivate {
75
76         /* The local disk cache */
77         ECalBackendStore *store;
78
79         /* should we sync for offline mode? */
80         gboolean do_offline;
81
82         /* TRUE after caldav_open */
83         gboolean loaded;
84         /* TRUE when server reachable */
85         gboolean opened;
86
87         /* lock to indicate a busy state */
88         GMutex *busy_lock;
89
90         /* cond to synch threads */
91         GCond *cond;
92
93         /* cond to know the slave gone */
94         GCond *slave_gone_cond;
95
96         /* BG synch thread */
97         const GThread *synch_slave; /* just for a reference, whether thread exists */
98         SlaveCommand slave_cmd;
99         gboolean slave_busy; /* whether is slave working */
100         GTimeVal refresh_time;
101
102         /* The main soup session  */
103         SoupSession *session;
104         EProxy *proxy;
105
106         /* well, guess what */
107         gboolean read_only;
108
109         /* clandar uri */
110         gchar *uri;
111
112         /* Authentication info */
113         ECredentials *credentials;
114         gboolean auth_required;
115
116         /* object cleanup */
117         gboolean disposed;
118
119         /* support for 'getctag' extension */
120         gboolean ctag_supported;
121         gchar *ctag_to_store;
122
123         /* TRUE when 'calendar-schedule' supported on the server */
124         gboolean calendar_schedule;
125         /* with 'calendar-schedule' supported, here's an outbox url
126          * for queries of free/busy information */
127         gchar *schedule_outbox_url;
128
129         /* "Temporary hack" to indicate it's talking to a google calendar.
130          * The proper solution should be to subclass whole backend and change only
131          * necessary parts in it, but this will give us more freedom, as also direct
132          * caldav calendars can profit from this. */
133         gboolean is_google;
134 };
135
136 /* ************************************************************************* */
137 /* Debugging */
138
139 #define DEBUG_MESSAGE "message"
140 #define DEBUG_MESSAGE_HEADER "message:header"
141 #define DEBUG_MESSAGE_BODY "message:body"
142 #define DEBUG_SERVER_ITEMS "items"
143 #define DEBUG_ATTACHMENTS "attachments"
144
145 static void convert_to_inline_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
146 static void convert_to_url_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
147 static void remove_cached_attachment (ECalBackendCalDAV *cbdav, const gchar *uid);
148
149 static gboolean caldav_debug_all = FALSE;
150 static GHashTable *caldav_debug_table = NULL;
151
152 static void
153 add_debug_key (const gchar *start,
154                const gchar *end)
155 {
156         gchar *debug_key;
157         gchar *debug_value;
158
159         if (start == end) {
160                 return;
161         }
162
163         debug_key = debug_value = g_strndup (start, end - start);
164
165         debug_key = g_strchug (debug_key);
166         debug_key = g_strchomp (debug_key);
167
168         if (strlen (debug_key) == 0) {
169                 g_free (debug_value);
170                 return;
171         }
172
173         g_hash_table_insert (caldav_debug_table,
174                              debug_key,
175                              debug_value);
176
177         d(g_debug ("Adding %s to enabled debugging keys", debug_key));
178 }
179
180 static gpointer
181 caldav_debug_init_once (gpointer data)
182 {
183         const gchar *dbg;
184
185         dbg = g_getenv ("CALDAV_DEBUG");
186
187         if (dbg) {
188                 const gchar *ptr;
189
190                 d(g_debug ("Got debug env variable: [%s]", dbg));
191
192                 caldav_debug_table = g_hash_table_new (g_str_hash,
193                                                        g_str_equal);
194
195                 ptr = dbg;
196
197                 while (*ptr != '\0') {
198                         if (*ptr == ',' || *ptr == ':') {
199
200                                 add_debug_key (dbg, ptr);
201
202                                 if (*ptr == ',') {
203                                         dbg = ptr + 1;
204                                 }
205                         }
206
207                         ptr++;
208                 }
209
210                 if (ptr - dbg > 0) {
211                         add_debug_key (dbg, ptr);
212                 }
213
214                 if (g_hash_table_lookup (caldav_debug_table, "all")) {
215                         caldav_debug_all = TRUE;
216                         g_hash_table_destroy (caldav_debug_table);
217                         caldav_debug_table = NULL;
218                 }
219         }
220
221         return NULL;
222 }
223
224 static void
225 caldav_debug_init (void)
226 {
227         static GOnce debug_once = G_ONCE_INIT;
228
229         g_once (&debug_once,
230                 caldav_debug_init_once,
231                 NULL);
232 }
233
234 static gboolean
235 caldav_debug_show (const gchar *component)
236 {
237         if (G_UNLIKELY (caldav_debug_all)) {
238                 return TRUE;
239         } else if (G_UNLIKELY (caldav_debug_table != NULL) &&
240                    g_hash_table_lookup (caldav_debug_table, component)) {
241                 return TRUE;
242         }
243
244         return FALSE;
245 }
246
247 #define DEBUG_MAX_BODY_SIZE (100 * 1024 * 1024)
248
249 static void
250 caldav_debug_setup (SoupSession *session)
251 {
252         SoupLogger *logger;
253         SoupLoggerLogLevel level;
254
255         if (caldav_debug_show (DEBUG_MESSAGE_BODY))
256                 level = SOUP_LOGGER_LOG_BODY;
257         else if (caldav_debug_show (DEBUG_MESSAGE_HEADER))
258                 level = SOUP_LOGGER_LOG_HEADERS;
259         else
260                 level = SOUP_LOGGER_LOG_MINIMAL;
261
262         logger = soup_logger_new (level, DEBUG_MAX_BODY_SIZE);
263         soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
264         g_object_unref (logger);
265 }
266
267 /* TODO Do not replicate this in every backend */
268 static icaltimezone *
269 resolve_tzid (const gchar *tzid,
270               gpointer user_data)
271 {
272         icaltimezone *zone;
273
274         zone = (!strcmp (tzid, "UTC"))
275                 ? icaltimezone_get_utc_timezone ()
276                 : icaltimezone_get_builtin_timezone_from_tzid (tzid);
277
278         if (!zone)
279                 zone = e_cal_backend_internal_get_timezone (E_CAL_BACKEND (user_data), tzid);
280
281         return zone;
282 }
283
284 static gboolean
285 put_component_to_store (ECalBackendCalDAV *cbdav,
286                         ECalComponent *comp)
287 {
288         time_t time_start, time_end;
289         ECalBackendCalDAVPrivate *priv;
290
291         priv = cbdav->priv;
292
293         e_cal_util_get_component_occur_times (comp, &time_start, &time_end,
294                                    resolve_tzid, cbdav,  icaltimezone_get_utc_timezone (),
295                                    e_cal_backend_get_kind (E_CAL_BACKEND (cbdav)));
296
297         return e_cal_backend_store_put_component_with_time_range (priv->store, comp, time_start, time_end);
298 }
299
300 static ECalBackendSyncClass *parent_class = NULL;
301
302 static icaltimezone *caldav_internal_get_timezone (ECalBackend *backend, const gchar *tzid);
303 static void caldav_source_changed_cb (ESource *source, ECalBackendCalDAV *cbdav);
304
305 static gboolean remove_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid);
306 static gboolean put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const gchar *href, const gchar *etag);
307
308 /* ************************************************************************* */
309 /* Misc. utility functions */
310 #define X_E_CALDAV "X-EVOLUTION-CALDAV-"
311 #define X_E_CALDAV_ATTACHMENT_NAME X_E_CALDAV "ATTACHMENT-NAME"
312
313 static void
314 icomp_x_prop_set (icalcomponent *comp,
315                   const gchar *key,
316                   const gchar *value)
317 {
318         icalproperty *xprop;
319
320         /* Find the old one first */
321         xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
322
323         while (xprop) {
324                 const gchar *str = icalproperty_get_x_name (xprop);
325
326                 if (!strcmp (str, key)) {
327                         if (value) {
328                                 icalproperty_set_value_from_string (xprop, value, "NO");
329                         } else {
330                                 icalcomponent_remove_property (comp, xprop);
331                                 icalproperty_free (xprop);
332                         }
333                         break;
334                 }
335
336                 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
337         }
338
339         if (!xprop && value) {
340                 xprop = icalproperty_new_x (value);
341                 icalproperty_set_x_name (xprop, key);
342                 icalcomponent_add_property (comp, xprop);
343         }
344 }
345
346 static gchar *
347 icomp_x_prop_get (icalcomponent *comp,
348                   const gchar *key)
349 {
350         icalproperty *xprop;
351
352         /* Find the old one first */
353         xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
354
355         while (xprop) {
356                 const gchar *str = icalproperty_get_x_name (xprop);
357
358                 if (!strcmp (str, key)) {
359                         break;
360                 }
361
362                 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
363         }
364
365         if (xprop) {
366                 return icalproperty_get_value_as_string_r (xprop);
367         }
368
369         return NULL;
370 }
371
372 /* passing NULL as 'href' removes the property */
373 static void
374 ecalcomp_set_href (ECalComponent *comp,
375                    const gchar *href)
376 {
377         icalcomponent *icomp;
378
379         icomp = e_cal_component_get_icalcomponent (comp);
380         g_return_if_fail (icomp != NULL);
381
382         icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
383 }
384
385 static gchar *
386 ecalcomp_get_href (ECalComponent *comp)
387 {
388         icalcomponent *icomp;
389         gchar          *str;
390
391         str = NULL;
392         icomp = e_cal_component_get_icalcomponent (comp);
393         g_return_val_if_fail (icomp != NULL, NULL);
394
395         str =  icomp_x_prop_get (icomp, X_E_CALDAV "HREF");
396
397         return str;
398 }
399
400 /* passing NULL as 'etag' removes the property */
401 static void
402 ecalcomp_set_etag (ECalComponent *comp,
403                    const gchar *etag)
404 {
405         icalcomponent *icomp;
406
407         icomp = e_cal_component_get_icalcomponent (comp);
408         g_return_if_fail (icomp != NULL);
409
410         icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", etag);
411 }
412
413 static gchar *
414 ecalcomp_get_etag (ECalComponent *comp)
415 {
416         icalcomponent *icomp;
417         gchar          *str;
418
419         str = NULL;
420         icomp = e_cal_component_get_icalcomponent (comp);
421         g_return_val_if_fail (icomp != NULL, NULL);
422
423         str =  icomp_x_prop_get (icomp, X_E_CALDAV "ETAG");
424
425         return str;
426 }
427
428 /*typedef enum {
429  *
430         / * object is in synch,
431          * now isnt that ironic? :) * /
432         ECALCOMP_IN_SYNCH = 0,
433  *
434         / * local changes * /
435         ECALCOMP_LOCALLY_CREATED,
436         ECALCOMP_LOCALLY_DELETED,
437         ECALCOMP_LOCALLY_MODIFIED
438  *
439 } ECalCompSyncState;
440  *
441 / * oos = out of synch * /
442 static void
443 ecalcomp_set_synch_state (ECalComponent *comp,
444  *                        ECalCompSyncState state)
445 {
446         icalcomponent *icomp;
447         gchar          *state_string;
448  *
449         icomp = e_cal_component_get_icalcomponent (comp);
450  *
451         state_string = g_strdup_printf ("%d", state);
452  *
453         icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", state_string);
454  *
455         g_free (state_string);
456 }*/
457
458 static gchar *
459 ecalcomp_gen_href (ECalComponent *comp)
460 {
461         gchar *href, *uid, *tmp;
462         icalcomponent *icomp;
463
464         icomp = e_cal_component_get_icalcomponent (comp);
465         g_return_val_if_fail (icomp != NULL, NULL);
466
467         uid = g_strdup (icalcomponent_get_uid (icomp));
468         if (!uid || !*uid) {
469                 g_free (uid);
470                 uid = e_cal_component_gen_uid ();
471
472                 tmp = uid ? strchr (uid, '@') : NULL;
473                 if (tmp)
474                         *tmp = '\0';
475
476                 tmp = NULL;
477         } else
478                 tmp = isodate_from_time_t (time (NULL));
479
480         /* quite long, but ensures uniqueness quite well, without using UUIDs */
481         href = g_strconcat (uid ? uid : "no-uid", tmp ? "-" : "", tmp ? tmp : "", ".ics", NULL);
482
483         g_free (tmp);
484         g_free (uid);
485
486         icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
487
488         return g_strdelimit (href, " /'\"`&();|<>$%{}!\\:*?#@", '_');
489 }
490
491 /* ensure etag is quoted (to workaround potential server bugs) */
492 static gchar *
493 quote_etag (const gchar *etag)
494 {
495         gchar *ret;
496
497         if (etag && (strlen (etag) < 2 || etag[strlen (etag) - 1] != '\"')) {
498                 ret = g_strdup_printf ("\"%s\"", etag);
499         } else {
500                 ret = g_strdup (etag);
501         }
502
503         return ret;
504 }
505
506 /* ************************************************************************* */
507
508 static gboolean
509 status_code_to_result (SoupMessage *message,
510                        ECalBackendCalDAVPrivate *priv,
511                        GError **perror)
512 {
513         g_return_val_if_fail (message != NULL, FALSE);
514
515         if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
516                 return TRUE;
517         }
518
519         switch (message->status_code) {
520         case SOUP_STATUS_CANT_CONNECT:
521         case SOUP_STATUS_CANT_CONNECT_PROXY:
522                 g_propagate_error (perror,
523                         e_data_cal_create_error_fmt (
524                                 OtherError,
525                                 _("Server is unreachable (%s)"),
526                                         message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
527                                         (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
528                 if (priv) {
529                         priv->opened = FALSE;
530                         priv->read_only = TRUE;
531                 }
532                 break;
533         case 404:
534                 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
535                 break;
536
537         case 403:
538                 g_propagate_error (perror, EDC_ERROR (AuthenticationFailed));
539                 break;
540
541         case 401:
542                 if (priv && priv->auth_required)
543                         g_propagate_error (perror, EDC_ERROR (AuthenticationFailed));
544                 else
545                         g_propagate_error (perror, EDC_ERROR (AuthenticationRequired));
546                 break;
547
548         default:
549                 d(g_debug ("CalDAV:%s: Unhandled status code %d\n", G_STRFUNC, status_code));
550                 g_propagate_error (perror,
551                         e_data_cal_create_error_fmt (
552                                 OtherError,
553                                 _("Unexpected HTTP status code %d returned (%s)"),
554                                         message->status_code,
555                                         message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
556                                         (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
557                 break;
558         }
559
560         return FALSE;
561 }
562
563 /* !TS, call with lock held */
564 static gboolean
565 check_state (ECalBackendCalDAV *cbdav,
566              gboolean *online,
567              GError **perror)
568 {
569         ECalBackendCalDAVPrivate *priv;
570
571         priv = cbdav->priv;
572
573         *online = FALSE;
574
575         if (!priv->loaded) {
576                 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Not loaded"));
577                 return FALSE;
578         }
579
580         if (!e_backend_get_online (E_BACKEND (cbdav))) {
581
582                 if (!priv->do_offline) {
583                         g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
584                         return FALSE;
585                 }
586
587         } else {
588                 *online = TRUE;
589         }
590
591         return TRUE;
592 }
593
594 /* ************************************************************************* */
595 /* XML Parsing code */
596
597 static xmlXPathObjectPtr
598 xpath_eval (xmlXPathContextPtr ctx,
599             const gchar *format,
600             ...)
601 {
602         xmlXPathObjectPtr  result;
603         va_list            args;
604         gchar              *expr;
605
606         if (ctx == NULL) {
607                 return NULL;
608         }
609
610         va_start (args, format);
611         expr = g_strdup_vprintf (format, args);
612         va_end (args);
613
614         result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
615         g_free (expr);
616
617         if (result == NULL) {
618                 return NULL;
619         }
620
621         if (result->type == XPATH_NODESET &&
622             xmlXPathNodeSetIsEmpty (result->nodesetval)) {
623                 xmlXPathFreeObject (result);
624                 return NULL;
625         }
626
627         return result;
628 }
629
630 #if 0
631 static gboolean
632 parse_status_node (xmlNodePtr node,
633                    guint *status_code)
634 {
635         xmlChar  *content;
636         gboolean  res;
637
638         content = xmlNodeGetContent (node);
639
640         res = soup_headers_parse_status_line ((gchar *) content,
641                                               NULL,
642                                               status_code,
643                                               NULL);
644         xmlFree (content);
645
646         return res;
647 }
648 #endif
649
650 static gchar *
651 xp_object_get_string (xmlXPathObjectPtr result)
652 {
653         gchar *ret = NULL;
654
655         if (result == NULL)
656                 return ret;
657
658         if (result->type == XPATH_STRING) {
659                 ret = g_strdup ((gchar *) result->stringval);
660         }
661
662         xmlXPathFreeObject (result);
663         return ret;
664 }
665
666 /* like get_string but will quote the etag if necessary */
667 static gchar *
668 xp_object_get_etag (xmlXPathObjectPtr result)
669 {
670         gchar *ret = NULL;
671         gchar *str;
672
673         if (result == NULL)
674                 return ret;
675
676         if (result->type == XPATH_STRING) {
677                 str = (gchar *) result->stringval;
678
679                 ret = quote_etag (str);
680         }
681
682         xmlXPathFreeObject (result);
683         return ret;
684 }
685
686 static guint
687 xp_object_get_status (xmlXPathObjectPtr result)
688 {
689         gboolean res;
690         guint    ret = 0;
691
692         if (result == NULL)
693                 return ret;
694
695         if (result->type == XPATH_STRING) {
696                 res = soup_headers_parse_status_line ((gchar *) result->stringval,
697                                                         NULL,
698                                                         &ret,
699                                                         NULL);
700
701                 if (!res) {
702                         ret = 0;
703                 }
704         }
705
706         xmlXPathFreeObject (result);
707         return ret;
708 }
709
710 #if 0
711 static gint
712 xp_object_get_number (xmlXPathObjectPtr result)
713 {
714         gint ret = -1;
715
716         if (result == NULL)
717                 return ret;
718
719         if (result->type == XPATH_STRING) {
720                 ret = result->boolval;
721         }
722
723         xmlXPathFreeObject (result);
724         return ret;
725 }
726 #endif
727
728 /*** *** *** *** *** *** */
729 #define XPATH_HREF "string(/D:multistatus/D:response[%d]/D:href)"
730 #define XPATH_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:status)"
731 #define XPATH_GETETAG_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag/../../D:status)"
732 #define XPATH_GETETAG "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag)"
733 #define XPATH_CALENDAR_DATA "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/C:calendar-data)"
734 #define XPATH_GETCTAG_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag/../../D:status)"
735 #define XPATH_GETCTAG "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag)"
736 #define XPATH_OWNER_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href/../../../D:status)"
737 #define XPATH_OWNER "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href)"
738 #define XPATH_SCHEDULE_OUTBOX_URL_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href/../../../D:status)"
739 #define XPATH_SCHEDULE_OUTBOX_URL "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href)"
740
741 typedef struct _CalDAVObject CalDAVObject;
742
743 struct _CalDAVObject {
744
745         gchar *href;
746         gchar *etag;
747
748         guint status;
749
750         gchar *cdata;
751 };
752
753 static void
754 caldav_object_free (CalDAVObject *object,
755                     gboolean free_object_itself)
756 {
757         g_free (object->href);
758         g_free (object->etag);
759         g_free (object->cdata);
760
761         if (free_object_itself) {
762                 g_free (object);
763         }
764 }
765
766 static gboolean
767 parse_report_response (SoupMessage *soup_message,
768                        CalDAVObject **objs,
769                        gint *len)
770 {
771         xmlXPathContextPtr xpctx;
772         xmlXPathObjectPtr  result;
773         xmlDocPtr          doc;
774         gint                i, n;
775         gboolean           res;
776
777         g_return_val_if_fail (soup_message != NULL, FALSE);
778         g_return_val_if_fail (objs != NULL || len != NULL, FALSE);
779
780         res = TRUE;
781         doc = xmlReadMemory (soup_message->response_body->data,
782                              soup_message->response_body->length,
783                              "response.xml",
784                              NULL,
785                              0);
786
787         if (doc == NULL) {
788                 return FALSE;
789         }
790
791         xpctx = xmlXPathNewContext (doc);
792
793         xmlXPathRegisterNs (xpctx, (xmlChar *) "D",
794                             (xmlChar *) "DAV:");
795
796         xmlXPathRegisterNs (xpctx, (xmlChar *) "C",
797                             (xmlChar *) "urn:ietf:params:xml:ns:caldav");
798
799         result = xpath_eval (xpctx, "/D:multistatus/D:response");
800
801         if (result == NULL || result->type != XPATH_NODESET) {
802                 *len = 0;
803                 res = FALSE;
804                 goto out;
805         }
806
807         n = xmlXPathNodeSetGetLength (result->nodesetval);
808         *len = n;
809
810         *objs = g_new0 (CalDAVObject, n);
811
812         for (i = 0; i < n; i++) {
813                 CalDAVObject *object;
814                 xmlXPathObjectPtr xpres;
815
816                 object = *objs + i;
817                 /* see if we got a status child in the response element */
818
819                 xpres = xpath_eval (xpctx, XPATH_HREF, i + 1);
820                 /* use full path from a href, to let calendar-multiget work properly */
821                 object->href = xp_object_get_string (xpres);
822
823                 xpres = xpath_eval (xpctx,XPATH_STATUS , i + 1);
824                 object->status = xp_object_get_status (xpres);
825
826                 if (object->status && object->status != 200) {
827                         continue;
828                 }
829
830                 xpres = xpath_eval (xpctx, XPATH_GETETAG_STATUS, i + 1);
831                 object->status = xp_object_get_status (xpres);
832
833                 if (object->status != 200) {
834                         continue;
835                 }
836
837                 xpres = xpath_eval (xpctx, XPATH_GETETAG, i + 1);
838                 object->etag = xp_object_get_etag (xpres);
839
840                 xpres = xpath_eval (xpctx, XPATH_CALENDAR_DATA, i + 1);
841                 object->cdata = xp_object_get_string (xpres);
842         }
843
844 out:
845         if (result != NULL)
846                 xmlXPathFreeObject (result);
847         xmlXPathFreeContext (xpctx);
848         xmlFreeDoc (doc);
849         return res;
850 }
851
852 /* returns whether was able to read the xpath_value from the server's response; *value contains the result */
853 static gboolean
854 parse_propfind_response (SoupMessage *message,
855                          const gchar *xpath_status,
856                          const gchar *xpath_value,
857                          gchar **value)
858 {
859         xmlXPathContextPtr xpctx;
860         xmlDocPtr          doc;
861         gboolean           res = FALSE;
862
863         g_return_val_if_fail (message != NULL, FALSE);
864         g_return_val_if_fail (value != NULL, FALSE);
865
866         doc = xmlReadMemory (message->response_body->data,
867                              message->response_body->length,
868                              "response.xml",
869                              NULL,
870                              0);
871
872         if (doc == NULL) {
873                 return FALSE;
874         }
875
876         xpctx = xmlXPathNewContext (doc);
877         xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
878         xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
879         xmlXPathRegisterNs (xpctx, (xmlChar *) "CS", (xmlChar *) "http://calendarserver.org/ns/");
880
881         if (xpath_status == NULL || xp_object_get_status (xpath_eval (xpctx, xpath_status)) == 200) {
882                 gchar *txt = xp_object_get_string (xpath_eval (xpctx, xpath_value));
883
884                 if (txt && *txt) {
885                         gint len = strlen (txt);
886
887                         if (*txt == '\"' && len > 2 && txt [len - 1] == '\"') {
888                                 /* dequote */
889                                 *value = g_strndup (txt + 1, len - 2);
890                         } else {
891                                 *value = txt;
892                                 txt = NULL;
893                         }
894
895                         res = (*value) != NULL;
896                 }
897
898                 g_free (txt);
899         }
900
901         xmlXPathFreeContext (xpctx);
902         xmlFreeDoc (doc);
903
904         return res;
905 }
906
907 /* ************************************************************************* */
908 /* Authentication helpers for libsoup */
909
910 static void
911 soup_authenticate (SoupSession *session,
912                    SoupMessage *msg,
913                    SoupAuth *auth,
914                    gboolean retrying,
915                    gpointer data)
916 {
917         ECalBackendCalDAVPrivate *priv;
918         ECalBackendCalDAV        *cbdav;
919
920         cbdav = E_CAL_BACKEND_CALDAV (data);
921         priv = cbdav->priv;
922
923         /* do not send same password twice, but keep it for later use */
924         if (!retrying && priv->credentials && e_credentials_has_key (priv->credentials, E_CREDENTIALS_KEY_USERNAME)) {
925                 soup_auth_authenticate (auth, e_credentials_peek (priv->credentials, E_CREDENTIALS_KEY_USERNAME), e_credentials_peek (priv->credentials, E_CREDENTIALS_KEY_PASSWORD));
926                 e_credentials_clear_peek (priv->credentials);
927         }
928 }
929
930 /* ************************************************************************* */
931 /* direct CalDAV server access functions */
932
933 static void
934 redirect_handler (SoupMessage *msg,
935                   gpointer user_data)
936 {
937         if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
938                 SoupSession *soup_session = user_data;
939                 SoupURI *new_uri;
940                 const gchar *new_loc;
941
942                 new_loc = soup_message_headers_get (msg->response_headers, "Location");
943                 if (!new_loc)
944                         return;
945
946                 new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
947                 if (!new_uri) {
948                         soup_message_set_status_full (msg,
949                                                       SOUP_STATUS_MALFORMED,
950                                                       "Invalid Redirect URL");
951                         return;
952                 }
953
954                 if (new_uri->host && g_str_has_suffix (new_uri->host, "yahoo.com")) {
955                         /* yahoo! returns port 7070, which is unreachable;
956                          * it also requires https being used (below call resets port as well) */
957                         soup_uri_set_scheme (new_uri, SOUP_URI_SCHEME_HTTPS);
958                 }
959
960                 soup_message_set_uri (msg, new_uri);
961                 soup_session_requeue_message (soup_session, msg);
962
963                 soup_uri_free (new_uri);
964         }
965 }
966
967 static void
968 send_and_handle_redirection (SoupSession *soup_session,
969                              SoupMessage *msg,
970                              gchar **new_location)
971 {
972         gchar *old_uri = NULL;
973
974         g_return_if_fail (msg != NULL);
975
976         if (new_location)
977                 old_uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
978
979         soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
980         soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), soup_session);
981         soup_message_headers_append (msg->request_headers, "Connection", "close");
982         soup_session_send_message (soup_session, msg);
983
984         if (new_location) {
985                 gchar *new_loc = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
986
987                 if (new_loc && old_uri && !g_str_equal (new_loc, old_uri))
988                         *new_location = new_loc;
989                 else
990                         g_free (new_loc);
991         }
992
993         g_free (old_uri);
994 }
995
996 static gchar *
997 caldav_generate_uri (ECalBackendCalDAV *cbdav,
998                      const gchar *target)
999 {
1000         ECalBackendCalDAVPrivate  *priv;
1001         gchar *uri;
1002         const gchar *slash;
1003
1004         priv = cbdav->priv;
1005
1006         slash = strrchr (target, '/');
1007         if (slash)
1008                 target = slash + 1;
1009
1010         /* priv->uri *have* trailing slash already */
1011         uri = g_strconcat (priv->uri, target, NULL);
1012
1013         return uri;
1014 }
1015
1016 static gboolean
1017 caldav_server_open_calendar (ECalBackendCalDAV *cbdav,
1018                              gboolean *server_unreachable,
1019                              GError **perror)
1020 {
1021         ECalBackendCalDAVPrivate  *priv;
1022         SoupMessage               *message;
1023         const gchar                *header;
1024         gboolean                   calendar_access;
1025         gboolean                   put_allowed;
1026         gboolean                   delete_allowed;
1027
1028         g_return_val_if_fail (cbdav != NULL, FALSE);
1029         g_return_val_if_fail (server_unreachable != NULL, FALSE);
1030
1031         priv = cbdav->priv;
1032
1033         /* FIXME: setup text_uri */
1034
1035         message = soup_message_new (SOUP_METHOD_OPTIONS, priv->uri);
1036         if (message == NULL) {
1037                 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1038                 return FALSE;
1039         }
1040         soup_message_headers_append (message->request_headers,
1041                                      "User-Agent", "Evolution/" VERSION);
1042
1043         send_and_handle_redirection (priv->session, message, NULL);
1044
1045         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1046                 switch (message->status_code) {
1047                 case SOUP_STATUS_CANT_CONNECT:
1048                 case SOUP_STATUS_CANT_CONNECT_PROXY:
1049                         *server_unreachable = TRUE;
1050                         break;
1051                 }
1052
1053                 status_code_to_result (message, priv, perror);
1054
1055                 g_object_unref (message);
1056                 return FALSE;
1057         }
1058
1059         /* parse the dav header, we are intreseted in the
1060          * calendar-access bit only at the moment */
1061         header = soup_message_headers_get (message->response_headers, "DAV");
1062         if (header) {
1063                 calendar_access = soup_header_contains (header, "calendar-access");
1064                 priv->calendar_schedule = soup_header_contains (header, "calendar-schedule");
1065         } else {
1066                 calendar_access = FALSE;
1067                 priv->calendar_schedule = FALSE;
1068         }
1069
1070         /* parse the Allow header and look for PUT, DELETE at the
1071          * moment (maybe we should check more here, for REPORT eg) */
1072         header = soup_message_headers_get (message->response_headers, "Allow");
1073         if (header) {
1074                 put_allowed = soup_header_contains (header, "PUT");
1075                 delete_allowed = soup_header_contains (header, "DELETE");
1076         } else
1077                 put_allowed = delete_allowed = FALSE;
1078
1079         g_object_unref (message);
1080
1081         if (calendar_access) {
1082                 priv->read_only = !(put_allowed && delete_allowed);
1083                 return TRUE;
1084         }
1085
1086         g_propagate_error (perror, EDC_ERROR (PermissionDenied));
1087         return FALSE;
1088 }
1089
1090 static void
1091 caldav_notify_auth_required (ECalBackendCalDAV *cbdav)
1092 {
1093         ECredentials *credentials;
1094         guint prompt_flags;
1095         gchar *prompt_flags_str;
1096
1097         g_return_if_fail (cbdav != NULL);
1098         g_return_if_fail (cbdav->priv != NULL);
1099
1100         cbdav->priv->opened = FALSE;
1101         cbdav->priv->slave_cmd = SLAVE_SHOULD_SLEEP;
1102
1103         if (!e_backend_get_online (E_BACKEND (cbdav)))
1104                 return;
1105
1106         if (cbdav->priv->credentials)
1107                 credentials = e_credentials_new_clone (cbdav->priv->credentials);
1108         else
1109                 credentials = e_credentials_new ();
1110         prompt_flags = E_CREDENTIALS_PROMPT_FLAG_REMEMBER_FOREVER
1111                      | E_CREDENTIALS_PROMPT_FLAG_SECRET
1112                      | E_CREDENTIALS_PROMPT_FLAG_ONLINE
1113                      | E_CREDENTIALS_PROMPT_FLAG_REPROMPT;
1114
1115         prompt_flags_str = e_credentials_util_prompt_flags_to_string (prompt_flags);
1116         e_credentials_set (credentials, E_CREDENTIALS_KEY_PROMPT_FLAGS, prompt_flags_str);
1117         g_free (prompt_flags_str);
1118
1119         e_cal_backend_notify_auth_required (E_CAL_BACKEND (cbdav), TRUE, credentials);
1120
1121         e_credentials_free (credentials);
1122 }
1123
1124 /* Returns whether calendar changed on the server. This works only when server
1125  * supports 'getctag' extension. */
1126 static gboolean
1127 check_calendar_changed_on_server (ECalBackendCalDAV *cbdav)
1128 {
1129         ECalBackendCalDAVPrivate *priv;
1130         xmlOutputBufferPtr        buf;
1131         SoupMessage              *message;
1132         xmlDocPtr                 doc;
1133         xmlNodePtr                root, node;
1134         xmlNsPtr                  ns, nsdav;
1135         gboolean                  result = TRUE;
1136
1137         g_return_val_if_fail (cbdav != NULL, TRUE);
1138
1139         priv   = cbdav->priv;
1140
1141         /* no support for 'getctag', thus update cache */
1142         if (!priv->ctag_supported)
1143                 return TRUE;
1144
1145         /* Prepare the soup message */
1146         message = soup_message_new ("PROPFIND", priv->uri);
1147         if (message == NULL)
1148                 return FALSE;
1149
1150         doc = xmlNewDoc ((xmlChar *) "1.0");
1151         root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1152         xmlDocSetRootElement (doc, root);
1153         nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1154         ns = xmlNewNs (root, (xmlChar *) "http://calendarserver.org/ns/", (xmlChar *) "CS");
1155
1156         node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1157         node = xmlNewTextChild (node, nsdav, (xmlChar *) "getctag", NULL);
1158         xmlSetNs (node, ns);
1159
1160         buf = xmlAllocOutputBuffer (NULL);
1161         xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1162         xmlOutputBufferFlush (buf);
1163
1164         soup_message_headers_append (message->request_headers,
1165                                      "User-Agent", "Evolution/" VERSION);
1166         soup_message_headers_append (message->request_headers,
1167                                      "Depth", "0");
1168
1169         soup_message_set_request (message,
1170                                   "application/xml",
1171                                   SOUP_MEMORY_COPY,
1172                                   (gchar *) buf->buffer->content,
1173                                   buf->buffer->use);
1174
1175         /* Send the request now */
1176         send_and_handle_redirection (priv->session, message, NULL);
1177
1178         /* Clean up the memory */
1179         xmlOutputBufferClose (buf);
1180         xmlFreeDoc (doc);
1181
1182         /* Check the result */
1183         if (message->status_code == 401) {
1184                 caldav_notify_auth_required (cbdav);
1185         } else if (message->status_code != 207) {
1186                 /* does not support it, but report calendar changed to update cache */
1187                 priv->ctag_supported = FALSE;
1188         } else {
1189                 gchar *ctag = NULL;
1190
1191                 if (parse_propfind_response (message, XPATH_GETCTAG_STATUS, XPATH_GETCTAG, &ctag)) {
1192                         const gchar *my_ctag;
1193
1194                         my_ctag = e_cal_backend_store_get_key_value (priv->store, CALDAV_CTAG_KEY);
1195
1196                         if (ctag && my_ctag && g_str_equal (ctag, my_ctag)) {
1197                                 /* ctag is same, no change in the calendar */
1198                                 result = FALSE;
1199                         } else {
1200                                 /* do not store ctag now, do it rather after complete sync */
1201                                 g_free (priv->ctag_to_store);
1202                                 priv->ctag_to_store = ctag;
1203                                 ctag = NULL;
1204                         }
1205
1206                         g_free (ctag);
1207                 } else {
1208                         priv->ctag_supported = FALSE;
1209                 }
1210         }
1211
1212         g_object_unref (message);
1213
1214         return result;
1215 }
1216
1217 /* only_hrefs is a list of requested objects to fetch; it has precedence from
1218  * start_time/end_time, which are used only when both positive.
1219  * Times are supposed to be in UTC, if set.
1220  */
1221 static gboolean
1222 caldav_server_list_objects (ECalBackendCalDAV *cbdav,
1223                             CalDAVObject **objs,
1224                             gint *len,
1225                             GSList *only_hrefs,
1226                             time_t start_time,
1227                             time_t end_time)
1228 {
1229         ECalBackendCalDAVPrivate *priv;
1230         xmlOutputBufferPtr   buf;
1231         SoupMessage         *message;
1232         xmlNodePtr           node;
1233         xmlNodePtr           sn;
1234         xmlNodePtr           root;
1235         xmlDocPtr            doc;
1236         xmlNsPtr             nsdav;
1237         xmlNsPtr             nscd;
1238         gboolean             result;
1239
1240         priv = cbdav->priv;
1241         /* Allocate the soup message */
1242         message = soup_message_new ("REPORT", priv->uri);
1243         if (message == NULL)
1244                 return FALSE;
1245
1246         /* Maybe we should just do a g_strdup_printf here? */
1247         /* Prepare request body */
1248         doc = xmlNewDoc ((xmlChar *) "1.0");
1249         if (!only_hrefs)
1250                 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-query", NULL);
1251         else
1252                 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-multiget", NULL);
1253         nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1254         xmlSetNs (root, nscd);
1255         xmlDocSetRootElement (doc, root);
1256
1257         /* Add webdav tags */
1258         nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1259         node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1260         xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1261         if (only_hrefs) {
1262                 GSList *l;
1263
1264                 xmlNewTextChild (node, nscd, (xmlChar *) "calendar-data", NULL);
1265                 for (l = only_hrefs; l; l = l->next) {
1266                         if (l->data) {
1267                                 xmlNewTextChild (root, nsdav, (xmlChar *) "href", (xmlChar *) l->data);
1268                         }
1269                 }
1270         } else {
1271                 node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1272                 node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1273                 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1274
1275                 sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1276                 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
1277                         default:
1278                         case ICAL_VEVENT_COMPONENT:
1279                                 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1280                                 break;
1281                         case ICAL_VJOURNAL_COMPONENT:
1282                                 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VJOURNAL");
1283                                 break;
1284                         case ICAL_VTODO_COMPONENT:
1285                                 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VTODO");
1286                                 break;
1287                 }
1288
1289                 if (start_time > 0 || end_time > 0) {
1290                         gchar *tmp;
1291
1292                         sn = xmlNewTextChild (sn, nscd, (xmlChar *) "time-range", NULL);
1293
1294                         if (start_time > 0) {
1295                                 tmp = isodate_from_time_t (start_time);
1296                                 xmlSetProp (sn, (xmlChar *) "start", (xmlChar *) tmp);
1297                                 g_free (tmp);
1298                         }
1299
1300                         if (end_time > 0) {
1301                                 tmp = isodate_from_time_t (end_time);
1302                                 xmlSetProp (sn, (xmlChar *) "end", (xmlChar *) tmp);
1303                                 g_free (tmp);
1304                         }
1305                 }
1306         }
1307
1308         buf = xmlAllocOutputBuffer (NULL);
1309         xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1310         xmlOutputBufferFlush (buf);
1311
1312         /* Prepare the soup message */
1313         soup_message_headers_append (message->request_headers,
1314                                      "User-Agent", "Evolution/" VERSION);
1315         soup_message_headers_append (message->request_headers,
1316                                      "Depth", "1");
1317
1318         soup_message_set_request (message,
1319                                   "application/xml",
1320                                   SOUP_MEMORY_COPY,
1321                                   (gchar *) buf->buffer->content,
1322                                   buf->buffer->use);
1323
1324         /* Send the request now */
1325         send_and_handle_redirection (priv->session, message, NULL);
1326
1327         /* Clean up the memory */
1328         xmlOutputBufferClose (buf);
1329         xmlFreeDoc (doc);
1330
1331         /* Check the result */
1332         if (message->status_code != 207) {
1333                 switch (message->status_code) {
1334                 case SOUP_STATUS_CANT_CONNECT:
1335                 case SOUP_STATUS_CANT_CONNECT_PROXY:
1336                         priv->opened = FALSE;
1337                         priv->slave_cmd = SLAVE_SHOULD_SLEEP;
1338                         priv->read_only = TRUE;
1339                         e_cal_backend_notify_readonly (E_CAL_BACKEND (cbdav), priv->read_only);
1340                         break;
1341                 case 401:
1342                         caldav_notify_auth_required (cbdav);
1343                         break;
1344                 default:
1345                         g_warning ("Server did not response with 207, but with code %d (%s)", message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1346                         break;
1347                 }
1348
1349                 g_object_unref (message);
1350                 return FALSE;
1351         }
1352
1353         /* Parse the response body */
1354         result = parse_report_response (message, objs, len);
1355
1356         g_object_unref (message);
1357         return result;
1358 }
1359
1360 static gboolean
1361 caldav_server_download_attachment (ECalBackendCalDAV *cbdav,
1362                                    const gchar *attachment_uri,
1363                                    gchar **content,
1364                                    gsize *len,
1365                                    GError **error)
1366 {
1367         SoupMessage *message;
1368
1369         g_return_val_if_fail (cbdav != NULL, FALSE);
1370         g_return_val_if_fail (cbdav->priv != NULL, FALSE);
1371         g_return_val_if_fail (attachment_uri != NULL, FALSE);
1372         g_return_val_if_fail (content != NULL, FALSE);
1373         g_return_val_if_fail (len != NULL, FALSE);
1374
1375         message = soup_message_new (SOUP_METHOD_GET, attachment_uri);
1376         if (message == NULL) {
1377                 g_propagate_error (error, EDC_ERROR (InvalidObject));
1378                 return FALSE;
1379         }
1380
1381         soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1382         send_and_handle_redirection (cbdav->priv->session, message, NULL);
1383
1384         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1385                 status_code_to_result (message, cbdav->priv, error);
1386
1387                 if (message->status_code == 401)
1388                         caldav_notify_auth_required (cbdav);
1389
1390                 g_object_unref (message);
1391                 return FALSE;
1392         }
1393
1394         *len = message->response_body->length;
1395         *content = g_memdup (message->response_body->data, *len);
1396
1397         g_object_unref (message);
1398
1399         return TRUE;
1400 }
1401
1402 static gboolean
1403 caldav_server_get_object (ECalBackendCalDAV *cbdav,
1404                           CalDAVObject *object,
1405                           GError **perror)
1406 {
1407         ECalBackendCalDAVPrivate *priv;
1408         SoupMessage              *message;
1409         const gchar               *hdr;
1410         gchar                     *uri;
1411
1412         priv = cbdav->priv;
1413
1414         g_assert (object != NULL && object->href != NULL);
1415
1416         uri = caldav_generate_uri (cbdav, object->href);
1417         message = soup_message_new (SOUP_METHOD_GET, uri);
1418         if (message == NULL) {
1419                 g_free (uri);
1420                 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1421                 return FALSE;
1422         }
1423
1424         soup_message_headers_append (message->request_headers,
1425                                      "User-Agent", "Evolution/" VERSION);
1426
1427         send_and_handle_redirection (priv->session, message, NULL);
1428
1429         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1430                 status_code_to_result (message, priv, perror);
1431
1432                 if (message->status_code == 401)
1433                         caldav_notify_auth_required (cbdav);
1434                 else
1435                         g_warning ("Could not fetch object '%s' from server, status:%d (%s)", uri, message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1436                 g_object_unref (message);
1437                 g_free (uri);
1438                 return FALSE;
1439         }
1440
1441         hdr = soup_message_headers_get (message->response_headers, "Content-Type");
1442
1443         if (hdr == NULL || g_ascii_strncasecmp (hdr, "text/calendar", 13)) {
1444                 g_propagate_error (perror, EDC_ERROR (InvalidObject));
1445                 g_object_unref (message);
1446                 g_warning ("Object to fetch '%s' not of type text/calendar", uri);
1447                 g_free (uri);
1448                 return FALSE;
1449         }
1450
1451         hdr = soup_message_headers_get (message->response_headers, "ETag");
1452
1453         if (hdr != NULL) {
1454                 g_free (object->etag);
1455                 object->etag = quote_etag (hdr);
1456         } else if (!object->etag) {
1457                 g_warning ("UUHH no ETag, now that's bad! (at '%s')", uri);
1458         }
1459         g_free (uri);
1460
1461         g_free (object->cdata);
1462         object->cdata = g_strdup (message->response_body->data);
1463
1464         g_object_unref (message);
1465
1466         return TRUE;
1467 }
1468
1469 static void
1470 caldav_post_freebusy (ECalBackendCalDAV *cbdav,
1471                       const gchar *url,
1472                       gchar **post_fb,
1473                       GError **error)
1474 {
1475         ECalBackendCalDAVPrivate *priv;
1476         SoupMessage *message;
1477
1478         e_return_data_cal_error_if_fail (cbdav != NULL, InvalidArg);
1479         e_return_data_cal_error_if_fail (url != NULL, InvalidArg);
1480         e_return_data_cal_error_if_fail (post_fb != NULL, InvalidArg);
1481         e_return_data_cal_error_if_fail (*post_fb != NULL, InvalidArg);
1482
1483         priv = cbdav->priv;
1484
1485         message = soup_message_new (SOUP_METHOD_POST, url);
1486         if (message == NULL) {
1487                 g_propagate_error (error, EDC_ERROR (NoSuchCal));
1488                 return;
1489         }
1490
1491         soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1492         soup_message_set_request (message,
1493                                   "text/calendar; charset=utf-8",
1494                                   SOUP_MEMORY_COPY,
1495                                   *post_fb, strlen (*post_fb));
1496
1497         send_and_handle_redirection (priv->session, message, NULL);
1498
1499         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1500                 status_code_to_result (message, priv, error);
1501                 if (message->status_code == 401)
1502                         caldav_notify_auth_required (cbdav);
1503                 else
1504                         g_warning ("Could not post free/busy request to '%s', status:%d (%s)", url, message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1505
1506                 g_object_unref (message);
1507
1508                 return;
1509         }
1510
1511         g_free (*post_fb);
1512         *post_fb = g_strdup (message->response_body->data);
1513
1514         g_object_unref (message);
1515 }
1516
1517 static gboolean
1518 caldav_server_put_object (ECalBackendCalDAV *cbdav,
1519                           CalDAVObject *object,
1520                           icalcomponent *icalcomp,
1521                           GError **perror)
1522 {
1523         ECalBackendCalDAVPrivate *priv;
1524         SoupMessage              *message;
1525         const gchar               *hdr;
1526         gchar                     *uri;
1527
1528         priv   = cbdav->priv;
1529         hdr    = NULL;
1530
1531         g_assert (object != NULL && object->cdata != NULL);
1532
1533         uri = caldav_generate_uri (cbdav, object->href);
1534         message = soup_message_new (SOUP_METHOD_PUT, uri);
1535         g_free (uri);
1536         if (message == NULL) {
1537                 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1538                 return FALSE;
1539         }
1540
1541         soup_message_headers_append (message->request_headers,
1542                                      "User-Agent", "Evolution/" VERSION);
1543
1544         /* For new items we use the If-None-Match so we don't
1545          * acidently override resources, for item updates we
1546          * use the If-Match header to avoid the Lost-update
1547          * problem */
1548         if (object->etag == NULL) {
1549                 soup_message_headers_append (message->request_headers, "If-None-Match", "*");
1550         } else {
1551                 soup_message_headers_append (message->request_headers,
1552                                              "If-Match", object->etag);
1553         }
1554
1555         soup_message_set_request (message,
1556                                   "text/calendar; charset=utf-8",
1557                                   SOUP_MEMORY_COPY,
1558                                   object->cdata,
1559                                   strlen (object->cdata));
1560
1561         uri = NULL;
1562         send_and_handle_redirection (priv->session, message, &uri);
1563
1564         if (uri) {
1565                 gchar *file = strrchr (uri, '/');
1566
1567                 /* there was a redirect, update href properly */
1568                 if (file) {
1569                         gchar *decoded;
1570
1571                         g_free (object->href);
1572
1573                         decoded = soup_uri_decode (file + 1);
1574                         object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1575
1576                         g_free (decoded);
1577                 }
1578
1579                 g_free (uri);
1580         }
1581
1582         if (status_code_to_result (message, priv, perror)) {
1583                 gboolean was_get = FALSE;
1584
1585                 hdr = soup_message_headers_get (message->response_headers, "ETag");
1586                 if (hdr != NULL) {
1587                         g_free (object->etag);
1588                         object->etag = quote_etag (hdr);
1589                 } else {
1590                         /* no ETag header returned, check for it with a GET */
1591                         hdr = soup_message_headers_get (message->response_headers, "Location");
1592                         if (hdr) {
1593                                 /* reflect possible href change first */
1594                                 gchar *file = strrchr (hdr, '/');
1595
1596                                 if (file) {
1597                                         gchar *decoded;
1598
1599                                         g_free (object->href);
1600
1601                                         decoded = soup_uri_decode (file + 1);
1602                                         object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1603
1604                                         g_free (decoded);
1605                                 }
1606                         }
1607                 }
1608
1609                 was_get = TRUE;
1610
1611                 if (caldav_server_get_object (cbdav, object, perror)) {
1612                         icalcomponent *use_comp = NULL;
1613
1614                         if (object->cdata && was_get) {
1615                                 /* maybe server also modified component, thus rather store the server's */
1616                                 use_comp = icalparser_parse_string (object->cdata);
1617                         }
1618
1619                         if (!use_comp)
1620                                 use_comp = icalcomp;
1621
1622                         put_comp_to_cache (cbdav, use_comp, object->href, object->etag);
1623
1624                         if (use_comp != icalcomp)
1625                                 icalcomponent_free (use_comp);
1626                 }
1627         } else if (message->status_code == 401) {
1628                 caldav_notify_auth_required (cbdav);
1629         }
1630
1631         g_object_unref (message);
1632
1633         return TRUE;
1634 }
1635
1636 static void
1637 caldav_server_delete_object (ECalBackendCalDAV *cbdav,
1638                              CalDAVObject *object,
1639                              GError **perror)
1640 {
1641         ECalBackendCalDAVPrivate *priv;
1642         SoupMessage              *message;
1643         gchar                     *uri;
1644
1645         priv = cbdav->priv;
1646
1647         g_assert (object != NULL && object->href != NULL);
1648
1649         uri = caldav_generate_uri (cbdav, object->href);
1650         message = soup_message_new (SOUP_METHOD_DELETE, uri);
1651         g_free (uri);
1652         if (message == NULL) {
1653                 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1654                 return;
1655         }
1656
1657         soup_message_headers_append (message->request_headers,
1658                                      "User-Agent", "Evolution/" VERSION);
1659
1660         if (object->etag != NULL) {
1661                 soup_message_headers_append (message->request_headers,
1662                                              "If-Match", object->etag);
1663         }
1664
1665         send_and_handle_redirection (priv->session, message, NULL);
1666
1667         status_code_to_result (message, priv, perror);
1668
1669         if (message->status_code == 401)
1670                 caldav_notify_auth_required (cbdav);
1671
1672         g_object_unref (message);
1673 }
1674
1675 static gboolean
1676 caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
1677 {
1678         ECalBackendCalDAVPrivate *priv;
1679         SoupMessage *message;
1680         xmlOutputBufferPtr buf;
1681         xmlDocPtr doc;
1682         xmlNodePtr root, node;
1683         xmlNsPtr nsdav;
1684         gchar *owner = NULL;
1685
1686         g_return_val_if_fail (cbdav != NULL, FALSE);
1687
1688         priv = cbdav->priv;
1689         g_return_val_if_fail (priv != NULL, FALSE);
1690         g_return_val_if_fail (priv->schedule_outbox_url == NULL, TRUE);
1691
1692         /* Prepare the soup message */
1693         message = soup_message_new ("PROPFIND", priv->uri);
1694         if (message == NULL)
1695                 return FALSE;
1696
1697         doc = xmlNewDoc ((xmlChar *) "1.0");
1698         root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1699         xmlDocSetRootElement (doc, root);
1700         nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1701
1702         node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1703         node = xmlNewTextChild (node, nsdav, (xmlChar *) "owner", NULL);
1704
1705         buf = xmlAllocOutputBuffer (NULL);
1706         xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1707         xmlOutputBufferFlush (buf);
1708
1709         soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1710         soup_message_headers_append (message->request_headers, "Depth", "0");
1711
1712         soup_message_set_request (message,
1713                                   "application/xml",
1714                                   SOUP_MEMORY_COPY,
1715                                   (gchar *) buf->buffer->content,
1716                                   buf->buffer->use);
1717
1718         /* Send the request now */
1719         send_and_handle_redirection (priv->session, message, NULL);
1720
1721         /* Clean up the memory */
1722         xmlOutputBufferClose (buf);
1723         xmlFreeDoc (doc);
1724
1725         /* Check the result */
1726         if (message->status_code == 207 && parse_propfind_response (message, XPATH_OWNER_STATUS, XPATH_OWNER, &owner) && owner && *owner) {
1727                 xmlNsPtr nscd;
1728                 SoupURI *suri;
1729
1730                 g_object_unref (message);
1731
1732                 /* owner is a full path to the user's URL, thus change it in
1733                  * calendar's uri when asking for schedule-outbox-URL */
1734                 suri = soup_uri_new (priv->uri);
1735                 soup_uri_set_path (suri, owner);
1736                 g_free (owner);
1737                 owner = soup_uri_to_string (suri, FALSE);
1738                 soup_uri_free (suri);
1739
1740                 message = soup_message_new ("PROPFIND", owner);
1741                 if (message == NULL) {
1742                         g_free (owner);
1743                         return FALSE;
1744                 }
1745
1746                 doc = xmlNewDoc ((xmlChar *) "1.0");
1747                 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1748                 xmlDocSetRootElement (doc, root);
1749                 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1750                 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1751
1752                 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1753                 node = xmlNewTextChild (node, nscd, (xmlChar *) "schedule-outbox-URL", NULL);
1754
1755                 buf = xmlAllocOutputBuffer (NULL);
1756                 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1757                 xmlOutputBufferFlush (buf);
1758
1759                 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1760                 soup_message_headers_append (message->request_headers, "Depth", "0");
1761
1762                 soup_message_set_request (message,
1763                                   "application/xml",
1764                                   SOUP_MEMORY_COPY,
1765                                   (gchar *) buf->buffer->content,
1766                                   buf->buffer->use);
1767
1768                 /* Send the request now */
1769                 send_and_handle_redirection (priv->session, message, NULL);
1770
1771                 if (message->status_code == 207 && parse_propfind_response (message, XPATH_SCHEDULE_OUTBOX_URL_STATUS, XPATH_SCHEDULE_OUTBOX_URL, &priv->schedule_outbox_url)) {
1772                         if (!*priv->schedule_outbox_url) {
1773                                 g_free (priv->schedule_outbox_url);
1774                                 priv->schedule_outbox_url = NULL;
1775                         } else {
1776                                 /* make it a full URI */
1777                                 suri = soup_uri_new (priv->uri);
1778                                 soup_uri_set_path (suri, priv->schedule_outbox_url);
1779                                 g_free (priv->schedule_outbox_url);
1780                                 priv->schedule_outbox_url = soup_uri_to_string (suri, FALSE);
1781                                 soup_uri_free (suri);
1782                         }
1783                 }
1784
1785                 /* Clean up the memory */
1786                 xmlOutputBufferClose (buf);
1787                 xmlFreeDoc (doc);
1788         } else if (message->status_code == 401) {
1789                 caldav_notify_auth_required (cbdav);
1790         }
1791
1792         if (message)
1793                 g_object_unref (message);
1794
1795         g_free (owner);
1796
1797         return priv->schedule_outbox_url != NULL;
1798 }
1799
1800 /* ************************************************************************* */
1801 /* Synchronization foo */
1802
1803 static gboolean extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp);
1804
1805 struct cache_comp_list
1806 {
1807         GSList *slist;
1808 };
1809
1810 static gboolean
1811 remove_complist_from_cache_and_notify_cb (gpointer key,
1812                                           gpointer value,
1813                                           gpointer data)
1814 {
1815         GSList *l;
1816         struct cache_comp_list *ccl = value;
1817         ECalBackendCalDAV *cbdav = data;
1818         ECalBackendCalDAVPrivate *priv = cbdav->priv;
1819
1820         for (l = ccl->slist; l; l = l->next) {
1821                 ECalComponent *old_comp = l->data;
1822                 ECalComponentId *id;
1823
1824                 id = e_cal_component_get_id (old_comp);
1825                 if (!id) {
1826                         continue;
1827                 }
1828
1829                 if (e_cal_backend_store_remove_component (priv->store, id->uid, id->rid)) {
1830                         e_cal_backend_notify_component_removed ((ECalBackend *) cbdav, id, old_comp, NULL);
1831                 }
1832
1833                 e_cal_component_free_id (id);
1834         }
1835         remove_cached_attachment (cbdav, (const gchar *) key);
1836
1837         return FALSE;
1838 }
1839
1840 static void
1841 free_comp_list (gpointer cclist)
1842 {
1843         struct cache_comp_list *ccl = cclist;
1844
1845         g_return_if_fail (ccl != NULL);
1846
1847         g_slist_foreach (ccl->slist, (GFunc) g_object_unref, NULL);
1848         g_slist_free (ccl->slist);
1849         g_free (ccl);
1850 }
1851
1852 #define etags_match(_tag1, _tag2) ((_tag1 == _tag2) ? TRUE :                 \
1853                                    g_str_equal (_tag1 != NULL ? _tag1 : "",  \
1854                                                 _tag2 != NULL ? _tag2 : ""))
1855
1856 /* start_time/end_time is an interval for checking changes. If both greater than zero,
1857  * only the interval is checked and the removed items are not notified, as they can
1858  * be still there.
1859 */
1860 static void
1861 synchronize_cache (ECalBackendCalDAV *cbdav,
1862                    time_t start_time,
1863                    time_t end_time)
1864 {
1865         ECalBackendCalDAVPrivate *priv;
1866         ECalBackend *bkend;
1867         CalDAVObject *sobjs, *object;
1868         GSList *c_objs, *c_iter; /* list of all items known from our cache */
1869         GTree *c_uid2complist;  /* cache components list (with detached instances) sorted by (master's) uid */
1870         GHashTable *c_href2uid; /* connection between href and a (master's) uid */
1871         GSList *hrefs_to_update, *htu; /* list of href-s to update */
1872         gint i, len;
1873
1874         if (!check_calendar_changed_on_server (cbdav)) {
1875                 /* no changes on the server, no update required */
1876                 return;
1877         }
1878
1879         priv   = cbdav->priv;
1880         bkend  = E_CAL_BACKEND (cbdav);
1881         len    = 0;
1882         sobjs  = NULL;
1883
1884         /* get list of server objects */
1885         if (!caldav_server_list_objects (cbdav, &sobjs, &len, NULL, start_time, end_time))
1886                 return;
1887
1888         c_objs = e_cal_backend_store_get_components (priv->store);
1889
1890         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
1891                 printf ("CalDAV - found %d objects on the server, locally stored %d objects\n", len, g_slist_length (c_objs)); fflush (stdout);
1892         }
1893
1894         /* do not store changes in cache immediately - makes things significantly quicker */
1895         e_cal_backend_store_freeze_changes (priv->store);
1896
1897         c_uid2complist = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL, g_free, free_comp_list);
1898         c_href2uid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1899
1900         /* fill indexed hash and tree with cached components */
1901         for (c_iter = c_objs; c_iter; c_iter = g_slist_next (c_iter)) {
1902                 ECalComponent *ccomp = E_CAL_COMPONENT (c_iter->data);
1903                 const gchar *uid = NULL;
1904                 struct cache_comp_list *ccl;
1905                 gchar *href;
1906
1907                 e_cal_component_get_uid (ccomp, &uid);
1908                 if (!uid) {
1909                         g_warning ("broken component with NULL Id");
1910                         continue;
1911                 }
1912
1913                 href = ecalcomp_get_href (ccomp);
1914
1915                 if (href == NULL) {
1916                         g_warning ("href of object NULL :(");
1917                         continue;
1918                 }
1919
1920                 ccl = g_tree_lookup (c_uid2complist, uid);
1921                 if (ccl) {
1922                         ccl->slist = g_slist_prepend (ccl->slist, g_object_ref (ccomp));
1923                 } else {
1924                         ccl = g_new0 (struct cache_comp_list, 1);
1925                         ccl->slist = g_slist_append (NULL, g_object_ref (ccomp));
1926
1927                         /* make a copy, which will be used in the c_href2uid too */
1928                         uid = g_strdup (uid);
1929
1930                         g_tree_insert (c_uid2complist, (gpointer) uid, ccl);
1931                 }
1932
1933                 if (g_hash_table_lookup (c_href2uid, href) == NULL) {
1934                         /* uid is from a component or c_uid2complist key, thus will not be
1935                          * freed before a removal from c_uid2complist, thus do not duplicate it,
1936                          * rather save memory */
1937                         g_hash_table_insert (c_href2uid, href, (gpointer) uid);
1938                 } else {
1939                         g_free (href);
1940                 }
1941         }
1942
1943         /* clear it now, we do not need it later */
1944         g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
1945         g_slist_free (c_objs);
1946         c_objs = NULL;
1947
1948         hrefs_to_update = NULL;
1949
1950         /* see if we have to update or add some objects */
1951         for (i = 0, object = sobjs; i < len && priv->slave_cmd == SLAVE_SHOULD_WORK; i++, object++) {
1952                 ECalComponent *ccomp = NULL;
1953                 gchar *etag = NULL;
1954                 const gchar *uid;
1955                 struct cache_comp_list *ccl;
1956
1957                 if (object->status != 200) {
1958                         /* just continue here, so that the object
1959                          * doesnt get removed from the cobjs list
1960                          * - therefore it will be removed */
1961                         continue;
1962                 }
1963
1964                 uid = g_hash_table_lookup (c_href2uid, object->href);
1965                 if (uid) {
1966                         ccl = g_tree_lookup (c_uid2complist, uid);
1967                         if (ccl) {
1968                                 GSList *sl;
1969                                 for (sl = ccl->slist; sl && !etag; sl = sl->next) {
1970                                         ccomp = sl->data;
1971                                         if (ccomp)
1972                                                 etag = ecalcomp_get_etag (ccomp);
1973                                 }
1974
1975                                 if (!etag)
1976                                         ccomp = NULL;
1977                         }
1978                 }
1979
1980                 if (!etag || !etags_match (etag, object->etag)) {
1981                         hrefs_to_update = g_slist_prepend (hrefs_to_update, object->href);
1982                 } else if (uid && ccl) {
1983                         /* all components cover by this uid are up-to-date */
1984                         GSList *p;
1985
1986                         for (p = ccl->slist; p; p = p->next) {
1987                                 g_object_unref (p->data);
1988                         }
1989
1990                         g_slist_free (ccl->slist);
1991                         ccl->slist = NULL;
1992                 }
1993
1994                 g_free (etag);
1995         }
1996
1997         /* free hash table, as it is not used anymore */
1998         g_hash_table_destroy (c_href2uid);
1999         c_href2uid = NULL;
2000
2001         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2002                 printf ("CalDAV - recognized %d items to update\n", g_slist_length (hrefs_to_update)); fflush (stdout);
2003         }
2004
2005         htu = hrefs_to_update;
2006         while (htu && priv->slave_cmd == SLAVE_SHOULD_WORK) {
2007                 gint count = 0;
2008                 GSList *to_fetch = NULL;
2009
2010                 while (count < CALDAV_MAX_MULTIGET_AMOUNT && htu) {
2011                         to_fetch = g_slist_prepend (to_fetch, htu->data);
2012                         htu = htu->next;
2013                         count++;
2014                 }
2015
2016                 if (to_fetch && priv->slave_cmd == SLAVE_SHOULD_WORK) {
2017                         CalDAVObject *up_sobjs = NULL;
2018
2019                         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2020                                 printf ("CalDAV - going to fetch %d items\n", g_slist_length (to_fetch)); fflush (stdout);
2021                         }
2022
2023                         count = 0;
2024                         if (!caldav_server_list_objects (cbdav, &up_sobjs, &count, to_fetch, 0, 0)) {
2025                                 fprintf (stderr, "CalDAV - failed to retrieve bunch of items\n"); fflush (stderr);
2026                                 break;
2027                         }
2028
2029                         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2030                                 printf ("CalDAV - fetched bunch of %d items\n", count); fflush (stdout);
2031                         }
2032
2033                         /* we are going to update cache */
2034                         /* they are downloaded, so process them */
2035                         for (i = 0, object = up_sobjs; i < count /*&& priv->slave_cmd == SLAVE_SHOULD_WORK */; i++, object++) {
2036                                 if (object->status == 200 && object->href && object->etag && object->cdata && *object->cdata) {
2037                                         icalcomponent *icomp = icalparser_parse_string (object->cdata);
2038
2039                                         if (icomp) {
2040                                                 icalcomponent_kind kind = icalcomponent_isa (icomp);
2041
2042                                                 extract_timezones (cbdav, icomp);
2043
2044                                                 if (kind == ICAL_VCALENDAR_COMPONENT) {
2045                                                         icalcomponent *subcomp;
2046
2047                                                         kind = e_cal_backend_get_kind (bkend);
2048
2049                                                         for (subcomp = icalcomponent_get_first_component (icomp, kind);
2050                                                              subcomp;
2051                                                              subcomp = icalcomponent_get_next_component (icomp, kind)) {
2052                                                                 ECalComponent *new_comp, *old_comp;
2053
2054                                                                 convert_to_url_attachment (cbdav, subcomp);
2055                                                                 new_comp = e_cal_component_new ();
2056                                                                 if (e_cal_component_set_icalcomponent (new_comp, icalcomponent_new_clone (subcomp))) {
2057                                                                         const gchar *uid = NULL;
2058                                                                         struct cache_comp_list *ccl;
2059
2060                                                                         e_cal_component_get_uid (new_comp, &uid);
2061                                                                         if (!uid) {
2062                                                                                 g_warning ("%s: no UID on component!", G_STRFUNC);
2063                                                                                 g_object_unref (new_comp);
2064                                                                                 continue;
2065                                                                         }
2066
2067                                                                         ecalcomp_set_href (new_comp, object->href);
2068                                                                         ecalcomp_set_etag (new_comp, object->etag);
2069
2070                                                                         old_comp = NULL;
2071                                                                         ccl = g_tree_lookup (c_uid2complist, uid);
2072                                                                         if (ccl) {
2073                                                                                 gchar *nc_rid = e_cal_component_get_recurid_as_string (new_comp);
2074                                                                                 GSList *p;
2075
2076                                                                                 for (p = ccl->slist; p && !old_comp; p = p->next) {
2077                                                                                         gchar *oc_rid;
2078
2079                                                                                         old_comp = p->data;
2080
2081                                                                                         oc_rid = e_cal_component_get_recurid_as_string (old_comp);
2082                                                                                         if (g_strcmp0 (nc_rid, oc_rid) != 0) {
2083                                                                                                 old_comp = NULL;
2084                                                                                         }
2085
2086                                                                                         g_free (oc_rid);
2087                                                                                 }
2088
2089                                                                                 g_free (nc_rid);
2090                                                                         }
2091
2092                                                                         put_component_to_store (cbdav, new_comp);
2093
2094                                                                         if (old_comp == NULL) {
2095                                                                                 e_cal_backend_notify_component_created (bkend, new_comp);
2096                                                                         } else {
2097                                                                                 e_cal_backend_notify_component_modified (bkend, old_comp, new_comp);
2098
2099                                                                                 ccl->slist = g_slist_remove (ccl->slist, old_comp);
2100                                                                                 g_object_unref (old_comp);
2101                                                                         }
2102                                                                 }
2103
2104                                                                 g_object_unref (new_comp);
2105                                                         }
2106                                                 }
2107
2108                                                 icalcomponent_free (icomp);
2109                                         }
2110                                 }
2111
2112                                 /* these free immediately */
2113                                 caldav_object_free (object, FALSE);
2114                         }
2115
2116                         /* cache update done for fetched items */
2117                         g_free (up_sobjs);
2118                 }
2119
2120                 /* do not free 'data' itself, it's part of 'sobjs' */
2121                 g_slist_free (to_fetch);
2122         }
2123
2124         /* if not interrupted and not using the time range... */
2125         if (priv->slave_cmd == SLAVE_SHOULD_WORK && (!start_time || !end_time)) {
2126                 /* ...remove old (not on server anymore) items from our cache and notify of a removal */
2127                 g_tree_foreach (c_uid2complist, remove_complist_from_cache_and_notify_cb, cbdav);
2128         }
2129
2130         if (priv->ctag_to_store) {
2131                 /* store only when wasn't interrupted */
2132                 if (priv->slave_cmd == SLAVE_SHOULD_WORK && start_time == 0 && end_time == 0) {
2133                         e_cal_backend_store_put_key_value (priv->store, CALDAV_CTAG_KEY, priv->ctag_to_store);
2134                 }
2135
2136                 g_free (priv->ctag_to_store);
2137                 priv->ctag_to_store = NULL;
2138         }
2139
2140         /* save cache changes to disk finally */
2141         e_cal_backend_store_thaw_changes (priv->store);
2142
2143         for (i = 0, object = sobjs; i < len; i++, object++) {
2144                 caldav_object_free (object, FALSE);
2145         }
2146
2147         g_tree_destroy (c_uid2complist);
2148         g_slist_free (hrefs_to_update);
2149         g_free (sobjs);
2150 }
2151
2152 static gboolean
2153 is_google_uri (const gchar *uri)
2154 {
2155         SoupURI *suri;
2156         gboolean res;
2157
2158         g_return_val_if_fail (uri != NULL, FALSE);
2159
2160         suri = soup_uri_new (uri);
2161         g_return_val_if_fail (suri != NULL, FALSE);
2162
2163         res = suri->host && g_ascii_strcasecmp (suri->host, "www.google.com") == 0;
2164
2165         soup_uri_free (suri);
2166
2167         return res;
2168 }
2169
2170 /* ************************************************************************* */
2171
2172 static gpointer
2173 caldav_synch_slave_loop (gpointer data)
2174 {
2175         ECalBackendCalDAVPrivate *priv;
2176         ECalBackendCalDAV        *cbdav;
2177         time_t now;
2178         icaltimezone *utc = icaltimezone_get_utc_timezone ();
2179         gboolean know_unreachable;
2180
2181         cbdav = E_CAL_BACKEND_CALDAV (data);
2182         priv = cbdav->priv;
2183
2184         g_mutex_lock (priv->busy_lock);
2185
2186         know_unreachable = !priv->opened;
2187
2188         while (priv->slave_cmd != SLAVE_SHOULD_DIE) {
2189                 GTimeVal alarm_clock;
2190                 if (priv->slave_cmd == SLAVE_SHOULD_SLEEP) {
2191                         /* just sleep until we get woken up again */
2192                         g_cond_wait (priv->cond, priv->busy_lock);
2193
2194                         /* check if we should die, work or sleep again */
2195                         continue;
2196                 }
2197
2198                 /* Ok here we go, do some real work
2199                  * Synch it baby one more time ...
2200                  */
2201                 priv->slave_busy = TRUE;
2202
2203                 if (!priv->opened) {
2204                         gboolean server_unreachable = FALSE;
2205                         GError *local_error = NULL;
2206                         gboolean online;
2207
2208                         if (caldav_server_open_calendar (cbdav, &server_unreachable, &local_error)) {
2209                                 priv->opened = TRUE;
2210                                 priv->slave_cmd = SLAVE_SHOULD_WORK;
2211                                 g_cond_signal (priv->cond);
2212
2213                                 priv->is_google = is_google_uri (priv->uri);
2214                                 know_unreachable = FALSE;
2215                         } else if (local_error) {
2216                                 priv->opened = FALSE;
2217                                 priv->read_only = TRUE;
2218
2219                                 if (!know_unreachable) {
2220                                         gchar *msg;
2221
2222                                         know_unreachable = TRUE;
2223
2224                                         msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2225                                         e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2226                                         g_free (msg);
2227                                 }
2228
2229                                 g_clear_error (&local_error);
2230                         } else {
2231                                 priv->opened = FALSE;
2232                                 priv->read_only = TRUE;
2233                                 know_unreachable = TRUE;
2234                         }
2235
2236                         e_cal_backend_notify_readonly (E_CAL_BACKEND (cbdav), priv->read_only);
2237
2238                         online = e_backend_get_online (E_BACKEND (cbdav));
2239                         e_cal_backend_notify_online (E_CAL_BACKEND (cbdav), online);
2240                 }
2241
2242                 if (priv->opened) {
2243                         time (&now);
2244                         /* check for events in the month before/after today first,
2245                          * to show user actual data as soon as possible */
2246                         synchronize_cache (cbdav, time_add_week_with_zone (now, -5, utc), time_add_week_with_zone (now, +5, utc));
2247
2248                         if (priv->slave_cmd != SLAVE_SHOULD_SLEEP) {
2249                                 /* and then check for changes in a whole calendar */
2250                                 synchronize_cache (cbdav, 0, 0);
2251                         }
2252
2253                         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2254                                 GSList *c_objs;
2255
2256                                 c_objs = e_cal_backend_store_get_components (priv->store);
2257
2258                                 printf ("CalDAV - finished syncing with %d items in a cache\n", g_slist_length (c_objs)); fflush (stdout);
2259
2260                                 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2261                                 g_slist_free (c_objs);
2262                         }
2263                 }
2264
2265                 priv->slave_busy = FALSE;
2266
2267                 /* puhh that was hard, get some rest :) */
2268                 g_get_current_time (&alarm_clock);
2269                 alarm_clock.tv_sec += priv->refresh_time.tv_sec;
2270                 g_cond_timed_wait (priv->cond,
2271                                    priv->busy_lock,
2272                                    &alarm_clock);
2273
2274         }
2275
2276         /* signal we are done */
2277         g_cond_signal (priv->slave_gone_cond);
2278
2279         priv->synch_slave = NULL;
2280
2281         /* we got killed ... */
2282         g_mutex_unlock (priv->busy_lock);
2283         return NULL;
2284 }
2285
2286 static gchar *
2287 maybe_append_email_domain (const gchar *username,
2288                            const gchar *may_append)
2289 {
2290         if (!username || !*username)
2291                 return NULL;
2292
2293         if (strchr (username, '@'))
2294                 return g_strdup (username);
2295
2296         return g_strconcat (username, may_append, NULL);
2297 }
2298
2299 static gchar *
2300 get_usermail (ECalBackend *backend)
2301 {
2302         ECalBackendCalDAV        *cbdav;
2303         ECalBackendCalDAVPrivate *priv;
2304         ESource *source;
2305         gchar *res = NULL;
2306
2307         g_return_val_if_fail (backend != NULL, NULL);
2308
2309         source = e_backend_get_source (E_BACKEND (backend));
2310         if (source) {
2311                 res = e_source_get_duped_property (source, "usermail");
2312                 if (res && *res)
2313                         return res;
2314
2315                 g_free (res);
2316                 res = NULL;
2317         }
2318
2319         cbdav = E_CAL_BACKEND_CALDAV (backend);
2320         priv  = cbdav->priv;
2321
2322         if (priv && priv->is_google && priv->credentials) {
2323                 res = maybe_append_email_domain (e_credentials_peek (priv->credentials, E_CREDENTIALS_KEY_USERNAME), "@gmail.com");
2324         }
2325
2326         return res;
2327 }
2328
2329 /* ************************************************************************* */
2330 /* ********** ECalBackendSync virtual function implementation *************  */
2331
2332 static gboolean
2333 caldav_get_backend_property (ECalBackendSync *backend,
2334                              EDataCal *cal,
2335                              GCancellable *cancellable,
2336                              const gchar *prop_name,
2337                              gchar **prop_value,
2338                              GError **perror)
2339 {
2340         gboolean processed = TRUE;
2341
2342         g_return_val_if_fail (prop_name != NULL, FALSE);
2343         g_return_val_if_fail (prop_value != NULL, FALSE);
2344
2345         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
2346                 ESource *source;
2347                 GString *caps;
2348                 gchar *usermail;
2349
2350                 caps = g_string_new (CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
2351                                      CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
2352                                      CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED);
2353
2354                 usermail = get_usermail (E_CAL_BACKEND (backend));
2355                 if (!usermail || !*usermail)
2356                         g_string_append (caps, "," CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS);
2357                 g_free (usermail);
2358
2359                 source = e_backend_get_source (E_BACKEND (backend));
2360                 if (source) {
2361                         const gchar *prop = e_source_get_property (source, "autoschedule");
2362
2363                         if (prop && g_str_equal (prop, "1"))
2364                                 g_string_append (caps, "," CAL_STATIC_CAPABILITY_CREATE_MESSAGES
2365                                                        "," CAL_STATIC_CAPABILITY_SAVE_SCHEDULES);
2366                 }
2367
2368                 *prop_value = g_string_free (caps, FALSE);
2369         } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
2370                    g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
2371                 *prop_value = get_usermail (E_CAL_BACKEND (backend));
2372         } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
2373                 ECalComponent *comp;
2374
2375                 comp = e_cal_component_new ();
2376
2377                 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2378                 case ICAL_VEVENT_COMPONENT:
2379                         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
2380                         break;
2381                 case ICAL_VTODO_COMPONENT:
2382                         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
2383                         break;
2384                 case ICAL_VJOURNAL_COMPONENT:
2385                         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
2386                         break;
2387                 default:
2388                         g_object_unref (comp);
2389                         g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
2390                         return TRUE;
2391                 }
2392
2393                 *prop_value = e_cal_component_get_as_string (comp);
2394                 g_object_unref (comp);
2395         } else {
2396                 processed = FALSE;
2397         }
2398
2399         return processed;
2400 }
2401
2402 static gboolean
2403 initialize_backend (ECalBackendCalDAV *cbdav,
2404                     GError **perror)
2405 {
2406         ECalBackendCalDAVPrivate *priv;
2407         ECalBackend              *backend;
2408         ESource                  *source;
2409         const gchar              *os_val;
2410         const gchar               *uri;
2411         gsize                     len;
2412         const gchar              *refresh;
2413         const gchar              *cache_dir;
2414
2415         priv  = cbdav->priv;
2416
2417         backend = E_CAL_BACKEND (cbdav);
2418         cache_dir = e_cal_backend_get_cache_dir (backend);
2419         source = e_backend_get_source (E_BACKEND (backend));
2420         uri = e_source_get_uri (source);
2421
2422         if (!g_signal_handler_find (G_OBJECT (source), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, caldav_source_changed_cb, cbdav))
2423                 g_signal_connect (G_OBJECT (source), "changed", G_CALLBACK (caldav_source_changed_cb), cbdav);
2424
2425         os_val = e_source_get_property (source, "offline_sync");
2426
2427         if (!os_val || !g_str_equal (os_val, "1")) {
2428                 priv->do_offline = FALSE;
2429         }
2430
2431         os_val = e_source_get_property (source, "auth");
2432         priv->auth_required = os_val != NULL;
2433
2434         os_val = e_source_get_property(source, "ssl");
2435
2436         g_free (priv->uri);
2437         priv->uri = NULL;
2438         if (g_str_has_prefix (uri, "caldav://")) {
2439                 const gchar *proto;
2440
2441                 if (os_val && os_val[0] == '1') {
2442                         proto = "https://";
2443                 } else {
2444                         proto = "http://";
2445                 }
2446
2447                 priv->uri = g_strconcat (proto, uri + 9, NULL);
2448         } else {
2449                 priv->uri = g_strdup (uri);
2450         }
2451
2452         if (priv->uri) {
2453                 SoupURI *suri = soup_uri_new (priv->uri);
2454
2455                 /* properly encode uri */
2456                 if (suri && suri->path) {
2457                         gchar *tmp, *path;
2458
2459                         if (suri->path && strchr (suri->path, '%')) {
2460                                 /* If path contains anything already encoded, then decode it first,
2461                                  * thus it'll be managed properly. For example, the '#' in a path
2462                                  * is in URI shown as %23 and not doing this decode makes it being
2463                                  * like %2523, which is not what is wanted here. */
2464                                 tmp = soup_uri_decode (suri->path);
2465                                 soup_uri_set_path (suri, tmp);
2466                                 g_free (tmp);
2467                         }
2468
2469                         tmp = soup_uri_encode (suri->path, NULL);
2470                         path = soup_uri_normalize (tmp, "/");
2471
2472                         soup_uri_set_path (suri, path);
2473
2474                         g_free (tmp);
2475                         g_free (path);
2476                         g_free (priv->uri);
2477
2478                         priv->uri = soup_uri_to_string (suri, FALSE);
2479                 }
2480
2481                 soup_uri_free (suri);
2482         }
2483
2484         /* remove trailing slashes... */
2485         len = strlen (priv->uri);
2486         while (len--) {
2487                 if (priv->uri[len] == '/') {
2488                         priv->uri[len] = '\0';
2489                 } else {
2490                         break;
2491                 }
2492         }
2493
2494         /* ...and append exactly one slash */
2495         if (priv->uri && *priv->uri) {
2496                 gchar *tmp = priv->uri;
2497
2498                 priv->uri = g_strconcat (priv->uri, "/", NULL);
2499
2500                 g_free (tmp);
2501         }
2502
2503         if (priv->store == NULL) {
2504                 /* remove the old cache while migrating to ECalBackendStore */
2505                 e_cal_backend_cache_remove (cache_dir, "cache.xml");
2506                 priv->store = e_cal_backend_file_store_new (cache_dir);
2507
2508                 if (priv->store == NULL) {
2509                         g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Cannot create local store"));
2510                         return FALSE;
2511                 }
2512
2513                 e_cal_backend_store_load (priv->store);
2514         }
2515
2516         /* Set the local attachment store */
2517         if (g_mkdir_with_parents (cache_dir, 0700) < 0) {
2518                 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "mkdir failed"));
2519                 return FALSE;
2520         }
2521
2522         refresh = e_source_get_property (source, "refresh");
2523         priv->refresh_time.tv_sec  = (refresh && atoi (refresh) > 0) ? (60 * atoi (refresh)) : (DEFAULT_REFRESH_TIME);
2524
2525         if (!priv->synch_slave) {
2526                 GThread *slave;
2527
2528                 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
2529                 slave = g_thread_create (caldav_synch_slave_loop, cbdav, FALSE, NULL);
2530
2531                 if (slave == NULL) {
2532                         g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Could not create synch slave"));
2533                 }
2534
2535                 priv->synch_slave = slave;
2536         }
2537
2538         return TRUE;
2539 }
2540
2541 static void
2542 proxy_settings_changed (EProxy *proxy,
2543                         gpointer user_data)
2544 {
2545         SoupURI *proxy_uri = NULL;
2546         ECalBackendCalDAVPrivate *priv = (ECalBackendCalDAVPrivate *) user_data;
2547
2548         if (!priv || !priv->uri || !priv->session)
2549                 return;
2550
2551         /* use proxy if necessary */
2552         if (e_proxy_require_proxy_for_uri (proxy, priv->uri)) {
2553                 proxy_uri = e_proxy_peek_uri_for (proxy, priv->uri);
2554         }
2555
2556         g_object_set (priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
2557 }
2558
2559 static void
2560 open_calendar (ECalBackendCalDAV *cbdav,
2561                GError **error)
2562 {
2563         gboolean server_unreachable = FALSE;
2564         ECalBackendCalDAVPrivate *priv;
2565         GError *local_error = NULL;
2566
2567         g_return_if_fail (cbdav != NULL);
2568
2569         priv  = cbdav->priv;
2570
2571         /* set forward proxy */
2572         proxy_settings_changed (priv->proxy, priv);
2573
2574         if (caldav_server_open_calendar (cbdav, &server_unreachable, &local_error)) {
2575                 priv->slave_cmd = SLAVE_SHOULD_WORK;
2576                 g_cond_signal (priv->cond);
2577
2578                 priv->is_google = is_google_uri (priv->uri);
2579         } else if (server_unreachable) {
2580                 priv->opened = FALSE;
2581                 priv->read_only = TRUE;
2582                 if (local_error) {
2583                         gchar *msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2584                         e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2585                         g_free (msg);
2586                         g_clear_error (&local_error);
2587                 }
2588         } else if (local_error) {
2589                 g_propagate_error (error, local_error);
2590         }
2591 }
2592
2593 static void
2594 caldav_do_open (ECalBackendSync *backend,
2595                 EDataCal *cal,
2596                 GCancellable *cancellable,
2597                 gboolean only_if_exists,
2598                 GError **perror)
2599 {
2600         ECalBackendCalDAV        *cbdav;
2601         ECalBackendCalDAVPrivate *priv;
2602         gboolean online;
2603
2604         cbdav = E_CAL_BACKEND_CALDAV (backend);
2605         priv  = cbdav->priv;
2606
2607         g_mutex_lock (priv->busy_lock);
2608
2609         /* let it decide the 'getctag' extension availability again */
2610         priv->ctag_supported = TRUE;
2611
2612         if (!priv->loaded && !initialize_backend (cbdav, perror)) {
2613                 g_mutex_unlock (priv->busy_lock);
2614                 return;
2615         }
2616
2617         online = e_backend_get_online (E_BACKEND (backend));
2618
2619         if (!priv->do_offline && !online) {
2620                 g_mutex_unlock (priv->busy_lock);
2621                 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
2622                 return;
2623         }
2624
2625         priv->loaded = TRUE;
2626         priv->opened = TRUE;
2627         priv->is_google = FALSE;
2628
2629         if (online) {
2630                 GError *local_error = NULL;
2631
2632                 open_calendar (cbdav, &local_error);
2633
2634                 if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationRequired) || g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
2635                         g_clear_error (&local_error);
2636                         e_cal_backend_notify_auth_required (E_CAL_BACKEND (cbdav), TRUE, priv->credentials);
2637                 } else {
2638                         e_cal_backend_notify_opened (E_CAL_BACKEND (backend), NULL);
2639                 }
2640
2641                 if (local_error)
2642                         g_propagate_error (perror, local_error);
2643         } else {
2644                 priv->read_only = TRUE;
2645                 e_cal_backend_notify_opened (E_CAL_BACKEND (backend), NULL);
2646         }
2647
2648         e_cal_backend_notify_readonly (E_CAL_BACKEND (backend), priv->read_only);
2649         e_cal_backend_notify_online (E_CAL_BACKEND (backend), online);
2650
2651         g_mutex_unlock (priv->busy_lock);
2652 }
2653
2654 static void
2655 caldav_authenticate_user (ECalBackendSync *backend,
2656                           GCancellable *cancellable,
2657                           ECredentials *credentials,
2658                           GError **error)
2659 {
2660         ECalBackendCalDAV        *cbdav;
2661         ECalBackendCalDAVPrivate *priv;
2662
2663         cbdav = E_CAL_BACKEND_CALDAV (backend);
2664         priv  = cbdav->priv;
2665
2666         g_mutex_lock (priv->busy_lock);
2667
2668         e_credentials_free (priv->credentials);
2669         priv->credentials = NULL;
2670
2671         if (!credentials || !e_credentials_has_key (credentials, E_CREDENTIALS_KEY_USERNAME)) {
2672                 g_mutex_unlock (priv->busy_lock);
2673                 g_propagate_error (error, EDC_ERROR (AuthenticationRequired));
2674                 return;
2675         }
2676
2677         priv->credentials = e_credentials_new_clone (credentials);
2678
2679         open_calendar (cbdav, error);
2680
2681         g_mutex_unlock (priv->busy_lock);
2682 }
2683
2684 static void
2685 caldav_refresh (ECalBackendSync *backend,
2686                 EDataCal *cal,
2687                 GCancellable *cancellable,
2688                 GError **perror)
2689 {
2690         ECalBackendCalDAV        *cbdav;
2691         ECalBackendCalDAVPrivate *priv;
2692         gboolean                  online;
2693
2694         cbdav = E_CAL_BACKEND_CALDAV (backend);
2695         priv  = cbdav->priv;
2696
2697         g_mutex_lock (priv->busy_lock);
2698
2699         if (!priv->loaded
2700             || priv->slave_cmd != SLAVE_SHOULD_SLEEP
2701             || !check_state (cbdav, &online, NULL)
2702             || !online) {
2703                 g_mutex_unlock (priv->busy_lock);
2704                 return;
2705         }
2706
2707         priv->slave_cmd = SLAVE_SHOULD_WORK;
2708
2709         /* wake it up */
2710         g_cond_signal (priv->cond);
2711         g_mutex_unlock (priv->busy_lock);
2712 }
2713
2714 static void
2715 caldav_remove (ECalBackendSync *backend,
2716                EDataCal *cal,
2717                GCancellable *cancellable,
2718                GError **perror)
2719 {
2720         ECalBackendCalDAV        *cbdav;
2721         ECalBackendCalDAVPrivate *priv;
2722         gboolean                  online;
2723
2724         cbdav = E_CAL_BACKEND_CALDAV (backend);
2725         priv  = cbdav->priv;
2726
2727         /* first tell it to die, then wait for its lock */
2728         priv->slave_cmd = SLAVE_SHOULD_DIE;
2729
2730         g_mutex_lock (priv->busy_lock);
2731
2732         if (!priv->loaded) {
2733                 g_mutex_unlock (priv->busy_lock);
2734                 return;
2735         }
2736
2737         if (!check_state (cbdav, &online, NULL)) {
2738                 /* lie here a bit, but otherwise the calendar will not be removed, even it should */
2739                 g_print (G_STRLOC ": Failed to check state");
2740         }
2741
2742         e_cal_backend_store_remove (priv->store);
2743         priv->store = NULL;
2744         priv->loaded = FALSE;
2745         priv->opened = FALSE;
2746
2747         if (priv->synch_slave) {
2748                 g_cond_signal (priv->cond);
2749
2750                 /* wait until the slave died */
2751                 g_cond_wait (priv->slave_gone_cond, priv->busy_lock);
2752         }
2753
2754         g_mutex_unlock (priv->busy_lock);
2755 }
2756
2757 static void
2758 remove_comp_from_cache_cb (gpointer value,
2759                            gpointer user_data)
2760 {
2761         ECalComponent *comp = value;
2762         ECalBackendStore *store = user_data;
2763         ECalComponentId *id;
2764
2765         g_return_if_fail (comp != NULL);
2766         g_return_if_fail (store != NULL);
2767
2768         id = e_cal_component_get_id (comp);
2769         g_return_if_fail (id != NULL);
2770
2771         e_cal_backend_store_remove_component (store, id->uid, id->rid);
2772         e_cal_component_free_id (id);
2773 }
2774
2775 static gboolean
2776 remove_comp_from_cache (ECalBackendCalDAV *cbdav,
2777                         const gchar *uid,
2778                         const gchar *rid)
2779 {
2780         ECalBackendCalDAVPrivate *priv;
2781         gboolean res = FALSE;
2782
2783         priv  = cbdav->priv;
2784
2785         if (!rid || !*rid) {
2786                 /* get with detached instances */
2787                 GSList *objects = e_cal_backend_store_get_components_by_uid (priv->store, uid);
2788
2789                 if (objects) {
2790                         g_slist_foreach (objects, (GFunc) remove_comp_from_cache_cb, priv->store);
2791                         g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2792                         g_slist_free (objects);
2793
2794                         res = TRUE;
2795                 }
2796         } else {
2797                 res = e_cal_backend_store_remove_component (priv->store, uid, rid);
2798         }
2799
2800         return res;
2801 }
2802
2803 static void
2804 add_detached_recur_to_vcalendar_cb (gpointer value,
2805                                     gpointer user_data)
2806 {
2807         icalcomponent *recurrence = e_cal_component_get_icalcomponent (value);
2808         icalcomponent *vcalendar = user_data;
2809
2810         icalcomponent_add_component (
2811                 vcalendar,
2812                 icalcomponent_new_clone (recurrence));
2813 }
2814
2815 static gint
2816 sort_master_first (gconstpointer a,
2817                    gconstpointer b)
2818 {
2819         icalcomponent *ca, *cb;
2820
2821         ca = e_cal_component_get_icalcomponent ((ECalComponent *) a);
2822         cb = e_cal_component_get_icalcomponent ((ECalComponent *) b);
2823
2824         if (!ca) {
2825                 if (!cb)
2826                         return 0;
2827                 else
2828                         return -1;
2829         } else if (!cb) {
2830                 return 1;
2831         }
2832
2833         return icaltime_compare (icalcomponent_get_recurrenceid (ca), icalcomponent_get_recurrenceid (cb));
2834 }
2835
2836 /* Returns new icalcomponent, with all detached instances stored in a cache.
2837  * The cache lock should be locked when called this function.
2838 */
2839 static icalcomponent *
2840 get_comp_from_cache (ECalBackendCalDAV *cbdav,
2841                      const gchar *uid,
2842                      const gchar *rid,
2843                      gchar **href,
2844                      gchar **etag)
2845 {
2846         ECalBackendCalDAVPrivate *priv;
2847         icalcomponent *icalcomp = NULL;
2848
2849         priv  = cbdav->priv;
2850
2851         if (rid == NULL || !*rid) {
2852                 /* get with detached instances */
2853                 GSList *objects = e_cal_backend_store_get_components_by_uid (priv->store, uid);
2854
2855                 if (!objects) {
2856                         return NULL;
2857                 }
2858
2859                 if (g_slist_length (objects) == 1) {
2860                         ECalComponent *comp = objects->data;
2861
2862                         /* will be unreffed a bit later */
2863                         if (comp)
2864                                 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2865                 } else {
2866                         /* if we have detached recurrences, return a VCALENDAR */
2867                         icalcomp = e_cal_util_new_top_level ();
2868
2869                         objects = g_slist_sort (objects, sort_master_first);
2870
2871                         /* add all detached recurrences and the master object */
2872                         g_slist_foreach (objects, add_detached_recur_to_vcalendar_cb, icalcomp);
2873                 }
2874
2875                 /* every component has set same href and etag, thus it doesn't matter where it will be read */
2876                 if (href)
2877                         *href = ecalcomp_get_href (objects->data);
2878                 if (etag)
2879                         *etag = ecalcomp_get_etag (objects->data);
2880
2881                 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2882                 g_slist_free (objects);
2883         } else {
2884                 /* get the exact object */
2885                 ECalComponent *comp = e_cal_backend_store_get_component (priv->store, uid, rid);
2886
2887                 if (comp) {
2888                         icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2889                         if (href)
2890                                 *href = ecalcomp_get_href (comp);
2891                         if (etag)
2892                                 *etag = ecalcomp_get_etag (comp);
2893                         g_object_unref (comp);
2894                 }
2895         }
2896
2897         return icalcomp;
2898 }
2899
2900 static gboolean
2901 put_comp_to_cache (ECalBackendCalDAV *cbdav,
2902                    icalcomponent *icalcomp,
2903                    const gchar *href,
2904                    const gchar *etag)
2905 {
2906         icalcomponent_kind my_kind;
2907         ECalComponent *comp;
2908         gboolean res = FALSE;
2909
2910         g_return_val_if_fail (cbdav != NULL, FALSE);
2911         g_return_val_if_fail (icalcomp != NULL, FALSE);
2912
2913         my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2914         comp = e_cal_component_new ();
2915
2916         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
2917                 icalcomponent *subcomp;
2918
2919                 /* remove all old components from the cache first */
2920                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2921                      subcomp;
2922                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2923                         remove_comp_from_cache (cbdav, icalcomponent_get_uid (subcomp), NULL);
2924                 }
2925
2926                 /* then put new. It's because some detached instances could be removed on the server. */
2927                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
2928                      subcomp;
2929                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
2930                         /* because reusing the same comp doesn't clear recur_id member properly */
2931                         g_object_unref (comp);
2932                         comp = e_cal_component_new ();
2933
2934                         if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
2935                                 if (href)
2936                                         ecalcomp_set_href (comp, href);
2937                                 if (etag)
2938                                         ecalcomp_set_etag (comp, etag);
2939
2940                                 if (put_component_to_store (cbdav, comp))
2941                                         res = TRUE;
2942                         }
2943                 }
2944         } else if (icalcomponent_isa (icalcomp) == my_kind) {
2945                 remove_comp_from_cache (cbdav, icalcomponent_get_uid (icalcomp), NULL);
2946
2947                 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
2948                         if (href)
2949                                 ecalcomp_set_href (comp, href);
2950                         if (etag)
2951                                 ecalcomp_set_etag (comp, etag);
2952
2953                         res = put_component_to_store (cbdav, comp);
2954                 }
2955         }
2956
2957         g_object_unref (comp);
2958
2959         return res;
2960 }
2961
2962 static void
2963 remove_property (gpointer prop,
2964                  gpointer icomp)
2965 {
2966         icalcomponent_remove_property (icomp, prop);
2967         icalproperty_free (prop);
2968 }
2969
2970 static void
2971 strip_unneeded_x_props (icalcomponent *icomp)
2972 {
2973         icalproperty *prop;
2974         GSList *to_remove = NULL;
2975
2976         g_return_if_fail (icomp != NULL);
2977         g_return_if_fail (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT);
2978
2979         for (prop = icalcomponent_get_first_property (icomp, ICAL_X_PROPERTY);
2980              prop;
2981              prop = icalcomponent_get_next_property (icomp, ICAL_X_PROPERTY)) {
2982                 if (g_str_has_prefix (icalproperty_get_x_name (prop), X_E_CALDAV)) {
2983                         to_remove = g_slist_prepend (to_remove, prop);
2984                 }
2985         }
2986
2987         for (prop = icalcomponent_get_first_property (icomp, ICAL_XLICERROR_PROPERTY);
2988              prop;
2989              prop = icalcomponent_get_next_property (icomp, ICAL_XLICERROR_PROPERTY)) {
2990                 to_remove = g_slist_prepend (to_remove, prop);
2991         }
2992
2993         g_slist_foreach (to_remove, remove_property, icomp);
2994         g_slist_free (to_remove);
2995 }
2996
2997 static gboolean
2998 is_stored_on_server (ECalBackendCalDAV *cbdav,
2999                      const gchar *uri)
3000 {
3001         SoupURI *my_uri, *test_uri;
3002         gboolean res;
3003
3004         g_return_val_if_fail (cbdav != NULL, FALSE);
3005         g_return_val_if_fail (cbdav->priv != NULL, FALSE);
3006         g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
3007         g_return_val_if_fail (uri != NULL, FALSE);
3008
3009         my_uri = soup_uri_new (cbdav->priv->uri);
3010         g_return_val_if_fail (my_uri != NULL, FALSE);
3011
3012         test_uri = soup_uri_new (uri);
3013         if (!test_uri) {
3014                 soup_uri_free (my_uri);
3015                 return FALSE;
3016         }
3017
3018         res = my_uri->host && test_uri->host && g_ascii_strcasecmp (my_uri->host, test_uri->host) == 0;
3019
3020         soup_uri_free (my_uri);
3021         soup_uri_free (test_uri);
3022
3023         return res;
3024 }
3025
3026 static void
3027 convert_to_inline_attachment (ECalBackendCalDAV *cbdav,
3028                               icalcomponent *icalcomp)
3029 {
3030         icalcomponent *cclone;
3031         icalproperty *p;
3032         GSList *to_remove = NULL;
3033
3034         g_return_if_fail (icalcomp != NULL);
3035
3036         cclone = icalcomponent_new_clone (icalcomp);
3037
3038         /* Remove local url attachments first */
3039         for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3040              p;
3041              p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3042                 icalattach *attach;
3043
3044                 attach = icalproperty_get_attach ((const icalproperty *) p);
3045                 if (icalattach_get_is_url (attach)) {
3046                         const gchar *url;
3047
3048                         url = icalattach_get_url (attach);
3049                         if (g_str_has_prefix (url, LOCAL_PREFIX))
3050                                 to_remove = g_slist_prepend (to_remove, p);
3051                 }
3052         }
3053         g_slist_foreach (to_remove, remove_property, icalcomp);
3054         g_slist_free (to_remove);
3055
3056         /* convert local url attachments to inline attachments now */
3057         for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY);
3058              p;
3059              p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY)) {
3060                 icalattach *attach;
3061                 GFile *file;
3062                 GError *error = NULL;
3063                 const gchar *uri;
3064                 gchar *basename;
3065                 gchar *content;
3066                 gsize len;
3067
3068                 attach = icalproperty_get_attach ((const icalproperty *) p);
3069                 if (!icalattach_get_is_url (attach))
3070                         continue;
3071
3072                 uri = icalattach_get_url (attach);
3073                 if (!g_str_has_prefix (uri, LOCAL_PREFIX))
3074                         continue;
3075
3076                 file = g_file_new_for_uri (uri);
3077                 basename = g_file_get_basename (file);
3078                 if (g_file_load_contents (file, NULL, &content, &len, NULL, &error)) {
3079                         icalproperty *prop;
3080                         icalparameter *param;
3081                         gchar *encoded;
3082
3083                         /*
3084                          * do a base64 encoding so it can
3085                          * be embedded in a soap message
3086                          */
3087                         encoded = g_base64_encode ((guchar *) content, len);
3088                         attach = icalattach_new_from_data (encoded, NULL, NULL);
3089                         g_free (content);
3090                         g_free (encoded);
3091
3092                         prop = icalproperty_new_attach (attach);
3093                         icalattach_unref (attach);
3094
3095                         param = icalparameter_new_value (ICAL_VALUE_BINARY);
3096                         icalproperty_add_parameter (prop, param);
3097
3098                         param = icalparameter_new_encoding (ICAL_ENCODING_BASE64);
3099                         icalproperty_add_parameter (prop, param);
3100
3101                         param = icalparameter_new_x (basename);
3102                         icalparameter_set_xname (param, X_E_CALDAV_ATTACHMENT_NAME);
3103                         icalproperty_add_parameter (prop, param);
3104
3105                         icalcomponent_add_property (icalcomp, prop);
3106                 } else {
3107                         g_warning ("%s\n", error->message);
3108                         g_clear_error (&error);
3109                 }
3110                 g_free (basename);
3111                 g_object_unref (file);
3112         }
3113         icalcomponent_free (cclone);
3114 }
3115
3116 static void
3117 convert_to_url_attachment (ECalBackendCalDAV *cbdav,
3118                            icalcomponent *icalcomp)
3119 {
3120         ECalBackend *backend;
3121         GSList *to_remove = NULL, *to_remove_after_download = NULL;
3122         icalcomponent *cclone;
3123         icalproperty *p;
3124         gint fileindex;
3125
3126         g_return_if_fail (cbdav != NULL);
3127         g_return_if_fail (icalcomp != NULL);
3128
3129         backend = E_CAL_BACKEND (cbdav);
3130         cclone = icalcomponent_new_clone (icalcomp);
3131
3132         /* Remove all inline attachments first */
3133         for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3134              p;
3135              p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3136                 icalattach *attach;
3137
3138                 attach = icalproperty_get_attach ((const icalproperty *) p);
3139                 if (!icalattach_get_is_url (attach))
3140                         to_remove = g_slist_prepend (to_remove, p);
3141                 else if (is_stored_on_server (cbdav, icalattach_get_url (attach)))
3142                         to_remove_after_download = g_slist_prepend (to_remove_after_download, p);
3143         }
3144         g_slist_foreach (to_remove, remove_property, icalcomp);
3145         g_slist_free (to_remove);
3146
3147         /* convert inline attachments to url attachments now */
3148         for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY), fileindex = 0;
3149              p;
3150              p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY), fileindex++) {
3151                 icalattach *attach;
3152                 gsize len = -1;
3153                 gchar *decoded = NULL;
3154                 gchar *basename, *local_filename;
3155
3156                 attach = icalproperty_get_attach ((const icalproperty *) p);
3157                 if (icalattach_get_is_url (attach)) {
3158                         const gchar *attach_url = icalattach_get_url (attach);
3159                         GError *error = NULL;
3160
3161                         if (!is_stored_on_server (cbdav, attach_url))
3162                                 continue;
3163
3164                         if (!caldav_server_download_attachment (cbdav, attach_url, &decoded, &len, &error)) {
3165                                 if (caldav_debug_show (DEBUG_ATTACHMENTS))
3166                                         g_print ("CalDAV::%s: Failed to download from a server: %s\n", G_STRFUNC, error ? error->message : "Unknown error");
3167                                 continue;
3168                         }
3169                 }
3170
3171                 basename = icalproperty_get_parameter_as_string_r (p, X_E_CALDAV_ATTACHMENT_NAME);
3172                 local_filename = e_cal_backend_create_cache_filename (backend, icalcomponent_get_uid (icalcomp), basename, fileindex);
3173                 g_free (basename);
3174
3175                 if (local_filename) {
3176                         GError *error = NULL;
3177
3178                         if (decoded == NULL) {
3179                                 gchar *content;
3180
3181                                 content = (gchar *) icalattach_get_data (attach);
3182                                 decoded = (gchar *) g_base64_decode (content, &len);
3183                         }
3184
3185                         if (g_file_set_contents (local_filename, decoded, len, &error)) {
3186                                 icalproperty *prop;
3187                                 gchar *url;
3188
3189                                 url = g_filename_to_uri (local_filename, NULL, NULL);
3190                                 attach = icalattach_new_from_url (url);
3191                                 prop = icalproperty_new_attach (attach);
3192                                 icalattach_unref (attach);
3193                                 icalcomponent_add_property (icalcomp, prop);
3194                                 g_free (url);
3195                         } else {
3196                                 g_warning ("%s\n", error->message);
3197                                 g_clear_error (&error);
3198                         }
3199
3200                         g_free (local_filename);
3201                 }
3202         }
3203
3204         icalcomponent_free (cclone);
3205
3206         g_slist_foreach (to_remove_after_download, remove_property, icalcomp);
3207         g_slist_free (to_remove_after_download);
3208 }
3209
3210 static void
3211 remove_files (const gchar *dir,
3212               const gchar *fileprefix)
3213 {
3214         GDir *d;
3215
3216         g_return_if_fail (dir != NULL);
3217         g_return_if_fail (fileprefix != NULL);
3218         g_return_if_fail (*fileprefix != '\0');
3219
3220         d = g_dir_open (dir, 0, NULL);
3221         if (d) {
3222                 const gchar *entry;
3223                 gint len = strlen (fileprefix);
3224
3225                 while ((entry = g_dir_read_name (d)) != NULL) {
3226                         if (entry && strncmp (entry, fileprefix, len) == 0) {
3227                                 gchar *path;
3228
3229                                 path = g_build_filename (dir, entry, NULL);
3230                                 if (!g_file_test (path, G_FILE_TEST_IS_DIR))
3231                                         g_unlink (path);
3232                                 g_free (path);
3233                         }
3234                 }
3235                 g_dir_close (d);
3236         }
3237 }
3238
3239 static void
3240 remove_cached_attachment (ECalBackendCalDAV *cbdav,
3241                           const gchar *uid)
3242 {
3243         ECalBackendCalDAVPrivate *priv;
3244         GSList *l;
3245         guint len;
3246         gchar *dir;
3247         gchar *fileprefix;
3248
3249         g_return_if_fail (cbdav != NULL);
3250         g_return_if_fail (uid != NULL);
3251
3252         priv = cbdav->priv;
3253         l = e_cal_backend_store_get_components_by_uid (priv->store, uid);
3254         len = g_slist_length (l);
3255         g_slist_foreach (l, (GFunc) g_object_unref, NULL);
3256         g_slist_free (l);
3257         if (len > 0)
3258                 return;
3259
3260         dir = e_cal_backend_create_cache_filename (E_CAL_BACKEND (cbdav), uid, "a", 0);
3261         if (!dir)
3262                 return;
3263
3264         fileprefix = g_strrstr (dir, G_DIR_SEPARATOR_S);
3265         if (fileprefix) {
3266                 *fileprefix = '\0';
3267                 fileprefix++;
3268
3269                 if (*fileprefix)
3270                         fileprefix[strlen (fileprefix) - 1] = '\0';
3271
3272                 remove_files (dir, fileprefix);
3273         }
3274
3275         g_free (dir);
3276 }
3277
3278 /* callback for icalcomponent_foreach_tzid */
3279 typedef struct {
3280         ECalBackendStore *store;
3281         icalcomponent *vcal_comp;
3282         icalcomponent *icalcomp;
3283 } ForeachTzidData;
3284
3285 static void
3286 add_timezone_cb (icalparameter *param,
3287                  gpointer data)
3288 {
3289         icaltimezone *tz;
3290         const gchar *tzid;
3291         icalcomponent *vtz_comp;
3292         ForeachTzidData *f_data = (ForeachTzidData *) data;
3293
3294         tzid = icalparameter_get_tzid (param);
3295         if (!tzid)
3296                 return;
3297
3298         tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
3299         if (tz)
3300                 return;
3301
3302         tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
3303         if (!tz) {
3304                 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3305                 if (!tz)
3306                         tz = (icaltimezone *) e_cal_backend_store_get_timezone (f_data->store, tzid);
3307                 if (!tz)
3308                         return;
3309         }
3310
3311         vtz_comp = icaltimezone_get_component (tz);
3312         if (!vtz_comp)
3313                 return;
3314
3315         icalcomponent_add_component (f_data->vcal_comp,
3316                                      icalcomponent_new_clone (vtz_comp));
3317 }
3318
3319 static void
3320 add_timezones_from_component (ECalBackendCalDAV *cbdav,
3321                               icalcomponent *vcal_comp,
3322                               icalcomponent *icalcomp)
3323 {
3324         ForeachTzidData f_data;
3325         ECalBackendCalDAVPrivate *priv;
3326
3327         g_return_if_fail (cbdav != NULL);
3328         g_return_if_fail (vcal_comp != NULL);
3329         g_return_if_fail (icalcomp != NULL);
3330
3331         priv  = cbdav->priv;
3332
3333         f_data.store = priv->store;
3334         f_data.vcal_comp = vcal_comp;
3335         f_data.icalcomp = icalcomp;
3336
3337         icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
3338 }
3339
3340 /* also removes X-EVOLUTION-CALDAV from all the components */
3341 static gchar *
3342 pack_cobj (ECalBackendCalDAV *cbdav,
3343            icalcomponent *icomp)
3344 {
3345         icalcomponent *calcomp;
3346         gchar          *objstr;
3347
3348         if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
3349                 icalcomponent *cclone;
3350
3351                 calcomp = e_cal_util_new_top_level ();
3352
3353                 cclone = icalcomponent_new_clone (icomp);
3354                 strip_unneeded_x_props (cclone);
3355                 convert_to_inline_attachment (cbdav, cclone);
3356                 icalcomponent_add_component (calcomp, cclone);
3357                 add_timezones_from_component (cbdav, calcomp, cclone);
3358         } else {
3359                 icalcomponent *subcomp;
3360                 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3361
3362                 calcomp = icalcomponent_new_clone (icomp);
3363                 for (subcomp = icalcomponent_get_first_component (calcomp, my_kind);
3364                      subcomp;
3365                      subcomp = icalcomponent_get_next_component (calcomp, my_kind)) {
3366                         strip_unneeded_x_props (subcomp);
3367                         convert_to_inline_attachment (cbdav, subcomp);
3368                         add_timezones_from_component (cbdav, calcomp, subcomp);
3369                 }
3370         }
3371
3372         objstr = icalcomponent_as_ical_string_r (calcomp);
3373         icalcomponent_free (calcomp);
3374
3375         g_assert (objstr);
3376
3377         return objstr;
3378
3379 }
3380
3381 static void
3382 sanitize_component (ECalBackend *cb,
3383                     ECalComponent *comp)
3384 {
3385         ECalComponentDateTime dt;
3386         icaltimezone *zone;
3387
3388         /* Check dtstart, dtend and due's timezone, and convert it to local
3389          * default timezone if the timezone is not in our builtin timezone
3390          * list */
3391         e_cal_component_get_dtstart (comp, &dt);
3392         if (dt.value && dt.tzid) {
3393                 zone = caldav_internal_get_timezone (cb, dt.tzid);
3394                 if (!zone) {
3395                         g_free ((gchar *) dt.tzid);
3396                         dt.tzid = g_strdup ("UTC");
3397                         e_cal_component_set_dtstart (comp, &dt);
3398                 }
3399         }
3400         e_cal_component_free_datetime (&dt);
3401
3402         e_cal_component_get_dtend (comp, &dt);
3403         if (dt.value && dt.tzid) {
3404                 zone = caldav_internal_get_timezone (cb, dt.tzid);
3405                 if (!zone) {
3406                         g_free ((gchar *) dt.tzid);
3407                         dt.tzid = g_strdup ("UTC");
3408                         e_cal_component_set_dtend (comp, &dt);
3409                 }
3410         }
3411         e_cal_component_free_datetime (&dt);
3412
3413         e_cal_component_get_due (comp, &dt);
3414         if (dt.value && dt.tzid) {
3415                 zone = caldav_internal_get_timezone (cb, dt.tzid);
3416                 if (!zone) {
3417                         g_free ((gchar *) dt.tzid);
3418                         dt.tzid = g_strdup ("UTC");
3419                         e_cal_component_set_due (comp, &dt);
3420                 }
3421         }
3422         e_cal_component_free_datetime (&dt);
3423         e_cal_component_abort_sequence (comp);
3424 }
3425
3426 static gboolean
3427 cache_contains (ECalBackendCalDAV *cbdav,
3428                 const gchar *uid,
3429                 const gchar *rid)
3430 {
3431         ECalBackendCalDAVPrivate *priv;
3432         gboolean res;
3433         ECalComponent *comp;
3434
3435         g_return_val_if_fail (cbdav != NULL, FALSE);
3436         g_return_val_if_fail (uid != NULL, FALSE);
3437
3438         priv  = cbdav->priv;
3439         g_return_val_if_fail (priv != NULL && priv->store != NULL, FALSE);
3440
3441         comp = e_cal_backend_store_get_component (priv->store, uid, rid);
3442         res = comp != NULL;
3443
3444         if (comp)
3445                 g_object_unref (comp);
3446
3447         return res;
3448 }
3449
3450 /* Returns subcomponent of icalcomp, which is a master object, or icalcomp itself, if it's not a VCALENDAR;
3451  * Do not free returned pointer, it'll be freed together with the icalcomp.
3452 */
3453 static icalcomponent *
3454 get_master_comp (ECalBackendCalDAV *cbdav,
3455                  icalcomponent *icalcomp)
3456 {
3457         icalcomponent *master = icalcomp;
3458
3459         g_return_val_if_fail (cbdav != NULL, NULL);
3460         g_return_val_if_fail (icalcomp != NULL, NULL);
3461
3462         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3463                 icalcomponent *subcomp;
3464                 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3465
3466                 master = NULL;
3467
3468                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3469                      subcomp;
3470                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3471                         struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3472
3473                         if (icaltime_is_null_time (sub_rid)) {
3474                                 master = subcomp;
3475                                 break;
3476                         }
3477                 }
3478         }
3479
3480         return master;
3481 }
3482
3483 static gboolean
3484 remove_instance (ECalBackendCalDAV *cbdav,
3485                  icalcomponent *icalcomp,
3486                  struct icaltimetype rid,
3487                  CalObjModType mod,
3488                  gboolean also_exdate)
3489 {
3490         icalcomponent *master = icalcomp;
3491         gboolean res = FALSE;
3492
3493         g_return_val_if_fail (icalcomp != NULL, res);
3494         g_return_val_if_fail (!icaltime_is_null_time (rid), res);
3495
3496         /* remove an instance only */
3497         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3498                 icalcomponent *subcomp;
3499                 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3500                 gint left = 0;
3501                 gboolean start_first = FALSE;
3502
3503                 master = NULL;
3504
3505                 /* remove old instance first */
3506                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3507                      subcomp;
3508                      subcomp = start_first ? icalcomponent_get_first_component (icalcomp, my_kind) : icalcomponent_get_next_component (icalcomp, my_kind)) {
3509                         struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3510
3511                         start_first = FALSE;
3512
3513                         if (icaltime_is_null_time (sub_rid)) {
3514                                 master = subcomp;
3515                                 left++;
3516                         } else if (icaltime_compare (sub_rid, rid) == 0) {
3517                                 icalcomponent_remove_component (icalcomp, subcomp);
3518                                 icalcomponent_free (subcomp);
3519                                 if (master) {
3520                                         break;
3521                                 } else {
3522                                         /* either no master or master not as the first component, thus rescan */
3523                                         left = 0;
3524                                         start_first = TRUE;
3525                                 }
3526                         } else {
3527                                 left++;
3528                         }
3529                 }
3530
3531                 /* whether left at least one instance or a master object */
3532                 res = left > 0;
3533         } else {
3534                 res = TRUE;
3535         }
3536
3537         if (master && also_exdate) {
3538                 e_cal_util_remove_instances (master, rid, mod);
3539         }
3540
3541         return res;
3542 }
3543
3544 static icalcomponent *
3545 replace_master (ECalBackendCalDAV *cbdav,
3546                 icalcomponent *old_comp,
3547                 icalcomponent *new_master)
3548 {
3549         icalcomponent *old_master;
3550         if (icalcomponent_isa (old_comp) != ICAL_VCALENDAR_COMPONENT) {
3551                 icalcomponent_free (old_comp);
3552                 return new_master;
3553         }
3554
3555         old_master = get_master_comp (cbdav, old_comp);
3556         if (!old_master) {
3557                 /* no master, strange */
3558                 icalcomponent_free (new_master);
3559         } else {
3560                 icalcomponent_remove_component (old_comp, old_master);
3561                 icalcomponent_free (old_master);
3562
3563                 icalcomponent_add_component (old_comp, new_master);
3564         }
3565
3566         return old_comp;
3567 }
3568
3569 /* the resulting component should be unreffed when done with it;
3570    the fallback_comp is cloned, if used */
3571 static ECalComponent *
3572 get_ecalcomp_master_from_cache_or_fallback (ECalBackendCalDAV *cbdav,
3573                                             const gchar *uid,
3574                                             const gchar *rid,
3575                                             ECalComponent *fallback_comp)
3576 {
3577         ECalComponent *comp = NULL;
3578         icalcomponent *icalcomp;
3579
3580         g_return_val_if_fail (cbdav != NULL, NULL);
3581         g_return_val_if_fail (uid != NULL, NULL);
3582
3583         icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
3584         if (icalcomp) {
3585                 icalcomponent *master = get_master_comp (cbdav, icalcomp);
3586
3587                 if (master) {
3588                         comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3589                 }
3590
3591                 icalcomponent_free (icalcomp);
3592         }
3593
3594         if (!comp && fallback_comp)
3595                 comp = e_cal_component_clone (fallback_comp);
3596
3597         return comp;
3598 }
3599
3600 /* a busy_lock is supposed to be locked already, when calling this function */
3601 static void
3602 do_create_object (ECalBackendCalDAV *cbdav,
3603                   const gchar *in_calobj,
3604                   gchar **uid,
3605                   ECalComponent **new_component,
3606                   GError **perror)
3607 {
3608         ECalComponent            *comp;
3609         gboolean                  online, did_put = FALSE;
3610         struct icaltimetype current;
3611         icalcomponent *icalcomp;
3612         const gchar *comp_uid;
3613
3614         if (!check_state (cbdav, &online, perror))
3615                 return;
3616
3617         comp = e_cal_component_new_from_string (in_calobj);
3618
3619         if (comp == NULL) {
3620                 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3621                 return;
3622         }
3623
3624         icalcomp = e_cal_component_get_icalcomponent (comp);
3625         if (icalcomp == NULL) {
3626                 g_object_unref (comp);
3627                 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3628                 return;
3629         }
3630
3631         comp_uid = icalcomponent_get_uid (icalcomp);
3632         if (!comp_uid) {
3633                 gchar *new_uid;
3634
3635                 new_uid = e_cal_component_gen_uid ();
3636                 if (!new_uid) {
3637                         g_object_unref (comp);
3638                         g_propagate_error (perror, EDC_ERROR (InvalidObject));
3639                         return;
3640                 }
3641
3642                 icalcomponent_set_uid (icalcomp, new_uid);
3643                 comp_uid = icalcomponent_get_uid (icalcomp);
3644
3645                 g_free (new_uid);
3646         }
3647
3648         /* check the object is not in our cache */
3649         if (cache_contains (cbdav, comp_uid, NULL)) {
3650                 g_object_unref (comp);
3651                 g_propagate_error (perror, EDC_ERROR (ObjectIdAlreadyExists));
3652                 return;
3653         }
3654
3655         /* Set the created and last modified times on the component */
3656         current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3657         e_cal_component_set_created (comp, &current);
3658         e_cal_component_set_last_modified (comp, &current);
3659
3660         /* sanitize the component*/
3661         sanitize_component ((ECalBackend *) cbdav, comp);
3662
3663         if (online) {
3664                 CalDAVObject object;
3665
3666                 object.href  = ecalcomp_gen_href (comp);
3667                 object.etag  = NULL;
3668                 object.cdata = pack_cobj (cbdav, icalcomp);
3669
3670                 did_put = caldav_server_put_object (cbdav, &object, icalcomp, perror);
3671
3672                 caldav_object_free (&object, FALSE);
3673         } else {
3674                 /* mark component as out of synch */
3675                 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
3676         }
3677
3678         if (did_put) {
3679                 if (uid)
3680                         *uid = g_strdup (comp_uid);
3681
3682                 if (new_component)
3683                         *new_component = get_ecalcomp_master_from_cache_or_fallback (cbdav, comp_uid, NULL, comp);
3684         }
3685
3686         g_object_unref (comp);
3687 }
3688
3689 /* a busy_lock is supposed to be locked already, when calling this function */
3690 static void
3691 do_modify_object (ECalBackendCalDAV *cbdav,
3692                   const gchar *calobj,
3693                   CalObjModType mod,
3694                   ECalComponent **old_component,
3695                   ECalComponent **new_component,
3696                   GError **error)
3697 {
3698         ECalBackendCalDAVPrivate *priv;
3699         ECalComponent            *comp;
3700         icalcomponent            *cache_comp;
3701         gboolean                  online, did_put = FALSE;
3702         ECalComponentId          *id;
3703         struct icaltimetype current;
3704         gchar *href = NULL, *etag = NULL;
3705
3706         priv  = cbdav->priv;
3707
3708         if (new_component)
3709                 *new_component = NULL;
3710
3711         if (!check_state (cbdav, &online, error))
3712                 return;
3713
3714         comp = e_cal_component_new_from_string (calobj);
3715
3716         if (comp == NULL) {
3717                 g_propagate_error (error, EDC_ERROR (InvalidObject));
3718                 return;
3719         }
3720
3721         if (!e_cal_component_get_icalcomponent (comp) ||
3722             icalcomponent_isa (e_cal_component_get_icalcomponent (comp)) != e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
3723                 g_object_unref (comp);
3724                 g_propagate_error (error, EDC_ERROR (InvalidObject));
3725                 return;
3726         }
3727
3728         /* Set the last modified time on the component */
3729         current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3730         e_cal_component_set_last_modified (comp, &current);
3731
3732         /* sanitize the component */
3733         sanitize_component ((ECalBackend *) cbdav, comp);
3734
3735         id = e_cal_component_get_id (comp);
3736         e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
3737
3738         /* fetch full component from cache, it will be pushed to the server */
3739         cache_comp = get_comp_from_cache (cbdav, id->uid, NULL, &href, &etag);
3740
3741         if (cache_comp == NULL) {
3742                 e_cal_component_free_id (id);
3743                 g_object_unref (comp);
3744                 g_free (href);
3745                 g_free (etag);
3746                 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
3747                 return;
3748         }
3749
3750         if (!online) {
3751                 /* mark component as out of synch */
3752                 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3753         }
3754
3755         if (old_component) {
3756                 *old_component = NULL;
3757
3758                 if (e_cal_component_is_instance (comp)) {
3759                         /* set detached instance as the old object, if any */
3760                         ECalComponent *old_instance = e_cal_backend_store_get_component (priv->store, id->uid, id->rid);
3761
3762                         /* This will give a reference to 'old_component' */
3763                         if (old_instance) {
3764                                 *old_component = e_cal_component_clone (old_instance);
3765                                 g_object_unref (old_instance);
3766                         }
3767                 }
3768
3769                 if (!*old_component) {
3770                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
3771
3772                         if (master) {
3773                                 /* set full component as the old object */
3774                                 *old_component = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3775                         }
3776                 }
3777         }
3778
3779         switch (mod) {
3780         case CALOBJ_MOD_ONLY_THIS:
3781         case CALOBJ_MOD_THIS:
3782                 if (e_cal_component_is_instance (comp)) {
3783                         icalcomponent *new_comp = e_cal_component_get_icalcomponent (comp);
3784
3785                         /* new object is only this instance */
3786                         if (new_component)
3787                                 *new_component = e_cal_component_clone (comp);
3788
3789                         /* add the detached instance */
3790                         if (icalcomponent_isa (cache_comp) == ICAL_VCALENDAR_COMPONENT) {
3791                                 /* do not modify the EXDATE, as the component will be put back */
3792                                 remove_instance (cbdav, cache_comp, icalcomponent_get_recurrenceid (new_comp), mod, FALSE);
3793                         } else {
3794                                 /* this is only a master object, thus make is a VCALENDAR component */
3795                                 icalcomponent *icomp;
3796
3797                                 icomp = e_cal_util_new_top_level ();
3798                                 icalcomponent_add_component (icomp, cache_comp);
3799
3800                                 /* no need to free the cache_comp, as it is inside icomp */
3801                                 cache_comp = icomp;
3802                         }
3803
3804                         if (cache_comp && priv->is_google) {
3805                                 icalcomponent_set_sequence (cache_comp, icalcomponent_get_sequence (cache_comp) + 1);
3806                                 icalcomponent_set_sequence (new_comp, icalcomponent_get_sequence (new_comp) + 1);
3807                         }
3808
3809                         /* add the detached instance finally */
3810                         icalcomponent_add_component (cache_comp, icalcomponent_new_clone (new_comp));
3811                 } else {
3812                         cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3813                 }
3814                 break;
3815         case CALOBJ_MOD_ALL:
3816                 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3817                 break;
3818         case CALOBJ_MOD_THISANDPRIOR:
3819         case CALOBJ_MOD_THISANDFUTURE:
3820                 break;
3821         }
3822
3823         if (online) {
3824                 CalDAVObject object;
3825
3826                 object.href  = href;
3827                 object.etag  = etag;
3828                 object.cdata = pack_cobj (cbdav, cache_comp);
3829
3830                 did_put = caldav_server_put_object (cbdav, &object, cache_comp, error);
3831
3832                 caldav_object_free (&object, FALSE);
3833                 href = NULL;
3834                 etag = NULL;
3835         } else {
3836                 /* mark component as out of synch */
3837                 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3838         }
3839
3840         if (did_put) {
3841                 if (new_component && !*new_component) {
3842                         /* read the comp from cache again, as some servers can modify it on put */
3843                         *new_component = get_ecalcomp_master_from_cache_or_fallback (cbdav, id->uid, id->rid, NULL);
3844
3845                         g_warn_if_fail (*new_component != NULL);
3846                 }
3847         }
3848
3849         e_cal_component_free_id (id);
3850         icalcomponent_free (cache_comp);
3851         g_object_unref (comp);
3852         g_free (href);
3853         g_free (etag);
3854 }
3855
3856 /* a busy_lock is supposed to be locked already, when calling this function */
3857 static void
3858 do_remove_object (ECalBackendCalDAV *cbdav,
3859                   const gchar *uid,
3860                   const gchar *rid,
3861                   CalObjModType mod,
3862                   ECalComponent **old_component,
3863                   ECalComponent **new_component,
3864                   GError **perror)
3865 {
3866         ECalBackendCalDAVPrivate *priv;
3867         icalcomponent            *cache_comp;
3868         gboolean                  online;
3869         gchar *href = NULL, *etag = NULL;
3870
3871         priv  = cbdav->priv;
3872
3873         if (new_component)
3874                 *new_component = NULL;
3875
3876         if (!check_state (cbdav, &online, perror))
3877                 return;
3878
3879         cache_comp = get_comp_from_cache (cbdav, uid, NULL, &href, &etag);
3880
3881         if (cache_comp == NULL) {
3882                 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
3883                 return;
3884         }
3885
3886         if (old_component) {
3887                 ECalComponent *old = e_cal_backend_store_get_component (priv->store, uid, rid);
3888
3889                 if (old) {
3890                         *old_component = e_cal_component_clone (old);
3891                         g_object_unref (old);
3892                 } else {
3893                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
3894
3895                         if (master) {
3896                                 *old_component = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3897                         }
3898                 }
3899         }
3900
3901         switch (mod) {
3902         case CALOBJ_MOD_ONLY_THIS:
3903         case CALOBJ_MOD_THIS:
3904                 if (rid && *rid) {
3905                         /* remove one instance from the component */
3906                         if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, mod != CALOBJ_MOD_ONLY_THIS)) {
3907                                 if (new_component) {
3908                                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
3909
3910                                         if (master)
3911                                                 *new_component = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3912                                 }
3913                         } else {
3914                                 /* this was the last instance, thus delete whole component */
3915                                 rid = NULL;
3916                                 remove_comp_from_cache (cbdav, uid, NULL);
3917                         }
3918                 } else {
3919                         /* remove whole object */
3920                         remove_comp_from_cache (cbdav, uid, NULL);
3921                 }
3922                 break;
3923         case CALOBJ_MOD_ALL:
3924                 remove_comp_from_cache (cbdav, uid, NULL);
3925                 break;
3926         case CALOBJ_MOD_THISANDPRIOR:
3927         case CALOBJ_MOD_THISANDFUTURE:
3928                 break;
3929         }
3930
3931         if (online) {
3932                 CalDAVObject caldav_object;
3933
3934                 caldav_object.href  = href;
3935                 caldav_object.etag  = etag;
3936                 caldav_object.cdata = NULL;
3937
3938                 if (mod == CALOBJ_MOD_THIS && rid && *rid) {
3939                         caldav_object.cdata = pack_cobj (cbdav, cache_comp);
3940
3941                         caldav_server_put_object (cbdav, &caldav_object, cache_comp, perror);
3942                 } else
3943                         caldav_server_delete_object (cbdav, &caldav_object, perror);
3944
3945                 caldav_object_free (&caldav_object, FALSE);
3946                 href = NULL;
3947                 etag = NULL;
3948         } else {
3949                 /* mark component as out of synch */
3950                 /*if (mod == CALOBJ_MOD_THIS && rid && *rid)
3951                         ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_MODIFIED);
3952                 else
3953                         ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_DELETED);*/
3954         }
3955         remove_cached_attachment (cbdav, uid);
3956
3957         icalcomponent_free (cache_comp);
3958         g_free (href);
3959         g_free (etag);
3960 }
3961
3962 static void
3963 extract_objects (icalcomponent *icomp,
3964                  icalcomponent_kind ekind,
3965                  GSList **objects,
3966                  GError **error)
3967 {
3968         icalcomponent         *scomp;
3969         icalcomponent_kind     kind;
3970
3971         e_return_data_cal_error_if_fail (icomp, InvalidArg);
3972         e_return_data_cal_error_if_fail (objects, InvalidArg);
3973
3974         kind = icalcomponent_isa (icomp);
3975
3976         if (kind == ekind) {
3977                 *objects = g_slist_prepend (NULL, icomp);
3978                 return;
3979         }
3980
3981         if (kind != ICAL_VCALENDAR_COMPONENT) {
3982                 g_propagate_error (error, EDC_ERROR (InvalidObject));
3983                 return;
3984         }
3985
3986         *objects = NULL;
3987         scomp = icalcomponent_get_first_component (icomp,
3988                                                    ekind);
3989
3990         while (scomp) {
3991                 /* Remove components from toplevel here */
3992                 *objects = g_slist_prepend (*objects, scomp);
3993                 icalcomponent_remove_component (icomp, scomp);
3994
3995                 scomp = icalcomponent_get_next_component (icomp, ekind);
3996         }
3997 }
3998
3999 static gboolean
4000 extract_timezones (ECalBackendCalDAV *cbdav,
4001                    icalcomponent *icomp)
4002 {
4003         ECalBackendCalDAVPrivate *priv;
4004         GSList *timezones = NULL, *iter;
4005         icaltimezone *zone;
4006         GError *err = NULL;
4007
4008         g_return_val_if_fail (cbdav != NULL, FALSE);
4009         g_return_val_if_fail (icomp != NULL, FALSE);
4010
4011         extract_objects (icomp, ICAL_VTIMEZONE_COMPONENT, &timezones, &err);
4012         if (err) {
4013                 g_error_free (err);
4014                 return FALSE;
4015         }
4016
4017         priv = cbdav->priv;
4018
4019         zone = icaltimezone_new ();
4020         for (iter = timezones; iter; iter = iter->next) {
4021                 if (icaltimezone_set_component (zone, iter->data)) {
4022                         e_cal_backend_store_put_timezone (priv->store, zone);
4023                 } else {
4024                         icalcomponent_free (iter->data);
4025                 }
4026         }
4027
4028         icaltimezone_free (zone, TRUE);
4029         g_slist_free (timezones);
4030
4031         return TRUE;
4032 }
4033
4034 static void
4035 process_object (ECalBackendCalDAV *cbdav,
4036                 ECalComponent *ecomp,
4037                 gboolean online,
4038                 icalproperty_method method,
4039                 GError **error)
4040 {
4041         ECalBackend              *backend;
4042         struct icaltimetype       now;
4043         gchar *new_obj_str;
4044         gboolean is_declined, is_in_cache;
4045         CalObjModType mod;
4046         ECalComponentId *id = e_cal_component_get_id (ecomp);
4047         GError *err = NULL;
4048
4049         backend = E_CAL_BACKEND (cbdav);
4050
4051         e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
4052
4053         /* ctime, mtime */
4054         now = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
4055         e_cal_component_set_created (ecomp, &now);
4056         e_cal_component_set_last_modified (ecomp, &now);
4057
4058         /* just to check whether component exists in a cache */
4059         is_in_cache = cache_contains (cbdav, id->uid, NULL) || cache_contains (cbdav, id->uid, id->rid);
4060
4061         new_obj_str = e_cal_component_get_as_string (ecomp);
4062         mod = e_cal_component_is_instance (ecomp) ? CALOBJ_MOD_THIS : CALOBJ_MOD_ALL;
4063
4064         switch (method) {
4065         case ICAL_METHOD_PUBLISH:
4066         case ICAL_METHOD_REQUEST:
4067         case ICAL_METHOD_REPLY:
4068                 is_declined = e_cal_backend_user_declined (e_cal_component_get_icalcomponent (ecomp));
4069                 if (is_in_cache) {
4070                         if (!is_declined) {
4071                                 ECalComponent *new_component = NULL, *old_component = NULL;
4072
4073                                 do_modify_object (cbdav, new_obj_str, mod,
4074                                                   &old_component, &new_component, &err);
4075                                 if (!err) {
4076                                         if (!old_component)
4077                                                 e_cal_backend_notify_component_created (backend, new_component);
4078                                         else
4079                                                 e_cal_backend_notify_component_modified (backend, old_component, new_component);
4080                                 }
4081
4082                                 if (new_component)
4083                                         g_object_unref (new_component);
4084                                 if (old_component)
4085                                         g_object_unref (old_component);
4086                         } else {
4087                                 ECalComponent *new_component = NULL, *old_component = NULL;
4088
4089                                 do_remove_object (cbdav, id->uid, id->rid, mod, &old_component, &new_component, &err);
4090                                 if (!err) {
4091                                         if (new_component) {
4092                                                 e_cal_backend_notify_component_modified (backend, old_component, new_component);
4093                                         } else {
4094                                                 e_cal_backend_notify_component_removed (backend, id, old_component, NULL);
4095                                         }
4096                                 }
4097
4098                                 if (new_component)
4099                                         g_object_unref (new_component);
4100                                 if (old_component)
4101                                         g_object_unref (old_component);
4102                         }
4103                 } else if (!is_declined) {
4104                         ECalComponent *new_component = NULL;
4105
4106                         do_create_object (cbdav, new_obj_str, NULL, &new_component, &err);
4107
4108                         if (!err) {
4109                                 e_cal_backend_notify_component_created (backend, new_component);
4110                         }
4111
4112                         if (new_component)
4113                                 g_object_unref (new_component);
4114
4115                 }
4116                 break;
4117         case ICAL_METHOD_CANCEL:
4118                 if (is_in_cache) {
4119                         ECalComponent *new_component = NULL, *old_component = NULL;
4120
4121                         do_remove_object (cbdav, id->uid, id->rid, CALOBJ_MOD_THIS, &old_component, &new_component, &err);
4122                         if (!err) {
4123                                 if (new_component) {
4124                                         e_cal_backend_notify_component_modified (backend, old_component, new_component);
4125                                 } else {
4126                                         e_cal_backend_notify_component_removed (backend, id, old_component, NULL);
4127                                 }
4128                         }
4129
4130                         if (new_component)
4131                                 g_object_unref (new_component);
4132                         if (old_component)
4133                                 g_object_unref (old_component);
4134                 } else {
4135                         err = EDC_ERROR (ObjectNotFound);
4136                 }
4137                 break;
4138
4139         default:
4140                 err = EDC_ERROR (UnsupportedMethod);
4141                 break;
4142         }
4143
4144         e_cal_component_free_id (id);
4145         g_free (new_obj_str);
4146
4147         if (err)
4148                 g_propagate_error (error, err);
4149 }
4150
4151 static void
4152 do_receive_objects (ECalBackendSync *backend,
4153                     EDataCal *cal,
4154                     GCancellable *cancellable,
4155                     const gchar *calobj,
4156                     GError **perror)
4157 {
4158         ECalBackendCalDAV        *cbdav;
4159         icalcomponent            *icomp;
4160         icalcomponent_kind        kind;
4161         icalproperty_method       tmethod;
4162         gboolean                  online;
4163         GSList                   *objects, *iter;
4164         GError *err = NULL;
4165
4166         cbdav = E_CAL_BACKEND_CALDAV (backend);
4167
4168         if (!check_state (cbdav, &online, perror))
4169                 return;
4170
4171         icomp = icalparser_parse_string (calobj);
4172
4173         /* Try to parse cal object string */
4174         if (icomp == NULL) {
4175                 g_propagate_error (perror, EDC_ERROR (InvalidObject));
4176                 return;
4177         }
4178
4179         kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
4180         extract_objects (icomp, kind, &objects, &err);
4181
4182         if (err) {
4183                 icalcomponent_free (icomp);
4184                 g_propagate_error (perror, err);
4185                 return;
4186         }
4187
4188         /* Extract optional timezone compnents */
4189         extract_timezones (cbdav, icomp);
4190
4191         tmethod = icalcomponent_get_method (icomp);
4192
4193         for (iter = objects; iter && !err; iter = iter->next) {
4194                 icalcomponent       *scomp;
4195                 ECalComponent       *ecomp;
4196                 icalproperty_method  method;
4197
4198                 scomp = (icalcomponent *) iter->data;
4199                 ecomp = e_cal_component_new ();
4200
4201                 e_cal_component_set_icalcomponent (ecomp, scomp);
4202
4203                 if (icalcomponent_get_first_property (scomp, ICAL_METHOD_PROPERTY)) {
4204                         method = icalcomponent_get_method (scomp);
4205                 } else {
4206                         method = tmethod;
4207                 }
4208
4209                 process_object (cbdav, ecomp, online, method, &err);
4210                 g_object_unref (ecomp);
4211         }
4212
4213         g_slist_free (objects);
4214
4215         icalcomponent_free (icomp);
4216
4217         if (err)
4218                 g_propagate_error (perror, err);
4219 }
4220
4221 #define caldav_busy_stub(_func_name, _params, _call_func, _call_params) \
4222 static void                                                             \
4223 _func_name _params                                                      \
4224 {                                                                       \
4225         ECalBackendCalDAV        *cbdav;                                \
4226         ECalBackendCalDAVPrivate *priv;                                 \
4227         SlaveCommand              old_slave_cmd;                        \
4228         gboolean                  was_slave_busy;                       \
4229                                                                         \
4230         cbdav = E_CAL_BACKEND_CALDAV (backend);                         \
4231         priv  = cbdav->priv;                                            \
4232                                                                         \
4233         /* this is done before locking */                               \
4234         old_slave_cmd = priv->slave_cmd;                                \
4235         was_slave_busy = priv->slave_busy;                              \
4236         if (was_slave_busy) {                                           \
4237                 /* let it pause its work and do our job */              \
4238                 priv->slave_cmd = SLAVE_SHOULD_SLEEP;                   \
4239         }                                                               \
4240                                                                         \
4241         g_mutex_lock (priv->busy_lock);                                 \
4242         _call_func _call_params;                                        \
4243                                                                         \
4244         /* this is done before unlocking */                             \
4245         if (was_slave_busy) {                                           \
4246                 priv->slave_cmd = old_slave_cmd;                        \
4247                 g_cond_signal (priv->cond);                             \
4248         }                                                               \
4249                                                                         \
4250         g_mutex_unlock (priv->busy_lock);                               \
4251 }
4252
4253 caldav_busy_stub (
4254         caldav_create_object,
4255                   (ECalBackendSync *backend,
4256                   EDataCal *cal,
4257                   GCancellable *cancellable,
4258                   const gchar *in_calobj,
4259                   gchar **uid,
4260                   ECalComponent **new_component,
4261                   GError **perror),
4262         do_create_object,
4263                   (cbdav,
4264                   in_calobj,
4265                   uid,
4266                   new_component,
4267                   perror))
4268
4269 caldav_busy_stub (
4270         caldav_modify_object,
4271                   (ECalBackendSync *backend,
4272                   EDataCal *cal,
4273                   GCancellable *cancellable,
4274                   const gchar *calobj,
4275                   CalObjModType mod,
4276                   ECalComponent **old_component,
4277                   ECalComponent **new_component,
4278                   GError **perror),
4279         do_modify_object,
4280                   (cbdav,
4281                   calobj,
4282                   mod,
4283                   old_component,
4284                   new_component,
4285                   perror))
4286
4287 caldav_busy_stub (
4288         caldav_remove_object,
4289                   (ECalBackendSync *backend,
4290                   EDataCal *cal,
4291                   GCancellable *cancellable,
4292                   const gchar *uid,
4293                   const gchar *rid,
4294                   CalObjModType mod,
4295                   ECalComponent **old_component,
4296                   ECalComponent **new_component,
4297                   GError **perror),
4298         do_remove_object,
4299                   (cbdav,
4300                   uid,
4301                   rid,
4302                   mod,
4303                   old_component,
4304                   new_component,
4305                   perror))
4306
4307 caldav_busy_stub (
4308         caldav_receive_objects,
4309                   (ECalBackendSync *backend,
4310                   EDataCal *cal,
4311                   GCancellable *cancellable,
4312                   const gchar *calobj,
4313                   GError **perror),
4314         do_receive_objects,
4315                   (backend,
4316                   cal,
4317                   cancellable,
4318                   calobj,
4319                   perror))
4320
4321 static void
4322 caldav_send_objects (ECalBackendSync *backend,
4323                      EDataCal *cal,
4324                      GCancellable *cancellable,
4325                      const gchar *calobj,
4326                      GSList **users,
4327                      gchar **modified_calobj,
4328                      GError **perror)
4329 {
4330         *users = NULL;
4331         *modified_calobj = g_strdup (calobj);
4332 }
4333
4334 static void
4335 caldav_get_object (ECalBackendSync *backend,
4336                    EDataCal *cal,
4337                    GCancellable *cancellable,
4338                    const gchar *uid,
4339                    const gchar *rid,
4340                    gchar **object,
4341                    GError **perror)
4342 {
4343         ECalBackendCalDAV        *cbdav;
4344         icalcomponent            *icalcomp;
4345
4346         cbdav = E_CAL_BACKEND_CALDAV (backend);
4347
4348         *object = NULL;
4349         icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
4350
4351         if (!icalcomp) {
4352                 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
4353                 return;
4354         }
4355
4356         *object = icalcomponent_as_ical_string_r (icalcomp);
4357         icalcomponent_free (icalcomp);
4358 }
4359
4360 static void
4361 caldav_add_timezone (ECalBackendSync *backend,
4362                      EDataCal *cal,
4363                      GCancellable *cancellable,
4364                      const gchar *tzobj,
4365                      GError **error)
4366 {
4367         icalcomponent *tz_comp;
4368         ECalBackendCalDAV *cbdav;
4369         ECalBackendCalDAVPrivate *priv;
4370
4371         cbdav = E_CAL_BACKEND_CALDAV (backend);
4372
4373         e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), InvalidArg);
4374         e_return_data_cal_error_if_fail (tzobj != NULL, InvalidArg);
4375
4376         priv = cbdav->priv;
4377
4378         tz_comp = icalparser_parse_string (tzobj);
4379         if (!tz_comp) {
4380                 g_propagate_error (error, EDC_ERROR (InvalidObject));
4381                 return;
4382         }
4383
4384         if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
4385                 icaltimezone *zone;
4386
4387                 zone = icaltimezone_new ();
4388                 icaltimezone_set_component (zone, tz_comp);
4389
4390                 e_cal_backend_store_put_timezone (priv->store, zone);
4391
4392                 icaltimezone_free (zone, TRUE);
4393         } else {
4394                 icalcomponent_free (tz_comp);
4395         }
4396 }
4397
4398 static void
4399 caldav_get_object_list (ECalBackendSync *backend,
4400                         EDataCal *cal,
4401                         GCancellable *cancellable,
4402                         const gchar *sexp_string,
4403                         GSList **objects,
4404                         GError **perror)
4405 {
4406         ECalBackendCalDAV        *cbdav;
4407         ECalBackendCalDAVPrivate *priv;
4408         ECalBackendSExp  *sexp;
4409         ECalBackend *bkend;
4410         gboolean                  do_search;
4411         GSList                   *list, *iter;
4412         time_t occur_start = -1, occur_end = -1;
4413         gboolean prunning_by_time;
4414
4415         cbdav = E_CAL_BACKEND_CALDAV (backend);
4416         priv  = cbdav->priv;
4417
4418         sexp = e_cal_backend_sexp_new (sexp_string);
4419
4420         if (sexp == NULL) {
4421                 g_propagate_error (perror, EDC_ERROR (InvalidQuery));
4422                 return;
4423         }
4424
4425         if (g_str_equal (sexp_string, "#t")) {
4426                 do_search = FALSE;
4427         } else {
4428                 do_search = TRUE;
4429         }
4430
4431         *objects = NULL;
4432
4433         prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp, &occur_start, &occur_end);
4434
4435         bkend = E_CAL_BACKEND (backend);
4436
4437         list = prunning_by_time ?
4438                 e_cal_backend_store_get_components_occuring_in_range (priv->store, occur_start, occur_end)
4439                 : e_cal_backend_store_get_components (priv->store);
4440
4441         bkend = E_CAL_BACKEND (backend);
4442
4443         for (iter = list; iter; iter = g_slist_next (iter)) {
4444                 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4445
4446                 if (!do_search ||
4447                     e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4448                         *objects = g_slist_prepend (*objects, e_cal_component_get_as_string (comp));
4449                 }
4450
4451                 g_object_unref (comp);
4452         }
4453
4454         g_object_unref (sexp);
4455         g_slist_free (list);
4456 }
4457
4458 static void
4459 caldav_start_view (ECalBackend *backend,
4460                    EDataCalView *query)
4461 {
4462         ECalBackendCalDAV        *cbdav;
4463         ECalBackendCalDAVPrivate *priv;
4464         ECalBackendSExp  *sexp;
4465         ECalBackend              *bkend;
4466         gboolean                  do_search;
4467         GSList                   *list, *iter;
4468         const gchar               *sexp_string;
4469         time_t occur_start = -1, occur_end = -1;
4470         gboolean prunning_by_time;
4471         cbdav = E_CAL_BACKEND_CALDAV (backend);
4472         priv  = cbdav->priv;
4473
4474         sexp_string = e_data_cal_view_get_text (query);
4475         sexp = e_cal_backend_sexp_new (sexp_string);
4476
4477         /* FIXME:check invalid sexp */
4478
4479         if (g_str_equal (sexp_string, "#t")) {
4480                 do_search = FALSE;
4481         } else {
4482                 do_search = TRUE;
4483         }
4484         prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp,
4485                                                                             &occur_start,
4486                                                                             &occur_end);
4487
4488         bkend = E_CAL_BACKEND (backend);
4489
4490         list = prunning_by_time ?
4491                 e_cal_backend_store_get_components_occuring_in_range (priv->store, occur_start, occur_end)
4492                 : e_cal_backend_store_get_components (priv->store);
4493
4494         for (iter = list; iter; iter = g_slist_next (iter)) {
4495                 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4496
4497                 if (!do_search ||
4498                     e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4499                         e_data_cal_view_notify_components_added_1 (query, comp);
4500                 }
4501
4502                 g_object_unref (comp);
4503         }
4504
4505         g_object_unref (sexp);
4506         g_slist_free (list);
4507
4508         e_data_cal_view_notify_complete (query, NULL /* Success */);
4509 }
4510
4511 static void
4512 caldav_get_free_busy (ECalBackendSync *backend,
4513                       EDataCal *cal,
4514                       GCancellable *cancellable,
4515                       const GSList *users,
4516                       time_t start,
4517                       time_t end,
4518                       GSList **freebusy,
4519                       GError **error)
4520 {
4521         ECalBackendCalDAV *cbdav;
4522         ECalBackendCalDAVPrivate *priv;
4523         icalcomponent *icalcomp;
4524         ECalComponent *comp;
4525         ECalComponentDateTime dt;
4526         struct icaltimetype dtvalue;
4527         icaltimezone *utc;
4528         gchar *str, *usermail;
4529         const GSList *u;
4530         GSList *attendees = NULL, *to_free = NULL;
4531         GError *err = NULL;
4532
4533         cbdav = E_CAL_BACKEND_CALDAV (backend);
4534         priv  = cbdav->priv;
4535
4536         e_return_data_cal_error_if_fail (priv != NULL, InvalidArg);
4537         e_return_data_cal_error_if_fail (users != NULL, InvalidArg);
4538         e_return_data_cal_error_if_fail (freebusy != NULL, InvalidArg);
4539         e_return_data_cal_error_if_fail (start < end, InvalidArg);
4540
4541         if (!priv->calendar_schedule) {
4542                 g_propagate_error (error, EDC_ERROR_EX (OtherError, _("Calendar doesn't support Free/Busy")));
4543                 return;
4544         }
4545
4546         if (!priv->schedule_outbox_url) {
4547                 caldav_receive_schedule_outbox_url (cbdav);
4548                 if (!priv->schedule_outbox_url) {
4549                         priv->calendar_schedule = FALSE;
4550                         g_propagate_error (error, EDC_ERROR_EX (OtherError, "Schedule outbox url not found"));
4551                         return;
4552                 }
4553         }
4554
4555         comp = e_cal_component_new ();
4556         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_FREEBUSY);
4557
4558         str = e_cal_component_gen_uid ();
4559         e_cal_component_set_uid (comp, str);
4560         g_free (str);
4561
4562         utc = icaltimezone_get_utc_timezone ();
4563         dt.value = &dtvalue;
4564         dt.tzid = icaltimezone_get_tzid (utc);
4565
4566         dtvalue = icaltime_current_time_with_zone (utc);
4567         e_cal_component_set_dtstamp (comp, &dtvalue);
4568
4569         dtvalue = icaltime_from_timet_with_zone (start, FALSE, utc);
4570         e_cal_component_set_dtstart (comp, &dt);
4571
4572         dtvalue = icaltime_from_timet_with_zone (end, FALSE, utc);
4573         e_cal_component_set_dtend (comp, &dt);
4574
4575         usermail = get_usermail (E_CAL_BACKEND (backend));
4576         if (usermail && !*usermail) {
4577                 g_free (usermail);
4578                 usermail = NULL;
4579         }
4580
4581         if ((priv->credentials && e_credentials_has_key (priv->credentials, E_CREDENTIALS_KEY_USERNAME)) || usermail) {
4582                 ECalComponentOrganizer organizer = {NULL};
4583
4584                 organizer.value = usermail ? usermail : e_credentials_peek (priv->credentials, E_CREDENTIALS_KEY_USERNAME);
4585                 organizer.value = g_strconcat ("mailto:", organizer.value, NULL);
4586
4587                 e_cal_component_set_organizer (comp, &organizer);
4588
4589                 g_free ((gchar *) organizer.value);
4590         }
4591
4592         g_free (usermail);
4593
4594         for (u = users; u; u = u->next) {
4595                 ECalComponentAttendee *ca;
4596                 gchar *temp = g_strconcat ("mailto:", (const gchar *)u->data, NULL);
4597
4598                 ca = g_new0 (ECalComponentAttendee, 1);
4599
4600                 ca->value = temp;
4601                 ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
4602                 ca->status = ICAL_PARTSTAT_NEEDSACTION;
4603                 ca->role = ICAL_ROLE_CHAIR;
4604
4605                 to_free = g_slist_prepend (to_free, temp);
4606                 attendees = g_slist_append (attendees, ca);
4607         }
4608
4609         e_cal_component_set_attendee_list (comp, attendees);
4610
4611         g_slist_foreach (attendees, (GFunc) g_free, NULL);
4612         g_slist_free (attendees);
4613
4614         g_slist_foreach (to_free, (GFunc) g_free, NULL);
4615         g_slist_free (to_free);
4616
4617         e_cal_component_abort_sequence (comp);
4618
4619         /* put the free/busy request to a VCALENDAR */
4620         icalcomp = e_cal_util_new_top_level ();
4621         icalcomponent_set_method (icalcomp, ICAL_METHOD_REQUEST);
4622         icalcomponent_add_component (icalcomp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
4623
4624         str = icalcomponent_as_ical_string_r (icalcomp);
4625
4626         icalcomponent_free (icalcomp);
4627         g_object_unref (comp);
4628
4629         e_return_data_cal_error_if_fail (str != NULL, OtherError);
4630
4631         caldav_post_freebusy (cbdav, priv->schedule_outbox_url, &str, &err);
4632
4633         if (!err) {
4634                 /* parse returned xml */
4635                 xmlDocPtr doc;
4636
4637                 doc = xmlReadMemory (str, strlen (str), "response.xml", NULL, 0);
4638                 if (doc != NULL) {
4639                         xmlXPathContextPtr xpctx;
4640                         xmlXPathObjectPtr result;
4641
4642                         xpctx = xmlXPathNewContext (doc);
4643                         xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
4644                         xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
4645
4646                         result = xpath_eval (xpctx, "/C:schedule-response/C:response");
4647
4648                         if (result == NULL || result->type != XPATH_NODESET) {
4649                                 err = EDC_ERROR_EX (OtherError, "Unexpected result in schedule-response");
4650                         } else {
4651                                 gint i, n;
4652
4653                                 n = xmlXPathNodeSetGetLength (result->nodesetval);
4654                                 for (i = 0; i < n; i++) {
4655                                         gchar *tmp;
4656
4657                                         tmp = xp_object_get_string (xpath_eval (xpctx, "string(/C:schedule-response/C:response[%d]/C:calendar-data)", i + 1));
4658                                         if (tmp && *tmp) {
4659                                                 GSList *objects = NULL, *o;
4660
4661                                                 icalcomp = icalparser_parse_string (tmp);
4662                                                 if (icalcomp)
4663                                                         extract_objects (icalcomp, ICAL_VFREEBUSY_COMPONENT, &objects, &err);
4664                                                 if (icalcomp && !err) {
4665                                                         for (o = objects; o; o = o->next) {
4666                                                                 gchar *obj_str = icalcomponent_as_ical_string_r (o->data);
4667
4668                                                                 if (obj_str && *obj_str)
4669                                                                         *freebusy = g_slist_append (*freebusy, obj_str);
4670                                                                 else
4671                                                                         g_free (obj_str);
4672                                                         }
4673                                                 }
4674
4675                                                 g_slist_foreach (objects, (GFunc) icalcomponent_free, NULL);
4676                                                 g_slist_free (objects);
4677
4678                                                 if (icalcomp)
4679                                                         icalcomponent_free (icalcomp);
4680                                                 if (err)
4681                                                         g_error_free (err);
4682                                                 err = NULL;
4683                                         }
4684
4685                                         g_free (tmp);
4686                                 }
4687                         }
4688
4689                         if (result != NULL)
4690                                 xmlXPathFreeObject (result);
4691                         xmlXPathFreeContext (xpctx);
4692                         xmlFreeDoc (doc);
4693                 }
4694         }
4695
4696         g_free (str);
4697
4698         if (err)
4699                 g_propagate_error (error, err);
4700 }
4701
4702 static void
4703 caldav_notify_online_cb (ECalBackend *backend,
4704                          GParamSpec *pspec)
4705 {
4706         ECalBackendCalDAV        *cbdav;
4707         ECalBackendCalDAVPrivate *priv;
4708         gboolean online;
4709
4710         cbdav = E_CAL_BACKEND_CALDAV (backend);
4711         priv  = cbdav->priv;
4712
4713         /*g_mutex_lock (priv->busy_lock);*/
4714
4715         online = e_backend_get_online (E_BACKEND (backend));
4716
4717         if (!priv->loaded) {
4718                 e_cal_backend_notify_online (backend, online);
4719                 /*g_mutex_unlock (priv->busy_lock);*/
4720                 return;
4721         }
4722
4723         if (online) {
4724                 /* Wake up the slave thread */
4725                 priv->slave_cmd = SLAVE_SHOULD_WORK;
4726                 g_cond_signal (priv->cond);
4727         } else {
4728                 soup_session_abort (priv->session);
4729                 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
4730         }
4731
4732         e_cal_backend_notify_online (backend, online);
4733
4734         /*g_mutex_unlock (priv->busy_lock);*/
4735 }
4736
4737 static icaltimezone *
4738 caldav_internal_get_timezone (ECalBackend *backend,
4739                               const gchar *tzid)
4740 {
4741         icaltimezone *zone;
4742         ECalBackendCalDAV *cbdav;
4743         ECalBackendCalDAVPrivate *priv;
4744
4745         cbdav = E_CAL_BACKEND_CALDAV (backend);
4746         priv  = cbdav->priv;
4747         zone = NULL;
4748
4749         if (priv->store)
4750                 zone = (icaltimezone *) e_cal_backend_store_get_timezone (priv->store, tzid);
4751
4752         if (!zone && E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone)
4753                 zone = E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone (backend, tzid);
4754
4755         return zone;
4756 }
4757
4758 static void
4759 caldav_source_changed_cb (ESource *source,
4760                           ECalBackendCalDAV *cbdav)
4761 {
4762         ECalBackendCalDAVPrivate *priv;
4763         SlaveCommand old_slave_cmd;
4764         gboolean old_slave_busy;
4765
4766         g_return_if_fail (source != NULL);
4767         g_return_if_fail (cbdav != NULL);
4768
4769         priv = cbdav->priv;
4770         g_return_if_fail (priv != NULL);
4771
4772         old_slave_cmd = priv->slave_cmd;
4773         old_slave_busy = priv->slave_busy;
4774         if (old_slave_busy) {
4775                 priv->slave_cmd = SLAVE_SHOULD_SLEEP;
4776                 g_mutex_lock (priv->busy_lock);
4777         }
4778
4779         initialize_backend (cbdav, NULL);
4780
4781         /* always wakeup thread, even when it was sleeping */
4782         g_cond_signal (priv->cond);
4783
4784         if (old_slave_busy) {
4785                 priv->slave_cmd = old_slave_cmd;
4786                 g_mutex_unlock (priv->busy_lock);
4787         }
4788 }
4789
4790 /* ************************************************************************* */
4791 /* ***************************** GObject Foo ******************************* */
4792
4793 G_DEFINE_TYPE (ECalBackendCalDAV, e_cal_backend_caldav, E_TYPE_CAL_BACKEND_SYNC)
4794
4795 static void
4796 e_cal_backend_caldav_dispose (GObject *object)
4797 {
4798         ECalBackendCalDAV        *cbdav;
4799         ECalBackendCalDAVPrivate *priv;
4800         ESource *source;
4801
4802         cbdav = E_CAL_BACKEND_CALDAV (object);
4803         priv = cbdav->priv;
4804
4805         /* tell the slave to stop before acquiring a lock,
4806          * as it can work at the moment, and lock can be locked */
4807         priv->slave_cmd = SLAVE_SHOULD_DIE;
4808
4809         g_mutex_lock (priv->busy_lock);
4810
4811         if (priv->disposed) {
4812                 g_mutex_unlock (priv->busy_lock);
4813                 return;
4814         }
4815
4816         source = e_backend_get_source (E_BACKEND (cbdav));
4817         if (source)
4818                 g_signal_handlers_disconnect_by_func (G_OBJECT (source), caldav_source_changed_cb, cbdav);
4819
4820         /* stop the slave  */
4821         if (priv->synch_slave) {
4822                 g_cond_signal (priv->cond);
4823
4824                 /* wait until the slave died */
4825                 g_cond_wait (priv->slave_gone_cond, priv->busy_lock);
4826         }
4827
4828         g_object_unref (priv->session);
4829         g_object_unref (priv->proxy);
4830
4831         e_credentials_free (priv->credentials);
4832         priv->credentials = NULL;
4833
4834         g_free (priv->uri);
4835         g_free (priv->schedule_outbox_url);
4836
4837         if (priv->store != NULL) {
4838                 g_object_unref (priv->store);
4839         }
4840
4841         priv->disposed = TRUE;
4842         g_mutex_unlock (priv->busy_lock);
4843
4844         /* Chain up to parent's dispose() method. */
4845         G_OBJECT_CLASS (parent_class)->dispose (object);
4846 }
4847
4848 static void
4849 e_cal_backend_caldav_finalize (GObject *object)
4850 {
4851         ECalBackendCalDAV        *cbdav;
4852         ECalBackendCalDAVPrivate *priv;
4853
4854         cbdav = E_CAL_BACKEND_CALDAV (object);
4855         priv = cbdav->priv;
4856
4857         g_mutex_free (priv->busy_lock);
4858         g_cond_free (priv->cond);
4859         g_cond_free (priv->slave_gone_cond);
4860
4861         /* Chain up to parent's finalize() method. */
4862         G_OBJECT_CLASS (parent_class)->finalize (object);
4863 }
4864
4865 static void
4866 e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
4867 {
4868         cbdav->priv = G_TYPE_INSTANCE_GET_PRIVATE (
4869                 cbdav, E_TYPE_CAL_BACKEND_CALDAV, ECalBackendCalDAVPrivate);
4870
4871         cbdav->priv->session = soup_session_sync_new ();
4872         g_object_set (cbdav->priv->session, SOUP_SESSION_TIMEOUT, 90, NULL);
4873
4874         cbdav->priv->proxy = e_proxy_new ();
4875         e_proxy_setup_proxy (cbdav->priv->proxy);
4876         g_signal_connect (cbdav->priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), cbdav->priv);
4877
4878         if (G_UNLIKELY (caldav_debug_show (DEBUG_MESSAGE)))
4879                 caldav_debug_setup (cbdav->priv->session);
4880
4881         cbdav->priv->disposed = FALSE;
4882         cbdav->priv->loaded   = FALSE;
4883         cbdav->priv->opened = FALSE;
4884
4885         /* Thinks the 'getctag' extension is available the first time, but unset it when realizes it isn't. */
4886         cbdav->priv->ctag_supported = TRUE;
4887         cbdav->priv->ctag_to_store = NULL;
4888
4889         cbdav->priv->schedule_outbox_url = NULL;
4890
4891         cbdav->priv->is_google = FALSE;
4892
4893         cbdav->priv->busy_lock = g_mutex_new ();
4894         cbdav->priv->cond = g_cond_new ();
4895         cbdav->priv->slave_gone_cond = g_cond_new ();
4896
4897         /* Slave control ... */
4898         cbdav->priv->slave_cmd = SLAVE_SHOULD_SLEEP;
4899         cbdav->priv->slave_busy = FALSE;
4900         cbdav->priv->refresh_time.tv_usec = 0;
4901         cbdav->priv->refresh_time.tv_sec  = DEFAULT_REFRESH_TIME;
4902
4903         g_signal_connect (cbdav->priv->session, "authenticate",
4904                           G_CALLBACK (soup_authenticate), cbdav);
4905
4906         e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbdav), FALSE);
4907
4908         g_signal_connect (
4909                 cbdav, "notify::online",
4910                 G_CALLBACK (caldav_notify_online_cb), NULL);
4911 }
4912
4913 static void
4914 e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
4915 {
4916         GObjectClass *object_class;
4917         ECalBackendClass *backend_class;
4918         ECalBackendSyncClass *sync_class;
4919
4920         object_class = (GObjectClass *) class;
4921         backend_class = (ECalBackendClass *) class;
4922         sync_class = (ECalBackendSyncClass *) class;
4923
4924         caldav_debug_init ();
4925
4926         parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
4927         g_type_class_add_private (class, sizeof (ECalBackendCalDAVPrivate));
4928
4929         object_class->dispose  = e_cal_backend_caldav_dispose;
4930         object_class->finalize = e_cal_backend_caldav_finalize;
4931
4932         sync_class->get_backend_property_sync   = caldav_get_backend_property;
4933
4934         sync_class->open_sync                   = caldav_do_open;
4935         sync_class->authenticate_user_sync      = caldav_authenticate_user;
4936         sync_class->refresh_sync                = caldav_refresh;
4937         sync_class->remove_sync                 = caldav_remove;
4938
4939         sync_class->create_object_sync          = caldav_create_object;
4940         sync_class->modify_object_sync          = caldav_modify_object;
4941         sync_class->remove_object_sync          = caldav_remove_object;
4942
4943         sync_class->receive_objects_sync        = caldav_receive_objects;
4944         sync_class->send_objects_sync           = caldav_send_objects;
4945         sync_class->get_object_sync             = caldav_get_object;
4946         sync_class->get_object_list_sync        = caldav_get_object_list;
4947         sync_class->add_timezone_sync           = caldav_add_timezone;
4948         sync_class->get_free_busy_sync          = caldav_get_free_busy;
4949
4950         backend_class->start_view               = caldav_start_view;
4951
4952         backend_class->internal_get_timezone    = caldav_internal_get_timezone;
4953 }