1 /*
2  * e-mail-folder-utils.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-config.h"
19 
20 #include "e-mail-folder-utils.h"
21 
22 #include <glib/gi18n-lib.h>
23 
24 #include <libedataserver/libedataserver.h>
25 
26 #include <libemail-engine/e-mail-session.h>
27 #include <libemail-engine/mail-tools.h>
28 
29 #include "e-mail-utils.h"
30 
31 /* User-Agent header value */
32 #define USER_AGENT ("Evolution " VERSION VERSION_SUBSTRING " " VERSION_COMMENT)
33 
34 typedef struct _AsyncContext AsyncContext;
35 
36 struct _AsyncContext {
37 	CamelMimeMessage *message;
38 	CamelMessageInfo *info;
39 	CamelMimePart *part;
40 	GHashTable *hash_table;
41 	GPtrArray *ptr_array;
42 	GFile *destination;
43 	gchar *fwd_subject;
44 	gchar *message_uid;
45 };
46 
47 static void
async_context_free(AsyncContext * context)48 async_context_free (AsyncContext *context)
49 {
50 	if (context->hash_table != NULL)
51 		g_hash_table_unref (context->hash_table);
52 
53 	if (context->ptr_array != NULL)
54 		g_ptr_array_unref (context->ptr_array);
55 
56 	g_clear_object (&context->message);
57 	g_clear_object (&context->info);
58 	g_clear_object (&context->part);
59 	g_clear_object (&context->destination);
60 
61 	g_free (context->fwd_subject);
62 	g_free (context->message_uid);
63 
64 	g_slice_free (AsyncContext, context);
65 }
66 
67 static void
mail_folder_append_message_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)68 mail_folder_append_message_thread (GSimpleAsyncResult *simple,
69                                    GObject *object,
70                                    GCancellable *cancellable)
71 {
72 	AsyncContext *context;
73 	GError *error = NULL;
74 
75 	context = g_simple_async_result_get_op_res_gpointer (simple);
76 
77 	e_mail_folder_append_message_sync (
78 		CAMEL_FOLDER (object), context->message,
79 		context->info, &context->message_uid,
80 		cancellable, &error);
81 
82 	if (error != NULL)
83 		g_simple_async_result_take_error (simple, error);
84 }
85 
86 gboolean
e_mail_folder_append_message_sync(CamelFolder * folder,CamelMimeMessage * message,CamelMessageInfo * info,gchar ** appended_uid,GCancellable * cancellable,GError ** error)87 e_mail_folder_append_message_sync (CamelFolder *folder,
88                                    CamelMimeMessage *message,
89                                    CamelMessageInfo *info,
90                                    gchar **appended_uid,
91                                    GCancellable *cancellable,
92                                    GError **error)
93 {
94 	CamelMedium *medium;
95 	gchar *full_display_name;
96 	gboolean success;
97 
98 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
99 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
100 
101 	medium = CAMEL_MEDIUM (message);
102 
103 	full_display_name = e_mail_folder_to_full_display_name (folder, NULL);
104 	camel_operation_push_message (
105 		cancellable,
106 		_("Saving message to folder “%s”"),
107 		full_display_name ? full_display_name : camel_folder_get_display_name (folder));
108 	g_free (full_display_name);
109 
110 	if (!camel_medium_get_header (medium, "X-Evolution-Is-Redirect")) {
111 		if (camel_medium_get_header (medium, "User-Agent") == NULL)
112 			camel_medium_set_header (medium, "User-Agent", USER_AGENT);
113 
114 		camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
115 	}
116 
117 	success = camel_folder_append_message_sync (
118 		folder, message, info, appended_uid, cancellable, error);
119 
120 	camel_operation_pop_message (cancellable);
121 
122 	return success;
123 }
124 
125 void
e_mail_folder_append_message(CamelFolder * folder,CamelMimeMessage * message,CamelMessageInfo * info,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)126 e_mail_folder_append_message (CamelFolder *folder,
127                               CamelMimeMessage *message,
128                               CamelMessageInfo *info,
129                               gint io_priority,
130                               GCancellable *cancellable,
131                               GAsyncReadyCallback callback,
132                               gpointer user_data)
133 {
134 	GSimpleAsyncResult *simple;
135 	AsyncContext *context;
136 
137 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
138 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
139 
140 	context = g_slice_new0 (AsyncContext);
141 	context->message = g_object_ref (message);
142 
143 	if (info != NULL)
144 		context->info = g_object_ref (info);
145 
146 	simple = g_simple_async_result_new (
147 		G_OBJECT (folder), callback, user_data,
148 		e_mail_folder_append_message);
149 
150 	g_simple_async_result_set_check_cancellable (simple, cancellable);
151 
152 	g_simple_async_result_set_op_res_gpointer (
153 		simple, context, (GDestroyNotify) async_context_free);
154 
155 	g_simple_async_result_run_in_thread (
156 		simple, mail_folder_append_message_thread,
157 		io_priority, cancellable);
158 
159 	g_object_unref (simple);
160 }
161 
162 gboolean
e_mail_folder_append_message_finish(CamelFolder * folder,GAsyncResult * result,gchar ** appended_uid,GError ** error)163 e_mail_folder_append_message_finish (CamelFolder *folder,
164                                      GAsyncResult *result,
165                                      gchar **appended_uid,
166                                      GError **error)
167 {
168 	GSimpleAsyncResult *simple;
169 	AsyncContext *context;
170 
171 	g_return_val_if_fail (
172 		g_simple_async_result_is_valid (
173 		result, G_OBJECT (folder),
174 		e_mail_folder_append_message), FALSE);
175 
176 	simple = G_SIMPLE_ASYNC_RESULT (result);
177 	context = g_simple_async_result_get_op_res_gpointer (simple);
178 
179 	if (appended_uid != NULL) {
180 		*appended_uid = context->message_uid;
181 		context->message_uid = NULL;
182 	}
183 
184 	/* Assume success unless a GError is set. */
185 	return !g_simple_async_result_propagate_error (simple, error);
186 }
187 
188 static void
mail_folder_expunge_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)189 mail_folder_expunge_thread (GSimpleAsyncResult *simple,
190                             GObject *object,
191                             GCancellable *cancellable)
192 {
193 	GError *error = NULL;
194 
195 	e_mail_folder_expunge_sync (
196 		CAMEL_FOLDER (object), cancellable, &error);
197 
198 	if (error != NULL)
199 		g_simple_async_result_take_error (simple, error);
200 }
201 
202 static gboolean
mail_folder_expunge_pop3_stores(CamelFolder * folder,GCancellable * cancellable,GError ** error)203 mail_folder_expunge_pop3_stores (CamelFolder *folder,
204                                  GCancellable *cancellable,
205                                  GError **error)
206 {
207 	GHashTable *expunging_uids;
208 	CamelStore *parent_store;
209 	CamelService *service;
210 	CamelSession *session;
211 	ESourceRegistry *registry;
212 	GPtrArray *uids;
213 	GList *list, *link;
214 	const gchar *extension_name;
215 	gboolean success = TRUE;
216 	guint ii;
217 
218 	parent_store = camel_folder_get_parent_store (folder);
219 
220 	service = CAMEL_SERVICE (parent_store);
221 	session = camel_service_ref_session (service);
222 	registry = e_mail_session_get_registry (E_MAIL_SESSION (session));
223 
224 	uids = camel_folder_get_uids (folder);
225 
226 	if (uids == NULL)
227 		goto exit;
228 
229 	expunging_uids = g_hash_table_new_full (
230 		(GHashFunc) g_str_hash,
231 		(GEqualFunc) g_str_equal,
232 		(GDestroyNotify) g_free,
233 		(GDestroyNotify) g_free);
234 
235 	for (ii = 0; ii < uids->len; ii++) {
236 		CamelMessageInfo *info;
237 		CamelMessageFlags flags = 0;
238 		CamelMimeMessage *message;
239 		const gchar *pop3_uid;
240 		const gchar *source_uid;
241 
242 		info = camel_folder_get_message_info (
243 			folder, uids->pdata[ii]);
244 
245 		if (info != NULL) {
246 			flags = camel_message_info_get_flags (info);
247 			g_clear_object (&info);
248 		}
249 
250 		/* Only interested in deleted messages. */
251 		if ((flags & CAMEL_MESSAGE_DELETED) == 0)
252 			continue;
253 
254 		/* because the UID in the local store doesn't
255 		 * match with the UID in the pop3 store */
256 		message = camel_folder_get_message_sync (
257 			folder, uids->pdata[ii], cancellable, NULL);
258 
259 		if (message == NULL)
260 			continue;
261 
262 		pop3_uid = camel_medium_get_header (
263 			CAMEL_MEDIUM (message), "X-Evolution-POP3-UID");
264 		source_uid = camel_mime_message_get_source (message);
265 
266 		if (pop3_uid != NULL)
267 			g_hash_table_insert (
268 				expunging_uids,
269 				g_strstrip (g_strdup (pop3_uid)),
270 				g_strstrip (g_strdup (source_uid)));
271 
272 		g_object_unref (message);
273 	}
274 
275 	camel_folder_free_uids (folder, uids);
276 	uids = NULL;
277 
278 	if (g_hash_table_size (expunging_uids) == 0) {
279 		g_hash_table_destroy (expunging_uids);
280 		return TRUE;
281 	}
282 
283 	extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
284 	list = e_source_registry_list_enabled (registry, extension_name);
285 
286 	for (link = list; link != NULL; link = g_list_next (link)) {
287 		ESource *source = E_SOURCE (link->data);
288 		ESourceBackend *extension;
289 		CamelFolder *inbox_folder;
290 		CamelService *service;
291 		CamelSettings *settings;
292 		const gchar *backend_name;
293 		const gchar *service_uid;
294 		const gchar *source_uid;
295 		gboolean any_found = FALSE;
296 		gboolean delete_expunged = FALSE;
297 		gboolean keep_on_server = FALSE;
298 
299 		source_uid = e_source_get_uid (source);
300 
301 		extension = e_source_get_extension (source, extension_name);
302 		backend_name = e_source_backend_get_backend_name (extension);
303 
304 		if (g_strcmp0 (backend_name, "pop") != 0)
305 			continue;
306 
307 		service = camel_session_ref_service (
308 			CAMEL_SESSION (session), source_uid);
309 
310 		service_uid = camel_service_get_uid (service);
311 		settings = camel_service_ref_settings (service);
312 
313 		g_object_get (
314 			settings,
315 			"delete-expunged", &delete_expunged,
316 			"keep-on-server", &keep_on_server,
317 			NULL);
318 
319 		g_object_unref (settings);
320 
321 		if (!keep_on_server || !delete_expunged) {
322 			g_object_unref (service);
323 			continue;
324 		}
325 
326 		inbox_folder = camel_store_get_inbox_folder_sync (
327 			CAMEL_STORE (service), cancellable, error);
328 
329 		/* Abort the loop on error. */
330 		if (inbox_folder == NULL) {
331 			g_object_unref (service);
332 			success = FALSE;
333 			break;
334 		}
335 
336 		uids = camel_folder_get_uids (inbox_folder);
337 
338 		if (uids == NULL) {
339 			g_object_unref (service);
340 			g_object_unref (inbox_folder);
341 			continue;
342 		}
343 
344 		for (ii = 0; ii < uids->len; ii++) {
345 			const gchar *source_uid;
346 
347 			source_uid = g_hash_table_lookup (
348 				expunging_uids, uids->pdata[ii]);
349 			if (g_strcmp0 (source_uid, service_uid) == 0) {
350 				any_found = TRUE;
351 				camel_folder_delete_message (
352 					inbox_folder, uids->pdata[ii]);
353 			}
354 		}
355 
356 		camel_folder_free_uids (inbox_folder, uids);
357 
358 		if (any_found)
359 			success = camel_folder_synchronize_sync (
360 				inbox_folder, TRUE, cancellable, error);
361 
362 		g_object_unref (inbox_folder);
363 		g_object_unref (service);
364 
365 		/* Abort the loop on error. */
366 		if (!success)
367 			break;
368 	}
369 
370 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
371 
372 	g_hash_table_destroy (expunging_uids);
373 
374 exit:
375 	g_object_unref (session);
376 
377 	return success;
378 }
379 
380 gboolean
e_mail_folder_expunge_sync(CamelFolder * folder,GCancellable * cancellable,GError ** error)381 e_mail_folder_expunge_sync (CamelFolder *folder,
382                             GCancellable *cancellable,
383                             GError **error)
384 {
385 	CamelFolder *local_inbox;
386 	CamelStore *parent_store;
387 	CamelService *service;
388 	CamelSession *session;
389 	gboolean is_local_inbox;
390 	gboolean is_local_trash;
391 	gboolean store_is_local;
392 	gboolean success = TRUE;
393 	const gchar *uid;
394 
395 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
396 
397 	parent_store = camel_folder_get_parent_store (folder);
398 
399 	service = CAMEL_SERVICE (parent_store);
400 	session = camel_service_ref_session (service);
401 
402 	uid = camel_service_get_uid (service);
403 	store_is_local = (g_strcmp0 (uid, E_MAIL_SESSION_LOCAL_UID) == 0);
404 
405 	local_inbox = e_mail_session_get_local_folder (
406 		E_MAIL_SESSION (session), E_MAIL_LOCAL_FOLDER_INBOX);
407 	is_local_inbox = (folder == local_inbox);
408 	is_local_trash = FALSE;
409 
410 	if (store_is_local && !is_local_inbox) {
411 		CamelFolder *local_trash;
412 
413 		local_trash = camel_store_get_trash_folder_sync (
414 			parent_store, cancellable, error);
415 
416 		if (local_trash != NULL) {
417 			is_local_trash = (folder == local_trash);
418 			g_object_unref (local_trash);
419 		} else {
420 			success = FALSE;
421 			goto exit;
422 		}
423 	}
424 
425 	/* Expunge all POP3 accounts when expunging
426 	 * the local Inbox or Trash folder. */
427 	if (is_local_inbox || is_local_trash)
428 		success = mail_folder_expunge_pop3_stores (
429 			folder, cancellable, error);
430 
431 	if (success)
432 		success = camel_folder_expunge_sync (
433 			folder, cancellable, error);
434 
435 exit:
436 	g_object_unref (session);
437 
438 	return success;
439 }
440 
441 void
e_mail_folder_expunge(CamelFolder * folder,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)442 e_mail_folder_expunge (CamelFolder *folder,
443                        gint io_priority,
444                        GCancellable *cancellable,
445                        GAsyncReadyCallback callback,
446                        gpointer user_data)
447 {
448 	GSimpleAsyncResult *simple;
449 
450 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
451 
452 	simple = g_simple_async_result_new (
453 		G_OBJECT (folder), callback,
454 		user_data, e_mail_folder_expunge);
455 
456 	g_simple_async_result_set_check_cancellable (simple, cancellable);
457 
458 	g_simple_async_result_run_in_thread (
459 		simple, mail_folder_expunge_thread,
460 		io_priority, cancellable);
461 
462 	g_object_unref (simple);
463 }
464 
465 gboolean
e_mail_folder_expunge_finish(CamelFolder * folder,GAsyncResult * result,GError ** error)466 e_mail_folder_expunge_finish (CamelFolder *folder,
467                               GAsyncResult *result,
468                               GError **error)
469 {
470 	GSimpleAsyncResult *simple;
471 
472 	g_return_val_if_fail (
473 		g_simple_async_result_is_valid (
474 		result, G_OBJECT (folder),
475 		e_mail_folder_expunge), FALSE);
476 
477 	simple = G_SIMPLE_ASYNC_RESULT (result);
478 
479 	/* Assume success unless a GError is set. */
480 	return !g_simple_async_result_propagate_error (simple, error);
481 }
482 
483 static void
mail_folder_build_attachment_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)484 mail_folder_build_attachment_thread (GSimpleAsyncResult *simple,
485                                      GObject *object,
486                                      GCancellable *cancellable)
487 {
488 	AsyncContext *context;
489 	GError *error = NULL;
490 
491 	context = g_simple_async_result_get_op_res_gpointer (simple);
492 
493 	context->part = e_mail_folder_build_attachment_sync (
494 		CAMEL_FOLDER (object), context->ptr_array,
495 		&context->fwd_subject, cancellable, &error);
496 
497 	if (error != NULL)
498 		g_simple_async_result_take_error (simple, error);
499 }
500 
501 CamelMimePart *
e_mail_folder_build_attachment_sync(CamelFolder * folder,GPtrArray * message_uids,gchar ** fwd_subject,GCancellable * cancellable,GError ** error)502 e_mail_folder_build_attachment_sync (CamelFolder *folder,
503                                      GPtrArray *message_uids,
504                                      gchar **fwd_subject,
505                                      GCancellable *cancellable,
506                                      GError **error)
507 {
508 	GHashTable *hash_table;
509 	CamelMimeMessage *message;
510 	CamelMimePart *part;
511 	const gchar *uid;
512 
513 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
514 	g_return_val_if_fail (message_uids != NULL, NULL);
515 
516 	/* Need at least one message UID to make an attachment. */
517 	g_return_val_if_fail (message_uids->len > 0, NULL);
518 
519 	hash_table = e_mail_folder_get_multiple_messages_sync (
520 		folder, message_uids, cancellable, error);
521 
522 	if (hash_table == NULL)
523 		return NULL;
524 
525 	/* Create the forward subject from the first message. */
526 
527 	uid = g_ptr_array_index (message_uids, 0);
528 	g_return_val_if_fail (uid != NULL, NULL);
529 
530 	message = g_hash_table_lookup (hash_table, uid);
531 	g_return_val_if_fail (message != NULL, NULL);
532 
533 	if (fwd_subject != NULL)
534 		*fwd_subject = mail_tool_generate_forward_subject (message);
535 
536 	if (message_uids->len == 1) {
537 		part = mail_tool_make_message_attachment (message);
538 
539 	} else {
540 		CamelMultipart *multipart;
541 		guint ii;
542 
543 		multipart = camel_multipart_new ();
544 		camel_data_wrapper_set_mime_type (
545 			CAMEL_DATA_WRAPPER (multipart), "multipart/digest");
546 		camel_multipart_set_boundary (multipart, NULL);
547 
548 		for (ii = 0; ii < message_uids->len; ii++) {
549 			uid = g_ptr_array_index (message_uids, ii);
550 			g_return_val_if_fail (uid != NULL, NULL);
551 
552 			message = g_hash_table_lookup (hash_table, uid);
553 			g_return_val_if_fail (message != NULL, NULL);
554 
555 			part = mail_tool_make_message_attachment (message);
556 			camel_multipart_add_part (multipart, part);
557 			g_object_unref (part);
558 		}
559 
560 		part = camel_mime_part_new ();
561 
562 		camel_medium_set_content (
563 			CAMEL_MEDIUM (part),
564 			CAMEL_DATA_WRAPPER (multipart));
565 
566 		camel_mime_part_set_description (
567 			part, _("Forwarded messages"));
568 
569 		g_object_unref (multipart);
570 	}
571 
572 	g_hash_table_unref (hash_table);
573 
574 	return part;
575 }
576 
577 void
e_mail_folder_build_attachment(CamelFolder * folder,GPtrArray * message_uids,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)578 e_mail_folder_build_attachment (CamelFolder *folder,
579                                 GPtrArray *message_uids,
580                                 gint io_priority,
581                                 GCancellable *cancellable,
582                                 GAsyncReadyCallback callback,
583                                 gpointer user_data)
584 {
585 	GSimpleAsyncResult *simple;
586 	AsyncContext *context;
587 
588 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
589 	g_return_if_fail (message_uids != NULL);
590 
591 	/* Need at least one message UID to make an attachment. */
592 	g_return_if_fail (message_uids->len > 0);
593 
594 	context = g_slice_new0 (AsyncContext);
595 	context->ptr_array = g_ptr_array_ref (message_uids);
596 
597 	simple = g_simple_async_result_new (
598 		G_OBJECT (folder), callback, user_data,
599 		e_mail_folder_build_attachment);
600 
601 	g_simple_async_result_set_check_cancellable (simple, cancellable);
602 
603 	g_simple_async_result_set_op_res_gpointer (
604 		simple, context, (GDestroyNotify) async_context_free);
605 
606 	g_simple_async_result_run_in_thread (
607 		simple, mail_folder_build_attachment_thread,
608 		io_priority, cancellable);
609 
610 	g_object_unref (simple);
611 }
612 
613 CamelMimePart *
e_mail_folder_build_attachment_finish(CamelFolder * folder,GAsyncResult * result,gchar ** fwd_subject,GError ** error)614 e_mail_folder_build_attachment_finish (CamelFolder *folder,
615                                        GAsyncResult *result,
616                                        gchar **fwd_subject,
617                                        GError **error)
618 {
619 	GSimpleAsyncResult *simple;
620 	AsyncContext *context;
621 
622 	g_return_val_if_fail (
623 		g_simple_async_result_is_valid (
624 		result, G_OBJECT (folder),
625 		e_mail_folder_build_attachment), NULL);
626 
627 	simple = G_SIMPLE_ASYNC_RESULT (result);
628 	context = g_simple_async_result_get_op_res_gpointer (simple);
629 
630 	if (g_simple_async_result_propagate_error (simple, error))
631 		return NULL;
632 
633 	if (fwd_subject != NULL) {
634 		*fwd_subject = context->fwd_subject;
635 		context->fwd_subject = NULL;
636 	}
637 
638 	g_return_val_if_fail (CAMEL_IS_MIME_PART (context->part), NULL);
639 
640 	return g_object_ref (context->part);
641 }
642 
643 static void
mail_folder_find_duplicate_messages_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)644 mail_folder_find_duplicate_messages_thread (GSimpleAsyncResult *simple,
645                                             GObject *object,
646                                             GCancellable *cancellable)
647 {
648 	AsyncContext *context;
649 	GError *error = NULL;
650 
651 	context = g_simple_async_result_get_op_res_gpointer (simple);
652 
653 	context->hash_table = e_mail_folder_find_duplicate_messages_sync (
654 		CAMEL_FOLDER (object), context->ptr_array,
655 		cancellable, &error);
656 
657 	if (error != NULL)
658 		g_simple_async_result_take_error (simple, error);
659 }
660 
661 static GHashTable *
emfu_get_messages_hash_sync(CamelFolder * folder,GPtrArray * message_uids,GCancellable * cancellable,GError ** error)662 emfu_get_messages_hash_sync (CamelFolder *folder,
663                              GPtrArray *message_uids,
664                              GCancellable *cancellable,
665                              GError **error)
666 {
667 	GHashTable *hash_table;
668 	CamelMimeMessage *message;
669 	guint ii;
670 
671 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
672 	g_return_val_if_fail (message_uids != NULL, NULL);
673 
674 	camel_operation_push_message (
675 		cancellable,
676 		ngettext (
677 			"Retrieving %d message",
678 			"Retrieving %d messages",
679 			message_uids->len),
680 		message_uids->len);
681 
682 	hash_table = g_hash_table_new_full (
683 		(GHashFunc) g_str_hash,
684 		(GEqualFunc) g_str_equal,
685 		(GDestroyNotify) g_free,
686 		(GDestroyNotify) g_free);
687 
688 	/* This is an all or nothing operation.  Destroy the
689 	 * hash table if we fail to retrieve any message. */
690 
691 	for (ii = 0; ii < message_uids->len; ii++) {
692 		const gchar *uid;
693 		gint percent;
694 
695 		uid = g_ptr_array_index (message_uids, ii);
696 		percent = ((ii + 1) * 100) / message_uids->len;
697 
698 		message = camel_folder_get_message_sync (
699 			folder, uid, cancellable, error);
700 
701 		camel_operation_progress (cancellable, percent);
702 
703 		if (CAMEL_IS_MIME_MESSAGE (message)) {
704 			CamelDataWrapper *content;
705 			gchar *digest = NULL;
706 
707 			/* Generate a digest string from the message's content. */
708 			content = camel_medium_get_content (CAMEL_MEDIUM (message));
709 
710 			if (content != NULL) {
711 				CamelStream *stream;
712 				GByteArray *buffer;
713 				gssize n_bytes;
714 
715 				stream = camel_stream_mem_new ();
716 
717 				n_bytes = camel_data_wrapper_decode_to_stream_sync (
718 					content, stream, cancellable, error);
719 
720 				if (n_bytes >= 0) {
721 					guint data_len;
722 
723 					/* The CamelStreamMem owns the buffer. */
724 					buffer = camel_stream_mem_get_byte_array (
725 						CAMEL_STREAM_MEM (stream));
726 					g_return_val_if_fail (buffer != NULL, NULL);
727 
728 					data_len = buffer->len;
729 
730 					/* Strip trailing white-spaces and empty lines */
731 					while (data_len > 0 && g_ascii_isspace (buffer->data[data_len - 1]))
732 						data_len--;
733 
734 					if (data_len > 0)
735 						digest = g_compute_checksum_for_data (G_CHECKSUM_SHA256, buffer->data, data_len);
736 				}
737 
738 				g_object_unref (stream);
739 			}
740 
741 			g_hash_table_insert (
742 				hash_table, g_strdup (uid), digest);
743 			g_object_unref (message);
744 		} else {
745 			g_hash_table_destroy (hash_table);
746 			hash_table = NULL;
747 			break;
748 		}
749 	}
750 
751 	camel_operation_pop_message (cancellable);
752 
753 	return hash_table;
754 }
755 
756 GHashTable *
e_mail_folder_find_duplicate_messages_sync(CamelFolder * folder,GPtrArray * message_uids,GCancellable * cancellable,GError ** error)757 e_mail_folder_find_duplicate_messages_sync (CamelFolder *folder,
758                                             GPtrArray *message_uids,
759                                             GCancellable *cancellable,
760                                             GError **error)
761 {
762 	GQueue trash = G_QUEUE_INIT;
763 	GHashTable *hash_table;
764 	GHashTable *unique_ids;
765 	GHashTableIter iter;
766 	gpointer key, value;
767 
768 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
769 	g_return_val_if_fail (message_uids != NULL, NULL);
770 
771 	/* hash_table = { MessageUID : digest-as-string } */
772 	hash_table = emfu_get_messages_hash_sync (
773 		folder, message_uids, cancellable, error);
774 
775 	if (hash_table == NULL)
776 		return NULL;
777 
778 	camel_operation_push_message (
779 		cancellable, _("Scanning messages for duplicates"));
780 
781 	unique_ids = g_hash_table_new_full (
782 		(GHashFunc) g_int64_hash,
783 		(GEqualFunc) g_int64_equal,
784 		(GDestroyNotify) g_free,
785 		(GDestroyNotify) g_free);
786 
787 	g_hash_table_iter_init (&iter, hash_table);
788 
789 	while (g_hash_table_iter_next (&iter, &key, &value)) {
790 		CamelSummaryMessageID message_id;
791 		CamelMessageFlags flags;
792 		CamelMessageInfo *info;
793 		gboolean duplicate;
794 		const gchar *digest;
795 
796 		info = camel_folder_get_message_info (folder, key);
797 		if (!info)
798 			continue;
799 
800 		message_id.id.id = camel_message_info_get_message_id (info);
801 		flags = camel_message_info_get_flags (info);
802 
803 		/* Skip messages marked for deletion. */
804 		if (flags & CAMEL_MESSAGE_DELETED) {
805 			g_queue_push_tail (&trash, key);
806 			g_clear_object (&info);
807 			continue;
808 		}
809 
810 		digest = value;
811 
812 		if (digest == NULL) {
813 			g_queue_push_tail (&trash, key);
814 			g_clear_object (&info);
815 			continue;
816 		}
817 
818 		/* Determine if the message a duplicate. */
819 
820 		value = g_hash_table_lookup (unique_ids, &message_id.id.id);
821 		duplicate = (value != NULL) && g_str_equal (digest, value);
822 
823 		if (!duplicate) {
824 			gint64 *v_int64;
825 
826 			/* XXX Might be better to create a GArray
827 			 *     of 64-bit integers and have the hash
828 			 *     table keys point to array elements. */
829 			v_int64 = g_new0 (gint64, 1);
830 			*v_int64 = (gint64) message_id.id.id;
831 
832 			g_hash_table_insert (unique_ids, v_int64, g_strdup (digest));
833 			g_queue_push_tail (&trash, key);
834 		}
835 
836 		g_clear_object (&info);
837 	}
838 
839 	/* Delete all non-duplicate messages from the hash table. */
840 	while ((key = g_queue_pop_head (&trash)) != NULL)
841 		g_hash_table_remove (hash_table, key);
842 
843 	camel_operation_pop_message (cancellable);
844 
845 	g_hash_table_destroy (unique_ids);
846 
847 	return hash_table;
848 }
849 
850 void
e_mail_folder_find_duplicate_messages(CamelFolder * folder,GPtrArray * message_uids,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)851 e_mail_folder_find_duplicate_messages (CamelFolder *folder,
852                                        GPtrArray *message_uids,
853                                        gint io_priority,
854                                        GCancellable *cancellable,
855                                        GAsyncReadyCallback callback,
856                                        gpointer user_data)
857 {
858 	GSimpleAsyncResult *simple;
859 	AsyncContext *context;
860 
861 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
862 	g_return_if_fail (message_uids != NULL);
863 
864 	context = g_slice_new0 (AsyncContext);
865 	context->ptr_array = g_ptr_array_ref (message_uids);
866 
867 	simple = g_simple_async_result_new (
868 		G_OBJECT (folder), callback, user_data,
869 		e_mail_folder_find_duplicate_messages);
870 
871 	g_simple_async_result_set_check_cancellable (simple, cancellable);
872 
873 	g_simple_async_result_set_op_res_gpointer (
874 		simple, context, (GDestroyNotify) async_context_free);
875 
876 	g_simple_async_result_run_in_thread (
877 		simple, mail_folder_find_duplicate_messages_thread,
878 		io_priority, cancellable);
879 
880 	g_object_unref (simple);
881 }
882 
883 GHashTable *
e_mail_folder_find_duplicate_messages_finish(CamelFolder * folder,GAsyncResult * result,GError ** error)884 e_mail_folder_find_duplicate_messages_finish (CamelFolder *folder,
885                                               GAsyncResult *result,
886                                               GError **error)
887 {
888 	GSimpleAsyncResult *simple;
889 	AsyncContext *context;
890 
891 	g_return_val_if_fail (
892 		g_simple_async_result_is_valid (
893 		result, G_OBJECT (folder),
894 		e_mail_folder_find_duplicate_messages), NULL);
895 
896 	simple = G_SIMPLE_ASYNC_RESULT (result);
897 	context = g_simple_async_result_get_op_res_gpointer (simple);
898 
899 	if (g_simple_async_result_propagate_error (simple, error))
900 		return NULL;
901 
902 	return g_hash_table_ref (context->hash_table);
903 }
904 
905 static void
mail_folder_get_multiple_messages_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)906 mail_folder_get_multiple_messages_thread (GSimpleAsyncResult *simple,
907                                           GObject *object,
908                                           GCancellable *cancellable)
909 {
910 	AsyncContext *context;
911 	GError *error = NULL;
912 
913 	context = g_simple_async_result_get_op_res_gpointer (simple);
914 
915 	context->hash_table = e_mail_folder_get_multiple_messages_sync (
916 		CAMEL_FOLDER (object), context->ptr_array,
917 		cancellable, &error);
918 
919 	if (error != NULL)
920 		g_simple_async_result_take_error (simple, error);
921 }
922 
923 GHashTable *
e_mail_folder_get_multiple_messages_sync(CamelFolder * folder,GPtrArray * message_uids,GCancellable * cancellable,GError ** error)924 e_mail_folder_get_multiple_messages_sync (CamelFolder *folder,
925                                           GPtrArray *message_uids,
926                                           GCancellable *cancellable,
927                                           GError **error)
928 {
929 	GHashTable *hash_table;
930 	CamelMimeMessage *message;
931 	guint ii;
932 
933 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
934 	g_return_val_if_fail (message_uids != NULL, NULL);
935 
936 	camel_operation_push_message (
937 		cancellable,
938 		ngettext (
939 			"Retrieving %d message",
940 			"Retrieving %d messages",
941 			message_uids->len),
942 		message_uids->len);
943 
944 	hash_table = g_hash_table_new_full (
945 		(GHashFunc) g_str_hash,
946 		(GEqualFunc) g_str_equal,
947 		(GDestroyNotify) g_free,
948 		(GDestroyNotify) g_object_unref);
949 
950 	/* This is an all or nothing operation.  Destroy the
951 	 * hash table if we fail to retrieve any message. */
952 
953 	for (ii = 0; ii < message_uids->len; ii++) {
954 		const gchar *uid;
955 		gint percent;
956 
957 		uid = g_ptr_array_index (message_uids, ii);
958 		percent = ((ii + 1) * 100) / message_uids->len;
959 
960 		message = camel_folder_get_message_sync (
961 			folder, uid, cancellable, error);
962 
963 		camel_operation_progress (cancellable, percent);
964 
965 		if (CAMEL_IS_MIME_MESSAGE (message)) {
966 			g_hash_table_insert (
967 				hash_table, g_strdup (uid), message);
968 		} else {
969 			g_hash_table_destroy (hash_table);
970 			hash_table = NULL;
971 			break;
972 		}
973 	}
974 
975 	camel_operation_pop_message (cancellable);
976 
977 	return hash_table;
978 }
979 
980 void
e_mail_folder_get_multiple_messages(CamelFolder * folder,GPtrArray * message_uids,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)981 e_mail_folder_get_multiple_messages (CamelFolder *folder,
982                                      GPtrArray *message_uids,
983                                      gint io_priority,
984                                      GCancellable *cancellable,
985                                      GAsyncReadyCallback callback,
986                                      gpointer user_data)
987 {
988 	GSimpleAsyncResult *simple;
989 	AsyncContext *context;
990 
991 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
992 	g_return_if_fail (message_uids != NULL);
993 
994 	context = g_slice_new0 (AsyncContext);
995 	context->ptr_array = g_ptr_array_ref (message_uids);
996 
997 	simple = g_simple_async_result_new (
998 		G_OBJECT (folder), callback, user_data,
999 		e_mail_folder_get_multiple_messages);
1000 
1001 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1002 
1003 	g_simple_async_result_set_op_res_gpointer (
1004 		simple, context, (GDestroyNotify) async_context_free);
1005 
1006 	g_simple_async_result_run_in_thread (
1007 		simple, mail_folder_get_multiple_messages_thread,
1008 		io_priority, cancellable);
1009 
1010 	g_object_unref (simple);
1011 }
1012 
1013 GHashTable *
e_mail_folder_get_multiple_messages_finish(CamelFolder * folder,GAsyncResult * result,GError ** error)1014 e_mail_folder_get_multiple_messages_finish (CamelFolder *folder,
1015                                             GAsyncResult *result,
1016                                             GError **error)
1017 {
1018 	GSimpleAsyncResult *simple;
1019 	AsyncContext *context;
1020 
1021 	g_return_val_if_fail (
1022 		g_simple_async_result_is_valid (
1023 		result, G_OBJECT (folder),
1024 		e_mail_folder_get_multiple_messages), NULL);
1025 
1026 	simple = G_SIMPLE_ASYNC_RESULT (result);
1027 	context = g_simple_async_result_get_op_res_gpointer (simple);
1028 
1029 	if (g_simple_async_result_propagate_error (simple, error))
1030 		return NULL;
1031 
1032 	return g_hash_table_ref (context->hash_table);
1033 }
1034 
1035 static void
mail_folder_remove_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)1036 mail_folder_remove_thread (GSimpleAsyncResult *simple,
1037                            GObject *object,
1038                            GCancellable *cancellable)
1039 {
1040 	GError *error = NULL;
1041 
1042 	e_mail_folder_remove_sync (
1043 		CAMEL_FOLDER (object), cancellable, &error);
1044 
1045 	if (error != NULL)
1046 		g_simple_async_result_take_error (simple, error);
1047 }
1048 
1049 static gboolean
mail_folder_remove_recursive(CamelStore * store,CamelFolderInfo * folder_info,GCancellable * cancellable,GError ** error)1050 mail_folder_remove_recursive (CamelStore *store,
1051                               CamelFolderInfo *folder_info,
1052                               GCancellable *cancellable,
1053                               GError **error)
1054 {
1055 	gboolean success = TRUE;
1056 
1057 	while (folder_info != NULL) {
1058 		CamelFolder *folder;
1059 
1060 		if (folder_info->child != NULL) {
1061 			success = mail_folder_remove_recursive (
1062 				store, folder_info->child, cancellable, error);
1063 			if (!success)
1064 				break;
1065 		}
1066 
1067 		folder = camel_store_get_folder_sync (
1068 			store, folder_info->full_name, 0, cancellable, error);
1069 		if (folder == NULL) {
1070 			success = FALSE;
1071 			break;
1072 		}
1073 
1074 		if (!CAMEL_IS_VEE_FOLDER (folder)) {
1075 			GPtrArray *uids;
1076 			guint ii;
1077 
1078 			/* Delete every message in this folder,
1079 			 * then expunge it. */
1080 
1081 			camel_folder_freeze (folder);
1082 
1083 			uids = camel_folder_get_uids (folder);
1084 
1085 			for (ii = 0; ii < uids->len; ii++)
1086 				camel_folder_delete_message (
1087 					folder, uids->pdata[ii]);
1088 
1089 			camel_folder_free_uids (folder, uids);
1090 
1091 			success = camel_folder_synchronize_sync (
1092 				folder, TRUE, cancellable, error);
1093 
1094 			camel_folder_thaw (folder);
1095 		}
1096 
1097 		g_object_unref (folder);
1098 
1099 		if (!success)
1100 			break;
1101 
1102 		/* If the store supports subscriptions,
1103 		 * then unsubscribe from this folder. */
1104 		if (CAMEL_IS_SUBSCRIBABLE (store)) {
1105 			success = camel_subscribable_unsubscribe_folder_sync (
1106 				CAMEL_SUBSCRIBABLE (store),
1107 				folder_info->full_name,
1108 				cancellable, error);
1109 			if (!success)
1110 				break;
1111 		}
1112 
1113 		success = camel_store_delete_folder_sync (
1114 			store, folder_info->full_name, cancellable, error);
1115 		if (!success)
1116 			break;
1117 
1118 		folder_info = folder_info->next;
1119 	}
1120 
1121 	return success;
1122 }
1123 
1124 static void
follow_cancel_cb(GCancellable * cancellable,GCancellable * transparent_cancellable)1125 follow_cancel_cb (GCancellable *cancellable,
1126                   GCancellable *transparent_cancellable)
1127 {
1128 	g_cancellable_cancel (transparent_cancellable);
1129 }
1130 
1131 gboolean
e_mail_folder_remove_sync(CamelFolder * folder,GCancellable * cancellable,GError ** error)1132 e_mail_folder_remove_sync (CamelFolder *folder,
1133                            GCancellable *cancellable,
1134                            GError **error)
1135 {
1136 	CamelFolderInfo *folder_info;
1137 	CamelFolderInfo *to_remove;
1138 	CamelFolderInfo *next = NULL;
1139 	CamelStore *parent_store;
1140 	const gchar *full_name;
1141 	gchar *full_display_name;
1142 	gboolean success = TRUE;
1143 	GCancellable *transparent_cancellable = NULL;
1144 	gulong cbid = 0;
1145 
1146 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1147 
1148 	full_name = camel_folder_get_full_name (folder);
1149 	parent_store = camel_folder_get_parent_store (folder);
1150 
1151 	full_display_name = e_mail_folder_to_full_display_name (folder, NULL);
1152 	camel_operation_push_message (cancellable, _("Removing folder “%s”"),
1153 		full_display_name ? full_display_name : camel_folder_get_display_name (folder));
1154 	g_free (full_display_name);
1155 
1156 	if (cancellable) {
1157 		transparent_cancellable = g_cancellable_new ();
1158 		cbid = g_cancellable_connect (
1159 			cancellable, G_CALLBACK (follow_cancel_cb),
1160 			transparent_cancellable, NULL);
1161 	}
1162 
1163 	if ((camel_store_get_flags (parent_store) & CAMEL_STORE_CAN_DELETE_FOLDERS_AT_ONCE) != 0) {
1164 		success = camel_store_delete_folder_sync (
1165 			parent_store, full_name, transparent_cancellable, error);
1166 	} else {
1167 		folder_info = camel_store_get_folder_info_sync (
1168 			parent_store, full_name,
1169 			CAMEL_STORE_FOLDER_INFO_RECURSIVE |
1170 			CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
1171 			cancellable, error);
1172 
1173 		if (folder_info == NULL) {
1174 			success = FALSE;
1175 			goto exit;
1176 		}
1177 
1178 		to_remove = folder_info;
1179 
1180 		/* For cases when the top-level folder_info contains siblings,
1181 		 * such as when full_name contains a wildcard letter, compare
1182 		 * the folder name against folder_info->full_name to avoid
1183 		 * removing more folders than requested. */
1184 		if (folder_info->next != NULL) {
1185 			while (to_remove != NULL) {
1186 				if (g_strcmp0 (to_remove->full_name, full_name) == 0)
1187 					break;
1188 				to_remove = to_remove->next;
1189 			}
1190 
1191 			/* XXX Should we set a GError and return FALSE here? */
1192 			if (to_remove == NULL) {
1193 				g_warning ("%s: Failed to find folder '%s'", G_STRFUNC, full_name);
1194 				camel_folder_info_free (folder_info);
1195 				success = TRUE;
1196 				goto exit;
1197 			}
1198 
1199 			/* Prevent iterating over siblings. */
1200 			next = to_remove->next;
1201 			to_remove->next = NULL;
1202 		}
1203 
1204 		success = mail_folder_remove_recursive (
1205 				parent_store, to_remove, transparent_cancellable, error);
1206 
1207 		/* Restore the folder_info tree to its original
1208 		 * state so we don't leak folder_info nodes. */
1209 		to_remove->next = next;
1210 
1211 		camel_folder_info_free (folder_info);
1212 	}
1213 
1214 exit:
1215 	if (transparent_cancellable) {
1216 		g_cancellable_disconnect (cancellable, cbid);
1217 		g_object_unref (transparent_cancellable);
1218 	}
1219 
1220 	camel_operation_pop_message (cancellable);
1221 
1222 	return success;
1223 }
1224 
1225 void
e_mail_folder_remove(CamelFolder * folder,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1226 e_mail_folder_remove (CamelFolder *folder,
1227                       gint io_priority,
1228                       GCancellable *cancellable,
1229                       GAsyncReadyCallback callback,
1230                       gpointer user_data)
1231 {
1232 	GSimpleAsyncResult *simple;
1233 
1234 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
1235 
1236 	simple = g_simple_async_result_new (
1237 		G_OBJECT (folder), callback,
1238 		user_data, e_mail_folder_remove);
1239 
1240 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1241 
1242 	g_simple_async_result_run_in_thread (
1243 		simple, mail_folder_remove_thread,
1244 		io_priority, cancellable);
1245 
1246 	g_object_unref (simple);
1247 }
1248 
1249 gboolean
e_mail_folder_remove_finish(CamelFolder * folder,GAsyncResult * result,GError ** error)1250 e_mail_folder_remove_finish (CamelFolder *folder,
1251                              GAsyncResult *result,
1252                              GError **error)
1253 {
1254 	GSimpleAsyncResult *simple;
1255 
1256 	g_return_val_if_fail (
1257 		g_simple_async_result_is_valid (
1258 		result, G_OBJECT (folder),
1259 		e_mail_folder_remove), FALSE);
1260 
1261 	simple = G_SIMPLE_ASYNC_RESULT (result);
1262 
1263 	/* Assume success unless a GError is set. */
1264 	return !g_simple_async_result_propagate_error (simple, error);
1265 }
1266 
1267 static void
mail_folder_remove_attachments_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)1268 mail_folder_remove_attachments_thread (GSimpleAsyncResult *simple,
1269                                        GObject *object,
1270                                        GCancellable *cancellable)
1271 {
1272 	AsyncContext *context;
1273 	GError *error = NULL;
1274 
1275 	context = g_simple_async_result_get_op_res_gpointer (simple);
1276 
1277 	e_mail_folder_remove_attachments_sync (
1278 		CAMEL_FOLDER (object), context->ptr_array,
1279 		cancellable, &error);
1280 
1281 	if (error != NULL)
1282 		g_simple_async_result_take_error (simple, error);
1283 }
1284 
1285 static gboolean
mail_folder_strip_message_level(CamelMimePart * in_part,GCancellable * cancellable)1286 mail_folder_strip_message_level (CamelMimePart *in_part,
1287 				 GCancellable *cancellable)
1288 {
1289 	CamelDataWrapper *content;
1290 	CamelMultipart *multipart;
1291 	gboolean modified = FALSE;
1292 	guint ii, n_parts;
1293 
1294 	g_return_val_if_fail (CAMEL_IS_MIME_PART (in_part), FALSE);
1295 
1296 	content = camel_medium_get_content (CAMEL_MEDIUM (in_part));
1297 
1298 	if (CAMEL_IS_MIME_MESSAGE (content)) {
1299 		return mail_folder_strip_message_level (CAMEL_MIME_PART (content), cancellable);
1300 	}
1301 
1302 	if (!CAMEL_IS_MULTIPART (content))
1303 		return FALSE;
1304 
1305 	multipart = CAMEL_MULTIPART (content);
1306 	n_parts = camel_multipart_get_number (multipart);
1307 
1308 	/* Replace MIME parts with "attachment" or "inline" dispositions
1309 	 * with a small "text/plain" part saying the file was removed. */
1310 	for (ii = 0; ii < n_parts && !g_cancellable_is_cancelled (cancellable); ii++) {
1311 		CamelMimePart *mime_part;
1312 		const gchar *disposition;
1313 		gboolean is_attachment;
1314 
1315 		mime_part = camel_multipart_get_part (multipart, ii);
1316 		disposition = camel_mime_part_get_disposition (mime_part);
1317 
1318 		is_attachment =
1319 			(g_strcmp0 (disposition, "attachment") == 0) ||
1320 			(g_strcmp0 (disposition, "inline") == 0);
1321 
1322 		if (is_attachment) {
1323 			const gchar *filename;
1324 			const gchar *content_type;
1325 			gchar *content;
1326 
1327 			disposition = "inline";
1328 			content_type = "text/plain";
1329 			filename = camel_mime_part_get_filename (mime_part);
1330 
1331 			if (filename != NULL && *filename != '\0')
1332 				content = g_strdup_printf (
1333 					_("File “%s” has been removed."),
1334 					filename);
1335 			else
1336 				content = g_strdup (
1337 					_("File has been removed."));
1338 
1339 			camel_mime_part_set_content (
1340 				mime_part, content,
1341 				strlen (content), content_type);
1342 			camel_mime_part_set_content_type (
1343 				mime_part, content_type);
1344 			camel_mime_part_set_disposition (
1345 				mime_part, disposition);
1346 
1347 			modified = TRUE;
1348 		} else {
1349 			modified = mail_folder_strip_message_level (mime_part, cancellable) || modified;
1350 		}
1351 	}
1352 
1353 	return modified;
1354 }
1355 
1356 /* Helper for e_mail_folder_remove_attachments_sync() */
1357 static gboolean
mail_folder_strip_message(CamelFolder * folder,CamelMimeMessage * message,const gchar * message_uid,GCancellable * cancellable,GError ** error)1358 mail_folder_strip_message (CamelFolder *folder,
1359                            CamelMimeMessage *message,
1360                            const gchar *message_uid,
1361                            GCancellable *cancellable,
1362                            GError **error)
1363 {
1364 	gboolean modified;
1365 	gboolean success = TRUE;
1366 
1367 	modified = mail_folder_strip_message_level (CAMEL_MIME_PART (message), cancellable);
1368 
1369 	/* Append the modified message with removed attachments to
1370 	 * the folder and mark the original message for deletion. */
1371 	if (modified) {
1372 		CamelMessageInfo *orig_info;
1373 		CamelMessageInfo *copy_info;
1374 		CamelMessageFlags flags;
1375 		const CamelNameValueArray *headers;
1376 
1377 		headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
1378 		orig_info = camel_folder_get_message_info (folder, message_uid);
1379 		copy_info = camel_message_info_new_from_headers (NULL, headers);
1380 
1381 		flags = camel_folder_get_message_flags (folder, message_uid);
1382 		camel_message_info_set_flags (copy_info, flags, flags);
1383 
1384 		success = camel_folder_append_message_sync (
1385 			folder, message, copy_info, NULL, cancellable, error);
1386 		if (success)
1387 			camel_message_info_set_flags (
1388 				orig_info,
1389 				CAMEL_MESSAGE_DELETED,
1390 				CAMEL_MESSAGE_DELETED);
1391 
1392 		g_clear_object (&orig_info);
1393 		g_clear_object (&copy_info);
1394 	}
1395 
1396 	return success;
1397 }
1398 
1399 gboolean
e_mail_folder_remove_attachments_sync(CamelFolder * folder,GPtrArray * message_uids,GCancellable * cancellable,GError ** error)1400 e_mail_folder_remove_attachments_sync (CamelFolder *folder,
1401                                        GPtrArray *message_uids,
1402                                        GCancellable *cancellable,
1403                                        GError **error)
1404 {
1405 	gboolean success = TRUE;
1406 	guint ii;
1407 
1408 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1409 	g_return_val_if_fail (message_uids != NULL, FALSE);
1410 
1411 	camel_folder_freeze (folder);
1412 
1413 	camel_operation_push_message (cancellable, _("Removing attachments"));
1414 
1415 	for (ii = 0; success && ii < message_uids->len; ii++) {
1416 		CamelMimeMessage *message;
1417 		CamelFolder *real_folder = NULL, *use_folder;
1418 		gchar *real_message_uid = NULL;
1419 		const gchar *uid, *use_message_uid;
1420 		gint percent;
1421 
1422 		uid = g_ptr_array_index (message_uids, ii);
1423 
1424 		em_utils_get_real_folder_and_message_uid (folder, uid, &real_folder, NULL, &real_message_uid);
1425 
1426 		use_folder = real_folder ? real_folder : folder;
1427 		use_message_uid = real_message_uid ? real_message_uid : uid;
1428 		message = camel_folder_get_message_sync (use_folder, use_message_uid, cancellable, error);
1429 
1430 		if (message == NULL) {
1431 			g_clear_object (&real_folder);
1432 			g_free (real_message_uid);
1433 			success = FALSE;
1434 			break;
1435 		}
1436 
1437 		success = mail_folder_strip_message (use_folder, message, use_message_uid, cancellable, error);
1438 
1439 		percent = ((ii + 1) * 100) / message_uids->len;
1440 		camel_operation_progress (cancellable, percent);
1441 
1442 		g_clear_object (&real_folder);
1443 		g_clear_object (&message);
1444 		g_free (real_message_uid);
1445 	}
1446 
1447 	camel_operation_pop_message (cancellable);
1448 
1449 	if (success)
1450 		camel_folder_synchronize_sync (
1451 			folder, FALSE, cancellable, error);
1452 
1453 	camel_folder_thaw (folder);
1454 
1455 	return success;
1456 }
1457 
1458 void
e_mail_folder_remove_attachments(CamelFolder * folder,GPtrArray * message_uids,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1459 e_mail_folder_remove_attachments (CamelFolder *folder,
1460                                   GPtrArray *message_uids,
1461                                   gint io_priority,
1462                                   GCancellable *cancellable,
1463                                   GAsyncReadyCallback callback,
1464                                   gpointer user_data)
1465 {
1466 	GSimpleAsyncResult *simple;
1467 	AsyncContext *context;
1468 
1469 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
1470 	g_return_if_fail (message_uids != NULL);
1471 
1472 	context = g_slice_new0 (AsyncContext);
1473 	context->ptr_array = g_ptr_array_ref (message_uids);
1474 
1475 	simple = g_simple_async_result_new (
1476 		G_OBJECT (folder), callback, user_data,
1477 		e_mail_folder_remove_attachments);
1478 
1479 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1480 
1481 	g_simple_async_result_set_op_res_gpointer (
1482 		simple, context, (GDestroyNotify) async_context_free);
1483 
1484 	g_simple_async_result_run_in_thread (
1485 		simple, mail_folder_remove_attachments_thread,
1486 		io_priority, cancellable);
1487 
1488 	g_object_unref (simple);
1489 }
1490 
1491 gboolean
e_mail_folder_remove_attachments_finish(CamelFolder * folder,GAsyncResult * result,GError ** error)1492 e_mail_folder_remove_attachments_finish (CamelFolder *folder,
1493                                          GAsyncResult *result,
1494                                          GError **error)
1495 {
1496 	GSimpleAsyncResult *simple;
1497 
1498 	g_return_val_if_fail (
1499 		g_simple_async_result_is_valid (
1500 		result, G_OBJECT (folder),
1501 		e_mail_folder_remove_attachments), FALSE);
1502 
1503 	simple = G_SIMPLE_ASYNC_RESULT (result);
1504 
1505 	/* Assume success unless a GError is set. */
1506 	return !g_simple_async_result_propagate_error (simple, error);
1507 }
1508 
1509 static void
mail_folder_save_messages_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)1510 mail_folder_save_messages_thread (GSimpleAsyncResult *simple,
1511                                   GObject *object,
1512                                   GCancellable *cancellable)
1513 {
1514 	AsyncContext *context;
1515 	GError *error = NULL;
1516 
1517 	context = g_simple_async_result_get_op_res_gpointer (simple);
1518 
1519 	e_mail_folder_save_messages_sync (
1520 		CAMEL_FOLDER (object), context->ptr_array,
1521 		context->destination, cancellable, &error);
1522 
1523 	if (error != NULL)
1524 		g_simple_async_result_take_error (simple, error);
1525 }
1526 
1527 /* Helper for e_mail_folder_save_messages_sync() */
1528 static void
mail_folder_save_prepare_part(CamelMimePart * mime_part)1529 mail_folder_save_prepare_part (CamelMimePart *mime_part)
1530 {
1531 	CamelDataWrapper *content;
1532 
1533 	content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
1534 
1535 	if (content == NULL)
1536 		return;
1537 
1538 	if (CAMEL_IS_MULTIPART (content)) {
1539 		guint n_parts, ii;
1540 
1541 		n_parts = camel_multipart_get_number (
1542 			CAMEL_MULTIPART (content));
1543 		for (ii = 0; ii < n_parts; ii++) {
1544 			mime_part = camel_multipart_get_part (
1545 				CAMEL_MULTIPART (content), ii);
1546 			mail_folder_save_prepare_part (mime_part);
1547 		}
1548 
1549 	} else if (CAMEL_IS_MIME_MESSAGE (content)) {
1550 		mail_folder_save_prepare_part (CAMEL_MIME_PART (content));
1551 
1552 	} else {
1553 		CamelContentType *type;
1554 
1555 		/* Save textual parts as 8-bit, not encoded. */
1556 		type = camel_data_wrapper_get_mime_type_field (content);
1557 		if (camel_content_type_is (type, "text", "*"))
1558 			camel_mime_part_set_encoding (
1559 				mime_part, CAMEL_TRANSFER_ENCODING_8BIT);
1560 	}
1561 }
1562 
1563 gboolean
e_mail_folder_save_messages_sync(CamelFolder * folder,GPtrArray * message_uids,GFile * destination,GCancellable * cancellable,GError ** error)1564 e_mail_folder_save_messages_sync (CamelFolder *folder,
1565                                   GPtrArray *message_uids,
1566                                   GFile *destination,
1567                                   GCancellable *cancellable,
1568                                   GError **error)
1569 {
1570 	GFileOutputStream *file_output_stream;
1571 	CamelStream *base_stream = NULL;
1572 	GByteArray *byte_array;
1573 	gboolean success = TRUE;
1574 	guint ii;
1575 
1576 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
1577 	g_return_val_if_fail (message_uids != NULL, FALSE);
1578 	g_return_val_if_fail (G_IS_FILE (destination), FALSE);
1579 
1580 	/* Need at least one message UID to save. */
1581 	g_return_val_if_fail (message_uids->len > 0, FALSE);
1582 
1583 	camel_operation_push_message (
1584 		cancellable, ngettext (
1585 			"Saving %d message",
1586 			"Saving %d messages",
1587 			message_uids->len),
1588 		message_uids->len);
1589 
1590 	file_output_stream = g_file_replace (
1591 		destination, NULL, FALSE,
1592 		G_FILE_CREATE_PRIVATE |
1593 		G_FILE_CREATE_REPLACE_DESTINATION,
1594 		cancellable, error);
1595 
1596 	if (file_output_stream == NULL) {
1597 		camel_operation_pop_message (cancellable);
1598 		return FALSE;
1599 	}
1600 
1601 	byte_array = g_byte_array_new ();
1602 
1603 	for (ii = 0; ii < message_uids->len; ii++) {
1604 		CamelMimeMessage *message;
1605 		CamelMimeFilter *filter;
1606 		CamelStream *stream;
1607 		const gchar *uid;
1608 		gchar *from_line;
1609 		gint percent;
1610 		gint retval;
1611 
1612 		if (base_stream != NULL)
1613 			g_object_unref (base_stream);
1614 
1615 		/* CamelStreamMem does NOT take ownership of the byte
1616 		 * array when set with camel_stream_mem_set_byte_array().
1617 		 * This allows us to reuse the same memory slab for each
1618 		 * message, which is slightly more efficient. */
1619 		base_stream = camel_stream_mem_new ();
1620 		camel_stream_mem_set_byte_array (
1621 			CAMEL_STREAM_MEM (base_stream), byte_array);
1622 
1623 		uid = g_ptr_array_index (message_uids, ii);
1624 
1625 		message = camel_folder_get_message_sync (
1626 			folder, uid, cancellable, error);
1627 		if (message == NULL) {
1628 			success = FALSE;
1629 			goto exit;
1630 		}
1631 
1632 		mail_folder_save_prepare_part (CAMEL_MIME_PART (message));
1633 
1634 		from_line = camel_mime_message_build_mbox_from (message);
1635 		g_return_val_if_fail (from_line != NULL, FALSE);
1636 
1637 		success = g_output_stream_write_all (
1638 			G_OUTPUT_STREAM (file_output_stream),
1639 			from_line, strlen (from_line), NULL,
1640 			cancellable, error);
1641 
1642 		g_free (from_line);
1643 
1644 		if (!success) {
1645 			g_object_unref (message);
1646 			goto exit;
1647 		}
1648 
1649 		filter = camel_mime_filter_from_new ();
1650 		stream = camel_stream_filter_new (base_stream);
1651 		camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
1652 
1653 		retval = camel_data_wrapper_write_to_stream_sync (
1654 			CAMEL_DATA_WRAPPER (message),
1655 			stream, cancellable, error);
1656 
1657 		g_object_unref (filter);
1658 		g_object_unref (stream);
1659 
1660 		if (retval == -1) {
1661 			g_object_unref (message);
1662 			goto exit;
1663 		}
1664 
1665 		g_byte_array_append (byte_array, (guint8 *) "\n", 1);
1666 
1667 		success = g_output_stream_write_all (
1668 			G_OUTPUT_STREAM (file_output_stream),
1669 			byte_array->data, byte_array->len,
1670 			NULL, cancellable, error);
1671 
1672 		if (!success) {
1673 			g_object_unref (message);
1674 			goto exit;
1675 		}
1676 
1677 		percent = ((ii + 1) * 100) / message_uids->len;
1678 		camel_operation_progress (cancellable, percent);
1679 
1680 		/* Reset the byte array for the next message. */
1681 		g_byte_array_set_size (byte_array, 0);
1682 
1683 		g_object_unref (message);
1684 	}
1685 
1686 exit:
1687 	if (base_stream != NULL)
1688 		g_object_unref (base_stream);
1689 
1690 	g_byte_array_free (byte_array, TRUE);
1691 
1692 	g_object_unref (file_output_stream);
1693 
1694 	camel_operation_pop_message (cancellable);
1695 
1696 	if (!success) {
1697 		/* Try deleting the destination file. */
1698 		g_file_delete (destination, NULL, NULL);
1699 	}
1700 
1701 	return success;
1702 }
1703 
1704 void
e_mail_folder_save_messages(CamelFolder * folder,GPtrArray * message_uids,GFile * destination,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1705 e_mail_folder_save_messages (CamelFolder *folder,
1706                              GPtrArray *message_uids,
1707                              GFile *destination,
1708                              gint io_priority,
1709                              GCancellable *cancellable,
1710                              GAsyncReadyCallback callback,
1711                              gpointer user_data)
1712 {
1713 	GSimpleAsyncResult *simple;
1714 	AsyncContext *context;
1715 
1716 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
1717 	g_return_if_fail (message_uids != NULL);
1718 	g_return_if_fail (G_IS_FILE (destination));
1719 
1720 	/* Need at least one message UID to save. */
1721 	g_return_if_fail (message_uids->len > 0);
1722 
1723 	context = g_slice_new0 (AsyncContext);
1724 	context->ptr_array = g_ptr_array_ref (message_uids);
1725 	context->destination = g_object_ref (destination);
1726 
1727 	simple = g_simple_async_result_new (
1728 		G_OBJECT (folder), callback, user_data,
1729 		e_mail_folder_save_messages);
1730 
1731 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1732 
1733 	g_simple_async_result_set_op_res_gpointer (
1734 		simple, context, (GDestroyNotify) async_context_free);
1735 
1736 	g_simple_async_result_run_in_thread (
1737 		simple, mail_folder_save_messages_thread,
1738 		io_priority, cancellable);
1739 
1740 	g_object_unref (simple);
1741 }
1742 
1743 gboolean
e_mail_folder_save_messages_finish(CamelFolder * folder,GAsyncResult * result,GError ** error)1744 e_mail_folder_save_messages_finish (CamelFolder *folder,
1745                                     GAsyncResult *result,
1746                                     GError **error)
1747 {
1748 	GSimpleAsyncResult *simple;
1749 
1750 	g_return_val_if_fail (
1751 		g_simple_async_result_is_valid (
1752 		result, G_OBJECT (folder),
1753 		e_mail_folder_save_messages), FALSE);
1754 
1755 	simple = G_SIMPLE_ASYNC_RESULT (result);
1756 
1757 	/* Assume success unless a GError is set. */
1758 	return !g_simple_async_result_propagate_error (simple, error);
1759 }
1760 
1761 /**
1762  * e_mail_folder_uri_build:
1763  * @store: a #CamelStore
1764  * @folder_name: a folder name
1765  *
1766  * Builds a folder URI string from @store and @folder_name.
1767  *
1768  * Returns: a newly-allocated folder URI string
1769  **/
1770 gchar *
e_mail_folder_uri_build(CamelStore * store,const gchar * folder_name)1771 e_mail_folder_uri_build (CamelStore *store,
1772                          const gchar *folder_name)
1773 {
1774 	const gchar *uid;
1775 	gchar *encoded_name;
1776 	gchar *encoded_uid;
1777 	gchar *uri;
1778 
1779 	g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
1780 	g_return_val_if_fail (folder_name != NULL, NULL);
1781 
1782 	/* Skip the leading slash, if present. */
1783 	if (*folder_name == '/')
1784 		folder_name++;
1785 
1786 	uid = camel_service_get_uid (CAMEL_SERVICE (store));
1787 
1788 	encoded_uid = camel_url_encode (uid, ":;@/");
1789 	encoded_name = camel_url_encode (folder_name, ":;@?#");
1790 
1791 	uri = g_strdup_printf ("folder://%s/%s", encoded_uid, encoded_name);
1792 
1793 	g_free (encoded_uid);
1794 	g_free (encoded_name);
1795 
1796 	return uri;
1797 }
1798 
1799 /**
1800  * e_mail_folder_uri_parse:
1801  * @session: a #CamelSession
1802  * @folder_uri: a folder URI
1803  * @out_store: return location for a #CamelStore, or %NULL
1804  * @out_folder_name: return location for a folder name, or %NULL
1805  * @error: return location for a #GError, or %NULL
1806  *
1807  * Parses a folder URI generated by e_mail_folder_uri_build() and
1808  * returns the corresponding #CamelStore instance in @out_store and
1809  * folder name string in @out_folder_name.  If the URI is malformed
1810  * or no corresponding store exists, the function sets @error and
1811  * returns %FALSE.
1812  *
1813  * If the function is able to parse the URI, the #CamelStore instance
1814  * set in @out_store should be unreferenced with g_object_unref() when
1815  * done with it, and the folder name string set in @out_folder_name
1816  * should be freed with g_free().
1817  *
1818  * The function also handles older style URIs, such as ones where the
1819  * #CamelStore's #CamelStore::uri string was embedded directly in the
1820  * folder URI, and account-based URIs that used an "email://" prefix.
1821  *
1822  * Returns: %TRUE if @folder_uri could be parsed, %FALSE otherwise
1823  **/
1824 gboolean
e_mail_folder_uri_parse(CamelSession * session,const gchar * folder_uri,CamelStore ** out_store,gchar ** out_folder_name,GError ** error)1825 e_mail_folder_uri_parse (CamelSession *session,
1826                          const gchar *folder_uri,
1827                          CamelStore **out_store,
1828                          gchar **out_folder_name,
1829                          GError **error)
1830 {
1831 	CamelURL *url;
1832 	CamelService *service = NULL;
1833 	gchar *folder_name = NULL;
1834 	gboolean success = FALSE;
1835 
1836 	g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
1837 	g_return_val_if_fail (folder_uri != NULL, FALSE);
1838 
1839 	url = camel_url_new (folder_uri, error);
1840 	if (url == NULL)
1841 		return FALSE;
1842 
1843 	/* Current URI Format: 'folder://' STORE_UID '/' FOLDER_PATH */
1844 	if (g_strcmp0 (url->protocol, "folder") == 0) {
1845 
1846 		if (url->host != NULL) {
1847 			gchar *uid;
1848 
1849 			if (url->user == NULL || *url->user == '\0')
1850 				uid = g_strdup (url->host);
1851 			else
1852 				uid = g_strconcat (
1853 					url->user, "@", url->host, NULL);
1854 
1855 			service = camel_session_ref_service (session, uid);
1856 			g_free (uid);
1857 		}
1858 
1859 		if (url->path != NULL && *url->path == '/')
1860 			folder_name = camel_url_decode_path (url->path + 1);
1861 
1862 	/* This style was used to reference accounts by UID before
1863 	 * CamelServices themselves had UIDs.  Some examples are:
1864 	 *
1865 	 * Special cases:
1866 	 *
1867 	 *   'email://local@local/' FOLDER_PATH
1868 	 *   'email://vfolder@local/' FOLDER_PATH
1869 	 *
1870 	 * General case:
1871 	 *
1872 	 *   'email://' ACCOUNT_UID '/' FOLDER_PATH
1873 	 *
1874 	 * Note: ACCOUNT_UID is now equivalent to STORE_UID, and
1875 	 *       the STORE_UIDs for the special cases are 'local'
1876 	 *       and 'vfolder'.
1877 	 */
1878 	} else if (g_strcmp0 (url->protocol, "email") == 0) {
1879 		gchar *uid = NULL;
1880 
1881 		/* Handle the special cases. */
1882 		if (g_strcmp0 (url->host, "local") == 0) {
1883 			if (g_strcmp0 (url->user, "local") == 0)
1884 				uid = g_strdup ("local");
1885 			if (g_strcmp0 (url->user, "vfolder") == 0)
1886 				uid = g_strdup ("vfolder");
1887 		}
1888 
1889 		/* Handle the general case. */
1890 		if (uid == NULL && url->host != NULL) {
1891 			if (url->user == NULL)
1892 				uid = g_strdup (url->host);
1893 			else
1894 				uid = g_strdup_printf (
1895 					"%s@%s", url->user, url->host);
1896 		}
1897 
1898 		if (uid != NULL) {
1899 			service = camel_session_ref_service (session, uid);
1900 			g_free (uid);
1901 		}
1902 
1903 		if (url->path != NULL && *url->path == '/')
1904 			folder_name = camel_url_decode_path (url->path + 1);
1905 
1906 	/* CamelFolderInfo URIs used to embed the store's URI, so the
1907 	 * folder name is appended as either a path part or a fragment
1908 	 * part, depending whether the store's URI used the path part.
1909 	 * To determine which it is, you have to check the provider
1910 	 * flags for CAMEL_URL_FRAGMENT_IS_PATH. */
1911 	} else {
1912 		gboolean local_mbox_folder;
1913 
1914 		/* In Evolution 2.x, the local mail store used mbox
1915 		 * format.  camel_session_ref_service_by_url() won't
1916 		 * match "mbox:///.../mail/local" folder URIs, since
1917 		 * the local mail store is now Maildir format.  Test
1918 		 * for this corner case and work around it.
1919 		 *
1920 		 * The folder path is kept in the fragment part of the
1921 		 * URL which makes it easy to test the filesystem path.
1922 		 * The suffix "evolution/mail/local" should match both
1923 		 * the current XDG-compliant location and the old "dot
1924 		 * folder" location (~/.evolution/mail/local). */
1925 		local_mbox_folder =
1926 			(g_strcmp0 (url->protocol, "mbox") == 0) &&
1927 			(url->path != NULL) &&
1928 			g_str_has_suffix (url->path, "evolution/mail/local");
1929 
1930 		if (local_mbox_folder) {
1931 			service = camel_session_ref_service (session, "local");
1932 		} else {
1933 			service = camel_session_ref_service_by_url (
1934 				session, url, CAMEL_PROVIDER_STORE);
1935 		}
1936 
1937 		if (CAMEL_IS_STORE (service)) {
1938 			CamelProvider *provider;
1939 
1940 			provider = camel_service_get_provider (service);
1941 
1942 			if (provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH)
1943 				folder_name = g_strdup (url->fragment);
1944 			else if (url->path != NULL && *url->path == '/')
1945 				folder_name = g_strdup (url->path + 1);
1946 		}
1947 	}
1948 
1949 	if (CAMEL_IS_STORE (service) && folder_name != NULL) {
1950 		if (out_store != NULL)
1951 			*out_store = CAMEL_STORE (g_object_ref (service));
1952 
1953 		if (out_folder_name != NULL) {
1954 			*out_folder_name = folder_name;
1955 			folder_name = NULL;
1956 		}
1957 
1958 		success = TRUE;
1959 	} else {
1960 		g_set_error (
1961 			error, CAMEL_FOLDER_ERROR,
1962 			CAMEL_FOLDER_ERROR_INVALID,
1963 			_("Invalid folder URI “%s”"),
1964 			folder_uri);
1965 	}
1966 
1967 	if (service != NULL)
1968 		g_object_unref (service);
1969 
1970 	g_free (folder_name);
1971 
1972 	camel_url_free (url);
1973 
1974 	return success;
1975 }
1976 
1977 /**
1978  * e_mail_folder_uri_equal:
1979  * @session: a #CamelSession
1980  * @folder_uri_a: a folder URI
1981  * @folder_uri_b: another folder URI
1982  *
1983  * Compares two folder URIs for equality.  If either URI is invalid,
1984  * the function returns %FALSE.
1985  *
1986  * Returns: %TRUE if the URIs are equal, %FALSE if not
1987  **/
1988 gboolean
e_mail_folder_uri_equal(CamelSession * session,const gchar * folder_uri_a,const gchar * folder_uri_b)1989 e_mail_folder_uri_equal (CamelSession *session,
1990                          const gchar *folder_uri_a,
1991                          const gchar *folder_uri_b)
1992 {
1993 	CamelStore *store_a;
1994 	CamelStore *store_b;
1995 	CamelStoreClass *class;
1996 	gchar *folder_name_a;
1997 	gchar *folder_name_b;
1998 	gboolean success_a;
1999 	gboolean success_b;
2000 	gboolean equal = FALSE;
2001 
2002 	g_return_val_if_fail (CAMEL_IS_SESSION (session), FALSE);
2003 	g_return_val_if_fail (folder_uri_a != NULL, FALSE);
2004 	g_return_val_if_fail (folder_uri_b != NULL, FALSE);
2005 
2006 	success_a = e_mail_folder_uri_parse (
2007 		session, folder_uri_a, &store_a, &folder_name_a, NULL);
2008 
2009 	success_b = e_mail_folder_uri_parse (
2010 		session, folder_uri_b, &store_b, &folder_name_b, NULL);
2011 
2012 	if (!success_a || !success_b)
2013 		goto exit;
2014 
2015 	if (store_a != store_b)
2016 		goto exit;
2017 
2018 	/* Doesn't matter which store we use since they're the same. */
2019 	class = CAMEL_STORE_GET_CLASS (store_a);
2020 	g_return_val_if_fail (class->equal_folder_name != NULL, FALSE);
2021 
2022 	equal = class->equal_folder_name (folder_name_a, folder_name_b);
2023 
2024 exit:
2025 	if (success_a) {
2026 		g_object_unref (store_a);
2027 		g_free (folder_name_a);
2028 	}
2029 
2030 	if (success_b) {
2031 		g_object_unref (store_b);
2032 		g_free (folder_name_b);
2033 	}
2034 
2035 	return equal;
2036 }
2037 
2038 /**
2039  * e_mail_folder_uri_from_folder:
2040  * @folder: a #CamelFolder
2041  *
2042  * Convenience function for building a folder URI from a #CamelFolder.
2043  * Free the returned URI string with g_free().
2044  *
2045  * Returns: a newly-allocated folder URI string
2046  **/
2047 gchar *
e_mail_folder_uri_from_folder(CamelFolder * folder)2048 e_mail_folder_uri_from_folder (CamelFolder *folder)
2049 {
2050 	CamelStore *store;
2051 	const gchar *folder_name;
2052 
2053 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
2054 
2055 	store = camel_folder_get_parent_store (folder);
2056 	folder_name = camel_folder_get_full_name (folder);
2057 
2058 	return e_mail_folder_uri_build (store, folder_name);
2059 }
2060 
2061 /**
2062  * e_mail_folder_uri_to_markup:
2063  * @session: a #CamelSession
2064  * @folder_uri: a folder URI
2065  * @error: return location for a #GError, or %NULL
2066  *
2067  * Converts @folder_uri to a markup string suitable for displaying to users.
2068  * The string consists of the #CamelStore display name (in bold), followed
2069  * by the folder path.  If the URI is malformed or no corresponding store
2070  * exists, the function sets @error and returns %NULL.  Free the returned
2071  * string with g_free().
2072  *
2073  * Returns: a newly-allocated markup string, or %NULL
2074  **/
2075 gchar *
e_mail_folder_uri_to_markup(CamelSession * session,const gchar * folder_uri,GError ** error)2076 e_mail_folder_uri_to_markup (CamelSession *session,
2077                              const gchar *folder_uri,
2078                              GError **error)
2079 {
2080 	CamelStore *store = NULL;
2081 	const gchar *display_name;
2082 	gchar *folder_name = NULL;
2083 	gchar *markup;
2084 	gboolean success;
2085 
2086 	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
2087 	g_return_val_if_fail (folder_uri != NULL, NULL);
2088 
2089 	success = e_mail_folder_uri_parse (
2090 		session, folder_uri, &store, &folder_name, error);
2091 
2092 	if (!success)
2093 		return NULL;
2094 
2095 	g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
2096 	g_return_val_if_fail (folder_name != NULL, NULL);
2097 
2098 	display_name = camel_service_get_display_name (CAMEL_SERVICE (store));
2099 
2100 	markup = g_markup_printf_escaped (
2101 		"<b>%s</b> : %s", display_name, folder_name);
2102 
2103 	g_object_unref (store);
2104 	g_free (folder_name);
2105 
2106 	return markup;
2107 }
2108 
2109 /**
2110  * e_mail_folder_to_full_display_name:
2111  * @folder: a #CamelFolder
2112  * @error: return location for a #GError, or %NULL
2113  *
2114  * Returns similar description as e_mail_folder_uri_to_markup(), only without markup
2115  * and rather for a @folder, than for a folder URI. Returned pointer should be freed
2116  * with g_free() when no longer needed.
2117  *
2118  * Returns: a newly-allocated string, or %NULL
2119  *
2120  * Since: 3.18
2121  **/
2122 gchar *
e_mail_folder_to_full_display_name(CamelFolder * folder,GError ** error)2123 e_mail_folder_to_full_display_name (CamelFolder *folder,
2124 				    GError **error)
2125 {
2126 	CamelSession *session;
2127 	CamelStore *store;
2128 	gchar *folder_uri, *full_display_name = NULL, *folder_name = NULL;
2129 
2130 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
2131 
2132 	folder_uri = e_mail_folder_uri_from_folder (folder);
2133 	if (!folder_uri)
2134 		return NULL;
2135 
2136 	store = camel_folder_get_parent_store (folder);
2137 	if (!store) {
2138 		g_warn_if_reached ();
2139 		g_free (folder_uri);
2140 
2141 		return NULL;
2142 	}
2143 
2144 	session = camel_service_ref_session (CAMEL_SERVICE (store));
2145 	if (!session) {
2146 		g_warn_if_reached ();
2147 		g_free (folder_uri);
2148 
2149 		return NULL;
2150 	}
2151 
2152 	if (e_mail_folder_uri_parse (session, folder_uri, NULL, &folder_name, error)) {
2153 		const gchar *service_display_name;
2154 
2155 		service_display_name = camel_service_get_display_name (CAMEL_SERVICE (store));
2156 
2157 		if (CAMEL_IS_VEE_FOLDER (folder) && (
2158 		    g_strcmp0 (folder_name, CAMEL_VTRASH_NAME) == 0 ||
2159 		    g_strcmp0 (folder_name, CAMEL_VJUNK_NAME) == 0)) {
2160 			full_display_name = g_strdup_printf ("%s : %s", service_display_name, camel_folder_get_display_name (folder));
2161 		} else {
2162 			full_display_name = g_strdup_printf ("%s : %s", service_display_name, folder_name);
2163 		}
2164 
2165 		g_free (folder_name);
2166 	}
2167 
2168 	g_clear_object (&session);
2169 	g_free (folder_uri);
2170 
2171 	return full_display_name;
2172 }
2173