1 /*
2  * e-mail-session-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-session-utils.h"
21 
22 #include <glib/gi18n-lib.h>
23 #include <libedataserver/libedataserver.h>
24 
25 #include <libemail-engine/e-mail-folder-utils.h>
26 #include <libemail-engine/e-mail-utils.h>
27 #include <libemail-engine/mail-tools.h>
28 
29 /* User-Agent header value */
30 #define USER_AGENT ("Evolution " VERSION VERSION_SUBSTRING " " VERSION_COMMENT)
31 
32 /* FIXME: Temporary - remove this after we move filter/ to eds */
33 #define E_FILTER_SOURCE_OUTGOING "outgoing"
34 
35 typedef struct _AsyncContext AsyncContext;
36 
37 struct _AsyncContext {
38 	CamelFolder *folder;
39 
40 	CamelMimeMessage *message;
41 	CamelMessageInfo *info;
42 
43 	CamelAddress *from;
44 	CamelAddress *recipients;
45 
46 	CamelFilterDriver *driver;
47 
48 	CamelService *transport;
49 
50 	GCancellable *cancellable;
51 	gint io_priority;
52 
53 	/* X-Evolution headers */
54 	CamelNameValueArray *xev_headers;
55 
56 	GPtrArray *post_to_uris;
57 
58 	EMailLocalFolder local_id;
59 
60 	gchar *folder_uri;
61 	gchar *message_uid;
62 
63 	gboolean use_sent_folder;
64 };
65 
66 static void
async_context_free(AsyncContext * context)67 async_context_free (AsyncContext *context)
68 {
69 	g_clear_object (&context->folder);
70 	g_clear_object (&context->message);
71 	g_clear_object (&context->info);
72 	g_clear_object (&context->from);
73 	g_clear_object (&context->recipients);
74 	g_clear_object (&context->driver);
75 	g_clear_object (&context->transport);
76 
77 	if (context->cancellable != NULL) {
78 		camel_operation_pop_message (context->cancellable);
79 		g_object_unref (context->cancellable);
80 	}
81 
82 	camel_name_value_array_free (context->xev_headers);
83 
84 	if (context->post_to_uris != NULL) {
85 		g_ptr_array_foreach (
86 			context->post_to_uris, (GFunc) g_free, NULL);
87 		g_ptr_array_free (context->post_to_uris, TRUE);
88 	}
89 
90 	g_free (context->folder_uri);
91 	g_free (context->message_uid);
92 
93 	g_slice_free (AsyncContext, context);
94 }
95 
96 GQuark
e_mail_error_quark(void)97 e_mail_error_quark (void)
98 {
99 	static GQuark quark = 0;
100 
101 	if (G_UNLIKELY (quark == 0)) {
102 		const gchar *string = "e-mail-error-quark";
103 		quark = g_quark_from_static_string (string);
104 	}
105 
106 	return quark;
107 }
108 
109 static void
mail_session_append_to_local_folder_thread(GSimpleAsyncResult * simple,GObject * object,GCancellable * cancellable)110 mail_session_append_to_local_folder_thread (GSimpleAsyncResult *simple,
111                                             GObject *object,
112                                             GCancellable *cancellable)
113 {
114 	AsyncContext *context;
115 	GError *error = NULL;
116 
117 	context = g_simple_async_result_get_op_res_gpointer (simple);
118 
119 	e_mail_session_append_to_local_folder_sync (
120 		E_MAIL_SESSION (object),
121 		context->local_id, context->message,
122 		context->info, &context->message_uid,
123 		cancellable, &error);
124 
125 	if (error != NULL)
126 		g_simple_async_result_take_error (simple, error);
127 }
128 
129 gboolean
e_mail_session_append_to_local_folder_sync(EMailSession * session,EMailLocalFolder local_id,CamelMimeMessage * message,CamelMessageInfo * info,gchar ** appended_uid,GCancellable * cancellable,GError ** error)130 e_mail_session_append_to_local_folder_sync (EMailSession *session,
131                                             EMailLocalFolder local_id,
132                                             CamelMimeMessage *message,
133                                             CamelMessageInfo *info,
134                                             gchar **appended_uid,
135                                             GCancellable *cancellable,
136                                             GError **error)
137 {
138 	CamelFolder *folder;
139 	const gchar *folder_uri;
140 	gboolean success = FALSE;
141 
142 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE);
143 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
144 
145 	folder_uri = e_mail_session_get_local_folder_uri (session, local_id);
146 	g_return_val_if_fail (folder_uri != NULL, FALSE);
147 
148 	folder = e_mail_session_uri_to_folder_sync (
149 		session, folder_uri, CAMEL_STORE_FOLDER_CREATE,
150 		cancellable, error);
151 
152 	if (folder != NULL) {
153 		success = e_mail_folder_append_message_sync (
154 			folder, message, info, appended_uid,
155 			cancellable, error);
156 		g_object_unref (folder);
157 	}
158 
159 	return success;
160 }
161 
162 void
e_mail_session_append_to_local_folder(EMailSession * session,EMailLocalFolder local_id,CamelMimeMessage * message,CamelMessageInfo * info,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)163 e_mail_session_append_to_local_folder (EMailSession *session,
164                                        EMailLocalFolder local_id,
165                                        CamelMimeMessage *message,
166                                        CamelMessageInfo *info,
167                                        gint io_priority,
168                                        GCancellable *cancellable,
169                                        GAsyncReadyCallback callback,
170                                        gpointer user_data)
171 {
172 	GSimpleAsyncResult *simple;
173 	AsyncContext *context;
174 
175 	g_return_if_fail (E_IS_MAIL_SESSION (session));
176 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
177 
178 	context = g_slice_new0 (AsyncContext);
179 	context->local_id = local_id;
180 	context->message = g_object_ref (message);
181 
182 	if (info != NULL)
183 		context->info = g_object_ref (info);
184 
185 	simple = g_simple_async_result_new (
186 		G_OBJECT (session), callback, user_data,
187 		e_mail_session_append_to_local_folder);
188 
189 	g_simple_async_result_set_check_cancellable (simple, cancellable);
190 
191 	g_simple_async_result_set_op_res_gpointer (
192 		simple, context, (GDestroyNotify) async_context_free);
193 
194 	g_simple_async_result_run_in_thread (
195 		simple, mail_session_append_to_local_folder_thread,
196 		io_priority, cancellable);
197 
198 	g_object_unref (simple);
199 }
200 
201 gboolean
e_mail_session_append_to_local_folder_finish(EMailSession * session,GAsyncResult * result,gchar ** appended_uid,GError ** error)202 e_mail_session_append_to_local_folder_finish (EMailSession *session,
203                                               GAsyncResult *result,
204                                               gchar **appended_uid,
205                                               GError **error)
206 {
207 	GSimpleAsyncResult *simple;
208 	AsyncContext *context;
209 
210 	g_return_val_if_fail (
211 		g_simple_async_result_is_valid (
212 		result, G_OBJECT (session),
213 		e_mail_session_append_to_local_folder), FALSE);
214 
215 	simple = G_SIMPLE_ASYNC_RESULT (result);
216 	context = g_simple_async_result_get_op_res_gpointer (simple);
217 
218 	if (appended_uid != NULL) {
219 		*appended_uid = context->message_uid;
220 		context->message_uid = NULL;
221 	}
222 
223 	/* Assume success unless a GError is set. */
224 	return !g_simple_async_result_propagate_error (simple, error);
225 }
226 
227 static void
mail_session_handle_draft_headers_thread(GSimpleAsyncResult * simple,EMailSession * session,GCancellable * cancellable)228 mail_session_handle_draft_headers_thread (GSimpleAsyncResult *simple,
229                                           EMailSession *session,
230                                           GCancellable *cancellable)
231 {
232 	AsyncContext *context;
233 	GError *error = NULL;
234 
235 	context = g_simple_async_result_get_op_res_gpointer (simple);
236 
237 	e_mail_session_handle_draft_headers_sync (
238 		session, context->message, cancellable, &error);
239 
240 	if (error != NULL)
241 		g_simple_async_result_take_error (simple, error);
242 }
243 
244 gboolean
e_mail_session_handle_draft_headers_sync(EMailSession * session,CamelMimeMessage * message,GCancellable * cancellable,GError ** error)245 e_mail_session_handle_draft_headers_sync (EMailSession *session,
246                                           CamelMimeMessage *message,
247                                           GCancellable *cancellable,
248                                           GError **error)
249 {
250 	CamelFolder *folder;
251 	CamelMedium *medium;
252 	const gchar *folder_uri;
253 	const gchar *message_uid;
254 	const gchar *header_name;
255 	gboolean success;
256 
257 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE);
258 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
259 
260 	medium = CAMEL_MEDIUM (message);
261 
262 	header_name = "X-Evolution-Draft-Folder";
263 	folder_uri = camel_medium_get_header (medium, header_name);
264 
265 	header_name = "X-Evolution-Draft-Message";
266 	message_uid = camel_medium_get_header (medium, header_name);
267 
268 	/* Don't report errors about missing X-Evolution-Draft
269 	 * headers.  These headers are optional, so their absence
270 	 * is handled by doing nothing. */
271 	if (folder_uri == NULL || message_uid == NULL)
272 		return TRUE;
273 
274 	folder = e_mail_session_uri_to_folder_sync (
275 		session, folder_uri, 0, cancellable, error);
276 
277 	if (folder == NULL)
278 		return FALSE;
279 
280 	camel_folder_set_message_flags (
281 		folder, message_uid,
282 		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN,
283 		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);
284 
285 	success = camel_folder_synchronize_message_sync (
286 		folder, message_uid, cancellable, error);
287 
288 	g_object_unref (folder);
289 
290 	return success;
291 }
292 
293 void
e_mail_session_handle_draft_headers(EMailSession * session,CamelMimeMessage * message,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)294 e_mail_session_handle_draft_headers (EMailSession *session,
295                                      CamelMimeMessage *message,
296                                      gint io_priority,
297                                      GCancellable *cancellable,
298                                      GAsyncReadyCallback callback,
299                                      gpointer user_data)
300 {
301 	GSimpleAsyncResult *simple;
302 	AsyncContext *context;
303 
304 	g_return_if_fail (E_IS_MAIL_SESSION (session));
305 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
306 
307 	context = g_slice_new0 (AsyncContext);
308 	context->message = g_object_ref (message);
309 
310 	simple = g_simple_async_result_new (
311 		G_OBJECT (session), callback, user_data,
312 		e_mail_session_handle_draft_headers);
313 
314 	g_simple_async_result_set_check_cancellable (simple, cancellable);
315 
316 	g_simple_async_result_set_op_res_gpointer (
317 		simple, context, (GDestroyNotify) async_context_free);
318 
319 	g_simple_async_result_run_in_thread (
320 		simple, (GSimpleAsyncThreadFunc)
321 		mail_session_handle_draft_headers_thread,
322 		io_priority, cancellable);
323 
324 	g_object_unref (simple);
325 }
326 
327 gboolean
e_mail_session_handle_draft_headers_finish(EMailSession * session,GAsyncResult * result,GError ** error)328 e_mail_session_handle_draft_headers_finish (EMailSession *session,
329                                             GAsyncResult *result,
330                                             GError **error)
331 {
332 	GSimpleAsyncResult *simple;
333 
334 	g_return_val_if_fail (
335 		g_simple_async_result_is_valid (
336 		result, G_OBJECT (session),
337 		e_mail_session_handle_draft_headers), FALSE);
338 
339 	simple = G_SIMPLE_ASYNC_RESULT (result);
340 
341 	/* Assume success unless a GError is set. */
342 	return !g_simple_async_result_propagate_error (simple, error);
343 }
344 
345 static void
mail_session_handle_source_headers_thread(GSimpleAsyncResult * simple,EMailSession * session,GCancellable * cancellable)346 mail_session_handle_source_headers_thread (GSimpleAsyncResult *simple,
347                                            EMailSession *session,
348                                            GCancellable *cancellable)
349 {
350 	AsyncContext *context;
351 	GError *error = NULL;
352 
353 	context = g_simple_async_result_get_op_res_gpointer (simple);
354 
355 	e_mail_session_handle_source_headers_sync (
356 		session, context->message, cancellable, &error);
357 
358 	if (error != NULL)
359 		g_simple_async_result_take_error (simple, error);
360 }
361 
362 gboolean
e_mail_session_handle_source_headers_sync(EMailSession * session,CamelMimeMessage * message,GCancellable * cancellable,GError ** error)363 e_mail_session_handle_source_headers_sync (EMailSession *session,
364                                            CamelMimeMessage *message,
365                                            GCancellable *cancellable,
366                                            GError **error)
367 {
368 	CamelFolder *folder;
369 	CamelMedium *medium;
370 	CamelMessageFlags flags = 0;
371 	const gchar *folder_uri;
372 	const gchar *message_uid;
373 	const gchar *flag_string;
374 	const gchar *header_name;
375 	gboolean success;
376 	guint length, ii;
377 	gchar **tokens;
378 	gchar *string;
379 
380 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE);
381 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE);
382 
383 	medium = CAMEL_MEDIUM (message);
384 
385 	header_name = "X-Evolution-Source-Folder";
386 	folder_uri = camel_medium_get_header (medium, header_name);
387 
388 	header_name = "X-Evolution-Source-Message";
389 	message_uid = camel_medium_get_header (medium, header_name);
390 
391 	header_name = "X-Evolution-Source-Flags";
392 	flag_string = camel_medium_get_header (medium, header_name);
393 
394 	/* Don't report errors about missing X-Evolution-Source
395 	 * headers.  These headers are optional, so their absence
396 	 * is handled by doing nothing. */
397 	if (folder_uri == NULL || message_uid == NULL || flag_string == NULL)
398 		return TRUE;
399 
400 	/* Convert the flag string to CamelMessageFlags. */
401 
402 	string = g_strstrip (g_strdup (flag_string));
403 	tokens = g_strsplit (string, " ", 0);
404 	g_free (string);
405 
406 	/* If tokens is NULL, a length of 0 will skip the loop. */
407 	length = (tokens != NULL) ? g_strv_length (tokens) : 0;
408 
409 	for (ii = 0; ii < length; ii++) {
410 		/* Note: We're only checking for flags known to
411 		 * be used in X-Evolution-Source-Flags headers.
412 		 * Add more as needed. */
413 		if (g_strcmp0 (tokens[ii], "ANSWERED") == 0)
414 			flags |= CAMEL_MESSAGE_ANSWERED;
415 		else if (g_strcmp0 (tokens[ii], "ANSWERED_ALL") == 0)
416 			flags |= CAMEL_MESSAGE_ANSWERED_ALL;
417 		else if (g_strcmp0 (tokens[ii], "FORWARDED") == 0)
418 			flags |= CAMEL_MESSAGE_FORWARDED;
419 		else if (g_strcmp0 (tokens[ii], "SEEN") == 0)
420 			flags |= CAMEL_MESSAGE_SEEN;
421 		else
422 			g_warning (
423 				"Unknown flag '%s' in %s",
424 				tokens[ii], header_name);
425 	}
426 
427 	g_strfreev (tokens);
428 
429 	folder = e_mail_session_uri_to_folder_sync (
430 		session, folder_uri, 0, cancellable, error);
431 
432 	if (folder == NULL)
433 		return FALSE;
434 
435 	camel_folder_set_message_flags (
436 		folder, message_uid, flags, flags);
437 
438 	success = camel_folder_synchronize_message_sync (
439 		folder, message_uid, cancellable, error);
440 
441 	g_object_unref (folder);
442 
443 	return success;
444 }
445 
446 void
e_mail_session_handle_source_headers(EMailSession * session,CamelMimeMessage * message,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)447 e_mail_session_handle_source_headers (EMailSession *session,
448                                       CamelMimeMessage *message,
449                                       gint io_priority,
450                                       GCancellable *cancellable,
451                                       GAsyncReadyCallback callback,
452                                       gpointer user_data)
453 {
454 	GSimpleAsyncResult *simple;
455 	AsyncContext *context;
456 
457 	g_return_if_fail (E_IS_MAIL_SESSION (session));
458 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
459 
460 	context = g_slice_new0 (AsyncContext);
461 	context->message = g_object_ref (message);
462 
463 	simple = g_simple_async_result_new (
464 		G_OBJECT (session), callback, user_data,
465 		e_mail_session_handle_source_headers);
466 
467 	g_simple_async_result_set_check_cancellable (simple, cancellable);
468 
469 	g_simple_async_result_set_op_res_gpointer (
470 		simple, context, (GDestroyNotify) async_context_free);
471 
472 	g_simple_async_result_run_in_thread (
473 		simple, (GSimpleAsyncThreadFunc)
474 		mail_session_handle_source_headers_thread,
475 		io_priority, cancellable);
476 
477 	g_object_unref (simple);
478 }
479 
480 gboolean
e_mail_session_handle_source_headers_finish(EMailSession * session,GAsyncResult * result,GError ** error)481 e_mail_session_handle_source_headers_finish (EMailSession *session,
482                                              GAsyncResult *result,
483                                              GError **error)
484 {
485 	GSimpleAsyncResult *simple;
486 
487 	g_return_val_if_fail (
488 		g_simple_async_result_is_valid (
489 		result, G_OBJECT (session),
490 		e_mail_session_handle_draft_headers), FALSE);
491 
492 	simple = G_SIMPLE_ASYNC_RESULT (result);
493 
494 	/* Assume success unless a GError is set. */
495 	return !g_simple_async_result_propagate_error (simple, error);
496 }
497 
498 static void
mail_session_send_to_thread(GSimpleAsyncResult * simple,EMailSession * session,GCancellable * cancellable)499 mail_session_send_to_thread (GSimpleAsyncResult *simple,
500                              EMailSession *session,
501                              GCancellable *cancellable)
502 {
503 	AsyncContext *context;
504 	CamelProvider *provider;
505 	CamelFolder *folder = NULL;
506 	CamelFolder *local_sent_folder;
507 	CamelServiceConnectionStatus status;
508 	GString *error_messages;
509 	gboolean copy_to_sent = TRUE;
510 	gboolean sent_message_saved = FALSE;
511 	gboolean did_connect = FALSE;
512 	guint ii;
513 	GError *error = NULL;
514 
515 	context = g_simple_async_result_get_op_res_gpointer (simple);
516 
517 	if (camel_address_length (context->recipients) == 0)
518 		goto skip_send;
519 
520 	/* Send the message to all recipients. */
521 
522 	if (context->transport == NULL) {
523 		mail_tool_restore_xevolution_headers (context->message, context->xev_headers);
524 		g_simple_async_result_set_error (
525 			simple, CAMEL_SERVICE_ERROR,
526 			CAMEL_SERVICE_ERROR_UNAVAILABLE,
527 			_("No mail transport service available"));
528 		return;
529 	}
530 
531 	if (!e_mail_session_mark_service_used_sync (session, context->transport, cancellable)) {
532 		g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error));
533 		mail_tool_restore_xevolution_headers (context->message, context->xev_headers);
534 		g_simple_async_result_take_error (simple, error);
535 		return;
536 	}
537 
538 	status = camel_service_get_connection_status (context->transport);
539 	if (status != CAMEL_SERVICE_CONNECTED) {
540 		ESourceRegistry *registry;
541 		ESource *source;
542 
543 		/* Make sure user will be asked for a password, in case he/she cancelled it */
544 		registry = e_mail_session_get_registry (session);
545 		source = e_source_registry_ref_source (registry, camel_service_get_uid (context->transport));
546 
547 		if (source) {
548 			e_mail_session_emit_allow_auth_prompt (session, source);
549 			g_object_unref (source);
550 		}
551 
552 		did_connect = TRUE;
553 
554 		camel_service_connect_sync (context->transport, cancellable, &error);
555 
556 		if (error != NULL) {
557 			mail_tool_restore_xevolution_headers (context->message, context->xev_headers);
558 			g_simple_async_result_take_error (simple, error);
559 			e_mail_session_unmark_service_used (session, context->transport);
560 			return;
561 		}
562 	}
563 
564 	provider = camel_service_get_provider (context->transport);
565 
566 	if (provider->flags & CAMEL_PROVIDER_DISABLE_SENT_FOLDER)
567 		copy_to_sent = FALSE;
568 
569 	camel_transport_send_to_sync (
570 		CAMEL_TRANSPORT (context->transport),
571 		context->message, context->from,
572 		context->recipients, &sent_message_saved, cancellable, &error);
573 
574 	if (did_connect) {
575 		/* Disconnect regardless of error or cancellation,
576 		 * but be mindful of these conditions when calling
577 		 * camel_service_disconnect_sync(). */
578 		if (g_cancellable_is_cancelled (cancellable)) {
579 			camel_service_disconnect_sync (
580 				context->transport, FALSE, NULL, NULL);
581 		} else if (error != NULL) {
582 			camel_service_disconnect_sync (
583 				context->transport, FALSE, cancellable, NULL);
584 		} else {
585 			camel_service_disconnect_sync (
586 				context->transport, TRUE, cancellable, &error);
587 		}
588 	}
589 
590 	e_mail_session_unmark_service_used (session, context->transport);
591 
592 	if (error != NULL) {
593 		mail_tool_restore_xevolution_headers (context->message, context->xev_headers);
594 		g_simple_async_result_take_error (simple, error);
595 		return;
596 	}
597 
598 skip_send:
599 	/* Post the message to requested folders. */
600 	for (ii = 0; ii < context->post_to_uris->len; ii++) {
601 		CamelFolder *folder;
602 		const gchar *folder_uri;
603 
604 		folder_uri = g_ptr_array_index (context->post_to_uris, ii);
605 
606 		folder = e_mail_session_uri_to_folder_sync (
607 			session, folder_uri, 0, cancellable, &error);
608 
609 		if (error != NULL) {
610 			g_warn_if_fail (folder == NULL);
611 			mail_tool_restore_xevolution_headers (context->message, context->xev_headers);
612 			g_simple_async_result_take_error (simple, error);
613 			return;
614 		}
615 
616 		g_return_if_fail (CAMEL_IS_FOLDER (folder));
617 
618 		camel_operation_push_message (cancellable, _("Posting message to “%s”"), camel_folder_get_full_name (folder));
619 
620 		camel_folder_append_message_sync (
621 			folder, context->message, context->info,
622 			NULL, cancellable, &error);
623 
624 		camel_operation_pop_message (cancellable);
625 
626 		g_object_unref (folder);
627 
628 		if (error != NULL) {
629 			mail_tool_restore_xevolution_headers (context->message, context->xev_headers);
630 			g_simple_async_result_take_error (simple, error);
631 			return;
632 		}
633 	}
634 
635 	/*** Post Processing ***/
636 
637 	/* This accumulates error messages during post-processing. */
638 	error_messages = g_string_sized_new (256);
639 
640 	mail_tool_restore_xevolution_headers (context->message, context->xev_headers);
641 
642 	/* Run filters on the outgoing message. */
643 	if (context->driver != NULL) {
644 		CamelMessageFlags message_flags;
645 
646 		camel_filter_driver_filter_message (
647 			context->driver, context->message, context->info,
648 			NULL, NULL, NULL, "", cancellable, &error);
649 
650 		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
651 			goto exit;
652 
653 		if (error != NULL) {
654 			g_string_append_printf (
655 				error_messages,
656 				_("Failed to apply outgoing filters: %s"),
657 				error->message);
658 			g_clear_error (&error);
659 		}
660 
661 		message_flags = camel_message_info_get_flags (context->info);
662 
663 		if (message_flags & CAMEL_MESSAGE_DELETED)
664 			copy_to_sent = FALSE;
665 	}
666 
667 	if (!copy_to_sent || sent_message_saved)
668 		goto cleanup;
669 
670 	/* Append the sent message to a Sent folder. */
671 
672 	local_sent_folder =
673 		e_mail_session_get_local_folder (
674 		session, E_MAIL_LOCAL_FOLDER_SENT);
675 
676 	folder = e_mail_session_get_fcc_for_message_sync (
677 		session, context->message, &copy_to_sent, cancellable, &error);
678 
679 	if (!copy_to_sent)
680 		goto cleanup;
681 
682 	/* Sanity check. */
683 	g_return_if_fail (
684 		((folder != NULL) && (error == NULL)) ||
685 		((folder == NULL) && (error != NULL)));
686 
687 	/* Append the message. */
688 	if (folder != NULL) {
689 		camel_operation_push_message (cancellable, _("Storing sent message to “%s”"), camel_folder_get_full_name (folder));
690 
691 		camel_folder_append_message_sync (
692 			folder, context->message,
693 			context->info, NULL, cancellable, &error);
694 
695 		camel_operation_pop_message (cancellable);
696 	}
697 
698 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
699 		goto exit;
700 
701 	if (error == NULL)
702 		goto cleanup;
703 
704 	if (folder != NULL && folder != local_sent_folder) {
705 		const gchar *description;
706 
707 		description = camel_folder_get_description (folder);
708 
709 		if (error_messages->len > 0)
710 			g_string_append (error_messages, "\n\n");
711 		g_string_append_printf (
712 			error_messages,
713 			_("Failed to append to %s: %s\n"
714 			"Appending to local “Sent” folder instead."),
715 			description, error->message);
716 	}
717 
718 	/* If appending to a remote Sent folder failed,
719 	 * try appending to the local Sent folder. */
720 	if (folder != local_sent_folder) {
721 
722 		g_clear_error (&error);
723 
724 		camel_operation_push_message (cancellable, _("Storing sent message to “%s”"), camel_folder_get_full_name (local_sent_folder));
725 
726 		camel_folder_append_message_sync (
727 			local_sent_folder, context->message,
728 			context->info, NULL, cancellable, &error);
729 
730 		camel_operation_pop_message (cancellable);
731 	}
732 
733 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
734 		goto exit;
735 
736 	/* We can't even append to the local Sent folder?
737 	 * In that case just leave the message in Outbox. */
738 	if (error != NULL) {
739 		if (error_messages->len > 0)
740 			g_string_append (error_messages, "\n\n");
741 		g_string_append_printf (
742 			error_messages,
743 			_("Failed to append to local “Sent” folder: %s"),
744 			error->message);
745 		g_clear_error (&error);
746 		goto exit;
747 	}
748 
749 cleanup:
750 
751 	/* The send operation was successful; ignore cleanup errors. */
752 
753 	/* Mark the draft message for deletion, if present. */
754 	e_mail_session_handle_draft_headers_sync (
755 		session, context->message, cancellable, &error);
756 	if (error != NULL) {
757 		g_warning ("%s", error->message);
758 		g_clear_error (&error);
759 	}
760 
761 	/* Set flags on the original source message, if present.
762 	 * Source message refers to the message being forwarded
763 	 * or replied to. */
764 	e_mail_session_handle_source_headers_sync (
765 		session, context->message, cancellable, &error);
766 	if (error &&
767 	    !g_error_matches (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID_UID) &&
768 	    !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
769 		g_warning (
770 			"%s: Failed to handle source headers: %s",
771 			G_STRFUNC, error->message);
772 	}
773 	g_clear_error (&error);
774 
775 exit:
776 
777 	/* If we were cancelled, disregard any other errors. */
778 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
779 		g_simple_async_result_take_error (simple, error);
780 
781 	/* Stuff the accumulated error messages in a GError. */
782 	} else if (error_messages->len > 0) {
783 		g_simple_async_result_set_error (
784 			simple, E_MAIL_ERROR,
785 			E_MAIL_ERROR_POST_PROCESSING,
786 			"%s", error_messages->str);
787 	}
788 
789 	/* Synchronize the Sent folder. */
790 	if (folder != NULL) {
791 		camel_folder_synchronize_sync (
792 			folder, FALSE, cancellable, NULL);
793 		g_object_unref (folder);
794 	}
795 
796 	g_string_free (error_messages, TRUE);
797 }
798 
799 void
e_mail_session_send_to(EMailSession * session,CamelMimeMessage * message,gint io_priority,GCancellable * cancellable,CamelFilterGetFolderFunc get_folder_func,gpointer get_folder_data,GAsyncReadyCallback callback,gpointer user_data)800 e_mail_session_send_to (EMailSession *session,
801                         CamelMimeMessage *message,
802                         gint io_priority,
803                         GCancellable *cancellable,
804                         CamelFilterGetFolderFunc get_folder_func,
805                         gpointer get_folder_data,
806                         GAsyncReadyCallback callback,
807                         gpointer user_data)
808 {
809 	GSimpleAsyncResult *simple;
810 	AsyncContext *context;
811 	CamelAddress *from;
812 	CamelAddress *recipients;
813 	CamelMedium *medium;
814 	CamelMessageInfo *info;
815 	CamelService *transport;
816 	GPtrArray *post_to_uris;
817 	CamelNameValueArray *xev_headers;
818 	const gchar *resent_from;
819 	guint ii, len;
820 	GError *error = NULL;
821 
822 	g_return_if_fail (E_IS_MAIL_SESSION (session));
823 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
824 
825 	medium = CAMEL_MEDIUM (message);
826 
827 	if (!camel_medium_get_header (medium, "X-Evolution-Is-Redirect"))
828 		camel_medium_set_header (medium, "User-Agent", USER_AGENT);
829 
830 	/* Do this before removing "X-Evolution" headers. */
831 	transport = e_mail_session_ref_transport_for_message (
832 		session, message);
833 
834 	xev_headers = mail_tool_remove_xevolution_headers (message);
835 	len = camel_name_value_array_get_length (xev_headers);
836 
837 	/* Extract directives from X-Evolution headers. */
838 
839 	post_to_uris = g_ptr_array_new ();
840 	for (ii = 0; ii < len; ii++) {
841 		const gchar *header_name = NULL, *header_value = NULL;
842 		gchar *folder_uri;
843 
844 		if (!camel_name_value_array_get (xev_headers, ii, &header_name, &header_value) ||
845 		    !header_name ||
846 		    g_ascii_strcasecmp (header_name, "X-Evolution-PostTo") != 0)
847 			continue;
848 
849 		folder_uri = g_strstrip (g_strdup (header_value));
850 		g_ptr_array_add (post_to_uris, folder_uri);
851 	}
852 
853 	/* Collect sender and recipients from headers. */
854 
855 	from = (CamelAddress *) camel_internet_address_new ();
856 	recipients = (CamelAddress *) camel_internet_address_new ();
857 	resent_from = camel_medium_get_header (medium, "Resent-From");
858 
859 	if (resent_from != NULL) {
860 		const CamelInternetAddress *addr;
861 		const gchar *type;
862 
863 		camel_address_decode (from, resent_from);
864 
865 		type = CAMEL_RECIPIENT_TYPE_RESENT_TO;
866 		addr = camel_mime_message_get_recipients (message, type);
867 		camel_address_cat (recipients, CAMEL_ADDRESS (addr));
868 
869 		type = CAMEL_RECIPIENT_TYPE_RESENT_CC;
870 		addr = camel_mime_message_get_recipients (message, type);
871 		camel_address_cat (recipients, CAMEL_ADDRESS (addr));
872 
873 		type = CAMEL_RECIPIENT_TYPE_RESENT_BCC;
874 		addr = camel_mime_message_get_recipients (message, type);
875 		camel_address_cat (recipients, CAMEL_ADDRESS (addr));
876 
877 	} else {
878 		const CamelInternetAddress *addr;
879 		const gchar *type;
880 
881 		addr = camel_mime_message_get_from (message);
882 		camel_address_copy (from, CAMEL_ADDRESS (addr));
883 
884 		type = CAMEL_RECIPIENT_TYPE_TO;
885 		addr = camel_mime_message_get_recipients (message, type);
886 		camel_address_cat (recipients, CAMEL_ADDRESS (addr));
887 
888 		type = CAMEL_RECIPIENT_TYPE_CC;
889 		addr = camel_mime_message_get_recipients (message, type);
890 		camel_address_cat (recipients, CAMEL_ADDRESS (addr));
891 
892 		type = CAMEL_RECIPIENT_TYPE_BCC;
893 		addr = camel_mime_message_get_recipients (message, type);
894 		camel_address_cat (recipients, CAMEL_ADDRESS (addr));
895 	}
896 
897 	/* Miscellaneous preparations. */
898 
899 	info = camel_message_info_new_from_headers (NULL, camel_medium_get_headers (CAMEL_MEDIUM (message)));
900 
901 	camel_message_info_set_size (info, camel_data_wrapper_calculate_size_sync (CAMEL_DATA_WRAPPER (message), cancellable, NULL));
902 	camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN |
903 		(camel_mime_message_has_attachment (message) ? CAMEL_MESSAGE_ATTACHMENTS : 0), ~0);
904 
905 	/* expand, or remove empty, group addresses */
906 	em_utils_expand_groups (CAMEL_INTERNET_ADDRESS (recipients));
907 
908 	/* The rest of the processing happens in a thread. */
909 
910 	context = g_slice_new0 (AsyncContext);
911 	context->message = g_object_ref (message);
912 	context->io_priority = io_priority;
913 	context->from = from;
914 	context->recipients = recipients;
915 	context->info = info;
916 	context->xev_headers = xev_headers;
917 	context->post_to_uris = post_to_uris;
918 	context->transport = transport;
919 
920 	if (G_IS_CANCELLABLE (cancellable))
921 		context->cancellable = g_object_ref (cancellable);
922 
923 	/* Failure here emits a runtime warning but is non-fatal. */
924 	context->driver = camel_session_get_filter_driver (CAMEL_SESSION (session), E_FILTER_SOURCE_OUTGOING, NULL, &error);
925 	if (context->driver != NULL && get_folder_func)
926 		camel_filter_driver_set_folder_func (
927 			context->driver, get_folder_func, get_folder_data);
928 	if (error != NULL) {
929 		g_warn_if_fail (context->driver == NULL);
930 		g_warning ("%s", error->message);
931 		g_error_free (error);
932 	}
933 
934 	/* This gets popped in async_context_free(). */
935 	camel_operation_push_message (
936 		context->cancellable, _("Sending message"));
937 
938 	simple = g_simple_async_result_new (
939 		G_OBJECT (session), callback,
940 		user_data, e_mail_session_send_to);
941 
942 	g_simple_async_result_set_check_cancellable (simple, cancellable);
943 
944 	g_simple_async_result_set_op_res_gpointer (
945 		simple, context, (GDestroyNotify) async_context_free);
946 
947 	g_simple_async_result_run_in_thread (
948 		simple, (GSimpleAsyncThreadFunc)
949 		mail_session_send_to_thread,
950 		context->io_priority,
951 		context->cancellable);
952 
953 	g_object_unref (simple);
954 }
955 
956 gboolean
e_mail_session_send_to_finish(EMailSession * session,GAsyncResult * result,GError ** error)957 e_mail_session_send_to_finish (EMailSession *session,
958                                GAsyncResult *result,
959                                GError **error)
960 {
961 	GSimpleAsyncResult *simple;
962 
963 	g_return_val_if_fail (
964 		g_simple_async_result_is_valid (
965 		result, G_OBJECT (session),
966 		e_mail_session_send_to), FALSE);
967 
968 	simple = G_SIMPLE_ASYNC_RESULT (result);
969 
970 	/* Assume success unless a GError is set. */
971 	return !g_simple_async_result_propagate_error (simple, error);
972 }
973 
974 /* Helper for e_mail_session_get_fcc_for_message_sync() */
975 static CamelFolder *
mail_session_try_uri_to_folder(EMailSession * session,const gchar * folder_uri,GCancellable * cancellable,GError ** error)976 mail_session_try_uri_to_folder (EMailSession *session,
977                                 const gchar *folder_uri,
978                                 GCancellable *cancellable,
979                                 GError **error)
980 {
981 	CamelFolder *folder;
982 	GError *local_error = NULL;
983 
984 	folder = e_mail_session_uri_to_folder_sync (
985 		session, folder_uri, 0, cancellable, &local_error);
986 
987 	/* Sanity check. */
988 	g_return_val_if_fail (
989 		((folder != NULL) && (local_error == NULL)) ||
990 		((folder == NULL) && (local_error != NULL)), NULL);
991 
992 	/* Disregard specific errors. */
993 
994 	/* Invalid URI. */
995 	if (g_error_matches (
996 		local_error, CAMEL_FOLDER_ERROR,
997 		CAMEL_FOLDER_ERROR_INVALID))
998 		g_clear_error (&local_error);
999 
1000 	/* Folder not found. */
1001 	if (g_error_matches (
1002 		local_error, CAMEL_STORE_ERROR,
1003 		CAMEL_STORE_ERROR_NO_FOLDER))
1004 		g_clear_error (&local_error);
1005 
1006 	if (local_error != NULL)
1007 		g_propagate_error (error, local_error);
1008 
1009 	return folder;
1010 }
1011 
1012 /* Helper for e_mail_session_get_fcc_for_message_sync() */
1013 static CamelFolder *
mail_session_ref_origin_folder(EMailSession * session,CamelMimeMessage * message,GCancellable * cancellable,GError ** error)1014 mail_session_ref_origin_folder (EMailSession *session,
1015                                 CamelMimeMessage *message,
1016                                 GCancellable *cancellable,
1017                                 GError **error)
1018 {
1019 	CamelMedium *medium;
1020 	const gchar *header_name;
1021 	const gchar *header_value;
1022 
1023 	medium = CAMEL_MEDIUM (message);
1024 
1025 	/* Check that a "X-Evolution-Source-Flags" header is present
1026 	 * and its value does not contain the substring "FORWARDED". */
1027 
1028 	header_name = "X-Evolution-Source-Flags";
1029 	header_value = camel_medium_get_header (medium, header_name);
1030 
1031 	if (header_value == NULL)
1032 		return NULL;
1033 
1034 	if (strstr (header_value, "FORWARDED") != NULL)
1035 		return NULL;
1036 
1037 	/* Check that a "X-Evolution-Source-Message" header is present. */
1038 
1039 	header_name = "X-Evolution-Source-Message";
1040 	header_value = camel_medium_get_header (medium, header_name);
1041 
1042 	if (header_value == NULL)
1043 		return NULL;
1044 
1045 	/* Check that a "X-Evolution-Source-Folder" header is present.
1046 	 * Its value specifies the origin folder as a folder URI. */
1047 
1048 	header_name = "X-Evolution-Source-Folder";
1049 	header_value = camel_medium_get_header (medium, header_name);
1050 
1051 	if (header_value == NULL)
1052 		return NULL;
1053 
1054 	/* This may return NULL without setting a GError. */
1055 	return mail_session_try_uri_to_folder (
1056 		session, header_value, cancellable, error);
1057 }
1058 
1059 /* Helper for e_mail_session_get_fcc_for_message_sync() */
1060 static CamelFolder *
mail_session_ref_fcc_from_identity(EMailSession * session,ESource * source,CamelMimeMessage * message,gboolean * out_use_sent_folder,GCancellable * cancellable,GError ** error)1061 mail_session_ref_fcc_from_identity (EMailSession *session,
1062                                     ESource *source,
1063                                     CamelMimeMessage *message,
1064 				    gboolean *out_use_sent_folder,
1065                                     GCancellable *cancellable,
1066                                     GError **error)
1067 {
1068 	ESourceRegistry *registry;
1069 	ESourceMailSubmission *extension;
1070 	CamelFolder *folder = NULL;
1071 	const gchar *extension_name;
1072 	gchar *folder_uri;
1073 	gboolean use_sent_folder;
1074 
1075 	registry = e_mail_session_get_registry (session);
1076 	extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
1077 
1078 	if (source == NULL)
1079 		return NULL;
1080 
1081 	if (!e_source_registry_check_enabled (registry, source))
1082 		return NULL;
1083 
1084 	if (!e_source_has_extension (source, extension_name))
1085 		return NULL;
1086 
1087 	extension = e_source_get_extension (source, extension_name);
1088 	use_sent_folder = e_source_mail_submission_get_use_sent_folder (extension);
1089 
1090 	if (out_use_sent_folder)
1091 		*out_use_sent_folder = use_sent_folder;
1092 
1093 	if (!use_sent_folder)
1094 		return NULL;
1095 
1096 	if (e_source_mail_submission_get_replies_to_origin_folder (extension)) {
1097 		GError *local_error = NULL;
1098 
1099 		/* This may return NULL without setting a GError. */
1100 		folder = mail_session_ref_origin_folder (
1101 			session, message, cancellable, &local_error);
1102 
1103 		if (local_error != NULL) {
1104 			g_warn_if_fail (folder == NULL);
1105 			g_propagate_error (error, local_error);
1106 			return NULL;
1107 		}
1108 	}
1109 
1110 	folder_uri = e_source_mail_submission_dup_sent_folder (extension);
1111 
1112 	if (folder_uri != NULL && folder == NULL) {
1113 		/* This may return NULL without setting a GError. */
1114 		folder = mail_session_try_uri_to_folder (
1115 			session, folder_uri, cancellable, error);
1116 	}
1117 
1118 	g_free (folder_uri);
1119 
1120 	return folder;
1121 }
1122 
1123 /* Helper for e_mail_session_get_fcc_for_message_sync() */
1124 static CamelFolder *
mail_session_ref_fcc_from_x_identity(EMailSession * session,CamelMimeMessage * message,gboolean * out_use_sent_folder,GCancellable * cancellable,GError ** error)1125 mail_session_ref_fcc_from_x_identity (EMailSession *session,
1126                                       CamelMimeMessage *message,
1127 				      gboolean *out_use_sent_folder,
1128                                       GCancellable *cancellable,
1129                                       GError **error)
1130 {
1131 	ESource *source;
1132 	ESourceRegistry *registry;
1133 	CamelFolder *folder;
1134 	CamelMedium *medium;
1135 	const gchar *header_name;
1136 	const gchar *header_value;
1137 	gchar *uid;
1138 
1139 	medium = CAMEL_MEDIUM (message);
1140 	header_name = "X-Evolution-Identity";
1141 	header_value = camel_medium_get_header (medium, header_name);
1142 
1143 	if (header_value == NULL)
1144 		return NULL;
1145 
1146 	uid = g_strstrip (g_strdup (header_value));
1147 
1148 	registry = e_mail_session_get_registry (session);
1149 	source = e_source_registry_ref_source (registry, uid);
1150 
1151 	/* This may return NULL without setting a GError. */
1152 	folder = mail_session_ref_fcc_from_identity (
1153 		session, source, message, out_use_sent_folder, cancellable, error);
1154 
1155 	g_clear_object (&source);
1156 
1157 	g_free (uid);
1158 
1159 	return folder;
1160 }
1161 
1162 /* Helper for e_mail_session_get_fcc_for_message_sync() */
1163 static CamelFolder *
mail_session_ref_fcc_from_x_fcc(EMailSession * session,CamelMimeMessage * message,GCancellable * cancellable,GError ** error)1164 mail_session_ref_fcc_from_x_fcc (EMailSession *session,
1165                                  CamelMimeMessage *message,
1166                                  GCancellable *cancellable,
1167                                  GError **error)
1168 {
1169 	CamelMedium *medium;
1170 	const gchar *header_name;
1171 	const gchar *header_value;
1172 
1173 	medium = CAMEL_MEDIUM (message);
1174 	header_name = "X-Evolution-Fcc";
1175 	header_value = camel_medium_get_header (medium, header_name);
1176 
1177 	if (header_value == NULL)
1178 		return NULL;
1179 
1180 	/* This may return NULL without setting a GError. */
1181 	return mail_session_try_uri_to_folder (
1182 		session, header_value, cancellable, error);
1183 }
1184 
1185 /* Helper for e_mail_session_get_fcc_for_message_sync() */
1186 static CamelFolder *
mail_session_ref_fcc_from_default_identity(EMailSession * session,CamelMimeMessage * message,gboolean * out_use_sent_folder,GCancellable * cancellable,GError ** error)1187 mail_session_ref_fcc_from_default_identity (EMailSession *session,
1188                                             CamelMimeMessage *message,
1189 					    gboolean *out_use_sent_folder,
1190                                             GCancellable *cancellable,
1191                                             GError **error)
1192 {
1193 	ESource *source;
1194 	ESourceRegistry *registry;
1195 	CamelFolder *folder;
1196 
1197 	registry = e_mail_session_get_registry (session);
1198 	source = e_source_registry_ref_default_mail_identity (registry);
1199 
1200 	/* This may return NULL without setting a GError. */
1201 	folder = mail_session_ref_fcc_from_identity (
1202 		session, source, message, out_use_sent_folder, cancellable, error);
1203 
1204 	g_clear_object (&source);
1205 
1206 	return folder;
1207 }
1208 
1209 /**
1210  * e_mail_session_get_fcc_for_message_sync:
1211  * @session: an #EMailSession
1212  * @message: a #CamelMimeMessage
1213  * @out_use_sent_folder: (out) (nullable): optional return location to store
1214  *    corresponding use-sent-folder for the mail account, or %NULL
1215  * @cancellable: optional #GCancellable object, or %NULL
1216  * @error: return location for a #GError, or %NULL
1217  *
1218  * Obtains the preferred "carbon-copy" folder (a.k.a Fcc) for @message
1219  * by first checking @message for an "X-Evolution-Identity" header, and
1220  * then an "X-Evolution-Fcc" header.  Failing that, the function checks
1221  * the default mail identity (if available), and failing even that, the
1222  * function falls back to the Sent folder from the built-in mail store.
1223  *
1224  * Where applicable, the function attempts to honor the
1225  * #ESourceMailSubmission:replies-to-origin-folder preference.
1226  *
1227  * The returned #CamelFolder is referenced for thread-safety and must be
1228  * unreferenced with g_object_unref() when finished with it.
1229  *
1230  * If a non-recoverable error occurs, the function sets @error and returns
1231  * %NULL. It returns %NULL without setting @error when the mail account
1232  * has set to not use sent folder, in which case it indicates that
1233  * in @out_use_sent_folder too.
1234  *
1235  * Returns: a #CamelFolder, or %NULL
1236  **/
1237 CamelFolder *
e_mail_session_get_fcc_for_message_sync(EMailSession * session,CamelMimeMessage * message,gboolean * out_use_sent_folder,GCancellable * cancellable,GError ** error)1238 e_mail_session_get_fcc_for_message_sync (EMailSession *session,
1239                                          CamelMimeMessage *message,
1240 					 gboolean *out_use_sent_folder,
1241                                          GCancellable *cancellable,
1242                                          GError **error)
1243 {
1244 	CamelFolder *folder = NULL;
1245 	gboolean use_sent_folder = TRUE;
1246 
1247 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
1248 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
1249 
1250 	if (out_use_sent_folder)
1251 		*out_use_sent_folder = TRUE;
1252 
1253 	/* Check for "X-Evolution-Identity" header. */
1254 	if (folder == NULL) {
1255 		GError *local_error = NULL;
1256 
1257 		/* This may return NULL without setting a GError. */
1258 		folder = mail_session_ref_fcc_from_x_identity (
1259 			session, message, &use_sent_folder, cancellable, &local_error);
1260 
1261 		if (local_error != NULL) {
1262 			g_warn_if_fail (folder == NULL);
1263 			g_propagate_error (error, local_error);
1264 			return NULL;
1265 		}
1266 
1267 		if (!use_sent_folder) {
1268 			if (out_use_sent_folder)
1269 				*out_use_sent_folder = use_sent_folder;
1270 			return NULL;
1271 		}
1272 	}
1273 
1274 	/* Check for "X-Evolution-Fcc" header. */
1275 	if (folder == NULL) {
1276 		GError *local_error = NULL;
1277 
1278 		/* This may return NULL without setting a GError. */
1279 		folder = mail_session_ref_fcc_from_x_fcc (
1280 			session, message, cancellable, &local_error);
1281 
1282 		if (local_error != NULL) {
1283 			g_warn_if_fail (folder == NULL);
1284 			g_propagate_error (error, local_error);
1285 			return NULL;
1286 		}
1287 	}
1288 
1289 	/* Check the default mail identity. */
1290 	if (folder == NULL) {
1291 		GError *local_error = NULL;
1292 
1293 		/* This may return NULL without setting a GError. */
1294 		folder = mail_session_ref_fcc_from_default_identity (
1295 			session, message, &use_sent_folder, cancellable, &local_error);
1296 
1297 		if (local_error != NULL) {
1298 			g_warn_if_fail (folder == NULL);
1299 			g_propagate_error (error, local_error);
1300 			return NULL;
1301 		}
1302 
1303 		if (!use_sent_folder) {
1304 			if (out_use_sent_folder)
1305 				*out_use_sent_folder = use_sent_folder;
1306 			return NULL;
1307 		}
1308 	}
1309 
1310 	/* Last resort - local Sent folder. */
1311 	if (folder == NULL) {
1312 		folder = e_mail_session_get_local_folder (
1313 			session, E_MAIL_LOCAL_FOLDER_SENT);
1314 		g_object_ref (folder);
1315 	}
1316 
1317 	return folder;
1318 }
1319 
1320 /* Helper for e_mail_session_get_fcc_for_message() */
1321 static void
mail_session_get_fcc_for_message_thread(GSimpleAsyncResult * simple,GObject * source_object,GCancellable * cancellable)1322 mail_session_get_fcc_for_message_thread (GSimpleAsyncResult *simple,
1323                                          GObject *source_object,
1324                                          GCancellable *cancellable)
1325 {
1326 	AsyncContext *async_context;
1327 	GError *local_error = NULL;
1328 
1329 	async_context = g_simple_async_result_get_op_res_gpointer (simple);
1330 
1331 	async_context->folder =
1332 		e_mail_session_get_fcc_for_message_sync (
1333 			E_MAIL_SESSION (source_object),
1334 			async_context->message,
1335 			&async_context->use_sent_folder,
1336 			cancellable, &local_error);
1337 
1338 	if (local_error != NULL)
1339 		g_simple_async_result_take_error (simple, local_error);
1340 }
1341 
1342 /**
1343  * e_mail_session_get_fcc_for_message:
1344  * @session: an #EMailSession
1345  * @message: a #CamelMimeMessage
1346  * @io_priority: the I/O priority of the request
1347  * @cancellable: optional #GCancellable object, or %NULL
1348  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
1349  * @user_data: data to pass to the callback function
1350  *
1351  * Asynchronously obtains the preferred "carbon-copy" folder (a.k.a Fcc) for
1352  * @message by first checking @message for an "X-Evolution-Identity" header,
1353  * and then an "X-Evolution-Fcc" header.  Failing that, the function checks
1354  * the default mail identity (if available), and failing even that, the
1355  * function falls back to the Sent folder from the built-in mail store.
1356  *
1357  * Where applicable, the function attempts to honor the
1358  * #ESourceMailSubmission:replies-to-origin-folder preference.
1359  *
1360  * When the operation is finished, @callback will be called.  You can then
1361  * call e_mail_session_get_fcc_for_message_finish() to get the result of the
1362  * operation.
1363  **/
1364 void
e_mail_session_get_fcc_for_message(EMailSession * session,CamelMimeMessage * message,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1365 e_mail_session_get_fcc_for_message (EMailSession *session,
1366                                     CamelMimeMessage *message,
1367                                     gint io_priority,
1368                                     GCancellable *cancellable,
1369                                     GAsyncReadyCallback callback,
1370                                     gpointer user_data)
1371 {
1372 	GSimpleAsyncResult *simple;
1373 	AsyncContext *async_context;
1374 
1375 	g_return_if_fail (E_IS_MAIL_SESSION (session));
1376 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
1377 
1378 	async_context = g_slice_new0 (AsyncContext);
1379 	async_context->message = g_object_ref (message);
1380 
1381 	simple = g_simple_async_result_new (
1382 		G_OBJECT (session), callback, user_data,
1383 		e_mail_session_get_fcc_for_message);
1384 
1385 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1386 
1387 	g_simple_async_result_set_op_res_gpointer (
1388 		simple, async_context, (GDestroyNotify) async_context_free);
1389 
1390 	g_simple_async_result_run_in_thread (
1391 		simple, mail_session_get_fcc_for_message_thread,
1392 		io_priority, cancellable);
1393 
1394 	g_object_unref (simple);
1395 }
1396 
1397 /**
1398  * e_mail_session_get_fcc_for_message_finish:
1399  * @session: an #EMailSession
1400  * @result: a #GAsyncResult
1401  * @out_use_sent_folder: (out) (nullable): optional return location to store
1402  *    corresponding use-sent-folder for the mail account, or %NULL
1403  * @error: return location for a #GError, or %NULL
1404  *
1405  * Finishes the operation started with e_mail_session_get_fcc_for_message().
1406  *
1407  * The returned #CamelFolder is referenced for thread-safety and must be
1408  * unreferenced with g_object_unref() when finished with it.
1409  *
1410  * If a non-recoverable error occurred, the function sets @error and
1411  * returns %NULL. It returns %NULL without setting @error when the mail account
1412  * has set to not use sent folder, in which case it indicates that
1413  * in @out_use_sent_folder too.
1414  *
1415  * Returns: a #CamelFolder, or %NULL
1416  **/
1417 CamelFolder *
e_mail_session_get_fcc_for_message_finish(EMailSession * session,GAsyncResult * result,gboolean * out_use_sent_folder,GError ** error)1418 e_mail_session_get_fcc_for_message_finish (EMailSession *session,
1419                                            GAsyncResult *result,
1420 					   gboolean *out_use_sent_folder,
1421                                            GError **error)
1422 {
1423 	GSimpleAsyncResult *simple;
1424 	AsyncContext *async_context;
1425 
1426 	g_return_val_if_fail (
1427 		g_simple_async_result_is_valid (
1428 		result, G_OBJECT (session),
1429 		e_mail_session_get_fcc_for_message), NULL);
1430 
1431 	simple = G_SIMPLE_ASYNC_RESULT (result);
1432 	async_context = g_simple_async_result_get_op_res_gpointer (simple);
1433 
1434 	if (g_simple_async_result_propagate_error (simple, error))
1435 		return NULL;
1436 
1437 	if (out_use_sent_folder)
1438 		*out_use_sent_folder = async_context->use_sent_folder;
1439 
1440 	if (!async_context->use_sent_folder) {
1441 		g_return_val_if_fail (async_context->folder == NULL, NULL);
1442 		return NULL;
1443 	}
1444 
1445 	g_return_val_if_fail (async_context->folder != NULL, NULL);
1446 
1447 	return g_object_ref (async_context->folder);
1448 }
1449 
1450 /**
1451  * e_mail_session_ref_transport:
1452  * @session: an #EMailSession
1453  * @transport_uid: the UID of a mail transport
1454  *
1455  * Returns the transport #CamelService instance for @transport_uid,
1456  * verifying first that the @transport_uid is indeed a mail transport and
1457  * that the corresponding #ESource is enabled.  If these checks fail, the
1458  * function returns %NULL.
1459  *
1460  * The returned #CamelService is referenced for thread-safety and must be
1461  * unreferenced with g_object_unref() when finished with it.
1462  *
1463  * Returns: a #CamelService, or %NULL
1464  **/
1465 CamelService *
e_mail_session_ref_transport(EMailSession * session,const gchar * transport_uid)1466 e_mail_session_ref_transport (EMailSession *session,
1467                               const gchar *transport_uid)
1468 {
1469 	ESourceRegistry *registry;
1470 	ESource *source = NULL;
1471 	CamelService *transport = NULL;
1472 	const gchar *extension_name;
1473 
1474 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
1475 	g_return_val_if_fail (transport_uid != NULL, NULL);
1476 
1477 	registry = e_mail_session_get_registry (session);
1478 	extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
1479 
1480 	source = e_source_registry_ref_source (registry, transport_uid);
1481 
1482 	if (source == NULL)
1483 		goto exit;
1484 
1485 	if (!e_source_registry_check_enabled (registry, source))
1486 		goto exit;
1487 
1488 	if (!e_source_has_extension (source, extension_name))
1489 		goto exit;
1490 
1491 	transport = camel_session_ref_service (
1492 		CAMEL_SESSION (session), transport_uid);
1493 
1494 	/* Sanity check. */
1495 	if (transport != NULL)
1496 		g_warn_if_fail (CAMEL_IS_TRANSPORT (transport));
1497 
1498 exit:
1499 	g_clear_object (&source);
1500 
1501 	return transport;
1502 }
1503 
1504 /* Helper for e_mail_session_ref_default_transport()
1505  * and mail_session_ref_transport_from_x_identity(). */
1506 static CamelService *
mail_session_ref_transport_for_identity(EMailSession * session,ESource * source)1507 mail_session_ref_transport_for_identity (EMailSession *session,
1508                                          ESource *source)
1509 {
1510 	ESourceRegistry *registry;
1511 	ESourceMailSubmission *extension;
1512 	CamelService *transport = NULL;
1513 	const gchar *extension_name;
1514 	gchar *uid;
1515 
1516 	registry = e_mail_session_get_registry (session);
1517 	extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
1518 
1519 	if (source == NULL)
1520 		return NULL;
1521 
1522 	if (!e_source_registry_check_enabled (registry, source))
1523 		return NULL;
1524 
1525 	if (!e_source_has_extension (source, extension_name))
1526 		return NULL;
1527 
1528 	extension = e_source_get_extension (source, extension_name);
1529 	uid = e_source_mail_submission_dup_transport_uid (extension);
1530 
1531 	if (uid != NULL) {
1532 		transport = e_mail_session_ref_transport (session, uid);
1533 		g_free (uid);
1534 	}
1535 
1536 	return transport;
1537 }
1538 
1539 /**
1540  * e_mail_session_ref_default_transport:
1541  * @session: an #EMailSession
1542  *
1543  * Returns the default transport #CamelService instance according to
1544  * #ESourceRegistry's #ESourceRegistry:default-mail-identity setting,
1545  * verifying first that the #ESourceMailSubmission:transport-uid named by
1546  * the #ESourceRegistry:default-mail-identity is indeed a mail transport,
1547  * and that the corresponding #ESource is enabled.  If these checks fail,
1548  * the function returns %NULL.
1549  *
1550  * The returned #CamelService is referenced for thread-safety and must be
1551  * unreferenced with g_object_unref() when finished with it.
1552  *
1553  * Returns: a #CamelService, or %NULL
1554  **/
1555 CamelService *
e_mail_session_ref_default_transport(EMailSession * session)1556 e_mail_session_ref_default_transport (EMailSession *session)
1557 {
1558 	ESource *source;
1559 	ESourceRegistry *registry;
1560 	CamelService *transport;
1561 
1562 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
1563 
1564 	registry = e_mail_session_get_registry (session);
1565 	source = e_source_registry_ref_default_mail_identity (registry);
1566 	transport = mail_session_ref_transport_for_identity (session, source);
1567 	g_clear_object (&source);
1568 
1569 	return transport;
1570 }
1571 
1572 /* Helper for e_mail_session_ref_transport_for_message() */
1573 static CamelService *
mail_session_ref_transport_from_x_identity(EMailSession * session,CamelMimeMessage * message)1574 mail_session_ref_transport_from_x_identity (EMailSession *session,
1575                                             CamelMimeMessage *message)
1576 {
1577 	ESource *source;
1578 	ESourceRegistry *registry;
1579 	CamelMedium *medium;
1580 	CamelService *transport;
1581 	const gchar *header_name;
1582 	const gchar *header_value;
1583 	gchar *uid;
1584 
1585 	medium = CAMEL_MEDIUM (message);
1586 	header_name = "X-Evolution-Identity";
1587 	header_value = camel_medium_get_header (medium, header_name);
1588 
1589 	if (header_value == NULL)
1590 		return NULL;
1591 
1592 	uid = g_strstrip (g_strdup (header_value));
1593 
1594 	registry = e_mail_session_get_registry (session);
1595 	source = e_source_registry_ref_source (registry, uid);
1596 	transport = mail_session_ref_transport_for_identity (session, source);
1597 	g_clear_object (&source);
1598 
1599 	g_free (uid);
1600 
1601 	return transport;
1602 }
1603 
1604 /* Helper for e_mail_session_ref_transport_for_message() */
1605 static CamelService *
mail_session_ref_transport_from_x_transport(EMailSession * session,CamelMimeMessage * message)1606 mail_session_ref_transport_from_x_transport (EMailSession *session,
1607                                              CamelMimeMessage *message)
1608 {
1609 	CamelMedium *medium;
1610 	CamelService *transport;
1611 	const gchar *header_name;
1612 	const gchar *header_value;
1613 	gchar *uid;
1614 
1615 	medium = CAMEL_MEDIUM (message);
1616 	header_name = "X-Evolution-Transport";
1617 	header_value = camel_medium_get_header (medium, header_name);
1618 
1619 	if (header_value == NULL)
1620 		return NULL;
1621 
1622 	uid = g_strstrip (g_strdup (header_value));
1623 
1624 	transport = e_mail_session_ref_transport (session, uid);
1625 
1626 	g_free (uid);
1627 
1628 	return transport;
1629 }
1630 
1631 /**
1632  * e_mail_session_ref_transport_for_message:
1633  * @session: an #EMailSession
1634  * @message: a #CamelMimeMessage
1635  *
1636  * Returns the preferred transport #CamelService instance for @message by
1637  * first checking @message for an "X-Evolution-Identity" header, and then
1638  * an "X-Evolution-Transport" header.  Failing that, the function returns
1639  * the default transport #CamelService instance (if available).
1640  *
1641  * The returned #CamelService is referenced for thread-safety and must be
1642  * unreferenced with g_object_unref() when finished with it.
1643  *
1644  * Returns: a #CamelService, or %NULL
1645  **/
1646 CamelService *
e_mail_session_ref_transport_for_message(EMailSession * session,CamelMimeMessage * message)1647 e_mail_session_ref_transport_for_message (EMailSession *session,
1648                                           CamelMimeMessage *message)
1649 {
1650 	CamelService *transport = NULL;
1651 
1652 	g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
1653 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
1654 
1655 	/* Check for "X-Evolution-Identity" header. */
1656 	if (transport == NULL)
1657 		transport = mail_session_ref_transport_from_x_identity (
1658 			session, message);
1659 
1660 	/* Check for "X-Evolution-Transport" header. */
1661 	if (transport == NULL)
1662 		transport = mail_session_ref_transport_from_x_transport (
1663 			session, message);
1664 
1665 	/* Fall back to the default mail transport. */
1666 	if (transport == NULL)
1667 		transport = e_mail_session_ref_default_transport (session);
1668 
1669 	return transport;
1670 }
1671 
1672