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