1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * SPDX-FileCopyrightText: (C) 1999-2011 Novell, Inc. (www.novell.com)
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  */
6 
7 #include "evolution-ews-config.h"
8 
9 #include <glib/gi18n-lib.h>
10 #include <glib/gstdio.h>
11 
12 #include "common/e-ews-message.h"
13 #include "common/e-ews-item-change.h"
14 
15 #include "e-ews-camel-common.h"
16 
17 struct _create_mime_msg_data {
18 	EEwsConnection *cnc;
19 	CamelMimeMessage *message;
20 	CamelMessageInfo *info;
21 	CamelAddress *from;
22 	CamelAddress *recipients;
23 	gboolean is_send;
24 };
25 
26 static void
filter_recipients(CamelMimeMessage * message,CamelAddress * recipients,GHashTable * recip_to,GHashTable * recip_cc,GHashTable * recip_bcc)27 filter_recipients (CamelMimeMessage *message,
28 		   CamelAddress *recipients,
29 		   GHashTable *recip_to,
30 		   GHashTable *recip_cc,
31 		   GHashTable *recip_bcc)
32 {
33 	CamelInternetAddress *addresses, *mime_cc, *mime_bcc;
34 	gint ii, len;
35 
36 	g_return_if_fail (message != NULL);
37 	g_return_if_fail (recipients != NULL);
38 	g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (recipients));
39 	g_return_if_fail (recip_to != NULL);
40 	g_return_if_fail (recip_cc != NULL);
41 	g_return_if_fail (recip_bcc != NULL);
42 
43 	mime_cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
44 	mime_bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
45 
46 	addresses = CAMEL_INTERNET_ADDRESS (recipients);
47 	len = camel_address_length (recipients);
48 	for (ii = 0; ii < len; ii++) {
49 		const gchar *name = NULL, *email = NULL;
50 
51 		if (!camel_internet_address_get (addresses, ii, &name, &email) ||
52 		    !email)
53 			continue;
54 
55 		if (mime_bcc && camel_internet_address_find_address (mime_bcc, email, NULL) != -1) {
56 			g_hash_table_insert (recip_bcc, (gpointer) email, GINT_TO_POINTER (1));
57 		} else if (mime_cc && camel_internet_address_find_address (mime_cc, email, NULL) != -1) {
58 			g_hash_table_insert (recip_cc, (gpointer) email, GINT_TO_POINTER (1));
59 		} else {
60 			g_hash_table_insert (recip_to, (gpointer) email, GINT_TO_POINTER (1));
61 		}
62 	}
63 }
64 
65 static void
write_recipients(ESoapMessage * msg,const gchar * elem_name,GHashTable * recips,gboolean is_resend)66 write_recipients (ESoapMessage *msg,
67 		  const gchar *elem_name,
68 		  GHashTable *recips,
69 		  gboolean is_resend)
70 {
71 	GHashTableIter iter;
72 	gpointer key, value;
73 
74 	g_return_if_fail (msg != NULL);
75 	g_return_if_fail (elem_name != NULL);
76 	g_return_if_fail (recips != NULL);
77 
78 	if (!is_resend && !g_hash_table_size (recips))
79 		return;
80 
81 	e_soap_message_start_element (msg, elem_name, NULL, NULL);
82 
83 	g_hash_table_iter_init (&iter, recips);
84 	while (g_hash_table_iter_next (&iter, &key, &value)) {
85 		e_soap_message_start_element (msg, "Mailbox", NULL, NULL);
86 		e_ews_message_write_string_parameter_with_attribute (msg, "EmailAddress", NULL, key, NULL, NULL);
87 		e_soap_message_end_element (msg); /* Mailbox */
88 	}
89 
90 	e_soap_message_end_element (msg); /* elem_name */
91 }
92 
93 static gboolean
is_any_address_filled(CamelInternetAddress * addrs)94 is_any_address_filled (CamelInternetAddress *addrs)
95 {
96 	return addrs && camel_address_length (CAMEL_ADDRESS (addrs)) > 0;
97 }
98 
99 /* MAPI flags gleaned from windows header files */
100 #define MAPI_MSGFLAG_READ	0x01
101 #define MAPI_MSGFLAG_UNSENT	0x08
102 
103 static gboolean
create_mime_message_cb(ESoapMessage * msg,gpointer user_data,GError ** error)104 create_mime_message_cb (ESoapMessage *msg,
105                         gpointer user_data,
106 			GError **error)
107 {
108 	struct _create_mime_msg_data *create_data = user_data;
109 	CamelStream *mem, *filtered;
110 	CamelMimeFilter *filter;
111 	CamelContentType *content_type;
112 	GByteArray *bytes;
113 	gchar *base64;
114 	gint msgflag;
115 	guint32 message_camel_flags = 0;
116 
117 	if (create_data->info)
118 		message_camel_flags = camel_message_info_get_flags (create_data->info);
119 
120 	if (create_data->is_send && !(message_camel_flags & CAMEL_MESSAGE_FLAGGED)) {
121 		const gchar *value;
122 
123 		value = camel_medium_get_header (CAMEL_MEDIUM (create_data->message), "X-Priority");
124 
125 		if (g_strcmp0 (value, "1") == 0) {
126 			message_camel_flags |= CAMEL_MESSAGE_FLAGGED;
127 		} else {
128 			value = camel_medium_get_header (CAMEL_MEDIUM (create_data->message), "Importance");
129 
130 			if (value && g_ascii_strcasecmp (value, "High") == 0)
131 				message_camel_flags |= CAMEL_MESSAGE_FLAGGED;
132 		}
133 	}
134 
135 	e_soap_message_start_element (msg, "Message", NULL, NULL);
136 	e_soap_message_start_element (msg, "MimeContent", NULL, NULL);
137 
138 	/* This is horrid. We really need to extend ESoapMessage to allow us
139 	 * to stream this directly rather than storing it in RAM. Which right
140 	 * now we are doing about four times: the GByteArray in the mem stream,
141 	 * then the base64 version, then the xmlDoc, then the soup request. */
142 
143 	mem = camel_stream_mem_new ();
144 	filtered = camel_stream_filter_new (mem);
145 
146 	filter = camel_mime_filter_crlf_new (
147 		CAMEL_MIME_FILTER_CRLF_ENCODE,
148 		CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
149 	camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered), filter);
150 	g_object_unref (filter);
151 
152 	camel_data_wrapper_write_to_stream_sync (
153 		CAMEL_DATA_WRAPPER (create_data->message),
154 		filtered, NULL, NULL);
155 	camel_stream_flush (filtered, NULL, NULL);
156 	camel_stream_flush (mem, NULL, NULL);
157 	bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem));
158 
159 	base64 = g_base64_encode (bytes->data, bytes->len);
160 	g_object_unref (mem);
161 	g_object_unref (filtered);
162 
163 	e_soap_message_write_string (msg, base64);
164 	g_free (base64);
165 
166 	e_soap_message_end_element (msg); /* MimeContent */
167 
168 	content_type = camel_mime_part_get_content_type (CAMEL_MIME_PART (create_data->message));
169 	if (content_type && camel_content_type_is (content_type, "multipart", "report") &&
170 	    camel_content_type_param (content_type, "report-type") &&
171 	    g_ascii_strcasecmp (camel_content_type_param (content_type, "report-type"), "disposition-notification") == 0) {
172 		/* it's a disposition notification reply, set ItemClass too */
173 		e_soap_message_start_element (msg, "ItemClass", NULL, NULL);
174 		e_soap_message_write_string (msg, "REPORT.IPM.NOTE.IPNRN");
175 		e_soap_message_end_element (msg); /* ItemClass */
176 	}
177 
178 	e_ews_message_write_string_parameter_with_attribute (
179 			msg,
180 			"Importance",
181 			NULL,
182 			(message_camel_flags & CAMEL_MESSAGE_FLAGGED) != 0 ? "High" : "Normal",
183 			NULL,
184 			NULL);
185 
186 	/* more MAPI crap.  You can't just set the IsDraft property
187 	 * here you have to use the MAPI MSGFLAG_UNSENT extended
188 	 * property Further crap is that Exchange 2007 assumes when it
189 	 * sees this property that you're setting the value to 0
190 	 * ... it never checks */
191 	msgflag = MAPI_MSGFLAG_READ; /* draft or sent is always read */
192 	if ((message_camel_flags & CAMEL_MESSAGE_DRAFT) != 0)
193 		msgflag |= MAPI_MSGFLAG_UNSENT;
194 
195 	e_ews_message_add_extended_property_tag_int (msg, 0x0e07, msgflag);
196 
197 	if ((message_camel_flags & (CAMEL_MESSAGE_FORWARDED | CAMEL_MESSAGE_ANSWERED)) != 0) {
198 		gint icon;
199 
200 		icon = (message_camel_flags & CAMEL_MESSAGE_ANSWERED) != 0 ? 0x105 : 0x106;
201 
202 		e_ews_message_add_extended_property_tag_int (msg, 0x1080, icon);
203 	}
204 
205 	if (create_data->info) {
206 		const gchar *followup, *completed, *dueby;
207 		time_t completed_tt = (time_t) 0 , dueby_tt = (time_t) 0;
208 
209 		/* follow-up flags */
210 		followup = camel_message_info_get_user_tag (create_data->info, "follow-up");
211 		completed = camel_message_info_get_user_tag (create_data->info, "completed-on");
212 		dueby = camel_message_info_get_user_tag (create_data->info, "due-by");
213 
214 		if (followup && !*followup)
215 			followup = NULL;
216 
217 		if (completed && *completed)
218 			completed_tt = camel_header_decode_date (completed, NULL);
219 
220 		if (dueby && *dueby)
221 			dueby_tt = camel_header_decode_date (dueby, NULL);
222 
223 		/* PidTagFlagStatus */
224 		e_ews_message_add_extended_property_tag_int (msg, 0x1090,
225 			followup ? (completed_tt != (time_t) 0 ? 0x01 /* followupComplete */: 0x02 /* followupFlagged */) : 0x0);
226 
227 		if (followup) {
228 			/* PidLidFlagRequest */
229 			e_ews_message_add_extended_property_distinguished_tag_string (msg, "Common", 0x8530, followup);
230 
231 			/* PidTagToDoItemFlags */
232 			e_ews_message_add_extended_property_tag_int (msg, 0x0e2b, 1);
233 		}
234 
235 		if (followup && completed_tt != (time_t) 0) {
236 			/* minute precision */
237 			completed_tt = completed_tt - (completed_tt % 60);
238 
239 			/* PidTagFlagCompleteTime */
240 			e_ews_message_add_extended_property_tag_time (msg, 0x1091, completed_tt);
241 
242 			/* PidLidTaskDateCompleted */
243 			e_ews_message_add_extended_property_distinguished_tag_time (msg, "Task", 0x810f, completed_tt);
244 
245 			/* PidLidTaskStatus */
246 			e_ews_message_add_extended_property_distinguished_tag_int (msg, "Task", 0x8101, 2);
247 
248 			/* PidLidPercentComplete */
249 			e_ews_message_add_extended_property_distinguished_tag_double (msg, "Task", 0x8102, 1.0);
250 
251 			/* PidLidTaskComplete */
252 			e_ews_message_add_extended_property_distinguished_tag_boolean (msg, "Task", 0x811c, TRUE);
253 		}
254 
255 		if (followup && dueby_tt != (time_t) 0 && completed_tt == (time_t) 0) {
256 			/* PidLidTaskStatus */
257 			e_ews_message_add_extended_property_distinguished_tag_int (msg, "Task", 0x8101, 0);
258 
259 			/* PidLidPercentComplete */
260 			e_ews_message_add_extended_property_distinguished_tag_double (msg, "Task", 0x8102, 0.0);
261 
262 			/* PidLidTaskDueDate */
263 			e_ews_message_add_extended_property_distinguished_tag_time (msg, "Task", 0x8105, dueby_tt);
264 
265 			/* PidLidTaskComplete */
266 			e_ews_message_add_extended_property_distinguished_tag_boolean (msg, "Task", 0x811c, FALSE);
267 		}
268 	}
269 
270 	if (create_data->cnc && create_data->is_send) {
271 		CamelEwsSettings *settings;
272 
273 		settings = e_ews_connection_ref_settings (create_data->cnc);
274 		if (settings) {
275 			e_soap_message_start_element (msg, "Sender", NULL, NULL);
276 
277 			e_soap_message_start_element (msg, "Mailbox", NULL, NULL);
278 			e_ews_message_write_string_parameter_with_attribute (msg, "EmailAddress", NULL, camel_ews_settings_get_email (settings), NULL, NULL);
279 			e_soap_message_end_element (msg); /* Mailbox */
280 
281 			e_soap_message_end_element (msg); /* Sender */
282 		}
283 		g_clear_object (&settings);
284 	}
285 
286 	if (create_data->recipients) {
287 		GHashTable *recip_to, *recip_cc, *recip_bcc;
288 		gboolean is_resend;
289 
290 		is_resend = is_any_address_filled (camel_mime_message_get_recipients (create_data->message, CAMEL_RECIPIENT_TYPE_RESENT_TO)) ||
291 			    is_any_address_filled (camel_mime_message_get_recipients (create_data->message, CAMEL_RECIPIENT_TYPE_RESENT_CC)) ||
292 			    is_any_address_filled (camel_mime_message_get_recipients (create_data->message, CAMEL_RECIPIENT_TYPE_RESENT_BCC));
293 
294 		recip_to = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
295 		recip_cc = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
296 		recip_bcc = g_hash_table_new (camel_strcase_hash, camel_strcase_equal);
297 
298 		filter_recipients (create_data->message, create_data->recipients, recip_to, recip_cc, recip_bcc);
299 
300 		write_recipients (msg, "ToRecipients", recip_to, is_resend);
301 		write_recipients (msg, "CcRecipients", recip_cc, is_resend);
302 		write_recipients (msg, "BccRecipients", recip_bcc, is_resend);
303 
304 		g_hash_table_destroy (recip_to);
305 		g_hash_table_destroy (recip_cc);
306 		g_hash_table_destroy (recip_bcc);
307 	}
308 
309 	if (create_data->is_send && create_data->from && CAMEL_IS_INTERNET_ADDRESS (create_data->from)) {
310 		const gchar *from_name = NULL, *from_email = NULL;
311 
312 		if (camel_internet_address_get (CAMEL_INTERNET_ADDRESS (create_data->from), 0, &from_name, &from_email) && from_email) {
313 			e_soap_message_start_element (msg, "From", NULL, NULL);
314 
315 			e_soap_message_start_element (msg, "Mailbox", NULL, NULL);
316 			if (from_name && *from_name)
317 				e_ews_message_write_string_parameter_with_attribute (msg, "Name", NULL, from_name, NULL, NULL);
318 			e_ews_message_write_string_parameter_with_attribute (msg, "EmailAddress", NULL, from_email, NULL, NULL);
319 			e_soap_message_end_element (msg); /* Mailbox */
320 
321 			e_soap_message_end_element (msg); /* From */
322 		}
323 	}
324 
325 	e_ews_message_write_string_parameter_with_attribute (
326 			msg,
327 			"IsRead",
328 			NULL,
329 			(message_camel_flags & CAMEL_MESSAGE_SEEN) != 0 ? "true" : "false",
330 			NULL,
331 			NULL);
332 
333 	e_soap_message_end_element (msg); /* Message */
334 
335 	g_free (create_data);
336 
337 	return TRUE;
338 }
339 
340 gboolean
camel_ews_utils_create_mime_message(EEwsConnection * cnc,const gchar * disposition,const EwsFolderId * fid,CamelMimeMessage * message,CamelMessageInfo * info,CamelAddress * from,CamelAddress * recipients,gchar ** itemid,gchar ** changekey,GCancellable * cancellable,GError ** error)341 camel_ews_utils_create_mime_message (EEwsConnection *cnc,
342                                      const gchar *disposition,
343                                      const EwsFolderId *fid,
344                                      CamelMimeMessage *message,
345                                      CamelMessageInfo *info,
346                                      CamelAddress *from,
347 				     CamelAddress *recipients,
348                                      gchar **itemid,
349                                      gchar **changekey,
350                                      GCancellable *cancellable,
351                                      GError **error)
352 {
353 	struct _create_mime_msg_data *create_data;
354 	GSList *ids;
355 	EEwsItem *item;
356 	const EwsId *ewsid;
357 	gboolean res;
358 
359 	create_data = g_new0 (struct _create_mime_msg_data, 1);
360 
361 	create_data->cnc = cnc;
362 	create_data->message = message;
363 	create_data->info = info;
364 	create_data->from = from;
365 	create_data->recipients = recipients;
366 	create_data->is_send = g_strcmp0 (disposition, "SendOnly") == 0 || g_strcmp0 (disposition, "SendAndSaveCopy") == 0;
367 
368 	if (create_data->is_send && !create_data->from) {
369 		CamelInternetAddress *address = camel_mime_message_get_from (message);
370 
371 		if (address)
372 			create_data->from = CAMEL_ADDRESS (address);
373 	}
374 
375 	res = e_ews_connection_create_items_sync (
376 		cnc, EWS_PRIORITY_MEDIUM,
377 		disposition, NULL, fid,
378 		create_mime_message_cb, create_data,
379 		&ids, cancellable, error);
380 
381 	if (!res || (!itemid && !changekey))
382 		return res;
383 
384 	item = (EEwsItem *) ids->data;
385 	if (!item || !(ewsid = e_ews_item_get_id (item))) {
386 		g_set_error (
387 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
388 			_("CreateItem call failed to return ID for new message"));
389 		return FALSE;
390 	}
391 
392 	if (itemid)
393 		*itemid = g_strdup (ewsid->id);
394 	if (changekey)
395 		*changekey = g_strdup (ewsid->change_key);
396 
397 	g_object_unref (item);
398 	g_slist_free (ids);
399 	return TRUE;
400 }
401