Create a branch for Aleksandar Andrejevic for his work on NTVDM. See http://jira...
[reactos.git] / dll / win32 / mapi32 / sendmail.c
1 /*
2 * MAPISendMail implementation
3 *
4 * Copyright 2005 Hans Leidekker
5 * Copyright 2009 Owen Rudge for CodeWeavers
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 */
21
22 #define WIN32_NO_STATUS
23 #define _INC_WINDOWS
24 #define COM_NO_WINDOWS_H
25
26 #include <config.h>
27 //#include "wine/port.h"
28
29 //#include <stdio.h>
30 #include <stdarg.h>
31
32 #define COBJMACROS
33
34 #include <windef.h>
35 #include <winbase.h>
36 //#include "winerror.h"
37 #include <winuser.h>
38 //#include "objbase.h"
39 //#include "objidl.h"
40 //#include "mapi.h"
41 //#include "mapix.h"
42 #include <mapiutil.h>
43 //#include "mapidefs.h"
44 //#include "winreg.h"
45 //#include "shellapi.h"
46 //#include "shlwapi.h"
47 #include <wine/debug.h>
48 #include "util.h"
49 #include "res.h"
50
51 WINE_DEFAULT_DEBUG_CHANNEL(mapi);
52
53 #define READ_BUF_SIZE 4096
54
55 /*
56 Internal function to send a message via Extended MAPI. Wrapper around the Simple
57 MAPI function MAPISendMail.
58 */
59 static ULONG sendmail_extended_mapi(LHANDLE mapi_session, ULONG_PTR uiparam, lpMapiMessage message,
60 FLAGS flags, ULONG reserved)
61 {
62 ULONG tags[] = {1, PR_IPM_DRAFTS_ENTRYID};
63 ULONG retval = MAPI_E_FAILURE;
64 IMAPISession *session = NULL;
65 IMAPITable* msg_table;
66 LPSRowSet rows = NULL;
67 IMsgStore* msg_store;
68 IMAPIFolder* folder = NULL, *draft_folder = NULL;
69 LPENTRYID entry_id;
70 LPSPropValue props;
71 ULONG entry_len;
72 DWORD obj_type;
73 IMessage* msg;
74 ULONG values;
75 HRESULT ret;
76
77 TRACE("Using Extended MAPI wrapper for MAPISendMail\n");
78
79 /* Attempt to log on via Extended MAPI */
80
81 ret = MAPILogonEx(0, NULL, NULL, MAPI_EXTENDED | MAPI_USE_DEFAULT | MAPI_NEW_SESSION, &session);
82 TRACE("MAPILogonEx: %x\n", ret);
83
84 if (ret != S_OK)
85 {
86 retval = MAPI_E_LOGIN_FAILURE;
87 goto cleanup;
88 }
89
90 /* Open the default message store */
91
92 if (IMAPISession_GetMsgStoresTable(session, 0, &msg_table) == S_OK)
93 {
94 /* We want the default store */
95 SizedSPropTagArray(2, columns) = {2, {PR_ENTRYID, PR_DEFAULT_STORE}};
96
97 /* Set the columns we want */
98 if (IMAPITable_SetColumns(msg_table, (LPSPropTagArray) &columns, 0) == S_OK)
99 {
100 while (1)
101 {
102 if (IMAPITable_QueryRows(msg_table, 1, 0, &rows) != S_OK)
103 {
104 MAPIFreeBuffer(rows);
105 rows = NULL;
106 }
107 else if (rows->cRows != 1)
108 {
109 FreeProws(rows);
110 rows = NULL;
111 }
112 else
113 {
114 /* If it's not the default store, try the next row */
115 if (!rows->aRow[0].lpProps[1].Value.b)
116 {
117 FreeProws(rows);
118 continue;
119 }
120 }
121
122 break;
123 }
124 }
125
126 IMAPITable_Release(msg_table);
127 }
128
129 /* Did we manage to get the right store? */
130 if (!rows)
131 goto logoff;
132
133 /* Open the message store */
134 IMAPISession_OpenMsgStore(session, 0, rows->aRow[0].lpProps[0].Value.bin.cb,
135 (ENTRYID *) rows->aRow[0].lpProps[0].Value.bin.lpb, NULL,
136 MDB_NO_DIALOG | MAPI_BEST_ACCESS, &msg_store);
137
138 /* We don't need this any more */
139 FreeProws(rows);
140
141 /* First open the inbox, from which the drafts folder can be opened */
142 if (IMsgStore_GetReceiveFolder(msg_store, NULL, 0, &entry_len, &entry_id, NULL) == S_OK)
143 {
144 IMsgStore_OpenEntry(msg_store, entry_len, entry_id, NULL, 0, &obj_type, (LPUNKNOWN*) &folder);
145 MAPIFreeBuffer(entry_id);
146 }
147
148 /* Open the drafts folder, or failing that, try asking the message store for the outbox */
149 if ((folder == NULL) || ((ret = IMAPIFolder_GetProps(folder, (LPSPropTagArray) tags, 0, &values, &props)) != S_OK))
150 {
151 TRACE("Unable to open Drafts folder; opening Outbox instead\n");
152 tags[1] = PR_IPM_OUTBOX_ENTRYID;
153 ret = IMsgStore_GetProps(msg_store, (LPSPropTagArray) tags, 0, &values, &props);
154 }
155
156 if (ret != S_OK)
157 goto logoff;
158
159 IMsgStore_OpenEntry(msg_store, props[0].Value.bin.cb, (LPENTRYID) props[0].Value.bin.lpb,
160 NULL, MAPI_MODIFY, &obj_type, (LPUNKNOWN *) &draft_folder);
161
162 /* Create a new message */
163 if (IMAPIFolder_CreateMessage(draft_folder, NULL, 0, &msg) == S_OK)
164 {
165 ULONG token;
166 SPropValue p;
167
168 /* Define message properties */
169 p.ulPropTag = PR_MESSAGE_FLAGS;
170 p.Value.l = MSGFLAG_FROMME | MSGFLAG_UNSENT;
171
172 IMessage_SetProps(msg, 1, &p, NULL);
173
174 p.ulPropTag = PR_SENTMAIL_ENTRYID;
175 p.Value.bin.cb = props[0].Value.bin.cb;
176 p.Value.bin.lpb = props[0].Value.bin.lpb;
177 IMessage_SetProps(msg, 1,&p, NULL);
178
179 /* Set message subject */
180 if (message->lpszSubject)
181 {
182 p.ulPropTag = PR_SUBJECT_A;
183 p.Value.lpszA = message->lpszSubject;
184 IMessage_SetProps(msg, 1, &p, NULL);
185 }
186
187 /* Set message body */
188 if (message->lpszNoteText)
189 {
190 LPSTREAM stream = NULL;
191
192 if (IMessage_OpenProperty(msg, PR_BODY_A, &IID_IStream, 0,
193 MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN*) &stream) == S_OK)
194 {
195 IStream_Write(stream, message->lpszNoteText, strlen(message->lpszNoteText)+1, NULL);
196 IStream_Release(stream);
197 }
198 }
199
200 /* Add message attachments */
201 if (message->nFileCount > 0)
202 {
203 ULONG num_attach = 0;
204 int i, j;
205
206 for (i = 0; i < message->nFileCount; i++)
207 {
208 IAttach* attachment = NULL;
209 SPropValue prop[4];
210 LPCSTR filename;
211 HANDLE file;
212
213 if (!message->lpFiles[i].lpszPathName)
214 continue;
215
216 /* Open the attachment for reading */
217 file = CreateFileA(message->lpFiles[i].lpszPathName, GENERIC_READ, FILE_SHARE_READ,
218 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
219
220 if (file == INVALID_HANDLE_VALUE)
221 continue;
222
223 /* Check if a display filename has been given; if not, get one ourselves from path name */
224 filename = message->lpFiles[i].lpszFileName;
225
226 if (!filename)
227 {
228 filename = message->lpFiles[i].lpszPathName;
229
230 for (j = strlen(message->lpFiles[i].lpszPathName)-1; j >= 0; j--)
231 {
232 if (message->lpFiles[i].lpszPathName[i] == '\\' ||
233 message->lpFiles[i].lpszPathName[i] == '/')
234 {
235 filename = &message->lpFiles[i].lpszPathName[i+1];
236 break;
237 }
238 }
239 }
240
241 TRACE("Attachment %d path: '%s'; filename: '%s'\n", i, debugstr_a(message->lpFiles[i].lpszPathName),
242 debugstr_a(filename));
243
244 /* Create the attachment */
245 if (IMessage_CreateAttach(msg, NULL, 0, &num_attach, &attachment) != S_OK)
246 {
247 TRACE("Unable to create attachment\n");
248 CloseHandle(file);
249 continue;
250 }
251
252 /* Set the attachment properties */
253 ZeroMemory(prop, sizeof(prop));
254
255 prop[0].ulPropTag = PR_ATTACH_METHOD;
256 prop[0].Value.ul = ATTACH_BY_VALUE;
257 prop[1].ulPropTag = PR_ATTACH_LONG_FILENAME_A;
258 prop[1].Value.lpszA = (LPSTR) filename;
259 prop[2].ulPropTag = PR_ATTACH_FILENAME_A;
260 prop[2].Value.lpszA = (LPSTR) filename;
261 prop[3].ulPropTag = PR_RENDERING_POSITION;
262 prop[3].Value.l = -1;
263
264 if (IAttach_SetProps(attachment, 4, prop, NULL) == S_OK)
265 {
266 LPSTREAM stream = NULL;
267
268 if (IAttach_OpenProperty(attachment, PR_ATTACH_DATA_BIN, &IID_IStream, 0,
269 MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN*) &stream) == S_OK)
270 {
271 BYTE data[READ_BUF_SIZE];
272 DWORD size = 0, read, written;
273
274 while (ReadFile(file, data, READ_BUF_SIZE, &read, NULL) && (read != 0))
275 {
276 IStream_Write(stream, data, read, &written);
277 size += read;
278 }
279
280 TRACE("%d bytes read, %d bytes written of attachment\n", read, written);
281
282 IStream_Commit(stream, STGC_DEFAULT);
283 IStream_Release(stream);
284
285 prop[0].ulPropTag = PR_ATTACH_SIZE;
286 prop[0].Value.ul = size;
287 IAttach_SetProps(attachment, 1, prop, NULL);
288
289 IAttach_SaveChanges(attachment, KEEP_OPEN_READONLY);
290 num_attach++;
291 }
292 }
293
294 CloseHandle(file);
295 IAttach_Release(attachment);
296 }
297 }
298
299 IMessage_SaveChanges(msg, KEEP_OPEN_READWRITE);
300
301 /* Prepare the message form */
302
303 if (IMAPISession_PrepareForm(session, NULL, msg, &token) == S_OK)
304 {
305 ULONG access = 0, status = 0, flags = 0, pc = 0;
306 ULONG pT[2] = {1, PR_MSG_STATUS};
307
308 /* Retrieve message status, flags, access rights and class */
309
310 if (IMessage_GetProps(msg, (LPSPropTagArray) pT, 0, &pc, &props) == S_OK)
311 {
312 status = props->Value.ul;
313 MAPIFreeBuffer(props);
314 }
315
316 pT[1] = PR_MESSAGE_FLAGS;
317
318 if (IMessage_GetProps(msg, (LPSPropTagArray) pT, 0, &pc, &props) == S_OK)
319 {
320 flags = props->Value.ul;
321 MAPIFreeBuffer(props);
322 }
323
324 pT[1] = PR_ACCESS;
325
326 if (IMessage_GetProps(msg, (LPSPropTagArray) pT, 0, &pc, &props) == S_OK)
327 {
328 access = props->Value.ul;
329 MAPIFreeBuffer(props);
330 }
331
332 pT[1] = PR_MESSAGE_CLASS_A;
333
334 if (IMessage_GetProps(msg, (LPSPropTagArray) pT, 0, &pc, &props) == S_OK)
335 {
336 /* Show the message form (edit window) */
337
338 ret = IMAPISession_ShowForm(session, 0, msg_store, draft_folder, NULL,
339 token, NULL, 0, status, flags, access,
340 props->Value.lpszA);
341
342 switch (ret)
343 {
344 case S_OK:
345 retval = SUCCESS_SUCCESS;
346 break;
347
348 case MAPI_E_USER_CANCEL:
349 retval = MAPI_E_USER_ABORT;
350 break;
351
352 default:
353 TRACE("ShowForm failure: %x\n", ret);
354 break;
355 }
356 }
357 }
358
359 IMessage_Release(msg);
360 }
361
362 /* Free up the resources we've used */
363 IMAPIFolder_Release(draft_folder);
364 if (folder) IMAPIFolder_Release(folder);
365 IMsgStore_Release(msg_store);
366
367 logoff: ;
368 IMAPISession_Logoff(session, 0, 0, 0);
369 IMAPISession_Release(session);
370
371 cleanup: ;
372 MAPIUninitialize();
373 return retval;
374 }
375
376 /**************************************************************************
377 * MAPISendMail (MAPI32.211)
378 *
379 * Send a mail.
380 *
381 * PARAMS
382 * session [I] Handle to a MAPI session.
383 * uiparam [I] Parent window handle.
384 * message [I] Pointer to a MAPIMessage structure.
385 * flags [I] Flags.
386 * reserved [I] Reserved, pass 0.
387 *
388 * RETURNS
389 * Success: SUCCESS_SUCCESS
390 * Failure: MAPI_E_FAILURE
391 *
392 */
393 ULONG WINAPI MAPISendMail( LHANDLE session, ULONG_PTR uiparam,
394 lpMapiMessage message, FLAGS flags, ULONG reserved )
395 {
396 WCHAR msg_title[READ_BUF_SIZE], error_msg[READ_BUF_SIZE];
397
398 /* Check to see if we have a Simple MAPI provider loaded */
399 if (mapiFunctions.MAPISendMail)
400 return mapiFunctions.MAPISendMail(session, uiparam, message, flags, reserved);
401
402 /* Check if we have an Extended MAPI provider - if so, use our wrapper */
403 if (MAPIInitialize(NULL) == S_OK)
404 return sendmail_extended_mapi(session, uiparam, message, flags, reserved);
405
406 /* Display an error message since we apparently have no mail clients */
407 LoadStringW(hInstMAPI32, IDS_NO_MAPI_CLIENT, error_msg, sizeof(error_msg) / sizeof(WCHAR));
408 LoadStringW(hInstMAPI32, IDS_SEND_MAIL, msg_title, sizeof(msg_title) / sizeof(WCHAR));
409
410 MessageBoxW((HWND) uiparam, error_msg, msg_title, MB_ICONEXCLAMATION);
411
412 return MAPI_E_NOT_SUPPORTED;
413 }
414
415 ULONG WINAPI MAPISendDocuments(ULONG_PTR uiparam, LPSTR delim, LPSTR paths,
416 LPSTR filenames, ULONG reserved)
417 {
418 if (mapiFunctions.MAPISendDocuments)
419 return mapiFunctions.MAPISendDocuments(uiparam, delim, paths, filenames, reserved);
420
421 return MAPI_E_NOT_SUPPORTED;
422 }