2 * MAPISendMail implementation
4 * Copyright 2005 Hans Leidekker
5 * Copyright 2009 Owen Rudge for CodeWeavers
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.
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.
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
28 #define READ_BUF_SIZE 4096
30 #define STORE_UNICODE_OK 0x00040000
32 static LPSTR
convert_from_unicode(LPCWSTR wstr
)
40 len
= WideCharToMultiByte(CP_ACP
, 0, wstr
, -1, NULL
, 0, NULL
, NULL
);
41 str
= HeapAlloc(GetProcessHeap(), 0, len
);
42 WideCharToMultiByte(CP_ACP
, 0, wstr
, -1, str
, len
, NULL
, NULL
);
47 static LPWSTR
convert_to_unicode(LPSTR str
)
55 len
= MultiByteToWideChar(CP_ACP
, 0, str
, -1, NULL
, 0);
56 wstr
= HeapAlloc(GetProcessHeap(), 0, len
* sizeof(WCHAR
));
57 MultiByteToWideChar(CP_ACP
, 0, str
, -1, wstr
, len
);
63 Internal function to send a message via Extended MAPI. Wrapper around the Simple
64 MAPI function MAPISendMail.
66 static ULONG
sendmail_extended_mapi(LHANDLE mapi_session
, ULONG_PTR uiparam
, lpMapiMessageW message
,
69 ULONG tags
[] = {1, 0};
70 char *subjectA
= NULL
, *bodyA
= NULL
;
71 ULONG retval
= MAPI_E_FAILURE
;
72 IMAPISession
*session
= NULL
;
73 BOOL unicode_aware
= FALSE
;
74 IMAPITable
* msg_table
;
75 LPSRowSet rows
= NULL
;
77 IMAPIFolder
* folder
= NULL
, *draft_folder
= NULL
;
86 TRACE("Using Extended MAPI wrapper for MAPISendMail\n");
88 /* Attempt to log on via Extended MAPI */
90 ret
= MAPILogonEx(0, NULL
, NULL
, MAPI_EXTENDED
| MAPI_USE_DEFAULT
| MAPI_NEW_SESSION
, &session
);
91 TRACE("MAPILogonEx: %x\n", ret
);
95 retval
= MAPI_E_LOGIN_FAILURE
;
99 /* Open the default message store */
101 if (IMAPISession_GetMsgStoresTable(session
, 0, &msg_table
) == S_OK
)
103 /* We want the default store */
104 SizedSPropTagArray(2, columns
) = {2, {PR_ENTRYID
, PR_DEFAULT_STORE
}};
106 /* Set the columns we want */
107 if (IMAPITable_SetColumns(msg_table
, (LPSPropTagArray
) &columns
, 0) == S_OK
)
111 if (IMAPITable_QueryRows(msg_table
, 1, 0, &rows
) != S_OK
)
113 MAPIFreeBuffer(rows
);
116 else if (rows
->cRows
!= 1)
123 /* If it's not the default store, try the next row */
124 if (!rows
->aRow
[0].lpProps
[1].Value
.b
)
135 IMAPITable_Release(msg_table
);
138 /* Did we manage to get the right store? */
142 /* Open the message store */
143 IMAPISession_OpenMsgStore(session
, 0, rows
->aRow
[0].lpProps
[0].Value
.bin
.cb
,
144 (ENTRYID
*) rows
->aRow
[0].lpProps
[0].Value
.bin
.lpb
, NULL
,
145 MDB_NO_DIALOG
| MAPI_BEST_ACCESS
, &msg_store
);
147 /* We don't need this any more */
150 /* Check if the message store supports Unicode */
151 tags
[1] = PR_STORE_SUPPORT_MASK
;
152 ret
= IMsgStore_GetProps(msg_store
, (LPSPropTagArray
) tags
, 0, &values
, &props
);
154 if ((ret
== S_OK
) && (props
[0].Value
.l
& STORE_UNICODE_OK
))
155 unicode_aware
= TRUE
;
158 /* Don't convert to ANSI */
159 if (flags
& MAPI_FORCE_UNICODE
)
161 WARN("No Unicode-capable mail client, and MAPI_FORCE_UNICODE is specified. MAPISendMail failed.\n");
162 retval
= MAPI_E_UNICODE_NOT_SUPPORTED
;
163 IMsgStore_Release(msg_store
);
168 /* First open the inbox, from which the drafts folder can be opened */
169 if (IMsgStore_GetReceiveFolder(msg_store
, NULL
, 0, &entry_len
, &entry_id
, NULL
) == S_OK
)
171 IMsgStore_OpenEntry(msg_store
, entry_len
, entry_id
, NULL
, 0, &obj_type
, (LPUNKNOWN
*) &folder
);
172 MAPIFreeBuffer(entry_id
);
175 tags
[1] = PR_IPM_DRAFTS_ENTRYID
;
177 /* Open the drafts folder, or failing that, try asking the message store for the outbox */
178 if ((folder
== NULL
) || ((ret
= IMAPIFolder_GetProps(folder
, (LPSPropTagArray
) tags
, 0, &values
, &props
)) != S_OK
))
180 TRACE("Unable to open Drafts folder; opening Outbox instead\n");
181 tags
[1] = PR_IPM_OUTBOX_ENTRYID
;
182 ret
= IMsgStore_GetProps(msg_store
, (LPSPropTagArray
) tags
, 0, &values
, &props
);
188 IMsgStore_OpenEntry(msg_store
, props
[0].Value
.bin
.cb
, (LPENTRYID
) props
[0].Value
.bin
.lpb
,
189 NULL
, MAPI_MODIFY
, &obj_type
, (LPUNKNOWN
*) &draft_folder
);
191 /* Create a new message */
192 if (IMAPIFolder_CreateMessage(draft_folder
, NULL
, 0, &msg
) == S_OK
)
197 /* Define message properties */
198 p
.ulPropTag
= PR_MESSAGE_FLAGS
;
199 p
.Value
.l
= MSGFLAG_FROMME
| MSGFLAG_UNSENT
;
201 IMessage_SetProps(msg
, 1, &p
, NULL
);
203 p
.ulPropTag
= PR_SENTMAIL_ENTRYID
;
204 p
.Value
.bin
.cb
= props
[0].Value
.bin
.cb
;
205 p
.Value
.bin
.lpb
= props
[0].Value
.bin
.lpb
;
206 IMessage_SetProps(msg
, 1,&p
, NULL
);
208 /* Set message subject */
209 if (message
->lpszSubject
)
213 p
.ulPropTag
= PR_SUBJECT_W
;
214 p
.Value
.lpszW
= message
->lpszSubject
;
218 subjectA
= convert_from_unicode(message
->lpszSubject
);
220 p
.ulPropTag
= PR_SUBJECT_A
;
221 p
.Value
.lpszA
= subjectA
;
224 IMessage_SetProps(msg
, 1, &p
, NULL
);
227 /* Set message body */
228 if (message
->lpszNoteText
)
230 LPSTREAM stream
= NULL
;
232 if (IMessage_OpenProperty(msg
, unicode_aware
? PR_BODY_W
: PR_BODY_A
, &IID_IStream
, 0,
233 MAPI_MODIFY
| MAPI_CREATE
, (LPUNKNOWN
*) &stream
) == S_OK
)
236 IStream_Write(stream
, message
->lpszNoteText
, (lstrlenW(message
->lpszNoteText
)+1) * sizeof(WCHAR
), NULL
);
239 bodyA
= convert_from_unicode(message
->lpszNoteText
);
240 IStream_Write(stream
, bodyA
, strlen(bodyA
)+1, NULL
);
243 IStream_Release(stream
);
247 /* Add message attachments */
248 if (message
->nFileCount
> 0)
250 ULONG num_attach
= 0;
253 for (i
= 0; i
< message
->nFileCount
; i
++)
255 IAttach
* attachment
= NULL
;
256 char *filenameA
= NULL
;
261 if (!message
->lpFiles
[i
].lpszPathName
)
264 /* Open the attachment for reading */
265 file
= CreateFileW(message
->lpFiles
[i
].lpszPathName
, GENERIC_READ
, FILE_SHARE_READ
,
266 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
268 if (file
== INVALID_HANDLE_VALUE
)
271 /* Check if a display filename has been given; if not, get one ourselves from path name */
272 filename
= message
->lpFiles
[i
].lpszFileName
;
278 filename
= message
->lpFiles
[i
].lpszPathName
;
280 for (j
= lstrlenW(message
->lpFiles
[i
].lpszPathName
)-1; j
>= 0; j
--)
282 if (message
->lpFiles
[i
].lpszPathName
[i
] == '\\' ||
283 message
->lpFiles
[i
].lpszPathName
[i
] == '/')
285 filename
= &message
->lpFiles
[i
].lpszPathName
[i
+1];
291 TRACE("Attachment %u path: '%s'; filename: '%s'\n", i
, debugstr_w(message
->lpFiles
[i
].lpszPathName
),
292 debugstr_w(filename
));
294 /* Create the attachment */
295 if (IMessage_CreateAttach(msg
, NULL
, 0, &num_attach
, &attachment
) != S_OK
)
297 TRACE("Unable to create attachment\n");
302 /* Set the attachment properties */
303 ZeroMemory(prop
, sizeof(prop
));
305 prop
[0].ulPropTag
= PR_ATTACH_METHOD
;
306 prop
[0].Value
.ul
= ATTACH_BY_VALUE
;
310 prop
[1].ulPropTag
= PR_ATTACH_LONG_FILENAME_W
;
311 prop
[1].Value
.lpszW
= (LPWSTR
) filename
;
312 prop
[2].ulPropTag
= PR_ATTACH_FILENAME_W
;
313 prop
[2].Value
.lpszW
= (LPWSTR
) filename
;
317 filenameA
= convert_from_unicode(filename
);
319 prop
[1].ulPropTag
= PR_ATTACH_LONG_FILENAME_A
;
320 prop
[1].Value
.lpszA
= (LPSTR
) filenameA
;
321 prop
[2].ulPropTag
= PR_ATTACH_FILENAME_A
;
322 prop
[2].Value
.lpszA
= (LPSTR
) filenameA
;
326 prop
[3].ulPropTag
= PR_RENDERING_POSITION
;
327 prop
[3].Value
.l
= -1;
329 if (IAttach_SetProps(attachment
, 4, prop
, NULL
) == S_OK
)
331 LPSTREAM stream
= NULL
;
333 if (IAttach_OpenProperty(attachment
, PR_ATTACH_DATA_BIN
, &IID_IStream
, 0,
334 MAPI_MODIFY
| MAPI_CREATE
, (LPUNKNOWN
*) &stream
) == S_OK
)
336 BYTE data
[READ_BUF_SIZE
];
337 DWORD size
= 0, read
, written
;
339 while (ReadFile(file
, data
, READ_BUF_SIZE
, &read
, NULL
) && (read
!= 0))
341 IStream_Write(stream
, data
, read
, &written
);
345 TRACE("%d bytes written of attachment\n", size
);
347 IStream_Commit(stream
, STGC_DEFAULT
);
348 IStream_Release(stream
);
350 prop
[0].ulPropTag
= PR_ATTACH_SIZE
;
351 prop
[0].Value
.ul
= size
;
352 IAttach_SetProps(attachment
, 1, prop
, NULL
);
354 IAttach_SaveChanges(attachment
, KEEP_OPEN_READONLY
);
360 IAttach_Release(attachment
);
362 HeapFree(GetProcessHeap(), 0, filenameA
);
366 IMessage_SaveChanges(msg
, KEEP_OPEN_READWRITE
);
368 /* Prepare the message form */
370 if (IMAPISession_PrepareForm(session
, NULL
, msg
, &token
) == S_OK
)
372 ULONG access
= 0, status
= 0, message_flags
= 0, pc
= 0;
373 ULONG pT
[2] = {1, PR_MSG_STATUS
};
375 /* Retrieve message status, flags, access rights and class */
377 if (IMessage_GetProps(msg
, (LPSPropTagArray
) pT
, 0, &pc
, &props
) == S_OK
)
379 status
= props
->Value
.ul
;
380 MAPIFreeBuffer(props
);
383 pT
[1] = PR_MESSAGE_FLAGS
;
385 if (IMessage_GetProps(msg
, (LPSPropTagArray
) pT
, 0, &pc
, &props
) == S_OK
)
387 message_flags
= props
->Value
.ul
;
388 MAPIFreeBuffer(props
);
393 if (IMessage_GetProps(msg
, (LPSPropTagArray
) pT
, 0, &pc
, &props
) == S_OK
)
395 access
= props
->Value
.ul
;
396 MAPIFreeBuffer(props
);
399 pT
[1] = PR_MESSAGE_CLASS_A
;
401 if (IMessage_GetProps(msg
, (LPSPropTagArray
) pT
, 0, &pc
, &props
) == S_OK
)
403 /* Show the message form (edit window) */
405 ret
= IMAPISession_ShowForm(session
, 0, msg_store
, draft_folder
, NULL
,
406 token
, NULL
, 0, status
, message_flags
, access
,
412 retval
= SUCCESS_SUCCESS
;
415 case MAPI_E_USER_CANCEL
:
416 retval
= MAPI_E_USER_ABORT
;
420 TRACE("ShowForm failure: %x\n", ret
);
426 IMessage_Release(msg
);
429 /* Free up the resources we've used */
430 IMAPIFolder_Release(draft_folder
);
431 if (folder
) IMAPIFolder_Release(folder
);
432 IMsgStore_Release(msg_store
);
434 HeapFree(GetProcessHeap(), 0, subjectA
);
435 HeapFree(GetProcessHeap(), 0, bodyA
);
438 IMAPISession_Logoff(session
, 0, 0, 0);
439 IMAPISession_Release(session
);
446 /**************************************************************************
447 * MAPISendMail (MAPI32.211)
452 * session [I] Handle to a MAPI session.
453 * uiparam [I] Parent window handle.
454 * message [I] Pointer to a MAPIMessage structure.
456 * reserved [I] Reserved, pass 0.
459 * Success: SUCCESS_SUCCESS
460 * Failure: MAPI_E_FAILURE
463 ULONG WINAPI
MAPISendMail( LHANDLE session
, ULONG_PTR uiparam
,
464 lpMapiMessage message
, FLAGS flags
, ULONG reserved
)
466 WCHAR msg_title
[READ_BUF_SIZE
], error_msg
[READ_BUF_SIZE
];
468 /* Check to see if we have a Simple MAPI provider loaded */
469 if (mapiFunctions
.MAPISendMail
)
470 return mapiFunctions
.MAPISendMail(session
, uiparam
, message
, flags
, reserved
);
472 /* Check if we have an Extended MAPI provider - if so, use our wrapper */
473 if (MAPIInitialize(NULL
) == S_OK
)
475 MapiMessageW messageW
;
478 ZeroMemory(&messageW
, sizeof(MapiMessageW
));
480 /* Convert the entries we need to Unicode */
481 messageW
.lpszSubject
= convert_to_unicode(message
->lpszSubject
);
482 messageW
.lpszNoteText
= convert_to_unicode(message
->lpszNoteText
);
483 messageW
.nFileCount
= message
->nFileCount
;
485 if (message
->nFileCount
&& message
->lpFiles
)
487 lpMapiFileDescW filesW
;
490 filesW
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(MapiFileDescW
) * message
->nFileCount
);
492 for (i
= 0; i
< message
->nFileCount
; i
++)
494 filesW
[i
].lpszPathName
= convert_to_unicode(message
->lpFiles
[i
].lpszPathName
);
495 filesW
[i
].lpszFileName
= convert_to_unicode(message
->lpFiles
[i
].lpszFileName
);
498 messageW
.lpFiles
= filesW
;
501 ret
= sendmail_extended_mapi(session
, uiparam
, &messageW
, flags
);
503 /* Now free everything we allocated */
504 if (message
->nFileCount
&& message
->lpFiles
)
508 for (i
= 0; i
< message
->nFileCount
; i
++)
510 HeapFree(GetProcessHeap(), 0, messageW
.lpFiles
[i
].lpszPathName
);
511 HeapFree(GetProcessHeap(), 0, messageW
.lpFiles
[i
].lpszFileName
);
514 HeapFree(GetProcessHeap(), 0, messageW
.lpFiles
);
517 HeapFree(GetProcessHeap(), 0, messageW
.lpszSubject
);
518 HeapFree(GetProcessHeap(), 0, messageW
.lpszNoteText
);
523 /* Display an error message since we apparently have no mail clients */
524 LoadStringW(hInstMAPI32
, IDS_NO_MAPI_CLIENT
, error_msg
, sizeof(error_msg
) / sizeof(WCHAR
));
525 LoadStringW(hInstMAPI32
, IDS_SEND_MAIL
, msg_title
, sizeof(msg_title
) / sizeof(WCHAR
));
527 MessageBoxW((HWND
) uiparam
, error_msg
, msg_title
, MB_ICONEXCLAMATION
);
529 return MAPI_E_NOT_SUPPORTED
;
532 static lpMapiRecipDesc
convert_recipient_from_unicode(lpMapiRecipDescW recipW
, lpMapiRecipDesc dest
)
542 ret
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(MapiRecipDesc
));
544 ret
->ulRecipClass
= recipW
->ulRecipClass
;
545 ret
->lpszName
= convert_from_unicode(recipW
->lpszName
);
546 ret
->lpszAddress
= convert_from_unicode(recipW
->lpszAddress
);
547 ret
->ulEIDSize
= recipW
->ulEIDSize
;
548 ret
->lpEntryID
= recipW
->lpEntryID
;
553 /**************************************************************************
554 * MAPISendMailW (MAPI32.256)
559 * session [I] Handle to a MAPI session.
560 * uiparam [I] Parent window handle.
561 * message [I] Pointer to a MAPIMessageW structure.
563 * reserved [I] Reserved, pass 0.
566 * Success: SUCCESS_SUCCESS
567 * Failure: MAPI_E_FAILURE
570 ULONG WINAPI
MAPISendMailW(LHANDLE session
, ULONG_PTR uiparam
,
571 lpMapiMessageW message
, FLAGS flags
, ULONG reserved
)
573 WCHAR msg_title
[READ_BUF_SIZE
], error_msg
[READ_BUF_SIZE
];
575 /* Check to see if we have a Simple MAPI provider loaded */
576 if (mapiFunctions
.MAPISendMailW
)
577 return mapiFunctions
.MAPISendMailW(session
, uiparam
, message
, flags
, reserved
);
579 /* Check if we have an Extended MAPI provider - if so, use our wrapper */
580 if (MAPIInitialize(NULL
) == S_OK
)
581 return sendmail_extended_mapi(session
, uiparam
, message
, flags
);
583 if (mapiFunctions
.MAPISendMail
)
585 MapiMessage messageA
;
588 if (flags
& MAPI_FORCE_UNICODE
)
589 return MAPI_E_UNICODE_NOT_SUPPORTED
;
591 /* Convert to ANSI and send to MAPISendMail */
592 ZeroMemory(&messageA
, sizeof(MapiMessage
));
594 messageA
.lpszSubject
= convert_from_unicode(message
->lpszSubject
);
595 messageA
.lpszNoteText
= convert_from_unicode(message
->lpszNoteText
);
596 messageA
.lpszMessageType
= convert_from_unicode(message
->lpszMessageType
);
597 messageA
.lpszDateReceived
= convert_from_unicode(message
->lpszDateReceived
);
598 messageA
.lpszConversationID
= convert_from_unicode(message
->lpszConversationID
);
599 messageA
.flFlags
= message
->flFlags
;
600 messageA
.lpOriginator
= convert_recipient_from_unicode(message
->lpOriginator
, NULL
);
601 messageA
.nRecipCount
= message
->nRecipCount
;
602 messageA
.nFileCount
= message
->nFileCount
;
604 if (message
->nRecipCount
&& message
->lpRecips
)
606 lpMapiRecipDesc recipsA
;
609 recipsA
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(MapiRecipDesc
) * message
->nRecipCount
);
611 for (i
= 0; i
< message
->nRecipCount
; i
++)
613 convert_recipient_from_unicode(&message
->lpRecips
[i
], &recipsA
[i
]);
616 messageA
.lpRecips
= recipsA
;
619 if (message
->nFileCount
&& message
->lpFiles
)
621 lpMapiFileDesc filesA
;
624 filesA
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(MapiFileDesc
) * message
->nFileCount
);
626 for (i
= 0; i
< message
->nFileCount
; i
++)
628 filesA
[i
].flFlags
= message
->lpFiles
[i
].flFlags
;
629 filesA
[i
].nPosition
= message
->lpFiles
[i
].nPosition
;
630 filesA
[i
].lpszPathName
= convert_from_unicode(message
->lpFiles
[i
].lpszPathName
);
631 filesA
[i
].lpszFileName
= convert_from_unicode(message
->lpFiles
[i
].lpszFileName
);
632 filesA
[i
].lpFileType
= message
->lpFiles
[i
].lpFileType
;
635 messageA
.lpFiles
= filesA
;
638 ret
= mapiFunctions
.MAPISendMail(session
, uiparam
, &messageA
, flags
, reserved
);
640 /* Now free everything we allocated */
641 if (message
->lpOriginator
)
643 HeapFree(GetProcessHeap(), 0, messageA
.lpOriginator
->lpszName
);
644 HeapFree(GetProcessHeap(), 0, messageA
.lpOriginator
->lpszAddress
);
645 HeapFree(GetProcessHeap(), 0, messageA
.lpOriginator
);
648 if (message
->nRecipCount
&& message
->lpRecips
)
652 for (i
= 0; i
< message
->nRecipCount
; i
++)
654 HeapFree(GetProcessHeap(), 0, messageA
.lpRecips
[i
].lpszName
);
655 HeapFree(GetProcessHeap(), 0, messageA
.lpRecips
[i
].lpszAddress
);
658 HeapFree(GetProcessHeap(), 0, messageA
.lpRecips
);
661 if (message
->nFileCount
&& message
->lpFiles
)
665 for (i
= 0; i
< message
->nFileCount
; i
++)
667 HeapFree(GetProcessHeap(), 0, messageA
.lpFiles
[i
].lpszPathName
);
668 HeapFree(GetProcessHeap(), 0, messageA
.lpFiles
[i
].lpszFileName
);
671 HeapFree(GetProcessHeap(), 0, messageA
.lpFiles
);
674 HeapFree(GetProcessHeap(), 0, messageA
.lpszSubject
);
675 HeapFree(GetProcessHeap(), 0, messageA
.lpszNoteText
);
676 HeapFree(GetProcessHeap(), 0, messageA
.lpszDateReceived
);
677 HeapFree(GetProcessHeap(), 0, messageA
.lpszConversationID
);
682 /* Display an error message since we apparently have no mail clients */
683 LoadStringW(hInstMAPI32
, IDS_NO_MAPI_CLIENT
, error_msg
, sizeof(error_msg
) / sizeof(WCHAR
));
684 LoadStringW(hInstMAPI32
, IDS_SEND_MAIL
, msg_title
, sizeof(msg_title
) / sizeof(WCHAR
));
686 MessageBoxW((HWND
) uiparam
, error_msg
, msg_title
, MB_ICONEXCLAMATION
);
688 return MAPI_E_NOT_SUPPORTED
;
691 ULONG WINAPI
MAPISendDocuments(ULONG_PTR uiparam
, LPSTR delim
, LPSTR paths
,
692 LPSTR filenames
, ULONG reserved
)
694 if (mapiFunctions
.MAPISendDocuments
)
695 return mapiFunctions
.MAPISendDocuments(uiparam
, delim
, paths
, filenames
, reserved
);
697 return MAPI_E_NOT_SUPPORTED
;