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