mshtml: Use special handling only for main documents in AsyncOpen.
[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 = NULL, *draft_folder = NULL;
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 *) &draft_folder);
155
156     /* Create a new message */
157     if (IMAPIFolder_CreateMessage(draft_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, draft_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(draft_folder);
358     if (folder) IMAPIFolder_Release(folder);
359     IMsgStore_Release(msg_store);
360
361 logoff: ;
362     IMAPISession_Logoff(session, 0, 0, 0);
363     IMAPISession_Release(session);
364
365 cleanup: ;
366     MAPIUninitialize();
367     return retval;
368 }
369
370 /**************************************************************************
371  *  MAPISendMail        (MAPI32.211)
372  *
373  * Send a mail.
374  *
375  * PARAMS
376  *  session  [I] Handle to a MAPI session.
377  *  uiparam  [I] Parent window handle.
378  *  message  [I] Pointer to a MAPIMessage structure.
379  *  flags    [I] Flags.
380  *  reserved [I] Reserved, pass 0.
381  *
382  * RETURNS
383  *  Success: SUCCESS_SUCCESS
384  *  Failure: MAPI_E_FAILURE
385  *
386  * NOTES
387  *  The fallback procedure is a temporary hack.
388  */
389 ULONG WINAPI MAPISendMail( LHANDLE session, ULONG_PTR uiparam,
390     lpMapiMessage message, FLAGS flags, ULONG reserved )
391 {
392     ULONG ret = MAPI_E_FAILURE;
393     unsigned int i, to_count = 0, cc_count = 0, bcc_count = 0;
394     unsigned int to_size = 0, cc_size = 0, bcc_size = 0, subj_size, body_size;
395
396     char *to = NULL, *cc = NULL, *bcc = NULL;
397     const char *address, *subject, *body;
398     static const char format[] =
399         "mailto:\"%s\"?subject=\"%s\"&cc=\"%s\"&bcc=\"%s\"&body=\"%s\"";
400     char *mailto = NULL, *escape = NULL;
401     char empty_string[] = "";
402     HRESULT res;
403     DWORD size;
404
405     TRACE( "(0x%08x 0x%08lx %p 0x%08x 0x%08x)\n", session, uiparam,
406            message, flags, reserved );
407
408     /* Check to see if we have a Simple MAPI provider loaded */
409     if (mapiFunctions.MAPISendMail)
410         return mapiFunctions.MAPISendMail(session, uiparam, message, flags, reserved);
411
412     /* Check if we have an Extended MAPI provider - if so, use our wrapper */
413     if (MAPIInitialize(NULL) == S_OK)
414         return sendmail_extended_mapi(session, uiparam, message, flags, reserved);
415
416     /* Fall back on our own implementation */
417     if (!message) return MAPI_E_FAILURE;
418
419     for (i = 0; i < message->nRecipCount; i++)
420     {
421         if (!message->lpRecips)
422         {
423             WARN("No recipients found\n");
424             return MAPI_E_FAILURE;
425         }
426
427         address = message->lpRecips[i].lpszAddress;
428         if (address)
429         {
430             switch (message->lpRecips[i].ulRecipClass)
431             {
432             case MAPI_ORIG:
433                 TRACE( "From: %s\n", debugstr_a(address) );
434                 break;
435             case MAPI_TO:
436                 TRACE( "To: %s\n", debugstr_a(address) );
437                 to_size += lstrlenA( address ) + 1;
438                 break;
439             case MAPI_CC:
440                 TRACE( "Cc: %s\n", debugstr_a(address) );
441                 cc_size += lstrlenA( address ) + 1;
442                 break;
443             case MAPI_BCC:
444                 TRACE( "Bcc: %s\n", debugstr_a(address) );
445                 bcc_size += lstrlenA( address ) + 1;
446                 break;
447             default:
448                 TRACE( "Unknown recipient class: %d\n",
449                        message->lpRecips[i].ulRecipClass );
450             }
451         }
452         else
453             FIXME("Name resolution and entry identifiers not supported\n");
454     }
455     if (message->nFileCount) FIXME("Ignoring attachments\n");
456
457     subject = message->lpszSubject ? message->lpszSubject : "";
458     body = message->lpszNoteText ? message->lpszNoteText : "";
459
460     TRACE( "Subject: %s\n", debugstr_a(subject) );
461     TRACE( "Body: %s\n", debugstr_a(body) );
462
463     subj_size = lstrlenA( subject );
464     body_size = lstrlenA( body );
465
466     ret = MAPI_E_INSUFFICIENT_MEMORY;
467     if (to_size)
468     {
469         to = HeapAlloc( GetProcessHeap(), 0, to_size );
470         if (!to) goto exit;
471         to[0] = 0;
472     }
473     if (cc_size)
474     {
475         cc = HeapAlloc( GetProcessHeap(), 0, cc_size );
476         if (!cc) goto exit;
477         cc[0] = 0;
478     }
479     if (bcc_size)
480     {
481         bcc = HeapAlloc( GetProcessHeap(), 0, bcc_size );
482         if (!bcc) goto exit;
483         bcc[0] = 0;
484     }
485
486     if (message->lpOriginator)
487         TRACE( "From: %s\n", debugstr_a(message->lpOriginator->lpszAddress) );
488
489     for (i = 0; i < message->nRecipCount; i++)
490     {
491         address = message->lpRecips[i].lpszAddress;
492         if (address)
493         {
494             switch (message->lpRecips[i].ulRecipClass)
495             {
496             case MAPI_TO:
497                 if (to_count) lstrcatA( to, "," );
498                 lstrcatA( to, address );
499                 to_count++;
500                 break;
501             case MAPI_CC:
502                 if (cc_count) lstrcatA( cc, "," );
503                 lstrcatA( cc, address );
504                 cc_count++;
505                 break;
506             case MAPI_BCC:
507                 if (bcc_count) lstrcatA( bcc, "," );
508                 lstrcatA( bcc, address );
509                 bcc_count++;
510                 break;
511             }
512         }
513     }
514     ret = MAPI_E_FAILURE;
515     size = sizeof(format) + to_size + cc_size + bcc_size + subj_size + body_size;
516     
517     mailto = HeapAlloc( GetProcessHeap(), 0, size );
518     if (!mailto) goto exit;
519
520     sprintf( mailto, format, to ? to : "", subject, cc ? cc : "", bcc ? bcc : "", body );
521
522     size = 1;
523     res = UrlEscapeA( mailto, empty_string, &size, URL_ESCAPE_SPACES_ONLY );
524     if (res != E_POINTER) goto exit;
525
526     escape = HeapAlloc( GetProcessHeap(), 0, size );
527     if (!escape) goto exit;
528
529     res = UrlEscapeA( mailto, escape, &size, URL_ESCAPE_SPACES_ONLY );
530     if (res != S_OK) goto exit;
531
532     if ((UINT_PTR)ShellExecuteA( NULL, "open", escape, NULL, NULL, 0 ) > 32)
533         ret = SUCCESS_SUCCESS;
534
535 exit:
536     HeapFree( GetProcessHeap(), 0, to );
537     HeapFree( GetProcessHeap(), 0, cc );
538     HeapFree( GetProcessHeap(), 0, bcc );
539     HeapFree( GetProcessHeap(), 0, mailto );
540     HeapFree( GetProcessHeap(), 0, escape );
541
542     return ret;
543 }
544
545 ULONG WINAPI MAPISendDocuments(ULONG_PTR uiparam, LPSTR delim, LPSTR paths,
546     LPSTR filenames, ULONG reserved)
547 {
548     if (mapiFunctions.MAPISendDocuments)
549         return mapiFunctions.MAPISendDocuments(uiparam, delim, paths, filenames, reserved);
550
551     return MAPI_E_NOT_SUPPORTED;
552 }