1 /*
2  * e-google-chooser-button.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-config.h"
19 
20 #include <glib/gi18n-lib.h>
21 
22 #include <libedataserverui/libedataserverui.h>
23 
24 #include "module-cal-config-google.h"
25 #include "e-google-chooser-button.h"
26 
27 #define E_GOOGLE_CHOOSER_BUTTON_GET_PRIVATE(obj) \
28 	(G_TYPE_INSTANCE_GET_PRIVATE \
29 	((obj), E_TYPE_GOOGLE_CHOOSER_BUTTON, EGoogleChooserButtonPrivate))
30 
31 struct _EGoogleChooserButtonPrivate {
32 	ESource *source;
33 	ESourceConfig *config;
34 	GtkWidget *label;
35 };
36 
37 enum {
38 	PROP_0,
39 	PROP_SOURCE,
40 	PROP_CONFIG
41 };
42 
G_DEFINE_DYNAMIC_TYPE(EGoogleChooserButton,e_google_chooser_button,GTK_TYPE_BUTTON)43 G_DEFINE_DYNAMIC_TYPE (
44 	EGoogleChooserButton,
45 	e_google_chooser_button,
46 	GTK_TYPE_BUTTON)
47 
48 static void
49 google_chooser_button_set_source (EGoogleChooserButton *button,
50                                   ESource *source)
51 {
52 	g_return_if_fail (E_IS_SOURCE (source));
53 	g_return_if_fail (button->priv->source == NULL);
54 
55 	button->priv->source = g_object_ref (source);
56 }
57 
58 static void
google_chooser_button_set_config(EGoogleChooserButton * button,ESourceConfig * config)59 google_chooser_button_set_config (EGoogleChooserButton *button,
60                                   ESourceConfig *config)
61 {
62 	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
63 	g_return_if_fail (button->priv->config == NULL);
64 
65 	button->priv->config = g_object_ref (config);
66 }
67 
68 static void
google_chooser_button_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)69 google_chooser_button_set_property (GObject *object,
70                                     guint property_id,
71                                     const GValue *value,
72                                     GParamSpec *pspec)
73 {
74 	switch (property_id) {
75 		case PROP_SOURCE:
76 			google_chooser_button_set_source (
77 				E_GOOGLE_CHOOSER_BUTTON (object),
78 				g_value_get_object (value));
79 			return;
80 
81 		case PROP_CONFIG:
82 			google_chooser_button_set_config (
83 				E_GOOGLE_CHOOSER_BUTTON (object),
84 				g_value_get_object (value));
85 			return;
86 	}
87 
88 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
89 }
90 
91 static void
google_chooser_button_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)92 google_chooser_button_get_property (GObject *object,
93                                     guint property_id,
94                                     GValue *value,
95                                     GParamSpec *pspec)
96 {
97 	switch (property_id) {
98 		case PROP_SOURCE:
99 			g_value_set_object (
100 				value,
101 				e_google_chooser_button_get_source (
102 				E_GOOGLE_CHOOSER_BUTTON (object)));
103 			return;
104 
105 		case PROP_CONFIG:
106 			g_value_set_object (
107 				value,
108 				e_google_chooser_button_get_config (
109 				E_GOOGLE_CHOOSER_BUTTON (object)));
110 			return;
111 	}
112 
113 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
114 }
115 
116 static void
google_chooser_button_dispose(GObject * object)117 google_chooser_button_dispose (GObject *object)
118 {
119 	EGoogleChooserButtonPrivate *priv;
120 
121 	priv = E_GOOGLE_CHOOSER_BUTTON_GET_PRIVATE (object);
122 
123 	g_clear_object (&priv->source);
124 	g_clear_object (&priv->config);
125 	g_clear_object (&priv->label);
126 
127 	/* Chain up to parent's dispose() method. */
128 	G_OBJECT_CLASS (e_google_chooser_button_parent_class)->dispose (object);
129 }
130 
131 static void
google_chooser_button_constructed(GObject * object)132 google_chooser_button_constructed (GObject *object)
133 {
134 	EGoogleChooserButton *button;
135 	ESourceWebdav *webdav_extension;
136 	GBindingFlags binding_flags;
137 	GtkWidget *widget;
138 	const gchar *display_name;
139 
140 	button = E_GOOGLE_CHOOSER_BUTTON (object);
141 
142 	/* Chain up to parent's constructed() method. */
143 	G_OBJECT_CLASS (e_google_chooser_button_parent_class)->constructed (object);
144 
145 	widget = gtk_label_new (_("Default User Calendar"));
146 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
147 	gtk_container_add (GTK_CONTAINER (button), widget);
148 	button->priv->label = g_object_ref (widget);
149 	gtk_widget_show (widget);
150 
151 	webdav_extension = e_source_get_extension (
152 		button->priv->source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
153 	display_name = e_source_webdav_get_display_name (webdav_extension);
154 
155 	binding_flags = G_BINDING_DEFAULT;
156 	if (display_name != NULL && *display_name != '\0')
157 		binding_flags |= G_BINDING_SYNC_CREATE;
158 
159 	e_binding_bind_property (
160 		webdav_extension, "display-name",
161 		button->priv->label, "label",
162 		binding_flags);
163 }
164 
165 static GtkWindow *
google_config_get_dialog_parent_cb(ECredentialsPrompter * prompter,GtkWindow * dialog)166 google_config_get_dialog_parent_cb (ECredentialsPrompter *prompter,
167 				    GtkWindow *dialog)
168 {
169 	return dialog;
170 }
171 
172 static void
google_chooser_button_clicked(GtkButton * button)173 google_chooser_button_clicked (GtkButton *button)
174 {
175 	EGoogleChooserButtonPrivate *priv;
176 	gpointer parent;
177 	ESourceRegistry *registry;
178 	ECredentialsPrompter *prompter;
179 	ESourceWebdav *webdav_extension;
180 	ESourceAuthentication *authentication_extension;
181 	SoupURI *uri;
182 	gchar *base_url;
183 	GtkDialog *dialog;
184 	gulong handler_id;
185 	guint supports_filter = 0;
186 	gboolean can_google_auth;
187 	const gchar *title = NULL;
188 
189 	priv = E_GOOGLE_CHOOSER_BUTTON_GET_PRIVATE (button);
190 
191 	parent = gtk_widget_get_toplevel (GTK_WIDGET (button));
192 	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
193 
194 	registry = e_source_config_get_registry (priv->config);
195 
196 	authentication_extension = e_source_get_extension (priv->source, E_SOURCE_EXTENSION_AUTHENTICATION);
197 	webdav_extension = e_source_get_extension (priv->source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
198 
199 	uri = e_source_webdav_dup_soup_uri (webdav_extension);
200 	can_google_auth = e_module_cal_config_google_is_supported (NULL, registry) &&
201 			  g_strcmp0 (e_source_authentication_get_method (authentication_extension), "OAuth2") != 0;
202 
203 	e_google_chooser_button_construct_default_uri (uri, e_source_authentication_get_user (authentication_extension));
204 
205 	if (can_google_auth) {
206 		/* Prefer 'Google', aka internal OAuth2, authentication method, if available */
207 		e_source_authentication_set_method (authentication_extension, "Google");
208 
209 		/* See https://developers.google.com/google-apps/calendar/caldav/v2/guide */
210 		soup_uri_set_host (uri, "apidata.googleusercontent.com");
211 		soup_uri_set_path (uri, "/caldav/v2/");
212 	} else {
213 		soup_uri_set_host (uri, "www.google.com");
214 		/* To find also calendar email, not only calendars */
215 		soup_uri_set_path (uri, "/calendar/dav/");
216 	}
217 
218 	/* Google's CalDAV interface requires a secure connection. */
219 	soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS);
220 
221 	e_source_webdav_set_soup_uri (webdav_extension, uri);
222 
223 	switch (e_cal_source_config_get_source_type (E_CAL_SOURCE_CONFIG (priv->config))) {
224 	case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
225 		supports_filter = E_WEBDAV_DISCOVER_SUPPORTS_EVENTS;
226 		title = _("Choose a Calendar");
227 		break;
228 	case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
229 		supports_filter = E_WEBDAV_DISCOVER_SUPPORTS_MEMOS;
230 		title = _("Choose a Memo List");
231 		break;
232 	case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
233 		supports_filter = E_WEBDAV_DISCOVER_SUPPORTS_TASKS;
234 		title = _("Choose a Task List");
235 		break;
236 	default:
237 		g_return_if_reached ();
238 	}
239 
240 	prompter = e_credentials_prompter_new (registry);
241 	e_credentials_prompter_set_auto_prompt (prompter, FALSE);
242 
243 	base_url = soup_uri_to_string (uri, FALSE);
244 
245 	dialog = e_webdav_discover_dialog_new (parent, title, prompter, priv->source, base_url, supports_filter);
246 
247 	if (parent != NULL)
248 		e_binding_bind_property (
249 			parent, "icon-name",
250 			dialog, "icon-name",
251 			G_BINDING_SYNC_CREATE);
252 
253 	handler_id = g_signal_connect (prompter, "get-dialog-parent",
254 		G_CALLBACK (google_config_get_dialog_parent_cb), dialog);
255 
256 	e_webdav_discover_dialog_refresh (dialog);
257 
258 	if (gtk_dialog_run (dialog) == GTK_RESPONSE_ACCEPT) {
259 		gchar *href = NULL, *display_name = NULL, *color = NULL, *email;
260 		guint supports = 0, order = 0;
261 		GtkWidget *content;
262 
263 		content = e_webdav_discover_dialog_get_content (dialog);
264 
265 		if (e_webdav_discover_content_get_selected (content, 0, &href, &supports, &display_name, &color, &order)) {
266 			soup_uri_free (uri);
267 			uri = soup_uri_new (href);
268 
269 			if (uri) {
270 				ESourceSelectable *selectable_extension;
271 				const gchar *extension_name;
272 
273 				switch (e_cal_source_config_get_source_type (E_CAL_SOURCE_CONFIG (priv->config))) {
274 					case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
275 						extension_name = E_SOURCE_EXTENSION_CALENDAR;
276 						break;
277 					case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
278 						extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
279 						break;
280 					case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
281 						extension_name = E_SOURCE_EXTENSION_TASK_LIST;
282 						break;
283 					default:
284 						g_return_if_reached ();
285 				}
286 
287 				selectable_extension = e_source_get_extension (priv->source, extension_name);
288 
289 				e_source_set_display_name (priv->source, display_name);
290 
291 				e_source_webdav_set_display_name (webdav_extension, display_name);
292 				e_source_webdav_set_soup_uri (webdav_extension, uri);
293 				e_source_webdav_set_order (webdav_extension, order);
294 
295 				if (color && *color)
296 					e_source_selectable_set_color (selectable_extension, color);
297 
298 				e_source_selectable_set_order (selectable_extension, order);
299 			}
300 
301 			g_free (href);
302 			g_free (display_name);
303 			g_free (color);
304 
305 			href = NULL;
306 			display_name = NULL;
307 			color = NULL;
308 		}
309 
310 		email = e_webdav_discover_content_get_user_address (content);
311 		if (email && *email)
312 			e_source_webdav_set_email_address (webdav_extension, email);
313 		g_free (email);
314 	}
315 
316 	g_signal_handler_disconnect (prompter, handler_id);
317 
318 	gtk_widget_destroy (GTK_WIDGET (dialog));
319 
320 	g_object_unref (prompter);
321 	if (uri)
322 		soup_uri_free (uri);
323 	g_free (base_url);
324 }
325 
326 static void
e_google_chooser_button_class_init(EGoogleChooserButtonClass * class)327 e_google_chooser_button_class_init (EGoogleChooserButtonClass *class)
328 {
329 	GObjectClass *object_class;
330 	GtkButtonClass *button_class;
331 
332 	g_type_class_add_private (class, sizeof (EGoogleChooserButtonPrivate));
333 
334 	object_class = G_OBJECT_CLASS (class);
335 	object_class->set_property = google_chooser_button_set_property;
336 	object_class->get_property = google_chooser_button_get_property;
337 	object_class->dispose = google_chooser_button_dispose;
338 	object_class->constructed = google_chooser_button_constructed;
339 
340 	button_class = GTK_BUTTON_CLASS (class);
341 	button_class->clicked = google_chooser_button_clicked;
342 
343 	g_object_class_install_property (
344 		object_class,
345 		PROP_SOURCE,
346 		g_param_spec_object (
347 			"source",
348 			NULL,
349 			NULL,
350 			E_TYPE_SOURCE,
351 			G_PARAM_READWRITE |
352 			G_PARAM_CONSTRUCT_ONLY));
353 
354 	g_object_class_install_property (
355 		object_class,
356 		PROP_CONFIG,
357 		g_param_spec_object (
358 			"config",
359 			NULL,
360 			NULL,
361 			E_TYPE_SOURCE_CONFIG,
362 			G_PARAM_READWRITE |
363 			G_PARAM_CONSTRUCT_ONLY));
364 }
365 
366 static void
e_google_chooser_button_class_finalize(EGoogleChooserButtonClass * class)367 e_google_chooser_button_class_finalize (EGoogleChooserButtonClass *class)
368 {
369 }
370 
371 static void
e_google_chooser_button_init(EGoogleChooserButton * button)372 e_google_chooser_button_init (EGoogleChooserButton *button)
373 {
374 	button->priv = E_GOOGLE_CHOOSER_BUTTON_GET_PRIVATE (button);
375 }
376 
377 void
e_google_chooser_button_type_register(GTypeModule * type_module)378 e_google_chooser_button_type_register (GTypeModule *type_module)
379 {
380 	/* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
381 	 *     function, so we have to wrap it with a public function in
382 	 *     order to register types from a separate compilation unit. */
383 	e_google_chooser_button_register_type (type_module);
384 }
385 
386 GtkWidget *
e_google_chooser_button_new(ESource * source,ESourceConfig * config)387 e_google_chooser_button_new (ESource *source,
388 			     ESourceConfig *config)
389 {
390 	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
391 
392 	return g_object_new (
393 		E_TYPE_GOOGLE_CHOOSER_BUTTON,
394 		"source", source,
395 		"config", config, NULL);
396 }
397 
398 ESource *
e_google_chooser_button_get_source(EGoogleChooserButton * button)399 e_google_chooser_button_get_source (EGoogleChooserButton *button)
400 {
401 	g_return_val_if_fail (E_IS_GOOGLE_CHOOSER_BUTTON (button), NULL);
402 
403 	return button->priv->source;
404 }
405 
406 ESourceConfig *
e_google_chooser_button_get_config(EGoogleChooserButton * button)407 e_google_chooser_button_get_config (EGoogleChooserButton *button)
408 {
409 	g_return_val_if_fail (E_IS_GOOGLE_CHOOSER_BUTTON (button), NULL);
410 
411 	return button->priv->config;
412 }
413 
414 static gchar *
google_chooser_decode_user(const gchar * user)415 google_chooser_decode_user (const gchar *user)
416 {
417 	gchar *decoded_user;
418 
419 	if (user == NULL || *user == '\0')
420 		return NULL;
421 
422 	/* Decode any encoded 'at' symbols ('%40' -> '@'). */
423 	if (strstr (user, "%40") != NULL) {
424 		gchar **segments;
425 
426 		segments = g_strsplit (user, "%40", 0);
427 		decoded_user = g_strjoinv ("@", segments);
428 		g_strfreev (segments);
429 
430 	/* If no domain is given, append "@gmail.com". */
431 	} else if (strstr (user, "@") == NULL) {
432 		decoded_user = g_strconcat (user, "@gmail.com", NULL);
433 
434 	/* Otherwise the user name should be fine as is. */
435 	} else {
436 		decoded_user = g_strdup (user);
437 	}
438 
439 	return decoded_user;
440 }
441 
442 void
e_google_chooser_button_construct_default_uri(SoupURI * soup_uri,const gchar * username)443 e_google_chooser_button_construct_default_uri (SoupURI *soup_uri,
444 					       const gchar *username)
445 {
446 	gchar *decoded_user, *path;
447 
448 	decoded_user = google_chooser_decode_user (username);
449 	if (!decoded_user)
450 		return;
451 
452 	if (g_strcmp0 (soup_uri_get_host (soup_uri), "apidata.googleusercontent.com") == 0)
453 		path = g_strdup_printf ("/caldav/v2/%s/events", decoded_user);
454 	else
455 		path = g_strdup_printf ("/calendar/dav/%s/events", decoded_user);
456 
457 	soup_uri_set_user (soup_uri, decoded_user);
458 	soup_uri_set_path (soup_uri, path);
459 
460 	g_free (decoded_user);
461 	g_free (path);
462 }
463