1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <osl/diagnose.h>
21 #include <sal/macros.h>
22
23 #include <o3tl/char16_t2wchar_t.hxx>
24 #include <rtl/bootstrap.hxx>
25 #include <sfx2/strings.hrc>
26 #include <unotools/resmgr.hxx>
27 #include <i18nlangtag/languagetag.hxx>
28
29 #include <wchar.h>
30 #define WIN32_LEAN_AND_MEAN
31 #include <windows.h>
32 #include <mapi.h>
33 #include <MapiUnicodeHelp.h>
34
35 #include <string>
36 #include <vector>
37 #if OSL_DEBUG_LEVEL > 0
38 #include <sstream>
39 #endif
40 #include <stdexcept>
41
42 #if OSL_DEBUG_LEVEL > 0
43 static void dumpParameter();
44 #endif
45
46 typedef std::vector<MapiRecipDescW> MapiRecipientList_t;
47 typedef std::vector<MapiFileDescW> MapiAttachmentList_t;
48
49 const int LEN_SMTP_PREFIX = 5; // "SMTP:"
50
51 namespace /* private */
52 {
53 OUString gLangTag;
54 OUString gBootstrap;
55 std::wstring gFrom;
56 std::wstring gSubject;
57 std::wstring gBody;
58 std::vector<std::wstring> gTo;
59 std::vector<std::wstring> gCc;
60 std::vector<std::wstring> gBcc;
61 // Keep temp filepath and displayed name
62 std::vector<std::pair<std::wstring, std::wstring>> gAttachments;
63 int gMapiFlags = 0;
64 }
65
66 /**
67 Add a prefix to an email address. MAPI requires that
68 email addresses have an 'SMTP:' prefix.
69
70 @param aEmailAddress
71 [in] the email address.
72
73 @param aPrefix
74 [in] the prefix to be added to the email address.
75
76 @returns
77 the email address prefixed with the specified prefix.
78 */
prefixEmailAddress(const std::wstring & aEmailAddress,const std::wstring & aPrefix=L"SMTP:")79 static std::wstring prefixEmailAddress(
80 const std::wstring& aEmailAddress,
81 const std::wstring& aPrefix = L"SMTP:")
82 {
83 return (aPrefix + aEmailAddress);
84 }
85
86 /** @internal */
addRecipient(ULONG recipClass,const std::wstring & recipAddress,MapiRecipientList_t * pMapiRecipientList)87 static void addRecipient(
88 ULONG recipClass,
89 const std::wstring& recipAddress,
90 MapiRecipientList_t* pMapiRecipientList)
91 {
92 MapiRecipDescW mrd;
93 ZeroMemory(&mrd, sizeof(mrd));
94
95 mrd.ulRecipClass = recipClass;
96 mrd.lpszName = const_cast<wchar_t*>(recipAddress.c_str()) + LEN_SMTP_PREFIX;
97 mrd.lpszAddress = const_cast<wchar_t*>(recipAddress.c_str());
98 pMapiRecipientList->push_back(mrd);
99 }
100
101 /** @internal */
initRecipientList(MapiRecipientList_t * pMapiRecipientList)102 static void initRecipientList(MapiRecipientList_t* pMapiRecipientList)
103 {
104 OSL_ASSERT(pMapiRecipientList->empty());
105
106 // add to recipients
107 for (const auto& address : gTo)
108 addRecipient(MAPI_TO, address, pMapiRecipientList);
109
110 // add cc recipients
111 for (const auto& address : gCc)
112 addRecipient(MAPI_CC, address, pMapiRecipientList);
113
114 // add bcc recipients
115 for (const auto& address : gBcc)
116 addRecipient(MAPI_BCC, address, pMapiRecipientList);
117 }
118
119 /** @internal */
initAttachmentList(MapiAttachmentList_t * pMapiAttachmentList)120 static void initAttachmentList(MapiAttachmentList_t* pMapiAttachmentList)
121 {
122 OSL_ASSERT(pMapiAttachmentList->empty());
123
124 for (const auto& attachment : gAttachments)
125 {
126 MapiFileDescW mfd;
127 ZeroMemory(&mfd, sizeof(mfd));
128 mfd.lpszPathName = const_cast<wchar_t*>(attachment.first.c_str());
129 // MapiFileDesc documentation (https://msdn.microsoft.com/en-us/library/hh707272)
130 // allows using here either nullptr, or a pointer to empty string. However,
131 // for Outlook 2013, we cannot use nullptr here, and must point to a (possibly
132 // empty) string: otherwise using MAPI_DIALOG_MODELESS results in MAPI_E_FAILURE.
133 // See http://peach.ease.lsoft.com/scripts/wa-PEACH.exe?A2=MAPI-L;d2bf3060.1604
134 // Since C++11, c_str() must return a pointer to single null character when the
135 // string is empty, so we are OK here in case when there's no explicit file name
136 // passed
137 mfd.lpszFileName = const_cast<wchar_t*>(attachment.second.c_str());
138 mfd.nPosition = sal::static_int_cast<ULONG>(-1);
139 pMapiAttachmentList->push_back(mfd);
140 }
141 }
142
143 /** @internal */
initMapiOriginator(MapiRecipDescW * pMapiOriginator)144 static void initMapiOriginator(MapiRecipDescW* pMapiOriginator)
145 {
146 ZeroMemory(pMapiOriginator, sizeof(*pMapiOriginator));
147
148 pMapiOriginator->ulRecipClass = MAPI_ORIG;
149 pMapiOriginator->lpszName = const_cast<wchar_t*>(L"");
150 pMapiOriginator->lpszAddress = const_cast<wchar_t*>(gFrom.c_str());
151 }
152
153 /** @internal */
initMapiMessage(MapiRecipDescW * aMapiOriginator,MapiRecipientList_t & aMapiRecipientList,MapiAttachmentList_t & aMapiAttachmentList,MapiMessageW * pMapiMessage)154 static void initMapiMessage(
155 MapiRecipDescW* aMapiOriginator,
156 MapiRecipientList_t& aMapiRecipientList,
157 MapiAttachmentList_t& aMapiAttachmentList,
158 MapiMessageW* pMapiMessage)
159 {
160 ZeroMemory(pMapiMessage, sizeof(*pMapiMessage));
161
162 pMapiMessage->lpszSubject = const_cast<wchar_t*>(gSubject.c_str());
163 pMapiMessage->lpszNoteText = (gBody.length() ? const_cast<wchar_t*>(gBody.c_str()) : nullptr);
164 pMapiMessage->lpOriginator = aMapiOriginator;
165 pMapiMessage->lpRecips = aMapiRecipientList.size() ? aMapiRecipientList.data() : nullptr;
166 pMapiMessage->nRecipCount = aMapiRecipientList.size();
167 if (!aMapiAttachmentList.empty())
168 pMapiMessage->lpFiles = aMapiAttachmentList.data();
169 pMapiMessage->nFileCount = aMapiAttachmentList.size();
170 }
171
172 const wchar_t* const KnownParameters[] =
173 {
174 L"--to",
175 L"--cc",
176 L"--bcc",
177 L"--from",
178 L"--subject",
179 L"--body",
180 L"--attach",
181 L"--mapi-dialog",
182 L"--mapi-logon-ui",
183 L"--langtag",
184 L"--bootstrap",
185 };
186
187 /** @internal */
isKnownParameter(const wchar_t * aParameterName)188 static bool isKnownParameter(const wchar_t* aParameterName)
189 {
190 for (const wchar_t* KnownParameter : KnownParameters)
191 if (_wcsicmp(aParameterName, KnownParameter) == 0)
192 return true;
193
194 return false;
195 }
196
197 /** @internal */
initParameter(int argc,wchar_t * argv[])198 static void initParameter(int argc, wchar_t* argv[])
199 {
200 for (int i = 1; i < argc; i++)
201 {
202 if (!isKnownParameter(argv[i]))
203 {
204 OSL_FAIL("Wrong parameter received");
205 continue;
206 }
207
208 if (_wcsicmp(argv[i], L"--mapi-dialog") == 0)
209 {
210 // MAPI_DIALOG_MODELESS has many problems and crashes Outlook 2016.
211 // see the commit message for a lengthy description.
212 gMapiFlags |= MAPI_DIALOG;
213 }
214 else if (_wcsicmp(argv[i], L"--mapi-logon-ui") == 0)
215 {
216 gMapiFlags |= MAPI_LOGON_UI;
217 }
218 else if ((i+1) < argc) // is the value of a parameter available too?
219 {
220 if (_wcsicmp(argv[i], L"--to") == 0)
221 gTo.push_back(prefixEmailAddress(argv[i+1]));
222 else if (_wcsicmp(argv[i], L"--cc") == 0)
223 gCc.push_back(prefixEmailAddress(argv[i+1]));
224 else if (_wcsicmp(argv[i], L"--bcc") == 0)
225 gBcc.push_back(prefixEmailAddress(argv[i+1]));
226 else if (_wcsicmp(argv[i], L"--from") == 0)
227 gFrom = prefixEmailAddress(argv[i+1]);
228 else if (_wcsicmp(argv[i], L"--subject") == 0)
229 gSubject = argv[i+1];
230 else if (_wcsicmp(argv[i], L"--body") == 0)
231 gBody = argv[i+1];
232 else if (_wcsicmp(argv[i], L"--attach") == 0)
233 {
234 std::wstring sPath(argv[i + 1]);
235 // An attachment may optionally be immediately followed by --attach-name and user-visible name
236 std::wstring sName;
237 if ((i + 3) < argc && _wcsicmp(argv[i+2], L"--attach-name") == 0)
238 {
239 sName = argv[i+3];
240 i += 2;
241 }
242 gAttachments.emplace_back(sPath, sName);
243 }
244 else if (_wcsicmp(argv[i], L"--langtag") == 0)
245 gLangTag = o3tl::toU(argv[i+1]);
246 else if (_wcsicmp(argv[i], L"--bootstrap") == 0)
247 gBootstrap = o3tl::toU(argv[i+1]);
248
249 i++;
250 }
251 }
252 }
253
ShowError(ULONG nMAPIResult)254 static void ShowError(ULONG nMAPIResult)
255 {
256 if (!gBootstrap.isEmpty())
257 rtl::Bootstrap::setIniFilename(gBootstrap);
258 LanguageTag aLangTag(gLangTag);
259 std::locale aLocale = Translate::Create("sfx", aLangTag);
260 OUString sMessage = Translate::get(STR_ERROR_SEND_MAIL_CODE, aLocale);
261 OUString sErrorId;
262 switch (nMAPIResult)
263 {
264 case MAPI_E_FAILURE:
265 sErrorId = "MAPI_E_FAILURE";
266 break;
267 case MAPI_E_LOGON_FAILURE:
268 sErrorId = "MAPI_E_LOGON_FAILURE";
269 break;
270 case MAPI_E_DISK_FULL:
271 sErrorId = "MAPI_E_DISK_FULL";
272 break;
273 case MAPI_E_INSUFFICIENT_MEMORY:
274 sErrorId = "MAPI_E_INSUFFICIENT_MEMORY";
275 break;
276 case MAPI_E_ACCESS_DENIED:
277 sErrorId = "MAPI_E_ACCESS_DENIED";
278 break;
279 case MAPI_E_TOO_MANY_SESSIONS:
280 sErrorId = "MAPI_E_ACCESS_DENIED";
281 break;
282 case MAPI_E_TOO_MANY_FILES:
283 sErrorId = "MAPI_E_TOO_MANY_FILES";
284 break;
285 case MAPI_E_TOO_MANY_RECIPIENTS:
286 sErrorId = "MAPI_E_TOO_MANY_RECIPIENTS";
287 break;
288 case MAPI_E_ATTACHMENT_NOT_FOUND:
289 sErrorId = "MAPI_E_ATTACHMENT_NOT_FOUND";
290 break;
291 case MAPI_E_ATTACHMENT_OPEN_FAILURE:
292 sErrorId = "MAPI_E_ATTACHMENT_OPEN_FAILURE";
293 break;
294 case MAPI_E_ATTACHMENT_WRITE_FAILURE:
295 sErrorId = "MAPI_E_ATTACHMENT_WRITE_FAILURE";
296 break;
297 case MAPI_E_UNKNOWN_RECIPIENT:
298 sErrorId = "MAPI_E_UNKNOWN_RECIPIENT";
299 break;
300 case MAPI_E_BAD_RECIPTYPE:
301 sErrorId = "MAPI_E_BAD_RECIPTYPE";
302 break;
303 case MAPI_E_NO_MESSAGES:
304 sErrorId = "MAPI_E_NO_MESSAGES";
305 break;
306 case MAPI_E_INVALID_MESSAGE:
307 sErrorId = "MAPI_E_INVALID_MESSAGE";
308 break;
309 case MAPI_E_TEXT_TOO_LARGE:
310 sErrorId = "MAPI_E_TEXT_TOO_LARGE";
311 break;
312 case MAPI_E_INVALID_SESSION:
313 sErrorId = "MAPI_E_INVALID_SESSION";
314 break;
315 case MAPI_E_TYPE_NOT_SUPPORTED:
316 sErrorId = "MAPI_E_TYPE_NOT_SUPPORTED";
317 break;
318 case MAPI_E_AMBIGUOUS_RECIPIENT:
319 sErrorId = "MAPI_E_AMBIGUOUS_RECIPIENT";
320 break;
321 case MAPI_E_MESSAGE_IN_USE:
322 sErrorId = "MAPI_E_MESSAGE_IN_USE";
323 break;
324 case MAPI_E_NETWORK_FAILURE:
325 sErrorId = "MAPI_E_NETWORK_FAILURE";
326 break;
327 case MAPI_E_INVALID_EDITFIELDS:
328 sErrorId = "MAPI_E_INVALID_EDITFIELDS";
329 break;
330 case MAPI_E_INVALID_RECIPS:
331 sErrorId = "MAPI_E_INVALID_RECIPS";
332 break;
333 case MAPI_E_NOT_SUPPORTED:
334 sErrorId = "MAPI_E_NOT_SUPPORTED";
335 break;
336 case MAPI_E_UNICODE_NOT_SUPPORTED:
337 sErrorId = "MAPI_E_UNICODE_NOT_SUPPORTED";
338 break;
339 default:
340 sErrorId = OUString::number(nMAPIResult);
341 }
342 sMessage = sMessage.replaceAll("$1", sErrorId);
343 OUString sTitle(Translate::get(STR_ERROR_SEND_MAIL_HEADER, aLocale));
344
345 MessageBoxW(nullptr, o3tl::toW(sMessage.getStr()), o3tl::toW(sTitle.getStr()),
346 MB_OK | MB_ICONINFORMATION);
347 }
348
349 /**
350 Main.
351 NOTE: Because this is program only serves implementation
352 purposes and should not be used by any end user the
353 parameter checking is very limited. Every unknown parameter
354 will be ignored.
355 */
wmain(int argc,wchar_t * argv[])356 int wmain(int argc, wchar_t* argv[])
357 {
358
359 initParameter(argc, argv);
360
361 #if OSL_DEBUG_LEVEL > 0
362 dumpParameter();
363 #endif
364
365 ULONG ulRet = MAPI_E_FAILURE;
366
367 try
368 {
369 LHANDLE const hSession = 0;
370
371 MapiRecipDescW mapiOriginator;
372 MapiRecipientList_t mapiRecipientList;
373 MapiAttachmentList_t mapiAttachmentList;
374 MapiMessageW mapiMsg;
375
376 initMapiOriginator(&mapiOriginator);
377 initRecipientList(&mapiRecipientList);
378 initAttachmentList(&mapiAttachmentList);
379 initMapiMessage((gFrom.length() ? &mapiOriginator : nullptr), mapiRecipientList, mapiAttachmentList, &mapiMsg);
380
381 ulRet = MAPISendMailHelper(hSession, 0, &mapiMsg, gMapiFlags, 0);
382
383 // There is no point in treating an aborted mail sending
384 // dialog as an error to be returned as our exit
385 // status. If the user decided to abort sending a document
386 // as mail, OK, that is not an error.
387
388 // Also, it seems that GroupWise makes MAPISendMail()
389 // return MAPI_E_USER_ABORT even if the mail sending
390 // dialog was not aborted by the user, and the mail was
391 // actually sent just fine. See bnc#660241 (visible to
392 // Novell people only, sorry).
393
394 if (ulRet == MAPI_E_USER_ABORT)
395 ulRet = SUCCESS_SUCCESS;
396
397 }
398 catch (const std::runtime_error& ex)
399 {
400 OSL_FAIL(ex.what());
401 }
402
403 // Now cleanup the temporary attachment files
404 for (const auto& rAttachment : gAttachments)
405 DeleteFileW(rAttachment.first.c_str());
406
407 // Only show the error message if UI was requested
408 if ((ulRet != SUCCESS_SUCCESS) && (gMapiFlags & (MAPI_DIALOG | MAPI_LOGON_UI)))
409 ShowError(ulRet);
410
411 return ulRet;
412 }
413
414 #if OSL_DEBUG_LEVEL > 0
dumpParameter()415 void dumpParameter()
416 {
417 std::wostringstream oss;
418
419 if (gFrom.length() > 0)
420 oss << "--from " << gFrom << std::endl;
421
422 if (gSubject.length() > 0)
423 oss << "--subject " << gSubject << std::endl;
424
425 if (gBody.length() > 0)
426 oss << "--body " << gBody << std::endl;
427
428 for (const auto& address : gTo)
429 oss << "--to " << address << std::endl;
430
431 for (const auto& address : gCc)
432 oss << "--cc " << address << std::endl;
433
434 for (const auto& address : gBcc)
435 oss << "--bcc " << address << std::endl;
436
437 for (const auto& attachment : gAttachments)
438 {
439 oss << "--attach " << attachment.first << std::endl;
440 if (!attachment.second.empty())
441 oss << "--attach-name " << attachment.second << std::endl;
442 }
443
444 if (gMapiFlags & MAPI_DIALOG)
445 oss << "--mapi-dialog" << std::endl;
446
447 if (gMapiFlags & MAPI_LOGON_UI)
448 oss << "--mapi-logon-ui" << std::endl;
449
450 if (!gLangTag.isEmpty())
451 oss << "--langtag " << gLangTag << std::endl;
452
453 if (!gBootstrap.isEmpty())
454 oss << "--bootstrap " << gBootstrap << std::endl;
455
456 MessageBoxW(nullptr, oss.str().c_str(), L"Arguments", MB_OK | MB_ICONINFORMATION);
457 }
458 #endif
459
460 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
461