ECArchiveAwareMessage.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  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 <new>
  18. #include <kopano/platform.h>
  19. #include "ECArchiveAwareMsgStore.h"
  20. #include "ECArchiveAwareAttach.h"
  21. #include <kopano/ECGuid.h>
  22. #include <edkguid.h>
  23. #include <kopano/mapi_ptr.h>
  24. #include <kopano/memory.hpp>
  25. #include "IECPropStorage.h"
  26. #include "Mem.h"
  27. #include <kopano/mapiext.h>
  28. #include <kopano/mapiguidext.h>
  29. #include "ECArchiveAwareMessage.h"
  30. #include <kopano/ECGetText.h>
  31. #include <kopano/stringutil.h>
  32. #include <sstream>
  33. #include <kopano/ECDebug.h>
  34. #include <kopano/charset/convert.h>
  35. #define dispidStoreEntryIds "store-entryids"
  36. #define dispidItemEntryIds "item-entryids"
  37. #define dispidStubbed "stubbed"
  38. #define dispidDirty "dirty"
  39. #define dispidOrigSourceKey "original-sourcekey"
  40. class PropFinder {
  41. public:
  42. PropFinder(ULONG ulPropTag): m_ulPropTag(ulPropTag) {}
  43. bool operator()(const ECProperty &prop) const { return prop.GetPropTag() == m_ulPropTag; }
  44. private:
  45. ULONG m_ulPropTag;
  46. };
  47. HRESULT ECArchiveAwareMessageFactory::Create(ECMsgStore *lpMsgStore, BOOL fNew, BOOL fModify, ULONG ulFlags, BOOL bEmbedded, ECMAPIProp* lpRoot, ECMessage **lppMessage) const
  48. {
  49. auto lpArchiveAwareStore = dynamic_cast<ECArchiveAwareMsgStore *>(lpMsgStore);
  50. // New and embedded messages don't need to be archive aware. Also if the calling store
  51. // is not archive aware, the message won't.
  52. if (fNew || bEmbedded || lpArchiveAwareStore == NULL)
  53. return ECMessage::Create(lpMsgStore, fNew, fModify, ulFlags, bEmbedded, lpRoot, lppMessage);
  54. return ECArchiveAwareMessage::Create(lpArchiveAwareStore, FALSE, fModify, ulFlags, lppMessage);
  55. }
  56. ECArchiveAwareMessage::ECArchiveAwareMessage(ECArchiveAwareMsgStore *lpMsgStore, BOOL fNew, BOOL fModify, ULONG ulFlags)
  57. : ECMessage(lpMsgStore, fNew, fModify, ulFlags, FALSE, NULL)
  58. , m_bLoading(false)
  59. , m_bNamedPropsMapped(false), __propmap(5)
  60. , m_mode(MODE_UNARCHIVED)
  61. , m_bChanged(false)
  62. {
  63. // Override the handler defined in ECMessage
  64. this->HrAddPropHandlers(PR_MESSAGE_SIZE, ECMessage::GetPropHandler, SetPropHandler, (void*)this, FALSE, FALSE);
  65. }
  66. HRESULT ECArchiveAwareMessage::Create(ECArchiveAwareMsgStore *lpMsgStore, BOOL fNew, BOOL fModify, ULONG ulFlags, ECMessage **lppMessage)
  67. {
  68. return alloc_wrap<ECArchiveAwareMessage>(lpMsgStore, fNew, fModify,
  69. ulFlags).as(IID_ECMessage, lppMessage);
  70. }
  71. HRESULT ECArchiveAwareMessage::HrLoadProps()
  72. {
  73. HRESULT hr = hrSuccess;
  74. BOOL fModifyCopy;
  75. ECMsgStore *lpMsgStore;
  76. m_bLoading = true;
  77. hr = ECMessage::HrLoadProps();
  78. if (hr != hrSuccess)
  79. goto exit;
  80. // If we noticed we are stubbed, we need to perform a merge here.
  81. if (m_mode != MODE_STUBBED) {
  82. m_bLoading = false;
  83. return hr;
  84. }
  85. fModifyCopy = this->fModify;
  86. lpMsgStore = GetMsgStore();
  87. // @todo: Put in MergePropsFromStub
  88. static constexpr const SizedSPropTagArray(4, sptaDeleteProps) =
  89. {4, {PR_RTF_COMPRESSED, PR_BODY, PR_HTML, PR_ICON_INDEX}};
  90. static constexpr const SizedSPropTagArray(6, sptaRestoreProps) =
  91. {6, {PR_RTF_COMPRESSED, PR_BODY, PR_HTML, PR_ICON_INDEX,
  92. PR_MESSAGE_CLASS, PR_MESSAGE_SIZE}};
  93. if (!m_ptrArchiveMsg) {
  94. auto lpStore = dynamic_cast<ECArchiveAwareMsgStore *>(lpMsgStore);
  95. if (lpStore == NULL) {
  96. // This is quite a serious error since an ECArchiveAwareMessage can only be created by an
  97. // ECArchiveAwareMsgStore. We won't just die here though...
  98. hr = MAPI_E_NOT_FOUND;
  99. goto exit;
  100. }
  101. hr = lpStore->OpenItemFromArchive(m_ptrStoreEntryIDs, m_ptrItemEntryIDs, &~m_ptrArchiveMsg);
  102. if (hr != hrSuccess) {
  103. hr = CreateInfoMessage(sptaDeleteProps, CreateErrorBodyUtf8(hr));
  104. goto exit;
  105. }
  106. }
  107. // Now merge the properties and reconstruct the attachment table.
  108. // We'll copy the PR_RTF_COMPRESSED property from the archive to the stub as PR_RTF_COMPRESSED is
  109. // obtained anyway to determine the type of the body.
  110. // Also if the stub's PR_MESSAGE_CLASS equals IPM.Zarafa.Stub (old migrator behaviour), we'll overwrite
  111. // that with the archive's PR_MESSAGE_CLASS and overwrite the PR_ICON_INDEX.
  112. // We need to temporary enable write access on the underlying objects in order for the following
  113. // 5 calls to succeed.
  114. this->fModify = TRUE;
  115. hr = DeleteProps(sptaDeleteProps, NULL);
  116. if (hr != hrSuccess) {
  117. this->fModify = fModifyCopy;
  118. goto exit;
  119. }
  120. hr = Util::DoCopyProps(&IID_IMAPIProp, &m_ptrArchiveMsg->m_xMAPIProp,
  121. sptaRestoreProps, 0, NULL, &IID_IMAPIProp,
  122. &this->m_xMAPIProp, 0, NULL);
  123. if (hr != hrSuccess) {
  124. this->fModify = fModifyCopy;
  125. goto exit;
  126. }
  127. // Now remove any dummy attachment(s) and copy the attachments from the archive (except the properties
  128. // that are too big in the firt place).
  129. hr = Util::HrDeleteAttachments(&m_xMessage);
  130. if (hr != hrSuccess) {
  131. this->fModify = fModifyCopy;
  132. goto exit;
  133. }
  134. hr = Util::CopyAttachments(&m_ptrArchiveMsg->m_xMessage, &m_xMessage, NULL);
  135. this->fModify = fModifyCopy;
  136. exit:
  137. m_bLoading = false;
  138. return hr;
  139. }
  140. HRESULT ECArchiveAwareMessage::HrSetRealProp(const SPropValue *lpsPropValue)
  141. {
  142. HRESULT hr;
  143. SPropValue copy;
  144. if (lpsPropValue != nullptr)
  145. copy = *lpsPropValue;
  146. /*
  147. * m_bLoading: This is where we end up if we're called through HrLoadProps. So this
  148. * is where we check if the loaded message is unarchived, archived or stubbed.
  149. */
  150. if (m_bLoading && lpsPropValue != nullptr &&
  151. PROP_TYPE(lpsPropValue->ulPropTag) != PT_ERROR &&
  152. PROP_ID(lpsPropValue->ulPropTag) >= 0x8500) {
  153. // We have a named property that's in the not-hardcoded range (where
  154. // the archive named properties are). We now need to check if that's
  155. // one of the properties we're interested in.
  156. // That might mean we need to first map the named properties now.
  157. if (!m_bNamedPropsMapped) {
  158. hr = MapNamedProps();
  159. if (hr != hrSuccess)
  160. return hr;
  161. }
  162. // Check the various props.
  163. if (lpsPropValue->ulPropTag == PROP_ARCHIVE_STORE_ENTRYIDS) {
  164. if (m_mode == MODE_UNARCHIVED)
  165. m_mode = MODE_ARCHIVED;
  166. // Store list
  167. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~m_ptrStoreEntryIDs);
  168. if (hr == hrSuccess)
  169. hr = Util::HrCopyProperty(m_ptrStoreEntryIDs, lpsPropValue, m_ptrStoreEntryIDs);
  170. if (hr != hrSuccess)
  171. return hr;
  172. }
  173. else if (lpsPropValue->ulPropTag == PROP_ARCHIVE_ITEM_ENTRYIDS) {
  174. if (m_mode == MODE_UNARCHIVED)
  175. m_mode = MODE_ARCHIVED;
  176. // Store list
  177. hr = MAPIAllocateBuffer(sizeof(SPropValue), &~m_ptrItemEntryIDs);
  178. if (hr == hrSuccess)
  179. hr = Util::HrCopyProperty(m_ptrItemEntryIDs, lpsPropValue, m_ptrItemEntryIDs);
  180. if (hr != hrSuccess)
  181. return hr;
  182. }
  183. else if (lpsPropValue->ulPropTag == PROP_STUBBED) {
  184. if (lpsPropValue->Value.b == TRUE)
  185. m_mode = MODE_STUBBED;
  186. // The message is not stubbed once destubbed.
  187. // This fixes all kind of weird copy issues where the stubbed property does not
  188. // represent the actual state of the message.
  189. copy.Value.b = FALSE;
  190. }
  191. else if (lpsPropValue->ulPropTag == PROP_DIRTY) {
  192. if (lpsPropValue->Value.b != FALSE)
  193. m_mode = MODE_DIRTY;
  194. }
  195. }
  196. hr = ECMessage::HrSetRealProp(lpsPropValue != nullptr ? &copy : nullptr);
  197. if (hr == hrSuccess && !m_bLoading)
  198. /*
  199. * This is where we end up if a property is actually altered through SetProps.
  200. */
  201. m_bChanged = true;
  202. return hr;
  203. }
  204. HRESULT ECArchiveAwareMessage::HrDeleteRealProp(ULONG ulPropTag, BOOL fOverwriteRO)
  205. {
  206. HRESULT hr = hrSuccess;
  207. hr = ECMessage::HrDeleteRealProp(ulPropTag, fOverwriteRO);
  208. if (hr == hrSuccess && !m_bLoading)
  209. m_bChanged = true;
  210. return hr;
  211. }
  212. HRESULT ECArchiveAwareMessage::OpenProperty(ULONG ulPropTag, LPCIID lpiid, ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN *lppUnk)
  213. {
  214. HRESULT hr = hrSuccess;
  215. hr = ECMessage::OpenProperty(ulPropTag, lpiid, ulInterfaceOptions, ulFlags, lppUnk);
  216. if (!m_bLoading && hr == hrSuccess && ((ulFlags & MAPI_MODIFY) || (fModify && (ulFlags & MAPI_BEST_ACCESS))))
  217. // We have no way of knowing if the property will modified since it operates directly
  218. // on the MAPIOBJECT data, which bypasses this subclass.
  219. // @todo wrap the property to track if it was altered.
  220. m_bChanged = true;
  221. return hr;
  222. }
  223. HRESULT ECArchiveAwareMessage::OpenAttach(ULONG ulAttachmentNum, LPCIID lpInterface, ULONG ulFlags, LPATTACH *lppAttach)
  224. {
  225. HRESULT hr = hrSuccess;
  226. hr = ECMessage::OpenAttach(ulAttachmentNum, lpInterface, ulFlags, lppAttach);
  227. // According to MSDN an attachment must explicitly be opened with MAPI_MODIFY or MAPI_BEST_ACCESS
  228. // in order to get write access. However, practice has thought that that's not always the case. So
  229. // if the parent object was openend with write access, we'll assume the object is changed the moment
  230. // the attachment is openend.
  231. if (hr == hrSuccess && ((ulFlags & MAPI_MODIFY) || fModify))
  232. // We have no way of knowing if the attachment will modified since it operates directly
  233. // on the MAPIOBJECT data, which bypasses this subclass.
  234. // @todo wrap the attachment to track if it was altered.
  235. m_bChanged = true;
  236. return hr;
  237. }
  238. HRESULT ECArchiveAwareMessage::CreateAttach(LPCIID lpInterface, ULONG ulFlags, ULONG *lpulAttachmentNum, LPATTACH *lppAttach)
  239. {
  240. HRESULT hr = hrSuccess;
  241. /*
  242. * Here, we want to create an ECArchiveAwareAttach when we are still
  243. * loading. We need that because an ECArchiveAwareAttach allows its
  244. * size to be set during load time.
  245. */
  246. if (m_bLoading)
  247. hr = ECMessage::CreateAttach(lpInterface, ulFlags, ECArchiveAwareAttachFactory(), lpulAttachmentNum, lppAttach);
  248. else {
  249. hr = ECMessage::CreateAttach(lpInterface, ulFlags, ECAttachFactory(), lpulAttachmentNum, lppAttach);
  250. if (hr == hrSuccess)
  251. m_bChanged = true; // Definitely changed.
  252. }
  253. return hr;
  254. }
  255. HRESULT ECArchiveAwareMessage::DeleteAttach(ULONG ulAttachmentNum, ULONG ulUIParam, LPMAPIPROGRESS lpProgress, ULONG ulFlags)
  256. {
  257. HRESULT hr = hrSuccess;
  258. hr = ECMessage::DeleteAttach(ulAttachmentNum, ulUIParam, lpProgress, ulFlags);
  259. if (hr == hrSuccess && !m_bLoading)
  260. m_bChanged = true; // Definitely changed.
  261. return hr;
  262. }
  263. HRESULT ECArchiveAwareMessage::ModifyRecipients(ULONG ulFlags,
  264. const ADRLIST *lpMods)
  265. {
  266. HRESULT hr = hrSuccess;
  267. hr = ECMessage::ModifyRecipients(ulFlags, lpMods);
  268. if (hr == hrSuccess)
  269. m_bChanged = true;
  270. return hr;
  271. }
  272. HRESULT ECArchiveAwareMessage::SaveChanges(ULONG ulFlags)
  273. {
  274. HRESULT hr;
  275. SizedSPropTagArray(1, sptaStubbedProp) = {1, {PROP_STUBBED}};
  276. if (!fModify)
  277. return MAPI_E_NO_ACCESS;
  278. // We can't use this->lstProps here since that would suggest things have changed because we might have
  279. // destubbed ourselves, which is a change from the object model point of view.
  280. if (!m_bChanged)
  281. return hrSuccess;
  282. // From here on we're no longer stubbed.
  283. if (m_bNamedPropsMapped) {
  284. hr = DeleteProps(sptaStubbedProp, NULL);
  285. if (hr != hrSuccess)
  286. return hr;
  287. }
  288. if (m_mode == MODE_STUBBED || m_mode == MODE_ARCHIVED) {
  289. SPropValue propDirty;
  290. propDirty.ulPropTag = PROP_DIRTY;
  291. propDirty.Value.b = TRUE;
  292. hr = SetProps(1, &propDirty, NULL);
  293. if (hr != hrSuccess)
  294. return hr;
  295. m_mode = MODE_DIRTY; // We have an archived version that's now out of sync.
  296. }
  297. return ECMessage::SaveChanges(ulFlags);
  298. }
  299. HRESULT ECArchiveAwareMessage::SetPropHandler(ULONG ulPropTag,
  300. void */*lpProvider*/, const SPropValue *lpsPropValue, void *lpParam)
  301. {
  302. auto lpMessage = static_cast<ECArchiveAwareMessage *>(lpParam);
  303. HRESULT hr = hrSuccess;
  304. switch(ulPropTag) {
  305. case PR_MESSAGE_SIZE:
  306. if (lpMessage->m_bLoading)
  307. hr = lpMessage->ECMessage::HrSetRealProp(lpsPropValue); // Don't call our own overridden HrSetRealProp
  308. else
  309. hr = MAPI_E_COMPUTED;
  310. break;
  311. default:
  312. hr = MAPI_E_NOT_FOUND;
  313. break;
  314. }
  315. return hr;
  316. }
  317. HRESULT ECArchiveAwareMessage::MapNamedProps()
  318. {
  319. HRESULT hr = hrSuccess;
  320. PROPMAP_INIT_NAMED_ID(ARCHIVE_STORE_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidStoreEntryIds);
  321. PROPMAP_INIT_NAMED_ID(ARCHIVE_ITEM_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidItemEntryIds);
  322. PROPMAP_INIT_NAMED_ID(STUBBED, PT_BOOLEAN, PSETID_Archive, dispidStubbed);
  323. PROPMAP_INIT_NAMED_ID(DIRTY, PT_BOOLEAN, PSETID_Archive, dispidDirty);
  324. PROPMAP_INIT_NAMED_ID(ORIGINAL_SOURCE_KEY, PT_BINARY, PSETID_Archive, dispidOrigSourceKey);
  325. PROPMAP_INIT(&this->m_xMAPIProp);
  326. m_bNamedPropsMapped = true;
  327. exitpm:
  328. return hr;
  329. }
  330. HRESULT ECArchiveAwareMessage::CreateInfoMessage(const SPropTagArray *lpptaDeleteProps,
  331. const std::string &strBodyHtml)
  332. {
  333. HRESULT hr = hrSuccess;
  334. SPropValue sPropVal;
  335. StreamPtr ptrHtmlStream;
  336. ULARGE_INTEGER liZero = {{0, 0}};
  337. this->fModify = TRUE;
  338. hr = DeleteProps(lpptaDeleteProps, NULL);
  339. if (hr != hrSuccess)
  340. goto exit;
  341. sPropVal.ulPropTag = PR_INTERNET_CPID;
  342. sPropVal.Value.l = 65001;
  343. hr = HrSetOneProp(&this->m_xMAPIProp, &sPropVal);
  344. if (hr != hrSuccess)
  345. goto exit;
  346. hr = OpenProperty(PR_HTML, &ptrHtmlStream.iid(), 0, MAPI_CREATE | MAPI_MODIFY, &~ptrHtmlStream);
  347. if (hr != hrSuccess)
  348. goto exit;
  349. hr = ptrHtmlStream->SetSize(liZero);
  350. if (hr != hrSuccess)
  351. goto exit;
  352. hr = ptrHtmlStream->Write(strBodyHtml.c_str(), strBodyHtml.size(), NULL);
  353. if (hr != hrSuccess)
  354. goto exit;
  355. hr = ptrHtmlStream->Commit(0);
  356. exit:
  357. this->fModify = FALSE;
  358. return hr;
  359. }
  360. std::string ECArchiveAwareMessage::CreateErrorBodyUtf8(HRESULT hResult) {
  361. std::basic_ostringstream<TCHAR> ossHtmlBody;
  362. ossHtmlBody << _T("<HTML><HEAD><STYLE type=\"text/css\">")
  363. _T("BODY {font-family: \"sans-serif\";margin-left: 1em;}")
  364. _T("P {margin: .1em 0;}")
  365. _T("P.spacing {margin: .8em 0;}")
  366. _T("H1 {margin: .3em 0;}")
  367. _T("SPAN#errcode {display: inline;font-weight: bold;}")
  368. _T("SPAN#errmsg {display: inline;font-style: italic;}")
  369. _T("DIV.indented {margin-left: 4em;}")
  370. _T("</STYLE></HEAD><BODY><H1>")
  371. << _("Kopano Archiver")
  372. << _T("</H1><P>")
  373. << _("An error has occurred while fetching the message from the archive.")
  374. << _T(" ")
  375. << _("Please contact your system administrator.")
  376. << _T("</P><P class=\"spacing\"></P>")
  377. _T("<P>")
  378. << _("Error code:")
  379. << _T("<SPAN id=\"errcode\">")
  380. << tstringify(hResult, true)
  381. << _T("</SPAN> (<SPAN id=\"errmsg\">")
  382. << convert_to<tstring>(GetMAPIErrorDescription(hResult))
  383. << _T("</SPAN>)</P>");
  384. if (hResult == MAPI_E_NO_SUPPORT) {
  385. ossHtmlBody << _T("<P class=\"spacing\"></P><P>")
  386. << _("It seems no valid archiver license is installed.")
  387. << _T("</P>");
  388. } else if (hResult == MAPI_E_NOT_FOUND) {
  389. ossHtmlBody << _T("<P class=\"spacing\"></P><P>")
  390. << _("The archive could not be found.")
  391. << _T("</P>");
  392. } else if (hResult == MAPI_E_NO_ACCESS) {
  393. ossHtmlBody << _T("<P class=\"spacing\"></P><P>")
  394. << _("You don't have sufficient access to the archive.")
  395. << _T("</P>");
  396. } else {
  397. KCHL::memory_ptr<TCHAR> lpszDescription;
  398. HRESULT hr = Util::HrMAPIErrorToText(hResult, &~lpszDescription);
  399. if (hr == hrSuccess)
  400. ossHtmlBody << _T("<P>")
  401. << _("Error description:")
  402. << _T("<DIV class=\"indented\">")
  403. << lpszDescription
  404. << _T("</DIV></P>");
  405. }
  406. ossHtmlBody << _T("</BODY></HTML>");
  407. tstring strHtmlBody = ossHtmlBody.str();
  408. return convert_to<std::string>("UTF-8", strHtmlBody, rawsize(strHtmlBody), CHARSET_TCHAR);
  409. }
  410. std::string ECArchiveAwareMessage::CreateOfflineWarnBodyUtf8()
  411. {
  412. std::basic_ostringstream<TCHAR> ossHtmlBody;
  413. ossHtmlBody << _T("<HTML><HEAD><STYLE type=\"text/css\">")
  414. _T("BODY {font-family: \"sans-serif\";margin-left: 1em;}")
  415. _T("P {margin: .1em 0;}")
  416. _T("P.spacing {margin: .8em 0;}")
  417. _T("H1 {margin: .3em 0;}")
  418. _T("SPAN#errcode {display: inline;font-weight: bold;}")
  419. _T("SPAN#errmsg {display: inline;font-style: italic;}")
  420. _T("DIV.indented {margin-left: 4em;}")
  421. _T("</STYLE></HEAD><BODY><H1>")
  422. << _("Kopano Archiver")
  423. << _T("</H1><P>")
  424. << _("Archives can not be destubbed when working offline.")
  425. << _T("</P></BODY></HTML>");
  426. tstring strHtmlBody = ossHtmlBody.str();
  427. return convert_to<std::string>("UTF-8", strHtmlBody, rawsize(strHtmlBody), CHARSET_TCHAR);
  428. }