mapi32: Use 0 instead of (wrongly) casting NULL to an integer.
[wine] / dlls / 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 #include "config.h"
23 #include "wine/port.h"
24
25 #include <stdio.h>
26 #include <stdarg.h>
27
28 #define COBJMACROS
29
30 #include "windef.h"
31 #include "winbase.h"
32 #include "winerror.h"
33 #include "objbase.h"
34 #include "objidl.h"
35 #include "mapi.h"
36 #include "mapix.h"
37 #include "mapiutil.h"
38 #include "mapidefs.h"
39 #include "winreg.h"
40 #include "shellapi.h"
41 #include "shlwapi.h"
42 #include "wine/debug.h"
43 #include "util.h"
44
45 WINE_DEFAULT_DEBUG_CHANNEL(mapi);
46
47 #define READ_BUF_SIZE    4096
48
49 /*
50    Internal function to send a message via Extended MAPI. Wrapper around the Simple
51    MAPI function MAPISendMail.
52 */
53 static ULONG sendmail_extended_mapi(LHANDLE mapi_session, ULONG_PTR uiparam, lpMapiMessage message,
54     FLAGS flags, ULONG reserved)
55 {
56     ULONG tags[] = {1, PR_IPM_DRAFTS_ENTRYID};
57     ULONG retval = MAPI_E_FAILURE;
58     IMAPISession *session = NULL;
59     IMAPITable* msg_table;
60     LPSRowSet rows = NULL;
61     IMsgStore* msg_store;
62     IMAPIFolder* folder;
63     LPENTRYID entry_id;
64     LPSPropValue props;
65     ULONG entry_len;
66     DWORD obj_type;
67     IMessage* msg;
68     ULONG values;
69     HRESULT ret;
70
71     TRACE("Using Extended MAPI wrapper for MAPISendMail\n");
72
73     /* Attempt to log on via Extended MAPI */
74
75     ret = MAPILogonEx(0, NULL, NULL, MAPI_EXTENDED | MAPI_USE_DEFAULT | MAPI_NEW_SESSION, &session);
76     TRACE("MAPILogonEx: %x\n", ret);
77
78     if (ret != S_OK)
79     {
80         retval = MAPI_E_LOGIN_FAILURE;
81         goto cleanup;
82     }
83
84     /* Open the default message store */
85
86     if (IMAPISession_GetMsgStoresTable(session, 0, &msg_table) == S_OK)
87     {
88         /* We want the default store */
89         SizedSPropTagArray(2, columns) = {2, {PR_ENTRYID, PR_DEFAULT_STORE}};
90
91         /* Set the columns we want */
92         if (IMAPITable_SetColumns(msg_table, (LPSPropTagArray) &columns, 0) == S_OK)
93         {
94             while (1)
95             {
96                 if (IMAPITable_QueryRows(msg_table, 1, 0, &rows) != S_OK)
97                 {
98                     MAPIFreeBuffer(rows);
99                     rows = NULL;
100                 }
101                 else if (rows->cRows != 1)
102                 {
103                     FreeProws(rows);
104                     rows = NULL;
105                 }
106                 else
107                 {
108                     /* If it's not the default store, try the next row */
109                     if (!rows->aRow[0].lpProps[1].Value.b)
110                     {
111                         FreeProws(rows);
112                         continue;
113                     }
114                 }
115
116                 break;
117             }
118         }
119
120         IMAPITable_Release(msg_table);
121     }
122
123     /* Did we manage to get the right store? */
124     if (!rows)
125         goto logoff;
126
127     /* Open the message store */
128     IMAPISession_OpenMsgStore(session, 0, rows->aRow[0].lpProps[0].Value.bin.cb,
129                               (ENTRYID *) rows->aRow[0].lpProps[0].Value.bin.lpb, NULL,
130                               MDB_NO_DIALOG | MAPI_BEST_ACCESS, &msg_store);
131
132     /* We don't need this any more */
133     FreeProws(rows);
134
135     /* First open the inbox, from which the drafts folder can be opened */
136     if (IMsgStore_GetReceiveFolder(msg_store, NULL, 0, &entry_len, &entry_id, NULL) == S_OK)
137     {
138         IMsgStore_OpenEntry(msg_store, entry_len, entry_id, NULL, 0, &obj_type, (LPUNKNOWN*) &folder);
139         MAPIFreeBuffer(entry_id);
140     }
141
142     /* Open the drafts folder, or failing that, try asking the message store for the outbox */
143     if ((folder == NULL) || ((ret = IMAPIFolder_GetProps(folder, (LPSPropTagArray) tags, 0, &values, &props)) != S_OK))
144     {
145         TRACE("Unable to open Drafts folder; opening Outbox instead\n");
146         tags[1] = PR_IPM_OUTBOX_ENTRYID;
147         ret = IMsgStore_GetProps(msg_store, (LPSPropTagArray) tags, 0, &values, &props);
148     }
149
150     if (ret != S_OK)
151         goto logoff;
152
153     IMsgStore_OpenEntry(msg_store, props[0].Value.bin.cb, (LPENTRYID) props[0].Value.bin.lpb,
154         NULL, MAPI_MODIFY, &obj_type, (LPUNKNOWN *) &folder);
155
156     /* Create a new message */
157     if (IMAPIFolder_CreateMessage(folder, NULL, 0, &msg) == S_OK)
158     {
159         ULONG token;
160         SPropValue p;
161
162         /* Define message properties */
163         p.ulPropTag = PR_MESSAGE_FLAGS;
164         p.Value.l = MSGFLAG_FROMME | MSGFLAG_UNSENT;
165
166         IMessage_SetProps(msg, 1, &p, NULL);
167
168         p.ulPropTag = PR_SENTMAIL_ENTRYID;
169         p.Value.bin.cb = props[0].Value.bin.cb;
170         p.Value.bin.lpb = props[0].Value.bin.lpb;
171         IMessage_SetProps(msg, 1,&p, NULL);
172
173         /* Set message subject */
174         if (message->lpszSubject)
175         {
176             p.ulPropTag = PR_SUBJECT_A;
177             p.Value.lpszA = message->lpszSubject;
178             IMessage_SetProps(msg, 1, &p, NULL);
179         }
180
181         /* Set message body */
182         if (message->lpszNoteText)
183         {
184             LPSTREAM stream = NULL;
185
186             if (IMessage_OpenProperty(msg, PR_BODY_A, &IID_IStream, 0,
187                 MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN*) &stream) == S_OK)
188             {
189                 IStream_Write(stream, message->lpszNoteText, strlen(message->lpszNoteText)+1, NULL);
190                 IStream_Release(stream);
191             }
192         }
193
194         /* Add message attachments */
195         if (message->nFileCount > 0)
196         {
197             ULONG num_attach = 0;
198             int i, j;
199
200             for (i = 0; i < message->nFileCount; i++)
201             {
202                 IAttach* attachment = NULL;
203                 SPropValue prop[4];
204                 LPCSTR filename;
205                 HANDLE file;
206
207                 if (!message->lpFiles[i].lpszPathName)
208                     continue;
209
210                 /* Open the attachment for reading */
211                 file = CreateFileA(message->lpFiles[i].lpszPathName, GENERIC_READ, FILE_SHARE_READ,
212                     NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
213
214                 if (file == INVALID_HANDLE_VALUE)
215                     continue;
216
217                 /* Check if a display filename has been given; if not, get one ourselves from path name */
218                 filename = message->lpFiles[i].lpszFileName;
219
220                 if (!filename)
221                 {
222                     filename = message->lpFiles[i].lpszPathName;
223
224                     for (j = strlen(message->lpFiles[i].lpszPathName)-1; j >= 0; j--)
225                     {
226                         if (message->lpFiles[i].lpszPathName[i] == '\\' ||
227                             message->lpFiles[i].lpszPathName[i] == '/')
228                         {
229                             filename = &message->lpFiles[i].lpszPathName[i+1];
230                             break;
231                         }
232                     }
233                 }
234
235                 TRACE("Attachment %d path: '%s'; filename: '%s'\n", i, debugstr_a(message->lpFiles[i].lpszPathName),
236                     debugstr_a(filename));
237
238                 /* Create the attachment */
239                 if (IMessage_CreateAttach(msg, NULL, 0, &num_attach, &attachment) != S_OK)
240                 {
241                     TRACE("Unable to create attachment\n");
242                     CloseHandle(file);
243                     continue;
244                 }
245
246                 /* Set the attachment properties */
247                 ZeroMemory(prop, sizeof(prop));
248
249                 prop[0].ulPropTag = PR_ATTACH_METHOD;
250                 prop[0].Value.ul = ATTACH_BY_VALUE;
251                 prop[1].ulPropTag = PR_ATTACH_LONG_FILENAME_A;
252                 prop[1].Value.lpszA = (LPSTR) filename;
253                 prop[2].ulPropTag = PR_ATTACH_FILENAME_A;
254                 prop[2].Value.lpszA = (LPSTR) filename;
255                 prop[3].ulPropTag = PR_RENDERING_POSITION;
256                 prop[3].Value.l = -1;
257
258                 if (IAttach_SetProps(attachment, 4, prop, NULL) == S_OK)
259                 {
260                     LPSTREAM stream = NULL;
261
262                     if (IAttach_OpenProperty(attachment, PR_ATTACH_DATA_BIN, &IID_IStream, 0,
263                         MAPI_MODIFY | MAPI_CREATE, (LPUNKNOWN*) &stream) == S_OK)
264                     {
265                         BYTE data[READ_BUF_SIZE];
266                         DWORD size = 0, read, written;
267
268                         while (ReadFile(file, data, READ_BUF_SIZE, &read, NULL) && (read != 0))
269                         {
270                             IStream_Write(stream, data, read, &written);
271                             size += read;
272                         }
273
274                         TRACE("%d bytes read, %d bytes written of attachment\n", read, written);
275
276                         IStream_Commit(stream, STGC_DEFAULT);
277                         IStream_Release(stream);
278
279                         prop[0].ulPropTag = PR_ATTACH_SIZE;
280                         prop[0].Value.ul = size;
281                         IAttach_SetProps(attachment, 1, prop, NULL);
282
283                         IAttach_SaveChanges(attachment, KEEP_OPEN_READONLY);
284                         num_attach++;
285                     }
286                 }
287
288                 CloseHandle(file);
289                 IAttach_Release(attachment);
290             }
291         }
292
293         IMessage_SaveChanges(msg, KEEP_OPEN_READWRITE);
294
295         /* Prepare the message form */
296
297         if (IMAPISession_PrepareForm(session, NULL, msg, &token) == S_OK)
298         {
299             ULONG access = 0, status = 0, flags = 0, pc = 0;
300             ULONG pT[2] = {1, PR_MSG_STATUS};
301
302             /* Retrieve message status, flags, access rights and class */
303
304             if (IMessage_GetProps(msg, (LPSPropTagArray) pT, 0, &pc, &props) == S_OK)
305             {
306                 status = props->Value.ul;
307                 MAPIFreeBuffer(props);
308             }
309
310             pT[1] = PR_MESSAGE_FLAGS;
311
312             if (IMessage_GetProps(msg, (LPSPropTagArray) pT, 0, &pc, &props) == S_OK)
313             {
314                 flags = props->Value.ul;
315                 MAPIFreeBuffer(props);
316             }
317
318             pT[1] = PR_ACCESS;
319
320             if (IMessage_GetProps(msg, (LPSPropTagArray) pT, 0, &pc, &props) == S_OK)
321             {
322                 access = props->Value.ul;
323                 MAPIFreeBuffer(props);
324             }
325
326             pT[1] = PR_MESSAGE_CLASS_A;
327
328             if (IMessage_GetProps(msg, (LPSPropTagArray) pT, 0, &pc, &props) == S_OK)
329             {
330                 /* Show the message form (edit window) */
331
332                 ret = IMAPISession_ShowForm(session, 0, msg_store, folder, NULL,
333                                             token, NULL, 0, status, flags, access,
334                                             props->Value.lpszA);
335
336                 switch (ret)
337                 {
338                     case S_OK:
339                         retval = SUCCESS_SUCCESS;
340                         break;
341
342                     case MAPI_E_USER_CANCEL:
343                         retval = MAPI_E_USER_ABORT;
344                         break;
345
346                     default:
347                         TRACE("ShowForm failure: %x\n", ret);
348                         break;
349                 }
350             }
351         }
352
353         IMessage_Release(msg);
354     }
355
356     /* Free up the resources we've used */
357     IMAPIFolder_Release(folder);
358     IMsgStore_Release(msg_store);
359
360 logoff: ;
361     IMAPISession_Logoff(session, 0, 0, 0);
362     IMAPISession_Release(session);
363
364 cleanup: ;
365     MAPIUninitialize();
366     return retval;
367 }
368
369 /**************************************************************************
370  *  MAPISendMail        (MAPI32.211)
371  *
372  * Send a mail.
373  *
374  * PARAMS
375  *  session  [I] Handle to a MAPI session.
376  *  uiparam  [I] Parent window handle.
377  *  message  [I] Pointer to a MAPIMessage structure.
378  *  flags    [I] Flags.
379  *  reserved [I] Reserved, pass 0.
380  *
381  * RETURNS
382  *  Success: SUCCESS_SUCCESS
383  *  Failure: MAPI_E_FAILURE
384  *
385  * NOTES
386  *  The fallback procedure is a temporary hack.
387  */
388 ULONG WINAPI MAPISendMail( LHANDLE session, ULONG_PTR uiparam,
389     lpMapiMessage message, FLAGS flags, ULONG reserved )
390 {
391     ULONG ret = MAPI_E_FAILURE;
392     unsigned int i, to_count = 0, cc_count = 0, bcc_count = 0;
393     unsigned int to_size = 0, cc_size = 0, bcc_size = 0, subj_size, body_size;
394
395     char *to = NULL, *cc = NULL, *bcc = NULL;
396     const char *address, *subject, *body;
397     static const char format[] =
398         "mailto:\"%s\"?subject=\"%s\"&cc=\"%s\"&bcc=\"%s\"&body=\"%s\"";
399     char *mailto = NULL, *escape = NULL;
400     char empty_string[] = "";
401     HRESULT res;
402     DWORD size;
403
404     TRACE( "(0x%08x 0x%08lx %p 0x%08x 0x%08x)\n", session, uiparam,
405            message, flags, reserved );
406
407     /* Check to see if we have a Simple MAPI provider loaded */
408     if (mapiFunctions.MAPISendMail)
409         return mapiFunctions.MAPISendMail(session, uiparam, message, flags, reserved);
410
411     /* Check if we have an Extended MAPI provider - if so, use our wrapper */
412     if (MAPIInitialize(NULL) == S_OK)
413         return sendmail_extended_mapi(session, uiparam, message, flags, reserved);
414
415     /* Fall back on our own implementation */
416     if (!message) return MAPI_E_FAILURE;
417
418     for (i = 0; i < message->nRecipCount; i++)
419     {
420         if (!message->lpRecips)
421         {
422             WARN("No recipients found\n");
423             return MAPI_E_FAILURE;
424         }
425
426         address = message->lpRecips[i].lpszAddress;
427         if (address)
428         {
429             switch (message->lpRecips[i].ulRecipClass)
430             {
431             case MAPI_ORIG:
432                 TRACE( "From: %s\n", debugstr_a(address) );
433                 break;
434             case MAPI_TO:
435                 TRACE( "To: %s\n", debugstr_a(address) );
436                 to_size += lstrlenA( address ) + 1;
437                 break;
438             case MAPI_CC:
439                 TRACE( "Cc: %s\n", debugstr_a(address) );
440                 cc_size += lstrlenA( address ) + 1;
441                 break;
442             case MAPI_BCC:
443                 TRACE( "Bcc: %s\n", debugstr_a(address) );
444                 bcc_size += lstrlenA( address ) + 1;
445                 break;
446             default:
447                 TRACE( "Unknown recipient class: %d\n",
448                        message->lpRecips[i].ulRecipClass );
449             }
450         }
451         else
452             FIXME("Name resolution and entry identifiers not supported\n");
453     }
454     if (message->nFileCount) FIXME("Ignoring attachments\n");
455
456     subject = message->lpszSubject ? message->lpszSubject : "";
457     body = message->lpszNoteText ? message->lpszNoteText : "";
458
459     TRACE( "Subject: %s\n", debugstr_a(subject) );
460     TRACE( "Body: %s\n", debugstr_a(body) );
461
462     subj_size = lstrlenA( subject );
463     body_size = lstrlenA( body );
464
465     ret = MAPI_E_INSUFFICIENT_MEMORY;
466     if (to_size)
467     {
468         to = HeapAlloc( GetProcessHeap(), 0, to_size );
469         if (!to) goto exit;
470         to[0] = 0;
471     }
472     if (cc_size)
473     {
474         cc = HeapAlloc( GetProcessHeap(), 0, cc_size );
475         if (!cc) goto exit;
476         cc[0] = 0;
477     }
478     if (bcc_size)
479     {
480         bcc = HeapAlloc( GetProcessHeap(), 0, bcc_size );
481         if (!bcc) goto exit;
482         bcc[0] = 0;
483     }
484
485     if (message->lpOriginator)
486         TRACE( "From: %s\n", debugstr_a(message->lpOriginator->lpszAddress) );
487
488     for (i = 0; i < message->nRecipCount; i++)
489     {
490         address = message->lpRecips[i].lpszAddress;
491         if (address)
492         {
493             switch (message->lpRecips[i].ulRecipClass)
494             {
495             case MAPI_TO:
496                 if (to_count) lstrcatA( to, "," );
497                 lstrcatA( to, address );
498                 to_count++;
499                 break;
500             case MAPI_CC:
501                 if (cc_count) lstrcatA( cc, "," );
502                 lstrcatA( cc, address );
503                 cc_count++;
504                 break;
505             case MAPI_BCC:
506                 if (bcc_count) lstrcatA( bcc, "," );
507                 lstrcatA( bcc, address );
508                 bcc_count++;
509                 break;
510             }
511         }
512     }
513     ret = MAPI_E_FAILURE;
514     size = sizeof(format) + to_size + cc_size + bcc_size + subj_size + body_size;
515     
516     mailto = HeapAlloc( GetProcessHeap(), 0, size );
517     if (!mailto) goto exit;
518
519     sprintf( mailto, format, to ? to : "", subject, cc ? cc : "", bcc ? bcc : "", body );
520
521     size = 1;
522     res = UrlEscapeA( mailto, empty_string, &size, URL_ESCAPE_SPACES_ONLY );
523     if (res != E_POINTER) goto exit;
524
525     escape = HeapAlloc( GetProcessHeap(), 0, size );
526     if (!escape) goto exit;
527
528     res = UrlEscapeA( mailto, escape, &size, URL_ESCAPE_SPACES_ONLY );
529     if (res != S_OK) goto exit;
530
531     if ((UINT_PTR)ShellExecuteA( NULL, "open", escape, NULL, NULL, 0 ) > 32)
532         ret = SUCCESS_SUCCESS;
533
534 exit:
535     HeapFree( GetProcessHeap(), 0, to );
536     HeapFree( GetProcessHeap(), 0, cc );
537     HeapFree( GetProcessHeap(), 0, bcc );
538     HeapFree( GetProcessHeap(), 0, mailto );
539     HeapFree( GetProcessHeap(), 0, escape );
540
541     return ret;
542 }
543
544 ULONG WINAPI MAPISendDocuments(ULONG_PTR uiparam, LPSTR delim, LPSTR paths,
545     LPSTR filenames, ULONG reserved)
546 {
547     if (mapiFunctions.MAPISendDocuments)
548         return mapiFunctions.MAPISendDocuments(uiparam, delim, paths, filenames, reserved);
549
550     return MAPI_E_NOT_SUPPORTED;
551 }