VMIMEToMAPI.cpp 131 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827
  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 <utility>
  20. #include "VMIMEToMAPI.h"
  21. #include <kopano/ECGuid.h>
  22. #include <kopano/ECLogger.h>
  23. #include <kopano/memory.hpp>
  24. #include <algorithm>
  25. #include <memory>
  26. #include <string>
  27. #include <fstream>
  28. #include <iostream>
  29. #include <cassert>
  30. #include <cctype>
  31. #include <cstdlib>
  32. #include <cstring>
  33. #include <kopano/ECDebug.h>
  34. #include <librosie.h>
  35. #include <vmime/vmime.hpp>
  36. #include <vmime/platforms/posix/posixHandler.hpp>
  37. #include <vmime/contentTypeField.hpp>
  38. #include <vmime/contentDispositionField.hpp>
  39. #include <libxml/HTMLparser.h>
  40. // mapi
  41. #include <mapi.h>
  42. #include <mapix.h>
  43. #include <mapiutil.h>
  44. #include <kopano/mapiext.h>
  45. #include <kopano/mapiguidext.h>
  46. #include <edkmdb.h>
  47. #include <kopano/EMSAbTag.h>
  48. #include "tnef.h"
  49. #include <kopano/codepage.h>
  50. #include <kopano/Util.h>
  51. #include <kopano/CommonUtil.h>
  52. #include <kopano/MAPIErrors.h>
  53. #include <kopano/namedprops.h>
  54. #include <kopano/charset/convert.h>
  55. #include <kopano/stringutil.h>
  56. #include <kopano/mapi_ptr.h>
  57. // inetmapi
  58. #include "ECMapiUtils.h"
  59. #include "ECVMIMEUtils.h"
  60. #include "outputStreamMAPIAdapter.h"
  61. // vcal support
  62. #include "ICalToMAPI.h"
  63. #include "config.h"
  64. using namespace std;
  65. using namespace KCHL;
  66. namespace KC {
  67. static vmime::charset vtm_upgrade_charset(vmime::charset cset, const char *ascii_upgrade = nullptr);
  68. static const char im_charset_unspec[] = "unspecified";
  69. /**
  70. * Create INT date
  71. *
  72. * @param[in] day Day of month 1-31
  73. * @param[in] month Month of year 1-12
  74. * @param[in] year Full year (eg 2008)
  75. * @return ULONG Calculated INT date
  76. */
  77. static ULONG CreateIntDate(ULONG day, ULONG month, ULONG year)
  78. {
  79. return day + month * 32 + year * 32 * 16;
  80. }
  81. /**
  82. * Create INT time
  83. *
  84. * @param[in] seconds Seconds 0-59
  85. * @param[in] minutes Minutes 0-59
  86. * @param[in] hours Hours
  87. * @return Calculated INT time
  88. */
  89. static ULONG CreateIntTime(ULONG seconds, ULONG minutes, ULONG hours)
  90. {
  91. return seconds + minutes * 64 + hours * 64 * 64;
  92. }
  93. /**
  94. * Create INT date from filetime
  95. *
  96. * Discards time information from the passed FILETIME stamp, and returns the
  97. * date part as an INT date. The passed FILETIME is interpreted in GMT.
  98. *
  99. * @param[in] ft FileTime to convert
  100. * @return Converted DATE part of the file time.
  101. */
  102. static ULONG FileTimeToIntDate(const FILETIME &ft)
  103. {
  104. struct tm date;
  105. time_t t;
  106. FileTimeToUnixTime(ft, &t);
  107. gmtime_safe(&t, &date);
  108. return CreateIntDate(date.tm_mday, date.tm_mon+1, date.tm_year+1900);
  109. }
  110. /**
  111. * Create INT time from offset in seconds
  112. *
  113. * Creates an INT time value for the moment at which the passed amount of
  114. * seconds has passed on a day.
  115. *
  116. * @param[in] seconds Number of seconds since beginning of day
  117. * @return Converted INT time
  118. */
  119. static ULONG SecondsToIntTime(ULONG seconds)
  120. {
  121. ULONG hours = seconds / (60*60);
  122. seconds -= hours * 60 * 60;
  123. ULONG minutes = seconds / 60;
  124. seconds -= minutes * 60;
  125. return CreateIntTime(seconds, minutes, hours);
  126. }
  127. /**
  128. * Default empty constructor for the inetmapi library. Sets all member
  129. * values to sane defaults.
  130. */
  131. VMIMEToMAPI::VMIMEToMAPI()
  132. {
  133. imopt_default_delivery_options(&m_dopt);
  134. m_dopt.use_received_date = false; // use Date header
  135. m_lpAdrBook = NULL;
  136. m_lpDefaultDir = NULL;
  137. m_dopt.html_safety_filter = false;
  138. }
  139. /**
  140. * Adds user set addressbook (to minimize opens on this object) and delivery options.
  141. *
  142. * @param[in] lpAdrBook Addressbook of a user.
  143. * @param[in] dopt delivery options handle differences in DAgent and Gateway behaviour.
  144. */
  145. VMIMEToMAPI::VMIMEToMAPI(LPADRBOOK lpAdrBook, delivery_options dopt) :
  146. m_dopt(dopt), m_lpAdrBook(lpAdrBook)
  147. {
  148. }
  149. VMIMEToMAPI::~VMIMEToMAPI()
  150. {
  151. if (m_lpDefaultDir)
  152. m_lpDefaultDir->Release();
  153. }
  154. /**
  155. * Parse a RFC 2822 email, and return the IMAP BODY and BODYSTRUCTURE
  156. * fetch values.
  157. *
  158. * @param[in] input The email to parse
  159. * @param[out] lpSimple The BODY value
  160. * @param[out] lpExtended The BODYSTRUCTURE value
  161. *
  162. * @return
  163. */
  164. HRESULT VMIMEToMAPI::createIMAPProperties(const std::string &input, std::string *lpEnvelope, std::string *lpBody, std::string *lpBodyStructure)
  165. {
  166. auto vmMessage = vmime::make_shared<vmime::message>();
  167. vmMessage->parse(input);
  168. if (lpBody || lpBodyStructure)
  169. messagePartToStructure(input, vmMessage, lpBody, lpBodyStructure);
  170. if (lpEnvelope)
  171. *lpEnvelope = createIMAPEnvelope(vmMessage);
  172. return hrSuccess;
  173. }
  174. /**
  175. * Entry point for the conversion from RFC 2822 mail to IMessage MAPI object.
  176. *
  177. * Finds the first block of headers to place in the
  178. * PR_TRANSPORT_MESSAGE_HEADERS property. Then it lets libvmime parse
  179. * the email and starts the main conversion function
  180. * fillMAPIMail. Afterwards it may handle signed messages, and set an
  181. * extra flag when all attachments were marked hidden.
  182. *
  183. * @param[in] input std::string containing the RFC 2822 mail.
  184. * @param[out] lpMessage Pointer to a message which was already created on a IMAPIFolder.
  185. * @return MAPI error code.
  186. * @retval MAPI_E_CALL_FAILED Caught an exception, which breaks the conversion.
  187. */
  188. HRESULT VMIMEToMAPI::convertVMIMEToMAPI(const string &input, IMessage *lpMessage) {
  189. HRESULT hr = hrSuccess;
  190. // signature variables
  191. ULONG ulAttNr;
  192. object_ptr<IStream> lpStream;
  193. ULONG nProps = 0;
  194. SPropValue attProps[3];
  195. SPropValue sPropSMIMEClass;
  196. object_ptr<IMAPITable> lpAttachTable;
  197. size_t posHeaderEnd;
  198. bool bUnix = false;
  199. try {
  200. if (m_mailState.ulMsgInMsg == 0)
  201. m_mailState.reset();
  202. // get raw headers
  203. posHeaderEnd = input.find("\r\n\r\n");
  204. if (posHeaderEnd == std::string::npos) {
  205. // input was not rfc compliant, try Unix enters
  206. posHeaderEnd = input.find("\n\n");
  207. bUnix = true;
  208. }
  209. if (posHeaderEnd != std::string::npos) {
  210. SPropValue sPropHeaders;
  211. std::string strHeaders = input.substr(0, posHeaderEnd);
  212. // make sure we have US-ASCII headers
  213. if (bUnix)
  214. StringLFtoCRLF(strHeaders);
  215. sPropHeaders.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS_A;
  216. sPropHeaders.Value.lpszA = (char *) strHeaders.c_str();
  217. HrSetOneProp(lpMessage, &sPropHeaders);
  218. }
  219. /*
  220. * Add PR_MESSAGE_SIZE initially to the size of the RFC2822
  221. * part. PR_MESSAGE_SIZE is needed for rule processing; if this
  222. * is not added, the message size is not known at processing
  223. * time because the message size is computed during save.
  224. * According to MAPI documentation, PR_MESSAGE_SIZE is an
  225. * estimated size of the message, therefore the size of the
  226. * RFC2822 message will qualify.
  227. */
  228. SPropValue sMessageSize;
  229. sMessageSize.ulPropTag = PR_MESSAGE_SIZE;
  230. sMessageSize.Value.ul = input.length();
  231. lpMessage->SetProps(1, &sMessageSize, nullptr);
  232. // turn buffer into a message
  233. auto vmMessage = vmime::make_shared<vmime::message>();
  234. vmMessage->parse(input);
  235. // save imap data first, seems vmMessage may be altered in the rest of the code.
  236. if (m_dopt.add_imap_data)
  237. createIMAPBody(input, vmMessage, lpMessage);
  238. hr = fillMAPIMail(vmMessage, lpMessage);
  239. if (hr != hrSuccess)
  240. return hr;
  241. if (m_mailState.bAttachSignature && !m_dopt.parse_smime_signed) {
  242. static constexpr const SizedSPropTagArray(2, sptaAttach) =
  243. {2, {PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN}};
  244. // Remove the parsed attachments since the client should be reading them from the
  245. // signed RFC 2822 data we are about to add.
  246. hr = lpMessage->GetAttachmentTable(0, &~lpAttachTable);
  247. if(hr != hrSuccess)
  248. return hr;
  249. rowset_ptr lpAttachRows;
  250. hr = HrQueryAllRows(lpAttachTable, sptaAttach, nullptr, nullptr, -1, &~lpAttachRows);
  251. if(hr != hrSuccess)
  252. return hr;
  253. for (unsigned int i = 0; i < lpAttachRows->cRows; ++i) {
  254. hr = lpMessage->DeleteAttach(lpAttachRows->aRow[i].lpProps[0].Value.ul, 0, NULL, 0);
  255. if(hr != hrSuccess)
  256. return hr;
  257. }
  258. // Include the entire RFC 2822 data in an attachment for the client to check
  259. auto vmHeader = vmMessage->getHeader();
  260. object_ptr<IAttach> lpAtt;
  261. hr = lpMessage->CreateAttach(nullptr, 0, &ulAttNr, &~lpAtt);
  262. if (hr != hrSuccess)
  263. return hr;
  264. // open stream
  265. hr = lpAtt->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, STGM_WRITE | STGM_TRANSACTED,
  266. MAPI_CREATE | MAPI_MODIFY, &~lpStream);
  267. if (hr != hrSuccess)
  268. return hr;
  269. outputStreamMAPIAdapter os(lpStream);
  270. // get the content-type string from the headers
  271. vmHeader->ContentType()->generate(os);
  272. // find the original received body
  273. // vmime re-generates different headers and spacings, so we can't use this.
  274. if (posHeaderEnd != string::npos)
  275. os.write(input.c_str() + posHeaderEnd, input.size() - posHeaderEnd);
  276. hr = lpStream->Commit(0);
  277. if (hr != hrSuccess)
  278. return hr;
  279. attProps[nProps].ulPropTag = PR_ATTACH_METHOD;
  280. attProps[nProps++].Value.ul = ATTACH_BY_VALUE;
  281. attProps[nProps].ulPropTag = PR_ATTACH_MIME_TAG_W;
  282. attProps[nProps++].Value.lpszW = const_cast<wchar_t *>(L"multipart/signed");
  283. attProps[nProps].ulPropTag = PR_RENDERING_POSITION;
  284. attProps[nProps++].Value.ul = -1;
  285. hr = lpAtt->SetProps(nProps, attProps, NULL);
  286. if (hr != hrSuccess)
  287. return hr;
  288. hr = lpAtt->SaveChanges(0);
  289. if (hr != hrSuccess)
  290. return hr;
  291. // saved, so mark the message so outlook knows how to find the encoded message
  292. sPropSMIMEClass.ulPropTag = PR_MESSAGE_CLASS_W;
  293. sPropSMIMEClass.Value.lpszW = const_cast<wchar_t *>(L"IPM.Note.SMIME.MultipartSigned");
  294. hr = lpMessage->SetProps(1, &sPropSMIMEClass, NULL);
  295. if (hr != hrSuccess) {
  296. ec_log_err("Unable to set message class");
  297. return hr;
  298. }
  299. }
  300. if ((m_mailState.attachLevel == ATTACH_INLINE && m_mailState.bodyLevel == BODY_HTML) || (m_mailState.bAttachSignature && m_mailState.attachLevel <= ATTACH_INLINE)) {
  301. /* Hide the attachment flag if:
  302. * - We have an HTML body and there are only INLINE attachments (don't need to hide no attachments)
  303. * - We have a signed message and there are only INLINE attachments or no attachments at all (except for the signed message)
  304. */
  305. MAPINAMEID sNameID;
  306. LPMAPINAMEID lpNameID = &sNameID;
  307. memory_ptr<SPropTagArray> lpPropTag;
  308. sNameID.lpguid = (GUID*)&PSETID_Common;
  309. sNameID.ulKind = MNID_ID;
  310. sNameID.Kind.lID = dispidSmartNoAttach;
  311. hr = lpMessage->GetIDsFromNames(1, &lpNameID, MAPI_CREATE, &~lpPropTag);
  312. if (hr != hrSuccess)
  313. return hrSuccess;
  314. attProps[0].ulPropTag = CHANGE_PROP_TYPE(lpPropTag->aulPropTag[0], PT_BOOLEAN);
  315. attProps[0].Value.b = TRUE;
  316. hr = lpMessage->SetProps(1, attProps, NULL);
  317. if (hr != hrSuccess)
  318. return hrSuccess;
  319. }
  320. }
  321. catch (vmime::exception& e) {
  322. ec_log_err("VMIME exception: %s", e.what());
  323. return MAPI_E_CALL_FAILED;
  324. }
  325. catch (std::exception& e) {
  326. ec_log_err("STD exception: %s", e.what());
  327. return MAPI_E_CALL_FAILED;
  328. }
  329. catch (...) {
  330. ec_log_err("Unknown generic exception occurred");
  331. return MAPI_E_CALL_FAILED;
  332. }
  333. return hrSuccess;
  334. }
  335. /**
  336. * The main conversion function from vmime to IMessage.
  337. *
  338. * After converting recipients and headers using their functions, it
  339. * will handle special message disposition notification bodies (read
  340. * reciept messages), or loop on all body parts
  341. * (text/html/attachments) using dissect_body() function, which in turn
  342. * may call this function to iterate on message-in-message mails.
  343. *
  344. * @param[in] vmMessage The message object from vmime.
  345. * @param[out] lpMessage The MAPI IMessage object to be filled.
  346. * @return MAPI error code.
  347. * @retval MAPI_E_CALL_FAILED Caught an exception, which breaks the conversion.
  348. */
  349. HRESULT VMIMEToMAPI::fillMAPIMail(vmime::shared_ptr<vmime::message> vmMessage,
  350. IMessage *lpMessage)
  351. {
  352. HRESULT hr;
  353. SPropValue sPropDefaults[3];
  354. sPropDefaults[0].ulPropTag = PR_MESSAGE_CLASS_W;
  355. sPropDefaults[0].Value.lpszW = const_cast<wchar_t *>(L"IPM.Note");
  356. sPropDefaults[1].ulPropTag = PR_MESSAGE_FLAGS;
  357. sPropDefaults[1].Value.ul = (m_dopt.mark_as_read ? MSGFLAG_READ : 0) | MSGFLAG_UNMODIFIED;
  358. // Default codepage is UTF-8, might be overwritten when writing
  359. // the body (plain or html). So this is only in effect when an
  360. // e-mail does not specify its charset. We use UTF-8 since it is
  361. // compatible with US-ASCII, and the conversion from plain-text
  362. // only to HTML by the client will use this codepage. This makes
  363. // sure the generated HTML version of plaintext only mails
  364. // contains all characters.
  365. sPropDefaults[2].ulPropTag = PR_INTERNET_CPID;
  366. sPropDefaults[2].Value.ul = 65001;
  367. hr = lpMessage->SetProps(3, sPropDefaults, NULL);
  368. if (hr != hrSuccess) {
  369. ec_log_err("Unable to set default mail properties");
  370. return hr;
  371. }
  372. try {
  373. // turn buffer into a message
  374. // get the part header and find out what it is...
  375. auto vmHeader = vmMessage->getHeader();
  376. auto vmBody = vmMessage->getBody();
  377. auto mt = vmime::dynamicCast<vmime::mediaType>(vmHeader->ContentType()->getValue());
  378. // pass recipients somewhere else
  379. hr = handleRecipients(vmHeader, lpMessage);
  380. if (hr != hrSuccess) {
  381. ec_log_err("Unable to parse mail recipients");
  382. return hr;
  383. }
  384. // Headers
  385. hr = handleHeaders(vmHeader, lpMessage);
  386. if (hr != hrSuccess) {
  387. ec_log_err("Unable to parse mail headers");
  388. return hr;
  389. }
  390. if (vmime::mdn::MDNHelper::isMDN(vmMessage) == true)
  391. {
  392. vmime::mdn::receivedMDNInfos receivedMDN = vmime::mdn::MDNHelper::getReceivedMDN(vmMessage);
  393. auto myBody = vmMessage->getBody();
  394. // it is possible to get 3 bodyparts.
  395. // text/plain, message/disposition-notification, text/rfc822-headers
  396. // the third part seems optional. and some clients send multipart/alternative instead of text/plain.
  397. // Loop to get text/plain body or multipart/alternative.
  398. for (size_t i = 0; i < myBody->getPartCount(); ++i) {
  399. auto bPart = myBody->getPartAt(i);
  400. auto ctf = bPart->getHeader()->findField(vmime::fields::CONTENT_TYPE);
  401. if ((vmime::dynamicCast<vmime::mediaType>(ctf->getValue())->getType() == vmime::mediaTypes::TEXT &&
  402. vmime::dynamicCast<vmime::mediaType>(ctf->getValue())->getSubType() == vmime::mediaTypes::TEXT_PLAIN) ||
  403. (vmime::dynamicCast<vmime::mediaType>(ctf->getValue())->getType() == vmime::mediaTypes::MULTIPART &&
  404. vmime::dynamicCast<vmime::mediaType>(ctf->getValue())->getSubType() == vmime::mediaTypes::MULTIPART_ALTERNATIVE)) {
  405. hr = dissect_body(bPart->getHeader(), bPart->getBody(), lpMessage);
  406. if (hr != hrSuccess) {
  407. ec_log_err("Unable to parse MDN mail body");
  408. return hr;
  409. }
  410. // we have a body, lets skip the other parts
  411. break;
  412. }
  413. }
  414. if (receivedMDN.getDisposition().getType() == vmime::dispositionTypes::DELETED)
  415. {
  416. sPropDefaults[0].ulPropTag = PR_MESSAGE_CLASS_W;
  417. sPropDefaults[0].Value.lpszW = const_cast<wchar_t *>(L"REPORT.IPM.Note.IPNNRN");
  418. } else {
  419. sPropDefaults[0].ulPropTag = PR_MESSAGE_CLASS_W;
  420. sPropDefaults[0].Value.lpszW = const_cast<wchar_t *>(L"REPORT.IPM.Note.IPNRN");
  421. }
  422. string strId = "<"+receivedMDN.getOriginalMessageId().getId()+">";
  423. sPropDefaults[1].ulPropTag = 0x1046001E; // ptagOriginalInetMessageID
  424. sPropDefaults[1].Value.lpszA = (LPSTR)strId.c_str();
  425. hr = lpMessage->SetProps(2, sPropDefaults, NULL);
  426. if (hr != hrSuccess) {
  427. ec_log_err("Unable to set MDN mail properties");
  428. return hr;
  429. }
  430. } else {
  431. // multiparts are handled in disectBody, if any
  432. hr = dissect_body(vmHeader, vmBody, lpMessage);
  433. if (hr != hrSuccess) {
  434. ec_log_err("Unable to parse mail body");
  435. return hr;
  436. }
  437. }
  438. }
  439. catch (vmime::exception& e) {
  440. ec_log_err("VMIME exception on create message: %s", e.what());
  441. return MAPI_E_CALL_FAILED;
  442. }
  443. catch (std::exception& e) {
  444. ec_log_err("STD exception on create message: %s", e.what());
  445. return MAPI_E_CALL_FAILED;
  446. }
  447. catch (...) {
  448. ec_log_err("Unknown generic exception occurred on create message");
  449. return MAPI_E_CALL_FAILED;
  450. }
  451. createIMAPEnvelope(vmMessage, lpMessage);
  452. // ignore error/warings from fixup function: it's not critical for correct delivery
  453. postWriteFixups(lpMessage);
  454. return hr;
  455. }
  456. /**
  457. * Convert all kinds of headers into MAPI properties.
  458. *
  459. * Converts most known headers to their respective MAPI property. It
  460. * will not handle the To/Cc/Bcc headers, but does the From/Sender
  461. * headers, and might convert those to known ZARAFA addressbook entries.
  462. * It also converts X-headers to named properties like PSTs do.
  463. *
  464. * @param[in] vmHeader vmime header part of a message.
  465. * @param[out] lpMessage MAPI message to write header properties in.
  466. * @return MAPI error code.
  467. * @retval MAPI_E_CALL_FAILED Caught an exception, which breaks the conversion.
  468. */
  469. HRESULT VMIMEToMAPI::handleHeaders(vmime::shared_ptr<vmime::header> vmHeader,
  470. IMessage *lpMessage)
  471. {
  472. HRESULT hr = hrSuccess;
  473. std::string strInternetMessageId, strInReplyTos, strReferences;
  474. std::wstring wstrSubject;
  475. std::wstring wstrReplyTo, wstrReplyToMail;
  476. std::string strClientTime;
  477. std::wstring wstrFromName, wstrSenderName;
  478. std::string strFromEmail, strFromSearchKey;
  479. std::string strSenderEmail, strSenderSearchKey;
  480. ULONG cbFromEntryID; // representing entry id
  481. memory_ptr<ENTRYID> lpFromEntryID, lpSenderEntryID;
  482. ULONG cbSenderEntryID;
  483. SPropValue sConTopic;
  484. // setprops
  485. memory_ptr<FLATENTRY> lpEntry;
  486. memory_ptr<FLATENTRYLIST> lpEntryList;
  487. ULONG cb = 0;
  488. int nProps = 0;
  489. SPropValue msgProps[22];
  490. // temp
  491. ULONG cbEntryID;
  492. memory_ptr<ENTRYID> lpEntryID;
  493. memory_ptr<SPropValue> lpRecipProps, lpPropNormalizedSubject;
  494. ULONG ulRecipProps;
  495. // order and types are important for modifyFromAddressBook()
  496. static constexpr const SizedSPropTagArray(7, sptaRecipPropsSentRepr) = {7, {
  497. PR_SENT_REPRESENTING_ADDRTYPE_W, PR_SENT_REPRESENTING_NAME_W,
  498. PR_NULL /* PR_xxx_DISPLAY_TYPE not available */,
  499. PR_SENT_REPRESENTING_EMAIL_ADDRESS_W, PR_SENT_REPRESENTING_ENTRYID,
  500. PR_SENT_REPRESENTING_SEARCH_KEY, PR_NULL /* PR_xxx_SMTP_ADDRESS not available */
  501. } };
  502. static constexpr const SizedSPropTagArray(7, sptaRecipPropsSender) = {7, {
  503. PR_SENDER_ADDRTYPE_W, PR_SENDER_NAME_W,
  504. PR_NULL /* PR_xxx_DISPLAY_TYPE not available */,
  505. PR_SENDER_EMAIL_ADDRESS_W, PR_SENDER_ENTRYID,
  506. PR_SENDER_SEARCH_KEY, PR_NULL /* PR_xxx_SMTP_ADDRESS not available */
  507. } };
  508. try {
  509. // internet message ID
  510. if(vmHeader->hasField(vmime::fields::MESSAGE_ID)) {
  511. strInternetMessageId = vmHeader->MessageId()->getValue()->generate();
  512. msgProps[nProps].ulPropTag = PR_INTERNET_MESSAGE_ID_A;
  513. msgProps[nProps++].Value.lpszA = (char*)strInternetMessageId.c_str();
  514. }
  515. // In-Reply-To header
  516. if(vmHeader->hasField(vmime::fields::IN_REPLY_TO)) {
  517. strInReplyTos = vmHeader->InReplyTo()->getValue()->generate();
  518. msgProps[nProps].ulPropTag = PR_IN_REPLY_TO_ID_A;
  519. msgProps[nProps++].Value.lpszA = (char*)strInReplyTos.c_str();
  520. }
  521. // References header
  522. if(vmHeader->hasField(vmime::fields::REFERENCES)) {
  523. strReferences = vmHeader->References()->getValue()->generate();
  524. msgProps[nProps].ulPropTag = PR_INTERNET_REFERENCES_A;
  525. msgProps[nProps++].Value.lpszA = (char*)strReferences.c_str();
  526. }
  527. // set subject
  528. if(vmHeader->hasField(vmime::fields::SUBJECT)) {
  529. wstrSubject = getWideFromVmimeText(*vmime::dynamicCast<vmime::text>(vmHeader->Subject()->getValue()));
  530. msgProps[nProps].ulPropTag = PR_SUBJECT_W;
  531. msgProps[nProps++].Value.lpszW = (WCHAR *)wstrSubject.c_str();
  532. }
  533. // set ReplyTo
  534. if (!vmime::dynamicCast<vmime::mailbox>(vmHeader->ReplyTo()->getValue())->isEmpty()) {
  535. // First, set PR_REPLY_RECIPIENT_NAMES
  536. wstrReplyTo = getWideFromVmimeText(vmime::dynamicCast<vmime::mailbox>(vmHeader->ReplyTo()->getValue())->getName());
  537. wstrReplyToMail = m_converter.convert_to<wstring>(vmime::dynamicCast<vmime::mailbox>(vmHeader->ReplyTo()->getValue())->getEmail().toString());
  538. if (wstrReplyTo.empty())
  539. wstrReplyTo = wstrReplyToMail;
  540. msgProps[nProps].ulPropTag = PR_REPLY_RECIPIENT_NAMES_W;
  541. msgProps[nProps++].Value.lpszW = (WCHAR *)wstrReplyTo.c_str();
  542. // Now, set PR_REPLY_RECIPIENT_ENTRIES (a FLATENTRYLIST)
  543. hr = ECCreateOneOff((LPTSTR)wstrReplyTo.c_str(), (LPTSTR)L"SMTP", (LPTSTR)wstrReplyToMail.c_str(), MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO, &cbEntryID, &~lpEntryID);
  544. if (hr != hrSuccess)
  545. return hr;
  546. cb = CbNewFLATENTRY(cbEntryID);
  547. hr = MAPIAllocateBuffer(cb, &~lpEntry);
  548. if (hr != hrSuccess)
  549. return hr;
  550. memcpy(lpEntry->abEntry, lpEntryID, cbEntryID);
  551. lpEntry->cb = cbEntryID;
  552. cb = CbNewFLATENTRYLIST(cb);
  553. hr = MAPIAllocateBuffer(cb, &~lpEntryList);
  554. if (hr != hrSuccess)
  555. return hr;
  556. lpEntryList->cEntries = 1;
  557. lpEntryList->cbEntries = CbFLATENTRY(lpEntry);
  558. memcpy(&lpEntryList->abEntries, lpEntry, CbFLATENTRY(lpEntry));
  559. msgProps[nProps].ulPropTag = PR_REPLY_RECIPIENT_ENTRIES;
  560. msgProps[nProps].Value.bin.cb = CbFLATENTRYLIST(lpEntryList);
  561. msgProps[nProps++].Value.bin.lpb = reinterpret_cast<unsigned char *>(lpEntryList.get());
  562. }
  563. // setting sent time
  564. if(vmHeader->hasField(vmime::fields::DATE)) {
  565. msgProps[nProps].ulPropTag = PR_CLIENT_SUBMIT_TIME;
  566. msgProps[nProps++].Value.ft = vmimeDatetimeToFiletime(*vmime::dynamicCast<vmime::datetime>(vmHeader->Date()->getValue()));
  567. // set sent date (actual send date, disregarding timezone)
  568. vmime::datetime d = *vmime::dynamicCast<vmime::datetime>(vmHeader->Date()->getValue());
  569. d.setTime(0,0,0,0);
  570. msgProps[nProps].ulPropTag = PR_EC_CLIENT_SUBMIT_DATE;
  571. msgProps[nProps++].Value.ft = vmimeDatetimeToFiletime(d);
  572. }
  573. // setting receive date (now)
  574. // parse from Received header, if possible
  575. vmime::datetime date = vmime::datetime::now();
  576. bool found_date = false;
  577. if (m_dopt.use_received_date || m_mailState.ulMsgInMsg) {
  578. if (vmHeader->hasField("Received")) {
  579. auto recv = vmime::dynamicCast<vmime::relay>(vmHeader->findField("Received")->getValue());
  580. if (recv) {
  581. date = recv->getDate();
  582. found_date = true;
  583. }
  584. } else if (m_mailState.ulMsgInMsg) {
  585. date = *vmime::dynamicCast<vmime::datetime>(vmHeader->Date()->getValue());
  586. found_date = true;
  587. } else {
  588. date = vmime::datetime::now();
  589. }
  590. }
  591. // When parse_smime_signed = True, we don't want to change the delivery date, since otherwise
  592. // clients which decode a signed email using mapi_inetmapi_imtomapi() will have a different deliver time
  593. // when opening a signed email in for example the WebApp
  594. if (!m_dopt.parse_smime_signed && (!m_mailState.ulMsgInMsg || found_date)) {
  595. msgProps[nProps].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
  596. msgProps[nProps++].Value.ft = vmimeDatetimeToFiletime(date);
  597. // Also save delivery DATE without timezone
  598. date.setTime(0,0,0,0);
  599. msgProps[nProps].ulPropTag = PR_EC_MESSAGE_DELIVERY_DATE;
  600. msgProps[nProps++].Value.ft = vmimeDatetimeToFiletime(date);
  601. }
  602. // The real sender of the mail
  603. if(vmHeader->hasField(vmime::fields::FROM)) {
  604. strFromEmail = vmime::dynamicCast<vmime::mailbox>(vmHeader->From()->getValue())->getEmail().toString();
  605. if (!vmime::dynamicCast<vmime::mailbox>(vmHeader->From()->getValue())->getName().isEmpty())
  606. wstrFromName = getWideFromVmimeText(vmime::dynamicCast<vmime::mailbox>(vmHeader->From()->getValue())->getName());
  607. hr = modifyFromAddressBook(&~lpRecipProps, &ulRecipProps,
  608. strFromEmail.c_str(), wstrFromName.c_str(),
  609. MAPI_ORIG, sptaRecipPropsSentRepr);
  610. if (hr == hrSuccess) {
  611. hr = lpMessage->SetProps(ulRecipProps, lpRecipProps, NULL);
  612. if (hr != hrSuccess)
  613. return hr;
  614. } else {
  615. if (wstrFromName.empty())
  616. wstrFromName = m_converter.convert_to<wstring>(strFromEmail);
  617. msgProps[nProps].ulPropTag = PR_SENT_REPRESENTING_NAME_W;
  618. msgProps[nProps++].Value.lpszW = (WCHAR *)wstrFromName.c_str();
  619. msgProps[nProps].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS_A;
  620. msgProps[nProps++].Value.lpszA = (char*)strFromEmail.c_str();
  621. strFromSearchKey = strToUpper("SMTP:" + strFromEmail);
  622. msgProps[nProps].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
  623. msgProps[nProps].Value.bin.cb = strFromSearchKey.size()+1; // include string terminator
  624. msgProps[nProps++].Value.bin.lpb = (BYTE*)strFromSearchKey.c_str();
  625. msgProps[nProps].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE_W;
  626. msgProps[nProps++].Value.lpszW = const_cast<wchar_t *>(L"SMTP");
  627. hr = ECCreateOneOff((LPTSTR)wstrFromName.c_str(), (LPTSTR)L"SMTP", (LPTSTR)m_converter.convert_to<wstring>(strFromEmail).c_str(),
  628. MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO, &cbFromEntryID, &~lpFromEntryID);
  629. if(hr != hrSuccess)
  630. return hr;
  631. msgProps[nProps].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
  632. msgProps[nProps].Value.bin.cb = cbFromEntryID;
  633. msgProps[nProps++].Value.bin.lpb = reinterpret_cast<unsigned char *>(lpFromEntryID.get());
  634. // SetProps is later on...
  635. }
  636. }
  637. if (vmHeader->hasField(vmime::fields::SENDER) || vmHeader->hasField(vmime::fields::FROM)) {
  638. // The original sender of the mail account (if non sender exist then the FROM)
  639. strSenderEmail = vmime::dynamicCast<vmime::mailbox>(vmHeader->Sender()->getValue())->getEmail().toString();
  640. if (vmime::dynamicCast<vmime::mailbox>(vmHeader->Sender()->getValue())->getName().isEmpty() &&
  641. (strSenderEmail.empty() || strSenderEmail == "@")) {
  642. // Fallback on the original from address
  643. wstrSenderName = wstrFromName;
  644. strSenderEmail = strFromEmail;
  645. } else if (!vmime::dynamicCast<vmime::mailbox>(vmHeader->Sender()->getValue())->getName().isEmpty()) {
  646. wstrSenderName = getWideFromVmimeText(vmime::dynamicCast<vmime::mailbox>(vmHeader->Sender()->getValue())->getName());
  647. } else {
  648. wstrSenderName = m_converter.convert_to<wstring>(strSenderEmail);
  649. }
  650. hr = modifyFromAddressBook(&~lpRecipProps, &ulRecipProps,
  651. strSenderEmail.c_str(), wstrSenderName.c_str(),
  652. MAPI_ORIG, sptaRecipPropsSender);
  653. if (hr == hrSuccess) {
  654. hr = lpMessage->SetProps(ulRecipProps, lpRecipProps, NULL);
  655. if (hr != hrSuccess)
  656. return hr;
  657. } else {
  658. msgProps[nProps].ulPropTag = PR_SENDER_NAME_W;
  659. msgProps[nProps++].Value.lpszW = (WCHAR *)wstrSenderName.c_str();
  660. msgProps[nProps].ulPropTag = PR_SENDER_EMAIL_ADDRESS_A;
  661. msgProps[nProps++].Value.lpszA = (char*)strSenderEmail.c_str();
  662. strSenderSearchKey = strToUpper("SMTP:" + strSenderEmail);
  663. msgProps[nProps].ulPropTag = PR_SENDER_SEARCH_KEY;
  664. msgProps[nProps].Value.bin.cb = strSenderSearchKey.size()+1; // include string terminator
  665. msgProps[nProps++].Value.bin.lpb = (BYTE*)strSenderSearchKey.c_str();
  666. msgProps[nProps].ulPropTag = PR_SENDER_ADDRTYPE_W;
  667. msgProps[nProps++].Value.lpszW = const_cast<wchar_t *>(L"SMTP");
  668. hr = ECCreateOneOff((LPTSTR)wstrSenderName.c_str(), (LPTSTR)L"SMTP", (LPTSTR)m_converter.convert_to<wstring>(strSenderEmail).c_str(),
  669. MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO, &cbSenderEntryID, &~lpSenderEntryID);
  670. if(hr != hrSuccess)
  671. return hr;
  672. msgProps[nProps].ulPropTag = PR_SENDER_ENTRYID;
  673. msgProps[nProps].Value.bin.cb = cbSenderEntryID;
  674. msgProps[nProps++].Value.bin.lpb = reinterpret_cast<unsigned char *>(lpSenderEntryID.get());
  675. }
  676. }
  677. hr = lpMessage->SetProps(nProps, msgProps, NULL);
  678. if (hr != hrSuccess)
  679. return hr;
  680. //Conversation topic
  681. if (vmHeader->hasField("Thread-Topic"))
  682. {
  683. wstring convTT = getWideFromVmimeText(*vmime::dynamicCast<vmime::text>(vmHeader->findField("Thread-Topic")->getValue()));
  684. sConTopic.ulPropTag = PR_CONVERSATION_TOPIC_W;
  685. sConTopic.Value.lpszW = (WCHAR *)convTT.c_str();
  686. hr = lpMessage->SetProps(1, &sConTopic, NULL);
  687. if (hr != hrSuccess)
  688. return hr;
  689. } else if (HrGetOneProp(lpMessage, PR_NORMALIZED_SUBJECT_W, &~lpPropNormalizedSubject) == hrSuccess) {
  690. sConTopic.ulPropTag = PR_CONVERSATION_TOPIC_W;
  691. sConTopic.Value.lpszW = lpPropNormalizedSubject->Value.lpszW;
  692. hr = lpMessage->SetProps(1, &sConTopic, NULL);
  693. if (hr != hrSuccess)
  694. return hr;
  695. }
  696. // Thread-Index header
  697. if (vmHeader->hasField("Thread-Index"))
  698. {
  699. vmime::string outString;
  700. SPropValue sThreadIndex;
  701. string threadIndex = vmHeader->findField("Thread-Index")->getValue()->generate();
  702. auto enc = vmime::utility::encoder::encoderFactory::getInstance()->create("base64");
  703. vmime::utility::inputStreamStringAdapter in(threadIndex);
  704. vmime::utility::outputStreamStringAdapter out(outString);
  705. enc->decode(in, out);
  706. sThreadIndex.ulPropTag = PR_CONVERSATION_INDEX;
  707. sThreadIndex.Value.bin.cb = outString.size();
  708. sThreadIndex.Value.bin.lpb = (LPBYTE)outString.c_str();
  709. hr = lpMessage->SetProps(1, &sThreadIndex, NULL);
  710. if (hr != hrSuccess)
  711. return hr;
  712. }
  713. if (vmHeader->hasField("Importance")) {
  714. SPropValue sPriority[2];
  715. sPriority[0].ulPropTag = PR_PRIORITY;
  716. sPriority[1].ulPropTag = PR_IMPORTANCE;
  717. auto importance = strToLower(vmHeader->findField("Importance")->getValue()->generate());
  718. if(importance.compare("high") == 0) {
  719. sPriority[0].Value.ul = PRIO_URGENT;
  720. sPriority[1].Value.ul = IMPORTANCE_HIGH;
  721. } else if(importance.compare("low") == 0) {
  722. sPriority[0].Value.ul = PRIO_NONURGENT;
  723. sPriority[1].Value.ul = IMPORTANCE_LOW;
  724. } else {
  725. sPriority[0].Value.ul = PRIO_NORMAL;
  726. sPriority[1].Value.ul = IMPORTANCE_NORMAL;
  727. }
  728. hr = lpMessage->SetProps(2, sPriority, NULL);
  729. if (hr != hrSuccess)
  730. return hr;
  731. }
  732. // X-Priority header
  733. if (vmHeader->hasField("X-Priority")) {
  734. SPropValue sPriority[2];
  735. sPriority[0].ulPropTag = PR_PRIORITY;
  736. sPriority[1].ulPropTag = PR_IMPORTANCE;
  737. string xprio = vmHeader->findField("X-Priority")->getValue()->generate();
  738. switch (xprio[0]) {
  739. case '1':
  740. case '2':
  741. sPriority[0].Value.ul = PRIO_URGENT;
  742. sPriority[1].Value.ul = IMPORTANCE_HIGH;
  743. break;
  744. case '4':
  745. case '5':
  746. sPriority[0].Value.ul = PRIO_NONURGENT;
  747. sPriority[1].Value.ul = IMPORTANCE_LOW;
  748. break;
  749. default:
  750. case '3':
  751. sPriority[0].Value.ul = PRIO_NORMAL;
  752. sPriority[1].Value.ul = IMPORTANCE_NORMAL;
  753. break;
  754. };
  755. hr = lpMessage->SetProps(2, sPriority, NULL);
  756. if (hr != hrSuccess)
  757. return hr;
  758. }
  759. // X-Kopano-Vacation header (TODO: other headers?)
  760. if (vmHeader->hasField("X-Kopano-Vacation")) {
  761. SPropValue sIcon[1];
  762. sIcon[0].ulPropTag = PR_ICON_INDEX;
  763. sIcon[0].Value.l = ICON_MAIL_OOF;
  764. // exchange sets PR_MESSAGE_CLASS to IPM.Note.Rules.OofTemplate.Microsoft to get the icon
  765. hr = lpMessage->SetProps(1, sIcon, NULL);
  766. if (hr != hrSuccess)
  767. return hr;
  768. }
  769. // Sensitivity header
  770. if (vmHeader->hasField("Sensitivity")) {
  771. SPropValue sSensitivity[1];
  772. auto sensitivity = strToLower(vmHeader->findField("Sensitivity")->getValue()->generate());
  773. sSensitivity[0].ulPropTag = PR_SENSITIVITY;
  774. if (sensitivity.compare("personal") == 0)
  775. sSensitivity[0].Value.ul = SENSITIVITY_PERSONAL;
  776. else if (sensitivity.compare("private") == 0)
  777. sSensitivity[0].Value.ul = SENSITIVITY_PRIVATE;
  778. else if (sensitivity.compare("company-confidential") == 0)
  779. sSensitivity[0].Value.ul = SENSITIVITY_COMPANY_CONFIDENTIAL;
  780. else
  781. sSensitivity[0].Value.ul = SENSITIVITY_NONE;
  782. hr = lpMessage->SetProps(1, sSensitivity, NULL);
  783. if (hr != hrSuccess)
  784. return hr;
  785. }
  786. // Expiry time header
  787. try {
  788. if (vmHeader->hasField("Expiry-Time")) {
  789. SPropValue sExpiryTime;
  790. // reparse string to datetime
  791. vmime::datetime expiry(vmHeader->findField("Expiry-Time")->getValue()->generate());
  792. sExpiryTime.ulPropTag = PR_EXPIRY_TIME;
  793. sExpiryTime.Value.ft = vmimeDatetimeToFiletime(expiry);
  794. hr = lpMessage->SetProps(1, &sExpiryTime, NULL);
  795. if (hr != hrSuccess)
  796. return hr;
  797. }
  798. }
  799. catch(...) {}
  800. // read receipt request
  801. // note: vmime never checks if the given pos to getMailboxAt() and similar functions is valid, so we check if the list is empty before using it
  802. if (vmHeader->hasField("Disposition-Notification-To") &&
  803. !vmime::dynamicCast<vmime::mailboxList>(vmHeader->DispositionNotificationTo()->getValue())->isEmpty())
  804. {
  805. auto mbReadReceipt = vmime::dynamicCast<vmime::mailboxList>(vmHeader->DispositionNotificationTo()->getValue())->getMailboxAt(0); // we only use the 1st
  806. if (mbReadReceipt && !mbReadReceipt->isEmpty())
  807. {
  808. wstring wstrRRName = getWideFromVmimeText(mbReadReceipt->getName());
  809. wstring wstrRREmail = m_converter.convert_to<wstring>(mbReadReceipt->getEmail().toString());
  810. if (wstrRRName.empty())
  811. wstrRRName = wstrRREmail;
  812. //FIXME: Use an addressbook entry for "ZARAFA"-type addresses?
  813. hr = ECCreateOneOff((LPTSTR)wstrRRName.c_str(), (LPTSTR)L"SMTP", (LPTSTR)wstrRREmail.c_str(), MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO, &cbEntryID, &~lpEntryID);
  814. if (hr != hrSuccess)
  815. return hr;
  816. SPropValue sRRProps[4];
  817. sRRProps[0].ulPropTag = PR_READ_RECEIPT_REQUESTED;
  818. sRRProps[0].Value.b = true;
  819. sRRProps[1].ulPropTag = PR_MESSAGE_FLAGS;
  820. sRRProps[1].Value.ul = (m_dopt.mark_as_read ? MSGFLAG_READ : 0) | MSGFLAG_UNMODIFIED | MSGFLAG_RN_PENDING | MSGFLAG_NRN_PENDING;
  821. sRRProps[2].ulPropTag = PR_REPORT_ENTRYID;
  822. sRRProps[2].Value.bin.cb = cbEntryID;
  823. sRRProps[2].Value.bin.lpb = reinterpret_cast<unsigned char *>(lpEntryID.get());
  824. sRRProps[3].ulPropTag = PR_REPORT_NAME_W;
  825. sRRProps[3].Value.lpszW = (WCHAR *)wstrRREmail.c_str();
  826. hr = lpMessage->SetProps(4, sRRProps, NULL);
  827. if (hr != hrSuccess)
  828. return hr;
  829. }
  830. }
  831. for (const auto &field : vmHeader->getFieldList()) {
  832. std::string value, name = field->getName();
  833. if (name[0] != 'X')
  834. continue;
  835. // exclusion list?
  836. if (name == "X-Priority")
  837. continue;
  838. name = strToLower(name);
  839. memory_ptr<MAPINAMEID> lpNameID;
  840. memory_ptr<SPropTagArray> lpPropTags;
  841. if ((hr = MAPIAllocateBuffer(sizeof(MAPINAMEID), &~lpNameID)) != hrSuccess)
  842. return hr;
  843. lpNameID->lpguid = (GUID*)&PS_INTERNET_HEADERS;
  844. lpNameID->ulKind = MNID_STRING;
  845. int vlen = mbstowcs(NULL, name.c_str(), 0) +1;
  846. if ((hr = MAPIAllocateMore(vlen*sizeof(WCHAR), lpNameID, (void**)&lpNameID->Kind.lpwstrName)) != hrSuccess)
  847. return hr;
  848. mbstowcs(lpNameID->Kind.lpwstrName, name.c_str(), vlen);
  849. hr = lpMessage->GetIDsFromNames(1, &+lpNameID, MAPI_CREATE, &~lpPropTags);
  850. if (hr != hrSuccess) {
  851. hr = hrSuccess;
  852. continue;
  853. }
  854. SPropValue sProp[1];
  855. value = field->getValue()->generate();
  856. sProp[0].ulPropTag = PROP_TAG(PT_STRING8, PROP_ID(lpPropTags->aulPropTag[0]));
  857. sProp[0].Value.lpszA = (char*)value.c_str();
  858. lpMessage->SetProps(1, sProp, NULL);
  859. // in case of error: ignore this x-header as named props then
  860. }
  861. }
  862. catch (vmime::exception& e) {
  863. ec_log_err("VMIME exception on parsing headers: %s", e.what());
  864. return MAPI_E_CALL_FAILED;
  865. }
  866. catch (std::exception& e) {
  867. ec_log_err("STD exception on parsing headers: %s", e.what());
  868. return MAPI_E_CALL_FAILED;
  869. }
  870. catch (...) {
  871. ec_log_err("Unknown generic exception occurred on parsing headers");
  872. return MAPI_E_CALL_FAILED;
  873. }
  874. return hr;
  875. }
  876. /**
  877. * Sets PR_MESSAGE_TO_ME, PR_MESSAGE_CC_ME and PR_MESSAGE_RECIP_ME appropriately.
  878. *
  879. * Delivery options struct should contain the EntryID of the user you
  880. * are delivering for.
  881. *
  882. * @param[out] lpMessage MAPI IMessage to set properties in
  883. * @param[in] lpRecipients List of MAPI recipients found in To/Cc/Bcc headers.
  884. * @return MAPI error code.
  885. */
  886. HRESULT VMIMEToMAPI::handleMessageToMeProps(IMessage *lpMessage, LPADRLIST lpRecipients) {
  887. unsigned int i = 0;
  888. bool bToMe = false;
  889. bool bCcMe = false;
  890. bool bRecipMe = false;
  891. SPropValue sProps[3];
  892. if (m_dopt.user_entryid == NULL)
  893. return hrSuccess; /* Not an error, but do not do any processing */
  894. // Loop through all recipients of the message to find ourselves in the recipient list.
  895. for (i = 0; i < lpRecipients->cEntries; ++i) {
  896. auto lpRecipType = PCpropFindProp(lpRecipients->aEntries[i].rgPropVals, lpRecipients->aEntries[i].cValues, PR_RECIPIENT_TYPE);
  897. auto lpEntryId = PCpropFindProp(lpRecipients->aEntries[i].rgPropVals, lpRecipients->aEntries[i].cValues, PR_ENTRYID);
  898. if(lpRecipType == NULL)
  899. continue;
  900. if(lpEntryId == NULL)
  901. continue;
  902. // The user matches if the entryid of the recipient is equal to ours
  903. if(lpEntryId->Value.bin.cb != m_dopt.user_entryid->cb)
  904. continue;
  905. if(memcmp(lpEntryId->Value.bin.lpb, m_dopt.user_entryid->lpb, lpEntryId->Value.bin.cb) != 0)
  906. continue;
  907. // Users match, check what type
  908. bRecipMe = true;
  909. if(lpRecipType->Value.ul == MAPI_TO)
  910. bToMe = true;
  911. else if(lpRecipType->Value.ul == MAPI_CC)
  912. bCcMe = true;
  913. }
  914. // Set the properties
  915. sProps[0].ulPropTag = PR_MESSAGE_RECIP_ME;
  916. sProps[0].Value.b = bRecipMe;
  917. sProps[1].ulPropTag = PR_MESSAGE_TO_ME;
  918. sProps[1].Value.b = bToMe;
  919. sProps[2].ulPropTag = PR_MESSAGE_CC_ME;
  920. sProps[2].Value.b = bCcMe;
  921. lpMessage->SetProps(3, sProps, NULL);
  922. return hrSuccess;
  923. }
  924. /**
  925. * Convert To/Cc/Bcc headers to a valid recipient table in the
  926. * IMessage object.
  927. *
  928. * @param[in] vmHeader vmime header part of a message.
  929. * @param[out] lpMessage MAPI message to write header properties in.
  930. * @return MAPI error code.
  931. * @retval MAPI_E_CALL_FAILED Caught an exception, which breaks the conversion.
  932. */
  933. HRESULT VMIMEToMAPI::handleRecipients(vmime::shared_ptr<vmime::header> vmHeader,
  934. IMessage *lpMessage)
  935. {
  936. HRESULT hr = hrSuccess;
  937. adrlist_ptr lpRecipients;
  938. try {
  939. auto lpVMAListRecip = vmime::dynamicCast<vmime::addressList>(vmHeader->To()->getValue());
  940. auto lpVMAListCopyRecip = vmime::dynamicCast<vmime::addressList>(vmHeader->Cc()->getValue());
  941. auto lpVMAListBlCpRecip = vmime::dynamicCast<vmime::addressList>(vmHeader->Bcc()->getValue());
  942. int iAdresCount = lpVMAListRecip->getAddressCount() + lpVMAListCopyRecip->getAddressCount() + lpVMAListBlCpRecip->getAddressCount();
  943. if (iAdresCount == 0)
  944. return hr;
  945. hr = MAPIAllocateBuffer(CbNewADRLIST(iAdresCount), &~lpRecipients);
  946. if (hr != hrSuccess)
  947. return hr;
  948. lpRecipients->cEntries = 0;
  949. if (!lpVMAListRecip->isEmpty()) {
  950. hr = modifyRecipientList(lpRecipients, lpVMAListRecip, MAPI_TO);
  951. if (hr != hrSuccess)
  952. return hr;
  953. }
  954. if (!lpVMAListCopyRecip->isEmpty()) {
  955. hr = modifyRecipientList(lpRecipients, lpVMAListCopyRecip, MAPI_CC);
  956. if (hr != hrSuccess)
  957. return hr;
  958. }
  959. if (!lpVMAListBlCpRecip->isEmpty()) {
  960. hr = modifyRecipientList(lpRecipients, lpVMAListBlCpRecip, MAPI_BCC);
  961. if (hr != hrSuccess)
  962. return hr;
  963. }
  964. // Handle PR_MESSAGE_*_ME props
  965. hr = handleMessageToMeProps(lpMessage, lpRecipients);
  966. if (hr != hrSuccess)
  967. return hr;
  968. // actually modify recipients in mapi object
  969. hr = lpMessage->ModifyRecipients(MODRECIP_ADD, lpRecipients);
  970. if (hr != hrSuccess)
  971. return hr;
  972. }
  973. catch (vmime::exception& e) {
  974. ec_log_err("VMIME exception on recipients: %s", e.what());
  975. return MAPI_E_CALL_FAILED;
  976. }
  977. catch (std::exception& e) {
  978. ec_log_err("STD exception on recipients: %s", e.what());
  979. return MAPI_E_CALL_FAILED;
  980. }
  981. catch (...) {
  982. ec_log_err("Unknown generic exception occurred on recipients");
  983. return MAPI_E_CALL_FAILED;
  984. }
  985. return hrSuccess;
  986. }
  987. /**
  988. * Adds recipients from a vmime list to rows for the recipient
  989. * table. Starts adding at offset in cEntries member of the lpRecipients
  990. * struct.
  991. *
  992. * Entries are either converted to an addressbook entry, or an one-off entry.
  993. *
  994. * @param[out] lpRecipients MAPI address list to be filled.
  995. * @param[in] vmAddressList List of recipient of a specific type (To/Cc/Bcc).
  996. * @param[in] ulRecipType Type of recipients found in vmAddressList.
  997. * @return MAPI error code.
  998. * @retval MAPI_E_CALL_FAILED Caught an exception, which breaks the conversion.
  999. */
  1000. HRESULT VMIMEToMAPI::modifyRecipientList(LPADRLIST lpRecipients,
  1001. vmime::shared_ptr<vmime::addressList> vmAddressList, ULONG ulRecipType)
  1002. {
  1003. HRESULT hr = hrSuccess;
  1004. int iAddressCount = vmAddressList->getAddressCount();
  1005. ULONG cbEntryID = 0;
  1006. memory_ptr<ENTRYID> lpEntryID;
  1007. vmime::shared_ptr<vmime::mailbox> mbx;
  1008. vmime::shared_ptr<vmime::mailboxGroup> grp;
  1009. vmime::shared_ptr<vmime::address> vmAddress;
  1010. std::wstring wstrName;
  1011. std::string strEmail, strSearch;
  1012. unsigned int iRecipNum = 0;
  1013. // order and types are important for modifyFromAddressBook()
  1014. static constexpr const SizedSPropTagArray(7, sptaRecipientProps) =
  1015. {7, {PR_ADDRTYPE_W, PR_DISPLAY_NAME_W, PR_DISPLAY_TYPE,
  1016. PR_EMAIL_ADDRESS_W, PR_ENTRYID, PR_SEARCH_KEY,
  1017. PR_SMTP_ADDRESS_W}};
  1018. // walk through all recipients
  1019. for (int iRecip = 0; iRecip < iAddressCount; ++iRecip) {
  1020. try {
  1021. vmime::text vmText;
  1022. mbx = NULL;
  1023. grp = NULL;
  1024. vmAddress = vmAddressList->getAddressAt(iRecip);
  1025. if (vmAddress->isGroup()) {
  1026. grp = vmime::dynamicCast<vmime::mailboxGroup>(vmAddress);
  1027. if (!grp)
  1028. continue;
  1029. strEmail.clear();
  1030. vmText = grp->getName();
  1031. if (grp->isEmpty() && vmText == vmime::text("undisclosed-recipients"))
  1032. continue;
  1033. } else {
  1034. mbx = vmime::dynamicCast<vmime::mailbox>(vmAddress);
  1035. if (!mbx)
  1036. continue;
  1037. strEmail = mbx->getEmail().toString();
  1038. vmText = mbx->getName();
  1039. }
  1040. if (!vmText.isEmpty())
  1041. wstrName = getWideFromVmimeText(vmText);
  1042. else
  1043. wstrName.clear();
  1044. }
  1045. catch (vmime::exception& e) {
  1046. ec_log_err("VMIME exception on modify recipient: %s", e.what());
  1047. return MAPI_E_CALL_FAILED;
  1048. }
  1049. catch (std::exception& e) {
  1050. ec_log_err("STD exception on modify recipient: %s", e.what());
  1051. return MAPI_E_CALL_FAILED;
  1052. }
  1053. catch (...) {
  1054. ec_log_err("Unknown generic exception occurred on modify recipient");
  1055. return MAPI_E_CALL_FAILED;
  1056. }
  1057. iRecipNum = lpRecipients->cEntries;
  1058. // use email address or fullname to find GAB entry, do not pass fullname to keep resolved addressbook fullname
  1059. strSearch = strEmail;
  1060. if (strSearch.empty())
  1061. strSearch = m_converter.convert_to<std::string>(wstrName);
  1062. // @todo: maybe make strSearch a wide string and check if we need to use the fullname argument for modifyFromAddressBook
  1063. hr = modifyFromAddressBook(&lpRecipients->aEntries[iRecipNum].rgPropVals,
  1064. &lpRecipients->aEntries[iRecipNum].cValues,
  1065. strSearch.c_str(), NULL, ulRecipType, sptaRecipientProps);
  1066. if (hr != hrSuccess) {
  1067. // Fallback if the entry was not found (or errored) in the addressbook
  1068. int iNumTags = 8;
  1069. iRecipNum = lpRecipients->cEntries;
  1070. if (wstrName.empty())
  1071. wstrName = m_converter.convert_to<wstring>(strEmail);
  1072. // will be cleaned up by caller.
  1073. hr = MAPIAllocateBuffer(sizeof(SPropValue) * iNumTags, (void **)&lpRecipients->aEntries[iRecipNum].rgPropVals);
  1074. if (hr != hrSuccess)
  1075. return hr;
  1076. lpRecipients->aEntries[iRecipNum].cValues = iNumTags;
  1077. lpRecipients->aEntries[iRecipNum].ulReserved1 = 0;
  1078. lpRecipients->aEntries[iRecipNum].rgPropVals[0].ulPropTag = PR_RECIPIENT_TYPE;
  1079. lpRecipients->aEntries[iRecipNum].rgPropVals[0].Value.l = ulRecipType;
  1080. lpRecipients->aEntries[iRecipNum].rgPropVals[1].ulPropTag = PR_DISPLAY_NAME_W;
  1081. hr = MAPIAllocateMore((wstrName.size()+1) * sizeof(WCHAR), lpRecipients->aEntries[iRecipNum].rgPropVals,
  1082. (void **)&lpRecipients->aEntries[iRecipNum].rgPropVals[1].Value.lpszW);
  1083. if (hr != hrSuccess)
  1084. return hr;
  1085. wcscpy(lpRecipients->aEntries[iRecipNum].rgPropVals[1].Value.lpszW, wstrName.c_str());
  1086. lpRecipients->aEntries[iRecipNum].rgPropVals[2].ulPropTag = PR_SMTP_ADDRESS_A;
  1087. hr = MAPIAllocateMore(strEmail.size()+1, lpRecipients->aEntries[iRecipNum].rgPropVals,
  1088. (void **)&lpRecipients->aEntries[iRecipNum].rgPropVals[2].Value.lpszA);
  1089. if (hr != hrSuccess)
  1090. return hr;
  1091. strcpy(lpRecipients->aEntries[iRecipNum].rgPropVals[2].Value.lpszA, strEmail.c_str());
  1092. lpRecipients->aEntries[iRecipNum].rgPropVals[3].ulPropTag = PR_ENTRYID;
  1093. hr = ECCreateOneOff((LPTSTR)wstrName.c_str(), (LPTSTR)L"SMTP", (LPTSTR)m_converter.convert_to<wstring>(strEmail).c_str(),
  1094. MAPI_UNICODE | MAPI_SEND_NO_RICH_INFO, &cbEntryID, &~lpEntryID);
  1095. if (hr != hrSuccess)
  1096. return hr;
  1097. hr = MAPIAllocateMore(cbEntryID, lpRecipients->aEntries[iRecipNum].rgPropVals,
  1098. (void **)&lpRecipients->aEntries[iRecipNum].rgPropVals[3].Value.bin.lpb);
  1099. if (hr != hrSuccess)
  1100. return hr;
  1101. lpRecipients->aEntries[iRecipNum].rgPropVals[3].Value.bin.cb = cbEntryID;
  1102. memcpy(lpRecipients->aEntries[iRecipNum].rgPropVals[3].Value.bin.lpb, lpEntryID, cbEntryID);
  1103. lpRecipients->aEntries[iRecipNum].rgPropVals[4].ulPropTag = PR_ADDRTYPE_W;
  1104. lpRecipients->aEntries[iRecipNum].rgPropVals[4].Value.lpszW = const_cast<wchar_t *>(L"SMTP");
  1105. strSearch = strToUpper("SMTP:" + strEmail);
  1106. lpRecipients->aEntries[iRecipNum].rgPropVals[5].ulPropTag = PR_SEARCH_KEY;
  1107. lpRecipients->aEntries[iRecipNum].rgPropVals[5].Value.bin.cb = strSearch.size() + 1; // we include the trailing 0 as MS does this also
  1108. hr = MAPIAllocateMore(strSearch.size()+1, lpRecipients->aEntries[iRecipNum].rgPropVals,
  1109. (void **)&lpRecipients->aEntries[iRecipNum].rgPropVals[5].Value.bin.lpb);
  1110. if (hr != hrSuccess)
  1111. return hr;
  1112. memcpy(lpRecipients->aEntries[iRecipNum].rgPropVals[5].Value.bin.lpb, strSearch.c_str(), strSearch.size()+1);
  1113. // Add Email address
  1114. lpRecipients->aEntries[iRecipNum].rgPropVals[6].ulPropTag = PR_EMAIL_ADDRESS_A;
  1115. hr = MAPIAllocateMore(strEmail.size()+1, lpRecipients->aEntries[iRecipNum].rgPropVals,
  1116. (void **)&lpRecipients->aEntries[iRecipNum].rgPropVals[6].Value.lpszA);
  1117. if (hr != hrSuccess)
  1118. return hr;
  1119. strcpy(lpRecipients->aEntries[iRecipNum].rgPropVals[6].Value.lpszA, strEmail.c_str());
  1120. // Add display type
  1121. lpRecipients->aEntries[iRecipNum].rgPropVals[7].ulPropTag = PR_DISPLAY_TYPE;
  1122. lpRecipients->aEntries[iRecipNum].rgPropVals[7].Value.ul = DT_MAILUSER;
  1123. }
  1124. ++lpRecipients->cEntries;
  1125. }
  1126. return hrSuccess;
  1127. }
  1128. /**
  1129. * copies data from addressbook into lpRecipient
  1130. *
  1131. * @param[out] lppPropVals Properties from addressbook.
  1132. * @param[out] lpulValues Number of properties returned in lppPropVals
  1133. * @param[in] email SMTP email address
  1134. * @param[in] fullname Fullname given in email for this address, can be different from fullname in addressbook.
  1135. * @param[in] ulRecipType PR_RECIPIENT_TYPE if ! MAPI_ORIG
  1136. * @param[in] lpPropList Properties to return in lppPropVals. Must be in specific order.
  1137. * @return MAPI error code.
  1138. */
  1139. HRESULT VMIMEToMAPI::modifyFromAddressBook(LPSPropValue *lppPropVals,
  1140. ULONG *lpulValues, const char *email, const wchar_t *fullname,
  1141. ULONG ulRecipType, const SPropTagArray *lpPropsList)
  1142. {
  1143. HRESULT hr = hrSuccess;
  1144. memory_ptr<ENTRYID> lpDDEntryID;
  1145. ULONG cbDDEntryID;
  1146. ULONG ulObj = 0;
  1147. adrlist_ptr lpAdrList;
  1148. memory_ptr<FlagList> lpFlagList;
  1149. const SPropValue *lpProp = nullptr;
  1150. SPropValue sRecipProps[9]; // 8 from addressbook + PR_RECIPIENT_TYPE == max
  1151. ULONG cValues = 0;
  1152. static constexpr const SizedSPropTagArray(8, sptaAddress) =
  1153. {8, {PR_SMTP_ADDRESS_W, PR_ADDRTYPE_W, PR_EMAIL_ADDRESS_W,
  1154. PR_DISPLAY_TYPE, PR_DISPLAY_NAME_W, PR_ENTRYID, PR_SEARCH_KEY,
  1155. PR_OBJECT_TYPE}};
  1156. if (m_lpAdrBook == nullptr)
  1157. return MAPI_E_NOT_FOUND;
  1158. if ((email == nullptr || *email == '\0') &&
  1159. (fullname == nullptr || *fullname == '\0'))
  1160. // we have no data to lookup
  1161. return MAPI_E_NOT_FOUND;
  1162. if (!m_lpDefaultDir) {
  1163. hr = m_lpAdrBook->GetDefaultDir(&cbDDEntryID, &~lpDDEntryID);
  1164. if (hr != hrSuccess)
  1165. return hr;
  1166. hr = m_lpAdrBook->OpenEntry(cbDDEntryID, lpDDEntryID, NULL, 0, &ulObj, (LPUNKNOWN*)&m_lpDefaultDir);
  1167. if (hr != hrSuccess)
  1168. return hr;
  1169. }
  1170. hr = MAPIAllocateBuffer(CbNewADRLIST(1), &~lpAdrList);
  1171. if (hr != hrSuccess)
  1172. return hr;
  1173. lpAdrList->cEntries = 1;
  1174. lpAdrList->aEntries[0].cValues = 1;
  1175. hr = MAPIAllocateBuffer(sizeof(SPropValue), (void **) &lpAdrList->aEntries[0].rgPropVals);
  1176. if (hr != hrSuccess)
  1177. return hr;
  1178. // static reference is OK here
  1179. if (!email || *email == '\0') {
  1180. lpAdrList->aEntries[0].rgPropVals[0].ulPropTag = PR_DISPLAY_NAME_W;
  1181. lpAdrList->aEntries[0].rgPropVals[0].Value.lpszW = (WCHAR *)fullname; // try to find with fullname for groups without email addresses
  1182. }
  1183. else {
  1184. lpAdrList->aEntries[0].rgPropVals[0].ulPropTag = PR_DISPLAY_NAME_A;
  1185. lpAdrList->aEntries[0].rgPropVals[0].Value.lpszA = (char *)email; // normally resolve on email address
  1186. }
  1187. hr = MAPIAllocateBuffer(CbNewFlagList(1), &~lpFlagList);
  1188. if (hr != hrSuccess)
  1189. return hr;
  1190. lpFlagList->cFlags = 1;
  1191. lpFlagList->ulFlag[0] = MAPI_UNRESOLVED;
  1192. hr = m_lpDefaultDir->ResolveNames(sptaAddress, EMS_AB_ADDRESS_LOOKUP,
  1193. lpAdrList, lpFlagList);
  1194. if (hr != hrSuccess)
  1195. return hr;
  1196. if (lpFlagList->cFlags != 1 || lpFlagList->ulFlag[0] != MAPI_RESOLVED)
  1197. return MAPI_E_NOT_FOUND;
  1198. // the server told us the entry is here. from this point on we
  1199. // don't want to return MAPI_E_NOT_FOUND anymore, so we need to
  1200. // deal with missing data (which really shouldn't be the case for
  1201. // some, so asserts in some places).
  1202. if (PROP_TYPE(lpPropsList->aulPropTag[0]) != PT_NULL) {
  1203. lpProp = PCpropFindProp(lpAdrList->aEntries[0].rgPropVals, lpAdrList->aEntries[0].cValues, PR_ADDRTYPE_W);
  1204. sRecipProps[cValues].ulPropTag = lpPropsList->aulPropTag[0]; // PR_xxx_ADDRTYPE;
  1205. assert(lpProp);
  1206. if (!lpProp) {
  1207. ec_log_warn("Missing PR_ADDRTYPE_W for search entry: email %s, fullname %ls", email ? email : "null", fullname ? fullname : L"null");
  1208. sRecipProps[cValues].Value.lpszW = const_cast<wchar_t *>(L"ZARAFA");
  1209. } else {
  1210. sRecipProps[cValues].Value.lpszW = lpProp->Value.lpszW;
  1211. }
  1212. ++cValues;
  1213. }
  1214. if (PROP_TYPE(lpPropsList->aulPropTag[1]) != PT_NULL) {
  1215. lpProp = PCpropFindProp(lpAdrList->aEntries[0].rgPropVals, lpAdrList->aEntries[0].cValues, PR_DISPLAY_NAME_W);
  1216. sRecipProps[cValues].ulPropTag = lpPropsList->aulPropTag[1]; // PR_xxx_DISPLAY_NAME;
  1217. if (lpProp)
  1218. sRecipProps[cValues].Value.lpszW = lpProp->Value.lpszW; // use addressbook version
  1219. else if (fullname && *fullname != '\0')
  1220. sRecipProps[cValues].Value.lpszW = (WCHAR *)fullname; // use email version
  1221. else if (email && *email != '\0')
  1222. sRecipProps[cValues].Value.lpszW = (WCHAR *)email; // use email address
  1223. else {
  1224. sRecipProps[cValues].ulPropTag = CHANGE_PROP_TYPE(lpPropsList->aulPropTag[1], PT_ERROR);
  1225. sRecipProps[cValues].Value.err = MAPI_E_NOT_FOUND;
  1226. }
  1227. ++cValues;
  1228. }
  1229. if (PROP_TYPE(lpPropsList->aulPropTag[2]) != PT_NULL) {
  1230. lpProp = PCpropFindProp(lpAdrList->aEntries[0].rgPropVals, lpAdrList->aEntries[0].cValues, PR_DISPLAY_TYPE);
  1231. sRecipProps[cValues].ulPropTag = lpPropsList->aulPropTag[2]; // PR_xxx_DISPLAY_TYPE;
  1232. if (lpProp == nullptr)
  1233. sRecipProps[cValues].Value.ul = DT_MAILUSER;
  1234. else
  1235. sRecipProps[cValues].Value.ul = lpProp->Value.ul;
  1236. ++cValues;
  1237. }
  1238. if (PROP_TYPE(lpPropsList->aulPropTag[3]) != PT_NULL) {
  1239. lpProp = PCpropFindProp(lpAdrList->aEntries[0].rgPropVals, lpAdrList->aEntries[0].cValues, PR_EMAIL_ADDRESS_W);
  1240. sRecipProps[cValues].ulPropTag = lpPropsList->aulPropTag[3]; // PR_xxx_EMAIL_ADDRESS;
  1241. assert(lpProp);
  1242. if (!lpProp) {
  1243. sRecipProps[cValues].ulPropTag = CHANGE_PROP_TYPE(lpPropsList->aulPropTag[3], PT_ERROR);
  1244. sRecipProps[cValues].Value.err = MAPI_E_NOT_FOUND;
  1245. } else {
  1246. sRecipProps[cValues].Value.lpszW = lpProp->Value.lpszW;
  1247. }
  1248. ++cValues;
  1249. }
  1250. if (PROP_TYPE(lpPropsList->aulPropTag[4]) != PT_NULL) {
  1251. lpProp = PCpropFindProp(lpAdrList->aEntries[0].rgPropVals, lpAdrList->aEntries[0].cValues, PR_ENTRYID);
  1252. assert(lpProp);
  1253. if (lpProp == nullptr)
  1254. // the one exception I guess? Let the fallback code create a one off entryid
  1255. return MAPI_E_NOT_FOUND;
  1256. sRecipProps[cValues].ulPropTag = lpPropsList->aulPropTag[4]; // PR_xxx_ENTRYID;
  1257. sRecipProps[cValues].Value.bin = lpProp->Value.bin;
  1258. ++cValues;
  1259. }
  1260. if (PROP_TYPE(lpPropsList->aulPropTag[5]) != PT_NULL) {
  1261. lpProp = PCpropFindProp(lpAdrList->aEntries[0].rgPropVals, lpAdrList->aEntries[0].cValues, PR_SEARCH_KEY);
  1262. if (!lpProp) {
  1263. sRecipProps[cValues].ulPropTag = CHANGE_PROP_TYPE(lpPropsList->aulPropTag[5], PT_ERROR);
  1264. sRecipProps[cValues].Value.err = MAPI_E_NOT_FOUND;
  1265. } else {
  1266. sRecipProps[cValues].ulPropTag = lpPropsList->aulPropTag[5]; // PR_xxx_SEARCH_KEY;
  1267. sRecipProps[cValues].Value.bin = lpProp->Value.bin;
  1268. }
  1269. ++cValues;
  1270. }
  1271. if (PROP_TYPE(lpPropsList->aulPropTag[6]) != PT_NULL) {
  1272. lpProp = PCpropFindProp(lpAdrList->aEntries[0].rgPropVals, lpAdrList->aEntries[0].cValues, PR_SMTP_ADDRESS_W);
  1273. if (!lpProp) {
  1274. sRecipProps[cValues].ulPropTag = CHANGE_PROP_TYPE(lpPropsList->aulPropTag[6], PT_ERROR); // PR_xxx_SMTP_ADDRESS;
  1275. sRecipProps[cValues].Value.err = MAPI_E_NOT_FOUND;
  1276. } else {
  1277. sRecipProps[cValues].ulPropTag = lpPropsList->aulPropTag[6]; // PR_xxx_SMTP_ADDRESS;
  1278. sRecipProps[cValues].Value.lpszW = lpProp->Value.lpszW;
  1279. }
  1280. ++cValues;
  1281. }
  1282. lpProp = PCpropFindProp(lpAdrList->aEntries[0].rgPropVals, lpAdrList->aEntries[0].cValues, PR_OBJECT_TYPE);
  1283. assert(lpProp);
  1284. if (lpProp == nullptr)
  1285. sRecipProps[cValues].Value.ul = MAPI_MAILUSER;
  1286. else
  1287. sRecipProps[cValues].Value.ul = lpProp->Value.ul;
  1288. sRecipProps[cValues].ulPropTag = PR_OBJECT_TYPE;
  1289. ++cValues;
  1290. if (ulRecipType != MAPI_ORIG) {
  1291. sRecipProps[cValues].ulPropTag = PR_RECIPIENT_TYPE;
  1292. sRecipProps[cValues].Value.ul = ulRecipType;
  1293. ++cValues;
  1294. }
  1295. hr = Util::HrCopyPropertyArray(sRecipProps, cValues, lppPropVals, &cValues);
  1296. if (hr == hrSuccess && lpulValues)
  1297. *lpulValues = cValues;
  1298. return hr;
  1299. }
  1300. /**
  1301. * Order alternatives in a body according to local preference.
  1302. *
  1303. * This function (currently) only deprioritizes text/plain parts, and leaves
  1304. * the priority of everything else as-is.
  1305. *
  1306. * This function also reverses the list. Whereas MIME parts in @vmBody are
  1307. * ordered from boring-to-interesting, the list returned by this function is
  1308. * interesting-to-boring.
  1309. */
  1310. static std::list<unsigned int>
  1311. vtm_order_alternatives(vmime::shared_ptr<vmime::body> vmBody)
  1312. {
  1313. vmime::shared_ptr<vmime::header> vmHeader;
  1314. vmime::shared_ptr<vmime::bodyPart> vmBodyPart;
  1315. vmime::shared_ptr<vmime::mediaType> mt;
  1316. std::list<unsigned int> lBodies, pgtext;
  1317. for (size_t i = 0; i < vmBody->getPartCount(); ++i) {
  1318. vmBodyPart = vmBody->getPartAt(i);
  1319. vmHeader = vmBodyPart->getHeader();
  1320. if (!vmHeader->hasField(vmime::fields::CONTENT_TYPE)) {
  1321. /* RFC 2046 §5.1 ¶2 says treat it as text/plain */
  1322. lBodies.push_front(i);
  1323. continue;
  1324. }
  1325. mt = vmime::dynamicCast<vmime::mediaType>(vmHeader->ContentType()->getValue());
  1326. // mostly better alternatives for text/plain, so try that last
  1327. if (mt->getType() == vmime::mediaTypes::TEXT && mt->getSubType() == vmime::mediaTypes::TEXT_PLAIN)
  1328. lBodies.push_back(i);
  1329. else
  1330. lBodies.push_front(i);
  1331. }
  1332. return lBodies;
  1333. }
  1334. HRESULT VMIMEToMAPI::dissect_multipart(vmime::shared_ptr<vmime::header> vmHeader,
  1335. vmime::shared_ptr<vmime::body> vmBody, IMessage *lpMessage,
  1336. bool bFilterDouble, bool bAppendBody)
  1337. {
  1338. bool bAlternative = false;
  1339. HRESULT hr = hrSuccess;
  1340. if (vmBody->getPartCount() <= 0) {
  1341. // a lonely attachment in a multipart, may not be empty when it's a signed part.
  1342. hr = handleAttachment(vmHeader, vmBody, lpMessage);
  1343. if (hr != hrSuccess)
  1344. ec_log_err("dissect_multipart: Unable to save attachment");
  1345. return hr;
  1346. }
  1347. // check new multipart type
  1348. auto mt = vmime::dynamicCast<vmime::mediaType>(vmHeader->ContentType()->getValue());
  1349. if (mt->getSubType() == "appledouble")
  1350. bFilterDouble = true;
  1351. else if (mt->getSubType() == "mixed")
  1352. bAppendBody = true;
  1353. else if (mt->getSubType() == "alternative")
  1354. bAlternative = true;
  1355. /*
  1356. * RFC 2046 §5.1.7: all unrecognized subtypes are to be
  1357. * treated like multipart/mixed.
  1358. *
  1359. * At least that is what it said back then. RFC 2387 then came
  1360. * along,… and now we don't set bAppendBody for unresearched
  1361. * reasons.
  1362. */
  1363. if (!bAlternative) {
  1364. // recursively process multipart message
  1365. for (size_t i = 0; i < vmBody->getPartCount(); ++i) {
  1366. auto vmBodyPart = vmBody->getPartAt(i);
  1367. hr = dissect_body(vmBodyPart->getHeader(), vmBodyPart->getBody(), lpMessage, bFilterDouble, bAppendBody);
  1368. if (hr != hrSuccess) {
  1369. ec_log_err("dissect_multipart: Unable to parse sub multipart %zu of mail body", i);
  1370. return hr;
  1371. }
  1372. }
  1373. return hrSuccess;
  1374. }
  1375. list<unsigned int> lBodies = vtm_order_alternatives(vmBody);
  1376. // recursively process multipart alternatives in reverse to select best body first
  1377. for (auto body_idx : lBodies) {
  1378. auto vmBodyPart = vmBody->getPartAt(body_idx);
  1379. ec_log_debug("Trying to parse alternative multipart %d of mail body", body_idx);
  1380. hr = dissect_body(vmBodyPart->getHeader(), vmBodyPart->getBody(), lpMessage, bFilterDouble, bAppendBody);
  1381. if (hr == hrSuccess)
  1382. return hrSuccess;
  1383. ec_log_err("Unable to parse alternative multipart %d of mail body, trying other alternatives", body_idx);
  1384. }
  1385. /* If lBodies was empty, we could get here, with hr being hrSuccess. */
  1386. if (hr != hrSuccess)
  1387. ec_log_err("Unable to parse all alternative multiparts of mail body");
  1388. return hr;
  1389. }
  1390. void VMIMEToMAPI::dissect_message(vmime::shared_ptr<vmime::body> vmBody,
  1391. IMessage *lpMessage)
  1392. {
  1393. // Create Attach
  1394. ULONG ulAttNr = 0;
  1395. object_ptr<IAttach> pAtt;
  1396. object_ptr<IMessage> lpNewMessage;
  1397. memory_ptr<SPropValue> lpSubject;
  1398. SPropValue sAttachMethod;
  1399. char *lpszBody = NULL, *lpszBodyOrig = NULL;
  1400. sMailState savedState;
  1401. std::string newMessage;
  1402. vmime::utility::outputStreamStringAdapter os(newMessage);
  1403. vmBody->generate(os);
  1404. lpszBodyOrig = lpszBody = (char *)newMessage.c_str();
  1405. // Skip any leading newlines from the e-mail (attached messaged produced by Microsoft MimeOLE seem to do this)
  1406. while (*lpszBody != '\0' && (*lpszBody == '\r' || *lpszBody == '\n'))
  1407. ++lpszBody;
  1408. // and remove from string
  1409. newMessage.erase(0, lpszBody - lpszBodyOrig);
  1410. HRESULT hr = lpMessage->CreateAttach(nullptr, 0, &ulAttNr, &~pAtt);
  1411. if (hr != hrSuccess)
  1412. goto next;
  1413. hr = pAtt->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0,
  1414. MAPI_CREATE | MAPI_MODIFY, &~lpNewMessage);
  1415. if (hr != hrSuccess)
  1416. goto next;
  1417. // handle message-in-message, save current state variables
  1418. savedState = m_mailState;
  1419. m_mailState.reset();
  1420. ++m_mailState.ulMsgInMsg;
  1421. hr = convertVMIMEToMAPI(newMessage, lpNewMessage);
  1422. // return to previous state
  1423. m_mailState = savedState;
  1424. if (hr != hrSuccess)
  1425. goto next;
  1426. if (HrGetOneProp(lpNewMessage, PR_SUBJECT_W, &~lpSubject) == hrSuccess) {
  1427. // Set PR_ATTACH_FILENAME of attachment to message subject, (WARNING: abuse of lpSubject variable)
  1428. lpSubject->ulPropTag = PR_DISPLAY_NAME_W;
  1429. pAtt->SetProps(1, lpSubject, NULL);
  1430. }
  1431. sAttachMethod.ulPropTag = PR_ATTACH_METHOD;
  1432. sAttachMethod.Value.ul = ATTACH_EMBEDDED_MSG;
  1433. pAtt->SetProps(1, &sAttachMethod, NULL);
  1434. lpNewMessage->SaveChanges(0);
  1435. pAtt->SaveChanges(0);
  1436. next:
  1437. ;
  1438. }
  1439. HRESULT VMIMEToMAPI::dissect_ical(vmime::shared_ptr<vmime::header> vmHeader,
  1440. vmime::shared_ptr<vmime::body> vmBody, IMessage *lpMessage,
  1441. bool bIsAttachment)
  1442. {
  1443. HRESULT hr;
  1444. // ical file
  1445. string icaldata;
  1446. vmime::utility::outputStreamStringAdapter os(icaldata);
  1447. std::string strCharset;
  1448. MessagePtr ptrNewMessage;
  1449. LPMESSAGE lpIcalMessage = lpMessage;
  1450. AttachPtr ptrAttach;
  1451. ULONG ulAttNr = 0;
  1452. std::unique_ptr<ICalToMapi> lpIcalMapi;
  1453. ICalToMapi *tmpicalmapi;
  1454. SPropValuePtr ptrSubject;
  1455. ULONG ical_mapi_flags = IC2M_NO_RECIPIENTS | IC2M_APPEND_ONLY;
  1456. /*
  1457. * Some senders send UTF-8 iCalendar information without a charset
  1458. * (Exchange does this). Default to UTF-8 if no charset was specified,
  1459. * as mandated by RFC 5545 § 3.1.4.
  1460. */
  1461. strCharset = vmBody->getCharset().getName();
  1462. if (strCharset == "us-ascii")
  1463. // We can safely upgrade from US-ASCII to UTF-8 since that is compatible
  1464. strCharset = "utf-8";
  1465. vmBody->getContents()->extract(os);
  1466. if (m_mailState.bodyLevel > BODY_NONE)
  1467. /* Force attachment if we already have some text. */
  1468. bIsAttachment = true;
  1469. if (bIsAttachment) {
  1470. // create message in message to create calendar message
  1471. SPropValue sAttProps[3];
  1472. hr = lpMessage->CreateAttach(nullptr, 0, &ulAttNr, &~ptrAttach);
  1473. if (hr != hrSuccess) {
  1474. ec_log_err("dissect_ical-1790: Unable to create attachment for ical data: %s (%x)", GetMAPIErrorMessage(hr), hr);
  1475. return hr;
  1476. }
  1477. hr = ptrAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, MAPI_CREATE | MAPI_MODIFY, &~ptrNewMessage);
  1478. if (hr != hrSuccess) {
  1479. ec_log_err("dissect_ical-1796: Unable to create message attachment for ical data: %s (%x)", GetMAPIErrorMessage(hr), hr);
  1480. return hr;
  1481. }
  1482. sAttProps[0].ulPropTag = PR_ATTACH_METHOD;
  1483. sAttProps[0].Value.ul = ATTACH_EMBEDDED_MSG;
  1484. sAttProps[1].ulPropTag = PR_ATTACHMENT_HIDDEN;
  1485. sAttProps[1].Value.b = FALSE;
  1486. sAttProps[2].ulPropTag = PR_ATTACH_FLAGS;
  1487. sAttProps[2].Value.ul = 0;
  1488. hr = ptrAttach->SetProps(3, sAttProps, NULL);
  1489. if (hr != hrSuccess) {
  1490. ec_log_err("dissect_ical-1811: Unable to create message attachment for ical data: %s (%x)", GetMAPIErrorMessage(hr), hr);
  1491. return hr;
  1492. }
  1493. lpIcalMessage = ptrNewMessage.get();
  1494. }
  1495. hr = CreateICalToMapi(lpMessage, m_lpAdrBook, true, &tmpicalmapi);
  1496. lpIcalMapi.reset(tmpicalmapi);
  1497. if (hr != hrSuccess) {
  1498. ec_log_err("dissect_ical-1820: Unable to create ical converter: %s (%x)", GetMAPIErrorMessage(hr), hr);
  1499. return hr;
  1500. }
  1501. hr = lpIcalMapi->ParseICal(icaldata, strCharset, "UTC" , NULL, 0);
  1502. if (hr != hrSuccess || lpIcalMapi->GetItemCount() != 1) {
  1503. ec_log_err("dissect_ical-1826: Unable to parse ical information: %s (%x), items: %d, adding as normal attachment",
  1504. GetMAPIErrorMessage(hr), hr, lpIcalMapi->GetItemCount());
  1505. return handleAttachment(vmHeader, vmBody, lpMessage);
  1506. }
  1507. if (lpIcalMessage != lpMessage) {
  1508. hr = lpIcalMapi->GetItem(0, 0, lpIcalMessage);
  1509. if (hr != hrSuccess) {
  1510. ec_log_err("dissect_ical-1833: Error while converting ical to mapi: %s (%x)", GetMAPIErrorMessage(hr), hr);
  1511. return hr;
  1512. }
  1513. }
  1514. if (bIsAttachment)
  1515. ical_mapi_flags |= IC2M_NO_BODY;
  1516. /* Calendar properties need to be on the main message in any case. */
  1517. hr = lpIcalMapi->GetItem(0, ical_mapi_flags, lpMessage);
  1518. if (hr != hrSuccess) {
  1519. ec_log_err("dissect_ical-1834: Error while converting ical to mapi: %s (%x)", GetMAPIErrorMessage(hr), hr);
  1520. return hr;
  1521. }
  1522. /* Evaluate whether vconverter gave us an initial body */
  1523. if (!bIsAttachment && m_mailState.bodyLevel < BODY_PLAIN &&
  1524. (FPropExists(lpMessage, PR_BODY_A) ||
  1525. FPropExists(lpMessage, PR_BODY_W)))
  1526. m_mailState.bodyLevel = BODY_PLAIN;
  1527. if (!bIsAttachment)
  1528. return hr;
  1529. // give attachment name of calendar item
  1530. if (HrGetOneProp(ptrNewMessage, PR_SUBJECT_W, &~ptrSubject) == hrSuccess) {
  1531. ptrSubject->ulPropTag = PR_DISPLAY_NAME_W;
  1532. hr = ptrAttach->SetProps(1, ptrSubject, NULL);
  1533. if (hr != hrSuccess)
  1534. return hr;
  1535. }
  1536. hr = ptrNewMessage->SaveChanges(0);
  1537. if (hr != hrSuccess) {
  1538. ec_log_err("dissect_ical-1851: Unable to save ical message: %s (%x)", GetMAPIErrorMessage(hr), hr);
  1539. return hr;
  1540. }
  1541. hr = ptrAttach->SaveChanges(0);
  1542. if (hr != hrSuccess) {
  1543. ec_log_err("dissect_ical-1856: Unable to save ical message attachment: %s (%x)", GetMAPIErrorMessage(hr), hr);
  1544. return hr;
  1545. }
  1546. // make sure we show the attachment icon
  1547. m_mailState.attachLevel = ATTACH_NORMAL;
  1548. return hrSuccess;
  1549. }
  1550. /**
  1551. * Disect Body
  1552. *
  1553. * Here we are going to split the body into pieces and throw every
  1554. * part into its container. We make decisions on the basis of Content
  1555. * Types...
  1556. *
  1557. * Content Types...
  1558. * http://www.faqs.org/rfcs/rfc2046.html
  1559. *
  1560. * Top level Subtypes
  1561. * discrete:
  1562. * text plain, html, richtext, enriched
  1563. * image jpeg, gif, png.. etc
  1564. * audio basic, wav, ai.. etc
  1565. * video mpeg, avi.. etc
  1566. * application octet-stream, postscript
  1567. *
  1568. * composite:
  1569. * multipart mixed, alternative, digest ( contains message ), paralell,
  1570. * message rfc 2822, partial ( please no fragmentation and reassembly ), external-body
  1571. *
  1572. * @param[in] vmHeader vmime header part which describes the contents of the body in vmBody.
  1573. * @param[in] vmBody a body part of the mail.
  1574. * @param[out] lpMessage MAPI message to write header properties in.
  1575. * @param[in] filterDouble skips some attachments when true, only happens then an appledouble attachment marker is found.
  1576. * @param[in] bAppendBody Concatenate with existing body if true, makes an attachment when false and a body was previously saved.
  1577. * @return MAPI error code.
  1578. * @retval MAPI_E_CALL_FAILED Caught an exception, which breaks the conversion.
  1579. */
  1580. HRESULT VMIMEToMAPI::dissect_body(vmime::shared_ptr<vmime::header> vmHeader,
  1581. vmime::shared_ptr<vmime::body> vmBody, IMessage *lpMessage,
  1582. bool filterDouble, bool appendBody)
  1583. {
  1584. HRESULT hr = hrSuccess;
  1585. object_ptr<IStream> lpStream;
  1586. SPropValue sPropSMIMEClass;
  1587. bool bFilterDouble = filterDouble;
  1588. bool bAppendBody = appendBody;
  1589. bool bIsAttachment = false;
  1590. if (vmHeader->hasField(vmime::fields::MIME_VERSION))
  1591. ++m_mailState.mime_vtag_nest;
  1592. try {
  1593. auto mt = vmime::dynamicCast<vmime::mediaType>(vmHeader->ContentType()->getValue());
  1594. bool force_raw = false;
  1595. try {
  1596. bIsAttachment = vmime::dynamicCast<vmime::contentDisposition>(vmHeader->ContentDisposition()->getValue())->getName() == vmime::contentDispositionTypes::ATTACHMENT;
  1597. } catch (vmime::exception) {
  1598. // ignore exception, a header needed to detect attachment status could not be used
  1599. // probably can not happen, but better safe than sorry.
  1600. }
  1601. try {
  1602. vmBody->getContents()->getEncoding().getEncoder();
  1603. } catch (vmime::exceptions::no_encoder_available &) {
  1604. /* RFC 2045 §6.4 page 17 */
  1605. ec_log_debug("Encountered unknown Content-Transfer-Encoding \"%s\".",
  1606. vmBody->getContents()->getEncoding().getName().c_str());
  1607. force_raw = true;
  1608. }
  1609. if (force_raw) {
  1610. hr = handleAttachment(vmHeader, vmBody, lpMessage, true);
  1611. if (hr != hrSuccess)
  1612. goto exit;
  1613. } else if (mt->getType() == "multipart") {
  1614. hr = dissect_multipart(vmHeader, vmBody, lpMessage, bFilterDouble, bAppendBody);
  1615. if (hr != hrSuccess)
  1616. goto exit;
  1617. // Only handle as inline text if no filename is specified and not specified as 'attachment'
  1618. } else if (mt->getType() == vmime::mediaTypes::TEXT &&
  1619. (mt->getSubType() == vmime::mediaTypes::TEXT_PLAIN || mt->getSubType() == vmime::mediaTypes::TEXT_HTML) &&
  1620. !bIsAttachment) {
  1621. if (mt->getSubType() == vmime::mediaTypes::TEXT_HTML || (m_mailState.bodyLevel == BODY_HTML && bAppendBody)) {
  1622. // handle real html part, or append a plain text bodypart to the html main body
  1623. // subtype guaranteed html or plain.
  1624. hr = handleHTMLTextpart(vmHeader, vmBody, lpMessage, bAppendBody);
  1625. if (hr != hrSuccess) {
  1626. ec_log_err("Unable to parse mail HTML text");
  1627. goto exit;
  1628. }
  1629. } else {
  1630. hr = handleTextpart(vmHeader, vmBody, lpMessage, bAppendBody);
  1631. if (hr != hrSuccess)
  1632. goto exit;
  1633. }
  1634. } else if (mt->getType() == vmime::mediaTypes::MESSAGE) {
  1635. dissect_message(vmBody, lpMessage);
  1636. } else if(mt->getType() == vmime::mediaTypes::APPLICATION && mt->getSubType() == "ms-tnef") {
  1637. LARGE_INTEGER zero = {{0,0}};
  1638. hr = CreateStreamOnHGlobal(nullptr, TRUE, &~lpStream);
  1639. if(hr != hrSuccess)
  1640. goto exit;
  1641. outputStreamMAPIAdapter str(lpStream);
  1642. vmBody->getContents()->extract(str);
  1643. hr = lpStream->Seek(zero, STREAM_SEEK_SET, NULL);
  1644. if(hr != hrSuccess)
  1645. goto exit;
  1646. ECTNEF tnef(TNEF_DECODE, lpMessage, lpStream);
  1647. hr = tnef.ExtractProps(TNEF_PROP_EXCLUDE, NULL);
  1648. if (hr == hrSuccess) {
  1649. hr = tnef.Finish();
  1650. if (hr != hrSuccess)
  1651. ec_log_warn("TNEF attachment saving failed: 0x%08X", hr);
  1652. } else {
  1653. ec_log_warn("TNEF attachment parsing failed: 0x%08X", hr);
  1654. }
  1655. hr = hrSuccess;
  1656. } else if (mt->getType() == vmime::mediaTypes::TEXT && mt->getSubType() == "calendar") {
  1657. hr = dissect_ical(vmHeader, vmBody, lpMessage, bIsAttachment);
  1658. if (hr != hrSuccess)
  1659. goto exit;
  1660. } else if (filterDouble && mt->getType() == vmime::mediaTypes::APPLICATION && mt->getSubType() == "applefile") {
  1661. } else if (filterDouble && mt->getType() == vmime::mediaTypes::APPLICATION && mt->getSubType() == "mac-binhex40") {
  1662. // ignore appledouble parts
  1663. // mac-binhex40 is appledouble v1, applefile is v2
  1664. // see: http://www.iana.org/assignments/media-types/multipart/appledouble
  1665. } else if (mt->getType() == vmime::mediaTypes::APPLICATION && (mt->getSubType() == "pkcs7-signature" || mt->getSubType() == "x-pkcs7-signature")) {
  1666. // smime signature (smime.p7s)
  1667. // must be handled a level above to get all headers and bodies beloning to the signed message
  1668. m_mailState.bAttachSignature = true;
  1669. } else if (mt->getType() == vmime::mediaTypes::APPLICATION && (mt->getSubType() == "pkcs7-mime" || mt->getSubType() == "x-pkcs7-mime")) {
  1670. // smime encrypted message (smime.p7m), attachment may not be empty
  1671. hr = handleAttachment(vmHeader, vmBody, lpMessage, false);
  1672. if (hr == MAPI_E_NOT_FOUND) {
  1673. // skip empty attachment
  1674. hr = hrSuccess;
  1675. goto exit;
  1676. }
  1677. if (hr != hrSuccess)
  1678. goto exit;
  1679. // Mark the message so outlook knows how to find the encoded message
  1680. sPropSMIMEClass.ulPropTag = PR_MESSAGE_CLASS_W;
  1681. sPropSMIMEClass.Value.lpszW = const_cast<wchar_t *>(L"IPM.Note.SMIME");
  1682. hr = lpMessage->SetProps(1, &sPropSMIMEClass, NULL);
  1683. if (hr != hrSuccess) {
  1684. ec_log_err("Unable to set message class");
  1685. goto exit;
  1686. }
  1687. } else if (mt->getType() == vmime::mediaTypes::APPLICATION && mt->getSubType() == vmime::mediaTypes::APPLICATION_OCTET_STREAM) {
  1688. if (vmime::dynamicCast<vmime::contentDispositionField>(vmHeader->ContentDisposition())->hasParameter("filename") ||
  1689. vmime::dynamicCast<vmime::contentTypeField>(vmHeader->ContentType())->hasParameter("name")) {
  1690. // should be attachment
  1691. hr = handleAttachment(vmHeader, vmBody, lpMessage);
  1692. if (hr != hrSuccess)
  1693. goto exit;
  1694. } else {
  1695. /*
  1696. * Possibly text?
  1697. * Unknown character set for text-* causes it
  1698. * the part to get interpreted as
  1699. * application-octet-stream (RFC 2049 §2
  1700. * item 6), and vmime presents it to us as
  1701. * such, making it impossible to know
  1702. * whether it was originally text-* or
  1703. * application-*.
  1704. */
  1705. hr = handleTextpart(vmHeader, vmBody, lpMessage, false);
  1706. if (hr != hrSuccess)
  1707. goto exit;
  1708. }
  1709. } else {
  1710. /* RFC 2049 §2 item 7 */
  1711. hr = handleAttachment(vmHeader, vmBody, lpMessage);
  1712. if (hr != hrSuccess)
  1713. goto exit;
  1714. }
  1715. }
  1716. catch (vmime::exception& e) {
  1717. ec_log_err("VMIME exception on parsing body: %s", e.what());
  1718. hr = MAPI_E_CALL_FAILED;
  1719. goto exit;
  1720. }
  1721. catch (std::exception& e) {
  1722. ec_log_err("STD exception on parsing body: %s", e.what());
  1723. hr = MAPI_E_CALL_FAILED;
  1724. goto exit;
  1725. }
  1726. catch (...) {
  1727. ec_log_err("Unknown generic exception occurred on parsing body");
  1728. hr = MAPI_E_CALL_FAILED;
  1729. goto exit;
  1730. }
  1731. exit:
  1732. if (vmHeader->hasField(vmime::fields::MIME_VERSION))
  1733. --m_mailState.mime_vtag_nest;
  1734. return hr;
  1735. }
  1736. /**
  1737. * Decode the MIME part as per its Content-Transfer-Encoding header.
  1738. * @im_body: Internet Message / VMIME body object
  1739. *
  1740. * Returns the transfer-decoded data.
  1741. */
  1742. std::string
  1743. VMIMEToMAPI::content_transfer_decode(vmime::shared_ptr<vmime::body> im_body) const
  1744. {
  1745. /* TODO: Research how conversion could be minimized using streams. */
  1746. std::string data;
  1747. vmime::utility::outputStreamStringAdapter str_adap(data);
  1748. auto im_cont = im_body->getContents();
  1749. try {
  1750. im_cont->extract(str_adap);
  1751. } catch (vmime::exceptions::no_encoder_available &e) {
  1752. ec_log_warn("VMIME could not process the Content-Transfer-Encoding \"%s\" (%s). Reading part raw.",
  1753. im_cont->getEncoding().generate().c_str(), e.what());
  1754. im_cont->extractRaw(str_adap);
  1755. }
  1756. return data;
  1757. }
  1758. /**
  1759. * Attempt to repair some data streams with illegal/unknown encodings.
  1760. * @charset: character set as specified in Content-Type,
  1761. * or what we so far know the encoding to be
  1762. * @data: data stream
  1763. *
  1764. * The function changes (may change) the mail @data in-place and returns the
  1765. * new character set for it.
  1766. */
  1767. vmime::charset
  1768. VMIMEToMAPI::get_mime_encoding(vmime::shared_ptr<vmime::header> im_header,
  1769. vmime::shared_ptr<vmime::body> im_body) const
  1770. {
  1771. auto ctf = vmime::dynamicCast<vmime::contentTypeField>(im_header->ContentType());
  1772. if (ctf != NULL && ctf->hasParameter("charset"))
  1773. return im_body->getCharset();
  1774. return vmime::charset(im_charset_unspec);
  1775. }
  1776. /**
  1777. * Try decoding the MIME body with a bunch of character sets
  1778. * @data: input body text, modified in-place if transformation successful
  1779. * @cs: list of character sets to try, ordered by descending preference
  1780. *
  1781. * Interpret the body text in various character sets and see in which one
  1782. * all input characters appear to be valid codepoints. If none match, it
  1783. * will be forcibly sanitized, possibly losing characters.
  1784. * The string will also be type-converted in the process.
  1785. * The index of the chosen character set will be returned.
  1786. */
  1787. int VMIMEToMAPI::renovate_encoding(std::string &data,
  1788. const std::vector<std::string> &cs)
  1789. {
  1790. /*
  1791. * First check if any charset converts without raising
  1792. * illegal_sequence_exceptions.
  1793. */
  1794. for (size_t i = 0; i < cs.size(); ++i) {
  1795. const char *name = cs[i].c_str();
  1796. try {
  1797. data = m_converter.convert_to<std::string>(
  1798. (cs[i] + "//NOIGNORE").c_str(),
  1799. data, rawsize(data), name);
  1800. ec_log_debug("renovate_encoding: reading data using charset \"%s\" succeeded.", name);
  1801. return i;
  1802. } catch (illegal_sequence_exception &ce) {
  1803. /*
  1804. * Basically, choices other than the first are subpar
  1805. * and may not yield an RFC-compliant result (but
  1806. * perhaps a readable one nevertheless). Therefore,
  1807. * be very vocant about bad mail on the first failed
  1808. * one.
  1809. */
  1810. unsigned int lvl = EC_LOGLEVEL_DEBUG;
  1811. if (i == 0)
  1812. lvl = EC_LOGLEVEL_WARNING;
  1813. ec_log(lvl, "renovate_encoding: reading data using charset \"%s\" produced partial results: %s",
  1814. name, ce.what());
  1815. } catch (unknown_charset_exception &) {
  1816. ec_log_warn("renovate_encoding: unknown charset \"%s\", skipping", name);
  1817. }
  1818. }
  1819. /*
  1820. * Take the hit, convert with the next best thing and
  1821. * drop illegal sequences.
  1822. */
  1823. for (size_t i = 0; i < cs.size(); ++i) {
  1824. const char *name = cs[i].c_str();
  1825. try {
  1826. data = m_converter.convert_to<std::string>(
  1827. (cs[i] + "//IGNORE").c_str(), data, rawsize(data), name);
  1828. } catch (unknown_charset_exception &) {
  1829. continue;
  1830. }
  1831. ec_log_debug("renovate_encoding: forced interpretation as charset \"%s\".", name);
  1832. return i;
  1833. }
  1834. return -1;
  1835. }
  1836. /**
  1837. * Saves a plain text body part in the body or creates a new attachment.
  1838. *
  1839. * @param[in] vmHeader header describing contents of vmBody.
  1840. * @param[in] vmBody body part contents.
  1841. * @param[out] lpMessage IMessage object to be altered.
  1842. * @param[in] bAppendBody Concatenate with existing body when still processing plain body parts (no HTML version already found).
  1843. * @return MAPI error code.
  1844. * @retval MAPI_E_CALL_FAILED Caught an exception, which breaks the conversion.
  1845. */
  1846. HRESULT VMIMEToMAPI::handleTextpart(vmime::shared_ptr<vmime::header> vmHeader,
  1847. vmime::shared_ptr<vmime::body> vmBody, IMessage* lpMessage, bool bAppendBody)
  1848. {
  1849. HRESULT hr = S_OK;
  1850. object_ptr<IStream> lpStream;
  1851. bool append = m_mailState.bodyLevel < BODY_PLAIN ||
  1852. (m_mailState.bodyLevel == BODY_PLAIN && bAppendBody);
  1853. if (!append) {
  1854. // we already had a plaintext or html body, so attach this text part
  1855. hr = handleAttachment(vmHeader, vmBody, lpMessage);
  1856. if (hr != hrSuccess) {
  1857. ec_log_err("Unable to parse attached text mail");
  1858. return hr;
  1859. }
  1860. return hrSuccess;
  1861. }
  1862. // we have no body, or need to append more plain text body parts
  1863. try {
  1864. SPropValue sCodepage;
  1865. /* determine first choice character set */
  1866. vmime::charset mime_charset =
  1867. get_mime_encoding(vmHeader, vmBody);
  1868. if (mime_charset == im_charset_unspec) {
  1869. if (m_mailState.mime_vtag_nest == 0) {
  1870. /* RFC 2045 §4 page 9 */
  1871. ec_log_debug("No charset (case #1). Defaulting to \"%s\".", m_dopt.ascii_upgrade);
  1872. mime_charset = m_dopt.ascii_upgrade;
  1873. } else {
  1874. /* RFC 2045 §5.2 */
  1875. ec_log_debug("No charset (case #2). Defaulting to \"us-ascii\".");
  1876. mime_charset = vmime::charsets::US_ASCII;
  1877. }
  1878. }
  1879. mime_charset = vtm_upgrade_charset(mime_charset, m_dopt.ascii_upgrade);
  1880. if (!ValidateCharset(mime_charset.getName().c_str())) {
  1881. /* RFC 2049 §2 item 6 subitem 5 */
  1882. ec_log_debug("Unknown Content-Type charset \"%s\". Storing as attachment instead.", mime_charset.getName().c_str());
  1883. return handleAttachment(vmHeader, vmBody, lpMessage, true);
  1884. }
  1885. /*
  1886. * Because PR_BODY is not of type PT_BINARY, the length is
  1887. * determined by looking for the first \0 rather than a
  1888. * dedicated length field. This interferes with multibyte
  1889. * encodings which use 0x00 bytes in their sequences, such as
  1890. * UTF-16. (For example '!' in UTF-16BE is 0x00 0x21.)
  1891. *
  1892. * To cure this, the input is converted to a wide string, so
  1893. * that we work with codepoints instead of bytes. Then, we only
  1894. * have to consider U+0000 codepoints, which we will just strip
  1895. * as they are not very useful in text.
  1896. *
  1897. * The data will be stored in PR_BODY_W, and since the encoding
  1898. * is prescribed for that, PR_INTERNET_CPID is not needed, but
  1899. * we record it anyway… for the testsuite, and for its
  1900. * unreviewed use in MAPIToVMIME.
  1901. */
  1902. std::string strBuffOut = content_transfer_decode(vmBody);
  1903. std::wstring strUnicodeText = m_converter.convert_to<std::wstring>(CHARSET_WCHAR "//IGNORE", strBuffOut, rawsize(strBuffOut), mime_charset.getName().c_str());
  1904. strUnicodeText.erase(std::remove(strUnicodeText.begin(), strUnicodeText.end(), L'\0'), strUnicodeText.end());
  1905. if (HrGetCPByCharset(mime_charset.getName().c_str(), &sCodepage.Value.ul) != hrSuccess)
  1906. /* pretend original input was UTF-8 */
  1907. sCodepage.Value.ul = 65001;
  1908. sCodepage.ulPropTag = PR_INTERNET_CPID;
  1909. HrSetOneProp(lpMessage, &sCodepage);
  1910. // create new or reset body
  1911. ULONG ulFlags = MAPI_MODIFY;
  1912. if (m_mailState.bodyLevel < BODY_PLAIN || !bAppendBody)
  1913. ulFlags |= MAPI_CREATE;
  1914. hr = lpMessage->OpenProperty(PR_BODY_W, &IID_IStream, STGM_TRANSACTED, ulFlags, &~lpStream);
  1915. if (hr != hrSuccess)
  1916. return hr;
  1917. if (bAppendBody) {
  1918. static const LARGE_INTEGER liZero = {{0, 0}};
  1919. hr = lpStream->Seek(liZero, SEEK_END, NULL);
  1920. if (hr != hrSuccess)
  1921. return hr;
  1922. }
  1923. hr = lpStream->Write(strUnicodeText.c_str(), strUnicodeText.length() * sizeof(wstring::value_type), NULL);
  1924. if (hr != hrSuccess)
  1925. return hr;
  1926. // commit triggers plain -> html/rtf conversion, PR_INTERNET_CPID must be set.
  1927. hr = lpStream->Commit(0);
  1928. if (hr != hrSuccess)
  1929. return hr;
  1930. }
  1931. catch (vmime::exception &e) {
  1932. ec_log_err("VMIME exception on text body: %s", e.what());
  1933. return MAPI_E_CALL_FAILED;
  1934. }
  1935. catch (std::exception &e) {
  1936. ec_log_err("STD exception on text body: %s", e.what());
  1937. return MAPI_E_CALL_FAILED;
  1938. }
  1939. catch (...) {
  1940. ec_log_err("Unknown generic exception occurred on text body");
  1941. return MAPI_E_CALL_FAILED;
  1942. }
  1943. m_mailState.bodyLevel = BODY_PLAIN;
  1944. return hrSuccess;
  1945. }
  1946. bool VMIMEToMAPI::filter_html(IMessage *msg, IStream *stream, ULONG flags,
  1947. const std::string &html)
  1948. {
  1949. #ifdef HAVE_TIDY_H
  1950. std::string clean_html;
  1951. std::vector<std::string> error;
  1952. HRESULT ret;
  1953. bool clean_ok = rosie_clean_html(html, &clean_html, &error);
  1954. for (size_t i = 0; i < error.size(); ++i)
  1955. ec_log_debug("HTML clean: %s", error[i].c_str());
  1956. if (!clean_ok)
  1957. return false;
  1958. ret = msg->OpenProperty(PR_EC_BODY_FILTERED, &IID_IStream,
  1959. STGM_TRANSACTED, flags, reinterpret_cast<LPUNKNOWN *>(&stream));
  1960. if (ret != hrSuccess) {
  1961. ec_log_warn("OpenProperty(PR_EC_BODY_FILTERED) failed: %s (%x)",
  1962. GetMAPIErrorDescription(ret).c_str(), ret);
  1963. return false;
  1964. }
  1965. ULONG written = 0;
  1966. ret = stream->Write(clean_html.c_str(), clean_html.length(), &written);
  1967. if (ret != hrSuccess) {
  1968. /* check cbWritten too? */
  1969. ec_log_warn("Write(PR_EC_BODY_FILTERED) failed: %s (%x)",
  1970. GetMAPIErrorDescription(ret).c_str(), ret);
  1971. return false;
  1972. }
  1973. ret = stream->Commit(0);
  1974. if (ret != hrSuccess) {
  1975. ec_log_warn("Commit(PR_EC_BODY_FILTERED) failed: %s (%x)",
  1976. GetMAPIErrorDescription(ret).c_str(), ret);
  1977. return false;
  1978. }
  1979. #endif
  1980. return true;
  1981. }
  1982. /**
  1983. * Converts a html body to the MAPI PR_HTML property using
  1984. * streams. Clients syncs this to PR_BODY and PR_RTF_COMPRESSED
  1985. * versions, to previously processed plain text bodies will be
  1986. * overwritten.
  1987. *
  1988. * @param[in] vmHeader header part describing the vmBody parameter.
  1989. * @param[in] vmBody body part containing HTML.
  1990. * @param[out] lpMessage IMessage to be modified.
  1991. * @param[in] bAppendBody Concatenate with existing body when still
  1992. * processing HTML body parts when set to true, otherwise it will
  1993. * become an attachment.
  1994. * @return MAPI error code.
  1995. * @retval MAPI_E_CALL_FAILED Caught an exception, which breaks the conversion.
  1996. *
  1997. * On the matter of character sets:
  1998. *
  1999. * “Using a <meta> tag for something like content-type and encoding is
  2000. * highly ironic, since without knowing those things, you couldn't parse
  2001. * the file to get the value of the meta tag.”
  2002. * — https://stackoverflow.com/q/4696499
  2003. *
  2004. * From that alone it already follows that encodings given inside the object
  2005. * itself are second-class.
  2006. *
  2007. * Two other considerations remain:
  2008. *
  2009. * 1. If a mail relay in the transport chain decides to recode a message (say,
  2010. * change it from ISO-8859-1 to ISO-8859-15), it should not modify the
  2011. * message content. (I claim that most MTAs do not even know HTML, nor
  2012. * should they.) Therefore, the new encoding must be conveyed external to
  2013. * the content, namely by means of the Content-Type field. => We must ignore
  2014. * the <meta> tag.
  2015. *
  2016. * 2. If decoding the MIME part with the Content-Type encoding produces an
  2017. * error (e.g. found a sequence that is undefined in this encoding), yet
  2018. * decoding the MIME part with the <meta> encoding succeeds, we still
  2019. * cannot be sure that the <meta> tag is the right one to use.
  2020. * => Could be transmission corruption or willful malignent mangling of
  2021. * the message.
  2022. *
  2023. * MIME hdr META hdr RFC says MUAs do Desired result
  2024. * --------------------------------------------------------------
  2025. * unspec unspec us-ascii us-ascii us-ascii
  2026. * unspec present unspec us-ascii meta
  2027. * present unspec mime mime mime
  2028. * present present mime mime mime
  2029. *
  2030. * Ideally, the message should be stored raw, and the mail body never be
  2031. * changed unless it is 100% certain that the transformation is unambiguously
  2032. * reversible. Like, how mbox systems actually do it.
  2033. * But with conversion to MAPI, we have this seemingly lossy conversion
  2034. * stage. :-(
  2035. */
  2036. HRESULT VMIMEToMAPI::handleHTMLTextpart(vmime::shared_ptr<vmime::header> vmHeader,
  2037. vmime::shared_ptr<vmime::body> vmBody, IMessage *lpMessage, bool bAppendBody)
  2038. {
  2039. HRESULT hr = hrSuccess;
  2040. object_ptr<IStream> lpHTMLStream;
  2041. ULONG cbWritten = 0;
  2042. std::string strHTML;
  2043. const char *lpszCharset = NULL;
  2044. SPropValue sCodepage;
  2045. LONG ulFlags;
  2046. bool new_text = m_mailState.bodyLevel < BODY_HTML ||
  2047. (m_mailState.bodyLevel == BODY_HTML && bAppendBody);
  2048. if (!new_text) {
  2049. // already found html as body, so this is an attachment
  2050. hr = handleAttachment(vmHeader, vmBody, lpMessage);
  2051. if (hr != hrSuccess) {
  2052. ec_log_err("Unable to parse attached text mail");
  2053. return hr;
  2054. }
  2055. return hrSuccess;
  2056. }
  2057. // we're overriding a plain text body, setting a new HTML body or appending HTML data
  2058. try {
  2059. /* process Content-Transfer-Encoding */
  2060. strHTML = content_transfer_decode(vmBody);
  2061. vmime::charset mime_charset =
  2062. get_mime_encoding(vmHeader, vmBody);
  2063. /* Look for alternative in HTML */
  2064. vmime::charset html_charset(im_charset_unspec);
  2065. int html_analyze = getCharsetFromHTML(strHTML, &html_charset);
  2066. if (html_analyze > 0 && html_charset != mime_charset &&
  2067. mime_charset != im_charset_unspec)
  2068. /*
  2069. * This is not actually a problem, it can
  2070. * happen when an MTA transcodes it.
  2071. */
  2072. ec_log_debug("MIME headers declare charset \"%s\", while HTML meta tag declares \"%s\".",
  2073. mime_charset.getName().c_str(),
  2074. html_charset.getName().c_str());
  2075. if (mime_charset == im_charset_unspec &&
  2076. html_charset == im_charset_unspec) {
  2077. if (m_mailState.mime_vtag_nest > 0) {
  2078. ec_log_debug("No charset (case #3), defaulting to \"us-ascii\".");
  2079. mime_charset = html_charset = vmime::charsets::US_ASCII;
  2080. } else if (html_analyze < 0) {
  2081. /*
  2082. * No HTML structure found when assuming ASCII,
  2083. * so we can just directly fallback to default_charset.
  2084. */
  2085. ec_log_debug("No charset (case #4), defaulting to \"%s\".", m_dopt.ascii_upgrade);
  2086. mime_charset = html_charset = m_dopt.ascii_upgrade;
  2087. } else {
  2088. /* HTML structure recognized when interpreting as ASCII. */
  2089. ec_log_debug("No charset (case #6), defaulting to \"us-ascii\".");
  2090. mime_charset = html_charset = vmime::charsets::US_ASCII;
  2091. }
  2092. } else if (mime_charset == im_charset_unspec) {
  2093. /* only place to name cset is <meta> */
  2094. ec_log_debug("Charset is \"%s\" (case #7).", html_charset.getName().c_str());
  2095. mime_charset = html_charset;
  2096. } else if (html_charset == im_charset_unspec) {
  2097. /* only place to name cset is MIME header */
  2098. ec_log_debug("Charset is \"%s\" (case #8).", mime_charset.getName().c_str());
  2099. html_charset = mime_charset;
  2100. }
  2101. mime_charset = vtm_upgrade_charset(mime_charset, m_dopt.ascii_upgrade);
  2102. html_charset = vtm_upgrade_charset(html_charset, m_dopt.ascii_upgrade);
  2103. /* Add secondary candidates and try all in order */
  2104. std::vector<std::string> cs_cand;
  2105. cs_cand.push_back(mime_charset.getName());
  2106. if (!m_dopt.charset_strict_rfc) {
  2107. if (mime_charset != html_charset)
  2108. cs_cand.push_back(html_charset.getName());
  2109. cs_cand.push_back(vmime::charsets::US_ASCII);
  2110. }
  2111. int cs_best = renovate_encoding(strHTML, cs_cand);
  2112. if (cs_best < 0) {
  2113. ec_log_err("HTML part not readable in any charset. Storing as attachment instead.");
  2114. return handleAttachment(vmHeader, vmBody, lpMessage, true);
  2115. }
  2116. /*
  2117. * PR_HTML is a PT_BINARY, and can handle 0x00 bytes
  2118. * (e.g. in case of UTF-16 encoding).
  2119. */
  2120. // write codepage for PR_HTML property
  2121. if (HrGetCPByCharset(cs_cand[cs_best].c_str(), &sCodepage.Value.ul) != hrSuccess) {
  2122. /* Win32 does not know the charset — change encoding to something it knows. */
  2123. sCodepage.Value.ul = 65001;
  2124. strHTML = m_converter.convert_to<std::string>("UTF-8", strHTML, rawsize(strHTML), cs_cand[cs_best].c_str());
  2125. ec_log_info("No Win32 CPID for \"%s\" - upgrading text/html MIME body to UTF-8", cs_cand[cs_best].c_str());
  2126. }
  2127. if (bAppendBody && m_mailState.bodyLevel == BODY_HTML && m_mailState.ulLastCP && sCodepage.Value.ul != m_mailState.ulLastCP) {
  2128. // we're appending but the new body part has a different codepage than the previous one. To support this
  2129. // we have to upgrade the old data to UTF-8, convert the new data to UTF-8 and append that.
  2130. if(m_mailState.ulLastCP != 65001) {
  2131. hr = HrGetCharsetByCP(m_mailState.ulLastCP, &lpszCharset);
  2132. if (hr != hrSuccess) {
  2133. assert(false); // Should not happen since ulLastCP was generated by HrGetCPByCharset()
  2134. return hr;
  2135. }
  2136. // Convert previous body part to UTF-8
  2137. std::string strCurrentHTML;
  2138. hr = Util::ReadProperty(lpMessage, PR_HTML, strCurrentHTML);
  2139. if (hr != hrSuccess)
  2140. return hr;
  2141. strCurrentHTML = m_converter.convert_to<std::string>("UTF-8", strCurrentHTML, rawsize(strCurrentHTML), lpszCharset);
  2142. hr = Util::WriteProperty(lpMessage, PR_HTML, strCurrentHTML);
  2143. if (hr != hrSuccess)
  2144. return hr;
  2145. }
  2146. if (sCodepage.Value.ul != 65001)
  2147. // Convert new body part to UTF-8
  2148. strHTML = m_converter.convert_to<std::string>("UTF-8", strHTML, rawsize(strHTML), mime_charset.getName().c_str());
  2149. // Everything is UTF-8 now
  2150. sCodepage.Value.ul = 65001;
  2151. mime_charset = "utf-8";
  2152. }
  2153. m_mailState.ulLastCP = sCodepage.Value.ul;
  2154. sCodepage.ulPropTag = PR_INTERNET_CPID;
  2155. HrSetOneProp(lpMessage, &sCodepage);
  2156. // we may have received a text part to append to the HTML body
  2157. if (vmime::dynamicCast<vmime::mediaType>(vmHeader->ContentType()->getValue())->getSubType() ==
  2158. vmime::mediaTypes::TEXT_PLAIN) {
  2159. // escape and wrap with <pre> tags
  2160. std::wstring strwBody = m_converter.convert_to<std::wstring>(CHARSET_WCHAR "//IGNORE", strHTML, rawsize(strHTML), mime_charset.getName().c_str());
  2161. strHTML = "<pre>";
  2162. hr = Util::HrTextToHtml(strwBody.c_str(), strHTML, sCodepage.Value.ul);
  2163. if (hr != hrSuccess)
  2164. return hr;
  2165. strHTML += "</pre>";
  2166. }
  2167. }
  2168. catch (vmime::exception &e) {
  2169. ec_log_err("VMIME exception on html body: %s", e.what());
  2170. return MAPI_E_CALL_FAILED;
  2171. }
  2172. catch (std::exception &e) {
  2173. ec_log_err("STD exception on html body: %s", e.what());
  2174. return MAPI_E_CALL_FAILED;
  2175. }
  2176. catch (...) {
  2177. ec_log_err("Unknown generic exception occurred on html body");
  2178. return MAPI_E_CALL_FAILED;
  2179. }
  2180. // create new or reset body
  2181. ulFlags = MAPI_MODIFY;
  2182. if (m_mailState.bodyLevel == BODY_NONE || (m_mailState.bodyLevel < BODY_HTML && !bAppendBody))
  2183. ulFlags |= MAPI_CREATE;
  2184. hr = lpMessage->OpenProperty(PR_HTML, &IID_IStream, STGM_TRANSACTED, ulFlags, &~lpHTMLStream);
  2185. if (hr != hrSuccess) {
  2186. ec_log_err("OpenProperty PR_HTML failed: %s", GetMAPIErrorMessage(hr));
  2187. return hr;
  2188. }
  2189. if (bAppendBody) {
  2190. static const LARGE_INTEGER liZero = {{0, 0}};
  2191. hr = lpHTMLStream->Seek(liZero, SEEK_END, NULL);
  2192. if (hr != hrSuccess)
  2193. return hr;
  2194. }
  2195. hr = lpHTMLStream->Write(strHTML.c_str(), strHTML.length(), &cbWritten);
  2196. if (hr != hrSuccess) // check cbWritten too?
  2197. return hr;
  2198. hr = lpHTMLStream->Commit(0);
  2199. if (hr != hrSuccess)
  2200. return hr;
  2201. m_mailState.bodyLevel = BODY_HTML;
  2202. if (bAppendBody)
  2203. m_mailState.strHTMLBody.append(strHTML);
  2204. else
  2205. swap(strHTML, m_mailState.strHTMLBody);
  2206. if (m_dopt.html_safety_filter)
  2207. filter_html(lpMessage, lpHTMLStream, ulFlags, strHTML);
  2208. return hrSuccess;
  2209. }
  2210. /**
  2211. * Handle Attachments.. Now works for inlines and attachments...
  2212. *
  2213. * @param[in] vmHeader headers describing vmBody parameter
  2214. * @param[in] vmBody body part
  2215. * @param[out] lpMessage IMessage to be modified.
  2216. * @return MAPI error code.
  2217. * @retval MAPI_E_CALL_FAILED Caught an exception, which breaks the conversion.
  2218. */
  2219. HRESULT VMIMEToMAPI::handleAttachment(vmime::shared_ptr<vmime::header> vmHeader,
  2220. vmime::shared_ptr<vmime::body> vmBody, IMessage *lpMessage, bool bAllowEmpty)
  2221. {
  2222. HRESULT hr = hrSuccess;
  2223. object_ptr<IStream> lpStream;
  2224. object_ptr<IAttach> lpAtt;
  2225. ULONG ulAttNr = 0;
  2226. std::string strId, strMimeType, strLocation, strTmp;
  2227. std::wstring strLongFilename;
  2228. int nProps = 0;
  2229. SPropValue attProps[12];
  2230. vmime::shared_ptr<vmime::contentDispositionField> cdf; // parameters of Content-Disposition header
  2231. vmime::shared_ptr<vmime::contentDisposition> cdv; // value of Content-Disposition header
  2232. vmime::shared_ptr<vmime::contentTypeField> ctf;
  2233. vmime::shared_ptr<vmime::mediaType> mt;
  2234. memset(attProps, 0, sizeof(attProps));
  2235. // Create Attach
  2236. hr = lpMessage->CreateAttach(nullptr, 0, &ulAttNr, &~lpAtt);
  2237. if (hr != hrSuccess)
  2238. goto exit;
  2239. // open stream
  2240. hr = lpAtt->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, STGM_WRITE|STGM_TRANSACTED,
  2241. MAPI_CREATE | MAPI_MODIFY, &~lpStream);
  2242. if (hr != hrSuccess)
  2243. goto exit;
  2244. try {
  2245. // attach adapter, generate in right encoding
  2246. outputStreamMAPIAdapter osMAPI(lpStream);
  2247. cdf = vmime::dynamicCast<vmime::contentDispositionField>(vmHeader->ContentDisposition());
  2248. cdv = vmime::dynamicCast<vmime::contentDisposition>(cdf->getValue());
  2249. ctf = vmime::dynamicCast<vmime::contentTypeField>(vmHeader->ContentType());
  2250. mt = vmime::dynamicCast<vmime::mediaType>(ctf->getValue());
  2251. try {
  2252. vmBody->getContents()->generate(osMAPI, vmime::encoding(vmime::encodingTypes::BINARY));
  2253. } catch (vmime::exceptions::no_encoder_available &) {
  2254. /* RFC 2045 §6.4 page 17 */
  2255. vmBody->getContents()->extractRaw(osMAPI);
  2256. mt->setType(vmime::mediaTypes::APPLICATION);
  2257. mt->setSubType(vmime::mediaTypes::APPLICATION_OCTET_STREAM);
  2258. }
  2259. if (!bAllowEmpty) {
  2260. STATSTG stat;
  2261. hr = lpStream->Stat(&stat, 0);
  2262. if (hr != hrSuccess)
  2263. goto exit;
  2264. if (stat.cbSize.QuadPart == 0) {
  2265. ec_log_err("Empty attachment found when not allowed, dropping empty attachment.");
  2266. hr = MAPI_E_NOT_FOUND;
  2267. goto exit;
  2268. }
  2269. }
  2270. hr = lpStream->Commit(0);
  2271. if (hr != hrSuccess)
  2272. goto exit;
  2273. // Free memory used by the stream
  2274. lpStream.reset();
  2275. // set info on attachment
  2276. attProps[nProps].ulPropTag = PR_ATTACH_METHOD;
  2277. attProps[nProps++].Value.ul = ATTACH_BY_VALUE;
  2278. // vmHeader->ContentId() is headerField ->getValue() returns headerFieldValue, which messageId is.
  2279. strId = vmime::dynamicCast<vmime::messageId>(vmHeader->ContentId()->getValue())->getId();
  2280. if (!strId.empty()) {
  2281. // only set this property when string is present
  2282. // otherwise, you don't get the 'save attachments' list in the main menu of outlook
  2283. attProps[nProps].ulPropTag = PR_ATTACH_CONTENT_ID_A;
  2284. attProps[nProps++].Value.lpszA = (char*)strId.c_str();
  2285. }
  2286. try {
  2287. strLocation = vmime::dynamicCast<vmime::text>(vmHeader->ContentLocation()->getValue())->getConvertedText(MAPI_CHARSET);
  2288. }
  2289. catch (vmime::exceptions::charset_conv_error) { }
  2290. if (!strLocation.empty()) {
  2291. attProps[nProps].ulPropTag = PR_ATTACH_CONTENT_LOCATION_A;
  2292. attProps[nProps++].Value.lpszA = (char*)strLocation.c_str();
  2293. }
  2294. // make hidden when inline, is an image or text, has a content id or location, is an HTML mail,
  2295. // has a CID reference in the HTML or has a location reference in the HTML.
  2296. if (cdv->getName() == vmime::contentDispositionTypes::INLINE &&
  2297. (mt->getType() == vmime::mediaTypes::IMAGE || mt->getType() == vmime::mediaTypes::TEXT) &&
  2298. (!strId.empty() || !strLocation.empty()) &&
  2299. m_mailState.bodyLevel == BODY_HTML &&
  2300. ((!strId.empty() && strcasestr(m_mailState.strHTMLBody.c_str(), string("cid:"+strId).c_str())) ||
  2301. (!strLocation.empty() && strcasestr(m_mailState.strHTMLBody.c_str(), strLocation.c_str())) ))
  2302. {
  2303. attProps[nProps].ulPropTag = PR_ATTACHMENT_HIDDEN;
  2304. attProps[nProps++].Value.b = TRUE;
  2305. attProps[nProps].ulPropTag = PR_ATTACH_FLAGS;
  2306. attProps[nProps++].Value.ul = 4; // ATT_MHTML_REF
  2307. attProps[nProps].ulPropTag = PR_ATTACHMENT_FLAGS;
  2308. attProps[nProps++].Value.ul = 8; // unknown, for now
  2309. if (m_mailState.attachLevel < ATTACH_NORMAL)
  2310. m_mailState.attachLevel = ATTACH_INLINE;
  2311. } else {
  2312. attProps[nProps].ulPropTag = PR_ATTACHMENT_HIDDEN;
  2313. attProps[nProps++].Value.b = FALSE;
  2314. attProps[nProps].ulPropTag = PR_ATTACH_FLAGS;
  2315. attProps[nProps++].Value.ul = 0;
  2316. m_mailState.attachLevel = ATTACH_NORMAL;
  2317. }
  2318. // filenames
  2319. if (cdf->hasParameter("filename"))
  2320. strLongFilename = getWideFromVmimeText(vmime::text(cdf->getFilename()));
  2321. else if (ctf->hasParameter("name"))
  2322. strLongFilename = getWideFromVmimeText(vmime::text(ctf->getParameter("name")->getValue()));
  2323. else if (mt->getType() == vmime::mediaTypes::TEXT && mt->getSubType() == "calendar")
  2324. // already catched in message-in-message code.
  2325. strLongFilename = L"calendar.ics";
  2326. else
  2327. // TODO: add guessFilenameFromContentType()
  2328. strLongFilename = L"inline.txt";
  2329. attProps[nProps].ulPropTag = PR_ATTACH_LONG_FILENAME_W;
  2330. attProps[nProps++].Value.lpszW = (WCHAR*)strLongFilename.c_str();
  2331. // outlook internal rendering sequence in RTF bodies. When set
  2332. // to -1, outlook will ignore it, when set to 0 or higher,
  2333. // outlook (mapi) will regenerate the numbering
  2334. attProps[nProps].ulPropTag = PR_RENDERING_POSITION;
  2335. attProps[nProps++].Value.ul = 0;
  2336. try {
  2337. if (!mt->getType().empty() &&
  2338. !mt->getSubType().empty()) {
  2339. strMimeType = mt->getType() + "/" + mt->getSubType();
  2340. // due to a bug in vmime 0.7, the continuation header text can be prefixed in the string, so strip it (easiest way to fix)
  2341. while (strMimeType[0] == '\r' || strMimeType[0] == '\n' || strMimeType[0] == '\t' || strMimeType[0] == ' ')
  2342. strMimeType.erase(0, 1);
  2343. attProps[nProps].ulPropTag = PR_ATTACH_MIME_TAG_A;
  2344. attProps[nProps++].Value.lpszA = (char*)strMimeType.c_str();
  2345. }
  2346. }
  2347. catch (vmime::exceptions::no_such_field) {
  2348. }
  2349. hr = lpAtt->SetProps(nProps, attProps, NULL);
  2350. if (hr != hrSuccess)
  2351. goto exit;
  2352. }
  2353. catch (vmime::exception& e) {
  2354. ec_log_err("VMIME exception on attachment: %s", e.what());
  2355. hr = MAPI_E_CALL_FAILED;
  2356. goto exit;
  2357. }
  2358. catch (std::exception& e) {
  2359. ec_log_err("STD exception on attachment: %s", e.what());
  2360. hr = MAPI_E_CALL_FAILED;
  2361. goto exit;
  2362. }
  2363. catch (...) {
  2364. ec_log_err("Unknown generic exception occurred on attachment");
  2365. hr = MAPI_E_CALL_FAILED;
  2366. goto exit;
  2367. }
  2368. hr = lpAtt->SaveChanges(0);
  2369. if (hr != hrSuccess)
  2370. goto exit;
  2371. exit:
  2372. if (hr != hrSuccess)
  2373. ec_log_err("Unable to create attachment");
  2374. return hr;
  2375. }
  2376. static const struct {
  2377. const char *original;
  2378. const char *update;
  2379. } vtm_cs_upgrade_list[] = {
  2380. {"cp-850", "cp850"},
  2381. {"gb2312", "gb18030"}, // gb18030 is an extended version of gb2312
  2382. {"x-gbk", "gb18030"}, // gb18030 > gbk > gb2312. x-gbk is an alias of gbk, which is not listed in iconv.
  2383. {"ks_c_5601-1987", "cp949"}, // cp949 is euc-kr with UHC extensions
  2384. {"iso-8859-8-i", "iso-8859-8"}, // logical vs visual order, does not matter. http://mirror.hamakor.org.il/archives/linux-il/08-2004/11445.html
  2385. {"win-1252", "windows-1252"},
  2386. /*
  2387. * This particular "unicode" is different from iconv's
  2388. * "unicode" character set. It is UTF-8 content with a UTF-16
  2389. * BOM (which we can just drop because it carries no
  2390. * relevant information).
  2391. */
  2392. {"unicode", "utf-8"}, /* UTF-16 BOM + UTF-8 content */
  2393. };
  2394. /**
  2395. * Perform upgrades of the character set name, or the character set itself.
  2396. *
  2397. * 1. Some e-mails carry strange unregistered names ("unicode"), or simply
  2398. * names which are registered with IANA but uncommon enough ("iso-8859-8-i")
  2399. * that iconv does not know about them. This function returns a compatible
  2400. * replacement usable with iconv.
  2401. *
  2402. * 2. The function performs compatible upgrades (such as gb2312→gb18030, both
  2403. * of which are known to iconv) which repairs some mistagged email and does not
  2404. * break properly-tagged mail.
  2405. *
  2406. * 3. The function also performs admin-configured compatible upgrades
  2407. * (such as us-ascii→utf-8).
  2408. */
  2409. static vmime::charset vtm_upgrade_charset(vmime::charset cset, const char *upg)
  2410. {
  2411. if (upg != nullptr && cset == vmime::charsets::US_ASCII &&
  2412. cset != upg) {
  2413. /*
  2414. * It is expected that the caller made sure that the
  2415. * replacement is in fact ASCII compatible.
  2416. */
  2417. ec_log_debug("Admin forced charset upgrade \"%s\" -> \"%s\".",
  2418. cset.getName().c_str(), upg);
  2419. cset = upg;
  2420. }
  2421. for (size_t i = 0; i < ARRAY_SIZE(vtm_cs_upgrade_list); ++i)
  2422. if (strcasecmp(vtm_cs_upgrade_list[i].original, cset.getName().c_str()) == 0)
  2423. return vtm_cs_upgrade_list[i].update;
  2424. return cset;
  2425. }
  2426. static htmlNodePtr find_node(htmlNodePtr lpNode, const char *name)
  2427. {
  2428. htmlNodePtr node = NULL;
  2429. for (node = lpNode; node; node = node->next) {
  2430. if (node->type != XML_ELEMENT_NODE)
  2431. continue;
  2432. htmlNodePtr child = NULL;
  2433. if (xmlStrcasecmp(node->name, reinterpret_cast<const xmlChar *>(name)) == 0)
  2434. break;
  2435. child = find_node(node->children, name);
  2436. if (child)
  2437. return child;
  2438. }
  2439. return node;
  2440. }
  2441. void ignoreError(void *ctx, const char *msg, ...)
  2442. {
  2443. }
  2444. /**
  2445. * Determine character set from a possibly broken Content-Type value.
  2446. * @in: string in the form of m{^text/foo\s*(;?\s*key=value)*}
  2447. *
  2448. * Attempt to extract the character set parameter, e.g. from a HTML <meta> tag,
  2449. * or from a Content-Type MIME header (though we do not use it for MIME headers
  2450. * currently).
  2451. */
  2452. static std::string fix_content_type_charset(const char *in)
  2453. {
  2454. const char *cset = im_charset_unspec, *cset_end = im_charset_unspec;
  2455. while (!isspace(*in) && *in != '\0') /* skip type */
  2456. ++in;
  2457. while (*in != '\0') {
  2458. while (isspace(*in))
  2459. ++in; /* skip possible whitespace before ';' */
  2460. if (*in == ';') {
  2461. ++in;
  2462. while (isspace(*in)) /* skip WS after ';' */
  2463. ++in;
  2464. }
  2465. if (strncasecmp(in, "charset=", 8) == 0) {
  2466. in += 8;
  2467. cset = in;
  2468. while (!isspace(*in) && *in != ';' && *in != '\0')
  2469. ++in; /* skip value */
  2470. cset_end = in;
  2471. continue;
  2472. /* continue parsing for more charset= values */
  2473. }
  2474. while (!isspace(*in) && *in != ';' && *in != '\0')
  2475. ++in;
  2476. }
  2477. return std::string(cset, cset_end - cset);
  2478. }
  2479. /**
  2480. * Find alternate backup character set declaration
  2481. *
  2482. * @strHTML: input MIME body part (HTML document)
  2483. * @htmlCharset: result from HTML <meta>
  2484. *
  2485. * In the MIME body, attempt to find the character set declaration in the
  2486. * <meta> tag of the HTML document. This function requires that the HTML
  2487. * skeleton is encoded in US-ASCII.
  2488. *
  2489. * If the MIME header specifies, for example, Content-Type: text/html;
  2490. * charset=utf-16, then this function will not find anything -- and that is
  2491. * correct, because if the MIME body is encoded in UTF-16, whatever else there
  2492. * is in <meta> is, if it is not UTF-16, is likely wrong to begin with.
  2493. *
  2494. * Returns -1 if it does not appear to be HTML at all,
  2495. * returns 0 if it looked like HTML/XML, but no character set was specified,
  2496. * and returns 1 if a character set was declared.
  2497. */
  2498. int VMIMEToMAPI::getCharsetFromHTML(const string &strHTML, vmime::charset *htmlCharset)
  2499. {
  2500. int ret = 0;
  2501. htmlDocPtr lpDoc = NULL;
  2502. htmlNodePtr root = NULL, lpNode = NULL;
  2503. xmlChar *lpValue = NULL;
  2504. std::string charset;
  2505. // really lazy html parsing and disable all error reporting
  2506. xmlSetGenericErrorFunc(NULL, ignoreError); // disable stderr output (ZCP-13337)
  2507. /*
  2508. * Parser will automatically lower-case element and attribute names.
  2509. * It appears to try decoding as UTF-16 as well.
  2510. */
  2511. lpDoc = htmlReadMemory(strHTML.c_str(), strHTML.length(), "", NULL, HTML_PARSE_RECOVER | HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
  2512. if (!lpDoc) {
  2513. ec_log_warn("Unable to parse HTML document");
  2514. ret = -1;
  2515. goto exit;
  2516. }
  2517. /*
  2518. * The HTML parser is very forgiving, so lpDoc is almost never %NULL
  2519. * (only if input buffer is size 0 apparently). But, if we have data
  2520. * in, for example, UTF-32 encoding, then @root will be NULL.
  2521. */
  2522. root = xmlDocGetRootElement(lpDoc);
  2523. if (root == NULL) {
  2524. ec_log_warn("Unable to parse HTML document");
  2525. ret = -1;
  2526. goto exit;
  2527. }
  2528. lpNode = find_node(root, "head");
  2529. if (!lpNode) {
  2530. ec_log_debug("HTML document contains no HEAD tag");
  2531. goto exit;
  2532. }
  2533. for (lpNode = lpNode->children; lpNode != NULL; lpNode = lpNode->next) {
  2534. if (lpNode->type != XML_ELEMENT_NODE)
  2535. continue;
  2536. if (xmlStrcasecmp(lpNode->name,
  2537. reinterpret_cast<const xmlChar *>("meta")) != 0)
  2538. continue;
  2539. // HTML 4, <meta http-equiv="Content-Type" content="text/html; charset=...">
  2540. lpValue = xmlGetProp(lpNode, (const xmlChar*)"http-equiv");
  2541. if (lpValue && xmlStrcasecmp(lpValue, (const xmlChar*)"Content-Type") == 0) {
  2542. xmlFree(lpValue);
  2543. lpValue = xmlGetProp(lpNode, (const xmlChar*)"content");
  2544. if (lpValue) {
  2545. ec_log_debug("HTML4 meta tag found: charset=\"%s\"", lpValue);
  2546. charset = fix_content_type_charset(reinterpret_cast<const char *>(lpValue));
  2547. }
  2548. break;
  2549. }
  2550. if (lpValue)
  2551. xmlFree(lpValue);
  2552. lpValue = NULL;
  2553. // HTML 5, <meta charset="...">
  2554. lpValue = xmlGetProp(lpNode, (const xmlChar*)"charset");
  2555. if (lpValue) {
  2556. ec_log_debug("HTML5 meta tag found: charset=\"%s\"", lpValue);
  2557. charset = reinterpret_cast<char *>(lpValue);
  2558. break;
  2559. }
  2560. }
  2561. if (!lpValue) {
  2562. ec_log_debug("HTML body does not contain meta charset information");
  2563. goto exit;
  2564. }
  2565. *htmlCharset = charset.size() != 0 ? vtm_upgrade_charset(charset) :
  2566. vmime::charsets::US_ASCII;
  2567. ec_log_debug("HTML charset adjusted to \"%s\"", htmlCharset->getName().c_str());
  2568. ret = 1;
  2569. exit:
  2570. if (lpValue)
  2571. xmlFree(lpValue);
  2572. if (lpDoc)
  2573. xmlFreeDoc(lpDoc);
  2574. return ret;
  2575. }
  2576. /**
  2577. * Convert a vmime::text object to wstring. This function may force
  2578. * another charset on the words in the text object for compatibility
  2579. * reasons.
  2580. *
  2581. * @param[in] vmText vmime text object containing encoded words (string + charset)
  2582. *
  2583. * @return converted text in unicode
  2584. */
  2585. std::wstring VMIMEToMAPI::getWideFromVmimeText(const vmime::text &vmText)
  2586. {
  2587. std::string myword;
  2588. std::wstring ret;
  2589. const auto &words = vmText.getWordList();
  2590. for (auto i = words.cbegin(); i != words.cend(); ++i) {
  2591. /*
  2592. * RFC 5322 §2.2 specifies header field bodies consist of
  2593. * US-ASCII characters only, and the only way to get other
  2594. * encodings is by RFC 2047. In other words, the use of
  2595. * m_dopt.default_charset is disallowed.
  2596. */
  2597. vmime::charset wordCharset = vtm_upgrade_charset((*i)->getCharset());
  2598. /*
  2599. * In case of unknown character sets, RFC 2047 §6.2 ¶5
  2600. * gives the following options:
  2601. *
  2602. * (a) display input as-is, e.g. as =?utf-8?Q?VielSpa=C3=9F?=
  2603. * if (!ValidateCharset(..))
  2604. * ret += m_converter.convert_to<std::wstring>((*i)->generate());
  2605. * (b) best effort conversion (which we pick) or
  2606. * (c) substitute by a message that decoding failed.
  2607. *
  2608. * We pick (b), which means something ASCII-compatible.
  2609. * (a) is also a good choice, but the unreadable parts may be
  2610. * longer for not much benefit to the human reader.
  2611. */
  2612. if (!ValidateCharset(wordCharset.getName().c_str()))
  2613. wordCharset = m_dopt.ascii_upgrade;
  2614. /*
  2615. * Concatenate words having the same charset, as the original
  2616. * input bytes may not have been safely split up. I cannot make
  2617. * out whether RFC 2047 §6.2 ¶6 actually discourages this
  2618. * concatenation, but permitting it gives the most pleasing
  2619. * result without violently disagreeing with the RFC. Hence,
  2620. * we also will not be adding if (m_dopt.charset_strict_rfc)
  2621. * here anytime soon.
  2622. */
  2623. myword = (*i)->getBuffer();
  2624. for (auto j = i + 1; j != words.cend() && (*j)->getCharset() == wordCharset; ++j, ++i)
  2625. myword += (*j)->getBuffer();
  2626. std::string tmp = vmime::word(myword, wordCharset).getConvertedText(CHARSET_WCHAR);
  2627. ret.append(reinterpret_cast<const wchar_t *>(tmp.c_str()), tmp.size() / sizeof(wchar_t));
  2628. }
  2629. return ret;
  2630. }
  2631. /**
  2632. * Do various fixups of missing or incorrect data.
  2633. *
  2634. * @param[in,out] lpMessage IMessage object to process
  2635. * @return MAPI error code.
  2636. */
  2637. HRESULT VMIMEToMAPI::postWriteFixups(IMessage *lpMessage)
  2638. {
  2639. HRESULT hr = hrSuccess;
  2640. memory_ptr<SPropValue> lpMessageClass, lpProps, lpRecProps;
  2641. ULONG cValues = 0;
  2642. ULONG cRecProps = 0;
  2643. ULONG cbConversationIndex = 0;
  2644. memory_ptr<unsigned char> lpConversationIndex;
  2645. PROPMAP_START(21)
  2646. PROPMAP_NAMED_ID(RECURRENCESTATE, PT_BINARY, PSETID_Appointment, dispidRecurrenceState)
  2647. PROPMAP_NAMED_ID(RESPONSESTATUS, PT_LONG, PSETID_Appointment, dispidResponseStatus)
  2648. PROPMAP_NAMED_ID(RECURRING, PT_BOOLEAN, PSETID_Appointment, dispidRecurring)
  2649. PROPMAP_NAMED_ID(ATTENDEECRITICALCHANGE, PT_SYSTIME, PSETID_Meeting, dispidAttendeeCriticalChange)
  2650. PROPMAP_NAMED_ID(OWNERCRITICALCHANGE, PT_SYSTIME, PSETID_Meeting, dispidOwnerCriticalChange)
  2651. PROPMAP_NAMED_ID(MEETING_RECURRING, PT_BOOLEAN, PSETID_Meeting, dispidIsRecurring)
  2652. PROPMAP_NAMED_ID(MEETING_STARTRECDATE, PT_LONG, PSETID_Meeting, dispidStartRecurrenceDate)
  2653. PROPMAP_NAMED_ID(MEETING_STARTRECTIME, PT_LONG, PSETID_Meeting, dispidStartRecurrenceTime)
  2654. PROPMAP_NAMED_ID(MEETING_ENDRECDATE, PT_LONG, PSETID_Meeting, dispidEndRecurrenceDate)
  2655. PROPMAP_NAMED_ID(MEETING_ENDRECTIME, PT_LONG, PSETID_Meeting, dispidEndRecurrenceTime)
  2656. PROPMAP_NAMED_ID(MEETING_DAYINTERVAL, PT_I2, PSETID_Meeting, dispidDayInterval)
  2657. PROPMAP_NAMED_ID(MEETING_WEEKINTERVAL, PT_I2, PSETID_Meeting, dispidWeekInterval)
  2658. PROPMAP_NAMED_ID(MEETING_MONTHINTERVAL, PT_I2, PSETID_Meeting, dispidMonthInterval)
  2659. PROPMAP_NAMED_ID(MEETING_YEARINTERVAL, PT_I2, PSETID_Meeting, dispidYearInterval)
  2660. PROPMAP_NAMED_ID(MEETING_DOWMASK, PT_LONG, PSETID_Meeting, dispidDayOfWeekMask)
  2661. PROPMAP_NAMED_ID(MEETING_DOMMASK, PT_LONG, PSETID_Meeting, dispidDayOfMonthMask)
  2662. PROPMAP_NAMED_ID(MEETING_MOYMASK, PT_LONG, PSETID_Meeting, dispidMonthOfYearMask)
  2663. PROPMAP_NAMED_ID(MEETING_RECURRENCETYPE, PT_I2, PSETID_Meeting, dispidOldRecurrenceType)
  2664. PROPMAP_NAMED_ID(MEETING_DOWSTART, PT_I2, PSETID_Meeting, dispidDayOfWeekStart)
  2665. PROPMAP_NAMED_ID(CLIPSTART, PT_SYSTIME, PSETID_Appointment, dispidClipStart)
  2666. PROPMAP_NAMED_ID(CLIPEND, PT_SYSTIME, PSETID_Appointment, dispidClipEnd)
  2667. PROPMAP_INIT(lpMessage)
  2668. hr = HrGetOneProp(lpMessage, PR_MESSAGE_CLASS_A, &~lpMessageClass);
  2669. if (hr != hrSuccess)
  2670. goto exitpm;
  2671. if (strncasecmp(lpMessageClass->Value.lpszA, "IPM.Schedule.Meeting.", strlen( "IPM.Schedule.Meeting." )) == 0)
  2672. {
  2673. // IPM.Schedule.Meeting.*
  2674. SizedSPropTagArray(6, sptaMeetingReqProps) = {6, {PROP_RESPONSESTATUS, PROP_RECURRING, PROP_ATTENDEECRITICALCHANGE, PROP_OWNERCRITICALCHANGE, PR_OWNER_APPT_ID, PR_CONVERSATION_INDEX }};
  2675. hr = lpMessage->GetProps(sptaMeetingReqProps, 0, &cValues, &~lpProps);
  2676. if(FAILED(hr))
  2677. goto exitpm;
  2678. // If hr is hrSuccess then all properties are available, and we don't need to do anything
  2679. if(hr != hrSuccess) {
  2680. hr = hrSuccess;
  2681. if(lpProps[0].ulPropTag != PROP_RESPONSESTATUS) {
  2682. lpProps[0].ulPropTag = PROP_RESPONSESTATUS;
  2683. lpProps[0].Value.ul = 0;
  2684. }
  2685. if(lpProps[1].ulPropTag != PROP_RECURRING) {
  2686. lpProps[1].ulPropTag = PROP_RECURRING;
  2687. lpProps[1].Value.b = false;
  2688. }
  2689. if(lpProps[2].ulPropTag != PROP_ATTENDEECRITICALCHANGE) {
  2690. lpProps[2].ulPropTag = PROP_ATTENDEECRITICALCHANGE;
  2691. UnixTimeToFileTime(time(NULL), &lpProps[2].Value.ft);
  2692. }
  2693. if(lpProps[3].ulPropTag != PROP_OWNERCRITICALCHANGE) {
  2694. lpProps[3].ulPropTag = PROP_OWNERCRITICALCHANGE;
  2695. UnixTimeToFileTime(time(NULL), &lpProps[3].Value.ft);
  2696. }
  2697. if(lpProps[4].ulPropTag != PR_OWNER_APPT_ID) {
  2698. lpProps[4].ulPropTag = PR_OWNER_APPT_ID;
  2699. lpProps[4].Value.ul = -1;
  2700. }
  2701. if(lpProps[5].ulPropTag != PR_CONVERSATION_INDEX) {
  2702. lpProps[5].ulPropTag = PR_CONVERSATION_INDEX;
  2703. hr = ScCreateConversationIndex(0, NULL, &cbConversationIndex, &~lpConversationIndex);
  2704. if(hr != hrSuccess)
  2705. goto exitpm;
  2706. lpProps[5].Value.bin.cb = cbConversationIndex;
  2707. lpProps[5].Value.bin.lpb = lpConversationIndex;
  2708. }
  2709. hr = lpMessage->SetProps(6, lpProps, NULL);
  2710. if(hr != hrSuccess)
  2711. goto exitpm;
  2712. }
  2713. // @todo
  2714. // this code should be in a separate function, which can easily
  2715. // do 'goto exit', and we can continue here with other fixes.
  2716. if(lpProps[1].Value.b)
  2717. {
  2718. // This is a recurring appointment. Generate the properties needed by CDO, which can be
  2719. // found in the recurrence state. Since these properties are completely redundant we always
  2720. // write them to correct any possible errors in the incoming message.
  2721. SPropValue sMeetingProps[14];
  2722. SizedSPropTagArray (3, sptaRecProps) = { 3, { PROP_RECURRENCESTATE, PROP_CLIPSTART, PROP_CLIPEND } };
  2723. RecurrenceState rec;
  2724. // @todo, if all properties are not available: remove recurrence true marker
  2725. hr = lpMessage->GetProps(sptaRecProps, 0, &cRecProps, &~lpRecProps);
  2726. if(hr != hrSuccess) // Warnings not accepted
  2727. goto exitpm;
  2728. hr = rec.ParseBlob(reinterpret_cast<const char *>(lpRecProps[0].Value.bin.lpb),
  2729. static_cast<unsigned int>(lpRecProps[0].Value.bin.cb), 0);
  2730. if(FAILED(hr))
  2731. goto exitpm;
  2732. // Ignore warnings
  2733. hr = hrSuccess;
  2734. sMeetingProps[0].ulPropTag = PROP_MEETING_STARTRECDATE;
  2735. sMeetingProps[0].Value.ul = FileTimeToIntDate(lpRecProps[1].Value.ft);
  2736. sMeetingProps[1].ulPropTag = PROP_MEETING_STARTRECTIME;
  2737. sMeetingProps[1].Value.ul = SecondsToIntTime(rec.ulStartTimeOffset * 60);
  2738. if(rec.ulEndType != ET_NEVER) {
  2739. sMeetingProps[2].ulPropTag = PROP_MEETING_ENDRECDATE;
  2740. sMeetingProps[2].Value.ul = FileTimeToIntDate(lpRecProps[2].Value.ft);
  2741. } else {
  2742. sMeetingProps[2].ulPropTag = PR_NULL;
  2743. }
  2744. sMeetingProps[3].ulPropTag = PROP_MEETING_ENDRECTIME;
  2745. sMeetingProps[3].Value.ul = SecondsToIntTime(rec.ulEndTimeOffset * 60);
  2746. // Default the following values to 0 and set them later if needed
  2747. sMeetingProps[4].ulPropTag = PROP_MEETING_DAYINTERVAL;
  2748. sMeetingProps[4].Value.i = 0;
  2749. sMeetingProps[5].ulPropTag = PROP_MEETING_WEEKINTERVAL;
  2750. sMeetingProps[5].Value.i = 0;
  2751. sMeetingProps[6].ulPropTag = PROP_MEETING_MONTHINTERVAL;
  2752. sMeetingProps[6].Value.i = 0;
  2753. sMeetingProps[7].ulPropTag = PROP_MEETING_YEARINTERVAL;
  2754. sMeetingProps[7].Value.i = 0;
  2755. sMeetingProps[8].ulPropTag = PROP_MEETING_DOWMASK;
  2756. sMeetingProps[8].Value.ul = 0 ;
  2757. sMeetingProps[9].ulPropTag = PROP_MEETING_DOMMASK;
  2758. sMeetingProps[9].Value.ul = 0;
  2759. sMeetingProps[10].ulPropTag = PROP_MEETING_MOYMASK;
  2760. sMeetingProps[10].Value.ul = 0;
  2761. sMeetingProps[11].ulPropTag = PROP_MEETING_RECURRENCETYPE;
  2762. sMeetingProps[11].Value.ul = 0;
  2763. sMeetingProps[12].ulPropTag = PROP_MEETING_DOWSTART;
  2764. sMeetingProps[12].Value.i = rec.ulFirstDOW;
  2765. sMeetingProps[13].ulPropTag = PROP_MEETING_RECURRING;
  2766. sMeetingProps[13].Value.b = true;
  2767. // Set the values depending on the type
  2768. switch(rec.ulRecurFrequency) {
  2769. case RF_DAILY:
  2770. if (rec.ulPatternType == PT_DAY) {
  2771. // Daily
  2772. sMeetingProps[4].Value.i = rec.ulPeriod / 1440; // DayInterval
  2773. sMeetingProps[11].Value.i = 64; // RecurrenceType
  2774. } else {
  2775. // Every workday, actually a weekly recurrence (weekly every workday)
  2776. sMeetingProps[5].Value.i = 1; // WeekInterval
  2777. sMeetingProps[8].Value.ul = 62; // Mo-Fri
  2778. sMeetingProps[11].Value.i = 48; // Weekly
  2779. }
  2780. break;
  2781. case RF_WEEKLY:
  2782. sMeetingProps[5].Value.i = rec.ulPeriod; // WeekInterval
  2783. sMeetingProps[8].Value.ul = rec.ulWeekDays; // DayOfWeekMask
  2784. sMeetingProps[11].Value.i = 48; // RecurrenceType
  2785. break;
  2786. case RF_MONTHLY:
  2787. sMeetingProps[6].Value.i = rec.ulPeriod; // MonthInterval
  2788. if (rec.ulPatternType == PT_MONTH_NTH) { // Every Nth [weekday] of the month
  2789. sMeetingProps[5].Value.ul = rec.ulWeekNumber; // WeekInterval
  2790. sMeetingProps[8].Value.ul = rec.ulWeekDays; // DayOfWeekMask
  2791. sMeetingProps[11].Value.i = 56; // RecurrenceType
  2792. } else {
  2793. sMeetingProps[9].Value.ul = 1 << (rec.ulDayOfMonth - 1); // day of month 1..31 mask
  2794. sMeetingProps[11].Value.i = 12; // RecurrenceType
  2795. }
  2796. break;
  2797. case RF_YEARLY:
  2798. sMeetingProps[6].Value.i = rec.ulPeriod; // YearInterval
  2799. sMeetingProps[7].Value.i = rec.ulPeriod / 12; // MonthInterval
  2800. /*
  2801. * The following calculation is needed because the month of the year is encoded as minutes since
  2802. * the beginning of a (non-leap-year) year until the beginning of the month. We can therefore
  2803. * divide the minutes by the minimum number of minutes in one month (24*60*29) and round down
  2804. * (which is automatic since it is an int), giving us month 0-11.
  2805. *
  2806. * Put a different way, lets just ASSUME each month has 29 days. Let X be the minute-offset, and M the
  2807. * month (0-11), then M = X/(24*60*29). In real life though, some months have more than 29 days, so X will
  2808. * actually be larger. Due to rounding, this keeps working until we have more then 29 days of error. In a
  2809. * year, you will have a maximum of 17 ((31-29)+(29-29)+(31-29)+(30-29)...etc) days of error which is under
  2810. * so this formula always gives a correct value if 0 < M < 12.
  2811. */
  2812. sMeetingProps[10].Value.ul = 1 << ((rec.ulFirstDateTime/(24*60*29)) % 12); // month of year (minutes since beginning of the year)
  2813. if (rec.ulPatternType == PT_MONTH_NTH) { // Every Nth [weekday] in Month X
  2814. sMeetingProps[5].Value.ul = rec.ulWeekNumber; // WeekInterval
  2815. sMeetingProps[8].Value.ul = rec.ulWeekDays; // DayOfWeekMask
  2816. sMeetingProps[11].Value.i = 51; // RecurrenceType
  2817. } else {
  2818. sMeetingProps[9].Value.ul = 1 << (rec.ulDayOfMonth - 1); // day of month 1..31 mask
  2819. sMeetingProps[11].Value.i = 7; // RecurrenceType
  2820. }
  2821. break;
  2822. default:
  2823. break;
  2824. }
  2825. hr = lpMessage->SetProps(14, sMeetingProps, NULL);
  2826. if(hr != hrSuccess)
  2827. goto exitpm;
  2828. }
  2829. }
  2830. exitpm:
  2831. return hr;
  2832. }
  2833. static std::string StringEscape(const char *input, const char *tokens,
  2834. const char escape)
  2835. {
  2836. std::string strEscaped;
  2837. int i = 0;
  2838. int t;
  2839. while (true) {
  2840. if (input[i] == 0)
  2841. break;
  2842. for (t = 0; tokens[t] != 0; ++t)
  2843. if (input[i] == tokens[t])
  2844. strEscaped += escape;
  2845. strEscaped += input[i];
  2846. ++i;
  2847. }
  2848. return strEscaped;
  2849. }
  2850. /**
  2851. * Convert a vmime mailbox to an IMAP envelope list part
  2852. *
  2853. * @param[in] mbox vmime mailbox (email address) to convert
  2854. *
  2855. * @return string with IMAP envelope list part
  2856. */
  2857. std::string VMIMEToMAPI::mailboxToEnvelope(vmime::shared_ptr<vmime::mailbox> mbox)
  2858. {
  2859. vector<string> lMBox;
  2860. string buffer;
  2861. string::size_type pos;
  2862. vmime::utility::outputStreamStringAdapter os(buffer);
  2863. if (!mbox || mbox->isEmpty())
  2864. throw vmime::exceptions::no_such_field();
  2865. // (( "personal name" NIL "mailbox name" "domain name" ))
  2866. mbox->getName().generate(os);
  2867. // encoded names never contain "
  2868. buffer = StringEscape(buffer.c_str(), "\"", '\\');
  2869. lMBox.push_back(buffer.empty() ? "NIL" : "\"" + buffer + "\"");
  2870. lMBox.push_back("NIL"); // at-domain-list (source route) ... whatever that means
  2871. buffer = "\"" + mbox->getEmail().toString() + "\"";
  2872. pos = buffer.find("@");
  2873. if (pos != string::npos)
  2874. buffer.replace(pos, 1, "\" \"");
  2875. lMBox.push_back(std::move(buffer));
  2876. if (pos == string::npos)
  2877. lMBox.push_back("NIL"); // domain was missing
  2878. return "(" + kc_join(lMBox, " ") + ")";
  2879. }
  2880. /**
  2881. * Convert a vmime addresslist (To/Cc/Bcc) to an IMAP envelope list part.
  2882. *
  2883. * @param[in] aList vmime addresslist to convert
  2884. *
  2885. * @return string with IMAP envelope list part
  2886. */
  2887. std::string VMIMEToMAPI::addressListToEnvelope(vmime::shared_ptr<vmime::addressList> aList)
  2888. {
  2889. list<string> lAddr;
  2890. string buffer;
  2891. int aCount = 0;
  2892. if (!aList)
  2893. throw vmime::exceptions::no_such_field();
  2894. aCount = aList->getAddressCount();
  2895. if (aCount == 0)
  2896. throw vmime::exceptions::no_such_field();
  2897. for (int i = 0; i < aCount; ++i) {
  2898. try {
  2899. buffer += mailboxToEnvelope(vmime::dynamicCast<vmime::mailbox>(aList->getAddressAt(i)));
  2900. lAddr.push_back(buffer);
  2901. } catch (vmime::exception &e) {
  2902. }
  2903. }
  2904. if (lAddr.empty())
  2905. return string("NIL");
  2906. return "(" + buffer + ")";
  2907. }
  2908. /**
  2909. * Create the IMAP ENVELOPE property, so we don't need to open the
  2910. * message to create this in the gateway.
  2911. *
  2912. * Format:
  2913. * ENVELOPE ("date" "subject" (from) (sender) (reply-to) ((to)*) ((cc)*) ((bcc)*) "in-reply-to" "message-id")
  2914. *
  2915. * If any of the fields aren't present in the received email, it should be substrituted by NIL.
  2916. *
  2917. * @param[in] vmMessage vmime message to create the envelope from
  2918. * @param[in] lpMessage message to store the data in
  2919. *
  2920. * @return MAPI Error code
  2921. */
  2922. HRESULT VMIMEToMAPI::createIMAPEnvelope(vmime::shared_ptr<vmime::message> vmMessage,
  2923. IMessage *lpMessage)
  2924. {
  2925. HRESULT hr = hrSuccess;
  2926. std::string buffer;
  2927. SPropValue sEnvelope;
  2928. PROPMAP_START(1)
  2929. PROPMAP_NAMED_ID(ENVELOPE, PT_STRING8, PS_EC_IMAP, dispidIMAPEnvelope);
  2930. PROPMAP_INIT(lpMessage);
  2931. buffer = createIMAPEnvelope(vmMessage);
  2932. sEnvelope.ulPropTag = PROP_ENVELOPE;
  2933. sEnvelope.Value.lpszA = (char*)buffer.c_str();
  2934. hr = lpMessage->SetProps(1, &sEnvelope, NULL);
  2935. exitpm:
  2936. return hr;
  2937. }
  2938. /**
  2939. * Create IMAP ENVELOPE() data from a vmime::message.
  2940. *
  2941. * @param[in] vmMessage message to create envelope for
  2942. *
  2943. * @return ENVELOPE data
  2944. */
  2945. std::string VMIMEToMAPI::createIMAPEnvelope(vmime::shared_ptr<vmime::message> vmMessage)
  2946. {
  2947. vector<string> lItems;
  2948. auto vmHeader = vmMessage->getHeader();
  2949. std::string buffer;
  2950. vmime::utility::outputStreamStringAdapter os(buffer);
  2951. // date
  2952. try {
  2953. vmime::shared_ptr<vmime::datetime> date;
  2954. try {
  2955. date = vmime::dynamicCast<vmime::datetime>(vmHeader->Date()->getValue());
  2956. } catch (vmime::exception &e) {
  2957. // date must not be empty, so force now() as the timestamp
  2958. date = vmime::make_shared<vmime::datetime>(vmime::datetime::now());
  2959. }
  2960. date->generate(os);
  2961. lItems.push_back("\"" + buffer + "\"");
  2962. } catch (vmime::exception &e) {
  2963. // this is not allowed, but better than nothing
  2964. lItems.push_back("NIL");
  2965. }
  2966. buffer.clear();
  2967. // subject
  2968. try {
  2969. vmHeader->Subject()->getValue()->generate(os);
  2970. // encoded subjects never contain ", so escape won't break those.
  2971. buffer = StringEscape(buffer.c_str(), "\"", '\\');
  2972. lItems.push_back(buffer.empty() ? "NIL" : "\"" + buffer + "\"");
  2973. } catch (vmime::exception &e) {
  2974. lItems.push_back("NIL");
  2975. }
  2976. buffer.clear();
  2977. // from
  2978. try {
  2979. buffer = mailboxToEnvelope(vmime::dynamicCast<vmime::mailbox>(vmHeader->From()->getValue()));
  2980. lItems.push_back("(" + buffer + ")");
  2981. } catch (vmime::exception &e) {
  2982. // this is not allowed, but better than nothing
  2983. lItems.push_back("NIL");
  2984. }
  2985. buffer.clear();
  2986. // sender
  2987. try {
  2988. buffer = mailboxToEnvelope(vmime::dynamicCast<vmime::mailbox>(vmHeader->Sender()->getValue()));
  2989. lItems.push_back("(" + buffer + ")");
  2990. } catch (vmime::exception &e) {
  2991. lItems.push_back(lItems.back());
  2992. }
  2993. buffer.clear();
  2994. // reply-to
  2995. try {
  2996. buffer = mailboxToEnvelope(vmime::dynamicCast<vmime::mailbox>(vmHeader->ReplyTo()->getValue()));
  2997. lItems.push_back("(" + buffer + ")");
  2998. } catch (vmime::exception &e) {
  2999. lItems.push_back(lItems.back());
  3000. }
  3001. buffer.clear();
  3002. // ((to),(to))
  3003. try {
  3004. buffer = addressListToEnvelope(vmime::dynamicCast<vmime::addressList>(vmHeader->To()->getValue()));
  3005. lItems.push_back(buffer);
  3006. } catch (vmime::exception &e) {
  3007. lItems.push_back("NIL");
  3008. }
  3009. buffer.clear();
  3010. // ((cc),(cc))
  3011. try {
  3012. auto aList = vmime::dynamicCast<vmime::addressList>(vmHeader->Cc()->getValue());
  3013. int aCount = aList->getAddressCount();
  3014. for (int i = 0; i < aCount; ++i)
  3015. buffer += mailboxToEnvelope(vmime::dynamicCast<vmime::mailbox>(aList->getAddressAt(i)));
  3016. lItems.push_back(buffer.empty() ? "NIL" : "(" + buffer + ")");
  3017. } catch (vmime::exception &e) {
  3018. lItems.push_back("NIL");
  3019. }
  3020. buffer.clear();
  3021. // ((bcc),(bcc))
  3022. try {
  3023. auto aList = vmime::dynamicCast<vmime::addressList>(vmHeader->Bcc()->getValue());
  3024. int aCount = aList->getAddressCount();
  3025. for (int i = 0; i < aCount; ++i)
  3026. buffer += mailboxToEnvelope(vmime::dynamicCast<vmime::mailbox>(aList->getAddressAt(i)));
  3027. lItems.push_back(buffer.empty() ? "NIL" : "(" + buffer + ")");
  3028. } catch (vmime::exception &e) {
  3029. lItems.push_back("NIL");
  3030. }
  3031. buffer.clear();
  3032. // in-reply-to
  3033. try {
  3034. vmHeader->InReplyTo()->getValue()->generate(os);
  3035. lItems.push_back(buffer.empty() ? "NIL" : "\"" + buffer + "\"");
  3036. } catch (vmime::exception &e) {
  3037. lItems.push_back("NIL");
  3038. }
  3039. buffer.clear();
  3040. // message-id
  3041. try {
  3042. vmHeader->MessageId()->getValue()->generate(os);
  3043. if (buffer.compare("<>") == 0)
  3044. buffer.clear();
  3045. lItems.push_back(buffer.empty() ? "NIL" : "\"" + buffer + "\"");
  3046. } catch (vmime::exception &e) {
  3047. lItems.push_back("NIL");
  3048. }
  3049. return kc_join(lItems, " ");
  3050. }
  3051. /**
  3052. * Store the complete received email in a hidden property and the size
  3053. * of that property too, for RFC822.SIZE requests.
  3054. *
  3055. * @param[in] input the received email
  3056. * @param[in] lpMessage message to store the data in
  3057. *
  3058. * @return MAPI error code
  3059. */
  3060. HRESULT VMIMEToMAPI::createIMAPBody(const string &input,
  3061. vmime::shared_ptr<vmime::message> vmMessage, IMessage *lpMessage)
  3062. {
  3063. SPropValue sProps[4];
  3064. string strBody;
  3065. string strBodyStructure;
  3066. messagePartToStructure(input, vmMessage, &strBody, &strBodyStructure);
  3067. sProps[0].ulPropTag = PR_EC_IMAP_EMAIL_SIZE;
  3068. sProps[0].Value.ul = input.length();
  3069. sProps[1].ulPropTag = PR_EC_IMAP_EMAIL;
  3070. sProps[1].Value.bin.lpb = (BYTE*)input.c_str();
  3071. sProps[1].Value.bin.cb = input.length();
  3072. sProps[2].ulPropTag = PR_EC_IMAP_BODY;
  3073. sProps[2].Value.lpszA = (char*)strBody.c_str();
  3074. sProps[3].ulPropTag = PR_EC_IMAP_BODYSTRUCTURE;
  3075. sProps[3].Value.lpszA = (char*)strBodyStructure.c_str();
  3076. return lpMessage->SetProps(4, sProps, NULL);
  3077. }
  3078. /**
  3079. * Convert a vmime message to a
  3080. *
  3081. * @param[in] input The original email
  3082. * @param[in] vmBodyPart Any message or body part to convert
  3083. * @param[out] lpSimple BODY result
  3084. * @param[out] lpExtended BODYSTRUCTURE result
  3085. *
  3086. * @return always success
  3087. */
  3088. HRESULT VMIMEToMAPI::messagePartToStructure(const string &input,
  3089. vmime::shared_ptr<vmime::bodyPart> vmBodyPart, std::string *lpSimple,
  3090. std::string *lpExtended)
  3091. {
  3092. HRESULT hr = hrSuccess;
  3093. list<string> lBody;
  3094. list<string> lBodyStructure;
  3095. auto vmHeaderPart = vmBodyPart->getHeader();
  3096. try {
  3097. vmime::shared_ptr<vmime::contentTypeField> ctf;
  3098. if (vmHeaderPart->hasField(vmime::fields::CONTENT_TYPE))
  3099. // use Content-Type header from part
  3100. ctf = vmime::dynamicCast<vmime::contentTypeField>(vmHeaderPart->ContentType());
  3101. else
  3102. // create empty default Content-Type header
  3103. ctf = vmime::dynamicCast<vmime::contentTypeField>(vmime::headerFieldFactory::getInstance()->create("Content-Type", ""));
  3104. auto mt = vmime::dynamicCast<vmime::mediaType>(ctf->getValue());
  3105. if (mt->getType() == vmime::mediaTypes::MULTIPART) {
  3106. // handle multipart
  3107. // alternative, mixed, related
  3108. if (vmBodyPart->getBody()->getPartCount() == 0)
  3109. return hr; // multipart without any real parts? let's completely skip this.
  3110. // function please:
  3111. string strBody;
  3112. string strBodyStructure;
  3113. for (size_t i = 0; i < vmBodyPart->getBody()->getPartCount(); ++i) {
  3114. messagePartToStructure(input, vmBodyPart->getBody()->getPartAt(i), &strBody, &strBodyStructure);
  3115. lBody.push_back(std::move(strBody));
  3116. lBodyStructure.push_back(std::move(strBodyStructure));
  3117. strBody.clear();
  3118. strBodyStructure.clear();
  3119. }
  3120. // concatenate without spaces, result: ((text)(html))
  3121. strBody = kc_join(lBody, "");
  3122. strBodyStructure = kc_join(lBodyStructure, "");
  3123. lBody.clear();
  3124. lBody.push_back(std::move(strBody));
  3125. lBodyStructure.clear();
  3126. lBodyStructure.push_back(std::move(strBodyStructure));
  3127. // body:
  3128. // (<SUB> "subtype")
  3129. // bodystructure:
  3130. // (<SUB> "subtype" ("boundary" "value") "disposition" "language")
  3131. lBody.push_back("\"" + mt->getSubType() + "\"");
  3132. lBodyStructure.push_back("\"" + mt->getSubType() + "\"");
  3133. lBodyStructure.push_back(parameterizedFieldToStructure(ctf));
  3134. lBodyStructure.push_back(getStructureExtendedFields(vmHeaderPart));
  3135. if (lpSimple)
  3136. *lpSimple = "(" + kc_join(lBody, " ") + ")";
  3137. if (lpExtended)
  3138. *lpExtended = "(" + kc_join(lBodyStructure, " ") + ")";
  3139. } else {
  3140. // just one part
  3141. bodyPartToStructure(input, vmBodyPart, lpSimple, lpExtended);
  3142. }
  3143. }
  3144. catch (vmime::exception &e) {
  3145. ec_log_warn("Unable to create optimized bodystructure: %s", e.what());
  3146. }
  3147. // add () around results?
  3148. return hr;
  3149. }
  3150. /**
  3151. * Convert a non-multipart body part to an IMAP BODY and BODYSTRUCTURE
  3152. * string.
  3153. *
  3154. * @param[in] input The original email
  3155. * @param[in] vmBodyPart the bodyPart to convert
  3156. * @param[out] lpSimple BODY result
  3157. * @param[out] lpExtended BODYSTRUCTURE result
  3158. *
  3159. * @return always success
  3160. */
  3161. HRESULT VMIMEToMAPI::bodyPartToStructure(const string &input,
  3162. vmime::shared_ptr<vmime::bodyPart> vmBodyPart, std::string *lpSimple,
  3163. std::string *lpExtended)
  3164. {
  3165. string strPart;
  3166. list<string> lBody;
  3167. list<string> lBodyStructure;
  3168. string buffer;
  3169. vmime::utility::outputStreamStringAdapter os(buffer);
  3170. vmime::shared_ptr<vmime::contentTypeField> ctf;
  3171. vmime::shared_ptr<vmime::mediaType> mt;
  3172. auto vmHeaderPart = vmBodyPart->getHeader();
  3173. if (!vmHeaderPart->hasField(vmime::fields::CONTENT_TYPE)) {
  3174. // create with text/plain; charset=us-ascii ?
  3175. lBody.push_back("NIL");
  3176. lBodyStructure.push_back("NIL");
  3177. goto nil;
  3178. }
  3179. ctf = vmime::dynamicCast<vmime::contentTypeField>(vmHeaderPart->findField(vmime::fields::CONTENT_TYPE));
  3180. mt = vmime::dynamicCast<vmime::mediaType>(ctf->getValue());
  3181. lBody.push_back("\"" + mt->getType() + "\"");
  3182. lBody.push_back("\"" + mt->getSubType() + "\"");
  3183. // if string == () force add charset.
  3184. lBody.push_back(parameterizedFieldToStructure(ctf));
  3185. if (vmHeaderPart->hasField(vmime::fields::CONTENT_ID)) {
  3186. buffer = vmime::dynamicCast<vmime::messageId>(vmHeaderPart->findField(vmime::fields::CONTENT_ID)->getValue())->getId();
  3187. lBody.push_back(buffer.empty() ? "NIL" : "\"<" + buffer + ">\"");
  3188. } else {
  3189. lBody.push_back("NIL");
  3190. }
  3191. if (vmHeaderPart->hasField(vmime::fields::CONTENT_DESCRIPTION)) {
  3192. buffer.clear();
  3193. vmHeaderPart->findField(vmime::fields::CONTENT_DESCRIPTION)->getValue()->generate(os);
  3194. lBody.push_back(buffer.empty() ? "NIL" : "\"" + buffer + "\"");
  3195. } else {
  3196. lBody.push_back("NIL");
  3197. }
  3198. if (vmHeaderPart->hasField(vmime::fields::CONTENT_TRANSFER_ENCODING)) {
  3199. buffer.clear();
  3200. vmHeaderPart->findField(vmime::fields::CONTENT_TRANSFER_ENCODING)->getValue()->generate(os);
  3201. lBody.push_back(buffer.empty() ? "NIL" : "\"" + buffer + "\"");
  3202. } else {
  3203. lBody.push_back("NIL");
  3204. }
  3205. if (mt->getType() == vmime::mediaTypes::TEXT) {
  3206. // body part size
  3207. buffer = stringify(vmBodyPart->getBody()->getParsedLength());
  3208. lBody.push_back(buffer);
  3209. // body part number of lines
  3210. buffer = stringify(countBodyLines(input, vmBodyPart->getBody()->getParsedOffset(), vmBodyPart->getBody()->getParsedLength()));
  3211. lBody.push_back(buffer);
  3212. } else {
  3213. // attachment: size only
  3214. buffer = stringify(vmBodyPart->getBody()->getParsedLength());
  3215. lBody.push_back(buffer);
  3216. }
  3217. // up until now, they were the same
  3218. lBodyStructure = lBody;
  3219. if (mt->getType() == vmime::mediaTypes::MESSAGE && mt->getSubType() == vmime::mediaTypes::MESSAGE_RFC822) {
  3220. string strSubSingle;
  3221. string strSubExtended;
  3222. auto subMessage = vmime::make_shared<vmime::message>();
  3223. // From RFC:
  3224. // A body type of type MESSAGE and subtype RFC822 contains,
  3225. // immediately after the basic fields, the envelope structure,
  3226. // body structure, and size in text lines of the encapsulated
  3227. // message.
  3228. // envelope eerst, dan message, dan lines
  3229. vmBodyPart->getBody()->getContents()->extractRaw(os); // generate? raw?
  3230. subMessage->parse(buffer);
  3231. lBody.push_back("("+createIMAPEnvelope(subMessage)+")");
  3232. lBodyStructure.push_back("("+createIMAPEnvelope(subMessage)+")");
  3233. // recurse message-in-message
  3234. messagePartToStructure(buffer, subMessage, &strSubSingle, &strSubExtended);
  3235. lBody.push_back(std::move(strSubSingle));
  3236. lBodyStructure.push_back(std::move(strSubExtended));
  3237. // dus hier nog de line count van vmBodyPart->getBody buffer?
  3238. lBody.push_back(stringify(countBodyLines(buffer, 0, buffer.length())));
  3239. }
  3240. nil:
  3241. if (lpSimple)
  3242. *lpSimple = "(" + kc_join(lBody, " ") + ")";
  3243. // just push some NIL's or also inbetween?
  3244. lBodyStructure.push_back("NIL"); // MD5 of body (use Content-MD5 header?)
  3245. lBodyStructure.push_back(getStructureExtendedFields(vmHeaderPart));
  3246. if (lpExtended)
  3247. *lpExtended = "(" + kc_join(lBodyStructure, " ") + ")";
  3248. return hrSuccess;
  3249. }
  3250. /**
  3251. * Return an IMAP list part containing the extended properties for a
  3252. * BODYSTRUCTURE.
  3253. * Adds disposition list, language and location.
  3254. *
  3255. * @param[in] vmHeaderPart The header to get the values from
  3256. *
  3257. * @return IMAP list part
  3258. */
  3259. std::string VMIMEToMAPI::getStructureExtendedFields(vmime::shared_ptr<vmime::header> vmHeaderPart)
  3260. {
  3261. list<string> lItems;
  3262. string buffer;
  3263. vmime::utility::outputStreamStringAdapter os(buffer);
  3264. // content-disposition header
  3265. if (vmHeaderPart->hasField(vmime::fields::CONTENT_DISPOSITION)) {
  3266. // use findField because we want an exception when missing
  3267. auto cdf = vmime::dynamicCast<vmime::contentDispositionField>(vmHeaderPart->findField(vmime::fields::CONTENT_DISPOSITION));
  3268. auto cd = vmime::dynamicCast<vmime::contentDisposition>(cdf->getValue());
  3269. lItems.push_back("(\"" + cd->getName() + "\" " + parameterizedFieldToStructure(cdf) + ")");
  3270. } else {
  3271. lItems.push_back("NIL");
  3272. }
  3273. // language
  3274. lItems.push_back("NIL");
  3275. // location
  3276. try {
  3277. buffer.clear();
  3278. vmHeaderPart->ContentLocation()->getValue()->generate(os);
  3279. lItems.push_back(buffer.empty() ? "NIL" : "\"" + buffer + "\"");
  3280. }
  3281. catch (vmime::exception &e) {
  3282. lItems.push_back("NIL");
  3283. }
  3284. return kc_join(lItems, " ");
  3285. }
  3286. /**
  3287. * Return an IMAP list containing the parameters of a specified header field as ("name" "value")
  3288. *
  3289. * @param[in] vmParamField The paramiterized header field to "convert"
  3290. *
  3291. * @return IMAP list
  3292. */
  3293. std::string VMIMEToMAPI::parameterizedFieldToStructure(vmime::shared_ptr<vmime::parameterizedHeaderField> vmParamField)
  3294. {
  3295. list<string> lParams;
  3296. string buffer;
  3297. vmime::utility::outputStreamStringAdapter os(buffer);
  3298. try {
  3299. for (const auto &param : vmParamField->getParameterList()) {
  3300. lParams.push_back("\"" + param->getName() + "\"");
  3301. param->getValue().generate(os);
  3302. lParams.push_back("\"" + buffer + "\"");
  3303. buffer.clear();
  3304. }
  3305. }
  3306. catch (vmime::exception &e) {
  3307. return "NIL";
  3308. }
  3309. if (lParams.empty())
  3310. return "NIL";
  3311. return "(" + kc_join(lParams, " ") + ")";
  3312. }
  3313. /**
  3314. * Return the number of lines in a string, with defined start and
  3315. * length.
  3316. *
  3317. * @param[in] input count number of \n chars in this string
  3318. * @param[in] start start from this point in input
  3319. * @param[in] length until the end, but no further than this length
  3320. *
  3321. * @return number of lines
  3322. */
  3323. std::string::size_type VMIMEToMAPI::countBodyLines(const std::string &input, std::string::size_type start, std::string::size_type length)
  3324. {
  3325. string::size_type lines = 0;
  3326. string::size_type pos = start;
  3327. while (true) {
  3328. pos = input.find_first_of('\n', pos);
  3329. if (pos == string::npos || pos > start+length)
  3330. break;
  3331. ++pos;
  3332. ++lines;
  3333. }
  3334. return lines;
  3335. }
  3336. // options.h code
  3337. /**
  3338. * Set all members in the delivery_options struct to their defaults
  3339. * (DAgent, not Gateway).
  3340. *
  3341. * @param[out] dopt struct filled with default values
  3342. */
  3343. void imopt_default_delivery_options(delivery_options *dopt) {
  3344. dopt->use_received_date = true;
  3345. dopt->mark_as_read = false;
  3346. dopt->add_imap_data = false;
  3347. dopt->charset_strict_rfc = true;
  3348. dopt->user_entryid = NULL;
  3349. dopt->parse_smime_signed = false;
  3350. dopt->ascii_upgrade = nullptr;
  3351. dopt->html_safety_filter = false;
  3352. }
  3353. /**
  3354. * Set all members in the sending_options struct to their defaults
  3355. * (Spooler, not Gateway).
  3356. *
  3357. * @param[out] sopt struct filled with default values
  3358. */
  3359. void imopt_default_sending_options(sending_options *sopt) {
  3360. sopt->alternate_boundary = NULL;
  3361. sopt->no_recipients_workaround = false;
  3362. sopt->msg_in_msg = false;
  3363. sopt->headers_only = false;
  3364. sopt->add_received_date = false;
  3365. sopt->use_tnef = 0;
  3366. sopt->force_utf8 = false;
  3367. sopt->charset_upgrade = const_cast<char *>("windows-1252");
  3368. sopt->allow_send_to_everyone = true;
  3369. sopt->enable_dsn = true;
  3370. sopt->always_expand_distr_list = false;
  3371. sopt->ignore_missing_attachments = false;
  3372. }
  3373. } /* namespace */