MAPIToVMIME.cpp 83 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/platform.h>
  18. #include <exception>
  19. #include <memory>
  20. #include <string>
  21. #include <utility>
  22. #include <vector>
  23. #include <kopano/MAPIErrors.h>
  24. // vmime
  25. #include <vmime/vmime.hpp>
  26. #include <vmime/platforms/posix/posixHandler.hpp>
  27. #include <vmime/contentTypeField.hpp>
  28. #include <vmime/parsedMessageAttachment.hpp>
  29. #include <vmime/emptyContentHandler.hpp>
  30. // mapi
  31. #include <kopano/memory.hpp>
  32. #include <mapi.h>
  33. #include <mapiutil.h>
  34. #include <kopano/mapiext.h>
  35. #include <edkmdb.h>
  36. #include <kopano/mapiguidext.h>
  37. #include <kopano/mapi_ptr.h>
  38. #include "tnef.h"
  39. // inetmapi
  40. #include "ECMapiUtils.h"
  41. #include "MAPIToVMIME.h"
  42. #include "VMIMEToMAPI.h"
  43. #include "outputStreamMAPIAdapter.h"
  44. #include "inputStreamMAPIAdapter.h"
  45. #include "mapiAttachment.h"
  46. #include "mapiTextPart.h"
  47. #include "rtfutil.h"
  48. #include <kopano/CommonUtil.h>
  49. #include <kopano/stringutil.h>
  50. #include "mapicontact.h"
  51. #include <kopano/Util.h>
  52. #include <kopano/ECLogger.h>
  53. #include <kopano/codepage.h>
  54. #include <kopano/ecversion.h>
  55. #include "SMIMEMessage.h"
  56. // icalmapi
  57. #include "MAPIToICal.h"
  58. using namespace std;
  59. using namespace KCHL;
  60. namespace KC {
  61. /* These are the properties that we DON'T send through TNEF. Reasons can be:
  62. *
  63. * - Privacy (PR_MDB_PROVIDER, etc)
  64. * - Sent through rfc2822 (PR_SUBJECT, PR_BODY, etc)
  65. * - Not part of an outgoing message (PR_TRANSPORT_MESSAGE_HEADERS)
  66. */
  67. // Since UNICODE is defined, the strings will be PT_UNICODE, as required by ECTNEF::AddProps()
  68. static constexpr const SizedSPropTagArray(54, sptaExclude) = {
  69. 54, {
  70. PR_BODY,
  71. PR_HTML,
  72. PR_RTF_COMPRESSED,
  73. PR_RTF_IN_SYNC,
  74. PR_ACCESS,
  75. PR_ACCESS_LEVEL,
  76. PR_CHANGE_KEY,
  77. PR_CLIENT_SUBMIT_TIME,
  78. PR_CREATION_TIME,
  79. PR_DELETE_AFTER_SUBMIT,
  80. PR_DISPLAY_BCC,
  81. PR_DISPLAY_CC,
  82. PR_DISPLAY_TO,
  83. PR_ENTRYID,
  84. PR_HASATTACH,
  85. PR_LAST_MODIFICATION_TIME,
  86. PR_MAPPING_SIGNATURE,
  87. PR_MDB_PROVIDER,
  88. PR_MESSAGE_DELIVERY_TIME,
  89. PR_MESSAGE_FLAGS,
  90. PR_MESSAGE_SIZE,
  91. PR_NORMALIZED_SUBJECT,
  92. PR_OBJECT_TYPE,
  93. PR_PARENT_ENTRYID,
  94. PR_PARENT_SOURCE_KEY,
  95. PR_PREDECESSOR_CHANGE_LIST,
  96. PR_RECORD_KEY,
  97. PR_RTF_SYNC_BODY_COUNT,
  98. PR_RTF_SYNC_BODY_CRC,
  99. PR_RTF_SYNC_BODY_TAG,
  100. PR_RTF_SYNC_PREFIX_COUNT,
  101. PR_RTF_SYNC_TRAILING_COUNT,
  102. PR_SEARCH_KEY,
  103. PR_SENDER_ADDRTYPE,
  104. PR_SENDER_EMAIL_ADDRESS,
  105. PR_SENDER_ENTRYID,
  106. PR_SENDER_NAME,
  107. PR_SENDER_SEARCH_KEY,
  108. PR_SENTMAIL_ENTRYID,
  109. PR_SENT_REPRESENTING_ADDRTYPE,
  110. PR_SENT_REPRESENTING_EMAIL_ADDRESS,
  111. PR_SENT_REPRESENTING_ENTRYID,
  112. PR_SENT_REPRESENTING_NAME,
  113. PR_SENT_REPRESENTING_SEARCH_KEY,
  114. PR_SOURCE_KEY,
  115. PR_STORE_ENTRYID,
  116. PR_STORE_RECORD_KEY,
  117. PR_STORE_SUPPORT_MASK,
  118. PR_SUBJECT,
  119. PR_SUBJECT_PREFIX,
  120. PR_SUBMIT_FLAGS,
  121. PR_TRANSPORT_MESSAGE_HEADERS,
  122. PR_LAST_MODIFIER_NAME, // IMPORTANT: exchange will drop the message if this is included
  123. PR_LAST_MODIFIER_ENTRYID, // IMPORTANT: exchange will drop the message if this is included
  124. }
  125. };
  126. // These are named properties that are mapped to fixed values in Kopano, so
  127. // we can use the ID values instead of using GetIDsFromNames every times
  128. #define PR_EC_USE_TNEF PROP_TAG(PT_BOOLEAN, 0x8222)
  129. #define PR_EC_SEND_AS_ICAL PROP_TAG(PT_BOOLEAN, 0x8000)
  130. #define PR_EC_OUTLOOK_VERSION PROP_TAG(PT_STRING8, 0x81F4)
  131. /**
  132. * Inits the class with empty/default values.
  133. */
  134. MAPIToVMIME::MAPIToVMIME()
  135. {
  136. srand((unsigned)time(NULL));
  137. m_lpAdrBook = NULL;
  138. imopt_default_sending_options(&sopt);
  139. m_lpSession = NULL;
  140. }
  141. /**
  142. * @param[in] lpSession current mapi session, used to open contact entryids
  143. * @param[in] lpAddrBook global addressbook
  144. * @param[in] newlogger logger object
  145. * @param[in] sopt struct with optional settings to change conversion
  146. */
  147. MAPIToVMIME::MAPIToVMIME(IMAPISession *lpSession, IAddrBook *lpAddrBook,
  148. sending_options sopt)
  149. {
  150. srand((unsigned)time(NULL));
  151. this->sopt = sopt;
  152. if (lpSession != nullptr && lpAddrBook == nullptr)
  153. lpSession->OpenAddressBook(0, NULL, AB_NO_DIALOG, &m_lpAdrBook);
  154. // ignore error
  155. else
  156. lpAddrBook->QueryInterface(IID_IAddrBook, (void**)&m_lpAdrBook);
  157. m_lpSession = lpSession;
  158. }
  159. MAPIToVMIME::~MAPIToVMIME()
  160. {
  161. if (m_lpAdrBook)
  162. m_lpAdrBook->Release();
  163. }
  164. /**
  165. * Convert recipient table to vmime recipients.
  166. *
  167. * @param[in] lpMessage Message with recipients to convert
  168. * @param[in] lpVMMessageBuilder messagebuilder to add recipients to
  169. * @param[in] charset Charset to use for fullname encoding of recipient
  170. * @return Mapi error code
  171. */
  172. HRESULT MAPIToVMIME::processRecipients(IMessage *lpMessage, vmime::messageBuilder *lpVMMessageBuilder)
  173. {
  174. HRESULT hr = hrSuccess;
  175. vmime::shared_ptr<vmime::address> vmMailbox;
  176. object_ptr<IMAPITable> lpRecipientTable;
  177. rowset_ptr pRows;
  178. bool fToFound = false;
  179. bool hasRecips = false;
  180. static constexpr const SizedSPropTagArray(7, sPropRecipColumns) =
  181. {7, {PR_ENTRYID, PR_EMAIL_ADDRESS_W, PR_DISPLAY_NAME_W,
  182. PR_RECIPIENT_TYPE, PR_SMTP_ADDRESS_W, PR_ADDRTYPE_W,
  183. PR_OBJECT_TYPE}};
  184. hr = lpMessage->GetRecipientTable(MAPI_UNICODE | MAPI_DEFERRED_ERRORS, &~lpRecipientTable);
  185. if (hr != hrSuccess) {
  186. ec_log_err("Unable to open recipient table. Error: 0x%08X", hr);
  187. return hr;
  188. }
  189. hr = lpRecipientTable->SetColumns(sPropRecipColumns, 0);
  190. if (hr != hrSuccess) {
  191. ec_log_err("Unable to set columns on recipient table. Error: 0x%08X", hr);
  192. return hr;
  193. }
  194. hr = HrQueryAllRows(lpRecipientTable, nullptr, nullptr, nullptr, 0, &~pRows);
  195. if (hr != hrSuccess) {
  196. ec_log_err("Unable to read recipient table. Error: 0x%08X", hr);
  197. return hr;
  198. }
  199. try {
  200. for (ULONG i = 0; i < pRows->cRows; ++i) {
  201. auto pPropRecipType = PCpropFindProp(pRows->aRow[i].lpProps, pRows->aRow[i].cValues, PR_RECIPIENT_TYPE);
  202. if(pPropRecipType == NULL) {
  203. // getMailBox properties
  204. auto pPropDispl = PCpropFindProp(pRows->aRow[i].lpProps, pRows->aRow[i].cValues, PR_DISPLAY_NAME_W);
  205. auto pPropAType = PCpropFindProp(pRows->aRow[i].lpProps, pRows->aRow[i].cValues, PR_ADDRTYPE_W);
  206. auto pPropEAddr = PCpropFindProp(pRows->aRow[i].lpProps, pRows->aRow[i].cValues, PR_EMAIL_ADDRESS_W);
  207. ec_log_err("No recipient type set for recipient. DisplayName: %ls, AddrType: %ls, Email: %ls",
  208. pPropDispl ? pPropDispl->Value.lpszW : L"(none)",
  209. pPropAType ? pPropAType->Value.lpszW : L"(none)",
  210. pPropEAddr ? pPropEAddr->Value.lpszW : L"(none)");
  211. continue;
  212. }
  213. hr = getMailBox(&pRows->aRow[i], &vmMailbox);
  214. if (hr == MAPI_E_INVALID_PARAMETER) // skip invalid addresses
  215. continue;
  216. if (hr != hrSuccess)
  217. return hr; // Logging done in getMailBox
  218. switch (pPropRecipType->Value.ul) {
  219. case MAPI_TO:
  220. lpVMMessageBuilder->getRecipients().appendAddress(vmMailbox);
  221. fToFound = true;
  222. hasRecips = true;
  223. break;
  224. case MAPI_CC:
  225. lpVMMessageBuilder->getCopyRecipients().appendAddress(vmMailbox);
  226. hasRecips = true;
  227. break;
  228. case MAPI_BCC:
  229. lpVMMessageBuilder->getBlindCopyRecipients().appendAddress(vmMailbox);
  230. hasRecips = true;
  231. break;
  232. }
  233. }
  234. if (!fToFound) {
  235. if (!hasRecips && sopt.no_recipients_workaround == false) {
  236. // spooler will exit here, gateway will continue with the workaround
  237. ec_log_err("No valid recipients found.");
  238. return MAPI_E_NOT_FOUND;
  239. }
  240. // No recipients were in the 'To' .. add 'undisclosed recipients'
  241. vmime::shared_ptr<vmime::mailboxGroup> undisclosed = vmime::make_shared<vmime::mailboxGroup>(vmime::text("undisclosed-recipients"));
  242. lpVMMessageBuilder->getRecipients().appendAddress(undisclosed);
  243. }
  244. }
  245. catch (vmime::exception& e) {
  246. ec_log_err("VMIME exception: %s", e.what());
  247. return MAPI_E_CALL_FAILED;
  248. }
  249. catch (std::exception& e) {
  250. ec_log_err("STD exception on recipients: %s", e.what());
  251. return MAPI_E_CALL_FAILED;
  252. }
  253. catch (...) {
  254. ec_log_err("Unknown generic exception occurred on recipients");
  255. return MAPI_E_CALL_FAILED;
  256. }
  257. return hrSuccess;
  258. }
  259. /**
  260. * Convert an attachment and attach to vmime messageBuilder.
  261. *
  262. * @param[in] lpMessage Message to open attachments on
  263. * @param[in] lpRow Current attachment to process, row from attachment table
  264. * @param[in] charset Charset to use for filename encoding of attachment
  265. * @param[in] lpVMMessageBuilder messagebuilder to add attachment to
  266. * @return Mapi error code
  267. */
  268. HRESULT MAPIToVMIME::handleSingleAttachment(IMessage* lpMessage, LPSRow lpRow, vmime::messageBuilder *lpVMMessageBuilder) {
  269. HRESULT hr = hrSuccess;
  270. object_ptr<IStream> lpStream;
  271. object_ptr<IAttach> lpAttach;
  272. const SPropValue *pPropAttachType = nullptr;
  273. memory_ptr<SPropValue> lpContentId, lpContentLocation, lpHidden;
  274. memory_ptr<SPropValue> lpFilename;
  275. ULONG ulAttachmentNum = 0;
  276. ULONG ulAttachmentMethod = 0;
  277. object_ptr<IMessage> lpAttachedMessage;
  278. vmime::shared_ptr<vmime::utility::inputStream> inputDataStream;
  279. vmime::shared_ptr<mapiAttachment> vmMapiAttach;
  280. vmime::shared_ptr<vmime::attachment> vmMsgAtt;
  281. std::string strContentId;
  282. std::string strContentLocation;
  283. bool bHidden = false;
  284. sending_options sopt_keep;
  285. memory_ptr<SPropValue> lpAMClass, lpAMAttach, lpMIMETag;
  286. const wchar_t *szFilename = NULL; // just a reference, don't free
  287. vmime::mediaType vmMIMEType;
  288. std::string strBoundary;
  289. bool bSendBinary = true;
  290. auto pPropAttachNum = PCpropFindProp(lpRow->lpProps, lpRow->cValues, PR_ATTACH_NUM);
  291. if (pPropAttachNum == NULL) {
  292. ec_log_err("Attachment in table not correct, no attachment number present.");
  293. return MAPI_E_NOT_FOUND;
  294. }
  295. ulAttachmentNum = pPropAttachNum->Value.ul;
  296. // check PR_ATTACH_METHOD to determine Attachment or email
  297. ulAttachmentMethod = ATTACH_BY_VALUE;
  298. pPropAttachType = PCpropFindProp(lpRow->lpProps, lpRow->cValues, PR_ATTACH_METHOD);
  299. if (pPropAttachType == NULL)
  300. ec_log_warn("Attachment method not present for attachment %d, assuming default value", ulAttachmentNum);
  301. else
  302. ulAttachmentMethod = pPropAttachType->Value.ul;
  303. if (ulAttachmentMethod == ATTACH_EMBEDDED_MSG) {
  304. vmime::shared_ptr<vmime::message> vmNewMess;
  305. std::string strBuff;
  306. vmime::utility::outputStreamStringAdapter mout(strBuff);
  307. hr = lpMessage->OpenAttach(ulAttachmentNum, nullptr, MAPI_BEST_ACCESS, &~lpAttach);
  308. if (hr != hrSuccess) {
  309. ec_log_err("Could not open message attachment %d. Error: 0x%08X", ulAttachmentNum, hr);
  310. return hr;
  311. }
  312. hr = lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, MAPI_DEFERRED_ERRORS, &~lpAttachedMessage);
  313. if (hr != hrSuccess) {
  314. ec_log_err("Could not open data of message attachment %d. Error: 0x%08X", ulAttachmentNum, hr);
  315. return hr;
  316. }
  317. // Check whether we're sending a calendar object
  318. // if so, we do not need to create a message-in-message object, but just attach the ics file.
  319. hr = HrGetOneProp(lpAttachedMessage, PR_MESSAGE_CLASS_A, &~lpAMClass);
  320. if (hr == hrSuccess &&
  321. strcmp(lpAMClass->Value.lpszA, "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}") == 0)
  322. // This is an exception message. We might get here because kopano-ical is able to
  323. // SubmitMessage for Mac Ical meeting requests. The way Outlook does this, is
  324. // send a message for each exception itself. We just ignore this exception, since
  325. // it's already in the main ical data on the top level message.
  326. return hr;
  327. // sub objects do not necessarily need to have valid recipients, so disable the test
  328. sopt_keep = sopt;
  329. sopt.no_recipients_workaround = true;
  330. sopt.msg_in_msg = true;
  331. sopt.add_received_date = false;
  332. if(sopt.alternate_boundary) {
  333. strBoundary = sopt.alternate_boundary;
  334. strBoundary += "_sub_";
  335. strBoundary += stringify(ulAttachmentNum);
  336. sopt.alternate_boundary = (char *)strBoundary.c_str();
  337. }
  338. // recursive processing of embedded message as a new e-mail
  339. hr = convertMAPIToVMIME(lpAttachedMessage, &vmNewMess);
  340. if (hr != hrSuccess)
  341. // Logging has been done by convertMAPIToVMIME()
  342. return hr;
  343. sopt = sopt_keep;
  344. vmMsgAtt = vmime::make_shared<vmime::parsedMessageAttachment>(vmNewMess);
  345. lpVMMessageBuilder->appendAttachment(vmMsgAtt);
  346. } else if (ulAttachmentMethod == ATTACH_BY_VALUE) {
  347. hr = lpMessage->OpenAttach(ulAttachmentNum, nullptr, MAPI_BEST_ACCESS, &~lpAttach);
  348. if (hr != hrSuccess) {
  349. ec_log_err("Could not open attachment %d. Error: 0x%08X", ulAttachmentNum, hr);
  350. return hr;
  351. }
  352. if (HrGetOneProp(lpAttach, PR_ATTACH_CONTENT_ID_A, &~lpContentId) == hrSuccess)
  353. strContentId = lpContentId->Value.lpszA;
  354. if (HrGetOneProp(lpAttach, PR_ATTACH_CONTENT_LOCATION_A, &~lpContentLocation) == hrSuccess)
  355. strContentLocation = lpContentLocation->Value.lpszA;
  356. if (HrGetOneProp(lpAttach, PR_ATTACHMENT_HIDDEN, &~lpHidden) == hrSuccess)
  357. bHidden = lpHidden->Value.b;
  358. // Optimize: if we're only outputting headers, we needn't bother with attachment data
  359. if(sopt.headers_only)
  360. CreateStreamOnHGlobal(nullptr, true, &~lpStream);
  361. else {
  362. hr = lpAttach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, MAPI_DEFERRED_ERRORS, &~lpStream);
  363. if (hr != hrSuccess) {
  364. ec_log_err("Could not open data of attachment %d. Error: 0x%08X", ulAttachmentNum, hr);
  365. return hr;
  366. }
  367. }
  368. try {
  369. // add ref to lpStream
  370. if (lpStream != nullptr)
  371. inputDataStream = vmime::make_shared<inputStreamMAPIAdapter>(lpStream);
  372. // Set filename
  373. szFilename = L"data.bin";
  374. if (HrGetOneProp(lpAttach, PR_ATTACH_LONG_FILENAME_W, &~lpFilename) == hrSuccess)
  375. szFilename = lpFilename->Value.lpszW;
  376. else if (HrGetOneProp(lpAttach, PR_ATTACH_FILENAME_W, &~lpFilename) == hrSuccess)
  377. szFilename = lpFilename->Value.lpszW;
  378. // Set MIME type
  379. parseMimeTypeFromFilename(szFilename, &vmMIMEType, &bSendBinary);
  380. if (HrGetOneProp(lpAttach, PR_ATTACH_MIME_TAG_A, &~lpMIMETag) == hrSuccess)
  381. vmMIMEType = lpMIMETag->Value.lpszA;
  382. static const char absent_note[] = "Attachment is missing.";
  383. vmime::mediaType note_type("text/plain");
  384. // inline attachments: Only make attachments inline if they are hidden and have a content-id or content-location.
  385. if ((!strContentId.empty() || !strContentLocation.empty()) && bHidden &&
  386. lpVMMessageBuilder->getTextPart()->getType() == vmime::mediaType(vmime::mediaTypes::TEXT, vmime::mediaTypes::TEXT_HTML))
  387. {
  388. // add inline object to html part
  389. vmime::mapiTextPart& textPart = dynamic_cast<vmime::mapiTextPart&>(*lpVMMessageBuilder->getTextPart());
  390. // had szFilename .. but how, on inline?
  391. // @todo find out how Content-Disposition receives highchar filename... always UTF-8?
  392. if (inputDataStream != nullptr)
  393. textPart.addObject(vmime::make_shared<vmime::streamContentHandler>(inputDataStream, 0), vmime::encoding("base64"), vmMIMEType, strContentId, string(), strContentLocation);
  394. else
  395. textPart.addObject(vmime::make_shared<vmime::stringContentHandler>(absent_note), vmime::encoding("base64"), note_type, strContentId, std::string(), strContentLocation);
  396. } else if (inputDataStream != nullptr) {
  397. vmMapiAttach = vmime::make_shared<mapiAttachment>(vmime::make_shared<vmime::streamContentHandler>(inputDataStream, 0),
  398. bSendBinary ? vmime::encoding("base64") : vmime::encoding("quoted-printable"),
  399. vmMIMEType, strContentId,
  400. vmime::word(m_converter.convert_to<string>(m_strCharset.c_str(), szFilename, rawsize(szFilename), CHARSET_WCHAR), m_vmCharset));
  401. // add to message (copies pointer, not data)
  402. lpVMMessageBuilder->appendAttachment(vmMapiAttach);
  403. } else {
  404. vmMapiAttach = vmime::make_shared<mapiAttachment>(vmime::make_shared<vmime::stringContentHandler>(absent_note),
  405. bSendBinary ? vmime::encoding("base64") : vmime::encoding("quoted-printable"),
  406. note_type, strContentId,
  407. vmime::word(m_converter.convert_to<std::string>(m_strCharset.c_str(), szFilename, rawsize(szFilename), CHARSET_WCHAR), m_vmCharset));
  408. lpVMMessageBuilder->appendAttachment(vmMapiAttach);
  409. }
  410. }
  411. catch (vmime::exception& e) {
  412. ec_log_err("VMIME exception: %s", e.what());
  413. return MAPI_E_CALL_FAILED;
  414. }
  415. catch (std::exception& e) {
  416. ec_log_err("STD exception on attachment: %s", e.what());
  417. return MAPI_E_CALL_FAILED;
  418. }
  419. catch (...) {
  420. ec_log_err("Unknown generic exception occurred on attachment");
  421. return MAPI_E_CALL_FAILED;
  422. }
  423. } else if (ulAttachmentMethod == ATTACH_OLE) {
  424. // Ignore ATTACH_OLE attachments, they are handled in handleTNEF()
  425. } else {
  426. ec_log_err("Attachment %d contains invalid method %d.", ulAttachmentNum, ulAttachmentMethod);
  427. hr = MAPI_E_INVALID_PARAMETER;
  428. }
  429. // ATTN: lpMapiAttach are linked in the VMMessageBuilder. The VMMessageBuilder will delete() it.
  430. return hr;
  431. }
  432. /**
  433. * Return a MIME type for the filename of an attachment.
  434. *
  435. * The MIME type is found using the extension. Also returns if the
  436. * attachment should be attached in text or binary mode. Currently
  437. * only edifact files (.edi) are forced as text attachment.
  438. *
  439. * @param[in] strFilename Widechar filename to find mimetype for
  440. * @param[out] lpMT mimetype in vmime class
  441. * @param[out] lpbSendBinary flag to specify if the attachment should be in binary or forced text format.
  442. * @return always hrSuccess
  443. *
  444. * @todo Use /etc/mime.types to find more mime types for extensions
  445. */
  446. HRESULT MAPIToVMIME::parseMimeTypeFromFilename(std::wstring strFilename, vmime::mediaType *lpMT, bool *lpbSendBinary)
  447. {
  448. std::string strExt;
  449. std::string strMedType;
  450. bool bSendBinary = true;
  451. // to lowercase
  452. transform(strFilename.begin(), strFilename.end(), strFilename.begin(), ::towlower);
  453. strExt = m_converter.convert_to<string>(m_strCharset.c_str(), strFilename, rawsize(strFilename), CHARSET_WCHAR);
  454. strExt.erase(0, strExt.find_last_of(".")+1);
  455. // application
  456. if (strExt == "bin" || strExt == "exe") {
  457. strMedType = "application/octet-stream";
  458. } else if (strExt == "ai" || strExt == "eps" || strExt == "ps") {
  459. strMedType = "application/postscript";
  460. } else if (strExt == "pdf") {
  461. strMedType = "application/pdf";
  462. } else if (strExt == "rtf") {
  463. strMedType = "application/rtf";
  464. } else if (strExt == "zip") {
  465. strMedType = "application/zip";
  466. } else if (strExt == "doc" || strExt == "dot") {
  467. strMedType = "application/msword";
  468. } else if (strExt == "mdb") {
  469. strMedType = "application/x-msaccess";
  470. } else if (strExt == "xla" || strExt == "xls" || strExt == "xlt" || strExt == "xlw") {
  471. strMedType = "application/vnd.ms-excel";
  472. } else if (strExt == "pot" || strExt == "ppt" || strExt == "pps") {
  473. strMedType = "application/vnd.ms-powerpoint";
  474. } else if (strExt == "mpp") {
  475. strMedType = "application/vnd.ms-project";
  476. } else if (strExt == "edi") {
  477. strMedType = "application/edifact";
  478. bSendBinary = false;
  479. } else if(strExt == "docm") {
  480. strMedType = "application/vnd.ms-word.document.macroEnabled.12";
  481. } else if(strExt == "docx") {
  482. strMedType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
  483. } else if(strExt == "dotm") {
  484. strMedType = "application/vnd.ms-word.template.macroEnabled.12";
  485. } else if(strExt == "dotx") {
  486. strMedType = "application/vnd.openxmlformats-officedocument.wordprocessingml.template";
  487. } else if(strExt == "potm") {
  488. strMedType = "application/vnd.ms-powerpoint.template.macroEnabled.12";
  489. } else if(strExt == "potx") {
  490. strMedType = "application/vnd.openxmlformats-officedocument.presentationml.template";
  491. } else if(strExt == "ppam") {
  492. strMedType = "application/vnd.ms-powerpoint.addin.macroEnabled.12";
  493. } else if(strExt == "ppsm") {
  494. strMedType = "application/vnd.ms-powerpoint.slideshow.macroEnabled.12";
  495. } else if(strExt == "ppsx") {
  496. strMedType = "application/vnd.openxmlformats-officedocument.presentationml.slideshow";
  497. } else if(strExt == "pptm") {
  498. strMedType = "application/vnd.ms-powerpoint.presentation.macroEnabled.12";
  499. } else if(strExt == "pptx") {
  500. strMedType = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
  501. } else if(strExt == "xlam") {
  502. strMedType = "application/vnd.ms-excel.addin.macroEnabled.12";
  503. } else if(strExt == "xlsb") {
  504. strMedType = "application/vnd.ms-excel.sheet.binary.macroEnabled.12";
  505. } else if(strExt == "xlsm") {
  506. strMedType = "application/vnd.ms-excel.sheet.macroEnabled.12";
  507. } else if(strExt == "xltm") {
  508. strMedType = "application/vnd.ms-excel.template.macroEnabled.12";
  509. } else if(strExt == "xlsx") {
  510. strMedType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
  511. } else if(strExt == "xltx") {
  512. strMedType = "application/vnd.openxmlformats-officedocument.spreadsheetml.template";
  513. }
  514. // audio
  515. else if (strExt == "ua") {
  516. strMedType = "audio/basic";
  517. } else if (strExt == "wav") {
  518. strMedType = "audio/x-wav";
  519. } else if (strExt == "mid") {
  520. strMedType = "audio/x-midi";
  521. }
  522. // image
  523. else if (strExt == "gif") {
  524. strMedType = "image/gif";
  525. } else if (strExt == "jpg" || strExt == "jpe" || strExt == "jpeg") {
  526. strMedType = "image/jpeg";
  527. } else if (strExt == "png") {
  528. strMedType = "image/png";
  529. } else if (strExt == "bmp") {
  530. strMedType = "image/x-ms-bmp";
  531. } else if (strExt == "tiff") {
  532. strMedType = "image/tiff";
  533. } else if (strExt == "xbm") {
  534. strMedType = "image/xbm";
  535. }
  536. // video
  537. else if (strExt == "mpg" || strExt == "mpe" || strExt == "mpeg") {
  538. strMedType = "video/mpeg";
  539. } else if (strExt == "qt" || strExt == "mov") {
  540. strMedType = "video/quicktime";
  541. } else if (strExt == "avi") {
  542. strMedType = "video/x-msvideo";
  543. }
  544. else {
  545. strMedType = "application/octet-stream";
  546. }
  547. *lpMT = vmime::mediaType(strMedType);
  548. *lpbSendBinary = bSendBinary;
  549. return hrSuccess;
  550. }
  551. /**
  552. * Loops through the attachment table and copies all attachments to vmime object
  553. *
  554. * @param[in] lpMessage Message to open attachments on
  555. * @param[in] charset Charset to use for filename encoding of attachment
  556. * @param[in] lpVMMessageBuilder messagebuilder to add attachments to
  557. * @return Mapi error code
  558. */
  559. HRESULT MAPIToVMIME::handleAttachments(IMessage* lpMessage, vmime::messageBuilder *lpVMMessageBuilder) {
  560. HRESULT hr = hrSuccess;
  561. rowset_ptr pRows;
  562. object_ptr<IMAPITable> lpAttachmentTable;
  563. static constexpr const SizedSSortOrderSet(1, sosRTFSeq) =
  564. {1, 0, 0, {{PR_RENDERING_POSITION, TABLE_SORT_ASCEND}}};
  565. // get attachment table
  566. hr = lpMessage->GetAttachmentTable(0, &~lpAttachmentTable);
  567. if (hr != hrSuccess) {
  568. ec_log_err("Unable to open attachment table. Error: 0x%08X", hr);
  569. return hr;
  570. }
  571. hr = HrQueryAllRows(lpAttachmentTable, nullptr, nullptr, sosRTFSeq, 0, &~pRows);
  572. if (hr != hrSuccess) {
  573. ec_log_err("Unable to fetch rows of attachment table. Error: 0x%08X", hr);
  574. return hr;
  575. }
  576. for (ULONG i = 0; i < pRows->cRows; ++i) {
  577. // if one attachment fails, we're not sending what the user intended to send so abort. Logging was done in handleSingleAttachment()
  578. hr = handleSingleAttachment(lpMessage, &pRows->aRow[i], lpVMMessageBuilder);
  579. if (sopt.ignore_missing_attachments && hr == MAPI_E_NOT_FOUND)
  580. continue;
  581. if (hr != hrSuccess)
  582. return hr;
  583. }
  584. return hrSuccess;
  585. }
  586. /**
  587. * Force the boundary string to a predefined value.
  588. *
  589. * Used for gateway, because vmime will otherwise always use a newly
  590. * generated boundary string. This is not good for consistency in the
  591. * IMAP gateway. This is done recursively for all multipart bodies in
  592. * the e-mail.
  593. *
  594. * @param[in] vmHeader The header block to find multipart Content-Type header in, and modify.
  595. * @param[in] vmBody vmBody part of vmHeader. Used to recurse for more boundaries to change.
  596. * @param[in] boundary new boundary string to use.
  597. * @return Always hrSuccess
  598. */
  599. HRESULT MAPIToVMIME::setBoundaries(vmime::shared_ptr<vmime::header> vmHeader,
  600. vmime::shared_ptr<vmime::body> vmBody, const std::string& boundary)
  601. {
  602. if (!vmHeader->hasField(vmime::fields::CONTENT_TYPE))
  603. return hrSuccess;
  604. auto vmCTF = vmime::dynamicCast<vmime::contentTypeField>(vmHeader->findField(vmime::fields::CONTENT_TYPE));
  605. if (vmime::dynamicCast<vmime::mediaType>(vmCTF->getValue())->getType() == vmime::mediaTypes::MULTIPART) {
  606. // This is a multipart, so set the boundary for this part
  607. vmCTF->setBoundary(boundary);
  608. // Set boundaries on children also
  609. for (size_t i = 0; i < vmBody->getPartCount(); ++i) {
  610. std::ostringstream os;
  611. vmime::shared_ptr<vmime::bodyPart> vmBodyPart = vmBody->getPartAt(i);
  612. os << boundary << "_" << i;
  613. setBoundaries(vmBodyPart->getHeader(), vmBodyPart->getBody(), os.str());
  614. }
  615. }
  616. return hrSuccess;
  617. }
  618. /**
  619. * Build a normal vmime message from a MAPI lpMessage.
  620. *
  621. * @param[in] lpMessage MAPI message to convert
  622. * @param[in] bSkipContent set to true if only the headers of the e-mail are required to obtain
  623. * @param[in] charset Charset to convert message in, valid for headers and bodies.
  624. * @param[out] lpvmMessage vmime message version of lpMessage
  625. * @return Mapi error code
  626. */
  627. HRESULT MAPIToVMIME::BuildNoteMessage(IMessage *lpMessage,
  628. vmime::shared_ptr<vmime::message> *lpvmMessage, unsigned int flags)
  629. {
  630. HRESULT hr = hrSuccess;
  631. memory_ptr<SPropValue> lpDeliveryDate, lpTransportHeaders;
  632. vmime::messageBuilder vmMessageBuilder;
  633. vmime::shared_ptr<vmime::message> vmMessage;
  634. // construct the message..
  635. try {
  636. // messageBuilder is body and simple headers only (to/cc/subject/...)
  637. hr = fillVMIMEMail(lpMessage, flags & MTV_SKIP_CONTENT, &vmMessageBuilder);
  638. if (hr != hrSuccess)
  639. return hr; // Logging has been done in fillVMIMEMail()
  640. vmMessage = vmMessageBuilder.construct();
  641. // message builder has set Date header to now(). this will be overwritten.
  642. auto vmHeader = vmMessage->getHeader();
  643. hr = handleExtraHeaders(lpMessage, vmHeader, flags);
  644. if (hr!= hrSuccess)
  645. return hr;
  646. // from/sender headers
  647. hr = handleSenderInfo(lpMessage, vmHeader);
  648. if (hr!= hrSuccess)
  649. return hr;
  650. // add reply to email, ignore errors
  651. hr = handleReplyTo(lpMessage, vmHeader);
  652. if (hr != hrSuccess) {
  653. ec_log_warn("Unable to set reply-to address");
  654. hr = hrSuccess;
  655. }
  656. // Set consistent boundary (optional)
  657. if (sopt.alternate_boundary != nullptr)
  658. setBoundaries(vmMessage->getHeader(), vmMessage->getBody(), sopt.alternate_boundary);
  659. HrGetOneProp(lpMessage, PR_MESSAGE_DELIVERY_TIME, &~lpDeliveryDate);
  660. // If we're sending a msg-in-msg, use the original date of that message
  661. if (sopt.msg_in_msg && lpDeliveryDate != nullptr)
  662. vmHeader->Date()->setValue(FiletimeTovmimeDatetime(lpDeliveryDate->Value.ft));
  663. // Regenerate some headers if available (basically a copy of the headers in
  664. // PR_TRANSPORT_MESSAGE_HEADERS)
  665. // currently includes: Received*, Return-Path, List* and Precedence.
  666. // New e-mails should not have this property.
  667. if (HrGetOneProp(lpMessage, PR_TRANSPORT_MESSAGE_HEADERS_A, &~lpTransportHeaders) == hrSuccess &&
  668. lpTransportHeaders != nullptr) {
  669. try {
  670. int j=0;
  671. vmime::header headers;
  672. std::string strHeaders = lpTransportHeaders->Value.lpszA;
  673. strHeaders += "\r\n\r\n";
  674. headers.parse(strHeaders);
  675. for (size_t i = 0; i < headers.getFieldCount(); ++i) {
  676. vmime::shared_ptr<vmime::headerField> vmField = headers.getFieldAt(i);
  677. std::string name = vmField->getName();
  678. // Received checks start of string to accept Received-SPF
  679. if (strncasecmp(name.c_str(), vmime::fields::RECEIVED, strlen(vmime::fields::RECEIVED)) == 0 ||
  680. strcasecmp(name.c_str(), vmime::fields::RETURN_PATH) == 0)
  681. // Insert in same order at start of headers
  682. vmHeader->insertFieldBefore(j++, vmField);
  683. else if (strncasecmp(name.c_str(), "list-", strlen("list-")) == 0 ||
  684. strcasecmp(name.c_str(), "precedence") == 0)
  685. // Just append at the end of this list, order is not important
  686. vmHeader->appendField(vmime::dynamicCast<vmime::headerField>(vmField->clone()));
  687. }
  688. } catch (vmime::exception& e) {
  689. ec_log_warn("VMIME exception adding extra headers: %s", e.what());
  690. }
  691. catch(...) {
  692. // If we can't parse, just ignore
  693. }
  694. }
  695. // POP3 wants the delivery date as received header to correctly show in outlook
  696. if (sopt.add_received_date && lpDeliveryDate) {
  697. auto hff = vmime::headerFieldFactory::getInstance();
  698. vmime::shared_ptr<vmime::headerField> rf = hff->create(vmime::fields::RECEIVED);
  699. vmime::dynamicCast<vmime::relay>(rf->getValue())->setDate(FiletimeTovmimeDatetime(lpDeliveryDate->Value.ft));
  700. // set received header at the start
  701. vmHeader->insertFieldBefore(0, rf);
  702. }
  703. // no hr checking
  704. handleXHeaders(lpMessage, vmHeader, flags);
  705. }
  706. catch (vmime::exception& e) {
  707. ec_log_err("VMIME exception: %s", e.what());
  708. return MAPI_E_CALL_FAILED; // set real error
  709. }
  710. catch (std::exception& e) {
  711. ec_log_err("STD exception on note message: %s", e.what());
  712. return MAPI_E_CALL_FAILED; // set real error
  713. }
  714. catch (...) {
  715. ec_log_err("Unknown generic exception on note message");
  716. return MAPI_E_CALL_FAILED;
  717. }
  718. *lpvmMessage = std::move(vmMessage);
  719. return hrSuccess;
  720. }
  721. /**
  722. * Build an MDN vmime message from a MAPI lpMessage.
  723. *
  724. * MDN (Mail Disposition Notification) is a read receipt message,
  725. * which notifies a sender that the e-mail was read.
  726. *
  727. * @param[in] lpMessage MAPI message to convert
  728. * @param[in] charset Charset to convert message in, valid for headers and bodies.
  729. * @param[out] lpvmMessage vmime message version of lpMessage
  730. * @return Mapi error code
  731. */
  732. HRESULT MAPIToVMIME::BuildMDNMessage(IMessage *lpMessage,
  733. vmime::shared_ptr<vmime::message> *lpvmMessage)
  734. {
  735. HRESULT hr = hrSuccess;
  736. vmime::shared_ptr<vmime::message> vmMessage;
  737. object_ptr<IStream> lpBodyStream;
  738. memory_ptr<SPropValue> lpiNetMsgId, lpMsgClass, lpSubject;
  739. object_ptr<IMAPITable> lpRecipientTable;
  740. vmime::mailbox expeditor; // From
  741. string strMDNText;
  742. vmime::disposition dispo;
  743. string reportingUA; //empty
  744. std::vector<string> reportingUAProducts; //empty
  745. vmime::shared_ptr<vmime::message> vmMsgOriginal;
  746. vmime::shared_ptr<vmime::address> vmRecipientbox;
  747. string strActionMode;
  748. std::wstring strOut;
  749. // sender information
  750. std::wstring strEmailAdd, strName, strType;
  751. std::wstring strRepEmailAdd, strRepName, strRepType;
  752. if (lpMessage == nullptr || m_lpAdrBook == nullptr ||
  753. lpvmMessage == nullptr)
  754. return MAPI_E_INVALID_OBJECT;
  755. try {
  756. rowset_ptr pRows;
  757. vmMsgOriginal = vmime::make_shared<vmime::message>();
  758. // Create original vmime message
  759. if (HrGetOneProp(lpMessage, PR_INTERNET_MESSAGE_ID_A, &~lpiNetMsgId) == hrSuccess) {
  760. vmMsgOriginal->getHeader()->OriginalMessageId()->setValue(lpiNetMsgId->Value.lpszA);
  761. vmMsgOriginal->getHeader()->MessageId()->setValue(lpiNetMsgId->Value.lpszA);
  762. }
  763. // Create Recipient
  764. hr = lpMessage->GetRecipientTable(MAPI_DEFERRED_ERRORS, &~lpRecipientTable);
  765. if (hr != hrSuccess) {
  766. ec_log_err("Unable to open MDN recipient table. Error: 0x%08X", hr);
  767. return hr;
  768. }
  769. hr = HrQueryAllRows(lpRecipientTable, nullptr, nullptr, nullptr, 0, &~pRows);
  770. if (hr != hrSuccess) {
  771. ec_log_err("Unable to read MDN recipient table. Error: 0x%08X", hr);
  772. return hr;
  773. }
  774. if (pRows->cRows == 0) {
  775. if (sopt.no_recipients_workaround == false) {
  776. ec_log_err("No MDN recipient found");
  777. return MAPI_E_NOT_FOUND;
  778. }
  779. // no recipient, but need to continue ... is this correct??
  780. vmRecipientbox = vmime::make_shared<vmime::mailbox>(string("undisclosed-recipients"));
  781. } else {
  782. hr = getMailBox(&pRows->aRow[0], &vmRecipientbox);
  783. if (hr != hrSuccess)
  784. return hr; // Logging done in getMailBox
  785. }
  786. // if vmRecipientbox is a vmime::mailboxGroup the dynamicCast to vmime::mailbox failed,
  787. // so never send a MDN to a group.
  788. if (vmRecipientbox->isGroup()) {
  789. ec_log_err("Not possible to send a MDN to a group");
  790. return MAPI_E_CORRUPT_DATA;
  791. }
  792. vmime::mdn::sendableMDNInfos mdnInfos(vmMsgOriginal, *vmime::dynamicCast<vmime::mailbox>(vmRecipientbox).get());
  793. //FIXME: Get the ActionMode and sending mode from the property 0x0081001E.
  794. // And the type in property 0x0080001E.
  795. // Vmime is broken, The RFC 3798 says: "The action type can be 'manual-action' or 'automatic-action'"
  796. // but VMime can set only 'manual' or 'automatic' so should be add the string '-action'
  797. strActionMode = vmime::dispositionActionModes::MANUAL;
  798. strActionMode+= "-action";
  799. dispo.setActionMode(strActionMode);
  800. dispo.setSendingMode(vmime::dispositionSendingModes::SENT_MANUALLY);
  801. if (HrGetOneProp(lpMessage, PR_MESSAGE_CLASS_A, &~lpMsgClass) != hrSuccess) {
  802. ec_log_err("MDN message has no class.");
  803. return MAPI_E_CORRUPT_DATA;
  804. }
  805. if(strcasecmp(lpMsgClass->Value.lpszA, "REPORT.IPM.Note.IPNNRN") == 0)
  806. dispo.setType(vmime::dispositionTypes::DELETED);
  807. else // if(strcasecmp(lpMsgClass->Value.lpszA, "REPORT.IPM.Note.IPNRN") == 0)
  808. dispo.setType(vmime::dispositionTypes::DISPLAYED);
  809. strMDNText.clear();// Default Empty body
  810. hr = lpMessage->OpenProperty(PR_BODY_W, &IID_IStream, 0, 0, &~lpBodyStream);
  811. if (hr == hrSuccess) {
  812. std::wstring strBuffer;
  813. hr = Util::HrStreamToString(lpBodyStream, strBuffer);
  814. if (hr != hrSuccess) {
  815. ec_log_err("Unable to read MDN message body.");
  816. return hr;
  817. }
  818. strMDNText = m_converter.convert_to<string>(m_strCharset.c_str(), strBuffer, rawsize(strBuffer), CHARSET_WCHAR);
  819. }
  820. // Store owner, actual sender
  821. hr = HrGetAddress(m_lpAdrBook, lpMessage, PR_SENDER_ENTRYID, PR_SENDER_NAME_W, PR_SENDER_ADDRTYPE_W, PR_SENDER_EMAIL_ADDRESS_W, strName, strType, strEmailAdd);
  822. if(hr != hrSuccess) {
  823. ec_log_err("Unable to get MDN sender information. Error: 0x%08X", hr);
  824. return hr;
  825. }
  826. // Ignore errors here and let strRep* untouched
  827. HrGetAddress(m_lpAdrBook, lpMessage, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME_W, PR_SENT_REPRESENTING_ADDRTYPE_W, PR_SENT_REPRESENTING_EMAIL_ADDRESS_W, strRepName, strRepType, strRepEmailAdd);
  828. expeditor.setEmail(m_converter.convert_to<string>(strEmailAdd));
  829. if(!strName.empty())
  830. expeditor.setName(getVmimeTextFromWide(strName));
  831. else
  832. expeditor.setName(getVmimeTextFromWide(strEmailAdd));
  833. //Create MDN message, does vmime convert again with charset, or was this wrong before?
  834. vmMessage = vmime::mdn::MDNHelper::buildMDN(mdnInfos, strMDNText, m_vmCharset, expeditor, dispo, reportingUA, reportingUAProducts);
  835. // rewrite subject
  836. if (HrGetOneProp(lpMessage, PR_SUBJECT_W, &~lpSubject) == hrSuccess) {
  837. removeEnters(lpSubject->Value.lpszW);
  838. strOut = lpSubject->Value.lpszW;
  839. vmMessage->getHeader()->Subject()->setValue(getVmimeTextFromWide(strOut));
  840. }
  841. if (!strRepEmailAdd.empty()) {
  842. vmMessage->getHeader()->Sender()->setValue(expeditor);
  843. if (strRepName.empty() || strRepName == strRepEmailAdd)
  844. vmMessage->getHeader()->From()->setValue(vmime::mailbox(m_converter.convert_to<string>(strRepEmailAdd)));
  845. else
  846. vmMessage->getHeader()->From()->setValue(vmime::mailbox(getVmimeTextFromWide(strRepName), m_converter.convert_to<string>(strRepEmailAdd)));
  847. }
  848. }
  849. catch (vmime::exception& e) {
  850. ec_log_err("VMIME exception: %s", e.what());
  851. return MAPI_E_CALL_FAILED; // set real error
  852. }
  853. catch (std::exception& e) {
  854. ec_log_err("STD exception on MDN message: %s", e.what());
  855. return MAPI_E_CALL_FAILED; // set real error
  856. }
  857. catch (...) {
  858. ec_log_err("Unknown generic exception on MDN message");
  859. return MAPI_E_CALL_FAILED;
  860. }
  861. *lpvmMessage = std::move(vmMessage);
  862. return hrSuccess;
  863. }
  864. /**
  865. * Returns a description of the conversion error that occurred.
  866. *
  867. * @return Description of the error if convertMAPIToVMIME returned an error.
  868. */
  869. std::wstring MAPIToVMIME::getConversionError(void) const
  870. {
  871. return m_strError;
  872. }
  873. /**
  874. * Entry point to start the conversion from lpMessage to message.
  875. *
  876. * @param[in] lpMessage MAPI message to convert
  877. * @param[out] lpvmMessage message to send to SMTP or Gateway client
  878. * @return Mapi error code
  879. */
  880. HRESULT MAPIToVMIME::convertMAPIToVMIME(IMessage *lpMessage,
  881. vmime::shared_ptr<vmime::message> *lpvmMessage, unsigned int flags)
  882. {
  883. HRESULT hr = hrSuccess;
  884. memory_ptr<SPropValue> lpInternetCPID, lpMsgClass;
  885. vmime::shared_ptr<vmime::message> vmMessage;
  886. const char *lpszCharset = NULL;
  887. std::unique_ptr<char[]> lpszRawSMTP;
  888. object_ptr<IMAPITable> lpAttachmentTable;
  889. const SPropValue *lpPropAttach = nullptr;
  890. object_ptr<IAttach> lpAttach;
  891. object_ptr<IStream> lpStream;
  892. STATSTG sStreamStat;
  893. static constexpr const SizedSPropTagArray(2, sPropAttachColumns) =
  894. {2, { PR_ATTACH_NUM, PR_ATTACH_MIME_TAG}};
  895. if (HrGetOneProp(lpMessage, PR_MESSAGE_CLASS_A, &~lpMsgClass) != hrSuccess) {
  896. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~lpMsgClass);
  897. if (hr != hrSuccess)
  898. return hr;
  899. lpMsgClass->ulPropTag = PR_MESSAGE_CLASS_A;
  900. lpMsgClass->Value.lpszA = const_cast<char *>("IPM.Note");
  901. }
  902. // Get the outgoing charset we want to be using
  903. if (HrGetOneProp(lpMessage, PR_INTERNET_CPID, &~lpInternetCPID) == hrSuccess &&
  904. HrGetCharsetByCP(lpInternetCPID->Value.ul, &lpszCharset) == hrSuccess)
  905. {
  906. m_strHTMLCharset = lpszCharset;
  907. if (sopt.force_utf8)
  908. m_vmCharset = MAPI_CHARSET_STRING;
  909. else
  910. m_vmCharset = m_strHTMLCharset;
  911. } else {
  912. // default to UTF-8 if not set
  913. m_vmCharset = MAPI_CHARSET_STRING;
  914. }
  915. if (strncasecmp(lpMsgClass->Value.lpszA, "IPM.Note", 8) && strncasecmp(lpMsgClass->Value.lpszA, "REPORT.IPM.Note", 15)) {
  916. // Outlook sets some other incorrect charset for meeting requests and such,
  917. // so for non-email we upgrade this to UTF-8
  918. m_vmCharset = MAPI_CHARSET_STRING;
  919. } else if (m_vmCharset == "us-ascii") {
  920. // silently upgrade, since recipients and attachment filenames may contain high-chars, while the body does not.
  921. if (sopt.charset_upgrade && strlen(sopt.charset_upgrade))
  922. m_vmCharset = sopt.charset_upgrade;
  923. else
  924. m_vmCharset = "windows-1252";
  925. }
  926. // Add iconv tag to convert non-exising chars without a fuss
  927. m_strCharset = m_vmCharset.getName() + "//TRANSLIT";
  928. if ((strcasecmp(lpMsgClass->Value.lpszA, "REPORT.IPM.Note.IPNNRN") == 0 ||
  929. strcasecmp(lpMsgClass->Value.lpszA, "REPORT.IPM.Note.IPNRN") == 0)
  930. )
  931. {
  932. // Create a read receipt message
  933. hr = BuildMDNMessage(lpMessage, &vmMessage);
  934. if(hr != hrSuccess)
  935. return hr;
  936. } else if ((strcasecmp(lpMsgClass->Value.lpszA, "IPM.Note.SMIME.MultiPartSigned") == 0) ||
  937. (strcasecmp(lpMsgClass->Value.lpszA, "IPM.Note.SMIME") == 0))
  938. {
  939. rowset_ptr lpRows;
  940. // - find attachment, and convert to char, and place in lpszRawSMTP
  941. // - normal convert the message, but only from/to headers and such .. nothing else
  942. hr = lpMessage->GetAttachmentTable(0, &~lpAttachmentTable);
  943. if (hr != hrSuccess) {
  944. ec_log_err("Could not get attachment table of signed attachment. Error: 0x%08X", hr);
  945. return hr;
  946. }
  947. // set columns to get pr attach mime tag and pr attach num only.
  948. hr = lpAttachmentTable->SetColumns(sPropAttachColumns, 0);
  949. if (hr != hrSuccess) {
  950. ec_log_err("Could set table contents of attachment table of signed attachment. Error: 0x%08X", hr);
  951. return hr;
  952. }
  953. hr = HrQueryAllRows(lpAttachmentTable, nullptr, nullptr, nullptr, 0, &~lpRows);
  954. if (hr != hrSuccess) {
  955. ec_log_err("Could not get table contents of attachment table of signed attachment. Error: 0x%08X", hr);
  956. return hr;
  957. }
  958. if (lpRows->cRows != 1)
  959. goto normal;
  960. lpPropAttach = PCpropFindProp(lpRows->aRow[0].lpProps, lpRows->aRow[0].cValues, PR_ATTACH_MIME_TAG);
  961. if (!lpPropAttach)
  962. goto normal;
  963. lpPropAttach = PCpropFindProp(lpRows->aRow[0].lpProps, lpRows->aRow[0].cValues, PR_ATTACH_NUM);
  964. if (!lpPropAttach)
  965. goto normal;
  966. hr = lpMessage->OpenAttach(lpPropAttach->Value.ul, nullptr, MAPI_BEST_ACCESS, &~lpAttach);
  967. if (hr != hrSuccess) {
  968. ec_log_err("Could not open signed attachment. Error: 0x%08X", hr);
  969. return hr;
  970. }
  971. hr = lpAttach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, MAPI_DEFERRED_ERRORS, &~lpStream);
  972. if (hr != hrSuccess) {
  973. ec_log_err("Could not open data of signed attachment. Error: 0x%08X", hr);
  974. return hr;
  975. }
  976. hr = lpStream->Stat(&sStreamStat, 0);
  977. if (hr != hrSuccess) {
  978. ec_log_err("Could not find size of signed attachment. Error: 0x%08X", hr);
  979. return hr;
  980. }
  981. lpszRawSMTP.reset(new char[(ULONG)sStreamStat.cbSize.QuadPart+1]);
  982. hr = lpStream->Read(lpszRawSMTP.get(), (ULONG)sStreamStat.cbSize.QuadPart, NULL);
  983. if (hr != hrSuccess) {
  984. ec_log_err("Could not read data of signed attachment. Error: 0x%08X", hr);
  985. return hr;
  986. }
  987. lpszRawSMTP[(ULONG)sStreamStat.cbSize.QuadPart] = '\0';
  988. // build the message, but without the bodies and attachments
  989. hr = BuildNoteMessage(lpMessage, &vmMessage, flags | MTV_SKIP_CONTENT);
  990. if (hr != hrSuccess)
  991. return hr;
  992. // remove excess headers
  993. if (vmMessage->getHeader()->hasField(vmime::fields::CONTENT_TYPE))
  994. vmMessage->getHeader()->removeField(vmMessage->getHeader()->findField(vmime::fields::CONTENT_TYPE));
  995. if (vmMessage->getHeader()->hasField(vmime::fields::CONTENT_TRANSFER_ENCODING))
  996. vmMessage->getHeader()->removeField(vmMessage->getHeader()->findField(vmime::fields::CONTENT_TRANSFER_ENCODING));
  997. if (strcasecmp(lpMsgClass->Value.lpszA, "IPM.Note.SMIME") != 0) {
  998. auto vmSMIMEMessage = vmime::make_shared<SMIMEMessage>();
  999. // not sure why this was needed, and causes problems, eg ZCP-12994.
  1000. //vmMessage->getHeader()->removeField(vmMessage->getHeader()->findField(vmime::fields::MIME_VERSION));
  1001. *vmSMIMEMessage->getHeader() = *vmMessage->getHeader();
  1002. vmSMIMEMessage->setSMIMEBody(lpszRawSMTP.get());
  1003. vmMessage = vmSMIMEMessage;
  1004. } else {
  1005. // encoded mail, set data as only mail body
  1006. memory_ptr<MAPINAMEID> lpNameID;
  1007. memory_ptr<SPropTagArray> lpPropTags;
  1008. memory_ptr<SPropValue> lpPropContentType;
  1009. const char *lpszContentType = NULL;
  1010. hr = MAPIAllocateBuffer(sizeof(MAPINAMEID), &~lpNameID);
  1011. if (hr != hrSuccess) {
  1012. ec_log_err("Not enough memory. Error: 0x%08X", hr);
  1013. return hr;
  1014. }
  1015. lpNameID->lpguid = (GUID*)&PS_INTERNET_HEADERS;
  1016. lpNameID->ulKind = MNID_STRING;
  1017. lpNameID->Kind.lpwstrName = const_cast<wchar_t *>(L"Content-Type");
  1018. hr = lpMessage->GetIDsFromNames(1, &+lpNameID, MAPI_CREATE, &~lpPropTags);
  1019. if (hr != hrSuccess) {
  1020. ec_log_err("Unable to read encrypted mail properties. Error: 0x%08X", hr);
  1021. return hr;
  1022. }
  1023. if (HrGetOneProp(lpMessage, CHANGE_PROP_TYPE(lpPropTags->aulPropTag[0], PT_STRING8), &~lpPropContentType) == hrSuccess)
  1024. lpszContentType = lpPropContentType->Value.lpszA;
  1025. else
  1026. // default, or exit?
  1027. lpszContentType = "application/x-pkcs7-mime;smime-type=enveloped-data;name=smime.p7m";
  1028. vmMessage->getHeader()->ContentType()->parse(lpszContentType);
  1029. // copy via string so we can set the size of the string since it's binary
  1030. vmime::string inString(lpszRawSMTP.get(), (size_t)sStreamStat.cbSize.QuadPart);
  1031. vmMessage->getBody()->setContents(vmime::make_shared<vmime::stringContentHandler>(inString));
  1032. // vmime now encodes the body too, so I don't have to
  1033. vmMessage->getHeader()->appendField(vmime::headerFieldFactory::getInstance()->create(vmime::fields::CONTENT_TRANSFER_ENCODING, "base64"));
  1034. }
  1035. } else {
  1036. normal:
  1037. // Create default
  1038. hr = BuildNoteMessage(lpMessage, &vmMessage, flags);
  1039. if (hr != hrSuccess)
  1040. return hr;
  1041. }
  1042. *lpvmMessage = std::move(vmMessage);
  1043. return hrSuccess;
  1044. }
  1045. /**
  1046. * Basic conversion of mapi message to messageBuilder.
  1047. *
  1048. * @param[in] lpMessage MAPI message to convert
  1049. * @param[in] bSkipContent set to true if only the headers of the e-mail are required to obtain
  1050. * @param[out] lpVMMessageBuilder vmime messagebuilder, used to construct the mail later.
  1051. * @param[in] charset Charset to convert message in, valid for headers and bodies.
  1052. * @return Mapi error code
  1053. */
  1054. HRESULT MAPIToVMIME::fillVMIMEMail(IMessage *lpMessage, bool bSkipContent, vmime::messageBuilder *lpVMMessageBuilder) {
  1055. std::wstring strOut;
  1056. HRESULT hr = hrSuccess;
  1057. memory_ptr<SPropValue> lpSubject;
  1058. eBestBody bestBody = plaintext;
  1059. try {
  1060. if (HrGetOneProp(lpMessage, PR_SUBJECT_W, &~lpSubject) == hrSuccess) {
  1061. removeEnters(lpSubject->Value.lpszW);
  1062. strOut = lpSubject->Value.lpszW;
  1063. }
  1064. else
  1065. strOut.clear();
  1066. // we can't ignore any errors.. the sender should know if an email is not sent correctly
  1067. lpVMMessageBuilder->setSubject(getVmimeTextFromWide(strOut));
  1068. // handle recipients
  1069. hr = processRecipients(lpMessage, lpVMMessageBuilder);
  1070. if (hr != hrSuccess)
  1071. // Logging has been done in processRecipients()
  1072. return hr;
  1073. if (!bSkipContent) {
  1074. // handle text
  1075. hr = handleTextparts(lpMessage, lpVMMessageBuilder, &bestBody);
  1076. if (hr != hrSuccess)
  1077. return hr;
  1078. // handle attachments
  1079. hr = handleAttachments(lpMessage, lpVMMessageBuilder);
  1080. if (hr != hrSuccess)
  1081. return hr;
  1082. // check for TNEF information, and add winmail.dat if needed
  1083. hr = handleTNEF(lpMessage, lpVMMessageBuilder, bestBody);
  1084. if (hr != hrSuccess)
  1085. return hr;
  1086. }
  1087. /*
  1088. To convert a VMMessageBuilder to a VMMessage object, an 'expeditor' needs to be set in vmime
  1089. later on, this from will be overwritten .. so maybe replace this with a dummy value?
  1090. */
  1091. std::wstring strName, strType, strEmAdd;
  1092. hr = HrGetAddress(m_lpAdrBook, lpMessage, PR_SENDER_ENTRYID, PR_SENDER_NAME_W, PR_SENDER_ADDRTYPE_W, PR_SENDER_EMAIL_ADDRESS_W, strName, strType, strEmAdd);
  1093. if (hr != hrSuccess) {
  1094. ec_log_err("Unable to get sender information. Error: 0x%08X", hr);
  1095. return hr;
  1096. }
  1097. if (!strName.empty())
  1098. lpVMMessageBuilder->setExpeditor(vmime::mailbox(getVmimeTextFromWide(strName), m_converter.convert_to<string>(strEmAdd)));
  1099. else
  1100. lpVMMessageBuilder->setExpeditor(vmime::mailbox(m_converter.convert_to<string>(strEmAdd)));
  1101. // sender and reply-to is set elsewhere because it can only be done on a message object...
  1102. }
  1103. catch (vmime::exception& e) {
  1104. ec_log_err("VMIME exception: %s", e.what());
  1105. return MAPI_E_CALL_FAILED; // set real error
  1106. }
  1107. catch (std::exception& e) {
  1108. ec_log_err("STD exception on fill message: %s", e.what());
  1109. return MAPI_E_CALL_FAILED; // set real error
  1110. }
  1111. catch (...) {
  1112. ec_log_err("Unknown generic exception on fill message");
  1113. return MAPI_E_CALL_FAILED;
  1114. }
  1115. return hrSuccess;
  1116. }
  1117. /**
  1118. * Make a vmime::mailbox from a recipient table row. If this function
  1119. * fails in the gateway, thunderbird will stop parsing the whole
  1120. * folder, and not just the single message.
  1121. *
  1122. * @param[in] lpRow One row from the recipient table.
  1123. * @param[out] lpvmMailbox vmime::mailbox object containing the recipient.
  1124. *
  1125. * @return MAPI error code
  1126. * @retval MAPI_E_INVALID_PARAMTER email address in row is not usable for the SMTP protocol
  1127. */
  1128. HRESULT MAPIToVMIME::getMailBox(LPSRow lpRow,
  1129. vmime::shared_ptr<vmime::address> *lpvmMailbox)
  1130. {
  1131. HRESULT hr;
  1132. vmime::shared_ptr<vmime::address> vmMailboxNew;
  1133. std::wstring strName, strEmail, strType;
  1134. hr = HrGetAddress(m_lpAdrBook, lpRow->lpProps, lpRow->cValues, PR_ENTRYID, PR_DISPLAY_NAME_W, PR_ADDRTYPE_W, PR_EMAIL_ADDRESS_W, strName, strType, strEmail);
  1135. if(hr != hrSuccess) {
  1136. ec_log_err("Unable to create mailbox. Error: %08X", hr);
  1137. return hr;
  1138. }
  1139. auto pPropObjectType = PCpropFindProp(lpRow->lpProps, lpRow->cValues, PR_OBJECT_TYPE);
  1140. if (strName.empty() && !strEmail.empty()) {
  1141. // email address only
  1142. vmMailboxNew = vmime::make_shared<vmime::mailbox>(m_converter.convert_to<string>(strEmail));
  1143. } else if (strEmail.find('@') != string::npos) {
  1144. // email with fullname
  1145. vmMailboxNew = vmime::make_shared<vmime::mailbox>(getVmimeTextFromWide(strName), m_converter.convert_to<string>(strEmail));
  1146. } else if (pPropObjectType && pPropObjectType->Value.ul == MAPI_DISTLIST) {
  1147. // if mailing to a group without email address
  1148. vmMailboxNew = vmime::make_shared<vmime::mailboxGroup>(getVmimeTextFromWide(strName));
  1149. } else if (sopt.no_recipients_workaround == true) {
  1150. // gateway must always return a mailbox object
  1151. if (strEmail.empty())
  1152. strEmail = L"@"; // force having an address to avoid vmime problems
  1153. vmMailboxNew = vmime::make_shared<vmime::mailbox>(getVmimeTextFromWide(strName), m_converter.convert_to<string>(strEmail));
  1154. } else {
  1155. if (strEmail.empty()) {
  1156. // not an email address and not a group: invalid
  1157. m_strError = L"Invalid email address in recipient list found: \"" + strName + L"\". Email Address is empty.";
  1158. ec_log_err("%ls", m_strError.c_str());
  1159. return MAPI_E_INVALID_PARAMETER;
  1160. }
  1161. // we only want to have this recipient fail, and not the whole message, if the user has a username
  1162. m_strError = L"Invalid email address in recipient list found: \"" + strName + L"\" <" + strEmail + L">.";
  1163. ec_log_err("%ls", m_strError.c_str());
  1164. return MAPI_E_INVALID_PARAMETER;
  1165. }
  1166. *lpvmMailbox = std::move(vmMailboxNew);
  1167. return hr;
  1168. }
  1169. /**
  1170. * Converts RTF (using TNEF) or HTML with plain to body parts.
  1171. *
  1172. * @param[in] lpMessage Message to convert body parts from
  1173. * @param[in] lpVMMessageBuilder messageBuilder object place bodyparts in
  1174. * @param[in] charset Use this charset for the HTML and plain text bodies.
  1175. * @param[out] lpbRealRTF true if real rtf should be used, which is attached later, since it can't be a body.
  1176. */
  1177. HRESULT MAPIToVMIME::handleTextparts(IMessage* lpMessage, vmime::messageBuilder *lpVMMessageBuilder, eBestBody *bestBody) {
  1178. std::string strHTMLOut, strRtf, strBodyConverted;
  1179. std::wstring strBody;
  1180. object_ptr<IStream> lpCompressedRTFStream, lpUncompressedRTFStream;
  1181. vmime::charset HTMLcharset;
  1182. // set the encoder of plaintext body part
  1183. vmime::encoding bodyEncoding("quoted-printable");
  1184. // will skip enters in q-p encoding, "fixes" plaintext mails to be more plain text
  1185. vmime::shared_ptr<vmime::utility::encoder::encoder> vmBodyEncoder = bodyEncoding.getEncoder();
  1186. vmBodyEncoder->getProperties().setProperty("text", true);
  1187. *bestBody = plaintext;
  1188. // grabbing rtf
  1189. HRESULT hr = lpMessage->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream,
  1190. 0, 0, &~lpCompressedRTFStream);
  1191. if(hr == hrSuccess) {
  1192. hr = WrapCompressedRTFStream(lpCompressedRTFStream, 0, &~lpUncompressedRTFStream);
  1193. if (hr != hrSuccess) {
  1194. ec_log_warn("Unable to create RTF-text stream. Error: 0x%08X", hr);
  1195. goto exit;
  1196. }
  1197. hr = Util::HrStreamToString(lpUncompressedRTFStream, strRtf);
  1198. if (hr != hrSuccess) {
  1199. ec_log_err("Unable to read RTF-text stream. Error: 0x%08X", hr);
  1200. goto exit;
  1201. }
  1202. if (isrtfhtml(strRtf.c_str(), strRtf.size()) || sopt.use_tnef == -1)
  1203. {
  1204. // Open html
  1205. object_ptr<IStream> lpHTMLStream;
  1206. hr = lpMessage->OpenProperty(PR_HTML, &IID_IStream, 0, 0, &~lpHTMLStream);
  1207. if (hr == hrSuccess) {
  1208. hr = Util::HrStreamToString(lpHTMLStream, strHTMLOut);
  1209. if (hr != hrSuccess) {
  1210. ec_log_warn("Unable to read HTML-text stream. Error: 0x%08X", hr);
  1211. goto exit;
  1212. }
  1213. // strHTMLout is now escaped us-ascii HTML, this is what we will be sending
  1214. // Or, if something failed, the HTML is now empty
  1215. *bestBody = html;
  1216. } else {
  1217. ec_log_warn("Unable to open HTML-text stream. Error: 0x%08X", hr);
  1218. // continue with plaintext
  1219. }
  1220. } else if (isrtftext(strRtf.c_str(), strRtf.size())) {
  1221. //Do nothing, only plain/text
  1222. } else {
  1223. // Real rtf
  1224. *bestBody = realRTF;
  1225. }
  1226. } else {
  1227. if (hr != MAPI_E_NOT_FOUND)
  1228. ec_log_info("Unable to open rtf-text stream. Error: 0x%08X", hr);
  1229. hr = hrSuccess;
  1230. }
  1231. try {
  1232. object_ptr<IStream> lpBody;
  1233. hr = lpMessage->OpenProperty(PR_BODY_W, &IID_IStream, 0, 0, &~lpBody);
  1234. if (hr == hrSuccess) {
  1235. hr = Util::HrStreamToString(lpBody, strBody);
  1236. } else {
  1237. if (hr != MAPI_E_NOT_FOUND)
  1238. ec_log_info("Unable to open plain-text stream. Error: 0x%08X", hr);
  1239. hr = hrSuccess;
  1240. }
  1241. // Convert body to correct charset
  1242. strBodyConverted = m_converter.convert_to<string>(m_strCharset.c_str(), strBody, rawsize(strBody), CHARSET_WCHAR);
  1243. // always use our textpart class
  1244. lpVMMessageBuilder->constructTextPart(vmime::mediaType(vmime::mediaTypes::TEXT, "mapi"));
  1245. // If HTML, create HTML and plaintext parts
  1246. if (!strHTMLOut.empty()) {
  1247. if (m_vmCharset.getName() != m_strHTMLCharset) {
  1248. // convert from HTML charset to vmime output charset
  1249. strHTMLOut = m_converter.convert_to<string>(m_vmCharset.getName().c_str(), strHTMLOut, rawsize(strHTMLOut), m_strHTMLCharset.c_str());
  1250. }
  1251. vmime::shared_ptr<vmime::mapiTextPart> textPart = vmime::dynamicCast<vmime::mapiTextPart>(lpVMMessageBuilder->getTextPart());
  1252. textPart->setText(vmime::make_shared<vmime::stringContentHandler>(strHTMLOut));
  1253. textPart->setCharset(m_vmCharset);
  1254. if (!strBodyConverted.empty())
  1255. textPart->setPlainText(vmime::make_shared<vmime::stringContentHandler>(strBodyConverted));
  1256. }
  1257. // else just plaintext
  1258. else if (!strBodyConverted.empty()) {
  1259. // make sure we give vmime CRLF data, so SMTP servers (qmail) won't complain on the forced plaintext
  1260. std::unique_ptr<char[]> crlfconv(new char[strBodyConverted.length()*2+1]);
  1261. size_t outsize = 0;
  1262. BufferLFtoCRLF(strBodyConverted.length(), strBodyConverted.c_str(), crlfconv.get(), &outsize);
  1263. strBodyConverted.assign(crlfconv.get(), outsize);
  1264. // encode to q-p ourselves
  1265. vmime::utility::inputStreamStringAdapter in(strBodyConverted);
  1266. vmime::string outString;
  1267. vmime::utility::outputStreamStringAdapter out(outString);
  1268. vmBodyEncoder->encode(in, out);
  1269. lpVMMessageBuilder->getTextPart()->setCharset(m_vmCharset);
  1270. vmime::dynamicCast<vmime::mapiTextPart>(lpVMMessageBuilder->getTextPart())->setPlainText(vmime::make_shared<vmime::stringContentHandler>(outString, bodyEncoding));
  1271. }
  1272. }
  1273. catch (vmime::exception& e) {
  1274. ec_log_err("VMIME exception: %s", e.what());
  1275. hr = MAPI_E_CALL_FAILED;
  1276. goto exit;
  1277. }
  1278. catch (std::exception& e) {
  1279. ec_log_err("STD exception on text part: %s", e.what());
  1280. hr = MAPI_E_CALL_FAILED;
  1281. goto exit;
  1282. }
  1283. catch (...) {
  1284. ec_log_err("Unknown generic exception on text part");
  1285. hr = MAPI_E_CALL_FAILED;
  1286. goto exit;
  1287. }
  1288. exit:
  1289. if (hr != hrSuccess)
  1290. ec_log_warn("Could not parse mail body");
  1291. return hr;
  1292. }
  1293. /**
  1294. * Add X-Headers to message from named properties.
  1295. * Contents of X-Headers can only be US-Ascii.
  1296. *
  1297. * @param[in] lpMessage MAPI Message to get X headers from
  1298. * @param[in] vmHeader vmime header object to add X headers to
  1299. * @return Mapi error code
  1300. */
  1301. HRESULT MAPIToVMIME::handleXHeaders(IMessage *lpMessage,
  1302. vmime::shared_ptr<vmime::header> vmHeader, unsigned int flags)
  1303. {
  1304. HRESULT hr;
  1305. ULONG i;
  1306. ULONG cValues;
  1307. memory_ptr<SPropTagArray> lpsNamedTags;
  1308. memory_ptr<SPropTagArray> lpsAllTags;
  1309. memory_ptr<SPropValue> lpPropArray;
  1310. ULONG cNames;
  1311. memory_ptr<MAPINAMEID *> lppNames;
  1312. auto hff = vmime::headerFieldFactory::getInstance();
  1313. // get all props on message
  1314. hr = lpMessage->GetPropList(0, &~lpsAllTags);
  1315. if (FAILED(hr))
  1316. return hr;
  1317. // find number of named props, which contain a string
  1318. cNames = 0;
  1319. for (i = 0; i < lpsAllTags->cValues; ++i)
  1320. if (PROP_ID(lpsAllTags->aulPropTag[i]) >= 0x8000 && PROP_TYPE(lpsAllTags->aulPropTag[i]) == PT_STRING8)
  1321. ++cNames;
  1322. // no string named properties found, we're done.
  1323. if (cNames == 0)
  1324. return hr;
  1325. hr = MAPIAllocateBuffer(CbNewSPropTagArray(cNames), &~lpsNamedTags);
  1326. if (hr != hrSuccess)
  1327. return hr;
  1328. lpsNamedTags->cValues = cNames;
  1329. // make named prop array
  1330. cNames = 0;
  1331. for (i = 0; i < lpsAllTags->cValues; ++i)
  1332. if (PROP_ID(lpsAllTags->aulPropTag[i]) >= 0x8000 && PROP_TYPE(lpsAllTags->aulPropTag[i]) == PT_STRING8)
  1333. lpsNamedTags->aulPropTag[cNames++] = lpsAllTags->aulPropTag[i];
  1334. hr = lpMessage->GetNamesFromIDs(&+lpsNamedTags, NULL, 0, &cNames, &~lppNames);
  1335. if (FAILED(hr))
  1336. return hr;
  1337. hr = lpMessage->GetProps(lpsNamedTags, 0, &cValues, &~lpPropArray);
  1338. if (FAILED(hr))
  1339. return hr;
  1340. for (i = 0; i < cNames; ++i) {
  1341. if (lppNames[i] == nullptr ||
  1342. lppNames[i]->ulKind != MNID_STRING ||
  1343. lppNames[i]->Kind.lpwstrName == nullptr ||
  1344. PROP_TYPE(lpPropArray[i].ulPropTag) != PT_STRING8)
  1345. continue;
  1346. std::unique_ptr<char[]> str;
  1347. int l = wcstombs(NULL, lppNames[i]->Kind.lpwstrName, 0) +1;
  1348. str.reset(new char[l]);
  1349. wcstombs(str.get(), lppNames[i]->Kind.lpwstrName, l);
  1350. if ((str[0] == 'X' || str[0] == 'x') && str[1] == '-') {
  1351. if (str[0] == 'x')
  1352. capitalize(str.get());
  1353. // keep the original x-mailer header under a different name
  1354. // we still want to know that this mail was generated by kopano in the x-mailer header from handleExtraHeaders
  1355. if (flags & MTV_SPOOL && strcasecmp(str.get(), "X-Mailer") == 0) {
  1356. str.reset(new char[18]);
  1357. strcpy(str.get(), "X-Original-Mailer");
  1358. }
  1359. if (!vmHeader->hasField(str.get()))
  1360. vmHeader->appendField(hff->create(str.get(), lpPropArray[i].Value.lpszA));
  1361. }
  1362. }
  1363. return hr;
  1364. }
  1365. /**
  1366. * Adds extra special headers to the e-mail.
  1367. *
  1368. * List of current extra e-mail headers:
  1369. * \li In-Reply-To
  1370. * \li References
  1371. * \li Importance with X-Priority
  1372. * \li X-Mailer
  1373. * \li Thread-Index
  1374. * \li Thread-Topic
  1375. * \li Sensitivity
  1376. * \li Expiry-Time
  1377. *
  1378. * @param[in] lpMessage Message to convert extra headers for
  1379. * @param[in] charset Charset to use in Thread-Topic header
  1380. * @param[in] vmHeader Add headers to this vmime header object
  1381. * @return always hrSuccess
  1382. */
  1383. HRESULT MAPIToVMIME::handleExtraHeaders(IMessage *lpMessage,
  1384. vmime::shared_ptr<vmime::header> vmHeader, unsigned int flags)
  1385. {
  1386. SPropValuePtr ptrMessageId;
  1387. memory_ptr<SPropValue> lpImportance, lpPriority, lpConversationIndex;
  1388. memory_ptr<SPropValue> lpConversationTopic, lpNormSubject;
  1389. memory_ptr<SPropValue> lpSensitivity, lpExpiryTime;
  1390. auto hff = vmime::headerFieldFactory::getInstance();
  1391. // Conversation headers. New Message-Id header is set just before sending.
  1392. if (HrGetOneProp(lpMessage, PR_IN_REPLY_TO_ID_A, &~ptrMessageId) == hrSuccess && ptrMessageId->Value.lpszA[0]) {
  1393. vmime::shared_ptr<vmime::messageId> mid = vmime::make_shared<vmime::messageId>(ptrMessageId->Value.lpszA);
  1394. vmime::dynamicCast<vmime::messageIdSequence>(vmHeader->InReplyTo()->getValue())->appendMessageId(mid);
  1395. }
  1396. // Outlook never adds this property
  1397. if (HrGetOneProp(lpMessage, PR_INTERNET_REFERENCES_A, &~ptrMessageId) == hrSuccess && ptrMessageId->Value.lpszA[0]) {
  1398. std::vector<std::string> ids = tokenize(ptrMessageId->Value.lpszA, ' ', true);
  1399. const size_t n = ids.size();
  1400. for (size_t i = 0; i < n; ++i) {
  1401. vmime::shared_ptr<vmime::messageId> mid = vmime::make_shared<vmime::messageId>(ids.at(i));
  1402. vmime::dynamicCast<vmime::messageIdSequence>(vmHeader->References()->getValue())->appendMessageId(mid);
  1403. }
  1404. }
  1405. // only for message-in-message items, add Message-ID header from MAPI
  1406. if (sopt.msg_in_msg && HrGetOneProp(lpMessage, PR_INTERNET_MESSAGE_ID_A, &~ptrMessageId) == hrSuccess && ptrMessageId->Value.lpszA[0])
  1407. vmHeader->MessageId()->setValue(ptrMessageId->Value.lpszA);
  1408. // priority settings
  1409. static const char *const priomap[3] = { "5 (Lowest)", "3 (Normal)", "1 (Highest)" }; // 2 and 4 cannot be set from outlook
  1410. if (HrGetOneProp(lpMessage, PR_IMPORTANCE, &~lpImportance) == hrSuccess)
  1411. vmHeader->appendField(hff->create("X-Priority", priomap[min(2, (int)(lpImportance->Value.ul)&3)])); // IMPORTANCE_* = 0..2
  1412. else if (HrGetOneProp(lpMessage, PR_PRIORITY, &~lpPriority) == hrSuccess)
  1413. vmHeader->appendField(hff->create("X-Priority", priomap[min(2, (int)(lpPriority->Value.ul+1)&3)])); // PRIO_* = -1..1
  1414. // When adding a X-Priority, spamassassin may add a severe punishment because no User-Agent header
  1415. // or X-Mailer header is present. So we set the X-Mailer header :)
  1416. if (flags & MTV_SPOOL)
  1417. vmHeader->appendField(hff->create("X-Mailer", "Kopano " PROJECT_VERSION_DOT_STR "-" PROJECT_SVN_REV_STR));
  1418. // PR_CONVERSATION_INDEX
  1419. if (HrGetOneProp(lpMessage, PR_CONVERSATION_INDEX, &~lpConversationIndex) == hrSuccess) {
  1420. vmime::string inString;
  1421. inString.assign((const char*)lpConversationIndex->Value.bin.lpb, lpConversationIndex->Value.bin.cb);
  1422. vmime::shared_ptr<vmime::utility::encoder::encoder> enc = vmime::utility::encoder::encoderFactory::getInstance()->create("base64");
  1423. vmime::utility::inputStreamStringAdapter in(inString);
  1424. vmime::string outString;
  1425. vmime::utility::outputStreamStringAdapter out(outString);
  1426. enc->encode(in, out);
  1427. vmHeader->appendField(hff->create("Thread-Index", outString));
  1428. }
  1429. // PR_CONVERSATION_TOPIC is always the original started topic
  1430. if (HrGetOneProp(lpMessage, PR_CONVERSATION_TOPIC_W, &~lpConversationTopic) == hrSuccess &&
  1431. (HrGetOneProp(lpMessage, PR_NORMALIZED_SUBJECT_W, &~lpNormSubject) != hrSuccess ||
  1432. wcscmp(lpNormSubject->Value.lpszW, lpConversationTopic->Value.lpszW) != 0)) {
  1433. removeEnters(lpConversationTopic->Value.lpszW);
  1434. vmHeader->appendField(hff->create("Thread-Topic", getVmimeTextFromWide(lpConversationTopic->Value.lpszW).generate()));
  1435. }
  1436. if (HrGetOneProp(lpMessage, PR_SENSITIVITY, &~lpSensitivity) == hrSuccess) {
  1437. const char *strSens;
  1438. switch (lpSensitivity->Value.ul) {
  1439. case SENSITIVITY_PERSONAL:
  1440. strSens = "Personal";
  1441. break;
  1442. case SENSITIVITY_PRIVATE:
  1443. strSens = "Private";
  1444. break;
  1445. case SENSITIVITY_COMPANY_CONFIDENTIAL:
  1446. strSens = "Company-Confidential";
  1447. break;
  1448. case SENSITIVITY_NONE:
  1449. default:
  1450. strSens = NULL;
  1451. break;
  1452. };
  1453. if (strSens)
  1454. vmHeader->appendField(hff->create("Sensitivity", strSens));
  1455. }
  1456. if (HrGetOneProp(lpMessage, PR_EXPIRY_TIME, &~lpExpiryTime) == hrSuccess)
  1457. vmHeader->appendField(hff->create("Expiry-Time", FiletimeTovmimeDatetime(lpExpiryTime->Value.ft).generate()));
  1458. if (flags & MTV_SPOOL) {
  1459. char buffer[4096] = {0};
  1460. if (gethostname(buffer, sizeof buffer) == -1)
  1461. strcpy(buffer, "???");
  1462. vmime::relay relay;
  1463. relay.setBy(std::string(buffer) + " (kopano-spooler)");
  1464. relay.getWithList().push_back("MAPI");
  1465. auto now = vmime::datetime::now();
  1466. relay.setDate(now);
  1467. auto header_field = hff->create("Received");
  1468. header_field->setValue(relay);
  1469. vmHeader->insertFieldBefore(0, header_field);
  1470. }
  1471. return hrSuccess;
  1472. }
  1473. /**
  1474. * Open the contact of the user if it is a contact folder and rewrite it to a
  1475. * usable e-mail recipient.
  1476. *
  1477. * @param[in] cValues Number of properties in lpProps (unused)
  1478. * @param[in] lpProps EntryID of contact in 1st property
  1479. * @param[out] strName Fullname of the contact
  1480. * @param[out] strName Type of the resolved contact
  1481. * @param[out] strName SMTP e-mail address of the contact
  1482. * @return Mapi error code
  1483. *
  1484. * @todo fix first two parameters
  1485. */
  1486. HRESULT MAPIToVMIME::handleContactEntryID(ULONG cValues, LPSPropValue lpProps, wstring &strName, wstring &strType, wstring &strEmail)
  1487. {
  1488. HRESULT hr = hrSuccess;
  1489. LPCONTAB_ENTRYID lpContabEntryID = NULL;
  1490. GUID* guid = NULL;
  1491. ULONG ulObjType;
  1492. memory_ptr<SPropTagArray> lpNameTags;
  1493. memory_ptr<SPropValue> lpNamedProps;
  1494. object_ptr<IMailUser> lpContact;
  1495. memory_ptr<MAPINAMEID *> lppNames;
  1496. ULONG i;
  1497. ULONG ulNames = 5;
  1498. MAPINAMEID mnNamedProps[5] = {
  1499. // offset 0, every offset < 3 is + 0x10
  1500. {(LPGUID)&PSETID_Address, MNID_ID, {0x8080}}, // display name
  1501. {(LPGUID)&PSETID_Address, MNID_ID, {0x8082}}, // address type
  1502. {(LPGUID)&PSETID_Address, MNID_ID, {0x8083}}, // email address
  1503. {(LPGUID)&PSETID_Address, MNID_ID, {0x8084}}, // original display name (unused)
  1504. {(LPGUID)&PSETID_Address, MNID_ID, {0x8085}}, // real entryid
  1505. };
  1506. if (PROP_TYPE(lpProps[0].ulPropTag) != PT_BINARY)
  1507. return MAPI_E_NOT_FOUND;
  1508. lpContabEntryID = (LPCONTAB_ENTRYID)lpProps[0].Value.bin.lpb;
  1509. if (lpContabEntryID == NULL)
  1510. return MAPI_E_NOT_FOUND;
  1511. guid = (GUID*)&lpContabEntryID->muid;
  1512. if (sizeof(CONTAB_ENTRYID) > lpProps[0].Value.bin.cb ||
  1513. *guid != PSETID_CONTACT_FOLDER_RECIPIENT ||
  1514. lpContabEntryID->email_offset > 2)
  1515. return MAPI_E_NOT_FOUND;
  1516. hr = m_lpSession->OpenEntry(lpContabEntryID->cbeid, reinterpret_cast<ENTRYID *>(lpContabEntryID->abeid), nullptr, 0, &ulObjType, &~lpContact);
  1517. if (hr != hrSuccess)
  1518. return hr;
  1519. // add offset to get correct named properties
  1520. for (i = 0; i < ulNames; ++i)
  1521. mnNamedProps[i].Kind.lID += (lpContabEntryID->email_offset * 0x10);
  1522. hr = MAPIAllocateBuffer(sizeof(LPMAPINAMEID) * (ulNames), &~lppNames);
  1523. if (hr != hrSuccess) {
  1524. ec_log_err("No memory for named ids from contact");
  1525. return hr;
  1526. }
  1527. for (i = 0; i < ulNames; ++i)
  1528. lppNames[i] = &mnNamedProps[i];
  1529. hr = lpContact->GetIDsFromNames(ulNames, lppNames, MAPI_CREATE, &~lpNameTags);
  1530. if (FAILED(hr))
  1531. return hr;
  1532. lpNameTags->aulPropTag[0] = CHANGE_PROP_TYPE(lpNameTags->aulPropTag[0], PT_UNICODE);
  1533. lpNameTags->aulPropTag[1] = CHANGE_PROP_TYPE(lpNameTags->aulPropTag[1], PT_UNICODE);
  1534. lpNameTags->aulPropTag[2] = CHANGE_PROP_TYPE(lpNameTags->aulPropTag[2], PT_UNICODE);
  1535. lpNameTags->aulPropTag[3] = CHANGE_PROP_TYPE(lpNameTags->aulPropTag[3], PT_UNICODE); // unused
  1536. lpNameTags->aulPropTag[4] = CHANGE_PROP_TYPE(lpNameTags->aulPropTag[4], PT_BINARY);
  1537. hr = lpContact->GetProps(lpNameTags, 0, &ulNames, &~lpNamedProps);
  1538. if (FAILED(hr))
  1539. return hr;
  1540. return HrGetAddress(m_lpAdrBook, lpNamedProps, ulNames,
  1541. lpNameTags->aulPropTag[4], lpNameTags->aulPropTag[0],
  1542. lpNameTags->aulPropTag[1], lpNameTags->aulPropTag[2],
  1543. strName, strType, strEmail);
  1544. }
  1545. /**
  1546. * Set From and possibly Sender header.
  1547. *
  1548. * @param[in] lpMessage Message to get From and Sender of.
  1549. * @param[in] charset charset to use for Fullname's of headers.
  1550. * @param[in] vmHeader vmime header object to modify.
  1551. * @return Mapi error code
  1552. */
  1553. HRESULT MAPIToVMIME::handleSenderInfo(IMessage *lpMessage,
  1554. vmime::shared_ptr<vmime::header> vmHeader)
  1555. {
  1556. ULONG cValues;
  1557. memory_ptr<SPropValue> lpProps, lpReadReceipt;
  1558. // sender information
  1559. std::wstring strEmail, strName, strType;
  1560. std::wstring strResEmail, strResName, strResType;
  1561. static constexpr const SizedSPropTagArray(4, sender_proptags) =
  1562. {4, {PR_SENDER_ENTRYID, PR_SENDER_NAME_W,
  1563. PR_SENDER_ADDRTYPE_W, PR_SENDER_EMAIL_ADDRESS_W}};
  1564. HRESULT hr = lpMessage->GetProps(sender_proptags, 0, &cValues, &~lpProps);
  1565. if (FAILED(hr))
  1566. return hr;
  1567. hr = handleContactEntryID(cValues, lpProps, strName, strType, strEmail);
  1568. if (hr != hrSuccess) {
  1569. // Store owner, actual sender
  1570. hr = HrGetAddress(m_lpAdrBook, lpProps, cValues, PR_SENDER_ENTRYID, PR_SENDER_NAME_W, PR_SENDER_ADDRTYPE_W, PR_SENDER_EMAIL_ADDRESS_W, strName, strType, strEmail);
  1571. if (hr != hrSuccess) {
  1572. ec_log_err("Unable to get sender information. Error: 0x%08X", hr);
  1573. return hr;
  1574. }
  1575. }
  1576. // -- sender
  1577. static constexpr const SizedSPropTagArray(4, repr_proptags) =
  1578. {4, {PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME_W,
  1579. PR_SENT_REPRESENTING_ADDRTYPE_W,
  1580. PR_SENT_REPRESENTING_EMAIL_ADDRESS_W}};
  1581. hr = lpMessage->GetProps(repr_proptags, 0, &cValues, &~lpProps);
  1582. if (FAILED(hr))
  1583. return hr;
  1584. hr = handleContactEntryID(cValues, lpProps, strResName, strResType, strResEmail);
  1585. if (hr != hrSuccess) {
  1586. hr = HrGetAddress(m_lpAdrBook, lpProps, cValues, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME_W, PR_SENT_REPRESENTING_ADDRTYPE_W, PR_SENT_REPRESENTING_EMAIL_ADDRESS_W, strResName, strResType, strResEmail);
  1587. if (hr != hrSuccess) {
  1588. ec_log_warn("Unable to get representing information. Error: 0x%08X", hr);
  1589. // ignore error, since we still have enough information to send, maybe not just under the correct name
  1590. hr = hrSuccess;
  1591. }
  1592. if (sopt.no_recipients_workaround == false && strResEmail.empty() && PROP_TYPE(lpProps[0].ulPropTag) != PT_ERROR) {
  1593. m_strError = L"Representing email address is empty";
  1594. ec_log_err("%ls", m_strError.c_str());
  1595. return MAPI_E_NOT_FOUND;
  1596. }
  1597. }
  1598. // Set representing as from address, when possible
  1599. // Ignore PR_SENT_REPRESENTING if the email adress is the same as the PR_SENDER email address
  1600. if (!strResEmail.empty() && strResEmail != strEmail) {
  1601. if (strResName.empty() || strResName == strResEmail)
  1602. vmHeader->From()->setValue(vmime::mailbox(m_converter.convert_to<string>(strResEmail)));
  1603. else
  1604. vmHeader->From()->setValue(vmime::mailbox(getVmimeTextFromWide(strResName), m_converter.convert_to<string>(strResEmail)));
  1605. // spooler checked if this is allowed
  1606. if (strResEmail != strEmail) {
  1607. // Set store owner as sender
  1608. if (strName.empty() || strName == strEmail)
  1609. vmHeader->Sender()->setValue(vmime::mailbox(m_converter.convert_to<string>(strEmail)));
  1610. else
  1611. vmHeader->Sender()->setValue(vmime::mailbox(getVmimeTextFromWide(strName), m_converter.convert_to<string>(strEmail)));
  1612. }
  1613. } else if (strName.empty() || strName == strEmail) {
  1614. // Set store owner as from, sender does not need to be set
  1615. vmHeader->From()->setValue(vmime::mailbox(m_converter.convert_to<string>(strEmail)));
  1616. } else {
  1617. vmHeader->From()->setValue(vmime::mailbox(getVmimeTextFromWide(strName), m_converter.convert_to<string>(strEmail)));
  1618. }
  1619. // read receipt request
  1620. if (HrGetOneProp(lpMessage, PR_READ_RECEIPT_REQUESTED, &~lpReadReceipt) == hrSuccess && lpReadReceipt->Value.b == TRUE) {
  1621. vmime::mailboxList mbl;
  1622. if (!strResEmail.empty() && strResEmail != strEmail) {
  1623. // use user added from address
  1624. if (strResName.empty() || strName == strResEmail)
  1625. mbl.appendMailbox(vmime::make_shared<vmime::mailbox>(m_converter.convert_to<string>(strResEmail)));
  1626. else
  1627. mbl.appendMailbox(vmime::make_shared<vmime::mailbox>(getVmimeTextFromWide(strResName), m_converter.convert_to<string>(strResEmail)));
  1628. } else if (strName.empty() || strName == strEmail) {
  1629. mbl.appendMailbox(vmime::make_shared<vmime::mailbox>(m_converter.convert_to<string>(strEmail)));
  1630. } else {
  1631. mbl.appendMailbox(vmime::make_shared<vmime::mailbox>(getVmimeTextFromWide(strName), m_converter.convert_to<string>(strEmail)));
  1632. }
  1633. vmHeader->DispositionNotificationTo()->setValue(mbl);
  1634. }
  1635. return hrSuccess;
  1636. }
  1637. /**
  1638. * Set Reply-To header.
  1639. *
  1640. * @note In RFC 2822 and MAPI, you can set multiple Reply-To
  1641. * values. However, in vmime this is currently not possible, so we
  1642. * only convert the first.
  1643. *
  1644. * @param[in] lpMessage Message to get Reply-To value of.
  1645. * @param[in] charset charset to use for Fullname's of headers.
  1646. * @param[in] vmHeader vmime header object to modify.
  1647. * @return Mapi error code
  1648. */
  1649. HRESULT MAPIToVMIME::handleReplyTo(IMessage *lpMessage,
  1650. vmime::shared_ptr<vmime::header> vmHeader)
  1651. {
  1652. HRESULT hr = hrSuccess;
  1653. FLATENTRYLIST *lpEntryList = NULL;
  1654. FLATENTRY *lpEntry = NULL;
  1655. LPCONTAB_ENTRYID lpContabEntryID = NULL;
  1656. GUID* guid = NULL;
  1657. ULONG ulObjType;
  1658. object_ptr<IMailUser> lpContact;
  1659. wstring strName, strType, strEmail;
  1660. // "Email1DisplayName","Email1AddressType","Email1Address","Email1EntryID"
  1661. static const ULONG lpulNamesIDs[] = {0x8080, 0x8082, 0x8083, 0x8085,
  1662. 0x8090, 0x8092, 0x8093, 0x8095,
  1663. 0x80A0, 0x80A2, 0x80A3, 0x80A5};
  1664. ULONG cNames, i, offset;
  1665. memory_ptr<MAPINAMEID> lpNames;
  1666. memory_ptr<MAPINAMEID *> lppNames;
  1667. memory_ptr<SPropTagArray> lpNameTagArray;
  1668. memory_ptr<SPropValue> lpAddressProps, lpReplyTo;
  1669. if (HrGetOneProp(lpMessage, PR_REPLY_RECIPIENT_ENTRIES, &~lpReplyTo) != hrSuccess)
  1670. return hr;
  1671. if (lpReplyTo->Value.bin.cb == 0)
  1672. return hr;
  1673. lpEntryList = (FLATENTRYLIST *)lpReplyTo->Value.bin.lpb;
  1674. if (lpEntryList->cEntries == 0)
  1675. return hr;
  1676. lpEntry = (FLATENTRY *)&lpEntryList->abEntries;
  1677. hr = HrGetAddress(m_lpAdrBook, (LPENTRYID)lpEntry->abEntry, lpEntry->cb, strName, strType, strEmail);
  1678. if (hr != hrSuccess) {
  1679. if (m_lpSession == nullptr)
  1680. return MAPI_E_INVALID_PARAMETER;
  1681. // user selected a contact (or distrolist ?) in the reply-to
  1682. lpContabEntryID = (LPCONTAB_ENTRYID)lpEntry->abEntry;
  1683. guid = (GUID*)&lpContabEntryID->muid;
  1684. if (sizeof(CONTAB_ENTRYID) > lpEntry->cb || *guid != PSETID_CONTACT_FOLDER_RECIPIENT || lpContabEntryID->email_offset > 2)
  1685. return hr;
  1686. hr = m_lpSession->OpenEntry(lpContabEntryID->cbeid, reinterpret_cast<ENTRYID *>(lpContabEntryID->abeid), nullptr, 0, &ulObjType, &~lpContact);
  1687. if (hr != hrSuccess)
  1688. return hr;
  1689. cNames = ARRAY_SIZE(lpulNamesIDs);
  1690. hr = MAPIAllocateBuffer(sizeof(MAPINAMEID) * cNames, &~lpNames);
  1691. if (hr != hrSuccess)
  1692. return hr;
  1693. hr = MAPIAllocateBuffer(sizeof(LPMAPINAMEID) * cNames, &~lppNames);
  1694. if (hr != hrSuccess)
  1695. return hr;
  1696. for (i = 0; i < cNames; ++i) {
  1697. lpNames[i].lpguid = (GUID*)&PSETID_Address;
  1698. lpNames[i].ulKind = MNID_ID;
  1699. lpNames[i].Kind.lID = lpulNamesIDs[i];
  1700. lppNames[i] = &lpNames[i];
  1701. }
  1702. hr = lpContact->GetIDsFromNames(cNames, lppNames, 0, &~lpNameTagArray);
  1703. if (FAILED(hr))
  1704. return hr;
  1705. // PT_UNSPECIFIED in tagarray, but we want PT_UNICODE
  1706. hr = lpContact->GetProps(lpNameTagArray, MAPI_UNICODE, &cNames, &~lpAddressProps);
  1707. if (FAILED(hr))
  1708. return hr;
  1709. offset = lpContabEntryID->email_offset * 4; // 4 props per email address
  1710. if (PROP_TYPE(lpAddressProps[offset+0].ulPropTag) == PT_ERROR || PROP_TYPE(lpAddressProps[offset+1].ulPropTag) == PT_ERROR ||
  1711. PROP_TYPE(lpAddressProps[offset+2].ulPropTag) == PT_ERROR || PROP_TYPE(lpAddressProps[offset+3].ulPropTag) == PT_ERROR)
  1712. return hr;
  1713. if (wcscmp(lpAddressProps[offset+1].Value.lpszW, L"SMTP") == 0) {
  1714. strName = lpAddressProps[offset+0].Value.lpszW;
  1715. strEmail = lpAddressProps[offset+2].Value.lpszW;
  1716. } else if (wcscmp(lpAddressProps[offset+1].Value.lpszW, L"ZARAFA") == 0) {
  1717. hr = HrGetAddress(m_lpAdrBook, (LPENTRYID)lpAddressProps[offset+2].Value.bin.lpb, lpAddressProps[offset+2].Value.bin.cb, strName, strType, strEmail);
  1718. if (hr != hrSuccess)
  1719. return hr;
  1720. }
  1721. }
  1722. // vmime can only set 1 email address in the ReplyTo field.
  1723. if (!strName.empty() && strName != strEmail)
  1724. vmHeader->ReplyTo()->setValue(vmime::make_shared<vmime::mailbox>(getVmimeTextFromWide(strName), m_converter.convert_to<string>(strEmail)));
  1725. else
  1726. vmHeader->ReplyTo()->setValue(vmime::make_shared<vmime::mailbox>(m_converter.convert_to<string>(strEmail)));
  1727. return hrSuccess;
  1728. }
  1729. /**
  1730. * check if named property exists which is used to hold voting options
  1731. */
  1732. bool MAPIToVMIME::is_voting_request(IMessage *lpMessage)
  1733. {
  1734. HRESULT hr = hrSuccess;
  1735. memory_ptr<SPropTagArray> lpPropTags;
  1736. memory_ptr<SPropValue> lpPropContentType;
  1737. MAPINAMEID named_prop = {(LPGUID)&PSETID_Common, MNID_ID, {0x8520}};
  1738. MAPINAMEID *named_proplist = &named_prop;
  1739. hr = lpMessage->GetIDsFromNames(1, &named_proplist, MAPI_CREATE, &~lpPropTags);
  1740. if (hr != hrSuccess)
  1741. ec_log_err("Unable to read voting property. Error: %s (0x%08X)",
  1742. GetMAPIErrorMessage(hr), hr);
  1743. else
  1744. hr = HrGetOneProp(lpMessage, CHANGE_PROP_TYPE(lpPropTags->aulPropTag[0], PT_BINARY), &~lpPropContentType);
  1745. return hr == hrSuccess;
  1746. }
  1747. /**
  1748. * CCheck if the named property exists which denotes if reminder is set
  1749. */
  1750. bool MAPIToVMIME::has_reminder(IMessage *msg)
  1751. {
  1752. memory_ptr<SPropTagArray> tags;
  1753. memory_ptr<SPropValue> content_type;
  1754. MAPINAMEID named_prop = {const_cast<GUID *>(&PSETID_Common), MNID_ID, {0x8503}};
  1755. auto named_proplist = &named_prop;
  1756. bool result = false;
  1757. auto hr = msg->GetIDsFromNames(1, &named_proplist, MAPI_CREATE, &~tags);
  1758. if (hr != hrSuccess)
  1759. ec_log_err("Unable to read reminder property: %s (0x%08x)",
  1760. GetMAPIErrorMessage(hr), hr);
  1761. else {
  1762. hr = HrGetOneProp(msg, CHANGE_PROP_TYPE(tags->aulPropTag[0], PT_BOOLEAN), &~content_type);
  1763. if(hr == hrSuccess)
  1764. result = content_type->Value.b;
  1765. else
  1766. ec_log_debug("Message has no reminder property");
  1767. }
  1768. return result;
  1769. }
  1770. /**
  1771. * Adds a TNEF (winmail.dat) attachment to the message, if special
  1772. * outlook data needs to be sent. May add iCal for calendar items
  1773. * instead of TNEF.
  1774. *
  1775. * @param[in] lpMessage Message to get Reply-To value of.
  1776. * @param[in] charset charset to use for Fullname's of headers.
  1777. * @param[in] vmHeader vmime header object to modify.
  1778. * @return Mapi error code
  1779. */
  1780. HRESULT MAPIToVMIME::handleTNEF(IMessage* lpMessage, vmime::messageBuilder* lpVMMessageBuilder, eBestBody bestBody) {
  1781. HRESULT hr = hrSuccess;
  1782. memory_ptr<SPropValue> lpSendAsICal, lpOutlookVersion, lpMessageClass;
  1783. memory_ptr<SPropValue> lpDelegateRule;
  1784. object_ptr<IStream> lpStream;
  1785. std::unique_ptr<MapiToICal> mapiical;
  1786. memory_ptr<MAPINAMEID> lpNames;
  1787. memory_ptr<SPropTagArray> lpNameTagArray;
  1788. int iUseTnef = sopt.use_tnef;
  1789. std::string strTnefReason;
  1790. std::list<ULONG> lstOLEAttach; // list of OLE attachments that must be sent via TNEF
  1791. object_ptr<IMAPITable> lpAttachTable;
  1792. static constexpr const SizedSPropTagArray(2, sptaAttachProps) =
  1793. {2, {PR_ATTACH_METHOD, PR_ATTACH_NUM}};
  1794. static constexpr const SizedSPropTagArray(5, sptaOLEAttachProps) =
  1795. {5, {PR_ATTACH_FILENAME, PR_ATTACH_LONG_FILENAME,
  1796. PR_ATTACH_DATA_OBJ, PR_ATTACH_CONTENT_ID, PR_ATTACHMENT_HIDDEN}};
  1797. static constexpr const SizedSSortOrderSet(1, sosRTFSeq) =
  1798. {1, 0, 0, {{PR_RENDERING_POSITION, TABLE_SORT_ASCEND}}};
  1799. try {
  1800. rowset_ptr lpAttachRows;
  1801. // Find all ATTACH_OLE attachments and put them in lstOLEAttach
  1802. hr = lpMessage->GetAttachmentTable(0, &~lpAttachTable);
  1803. if(hr != hrSuccess)
  1804. return hr;
  1805. hr = HrQueryAllRows(lpAttachTable, sptaAttachProps, NULL,
  1806. sosRTFSeq, 0, &~lpAttachRows);
  1807. if (hr != hrSuccess)
  1808. return hr;
  1809. for (unsigned int i = 0; i < lpAttachRows->cRows; ++i)
  1810. if(lpAttachRows->aRow[i].lpProps[0].ulPropTag == PR_ATTACH_METHOD &&
  1811. lpAttachRows->aRow[i].lpProps[1].ulPropTag == PR_ATTACH_NUM &&
  1812. lpAttachRows->aRow[i].lpProps[0].Value.ul == ATTACH_OLE)
  1813. lstOLEAttach.push_back(lpAttachRows->aRow[i].lpProps[1].Value.ul);
  1814. // Start processing TNEF properties
  1815. if (HrGetOneProp(lpMessage, PR_EC_SEND_AS_ICAL, &~lpSendAsICal) != hrSuccess)
  1816. lpSendAsICal = NULL;
  1817. if (HrGetOneProp(lpMessage, PR_EC_OUTLOOK_VERSION, &~lpOutlookVersion) != hrSuccess)
  1818. lpOutlookVersion = NULL;
  1819. if (HrGetOneProp(lpMessage, PR_MESSAGE_CLASS_A, &~lpMessageClass) != hrSuccess)
  1820. lpMessageClass = NULL;
  1821. if (HrGetOneProp(lpMessage, PR_DELEGATED_BY_RULE, &~lpDelegateRule) != hrSuccess)
  1822. lpDelegateRule = NULL;
  1823. if (iUseTnef > 0)
  1824. strTnefReason = "Force TNEF on request";
  1825. // currently no task support for ical
  1826. if (iUseTnef <= 0 && lpMessageClass && strncasecmp("IPM.Task", lpMessageClass->Value.lpszA, 8) == 0) {
  1827. iUseTnef = 1;
  1828. strTnefReason = "Force TNEF because of task request";
  1829. }
  1830. // delegation of meeting requests need to be in tnef too because of special properties
  1831. if (iUseTnef <= 0 && lpDelegateRule && lpDelegateRule->Value.b == TRUE) {
  1832. iUseTnef = 1;
  1833. strTnefReason = "Force TNEF because of delegation";
  1834. }
  1835. if(iUseTnef <= 0 && is_voting_request(lpMessage)) {
  1836. iUseTnef = 1;
  1837. strTnefReason = "Force TNEF because of voting request";
  1838. }
  1839. if (iUseTnef == 0 && has_reminder(lpMessage)) {
  1840. iUseTnef = 1;
  1841. strTnefReason = "Force TNEF because of reminder";
  1842. }
  1843. /*
  1844. * Outlook 2000 always sets PR_EC_SEND_AS_ICAL to FALSE, because the
  1845. * iCal option is somehow missing from the options property sheet, and
  1846. * PR_EC_USE_TNEF is never set. So for outlook 2000 (9.0), we check the
  1847. * _existence_ of PR_EC_SEND_AS_ICAL as a hint to use TNEF.
  1848. */
  1849. if (iUseTnef == 1 ||
  1850. (lpSendAsICal && lpSendAsICal->Value.b) ||
  1851. (lpSendAsICal && lpOutlookVersion && strcmp(lpOutlookVersion->Value.lpszA, "9.0") == 0) ||
  1852. (lpMessageClass && (strncasecmp("IPM.Note", lpMessageClass->Value.lpszA, 8) != 0) ) ||
  1853. bestBody == realRTF)
  1854. {
  1855. // Send either TNEF or iCal data
  1856. vmime::shared_ptr<vmime::attachment> vmTNEFAtt;
  1857. vmime::shared_ptr<vmime::utility::inputStream> inputDataStream = NULL;
  1858. /*
  1859. * Send TNEF information for this message if we really need to, or otherwise iCal
  1860. */
  1861. if (lstOLEAttach.size() == 0 && iUseTnef <= 0 && lpMessageClass && (strncasecmp("IPM.Note", lpMessageClass->Value.lpszA, 8) != 0)) {
  1862. // iCAL
  1863. string ical, method;
  1864. vmime::shared_ptr<mapiAttachment> vmAttach = NULL;
  1865. MapiToICal *tmp;
  1866. ec_log_info("Adding ICS attachment for extra information");
  1867. CreateMapiToICal(m_lpAdrBook, "utf-8", &tmp);
  1868. mapiical.reset(tmp);
  1869. hr = mapiical->AddMessage(lpMessage, std::string(), 0);
  1870. if (hr != hrSuccess) {
  1871. ec_log_warn("Unable to create ical object, sending as TNEF");
  1872. goto tnef_anyway;
  1873. }
  1874. hr = mapiical->Finalize(0, &method, &ical);
  1875. if (hr != hrSuccess) {
  1876. ec_log_warn("Unable to create ical object, sending as TNEF");
  1877. goto tnef_anyway;
  1878. }
  1879. vmime::shared_ptr<vmime::mapiTextPart> lpvmMapiText = vmime::dynamicCast<vmime::mapiTextPart>(lpVMMessageBuilder->getTextPart());
  1880. lpvmMapiText->setOtherText(vmime::make_shared<vmime::stringContentHandler>(ical));
  1881. lpvmMapiText->setOtherContentType(vmime::mediaType(vmime::mediaTypes::TEXT, "calendar"));
  1882. lpvmMapiText->setOtherContentEncoding(vmime::encoding(vmime::encodingTypes::EIGHT_BIT));
  1883. lpvmMapiText->setOtherMethod(method);
  1884. lpvmMapiText->setOtherCharset(vmime::charset("utf-8"));
  1885. } else {
  1886. if (lstOLEAttach.size())
  1887. ec_log_info("TNEF because of OLE attachments");
  1888. else if (iUseTnef == 0)
  1889. ec_log_info("TNEF because of RTF body");
  1890. else
  1891. ec_log_info(strTnefReason);
  1892. tnef_anyway:
  1893. hr = CreateStreamOnHGlobal(nullptr, TRUE, &~lpStream);
  1894. if (hr != hrSuccess) {
  1895. ec_log_err("Unable to create stream for TNEF attachment. Error 0x%08X", hr);
  1896. return hr;
  1897. }
  1898. ECTNEF tnef(TNEF_ENCODE, lpMessage, lpStream);
  1899. // Encode the properties now, add all message properties except for the exclude list
  1900. hr = tnef.AddProps(TNEF_PROP_EXCLUDE, sptaExclude);
  1901. if(hr != hrSuccess) {
  1902. ec_log_err("Unable to exclude properties from TNEF object");
  1903. return hr;
  1904. }
  1905. // plaintext is never added to TNEF, only HTML or "real" RTF
  1906. if (bestBody != plaintext) {
  1907. SizedSPropTagArray(1, sptaBestBodyInclude) = {1, {PR_RTF_COMPRESSED}};
  1908. if (bestBody == html) sptaBestBodyInclude.aulPropTag[0] = PR_HTML;
  1909. else if (bestBody == realRTF) sptaBestBodyInclude.aulPropTag[0] = PR_RTF_COMPRESSED;
  1910. hr = tnef.AddProps(TNEF_PROP_INCLUDE, sptaBestBodyInclude);
  1911. if(hr != hrSuccess) {
  1912. ec_log_err("Unable to include body property 0x%08x to TNEF object", sptaBestBodyInclude.aulPropTag[0]);
  1913. return hr;
  1914. }
  1915. }
  1916. // Add all OLE attachments
  1917. for (const auto atnum : lstOLEAttach)
  1918. tnef.FinishComponent(0x00002000, atnum, sptaOLEAttachProps);
  1919. // Write the stream
  1920. hr = tnef.Finish();
  1921. inputDataStream = vmime::make_shared<inputStreamMAPIAdapter>(lpStream);
  1922. // Now, add the stream as an attachment to the message, filename winmail.dat
  1923. // and MIME type 'application/ms-tnef', no content-id
  1924. vmTNEFAtt = vmime::make_shared<mapiAttachment>(vmime::make_shared<vmime::streamContentHandler>(inputDataStream, 0),
  1925. vmime::encoding("base64"), vmime::mediaType("application/ms-tnef"), string(),
  1926. vmime::word("winmail.dat"));
  1927. // add to message (copies pointer, not data)
  1928. lpVMMessageBuilder->appendAttachment(vmTNEFAtt);
  1929. }
  1930. }
  1931. }
  1932. catch (vmime::exception& e) {
  1933. ec_log_err("VMIME exception: %s", e.what());
  1934. return MAPI_E_CALL_FAILED; // set real error
  1935. }
  1936. catch (std::exception& e) {
  1937. ec_log_err("STD exception on fill message: %s", e.what());
  1938. return MAPI_E_CALL_FAILED; // set real error
  1939. }
  1940. catch (...) {
  1941. ec_log_err("Unknown generic exception on fill message");
  1942. return MAPI_E_CALL_FAILED;
  1943. }
  1944. return hr;
  1945. }
  1946. /**
  1947. * converts x-mail-sender -> X-Mail-Sender
  1948. *
  1949. * @param[in,out] s String to capitalize
  1950. */
  1951. void MAPIToVMIME::capitalize(char *s) {
  1952. char *p;
  1953. s[0] = toupper(s[0]); // x to X
  1954. p = s;
  1955. while ((p = strchr(p, '-'))) { // capitalize every char after a -
  1956. ++p;
  1957. if (*p != '\0')
  1958. *p = toupper(*p);
  1959. }
  1960. }
  1961. /**
  1962. * makes spaces from enters, avoid enters in Subject
  1963. *
  1964. * @param[in,out] s String to fix enter to spaces in
  1965. */
  1966. void MAPIToVMIME::removeEnters(WCHAR *s) {
  1967. WCHAR *p = s;
  1968. while (*p) {
  1969. if (*p == '\r' || *p == '\n')
  1970. *p = ' ';
  1971. ++p;
  1972. }
  1973. }
  1974. /**
  1975. * Shortcut for common conversion used in this file.
  1976. * Note: Uses class members m_converter, m_vmCharset and m_strCharset.
  1977. *
  1978. * @param[in] lpszwInput input string in WCHAR
  1979. * @return the converted text from WCHAR to vmime::text with specified charset
  1980. */
  1981. vmime::text MAPIToVMIME::getVmimeTextFromWide(const WCHAR* lpszwInput, bool bWrapInWord) {
  1982. std::string output = m_converter.convert_to<std::string>(m_strCharset.c_str(), lpszwInput, rawsize(lpszwInput), CHARSET_WCHAR);
  1983. if (bWrapInWord)
  1984. return vmime::text(vmime::word(output, m_vmCharset));
  1985. else
  1986. return vmime::text(output, m_vmCharset);
  1987. }
  1988. /**
  1989. * Shortcut for common conversion used in this file.
  1990. * Note: Uses class members m_converter, m_vmCharset and m_strCharset.
  1991. *
  1992. * @param[in] lpszwInput input string in std::wstring
  1993. * @return the converted text from WCHAR to vmime::text with specified charset
  1994. */
  1995. vmime::text MAPIToVMIME::getVmimeTextFromWide(const std::wstring& strwInput, bool bWrapInWord) {
  1996. std::string output = m_converter.convert_to<std::string>(m_strCharset.c_str(), strwInput, rawsize(strwInput), CHARSET_WCHAR);
  1997. if (bWrapInWord)
  1998. return vmime::text(vmime::word(output, m_vmCharset));
  1999. else
  2000. return vmime::text(output, m_vmCharset);
  2001. }
  2002. } /* namespace */