1 /*
2  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
3  * Copyright (C) 2008 - Diego Escalante Urrelo
4  * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com)
5  *
6  * This program is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by
8  * the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors:
19  *		Diego Escalante Urrelo <diegoe@gnome.org>
20  *		Bharath Acharya <abharath@novell.com>
21  */
22 
23 #include "evolution-config.h"
24 
25 #include <string.h>
26 
27 #include "e-util/e-util.h"
28 #include "em-format/e-mail-stripsig-filter.h"
29 
30 #include "em-composer-utils.h"
31 #include "em-utils.h"
32 
33 #include "e-mail-templates.h"
34 
35 static void
replace_in_string(GString * text,const gchar * find,const gchar * replacement)36 replace_in_string (GString *text,
37 		   const gchar *find,
38 		   const gchar *replacement)
39 {
40 	const gchar *p, *next;
41 	GString *str;
42 	gint find_len;
43 
44 	g_return_if_fail (text != NULL);
45 	g_return_if_fail (find != NULL);
46 
47 	find_len = strlen (find);
48 	str = g_string_new ("");
49 	p = text->str;
50 	while (next = e_util_strstrcase (p, find), next) {
51 		if (p < next)
52 			g_string_append_len (str, p, next - p);
53 		if (replacement && *replacement)
54 			g_string_append (str, replacement);
55 		p = next + find_len;
56 	}
57 
58 	/* Avoid unnecessary allocation when the 'text' doesn't contain the variable */
59 	if (p != text->str) {
60 		g_string_append (str, p);
61 		g_string_assign (text, str->str);
62 	}
63 
64 	g_string_free (str, TRUE);
65 }
66 
67 /* Replaces $ORIG[variable] in given template by given replacement from the original message */
68 static void
replace_template_variable(GString * text,const gchar * variable,const gchar * replacement)69 replace_template_variable (GString *text,
70                            const gchar *variable,
71                            const gchar *replacement)
72 {
73 	gchar *find;
74 
75 	g_return_if_fail (text != NULL);
76 	g_return_if_fail (variable != NULL);
77 	g_return_if_fail (*variable);
78 
79 	find = g_strconcat ("$ORIG[", variable, "]", NULL);
80 
81 	replace_in_string (text, find, replacement);
82 
83 	g_free (find);
84 }
85 
86 static void
replace_user_variables(GString * text,CamelMimeMessage * source_message)87 replace_user_variables (GString *text,
88 			CamelMimeMessage *source_message)
89 {
90 	CamelInternetAddress *to;
91 	const gchar *name, *addr;
92 	GSettings *settings;
93 	gchar **strv;
94 	gint ii;
95 
96 	g_return_if_fail (text);
97 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (source_message));
98 
99 	settings = e_util_ref_settings ("org.gnome.evolution.plugin.templates");
100 	strv = g_settings_get_strv (settings, "template-placeholders");
101 	g_object_unref (settings);
102 
103 	for (ii = 0; strv && strv[ii]; ii++) {
104 		gchar *equal_sign, *find, *var_name = strv[ii];
105 		const gchar *var_value;
106 
107 		equal_sign = strchr (var_name, '=');
108 		if (!equal_sign)
109 			continue;
110 
111 		*equal_sign = '\0';
112 		var_value = equal_sign + 1;
113 
114 		find = g_strconcat ("$", var_name, NULL);
115 		replace_in_string (text, find, var_value);
116 		g_free (find);
117 
118 		*equal_sign = '=';
119 	}
120 
121 	g_strfreev (strv);
122 
123 	to = camel_mime_message_get_recipients (source_message, CAMEL_RECIPIENT_TYPE_TO);
124 	if (to && camel_internet_address_get (to, 0, &name, &addr)) {
125 		replace_in_string (text, "$sender_name", name);
126 		replace_in_string (text, "$sender_email", addr);
127 	}
128 }
129 
130 static void
replace_email_addresses(GString * template,CamelInternetAddress * internet_address,const gchar * variable)131 replace_email_addresses (GString *template,
132                          CamelInternetAddress *internet_address,
133                          const gchar *variable)
134 {
135 	gint address_index = 0;
136 	GString *emails = g_string_new ("");
137 	const gchar *address_name, *address_email;
138 
139 	g_return_if_fail (template);
140 	g_return_if_fail (internet_address);
141 	g_return_if_fail (variable);
142 
143 	while (camel_internet_address_get (internet_address, address_index, &address_name, &address_email)) {
144 		gchar *address = camel_internet_address_format_address (address_name, address_email);
145 
146 		if (address_index > 0)
147 			g_string_append_printf (emails, ", %s", address);
148 		else
149 			g_string_append_printf (emails, "%s", address);
150 
151 		address_index++;
152 		g_free (address);
153 	}
154 	replace_template_variable (template, variable, emails->str);
155 	g_string_free (emails, TRUE);
156 }
157 
158 static ESource *
ref_identity_source_from_message_and_folder(CamelMimeMessage * message,CamelFolder * folder,const gchar * message_uid)159 ref_identity_source_from_message_and_folder (CamelMimeMessage *message,
160 					     CamelFolder *folder,
161 					     const gchar *message_uid)
162 {
163 	EShell *shell;
164 
165 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
166 
167 	shell = e_shell_get_default ();
168 	if (!shell)
169 		return NULL;
170 
171 	return em_composer_utils_guess_identity_source (shell, message, folder, message_uid, NULL, NULL);
172 }
173 
174 static CamelMimePart *
fill_template(CamelMimeMessage * message,CamelFolder * source_folder,const gchar * source_message_uid,CamelFolder * templates_folder,CamelMimePart * template)175 fill_template (CamelMimeMessage *message,
176 	       CamelFolder *source_folder,
177 	       const gchar *source_message_uid,
178 	       CamelFolder *templates_folder,
179                CamelMimePart *template)
180 {
181 	const CamelNameValueArray *headers;
182 	CamelContentType *ct;
183 	CamelStream *stream;
184 	CamelMimePart *return_part;
185 	CamelMimePart *message_part = NULL;
186 	CamelDataWrapper *dw;
187 	CamelInternetAddress *internet_address;
188 	GString *template_body;
189 	GByteArray *byte_array;
190 	gint i;
191 	guint jj, len;
192 	gboolean message_html, template_html;
193 	gboolean has_quoted_body;
194 
195 	ct = camel_mime_part_get_content_type (template);
196 	template_html = ct && camel_content_type_is (ct, "text", "html");
197 
198 	message_html = FALSE;
199 	/* When template is html, then prefer HTML part of the original message. Otherwise go for plaintext */
200 	dw = camel_medium_get_content (CAMEL_MEDIUM (message));
201 	if (CAMEL_IS_MULTIPART (dw)) {
202 		CamelMultipart *multipart = CAMEL_MULTIPART (dw);
203 
204 		for (i = 0; i < camel_multipart_get_number (multipart); i++) {
205 			CamelMimePart *part = camel_multipart_get_part (multipart, i);
206 			CamelContentType *ct = camel_mime_part_get_content_type (part);
207 
208 			if (!ct)
209 				continue;
210 
211 			if (camel_content_type_is (ct, "text", "html") && template_html) {
212 				message_part = camel_multipart_get_part (multipart, i);
213 				message_html = TRUE;
214 				break;
215 			} else if (camel_content_type_is (ct, "text", "plain") && message_html == FALSE) {
216 				message_part = camel_multipart_get_part (multipart, i);
217 			}
218 		}
219 	} else {
220 		CamelContentType *mpct;
221 
222 		message_part = CAMEL_MIME_PART (message);
223 
224 		mpct = camel_mime_part_get_content_type (message_part);
225 		message_html = mpct && camel_content_type_is (mpct, "text", "html");
226 	}
227 
228 	/* Get content of the template */
229 	stream = camel_stream_mem_new ();
230 	camel_data_wrapper_decode_to_stream_sync (camel_medium_get_content (CAMEL_MEDIUM (template)), stream, NULL, NULL);
231 	camel_stream_flush (stream, NULL, NULL);
232 	byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (stream));
233 	template_body = g_string_new_len ((gchar *) byte_array->data, byte_array->len);
234 	g_object_unref (stream);
235 
236 	/* Replace all $ORIG[header_name] by respective values */
237 	headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
238 	len = camel_name_value_array_get_length (headers);
239 	for (jj = 0; jj < len; jj++) {
240 		const gchar *header_name = NULL, *header_value = NULL;
241 
242 		if (!camel_name_value_array_get (headers, jj, &header_name, &header_value) ||
243 		    !header_name)
244 			continue;
245 
246 		if (g_ascii_strncasecmp (header_name, "content-", 8) != 0 &&
247 		    g_ascii_strcasecmp (header_name, "to") != 0 &&
248 		    g_ascii_strcasecmp (header_name, "cc") != 0 &&
249 		    g_ascii_strcasecmp (header_name, "bcc") != 0 &&
250 		    g_ascii_strcasecmp (header_name, "from") != 0 &&
251 		    g_ascii_strcasecmp (header_name, "subject") != 0)
252 			replace_template_variable (template_body, header_name, header_value);
253 	}
254 
255 	/* Now manually replace the *subject* header. The header->value for subject header could be
256 	 * base64 encoded, so let camel_mime_message to decode it for us if needed */
257 	replace_template_variable (template_body, "subject", camel_mime_message_get_subject (message));
258 
259 	/* Replace TO and FROM modifiers. */
260 	internet_address = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
261 	replace_email_addresses (template_body, internet_address, "to");
262 
263 	internet_address = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
264 	replace_email_addresses (template_body, internet_address, "cc");
265 
266 	internet_address = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
267 	replace_email_addresses (template_body, internet_address, "bcc");
268 
269 	internet_address = camel_mime_message_get_from (message);
270 	replace_email_addresses (template_body, internet_address, "from");
271 
272 	has_quoted_body = e_util_strstrcase (template_body->str, "$ORIG[quoted-body]") != NULL;
273 	if (has_quoted_body && !template_html) {
274 		gchar *html;
275 
276 		template_html = TRUE;
277 
278 		html = camel_text_to_html (
279 			template_body->str,
280 			CAMEL_MIME_FILTER_TOHTML_DIV |
281 			CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
282 			CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS |
283 			CAMEL_MIME_FILTER_TOHTML_MARK_CITATION |
284 			CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES, 0);
285 		g_string_assign (template_body, html);
286 		g_free (html);
287 
288 		g_string_append (template_body, "<!-- disable-format-prompt -->");
289 	}
290 
291 	g_string_append (template_body, "<span id=\"x-evo-template-fix-paragraphs\"></span>");
292 
293 	/* Now extract body of the original message and replace the $ORIG[body] modifier in template */
294 	if (message_part && (has_quoted_body || e_util_strstrcase (template_body->str, "$ORIG[body]"))) {
295 		GString *message_body, *message_body_nosig = NULL;
296 		CamelStream *mem_stream;
297 
298 		stream = camel_stream_mem_new ();
299 		mem_stream = stream;
300 
301 		ct = camel_mime_part_get_content_type (message_part);
302 		if (ct) {
303 			const gchar *charset = camel_content_type_param (ct, "charset");
304 			if (charset && *charset) {
305 				CamelMimeFilter *filter = camel_mime_filter_charset_new (charset, "UTF-8");
306 				if (filter) {
307 					CamelStream *filtered = camel_stream_filter_new (stream);
308 
309 					if (filtered) {
310 						camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered), filter);
311 						g_object_unref (stream);
312 						stream = filtered;
313 					}
314 
315 					g_object_unref (filter);
316 				}
317 			}
318 		}
319 
320 		camel_data_wrapper_decode_to_stream_sync (camel_medium_get_content (CAMEL_MEDIUM (message_part)), stream, NULL, NULL);
321 		camel_stream_flush (stream, NULL, NULL);
322 		byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem_stream));
323 		message_body = g_string_new_len ((gchar *) byte_array->data, byte_array->len);
324 		g_object_unref (stream);
325 
326 		if (has_quoted_body) {
327 			CamelMimeFilter *filter;
328 
329 			stream = camel_stream_mem_new ();
330 			mem_stream = stream;
331 
332 			ct = camel_mime_part_get_content_type (message_part);
333 			if (ct) {
334 				const gchar *charset = camel_content_type_param (ct, "charset");
335 				if (charset && *charset) {
336 					CamelMimeFilter *filter = camel_mime_filter_charset_new (charset, "UTF-8");
337 					if (filter) {
338 						CamelStream *filtered = camel_stream_filter_new (stream);
339 
340 						if (filtered) {
341 							camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered), filter);
342 							g_object_unref (stream);
343 							stream = filtered;
344 						}
345 
346 						g_object_unref (filter);
347 					}
348 				}
349 			}
350 
351 			filter = e_mail_stripsig_filter_new (!message_html);
352 			if (filter) {
353 				CamelStream *filtered = camel_stream_filter_new (stream);
354 
355 				if (filtered) {
356 					camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered), filter);
357 					g_object_unref (stream);
358 					stream = filtered;
359 				}
360 
361 				g_object_unref (filter);
362 			}
363 
364 			camel_data_wrapper_decode_to_stream_sync (camel_medium_get_content (CAMEL_MEDIUM (message_part)), stream, NULL, NULL);
365 			camel_stream_flush (stream, NULL, NULL);
366 			byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem_stream));
367 			message_body_nosig = g_string_new_len ((gchar *) byte_array->data, byte_array->len);
368 			g_object_unref (stream);
369 		}
370 
371 		if (template_html && !message_html) {
372 			gchar *html = camel_text_to_html (
373 				message_body->str,
374 				CAMEL_MIME_FILTER_TOHTML_PRE |
375 				CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
376 				CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS |
377 				CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES, 0);
378 			replace_template_variable (template_body, "body", html);
379 			g_free (html);
380 		} else if (!template_html && message_html) {
381 			gchar *html;
382 
383 			g_string_prepend (message_body, "<pre>");
384 			g_string_append (message_body, "</pre>");
385 
386 			template_html = TRUE;
387 
388 			html = camel_text_to_html (
389 				template_body->str,
390 				CAMEL_MIME_FILTER_TOHTML_DIV |
391 				CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
392 				CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS |
393 				CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES, 0);
394 			g_string_assign (template_body, html);
395 			g_free (html);
396 
397 			replace_template_variable (template_body, "body", message_body->str);
398 		} else { /* Other cases should not occur. And even if they happen to do, there's nothing we can really do about it */
399 			replace_template_variable (template_body, "body", message_body->str);
400 		}
401 
402 		if (has_quoted_body) {
403 			if (!message_html) {
404 				gchar *html = camel_text_to_html (
405 					message_body_nosig->str,
406 					CAMEL_MIME_FILTER_TOHTML_PRE |
407 					CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
408 					CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS |
409 					CAMEL_MIME_FILTER_TOHTML_QUOTE_CITATION |
410 					CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES, 0);
411 				g_string_assign (message_body_nosig, html);
412 				g_free (html);
413 			}
414 
415 			g_string_prepend (message_body_nosig, "<blockquote type=\"cite\">");
416 			g_string_append (message_body_nosig, "</blockquote>");
417 
418 			replace_template_variable (template_body, "quoted-body", message_body_nosig->str);
419 		}
420 
421 		if (message_body_nosig)
422 			g_string_free (message_body_nosig, TRUE);
423 
424 		g_string_free (message_body, TRUE);
425 	} else {
426 		replace_template_variable (template_body, "body", "");
427 		replace_template_variable (template_body, "quoted-body", "");
428 	}
429 
430 	if (e_util_strstrcase (template_body->str, "$ORIG[reply-credits]")) {
431 		ESource *identity_source;
432 		gchar *reply_credits;
433 
434 		identity_source = ref_identity_source_from_message_and_folder (message, source_folder, source_message_uid);
435 
436 		reply_credits = em_composer_utils_get_reply_credits (identity_source, message);
437 
438 		if (reply_credits && template_html) {
439 			gchar *html = camel_text_to_html (
440 				reply_credits,
441 				CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS |
442 				CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES, 0);
443 			g_free (reply_credits);
444 			reply_credits = html;
445 		}
446 
447 		replace_template_variable (template_body, "reply-credits", reply_credits ? reply_credits : "");
448 
449 		g_clear_object (&identity_source);
450 		g_free (reply_credits);
451 	}
452 
453 	replace_user_variables (template_body, message);
454 
455 	return_part = camel_mime_part_new ();
456 
457 	if (template_html)
458 		camel_mime_part_set_content (return_part, template_body->str, template_body->len, "text/html");
459 	else
460 		camel_mime_part_set_content (return_part, template_body->str, template_body->len, "text/plain");
461 
462 	g_string_free (template_body, TRUE);
463 
464 	return return_part;
465 }
466 
467 static CamelMimePart *
find_template_part_in_multipart(CamelMultipart * multipart,CamelMultipart * new_multipart)468 find_template_part_in_multipart (CamelMultipart *multipart,
469 				 CamelMultipart *new_multipart)
470 {
471 	CamelMimePart *template_part = NULL;
472 	gint ii;
473 
474 	for (ii = 0; ii < camel_multipart_get_number (multipart); ii++) {
475 		CamelMimePart *part = camel_multipart_get_part (multipart, ii);
476 		CamelContentType *ct = camel_mime_part_get_content_type (part);
477 
478 		if (!template_part && ct && camel_content_type_is (ct, "multipart", "*")) {
479 			CamelDataWrapper *dw;
480 
481 			dw = camel_medium_get_content (CAMEL_MEDIUM (part));
482 			template_part = (dw && CAMEL_IS_MULTIPART (dw)) ?
483 				find_template_part_in_multipart (CAMEL_MULTIPART (dw), new_multipart) : NULL;
484 
485 			if (!template_part) {
486 				/* Copy any other parts (attachments...) to the output message */
487 				camel_mime_part_set_disposition (part, "attachment");
488 				camel_multipart_add_part (new_multipart, part);
489 			}
490 		} else if (ct && camel_content_type_is (ct, "text", "html")) {
491 			template_part = part;
492 		} else if (ct && camel_content_type_is (ct, "text", "plain") && !template_part) {
493 			template_part = part;
494 		} else {
495 			/* Copy any other parts (attachments...) to the output message */
496 			camel_mime_part_set_disposition (part, "attachment");
497 			camel_multipart_add_part (new_multipart, part);
498 		}
499 	}
500 
501 	return template_part;
502 }
503 
504 CamelMimeMessage *
e_mail_templates_apply_sync(CamelMimeMessage * source_message,CamelFolder * source_folder,const gchar * source_message_uid,CamelFolder * templates_folder,const gchar * templates_message_uid,GCancellable * cancellable,GError ** error)505 e_mail_templates_apply_sync (CamelMimeMessage *source_message,
506 			     CamelFolder *source_folder,
507 			     const gchar *source_message_uid,
508 			     CamelFolder *templates_folder,
509 			     const gchar *templates_message_uid,
510 			     GCancellable *cancellable,
511 			     GError **error)
512 {
513 	CamelMimeMessage *template_message, *result_message = NULL;
514 	CamelMultipart *new_multipart;
515 	CamelDataWrapper *dw;
516 	const CamelNameValueArray *headers;
517 	CamelMimePart *template_part = NULL;
518 	gchar *references, *message_id;
519 	guint ii, len;
520 
521 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (source_message), NULL);
522 	g_return_val_if_fail (CAMEL_IS_FOLDER (templates_folder), NULL);
523 	g_return_val_if_fail (templates_message_uid != NULL, NULL);
524 
525 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
526 		return NULL;
527 
528 	template_message = camel_folder_get_message_sync (templates_folder, templates_message_uid, cancellable, error);
529 	if (!template_message)
530 		return NULL;
531 
532 	result_message = camel_mime_message_new ();
533 	new_multipart = camel_multipart_new ();
534 	camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (new_multipart), "multipart/alternative");
535 	camel_multipart_set_boundary (new_multipart, NULL);
536 
537 	dw = camel_medium_get_content (CAMEL_MEDIUM (template_message));
538 	/* If template is a multipart, then try to use HTML. When no HTML part is available, use plaintext. Every other
539 	 * add as an attachment */
540 	if (CAMEL_IS_MULTIPART (dw)) {
541 		template_part = find_template_part_in_multipart (CAMEL_MULTIPART (dw), new_multipart);
542 	} else {
543 		CamelContentType *ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (template_message));
544 
545 		if (ct && (camel_content_type_is (ct, "text", "html") ||
546 		    camel_content_type_is (ct, "text", "plain"))) {
547 			template_part = CAMEL_MIME_PART (template_message);
548 		}
549 	}
550 
551 	g_warn_if_fail (template_part != NULL);
552 
553 	if (template_part) {
554 		CamelMimePart *out_part = NULL;
555 
556 		/* Here replace all the modifiers in template body by values
557 		   from message and return the newly created part */
558 		out_part = fill_template (source_message, source_folder, source_message_uid, templates_folder, template_part);
559 
560 		/* Assigning part directly to mime_message causes problem with
561 		   "Content-type" header displaying in the HTML message (camel parsing bug?) */
562 		camel_multipart_add_part (new_multipart, out_part);
563 		g_object_unref (out_part);
564 	}
565 
566 	camel_medium_set_content (CAMEL_MEDIUM (result_message), CAMEL_DATA_WRAPPER (new_multipart));
567 
568 	/* Add the headers from the message we are replying to, so CC and that
569 	 * stuff is preserved. Also replace any $ORIG[header-name] modifiers ignoring
570 	 * 'content-*' headers */
571 	headers = camel_medium_get_headers (CAMEL_MEDIUM (source_message));
572 	len = camel_name_value_array_get_length (headers);
573 	for (ii = 0; ii < len; ii++) {
574 		const gchar *header_name = NULL, *header_value = NULL;
575 
576 		if (!camel_name_value_array_get (headers, ii, &header_name, &header_value) ||
577 		    !header_name)
578 			continue;
579 
580 		if (g_ascii_strncasecmp (header_name, "content-", 8) != 0 &&
581 		    g_ascii_strcasecmp (header_name, "from") != 0 &&
582 		    g_ascii_strcasecmp (header_name, "Message-ID") != 0 &&
583 		    g_ascii_strcasecmp (header_name, "In-Reply-To") != 0 &&
584 		    g_ascii_strcasecmp (header_name, "References") != 0) {
585 			gchar *new_header_value = NULL;
586 
587 			/* Some special handling of the 'subject' header */
588 			if (g_ascii_strncasecmp (header_name, "subject", 7) == 0) {
589 				GString *subject = g_string_new (camel_mime_message_get_subject (template_message));
590 				guint jj;
591 
592 				/* Now replace all possible $ORIG[]s in the subject line by values from original message */
593 				for (jj = 0; jj < len; jj++) {
594 					const gchar *m_header_name = NULL, *m_header_value = NULL;
595 
596 					if (camel_name_value_array_get (headers, jj, &m_header_name, &m_header_value) &&
597 					    m_header_name &&
598 					    g_ascii_strncasecmp (m_header_name, "content-", 8) != 0 &&
599 					    g_ascii_strcasecmp (m_header_name, "subject") != 0)
600 						replace_template_variable (subject, m_header_name, m_header_value);
601 				}
602 
603 				/* Now replace $ORIG[subject] variable, handling possible base64 encryption */
604 				replace_template_variable (
605 					subject, "subject",
606 					camel_mime_message_get_subject (source_message));
607 
608 				replace_user_variables (subject, source_message);
609 
610 				new_header_value = g_string_free (subject, FALSE);
611 			}
612 
613 			camel_medium_add_header (CAMEL_MEDIUM (result_message), header_name, new_header_value ? new_header_value : header_value);
614 
615 			g_free (new_header_value);
616 		}
617 	}
618 
619 	/* Set the To: field to the same To: field of the message we are replying to. */
620 	camel_mime_message_set_recipients (
621 		result_message, CAMEL_RECIPIENT_TYPE_TO,
622 		camel_mime_message_get_reply_to (source_message) ? camel_mime_message_get_reply_to (source_message) :
623 		camel_mime_message_get_from (source_message));
624 
625 	/* Copy the recipients from the template. */
626 	camel_mime_message_set_recipients (result_message, CAMEL_RECIPIENT_TYPE_CC,
627 		camel_mime_message_get_recipients (template_message, CAMEL_RECIPIENT_TYPE_CC));
628 
629 	camel_mime_message_set_recipients (result_message, CAMEL_RECIPIENT_TYPE_BCC,
630 		camel_mime_message_get_recipients (template_message, CAMEL_RECIPIENT_TYPE_BCC));
631 
632 	if (camel_mime_message_get_reply_to (template_message))
633 		camel_mime_message_set_reply_to (result_message, camel_mime_message_get_reply_to (template_message));
634 
635 	/* Add In-Reply-To and References. */
636 
637 	message_id = camel_header_unfold (camel_medium_get_header (CAMEL_MEDIUM (source_message), "Message-ID"));
638 	references = camel_header_unfold (camel_medium_get_header (CAMEL_MEDIUM (source_message), "References"));
639 
640 	if (message_id && *message_id) {
641 		gchar *reply_refs;
642 
643 		camel_medium_add_header (CAMEL_MEDIUM (result_message), "In-Reply-To", message_id);
644 
645 		if (references)
646 			reply_refs = g_strdup_printf ("%s %s", references, message_id);
647 		else
648 			reply_refs = NULL;
649 
650 		camel_medium_add_header (CAMEL_MEDIUM (result_message), "References", reply_refs ? reply_refs : message_id);
651 
652 		g_free (reply_refs);
653 
654 	} else if (references && *references) {
655 		camel_medium_add_header (CAMEL_MEDIUM (result_message), "References", references);
656 	}
657 
658 	g_free (message_id);
659 	g_free (references);
660 
661 	g_clear_object (&template_message);
662 	g_clear_object (&new_multipart);
663 
664 	return result_message;
665 }
666 
667 typedef struct _AsyncContext {
668 	CamelMimeMessage *source_message;
669 	CamelFolder *source_folder;
670 	CamelFolder *templates_folder;
671 	gchar *source_message_uid;
672 	gchar *templates_message_uid;
673 	CamelMimeMessage *result_message;
674 } AsyncContext;
675 
676 static void
async_context_free(gpointer ptr)677 async_context_free (gpointer ptr)
678 {
679 	AsyncContext *context = ptr;
680 
681 	if (context) {
682 		g_clear_object (&context->source_message);
683 		g_clear_object (&context->source_folder);
684 		g_clear_object (&context->templates_folder);
685 		g_clear_object (&context->result_message);
686 		g_free (context->source_message_uid);
687 		g_free (context->templates_message_uid);
688 		g_slice_free (AsyncContext, context);
689 	}
690 }
691 
692 static void
e_mail_templates_apply_thread(ESimpleAsyncResult * simple,gpointer source_object,GCancellable * cancellable)693 e_mail_templates_apply_thread (ESimpleAsyncResult *simple,
694 			       gpointer source_object,
695 			       GCancellable *cancellable)
696 {
697 	AsyncContext *context;
698 	GError *local_error = NULL;
699 
700 	context = e_simple_async_result_get_op_pointer (simple);
701 	g_return_if_fail (context != NULL);
702 
703 	context->result_message = e_mail_templates_apply_sync (
704 		context->source_message, context->source_folder, context->source_message_uid,
705 		context->templates_folder, context->templates_message_uid,
706 		cancellable, &local_error);
707 
708 	if (local_error)
709 		e_simple_async_result_take_error (simple, local_error);
710 }
711 
712 void
e_mail_templates_apply(CamelMimeMessage * source_message,CamelFolder * source_folder,const gchar * source_message_uid,CamelFolder * templates_folder,const gchar * templates_message_uid,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)713 e_mail_templates_apply (CamelMimeMessage *source_message,
714 			CamelFolder *source_folder,
715 			const gchar *source_message_uid,
716 			CamelFolder *templates_folder,
717 			const gchar *templates_message_uid,
718 			GCancellable *cancellable,
719 			GAsyncReadyCallback callback,
720 			gpointer user_data)
721 {
722 	ESimpleAsyncResult *simple;
723 	AsyncContext *context;
724 
725 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (source_message));
726 	g_return_if_fail (CAMEL_IS_FOLDER (templates_folder));
727 	g_return_if_fail (templates_message_uid != NULL);
728 	g_return_if_fail (callback != NULL);
729 
730 	context = g_slice_new0 (AsyncContext);
731 	context->source_message = g_object_ref (source_message);
732 	context->source_folder = source_folder ? g_object_ref (source_folder) : NULL;
733 	context->source_message_uid = g_strdup (source_message_uid);
734 	context->templates_folder = g_object_ref (templates_folder);
735 	context->templates_message_uid = g_strdup (templates_message_uid);
736 	context->result_message = NULL;
737 
738 	simple = e_simple_async_result_new (G_OBJECT (source_message), callback,
739 		user_data, e_mail_templates_apply);
740 
741 	e_simple_async_result_set_op_pointer (simple, context, (GDestroyNotify) async_context_free);
742 
743 	e_simple_async_result_run_in_thread (simple, G_PRIORITY_DEFAULT, e_mail_templates_apply_thread, cancellable);
744 
745 	g_object_unref (simple);
746 }
747 
748 CamelMimeMessage *
e_mail_templates_apply_finish(GObject * source_object,GAsyncResult * result,GError ** error)749 e_mail_templates_apply_finish (GObject *source_object,
750 			       GAsyncResult *result,
751 			       GError **error)
752 {
753 	ESimpleAsyncResult *simple;
754 	AsyncContext *context;
755 
756 	g_return_val_if_fail (e_simple_async_result_is_valid (result, source_object, e_mail_templates_apply), NULL);
757 
758 	simple = E_SIMPLE_ASYNC_RESULT (result);
759 	context = e_simple_async_result_get_op_pointer (simple);
760 
761 	if (e_simple_async_result_propagate_error (simple, error))
762 		return NULL;
763 
764 	return context->result_message ? g_object_ref (context->result_message) : NULL;
765 }
766