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, "&", 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, "&", 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