0219b34e198e20801574f3091cce9cfc01c34847
[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 /* Returns whether calendar changed on the server. This works only when server
1192  * supports 'getctag' extension. */
1193 static gboolean
1194 check_calendar_changed_on_server (ECalBackendCalDAV *cbdav)
1195 {
1196         xmlOutputBufferPtr        buf;
1197         SoupMessage              *message;
1198         xmlDocPtr                 doc;
1199         xmlNodePtr                root, node;
1200         xmlNsPtr                  ns, nsdav;
1201         gboolean                  result = TRUE;
1202
1203         g_return_val_if_fail (cbdav != NULL, TRUE);
1204
1205         /* no support for 'getctag', thus update cache */
1206         if (!cbdav->priv->ctag_supported)
1207                 return TRUE;
1208
1209         /* Prepare the soup message */
1210         message = soup_message_new ("PROPFIND", cbdav->priv->uri);
1211         if (message == NULL)
1212                 return FALSE;
1213
1214         doc = xmlNewDoc ((xmlChar *) "1.0");
1215         root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1216         xmlDocSetRootElement (doc, root);
1217         nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1218         ns = xmlNewNs (root, (xmlChar *) "http://calendarserver.org/ns/", (xmlChar *) "CS");
1219
1220         node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1221         node = xmlNewTextChild (node, nsdav, (xmlChar *) "getctag", NULL);
1222         xmlSetNs (node, ns);
1223
1224         buf = xmlAllocOutputBuffer (NULL);
1225         xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1226         xmlOutputBufferFlush (buf);
1227
1228         soup_message_headers_append (message->request_headers,
1229                                      "User-Agent", "Evolution/" VERSION);
1230         soup_message_headers_append (message->request_headers,
1231                                      "Depth", "0");
1232
1233         soup_message_set_request (message,
1234                                   "application/xml",
1235                                   SOUP_MEMORY_COPY,
1236                                   (gchar *) buf->buffer->content,
1237                                   buf->buffer->use);
1238
1239         /* Send the request now */
1240         send_and_handle_redirection (cbdav->priv->session, message, NULL);
1241
1242         /* Clean up the memory */
1243         xmlOutputBufferClose (buf);
1244         xmlFreeDoc (doc);
1245
1246         /* Check the result */
1247         if (message->status_code == 401) {
1248                 caldav_authenticate (cbdav, TRUE, NULL, NULL);
1249         } else if (message->status_code != 207) {
1250                 /* does not support it, but report calendar changed to update cache */
1251                 cbdav->priv->ctag_supported = FALSE;
1252         } else {
1253                 gchar *ctag = NULL;
1254
1255                 if (parse_propfind_response (message, XPATH_GETCTAG_STATUS, XPATH_GETCTAG, &ctag)) {
1256                         const gchar *my_ctag;
1257
1258                         my_ctag = e_cal_backend_store_get_key_value (
1259                                 cbdav->priv->store, CALDAV_CTAG_KEY);
1260
1261                         if (ctag && my_ctag && g_str_equal (ctag, my_ctag)) {
1262                                 /* ctag is same, no change in the calendar */
1263                                 result = FALSE;
1264                         } else {
1265                                 /* do not store ctag now, do it rather after complete sync */
1266                                 g_free (cbdav->priv->ctag_to_store);
1267                                 cbdav->priv->ctag_to_store = ctag;
1268                                 ctag = NULL;
1269                         }
1270
1271                         g_free (ctag);
1272                 } else {
1273                         cbdav->priv->ctag_supported = FALSE;
1274                 }
1275         }
1276
1277         g_object_unref (message);
1278
1279         return result;
1280 }
1281
1282 /* only_hrefs is a list of requested objects to fetch; it has precedence from
1283  * start_time/end_time, which are used only when both positive.
1284  * Times are supposed to be in UTC, if set.
1285  */
1286 static gboolean
1287 caldav_server_list_objects (ECalBackendCalDAV *cbdav,
1288                             CalDAVObject **objs,
1289                             gint *len,
1290                             GSList *only_hrefs,
1291                             time_t start_time,
1292                             time_t end_time)
1293 {
1294         xmlOutputBufferPtr   buf;
1295         SoupMessage         *message;
1296         xmlNodePtr           node;
1297         xmlNodePtr           sn;
1298         xmlNodePtr           root;
1299         xmlDocPtr            doc;
1300         xmlNsPtr             nsdav;
1301         xmlNsPtr             nscd;
1302         gboolean             result;
1303
1304         /* Allocate the soup message */
1305         message = soup_message_new ("REPORT", cbdav->priv->uri);
1306         if (message == NULL)
1307                 return FALSE;
1308
1309         /* Maybe we should just do a g_strdup_printf here? */
1310         /* Prepare request body */
1311         doc = xmlNewDoc ((xmlChar *) "1.0");
1312         if (!only_hrefs)
1313                 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-query", NULL);
1314         else
1315                 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-multiget", NULL);
1316         nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1317         xmlSetNs (root, nscd);
1318         xmlDocSetRootElement (doc, root);
1319
1320         /* Add webdav tags */
1321         nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1322         node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1323         xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1324         if (only_hrefs) {
1325                 GSList *l;
1326
1327                 xmlNewTextChild (node, nscd, (xmlChar *) "calendar-data", NULL);
1328                 for (l = only_hrefs; l; l = l->next) {
1329                         if (l->data) {
1330                                 xmlNewTextChild (root, nsdav, (xmlChar *) "href", (xmlChar *) l->data);
1331                         }
1332                 }
1333         } else {
1334                 node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1335                 node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1336                 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1337
1338                 sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1339                 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
1340                         default:
1341                         case ICAL_VEVENT_COMPONENT:
1342                                 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1343                                 break;
1344                         case ICAL_VJOURNAL_COMPONENT:
1345                                 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VJOURNAL");
1346                                 break;
1347                         case ICAL_VTODO_COMPONENT:
1348                                 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VTODO");
1349                                 break;
1350                 }
1351
1352                 if (start_time > 0 || end_time > 0) {
1353                         gchar *tmp;
1354
1355                         sn = xmlNewTextChild (sn, nscd, (xmlChar *) "time-range", NULL);
1356
1357                         if (start_time > 0) {
1358                                 tmp = isodate_from_time_t (start_time);
1359                                 xmlSetProp (sn, (xmlChar *) "start", (xmlChar *) tmp);
1360                                 g_free (tmp);
1361                         }
1362
1363                         if (end_time > 0) {
1364                                 tmp = isodate_from_time_t (end_time);
1365                                 xmlSetProp (sn, (xmlChar *) "end", (xmlChar *) tmp);
1366                                 g_free (tmp);
1367                         }
1368                 }
1369         }
1370
1371         buf = xmlAllocOutputBuffer (NULL);
1372         xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1373         xmlOutputBufferFlush (buf);
1374
1375         /* Prepare the soup message */
1376         soup_message_headers_append (message->request_headers,
1377                                      "User-Agent", "Evolution/" VERSION);
1378         soup_message_headers_append (message->request_headers,
1379                                      "Depth", "1");
1380
1381         soup_message_set_request (message,
1382                                   "application/xml",
1383                                   SOUP_MEMORY_COPY,
1384                                   (gchar *) buf->buffer->content,
1385                                   buf->buffer->use);
1386
1387         /* Send the request now */
1388         send_and_handle_redirection (cbdav->priv->session, message, NULL);
1389
1390         /* Clean up the memory */
1391         xmlOutputBufferClose (buf);
1392         xmlFreeDoc (doc);
1393
1394         /* Check the result */
1395         if (message->status_code != 207) {
1396                 switch (message->status_code) {
1397                 case SOUP_STATUS_CANT_CONNECT:
1398                 case SOUP_STATUS_CANT_CONNECT_PROXY:
1399                         cbdav->priv->opened = FALSE;
1400                         update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
1401                         cbdav->priv->read_only = TRUE;
1402                         e_cal_backend_notify_readonly (
1403                                 E_CAL_BACKEND (cbdav), cbdav->priv->read_only);
1404                         break;
1405                 case 401:
1406                         caldav_authenticate (cbdav, TRUE, NULL, NULL);
1407                         break;
1408                 default:
1409                         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");
1410                         break;
1411                 }
1412
1413                 g_object_unref (message);
1414                 return FALSE;
1415         }
1416
1417         /* Parse the response body */
1418         result = parse_report_response (message, objs, len);
1419
1420         g_object_unref (message);
1421         return result;
1422 }
1423
1424 static gboolean
1425 caldav_server_download_attachment (ECalBackendCalDAV *cbdav,
1426                                    const gchar *attachment_uri,
1427                                    gchar **content,
1428                                    gsize *len,
1429                                    GError **error)
1430 {
1431         SoupMessage *message;
1432
1433         g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
1434         g_return_val_if_fail (attachment_uri != NULL, FALSE);
1435         g_return_val_if_fail (content != NULL, FALSE);
1436         g_return_val_if_fail (len != NULL, FALSE);
1437
1438         message = soup_message_new (SOUP_METHOD_GET, attachment_uri);
1439         if (message == NULL) {
1440                 g_propagate_error (error, EDC_ERROR (InvalidObject));
1441                 return FALSE;
1442         }
1443
1444         soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1445         send_and_handle_redirection (cbdav->priv->session, message, NULL);
1446
1447         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1448                 status_code_to_result (message, cbdav, FALSE, error);
1449
1450                 if (message->status_code == 401)
1451                         caldav_authenticate (cbdav, FALSE, NULL, NULL);
1452
1453                 g_object_unref (message);
1454                 return FALSE;
1455         }
1456
1457         *len = message->response_body->length;
1458         *content = g_memdup (message->response_body->data, *len);
1459
1460         g_object_unref (message);
1461
1462         return TRUE;
1463 }
1464
1465 static gboolean
1466 caldav_server_get_object (ECalBackendCalDAV *cbdav,
1467                           CalDAVObject *object,
1468                           GError **perror)
1469 {
1470         SoupMessage              *message;
1471         const gchar               *hdr;
1472         gchar                     *uri;
1473
1474         g_assert (object != NULL && object->href != NULL);
1475
1476         uri = caldav_generate_uri (cbdav, object->href);
1477         message = soup_message_new (SOUP_METHOD_GET, uri);
1478         if (message == NULL) {
1479                 g_free (uri);
1480                 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1481                 return FALSE;
1482         }
1483
1484         soup_message_headers_append (message->request_headers,
1485                                      "User-Agent", "Evolution/" VERSION);
1486
1487         send_and_handle_redirection (cbdav->priv->session, message, NULL);
1488
1489         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1490                 status_code_to_result (message, cbdav, FALSE, perror);
1491
1492                 if (message->status_code == 401)
1493                         caldav_authenticate (cbdav, FALSE, NULL, NULL);
1494                 else
1495                         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");
1496                 g_object_unref (message);
1497                 g_free (uri);
1498                 return FALSE;
1499         }
1500
1501         hdr = soup_message_headers_get (message->response_headers, "Content-Type");
1502
1503         if (hdr == NULL || g_ascii_strncasecmp (hdr, "text/calendar", 13)) {
1504                 g_propagate_error (perror, EDC_ERROR (InvalidObject));
1505                 g_object_unref (message);
1506                 g_warning ("Object to fetch '%s' not of type text/calendar", uri);
1507                 g_free (uri);
1508                 return FALSE;
1509         }
1510
1511         hdr = soup_message_headers_get (message->response_headers, "ETag");
1512
1513         if (hdr != NULL) {
1514                 g_free (object->etag);
1515                 object->etag = quote_etag (hdr);
1516         } else if (!object->etag) {
1517                 g_warning ("UUHH no ETag, now that's bad! (at '%s')", uri);
1518         }
1519         g_free (uri);
1520
1521         g_free (object->cdata);
1522         object->cdata = g_strdup (message->response_body->data);
1523
1524         g_object_unref (message);
1525
1526         return TRUE;
1527 }
1528
1529 static void
1530 caldav_post_freebusy (ECalBackendCalDAV *cbdav,
1531                       const gchar *url,
1532                       gchar **post_fb,
1533                       GError **error)
1534 {
1535         SoupMessage *message;
1536
1537         e_return_data_cal_error_if_fail (cbdav != NULL, InvalidArg);
1538         e_return_data_cal_error_if_fail (url != NULL, InvalidArg);
1539         e_return_data_cal_error_if_fail (post_fb != NULL, InvalidArg);
1540         e_return_data_cal_error_if_fail (*post_fb != NULL, InvalidArg);
1541
1542         message = soup_message_new (SOUP_METHOD_POST, url);
1543         if (message == NULL) {
1544                 g_propagate_error (error, EDC_ERROR (NoSuchCal));
1545                 return;
1546         }
1547
1548         soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1549         soup_message_set_request (message,
1550                                   "text/calendar; charset=utf-8",
1551                                   SOUP_MEMORY_COPY,
1552                                   *post_fb, strlen (*post_fb));
1553
1554         send_and_handle_redirection (cbdav->priv->session, message, NULL);
1555
1556         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1557                 status_code_to_result (message, cbdav, FALSE, error);
1558                 if (message->status_code == 401)
1559                         caldav_authenticate (cbdav, FALSE, NULL, NULL);
1560                 else
1561                         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");
1562
1563                 g_object_unref (message);
1564
1565                 return;
1566         }
1567
1568         g_free (*post_fb);
1569         *post_fb = g_strdup (message->response_body->data);
1570
1571         g_object_unref (message);
1572 }
1573
1574 static gchar *
1575 caldav_gen_file_from_uid_cal (ECalBackendCalDAV *cbdav,
1576                               icalcomponent *icalcomp)
1577 {
1578         icalcomponent_kind my_kind;
1579         const gchar *uid = NULL;
1580         gchar *filename, *res;
1581
1582         g_return_val_if_fail (cbdav != NULL, NULL);
1583         g_return_val_if_fail (icalcomp != NULL, NULL);
1584
1585         my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
1586         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
1587                 icalcomponent *subcomp;
1588
1589                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
1590                      subcomp;
1591                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
1592                         uid = icalcomponent_get_uid (subcomp);
1593                         break;
1594                 }
1595         } else if (icalcomponent_isa (icalcomp) == my_kind) {
1596                 uid = icalcomponent_get_uid (icalcomp);
1597         }
1598
1599         if (!uid)
1600                 return NULL;
1601
1602         filename = g_strconcat (uid, ".ics", NULL);
1603         res = soup_uri_encode (filename, NULL);
1604         g_free (filename);
1605
1606         return res;
1607 }
1608
1609 static gboolean
1610 caldav_server_put_object (ECalBackendCalDAV *cbdav,
1611                           CalDAVObject *object,
1612                           icalcomponent *icalcomp,
1613                           GError **perror)
1614 {
1615         SoupMessage              *message;
1616         const gchar               *hdr;
1617         gchar                     *uri;
1618
1619         hdr = NULL;
1620
1621         g_assert (object != NULL && object->cdata != NULL);
1622
1623         uri = caldav_generate_uri (cbdav, object->href);
1624         message = soup_message_new (SOUP_METHOD_PUT, uri);
1625         g_free (uri);
1626         if (message == NULL) {
1627                 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1628                 return FALSE;
1629         }
1630
1631         soup_message_headers_append (message->request_headers,
1632                                      "User-Agent", "Evolution/" VERSION);
1633
1634         /* For new items we use the If-None-Match so we don't
1635          * acidently override resources, for item updates we
1636          * use the If-Match header to avoid the Lost-update
1637          * problem */
1638         if (object->etag == NULL) {
1639                 soup_message_headers_append (message->request_headers, "If-None-Match", "*");
1640         } else {
1641                 soup_message_headers_append (message->request_headers,
1642                                              "If-Match", object->etag);
1643         }
1644
1645         soup_message_set_request (message,
1646                                   "text/calendar; charset=utf-8",
1647                                   SOUP_MEMORY_COPY,
1648                                   object->cdata,
1649                                   strlen (object->cdata));
1650
1651         uri = NULL;
1652         send_and_handle_redirection (cbdav->priv->session, message, &uri);
1653
1654         if (uri) {
1655                 gchar *file = strrchr (uri, '/');
1656
1657                 /* there was a redirect, update href properly */
1658                 if (file) {
1659                         gchar *decoded;
1660
1661                         g_free (object->href);
1662
1663                         decoded = soup_uri_decode (file + 1);
1664                         object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1665
1666                         g_free (decoded);
1667                 }
1668
1669                 g_free (uri);
1670         }
1671
1672         if (status_code_to_result (message, cbdav, FALSE, perror)) {
1673                 GError *local_error = NULL;
1674
1675                 hdr = soup_message_headers_get (message->response_headers, "ETag");
1676                 if (hdr != NULL) {
1677                         g_free (object->etag);
1678                         object->etag = quote_etag (hdr);
1679                 } else {
1680                         /* no ETag header returned, check for it with a GET */
1681                         hdr = soup_message_headers_get (message->response_headers, "Location");
1682                         if (hdr) {
1683                                 /* reflect possible href change first */
1684                                 gchar *file = strrchr (hdr, '/');
1685
1686                                 if (file) {
1687                                         gchar *decoded;
1688
1689                                         g_free (object->href);
1690
1691                                         decoded = soup_uri_decode (file + 1);
1692                                         object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1693
1694                                         g_free (decoded);
1695                                 }
1696                         }
1697                 }
1698
1699                 if (!caldav_server_get_object (cbdav, object, &local_error)) {
1700                         if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
1701                                 gchar *file;
1702
1703                                 /* OK, the event was properly created, but cannot be found on the place
1704                                  * where it was PUT - why didn't server tell us where it saved it? */
1705                                 g_clear_error (&local_error);
1706
1707                                 /* try whether it's saved as its UID.ics file */
1708                                 file = caldav_gen_file_from_uid_cal (cbdav, icalcomp);
1709                                 if (file) {
1710                                         g_free (object->href);
1711                                         object->href = file;
1712
1713                                         if (!caldav_server_get_object (cbdav, object, &local_error)) {
1714                                                 if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
1715                                                         g_clear_error (&local_error);
1716
1717                                                         /* not sure what can happen, but do not need to guess for ever,
1718                                                          * thus report success and update the calendar to get fresh info */
1719                                                         update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
1720                                                         g_cond_signal (cbdav->priv->cond);
1721                                                 }
1722                                         }
1723                                 }
1724                         }
1725                 }
1726
1727                 if (!local_error) {
1728                         icalcomponent *use_comp = NULL;
1729
1730                         if (object->cdata) {
1731                                 /* maybe server also modified component, thus rather store the server's */
1732                                 use_comp = icalparser_parse_string (object->cdata);
1733                         }
1734
1735                         if (!use_comp)
1736                                 use_comp = icalcomp;
1737
1738                         put_comp_to_cache (cbdav, use_comp, object->href, object->etag);
1739
1740                         if (use_comp != icalcomp)
1741                                 icalcomponent_free (use_comp);
1742                 } else {
1743                         g_propagate_error (perror, local_error);
1744                 }
1745         } else if (message->status_code == 401) {
1746                 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1747         }
1748
1749         g_object_unref (message);
1750
1751         return TRUE;
1752 }
1753
1754 static void
1755 caldav_server_delete_object (ECalBackendCalDAV *cbdav,
1756                              CalDAVObject *object,
1757                              GError **perror)
1758 {
1759         SoupMessage              *message;
1760         gchar                     *uri;
1761
1762         g_assert (object != NULL && object->href != NULL);
1763
1764         uri = caldav_generate_uri (cbdav, object->href);
1765         message = soup_message_new (SOUP_METHOD_DELETE, uri);
1766         g_free (uri);
1767         if (message == NULL) {
1768                 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1769                 return;
1770         }
1771
1772         soup_message_headers_append (message->request_headers,
1773                                      "User-Agent", "Evolution/" VERSION);
1774
1775         if (object->etag != NULL) {
1776                 soup_message_headers_append (message->request_headers,
1777                                              "If-Match", object->etag);
1778         }
1779
1780         send_and_handle_redirection (cbdav->priv->session, message, NULL);
1781
1782         status_code_to_result (message, cbdav, FALSE, perror);
1783
1784         if (message->status_code == 401)
1785                 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1786
1787         g_object_unref (message);
1788 }
1789
1790 static gboolean
1791 caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
1792 {
1793         SoupMessage *message;
1794         xmlOutputBufferPtr buf;
1795         xmlDocPtr doc;
1796         xmlNodePtr root, node;
1797         xmlNsPtr nsdav;
1798         gchar *owner = NULL;
1799
1800         g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
1801         g_return_val_if_fail (cbdav->priv->schedule_outbox_url == NULL, TRUE);
1802
1803         /* Prepare the soup message */
1804         message = soup_message_new ("PROPFIND", cbdav->priv->uri);
1805         if (message == NULL)
1806                 return FALSE;
1807
1808         doc = xmlNewDoc ((xmlChar *) "1.0");
1809         root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1810         xmlDocSetRootElement (doc, root);
1811         nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1812
1813         node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1814         xmlNewTextChild (node, nsdav, (xmlChar *) "owner", NULL);
1815
1816         buf = xmlAllocOutputBuffer (NULL);
1817         xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1818         xmlOutputBufferFlush (buf);
1819
1820         soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1821         soup_message_headers_append (message->request_headers, "Depth", "0");
1822
1823         soup_message_set_request (message,
1824                                   "application/xml",
1825                                   SOUP_MEMORY_COPY,
1826                                   (gchar *) buf->buffer->content,
1827                                   buf->buffer->use);
1828
1829         /* Send the request now */
1830         send_and_handle_redirection (cbdav->priv->session, message, NULL);
1831
1832         /* Clean up the memory */
1833         xmlOutputBufferClose (buf);
1834         xmlFreeDoc (doc);
1835
1836         /* Check the result */
1837         if (message->status_code == 207 && parse_propfind_response (message, XPATH_OWNER_STATUS, XPATH_OWNER, &owner) && owner && *owner) {
1838                 xmlNsPtr nscd;
1839                 SoupURI *suri;
1840
1841                 g_object_unref (message);
1842
1843                 /* owner is a full path to the user's URL, thus change it in
1844                  * calendar's uri when asking for schedule-outbox-URL */
1845                 suri = soup_uri_new (cbdav->priv->uri);
1846                 soup_uri_set_path (suri, owner);
1847                 g_free (owner);
1848                 owner = soup_uri_to_string (suri, FALSE);
1849                 soup_uri_free (suri);
1850
1851                 message = soup_message_new ("PROPFIND", owner);
1852                 if (message == NULL) {
1853                         g_free (owner);
1854                         return FALSE;
1855                 }
1856
1857                 doc = xmlNewDoc ((xmlChar *) "1.0");
1858                 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1859                 xmlDocSetRootElement (doc, root);
1860                 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1861                 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1862
1863                 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1864                 xmlNewTextChild (node, nscd, (xmlChar *) "schedule-outbox-URL", NULL);
1865
1866                 buf = xmlAllocOutputBuffer (NULL);
1867                 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1868                 xmlOutputBufferFlush (buf);
1869
1870                 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1871                 soup_message_headers_append (message->request_headers, "Depth", "0");
1872
1873                 soup_message_set_request (message,
1874                                   "application/xml",
1875                                   SOUP_MEMORY_COPY,
1876                                   (gchar *) buf->buffer->content,
1877                                   buf->buffer->use);
1878
1879                 /* Send the request now */
1880                 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1881
1882                 if (message->status_code == 207 && parse_propfind_response (message, XPATH_SCHEDULE_OUTBOX_URL_STATUS, XPATH_SCHEDULE_OUTBOX_URL, &cbdav->priv->schedule_outbox_url)) {
1883                         if (!*cbdav->priv->schedule_outbox_url) {
1884                                 g_free (cbdav->priv->schedule_outbox_url);
1885                                 cbdav->priv->schedule_outbox_url = NULL;
1886                         } else {
1887                                 /* make it a full URI */
1888                                 suri = soup_uri_new (cbdav->priv->uri);
1889                                 soup_uri_set_path (suri, cbdav->priv->schedule_outbox_url);
1890                                 g_free (cbdav->priv->schedule_outbox_url);
1891                                 cbdav->priv->schedule_outbox_url = soup_uri_to_string (suri, FALSE);
1892                                 soup_uri_free (suri);
1893                         }
1894                 }
1895
1896                 /* Clean up the memory */
1897                 xmlOutputBufferClose (buf);
1898                 xmlFreeDoc (doc);
1899         } else if (message->status_code == 401) {
1900                 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1901         }
1902
1903         if (message)
1904                 g_object_unref (message);
1905
1906         g_free (owner);
1907
1908         return cbdav->priv->schedule_outbox_url != NULL;
1909 }
1910
1911 /* ************************************************************************* */
1912 /* Synchronization foo */
1913
1914 static gboolean extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp);
1915
1916 struct cache_comp_list
1917 {
1918         GSList *slist;
1919 };
1920
1921 static gboolean
1922 remove_complist_from_cache_and_notify_cb (gpointer key,
1923                                           gpointer value,
1924                                           gpointer data)
1925 {
1926         GSList *l;
1927         struct cache_comp_list *ccl = value;
1928         ECalBackendCalDAV *cbdav = data;
1929
1930         for (l = ccl->slist; l; l = l->next) {
1931                 ECalComponent *old_comp = l->data;
1932                 ECalComponentId *id;
1933
1934                 id = e_cal_component_get_id (old_comp);
1935                 if (!id) {
1936                         continue;
1937                 }
1938
1939                 if (e_cal_backend_store_remove_component (cbdav->priv->store, id->uid, id->rid)) {
1940                         e_cal_backend_notify_component_removed ((ECalBackend *) cbdav, id, old_comp, NULL);
1941                 }
1942
1943                 e_cal_component_free_id (id);
1944         }
1945         remove_cached_attachment (cbdav, (const gchar *) key);
1946
1947         return FALSE;
1948 }
1949
1950 static void
1951 free_comp_list (gpointer cclist)
1952 {
1953         struct cache_comp_list *ccl = cclist;
1954
1955         g_return_if_fail (ccl != NULL);
1956
1957         g_slist_foreach (ccl->slist, (GFunc) g_object_unref, NULL);
1958         g_slist_free (ccl->slist);
1959         g_free (ccl);
1960 }
1961
1962 #define etags_match(_tag1, _tag2) ((_tag1 == _tag2) ? TRUE :                 \
1963                                    g_str_equal (_tag1 != NULL ? _tag1 : "",  \
1964                                                 _tag2 != NULL ? _tag2 : ""))
1965
1966 /* start_time/end_time is an interval for checking changes. If both greater than zero,
1967  * only the interval is checked and the removed items are not notified, as they can
1968  * be still there.
1969 */
1970 static void
1971 synchronize_cache (ECalBackendCalDAV *cbdav,
1972                    time_t start_time,
1973                    time_t end_time)
1974 {
1975         ECalBackend *bkend;
1976         CalDAVObject *sobjs, *object;
1977         GSList *c_objs, *c_iter; /* list of all items known from our cache */
1978         GTree *c_uid2complist;  /* cache components list (with detached instances) sorted by (master's) uid */
1979         GHashTable *c_href2uid; /* connection between href and a (master's) uid */
1980         GSList *hrefs_to_update, *htu; /* list of href-s to update */
1981         gint i, len;
1982
1983         if (!check_calendar_changed_on_server (cbdav)) {
1984                 /* no changes on the server, no update required */
1985                 return;
1986         }
1987
1988         bkend  = E_CAL_BACKEND (cbdav);
1989         len    = 0;
1990         sobjs  = NULL;
1991
1992         /* get list of server objects */
1993         if (!caldav_server_list_objects (cbdav, &sobjs, &len, NULL, start_time, end_time))
1994                 return;
1995
1996         c_objs = e_cal_backend_store_get_components (cbdav->priv->store);
1997
1998         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
1999                 printf ("CalDAV - found %d objects on the server, locally stored %d objects\n", len, g_slist_length (c_objs)); fflush (stdout);
2000         }
2001
2002         /* do not store changes in cache immediately - makes things significantly quicker */
2003         e_cal_backend_store_freeze_changes (cbdav->priv->store);
2004
2005         c_uid2complist = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL, g_free, free_comp_list);
2006         c_href2uid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2007
2008         /* fill indexed hash and tree with cached components */
2009         for (c_iter = c_objs; c_iter; c_iter = g_slist_next (c_iter)) {
2010                 ECalComponent *ccomp = E_CAL_COMPONENT (c_iter->data);
2011                 const gchar *uid = NULL;
2012                 struct cache_comp_list *ccl;
2013                 gchar *href;
2014
2015                 e_cal_component_get_uid (ccomp, &uid);
2016                 if (!uid) {
2017                         g_warning ("broken component with NULL Id");
2018                         continue;
2019                 }
2020
2021                 href = ecalcomp_get_href (ccomp);
2022
2023                 if (href == NULL) {
2024                         g_warning ("href of object NULL :(");
2025                         continue;
2026                 }
2027
2028                 ccl = g_tree_lookup (c_uid2complist, uid);
2029                 if (ccl) {
2030                         ccl->slist = g_slist_prepend (ccl->slist, g_object_ref (ccomp));
2031                 } else {
2032                         ccl = g_new0 (struct cache_comp_list, 1);
2033                         ccl->slist = g_slist_append (NULL, g_object_ref (ccomp));
2034
2035                         /* make a copy, which will be used in the c_href2uid too */
2036                         uid = g_strdup (uid);
2037
2038                         g_tree_insert (c_uid2complist, (gpointer) uid, ccl);
2039                 }
2040
2041                 if (g_hash_table_lookup (c_href2uid, href) == NULL) {
2042                         /* uid is from a component or c_uid2complist key, thus will not be
2043                          * freed before a removal from c_uid2complist, thus do not duplicate it,
2044                          * rather save memory */
2045                         g_hash_table_insert (c_href2uid, href, (gpointer) uid);
2046                 } else {
2047                         g_free (href);
2048                 }
2049         }
2050
2051         /* clear it now, we do not need it later */
2052         g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2053         g_slist_free (c_objs);
2054         c_objs = NULL;
2055
2056         hrefs_to_update = NULL;
2057
2058         /* see if we have to update or add some objects */
2059         for (i = 0, object = sobjs; i < len && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK; i++, object++) {
2060                 ECalComponent *ccomp = NULL;
2061                 gchar *etag = NULL;
2062                 const gchar *uid;
2063                 struct cache_comp_list *ccl;
2064
2065                 if (object->status != 200) {
2066                         /* just continue here, so that the object
2067                          * doesnt get removed from the cobjs list
2068                          * - therefore it will be removed */
2069                         continue;
2070                 }
2071
2072                 uid = g_hash_table_lookup (c_href2uid, object->href);
2073                 if (uid) {
2074                         ccl = g_tree_lookup (c_uid2complist, uid);
2075                         if (ccl) {
2076                                 GSList *sl;
2077                                 for (sl = ccl->slist; sl && !etag; sl = sl->next) {
2078                                         ccomp = sl->data;
2079                                         if (ccomp)
2080                                                 etag = ecalcomp_get_etag (ccomp);
2081                                 }
2082
2083                                 if (!etag)
2084                                         ccomp = NULL;
2085                         }
2086                 }
2087
2088                 if (!etag || !etags_match (etag, object->etag)) {
2089                         hrefs_to_update = g_slist_prepend (hrefs_to_update, object->href);
2090                 } else if (uid && ccl) {
2091                         /* all components cover by this uid are up-to-date */
2092                         GSList *p;
2093
2094                         for (p = ccl->slist; p; p = p->next) {
2095                                 g_object_unref (p->data);
2096                         }
2097
2098                         g_slist_free (ccl->slist);
2099                         ccl->slist = NULL;
2100                 }
2101
2102                 g_free (etag);
2103         }
2104
2105         /* free hash table, as it is not used anymore */
2106         g_hash_table_destroy (c_href2uid);
2107         c_href2uid = NULL;
2108
2109         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2110                 printf ("CalDAV - recognized %d items to update\n", g_slist_length (hrefs_to_update)); fflush (stdout);
2111         }
2112
2113         htu = hrefs_to_update;
2114         while (htu && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK) {
2115                 gint count = 0;
2116                 GSList *to_fetch = NULL;
2117
2118                 while (count < CALDAV_MAX_MULTIGET_AMOUNT && htu) {
2119                         to_fetch = g_slist_prepend (to_fetch, htu->data);
2120                         htu = htu->next;
2121                         count++;
2122                 }
2123
2124                 if (to_fetch && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK) {
2125                         CalDAVObject *up_sobjs = NULL;
2126
2127                         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2128                                 printf ("CalDAV - going to fetch %d items\n", g_slist_length (to_fetch)); fflush (stdout);
2129                         }
2130
2131                         count = 0;
2132                         if (!caldav_server_list_objects (cbdav, &up_sobjs, &count, to_fetch, 0, 0)) {
2133                                 fprintf (stderr, "CalDAV - failed to retrieve bunch of items\n"); fflush (stderr);
2134                                 break;
2135                         }
2136
2137                         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2138                                 printf ("CalDAV - fetched bunch of %d items\n", count); fflush (stdout);
2139                         }
2140
2141                         /* we are going to update cache */
2142                         /* they are downloaded, so process them */
2143                         for (i = 0, object = up_sobjs; i < count /*&& cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK */; i++, object++) {
2144                                 if (object->status == 200 && object->href && object->etag && object->cdata && *object->cdata) {
2145                                         icalcomponent *icomp = icalparser_parse_string (object->cdata);
2146
2147                                         if (icomp) {
2148                                                 icalcomponent_kind kind = icalcomponent_isa (icomp);
2149
2150                                                 extract_timezones (cbdav, icomp);
2151
2152                                                 if (kind == ICAL_VCALENDAR_COMPONENT) {
2153                                                         icalcomponent *subcomp;
2154
2155                                                         kind = e_cal_backend_get_kind (bkend);
2156
2157                                                         for (subcomp = icalcomponent_get_first_component (icomp, kind);
2158                                                              subcomp;
2159                                                              subcomp = icalcomponent_get_next_component (icomp, kind)) {
2160                                                                 ECalComponent *new_comp, *old_comp;
2161
2162                                                                 convert_to_url_attachment (cbdav, subcomp);
2163                                                                 new_comp = e_cal_component_new ();
2164                                                                 if (e_cal_component_set_icalcomponent (new_comp, icalcomponent_new_clone (subcomp))) {
2165                                                                         const gchar *uid = NULL;
2166                                                                         struct cache_comp_list *ccl;
2167
2168                                                                         e_cal_component_get_uid (new_comp, &uid);
2169                                                                         if (!uid) {
2170                                                                                 g_warning ("%s: no UID on component!", G_STRFUNC);
2171                                                                                 g_object_unref (new_comp);
2172                                                                                 continue;
2173                                                                         }
2174
2175                                                                         ecalcomp_set_href (new_comp, object->href);
2176                                                                         ecalcomp_set_etag (new_comp, object->etag);
2177
2178                                                                         old_comp = NULL;
2179                                                                         ccl = g_tree_lookup (c_uid2complist, uid);
2180                                                                         if (ccl) {
2181                                                                                 gchar *nc_rid = e_cal_component_get_recurid_as_string (new_comp);
2182                                                                                 GSList *p;
2183
2184                                                                                 for (p = ccl->slist; p && !old_comp; p = p->next) {
2185                                                                                         gchar *oc_rid;
2186
2187                                                                                         old_comp = p->data;
2188
2189                                                                                         oc_rid = e_cal_component_get_recurid_as_string (old_comp);
2190                                                                                         if (g_strcmp0 (nc_rid, oc_rid) != 0) {
2191                                                                                                 old_comp = NULL;
2192                                                                                         }
2193
2194                                                                                         g_free (oc_rid);
2195                                                                                 }
2196
2197                                                                                 g_free (nc_rid);
2198                                                                         }
2199
2200                                                                         put_component_to_store (cbdav, new_comp);
2201
2202                                                                         if (old_comp == NULL) {
2203                                                                                 e_cal_backend_notify_component_created (bkend, new_comp);
2204                                                                         } else {
2205                                                                                 e_cal_backend_notify_component_modified (bkend, old_comp, new_comp);
2206
2207                                                                                 ccl->slist = g_slist_remove (ccl->slist, old_comp);
2208                                                                                 g_object_unref (old_comp);
2209                                                                         }
2210                                                                 }
2211
2212                                                                 g_object_unref (new_comp);
2213                                                         }
2214                                                 }
2215
2216                                                 icalcomponent_free (icomp);
2217                                         }
2218                                 }
2219
2220                                 /* these free immediately */
2221                                 caldav_object_free (object, FALSE);
2222                         }
2223
2224                         /* cache update done for fetched items */
2225                         g_free (up_sobjs);
2226                 }
2227
2228                 /* do not free 'data' itself, it's part of 'sobjs' */
2229                 g_slist_free (to_fetch);
2230         }
2231
2232         /* if not interrupted and not using the time range... */
2233         if (cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK && (!start_time || !end_time)) {
2234                 /* ...remove old (not on server anymore) items from our cache and notify of a removal */
2235                 g_tree_foreach (c_uid2complist, remove_complist_from_cache_and_notify_cb, cbdav);
2236         }
2237
2238         if (cbdav->priv->ctag_to_store) {
2239                 /* store only when wasn't interrupted */
2240                 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK && start_time == 0 && end_time == 0) {
2241                         e_cal_backend_store_put_key_value (cbdav->priv->store, CALDAV_CTAG_KEY, cbdav->priv->ctag_to_store);
2242                 }
2243
2244                 g_free (cbdav->priv->ctag_to_store);
2245                 cbdav->priv->ctag_to_store = NULL;
2246         }
2247
2248         /* save cache changes to disk finally */
2249         e_cal_backend_store_thaw_changes (cbdav->priv->store);
2250
2251         for (i = 0, object = sobjs; i < len; i++, object++) {
2252                 caldav_object_free (object, FALSE);
2253         }
2254
2255         g_tree_destroy (c_uid2complist);
2256         g_slist_free (hrefs_to_update);
2257         g_free (sobjs);
2258 }
2259
2260 static gboolean
2261 is_google_uri (const gchar *uri)
2262 {
2263         SoupURI *suri;
2264         gboolean res;
2265
2266         g_return_val_if_fail (uri != NULL, FALSE);
2267
2268         suri = soup_uri_new (uri);
2269         g_return_val_if_fail (suri != NULL, FALSE);
2270
2271         res = suri->host && g_ascii_strcasecmp (suri->host, "www.google.com") == 0;
2272
2273         soup_uri_free (suri);
2274
2275         return res;
2276 }
2277
2278 /* ************************************************************************* */
2279
2280 static gpointer
2281 caldav_synch_slave_loop (gpointer data)
2282 {
2283         ECalBackendCalDAV        *cbdav;
2284         time_t now;
2285         icaltimezone *utc = icaltimezone_get_utc_timezone ();
2286         gboolean know_unreachable;
2287
2288         cbdav = E_CAL_BACKEND_CALDAV (data);
2289
2290         g_mutex_lock (cbdav->priv->busy_lock);
2291
2292         know_unreachable = !cbdav->priv->opened;
2293
2294         while (cbdav->priv->slave_cmd != SLAVE_SHOULD_DIE) {
2295                 GTimeVal alarm_clock;
2296                 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_SLEEP) {
2297                         /* just sleep until we get woken up again */
2298                         g_cond_wait (cbdav->priv->cond, cbdav->priv->busy_lock);
2299
2300                         /* check if we should die, work or sleep again */
2301                         continue;
2302                 }
2303
2304                 /* Ok here we go, do some real work
2305                  * Synch it baby one more time ...
2306                  */
2307                 cbdav->priv->slave_busy = TRUE;
2308
2309                 if (!cbdav->priv->opened) {
2310                         gboolean server_unreachable = FALSE;
2311                         GError *local_error = NULL;
2312                         gboolean online;
2313
2314                         if (caldav_server_open_calendar (cbdav, &server_unreachable, &local_error)) {
2315                                 cbdav->priv->opened = TRUE;
2316                                 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2317                                 g_cond_signal (cbdav->priv->cond);
2318
2319                                 cbdav->priv->is_google = is_google_uri (cbdav->priv->uri);
2320                                 know_unreachable = FALSE;
2321                         } else if (local_error) {
2322                                 cbdav->priv->opened = FALSE;
2323                                 cbdav->priv->read_only = TRUE;
2324
2325                                 if (!know_unreachable) {
2326                                         gchar *msg;
2327
2328                                         know_unreachable = TRUE;
2329
2330                                         msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2331                                         e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2332                                         g_free (msg);
2333                                 }
2334
2335                                 g_clear_error (&local_error);
2336                         } else {
2337                                 cbdav->priv->opened = FALSE;
2338                                 cbdav->priv->read_only = TRUE;
2339                                 know_unreachable = TRUE;
2340                         }
2341
2342                         e_cal_backend_notify_readonly (E_CAL_BACKEND (cbdav), cbdav->priv->read_only);
2343
2344                         online = e_backend_get_online (E_BACKEND (cbdav));
2345                         e_cal_backend_notify_online (E_CAL_BACKEND (cbdav), online);
2346                 }
2347
2348                 if (cbdav->priv->opened) {
2349                         time (&now);
2350                         /* check for events in the month before/after today first,
2351                          * to show user actual data as soon as possible */
2352                         synchronize_cache (cbdav, time_add_week_with_zone (now, -5, utc), time_add_week_with_zone (now, +5, utc));
2353
2354                         if (cbdav->priv->slave_cmd != SLAVE_SHOULD_SLEEP) {
2355                                 /* and then check for changes in a whole calendar */
2356                                 synchronize_cache (cbdav, 0, 0);
2357                         }
2358
2359                         if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2360                                 GSList *c_objs;
2361
2362                                 c_objs = e_cal_backend_store_get_components (cbdav->priv->store);
2363
2364                                 printf ("CalDAV - finished syncing with %d items in a cache\n", g_slist_length (c_objs)); fflush (stdout);
2365
2366                                 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2367                                 g_slist_free (c_objs);
2368                         }
2369                 }
2370
2371                 cbdav->priv->slave_busy = FALSE;
2372
2373                 /* puhh that was hard, get some rest :) */
2374                 g_get_current_time (&alarm_clock);
2375                 alarm_clock.tv_sec += cbdav->priv->refresh_time.tv_sec;
2376                 g_cond_timed_wait (cbdav->priv->cond,
2377                                    cbdav->priv->busy_lock,
2378                                    &alarm_clock);
2379
2380         }
2381
2382         /* signal we are done */
2383         g_cond_signal (cbdav->priv->slave_gone_cond);
2384
2385         cbdav->priv->synch_slave = NULL;
2386
2387         /* we got killed ... */
2388         g_mutex_unlock (cbdav->priv->busy_lock);
2389         return NULL;
2390 }
2391
2392 static gchar *
2393 maybe_append_email_domain (const gchar *username,
2394                            const gchar *may_append)
2395 {
2396         if (!username || !*username)
2397                 return NULL;
2398
2399         if (strchr (username, '@'))
2400                 return g_strdup (username);
2401
2402         return g_strconcat (username, may_append, NULL);
2403 }
2404
2405 static gchar *
2406 get_usermail (ECalBackend *backend)
2407 {
2408         ECalBackendCalDAV *cbdav;
2409         ESource *source;
2410         ESourceAuthentication *auth_extension;
2411         ESourceWebdav *webdav_extension;
2412         const gchar *extension_name;
2413         gchar *usermail;
2414         gchar *username;
2415         gchar *res = NULL;
2416
2417         g_return_val_if_fail (backend != NULL, NULL);
2418
2419         source = e_backend_get_source (E_BACKEND (backend));
2420
2421         extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2422         webdav_extension = e_source_get_extension (source, extension_name);
2423
2424         /* This will never return an empty string. */
2425         usermail = e_source_webdav_dup_email_address (webdav_extension);
2426
2427         if (usermail != NULL)
2428                 return usermail;
2429
2430         cbdav = E_CAL_BACKEND_CALDAV (backend);
2431
2432         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
2433         auth_extension = e_source_get_extension (source, extension_name);
2434         username = e_source_authentication_dup_user (auth_extension);
2435
2436         if (cbdav->priv && cbdav->priv->is_google)
2437                 res = maybe_append_email_domain (username, "@gmail.com");
2438
2439         g_free (username);
2440
2441         return res;
2442 }
2443
2444 /* ************************************************************************* */
2445 /* ********** ECalBackendSync virtual function implementation *************  */
2446
2447 static gboolean
2448 caldav_get_backend_property (ECalBackendSync *backend,
2449                              EDataCal *cal,
2450                              GCancellable *cancellable,
2451                              const gchar *prop_name,
2452                              gchar **prop_value,
2453                              GError **perror)
2454 {
2455         gboolean processed = TRUE;
2456
2457         g_return_val_if_fail (prop_name != NULL, FALSE);
2458         g_return_val_if_fail (prop_value != NULL, FALSE);
2459
2460         if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
2461                 ESourceWebdav *extension;
2462                 ESource *source;
2463                 GString *caps;
2464                 gchar *usermail;
2465                 const gchar *extension_name;
2466
2467                 caps = g_string_new (CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
2468                                      CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
2469                                      CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED);
2470
2471                 usermail = get_usermail (E_CAL_BACKEND (backend));
2472                 if (!usermail || !*usermail)
2473                         g_string_append (caps, "," CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS);
2474                 g_free (usermail);
2475
2476                 source = e_backend_get_source (E_BACKEND (backend));
2477
2478                 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2479                 extension = e_source_get_extension (source, extension_name);
2480
2481                 if (e_source_webdav_get_calendar_auto_schedule (extension)) {
2482                         g_string_append (caps, "," CAL_STATIC_CAPABILITY_CREATE_MESSAGES
2483                                                "," CAL_STATIC_CAPABILITY_SAVE_SCHEDULES);
2484                 }
2485
2486                 *prop_value = g_string_free (caps, FALSE);
2487         } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
2488                    g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
2489                 *prop_value = get_usermail (E_CAL_BACKEND (backend));
2490         } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
2491                 ECalComponent *comp;
2492
2493                 comp = e_cal_component_new ();
2494
2495                 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2496                 case ICAL_VEVENT_COMPONENT:
2497                         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
2498                         break;
2499                 case ICAL_VTODO_COMPONENT:
2500                         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
2501                         break;
2502                 case ICAL_VJOURNAL_COMPONENT:
2503                         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
2504                         break;
2505                 default:
2506                         g_object_unref (comp);
2507                         g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
2508                         return TRUE;
2509                 }
2510
2511                 *prop_value = e_cal_component_get_as_string (comp);
2512                 g_object_unref (comp);
2513         } else {
2514                 processed = FALSE;
2515         }
2516
2517         return processed;
2518 }
2519
2520 static gboolean
2521 initialize_backend (ECalBackendCalDAV *cbdav,
2522                     GError **perror)
2523 {
2524         ESourceAuthentication    *auth_extension;
2525         ESourceOffline           *offline_extension;
2526         ESourceRefresh           *refresh_extension;
2527         ESourceWebdav            *webdav_extension;
2528         ECalBackend              *backend;
2529         SoupURI                  *soup_uri;
2530         ESource                  *source;
2531         gsize                     len;
2532         const gchar              *cache_dir;
2533         const gchar              *extension_name;
2534         guint                     interval_in_minutes;
2535
2536         backend = E_CAL_BACKEND (cbdav);
2537         cache_dir = e_cal_backend_get_cache_dir (backend);
2538         source = e_backend_get_source (E_BACKEND (backend));
2539
2540         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
2541         auth_extension = e_source_get_extension (source, extension_name);
2542
2543         extension_name = E_SOURCE_EXTENSION_OFFLINE;
2544         offline_extension = e_source_get_extension (source, extension_name);
2545
2546         extension_name = E_SOURCE_EXTENSION_REFRESH;
2547         refresh_extension = e_source_get_extension (source, extension_name);
2548
2549         extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2550         webdav_extension = e_source_get_extension (source, extension_name);
2551
2552         if (!g_signal_handler_find (G_OBJECT (source), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, caldav_source_changed_cb, cbdav))
2553                 g_signal_connect (G_OBJECT (source), "changed", G_CALLBACK (caldav_source_changed_cb), cbdav);
2554
2555         cbdav->priv->do_offline = e_source_offline_get_stay_synchronized (offline_extension);
2556
2557         cbdav->priv->auth_required = e_source_authentication_required (auth_extension);
2558
2559         soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
2560
2561         /* properly encode uri */
2562         if (soup_uri != NULL && soup_uri->path != NULL) {
2563                 gchar *tmp, *path;
2564
2565                 if (strchr (soup_uri->path, '%')) {
2566                         /* If path contains anything already encoded, then
2567                          * decode it first, thus it'll be managed properly.
2568                          * For example, the '#' in a path is in URI shown as
2569                          * %23 and not doing this decode makes it being like
2570                          * %2523, which is not what is wanted here. */
2571                         tmp = soup_uri_decode (soup_uri->path);
2572                         soup_uri_set_path (soup_uri, tmp);
2573                         g_free (tmp);
2574                 }
2575
2576                 tmp = soup_uri_encode (soup_uri->path, NULL);
2577                 path = soup_uri_normalize (tmp, "/");
2578
2579                 soup_uri_set_path (soup_uri, path);
2580
2581                 g_free (tmp);
2582                 g_free (path);
2583         }
2584
2585         g_free (cbdav->priv->uri);
2586         cbdav->priv->uri = soup_uri_to_string (soup_uri, FALSE);
2587
2588         soup_uri_free (soup_uri);
2589
2590         g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
2591
2592         /* remove trailing slashes... */
2593         if (cbdav->priv->uri != NULL) {
2594                 len = strlen (cbdav->priv->uri);
2595                 while (len--) {
2596                         if (cbdav->priv->uri[len] == '/') {
2597                                 cbdav->priv->uri[len] = '\0';
2598                         } else {
2599                                 break;
2600                         }
2601                 }
2602         }
2603
2604         /* ...and append exactly one slash */
2605         if (cbdav->priv->uri && *cbdav->priv->uri) {
2606                 gchar *tmp = cbdav->priv->uri;
2607
2608                 cbdav->priv->uri = g_strconcat (cbdav->priv->uri, "/", NULL);
2609
2610                 g_free (tmp);
2611         }
2612
2613         if (cbdav->priv->store == NULL) {
2614                 /* remove the old cache while migrating to ECalBackendStore */
2615                 e_cal_backend_cache_remove (cache_dir, "cache.xml");
2616                 cbdav->priv->store = e_cal_backend_file_store_new (cache_dir);
2617
2618                 if (cbdav->priv->store == NULL) {
2619                         g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Cannot create local store"));
2620                         return FALSE;
2621                 }
2622
2623                 e_cal_backend_store_load (cbdav->priv->store);
2624         }
2625
2626         /* Set the local attachment store */
2627         if (g_mkdir_with_parents (cache_dir, 0700) < 0) {
2628                 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "mkdir failed"));
2629                 return FALSE;
2630         }
2631
2632         /* FIXME Not honoring ESourceRefresh:enabled. */
2633         interval_in_minutes =
2634                 e_source_refresh_get_interval_minutes (refresh_extension);
2635
2636         if (interval_in_minutes == 0)
2637                 cbdav->priv->refresh_time.tv_sec = DEFAULT_REFRESH_TIME;
2638         else
2639                 cbdav->priv->refresh_time.tv_sec = interval_in_minutes * 60;
2640
2641         if (!cbdav->priv->synch_slave) {
2642                 GThread *slave;
2643
2644                 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
2645                 slave = g_thread_create (caldav_synch_slave_loop, cbdav, FALSE, NULL);
2646
2647                 if (slave == NULL) {
2648                         g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Could not create synch slave"));
2649                 }
2650
2651                 cbdav->priv->synch_slave = slave;
2652         }
2653
2654         return TRUE;
2655 }
2656
2657 static void
2658 proxy_settings_changed (EProxy *proxy,
2659                         gpointer user_data)
2660 {
2661         SoupURI *proxy_uri = NULL;
2662         ECalBackendCalDAVPrivate *priv = (ECalBackendCalDAVPrivate *) user_data;
2663
2664         if (!priv || !priv->uri || !priv->session)
2665                 return;
2666
2667         /* use proxy if necessary */
2668         if (e_proxy_require_proxy_for_uri (proxy, priv->uri)) {
2669                 proxy_uri = e_proxy_peek_uri_for (proxy, priv->uri);
2670         }
2671
2672         g_object_set (priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
2673 }
2674
2675 static gboolean
2676 open_calendar (ECalBackendCalDAV *cbdav,
2677                GError **error)
2678 {
2679         gboolean server_unreachable = FALSE;
2680         gboolean success;
2681         GError *local_error = NULL;
2682
2683         g_return_val_if_fail (cbdav != NULL, FALSE);
2684
2685         /* set forward proxy */
2686         proxy_settings_changed (cbdav->priv->proxy, cbdav->priv);
2687
2688         success = caldav_server_open_calendar (
2689                 cbdav, &server_unreachable, &local_error);
2690
2691         if (success) {
2692                 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2693                 g_cond_signal (cbdav->priv->cond);
2694
2695                 cbdav->priv->is_google = is_google_uri (cbdav->priv->uri);
2696         } else if (server_unreachable) {
2697                 cbdav->priv->opened = FALSE;
2698                 cbdav->priv->read_only = TRUE;
2699                 if (local_error) {
2700                         gchar *msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2701                         e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2702                         g_free (msg);
2703                         g_clear_error (&local_error);
2704                         success = TRUE;
2705                 }
2706         }
2707
2708         if (local_error != NULL)
2709                 g_propagate_error (error, local_error);
2710
2711         return success;
2712 }
2713
2714 static void
2715 caldav_do_open (ECalBackendSync *backend,
2716                 EDataCal *cal,
2717                 GCancellable *cancellable,
2718                 gboolean only_if_exists,
2719                 GError **perror)
2720 {
2721         ECalBackendCalDAV        *cbdav;
2722         gboolean opened = TRUE;
2723         gboolean online;
2724
2725         cbdav = E_CAL_BACKEND_CALDAV (backend);
2726
2727         g_mutex_lock (cbdav->priv->busy_lock);
2728
2729         /* let it decide the 'getctag' extension availability again */
2730         cbdav->priv->ctag_supported = TRUE;
2731
2732         if (!cbdav->priv->loaded && !initialize_backend (cbdav, perror)) {
2733                 g_mutex_unlock (cbdav->priv->busy_lock);
2734                 return;
2735         }
2736
2737         online = e_backend_get_online (E_BACKEND (backend));
2738
2739         if (!cbdav->priv->do_offline && !online) {
2740                 g_mutex_unlock (cbdav->priv->busy_lock);
2741                 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
2742                 return;
2743         }
2744
2745         cbdav->priv->loaded = TRUE;
2746         cbdav->priv->opened = TRUE;
2747         cbdav->priv->is_google = FALSE;
2748
2749         if (online) {
2750                 GError *local_error = NULL;
2751
2752                 opened = open_calendar (cbdav, &local_error);
2753
2754                 if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationRequired) || g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
2755                         g_clear_error (&local_error);
2756                         opened = caldav_authenticate (
2757                                 cbdav, FALSE, cancellable, perror);
2758                 }
2759
2760                 if (local_error != NULL)
2761                         g_propagate_error (perror, local_error);
2762
2763         } else {
2764                 cbdav->priv->read_only = TRUE;
2765         }
2766
2767         if (opened)
2768                 e_cal_backend_notify_opened (E_CAL_BACKEND (backend), NULL);
2769
2770         e_cal_backend_notify_readonly (
2771                 E_CAL_BACKEND (backend), cbdav->priv->read_only);
2772         e_cal_backend_notify_online (E_CAL_BACKEND (backend), online);
2773
2774         g_mutex_unlock (cbdav->priv->busy_lock);
2775 }
2776
2777 static void
2778 caldav_refresh (ECalBackendSync *backend,
2779                 EDataCal *cal,
2780                 GCancellable *cancellable,
2781                 GError **perror)
2782 {
2783         ECalBackendCalDAV        *cbdav;
2784         gboolean                  online;
2785
2786         cbdav = E_CAL_BACKEND_CALDAV (backend);
2787
2788         g_mutex_lock (cbdav->priv->busy_lock);
2789
2790         if (!cbdav->priv->loaded
2791             || cbdav->priv->slave_cmd == SLAVE_SHOULD_DIE
2792             || !check_state (cbdav, &online, NULL)
2793             || !online) {
2794                 g_mutex_unlock (cbdav->priv->busy_lock);
2795                 return;
2796         }
2797
2798         update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2799
2800         /* wake it up */
2801         g_cond_signal (cbdav->priv->cond);
2802         g_mutex_unlock (cbdav->priv->busy_lock);
2803 }
2804
2805 static void
2806 caldav_remove (ECalBackendSync *backend,
2807                EDataCal *cal,
2808                GCancellable *cancellable,
2809                GError **perror)
2810 {
2811         ECalBackendCalDAV        *cbdav;
2812         gboolean                  online;
2813
2814         cbdav = E_CAL_BACKEND_CALDAV (backend);
2815
2816         /* first tell it to die, then wait for its lock */
2817         update_slave_cmd (cbdav->priv, SLAVE_SHOULD_DIE);
2818
2819         g_mutex_lock (cbdav->priv->busy_lock);
2820
2821         if (!cbdav->priv->loaded) {
2822                 g_mutex_unlock (cbdav->priv->busy_lock);
2823                 return;
2824         }
2825
2826         if (!check_state (cbdav, &online, NULL)) {
2827                 /* lie here a bit, but otherwise the calendar will not be removed, even it should */
2828                 g_print (G_STRLOC ": Failed to check state");
2829         }
2830
2831         e_cal_backend_store_remove (cbdav->priv->store);
2832         cbdav->priv->store = NULL;
2833         cbdav->priv->loaded = FALSE;
2834         cbdav->priv->opened = FALSE;
2835
2836         if (cbdav->priv->synch_slave) {
2837                 g_cond_signal (cbdav->priv->cond);
2838
2839                 /* wait until the slave died */
2840                 g_cond_wait (cbdav->priv->slave_gone_cond, cbdav->priv->busy_lock);
2841         }
2842
2843         g_mutex_unlock (cbdav->priv->busy_lock);
2844 }
2845
2846 static void
2847 remove_comp_from_cache_cb (gpointer value,
2848                            gpointer user_data)
2849 {
2850         ECalComponent *comp = value;
2851         ECalBackendStore *store = user_data;
2852         ECalComponentId *id;
2853
2854         g_return_if_fail (comp != NULL);
2855         g_return_if_fail (store != NULL);
2856
2857         id = e_cal_component_get_id (comp);
2858         g_return_if_fail (id != NULL);
2859
2860         e_cal_backend_store_remove_component (store, id->uid, id->rid);
2861         e_cal_component_free_id (id);
2862 }
2863
2864 static gboolean
2865 remove_comp_from_cache (ECalBackendCalDAV *cbdav,
2866                         const gchar *uid,
2867                         const gchar *rid)
2868 {
2869         gboolean res = FALSE;
2870
2871         if (!rid || !*rid) {
2872                 /* get with detached instances */
2873                 GSList *objects = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
2874
2875                 if (objects) {
2876                         g_slist_foreach (objects, (GFunc) remove_comp_from_cache_cb, cbdav->priv->store);
2877                         g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2878                         g_slist_free (objects);
2879
2880                         res = TRUE;
2881                 }
2882         } else {
2883                 res = e_cal_backend_store_remove_component (cbdav->priv->store, uid, rid);
2884         }
2885
2886         return res;
2887 }
2888
2889 static void
2890 add_detached_recur_to_vcalendar_cb (gpointer value,
2891                                     gpointer user_data)
2892 {
2893         icalcomponent *recurrence = e_cal_component_get_icalcomponent (value);
2894         icalcomponent *vcalendar = user_data;
2895
2896         icalcomponent_add_component (
2897                 vcalendar,
2898                 icalcomponent_new_clone (recurrence));
2899 }
2900
2901 static gint
2902 sort_master_first (gconstpointer a,
2903                    gconstpointer b)
2904 {
2905         icalcomponent *ca, *cb;
2906
2907         ca = e_cal_component_get_icalcomponent ((ECalComponent *) a);
2908         cb = e_cal_component_get_icalcomponent ((ECalComponent *) b);
2909
2910         if (!ca) {
2911                 if (!cb)
2912                         return 0;
2913                 else
2914                         return -1;
2915         } else if (!cb) {
2916                 return 1;
2917         }
2918
2919         return icaltime_compare (icalcomponent_get_recurrenceid (ca), icalcomponent_get_recurrenceid (cb));
2920 }
2921
2922 /* Returns new icalcomponent, with all detached instances stored in a cache.
2923  * The cache lock should be locked when called this function.
2924 */
2925 static icalcomponent *
2926 get_comp_from_cache (ECalBackendCalDAV *cbdav,
2927                      const gchar *uid,
2928                      const gchar *rid,
2929                      gchar **href,
2930                      gchar **etag)
2931 {
2932         icalcomponent *icalcomp = NULL;
2933
2934         if (rid == NULL || !*rid) {
2935                 /* get with detached instances */
2936                 GSList *objects = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
2937
2938                 if (!objects) {
2939                         return NULL;
2940                 }
2941
2942                 if (g_slist_length (objects) == 1) {
2943                         ECalComponent *comp = objects->data;
2944
2945                         /* will be unreffed a bit later */
2946                         if (comp)
2947                                 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2948                 } else {
2949                         /* if we have detached recurrences, return a VCALENDAR */
2950                         icalcomp = e_cal_util_new_top_level ();
2951
2952                         objects = g_slist_sort (objects, sort_master_first);
2953
2954                         /* add all detached recurrences and the master object */
2955                         g_slist_foreach (objects, add_detached_recur_to_vcalendar_cb, icalcomp);
2956                 }
2957
2958                 /* every component has set same href and etag, thus it doesn't matter where it will be read */
2959                 if (href)
2960                         *href = ecalcomp_get_href (objects->data);
2961                 if (etag)
2962                         *etag = ecalcomp_get_etag (objects->data);
2963
2964                 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2965                 g_slist_free (objects);
2966         } else {
2967                 /* get the exact object */
2968                 ECalComponent *comp = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
2969
2970                 if (comp) {
2971                         icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2972                         if (href)
2973                                 *href = ecalcomp_get_href (comp);
2974                         if (etag)
2975                                 *etag = ecalcomp_get_etag (comp);
2976                         g_object_unref (comp);
2977                 }
2978         }
2979
2980         return icalcomp;
2981 }
2982
2983 static gboolean
2984 put_comp_to_cache (ECalBackendCalDAV *cbdav,
2985                    icalcomponent *icalcomp,
2986                    const gchar *href,
2987                    const gchar *etag)
2988 {
2989         icalcomponent_kind my_kind;
2990         ECalComponent *comp;
2991         gboolean res = FALSE;
2992
2993         g_return_val_if_fail (cbdav != NULL, FALSE);
2994         g_return_val_if_fail (icalcomp != NULL, FALSE);
2995
2996         my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
2997         comp = e_cal_component_new ();
2998
2999         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3000                 icalcomponent *subcomp;
3001
3002                 /* remove all old components from the cache first */
3003                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3004                      subcomp;
3005                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3006                         remove_comp_from_cache (cbdav, icalcomponent_get_uid (subcomp), NULL);
3007                 }
3008
3009                 /* then put new. It's because some detached instances could be removed on the server. */
3010                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3011                      subcomp;
3012                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3013                         /* because reusing the same comp doesn't clear recur_id member properly */
3014                         g_object_unref (comp);
3015                         comp = e_cal_component_new ();
3016
3017                         if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
3018                                 if (href)
3019                                         ecalcomp_set_href (comp, href);
3020                                 if (etag)
3021                                         ecalcomp_set_etag (comp, etag);
3022
3023                                 if (put_component_to_store (cbdav, comp))
3024                                         res = TRUE;
3025                         }
3026                 }
3027         } else if (icalcomponent_isa (icalcomp) == my_kind) {
3028                 remove_comp_from_cache (cbdav, icalcomponent_get_uid (icalcomp), NULL);
3029
3030                 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
3031                         if (href)
3032                                 ecalcomp_set_href (comp, href);
3033                         if (etag)
3034                                 ecalcomp_set_etag (comp, etag);
3035
3036                         res = put_component_to_store (cbdav, comp);
3037                 }
3038         }
3039
3040         g_object_unref (comp);
3041
3042         return res;
3043 }
3044
3045 static void
3046 remove_property (gpointer prop,
3047                  gpointer icomp)
3048 {
3049         icalcomponent_remove_property (icomp, prop);
3050         icalproperty_free (prop);
3051 }
3052
3053 static void
3054 strip_unneeded_x_props (icalcomponent *icomp)
3055 {
3056         icalproperty *prop;
3057         GSList *to_remove = NULL;
3058
3059         g_return_if_fail (icomp != NULL);
3060         g_return_if_fail (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT);
3061
3062         for (prop = icalcomponent_get_first_property (icomp, ICAL_X_PROPERTY);
3063              prop;
3064              prop = icalcomponent_get_next_property (icomp, ICAL_X_PROPERTY)) {
3065                 if (g_str_has_prefix (icalproperty_get_x_name (prop), X_E_CALDAV)) {
3066                         to_remove = g_slist_prepend (to_remove, prop);
3067                 }
3068         }
3069
3070         for (prop = icalcomponent_get_first_property (icomp, ICAL_XLICERROR_PROPERTY);
3071              prop;
3072              prop = icalcomponent_get_next_property (icomp, ICAL_XLICERROR_PROPERTY)) {
3073                 to_remove = g_slist_prepend (to_remove, prop);
3074         }
3075
3076         g_slist_foreach (to_remove, remove_property, icomp);
3077         g_slist_free (to_remove);
3078 }
3079
3080 static gboolean
3081 is_stored_on_server (ECalBackendCalDAV *cbdav,
3082                      const gchar *uri)
3083 {
3084         SoupURI *my_uri, *test_uri;
3085         gboolean res;
3086
3087         g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
3088         g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
3089         g_return_val_if_fail (uri != NULL, FALSE);
3090
3091         my_uri = soup_uri_new (cbdav->priv->uri);
3092         g_return_val_if_fail (my_uri != NULL, FALSE);
3093
3094         test_uri = soup_uri_new (uri);
3095         if (!test_uri) {
3096                 soup_uri_free (my_uri);
3097                 return FALSE;
3098         }
3099
3100         res = my_uri->host && test_uri->host && g_ascii_strcasecmp (my_uri->host, test_uri->host) == 0;
3101
3102         soup_uri_free (my_uri);
3103         soup_uri_free (test_uri);
3104
3105         return res;
3106 }
3107
3108 static void
3109 convert_to_inline_attachment (ECalBackendCalDAV *cbdav,
3110                               icalcomponent *icalcomp)
3111 {
3112         icalcomponent *cclone;
3113         icalproperty *p;
3114         GSList *to_remove = NULL;
3115
3116         g_return_if_fail (icalcomp != NULL);
3117
3118         cclone = icalcomponent_new_clone (icalcomp);
3119
3120         /* Remove local url attachments first */
3121         for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3122              p;
3123              p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3124                 icalattach *attach;
3125
3126                 attach = icalproperty_get_attach ((const icalproperty *) p);
3127                 if (icalattach_get_is_url (attach)) {
3128                         const gchar *url;
3129
3130                         url = icalattach_get_url (attach);
3131                         if (g_str_has_prefix (url, LOCAL_PREFIX))
3132                                 to_remove = g_slist_prepend (to_remove, p);
3133                 }
3134         }
3135         g_slist_foreach (to_remove, remove_property, icalcomp);
3136         g_slist_free (to_remove);
3137
3138         /* convert local url attachments to inline attachments now */
3139         for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY);
3140              p;
3141              p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY)) {
3142                 icalattach *attach;
3143                 GFile *file;
3144                 GError *error = NULL;
3145                 const gchar *uri;
3146                 gchar *basename;
3147                 gchar *content;
3148                 gsize len;
3149
3150                 attach = icalproperty_get_attach ((const icalproperty *) p);
3151                 if (!icalattach_get_is_url (attach))
3152                         continue;
3153
3154                 uri = icalattach_get_url (attach);
3155                 if (!g_str_has_prefix (uri, LOCAL_PREFIX))
3156                         continue;
3157
3158                 file = g_file_new_for_uri (uri);
3159                 basename = g_file_get_basename (file);
3160                 if (g_file_load_contents (file, NULL, &content, &len, NULL, &error)) {
3161                         icalproperty *prop;
3162                         icalparameter *param;
3163                         gchar *encoded;
3164
3165                         /*
3166                          * do a base64 encoding so it can
3167                          * be embedded in a soap message
3168                          */
3169                         encoded = g_base64_encode ((guchar *) content, len);
3170                         attach = icalattach_new_from_data (encoded, NULL, NULL);
3171                         g_free (content);
3172                         g_free (encoded);
3173
3174                         prop = icalproperty_new_attach (attach);
3175                         icalattach_unref (attach);
3176
3177                         param = icalparameter_new_value (ICAL_VALUE_BINARY);
3178                         icalproperty_add_parameter (prop, param);
3179
3180                         param = icalparameter_new_encoding (ICAL_ENCODING_BASE64);
3181                         icalproperty_add_parameter (prop, param);
3182
3183                         param = icalparameter_new_x (basename);
3184                         icalparameter_set_xname (param, X_E_CALDAV_ATTACHMENT_NAME);
3185                         icalproperty_add_parameter (prop, param);
3186
3187                         icalcomponent_add_property (icalcomp, prop);
3188                 } else {
3189                         g_warning ("%s\n", error->message);
3190                         g_clear_error (&error);
3191                 }
3192                 g_free (basename);
3193                 g_object_unref (file);
3194         }
3195         icalcomponent_free (cclone);
3196 }
3197
3198 static void
3199 convert_to_url_attachment (ECalBackendCalDAV *cbdav,
3200                            icalcomponent *icalcomp)
3201 {
3202         ECalBackend *backend;
3203         GSList *to_remove = NULL, *to_remove_after_download = NULL;
3204         icalcomponent *cclone;
3205         icalproperty *p;
3206         gint fileindex;
3207
3208         g_return_if_fail (cbdav != NULL);
3209         g_return_if_fail (icalcomp != NULL);
3210
3211         backend = E_CAL_BACKEND (cbdav);
3212         cclone = icalcomponent_new_clone (icalcomp);
3213
3214         /* Remove all inline attachments first */
3215         for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3216              p;
3217              p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3218                 icalattach *attach;
3219
3220                 attach = icalproperty_get_attach ((const icalproperty *) p);
3221                 if (!icalattach_get_is_url (attach))
3222                         to_remove = g_slist_prepend (to_remove, p);
3223                 else if (is_stored_on_server (cbdav, icalattach_get_url (attach)))
3224                         to_remove_after_download = g_slist_prepend (to_remove_after_download, p);
3225         }
3226         g_slist_foreach (to_remove, remove_property, icalcomp);
3227         g_slist_free (to_remove);
3228
3229         /* convert inline attachments to url attachments now */
3230         for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY), fileindex = 0;
3231              p;
3232              p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY), fileindex++) {
3233                 icalattach *attach;
3234                 gsize len = -1;
3235                 gchar *decoded = NULL;
3236                 gchar *basename, *local_filename;
3237
3238                 attach = icalproperty_get_attach ((const icalproperty *) p);
3239                 if (icalattach_get_is_url (attach)) {
3240                         const gchar *attach_url = icalattach_get_url (attach);
3241                         GError *error = NULL;
3242
3243                         if (!is_stored_on_server (cbdav, attach_url))
3244                                 continue;
3245
3246                         if (!caldav_server_download_attachment (cbdav, attach_url, &decoded, &len, &error)) {
3247                                 if (caldav_debug_show (DEBUG_ATTACHMENTS))
3248                                         g_print ("CalDAV::%s: Failed to download from a server: %s\n", G_STRFUNC, error ? error->message : "Unknown error");
3249                                 continue;
3250                         }
3251                 }
3252
3253                 basename = icalproperty_get_parameter_as_string_r (p, X_E_CALDAV_ATTACHMENT_NAME);
3254                 local_filename = e_cal_backend_create_cache_filename (backend, icalcomponent_get_uid (icalcomp), basename, fileindex);
3255                 g_free (basename);
3256
3257                 if (local_filename) {
3258                         GError *error = NULL;
3259
3260                         if (decoded == NULL) {
3261                                 gchar *content;
3262
3263                                 content = (gchar *) icalattach_get_data (attach);
3264                                 decoded = (gchar *) g_base64_decode (content, &len);
3265                         }
3266
3267                         if (g_file_set_contents (local_filename, decoded, len, &error)) {
3268                                 icalproperty *prop;
3269                                 gchar *url;
3270
3271                                 url = g_filename_to_uri (local_filename, NULL, NULL);
3272                                 attach = icalattach_new_from_url (url);
3273                                 prop = icalproperty_new_attach (attach);
3274                                 icalattach_unref (attach);
3275                                 icalcomponent_add_property (icalcomp, prop);
3276                                 g_free (url);
3277                         } else {
3278                                 g_warning ("%s\n", error->message);
3279                                 g_clear_error (&error);
3280                         }
3281
3282                         g_free (local_filename);
3283                 }
3284         }
3285
3286         icalcomponent_free (cclone);
3287
3288         g_slist_foreach (to_remove_after_download, remove_property, icalcomp);
3289         g_slist_free (to_remove_after_download);
3290 }
3291
3292 static void
3293 remove_files (const gchar *dir,
3294               const gchar *fileprefix)
3295 {
3296         GDir *d;
3297
3298         g_return_if_fail (dir != NULL);
3299         g_return_if_fail (fileprefix != NULL);
3300         g_return_if_fail (*fileprefix != '\0');
3301
3302         d = g_dir_open (dir, 0, NULL);
3303         if (d) {
3304                 const gchar *entry;
3305                 gint len = strlen (fileprefix);
3306
3307                 while ((entry = g_dir_read_name (d)) != NULL) {
3308                         if (entry && strncmp (entry, fileprefix, len) == 0) {
3309                                 gchar *path;
3310
3311                                 path = g_build_filename (dir, entry, NULL);
3312                                 if (!g_file_test (path, G_FILE_TEST_IS_DIR))
3313                                         g_unlink (path);
3314                                 g_free (path);
3315                         }
3316                 }
3317                 g_dir_close (d);
3318         }
3319 }
3320
3321 static void
3322 remove_cached_attachment (ECalBackendCalDAV *cbdav,
3323                           const gchar *uid)
3324 {
3325         GSList *l;
3326         guint len;
3327         gchar *dir;
3328         gchar *fileprefix;
3329
3330         g_return_if_fail (cbdav != NULL);
3331         g_return_if_fail (uid != NULL);
3332
3333         l = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
3334         len = g_slist_length (l);
3335         g_slist_foreach (l, (GFunc) g_object_unref, NULL);
3336         g_slist_free (l);
3337         if (len > 0)
3338                 return;
3339
3340         dir = e_cal_backend_create_cache_filename (E_CAL_BACKEND (cbdav), uid, "a", 0);
3341         if (!dir)
3342                 return;
3343
3344         fileprefix = g_strrstr (dir, G_DIR_SEPARATOR_S);
3345         if (fileprefix) {
3346                 *fileprefix = '\0';
3347                 fileprefix++;
3348
3349                 if (*fileprefix)
3350                         fileprefix[strlen (fileprefix) - 1] = '\0';
3351
3352                 remove_files (dir, fileprefix);
3353         }
3354
3355         g_free (dir);
3356 }
3357
3358 /* callback for icalcomponent_foreach_tzid */
3359 typedef struct {
3360         ECalBackendStore *store;
3361         icalcomponent *vcal_comp;
3362         icalcomponent *icalcomp;
3363 } ForeachTzidData;
3364
3365 static void
3366 add_timezone_cb (icalparameter *param,
3367                  gpointer data)
3368 {
3369         icaltimezone *tz;
3370         const gchar *tzid;
3371         icalcomponent *vtz_comp;
3372         ForeachTzidData *f_data = (ForeachTzidData *) data;
3373
3374         tzid = icalparameter_get_tzid (param);
3375         if (!tzid)
3376                 return;
3377
3378         tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
3379         if (tz)
3380                 return;
3381
3382         tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
3383         if (!tz) {
3384                 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3385                 if (!tz)
3386                         tz = (icaltimezone *) e_cal_backend_store_get_timezone (f_data->store, tzid);
3387                 if (!tz)
3388                         return;
3389         }
3390
3391         vtz_comp = icaltimezone_get_component (tz);
3392         if (!vtz_comp)
3393                 return;
3394
3395         icalcomponent_add_component (f_data->vcal_comp,
3396                                      icalcomponent_new_clone (vtz_comp));
3397 }
3398
3399 static void
3400 add_timezones_from_component (ECalBackendCalDAV *cbdav,
3401                               icalcomponent *vcal_comp,
3402                               icalcomponent *icalcomp)
3403 {
3404         ForeachTzidData f_data;
3405
3406         g_return_if_fail (cbdav != NULL);
3407         g_return_if_fail (vcal_comp != NULL);
3408         g_return_if_fail (icalcomp != NULL);
3409
3410         f_data.store = cbdav->priv->store;
3411         f_data.vcal_comp = vcal_comp;
3412         f_data.icalcomp = icalcomp;
3413
3414         icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
3415 }
3416
3417 /* also removes X-EVOLUTION-CALDAV from all the components */
3418 static gchar *
3419 pack_cobj (ECalBackendCalDAV *cbdav,
3420            icalcomponent *icomp)
3421 {
3422         icalcomponent *calcomp;
3423         gchar          *objstr;
3424
3425         if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
3426                 icalcomponent *cclone;
3427
3428                 calcomp = e_cal_util_new_top_level ();
3429
3430                 cclone = icalcomponent_new_clone (icomp);
3431                 strip_unneeded_x_props (cclone);
3432                 convert_to_inline_attachment (cbdav, cclone);
3433                 icalcomponent_add_component (calcomp, cclone);
3434                 add_timezones_from_component (cbdav, calcomp, cclone);
3435         } else {
3436                 icalcomponent *subcomp;
3437                 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3438
3439                 calcomp = icalcomponent_new_clone (icomp);
3440                 for (subcomp = icalcomponent_get_first_component (calcomp, my_kind);
3441                      subcomp;
3442                      subcomp = icalcomponent_get_next_component (calcomp, my_kind)) {
3443                         strip_unneeded_x_props (subcomp);
3444                         convert_to_inline_attachment (cbdav, subcomp);
3445                         add_timezones_from_component (cbdav, calcomp, subcomp);
3446                 }
3447         }
3448
3449         objstr = icalcomponent_as_ical_string_r (calcomp);
3450         icalcomponent_free (calcomp);
3451
3452         g_assert (objstr);
3453
3454         return objstr;
3455
3456 }
3457
3458 static void
3459 sanitize_component (ECalBackend *cb,
3460                     ECalComponent *comp)
3461 {
3462         ECalComponentDateTime dt;
3463         icaltimezone *zone;
3464
3465         /* Check dtstart, dtend and due's timezone, and convert it to local
3466          * default timezone if the timezone is not in our builtin timezone
3467          * list */
3468         e_cal_component_get_dtstart (comp, &dt);
3469         if (dt.value && dt.tzid) {
3470                 zone = caldav_internal_get_timezone (cb, dt.tzid);
3471                 if (!zone) {
3472                         g_free ((gchar *) dt.tzid);
3473                         dt.tzid = g_strdup ("UTC");
3474                         e_cal_component_set_dtstart (comp, &dt);
3475                 }
3476         }
3477         e_cal_component_free_datetime (&dt);
3478
3479         e_cal_component_get_dtend (comp, &dt);
3480         if (dt.value && dt.tzid) {
3481                 zone = caldav_internal_get_timezone (cb, dt.tzid);
3482                 if (!zone) {
3483                         g_free ((gchar *) dt.tzid);
3484                         dt.tzid = g_strdup ("UTC");
3485                         e_cal_component_set_dtend (comp, &dt);
3486                 }
3487         }
3488         e_cal_component_free_datetime (&dt);
3489
3490         e_cal_component_get_due (comp, &dt);
3491         if (dt.value && dt.tzid) {
3492                 zone = caldav_internal_get_timezone (cb, dt.tzid);
3493                 if (!zone) {
3494                         g_free ((gchar *) dt.tzid);
3495                         dt.tzid = g_strdup ("UTC");
3496                         e_cal_component_set_due (comp, &dt);
3497                 }
3498         }
3499         e_cal_component_free_datetime (&dt);
3500         e_cal_component_abort_sequence (comp);
3501 }
3502
3503 static gboolean
3504 cache_contains (ECalBackendCalDAV *cbdav,
3505                 const gchar *uid,
3506                 const gchar *rid)
3507 {
3508         gboolean res;
3509         ECalComponent *comp;
3510
3511         g_return_val_if_fail (cbdav != NULL, FALSE);
3512         g_return_val_if_fail (uid != NULL, FALSE);
3513
3514         g_return_val_if_fail (cbdav->priv->store != NULL, FALSE);
3515
3516         comp = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
3517         res = comp != NULL;
3518
3519         if (comp)
3520                 g_object_unref (comp);
3521
3522         return res;
3523 }
3524
3525 /* Returns subcomponent of icalcomp, which is a master object, or icalcomp itself, if it's not a VCALENDAR;
3526  * Do not free returned pointer, it'll be freed together with the icalcomp.
3527 */
3528 static icalcomponent *
3529 get_master_comp (ECalBackendCalDAV *cbdav,
3530                  icalcomponent *icalcomp)
3531 {
3532         icalcomponent *master = icalcomp;
3533
3534         g_return_val_if_fail (cbdav != NULL, NULL);
3535         g_return_val_if_fail (icalcomp != NULL, NULL);
3536
3537         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3538                 icalcomponent *subcomp;
3539                 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3540
3541                 master = NULL;
3542
3543                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3544                      subcomp;
3545                      subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3546                         struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3547
3548                         if (icaltime_is_null_time (sub_rid)) {
3549                                 master = subcomp;
3550                                 break;
3551                         }
3552                 }
3553         }
3554
3555         return master;
3556 }
3557
3558 static gboolean
3559 remove_instance (ECalBackendCalDAV *cbdav,
3560                  icalcomponent *icalcomp,
3561                  struct icaltimetype rid,
3562                  CalObjModType mod,
3563                  gboolean also_exdate)
3564 {
3565         icalcomponent *master = icalcomp;
3566         gboolean res = FALSE;
3567
3568         g_return_val_if_fail (icalcomp != NULL, res);
3569         g_return_val_if_fail (!icaltime_is_null_time (rid), res);
3570
3571         /* remove an instance only */
3572         if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3573                 icalcomponent *subcomp;
3574                 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3575                 gint left = 0;
3576                 gboolean start_first = FALSE;
3577
3578                 master = NULL;
3579
3580                 /* remove old instance first */
3581                 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3582                      subcomp;
3583                      subcomp = start_first ? icalcomponent_get_first_component (icalcomp, my_kind) : icalcomponent_get_next_component (icalcomp, my_kind)) {
3584                         struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3585
3586                         start_first = FALSE;
3587
3588                         if (icaltime_is_null_time (sub_rid)) {
3589                                 master = subcomp;
3590                                 left++;
3591                         } else if (icaltime_compare (sub_rid, rid) == 0) {
3592                                 icalcomponent_remove_component (icalcomp, subcomp);
3593                                 icalcomponent_free (subcomp);
3594                                 if (master) {
3595                                         break;
3596                                 } else {
3597                                         /* either no master or master not as the first component, thus rescan */
3598                                         left = 0;
3599                                         start_first = TRUE;
3600                                 }
3601                         } else {
3602                                 left++;
3603                         }
3604                 }
3605
3606                 /* whether left at least one instance or a master object */
3607                 res = left > 0;
3608         } else {
3609                 res = TRUE;
3610         }
3611
3612         if (master && also_exdate) {
3613                 e_cal_util_remove_instances (master, rid, mod);
3614         }
3615
3616         return res;
3617 }
3618
3619 static icalcomponent *
3620 replace_master (ECalBackendCalDAV *cbdav,
3621                 icalcomponent *old_comp,
3622                 icalcomponent *new_master)
3623 {
3624         icalcomponent *old_master;
3625         if (icalcomponent_isa (old_comp) != ICAL_VCALENDAR_COMPONENT) {
3626                 icalcomponent_free (old_comp);
3627                 return new_master;
3628         }
3629
3630         old_master = get_master_comp (cbdav, old_comp);
3631         if (!old_master) {
3632                 /* no master, strange */
3633                 icalcomponent_free (new_master);
3634         } else {
3635                 icalcomponent_remove_component (old_comp, old_master);
3636                 icalcomponent_free (old_master);
3637
3638                 icalcomponent_add_component (old_comp, new_master);
3639         }
3640
3641         return old_comp;
3642 }
3643
3644 /* the resulting component should be unreffed when done with it;
3645  * the fallback_comp is cloned, if used */
3646 static ECalComponent *
3647 get_ecalcomp_master_from_cache_or_fallback (ECalBackendCalDAV *cbdav,
3648                                             const gchar *uid,
3649                                             const gchar *rid,
3650                                             ECalComponent *fallback_comp)
3651 {
3652         ECalComponent *comp = NULL;
3653         icalcomponent *icalcomp;
3654
3655         g_return_val_if_fail (cbdav != NULL, NULL);
3656         g_return_val_if_fail (uid != NULL, NULL);
3657
3658         icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
3659         if (icalcomp) {
3660                 icalcomponent *master = get_master_comp (cbdav, icalcomp);
3661
3662                 if (master) {
3663                         comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3664                 }
3665
3666                 icalcomponent_free (icalcomp);
3667         }
3668
3669         if (!comp && fallback_comp)
3670                 comp = e_cal_component_clone (fallback_comp);
3671
3672         return comp;
3673 }
3674
3675 /* a busy_lock is supposed to be locked already, when calling this function */
3676 static void
3677 do_create_objects (ECalBackendCalDAV *cbdav,
3678                    const GSList *in_calobjs,
3679                    GSList **uids,
3680                    GSList **new_components,
3681                    GError **perror)
3682 {
3683         ECalComponent            *comp;
3684         gboolean                  online, did_put = FALSE;
3685         struct icaltimetype current;
3686         icalcomponent *icalcomp;
3687         const gchar *in_calobj = in_calobjs->data;
3688         const gchar *comp_uid;
3689
3690         if (!check_state (cbdav, &online, perror))
3691                 return;
3692
3693         /* 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"
3694          * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3695         if (in_calobjs->next != NULL) {
3696                 g_propagate_error (perror, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk additions")));
3697                 return;
3698         }
3699
3700         comp = e_cal_component_new_from_string (in_calobj);
3701
3702         if (comp == NULL) {
3703                 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3704                 return;
3705         }
3706
3707         icalcomp = e_cal_component_get_icalcomponent (comp);
3708         if (icalcomp == NULL) {
3709                 g_object_unref (comp);
3710                 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3711                 return;
3712         }
3713
3714         comp_uid = icalcomponent_get_uid (icalcomp);
3715         if (!comp_uid) {
3716                 gchar *new_uid;
3717
3718                 new_uid = e_cal_component_gen_uid ();
3719                 if (!new_uid) {
3720                         g_object_unref (comp);
3721                         g_propagate_error (perror, EDC_ERROR (InvalidObject));
3722                         return;
3723                 }
3724
3725                 icalcomponent_set_uid (icalcomp, new_uid);
3726                 comp_uid = icalcomponent_get_uid (icalcomp);
3727
3728                 g_free (new_uid);
3729         }
3730
3731         /* check the object is not in our cache */
3732         if (cache_contains (cbdav, comp_uid, NULL)) {
3733                 g_object_unref (comp);
3734                 g_propagate_error (perror, EDC_ERROR (ObjectIdAlreadyExists));
3735                 return;
3736         }
3737
3738         /* Set the created and last modified times on the component */
3739         current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3740         e_cal_component_set_created (comp, &current);
3741         e_cal_component_set_last_modified (comp, &current);
3742
3743         /* sanitize the component*/
3744         sanitize_component ((ECalBackend *) cbdav, comp);
3745
3746         if (online) {
3747                 CalDAVObject object;
3748
3749                 object.href  = ecalcomp_gen_href (comp);
3750                 object.etag  = NULL;
3751                 object.cdata = pack_cobj (cbdav, icalcomp);
3752
3753                 did_put = caldav_server_put_object (cbdav, &object, icalcomp, perror);
3754
3755                 caldav_object_free (&object, FALSE);
3756         } else {
3757                 /* mark component as out of synch */
3758                 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
3759         }
3760
3761         if (did_put) {
3762                 if (uids)
3763                         *uids = g_slist_prepend (*uids, g_strdup (comp_uid));
3764
3765                 if (new_components)
3766                         *new_components = g_slist_prepend(*new_components, get_ecalcomp_master_from_cache_or_fallback (cbdav, comp_uid, NULL, comp));
3767         }
3768
3769         g_object_unref (comp);
3770 }
3771
3772 /* a busy_lock is supposed to be locked already, when calling this function */
3773 static void
3774 do_modify_objects (ECalBackendCalDAV *cbdav,
3775                    const GSList *calobjs,
3776                    CalObjModType mod,
3777                    GSList **old_components,
3778                    GSList **new_components,
3779                    GError **error)
3780 {
3781         ECalComponent            *comp;
3782         icalcomponent            *cache_comp;
3783         gboolean                  online, did_put = FALSE;
3784         ECalComponentId          *id;
3785         struct icaltimetype current;
3786         gchar *href = NULL, *etag = NULL;
3787         const gchar *calobj = calobjs->data;
3788
3789         if (new_components)
3790                 *new_components = NULL;
3791
3792         if (!check_state (cbdav, &online, error))
3793                 return;
3794
3795         /* We make the assumption that the calobjs list we're passed is always exactly one element long, since we haven't specified "bulk-modifies"
3796          * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3797         if (calobjs->next != NULL) {
3798                 g_propagate_error (error, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk modifications")));
3799                 return;
3800         }
3801
3802         comp = e_cal_component_new_from_string (calobj);
3803
3804         if (comp == NULL) {
3805                 g_propagate_error (error, EDC_ERROR (InvalidObject));
3806                 return;
3807         }
3808
3809         if (!e_cal_component_get_icalcomponent (comp) ||
3810             icalcomponent_isa (e_cal_component_get_icalcomponent (comp)) != e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
3811                 g_object_unref (comp);
3812                 g_propagate_error (error, EDC_ERROR (InvalidObject));
3813                 return;
3814         }
3815
3816         /* Set the last modified time on the component */
3817         current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3818         e_cal_component_set_last_modified (comp, &current);
3819
3820         /* sanitize the component */
3821         sanitize_component ((ECalBackend *) cbdav, comp);
3822
3823         id = e_cal_component_get_id (comp);
3824         e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
3825
3826         /* fetch full component from cache, it will be pushed to the server */
3827         cache_comp = get_comp_from_cache (cbdav, id->uid, NULL, &href, &etag);
3828
3829         if (cache_comp == NULL) {
3830                 e_cal_component_free_id (id);
3831                 g_object_unref (comp);
3832                 g_free (href);
3833                 g_free (etag);
3834                 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
3835                 return;
3836         }
3837
3838         if (!online) {
3839                 /* mark component as out of synch */
3840                 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3841         }
3842
3843         if (old_components) {
3844                 *old_components = NULL;
3845
3846                 if (e_cal_component_is_instance (comp)) {
3847                         /* set detached instance as the old object, if any */
3848                         ECalComponent *old_instance = e_cal_backend_store_get_component (cbdav->priv->store, id->uid, id->rid);
3849
3850                         /* This will give a reference to 'old_component' */
3851                         if (old_instance) {
3852                                 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (old_instance));
3853                                 g_object_unref (old_instance);
3854                         }
3855                 }
3856
3857                 if (!*old_components) {
3858                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
3859
3860                         if (master) {
3861                                 /* set full component as the old object */
3862                                 *old_components = g_slist_prepend (*old_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
3863                         }
3864                 }
3865         }
3866
3867         switch (mod) {
3868         case CALOBJ_MOD_ONLY_THIS:
3869         case CALOBJ_MOD_THIS:
3870                 if (e_cal_component_is_instance (comp)) {
3871                         icalcomponent *new_comp = e_cal_component_get_icalcomponent (comp);
3872
3873                         /* new object is only this instance */
3874                         if (new_components)
3875                                 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
3876
3877                         /* add the detached instance */
3878                         if (icalcomponent_isa (cache_comp) == ICAL_VCALENDAR_COMPONENT) {
3879                                 /* do not modify the EXDATE, as the component will be put back */
3880                                 remove_instance (cbdav, cache_comp, icalcomponent_get_recurrenceid (new_comp), mod, FALSE);
3881                         } else {
3882                                 /* this is only a master object, thus make is a VCALENDAR component */
3883                                 icalcomponent *icomp;
3884
3885                                 icomp = e_cal_util_new_top_level ();
3886                                 icalcomponent_add_component (icomp, cache_comp);
3887
3888                                 /* no need to free the cache_comp, as it is inside icomp */
3889                                 cache_comp = icomp;
3890                         }
3891
3892                         if (cache_comp && cbdav->priv->is_google) {
3893                                 icalcomponent_set_sequence (cache_comp, icalcomponent_get_sequence (cache_comp) + 1);
3894                                 icalcomponent_set_sequence (new_comp, icalcomponent_get_sequence (new_comp) + 1);
3895                         }
3896
3897                         /* add the detached instance finally */
3898                         icalcomponent_add_component (cache_comp, icalcomponent_new_clone (new_comp));
3899                 } else {
3900                         cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3901                 }
3902                 break;
3903         case CALOBJ_MOD_ALL:
3904                 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3905                 break;
3906         case CALOBJ_MOD_THISANDPRIOR:
3907         case CALOBJ_MOD_THISANDFUTURE:
3908                 break;
3909         }
3910
3911         if (online) {
3912                 CalDAVObject object;
3913
3914                 object.href  = href;
3915                 object.etag  = etag;
3916                 object.cdata = pack_cobj (cbdav, cache_comp);
3917
3918                 did_put = caldav_server_put_object (cbdav, &object, cache_comp, error);
3919
3920                 caldav_object_free (&object, FALSE);
3921                 href = NULL;
3922                 etag = NULL;
3923         } else {
3924                 /* mark component as out of synch */
3925                 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3926         }
3927
3928         if (did_put) {
3929                 if (new_components && !*new_components) {
3930                         /* read the comp from cache again, as some servers can modify it on put */
3931                         *new_components = g_slist_prepend (*new_components, get_ecalcomp_master_from_cache_or_fallback (cbdav, id->uid, id->rid, NULL));
3932                 }
3933         }
3934
3935         e_cal_component_free_id (id);
3936         icalcomponent_free (cache_comp);
3937         g_object_unref (comp);
3938         g_free (href);
3939         g_free (etag);
3940 }
3941
3942 /* a busy_lock is supposed to be locked already, when calling this function */
3943 static void
3944 do_remove_objects (ECalBackendCalDAV *cbdav,
3945                    const GSList *ids,
3946                    CalObjModType mod,
3947                    GSList **old_components,
3948                    GSList **new_components,
3949                    GError **perror)
3950 {
3951         icalcomponent            *cache_comp;
3952         gboolean                  online;
3953         gchar *href = NULL, *etag = NULL;
3954         const gchar *uid = ((ECalComponentId *) ids->data)->uid;
3955         const gchar *rid = ((ECalComponentId *) ids->data)->rid;
3956
3957         if (new_components)
3958                 *new_components = NULL;
3959
3960         if (!check_state (cbdav, &online, perror))
3961                 return;
3962
3963         /* We make the assumption that the ids list we're passed is always exactly one element long, since we haven't specified "bulk-removes"
3964          * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3965         if (ids->next != NULL) {
3966                 g_propagate_error (perror, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk removals")));
3967                 return;
3968         }
3969
3970         cache_comp = get_comp_from_cache (cbdav, uid, NULL, &href, &etag);
3971
3972         if (cache_comp == NULL) {
3973                 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
3974                 return;
3975         }
3976
3977         if (old_components) {
3978                 ECalComponent *old = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
3979
3980                 if (old) {
3981                         *old_components = g_slist_prepend (*old_components, e_cal_component_clone (old));
3982                         g_object_unref (old);
3983                 } else {
3984                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
3985                         if (master) {
3986                                 *old_components = g_slist_prepend (*old_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
3987                         }
3988                 }
3989         }
3990
3991         switch (mod) {
3992         case CALOBJ_MOD_ONLY_THIS:
3993         case CALOBJ_MOD_THIS:
3994                 if (rid && *rid) {
3995                         /* remove one instance from the component */
3996                         if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, mod != CALOBJ_MOD_ONLY_THIS)) {
3997                                 if (new_components) {
3998                                         icalcomponent *master = get_master_comp (cbdav, cache_comp);
3999                                         if (master) {
4000                                                 *new_components = g_slist_prepend (*new_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
4001                                         }
4002                                 }
4003                         } else {
4004                                 /* this was the last instance, thus delete whole component */
4005                                 rid = NULL;
4006                                 remove_comp_from_cache (cbdav, uid, NULL);
4007                         }
4008                 } else {
4009                         /* remove whole object */
4010                         remove_comp_from_cache (cbdav, uid, NULL);
4011                 }
4012                 break;
4013         case CALOBJ_MOD_ALL:
4014                 remove_comp_from_cache (cbdav, uid, NULL);
4015                 break;
4016         case CALOBJ_MOD_THISANDPRIOR:
4017         case CALOBJ_MOD_THISANDFUTURE:
4018                 break;
4019         }
4020
4021         if (online) {
4022                 CalDAVObject caldav_object;
4023
4024                 caldav_object.href  = href;
4025                 caldav_object.etag  = etag;
4026                 caldav_object.cdata = NULL;
4027
4028                 if (mod == CALOBJ_MOD_THIS && rid && *rid) {
4029                         caldav_object.cdata = pack_cobj (cbdav, cache_comp);
4030
4031                         caldav_server_put_object (cbdav, &caldav_object, cache_comp, perror);
4032                 } else
4033                         caldav_server_delete_object (cbdav, &caldav_object, perror);
4034
4035                 caldav_object_free (&caldav_object, FALSE);
4036                 href = NULL;
4037                 etag = NULL;
4038         } else {
4039                 /* mark component as out of synch */
4040                 /*if (mod == CALOBJ_MOD_THIS && rid && *rid)
4041                         ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_MODIFIED);
4042                 else
4043                         ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_DELETED);*/
4044         }
4045         remove_cached_attachment (cbdav, uid);
4046
4047         icalcomponent_free (cache_comp);
4048         g_free (href);
4049         g_free (etag);
4050 }
4051
4052 static void
4053 extract_objects (icalcomponent *icomp,
4054                  icalcomponent_kind ekind,
4055                  GSList **objects,
4056                  GError **error)
4057 {
4058         icalcomponent         *scomp;
4059         icalcomponent_kind     kind;
4060
4061         e_return_data_cal_error_if_fail (icomp, InvalidArg);
4062         e_return_data_cal_error_if_fail (objects, InvalidArg);
4063
4064         kind = icalcomponent_isa (icomp);
4065
4066         if (kind == ekind) {
4067                 *objects = g_slist_prepend (NULL, icomp);
4068                 return;
4069         }
4070
4071         if (kind != ICAL_VCALENDAR_COMPONENT) {
4072                 g_propagate_error (error, EDC_ERROR (InvalidObject));
4073                 return;
4074         }
4075
4076         *objects = NULL;
4077         scomp = icalcomponent_get_first_component (icomp,
4078                                                    ekind);
4079
4080         while (scomp) {
4081                 /* Remove components from toplevel here */
4082                 *objects = g_slist_prepend (*objects, scomp);
4083                 icalcomponent_remove_component (icomp, scomp);
4084
4085                 scomp = icalcomponent_get_next_component (icomp, ekind);
4086         }
4087 }
4088
4089 static gboolean
4090 extract_timezones (ECalBackendCalDAV *cbdav,
4091                    icalcomponent *icomp)
4092 {
4093         GSList *timezones = NULL, *iter;
4094         icaltimezone *zone;
4095         GError *err = NULL;
4096
4097         g_return_val_if_fail (cbdav != NULL, FALSE);
4098         g_return_val_if_fail (icomp != NULL, FALSE);
4099
4100         extract_objects (icomp, ICAL_VTIMEZONE_COMPONENT, &timezones, &err);
4101         if (err) {
4102                 g_error_free (err);
4103                 return FALSE;
4104         }
4105
4106         zone = icaltimezone_new ();
4107         for (iter = timezones; iter; iter = iter->next) {
4108                 if (icaltimezone_set_component (zone, iter->data)) {
4109                         e_cal_backend_store_put_timezone (cbdav->priv->store, zone);
4110                 } else {
4111                         icalcomponent_free (iter->data);
4112                 }
4113         }
4114
4115         icaltimezone_free (zone, TRUE);
4116         g_slist_free (timezones);
4117
4118         return TRUE;
4119 }
4120
4121 static void
4122 process_object (ECalBackendCalDAV *cbdav,
4123                 ECalComponent *ecomp,
4124                 gboolean online,
4125                 icalproperty_method method,
4126                 GError **error)
4127 {
4128         ESourceRegistry *registry;
4129         ECalBackend              *backend;
4130         struct icaltimetype       now;
4131         gchar *new_obj_str;
4132         gboolean is_declined, is_in_cache;
4133         CalObjModType mod;
4134         ECalComponentId *id = e_cal_component_get_id (ecomp);
4135         GError *err = NULL;
4136
4137         backend = E_CAL_BACKEND (cbdav);
4138
4139         e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
4140
4141         registry = e_cal_backend_get_registry (E_CAL_BACKEND (cbdav));
4142
4143         /* ctime, mtime */
4144         now = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
4145         e_cal_component_set_created (ecomp, &now);
4146         e_cal_component_set_last_modified (ecomp, &now);
4147
4148         /* just to check whether component exists in a cache */
4149         is_in_cache = cache_contains (cbdav, id->uid, NULL) || cache_contains (cbdav, id->uid, id->rid);
4150
4151         new_obj_str = e_cal_component_get_as_string (ecomp);
4152         mod = e_cal_component_is_instance (ecomp) ? CALOBJ_MOD_THIS : CALOBJ_MOD_ALL;
4153
4154         switch (method) {
4155         case ICAL_METHOD_PUBLISH:
4156         case ICAL_METHOD_REQUEST:
4157         case ICAL_METHOD_REPLY:
4158                 is_declined = e_cal_backend_user_declined (
4159                         registry, e_cal_component_get_icalcomponent (ecomp));
4160                 if (is_in_cache) {
4161                         if (!is_declined) {
4162                                 GSList *new_components = NULL, *old_components = NULL;
4163                                 GSList new_obj_strs = {0,};
4164
4165                                 new_obj_strs.data = new_obj_str;
4166                                 do_modify_objects (cbdav, &new_obj_strs, mod,
4167                                                   &old_components, &new_components, &err);
4168                                 if (!err && new_components && new_components->data) {
4169                                         if (!old_components || !old_components->data) {
4170                                                 e_cal_backend_notify_component_created (backend, new_components->data);
4171                                         } else {
4172                                                 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4173                                         }
4174                                 }
4175
4176                                 e_util_free_nullable_object_slist (old_components);
4177                                 e_util_free_nullable_object_slist (new_components);
4178                         } else {
4179                                 GSList *new_components = NULL, *old_components = NULL;
4180                                 GSList ids = {0,};
4181
4182                                 ids.data = id;
4183                                 do_remove_objects (cbdav, &ids, mod, &old_components, &new_components, &err);
4184                                 if (!err && old_components && old_components->data) {
4185                                         if (new_components && new_components->data) {
4186                                                 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4187                                         } else {
4188                                                 e_cal_backend_notify_component_removed (backend, id, old_components->data, NULL);
4189                                         }
4190                                 }
4191
4192                                 e_util_free_nullable_object_slist (old_components);
4193                                 e_util_free_nullable_object_slist (new_components);
4194                         }
4195                 } else if (!is_declined) {
4196                         GSList *new_components = NULL;
4197                         GSList new_objs = {0,};
4198
4199                         new_objs.data = new_obj_str;
4200
4201                         do_create_objects (cbdav, &new_objs, NULL, &new_components, &err);
4202
4203                         if (!err) {
4204                                 if (new_components && new_components->data)
4205                                         e_cal_backend_notify_component_created (backend, new_components->data);
4206                         }
4207
4208                         e_util_free_nullable_object_slist (new_components);
4209                 }
4210                 break;
4211         case ICAL_METHOD_CANCEL:
4212                 if (is_in_cache) {
4213                         GSList *new_components = NULL, *old_components = NULL;
4214                         GSList ids = {0,};
4215
4216                         ids.data = id;
4217                         do_remove_objects (cbdav, &ids, CALOBJ_MOD_THIS, &old_components, &new_components, &err);
4218                         if (!err && old_components && old_components->data) {
4219                                 if (new_components && new_components->data) {
4220                                         e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4221                                 } else {
4222                                         e_cal_backend_notify_component_removed (backend, id, old_components->data, NULL);
4223                                 }
4224                         }
4225
4226                         e_util_free_nullable_object_slist (old_components);
4227                         e_util_free_nullable_object_slist (new_components);
4228                 } else {
4229                         err = EDC_ERROR (ObjectNotFound);
4230                 }
4231                 break;
4232
4233         default:
4234                 err = EDC_ERROR (UnsupportedMethod);
4235                 break;
4236         }
4237
4238         e_cal_component_free_id (id);
4239         g_free (new_obj_str);
4240
4241         if (err)
4242                 g_propagate_error (error, err);
4243 }
4244
4245 static void
4246 do_receive_objects (ECalBackendSync *backend,
4247                     EDataCal *cal,
4248                     GCancellable *cancellable,
4249                     const gchar *calobj,
4250                     GError **perror)
4251 {
4252         ECalBackendCalDAV        *cbdav;
4253         icalcomponent            *icomp;
4254         icalcomponent_kind        kind;
4255         icalproperty_method       tmethod;
4256         gboolean                  online;
4257         GSList                   *objects, *iter;
4258         GError *err = NULL;
4259
4260         cbdav = E_CAL_BACKEND_CALDAV (backend);
4261
4262         if (!check_state (cbdav, &online, perror))
4263                 return;
4264
4265         icomp = icalparser_parse_string (calobj);
4266
4267         /* Try to parse cal object string */
4268         if (icomp == NULL) {
4269                 g_propagate_error (perror, EDC_ERROR (InvalidObject));
4270                 return;
4271         }
4272
4273         kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
4274         extract_objects (icomp, kind, &objects, &err);
4275
4276         if (err) {
4277                 icalcomponent_free (icomp);
4278                 g_propagate_error (perror, err);
4279                 return;
4280         }
4281
4282         /* Extract optional timezone compnents */
4283         extract_timezones (cbdav, icomp);
4284
4285         tmethod = icalcomponent_get_method (icomp);
4286
4287         for (iter = objects; iter && !err; iter = iter->next) {
4288                 icalcomponent       *scomp;
4289                 ECalComponent       *ecomp;
4290                 icalproperty_method  method;
4291
4292                 scomp = (icalcomponent *) iter->data;
4293                 ecomp = e_cal_component_new ();
4294
4295                 e_cal_component_set_icalcomponent (ecomp, scomp);
4296
4297                 if (icalcomponent_get_first_property (scomp, ICAL_METHOD_PROPERTY)) {
4298                         method = icalcomponent_get_method (scomp);
4299                 } else {
4300                         method = tmethod;
4301                 }
4302
4303                 process_object (cbdav, ecomp, online, method, &err);
4304                 g_object_unref (ecomp);
4305         }
4306
4307         g_slist_free (objects);
4308
4309         icalcomponent_free (icomp);
4310
4311         if (err)
4312                 g_propagate_error (perror, err);
4313 }
4314
4315 #define caldav_busy_stub(_func_name, _params, _call_func, _call_params) \
4316 static void                                                             \
4317 _func_name _params                                                      \
4318 {                                                                       \
4319         ECalBackendCalDAV        *cbdav;                                \
4320         SlaveCommand              old_slave_cmd;                        \
4321         gboolean                  was_slave_busy;                       \
4322                                                                         \
4323         cbdav = E_CAL_BACKEND_CALDAV (backend);                         \
4324                                                                         \
4325         /* this is done before locking */                               \
4326         old_slave_cmd = cbdav->priv->slave_cmd;                         \
4327         was_slave_busy = cbdav->priv->slave_busy;                       \
4328         if (was_slave_busy) {                                           \
4329                 /* let it pause its work and do our job */              \
4330                 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);     \
4331         }                                                               \
4332                                                                         \
4333         g_mutex_lock (cbdav->priv->busy_lock);                          \
4334         _call_func _call_params;                                        \
4335                                                                         \
4336         /* this is done before unlocking */                             \
4337         if (was_slave_busy) {                                           \
4338                 update_slave_cmd (cbdav->priv, old_slave_cmd);          \
4339                 g_cond_signal (cbdav->priv->cond);                      \
4340         }                                                               \
4341                                                                         \
4342         g_mutex_unlock (cbdav->priv->busy_lock);                        \
4343 }
4344
4345 caldav_busy_stub (
4346         caldav_create_objects,
4347                   (ECalBackendSync *backend,
4348                   EDataCal *cal,
4349                   GCancellable *cancellable,
4350                   const GSList *in_calobjs,
4351                   GSList **uids,
4352                   GSList **new_components,
4353                   GError **perror),
4354         do_create_objects,
4355                   (cbdav,
4356                   in_calobjs,
4357                   uids,
4358                   new_components,
4359                   perror))
4360
4361 caldav_busy_stub (
4362         caldav_modify_objects,
4363                   (ECalBackendSync *backend,
4364                   EDataCal *cal,
4365                   GCancellable *cancellable,
4366                   const GSList *calobjs,
4367                   CalObjModType mod,
4368                   GSList **old_components,
4369                   GSList **new_components,
4370                   GError **perror),
4371         do_modify_objects,
4372                   (cbdav,
4373                   calobjs,
4374                   mod,
4375                   old_components,
4376                   new_components,
4377                   perror))
4378
4379 caldav_busy_stub (
4380         caldav_remove_objects,
4381                   (ECalBackendSync *backend,
4382                   EDataCal *cal,
4383                   GCancellable *cancellable,
4384                   const GSList *ids,
4385                   CalObjModType mod,
4386                   GSList **old_components,
4387                   GSList **new_components,
4388                   GError **perror),
4389         do_remove_objects,
4390                   (cbdav,
4391                   ids,
4392                   mod,
4393                   old_components,
4394                   new_components,
4395                   perror))
4396
4397 caldav_busy_stub (
4398         caldav_receive_objects,
4399                   (ECalBackendSync *backend,
4400                   EDataCal *cal,
4401                   GCancellable *cancellable,
4402                   const gchar *calobj,
4403                   GError **perror),
4404         do_receive_objects,
4405                   (backend,
4406                   cal,
4407                   cancellable,
4408                   calobj,
4409                   perror))
4410
4411 static void
4412 caldav_send_objects (ECalBackendSync *backend,
4413                      EDataCal *cal,
4414                      GCancellable *cancellable,
4415                      const gchar *calobj,
4416                      GSList **users,
4417                      gchar **modified_calobj,
4418                      GError **perror)
4419 {
4420         *users = NULL;
4421         *modified_calobj = g_strdup (calobj);
4422 }
4423
4424 static void
4425 caldav_get_object (ECalBackendSync *backend,
4426                    EDataCal *cal,
4427                    GCancellable *cancellable,
4428                    const gchar *uid,
4429                    const gchar *rid,
4430                    gchar **object,
4431                    GError **perror)
4432 {
4433         ECalBackendCalDAV        *cbdav;
4434         icalcomponent            *icalcomp;
4435
4436         cbdav = E_CAL_BACKEND_CALDAV (backend);
4437
4438         *object = NULL;
4439         icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
4440
4441         if (!icalcomp) {
4442                 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
4443                 return;
4444         }
4445
4446         *object = icalcomponent_as_ical_string_r (icalcomp);
4447         icalcomponent_free (icalcomp);
4448 }
4449
4450 static void
4451 caldav_add_timezone (ECalBackendSync *backend,
4452                      EDataCal *cal,
4453                      GCancellable *cancellable,
4454                      const gchar *tzobj,
4455                      GError **error)
4456 {
4457         icalcomponent *tz_comp;
4458         ECalBackendCalDAV *cbdav;
4459
4460         cbdav = E_CAL_BACKEND_CALDAV (backend);
4461
4462         e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), InvalidArg);
4463         e_return_data_cal_error_if_fail (tzobj != NULL, InvalidArg);
4464
4465         tz_comp = icalparser_parse_string (tzobj);
4466         if (!tz_comp) {
4467                 g_propagate_error (error, EDC_ERROR (InvalidObject));
4468                 return;
4469         }
4470
4471         if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
4472                 icaltimezone *zone;
4473
4474                 zone = icaltimezone_new ();
4475                 icaltimezone_set_component (zone, tz_comp);
4476
4477                 e_cal_backend_store_put_timezone (cbdav->priv->store, zone);
4478
4479                 icaltimezone_free (zone, TRUE);
4480         } else {
4481                 icalcomponent_free (tz_comp);
4482         }
4483 }
4484
4485 static void
4486 caldav_get_object_list (ECalBackendSync *backend,
4487                         EDataCal *cal,
4488                         GCancellable *cancellable,
4489                         const gchar *sexp_string,
4490                         GSList **objects,
4491                         GError **perror)
4492 {
4493         ECalBackendCalDAV        *cbdav;
4494         ECalBackendSExp  *sexp;
4495         ECalBackend *bkend;
4496         gboolean                  do_search;
4497         GSList                   *list, *iter;
4498         time_t occur_start = -1, occur_end = -1;
4499         gboolean prunning_by_time;
4500
4501         cbdav = E_CAL_BACKEND_CALDAV (backend);
4502
4503         sexp = e_cal_backend_sexp_new (sexp_string);
4504
4505         if (sexp == NULL) {
4506                 g_propagate_error (perror, EDC_ERROR (InvalidQuery));
4507                 return;
4508         }
4509
4510         if (g_str_equal (sexp_string, "#t")) {
4511                 do_search = FALSE;
4512         } else {
4513                 do_search = TRUE;
4514         }
4515
4516         *objects = NULL;
4517
4518         prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp, &occur_start, &occur_end);
4519
4520         list = prunning_by_time ?
4521                 e_cal_backend_store_get_components_occuring_in_range (cbdav->priv->store, occur_start, occur_end)
4522                 : e_cal_backend_store_get_components (cbdav->priv->store);
4523
4524         bkend = E_CAL_BACKEND (backend);
4525
4526         for (iter = list; iter; iter = g_slist_next (iter)) {
4527                 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4528
4529                 if (!do_search ||
4530                     e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4531                         *objects = g_slist_prepend (*objects, e_cal_component_get_as_string (comp));
4532                 }
4533
4534                 g_object_unref (comp);
4535         }
4536
4537         g_object_unref (sexp);
4538         g_slist_free (list);
4539 }
4540
4541 static void
4542 caldav_start_view (ECalBackend *backend,
4543                    EDataCalView *query)
4544 {
4545         ECalBackendCalDAV        *cbdav;
4546         ECalBackendSExp  *sexp;
4547         ECalBackend              *bkend;
4548         gboolean                  do_search;
4549         GSList                   *list, *iter;
4550         const gchar               *sexp_string;
4551         time_t occur_start = -1, occur_end = -1;
4552         gboolean prunning_by_time;
4553         cbdav = E_CAL_BACKEND_CALDAV (backend);
4554
4555         sexp_string = e_data_cal_view_get_text (query);
4556         sexp = e_cal_backend_sexp_new (sexp_string);
4557
4558         /* FIXME:check invalid sexp */
4559
4560         if (g_str_equal (sexp_string, "#t")) {
4561                 do_search = FALSE;
4562         } else {
4563                 do_search = TRUE;
4564         }
4565         prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp,
4566                                                                             &occur_start,
4567                                                                             &occur_end);
4568
4569         bkend = E_CAL_BACKEND (backend);
4570
4571         list = prunning_by_time ?
4572                 e_cal_backend_store_get_components_occuring_in_range (cbdav->priv->store, occur_start, occur_end)
4573                 : e_cal_backend_store_get_components (cbdav->priv->store);
4574
4575         for (iter = list; iter; iter = g_slist_next (iter)) {
4576                 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4577
4578                 if (!do_search ||
4579                     e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4580                         e_data_cal_view_notify_components_added_1 (query, comp);
4581                 }
4582
4583                 g_object_unref (comp);
4584         }
4585
4586         g_object_unref (sexp);
4587         g_slist_free (list);
4588
4589         e_data_cal_view_notify_complete (query, NULL /* Success */);
4590 }
4591
4592 static void
4593 caldav_get_free_busy (ECalBackendSync *backend,
4594                       EDataCal *cal,
4595                       GCancellable *cancellable,
4596                       const GSList *users,
4597                       time_t start,
4598                       time_t end,
4599                       GSList **freebusy,
4600                       GError **error)
4601 {
4602         ECalBackendCalDAV *cbdav;
4603         icalcomponent *icalcomp;
4604         ECalComponent *comp;
4605         ECalComponentDateTime dt;
4606         ECalComponentOrganizer organizer = {NULL};
4607         ESourceAuthentication *auth_extension;
4608         ESource *source;
4609         struct icaltimetype dtvalue;
4610         icaltimezone *utc;
4611         gchar *str;
4612         const GSList *u;
4613         GSList *attendees = NULL, *to_free = NULL;
4614         const gchar *extension_name;
4615         gchar *usermail;
4616         GError *err = NULL;
4617
4618         cbdav = E_CAL_BACKEND_CALDAV (backend);
4619
4620         e_return_data_cal_error_if_fail (users != NULL, InvalidArg);
4621         e_return_data_cal_error_if_fail (freebusy != NULL, InvalidArg);
4622         e_return_data_cal_error_if_fail (start < end, InvalidArg);
4623
4624         if (!cbdav->priv->calendar_schedule) {
4625                 g_propagate_error (error, EDC_ERROR_EX (OtherError, _("Calendar doesn't support Free/Busy")));
4626                 return;
4627         }
4628
4629         if (!cbdav->priv->schedule_outbox_url) {
4630                 caldav_receive_schedule_outbox_url (cbdav);
4631                 if (!cbdav->priv->schedule_outbox_url) {
4632                         cbdav->priv->calendar_schedule = FALSE;
4633                         g_propagate_error (error, EDC_ERROR_EX (OtherError, "Schedule outbox url not found"));
4634                         return;
4635                 }
4636         }
4637
4638         comp = e_cal_component_new ();
4639         e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_FREEBUSY);
4640
4641         str = e_cal_component_gen_uid ();
4642         e_cal_component_set_uid (comp, str);
4643         g_free (str);
4644
4645         utc = icaltimezone_get_utc_timezone ();
4646         dt.value = &dtvalue;
4647         dt.tzid = icaltimezone_get_tzid (utc);
4648
4649         dtvalue = icaltime_current_time_with_zone (utc);
4650         e_cal_component_set_dtstamp (comp, &dtvalue);
4651
4652         dtvalue = icaltime_from_timet_with_zone (start, FALSE, utc);
4653         e_cal_component_set_dtstart (comp, &dt);
4654
4655         dtvalue = icaltime_from_timet_with_zone (end, FALSE, utc);
4656         e_cal_component_set_dtend (comp, &dt);
4657
4658         usermail = get_usermail (E_CAL_BACKEND (backend));
4659         if (usermail != NULL && *usermail == '\0') {
4660                 g_free (usermail);
4661                 usermail = NULL;
4662         }
4663
4664         source = e_backend_get_source (E_BACKEND (backend));
4665         extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
4666         auth_extension = e_source_get_extension (source, extension_name);
4667
4668         if (usermail == NULL)
4669                 usermail = e_source_authentication_dup_user (auth_extension);
4670
4671         organizer.value = g_strconcat ("mailto:", usermail, NULL);
4672         e_cal_component_set_organizer (comp, &organizer);
4673         g_free ((gchar *) organizer.value);
4674
4675         g_free (usermail);
4676
4677         for (u = users; u; u = u->next) {
4678                 ECalComponentAttendee *ca;
4679                 gchar *temp = g_strconcat ("mailto:", (const gchar *)u->data, NULL);
4680
4681                 ca = g_new0 (ECalComponentAttendee, 1);
4682
4683                 ca->value = temp;
4684                 ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
4685                 ca->status = ICAL_PARTSTAT_NEEDSACTION;
4686                 ca->role = ICAL_ROLE_CHAIR;
4687
4688                 to_free = g_slist_prepend (to_free, temp);
4689                 attendees = g_slist_append (attendees, ca);
4690         }
4691
4692         e_cal_component_set_attendee_list (comp, attendees);
4693
4694         g_slist_foreach (attendees, (GFunc) g_free, NULL);
4695         g_slist_free (attendees);
4696
4697         g_slist_foreach (to_free, (GFunc) g_free, NULL);
4698         g_slist_free (to_free);
4699
4700         e_cal_component_abort_sequence (comp);
4701
4702         /* put the free/busy request to a VCALENDAR */
4703         icalcomp = e_cal_util_new_top_level ();
4704         icalcomponent_set_method (icalcomp, ICAL_METHOD_REQUEST);
4705         icalcomponent_add_component (icalcomp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
4706
4707         str = icalcomponent_as_ical_string_r (icalcomp);
4708
4709         icalcomponent_free (icalcomp);
4710         g_object_unref (comp);
4711
4712         e_return_data_cal_error_if_fail (str != NULL, OtherError);
4713
4714         caldav_post_freebusy (cbdav, cbdav->priv->schedule_outbox_url, &str, &err);
4715
4716         if (!err) {
4717                 /* parse returned xml */
4718                 xmlDocPtr doc;
4719
4720                 doc = xmlReadMemory (str, strlen (str), "response.xml", NULL, 0);
4721                 if (doc != NULL) {
4722                         xmlXPathContextPtr xpctx;
4723                         xmlXPathObjectPtr result;
4724
4725                         xpctx = xmlXPathNewContext (doc);
4726                         xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
4727                         xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
4728
4729                         result = xpath_eval (xpctx, "/C:schedule-response/C:response");
4730
4731                         if (result == NULL || result->type != XPATH_NODESET) {
4732                                 err = EDC_ERROR_EX (OtherError, "Unexpected result in schedule-response");
4733                         } else {
4734                                 gint i, n;
4735
4736                                 n = xmlXPathNodeSetGetLength (result->nodesetval);
4737                                 for (i = 0; i < n; i++) {
4738                                         gchar *tmp;
4739
4740                                         tmp = xp_object_get_string (xpath_eval (xpctx, "string(/C:schedule-response/C:response[%d]/C:calendar-data)", i + 1));
4741                                         if (tmp && *tmp) {
4742                                                 GSList *objects = NULL, *o;
4743
4744                                                 icalcomp = icalparser_parse_string (tmp);
4745                                                 if (icalcomp)
4746                                                         extract_objects (icalcomp, ICAL_VFREEBUSY_COMPONENT, &objects, &err);
4747                                                 if (icalcomp && !err) {
4748                                                         for (o = objects; o; o = o->next) {
4749                                                                 gchar *obj_str = icalcomponent_as_ical_string_r (o->data);
4750
4751                                                                 if (obj_str && *obj_str)
4752                                                                         *freebusy = g_slist_append (*freebusy, obj_str);
4753                                                                 else
4754                                                                         g_free (obj_str);
4755                                                         }
4756                                                 }
4757
4758                                                 g_slist_foreach (objects, (GFunc) icalcomponent_free, NULL);
4759                                                 g_slist_free (objects);
4760
4761                                                 if (icalcomp)
4762                                                         icalcomponent_free (icalcomp);
4763                                                 if (err)
4764                                                         g_error_free (err);
4765                                                 err = NULL;
4766                                         }
4767
4768                                         g_free (tmp);
4769                                 }
4770                         }
4771
4772                         if (result != NULL)
4773                                 xmlXPathFreeObject (result);
4774                         xmlXPathFreeContext (xpctx);
4775                         xmlFreeDoc (doc);
4776                 }
4777         }
4778
4779         g_free (str);
4780
4781         if (err)
4782                 g_propagate_error (error, err);
4783 }
4784
4785 static void
4786 caldav_notify_online_cb (ECalBackend *backend,
4787                          GParamSpec *pspec)
4788 {
4789         ECalBackendCalDAV        *cbdav;
4790         gboolean online;
4791
4792         cbdav = E_CAL_BACKEND_CALDAV (backend);
4793
4794         /*g_mutex_lock (cbdav->priv->busy_lock);*/
4795
4796         online = e_backend_get_online (E_BACKEND (backend));
4797
4798         if (!cbdav->priv->loaded) {
4799                 e_cal_backend_notify_online (backend, online);
4800                 /*g_mutex_unlock (cbdav->priv->busy_lock);*/
4801                 return;
4802         }
4803
4804         if (online) {
4805                 /* Wake up the slave thread */
4806                 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
4807                 g_cond_signal (cbdav->priv->cond);
4808         } else {
4809                 soup_session_abort (cbdav->priv->session);
4810                 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
4811         }
4812
4813         e_cal_backend_notify_online (backend, online);
4814
4815         /*g_mutex_unlock (cbdav->priv->busy_lock);*/
4816 }
4817
4818 static icaltimezone *
4819 caldav_internal_get_timezone (ECalBackend *backend,
4820                               const gchar *tzid)
4821 {
4822         icaltimezone *zone;
4823         ECalBackendCalDAV *cbdav;
4824
4825         cbdav = E_CAL_BACKEND_CALDAV (backend);
4826         zone = NULL;
4827
4828         if (cbdav->priv->store)
4829                 zone = (icaltimezone *) e_cal_backend_store_get_timezone (cbdav->priv->store, tzid);
4830
4831         if (!zone && E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone)
4832                 zone = E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone (backend, tzid);
4833
4834         return zone;
4835 }
4836
4837 static gpointer
4838 caldav_source_changed_thread (gpointer data)
4839 {
4840         ECalBackendCalDAV *cbdav = data;
4841         SlaveCommand old_slave_cmd;
4842         gboolean old_slave_busy;
4843
4844         g_return_val_if_fail (cbdav != NULL, NULL);
4845
4846         old_slave_cmd = cbdav->priv->slave_cmd;
4847         old_slave_busy = cbdav->priv->slave_busy;
4848         if (old_slave_busy) {
4849                 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
4850                 g_mutex_lock (cbdav->priv->busy_lock);
4851         }
4852
4853         initialize_backend (cbdav, NULL);
4854
4855         /* always wakeup thread, even when it was sleeping */
4856         g_cond_signal (cbdav->priv->cond);
4857
4858         if (old_slave_busy) {
4859                 update_slave_cmd (cbdav->priv, old_slave_cmd);
4860                 g_mutex_unlock (cbdav->priv->busy_lock);
4861         }
4862
4863         cbdav->priv->updating_source = FALSE;
4864
4865         g_object_unref (cbdav);
4866
4867         return NULL;
4868 }
4869
4870 static void
4871 caldav_source_changed_cb (ESource *source,
4872                           ECalBackendCalDAV *cbdav)
4873 {
4874         GThread *thread;
4875
4876         g_return_if_fail (source != NULL);
4877         g_return_if_fail (cbdav != NULL);
4878
4879         if (cbdav->priv->updating_source)
4880                 return;
4881
4882         cbdav->priv->updating_source = TRUE;
4883
4884         thread = g_thread_new (NULL, caldav_source_changed_thread, g_object_ref (cbdav));
4885         g_thread_unref (thread);
4886 }
4887
4888 static ESourceAuthenticationResult
4889 caldav_try_password_sync (ESourceAuthenticator *authenticator,
4890                           const GString *password,
4891                           GCancellable *cancellable,
4892                           GError **error)
4893 {
4894         ECalBackendCalDAV *cbdav;
4895         ESourceAuthenticationResult result;
4896         GError *local_error = NULL;
4897
4898         cbdav = E_CAL_BACKEND_CALDAV (authenticator);
4899
4900         /* Busy lock is already acquired by caldav_do_open(). */
4901
4902         g_free (cbdav->priv->password);
4903         cbdav->priv->password = g_strdup (password->str);
4904
4905         open_calendar (cbdav, &local_error);
4906
4907         if (local_error == NULL) {
4908                 result = E_SOURCE_AUTHENTICATION_ACCEPTED;
4909         } else if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
4910                 result = E_SOURCE_AUTHENTICATION_REJECTED;
4911                 g_clear_error (&local_error);
4912         } else {
4913                 result = E_SOURCE_AUTHENTICATION_ERROR;
4914                 g_propagate_error (error, local_error);
4915         }
4916
4917         return result;
4918 }
4919
4920 static void
4921 caldav_source_authenticator_init (ESourceAuthenticatorInterface *interface)
4922 {
4923         interface->try_password_sync = caldav_try_password_sync;
4924 }
4925
4926 /* ************************************************************************* */
4927 /* ***************************** GObject Foo ******************************* */
4928
4929 G_DEFINE_TYPE_WITH_CODE (
4930         ECalBackendCalDAV,
4931         e_cal_backend_caldav,
4932         E_TYPE_CAL_BACKEND_SYNC,
4933         G_IMPLEMENT_INTERFACE (
4934                 E_TYPE_SOURCE_AUTHENTICATOR,
4935                 caldav_source_authenticator_init))
4936
4937 static void
4938 e_cal_backend_caldav_dispose (GObject *object)
4939 {
4940         ECalBackendCalDAVPrivate *priv;
4941         ESource *source;
4942
4943         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (object);
4944
4945         /* tell the slave to stop before acquiring a lock,
4946          * as it can work at the moment, and lock can be locked */
4947         update_slave_cmd (priv, SLAVE_SHOULD_DIE);
4948
4949         g_mutex_lock (priv->busy_lock);
4950
4951         if (priv->disposed) {
4952                 g_mutex_unlock (priv->busy_lock);
4953                 return;
4954         }
4955
4956         source = e_backend_get_source (E_BACKEND (object));
4957         if (source)
4958                 g_signal_handlers_disconnect_by_func (G_OBJECT (source), caldav_source_changed_cb, object);
4959
4960         /* stop the slave  */
4961         if (priv->synch_slave) {
4962                 g_cond_signal (priv->cond);
4963
4964                 /* wait until the slave died */
4965                 g_cond_wait (priv->slave_gone_cond, priv->busy_lock);
4966         }
4967
4968         g_object_unref (priv->session);
4969         g_object_unref (priv->proxy);
4970
4971         g_free (priv->uri);
4972         g_free (priv->schedule_outbox_url);
4973
4974         if (priv->store != NULL) {
4975                 g_object_unref (priv->store);
4976         }
4977
4978         priv->disposed = TRUE;
4979         g_mutex_unlock (priv->busy_lock);
4980
4981         /* Chain up to parent's dispose() method. */
4982         G_OBJECT_CLASS (parent_class)->dispose (object);
4983 }
4984
4985 static void
4986 e_cal_backend_caldav_finalize (GObject *object)
4987 {
4988         ECalBackendCalDAVPrivate *priv;
4989
4990         priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (object);
4991
4992         g_mutex_free (priv->busy_lock);
4993         g_cond_free (priv->cond);
4994         g_cond_free (priv->slave_gone_cond);
4995
4996         g_free (priv->password);
4997
4998         /* Chain up to parent's finalize() method. */
4999         G_OBJECT_CLASS (parent_class)->finalize (object);
5000 }
5001
5002 static void
5003 cal_backend_caldav_constructed (GObject *object)
5004 {
5005         ESource *source;
5006         ESourceWebdav *extension;
5007         ECalBackendCalDAV *cbdav;
5008         const gchar *extension_name;
5009
5010         cbdav = E_CAL_BACKEND_CALDAV (object);
5011
5012         source = e_backend_get_source (E_BACKEND (cbdav));
5013         extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
5014         extension = e_source_get_extension (source, extension_name);
5015
5016         g_object_bind_property (
5017                 extension, "ignore-invalid-cert",
5018                 cbdav->priv->session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE,
5019                 G_BINDING_SYNC_CREATE |
5020                 G_BINDING_INVERT_BOOLEAN);
5021
5022         /* Chain up to parent's constructed() method. */
5023         G_OBJECT_CLASS (e_cal_backend_caldav_parent_class)->
5024                 constructed (object);
5025 }
5026
5027 static void
5028 e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
5029 {
5030         cbdav->priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
5031         cbdav->priv->session = soup_session_sync_new ();
5032         g_object_set (cbdav->priv->session, SOUP_SESSION_TIMEOUT, 90, NULL);
5033
5034         cbdav->priv->proxy = e_proxy_new ();
5035         e_proxy_setup_proxy (cbdav->priv->proxy);
5036         g_signal_connect (cbdav->priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), cbdav->priv);
5037
5038         if (G_UNLIKELY (caldav_debug_show (DEBUG_MESSAGE)))
5039                 caldav_debug_setup (cbdav->priv->session);
5040
5041         cbdav->priv->disposed = FALSE;
5042         cbdav->priv->loaded   = FALSE;
5043         cbdav->priv->opened = FALSE;
5044
5045         /* Thinks the 'getctag' extension is available the first time, but unset it when realizes it isn't. */
5046         cbdav->priv->ctag_supported = TRUE;
5047         cbdav->priv->ctag_to_store = NULL;
5048
5049         cbdav->priv->schedule_outbox_url = NULL;
5050
5051         cbdav->priv->is_google = FALSE;
5052
5053         cbdav->priv->busy_lock = g_mutex_new ();
5054         cbdav->priv->cond = g_cond_new ();
5055         cbdav->priv->slave_gone_cond = g_cond_new ();
5056
5057         /* Slave control ... */
5058         cbdav->priv->slave_cmd = SLAVE_SHOULD_SLEEP;
5059         cbdav->priv->slave_busy = FALSE;
5060         cbdav->priv->refresh_time.tv_usec = 0;
5061         cbdav->priv->refresh_time.tv_sec  = DEFAULT_REFRESH_TIME;
5062
5063         g_signal_connect (cbdav->priv->session, "authenticate",
5064                           G_CALLBACK (soup_authenticate), cbdav);
5065
5066         e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbdav), FALSE);
5067
5068         g_signal_connect (
5069                 cbdav, "notify::online",
5070                 G_CALLBACK (caldav_notify_online_cb), NULL);
5071 }
5072
5073 static void
5074 e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
5075 {
5076         GObjectClass *object_class;
5077         ECalBackendClass *backend_class;
5078         ECalBackendSyncClass *sync_class;
5079
5080         object_class = (GObjectClass *) class;
5081         backend_class = (ECalBackendClass *) class;
5082         sync_class = (ECalBackendSyncClass *) class;
5083
5084         caldav_debug_init ();
5085
5086         parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
5087         g_type_class_add_private (class, sizeof (ECalBackendCalDAVPrivate));
5088
5089         object_class->dispose  = e_cal_backend_caldav_dispose;
5090         object_class->finalize = e_cal_backend_caldav_finalize;
5091         object_class->constructed = cal_backend_caldav_constructed;
5092
5093         sync_class->get_backend_property_sync   = caldav_get_backend_property;
5094
5095         sync_class->open_sync                   = caldav_do_open;
5096         sync_class->refresh_sync                = caldav_refresh;
5097         sync_class->remove_sync                 = caldav_remove;
5098
5099         sync_class->create_objects_sync         = caldav_create_objects;
5100         sync_class->modify_objects_sync         = caldav_modify_objects;
5101         sync_class->remove_objects_sync         = caldav_remove_objects;
5102
5103         sync_class->receive_objects_sync        = caldav_receive_objects;
5104         sync_class->send_objects_sync           = caldav_send_objects;
5105         sync_class->get_object_sync             = caldav_get_object;
5106         sync_class->get_object_list_sync        = caldav_get_object_list;
5107         sync_class->add_timezone_sync           = caldav_add_timezone;
5108         sync_class->get_free_busy_sync          = caldav_get_free_busy;
5109
5110         backend_class->start_view               = caldav_start_view;
5111
5112         backend_class->internal_get_timezone    = caldav_internal_get_timezone;
5113 }