1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* Evolution calendar - iCalendar http backend
3 *
4 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5 *
6 * This library is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation.
9 *
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authors: Hans Petter Jansson <hpj@ximian.com>
19 */
20
21 #include "evolution-data-server-config.h"
22
23 #include <string.h>
24 #include <unistd.h>
25 #include <glib/gi18n-lib.h>
26
27 #include <libsoup/soup.h>
28 #include <libedata-cal/libedata-cal.h>
29
30 #include "e-cal-backend-http.h"
31
32 #define EC_ERROR(_code) e_client_error_create (_code, NULL)
33 #define EC_ERROR_EX(_code, _msg) e_client_error_create (_code, _msg)
34 #define ECC_ERROR(_code) e_cal_client_error_create (_code, NULL)
35
36 struct _ECalBackendHttpPrivate {
37 ESoupSession *session;
38
39 SoupRequestHTTP *request;
40 GInputStream *input_stream;
41 GRecMutex conn_lock;
42 GHashTable *components; /* gchar *uid ~> ICalComponent * */
43 gint64 hsts_until_time;
44 };
45
G_DEFINE_TYPE_WITH_PRIVATE(ECalBackendHttp,e_cal_backend_http,E_TYPE_CAL_META_BACKEND)46 G_DEFINE_TYPE_WITH_PRIVATE (ECalBackendHttp, e_cal_backend_http, E_TYPE_CAL_META_BACKEND)
47
48 static gchar *
49 ecb_http_webcal_to_http_method (const gchar *webcal_str,
50 gboolean secure)
51 {
52 if (secure && (
53 g_str_has_prefix (webcal_str, "http://") ||
54 g_str_has_prefix (webcal_str, "webcal://")))
55 return g_strconcat ("https://", webcal_str + sizeof ("http://") - 1, NULL);
56
57 if (!g_str_has_prefix (webcal_str, "webcal://"))
58 return g_strdup (webcal_str);
59
60 if (secure)
61 return g_strconcat ("https://", webcal_str + sizeof ("webcal://") - 1, NULL);
62 else
63 return g_strconcat ("http://", webcal_str + sizeof ("webcal://") - 1, NULL);
64 }
65
66 static gchar *
ecb_http_dup_uri(ECalBackendHttp * cbhttp)67 ecb_http_dup_uri (ECalBackendHttp *cbhttp)
68 {
69 ESource *source;
70 ESourceSecurity *security_extension;
71 ESourceWebdav *webdav_extension;
72 SoupURI *soup_uri;
73 gboolean secure_connection;
74 const gchar *extension_name;
75 gchar *uri_string, *uri;
76
77 g_return_val_if_fail (E_IS_CAL_BACKEND_HTTP (cbhttp), NULL);
78
79 source = e_backend_get_source (E_BACKEND (cbhttp));
80
81 extension_name = E_SOURCE_EXTENSION_SECURITY;
82 security_extension = e_source_get_extension (source, extension_name);
83
84 extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
85 webdav_extension = e_source_get_extension (source, extension_name);
86
87 secure_connection = e_source_security_get_secure (security_extension);
88
89 soup_uri = e_source_webdav_dup_soup_uri (webdav_extension);
90 uri_string = soup_uri_to_string (soup_uri, FALSE);
91 soup_uri_free (soup_uri);
92
93 if (!uri_string || !*uri_string) {
94 g_free (uri_string);
95 return NULL;
96 }
97
98 secure_connection = secure_connection || (cbhttp->priv->hsts_until_time && g_get_real_time () <= cbhttp->priv->hsts_until_time);
99
100 uri = ecb_http_webcal_to_http_method (uri_string, secure_connection);
101
102 g_free (uri_string);
103
104 return uri;
105 }
106
107 /* https://tools.ietf.org/html/rfc6797 */
108 static gint64
ecb_http_extract_hsts_until_time(ECalBackendHttp * cbhttp)109 ecb_http_extract_hsts_until_time (ECalBackendHttp *cbhttp)
110 {
111 SoupMessage *message;
112 GTlsCertificate *cert = NULL;
113 GTlsCertificateFlags cert_errors = 0;
114 gint64 hsts_until_time = 0;
115
116 g_return_val_if_fail (E_IS_CAL_BACKEND_HTTP (cbhttp), hsts_until_time);
117 g_return_val_if_fail (cbhttp->priv->request, hsts_until_time);
118
119 message = soup_request_http_get_message (cbhttp->priv->request);
120 if (!message)
121 return hsts_until_time;
122
123 if (message->response_headers &&
124 soup_message_get_https_status (message, &cert, &cert_errors) &&
125 !cert_errors) {
126 const gchar *hsts_header;
127
128 hsts_header = soup_message_headers_get_one (message->response_headers, "Strict-Transport-Security");
129 if (hsts_header && *hsts_header) {
130 GHashTable *params;
131
132 params = soup_header_parse_semi_param_list (hsts_header);
133 if (params) {
134 const gchar *max_age;
135
136 max_age = g_hash_table_lookup (params, "max-age");
137
138 if (max_age && *max_age) {
139 gint64 value;
140
141 if (*max_age == '\"')
142 max_age++;
143
144 value = g_ascii_strtoll (max_age, NULL, 10);
145
146 if (value > 0)
147 hsts_until_time = g_get_real_time () + (value * G_USEC_PER_SEC);
148 }
149
150 soup_header_free_param_list (params);
151 }
152 }
153 }
154
155 g_object_unref (message);
156
157 return hsts_until_time;
158 }
159
160 static gchar *
ecb_http_read_stream_sync(GInputStream * input_stream,goffset expected_length,GCancellable * cancellable,GError ** error)161 ecb_http_read_stream_sync (GInputStream *input_stream,
162 goffset expected_length,
163 GCancellable *cancellable,
164 GError **error)
165 {
166 GString *icalstr;
167 void *buffer;
168 gsize nread = 0;
169 gboolean success = FALSE;
170
171 g_return_val_if_fail (G_IS_INPUT_STREAM (input_stream), NULL);
172
173 icalstr = g_string_sized_new ((expected_length > 0 && expected_length <= 1024 * 1024) ? expected_length + 1 : 1024);
174
175 buffer = g_malloc (16384);
176
177 while (success = g_input_stream_read_all (input_stream, buffer, 16384, &nread, cancellable, error),
178 success && nread > 0) {
179 g_string_append_len (icalstr, (const gchar *) buffer, nread);
180 }
181
182 g_free (buffer);
183
184 return g_string_free (icalstr, !success);
185 }
186
187 static gboolean
ecb_http_connect_sync(ECalMetaBackend * meta_backend,const ENamedParameters * credentials,ESourceAuthenticationResult * out_auth_result,gchar ** out_certificate_pem,GTlsCertificateFlags * out_certificate_errors,GCancellable * cancellable,GError ** error)188 ecb_http_connect_sync (ECalMetaBackend *meta_backend,
189 const ENamedParameters *credentials,
190 ESourceAuthenticationResult *out_auth_result,
191 gchar **out_certificate_pem,
192 GTlsCertificateFlags *out_certificate_errors,
193 GCancellable *cancellable,
194 GError **error)
195 {
196 ECalBackendHttp *cbhttp;
197 ESource *source;
198 SoupRequestHTTP *request;
199 GInputStream *input_stream = NULL;
200 gchar *uri;
201 gboolean success;
202 GError *local_error = NULL;
203
204 g_return_val_if_fail (E_IS_CAL_BACKEND_HTTP (meta_backend), FALSE);
205 g_return_val_if_fail (out_auth_result != NULL, FALSE);
206
207 cbhttp = E_CAL_BACKEND_HTTP (meta_backend);
208
209 g_rec_mutex_lock (&cbhttp->priv->conn_lock);
210
211 if (cbhttp->priv->request && cbhttp->priv->input_stream) {
212 g_rec_mutex_unlock (&cbhttp->priv->conn_lock);
213 return TRUE;
214 }
215
216 source = e_backend_get_source (E_BACKEND (meta_backend));
217
218 g_clear_object (&cbhttp->priv->input_stream);
219 g_clear_object (&cbhttp->priv->request);
220
221 uri = ecb_http_dup_uri (cbhttp);
222
223 if (!uri || !*uri) {
224 g_rec_mutex_unlock (&cbhttp->priv->conn_lock);
225 g_free (uri);
226
227 g_propagate_error (error, EC_ERROR_EX (E_CLIENT_ERROR_OTHER_ERROR, _("URI not set")));
228 return FALSE;
229 }
230
231 e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTING);
232
233 e_soup_session_set_credentials (cbhttp->priv->session, credentials);
234
235 request = e_soup_session_new_request (cbhttp->priv->session, SOUP_METHOD_GET, uri, &local_error);
236 success = request != NULL;
237
238 if (success) {
239 SoupMessage *message;
240
241 message = soup_request_http_get_message (request);
242
243 if (message) {
244 gchar *last_etag;
245
246 last_etag = e_cal_meta_backend_dup_sync_tag (meta_backend);
247
248 if (last_etag && *last_etag)
249 soup_message_headers_append (message->request_headers, "If-None-Match", last_etag);
250
251 g_free (last_etag);
252 }
253
254 input_stream = e_soup_session_send_request_sync (cbhttp->priv->session, request, cancellable, &local_error);
255
256 success = input_stream != NULL;
257
258 if (success && message && !SOUP_STATUS_IS_SUCCESSFUL (message->status_code) && message->status_code != SOUP_STATUS_NOT_MODIFIED) {
259 g_clear_object (&input_stream);
260 success = FALSE;
261 }
262
263 if (success) {
264 e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED);
265 } else {
266 guint status_code;
267 gboolean credentials_empty;
268
269 if (local_error && local_error->domain == SOUP_HTTP_ERROR)
270 status_code = local_error->code;
271 else
272 status_code = message ? message->status_code : SOUP_STATUS_MALFORMED;
273
274 credentials_empty = !credentials || !e_named_parameters_count (credentials);
275
276 *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR;
277
278 /* because evolution knows only G_IO_ERROR_CANCELLED */
279 if (status_code == SOUP_STATUS_CANCELLED) {
280 g_set_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
281 "%s", (local_error && local_error->domain == SOUP_HTTP_ERROR) ? local_error->message :
282 (message && message->reason_phrase) ? message->reason_phrase : soup_status_get_phrase (status_code));
283 } else if (status_code == SOUP_STATUS_FORBIDDEN && credentials_empty) {
284 *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED;
285 } else if (status_code == SOUP_STATUS_UNAUTHORIZED) {
286 if (credentials_empty)
287 *out_auth_result = E_SOURCE_AUTHENTICATION_REQUIRED;
288 else
289 *out_auth_result = E_SOURCE_AUTHENTICATION_REJECTED;
290 } else if (local_error) {
291 g_propagate_error (error, local_error);
292 local_error = NULL;
293 } else {
294 g_set_error_literal (error, SOUP_HTTP_ERROR, status_code,
295 message ? message->reason_phrase : soup_status_get_phrase (status_code));
296 }
297
298 if (status_code == SOUP_STATUS_SSL_FAILED) {
299 *out_auth_result = E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED;
300
301 e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_SSL_FAILED);
302 e_soup_session_get_ssl_error_details (cbhttp->priv->session, out_certificate_pem, out_certificate_errors);
303 } else {
304 e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
305 }
306 }
307
308 g_clear_object (&message);
309 } else {
310 e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
311
312 g_set_error (error, E_CLIENT_ERROR, E_CLIENT_ERROR_OTHER_ERROR, _("Malformed URI “%s”: %s"),
313 uri, local_error ? local_error->message : _("Unknown error"));
314 }
315
316 if (success) {
317 cbhttp->priv->request = request;
318 cbhttp->priv->input_stream = input_stream;
319 cbhttp->priv->hsts_until_time = ecb_http_extract_hsts_until_time (cbhttp);
320
321 *out_auth_result = E_SOURCE_AUTHENTICATION_ACCEPTED;
322 } else {
323 g_clear_object (&request);
324 g_clear_object (&input_stream);
325
326 if (*out_auth_result != E_SOURCE_AUTHENTICATION_REQUIRED &&
327 *out_auth_result != E_SOURCE_AUTHENTICATION_REJECTED)
328 cbhttp->priv->hsts_until_time = 0;
329 }
330
331 g_rec_mutex_unlock (&cbhttp->priv->conn_lock);
332 g_clear_error (&local_error);
333 g_free (uri);
334
335 return success;
336 }
337
338 static gboolean
ecb_http_disconnect_sync(ECalMetaBackend * meta_backend,GCancellable * cancellable,GError ** error)339 ecb_http_disconnect_sync (ECalMetaBackend *meta_backend,
340 GCancellable *cancellable,
341 GError **error)
342 {
343 ECalBackendHttp *cbhttp;
344 ESource *source;
345
346 g_return_val_if_fail (E_IS_CAL_BACKEND_HTTP (meta_backend), FALSE);
347
348 cbhttp = E_CAL_BACKEND_HTTP (meta_backend);
349
350 g_rec_mutex_lock (&cbhttp->priv->conn_lock);
351
352 g_clear_object (&cbhttp->priv->input_stream);
353 g_clear_object (&cbhttp->priv->request);
354
355 if (cbhttp->priv->session)
356 soup_session_abort (SOUP_SESSION (cbhttp->priv->session));
357
358 g_clear_pointer (&cbhttp->priv->components, g_hash_table_destroy);
359
360 g_rec_mutex_unlock (&cbhttp->priv->conn_lock);
361
362 source = e_backend_get_source (E_BACKEND (meta_backend));
363 e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED);
364
365 return TRUE;
366 }
367
368 static gboolean
ecb_http_get_changes_sync(ECalMetaBackend * meta_backend,const gchar * last_sync_tag,gboolean is_repeat,gchar ** out_new_sync_tag,gboolean * out_repeat,GSList ** out_created_objects,GSList ** out_modified_objects,GSList ** out_removed_objects,GCancellable * cancellable,GError ** error)369 ecb_http_get_changes_sync (ECalMetaBackend *meta_backend,
370 const gchar *last_sync_tag,
371 gboolean is_repeat,
372 gchar **out_new_sync_tag,
373 gboolean *out_repeat,
374 GSList **out_created_objects,
375 GSList **out_modified_objects,
376 GSList **out_removed_objects,
377 GCancellable *cancellable,
378 GError **error)
379 {
380 ECalBackendHttp *cbhttp;
381 SoupMessage *message;
382 gchar *icalstring;
383 ICalCompIter *iter = NULL;
384 ICalComponent *maincomp, *subcomp;
385 ICalComponentKind backend_kind;
386 GHashTable *components = NULL;
387 gboolean success = TRUE;
388
389 g_return_val_if_fail (E_IS_CAL_BACKEND_HTTP (meta_backend), FALSE);
390 g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
391 g_return_val_if_fail (out_created_objects != NULL, FALSE);
392 g_return_val_if_fail (out_modified_objects != NULL, FALSE);
393 g_return_val_if_fail (out_removed_objects != NULL, FALSE);
394
395 cbhttp = E_CAL_BACKEND_HTTP (meta_backend);
396 backend_kind = e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend));
397
398 g_rec_mutex_lock (&cbhttp->priv->conn_lock);
399
400 if (!cbhttp->priv->request || !cbhttp->priv->input_stream) {
401 g_rec_mutex_unlock (&cbhttp->priv->conn_lock);
402 g_propagate_error (error, EC_ERROR (E_CLIENT_ERROR_REPOSITORY_OFFLINE));
403 return FALSE;
404 }
405
406 message = soup_request_http_get_message (cbhttp->priv->request);
407 if (message) {
408 const gchar *new_etag;
409
410 if (message->status_code == SOUP_STATUS_NOT_MODIFIED) {
411 g_rec_mutex_unlock (&cbhttp->priv->conn_lock);
412 g_object_unref (message);
413
414 ecb_http_disconnect_sync (meta_backend, cancellable, NULL);
415
416 return TRUE;
417 }
418
419 new_etag = soup_message_headers_get_one (message->response_headers, "ETag");
420 if (new_etag && !*new_etag) {
421 new_etag = NULL;
422 } else if (new_etag && g_strcmp0 (last_sync_tag, new_etag) == 0) {
423 g_rec_mutex_unlock (&cbhttp->priv->conn_lock);
424 /* Nothing changed */
425 g_object_unref (message);
426
427 ecb_http_disconnect_sync (meta_backend, cancellable, NULL);
428
429 return TRUE;
430 }
431
432 *out_new_sync_tag = g_strdup (new_etag);
433 }
434
435 g_clear_object (&message);
436
437 icalstring = ecb_http_read_stream_sync (cbhttp->priv->input_stream,
438 soup_request_get_content_length (SOUP_REQUEST (cbhttp->priv->request)), cancellable, error);
439
440 g_rec_mutex_unlock (&cbhttp->priv->conn_lock);
441
442 if (!icalstring) {
443 /* The error is already set */
444 e_cal_meta_backend_empty_cache_sync (meta_backend, cancellable, NULL);
445 ecb_http_disconnect_sync (meta_backend, cancellable, NULL);
446 return FALSE;
447 }
448
449 /* Skip the UTF-8 marker at the beginning of the string */
450 if (((guchar) icalstring[0]) == 0xEF &&
451 ((guchar) icalstring[1]) == 0xBB &&
452 ((guchar) icalstring[2]) == 0xBF)
453 maincomp = i_cal_parser_parse_string (icalstring + 3);
454 else
455 maincomp = i_cal_parser_parse_string (icalstring);
456
457 g_free (icalstring);
458
459 if (!maincomp) {
460 g_set_error (error, SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED, _("Bad file format."));
461 e_cal_meta_backend_empty_cache_sync (meta_backend, cancellable, NULL);
462 ecb_http_disconnect_sync (meta_backend, cancellable, NULL);
463 return FALSE;
464 }
465
466 if (i_cal_component_isa (maincomp) != I_CAL_VCALENDAR_COMPONENT &&
467 i_cal_component_isa (maincomp) != I_CAL_XROOT_COMPONENT) {
468 g_object_unref (maincomp);
469 g_set_error (error, SOUP_HTTP_ERROR, SOUP_STATUS_MALFORMED, _("Not a calendar."));
470 e_cal_meta_backend_empty_cache_sync (meta_backend, cancellable, NULL);
471 ecb_http_disconnect_sync (meta_backend, cancellable, NULL);
472 return FALSE;
473 }
474
475 if (i_cal_component_isa (maincomp) == I_CAL_VCALENDAR_COMPONENT) {
476 subcomp = g_object_ref (maincomp);
477 } else {
478 iter = i_cal_component_begin_component (maincomp, I_CAL_VCALENDAR_COMPONENT);
479 subcomp = i_cal_comp_iter_deref (iter);
480 }
481
482 while (subcomp && success) {
483 ICalComponent *next_subcomp = NULL;
484
485 if (iter)
486 next_subcomp = i_cal_comp_iter_next (iter);
487
488 if (i_cal_component_isa (subcomp) == I_CAL_VCALENDAR_COMPONENT) {
489 success = e_cal_meta_backend_gather_timezones_sync (meta_backend, subcomp, TRUE, cancellable, error);
490 if (success) {
491 ICalComponent *icomp;
492
493 while (icomp = i_cal_component_get_first_component (subcomp, backend_kind), icomp) {
494 ICalComponent *existing_icomp;
495 gpointer orig_key, orig_value;
496 const gchar *uid;
497
498 i_cal_component_remove_component (subcomp, icomp);
499
500 if (!components)
501 components = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
502
503 if (!e_cal_util_component_has_property (icomp, I_CAL_UID_PROPERTY)) {
504 gchar *new_uid = e_util_generate_uid ();
505 i_cal_component_set_uid (icomp, new_uid);
506 g_free (new_uid);
507 }
508
509 uid = i_cal_component_get_uid (icomp);
510
511 if (!g_hash_table_lookup_extended (components, uid, &orig_key, &orig_value)) {
512 orig_key = NULL;
513 orig_value = NULL;
514 }
515
516 existing_icomp = orig_value;
517 if (existing_icomp) {
518 if (i_cal_component_isa (existing_icomp) != I_CAL_VCALENDAR_COMPONENT) {
519 ICalComponent *vcal;
520
521 vcal = e_cal_util_new_top_level ();
522
523 g_warn_if_fail (g_hash_table_steal (components, uid));
524
525 i_cal_component_take_component (vcal, existing_icomp);
526 g_hash_table_insert (components, g_strdup (uid), vcal);
527
528 g_free (orig_key);
529
530 existing_icomp = vcal;
531 }
532
533 i_cal_component_take_component (existing_icomp, icomp);
534 } else {
535 g_hash_table_insert (components, g_strdup (uid), icomp);
536 }
537 }
538 }
539 }
540
541 g_object_unref (subcomp);
542 subcomp = next_subcomp;
543 }
544
545 g_clear_object (&subcomp);
546 g_clear_object (&iter);
547
548 if (components) {
549 g_warn_if_fail (cbhttp->priv->components == NULL);
550 cbhttp->priv->components = components;
551
552 g_object_unref (maincomp);
553
554 success = E_CAL_META_BACKEND_CLASS (e_cal_backend_http_parent_class)->get_changes_sync (meta_backend,
555 last_sync_tag, is_repeat, out_new_sync_tag, out_repeat, out_created_objects,
556 out_modified_objects, out_removed_objects, cancellable, error);
557 } else {
558 g_object_unref (maincomp);
559 }
560
561 if (!success)
562 ecb_http_disconnect_sync (meta_backend, cancellable, NULL);
563
564 return success;
565 }
566
567 static gboolean
ecb_http_list_existing_sync(ECalMetaBackend * meta_backend,gchar ** out_new_sync_tag,GSList ** out_existing_objects,GCancellable * cancellable,GError ** error)568 ecb_http_list_existing_sync (ECalMetaBackend *meta_backend,
569 gchar **out_new_sync_tag,
570 GSList **out_existing_objects, /* ECalMetaBackendInfo * */
571 GCancellable *cancellable,
572 GError **error)
573 {
574 ECalBackendHttp *cbhttp;
575 ECalCache *cal_cache;
576 ICalComponentKind kind;
577 GHashTableIter iter;
578 gpointer key, value;
579
580 g_return_val_if_fail (E_IS_CAL_BACKEND_HTTP (meta_backend), FALSE);
581 g_return_val_if_fail (out_existing_objects != NULL, FALSE);
582
583 cbhttp = E_CAL_BACKEND_HTTP (meta_backend);
584
585 *out_existing_objects = NULL;
586
587 g_return_val_if_fail (cbhttp->priv->components != NULL, FALSE);
588
589 cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
590 g_return_val_if_fail (cal_cache != NULL, FALSE);
591
592 kind = e_cal_backend_get_kind (E_CAL_BACKEND (meta_backend));
593
594 g_hash_table_iter_init (&iter, cbhttp->priv->components);
595 while (g_hash_table_iter_next (&iter, &key, &value)) {
596 ICalComponent *icomp = value;
597 ECalMetaBackendInfo *nfo;
598 const gchar *uid;
599 gchar *revision, *object;
600
601 if (icomp && i_cal_component_isa (icomp) == I_CAL_VCALENDAR_COMPONENT)
602 icomp = i_cal_component_get_first_component (icomp, kind);
603 else if (icomp)
604 icomp = g_object_ref (icomp);
605
606 if (!icomp)
607 continue;
608
609 uid = i_cal_component_get_uid (icomp);
610 revision = e_cal_cache_dup_component_revision (cal_cache, icomp);
611 object = i_cal_component_as_ical_string (value);
612
613 nfo = e_cal_meta_backend_info_new (uid, revision, object, NULL);
614
615 *out_existing_objects = g_slist_prepend (*out_existing_objects, nfo);
616
617 g_object_unref (icomp);
618 g_free (revision);
619 g_free (object);
620 }
621
622 g_object_unref (cal_cache);
623
624 ecb_http_disconnect_sync (meta_backend, cancellable, NULL);
625
626 return TRUE;
627 }
628
629 static gboolean
ecb_http_load_component_sync(ECalMetaBackend * meta_backend,const gchar * uid,const gchar * extra,ICalComponent ** out_component,gchar ** out_extra,GCancellable * cancellable,GError ** error)630 ecb_http_load_component_sync (ECalMetaBackend *meta_backend,
631 const gchar *uid,
632 const gchar *extra,
633 ICalComponent **out_component,
634 gchar **out_extra,
635 GCancellable *cancellable,
636 GError **error)
637 {
638 ECalBackendHttp *cbhttp;
639 gpointer key = NULL, value = NULL;
640
641 g_return_val_if_fail (E_IS_CAL_BACKEND_HTTP (meta_backend), FALSE);
642 g_return_val_if_fail (uid != NULL, FALSE);
643 g_return_val_if_fail (out_component != NULL, FALSE);
644
645 cbhttp = E_CAL_BACKEND_HTTP (meta_backend);
646 g_return_val_if_fail (cbhttp->priv->components != NULL, FALSE);
647
648 if (!cbhttp->priv->components ||
649 !g_hash_table_contains (cbhttp->priv->components, uid)) {
650 g_propagate_error (error, ECC_ERROR (E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND));
651 return FALSE;
652 }
653
654 g_warn_if_fail (g_hash_table_lookup_extended (cbhttp->priv->components, uid, &key, &value));
655 g_warn_if_fail (g_hash_table_steal (cbhttp->priv->components, uid));
656
657 *out_component = value;
658
659 g_free (key);
660
661 if (!g_hash_table_size (cbhttp->priv->components)) {
662 g_hash_table_destroy (cbhttp->priv->components);
663 cbhttp->priv->components = NULL;
664
665 ecb_http_disconnect_sync (meta_backend, cancellable, NULL);
666 }
667
668 return value != NULL;
669 }
670
671 static void
e_cal_backend_http_constructed(GObject * object)672 e_cal_backend_http_constructed (GObject *object)
673 {
674 ECalBackendHttp *cbhttp = E_CAL_BACKEND_HTTP (object);
675
676 /* Chain up to parent's method. */
677 G_OBJECT_CLASS (e_cal_backend_http_parent_class)->constructed (object);
678
679 cbhttp->priv->session = e_soup_session_new (e_backend_get_source (E_BACKEND (cbhttp)));
680
681 e_soup_session_setup_logging (cbhttp->priv->session, g_getenv ("WEBCAL_DEBUG"));
682
683 e_binding_bind_property (
684 cbhttp, "proxy-resolver",
685 cbhttp->priv->session, "proxy-resolver",
686 G_BINDING_SYNC_CREATE);
687 }
688
689 static void
e_cal_backend_http_dispose(GObject * object)690 e_cal_backend_http_dispose (GObject *object)
691 {
692 ECalBackendHttp *cbhttp;
693
694 cbhttp = E_CAL_BACKEND_HTTP (object);
695
696 g_rec_mutex_lock (&cbhttp->priv->conn_lock);
697
698 g_clear_object (&cbhttp->priv->request);
699 g_clear_object (&cbhttp->priv->input_stream);
700
701 if (cbhttp->priv->session)
702 soup_session_abort (SOUP_SESSION (cbhttp->priv->session));
703
704 g_clear_pointer (&cbhttp->priv->components, g_hash_table_destroy);
705
706 g_rec_mutex_unlock (&cbhttp->priv->conn_lock);
707
708 /* Chain up to parent's method. */
709 G_OBJECT_CLASS (e_cal_backend_http_parent_class)->dispose (object);
710 }
711
712 static void
e_cal_backend_http_finalize(GObject * object)713 e_cal_backend_http_finalize (GObject *object)
714 {
715 ECalBackendHttp *cbhttp = E_CAL_BACKEND_HTTP (object);
716
717 g_clear_object (&cbhttp->priv->session);
718 g_rec_mutex_clear (&cbhttp->priv->conn_lock);
719
720 /* Chain up to parent's method. */
721 G_OBJECT_CLASS (e_cal_backend_http_parent_class)->finalize (object);
722 }
723
724 static void
e_cal_backend_http_init(ECalBackendHttp * cbhttp)725 e_cal_backend_http_init (ECalBackendHttp *cbhttp)
726 {
727 cbhttp->priv = e_cal_backend_http_get_instance_private (cbhttp);
728
729 g_rec_mutex_init (&cbhttp->priv->conn_lock);
730
731 e_cal_backend_set_writable (E_CAL_BACKEND (cbhttp), FALSE);
732 }
733
734 static void
e_cal_backend_http_class_init(ECalBackendHttpClass * klass)735 e_cal_backend_http_class_init (ECalBackendHttpClass *klass)
736 {
737 GObjectClass *object_class;
738 ECalBackendSyncClass *cal_backend_sync_class;
739 ECalMetaBackendClass *cal_meta_backend_class;
740
741 cal_meta_backend_class = E_CAL_META_BACKEND_CLASS (klass);
742 cal_meta_backend_class->connect_sync = ecb_http_connect_sync;
743 cal_meta_backend_class->disconnect_sync = ecb_http_disconnect_sync;
744 cal_meta_backend_class->get_changes_sync = ecb_http_get_changes_sync;
745 cal_meta_backend_class->list_existing_sync = ecb_http_list_existing_sync;
746 cal_meta_backend_class->load_component_sync = ecb_http_load_component_sync;
747
748 /* Setting these methods to NULL will cause "Not supported" error,
749 which is more accurate than "Permission denied" error */
750 cal_backend_sync_class = E_CAL_BACKEND_SYNC_CLASS (klass);
751 cal_backend_sync_class->create_objects_sync = NULL;
752 cal_backend_sync_class->modify_objects_sync = NULL;
753 cal_backend_sync_class->remove_objects_sync = NULL;
754
755 object_class = G_OBJECT_CLASS (klass);
756 object_class->constructed = e_cal_backend_http_constructed;
757 object_class->dispose = e_cal_backend_http_dispose;
758 object_class->finalize = e_cal_backend_http_finalize;
759 }
760