2 * Evolution calendar - caldav backend
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
19 * Christian Kellner <gicmo@gnome.org>
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
28 #include <glib/gstdio.h>
29 #include <glib/gi18n-lib.h>
31 /* LibXML2 includes */
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34 #include <libxml/xpath.h>
35 #include <libxml/xpathInternals.h>
37 /* LibSoup includes */
38 #include <libsoup/soup.h>
40 #include "e-cal-backend-caldav.h"
44 #define E_CAL_BACKEND_CALDAV_GET_PRIVATE(obj) \
45 (G_TYPE_INSTANCE_GET_PRIVATE \
46 ((obj), E_TYPE_CAL_BACKEND_CALDAV, ECalBackendCalDAVPrivate))
48 #define CALDAV_CTAG_KEY "CALDAV_CTAG"
49 #define CALDAV_MAX_MULTIGET_AMOUNT 100 /* what's the maximum count of items to fetch within a multiget request */
50 #define LOCAL_PREFIX "file://"
53 #define DEFAULT_REFRESH_TIME 60
55 #define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
56 #define EDC_ERROR_EX(_code, _msg) e_data_cal_create_error (_code, _msg)
66 /* Private part of the ECalBackendHttp structure */
67 struct _ECalBackendCalDAVPrivate {
69 /* The local disk cache */
70 ECalBackendStore *store;
72 /* should we sync for offline mode? */
75 /* TRUE after caldav_open */
77 /* TRUE when server reachable */
80 /* lock to indicate a busy state */
83 /* cond to synch threads */
86 /* cond to know the slave gone */
87 GCond *slave_gone_cond;
90 const GThread *synch_slave; /* just for a reference, whether thread exists */
91 SlaveCommand slave_cmd;
92 gboolean slave_busy; /* whether is slave working */
93 GTimeVal refresh_time;
95 /* The main soup session */
99 /* well, guess what */
105 /* Authentication info */
107 gboolean auth_required;
112 /* support for 'getctag' extension */
113 gboolean ctag_supported;
114 gchar *ctag_to_store;
116 /* TRUE when 'calendar-schedule' supported on the server */
117 gboolean calendar_schedule;
118 /* with 'calendar-schedule' supported, here's an outbox url
119 * for queries of free/busy information */
120 gchar *schedule_outbox_url;
122 /* "Temporary hack" to indicate it's talking to a google calendar.
123 * The proper solution should be to subclass whole backend and change only
124 * necessary parts in it, but this will give us more freedom, as also direct
125 * caldav calendars can profit from this. */
128 /* set to true if thread for ESource::changed is invoked */
129 gboolean updating_source;
132 /* ************************************************************************* */
135 #define DEBUG_MESSAGE "message"
136 #define DEBUG_MESSAGE_HEADER "message:header"
137 #define DEBUG_MESSAGE_BODY "message:body"
138 #define DEBUG_SERVER_ITEMS "items"
139 #define DEBUG_ATTACHMENTS "attachments"
141 static void convert_to_inline_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
142 static void convert_to_url_attachment (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp);
143 static void remove_cached_attachment (ECalBackendCalDAV *cbdav, const gchar *uid);
145 static gboolean caldav_debug_all = FALSE;
146 static GHashTable *caldav_debug_table = NULL;
149 add_debug_key (const gchar *start,
159 debug_key = debug_value = g_strndup (start, end - start);
161 debug_key = g_strchug (debug_key);
162 debug_key = g_strchomp (debug_key);
164 if (strlen (debug_key) == 0) {
165 g_free (debug_value);
169 g_hash_table_insert (caldav_debug_table,
173 d(g_debug ("Adding %s to enabled debugging keys", debug_key));
177 caldav_debug_init_once (gpointer data)
181 dbg = g_getenv ("CALDAV_DEBUG");
186 d(g_debug ("Got debug env variable: [%s]", dbg));
188 caldav_debug_table = g_hash_table_new (g_str_hash,
193 while (*ptr != '\0') {
194 if (*ptr == ',' || *ptr == ':') {
196 add_debug_key (dbg, ptr);
207 add_debug_key (dbg, ptr);
210 if (g_hash_table_lookup (caldav_debug_table, "all")) {
211 caldav_debug_all = TRUE;
212 g_hash_table_destroy (caldav_debug_table);
213 caldav_debug_table = NULL;
221 caldav_debug_init (void)
223 static GOnce debug_once = G_ONCE_INIT;
226 caldav_debug_init_once,
231 caldav_debug_show (const gchar *component)
233 if (G_UNLIKELY (caldav_debug_all)) {
235 } else if (G_UNLIKELY (caldav_debug_table != NULL) &&
236 g_hash_table_lookup (caldav_debug_table, component)) {
243 #define DEBUG_MAX_BODY_SIZE (100 * 1024 * 1024)
246 caldav_debug_setup (SoupSession *session)
249 SoupLoggerLogLevel level;
251 if (caldav_debug_show (DEBUG_MESSAGE_BODY))
252 level = SOUP_LOGGER_LOG_BODY;
253 else if (caldav_debug_show (DEBUG_MESSAGE_HEADER))
254 level = SOUP_LOGGER_LOG_HEADERS;
256 level = SOUP_LOGGER_LOG_MINIMAL;
258 logger = soup_logger_new (level, DEBUG_MAX_BODY_SIZE);
259 soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
260 g_object_unref (logger);
263 /* TODO Do not replicate this in every backend */
264 static icaltimezone *
265 resolve_tzid (const gchar *tzid,
270 zone = (!strcmp (tzid, "UTC"))
271 ? icaltimezone_get_utc_timezone ()
272 : icaltimezone_get_builtin_timezone_from_tzid (tzid);
275 zone = e_cal_backend_internal_get_timezone (E_CAL_BACKEND (user_data), tzid);
281 put_component_to_store (ECalBackendCalDAV *cbdav,
284 time_t time_start, time_end;
286 e_cal_util_get_component_occur_times (
287 comp, &time_start, &time_end,
288 resolve_tzid, cbdav, icaltimezone_get_utc_timezone (),
289 e_cal_backend_get_kind (E_CAL_BACKEND (cbdav)));
291 return e_cal_backend_store_put_component_with_time_range (
292 cbdav->priv->store, comp, time_start, time_end);
295 static ECalBackendSyncClass *parent_class = NULL;
297 static icaltimezone *caldav_internal_get_timezone (ECalBackend *backend, const gchar *tzid);
298 static void caldav_source_changed_cb (ESource *source, ECalBackendCalDAV *cbdav);
300 static gboolean remove_comp_from_cache (ECalBackendCalDAV *cbdav, const gchar *uid, const gchar *rid);
301 static gboolean put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const gchar *href, const gchar *etag);
303 /* ************************************************************************* */
304 /* Misc. utility functions */
307 update_slave_cmd (ECalBackendCalDAVPrivate *priv,
308 SlaveCommand slave_cmd)
310 g_return_if_fail (priv != NULL);
312 if (priv->slave_cmd == SLAVE_SHOULD_DIE)
315 priv->slave_cmd = slave_cmd;
318 #define X_E_CALDAV "X-EVOLUTION-CALDAV-"
319 #define X_E_CALDAV_ATTACHMENT_NAME X_E_CALDAV "ATTACHMENT-NAME"
322 icomp_x_prop_set (icalcomponent *comp,
328 /* Find the old one first */
329 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
332 const gchar *str = icalproperty_get_x_name (xprop);
334 if (!strcmp (str, key)) {
336 icalproperty_set_value_from_string (xprop, value, "NO");
338 icalcomponent_remove_property (comp, xprop);
339 icalproperty_free (xprop);
344 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
347 if (!xprop && value) {
348 xprop = icalproperty_new_x (value);
349 icalproperty_set_x_name (xprop, key);
350 icalcomponent_add_property (comp, xprop);
355 icomp_x_prop_get (icalcomponent *comp,
360 /* Find the old one first */
361 xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
364 const gchar *str = icalproperty_get_x_name (xprop);
366 if (!strcmp (str, key)) {
370 xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
374 return icalproperty_get_value_as_string_r (xprop);
380 /* passing NULL as 'href' removes the property */
382 ecalcomp_set_href (ECalComponent *comp,
385 icalcomponent *icomp;
387 icomp = e_cal_component_get_icalcomponent (comp);
388 g_return_if_fail (icomp != NULL);
390 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
394 ecalcomp_get_href (ECalComponent *comp)
396 icalcomponent *icomp;
400 icomp = e_cal_component_get_icalcomponent (comp);
401 g_return_val_if_fail (icomp != NULL, NULL);
403 str = icomp_x_prop_get (icomp, X_E_CALDAV "HREF");
408 /* passing NULL as 'etag' removes the property */
410 ecalcomp_set_etag (ECalComponent *comp,
413 icalcomponent *icomp;
415 icomp = e_cal_component_get_icalcomponent (comp);
416 g_return_if_fail (icomp != NULL);
418 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", etag);
422 ecalcomp_get_etag (ECalComponent *comp)
424 icalcomponent *icomp;
428 icomp = e_cal_component_get_icalcomponent (comp);
429 g_return_val_if_fail (icomp != NULL, NULL);
431 str = icomp_x_prop_get (icomp, X_E_CALDAV "ETAG");
433 /* libical 0.48 escapes quotes, thus unescape them */
434 if (str && strchr (str, '\\')) {
437 for (ii = 0, jj = 0; str[ii]; ii++) {
438 if (str[ii] == '\\') {
456 / * object is in synch,
457 * now isnt that ironic? :) * /
458 ECALCOMP_IN_SYNCH = 0,
460 / * local changes * /
461 ECALCOMP_LOCALLY_CREATED,
462 ECALCOMP_LOCALLY_DELETED,
463 ECALCOMP_LOCALLY_MODIFIED
467 / * oos = out of synch * /
469 ecalcomp_set_synch_state (ECalComponent *comp,
470 * ECalCompSyncState state)
472 icalcomponent *icomp;
475 icomp = e_cal_component_get_icalcomponent (comp);
477 state_string = g_strdup_printf ("%d", state);
479 icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", state_string);
481 g_free (state_string);
485 ecalcomp_gen_href (ECalComponent *comp)
487 gchar *href, *uid, *tmp;
488 icalcomponent *icomp;
490 icomp = e_cal_component_get_icalcomponent (comp);
491 g_return_val_if_fail (icomp != NULL, NULL);
493 uid = g_strdup (icalcomponent_get_uid (icomp));
496 uid = e_cal_component_gen_uid ();
498 tmp = uid ? strchr (uid, '@') : NULL;
504 tmp = isodate_from_time_t (time (NULL));
506 /* quite long, but ensures uniqueness quite well, without using UUIDs */
507 href = g_strconcat (uid ? uid : "no-uid", tmp ? "-" : "", tmp ? tmp : "", ".ics", NULL);
512 icomp_x_prop_set (icomp, X_E_CALDAV "HREF", href);
514 return g_strdelimit (href, " /'\"`&();|<>$%{}!\\:*?#@", '_');
517 /* ensure etag is quoted (to workaround potential server bugs) */
519 quote_etag (const gchar *etag)
523 if (etag && (strlen (etag) < 2 || etag[strlen (etag) - 1] != '\"')) {
524 ret = g_strdup_printf ("\"%s\"", etag);
526 ret = g_strdup (etag);
532 /* ************************************************************************* */
535 status_code_to_result (SoupMessage *message,
536 ECalBackendCalDAV *cbdav,
540 ECalBackendCalDAVPrivate *priv;
542 ESourceWebdav *extension;
543 const gchar *extension_name;
544 gboolean ignore_invalid_cert;
546 g_return_val_if_fail (cbdav != NULL, FALSE);
547 g_return_val_if_fail (message != NULL, FALSE);
551 if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
555 source = e_backend_get_source (E_BACKEND (cbdav));
557 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
558 extension = e_source_get_extension (source, extension_name);
559 ignore_invalid_cert = e_source_webdav_get_ignore_invalid_cert (extension);
561 switch (message->status_code) {
562 case SOUP_STATUS_CANT_CONNECT:
563 case SOUP_STATUS_CANT_CONNECT_PROXY:
564 g_propagate_error (perror,
565 e_data_cal_create_error_fmt (
567 _("Server is unreachable (%s)"),
568 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
569 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
571 priv->opened = FALSE;
572 priv->read_only = TRUE;
577 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
579 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
583 g_propagate_error (perror, EDC_ERROR (AuthenticationFailed));
587 if (priv && priv->auth_required)
588 g_propagate_error (perror, EDC_ERROR (AuthenticationFailed));
590 g_propagate_error (perror, EDC_ERROR (AuthenticationRequired));
593 case SOUP_STATUS_SSL_FAILED:
594 if (ignore_invalid_cert) {
595 g_propagate_error (perror,
596 e_data_cal_create_error_fmt ( OtherError,
597 _("Failed to connect to a server using SSL: %s"),
598 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
599 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
601 g_propagate_error (perror, EDC_ERROR_EX (OtherError,
602 _("Failed to connect to a server using SSL. "
603 "One possible reason is an invalid certificate being used by the server. "
604 "If this is expected, like self-signed certificate being used on the server, "
605 "then disable certificate validity tests by selecting 'Ignore invalid SSL certificate' option "
611 d(g_debug ("CalDAV:%s: Unhandled status code %d\n", G_STRFUNC, status_code));
612 g_propagate_error (perror,
613 e_data_cal_create_error_fmt (
615 _("Unexpected HTTP status code %d returned (%s)"),
616 message->status_code,
617 message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
618 (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
625 /* !TS, call with lock held */
627 check_state (ECalBackendCalDAV *cbdav,
633 if (!cbdav->priv->loaded) {
634 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Not loaded"));
638 if (!e_backend_get_online (E_BACKEND (cbdav))) {
640 if (!cbdav->priv->do_offline) {
641 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
652 /* ************************************************************************* */
653 /* XML Parsing code */
655 static xmlXPathObjectPtr
656 xpath_eval (xmlXPathContextPtr ctx,
660 xmlXPathObjectPtr result;
668 va_start (args, format);
669 expr = g_strdup_vprintf (format, args);
672 result = xmlXPathEvalExpression ((xmlChar *) expr, ctx);
675 if (result == NULL) {
679 if (result->type == XPATH_NODESET &&
680 xmlXPathNodeSetIsEmpty (result->nodesetval)) {
681 xmlXPathFreeObject (result);
690 parse_status_node (xmlNodePtr node,
696 content = xmlNodeGetContent (node);
698 res = soup_headers_parse_status_line ((gchar *) content,
709 xp_object_get_string (xmlXPathObjectPtr result)
716 if (result->type == XPATH_STRING) {
717 ret = g_strdup ((gchar *) result->stringval);
720 xmlXPathFreeObject (result);
724 /* like get_string but will quote the etag if necessary */
726 xp_object_get_etag (xmlXPathObjectPtr result)
734 if (result->type == XPATH_STRING) {
735 str = (gchar *) result->stringval;
737 ret = quote_etag (str);
740 xmlXPathFreeObject (result);
745 xp_object_get_status (xmlXPathObjectPtr result)
753 if (result->type == XPATH_STRING) {
754 res = soup_headers_parse_status_line ((gchar *) result->stringval,
764 xmlXPathFreeObject (result);
770 xp_object_get_number (xmlXPathObjectPtr result)
777 if (result->type == XPATH_STRING) {
778 ret = result->boolval;
781 xmlXPathFreeObject (result);
786 /*** *** *** *** *** *** */
787 #define XPATH_HREF "string(/D:multistatus/D:response[%d]/D:href)"
788 #define XPATH_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:status)"
789 #define XPATH_GETETAG_STATUS "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag/../../D:status)"
790 #define XPATH_GETETAG "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/D:getetag)"
791 #define XPATH_CALENDAR_DATA "string(/D:multistatus/D:response[%d]/D:propstat/D:prop/C:calendar-data)"
792 #define XPATH_GETCTAG_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag/../../D:status)"
793 #define XPATH_GETCTAG "string(/D:multistatus/D:response/D:propstat/D:prop/CS:getctag)"
794 #define XPATH_OWNER_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href/../../../D:status)"
795 #define XPATH_OWNER "string(/D:multistatus/D:response/D:propstat/D:prop/D:owner/D:href)"
796 #define XPATH_SCHEDULE_OUTBOX_URL_STATUS "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href/../../../D:status)"
797 #define XPATH_SCHEDULE_OUTBOX_URL "string(/D:multistatus/D:response/D:propstat/D:prop/C:schedule-outbox-URL/D:href)"
799 typedef struct _CalDAVObject CalDAVObject;
801 struct _CalDAVObject {
812 caldav_object_free (CalDAVObject *object,
813 gboolean free_object_itself)
815 g_free (object->href);
816 g_free (object->etag);
817 g_free (object->cdata);
819 if (free_object_itself) {
825 parse_report_response (SoupMessage *soup_message,
829 xmlXPathContextPtr xpctx;
830 xmlXPathObjectPtr result;
835 g_return_val_if_fail (soup_message != NULL, FALSE);
836 g_return_val_if_fail (objs != NULL || len != NULL, FALSE);
839 doc = xmlReadMemory (soup_message->response_body->data,
840 soup_message->response_body->length,
849 xpctx = xmlXPathNewContext (doc);
851 xmlXPathRegisterNs (xpctx, (xmlChar *) "D",
854 xmlXPathRegisterNs (xpctx, (xmlChar *) "C",
855 (xmlChar *) "urn:ietf:params:xml:ns:caldav");
857 result = xpath_eval (xpctx, "/D:multistatus/D:response");
859 if (result == NULL || result->type != XPATH_NODESET) {
865 n = xmlXPathNodeSetGetLength (result->nodesetval);
868 *objs = g_new0 (CalDAVObject, n);
870 for (i = 0; i < n; i++) {
871 CalDAVObject *object;
872 xmlXPathObjectPtr xpres;
875 /* see if we got a status child in the response element */
877 xpres = xpath_eval (xpctx, XPATH_HREF, i + 1);
878 /* use full path from a href, to let calendar-multiget work properly */
879 object->href = xp_object_get_string (xpres);
881 xpres = xpath_eval (xpctx,XPATH_STATUS , i + 1);
882 object->status = xp_object_get_status (xpres);
884 if (object->status && object->status != 200) {
888 xpres = xpath_eval (xpctx, XPATH_GETETAG_STATUS, i + 1);
889 object->status = xp_object_get_status (xpres);
891 if (object->status != 200) {
895 xpres = xpath_eval (xpctx, XPATH_GETETAG, i + 1);
896 object->etag = xp_object_get_etag (xpres);
898 xpres = xpath_eval (xpctx, XPATH_CALENDAR_DATA, i + 1);
899 object->cdata = xp_object_get_string (xpres);
904 xmlXPathFreeObject (result);
905 xmlXPathFreeContext (xpctx);
910 /* returns whether was able to read the xpath_value from the server's response; *value contains the result */
912 parse_propfind_response (SoupMessage *message,
913 const gchar *xpath_status,
914 const gchar *xpath_value,
917 xmlXPathContextPtr xpctx;
919 gboolean res = FALSE;
921 g_return_val_if_fail (message != NULL, FALSE);
922 g_return_val_if_fail (value != NULL, FALSE);
924 doc = xmlReadMemory (message->response_body->data,
925 message->response_body->length,
934 xpctx = xmlXPathNewContext (doc);
935 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
936 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
937 xmlXPathRegisterNs (xpctx, (xmlChar *) "CS", (xmlChar *) "http://calendarserver.org/ns/");
939 if (xpath_status == NULL || xp_object_get_status (xpath_eval (xpctx, xpath_status)) == 200) {
940 gchar *txt = xp_object_get_string (xpath_eval (xpctx, xpath_value));
943 gint len = strlen (txt);
945 if (*txt == '\"' && len > 2 && txt [len - 1] == '\"') {
947 *value = g_strndup (txt + 1, len - 2);
953 res = (*value) != NULL;
959 xmlXPathFreeContext (xpctx);
965 /* ************************************************************************* */
966 /* Authentication helpers for libsoup */
969 soup_authenticate (SoupSession *session,
975 ECalBackendCalDAV *cbdav;
976 ESourceAuthentication *auth_extension;
978 const gchar *extension_name;
980 cbdav = E_CAL_BACKEND_CALDAV (data);
982 source = e_backend_get_source (E_BACKEND (data));
983 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
984 auth_extension = e_source_get_extension (source, extension_name);
986 /* do not send same password twice, but keep it for later use */
987 if (!retrying && cbdav->priv->password != NULL) {
990 user = e_source_authentication_dup_user (auth_extension);
991 soup_auth_authenticate (auth, user, cbdav->priv->password);
996 /* ************************************************************************* */
997 /* direct CalDAV server access functions */
1000 redirect_handler (SoupMessage *msg,
1003 if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
1004 SoupSession *soup_session = user_data;
1006 const gchar *new_loc;
1008 new_loc = soup_message_headers_get (msg->response_headers, "Location");
1012 new_uri = soup_uri_new_with_base (soup_message_get_uri (msg), new_loc);
1014 soup_message_set_status_full (msg,
1015 SOUP_STATUS_MALFORMED,
1016 "Invalid Redirect URL");
1020 if (new_uri->host && g_str_has_suffix (new_uri->host, "yahoo.com")) {
1021 /* yahoo! returns port 7070, which is unreachable;
1022 * it also requires https being used (below call resets port as well) */
1023 soup_uri_set_scheme (new_uri, SOUP_URI_SCHEME_HTTPS);
1026 soup_message_set_uri (msg, new_uri);
1027 soup_session_requeue_message (soup_session, msg);
1029 soup_uri_free (new_uri);
1034 send_and_handle_redirection (SoupSession *soup_session,
1036 gchar **new_location)
1038 gchar *old_uri = NULL;
1040 g_return_if_fail (msg != NULL);
1043 old_uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1045 soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
1046 soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), soup_session);
1047 soup_message_headers_append (msg->request_headers, "Connection", "close");
1048 soup_session_send_message (soup_session, msg);
1051 gchar *new_loc = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
1053 if (new_loc && old_uri && !g_str_equal (new_loc, old_uri))
1054 *new_location = new_loc;
1063 caldav_generate_uri (ECalBackendCalDAV *cbdav,
1064 const gchar *target)
1069 slash = strrchr (target, '/');
1073 /* uri *have* trailing slash already */
1074 uri = g_strconcat (cbdav->priv->uri, target, NULL);
1080 caldav_server_open_calendar (ECalBackendCalDAV *cbdav,
1081 gboolean *server_unreachable,
1084 SoupMessage *message;
1085 const gchar *header;
1086 gboolean calendar_access;
1087 gboolean put_allowed;
1088 gboolean delete_allowed;
1090 g_return_val_if_fail (cbdav != NULL, FALSE);
1091 g_return_val_if_fail (server_unreachable != NULL, FALSE);
1093 /* FIXME: setup text_uri */
1095 message = soup_message_new (SOUP_METHOD_OPTIONS, cbdav->priv->uri);
1096 if (message == NULL) {
1097 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1100 soup_message_headers_append (message->request_headers,
1101 "User-Agent", "Evolution/" VERSION);
1103 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1105 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1106 switch (message->status_code) {
1107 case SOUP_STATUS_CANT_CONNECT:
1108 case SOUP_STATUS_CANT_CONNECT_PROXY:
1109 *server_unreachable = TRUE;
1113 status_code_to_result (message, cbdav, TRUE, perror);
1115 g_object_unref (message);
1119 /* parse the dav header, we are intreseted in the
1120 * calendar-access bit only at the moment */
1121 header = soup_message_headers_get (message->response_headers, "DAV");
1123 calendar_access = soup_header_contains (header, "calendar-access");
1124 cbdav->priv->calendar_schedule = soup_header_contains (header, "calendar-schedule");
1126 calendar_access = FALSE;
1127 cbdav->priv->calendar_schedule = FALSE;
1130 /* parse the Allow header and look for PUT, DELETE at the
1131 * moment (maybe we should check more here, for REPORT eg) */
1132 header = soup_message_headers_get (message->response_headers, "Allow");
1134 put_allowed = soup_header_contains (header, "PUT");
1135 delete_allowed = soup_header_contains (header, "DELETE");
1137 put_allowed = delete_allowed = FALSE;
1139 g_object_unref (message);
1141 if (calendar_access) {
1142 cbdav->priv->read_only = !(put_allowed && delete_allowed);
1146 g_propagate_error (perror, EDC_ERROR (PermissionDenied));
1151 caldav_unref_thread (gpointer cbdav)
1153 g_object_unref (cbdav);
1159 caldav_unref_in_thread (ECalBackendCalDAV *cbdav)
1163 g_return_if_fail (cbdav != NULL);
1165 thread = g_thread_new (NULL, caldav_unref_thread, cbdav);
1166 g_thread_unref (thread);
1170 caldav_authenticate (ECalBackendCalDAV *cbdav,
1172 GCancellable *cancellable,
1178 g_object_ref (cbdav);
1180 success = e_backend_authenticate_sync (
1182 E_SOURCE_AUTHENTICATOR (cbdav),
1183 cancellable, error);
1186 caldav_unref_in_thread (cbdav);
1191 static gconstpointer
1192 compat_libxml_output_buffer_get_content (xmlOutputBufferPtr buf,
1195 #ifdef LIBXML2_NEW_BUFFER
1196 *out_len = xmlOutputBufferGetSize (buf);
1197 return xmlOutputBufferGetContent (buf);
1199 *out_len = buf->buffer->use;
1200 return buf->buffer->content;
1204 /* Returns whether calendar changed on the server. This works only when server
1205 * supports 'getctag' extension. */
1207 check_calendar_changed_on_server (ECalBackendCalDAV *cbdav)
1209 xmlOutputBufferPtr buf;
1210 SoupMessage *message;
1212 xmlNodePtr root, node;
1214 gconstpointer buf_content;
1216 gboolean result = TRUE;
1218 g_return_val_if_fail (cbdav != NULL, TRUE);
1220 /* no support for 'getctag', thus update cache */
1221 if (!cbdav->priv->ctag_supported)
1224 /* Prepare the soup message */
1225 message = soup_message_new ("PROPFIND", cbdav->priv->uri);
1226 if (message == NULL)
1229 doc = xmlNewDoc ((xmlChar *) "1.0");
1230 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1231 xmlDocSetRootElement (doc, root);
1232 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1233 ns = xmlNewNs (root, (xmlChar *) "http://calendarserver.org/ns/", (xmlChar *) "CS");
1235 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1236 node = xmlNewTextChild (node, nsdav, (xmlChar *) "getctag", NULL);
1237 xmlSetNs (node, ns);
1239 buf = xmlAllocOutputBuffer (NULL);
1240 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1241 xmlOutputBufferFlush (buf);
1243 soup_message_headers_append (message->request_headers,
1244 "User-Agent", "Evolution/" VERSION);
1245 soup_message_headers_append (message->request_headers,
1248 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
1249 soup_message_set_request (message,
1252 buf_content, buf_size);
1254 /* Send the request now */
1255 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1257 /* Clean up the memory */
1258 xmlOutputBufferClose (buf);
1261 /* Check the result */
1262 if (message->status_code == 401) {
1263 caldav_authenticate (cbdav, TRUE, NULL, NULL);
1264 } else if (message->status_code != 207) {
1265 /* does not support it, but report calendar changed to update cache */
1266 cbdav->priv->ctag_supported = FALSE;
1270 if (parse_propfind_response (message, XPATH_GETCTAG_STATUS, XPATH_GETCTAG, &ctag)) {
1271 const gchar *my_ctag;
1273 my_ctag = e_cal_backend_store_get_key_value (
1274 cbdav->priv->store, CALDAV_CTAG_KEY);
1276 if (ctag && my_ctag && g_str_equal (ctag, my_ctag)) {
1277 /* ctag is same, no change in the calendar */
1280 /* do not store ctag now, do it rather after complete sync */
1281 g_free (cbdav->priv->ctag_to_store);
1282 cbdav->priv->ctag_to_store = ctag;
1288 cbdav->priv->ctag_supported = FALSE;
1292 g_object_unref (message);
1297 /* only_hrefs is a list of requested objects to fetch; it has precedence from
1298 * start_time/end_time, which are used only when both positive.
1299 * Times are supposed to be in UTC, if set.
1302 caldav_server_list_objects (ECalBackendCalDAV *cbdav,
1303 CalDAVObject **objs,
1309 xmlOutputBufferPtr buf;
1310 SoupMessage *message;
1317 gconstpointer buf_content;
1321 /* Allocate the soup message */
1322 message = soup_message_new ("REPORT", cbdav->priv->uri);
1323 if (message == NULL)
1326 /* Maybe we should just do a g_strdup_printf here? */
1327 /* Prepare request body */
1328 doc = xmlNewDoc ((xmlChar *) "1.0");
1330 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-query", NULL);
1332 root = xmlNewDocNode (doc, NULL, (xmlChar *) "calendar-multiget", NULL);
1333 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1334 xmlSetNs (root, nscd);
1335 xmlDocSetRootElement (doc, root);
1337 /* Add webdav tags */
1338 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", (xmlChar *) "D");
1339 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1340 xmlNewTextChild (node, nsdav, (xmlChar *) "getetag", NULL);
1344 xmlNewTextChild (node, nscd, (xmlChar *) "calendar-data", NULL);
1345 for (l = only_hrefs; l; l = l->next) {
1347 xmlNewTextChild (root, nsdav, (xmlChar *) "href", (xmlChar *) l->data);
1351 node = xmlNewTextChild (root, nscd, (xmlChar *) "filter", NULL);
1352 node = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1353 xmlSetProp (node, (xmlChar *) "name", (xmlChar *) "VCALENDAR");
1355 sn = xmlNewTextChild (node, nscd, (xmlChar *) "comp-filter", NULL);
1356 switch (e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
1358 case ICAL_VEVENT_COMPONENT:
1359 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VEVENT");
1361 case ICAL_VJOURNAL_COMPONENT:
1362 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VJOURNAL");
1364 case ICAL_VTODO_COMPONENT:
1365 xmlSetProp (sn, (xmlChar *) "name", (xmlChar *) "VTODO");
1369 if (start_time > 0 || end_time > 0) {
1372 sn = xmlNewTextChild (sn, nscd, (xmlChar *) "time-range", NULL);
1374 if (start_time > 0) {
1375 tmp = isodate_from_time_t (start_time);
1376 xmlSetProp (sn, (xmlChar *) "start", (xmlChar *) tmp);
1381 tmp = isodate_from_time_t (end_time);
1382 xmlSetProp (sn, (xmlChar *) "end", (xmlChar *) tmp);
1388 buf = xmlAllocOutputBuffer (NULL);
1389 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1390 xmlOutputBufferFlush (buf);
1392 /* Prepare the soup message */
1393 soup_message_headers_append (message->request_headers,
1394 "User-Agent", "Evolution/" VERSION);
1395 soup_message_headers_append (message->request_headers,
1398 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
1399 soup_message_set_request (message,
1402 buf_content, buf_size);
1404 /* Send the request now */
1405 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1407 /* Clean up the memory */
1408 xmlOutputBufferClose (buf);
1411 /* Check the result */
1412 if (message->status_code != 207) {
1413 switch (message->status_code) {
1414 case SOUP_STATUS_CANT_CONNECT:
1415 case SOUP_STATUS_CANT_CONNECT_PROXY:
1416 cbdav->priv->opened = FALSE;
1417 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
1418 cbdav->priv->read_only = TRUE;
1419 e_cal_backend_notify_readonly (
1420 E_CAL_BACKEND (cbdav), cbdav->priv->read_only);
1423 caldav_authenticate (cbdav, TRUE, NULL, NULL);
1426 g_warning ("Server did not response with 207, but with code %d (%s)", message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1430 g_object_unref (message);
1434 /* Parse the response body */
1435 result = parse_report_response (message, objs, len);
1437 g_object_unref (message);
1442 caldav_server_download_attachment (ECalBackendCalDAV *cbdav,
1443 const gchar *attachment_uri,
1448 SoupMessage *message;
1450 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
1451 g_return_val_if_fail (attachment_uri != NULL, FALSE);
1452 g_return_val_if_fail (content != NULL, FALSE);
1453 g_return_val_if_fail (len != NULL, FALSE);
1455 message = soup_message_new (SOUP_METHOD_GET, attachment_uri);
1456 if (message == NULL) {
1457 g_propagate_error (error, EDC_ERROR (InvalidObject));
1461 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1462 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1464 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1465 status_code_to_result (message, cbdav, FALSE, error);
1467 if (message->status_code == 401)
1468 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1470 g_object_unref (message);
1474 *len = message->response_body->length;
1475 *content = g_memdup (message->response_body->data, *len);
1477 g_object_unref (message);
1483 caldav_server_get_object (ECalBackendCalDAV *cbdav,
1484 CalDAVObject *object,
1487 SoupMessage *message;
1491 g_assert (object != NULL && object->href != NULL);
1493 uri = caldav_generate_uri (cbdav, object->href);
1494 message = soup_message_new (SOUP_METHOD_GET, uri);
1495 if (message == NULL) {
1497 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1501 soup_message_headers_append (message->request_headers,
1502 "User-Agent", "Evolution/" VERSION);
1504 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1506 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1507 status_code_to_result (message, cbdav, FALSE, perror);
1509 if (message->status_code == 401)
1510 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1512 g_warning ("Could not fetch object '%s' from server, status:%d (%s)", uri, message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1513 g_object_unref (message);
1518 hdr = soup_message_headers_get (message->response_headers, "Content-Type");
1520 if (hdr == NULL || g_ascii_strncasecmp (hdr, "text/calendar", 13)) {
1521 g_propagate_error (perror, EDC_ERROR (InvalidObject));
1522 g_object_unref (message);
1523 g_warning ("Object to fetch '%s' not of type text/calendar", uri);
1528 hdr = soup_message_headers_get (message->response_headers, "ETag");
1531 g_free (object->etag);
1532 object->etag = quote_etag (hdr);
1533 } else if (!object->etag) {
1534 g_warning ("UUHH no ETag, now that's bad! (at '%s')", uri);
1538 g_free (object->cdata);
1539 object->cdata = g_strdup (message->response_body->data);
1541 g_object_unref (message);
1547 caldav_post_freebusy (ECalBackendCalDAV *cbdav,
1552 SoupMessage *message;
1554 e_return_data_cal_error_if_fail (cbdav != NULL, InvalidArg);
1555 e_return_data_cal_error_if_fail (url != NULL, InvalidArg);
1556 e_return_data_cal_error_if_fail (post_fb != NULL, InvalidArg);
1557 e_return_data_cal_error_if_fail (*post_fb != NULL, InvalidArg);
1559 message = soup_message_new (SOUP_METHOD_POST, url);
1560 if (message == NULL) {
1561 g_propagate_error (error, EDC_ERROR (NoSuchCal));
1565 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1566 soup_message_set_request (message,
1567 "text/calendar; charset=utf-8",
1569 *post_fb, strlen (*post_fb));
1571 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1573 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
1574 status_code_to_result (message, cbdav, FALSE, error);
1575 if (message->status_code == 401)
1576 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1578 g_warning ("Could not post free/busy request to '%s', status:%d (%s)", url, message->status_code, soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : "Unknown code");
1580 g_object_unref (message);
1586 *post_fb = g_strdup (message->response_body->data);
1588 g_object_unref (message);
1592 caldav_gen_file_from_uid_cal (ECalBackendCalDAV *cbdav,
1593 icalcomponent *icalcomp)
1595 icalcomponent_kind my_kind;
1596 const gchar *uid = NULL;
1597 gchar *filename, *res;
1599 g_return_val_if_fail (cbdav != NULL, NULL);
1600 g_return_val_if_fail (icalcomp != NULL, NULL);
1602 my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
1603 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
1604 icalcomponent *subcomp;
1606 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
1608 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
1609 uid = icalcomponent_get_uid (subcomp);
1612 } else if (icalcomponent_isa (icalcomp) == my_kind) {
1613 uid = icalcomponent_get_uid (icalcomp);
1619 filename = g_strconcat (uid, ".ics", NULL);
1620 res = soup_uri_encode (filename, NULL);
1627 caldav_server_put_object (ECalBackendCalDAV *cbdav,
1628 CalDAVObject *object,
1629 icalcomponent *icalcomp,
1632 SoupMessage *message;
1638 g_assert (object != NULL && object->cdata != NULL);
1640 uri = caldav_generate_uri (cbdav, object->href);
1641 message = soup_message_new (SOUP_METHOD_PUT, uri);
1643 if (message == NULL) {
1644 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1648 soup_message_headers_append (message->request_headers,
1649 "User-Agent", "Evolution/" VERSION);
1651 /* For new items we use the If-None-Match so we don't
1652 * acidently override resources, for item updates we
1653 * use the If-Match header to avoid the Lost-update
1655 if (object->etag == NULL) {
1656 soup_message_headers_append (message->request_headers, "If-None-Match", "*");
1658 soup_message_headers_append (message->request_headers,
1659 "If-Match", object->etag);
1662 soup_message_set_request (message,
1663 "text/calendar; charset=utf-8",
1666 strlen (object->cdata));
1669 send_and_handle_redirection (cbdav->priv->session, message, &uri);
1672 gchar *file = strrchr (uri, '/');
1674 /* there was a redirect, update href properly */
1678 g_free (object->href);
1680 decoded = soup_uri_decode (file + 1);
1681 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1689 if (status_code_to_result (message, cbdav, FALSE, perror)) {
1690 GError *local_error = NULL;
1692 hdr = soup_message_headers_get (message->response_headers, "ETag");
1694 g_free (object->etag);
1695 object->etag = quote_etag (hdr);
1697 /* no ETag header returned, check for it with a GET */
1698 hdr = soup_message_headers_get (message->response_headers, "Location");
1700 /* reflect possible href change first */
1701 gchar *file = strrchr (hdr, '/');
1706 g_free (object->href);
1708 decoded = soup_uri_decode (file + 1);
1709 object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
1716 if (!caldav_server_get_object (cbdav, object, &local_error)) {
1717 if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
1720 /* OK, the event was properly created, but cannot be found on the place
1721 * where it was PUT - why didn't server tell us where it saved it? */
1722 g_clear_error (&local_error);
1724 /* try whether it's saved as its UID.ics file */
1725 file = caldav_gen_file_from_uid_cal (cbdav, icalcomp);
1727 g_free (object->href);
1728 object->href = file;
1730 if (!caldav_server_get_object (cbdav, object, &local_error)) {
1731 if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
1732 g_clear_error (&local_error);
1734 /* not sure what can happen, but do not need to guess for ever,
1735 * thus report success and update the calendar to get fresh info */
1736 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
1737 g_cond_signal (cbdav->priv->cond);
1745 icalcomponent *use_comp = NULL;
1747 if (object->cdata) {
1748 /* maybe server also modified component, thus rather store the server's */
1749 use_comp = icalparser_parse_string (object->cdata);
1753 use_comp = icalcomp;
1755 put_comp_to_cache (cbdav, use_comp, object->href, object->etag);
1757 if (use_comp != icalcomp)
1758 icalcomponent_free (use_comp);
1760 g_propagate_error (perror, local_error);
1762 } else if (message->status_code == 401) {
1763 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1766 g_object_unref (message);
1772 caldav_server_delete_object (ECalBackendCalDAV *cbdav,
1773 CalDAVObject *object,
1776 SoupMessage *message;
1779 g_assert (object != NULL && object->href != NULL);
1781 uri = caldav_generate_uri (cbdav, object->href);
1782 message = soup_message_new (SOUP_METHOD_DELETE, uri);
1784 if (message == NULL) {
1785 g_propagate_error (perror, EDC_ERROR (NoSuchCal));
1789 soup_message_headers_append (message->request_headers,
1790 "User-Agent", "Evolution/" VERSION);
1792 if (object->etag != NULL) {
1793 soup_message_headers_append (message->request_headers,
1794 "If-Match", object->etag);
1797 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1799 status_code_to_result (message, cbdav, FALSE, perror);
1801 if (message->status_code == 401)
1802 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1804 g_object_unref (message);
1808 caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
1810 SoupMessage *message;
1811 xmlOutputBufferPtr buf;
1813 xmlNodePtr root, node;
1815 gconstpointer buf_content;
1817 gchar *owner = NULL;
1819 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
1820 g_return_val_if_fail (cbdav->priv->schedule_outbox_url == NULL, TRUE);
1822 /* Prepare the soup message */
1823 message = soup_message_new ("PROPFIND", cbdav->priv->uri);
1824 if (message == NULL)
1827 doc = xmlNewDoc ((xmlChar *) "1.0");
1828 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1829 xmlDocSetRootElement (doc, root);
1830 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1832 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1833 xmlNewTextChild (node, nsdav, (xmlChar *) "owner", NULL);
1835 buf = xmlAllocOutputBuffer (NULL);
1836 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1837 xmlOutputBufferFlush (buf);
1839 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1840 soup_message_headers_append (message->request_headers, "Depth", "0");
1842 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
1843 soup_message_set_request (message,
1846 buf_content, buf_size);
1848 /* Send the request now */
1849 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1851 /* Clean up the memory */
1852 xmlOutputBufferClose (buf);
1855 /* Check the result */
1856 if (message->status_code == 207 && parse_propfind_response (message, XPATH_OWNER_STATUS, XPATH_OWNER, &owner) && owner && *owner) {
1860 g_object_unref (message);
1862 /* owner is a full path to the user's URL, thus change it in
1863 * calendar's uri when asking for schedule-outbox-URL */
1864 suri = soup_uri_new (cbdav->priv->uri);
1865 soup_uri_set_path (suri, owner);
1867 owner = soup_uri_to_string (suri, FALSE);
1868 soup_uri_free (suri);
1870 message = soup_message_new ("PROPFIND", owner);
1871 if (message == NULL) {
1876 doc = xmlNewDoc ((xmlChar *) "1.0");
1877 root = xmlNewDocNode (doc, NULL, (xmlChar *) "propfind", NULL);
1878 xmlDocSetRootElement (doc, root);
1879 nsdav = xmlNewNs (root, (xmlChar *) "DAV:", NULL);
1880 nscd = xmlNewNs (root, (xmlChar *) "urn:ietf:params:xml:ns:caldav", (xmlChar *) "C");
1882 node = xmlNewTextChild (root, nsdav, (xmlChar *) "prop", NULL);
1883 xmlNewTextChild (node, nscd, (xmlChar *) "schedule-outbox-URL", NULL);
1885 buf = xmlAllocOutputBuffer (NULL);
1886 xmlNodeDumpOutput (buf, doc, root, 0, 1, NULL);
1887 xmlOutputBufferFlush (buf);
1889 soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
1890 soup_message_headers_append (message->request_headers, "Depth", "0");
1892 buf_content = compat_libxml_output_buffer_get_content (buf, &buf_size);
1893 soup_message_set_request (message,
1896 buf_content, buf_size);
1898 /* Send the request now */
1899 send_and_handle_redirection (cbdav->priv->session, message, NULL);
1901 if (message->status_code == 207 && parse_propfind_response (message, XPATH_SCHEDULE_OUTBOX_URL_STATUS, XPATH_SCHEDULE_OUTBOX_URL, &cbdav->priv->schedule_outbox_url)) {
1902 if (!*cbdav->priv->schedule_outbox_url) {
1903 g_free (cbdav->priv->schedule_outbox_url);
1904 cbdav->priv->schedule_outbox_url = NULL;
1906 /* make it a full URI */
1907 suri = soup_uri_new (cbdav->priv->uri);
1908 soup_uri_set_path (suri, cbdav->priv->schedule_outbox_url);
1909 g_free (cbdav->priv->schedule_outbox_url);
1910 cbdav->priv->schedule_outbox_url = soup_uri_to_string (suri, FALSE);
1911 soup_uri_free (suri);
1915 /* Clean up the memory */
1916 xmlOutputBufferClose (buf);
1918 } else if (message->status_code == 401) {
1919 caldav_authenticate (cbdav, FALSE, NULL, NULL);
1923 g_object_unref (message);
1927 return cbdav->priv->schedule_outbox_url != NULL;
1930 /* ************************************************************************* */
1931 /* Synchronization foo */
1933 static gboolean extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp);
1935 struct cache_comp_list
1941 remove_complist_from_cache_and_notify_cb (gpointer key,
1946 struct cache_comp_list *ccl = value;
1947 ECalBackendCalDAV *cbdav = data;
1949 for (l = ccl->slist; l; l = l->next) {
1950 ECalComponent *old_comp = l->data;
1951 ECalComponentId *id;
1953 id = e_cal_component_get_id (old_comp);
1958 if (e_cal_backend_store_remove_component (cbdav->priv->store, id->uid, id->rid)) {
1959 e_cal_backend_notify_component_removed ((ECalBackend *) cbdav, id, old_comp, NULL);
1962 e_cal_component_free_id (id);
1964 remove_cached_attachment (cbdav, (const gchar *) key);
1970 free_comp_list (gpointer cclist)
1972 struct cache_comp_list *ccl = cclist;
1974 g_return_if_fail (ccl != NULL);
1976 g_slist_foreach (ccl->slist, (GFunc) g_object_unref, NULL);
1977 g_slist_free (ccl->slist);
1981 #define etags_match(_tag1, _tag2) ((_tag1 == _tag2) ? TRUE : \
1982 g_str_equal (_tag1 != NULL ? _tag1 : "", \
1983 _tag2 != NULL ? _tag2 : ""))
1985 /* start_time/end_time is an interval for checking changes. If both greater than zero,
1986 * only the interval is checked and the removed items are not notified, as they can
1990 synchronize_cache (ECalBackendCalDAV *cbdav,
1995 CalDAVObject *sobjs, *object;
1996 GSList *c_objs, *c_iter; /* list of all items known from our cache */
1997 GTree *c_uid2complist; /* cache components list (with detached instances) sorted by (master's) uid */
1998 GHashTable *c_href2uid; /* connection between href and a (master's) uid */
1999 GSList *hrefs_to_update, *htu; /* list of href-s to update */
2002 if (!check_calendar_changed_on_server (cbdav)) {
2003 /* no changes on the server, no update required */
2007 bkend = E_CAL_BACKEND (cbdav);
2011 /* get list of server objects */
2012 if (!caldav_server_list_objects (cbdav, &sobjs, &len, NULL, start_time, end_time))
2015 c_objs = e_cal_backend_store_get_components (cbdav->priv->store);
2017 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2018 printf ("CalDAV - found %d objects on the server, locally stored %d objects\n", len, g_slist_length (c_objs)); fflush (stdout);
2021 /* do not store changes in cache immediately - makes things significantly quicker */
2022 e_cal_backend_store_freeze_changes (cbdav->priv->store);
2024 c_uid2complist = g_tree_new_full ((GCompareDataFunc) g_strcmp0, NULL, g_free, free_comp_list);
2025 c_href2uid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
2027 /* fill indexed hash and tree with cached components */
2028 for (c_iter = c_objs; c_iter; c_iter = g_slist_next (c_iter)) {
2029 ECalComponent *ccomp = E_CAL_COMPONENT (c_iter->data);
2030 const gchar *uid = NULL;
2031 struct cache_comp_list *ccl;
2034 e_cal_component_get_uid (ccomp, &uid);
2036 g_warning ("broken component with NULL Id");
2040 href = ecalcomp_get_href (ccomp);
2043 g_warning ("href of object NULL :(");
2047 ccl = g_tree_lookup (c_uid2complist, uid);
2049 ccl->slist = g_slist_prepend (ccl->slist, g_object_ref (ccomp));
2051 ccl = g_new0 (struct cache_comp_list, 1);
2052 ccl->slist = g_slist_append (NULL, g_object_ref (ccomp));
2054 /* make a copy, which will be used in the c_href2uid too */
2055 uid = g_strdup (uid);
2057 g_tree_insert (c_uid2complist, (gpointer) uid, ccl);
2060 if (g_hash_table_lookup (c_href2uid, href) == NULL) {
2061 /* uid is from a component or c_uid2complist key, thus will not be
2062 * freed before a removal from c_uid2complist, thus do not duplicate it,
2063 * rather save memory */
2064 g_hash_table_insert (c_href2uid, href, (gpointer) uid);
2070 /* clear it now, we do not need it later */
2071 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2072 g_slist_free (c_objs);
2075 hrefs_to_update = NULL;
2077 /* see if we have to update or add some objects */
2078 for (i = 0, object = sobjs; i < len && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK; i++, object++) {
2079 ECalComponent *ccomp = NULL;
2082 struct cache_comp_list *ccl;
2084 if (object->status != 200) {
2085 /* just continue here, so that the object
2086 * doesnt get removed from the cobjs list
2087 * - therefore it will be removed */
2091 uid = g_hash_table_lookup (c_href2uid, object->href);
2093 ccl = g_tree_lookup (c_uid2complist, uid);
2096 for (sl = ccl->slist; sl && !etag; sl = sl->next) {
2099 etag = ecalcomp_get_etag (ccomp);
2107 if (!etag || !etags_match (etag, object->etag)) {
2108 hrefs_to_update = g_slist_prepend (hrefs_to_update, object->href);
2109 } else if (uid && ccl) {
2110 /* all components cover by this uid are up-to-date */
2113 for (p = ccl->slist; p; p = p->next) {
2114 g_object_unref (p->data);
2117 g_slist_free (ccl->slist);
2124 /* free hash table, as it is not used anymore */
2125 g_hash_table_destroy (c_href2uid);
2128 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2129 printf ("CalDAV - recognized %d items to update\n", g_slist_length (hrefs_to_update)); fflush (stdout);
2132 htu = hrefs_to_update;
2133 while (htu && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK) {
2135 GSList *to_fetch = NULL;
2137 while (count < CALDAV_MAX_MULTIGET_AMOUNT && htu) {
2138 to_fetch = g_slist_prepend (to_fetch, htu->data);
2143 if (to_fetch && cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK) {
2144 CalDAVObject *up_sobjs = NULL;
2146 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2147 printf ("CalDAV - going to fetch %d items\n", g_slist_length (to_fetch)); fflush (stdout);
2151 if (!caldav_server_list_objects (cbdav, &up_sobjs, &count, to_fetch, 0, 0)) {
2152 fprintf (stderr, "CalDAV - failed to retrieve bunch of items\n"); fflush (stderr);
2156 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2157 printf ("CalDAV - fetched bunch of %d items\n", count); fflush (stdout);
2160 /* we are going to update cache */
2161 /* they are downloaded, so process them */
2162 for (i = 0, object = up_sobjs; i < count /*&& cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK */; i++, object++) {
2163 if (object->status == 200 && object->href && object->etag && object->cdata && *object->cdata) {
2164 icalcomponent *icomp = icalparser_parse_string (object->cdata);
2167 icalcomponent_kind kind = icalcomponent_isa (icomp);
2169 extract_timezones (cbdav, icomp);
2171 if (kind == ICAL_VCALENDAR_COMPONENT) {
2172 icalcomponent *subcomp;
2174 kind = e_cal_backend_get_kind (bkend);
2176 for (subcomp = icalcomponent_get_first_component (icomp, kind);
2178 subcomp = icalcomponent_get_next_component (icomp, kind)) {
2179 ECalComponent *new_comp, *old_comp;
2181 convert_to_url_attachment (cbdav, subcomp);
2182 new_comp = e_cal_component_new ();
2183 if (e_cal_component_set_icalcomponent (new_comp, icalcomponent_new_clone (subcomp))) {
2184 const gchar *uid = NULL;
2185 struct cache_comp_list *ccl;
2187 e_cal_component_get_uid (new_comp, &uid);
2189 g_warning ("%s: no UID on component!", G_STRFUNC);
2190 g_object_unref (new_comp);
2194 ecalcomp_set_href (new_comp, object->href);
2195 ecalcomp_set_etag (new_comp, object->etag);
2198 ccl = g_tree_lookup (c_uid2complist, uid);
2200 gchar *nc_rid = e_cal_component_get_recurid_as_string (new_comp);
2203 for (p = ccl->slist; p && !old_comp; p = p->next) {
2208 oc_rid = e_cal_component_get_recurid_as_string (old_comp);
2209 if (g_strcmp0 (nc_rid, oc_rid) != 0) {
2219 put_component_to_store (cbdav, new_comp);
2221 if (old_comp == NULL) {
2222 e_cal_backend_notify_component_created (bkend, new_comp);
2224 e_cal_backend_notify_component_modified (bkend, old_comp, new_comp);
2226 ccl->slist = g_slist_remove (ccl->slist, old_comp);
2227 g_object_unref (old_comp);
2231 g_object_unref (new_comp);
2235 icalcomponent_free (icomp);
2239 /* these free immediately */
2240 caldav_object_free (object, FALSE);
2243 /* cache update done for fetched items */
2247 /* do not free 'data' itself, it's part of 'sobjs' */
2248 g_slist_free (to_fetch);
2251 /* if not interrupted and not using the time range... */
2252 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK && (!start_time || !end_time)) {
2253 /* ...remove old (not on server anymore) items from our cache and notify of a removal */
2254 g_tree_foreach (c_uid2complist, remove_complist_from_cache_and_notify_cb, cbdav);
2257 if (cbdav->priv->ctag_to_store) {
2258 /* store only when wasn't interrupted */
2259 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_WORK && start_time == 0 && end_time == 0) {
2260 e_cal_backend_store_put_key_value (cbdav->priv->store, CALDAV_CTAG_KEY, cbdav->priv->ctag_to_store);
2263 g_free (cbdav->priv->ctag_to_store);
2264 cbdav->priv->ctag_to_store = NULL;
2267 /* save cache changes to disk finally */
2268 e_cal_backend_store_thaw_changes (cbdav->priv->store);
2270 for (i = 0, object = sobjs; i < len; i++, object++) {
2271 caldav_object_free (object, FALSE);
2274 g_tree_destroy (c_uid2complist);
2275 g_slist_free (hrefs_to_update);
2280 is_google_uri (const gchar *uri)
2285 g_return_val_if_fail (uri != NULL, FALSE);
2287 suri = soup_uri_new (uri);
2288 g_return_val_if_fail (suri != NULL, FALSE);
2290 res = suri->host && g_ascii_strcasecmp (suri->host, "www.google.com") == 0;
2292 soup_uri_free (suri);
2297 /* ************************************************************************* */
2300 caldav_synch_slave_loop (gpointer data)
2302 ECalBackendCalDAV *cbdav;
2304 icaltimezone *utc = icaltimezone_get_utc_timezone ();
2305 gboolean know_unreachable;
2307 cbdav = E_CAL_BACKEND_CALDAV (data);
2309 g_mutex_lock (cbdav->priv->busy_lock);
2311 know_unreachable = !cbdav->priv->opened;
2313 while (cbdav->priv->slave_cmd != SLAVE_SHOULD_DIE) {
2314 GTimeVal alarm_clock;
2315 if (cbdav->priv->slave_cmd == SLAVE_SHOULD_SLEEP) {
2316 /* just sleep until we get woken up again */
2317 g_cond_wait (cbdav->priv->cond, cbdav->priv->busy_lock);
2319 /* check if we should die, work or sleep again */
2323 /* Ok here we go, do some real work
2324 * Synch it baby one more time ...
2326 cbdav->priv->slave_busy = TRUE;
2328 if (!cbdav->priv->opened) {
2329 gboolean server_unreachable = FALSE;
2330 GError *local_error = NULL;
2333 if (caldav_server_open_calendar (cbdav, &server_unreachable, &local_error)) {
2334 cbdav->priv->opened = TRUE;
2335 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2336 g_cond_signal (cbdav->priv->cond);
2338 cbdav->priv->is_google = is_google_uri (cbdav->priv->uri);
2339 know_unreachable = FALSE;
2340 } else if (local_error) {
2341 cbdav->priv->opened = FALSE;
2342 cbdav->priv->read_only = TRUE;
2344 if (!know_unreachable) {
2347 know_unreachable = TRUE;
2349 msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2350 e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2354 g_clear_error (&local_error);
2356 cbdav->priv->opened = FALSE;
2357 cbdav->priv->read_only = TRUE;
2358 know_unreachable = TRUE;
2361 e_cal_backend_notify_readonly (E_CAL_BACKEND (cbdav), cbdav->priv->read_only);
2363 online = e_backend_get_online (E_BACKEND (cbdav));
2364 e_cal_backend_notify_online (E_CAL_BACKEND (cbdav), online);
2367 if (cbdav->priv->opened) {
2369 /* check for events in the month before/after today first,
2370 * to show user actual data as soon as possible */
2371 synchronize_cache (cbdav, time_add_week_with_zone (now, -5, utc), time_add_week_with_zone (now, +5, utc));
2373 if (cbdav->priv->slave_cmd != SLAVE_SHOULD_SLEEP) {
2374 /* and then check for changes in a whole calendar */
2375 synchronize_cache (cbdav, 0, 0);
2378 if (caldav_debug_show (DEBUG_SERVER_ITEMS)) {
2381 c_objs = e_cal_backend_store_get_components (cbdav->priv->store);
2383 printf ("CalDAV - finished syncing with %d items in a cache\n", g_slist_length (c_objs)); fflush (stdout);
2385 g_slist_foreach (c_objs, (GFunc) g_object_unref, NULL);
2386 g_slist_free (c_objs);
2390 cbdav->priv->slave_busy = FALSE;
2392 /* puhh that was hard, get some rest :) */
2393 g_get_current_time (&alarm_clock);
2394 alarm_clock.tv_sec += cbdav->priv->refresh_time.tv_sec;
2395 g_cond_timed_wait (cbdav->priv->cond,
2396 cbdav->priv->busy_lock,
2401 /* signal we are done */
2402 g_cond_signal (cbdav->priv->slave_gone_cond);
2404 cbdav->priv->synch_slave = NULL;
2406 /* we got killed ... */
2407 g_mutex_unlock (cbdav->priv->busy_lock);
2412 maybe_append_email_domain (const gchar *username,
2413 const gchar *may_append)
2415 if (!username || !*username)
2418 if (strchr (username, '@'))
2419 return g_strdup (username);
2421 return g_strconcat (username, may_append, NULL);
2425 get_usermail (ECalBackend *backend)
2427 ECalBackendCalDAV *cbdav;
2429 ESourceAuthentication *auth_extension;
2430 ESourceWebdav *webdav_extension;
2431 const gchar *extension_name;
2436 g_return_val_if_fail (backend != NULL, NULL);
2438 source = e_backend_get_source (E_BACKEND (backend));
2440 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2441 webdav_extension = e_source_get_extension (source, extension_name);
2443 /* This will never return an empty string. */
2444 usermail = e_source_webdav_dup_email_address (webdav_extension);
2446 if (usermail != NULL)
2449 cbdav = E_CAL_BACKEND_CALDAV (backend);
2451 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
2452 auth_extension = e_source_get_extension (source, extension_name);
2453 username = e_source_authentication_dup_user (auth_extension);
2455 if (cbdav->priv && cbdav->priv->is_google)
2456 res = maybe_append_email_domain (username, "@gmail.com");
2463 /* ************************************************************************* */
2464 /* ********** ECalBackendSync virtual function implementation ************* */
2467 caldav_get_backend_property (ECalBackendSync *backend,
2469 GCancellable *cancellable,
2470 const gchar *prop_name,
2474 gboolean processed = TRUE;
2476 g_return_val_if_fail (prop_name != NULL, FALSE);
2477 g_return_val_if_fail (prop_value != NULL, FALSE);
2479 if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
2480 ESourceWebdav *extension;
2484 const gchar *extension_name;
2486 caps = g_string_new (CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
2487 CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
2488 CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED);
2490 usermail = get_usermail (E_CAL_BACKEND (backend));
2491 if (!usermail || !*usermail)
2492 g_string_append (caps, "," CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS);
2495 source = e_backend_get_source (E_BACKEND (backend));
2497 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2498 extension = e_source_get_extension (source, extension_name);
2500 if (e_source_webdav_get_calendar_auto_schedule (extension)) {
2501 g_string_append (caps, "," CAL_STATIC_CAPABILITY_CREATE_MESSAGES
2502 "," CAL_STATIC_CAPABILITY_SAVE_SCHEDULES);
2505 *prop_value = g_string_free (caps, FALSE);
2506 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
2507 g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
2508 *prop_value = get_usermail (E_CAL_BACKEND (backend));
2509 } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
2510 ECalComponent *comp;
2512 comp = e_cal_component_new ();
2514 switch (e_cal_backend_get_kind (E_CAL_BACKEND (backend))) {
2515 case ICAL_VEVENT_COMPONENT:
2516 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
2518 case ICAL_VTODO_COMPONENT:
2519 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
2521 case ICAL_VJOURNAL_COMPONENT:
2522 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
2525 g_object_unref (comp);
2526 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
2530 *prop_value = e_cal_component_get_as_string (comp);
2531 g_object_unref (comp);
2540 initialize_backend (ECalBackendCalDAV *cbdav,
2543 ESourceAuthentication *auth_extension;
2544 ESourceOffline *offline_extension;
2545 ESourceRefresh *refresh_extension;
2546 ESourceWebdav *webdav_extension;
2547 ECalBackend *backend;
2551 const gchar *cache_dir;
2552 const gchar *extension_name;
2553 guint interval_in_minutes;
2555 backend = E_CAL_BACKEND (cbdav);
2556 cache_dir = e_cal_backend_get_cache_dir (backend);
2557 source = e_backend_get_source (E_BACKEND (backend));
2559 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
2560 auth_extension = e_source_get_extension (source, extension_name);
2562 extension_name = E_SOURCE_EXTENSION_OFFLINE;
2563 offline_extension = e_source_get_extension (source, extension_name);
2565 extension_name = E_SOURCE_EXTENSION_REFRESH;
2566 refresh_extension = e_source_get_extension (source, extension_name);
2568 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
2569 webdav_extension = e_source_get_extension (source, extension_name);
2571 if (!g_signal_handler_find (G_OBJECT (source), G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, caldav_source_changed_cb, cbdav))
2572 g_signal_connect (G_OBJECT (source), "changed", G_CALLBACK (caldav_source_changed_cb), cbdav);
2574 cbdav->priv->do_offline = e_source_offline_get_stay_synchronized (offline_extension);
2576 cbdav->priv->auth_required = e_source_authentication_required (auth_extension);
2578 soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
2580 /* properly encode uri */
2581 if (soup_uri != NULL && soup_uri->path != NULL) {
2584 if (strchr (soup_uri->path, '%')) {
2585 /* If path contains anything already encoded, then
2586 * decode it first, thus it'll be managed properly.
2587 * For example, the '#' in a path is in URI shown as
2588 * %23 and not doing this decode makes it being like
2589 * %2523, which is not what is wanted here. */
2590 tmp = soup_uri_decode (soup_uri->path);
2591 soup_uri_set_path (soup_uri, tmp);
2595 tmp = soup_uri_encode (soup_uri->path, NULL);
2596 path = soup_uri_normalize (tmp, "/");
2598 soup_uri_set_path (soup_uri, path);
2604 g_free (cbdav->priv->uri);
2605 cbdav->priv->uri = soup_uri_to_string (soup_uri, FALSE);
2607 soup_uri_free (soup_uri);
2609 g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
2611 /* remove trailing slashes... */
2612 if (cbdav->priv->uri != NULL) {
2613 len = strlen (cbdav->priv->uri);
2615 if (cbdav->priv->uri[len] == '/') {
2616 cbdav->priv->uri[len] = '\0';
2623 /* ...and append exactly one slash */
2624 if (cbdav->priv->uri && *cbdav->priv->uri) {
2625 gchar *tmp = cbdav->priv->uri;
2627 cbdav->priv->uri = g_strconcat (cbdav->priv->uri, "/", NULL);
2632 if (cbdav->priv->store == NULL) {
2633 /* remove the old cache while migrating to ECalBackendStore */
2634 e_cal_backend_cache_remove (cache_dir, "cache.xml");
2635 cbdav->priv->store = e_cal_backend_file_store_new (cache_dir);
2637 if (cbdav->priv->store == NULL) {
2638 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Cannot create local store"));
2642 e_cal_backend_store_load (cbdav->priv->store);
2645 /* Set the local attachment store */
2646 if (g_mkdir_with_parents (cache_dir, 0700) < 0) {
2647 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "mkdir failed"));
2651 /* FIXME Not honoring ESourceRefresh:enabled. */
2652 interval_in_minutes =
2653 e_source_refresh_get_interval_minutes (refresh_extension);
2655 if (interval_in_minutes == 0)
2656 cbdav->priv->refresh_time.tv_sec = DEFAULT_REFRESH_TIME;
2658 cbdav->priv->refresh_time.tv_sec = interval_in_minutes * 60;
2660 if (!cbdav->priv->synch_slave) {
2663 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
2664 slave = g_thread_create (caldav_synch_slave_loop, cbdav, FALSE, NULL);
2666 if (slave == NULL) {
2667 g_propagate_error (perror, EDC_ERROR_EX (OtherError, "Could not create synch slave"));
2670 cbdav->priv->synch_slave = slave;
2677 proxy_settings_changed (EProxy *proxy,
2680 SoupURI *proxy_uri = NULL;
2681 ECalBackendCalDAVPrivate *priv = (ECalBackendCalDAVPrivate *) user_data;
2683 if (!priv || !priv->uri || !priv->session)
2686 /* use proxy if necessary */
2687 if (e_proxy_require_proxy_for_uri (proxy, priv->uri)) {
2688 proxy_uri = e_proxy_peek_uri_for (proxy, priv->uri);
2691 g_object_set (priv->session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
2695 open_calendar (ECalBackendCalDAV *cbdav,
2698 gboolean server_unreachable = FALSE;
2700 GError *local_error = NULL;
2702 g_return_val_if_fail (cbdav != NULL, FALSE);
2704 /* set forward proxy */
2705 proxy_settings_changed (cbdav->priv->proxy, cbdav->priv);
2707 success = caldav_server_open_calendar (
2708 cbdav, &server_unreachable, &local_error);
2711 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2712 g_cond_signal (cbdav->priv->cond);
2714 cbdav->priv->is_google = is_google_uri (cbdav->priv->uri);
2715 } else if (server_unreachable) {
2716 cbdav->priv->opened = FALSE;
2717 cbdav->priv->read_only = TRUE;
2719 gchar *msg = g_strdup_printf (_("Server is unreachable, calendar is opened in read-only mode.\nError message: %s"), local_error->message);
2720 e_cal_backend_notify_error (E_CAL_BACKEND (cbdav), msg);
2722 g_clear_error (&local_error);
2727 if (local_error != NULL)
2728 g_propagate_error (error, local_error);
2734 caldav_do_open (ECalBackendSync *backend,
2736 GCancellable *cancellable,
2737 gboolean only_if_exists,
2740 ECalBackendCalDAV *cbdav;
2741 gboolean opened = TRUE;
2744 cbdav = E_CAL_BACKEND_CALDAV (backend);
2746 g_mutex_lock (cbdav->priv->busy_lock);
2748 /* let it decide the 'getctag' extension availability again */
2749 cbdav->priv->ctag_supported = TRUE;
2751 if (!cbdav->priv->loaded && !initialize_backend (cbdav, perror)) {
2752 g_mutex_unlock (cbdav->priv->busy_lock);
2756 online = e_backend_get_online (E_BACKEND (backend));
2758 if (!cbdav->priv->do_offline && !online) {
2759 g_mutex_unlock (cbdav->priv->busy_lock);
2760 g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
2764 cbdav->priv->loaded = TRUE;
2765 cbdav->priv->opened = TRUE;
2766 cbdav->priv->is_google = FALSE;
2769 GError *local_error = NULL;
2771 opened = open_calendar (cbdav, &local_error);
2773 if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationRequired) || g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
2774 g_clear_error (&local_error);
2775 opened = caldav_authenticate (
2776 cbdav, FALSE, cancellable, perror);
2779 if (local_error != NULL)
2780 g_propagate_error (perror, local_error);
2783 cbdav->priv->read_only = TRUE;
2787 e_cal_backend_notify_opened (E_CAL_BACKEND (backend), NULL);
2789 e_cal_backend_notify_readonly (
2790 E_CAL_BACKEND (backend), cbdav->priv->read_only);
2791 e_cal_backend_notify_online (E_CAL_BACKEND (backend), online);
2793 g_mutex_unlock (cbdav->priv->busy_lock);
2797 caldav_refresh (ECalBackendSync *backend,
2799 GCancellable *cancellable,
2802 ECalBackendCalDAV *cbdav;
2805 cbdav = E_CAL_BACKEND_CALDAV (backend);
2807 g_mutex_lock (cbdav->priv->busy_lock);
2809 if (!cbdav->priv->loaded
2810 || cbdav->priv->slave_cmd == SLAVE_SHOULD_DIE
2811 || !check_state (cbdav, &online, NULL)
2813 g_mutex_unlock (cbdav->priv->busy_lock);
2817 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
2820 g_cond_signal (cbdav->priv->cond);
2821 g_mutex_unlock (cbdav->priv->busy_lock);
2825 caldav_remove (ECalBackendSync *backend,
2827 GCancellable *cancellable,
2830 ECalBackendCalDAV *cbdav;
2833 cbdav = E_CAL_BACKEND_CALDAV (backend);
2835 /* first tell it to die, then wait for its lock */
2836 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_DIE);
2838 g_mutex_lock (cbdav->priv->busy_lock);
2840 if (!cbdav->priv->loaded) {
2841 g_mutex_unlock (cbdav->priv->busy_lock);
2845 if (!check_state (cbdav, &online, NULL)) {
2846 /* lie here a bit, but otherwise the calendar will not be removed, even it should */
2847 g_print (G_STRLOC ": Failed to check state");
2850 e_cal_backend_store_remove (cbdav->priv->store);
2851 cbdav->priv->store = NULL;
2852 cbdav->priv->loaded = FALSE;
2853 cbdav->priv->opened = FALSE;
2855 if (cbdav->priv->synch_slave) {
2856 g_cond_signal (cbdav->priv->cond);
2858 /* wait until the slave died */
2859 g_cond_wait (cbdav->priv->slave_gone_cond, cbdav->priv->busy_lock);
2862 g_mutex_unlock (cbdav->priv->busy_lock);
2866 remove_comp_from_cache_cb (gpointer value,
2869 ECalComponent *comp = value;
2870 ECalBackendStore *store = user_data;
2871 ECalComponentId *id;
2873 g_return_if_fail (comp != NULL);
2874 g_return_if_fail (store != NULL);
2876 id = e_cal_component_get_id (comp);
2877 g_return_if_fail (id != NULL);
2879 e_cal_backend_store_remove_component (store, id->uid, id->rid);
2880 e_cal_component_free_id (id);
2884 remove_comp_from_cache (ECalBackendCalDAV *cbdav,
2888 gboolean res = FALSE;
2890 if (!rid || !*rid) {
2891 /* get with detached instances */
2892 GSList *objects = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
2895 g_slist_foreach (objects, (GFunc) remove_comp_from_cache_cb, cbdav->priv->store);
2896 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2897 g_slist_free (objects);
2902 res = e_cal_backend_store_remove_component (cbdav->priv->store, uid, rid);
2909 add_detached_recur_to_vcalendar_cb (gpointer value,
2912 icalcomponent *recurrence = e_cal_component_get_icalcomponent (value);
2913 icalcomponent *vcalendar = user_data;
2915 icalcomponent_add_component (
2917 icalcomponent_new_clone (recurrence));
2921 sort_master_first (gconstpointer a,
2924 icalcomponent *ca, *cb;
2926 ca = e_cal_component_get_icalcomponent ((ECalComponent *) a);
2927 cb = e_cal_component_get_icalcomponent ((ECalComponent *) b);
2938 return icaltime_compare (icalcomponent_get_recurrenceid (ca), icalcomponent_get_recurrenceid (cb));
2941 /* Returns new icalcomponent, with all detached instances stored in a cache.
2942 * The cache lock should be locked when called this function.
2944 static icalcomponent *
2945 get_comp_from_cache (ECalBackendCalDAV *cbdav,
2951 icalcomponent *icalcomp = NULL;
2953 if (rid == NULL || !*rid) {
2954 /* get with detached instances */
2955 GSList *objects = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
2961 if (g_slist_length (objects) == 1) {
2962 ECalComponent *comp = objects->data;
2964 /* will be unreffed a bit later */
2966 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2968 /* if we have detached recurrences, return a VCALENDAR */
2969 icalcomp = e_cal_util_new_top_level ();
2971 objects = g_slist_sort (objects, sort_master_first);
2973 /* add all detached recurrences and the master object */
2974 g_slist_foreach (objects, add_detached_recur_to_vcalendar_cb, icalcomp);
2977 /* every component has set same href and etag, thus it doesn't matter where it will be read */
2979 *href = ecalcomp_get_href (objects->data);
2981 *etag = ecalcomp_get_etag (objects->data);
2983 g_slist_foreach (objects, (GFunc) g_object_unref, NULL);
2984 g_slist_free (objects);
2986 /* get the exact object */
2987 ECalComponent *comp = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
2990 icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
2992 *href = ecalcomp_get_href (comp);
2994 *etag = ecalcomp_get_etag (comp);
2995 g_object_unref (comp);
3003 put_comp_to_cache (ECalBackendCalDAV *cbdav,
3004 icalcomponent *icalcomp,
3008 icalcomponent_kind my_kind;
3009 ECalComponent *comp;
3010 gboolean res = FALSE;
3012 g_return_val_if_fail (cbdav != NULL, FALSE);
3013 g_return_val_if_fail (icalcomp != NULL, FALSE);
3015 my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3016 comp = e_cal_component_new ();
3018 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3019 icalcomponent *subcomp;
3021 /* remove all old components from the cache first */
3022 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3024 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3025 remove_comp_from_cache (cbdav, icalcomponent_get_uid (subcomp), NULL);
3028 /* then put new. It's because some detached instances could be removed on the server. */
3029 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3031 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3032 /* because reusing the same comp doesn't clear recur_id member properly */
3033 g_object_unref (comp);
3034 comp = e_cal_component_new ();
3036 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
3038 ecalcomp_set_href (comp, href);
3040 ecalcomp_set_etag (comp, etag);
3042 if (put_component_to_store (cbdav, comp))
3046 } else if (icalcomponent_isa (icalcomp) == my_kind) {
3047 remove_comp_from_cache (cbdav, icalcomponent_get_uid (icalcomp), NULL);
3049 if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
3051 ecalcomp_set_href (comp, href);
3053 ecalcomp_set_etag (comp, etag);
3055 res = put_component_to_store (cbdav, comp);
3059 g_object_unref (comp);
3065 remove_property (gpointer prop,
3068 icalcomponent_remove_property (icomp, prop);
3069 icalproperty_free (prop);
3073 strip_unneeded_x_props (icalcomponent *icomp)
3076 GSList *to_remove = NULL;
3078 g_return_if_fail (icomp != NULL);
3079 g_return_if_fail (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT);
3081 for (prop = icalcomponent_get_first_property (icomp, ICAL_X_PROPERTY);
3083 prop = icalcomponent_get_next_property (icomp, ICAL_X_PROPERTY)) {
3084 if (g_str_has_prefix (icalproperty_get_x_name (prop), X_E_CALDAV)) {
3085 to_remove = g_slist_prepend (to_remove, prop);
3089 for (prop = icalcomponent_get_first_property (icomp, ICAL_XLICERROR_PROPERTY);
3091 prop = icalcomponent_get_next_property (icomp, ICAL_XLICERROR_PROPERTY)) {
3092 to_remove = g_slist_prepend (to_remove, prop);
3095 g_slist_foreach (to_remove, remove_property, icomp);
3096 g_slist_free (to_remove);
3100 is_stored_on_server (ECalBackendCalDAV *cbdav,
3103 SoupURI *my_uri, *test_uri;
3106 g_return_val_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), FALSE);
3107 g_return_val_if_fail (cbdav->priv->uri != NULL, FALSE);
3108 g_return_val_if_fail (uri != NULL, FALSE);
3110 my_uri = soup_uri_new (cbdav->priv->uri);
3111 g_return_val_if_fail (my_uri != NULL, FALSE);
3113 test_uri = soup_uri_new (uri);
3115 soup_uri_free (my_uri);
3119 res = my_uri->host && test_uri->host && g_ascii_strcasecmp (my_uri->host, test_uri->host) == 0;
3121 soup_uri_free (my_uri);
3122 soup_uri_free (test_uri);
3128 convert_to_inline_attachment (ECalBackendCalDAV *cbdav,
3129 icalcomponent *icalcomp)
3131 icalcomponent *cclone;
3133 GSList *to_remove = NULL;
3135 g_return_if_fail (icalcomp != NULL);
3137 cclone = icalcomponent_new_clone (icalcomp);
3139 /* Remove local url attachments first */
3140 for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3142 p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3145 attach = icalproperty_get_attach ((const icalproperty *) p);
3146 if (icalattach_get_is_url (attach)) {
3149 url = icalattach_get_url (attach);
3150 if (g_str_has_prefix (url, LOCAL_PREFIX))
3151 to_remove = g_slist_prepend (to_remove, p);
3154 g_slist_foreach (to_remove, remove_property, icalcomp);
3155 g_slist_free (to_remove);
3157 /* convert local url attachments to inline attachments now */
3158 for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY);
3160 p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY)) {
3163 GError *error = NULL;
3169 attach = icalproperty_get_attach ((const icalproperty *) p);
3170 if (!icalattach_get_is_url (attach))
3173 uri = icalattach_get_url (attach);
3174 if (!g_str_has_prefix (uri, LOCAL_PREFIX))
3177 file = g_file_new_for_uri (uri);
3178 basename = g_file_get_basename (file);
3179 if (g_file_load_contents (file, NULL, &content, &len, NULL, &error)) {
3181 icalparameter *param;
3185 * do a base64 encoding so it can
3186 * be embedded in a soap message
3188 encoded = g_base64_encode ((guchar *) content, len);
3189 attach = icalattach_new_from_data (encoded, NULL, NULL);
3193 prop = icalproperty_new_attach (attach);
3194 icalattach_unref (attach);
3196 param = icalparameter_new_value (ICAL_VALUE_BINARY);
3197 icalproperty_add_parameter (prop, param);
3199 param = icalparameter_new_encoding (ICAL_ENCODING_BASE64);
3200 icalproperty_add_parameter (prop, param);
3202 param = icalparameter_new_x (basename);
3203 icalparameter_set_xname (param, X_E_CALDAV_ATTACHMENT_NAME);
3204 icalproperty_add_parameter (prop, param);
3206 icalcomponent_add_property (icalcomp, prop);
3208 g_warning ("%s\n", error->message);
3209 g_clear_error (&error);
3212 g_object_unref (file);
3214 icalcomponent_free (cclone);
3218 convert_to_url_attachment (ECalBackendCalDAV *cbdav,
3219 icalcomponent *icalcomp)
3221 ECalBackend *backend;
3222 GSList *to_remove = NULL, *to_remove_after_download = NULL;
3223 icalcomponent *cclone;
3227 g_return_if_fail (cbdav != NULL);
3228 g_return_if_fail (icalcomp != NULL);
3230 backend = E_CAL_BACKEND (cbdav);
3231 cclone = icalcomponent_new_clone (icalcomp);
3233 /* Remove all inline attachments first */
3234 for (p = icalcomponent_get_first_property (icalcomp, ICAL_ATTACH_PROPERTY);
3236 p = icalcomponent_get_next_property (icalcomp, ICAL_ATTACH_PROPERTY)) {
3239 attach = icalproperty_get_attach ((const icalproperty *) p);
3240 if (!icalattach_get_is_url (attach))
3241 to_remove = g_slist_prepend (to_remove, p);
3242 else if (is_stored_on_server (cbdav, icalattach_get_url (attach)))
3243 to_remove_after_download = g_slist_prepend (to_remove_after_download, p);
3245 g_slist_foreach (to_remove, remove_property, icalcomp);
3246 g_slist_free (to_remove);
3248 /* convert inline attachments to url attachments now */
3249 for (p = icalcomponent_get_first_property (cclone, ICAL_ATTACH_PROPERTY), fileindex = 0;
3251 p = icalcomponent_get_next_property (cclone, ICAL_ATTACH_PROPERTY), fileindex++) {
3254 gchar *decoded = NULL;
3255 gchar *basename, *local_filename;
3257 attach = icalproperty_get_attach ((const icalproperty *) p);
3258 if (icalattach_get_is_url (attach)) {
3259 const gchar *attach_url = icalattach_get_url (attach);
3260 GError *error = NULL;
3262 if (!is_stored_on_server (cbdav, attach_url))
3265 if (!caldav_server_download_attachment (cbdav, attach_url, &decoded, &len, &error)) {
3266 if (caldav_debug_show (DEBUG_ATTACHMENTS))
3267 g_print ("CalDAV::%s: Failed to download from a server: %s\n", G_STRFUNC, error ? error->message : "Unknown error");
3272 basename = icalproperty_get_parameter_as_string_r (p, X_E_CALDAV_ATTACHMENT_NAME);
3273 local_filename = e_cal_backend_create_cache_filename (backend, icalcomponent_get_uid (icalcomp), basename, fileindex);
3276 if (local_filename) {
3277 GError *error = NULL;
3279 if (decoded == NULL) {
3282 content = (gchar *) icalattach_get_data (attach);
3283 decoded = (gchar *) g_base64_decode (content, &len);
3286 if (g_file_set_contents (local_filename, decoded, len, &error)) {
3290 url = g_filename_to_uri (local_filename, NULL, NULL);
3291 attach = icalattach_new_from_url (url);
3292 prop = icalproperty_new_attach (attach);
3293 icalattach_unref (attach);
3294 icalcomponent_add_property (icalcomp, prop);
3297 g_warning ("%s\n", error->message);
3298 g_clear_error (&error);
3301 g_free (local_filename);
3305 icalcomponent_free (cclone);
3307 g_slist_foreach (to_remove_after_download, remove_property, icalcomp);
3308 g_slist_free (to_remove_after_download);
3312 remove_files (const gchar *dir,
3313 const gchar *fileprefix)
3317 g_return_if_fail (dir != NULL);
3318 g_return_if_fail (fileprefix != NULL);
3319 g_return_if_fail (*fileprefix != '\0');
3321 d = g_dir_open (dir, 0, NULL);
3324 gint len = strlen (fileprefix);
3326 while ((entry = g_dir_read_name (d)) != NULL) {
3327 if (entry && strncmp (entry, fileprefix, len) == 0) {
3330 path = g_build_filename (dir, entry, NULL);
3331 if (!g_file_test (path, G_FILE_TEST_IS_DIR))
3341 remove_cached_attachment (ECalBackendCalDAV *cbdav,
3349 g_return_if_fail (cbdav != NULL);
3350 g_return_if_fail (uid != NULL);
3352 l = e_cal_backend_store_get_components_by_uid (cbdav->priv->store, uid);
3353 len = g_slist_length (l);
3354 g_slist_foreach (l, (GFunc) g_object_unref, NULL);
3359 dir = e_cal_backend_create_cache_filename (E_CAL_BACKEND (cbdav), uid, "a", 0);
3363 fileprefix = g_strrstr (dir, G_DIR_SEPARATOR_S);
3369 fileprefix[strlen (fileprefix) - 1] = '\0';
3371 remove_files (dir, fileprefix);
3377 /* callback for icalcomponent_foreach_tzid */
3379 ECalBackendStore *store;
3380 icalcomponent *vcal_comp;
3381 icalcomponent *icalcomp;
3385 add_timezone_cb (icalparameter *param,
3390 icalcomponent *vtz_comp;
3391 ForeachTzidData *f_data = (ForeachTzidData *) data;
3393 tzid = icalparameter_get_tzid (param);
3397 tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
3401 tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
3403 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
3405 tz = (icaltimezone *) e_cal_backend_store_get_timezone (f_data->store, tzid);
3410 vtz_comp = icaltimezone_get_component (tz);
3414 icalcomponent_add_component (f_data->vcal_comp,
3415 icalcomponent_new_clone (vtz_comp));
3419 add_timezones_from_component (ECalBackendCalDAV *cbdav,
3420 icalcomponent *vcal_comp,
3421 icalcomponent *icalcomp)
3423 ForeachTzidData f_data;
3425 g_return_if_fail (cbdav != NULL);
3426 g_return_if_fail (vcal_comp != NULL);
3427 g_return_if_fail (icalcomp != NULL);
3429 f_data.store = cbdav->priv->store;
3430 f_data.vcal_comp = vcal_comp;
3431 f_data.icalcomp = icalcomp;
3433 icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
3436 /* also removes X-EVOLUTION-CALDAV from all the components */
3438 pack_cobj (ECalBackendCalDAV *cbdav,
3439 icalcomponent *icomp)
3441 icalcomponent *calcomp;
3444 if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
3445 icalcomponent *cclone;
3447 calcomp = e_cal_util_new_top_level ();
3449 cclone = icalcomponent_new_clone (icomp);
3450 strip_unneeded_x_props (cclone);
3451 convert_to_inline_attachment (cbdav, cclone);
3452 icalcomponent_add_component (calcomp, cclone);
3453 add_timezones_from_component (cbdav, calcomp, cclone);
3455 icalcomponent *subcomp;
3456 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3458 calcomp = icalcomponent_new_clone (icomp);
3459 for (subcomp = icalcomponent_get_first_component (calcomp, my_kind);
3461 subcomp = icalcomponent_get_next_component (calcomp, my_kind)) {
3462 strip_unneeded_x_props (subcomp);
3463 convert_to_inline_attachment (cbdav, subcomp);
3464 add_timezones_from_component (cbdav, calcomp, subcomp);
3468 objstr = icalcomponent_as_ical_string_r (calcomp);
3469 icalcomponent_free (calcomp);
3478 sanitize_component (ECalBackend *cb,
3479 ECalComponent *comp)
3481 ECalComponentDateTime dt;
3484 /* Check dtstart, dtend and due's timezone, and convert it to local
3485 * default timezone if the timezone is not in our builtin timezone
3487 e_cal_component_get_dtstart (comp, &dt);
3488 if (dt.value && dt.tzid) {
3489 zone = caldav_internal_get_timezone (cb, dt.tzid);
3491 g_free ((gchar *) dt.tzid);
3492 dt.tzid = g_strdup ("UTC");
3493 e_cal_component_set_dtstart (comp, &dt);
3496 e_cal_component_free_datetime (&dt);
3498 e_cal_component_get_dtend (comp, &dt);
3499 if (dt.value && dt.tzid) {
3500 zone = caldav_internal_get_timezone (cb, dt.tzid);
3502 g_free ((gchar *) dt.tzid);
3503 dt.tzid = g_strdup ("UTC");
3504 e_cal_component_set_dtend (comp, &dt);
3507 e_cal_component_free_datetime (&dt);
3509 e_cal_component_get_due (comp, &dt);
3510 if (dt.value && dt.tzid) {
3511 zone = caldav_internal_get_timezone (cb, dt.tzid);
3513 g_free ((gchar *) dt.tzid);
3514 dt.tzid = g_strdup ("UTC");
3515 e_cal_component_set_due (comp, &dt);
3518 e_cal_component_free_datetime (&dt);
3519 e_cal_component_abort_sequence (comp);
3523 cache_contains (ECalBackendCalDAV *cbdav,
3528 ECalComponent *comp;
3530 g_return_val_if_fail (cbdav != NULL, FALSE);
3531 g_return_val_if_fail (uid != NULL, FALSE);
3533 g_return_val_if_fail (cbdav->priv->store != NULL, FALSE);
3535 comp = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
3539 g_object_unref (comp);
3544 /* Returns subcomponent of icalcomp, which is a master object, or icalcomp itself, if it's not a VCALENDAR;
3545 * Do not free returned pointer, it'll be freed together with the icalcomp.
3547 static icalcomponent *
3548 get_master_comp (ECalBackendCalDAV *cbdav,
3549 icalcomponent *icalcomp)
3551 icalcomponent *master = icalcomp;
3553 g_return_val_if_fail (cbdav != NULL, NULL);
3554 g_return_val_if_fail (icalcomp != NULL, NULL);
3556 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3557 icalcomponent *subcomp;
3558 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3562 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3564 subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
3565 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3567 if (icaltime_is_null_time (sub_rid)) {
3578 remove_instance (ECalBackendCalDAV *cbdav,
3579 icalcomponent *icalcomp,
3580 struct icaltimetype rid,
3582 gboolean also_exdate)
3584 icalcomponent *master = icalcomp;
3585 gboolean res = FALSE;
3587 g_return_val_if_fail (icalcomp != NULL, res);
3588 g_return_val_if_fail (!icaltime_is_null_time (rid), res);
3590 /* remove an instance only */
3591 if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
3592 icalcomponent *subcomp;
3593 icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
3595 gboolean start_first = FALSE;
3599 /* remove old instance first */
3600 for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
3602 subcomp = start_first ? icalcomponent_get_first_component (icalcomp, my_kind) : icalcomponent_get_next_component (icalcomp, my_kind)) {
3603 struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
3605 start_first = FALSE;
3607 if (icaltime_is_null_time (sub_rid)) {
3610 } else if (icaltime_compare (sub_rid, rid) == 0) {
3611 icalcomponent_remove_component (icalcomp, subcomp);
3612 icalcomponent_free (subcomp);
3616 /* either no master or master not as the first component, thus rescan */
3625 /* whether left at least one instance or a master object */
3631 if (master && also_exdate) {
3632 e_cal_util_remove_instances (master, rid, mod);
3638 static icalcomponent *
3639 replace_master (ECalBackendCalDAV *cbdav,
3640 icalcomponent *old_comp,
3641 icalcomponent *new_master)
3643 icalcomponent *old_master;
3644 if (icalcomponent_isa (old_comp) != ICAL_VCALENDAR_COMPONENT) {
3645 icalcomponent_free (old_comp);
3649 old_master = get_master_comp (cbdav, old_comp);
3651 /* no master, strange */
3652 icalcomponent_free (new_master);
3654 icalcomponent_remove_component (old_comp, old_master);
3655 icalcomponent_free (old_master);
3657 icalcomponent_add_component (old_comp, new_master);
3663 /* the resulting component should be unreffed when done with it;
3664 * the fallback_comp is cloned, if used */
3665 static ECalComponent *
3666 get_ecalcomp_master_from_cache_or_fallback (ECalBackendCalDAV *cbdav,
3669 ECalComponent *fallback_comp)
3671 ECalComponent *comp = NULL;
3672 icalcomponent *icalcomp;
3674 g_return_val_if_fail (cbdav != NULL, NULL);
3675 g_return_val_if_fail (uid != NULL, NULL);
3677 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
3679 icalcomponent *master = get_master_comp (cbdav, icalcomp);
3682 comp = e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master));
3685 icalcomponent_free (icalcomp);
3688 if (!comp && fallback_comp)
3689 comp = e_cal_component_clone (fallback_comp);
3694 /* a busy_lock is supposed to be locked already, when calling this function */
3696 do_create_objects (ECalBackendCalDAV *cbdav,
3697 const GSList *in_calobjs,
3699 GSList **new_components,
3702 ECalComponent *comp;
3703 gboolean online, did_put = FALSE;
3704 struct icaltimetype current;
3705 icalcomponent *icalcomp;
3706 const gchar *in_calobj = in_calobjs->data;
3707 const gchar *comp_uid;
3709 if (!check_state (cbdav, &online, perror))
3712 /* We make the assumption that the in_calobjs list we're passed is always exactly one element long, since we haven't specified "bulk-adds"
3713 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3714 if (in_calobjs->next != NULL) {
3715 g_propagate_error (perror, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk additions")));
3719 comp = e_cal_component_new_from_string (in_calobj);
3722 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3726 icalcomp = e_cal_component_get_icalcomponent (comp);
3727 if (icalcomp == NULL) {
3728 g_object_unref (comp);
3729 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3733 comp_uid = icalcomponent_get_uid (icalcomp);
3737 new_uid = e_cal_component_gen_uid ();
3739 g_object_unref (comp);
3740 g_propagate_error (perror, EDC_ERROR (InvalidObject));
3744 icalcomponent_set_uid (icalcomp, new_uid);
3745 comp_uid = icalcomponent_get_uid (icalcomp);
3750 /* check the object is not in our cache */
3751 if (cache_contains (cbdav, comp_uid, NULL)) {
3752 g_object_unref (comp);
3753 g_propagate_error (perror, EDC_ERROR (ObjectIdAlreadyExists));
3757 /* Set the created and last modified times on the component */
3758 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3759 e_cal_component_set_created (comp, ¤t);
3760 e_cal_component_set_last_modified (comp, ¤t);
3762 /* sanitize the component*/
3763 sanitize_component ((ECalBackend *) cbdav, comp);
3766 CalDAVObject object;
3768 object.href = ecalcomp_gen_href (comp);
3770 object.cdata = pack_cobj (cbdav, icalcomp);
3772 did_put = caldav_server_put_object (cbdav, &object, icalcomp, perror);
3774 caldav_object_free (&object, FALSE);
3776 /* mark component as out of synch */
3777 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
3782 *uids = g_slist_prepend (*uids, g_strdup (comp_uid));
3785 *new_components = g_slist_prepend(*new_components, get_ecalcomp_master_from_cache_or_fallback (cbdav, comp_uid, NULL, comp));
3788 g_object_unref (comp);
3791 /* a busy_lock is supposed to be locked already, when calling this function */
3793 do_modify_objects (ECalBackendCalDAV *cbdav,
3794 const GSList *calobjs,
3796 GSList **old_components,
3797 GSList **new_components,
3800 ECalComponent *comp;
3801 icalcomponent *cache_comp;
3802 gboolean online, did_put = FALSE;
3803 ECalComponentId *id;
3804 struct icaltimetype current;
3805 gchar *href = NULL, *etag = NULL;
3806 const gchar *calobj = calobjs->data;
3809 *new_components = NULL;
3811 if (!check_state (cbdav, &online, error))
3814 /* We make the assumption that the calobjs list we're passed is always exactly one element long, since we haven't specified "bulk-modifies"
3815 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3816 if (calobjs->next != NULL) {
3817 g_propagate_error (error, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk modifications")));
3821 comp = e_cal_component_new_from_string (calobj);
3824 g_propagate_error (error, EDC_ERROR (InvalidObject));
3828 if (!e_cal_component_get_icalcomponent (comp) ||
3829 icalcomponent_isa (e_cal_component_get_icalcomponent (comp)) != e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
3830 g_object_unref (comp);
3831 g_propagate_error (error, EDC_ERROR (InvalidObject));
3835 /* Set the last modified time on the component */
3836 current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
3837 e_cal_component_set_last_modified (comp, ¤t);
3839 /* sanitize the component */
3840 sanitize_component ((ECalBackend *) cbdav, comp);
3842 id = e_cal_component_get_id (comp);
3843 e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
3845 /* fetch full component from cache, it will be pushed to the server */
3846 cache_comp = get_comp_from_cache (cbdav, id->uid, NULL, &href, &etag);
3848 if (cache_comp == NULL) {
3849 e_cal_component_free_id (id);
3850 g_object_unref (comp);
3853 g_propagate_error (error, EDC_ERROR (ObjectNotFound));
3858 /* mark component as out of synch */
3859 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3862 if (old_components) {
3863 *old_components = NULL;
3865 if (e_cal_component_is_instance (comp)) {
3866 /* set detached instance as the old object, if any */
3867 ECalComponent *old_instance = e_cal_backend_store_get_component (cbdav->priv->store, id->uid, id->rid);
3869 /* This will give a reference to 'old_component' */
3871 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (old_instance));
3872 g_object_unref (old_instance);
3876 if (!*old_components) {
3877 icalcomponent *master = get_master_comp (cbdav, cache_comp);
3880 /* set full component as the old object */
3881 *old_components = g_slist_prepend (*old_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
3887 case CALOBJ_MOD_ONLY_THIS:
3888 case CALOBJ_MOD_THIS:
3889 if (e_cal_component_is_instance (comp)) {
3890 icalcomponent *new_comp = e_cal_component_get_icalcomponent (comp);
3892 /* new object is only this instance */
3894 *new_components = g_slist_prepend (*new_components, e_cal_component_clone (comp));
3896 /* add the detached instance */
3897 if (icalcomponent_isa (cache_comp) == ICAL_VCALENDAR_COMPONENT) {
3898 /* do not modify the EXDATE, as the component will be put back */
3899 remove_instance (cbdav, cache_comp, icalcomponent_get_recurrenceid (new_comp), mod, FALSE);
3901 /* this is only a master object, thus make is a VCALENDAR component */
3902 icalcomponent *icomp;
3904 icomp = e_cal_util_new_top_level ();
3905 icalcomponent_add_component (icomp, cache_comp);
3907 /* no need to free the cache_comp, as it is inside icomp */
3911 if (cache_comp && cbdav->priv->is_google) {
3912 icalcomponent_set_sequence (cache_comp, icalcomponent_get_sequence (cache_comp) + 1);
3913 icalcomponent_set_sequence (new_comp, icalcomponent_get_sequence (new_comp) + 1);
3916 /* add the detached instance finally */
3917 icalcomponent_add_component (cache_comp, icalcomponent_new_clone (new_comp));
3919 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3922 case CALOBJ_MOD_ALL:
3923 cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
3925 case CALOBJ_MOD_THISANDPRIOR:
3926 case CALOBJ_MOD_THISANDFUTURE:
3931 CalDAVObject object;
3935 object.cdata = pack_cobj (cbdav, cache_comp);
3937 did_put = caldav_server_put_object (cbdav, &object, cache_comp, error);
3939 caldav_object_free (&object, FALSE);
3943 /* mark component as out of synch */
3944 /*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
3948 if (new_components && !*new_components) {
3949 /* read the comp from cache again, as some servers can modify it on put */
3950 *new_components = g_slist_prepend (*new_components, get_ecalcomp_master_from_cache_or_fallback (cbdav, id->uid, id->rid, NULL));
3954 e_cal_component_free_id (id);
3955 icalcomponent_free (cache_comp);
3956 g_object_unref (comp);
3961 /* a busy_lock is supposed to be locked already, when calling this function */
3963 do_remove_objects (ECalBackendCalDAV *cbdav,
3966 GSList **old_components,
3967 GSList **new_components,
3970 icalcomponent *cache_comp;
3972 gchar *href = NULL, *etag = NULL;
3973 const gchar *uid = ((ECalComponentId *) ids->data)->uid;
3974 const gchar *rid = ((ECalComponentId *) ids->data)->rid;
3977 *new_components = NULL;
3979 if (!check_state (cbdav, &online, perror))
3982 /* We make the assumption that the ids list we're passed is always exactly one element long, since we haven't specified "bulk-removes"
3983 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
3984 if (ids->next != NULL) {
3985 g_propagate_error (perror, e_data_cal_create_error (UnsupportedMethod, _("CalDAV does not support bulk removals")));
3989 cache_comp = get_comp_from_cache (cbdav, uid, NULL, &href, &etag);
3991 if (cache_comp == NULL) {
3992 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
3996 if (old_components) {
3997 ECalComponent *old = e_cal_backend_store_get_component (cbdav->priv->store, uid, rid);
4000 *old_components = g_slist_prepend (*old_components, e_cal_component_clone (old));
4001 g_object_unref (old);
4003 icalcomponent *master = get_master_comp (cbdav, cache_comp);
4005 *old_components = g_slist_prepend (*old_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
4011 case CALOBJ_MOD_ONLY_THIS:
4012 case CALOBJ_MOD_THIS:
4014 /* remove one instance from the component */
4015 if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, mod != CALOBJ_MOD_ONLY_THIS)) {
4016 if (new_components) {
4017 icalcomponent *master = get_master_comp (cbdav, cache_comp);
4019 *new_components = g_slist_prepend (*new_components, e_cal_component_new_from_icalcomponent (icalcomponent_new_clone (master)));
4023 /* this was the last instance, thus delete whole component */
4025 remove_comp_from_cache (cbdav, uid, NULL);
4028 /* remove whole object */
4029 remove_comp_from_cache (cbdav, uid, NULL);
4032 case CALOBJ_MOD_ALL:
4033 remove_comp_from_cache (cbdav, uid, NULL);
4035 case CALOBJ_MOD_THISANDPRIOR:
4036 case CALOBJ_MOD_THISANDFUTURE:
4041 CalDAVObject caldav_object;
4043 caldav_object.href = href;
4044 caldav_object.etag = etag;
4045 caldav_object.cdata = NULL;
4047 if (mod == CALOBJ_MOD_THIS && rid && *rid) {
4048 caldav_object.cdata = pack_cobj (cbdav, cache_comp);
4050 caldav_server_put_object (cbdav, &caldav_object, cache_comp, perror);
4052 caldav_server_delete_object (cbdav, &caldav_object, perror);
4054 caldav_object_free (&caldav_object, FALSE);
4058 /* mark component as out of synch */
4059 /*if (mod == CALOBJ_MOD_THIS && rid && *rid)
4060 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_MODIFIED);
4062 ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_DELETED);*/
4064 remove_cached_attachment (cbdav, uid);
4066 icalcomponent_free (cache_comp);
4072 extract_objects (icalcomponent *icomp,
4073 icalcomponent_kind ekind,
4077 icalcomponent *scomp;
4078 icalcomponent_kind kind;
4080 e_return_data_cal_error_if_fail (icomp, InvalidArg);
4081 e_return_data_cal_error_if_fail (objects, InvalidArg);
4083 kind = icalcomponent_isa (icomp);
4085 if (kind == ekind) {
4086 *objects = g_slist_prepend (NULL, icomp);
4090 if (kind != ICAL_VCALENDAR_COMPONENT) {
4091 g_propagate_error (error, EDC_ERROR (InvalidObject));
4096 scomp = icalcomponent_get_first_component (icomp,
4100 /* Remove components from toplevel here */
4101 *objects = g_slist_prepend (*objects, scomp);
4102 icalcomponent_remove_component (icomp, scomp);
4104 scomp = icalcomponent_get_next_component (icomp, ekind);
4109 extract_timezones (ECalBackendCalDAV *cbdav,
4110 icalcomponent *icomp)
4112 GSList *timezones = NULL, *iter;
4116 g_return_val_if_fail (cbdav != NULL, FALSE);
4117 g_return_val_if_fail (icomp != NULL, FALSE);
4119 extract_objects (icomp, ICAL_VTIMEZONE_COMPONENT, &timezones, &err);
4125 zone = icaltimezone_new ();
4126 for (iter = timezones; iter; iter = iter->next) {
4127 if (icaltimezone_set_component (zone, iter->data)) {
4128 e_cal_backend_store_put_timezone (cbdav->priv->store, zone);
4130 icalcomponent_free (iter->data);
4134 icaltimezone_free (zone, TRUE);
4135 g_slist_free (timezones);
4141 process_object (ECalBackendCalDAV *cbdav,
4142 ECalComponent *ecomp,
4144 icalproperty_method method,
4147 ESourceRegistry *registry;
4148 ECalBackend *backend;
4149 struct icaltimetype now;
4151 gboolean is_declined, is_in_cache;
4153 ECalComponentId *id = e_cal_component_get_id (ecomp);
4156 backend = E_CAL_BACKEND (cbdav);
4158 e_return_data_cal_error_if_fail (id != NULL, InvalidObject);
4160 registry = e_cal_backend_get_registry (E_CAL_BACKEND (cbdav));
4163 now = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
4164 e_cal_component_set_created (ecomp, &now);
4165 e_cal_component_set_last_modified (ecomp, &now);
4167 /* just to check whether component exists in a cache */
4168 is_in_cache = cache_contains (cbdav, id->uid, NULL) || cache_contains (cbdav, id->uid, id->rid);
4170 new_obj_str = e_cal_component_get_as_string (ecomp);
4171 mod = e_cal_component_is_instance (ecomp) ? CALOBJ_MOD_THIS : CALOBJ_MOD_ALL;
4174 case ICAL_METHOD_PUBLISH:
4175 case ICAL_METHOD_REQUEST:
4176 case ICAL_METHOD_REPLY:
4177 is_declined = e_cal_backend_user_declined (
4178 registry, e_cal_component_get_icalcomponent (ecomp));
4181 GSList *new_components = NULL, *old_components = NULL;
4182 GSList new_obj_strs = {0,};
4184 new_obj_strs.data = new_obj_str;
4185 do_modify_objects (cbdav, &new_obj_strs, mod,
4186 &old_components, &new_components, &err);
4187 if (!err && new_components && new_components->data) {
4188 if (!old_components || !old_components->data) {
4189 e_cal_backend_notify_component_created (backend, new_components->data);
4191 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4195 e_util_free_nullable_object_slist (old_components);
4196 e_util_free_nullable_object_slist (new_components);
4198 GSList *new_components = NULL, *old_components = NULL;
4202 do_remove_objects (cbdav, &ids, mod, &old_components, &new_components, &err);
4203 if (!err && old_components && old_components->data) {
4204 if (new_components && new_components->data) {
4205 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4207 e_cal_backend_notify_component_removed (backend, id, old_components->data, NULL);
4211 e_util_free_nullable_object_slist (old_components);
4212 e_util_free_nullable_object_slist (new_components);
4214 } else if (!is_declined) {
4215 GSList *new_components = NULL;
4216 GSList new_objs = {0,};
4218 new_objs.data = new_obj_str;
4220 do_create_objects (cbdav, &new_objs, NULL, &new_components, &err);
4223 if (new_components && new_components->data)
4224 e_cal_backend_notify_component_created (backend, new_components->data);
4227 e_util_free_nullable_object_slist (new_components);
4230 case ICAL_METHOD_CANCEL:
4232 GSList *new_components = NULL, *old_components = NULL;
4236 do_remove_objects (cbdav, &ids, CALOBJ_MOD_THIS, &old_components, &new_components, &err);
4237 if (!err && old_components && old_components->data) {
4238 if (new_components && new_components->data) {
4239 e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
4241 e_cal_backend_notify_component_removed (backend, id, old_components->data, NULL);
4245 e_util_free_nullable_object_slist (old_components);
4246 e_util_free_nullable_object_slist (new_components);
4248 err = EDC_ERROR (ObjectNotFound);
4253 err = EDC_ERROR (UnsupportedMethod);
4257 e_cal_component_free_id (id);
4258 g_free (new_obj_str);
4261 g_propagate_error (error, err);
4265 do_receive_objects (ECalBackendSync *backend,
4267 GCancellable *cancellable,
4268 const gchar *calobj,
4271 ECalBackendCalDAV *cbdav;
4272 icalcomponent *icomp;
4273 icalcomponent_kind kind;
4274 icalproperty_method tmethod;
4276 GSList *objects, *iter;
4279 cbdav = E_CAL_BACKEND_CALDAV (backend);
4281 if (!check_state (cbdav, &online, perror))
4284 icomp = icalparser_parse_string (calobj);
4286 /* Try to parse cal object string */
4287 if (icomp == NULL) {
4288 g_propagate_error (perror, EDC_ERROR (InvalidObject));
4292 kind = e_cal_backend_get_kind (E_CAL_BACKEND (backend));
4293 extract_objects (icomp, kind, &objects, &err);
4296 icalcomponent_free (icomp);
4297 g_propagate_error (perror, err);
4301 /* Extract optional timezone compnents */
4302 extract_timezones (cbdav, icomp);
4304 tmethod = icalcomponent_get_method (icomp);
4306 for (iter = objects; iter && !err; iter = iter->next) {
4307 icalcomponent *scomp;
4308 ECalComponent *ecomp;
4309 icalproperty_method method;
4311 scomp = (icalcomponent *) iter->data;
4312 ecomp = e_cal_component_new ();
4314 e_cal_component_set_icalcomponent (ecomp, scomp);
4316 if (icalcomponent_get_first_property (scomp, ICAL_METHOD_PROPERTY)) {
4317 method = icalcomponent_get_method (scomp);
4322 process_object (cbdav, ecomp, online, method, &err);
4323 g_object_unref (ecomp);
4326 g_slist_free (objects);
4328 icalcomponent_free (icomp);
4331 g_propagate_error (perror, err);
4334 #define caldav_busy_stub(_func_name, _params, _call_func, _call_params) \
4336 _func_name _params \
4338 ECalBackendCalDAV *cbdav; \
4339 SlaveCommand old_slave_cmd; \
4340 gboolean was_slave_busy; \
4342 cbdav = E_CAL_BACKEND_CALDAV (backend); \
4344 /* this is done before locking */ \
4345 old_slave_cmd = cbdav->priv->slave_cmd; \
4346 was_slave_busy = cbdav->priv->slave_busy; \
4347 if (was_slave_busy) { \
4348 /* let it pause its work and do our job */ \
4349 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP); \
4352 g_mutex_lock (cbdav->priv->busy_lock); \
4353 _call_func _call_params; \
4355 /* this is done before unlocking */ \
4356 if (was_slave_busy) { \
4357 update_slave_cmd (cbdav->priv, old_slave_cmd); \
4358 g_cond_signal (cbdav->priv->cond); \
4361 g_mutex_unlock (cbdav->priv->busy_lock); \
4365 caldav_create_objects,
4366 (ECalBackendSync *backend,
4368 GCancellable *cancellable,
4369 const GSList *in_calobjs,
4371 GSList **new_components,
4381 caldav_modify_objects,
4382 (ECalBackendSync *backend,
4384 GCancellable *cancellable,
4385 const GSList *calobjs,
4387 GSList **old_components,
4388 GSList **new_components,
4399 caldav_remove_objects,
4400 (ECalBackendSync *backend,
4402 GCancellable *cancellable,
4405 GSList **old_components,
4406 GSList **new_components,
4417 caldav_receive_objects,
4418 (ECalBackendSync *backend,
4420 GCancellable *cancellable,
4421 const gchar *calobj,
4431 caldav_send_objects (ECalBackendSync *backend,
4433 GCancellable *cancellable,
4434 const gchar *calobj,
4436 gchar **modified_calobj,
4440 *modified_calobj = g_strdup (calobj);
4444 caldav_get_object (ECalBackendSync *backend,
4446 GCancellable *cancellable,
4452 ECalBackendCalDAV *cbdav;
4453 icalcomponent *icalcomp;
4455 cbdav = E_CAL_BACKEND_CALDAV (backend);
4458 icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
4461 g_propagate_error (perror, EDC_ERROR (ObjectNotFound));
4465 *object = icalcomponent_as_ical_string_r (icalcomp);
4466 icalcomponent_free (icalcomp);
4470 caldav_add_timezone (ECalBackendSync *backend,
4472 GCancellable *cancellable,
4476 icalcomponent *tz_comp;
4477 ECalBackendCalDAV *cbdav;
4479 cbdav = E_CAL_BACKEND_CALDAV (backend);
4481 e_return_data_cal_error_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav), InvalidArg);
4482 e_return_data_cal_error_if_fail (tzobj != NULL, InvalidArg);
4484 tz_comp = icalparser_parse_string (tzobj);
4486 g_propagate_error (error, EDC_ERROR (InvalidObject));
4490 if (icalcomponent_isa (tz_comp) == ICAL_VTIMEZONE_COMPONENT) {
4493 zone = icaltimezone_new ();
4494 icaltimezone_set_component (zone, tz_comp);
4496 e_cal_backend_store_put_timezone (cbdav->priv->store, zone);
4498 icaltimezone_free (zone, TRUE);
4500 icalcomponent_free (tz_comp);
4505 caldav_get_object_list (ECalBackendSync *backend,
4507 GCancellable *cancellable,
4508 const gchar *sexp_string,
4512 ECalBackendCalDAV *cbdav;
4513 ECalBackendSExp *sexp;
4516 GSList *list, *iter;
4517 time_t occur_start = -1, occur_end = -1;
4518 gboolean prunning_by_time;
4520 cbdav = E_CAL_BACKEND_CALDAV (backend);
4522 sexp = e_cal_backend_sexp_new (sexp_string);
4525 g_propagate_error (perror, EDC_ERROR (InvalidQuery));
4529 if (g_str_equal (sexp_string, "#t")) {
4537 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp, &occur_start, &occur_end);
4539 list = prunning_by_time ?
4540 e_cal_backend_store_get_components_occuring_in_range (cbdav->priv->store, occur_start, occur_end)
4541 : e_cal_backend_store_get_components (cbdav->priv->store);
4543 bkend = E_CAL_BACKEND (backend);
4545 for (iter = list; iter; iter = g_slist_next (iter)) {
4546 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4549 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4550 *objects = g_slist_prepend (*objects, e_cal_component_get_as_string (comp));
4553 g_object_unref (comp);
4556 g_object_unref (sexp);
4557 g_slist_free (list);
4561 caldav_start_view (ECalBackend *backend,
4562 EDataCalView *query)
4564 ECalBackendCalDAV *cbdav;
4565 ECalBackendSExp *sexp;
4568 GSList *list, *iter;
4569 const gchar *sexp_string;
4570 time_t occur_start = -1, occur_end = -1;
4571 gboolean prunning_by_time;
4572 cbdav = E_CAL_BACKEND_CALDAV (backend);
4574 sexp_string = e_data_cal_view_get_text (query);
4575 sexp = e_cal_backend_sexp_new (sexp_string);
4577 /* FIXME:check invalid sexp */
4579 if (g_str_equal (sexp_string, "#t")) {
4584 prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp,
4588 bkend = E_CAL_BACKEND (backend);
4590 list = prunning_by_time ?
4591 e_cal_backend_store_get_components_occuring_in_range (cbdav->priv->store, occur_start, occur_end)
4592 : e_cal_backend_store_get_components (cbdav->priv->store);
4594 for (iter = list; iter; iter = g_slist_next (iter)) {
4595 ECalComponent *comp = E_CAL_COMPONENT (iter->data);
4598 e_cal_backend_sexp_match_comp (sexp, comp, bkend)) {
4599 e_data_cal_view_notify_components_added_1 (query, comp);
4602 g_object_unref (comp);
4605 g_object_unref (sexp);
4606 g_slist_free (list);
4608 e_data_cal_view_notify_complete (query, NULL /* Success */);
4612 caldav_get_free_busy (ECalBackendSync *backend,
4614 GCancellable *cancellable,
4615 const GSList *users,
4621 ECalBackendCalDAV *cbdav;
4622 icalcomponent *icalcomp;
4623 ECalComponent *comp;
4624 ECalComponentDateTime dt;
4625 ECalComponentOrganizer organizer = {NULL};
4626 ESourceAuthentication *auth_extension;
4628 struct icaltimetype dtvalue;
4632 GSList *attendees = NULL, *to_free = NULL;
4633 const gchar *extension_name;
4637 cbdav = E_CAL_BACKEND_CALDAV (backend);
4639 e_return_data_cal_error_if_fail (users != NULL, InvalidArg);
4640 e_return_data_cal_error_if_fail (freebusy != NULL, InvalidArg);
4641 e_return_data_cal_error_if_fail (start < end, InvalidArg);
4643 if (!cbdav->priv->calendar_schedule) {
4644 g_propagate_error (error, EDC_ERROR_EX (OtherError, _("Calendar doesn't support Free/Busy")));
4648 if (!cbdav->priv->schedule_outbox_url) {
4649 caldav_receive_schedule_outbox_url (cbdav);
4650 if (!cbdav->priv->schedule_outbox_url) {
4651 cbdav->priv->calendar_schedule = FALSE;
4652 g_propagate_error (error, EDC_ERROR_EX (OtherError, "Schedule outbox url not found"));
4657 comp = e_cal_component_new ();
4658 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_FREEBUSY);
4660 str = e_cal_component_gen_uid ();
4661 e_cal_component_set_uid (comp, str);
4664 utc = icaltimezone_get_utc_timezone ();
4665 dt.value = &dtvalue;
4666 dt.tzid = icaltimezone_get_tzid (utc);
4668 dtvalue = icaltime_current_time_with_zone (utc);
4669 e_cal_component_set_dtstamp (comp, &dtvalue);
4671 dtvalue = icaltime_from_timet_with_zone (start, FALSE, utc);
4672 e_cal_component_set_dtstart (comp, &dt);
4674 dtvalue = icaltime_from_timet_with_zone (end, FALSE, utc);
4675 e_cal_component_set_dtend (comp, &dt);
4677 usermail = get_usermail (E_CAL_BACKEND (backend));
4678 if (usermail != NULL && *usermail == '\0') {
4683 source = e_backend_get_source (E_BACKEND (backend));
4684 extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
4685 auth_extension = e_source_get_extension (source, extension_name);
4687 if (usermail == NULL)
4688 usermail = e_source_authentication_dup_user (auth_extension);
4690 organizer.value = g_strconcat ("mailto:", usermail, NULL);
4691 e_cal_component_set_organizer (comp, &organizer);
4692 g_free ((gchar *) organizer.value);
4696 for (u = users; u; u = u->next) {
4697 ECalComponentAttendee *ca;
4698 gchar *temp = g_strconcat ("mailto:", (const gchar *)u->data, NULL);
4700 ca = g_new0 (ECalComponentAttendee, 1);
4703 ca->cutype = ICAL_CUTYPE_INDIVIDUAL;
4704 ca->status = ICAL_PARTSTAT_NEEDSACTION;
4705 ca->role = ICAL_ROLE_CHAIR;
4707 to_free = g_slist_prepend (to_free, temp);
4708 attendees = g_slist_append (attendees, ca);
4711 e_cal_component_set_attendee_list (comp, attendees);
4713 g_slist_foreach (attendees, (GFunc) g_free, NULL);
4714 g_slist_free (attendees);
4716 g_slist_foreach (to_free, (GFunc) g_free, NULL);
4717 g_slist_free (to_free);
4719 e_cal_component_abort_sequence (comp);
4721 /* put the free/busy request to a VCALENDAR */
4722 icalcomp = e_cal_util_new_top_level ();
4723 icalcomponent_set_method (icalcomp, ICAL_METHOD_REQUEST);
4724 icalcomponent_add_component (icalcomp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
4726 str = icalcomponent_as_ical_string_r (icalcomp);
4728 icalcomponent_free (icalcomp);
4729 g_object_unref (comp);
4731 e_return_data_cal_error_if_fail (str != NULL, OtherError);
4733 caldav_post_freebusy (cbdav, cbdav->priv->schedule_outbox_url, &str, &err);
4736 /* parse returned xml */
4739 doc = xmlReadMemory (str, strlen (str), "response.xml", NULL, 0);
4741 xmlXPathContextPtr xpctx;
4742 xmlXPathObjectPtr result;
4744 xpctx = xmlXPathNewContext (doc);
4745 xmlXPathRegisterNs (xpctx, (xmlChar *) "D", (xmlChar *) "DAV:");
4746 xmlXPathRegisterNs (xpctx, (xmlChar *) "C", (xmlChar *) "urn:ietf:params:xml:ns:caldav");
4748 result = xpath_eval (xpctx, "/C:schedule-response/C:response");
4750 if (result == NULL || result->type != XPATH_NODESET) {
4751 err = EDC_ERROR_EX (OtherError, "Unexpected result in schedule-response");
4755 n = xmlXPathNodeSetGetLength (result->nodesetval);
4756 for (i = 0; i < n; i++) {
4759 tmp = xp_object_get_string (xpath_eval (xpctx, "string(/C:schedule-response/C:response[%d]/C:calendar-data)", i + 1));
4761 GSList *objects = NULL, *o;
4763 icalcomp = icalparser_parse_string (tmp);
4765 extract_objects (icalcomp, ICAL_VFREEBUSY_COMPONENT, &objects, &err);
4766 if (icalcomp && !err) {
4767 for (o = objects; o; o = o->next) {
4768 gchar *obj_str = icalcomponent_as_ical_string_r (o->data);
4770 if (obj_str && *obj_str)
4771 *freebusy = g_slist_append (*freebusy, obj_str);
4777 g_slist_foreach (objects, (GFunc) icalcomponent_free, NULL);
4778 g_slist_free (objects);
4781 icalcomponent_free (icalcomp);
4792 xmlXPathFreeObject (result);
4793 xmlXPathFreeContext (xpctx);
4801 g_propagate_error (error, err);
4805 caldav_notify_online_cb (ECalBackend *backend,
4808 ECalBackendCalDAV *cbdav;
4811 cbdav = E_CAL_BACKEND_CALDAV (backend);
4813 /*g_mutex_lock (cbdav->priv->busy_lock);*/
4815 online = e_backend_get_online (E_BACKEND (backend));
4817 if (!cbdav->priv->loaded) {
4818 e_cal_backend_notify_online (backend, online);
4819 /*g_mutex_unlock (cbdav->priv->busy_lock);*/
4824 /* Wake up the slave thread */
4825 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
4826 g_cond_signal (cbdav->priv->cond);
4828 soup_session_abort (cbdav->priv->session);
4829 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
4832 e_cal_backend_notify_online (backend, online);
4834 /*g_mutex_unlock (cbdav->priv->busy_lock);*/
4837 static icaltimezone *
4838 caldav_internal_get_timezone (ECalBackend *backend,
4842 ECalBackendCalDAV *cbdav;
4844 cbdav = E_CAL_BACKEND_CALDAV (backend);
4847 if (cbdav->priv->store)
4848 zone = (icaltimezone *) e_cal_backend_store_get_timezone (cbdav->priv->store, tzid);
4850 if (!zone && E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone)
4851 zone = E_CAL_BACKEND_CLASS (parent_class)->internal_get_timezone (backend, tzid);
4857 caldav_source_changed_thread (gpointer data)
4859 ECalBackendCalDAV *cbdav = data;
4860 SlaveCommand old_slave_cmd;
4861 gboolean old_slave_busy;
4863 g_return_val_if_fail (cbdav != NULL, NULL);
4865 old_slave_cmd = cbdav->priv->slave_cmd;
4866 old_slave_busy = cbdav->priv->slave_busy;
4867 if (old_slave_busy) {
4868 update_slave_cmd (cbdav->priv, SLAVE_SHOULD_SLEEP);
4869 g_mutex_lock (cbdav->priv->busy_lock);
4872 initialize_backend (cbdav, NULL);
4874 /* always wakeup thread, even when it was sleeping */
4875 g_cond_signal (cbdav->priv->cond);
4877 if (old_slave_busy) {
4878 update_slave_cmd (cbdav->priv, old_slave_cmd);
4879 g_mutex_unlock (cbdav->priv->busy_lock);
4882 cbdav->priv->updating_source = FALSE;
4884 g_object_unref (cbdav);
4890 caldav_source_changed_cb (ESource *source,
4891 ECalBackendCalDAV *cbdav)
4895 g_return_if_fail (source != NULL);
4896 g_return_if_fail (cbdav != NULL);
4898 if (cbdav->priv->updating_source)
4901 cbdav->priv->updating_source = TRUE;
4903 thread = g_thread_new (NULL, caldav_source_changed_thread, g_object_ref (cbdav));
4904 g_thread_unref (thread);
4907 static ESourceAuthenticationResult
4908 caldav_try_password_sync (ESourceAuthenticator *authenticator,
4909 const GString *password,
4910 GCancellable *cancellable,
4913 ECalBackendCalDAV *cbdav;
4914 ESourceAuthenticationResult result;
4915 GError *local_error = NULL;
4917 cbdav = E_CAL_BACKEND_CALDAV (authenticator);
4919 /* Busy lock is already acquired by caldav_do_open(). */
4921 g_free (cbdav->priv->password);
4922 cbdav->priv->password = g_strdup (password->str);
4924 open_calendar (cbdav, &local_error);
4926 if (local_error == NULL) {
4927 result = E_SOURCE_AUTHENTICATION_ACCEPTED;
4928 } else if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
4929 result = E_SOURCE_AUTHENTICATION_REJECTED;
4930 g_clear_error (&local_error);
4932 result = E_SOURCE_AUTHENTICATION_ERROR;
4933 g_propagate_error (error, local_error);
4940 caldav_source_authenticator_init (ESourceAuthenticatorInterface *interface)
4942 interface->try_password_sync = caldav_try_password_sync;
4945 /* ************************************************************************* */
4946 /* ***************************** GObject Foo ******************************* */
4948 G_DEFINE_TYPE_WITH_CODE (
4950 e_cal_backend_caldav,
4951 E_TYPE_CAL_BACKEND_SYNC,
4952 G_IMPLEMENT_INTERFACE (
4953 E_TYPE_SOURCE_AUTHENTICATOR,
4954 caldav_source_authenticator_init))
4957 e_cal_backend_caldav_dispose (GObject *object)
4959 ECalBackendCalDAVPrivate *priv;
4962 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (object);
4964 /* tell the slave to stop before acquiring a lock,
4965 * as it can work at the moment, and lock can be locked */
4966 update_slave_cmd (priv, SLAVE_SHOULD_DIE);
4968 g_mutex_lock (priv->busy_lock);
4970 if (priv->disposed) {
4971 g_mutex_unlock (priv->busy_lock);
4975 source = e_backend_get_source (E_BACKEND (object));
4977 g_signal_handlers_disconnect_by_func (G_OBJECT (source), caldav_source_changed_cb, object);
4979 /* stop the slave */
4980 if (priv->synch_slave) {
4981 g_cond_signal (priv->cond);
4983 /* wait until the slave died */
4984 g_cond_wait (priv->slave_gone_cond, priv->busy_lock);
4987 g_object_unref (priv->session);
4988 g_object_unref (priv->proxy);
4991 g_free (priv->schedule_outbox_url);
4993 if (priv->store != NULL) {
4994 g_object_unref (priv->store);
4997 priv->disposed = TRUE;
4998 g_mutex_unlock (priv->busy_lock);
5000 /* Chain up to parent's dispose() method. */
5001 G_OBJECT_CLASS (parent_class)->dispose (object);
5005 e_cal_backend_caldav_finalize (GObject *object)
5007 ECalBackendCalDAVPrivate *priv;
5009 priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (object);
5011 g_mutex_free (priv->busy_lock);
5012 g_cond_free (priv->cond);
5013 g_cond_free (priv->slave_gone_cond);
5015 g_free (priv->password);
5017 /* Chain up to parent's finalize() method. */
5018 G_OBJECT_CLASS (parent_class)->finalize (object);
5022 cal_backend_caldav_constructed (GObject *object)
5025 ESourceWebdav *extension;
5026 ECalBackendCalDAV *cbdav;
5027 const gchar *extension_name;
5029 cbdav = E_CAL_BACKEND_CALDAV (object);
5031 source = e_backend_get_source (E_BACKEND (cbdav));
5032 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
5033 extension = e_source_get_extension (source, extension_name);
5035 g_object_bind_property (
5036 extension, "ignore-invalid-cert",
5037 cbdav->priv->session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE,
5038 G_BINDING_SYNC_CREATE |
5039 G_BINDING_INVERT_BOOLEAN);
5041 /* Chain up to parent's constructed() method. */
5042 G_OBJECT_CLASS (e_cal_backend_caldav_parent_class)->
5043 constructed (object);
5047 e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
5049 cbdav->priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
5050 cbdav->priv->session = soup_session_sync_new ();
5051 g_object_set (cbdav->priv->session, SOUP_SESSION_TIMEOUT, 90, NULL);
5053 cbdav->priv->proxy = e_proxy_new ();
5054 e_proxy_setup_proxy (cbdav->priv->proxy);
5055 g_signal_connect (cbdav->priv->proxy, "changed", G_CALLBACK (proxy_settings_changed), cbdav->priv);
5057 if (G_UNLIKELY (caldav_debug_show (DEBUG_MESSAGE)))
5058 caldav_debug_setup (cbdav->priv->session);
5060 cbdav->priv->disposed = FALSE;
5061 cbdav->priv->loaded = FALSE;
5062 cbdav->priv->opened = FALSE;
5064 /* Thinks the 'getctag' extension is available the first time, but unset it when realizes it isn't. */
5065 cbdav->priv->ctag_supported = TRUE;
5066 cbdav->priv->ctag_to_store = NULL;
5068 cbdav->priv->schedule_outbox_url = NULL;
5070 cbdav->priv->is_google = FALSE;
5072 cbdav->priv->busy_lock = g_mutex_new ();
5073 cbdav->priv->cond = g_cond_new ();
5074 cbdav->priv->slave_gone_cond = g_cond_new ();
5076 /* Slave control ... */
5077 cbdav->priv->slave_cmd = SLAVE_SHOULD_SLEEP;
5078 cbdav->priv->slave_busy = FALSE;
5079 cbdav->priv->refresh_time.tv_usec = 0;
5080 cbdav->priv->refresh_time.tv_sec = DEFAULT_REFRESH_TIME;
5082 g_signal_connect (cbdav->priv->session, "authenticate",
5083 G_CALLBACK (soup_authenticate), cbdav);
5085 e_cal_backend_sync_set_lock (E_CAL_BACKEND_SYNC (cbdav), FALSE);
5088 cbdav, "notify::online",
5089 G_CALLBACK (caldav_notify_online_cb), NULL);
5093 e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
5095 GObjectClass *object_class;
5096 ECalBackendClass *backend_class;
5097 ECalBackendSyncClass *sync_class;
5099 object_class = (GObjectClass *) class;
5100 backend_class = (ECalBackendClass *) class;
5101 sync_class = (ECalBackendSyncClass *) class;
5103 caldav_debug_init ();
5105 parent_class = (ECalBackendSyncClass *) g_type_class_peek_parent (class);
5106 g_type_class_add_private (class, sizeof (ECalBackendCalDAVPrivate));
5108 object_class->dispose = e_cal_backend_caldav_dispose;
5109 object_class->finalize = e_cal_backend_caldav_finalize;
5110 object_class->constructed = cal_backend_caldav_constructed;
5112 sync_class->get_backend_property_sync = caldav_get_backend_property;
5114 sync_class->open_sync = caldav_do_open;
5115 sync_class->refresh_sync = caldav_refresh;
5116 sync_class->remove_sync = caldav_remove;
5118 sync_class->create_objects_sync = caldav_create_objects;
5119 sync_class->modify_objects_sync = caldav_modify_objects;
5120 sync_class->remove_objects_sync = caldav_remove_objects;
5122 sync_class->receive_objects_sync = caldav_receive_objects;
5123 sync_class->send_objects_sync = caldav_send_objects;
5124 sync_class->get_object_sync = caldav_get_object;
5125 sync_class->get_object_list_sync = caldav_get_object_list;
5126 sync_class->add_timezone_sync = caldav_add_timezone;
5127 sync_class->get_free_busy_sync = caldav_get_free_busy;
5129 backend_class->start_view = caldav_start_view;
5131 backend_class->internal_get_timezone = caldav_internal_get_timezone;