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