MAPIPropHelper.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  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 <memory>
  19. #include <new>
  20. #include <utility>
  21. #include "MAPIPropHelper.h"
  22. #include "ArchiverSession.h"
  23. #include <kopano/archiver-common.h>
  24. #include <mapiutil.h>
  25. #include <kopano/Util.h>
  26. #include <kopano/mapi_ptr.h>
  27. #include <kopano/mapiguidext.h>
  28. using namespace std;
  29. namespace KC { namespace helpers {
  30. /**
  31. * Create a MAPIPropHelper object.
  32. *
  33. * @param[in] ptrMapiProp
  34. * The MAPIPropPtr that points to the IMAPIProp object for which to create
  35. * a MAPIPropHelper.
  36. * @param[out] lppptrMAPIPropHelper
  37. * Pointer to a MAPIPropHelperPtr that will be assigned the returned
  38. * MAPIPropHelper object.
  39. */
  40. HRESULT MAPIPropHelper::Create(MAPIPropPtr ptrMapiProp, MAPIPropHelperPtr *lpptrMAPIPropHelper)
  41. {
  42. HRESULT hr;
  43. MAPIPropHelperPtr ptrMAPIPropHelper(new(std::nothrow) MAPIPropHelper(ptrMapiProp));
  44. if (ptrMAPIPropHelper == nullptr)
  45. return MAPI_E_NOT_ENOUGH_MEMORY;
  46. hr = ptrMAPIPropHelper->Init();
  47. if (hr != hrSuccess)
  48. return hr;
  49. *lpptrMAPIPropHelper = std::move(ptrMAPIPropHelper);
  50. return hrSuccess;
  51. }
  52. MAPIPropHelper::MAPIPropHelper(MAPIPropPtr ptrMapiProp) :
  53. m_ptrMapiProp(ptrMapiProp), __propmap(8)
  54. { }
  55. /**
  56. * Initialize a MAPIPropHelper object.
  57. */
  58. HRESULT MAPIPropHelper::Init()
  59. {
  60. HRESULT hr = hrSuccess;
  61. PROPMAP_INIT_NAMED_ID(ARCHIVE_STORE_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidStoreEntryIds)
  62. PROPMAP_INIT_NAMED_ID(ARCHIVE_ITEM_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidItemEntryIds)
  63. PROPMAP_INIT_NAMED_ID(ORIGINAL_SOURCEKEY, PT_BINARY, PSETID_Archive, dispidOrigSourceKey)
  64. PROPMAP_INIT_NAMED_ID(STUBBED, PT_BOOLEAN, PSETID_Archive, dispidStubbed)
  65. PROPMAP_INIT_NAMED_ID(DIRTY, PT_BOOLEAN, PSETID_Archive, dispidDirty)
  66. PROPMAP_INIT_NAMED_ID(REF_STORE_ENTRYID, PT_BINARY, PSETID_Archive, dispidRefStoreEntryId)
  67. PROPMAP_INIT_NAMED_ID(REF_ITEM_ENTRYID, PT_BINARY, PSETID_Archive, dispidRefItemEntryId)
  68. PROPMAP_INIT_NAMED_ID(REF_PREV_ENTRYID, PT_BINARY, PSETID_Archive, dispidRefPrevEntryId)
  69. PROPMAP_INIT(m_ptrMapiProp)
  70. exitpm:
  71. return hr;
  72. }
  73. /**
  74. * Determine the state of the message. With this state one can determine if a
  75. * message is stubbed or dirty and copied or moved.
  76. *
  77. * @param[in] ptrSession
  78. * The session needed to open the archive message(s) to determine
  79. * if a message was copied or moved.
  80. * @param[out] lpState
  81. * The state that will be setup according to the message state.
  82. */
  83. HRESULT MAPIPropHelper::GetMessageState(ArchiverSessionPtr ptrSession, MessageState *lpState)
  84. {
  85. HRESULT hr;
  86. ULONG cMessageProps = 0;
  87. SPropArrayPtr ptrMessageProps;
  88. ULONG ulState = 0;
  89. int result = 0;
  90. SizedSPropTagArray(6, sptaMessageProps) = {6, {PR_ENTRYID, PROP_STUBBED, PROP_DIRTY, PR_SOURCE_KEY, PROP_ORIGINAL_SOURCEKEY, PR_EC_HIERARCHYID}};
  91. enum {IDX_ENTRYID, IDX_STUBBED, IDX_DIRTY, IDX_SOURCE_KEY, IDX_ORIGINAL_SOURCEKEY, IDX_HIERARCHYID};
  92. if (lpState == NULL)
  93. return MAPI_E_INVALID_PARAMETER;
  94. hr = m_ptrMapiProp->GetProps(sptaMessageProps, 0, &cMessageProps, &~ptrMessageProps);
  95. if (FAILED(hr))
  96. return hr;
  97. if (PROP_TYPE(ptrMessageProps[IDX_ENTRYID].ulPropTag) == PT_ERROR)
  98. return ptrMessageProps[IDX_ENTRYID].Value.err;
  99. if (PROP_TYPE(ptrMessageProps[IDX_SOURCE_KEY].ulPropTag) == PT_ERROR)
  100. return ptrMessageProps[IDX_SOURCE_KEY].Value.err;
  101. if (PROP_TYPE(ptrMessageProps[IDX_STUBBED].ulPropTag) == PT_ERROR &&
  102. ptrMessageProps[IDX_STUBBED].Value.err != MAPI_E_NOT_FOUND)
  103. return ptrMessageProps[IDX_STUBBED].Value.err;
  104. if (PROP_TYPE(ptrMessageProps[IDX_DIRTY].ulPropTag) == PT_ERROR &&
  105. ptrMessageProps[IDX_DIRTY].Value.err != MAPI_E_NOT_FOUND)
  106. return ptrMessageProps[IDX_DIRTY].Value.err;
  107. if (PROP_TYPE(ptrMessageProps[IDX_ORIGINAL_SOURCEKEY].ulPropTag) == PT_ERROR &&
  108. ptrMessageProps[IDX_ORIGINAL_SOURCEKEY].Value.err != MAPI_E_NOT_FOUND)
  109. return ptrMessageProps[IDX_ORIGINAL_SOURCEKEY].Value.err;
  110. if (PROP_TYPE(ptrMessageProps[IDX_HIERARCHYID].ulPropTag) == PT_ERROR)
  111. return ptrMessageProps[IDX_HIERARCHYID].Value.err;
  112. hr = hrSuccess;
  113. // Determine stubbed / dirty state.
  114. if (PROP_TYPE(ptrMessageProps[IDX_STUBBED].ulPropTag) != PT_ERROR && ptrMessageProps[IDX_STUBBED].Value.b == TRUE)
  115. ulState |= MessageState::msStubbed;
  116. if (PROP_TYPE(ptrMessageProps[IDX_DIRTY].ulPropTag) != PT_ERROR &&
  117. ptrMessageProps[IDX_DIRTY].Value.b == TRUE &&
  118. !(ulState & MessageState::msStubbed))
  119. // If, for some reason, both dirty and stubbed are set, it is safest to mark the message
  120. // as stubbed. That might cause the archive to miss out some changes, but if we marked
  121. // it as dirty, we might be rearchiving a stub, loosing all interesting information.
  122. ulState |= MessageState::msDirty;
  123. // Determine copy / move state.
  124. if (PROP_TYPE(ptrMessageProps[IDX_ORIGINAL_SOURCEKEY].ulPropTag) == PT_ERROR) {
  125. assert(ptrMessageProps[IDX_ORIGINAL_SOURCEKEY].Value.err == MAPI_E_NOT_FOUND);
  126. // No way to determine of message was copied/moved, so assume it's not.
  127. return hr;
  128. }
  129. hr = Util::CompareProp(&ptrMessageProps[IDX_SOURCE_KEY], &ptrMessageProps[IDX_ORIGINAL_SOURCEKEY], createLocaleFromName(""), &result);
  130. if (hr != hrSuccess)
  131. return hr;
  132. if (result != 0) {
  133. // The message is copied. Now check if it was moved.
  134. ObjectEntryList lstArchives;
  135. ULONG ulType;
  136. MessagePtr ptrArchiveMsg;
  137. MAPIPropHelperPtr ptrArchiveHelper;
  138. SObjectEntry refEntry;
  139. MsgStorePtr ptrStore;
  140. MessagePtr ptrMessage;
  141. hr = GetArchiveList(&lstArchives, true);
  142. if (hr != hrSuccess)
  143. return hr;
  144. for (const auto &arc : lstArchives) {
  145. HRESULT hrTmp;
  146. MsgStorePtr ptrArchiveStore;
  147. hrTmp = ptrSession->OpenReadOnlyStore(arc.sStoreEntryId, &~ptrArchiveStore);
  148. if (hrTmp != hrSuccess)
  149. continue;
  150. hrTmp = ptrArchiveStore->OpenEntry(arc.sItemEntryId.size(), arc.sItemEntryId, &ptrArchiveMsg.iid(), 0, &ulType, &~ptrArchiveMsg);
  151. if (hrTmp != hrSuccess)
  152. continue;
  153. break;
  154. }
  155. if (!ptrArchiveMsg) {
  156. if (ulState & MessageState::msStubbed)
  157. return MAPI_E_NOT_FOUND;
  158. else
  159. /*
  160. * We were unable to open any archived message, but the message is
  161. * not stubbed anyway. Just mark it as a copy.
  162. */
  163. ulState |= MessageState::msCopy;
  164. } else {
  165. hr = MAPIPropHelper::Create(ptrArchiveMsg.as<MAPIPropPtr>(), &ptrArchiveHelper);
  166. if (hr != hrSuccess)
  167. return hr;
  168. hr = ptrArchiveHelper->GetReference(&refEntry);
  169. if (hr != hrSuccess)
  170. return hr;
  171. hr = ptrSession->OpenReadOnlyStore(refEntry.sStoreEntryId, &~ptrStore);
  172. if (hr != hrSuccess)
  173. return hr;
  174. hr = ptrStore->OpenEntry(refEntry.sItemEntryId.size(), refEntry.sItemEntryId, &ptrArchiveMsg.iid(), 0, &ulType, &~ptrMessage);
  175. if (hr == hrSuccess) {
  176. /*
  177. * One would expect that if the message was opened properly here, the message that's being
  178. * processed was copied because we were able to open the original reference, which should
  179. * have been removed either way.
  180. * However, because of a currently (13-07-2011) unknown issue, the moved message can be
  181. * opened with its old entryid. This is probably a cache issue.
  182. * If this happens, the message just opened is the same message as the one that's being
  183. * processed. That can be easily verified by comparing the record key.
  184. */
  185. SPropValuePtr ptrRecordKey;
  186. hr = HrGetOneProp(ptrMessage, PR_EC_HIERARCHYID, &~ptrRecordKey);
  187. if (hr != hrSuccess)
  188. return hr;
  189. if (ptrMessageProps[IDX_HIERARCHYID].Value.ul == ptrRecordKey->Value.ul) {
  190. // We opened the same message through the reference, which shouldn't be possible. This
  191. // must have been a move operation.
  192. ulState |= MessageState::msMove;
  193. } else
  194. ulState |= MessageState::msCopy;
  195. } else if (hr == MAPI_E_NOT_FOUND) {
  196. hr = hrSuccess;
  197. ulState |= MessageState::msMove;
  198. } else
  199. return hr;
  200. }
  201. }
  202. lpState->m_ulState = ulState;
  203. return hr;
  204. }
  205. /**
  206. * Get the list of archives for the object.
  207. * This has a different meaning for different objects:
  208. * Message store: A list of folders that are the root folders of the attached archives.
  209. * Folders: A list of folders that are the corresponding folders in the attached archives.
  210. * Messages: A list of messages that are archived versions of the current message.
  211. *
  212. * @param[out] lplstArchives
  213. * Pointer to a list that will be populated with the archive references.
  214. *
  215. * @param[in] bIgnoreSourceKey
  216. * Don't try to detect a copy/move and return an empty list in that case.
  217. */
  218. HRESULT MAPIPropHelper::GetArchiveList(ObjectEntryList *lplstArchives, bool bIgnoreSourceKey)
  219. {
  220. HRESULT hr;
  221. ULONG cbValues = 0;
  222. SPropArrayPtr ptrPropArray;
  223. ObjectEntryList lstArchives;
  224. int result = 0;
  225. SizedSPropTagArray (4, sptaArchiveProps) = {4, {PROP_ARCHIVE_STORE_ENTRYIDS, PROP_ARCHIVE_ITEM_ENTRYIDS, PROP_ORIGINAL_SOURCEKEY, PR_SOURCE_KEY}};
  226. enum {
  227. IDX_ARCHIVE_STORE_ENTRYIDS,
  228. IDX_ARCHIVE_ITEM_ENTRYIDS,
  229. IDX_ORIGINAL_SOURCEKEY,
  230. IDX_SOURCE_KEY
  231. };
  232. hr = m_ptrMapiProp->GetProps(sptaArchiveProps, 0, &cbValues, &~ptrPropArray);
  233. if (FAILED(hr))
  234. return hr;
  235. if (hr == MAPI_W_ERRORS_RETURNED) {
  236. /**
  237. * We expect all three PROP_* properties to be present or all three to be absent, with
  238. * one exception: If PR_SOURCE_KEY is missing PROP_ORIGINAL_SOURCEKEY is not needed.
  239. **/
  240. if (PROP_TYPE(ptrPropArray[IDX_ARCHIVE_STORE_ENTRYIDS].ulPropTag) == PT_ERROR &&
  241. PROP_TYPE(ptrPropArray[IDX_ARCHIVE_ITEM_ENTRYIDS].ulPropTag) == PT_ERROR)
  242. {
  243. // No entry ids exist. So that's fine
  244. return hrSuccess;
  245. }
  246. else if (PROP_TYPE(ptrPropArray[IDX_ARCHIVE_STORE_ENTRYIDS].ulPropTag) != PT_ERROR &&
  247. PROP_TYPE(ptrPropArray[IDX_ARCHIVE_ITEM_ENTRYIDS].ulPropTag) != PT_ERROR)
  248. {
  249. // Both exist. So if PR_SOURCEKEY_EXISTS and PROP_ORIGINAL_SOURCEKEY doesn't
  250. // the entry is corrupt
  251. if (PROP_TYPE(ptrPropArray[IDX_SOURCE_KEY].ulPropTag) != PT_ERROR) {
  252. if (PROP_TYPE(ptrPropArray[IDX_ORIGINAL_SOURCEKEY].ulPropTag) == PT_ERROR) {
  253. return MAPI_E_CORRUPT_DATA;
  254. } else if (!bIgnoreSourceKey) {
  255. // @todo: Create correct locale.
  256. hr = Util::CompareProp(&ptrPropArray[IDX_SOURCE_KEY], &ptrPropArray[IDX_ORIGINAL_SOURCEKEY], createLocaleFromName(""), &result);
  257. if (hr != hrSuccess)
  258. return hr;
  259. if (result != 0)
  260. // The archive list was apparently copied into this message. So it's not valid (not an error).
  261. return hr;
  262. }
  263. } else
  264. hr = hrSuccess;
  265. }
  266. else
  267. {
  268. // One exists, one doesn't.
  269. return MAPI_E_CORRUPT_DATA;
  270. }
  271. }
  272. if (ptrPropArray[IDX_ARCHIVE_STORE_ENTRYIDS].Value.MVbin.cValues !=
  273. ptrPropArray[IDX_ARCHIVE_ITEM_ENTRYIDS].Value.MVbin.cValues)
  274. return MAPI_E_CORRUPT_DATA;
  275. for (ULONG i = 0; i < ptrPropArray[0].Value.MVbin.cValues; ++i) {
  276. SObjectEntry objectEntry;
  277. objectEntry.sStoreEntryId.assign(ptrPropArray[IDX_ARCHIVE_STORE_ENTRYIDS].Value.MVbin.lpbin[i]);
  278. objectEntry.sItemEntryId.assign(ptrPropArray[IDX_ARCHIVE_ITEM_ENTRYIDS].Value.MVbin.lpbin[i]);
  279. lstArchives.push_back(std::move(objectEntry));
  280. }
  281. swap(*lplstArchives, lstArchives);
  282. return hr;
  283. }
  284. /**
  285. * Set or replace the list of archives for the current object.
  286. *
  287. * @param[in] lstArchives
  288. * The list of archive references that should be stored in the object.
  289. * @param[in] bExplicitCommit
  290. * If set to true, the changes are committed before this function returns.
  291. */
  292. HRESULT MAPIPropHelper::SetArchiveList(const ObjectEntryList &lstArchives, bool bExplicitCommit)
  293. {
  294. HRESULT hr;
  295. ULONG cValues = lstArchives.size();
  296. SPropArrayPtr ptrPropArray;
  297. SPropValuePtr ptrSourceKey;
  298. ObjectEntryList::const_iterator iArchive;
  299. ULONG cbProps = 2;
  300. hr = MAPIAllocateBuffer(3 * sizeof(SPropValue), &~ptrPropArray);
  301. if (hr != hrSuccess)
  302. return hr;
  303. ptrPropArray[0].ulPropTag = PROP_ARCHIVE_STORE_ENTRYIDS;
  304. ptrPropArray[0].Value.MVbin.cValues = cValues;
  305. hr = MAPIAllocateMore(cValues * sizeof(SBinary), ptrPropArray, (LPVOID*)&ptrPropArray[0].Value.MVbin.lpbin);
  306. if (hr != hrSuccess)
  307. return hr;
  308. ptrPropArray[1].ulPropTag = PROP_ARCHIVE_ITEM_ENTRYIDS;
  309. ptrPropArray[1].Value.MVbin.cValues = cValues;
  310. hr = MAPIAllocateMore(cValues * sizeof(SBinary), ptrPropArray, (LPVOID*)&ptrPropArray[1].Value.MVbin.lpbin);
  311. if (hr != hrSuccess)
  312. return hr;
  313. iArchive = lstArchives.cbegin();
  314. for (ULONG i = 0; i < cValues; ++i, ++iArchive) {
  315. ptrPropArray[0].Value.MVbin.lpbin[i].cb = iArchive->sStoreEntryId.size();
  316. hr = MAPIAllocateMore(iArchive->sStoreEntryId.size(), ptrPropArray, (LPVOID*)&ptrPropArray[0].Value.MVbin.lpbin[i].lpb);
  317. if (hr != hrSuccess)
  318. return hr;
  319. memcpy(ptrPropArray[0].Value.MVbin.lpbin[i].lpb, iArchive->sStoreEntryId, iArchive->sStoreEntryId.size());
  320. ptrPropArray[1].Value.MVbin.lpbin[i].cb = iArchive->sItemEntryId.size();
  321. hr = MAPIAllocateMore(iArchive->sItemEntryId.size(), ptrPropArray, (LPVOID*)&ptrPropArray[1].Value.MVbin.lpbin[i].lpb);
  322. if (hr != hrSuccess)
  323. return hr;
  324. memcpy(ptrPropArray[1].Value.MVbin.lpbin[i].lpb, iArchive->sItemEntryId, iArchive->sItemEntryId.size());
  325. }
  326. /**
  327. * We store the sourcekey of the item for which the list of archives is valid. This way if the
  328. * item gets moved everything is fine. But when it gets copied a new archive will be created
  329. * for it.
  330. **/
  331. hr = HrGetOneProp(m_ptrMapiProp, PR_SOURCE_KEY, &~ptrSourceKey);
  332. if (hr == hrSuccess) {
  333. ptrPropArray[2].ulPropTag = PROP_ORIGINAL_SOURCEKEY;
  334. ptrPropArray[2].Value.bin = ptrSourceKey->Value.bin; // Cheap copy
  335. cbProps = 3;
  336. }
  337. hr = m_ptrMapiProp->SetProps(cbProps, ptrPropArray.get(), NULL);
  338. if (hr != hrSuccess)
  339. return hr;
  340. if (bExplicitCommit)
  341. hr = m_ptrMapiProp->SaveChanges(KEEP_OPEN_READWRITE);
  342. return hr;
  343. }
  344. /**
  345. * Set a reference to a primary object in an archived object. A reference is set on archive
  346. * folders and archive messages. They reference to the original folder or message for which the
  347. * archived version exists.
  348. *
  349. * @param[in] sEntryId
  350. * The id of the referenced object.
  351. * @param[in] bExplicitCommit
  352. * If set to true, the changes are committed before this function returns.
  353. */
  354. HRESULT MAPIPropHelper::SetReference(const SObjectEntry &sEntry, bool bExplicitCommit)
  355. {
  356. HRESULT hr;
  357. SPropValue sPropArray[2] = {{0}};
  358. sPropArray[0].ulPropTag = PROP_REF_STORE_ENTRYID;
  359. sPropArray[0].Value.bin.cb = sEntry.sStoreEntryId.size();
  360. sPropArray[0].Value.bin.lpb = sEntry.sStoreEntryId;
  361. sPropArray[1].ulPropTag = PROP_REF_ITEM_ENTRYID;
  362. sPropArray[1].Value.bin.cb = sEntry.sItemEntryId.size();
  363. sPropArray[1].Value.bin.lpb = sEntry.sItemEntryId;
  364. hr = m_ptrMapiProp->SetProps(2, sPropArray, NULL);
  365. if (hr != hrSuccess)
  366. return hr;
  367. if (bExplicitCommit)
  368. hr = m_ptrMapiProp->SaveChanges(KEEP_OPEN_READWRITE);
  369. return hr;
  370. }
  371. HRESULT MAPIPropHelper::ClearReference(bool bExplicitCommit)
  372. {
  373. HRESULT hr;
  374. SizedSPropTagArray(2, sptaReferenceProps) = {2, {PROP_REF_STORE_ENTRYID, PROP_REF_ITEM_ENTRYID}};
  375. hr = m_ptrMapiProp->DeleteProps(sptaReferenceProps, NULL);
  376. if (hr != hrSuccess)
  377. return hr;
  378. if (bExplicitCommit)
  379. hr = m_ptrMapiProp->SaveChanges(KEEP_OPEN_READWRITE);
  380. return hr;
  381. }
  382. HRESULT MAPIPropHelper::GetReference(SObjectEntry *lpEntry)
  383. {
  384. HRESULT hr;
  385. ULONG cMessageProps = 0;
  386. SPropArrayPtr ptrMessageProps;
  387. SizedSPropTagArray(2, sptaMessageProps) = {2, {PROP_REF_STORE_ENTRYID, PROP_REF_ITEM_ENTRYID}};
  388. enum {IDX_REF_STORE_ENTRYID, IDX_REF_ITEM_ENTRYID};
  389. if (lpEntry == NULL)
  390. return MAPI_E_INVALID_PARAMETER;
  391. hr = m_ptrMapiProp->GetProps(sptaMessageProps, 0, &cMessageProps, &~ptrMessageProps);
  392. if (FAILED(hr))
  393. return hr;
  394. if (PROP_TYPE(ptrMessageProps[IDX_REF_STORE_ENTRYID].ulPropTag) == PT_ERROR)
  395. return ptrMessageProps[IDX_REF_STORE_ENTRYID].Value.err;
  396. if (PROP_TYPE(ptrMessageProps[IDX_REF_ITEM_ENTRYID].ulPropTag) == PT_ERROR)
  397. return ptrMessageProps[IDX_REF_ITEM_ENTRYID].Value.err;
  398. lpEntry->sStoreEntryId.assign(ptrMessageProps[IDX_REF_STORE_ENTRYID].Value.bin);
  399. lpEntry->sItemEntryId.assign(ptrMessageProps[IDX_REF_ITEM_ENTRYID].Value.bin);
  400. return hr;
  401. }
  402. HRESULT MAPIPropHelper::ReferencePrevious(const SObjectEntry &sEntry)
  403. {
  404. SPropValue sPropValue = {0};
  405. sPropValue.ulPropTag = PROP_REF_PREV_ENTRYID;
  406. sPropValue.Value.bin.cb = sEntry.sItemEntryId.size();
  407. sPropValue.Value.bin.lpb = sEntry.sItemEntryId;
  408. return HrSetOneProp(m_ptrMapiProp, &sPropValue);
  409. }
  410. HRESULT MAPIPropHelper::OpenPrevious(ArchiverSessionPtr ptrSession, LPMESSAGE *lppMessage)
  411. {
  412. HRESULT hr;
  413. SPropValuePtr ptrEntryID;
  414. ULONG ulType;
  415. MessagePtr ptrMessage;
  416. if (lppMessage == NULL)
  417. return MAPI_E_INVALID_PARAMETER;
  418. hr = HrGetOneProp(m_ptrMapiProp, PROP_REF_PREV_ENTRYID, &~ptrEntryID);
  419. if (hr != hrSuccess)
  420. return hr;
  421. hr = ptrSession->GetMAPISession()->OpenEntry(ptrEntryID->Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrEntryID->Value.bin.lpb), &ptrMessage.iid(), MAPI_MODIFY, &ulType, &~ptrMessage);
  422. if (hr == MAPI_E_NOT_FOUND) {
  423. SPropValuePtr ptrStoreEntryID;
  424. MsgStorePtr ptrStore;
  425. hr = HrGetOneProp(m_ptrMapiProp, PR_STORE_ENTRYID, &~ptrStoreEntryID);
  426. if (hr != hrSuccess)
  427. return hr;
  428. hr = ptrSession->OpenStore(ptrStoreEntryID->Value.bin, &~ptrStore);
  429. if (hr != hrSuccess)
  430. return hr;
  431. hr = ptrStore->OpenEntry(ptrEntryID->Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrEntryID->Value.bin.lpb), &ptrMessage.iid(), MAPI_MODIFY, &ulType, &~ptrMessage);
  432. }
  433. if (hr != hrSuccess)
  434. return hr;
  435. return ptrMessage->QueryInterface(IID_IMessage,
  436. reinterpret_cast<LPVOID *>(lppMessage));
  437. }
  438. /**
  439. * Remove the {72e98ebc-57d2-4ab5-b0aad50a7b531cb9}/stubbed property. Note that IsStubbed can still
  440. * return true if the message class is not updated properly. However, this is done in the caller
  441. * of this function, which has no notion of the set of named properies that are needed to remove this
  442. * property.
  443. */
  444. HRESULT MAPIPropHelper::RemoveStub()
  445. {
  446. SizedSPropTagArray(1, sptaArchiveProps) = {1, {PROP_STUBBED}};
  447. return m_ptrMapiProp->DeleteProps(sptaArchiveProps, NULL);
  448. }
  449. HRESULT MAPIPropHelper::SetClean()
  450. {
  451. SizedSPropTagArray(1, sptaDirtyProps) = {1, {PROP_DIRTY}};
  452. return m_ptrMapiProp->DeleteProps(sptaDirtyProps, NULL);
  453. }
  454. /**
  455. * Detach an object from its archived version.
  456. * This does not cause the reference in the archived version to be removed.
  457. */
  458. HRESULT MAPIPropHelper::DetachFromArchives()
  459. {
  460. SizedSPropTagArray(5, sptaArchiveProps) = {5, {PROP_ARCHIVE_STORE_ENTRYIDS, PROP_ARCHIVE_ITEM_ENTRYIDS, PROP_STUBBED, PROP_DIRTY, PROP_ORIGINAL_SOURCEKEY}};
  461. return m_ptrMapiProp->DeleteProps(sptaArchiveProps, NULL);
  462. }
  463. /**
  464. * Get the parent folder of an object.
  465. *
  466. * @param[in] lpSession
  467. * Pointer to a session object that's used to open the folder with.
  468. * @param[in] lppFolder
  469. * Pointer to a IMAPIFolder pointer that will be assigned the address
  470. * of the returned folder.
  471. */
  472. HRESULT MAPIPropHelper::GetParentFolder(ArchiverSessionPtr ptrSession, LPMAPIFOLDER *lppFolder)
  473. {
  474. HRESULT hr;
  475. SPropArrayPtr ptrPropArray;
  476. MsgStorePtr ptrMsgStore;
  477. MAPIFolderPtr ptrFolder;
  478. ULONG cValues = 0;
  479. ULONG ulType = 0;
  480. static constexpr const SizedSPropTagArray(2, sptaProps) =
  481. {2, {PR_PARENT_ENTRYID, PR_STORE_ENTRYID}};
  482. if (ptrSession == NULL)
  483. return MAPI_E_INVALID_PARAMETER;
  484. // We can't just open a folder on the session (at least not in Linux). So we open the store first
  485. hr = m_ptrMapiProp->GetProps(sptaProps, 0, &cValues, &~ptrPropArray);
  486. if (hr != hrSuccess)
  487. return hr;
  488. hr = ptrSession->OpenStore(ptrPropArray[1].Value.bin, &~ptrMsgStore);
  489. if (hr != hrSuccess)
  490. return hr;
  491. hr = ptrMsgStore->OpenEntry(ptrPropArray[0].Value.bin.cb, reinterpret_cast<ENTRYID *>(ptrPropArray[0].Value.bin.lpb), &ptrFolder.iid(), MAPI_BEST_ACCESS | fMapiDeferredErrors, &ulType, &~ptrFolder);
  492. if (hr != hrSuccess)
  493. return hr;
  494. return ptrFolder->QueryInterface(IID_IMAPIFolder,
  495. reinterpret_cast<LPVOID *>(lppFolder));
  496. }
  497. /**
  498. * Get the list of archives for the object.
  499. * This has a different meaning for different objects:
  500. * Message store: A list of folders that are the root folders of the attached archives.
  501. * Folders: A list of folders that are the corresponding folders in the attached archives.
  502. * Messages: A list of messages that are archived versions of the current message.
  503. *
  504. * @param[in] ptrMapiProp An IMAPIProp that lives on the same server as the the
  505. * message from which the properties were obtained. This
  506. * is needed to properly resolve the named properties.
  507. * @param[in] lpProps The list of properties to use to get the list of archives
  508. * for the message from which they were obtained.
  509. * @param[in] cbProps The amount of properties in lpProps.
  510. * @param[out] lplstArchives Pointer to a list that will be populated with the archive references.
  511. */
  512. HRESULT MAPIPropHelper::GetArchiveList(MAPIPropPtr ptrMapiProp, LPSPropValue lpProps, ULONG cbProps, ObjectEntryList *lplstArchives)
  513. {
  514. HRESULT hr = hrSuccess;
  515. ObjectEntryList lstArchives;
  516. int result = 0;
  517. const SPropValue *lpPropStoreEIDs = NULL, *lpPropItemEIDs = NULL;
  518. const SPropValue *lpPropOrigSK = NULL, *lpPropSourceKey = NULL;
  519. PROPMAP_START(3)
  520. PROPMAP_NAMED_ID(ARCHIVE_STORE_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidStoreEntryIds)
  521. PROPMAP_NAMED_ID(ARCHIVE_ITEM_ENTRYIDS, PT_MV_BINARY, PSETID_Archive, dispidItemEntryIds)
  522. PROPMAP_NAMED_ID(ORIGINAL_SOURCEKEY, PT_BINARY, PSETID_Archive, dispidOrigSourceKey)
  523. PROPMAP_INIT(ptrMapiProp)
  524. lpPropStoreEIDs = PCpropFindProp(lpProps, cbProps, PROP_ARCHIVE_STORE_ENTRYIDS);
  525. lpPropItemEIDs = PCpropFindProp(lpProps, cbProps, PROP_ARCHIVE_ITEM_ENTRYIDS);
  526. lpPropOrigSK = PCpropFindProp(lpProps, cbProps, PROP_ORIGINAL_SOURCEKEY);
  527. lpPropSourceKey = PCpropFindProp(lpProps, cbProps, PR_SOURCE_KEY);
  528. if (!lpPropStoreEIDs || !lpPropItemEIDs || !lpPropOrigSK || !lpPropSourceKey) {
  529. /**
  530. * We expect all three PROP_* properties to be present or all three to be absent, with
  531. * one exception: If PR_SOURCE_KEY is missing PROP_ORIGINAL_SOURCEKEY is not needed.
  532. **/
  533. if (!lpPropStoreEIDs && !lpPropItemEIDs)
  534. {
  535. // No entry ids exist. So that's fine
  536. hr = hrSuccess;
  537. goto exitpm;
  538. } else if (lpPropStoreEIDs == nullptr || lpPropItemEIDs == nullptr) {
  539. // One exists, one doesn't.
  540. hr = MAPI_E_CORRUPT_DATA;
  541. goto exitpm;
  542. }
  543. // Both exist. So if PR_SOURCEKEY_EXISTS and PROP_ORIGINAL_SOURCEKEY doesn't
  544. // the entry is corrupt
  545. if (lpPropSourceKey) {
  546. if (!lpPropOrigSK) {
  547. hr = MAPI_E_CORRUPT_DATA;
  548. goto exitpm;
  549. }
  550. // @todo: Create correct locale.
  551. hr = Util::CompareProp(lpPropSourceKey, lpPropOrigSK, createLocaleFromName(""), &result);
  552. if (hr != hrSuccess)
  553. goto exitpm;
  554. if (result != 0)
  555. // The archive list was apparently copied into this message. So it's not valid (not an error).
  556. goto exitpm;
  557. } else
  558. hr = hrSuccess;
  559. }
  560. if (lpPropStoreEIDs->Value.MVbin.cValues != lpPropItemEIDs->Value.MVbin.cValues) {
  561. hr = MAPI_E_CORRUPT_DATA;
  562. goto exitpm;
  563. }
  564. for (ULONG i = 0; i < lpPropStoreEIDs->Value.MVbin.cValues; ++i) {
  565. SObjectEntry objectEntry;
  566. objectEntry.sStoreEntryId.assign(lpPropStoreEIDs->Value.MVbin.lpbin[i]);
  567. objectEntry.sItemEntryId.assign(lpPropItemEIDs->Value.MVbin.lpbin[i]);
  568. lstArchives.push_back(std::move(objectEntry));
  569. }
  570. swap(*lplstArchives, lstArchives);
  571. exitpm:
  572. return hr;
  573. }
  574. }} /* namespace */