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