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