1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This program is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this program; if not, see <http://www.gnu.org/licenses/>.
13  *
14  *
15  * Authors:
16  *		Jeffrey Stedfast <fejj@ximian.com>
17  *
18  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19  *
20  */
21 
22 #include "evolution-config.h"
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <errno.h>
29 #include <time.h>
30 
31 #include <glib/gi18n.h>
32 #include <glib/gstdio.h>
33 
34 #ifdef G_OS_WIN32
35 /* Work around namespace clobbage in <windows.h> */
36 #define DATADIR windows_DATADIR
37 #include <windows.h>
38 #undef DATADIR
39 #endif
40 
41 #include <libebook/libebook.h>
42 
43 #include <shell/e-shell.h>
44 
45 #include <em-format/e-mail-parser.h>
46 #include <em-format/e-mail-formatter-quote.h>
47 
48 #include "e-mail-printer.h"
49 #include "e-mail-tag-editor.h"
50 #include "em-composer-utils.h"
51 #include "em-filter-editor.h"
52 #include "em-folder-properties.h"
53 
54 #include "em-utils.h"
55 
56 /* How many is too many? */
57 /* Used in em_util_ask_open_many() */
58 #define TOO_MANY 10
59 
60 #define d(x)
61 
62 gboolean
em_utils_ask_open_many(GtkWindow * parent,gint how_many)63 em_utils_ask_open_many (GtkWindow *parent,
64                         gint how_many)
65 {
66 	gchar *string;
67 	gboolean proceed;
68 
69 	if (how_many < TOO_MANY)
70 		return TRUE;
71 
72 	string = g_strdup_printf (ngettext (
73 		/* Translators: This message is shown only for ten or more
74 		 * messages to be opened.  The %d is replaced with the actual
75 		 * count of messages. If you need a '%' in your text, then
76 		 * write it doubled, like '%%'. */
77 		"Are you sure you want to open %d message at once?",
78 		"Are you sure you want to open %d messages at once?",
79 		how_many), how_many);
80 	proceed = e_util_prompt_user (
81 		parent, "org.gnome.evolution.mail", "prompt-on-open-many",
82 		"mail:ask-open-many", string, NULL);
83 	g_free (string);
84 
85 	return proceed;
86 }
87 
88 /* Editing Filters/Search Folders... */
89 
90 static GtkWidget *filter_editor = NULL;
91 
92 static void
em_filter_editor_response(GtkWidget * dialog,gint button,gpointer user_data)93 em_filter_editor_response (GtkWidget *dialog,
94                            gint button,
95                            gpointer user_data)
96 {
97 	EMFilterContext *fc;
98 
99 	if (button == GTK_RESPONSE_OK) {
100 		const gchar *config_dir;
101 		gchar *user;
102 
103 		config_dir = mail_session_get_config_dir ();
104 		fc = g_object_get_data ((GObject *) dialog, "context");
105 		user = g_build_filename (config_dir, "filters.xml", NULL);
106 		e_rule_context_save ((ERuleContext *) fc, user);
107 		g_free (user);
108 	}
109 
110 	gtk_widget_destroy (dialog);
111 
112 	filter_editor = NULL;
113 }
114 
115 static EMFilterSource em_filter_source_element_names[] = {
116 	{ "incoming", },
117 	{ "outgoing", },
118 	{ NULL }
119 };
120 
121 /**
122  * em_utils_edit_filters:
123  * @session: an #EMailSession
124  * @alert_sink: an #EAlertSink
125  * @parent_window: a parent #GtkWindow
126  *
127  * Opens or raises the filters editor dialog so that the user may edit
128  * his/her filters. If @parent is non-NULL, then the dialog will be
129  * created as a child window of @parent's toplevel window.
130  **/
131 void
em_utils_edit_filters(EMailSession * session,EAlertSink * alert_sink,GtkWindow * parent_window)132 em_utils_edit_filters (EMailSession *session,
133                        EAlertSink *alert_sink,
134                        GtkWindow *parent_window)
135 {
136 	const gchar *config_dir;
137 	gchar *user, *system;
138 	EMFilterContext *fc;
139 
140 	g_return_if_fail (E_IS_MAIL_SESSION (session));
141 	g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
142 
143 	if (filter_editor) {
144 		gtk_window_present (GTK_WINDOW (filter_editor));
145 		return;
146 	}
147 
148 	config_dir = mail_session_get_config_dir ();
149 
150 	fc = em_filter_context_new (session);
151 	user = g_build_filename (config_dir, "filters.xml", NULL);
152 	system = g_build_filename (EVOLUTION_PRIVDATADIR, "filtertypes.xml", NULL);
153 	e_rule_context_load ((ERuleContext *) fc, system, user);
154 	g_free (user);
155 	g_free (system);
156 
157 	if (((ERuleContext *) fc)->error) {
158 		e_alert_submit (
159 			alert_sink,
160 			"mail:filter-load-error",
161 			((ERuleContext *) fc)->error, NULL);
162 		return;
163 	}
164 
165 	if (em_filter_source_element_names[0].name == NULL) {
166 		em_filter_source_element_names[0].name = _("Incoming");
167 		em_filter_source_element_names[1].name = _("Outgoing");
168 	}
169 
170 	filter_editor = (GtkWidget *) em_filter_editor_new (
171 		fc, em_filter_source_element_names);
172 
173 	if (GTK_IS_WINDOW (parent_window))
174 		gtk_window_set_transient_for (
175 			GTK_WINDOW (filter_editor), parent_window);
176 
177 	gtk_window_set_title (
178 		GTK_WINDOW (filter_editor), _("Message Filters"));
179 	g_object_set_data_full (
180 		G_OBJECT (filter_editor), "context", fc,
181 		(GDestroyNotify) g_object_unref);
182 	g_signal_connect (
183 		filter_editor, "response",
184 		G_CALLBACK (em_filter_editor_response), NULL);
185 	gtk_widget_show (GTK_WIDGET (filter_editor));
186 }
187 
188 /*
189  * Picked this from e-d-s/libedataserver/e-data.
190  * But it allows more characters to occur in filenames, especially
191  * when saving attachment.
192  */
193 void
em_filename_make_safe(gchar * string)194 em_filename_make_safe (gchar *string)
195 {
196 	gchar *p, *ts;
197 	gunichar c;
198 #ifdef G_OS_WIN32
199 	const gchar *unsafe_chars = "/\":*?<>|\\#";
200 #else
201 	const gchar *unsafe_chars = "/#";
202 #endif
203 
204 	g_return_if_fail (string != NULL);
205 	p = string;
206 
207 	while (p && *p) {
208 		c = g_utf8_get_char (p);
209 		ts = p;
210 		p = g_utf8_next_char (p);
211 		/* I wonder what this code is supposed to actually
212 		 * achieve, and whether it does that as currently
213 		 * written?
214 		 */
215 		if (!g_unichar_isprint (c) || (c < 0xff && strchr (unsafe_chars, c&0xff))) {
216 			while (ts < p)
217 				*ts++ = '_';
218 		}
219 	}
220 }
221 
222 /* ********************************************************************** */
223 /* Flag-for-Followup... */
224 
225 /**
226  * em_utils_flag_for_followup:
227  * @reader: an #EMailReader
228  * @folder: folder containing messages to flag
229  * @uids: uids of messages to flag
230  *
231  * Open the Flag-for-Followup editor for the messages specified by
232  * @folder and @uids.
233  **/
234 void
em_utils_flag_for_followup(EMailReader * reader,CamelFolder * folder,GPtrArray * uids)235 em_utils_flag_for_followup (EMailReader *reader,
236                             CamelFolder *folder,
237                             GPtrArray *uids)
238 {
239 	GtkWidget *editor;
240 	GtkWindow *window;
241 	CamelNameValueArray *tags;
242 	guint ii, tags_len;
243 	gint response;
244 
245 	g_return_if_fail (E_IS_MAIL_READER (reader));
246 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
247 	g_return_if_fail (uids != NULL);
248 
249 	window = e_mail_reader_get_window (reader);
250 
251 	editor = e_mail_tag_editor_new ();
252 	gtk_window_set_transient_for (GTK_WINDOW (editor), window);
253 
254 	for (ii = 0; ii < uids->len; ii++) {
255 		CamelMessageInfo *info;
256 
257 		info = camel_folder_get_message_info (folder, uids->pdata[ii]);
258 
259 		if (info == NULL)
260 			continue;
261 
262 		e_mail_tag_editor_add_message (
263 			E_MAIL_TAG_EDITOR (editor),
264 			camel_message_info_get_from (info),
265 			camel_message_info_get_subject (info));
266 
267 		g_clear_object (&info);
268 	}
269 
270 	/* special-case... */
271 	if (uids->len == 1) {
272 		CamelMessageInfo *info;
273 		const gchar *message_uid;
274 
275 		message_uid = g_ptr_array_index (uids, 0);
276 		info = camel_folder_get_message_info (folder, message_uid);
277 		if (info) {
278 			tags = camel_message_info_dup_user_tags (info);
279 
280 			if (tags)
281 				e_mail_tag_editor_set_tag_list (E_MAIL_TAG_EDITOR (editor), tags);
282 
283 			camel_name_value_array_free (tags);
284 			g_clear_object (&info);
285 		}
286 	}
287 
288 	response = gtk_dialog_run (GTK_DIALOG (editor));
289 	if (response != GTK_RESPONSE_OK && response != GTK_RESPONSE_REJECT)
290 		goto exit;
291 
292 	if (response == GTK_RESPONSE_OK) {
293 		tags = e_mail_tag_editor_get_tag_list (E_MAIL_TAG_EDITOR (editor));
294 		if (!tags)
295 			goto exit;
296 	} else {
297 		tags = NULL;
298 	}
299 
300 	tags_len = tags ? camel_name_value_array_get_length (tags) : 0;
301 
302 	camel_folder_freeze (folder);
303 	for (ii = 0; ii < uids->len; ii++) {
304 		CamelMessageInfo *info;
305 		guint jj;
306 
307 		info = camel_folder_get_message_info (folder, uids->pdata[ii]);
308 
309 		if (info == NULL)
310 			continue;
311 
312 		camel_message_info_freeze_notifications (info);
313 
314 		if (response == GTK_RESPONSE_REJECT) {
315 			camel_message_info_set_user_tag (info, "follow-up", NULL);
316 			camel_message_info_set_user_tag (info, "due-by", NULL);
317 			camel_message_info_set_user_tag (info, "completed-on", NULL);
318 		} else {
319 			for (jj = 0; jj < tags_len; jj++) {
320 				const gchar *name = NULL, *value = NULL;
321 
322 				if (!camel_name_value_array_get (tags, jj, &name, &value))
323 					continue;
324 
325 				camel_message_info_set_user_tag (info, name, value);
326 			}
327 		}
328 
329 		camel_message_info_thaw_notifications (info);
330 		g_clear_object (&info);
331 	}
332 
333 	camel_folder_thaw (folder);
334 	camel_name_value_array_free (tags);
335 
336 exit:
337 	gtk_widget_destroy (GTK_WIDGET (editor));
338 }
339 
340 /**
341  * em_utils_flag_for_followup_clear:
342  * @parent: parent window
343  * @folder: folder containing messages to unflag
344  * @uids: uids of messages to unflag
345  *
346  * Clears the Flag-for-Followup flag on the messages referenced by
347  * @folder and @uids.
348  **/
349 void
em_utils_flag_for_followup_clear(GtkWindow * parent,CamelFolder * folder,GPtrArray * uids)350 em_utils_flag_for_followup_clear (GtkWindow *parent,
351                                   CamelFolder *folder,
352                                   GPtrArray *uids)
353 {
354 	gint i;
355 
356 	g_return_if_fail (GTK_IS_WINDOW (parent));
357 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
358 	g_return_if_fail (uids != NULL);
359 
360 	camel_folder_freeze (folder);
361 	for (i = 0; i < uids->len; i++) {
362 		CamelMessageInfo *mi = camel_folder_get_message_info (folder, uids->pdata[i]);
363 
364 		if (mi) {
365 			camel_message_info_freeze_notifications (mi);
366 			camel_message_info_set_user_tag (mi, "follow-up", NULL);
367 			camel_message_info_set_user_tag (mi, "due-by", NULL);
368 			camel_message_info_set_user_tag (mi, "completed-on", NULL);
369 			camel_message_info_thaw_notifications (mi);
370 			g_clear_object (&mi);
371 		}
372 	}
373 
374 	camel_folder_thaw (folder);
375 }
376 
377 /**
378  * em_utils_flag_for_followup_completed:
379  * @parent: parent window
380  * @folder: folder containing messages to 'complete'
381  * @uids: uids of messages to 'complete'
382  *
383  * Sets the completed state (and date/time) for each message
384  * referenced by @folder and @uids that is marked for
385  * Flag-for-Followup.
386  **/
387 void
em_utils_flag_for_followup_completed(GtkWindow * parent,CamelFolder * folder,GPtrArray * uids)388 em_utils_flag_for_followup_completed (GtkWindow *parent,
389                                       CamelFolder *folder,
390                                       GPtrArray *uids)
391 {
392 	gchar *now;
393 	gint i;
394 
395 	g_return_if_fail (GTK_IS_WINDOW (parent));
396 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
397 	g_return_if_fail (uids != NULL);
398 
399 	now = camel_header_format_date (time (NULL), 0);
400 
401 	camel_folder_freeze (folder);
402 	for (i = 0; i < uids->len; i++) {
403 		const gchar *tag;
404 		CamelMessageInfo *mi = camel_folder_get_message_info (folder, uids->pdata[i]);
405 
406 		if (mi) {
407 			tag = camel_message_info_get_user_tag (mi, "follow-up");
408 			if (tag && tag[0])
409 				camel_message_info_set_user_tag (mi, "completed-on", now);
410 			g_clear_object (&mi);
411 		}
412 	}
413 
414 	camel_folder_thaw (folder);
415 
416 	g_free (now);
417 }
418 
419 /* This kind of sucks, because for various reasons most callers need to run
420  * synchronously in the gui thread, however this could take a long, blocking
421  * time to run. */
422 static gint
em_utils_write_messages_to_stream(CamelFolder * folder,GPtrArray * uids,CamelStream * stream)423 em_utils_write_messages_to_stream (CamelFolder *folder,
424                                    GPtrArray *uids,
425                                    CamelStream *stream)
426 {
427 	CamelStream *filtered_stream;
428 	CamelMimeFilter *from_filter;
429 	gint i, res = 0;
430 
431 	from_filter = camel_mime_filter_from_new ();
432 	filtered_stream = camel_stream_filter_new (stream);
433 	camel_stream_filter_add (
434 		CAMEL_STREAM_FILTER (filtered_stream), from_filter);
435 	g_object_unref (from_filter);
436 
437 	for (i = 0; i < uids->len; i++) {
438 		CamelMimeMessage *message;
439 		gchar *from;
440 
441 		/* FIXME camel_folder_get_message_sync() may block. */
442 		message = camel_folder_get_message_sync (
443 			folder, uids->pdata[i], NULL, NULL);
444 		if (message == NULL) {
445 			res = -1;
446 			break;
447 		}
448 
449 		/* We need to flush after each stream write since we are
450 		 * writing to the same stream. */
451 		from = camel_mime_message_build_mbox_from (message);
452 
453 		if (camel_stream_write_string (stream, from, NULL, NULL) == -1
454 		    || camel_stream_flush (stream, NULL, NULL) == -1
455 		    || camel_data_wrapper_write_to_stream_sync (
456 			(CamelDataWrapper *) message, (CamelStream *)
457 			filtered_stream, NULL, NULL) == -1
458 		    || camel_stream_flush (
459 			(CamelStream *) filtered_stream, NULL, NULL) == -1)
460 			res = -1;
461 
462 		g_free (from);
463 		g_object_unref (message);
464 
465 		if (res == -1)
466 			break;
467 	}
468 
469 	g_object_unref (filtered_stream);
470 
471 	return res;
472 }
473 
474 static gboolean
em_utils_print_messages_to_file(CamelFolder * folder,const gchar * uid,const gchar * filename)475 em_utils_print_messages_to_file (CamelFolder *folder,
476                                  const gchar *uid,
477                                  const gchar *filename)
478 {
479 	EMailParser *parser;
480 	EMailPartList *parts_list;
481 	CamelMimeMessage *message;
482 	CamelStore *parent_store;
483 	CamelSession *session;
484 	gboolean success = FALSE;
485 
486 	message = camel_folder_get_message_sync (folder, uid, NULL, NULL);
487 	if (message == NULL)
488 		return FALSE;
489 
490 	parent_store = camel_folder_get_parent_store (folder);
491 	session = camel_service_ref_session (CAMEL_SERVICE (parent_store));
492 
493 	parser = e_mail_parser_new (session);
494 
495 	/* XXX em_utils_selection_set_urilist() is synchronous,
496 	 *     so this function has to be synchronous as well.
497 	 *     That means potentially blocking for awhile. */
498 	parts_list = e_mail_parser_parse_sync (
499 		parser, folder, uid, message, NULL);
500 	if (parts_list != NULL) {
501 		EMailBackend *mail_backend;
502 		EAsyncClosure *closure;
503 		GAsyncResult *result;
504 		EMailPrinter *printer;
505 		GtkPrintOperationResult print_result;
506 
507 		mail_backend = E_MAIL_BACKEND (e_shell_get_backend_by_name (e_shell_get_default (), "mail"));
508 		g_return_val_if_fail (mail_backend != NULL, FALSE);
509 
510 		printer = e_mail_printer_new (parts_list, e_mail_backend_get_remote_content (mail_backend));
511 		e_mail_printer_set_export_filename (printer, filename);
512 
513 		closure = e_async_closure_new ();
514 
515 		e_mail_printer_print (
516 			printer, GTK_PRINT_OPERATION_ACTION_EXPORT,
517 			NULL, NULL, e_async_closure_callback, closure);
518 
519 		result = e_async_closure_wait (closure);
520 
521 		print_result = e_mail_printer_print_finish (
522 			printer, result, NULL);
523 
524 		e_async_closure_free (closure);
525 
526 		g_object_unref (printer);
527 		g_object_unref (parts_list);
528 
529 		success = (print_result != GTK_PRINT_OPERATION_RESULT_ERROR);
530 	}
531 
532 	g_object_unref (parser);
533 	g_object_unref (session);
534 
535 	return success;
536 }
537 
538 /* This kind of sucks, because for various reasons most callers need to run
539  * synchronously in the gui thread, however this could take a long, blocking
540  * time to run. */
541 static gint
em_utils_read_messages_from_stream(CamelFolder * folder,CamelStream * stream)542 em_utils_read_messages_from_stream (CamelFolder *folder,
543                                     CamelStream *stream)
544 {
545 	CamelMimeParser *mp = camel_mime_parser_new ();
546 	gboolean success = TRUE;
547 	gboolean any_read = FALSE;
548 
549 	camel_mime_parser_scan_from (mp, TRUE);
550 	camel_mime_parser_init_with_stream (mp, stream, NULL);
551 
552 	while (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM) {
553 		CamelMimeMessage *msg;
554 
555 		any_read = TRUE;
556 
557 		/* NB: de-from filter, once written */
558 		msg = camel_mime_message_new ();
559 		if (!camel_mime_part_construct_from_parser_sync (
560 			(CamelMimePart *) msg, mp, NULL, NULL)) {
561 			g_object_unref (msg);
562 			break;
563 		}
564 
565 		/* FIXME camel_folder_append_message_sync() may block. */
566 		success = camel_folder_append_message_sync (
567 			folder, msg, NULL, NULL, NULL, NULL);
568 		g_object_unref (msg);
569 
570 		if (!success)
571 			break;
572 
573 		camel_mime_parser_step (mp, NULL, NULL);
574 	}
575 
576 	g_object_unref (mp);
577 
578 	/* No message had bean read, maybe it's not MBOX, but a plain message */
579 	if (!any_read) {
580 		CamelMimeMessage *msg;
581 
582 		if (G_IS_SEEKABLE (stream))
583 			g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, NULL, NULL);
584 
585 		msg = camel_mime_message_new ();
586 		if (camel_data_wrapper_construct_from_stream_sync (
587 			(CamelDataWrapper *) msg, stream, NULL, NULL))
588 			/* FIXME camel_folder_append_message_sync() may block. */
589 			camel_folder_append_message_sync (
590 				folder, msg, NULL, NULL, NULL, NULL);
591 		g_object_unref (msg);
592 	}
593 
594 	return success ? 0 : -1;
595 }
596 
597 /**
598  * em_utils_selection_set_mailbox:
599  * @data: selection data
600  * @folder: folder containign messages to copy into the selection
601  * @uids: uids of the messages to copy into the selection
602  *
603  * Creates a mailbox-format selection.
604  * Warning: Could be BIG!
605  * Warning: This could block the ui for an extended period.
606  **/
607 void
em_utils_selection_set_mailbox(GtkSelectionData * data,CamelFolder * folder,GPtrArray * uids)608 em_utils_selection_set_mailbox (GtkSelectionData *data,
609                                 CamelFolder *folder,
610                                 GPtrArray *uids)
611 {
612 	GByteArray *byte_array;
613 	CamelStream *stream;
614 	GdkAtom target;
615 
616 	target = gtk_selection_data_get_target (data);
617 
618 	byte_array = g_byte_array_new ();
619 	stream = camel_stream_mem_new_with_byte_array (byte_array);
620 
621 	if (em_utils_write_messages_to_stream (folder, uids, stream) == 0)
622 		gtk_selection_data_set (
623 			data, target, 8,
624 			byte_array->data, byte_array->len);
625 
626 	g_object_unref (stream);
627 }
628 
629 /**
630  * em_utils_selection_get_mailbox:
631  * @selection_data: selection data
632  * @folder:
633  *
634  * Receive a mailbox selection/dnd
635  * Warning: Could be BIG!
636  * Warning: This could block the ui for an extended period.
637  * FIXME: Exceptions?
638  **/
639 void
em_utils_selection_get_mailbox(GtkSelectionData * selection_data,CamelFolder * folder)640 em_utils_selection_get_mailbox (GtkSelectionData *selection_data,
641                                 CamelFolder *folder)
642 {
643 	CamelStream *stream;
644 	const guchar *data;
645 	gint length;
646 
647 	data = gtk_selection_data_get_data (selection_data);
648 	length = gtk_selection_data_get_length (selection_data);
649 
650 	if (data == NULL || length == -1)
651 		return;
652 
653 	/* TODO: a stream mem with read-only access to existing data? */
654 	/* NB: Although copying would let us run this async ... which it should */
655 	stream = (CamelStream *)
656 		camel_stream_mem_new_with_buffer ((gchar *) data, length);
657 	em_utils_read_messages_from_stream (folder, stream);
658 	g_object_unref (stream);
659 }
660 
661 /**
662  * em_utils_selection_get_message:
663  * @selection_data:
664  * @folder:
665  *
666  * get a message/rfc822 data.
667  **/
668 void
em_utils_selection_get_message(GtkSelectionData * selection_data,CamelFolder * folder)669 em_utils_selection_get_message (GtkSelectionData *selection_data,
670                                 CamelFolder *folder)
671 {
672 	CamelStream *stream;
673 	const guchar *data;
674 	gint length;
675 
676 	data = gtk_selection_data_get_data (selection_data);
677 	length = gtk_selection_data_get_length (selection_data);
678 
679 	if (data == NULL || length == -1)
680 		return;
681 
682 	stream = camel_stream_mem_new_with_buffer ((const gchar *) data, length);
683 
684 	em_utils_read_messages_from_stream (folder, stream);
685 
686 	g_object_unref (stream);
687 }
688 
689 /**
690  * em_utils_selection_set_uidlist:
691  * @selection_data: selection data
692  * @folder:
693  * @uids:
694  *
695  * Sets a "x-uid-list" format selection data.
696  **/
697 void
em_utils_selection_set_uidlist(GtkSelectionData * selection_data,CamelFolder * folder,GPtrArray * uids)698 em_utils_selection_set_uidlist (GtkSelectionData *selection_data,
699                                 CamelFolder *folder,
700                                 GPtrArray *uids)
701 {
702 	GByteArray *array = g_byte_array_new ();
703 	GdkAtom target;
704 	gchar *folder_uri;
705 	gint ii;
706 
707 	/* format: "uri1\0uid1\0uri2\0uid2\0...\0urin\0uidn\0" */
708 
709 	if (CAMEL_IS_VEE_FOLDER (folder) &&
710 	    CAMEL_IS_VEE_STORE (camel_folder_get_parent_store (folder))) {
711 		CamelVeeFolder *vfolder = CAMEL_VEE_FOLDER (folder);
712 		CamelFolder *real_folder;
713 		CamelMessageInfo *info;
714 		gchar *real_uid;
715 
716 		for (ii = 0; ii < uids->len; ii++) {
717 			info = camel_folder_get_message_info (folder, uids->pdata[ii]);
718 			if (!info) {
719 				g_warn_if_reached ();
720 				continue;
721 			}
722 
723 			real_folder = camel_vee_folder_get_location (
724 				vfolder, (CamelVeeMessageInfo *) info, &real_uid);
725 
726 			if (real_folder) {
727 				folder_uri = e_mail_folder_uri_from_folder (real_folder);
728 
729 				g_byte_array_append (array, (guchar *) folder_uri, strlen (folder_uri) + 1);
730 				g_byte_array_append (array, (guchar *) real_uid, strlen (real_uid) + 1);
731 
732 				g_free (folder_uri);
733 			}
734 
735 			g_clear_object (&info);
736 		}
737 	} else {
738 		folder_uri = e_mail_folder_uri_from_folder (folder);
739 
740 		for (ii = 0; ii < uids->len; ii++) {
741 			g_byte_array_append (array, (guchar *) folder_uri, strlen (folder_uri) + 1);
742 			g_byte_array_append (array, uids->pdata[ii], strlen (uids->pdata[ii]) + 1);
743 		}
744 
745 		g_free (folder_uri);
746 	}
747 
748 	target = gtk_selection_data_get_target (selection_data);
749 	gtk_selection_data_set (
750 		selection_data, target, 8, array->data, array->len);
751 	g_byte_array_free (array, TRUE);
752 }
753 
754 /**
755  * em_utils_selection_uidlist_foreach_sync:
756  * @selection_data: a #GtkSelectionData with x-uid-list content
757  * @session: an #EMailSession
758  * @func: a function to call for each UID and its folder
759  * @user_data: user data for @func
760  * @cancellable: optional #GCancellable object, or %NULL
761  * @error: return location for a #GError, or %NULL
762  *
763  * Calls @func for each folder and UID provided in @selection_data.
764  *
765  * Warning: Could take some time to run.
766  *
767  * Since: 3.28
768  **/
769 void
em_utils_selection_uidlist_foreach_sync(GtkSelectionData * selection_data,EMailSession * session,EMUtilsUIDListFunc func,gpointer user_data,GCancellable * cancellable,GError ** error)770 em_utils_selection_uidlist_foreach_sync (GtkSelectionData *selection_data,
771 					 EMailSession *session,
772 					 EMUtilsUIDListFunc func,
773 					 gpointer user_data,
774 					 GCancellable *cancellable,
775 					 GError **error)
776 {
777 	/* format: "uri1\0uid1\0uri2\0uid2\0...\0urin\0uidn\0" */
778 	gchar *inptr, *inend;
779 	GPtrArray *items;
780 	CamelFolder *folder;
781 	const guchar *data;
782 	gint length, ii;
783 	GHashTable *uids_by_uri;
784 	GHashTableIter iter;
785 	gpointer key, value;
786 	gboolean can_continue = TRUE;
787 	GError *local_error = NULL;
788 
789 	g_return_if_fail (selection_data != NULL);
790 	g_return_if_fail (E_IS_MAIL_SESSION (session));
791 	g_return_if_fail (func != NULL);
792 
793 	data = gtk_selection_data_get_data (selection_data);
794 	length = gtk_selection_data_get_length (selection_data);
795 
796 	if (data == NULL || length == -1)
797 		return;
798 
799 	items = g_ptr_array_new ();
800 	g_ptr_array_set_free_func (items, (GDestroyNotify) g_free);
801 
802 	inptr = (gchar *) data;
803 	inend = (gchar *) (data + length);
804 	while (inptr < inend) {
805 		gchar *start = inptr;
806 
807 		while (inptr < inend && *inptr)
808 			inptr++;
809 
810 		g_ptr_array_add (items, g_strndup (start, inptr - start));
811 
812 		inptr++;
813 	}
814 
815 	if (items->len == 0) {
816 		g_ptr_array_unref (items);
817 		return;
818 	}
819 
820 	uids_by_uri = g_hash_table_new (g_str_hash, g_str_equal);
821 	for (ii = 0; ii < items->len - 1; ii += 2) {
822 		gchar *uri, *uid;
823 		GPtrArray *uids;
824 
825 		uri = items->pdata[ii];
826 		uid = items->pdata[ii + 1];
827 
828 		uids = g_hash_table_lookup (uids_by_uri, uri);
829 		if (!uids) {
830 			uids = g_ptr_array_new ();
831 			g_hash_table_insert (uids_by_uri, uri, uids);
832 		}
833 
834 		/* reuse uid pointer from uids, do not strdup it */
835 		g_ptr_array_add (uids, uid);
836 	}
837 
838 	g_hash_table_iter_init (&iter, uids_by_uri);
839 	while (g_hash_table_iter_next (&iter, &key, &value)) {
840 		const gchar *uri = key;
841 		GPtrArray *uids = value;
842 
843 		if (!local_error && can_continue) {
844 			/* FIXME e_mail_session_uri_to_folder_sync() may block. */
845 			folder = e_mail_session_uri_to_folder_sync (
846 				session, uri, 0, cancellable, &local_error);
847 			if (folder) {
848 				can_continue = func (folder, uids, user_data, cancellable, &local_error);
849 				g_object_unref (folder);
850 			}
851 		}
852 
853 		g_ptr_array_free (uids, TRUE);
854 	}
855 
856 	g_hash_table_destroy (uids_by_uri);
857 	g_ptr_array_unref (items);
858 
859 	if (local_error)
860 		g_propagate_error (error, local_error);
861 }
862 
863 struct UIDListData {
864 	CamelFolder *dest;
865 	gboolean move;
866 };
867 
868 static gboolean
uidlist_move_uids_cb(CamelFolder * folder,const GPtrArray * uids,gpointer user_data,GCancellable * cancellable,GError ** error)869 uidlist_move_uids_cb (CamelFolder *folder,
870 		      const GPtrArray *uids,
871 		      gpointer user_data,
872 		      GCancellable *cancellable,
873 		      GError **error)
874 {
875 	struct UIDListData *uld = user_data;
876 
877 	g_return_val_if_fail (uld != NULL, FALSE);
878 
879 	/* FIXME camel_folder_transfer_messages_to_sync() may block. */
880 	return camel_folder_transfer_messages_to_sync (
881 		folder, (GPtrArray *) uids, uld->dest, uld->move, NULL, cancellable, error);
882 }
883 
884 /**
885  * em_utils_selection_get_uidlist:
886  * @data: selection data
887  * @session: an #EMailSession
888  * @move: do we delete the messages.
889  *
890  * Convert a uid list into a copy/move operation.
891  *
892  * Warning: Could take some time to run.
893  **/
894 void
em_utils_selection_get_uidlist(GtkSelectionData * selection_data,EMailSession * session,CamelFolder * dest,gint move,GCancellable * cancellable,GError ** error)895 em_utils_selection_get_uidlist (GtkSelectionData *selection_data,
896                                 EMailSession *session,
897                                 CamelFolder *dest,
898                                 gint move,
899                                 GCancellable *cancellable,
900                                 GError **error)
901 {
902 	struct UIDListData uld;
903 
904 	g_return_if_fail (CAMEL_IS_FOLDER (dest));
905 
906 	uld.dest = dest;
907 	uld.move = move;
908 
909 	em_utils_selection_uidlist_foreach_sync	(selection_data, session, uidlist_move_uids_cb, &uld, cancellable, error);
910 }
911 
912 /**
913  * em_utils_build_export_basename:
914  * @folder: a #CamelFolder where the message belongs
915  * @uid: a message UID
916  * @extension: (nullable): a filename extension
917  *
918  * Builds a name that consists of data and time when the message was received,
919  * message subject and extension.
920  *
921  * Returns: (transfer full): a newly allocated string with generated basename
922  *
923  * Since: 3.22
924  **/
925 gchar *
em_utils_build_export_basename(CamelFolder * folder,const gchar * uid,const gchar * extension)926 em_utils_build_export_basename (CamelFolder *folder,
927                                 const gchar *uid,
928                                 const gchar *extension)
929 {
930 	CamelMessageInfo *info;
931 	gchar *basename;
932 	const gchar *subject = NULL;
933 	struct tm *ts;
934 	time_t reftime;
935 	gchar datetmp[15];
936 
937 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
938 	g_return_val_if_fail (uid != NULL, NULL);
939 
940 	reftime = time (NULL);
941 
942 	/* Try to get the drop filename from the message or folder. */
943 	info = camel_folder_get_message_info (folder, uid);
944 	if (info != NULL) {
945 		subject = camel_message_info_get_subject (info);
946 		reftime = camel_message_info_get_date_sent (info);
947 	}
948 
949 	ts = localtime (&reftime);
950 	strftime (datetmp, sizeof (datetmp), "%Y%m%d%H%M%S", ts);
951 
952 	if (subject == NULL || *subject == '\0')
953 		subject = "Untitled Message";
954 
955 	if (extension == NULL)
956 		extension = "";
957 
958 	basename = g_strdup_printf ("%s_%s%s", datetmp, subject, extension);
959 
960 	g_clear_object (&info);
961 
962 	return basename;
963 }
964 
965 /**
966  * em_utils_selection_set_urilist:
967  * @data:
968  * @folder:
969  * @uids:
970  *
971  * Set the selection data @data to a uri which points to a file, which is
972  * a berkely mailbox format mailbox.  The file is automatically cleaned
973  * up when the application quits.
974  **/
975 void
em_utils_selection_set_urilist(GtkSelectionData * data,CamelFolder * folder,GPtrArray * uids)976 em_utils_selection_set_urilist (GtkSelectionData *data,
977                                 CamelFolder *folder,
978                                 GPtrArray *uids)
979 {
980 	gchar *tmpdir;
981 	gchar *uri;
982 	gint fd;
983 	/* This is waiting for https://bugs.webkit.org/show_bug.cgi?id=212814 */
984 	#if 0
985 	GSettings *settings;
986 	gchar *save_file_format;
987 	#endif
988 	gboolean save_as_mbox;
989 
990 	g_return_if_fail (uids != NULL);
991 
992 	/* can be 0 with empty folders */
993 	if (!uids->len)
994 		return;
995 
996 	tmpdir = e_mkdtemp ("drag-n-drop-XXXXXX");
997 	if (tmpdir == NULL)
998 		return;
999 
1000 	/* This is waiting for https://bugs.webkit.org/show_bug.cgi?id=212814 */
1001 	#if 0
1002 	settings = e_util_ref_settings ("org.gnome.evolution.mail");
1003 
1004 	/* Save format is mbox unless pdf is explicitly requested. */
1005 	save_file_format = g_settings_get_string (
1006 		settings, "drag-and-drop-save-file-format");
1007 	save_as_mbox = (g_strcmp0 (save_file_format, "pdf") != 0);
1008 	g_free (save_file_format);
1009 
1010 	g_object_unref (settings);
1011 	#else
1012 	save_as_mbox = TRUE;
1013 	#endif
1014 
1015 	if (save_as_mbox) {
1016 		CamelStream *fstream;
1017 		gchar *basename;
1018 		gchar *filename;
1019 
1020 		if (uids->len > 1) {
1021 			basename = g_strdup_printf (
1022 				_("Messages from %s"),
1023 				camel_folder_get_display_name (folder));
1024 		} else {
1025 			basename = em_utils_build_export_basename (
1026 				folder, uids->pdata[0], NULL);
1027 		}
1028 		e_util_make_safe_filename (basename);
1029 		filename = g_build_filename (tmpdir, basename, NULL);
1030 		g_free (basename);
1031 
1032 		fd = g_open (
1033 			filename,
1034 			O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0666);
1035 		if (fd == -1) {
1036 			g_free (filename);
1037 			goto exit;
1038 		}
1039 
1040 		uri = g_filename_to_uri (filename, NULL, NULL);
1041 		fstream = camel_stream_fs_new_with_fd (fd);
1042 		if (fstream != NULL) {
1043 			if (em_utils_write_messages_to_stream (folder, uids, fstream) == 0) {
1044 				GdkAtom type;
1045 				gchar *uri_crlf;
1046 
1047 				/* terminate with \r\n to be compliant with the spec */
1048 				uri_crlf = g_strconcat (uri, "\r\n", NULL);
1049 
1050 				type = gtk_selection_data_get_target (data);
1051 				gtk_selection_data_set (
1052 					data, type, 8,
1053 					(guchar *) uri_crlf,
1054 					strlen (uri_crlf));
1055 				g_free (uri_crlf);
1056 			}
1057 			g_object_unref (fstream);
1058 		} else
1059 			close (fd);
1060 
1061 		g_free (filename);
1062 		g_free (uri);
1063 
1064 	} else {  /* save as pdf */
1065 		gchar **uris;
1066 		guint n_uris = 0;
1067 		guint ii;
1068 
1069 		uris = g_new0 (gchar *, uids->len + 1);
1070 		for (ii = 0; ii < uids->len; ii++) {
1071 			gchar *basename;
1072 			gchar *filename;
1073 			gboolean success;
1074 
1075 			basename = em_utils_build_export_basename (
1076 				folder, uids->pdata[ii], ".pdf");
1077 			e_util_make_safe_filename (basename);
1078 			filename = g_build_filename (tmpdir, basename, NULL);
1079 			g_free (basename);
1080 
1081 			/* validity test */
1082 			fd = g_open (
1083 				filename,
1084 				O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0666);
1085 			if (fd == -1) {
1086 				g_strfreev (uris);
1087 				g_free (filename);
1088 				goto exit;
1089 			}
1090 			close (fd);
1091 
1092 			/* export */
1093 			success = em_utils_print_messages_to_file (
1094 				folder, uids->pdata[ii], filename);
1095 			if (success) {
1096 				/* terminate with \r\n to be compliant with the spec */
1097 				uri = g_filename_to_uri (filename, NULL, NULL);
1098 				uris[n_uris++] = g_strconcat (uri, "\r\n", NULL);
1099 				g_free (uri);
1100 			}
1101 
1102 			g_free (filename);
1103 		}
1104 
1105 		gtk_selection_data_set_uris (data, uris);
1106 
1107 		g_strfreev (uris);
1108 	}
1109 
1110 exit:
1111 	g_free (tmpdir);
1112 	/* the 'fd' from the 'save_as_mbox' part is freed within the 'fstream' */
1113 	/* coverity[leaked_handle] */
1114 }
1115 
1116 /**
1117  * em_utils_selection_get_urilist:
1118  * @data:
1119  * @folder:
1120  * @uids:
1121  *
1122  * Get the selection data @data from a uri list which points to a
1123  * file, which is a berkely mailbox format mailbox.  The file is
1124  * automatically cleaned up when the application quits.
1125  **/
1126 void
em_utils_selection_get_urilist(GtkSelectionData * selection_data,CamelFolder * folder)1127 em_utils_selection_get_urilist (GtkSelectionData *selection_data,
1128                                 CamelFolder *folder)
1129 {
1130 	CamelStream *stream;
1131 	CamelURL *url;
1132 	gint fd, i, res = 0;
1133 	gchar **uris;
1134 
1135 	d (printf (" * drop uri list\n"));
1136 
1137 	uris = gtk_selection_data_get_uris (selection_data);
1138 
1139 	for (i = 0; res == 0 && uris[i]; i++) {
1140 		g_strstrip (uris[i]);
1141 		if (uris[i][0] == '#')
1142 			continue;
1143 
1144 		url = camel_url_new (uris[i], NULL);
1145 		if (url == NULL)
1146 			continue;
1147 
1148 		/* 'fd', if set, is freed within the 'stream' */
1149 		/* coverity[overwrite_var] */
1150 		if (strcmp (url->protocol, "file") == 0
1151 		    && (fd = g_open (url->path, O_RDONLY | O_BINARY, 0)) != -1) {
1152 			stream = camel_stream_fs_new_with_fd (fd);
1153 			if (stream) {
1154 				res = em_utils_read_messages_from_stream (folder, stream);
1155 				g_object_unref (stream);
1156 			} else
1157 				close (fd);
1158 		}
1159 		camel_url_free (url);
1160 	}
1161 
1162 	g_strfreev (uris);
1163 
1164 	/* 'fd', if set, is freed within the 'stream' */
1165 	/* coverity[leaked_handle] */
1166 }
1167 
1168 /* ********************************************************************** */
1169 
1170 static gboolean
is_only_text_part_in_this_level(GList * parts,EMailPart * text_html_part)1171 is_only_text_part_in_this_level (GList *parts,
1172                                  EMailPart *text_html_part)
1173 {
1174 	const gchar *text_html_part_id;
1175 	const gchar *dot;
1176 	gint level_len;
1177 	GList *iter;
1178 
1179 	g_return_val_if_fail (parts != NULL, FALSE);
1180 	g_return_val_if_fail (text_html_part != NULL, FALSE);
1181 
1182 	text_html_part_id = e_mail_part_get_id (text_html_part);
1183 
1184 	dot = strrchr (text_html_part_id, '.');
1185 	if (!dot)
1186 		return FALSE;
1187 
1188 	level_len = dot - text_html_part_id;
1189 	for (iter = parts; iter; iter = iter->next) {
1190 		EMailPart *part = E_MAIL_PART (iter->data);
1191 		const gchar *mime_type;
1192 		const gchar *part_id;
1193 
1194 		if (part == NULL)
1195 			continue;
1196 
1197 		if (part == text_html_part)
1198 			continue;
1199 
1200 		if (part->is_hidden)
1201 			continue;
1202 
1203 		if (e_mail_part_get_is_attachment (part))
1204 			continue;
1205 
1206 		mime_type = e_mail_part_get_mime_type (part);
1207 		if (mime_type == NULL)
1208 			continue;
1209 
1210 		part_id = e_mail_part_get_id (part);
1211 		dot = strrchr (part_id, '.');
1212 		if (dot - part_id != level_len ||
1213 		    strncmp (text_html_part_id, part_id, level_len) != 0)
1214 			continue;
1215 
1216 		if (g_ascii_strncasecmp (mime_type, "text/", 5) == 0)
1217 			return FALSE;
1218 	}
1219 
1220 	return TRUE;
1221 }
1222 
1223 /**
1224  * em_utils_message_to_html:
1225  * @session: a #CamelSession
1226  * @message: a #CamelMimeMessage
1227  * @credits: (nullable): credits attribution string when quoting, or %NULL
1228  * @flags: the %EMFormatQuote flags
1229  * @part_list: (nullable): an #EMailPartList
1230  * @prepend: (nulalble): text to prepend, or %NULL
1231  * @append: (nullable): text to append, or %NULL
1232  * @validity_found: (nullable): if not %NULL, then here will be set what validities
1233  *         had been found during message conversion. Value is a bit OR
1234  *         of EM_FORMAT_VALIDITY_FOUND_* constants.
1235  *
1236  * Convert a message to html, quoting if the @credits attribution
1237  * string is given.
1238  *
1239  * Return value: The html version as a NULL terminated string.
1240  *
1241  * See: em_utils_message_to_html_ex
1242  **/
1243 gchar *
em_utils_message_to_html(CamelSession * session,CamelMimeMessage * message,const gchar * credits,guint32 flags,EMailPartList * part_list,const gchar * prepend,const gchar * append,EMailPartValidityFlags * validity_found)1244 em_utils_message_to_html (CamelSession *session,
1245                           CamelMimeMessage *message,
1246                           const gchar *credits,
1247                           guint32 flags,
1248                           EMailPartList *part_list,
1249                           const gchar *prepend,
1250                           const gchar *append,
1251                           EMailPartValidityFlags *validity_found)
1252 {
1253 	return em_utils_message_to_html_ex (session, message, credits, flags, part_list, prepend, append, validity_found, NULL);
1254 }
1255 
1256 /**
1257  * em_utils_message_to_html_ex:
1258  * @session: a #CamelSession
1259  * @message: a #CamelMimeMessage
1260  * @credits: (nullable): credits attribution string when quoting, or %NULL
1261  * @flags: the %EMFormatQuote flags
1262  * @part_list: (nullable): an #EMailPartList
1263  * @prepend: (nulalble): text to prepend, or %NULL
1264  * @append: (nullable): text to append, or %NULL
1265  * @validity_found: (nullable): if not %NULL, then here will be set what validities
1266  *         had been found during message conversion. Value is a bit OR
1267  *         of EM_FORMAT_VALIDITY_FOUND_* constants.
1268  * @out_part_list: (nullable): if not %NULL, sets it to the part list being
1269  *         used to generate the body. Unref it with g_object_unref(),
1270  *         when no longer needed.
1271  *
1272  * Convert a message to html, quoting if the @credits attribution
1273  * string is given.
1274  *
1275  * Return value: The html version as a NULL terminated string.
1276  *
1277  * Since: 3.42
1278  **/
1279 gchar *
em_utils_message_to_html_ex(CamelSession * session,CamelMimeMessage * message,const gchar * credits,guint32 flags,EMailPartList * part_list,const gchar * prepend,const gchar * append,EMailPartValidityFlags * validity_found,EMailPartList ** out_part_list)1280 em_utils_message_to_html_ex (CamelSession *session,
1281                              CamelMimeMessage *message,
1282                              const gchar *credits,
1283                              guint32 flags,
1284                              EMailPartList *part_list,
1285                              const gchar *prepend,
1286                              const gchar *append,
1287                              EMailPartValidityFlags *validity_found,
1288 			     EMailPartList **out_part_list)
1289 {
1290 	EMailFormatter *formatter;
1291 	EMailParser *parser = NULL;
1292 	GOutputStream *stream;
1293 	EShell *shell;
1294 	GtkWindow *window;
1295 	EMailPart *hidden_text_html_part = NULL;
1296 	EMailPartValidityFlags is_validity_found = 0;
1297 	gsize n_bytes_written = 0;
1298 	GQueue queue = G_QUEUE_INIT;
1299 	GList *head, *link;
1300 	gboolean found_text_part = FALSE;
1301 	gchar *data;
1302 
1303 	shell = e_shell_get_default ();
1304 	window = e_shell_get_active_window (shell);
1305 
1306 	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
1307 
1308 	stream = g_memory_output_stream_new_resizable ();
1309 
1310 	formatter = e_mail_formatter_quote_new (credits, flags);
1311 	e_mail_formatter_update_style (formatter,
1312 		gtk_widget_get_state_flags (GTK_WIDGET (window)));
1313 
1314 	if (part_list == NULL) {
1315 		GSettings *settings;
1316 		gchar *charset;
1317 
1318 		/* FIXME We should be getting this from the
1319 		 *       current view, not the global setting. */
1320 		settings = e_util_ref_settings ("org.gnome.evolution.mail");
1321 		charset = g_settings_get_string (settings, "charset");
1322 		if (charset && *charset)
1323 			e_mail_formatter_set_default_charset (formatter, charset);
1324 		g_object_unref (settings);
1325 		g_free (charset);
1326 
1327 		parser = e_mail_parser_new (session);
1328 		part_list = e_mail_parser_parse_sync (parser, NULL, NULL, message, NULL);
1329 	} else {
1330 		g_object_ref (part_list);
1331 	}
1332 
1333 	/* Return all found validities and possibly show hidden prefer-plain part */
1334 	e_mail_part_list_queue_parts (part_list, NULL, &queue);
1335 	head = g_queue_peek_head_link (&queue);
1336 
1337 	for (link = head; link != NULL; link = g_list_next (link)) {
1338 		EMailPart *part = link->data;
1339 		const gchar *mime_type;
1340 
1341 		mime_type = e_mail_part_get_mime_type (part);
1342 
1343 		/* prefer-plain can hide HTML parts, even when it's the only
1344 		 * text part in the email, thus show it (and hide again later) */
1345 		if (!found_text_part && !hidden_text_html_part &&
1346 		    mime_type != NULL &&
1347 		    !e_mail_part_get_is_attachment (part)) {
1348 			if (!part->is_hidden &&
1349 			    g_ascii_strcasecmp (mime_type, "text/plain") == 0) {
1350 				found_text_part = TRUE;
1351 			} else if (g_ascii_strcasecmp (mime_type, "text/html") == 0) {
1352 				if (!part->is_hidden) {
1353 					found_text_part = TRUE;
1354 				} else if (is_only_text_part_in_this_level (head, part)) {
1355 					part->is_hidden = FALSE;
1356 					hidden_text_html_part = part;
1357 				}
1358 			}
1359 		}
1360 
1361 		is_validity_found |= e_mail_part_get_validity_flags (part);
1362 	}
1363 
1364 	while (!g_queue_is_empty (&queue))
1365 		g_object_unref (g_queue_pop_head (&queue));
1366 
1367 	if (validity_found != NULL)
1368 		*validity_found = is_validity_found;
1369 
1370 	if (prepend != NULL && *prepend != '\0')
1371 		g_output_stream_write_all (
1372 			stream, prepend, strlen (prepend), NULL, NULL, NULL);
1373 
1374 	e_mail_formatter_format_sync (
1375 		formatter, part_list, stream, 0,
1376 		E_MAIL_FORMATTER_MODE_PRINTING, NULL);
1377 	g_object_unref (formatter);
1378 
1379 	if (hidden_text_html_part != NULL)
1380 		hidden_text_html_part->is_hidden = TRUE;
1381 
1382 	if (out_part_list)
1383 		*out_part_list = part_list;
1384 	else
1385 		g_object_unref (part_list);
1386 
1387 	g_clear_object (&parser);
1388 
1389 	if (append != NULL && *append != '\0')
1390 		g_output_stream_write_all (
1391 			stream, append, strlen (append), NULL, NULL, NULL);
1392 
1393 	g_output_stream_write_all (stream, "", 1, &n_bytes_written, NULL, NULL);
1394 
1395 	g_output_stream_close (stream, NULL, NULL);
1396 
1397 	data = g_memory_output_stream_steal_data (
1398 		G_MEMORY_OUTPUT_STREAM (stream));
1399 
1400 	g_object_unref (stream);
1401 
1402 	return data;
1403 }
1404 
1405 /* ********************************************************************** */
1406 
1407 /**
1408  * em_utils_empty_trash:
1409  * @parent: parent window
1410  * @session: an #EMailSession
1411  *
1412  * Empties all Trash folders.
1413  **/
1414 void
em_utils_empty_trash(GtkWidget * parent,EMailSession * session)1415 em_utils_empty_trash (GtkWidget *parent,
1416                       EMailSession *session)
1417 {
1418 	ESourceRegistry *registry;
1419 	GList *list, *link;
1420 
1421 	g_return_if_fail (E_IS_MAIL_SESSION (session));
1422 
1423 	registry = e_mail_session_get_registry (session);
1424 
1425 	if (!e_util_prompt_user ((GtkWindow *) parent,
1426 		"org.gnome.evolution.mail",
1427 		"prompt-on-empty-trash",
1428 		"mail:ask-empty-trash", NULL))
1429 		return;
1430 
1431 	list = camel_session_list_services (CAMEL_SESSION (session));
1432 
1433 	for (link = list; link != NULL; link = g_list_next (link)) {
1434 		CamelProvider *provider;
1435 		CamelService *service;
1436 		ESource *source;
1437 		const gchar *uid;
1438 		gboolean enabled = TRUE;
1439 
1440 		service = CAMEL_SERVICE (link->data);
1441 		provider = camel_service_get_provider (service);
1442 		uid = camel_service_get_uid (service);
1443 
1444 		if (!CAMEL_IS_STORE (service))
1445 			continue;
1446 
1447 		if ((provider->flags & CAMEL_PROVIDER_IS_STORAGE) == 0)
1448 			continue;
1449 
1450 		source = e_source_registry_ref_source (registry, uid);
1451 
1452 		if (source != NULL) {
1453 			enabled = e_source_registry_check_enabled (
1454 				registry, source);
1455 			g_object_unref (source);
1456 		}
1457 
1458 		if (enabled)
1459 			mail_empty_trash (CAMEL_STORE (service));
1460 	}
1461 
1462 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
1463 }
1464 
1465 /* ********************************************************************** */
1466 
1467 gchar *
em_utils_url_unescape_amp(const gchar * url)1468 em_utils_url_unescape_amp (const gchar *url)
1469 {
1470 	gchar *buff;
1471 	gint i, j, amps;
1472 
1473 	if (!url)
1474 		return NULL;
1475 
1476 	amps = 0;
1477 	for (i = 0; url[i]; i++) {
1478 		if (url[i] == '&' && strncmp (url + i, "&amp;", 5) == 0)
1479 			amps++;
1480 	}
1481 
1482 	buff = g_strdup (url);
1483 
1484 	if (!amps)
1485 		return buff;
1486 
1487 	for (i = 0, j = 0; url[i]; i++, j++) {
1488 		buff[j] = url[i];
1489 
1490 		if (url[i] == '&' && strncmp (url + i, "&amp;", 5) == 0)
1491 			i += 4;
1492 	}
1493 	buff[j] = 0;
1494 
1495 	return buff;
1496 }
1497 
1498 void
emu_restore_folder_tree_state(EMFolderTree * folder_tree)1499 emu_restore_folder_tree_state (EMFolderTree *folder_tree)
1500 {
1501 	EShell *shell;
1502 	EShellBackend *backend;
1503 	GKeyFile *key_file;
1504 	const gchar *config_dir;
1505 	gchar *filename;
1506 
1507 	g_return_if_fail (folder_tree != NULL);
1508 	g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
1509 
1510 	shell = e_shell_get_default ();
1511 	backend = e_shell_get_backend_by_name (shell, "mail");
1512 	g_return_if_fail (backend != NULL);
1513 
1514 	config_dir = e_shell_backend_get_config_dir (backend);
1515 	g_return_if_fail (config_dir != NULL);
1516 
1517 	filename = g_build_filename (config_dir, "state.ini", NULL);
1518 
1519 	key_file = g_key_file_new ();
1520 	g_key_file_load_from_file (key_file, filename, 0, NULL);
1521 	g_free (filename);
1522 
1523 	em_folder_tree_restore_state (folder_tree, key_file);
1524 
1525 	g_key_file_free (key_file);
1526 }
1527 
1528 static gboolean
check_prefix(const gchar * subject,const gchar * prefix,const gchar * const * separators,gint * skip_len)1529 check_prefix (const gchar *subject,
1530 	      const gchar *prefix,
1531 	      const gchar * const *separators,
1532               gint *skip_len)
1533 {
1534 	gboolean res = FALSE;
1535 	gint plen;
1536 
1537 	g_return_val_if_fail (subject != NULL, FALSE);
1538 	g_return_val_if_fail (prefix != NULL, FALSE);
1539 	g_return_val_if_fail (*prefix, FALSE);
1540 	g_return_val_if_fail (skip_len != NULL, FALSE);
1541 
1542 	plen = strlen (prefix);
1543 	if (g_ascii_strncasecmp (subject, prefix, plen) != 0)
1544 		return FALSE;
1545 
1546 	if (g_ascii_isspace (subject[plen]))
1547 		plen++;
1548 
1549 	res = e_util_utf8_strstrcase (subject + plen, ":") == subject + plen;
1550 	if (res)
1551 		plen += strlen (":");
1552 
1553 	if (!res) {
1554 		res = e_util_utf8_strstrcase (subject + plen, "︰") == subject + plen;
1555 		if (res)
1556 			plen += strlen ("︰");
1557 	}
1558 
1559 	if (!res && separators) {
1560 		gint ii;
1561 
1562 		for (ii = 0; separators[ii]; ii++) {
1563 			const gchar *separator = separators[ii];
1564 
1565 			res = *separator && e_util_utf8_strstrcase (subject + plen, separator) == subject + plen;
1566 			if (res) {
1567 				plen += strlen (separator);
1568 				break;
1569 			}
1570 		}
1571 	}
1572 
1573 	if (res) {
1574 		if (g_ascii_isspace (subject[plen]))
1575 			plen++;
1576 
1577 		*skip_len = plen;
1578 	}
1579 
1580 	return res;
1581 }
1582 
1583 gboolean
em_utils_is_re_in_subject(const gchar * subject,gint * skip_len,const gchar * const * use_prefixes_strv,const gchar * const * use_separators_strv)1584 em_utils_is_re_in_subject (const gchar *subject,
1585                            gint *skip_len,
1586 			   const gchar * const *use_prefixes_strv,
1587 			   const gchar * const *use_separators_strv)
1588 {
1589 	gchar **prefixes_strv;
1590 	gchar **separators_strv;
1591 	const gchar *localized_re, *localized_separator;
1592 	gboolean res;
1593 	gint ii;
1594 
1595 	g_return_val_if_fail (subject != NULL, FALSE);
1596 	g_return_val_if_fail (skip_len != NULL, FALSE);
1597 
1598 	*skip_len = -1;
1599 
1600 	if (strlen (subject) < 3)
1601 		return FALSE;
1602 
1603 	if (use_separators_strv) {
1604 		separators_strv = (gchar **) use_separators_strv;
1605 	} else {
1606 		GSettings *settings;
1607 
1608 		settings = e_util_ref_settings ("org.gnome.evolution.mail");
1609 		separators_strv = g_settings_get_strv (settings, "composer-localized-re-separators");
1610 		g_object_unref (settings);
1611 
1612 		if (separators_strv && !*separators_strv) {
1613 			g_strfreev (separators_strv);
1614 			separators_strv = NULL;
1615 		}
1616 	}
1617 
1618 	if (check_prefix (subject, "Re", (const gchar * const *) separators_strv, skip_len)) {
1619 		if (!use_separators_strv)
1620 			g_strfreev (separators_strv);
1621 
1622 		return TRUE;
1623 	}
1624 
1625 	/* Translators: This is a reply attribution in the message reply subject. Both 'Re'-s in the 'reply-attribution' translation context should translate into the same string. */
1626 	localized_re = C_("reply-attribution", "Re");
1627 
1628 	/* Translators: This is a reply attribution separator in the message reply subject. This should match the ':' in 'Re: %s' in the 'reply-attribution' translation context. */
1629 	localized_separator = C_("reply-attribution", ":");
1630 
1631 	if (check_prefix (subject, localized_re, (const gchar * const *) separators_strv, skip_len)) {
1632 		if (!use_separators_strv)
1633 			g_strfreev (separators_strv);
1634 
1635 		return TRUE;
1636 	}
1637 
1638 	if (localized_separator && g_strcmp0 (localized_separator, ":") != 0) {
1639 		const gchar *localized_separator_strv[2];
1640 
1641 		localized_separator_strv[0] = localized_separator;
1642 		localized_separator_strv[1] = NULL;
1643 
1644 		if (check_prefix (subject, localized_re, (const gchar * const *) localized_separator_strv, skip_len)) {
1645 			if (!use_separators_strv)
1646 				g_strfreev (separators_strv);
1647 
1648 			return TRUE;
1649 		}
1650 	}
1651 
1652 	if (use_prefixes_strv) {
1653 		prefixes_strv = (gchar **) use_prefixes_strv;
1654 	} else {
1655 		GSettings *settings;
1656 		gchar *prefixes;
1657 
1658 		settings = e_util_ref_settings ("org.gnome.evolution.mail");
1659 		prefixes = g_settings_get_string (settings, "composer-localized-re");
1660 		g_object_unref (settings);
1661 
1662 		if (!prefixes || !*prefixes) {
1663 			g_free (prefixes);
1664 
1665 			if (!use_separators_strv)
1666 				g_strfreev (separators_strv);
1667 
1668 			return FALSE;
1669 		}
1670 
1671 		prefixes_strv = g_strsplit (prefixes, ",", -1);
1672 		g_free (prefixes);
1673 	}
1674 
1675 	if (!prefixes_strv) {
1676 		if (!use_separators_strv)
1677 			g_strfreev (separators_strv);
1678 
1679 		return FALSE;
1680 	}
1681 
1682 	res = FALSE;
1683 
1684 	for (ii = 0; !res && prefixes_strv[ii]; ii++) {
1685 		const gchar *prefix = prefixes_strv[ii];
1686 
1687 		if (*prefix)
1688 			res = check_prefix (subject, prefix, (const gchar * const *) separators_strv, skip_len);
1689 	}
1690 
1691 	if (!use_prefixes_strv)
1692 		g_strfreev (prefixes_strv);
1693 	if (!use_separators_strv)
1694 		g_strfreev (separators_strv);
1695 
1696 	return res;
1697 }
1698 
1699 gchar *
em_utils_get_archive_folder_uri_from_folder(CamelFolder * folder,EMailBackend * mail_backend,GPtrArray * uids,gboolean deep_uids_check)1700 em_utils_get_archive_folder_uri_from_folder (CamelFolder *folder,
1701 					     EMailBackend *mail_backend,
1702 					     GPtrArray *uids,
1703 					     gboolean deep_uids_check)
1704 {
1705 	CamelStore *store;
1706 	ESource *source = NULL;
1707 	gchar *archive_folder = NULL;
1708 	gchar *folder_uri;
1709 	gboolean aa_enabled;
1710 	EAutoArchiveConfig aa_config;
1711 	gint aa_n_units;
1712 	EAutoArchiveUnit aa_unit;
1713 	gchar *aa_custom_target_folder_uri;
1714 
1715 	if (!folder)
1716 		return NULL;
1717 
1718 	folder_uri = e_mail_folder_uri_build (
1719 		camel_folder_get_parent_store (folder),
1720 		camel_folder_get_full_name (folder));
1721 
1722 	if (em_folder_properties_autoarchive_get (mail_backend, folder_uri,
1723 		&aa_enabled, &aa_config, &aa_n_units, &aa_unit, &aa_custom_target_folder_uri)) {
1724 		if (aa_config == E_AUTO_ARCHIVE_CONFIG_MOVE_TO_CUSTOM &&
1725 		    aa_custom_target_folder_uri && *aa_custom_target_folder_uri) {
1726 			g_free (folder_uri);
1727 			return aa_custom_target_folder_uri;
1728 		}
1729 
1730 		g_free (aa_custom_target_folder_uri);
1731 
1732 		if (aa_config == E_AUTO_ARCHIVE_CONFIG_DELETE) {
1733 			g_free (folder_uri);
1734 			return NULL;
1735 		}
1736 	}
1737 	g_free (folder_uri);
1738 
1739 	store = camel_folder_get_parent_store (folder);
1740 	if (g_strcmp0 (E_MAIL_SESSION_LOCAL_UID, camel_service_get_uid (CAMEL_SERVICE (store))) == 0) {
1741 		return mail_config_dup_local_archive_folder ();
1742 	}
1743 
1744 	if (CAMEL_IS_VEE_FOLDER (folder) && uids && uids->len > 0) {
1745 		CamelVeeFolder *vee_folder = CAMEL_VEE_FOLDER (folder);
1746 		CamelFolder *orig_folder = NULL;
1747 
1748 		store = NULL;
1749 
1750 		if (deep_uids_check) {
1751 			gint ii;
1752 
1753 			for (ii = 0; ii < uids->len; ii++) {
1754 				orig_folder = camel_vee_folder_get_vee_uid_folder (vee_folder, uids->pdata[ii]);
1755 				if (orig_folder) {
1756 					if (store && camel_folder_get_parent_store (orig_folder) != store) {
1757 						/* Do not know which archive folder to use when there are
1758 						   selected messages from multiple accounts/stores. */
1759 						store = NULL;
1760 						break;
1761 					}
1762 
1763 					store = camel_folder_get_parent_store (orig_folder);
1764 				}
1765 			}
1766 		} else {
1767 			orig_folder = camel_vee_folder_get_vee_uid_folder (CAMEL_VEE_FOLDER (folder), uids->pdata[0]);
1768 			if (orig_folder)
1769 				store = camel_folder_get_parent_store (orig_folder);
1770 		}
1771 
1772 		if (store && orig_folder) {
1773 			folder_uri = e_mail_folder_uri_build (
1774 				camel_folder_get_parent_store (orig_folder),
1775 				camel_folder_get_full_name (orig_folder));
1776 
1777 			if (em_folder_properties_autoarchive_get (mail_backend, folder_uri,
1778 				&aa_enabled, &aa_config, &aa_n_units, &aa_unit, &aa_custom_target_folder_uri)) {
1779 				if (aa_config == E_AUTO_ARCHIVE_CONFIG_MOVE_TO_CUSTOM &&
1780 				    aa_custom_target_folder_uri && *aa_custom_target_folder_uri) {
1781 					g_free (folder_uri);
1782 					return aa_custom_target_folder_uri;
1783 				}
1784 
1785 				g_free (aa_custom_target_folder_uri);
1786 
1787 				if (aa_config == E_AUTO_ARCHIVE_CONFIG_DELETE) {
1788 					g_free (folder_uri);
1789 					return NULL;
1790 				}
1791 			}
1792 
1793 			g_free (folder_uri);
1794 		}
1795 	}
1796 
1797 	if (store) {
1798 		ESourceRegistry *registry;
1799 
1800 		registry = e_mail_session_get_registry (e_mail_backend_get_session (mail_backend));
1801 		source = e_source_registry_ref_source (registry, camel_service_get_uid (CAMEL_SERVICE (store)));
1802 	}
1803 
1804 	if (source) {
1805 		if (e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT)) {
1806 			ESourceMailAccount *account_ext;
1807 
1808 			account_ext = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
1809 
1810 			archive_folder = e_source_mail_account_dup_archive_folder (account_ext);
1811 			if (!archive_folder || !*archive_folder) {
1812 				g_free (archive_folder);
1813 				archive_folder = NULL;
1814 			}
1815 		}
1816 
1817 		g_object_unref (source);
1818 	}
1819 
1820 	return archive_folder;
1821 }
1822 
1823 gboolean
em_utils_process_autoarchive_sync(EMailBackend * mail_backend,CamelFolder * folder,const gchar * folder_uri,GCancellable * cancellable,GError ** error)1824 em_utils_process_autoarchive_sync (EMailBackend *mail_backend,
1825 				   CamelFolder *folder,
1826 				   const gchar *folder_uri,
1827 				   GCancellable *cancellable,
1828 				   GError **error)
1829 {
1830 	gboolean aa_enabled;
1831 	EAutoArchiveConfig aa_config;
1832 	gint aa_n_units;
1833 	EAutoArchiveUnit aa_unit;
1834 	gchar *aa_custom_target_folder_uri = NULL;
1835 	GDateTime *now_time, *use_time;
1836 	gchar *search_sexp;
1837 	GPtrArray *uids;
1838 	gboolean success = TRUE;
1839 
1840 	g_return_val_if_fail (E_IS_MAIL_BACKEND (mail_backend), FALSE);
1841 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1842 	g_return_val_if_fail (folder_uri != NULL, FALSE);
1843 
1844 	if (!em_folder_properties_autoarchive_get (mail_backend, folder_uri,
1845 		&aa_enabled, &aa_config, &aa_n_units, &aa_unit, &aa_custom_target_folder_uri))
1846 		return TRUE;
1847 
1848 	if (!aa_enabled) {
1849 		g_free (aa_custom_target_folder_uri);
1850 		return TRUE;
1851 	}
1852 
1853 	if (aa_config == E_AUTO_ARCHIVE_CONFIG_MOVE_TO_CUSTOM && (!aa_custom_target_folder_uri || !*aa_custom_target_folder_uri)) {
1854 		g_free (aa_custom_target_folder_uri);
1855 		return TRUE;
1856 	}
1857 
1858 	now_time = g_date_time_new_now_utc ();
1859 	switch (aa_unit) {
1860 		case E_AUTO_ARCHIVE_UNIT_DAYS:
1861 			use_time = g_date_time_add_days (now_time, -aa_n_units);
1862 			break;
1863 		case E_AUTO_ARCHIVE_UNIT_WEEKS:
1864 			use_time = g_date_time_add_weeks (now_time, -aa_n_units);
1865 			break;
1866 		case E_AUTO_ARCHIVE_UNIT_MONTHS:
1867 			use_time = g_date_time_add_months (now_time, -aa_n_units);
1868 			break;
1869 		default:
1870 			g_date_time_unref (now_time);
1871 			g_free (aa_custom_target_folder_uri);
1872 			return TRUE;
1873 	}
1874 
1875 	g_date_time_unref (now_time);
1876 
1877 	search_sexp = g_strdup_printf ("(match-all (and "
1878 		"(not (system-flag \"junk\")) "
1879 		"(not (system-flag \"deleted\")) "
1880 		"(< (get-sent-date) %" G_GINT64_FORMAT ")"
1881 		"))", g_date_time_to_unix (use_time));
1882 	uids = camel_folder_search_by_expression (folder, search_sexp, cancellable, error);
1883 
1884 	if (!uids) {
1885 		success = FALSE;
1886 	} else if (uids->len > 0) {
1887 		gint ii;
1888 
1889 		if (aa_config == E_AUTO_ARCHIVE_CONFIG_MOVE_TO_ARCHIVE ||
1890 		    aa_config == E_AUTO_ARCHIVE_CONFIG_MOVE_TO_CUSTOM) {
1891 			CamelFolder *dest;
1892 
1893 			if (aa_config == E_AUTO_ARCHIVE_CONFIG_MOVE_TO_ARCHIVE) {
1894 				g_free (aa_custom_target_folder_uri);
1895 				aa_custom_target_folder_uri = em_utils_get_archive_folder_uri_from_folder (folder, mail_backend, uids, TRUE);
1896 			}
1897 
1898 			dest = aa_custom_target_folder_uri ? e_mail_session_uri_to_folder_sync (
1899 				e_mail_backend_get_session (mail_backend), aa_custom_target_folder_uri, 0,
1900 				cancellable, error) : NULL;
1901 			if (dest != NULL && dest != folder) {
1902 				camel_folder_freeze (folder);
1903 				camel_folder_freeze (dest);
1904 
1905 				if (camel_folder_transfer_messages_to_sync (
1906 					folder, uids, dest, TRUE, NULL,
1907 					cancellable, error)) {
1908 					/* make sure all deleted messages are marked as seen */
1909 					for (ii = 0; ii < uids->len; ii++) {
1910 						camel_folder_set_message_flags (
1911 							folder, uids->pdata[ii],
1912 							CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN);
1913 					}
1914 				} else {
1915 					success = FALSE;
1916 				}
1917 
1918 				camel_folder_thaw (folder);
1919 				camel_folder_thaw (dest);
1920 
1921 				if (success)
1922 					success = camel_folder_synchronize_sync (dest, FALSE, cancellable, error);
1923 			}
1924 
1925 			g_clear_object (&dest);
1926 		} else if (aa_config == E_AUTO_ARCHIVE_CONFIG_DELETE) {
1927 			camel_folder_freeze (folder);
1928 
1929 			camel_operation_push_message (cancellable, "%s", _("Deleting old messages"));
1930 
1931 			for (ii = 0; ii < uids->len; ii++) {
1932 				camel_folder_set_message_flags (
1933 					folder, uids->pdata[ii],
1934 					CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);
1935 			}
1936 
1937 			camel_operation_pop_message (cancellable);
1938 
1939 			camel_folder_thaw (folder);
1940 		}
1941 	}
1942 
1943 	if (uids)
1944 		camel_folder_search_free (folder, uids);
1945 
1946 	g_free (search_sexp);
1947 	g_free (aa_custom_target_folder_uri);
1948 	g_date_time_unref (use_time);
1949 
1950 	return success;
1951 }
1952 
1953 /**
1954  * em_utils_account_path_to_folder_uri:
1955  * @session: (nullable): a #CamelSession, or %NULL
1956  * @account_path: the account path to transform to folder URI
1957  *
1958  * Transform the @account_path to a folder URI. It can be in a form
1959  * of 'account-name/folder/path', aka 'On This Computer/Inbox/Private'.
1960  * The account name is compared case insensitively.
1961  *
1962  * When the @session is %NULL, it' is taken from the default shell, if
1963  * such exists.
1964  *
1965  * Returns: (nullable): a folder URI corresponding to the @account_path,
1966  *    or %NULL, when the account could not be found. Free the returned
1967  *    string with g_free(), when no longer needed.
1968  *
1969  * Since: 3.40
1970  **/
1971 gchar *
em_utils_account_path_to_folder_uri(CamelSession * session,const gchar * account_path)1972 em_utils_account_path_to_folder_uri (CamelSession *session,
1973 				     const gchar *account_path)
1974 {
1975 	GList *services, *link;
1976 	const gchar *dash;
1977 	gchar *folder_uri = NULL, *account_name;
1978 
1979 	g_return_val_if_fail (account_path != NULL, NULL);
1980 
1981 	dash = strchr (account_path, '/');
1982 	if (!dash)
1983 		return NULL;
1984 
1985 	if (!session) {
1986 		EShell *shell;
1987 		EShellBackend *shell_backend;
1988 		EMailSession *mail_session;
1989 
1990 		shell = e_shell_get_default ();
1991 		if (!shell)
1992 			return NULL;
1993 
1994 		shell_backend = e_shell_get_backend_by_name (shell, "mail");
1995 		if (!shell_backend)
1996 			return NULL;
1997 
1998 		mail_session = e_mail_backend_get_session (E_MAIL_BACKEND (shell_backend));
1999 		if (!mail_session)
2000 			return NULL;
2001 
2002 		session = CAMEL_SESSION (mail_session);
2003 	}
2004 
2005 	account_name = e_util_utf8_data_make_valid (account_path, dash - account_path);
2006 
2007 	services = camel_session_list_services (session);
2008 	for (link = services; link; link = g_list_next (link)) {
2009 		CamelService *service = link->data;
2010 
2011 		/* Case sensitive account name compare, because the folder names are also compared case sensitively */
2012 		if (CAMEL_IS_STORE (service) && g_strcmp0 (camel_service_get_display_name (service), account_name) == 0) {
2013 			folder_uri = e_mail_folder_uri_build (CAMEL_STORE (service), dash + 1);
2014 			break;
2015 		}
2016 	}
2017 
2018 	g_list_free_full (services, g_object_unref);
2019 	g_free (account_name);
2020 
2021 	return folder_uri;
2022 }
2023 
2024 EMailBrowser *
em_utils_find_message_window(EMailFormatterMode display_mode,CamelFolder * folder,const gchar * message_uid)2025 em_utils_find_message_window (EMailFormatterMode display_mode,
2026 			      CamelFolder *folder,
2027 			      const gchar *message_uid)
2028 {
2029 	EShell *shell;
2030 	GList *windows, *link;
2031 
2032 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
2033 	g_return_val_if_fail (message_uid != NULL, NULL);
2034 
2035 	shell = e_shell_get_default ();
2036 	windows = gtk_application_get_windows (GTK_APPLICATION (shell));
2037 
2038 	for (link = windows; link; link = g_list_next (link)) {
2039 		GtkWindow *window = link->data;
2040 
2041 		if (E_IS_MAIL_BROWSER (window)) {
2042 			EMailBrowser *browser = E_MAIL_BROWSER (window);
2043 			gboolean matched = FALSE;
2044 
2045 			if (e_mail_browser_get_display_mode (browser) == display_mode) {
2046 				CamelFolder *tmp_folder;
2047 				GPtrArray *uids;
2048 
2049 				tmp_folder = e_mail_reader_ref_folder (E_MAIL_READER (browser));
2050 				uids = e_mail_reader_get_selected_uids (E_MAIL_READER (browser));
2051 
2052 				if (uids->len == 1) {
2053 					const gchar *uid = g_ptr_array_index (uids, 0);
2054 
2055 					matched = g_strcmp0 (message_uid, uid) == 0 &&
2056 						  folder == tmp_folder;
2057 
2058 					if (!matched) {
2059 						CamelFolder *real_folder = NULL, *tmp_real_folder = NULL;
2060 						gchar *real_uid = NULL, *tmp_real_uid = NULL;
2061 
2062 						if (CAMEL_IS_VEE_FOLDER (folder))
2063 							em_utils_get_real_folder_and_message_uid (folder, message_uid, &real_folder, NULL, &real_uid);
2064 
2065 						if (CAMEL_IS_VEE_FOLDER (tmp_folder))
2066 							em_utils_get_real_folder_and_message_uid (tmp_folder, uid, &tmp_real_folder, NULL, &tmp_real_uid);
2067 
2068 						matched = (real_folder || tmp_real_folder) &&
2069 							(real_folder ? real_folder : folder) == (tmp_real_folder ? tmp_real_folder : tmp_folder) &&
2070 							g_strcmp0 (real_uid ? real_uid : message_uid, tmp_real_uid ? tmp_real_uid : uid) == 0;
2071 
2072 						g_clear_object (&tmp_real_folder);
2073 						g_clear_object (&real_folder);
2074 						g_free (tmp_real_uid);
2075 						g_free (real_uid);
2076 					}
2077 				}
2078 
2079 				g_ptr_array_unref (uids);
2080 				g_clear_object (&tmp_folder);
2081 			}
2082 
2083 			if (matched)
2084 				return browser;
2085 		}
2086 	}
2087 
2088 	return NULL;
2089 }
2090