ECNamedProp.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  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 <mapitags.h>
  19. #include <mapidefs.h>
  20. #include <mapicode.h>
  21. #include <list>
  22. #include "Mem.h"
  23. #include "ECNamedProp.h"
  24. #include "WSTransport.h"
  25. /*
  26. * How our named properties work
  27. *
  28. * Basically, named properties in objects with the same PR_MAPPING_SIGNATURE should have the same
  29. * mapping of named properties to property IDs and vice-versa. We can use this information, together
  30. * with the information that Outlook mainly uses a fixed set of named properties to speed up things
  31. * considerably;
  32. *
  33. * Normally, each GetIDsFromNames calls would have to consult the server for an ID, and then cache
  34. * and return the value to the client. This is a rather time-consuming thing to do as Outlook requests
  35. * quite a few different named properties at startup. We can make this much faster by hard-wiring
  36. * a bunch of known named properties into the CLIENT-side DLL. This makes sure that most (say 90%) of
  37. * GetIDsFromNames calls can be handled locally without any reference to the server, while any other
  38. * (new) named properties can be handled in the standard way. This reduces client-server communications
  39. * dramatically, resulting in both a faster client as less datacommunication between client and server.
  40. *
  41. * In fact, most of the time, the named property mechanism will work even if the server is down...
  42. */
  43. /*
  44. * Currently, serverside named properties are cached locally in a map<> object,
  45. * however, in the future, a bimap<> may be used to speed up reverse lookups (ie
  46. * getNamesFromIDs) but this is not used frequently so we can leave it like
  47. * this for now
  48. *
  49. * For the most part, this implementation is rather fast, (possible faster than
  50. * Exchange) due to the fact that we know which named properties are likely to be
  51. * requested. This means that we have
  52. */
  53. /* Our local names
  54. *
  55. * It is VERY IMPORTANT that these values are NOT MODIFIED, otherwise the mapping of
  56. * named properties will change, which will BREAK THINGS BADLY
  57. *
  58. * Special constraints for this structure:
  59. * - The ulMappedIds must not overlap the previous row
  60. * - The ulMappedIds must be in ascending order
  61. */
  62. static const struct _sLocalNames {
  63. GUID guid;
  64. LONG ulMin;
  65. LONG ulMax;
  66. ULONG ulMappedId; // mapped ID of the FIRST property in the range
  67. } sLocalNames[] = {{{ 0x62002, 0x0, 0x0, { 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46 } }, 0x8200, 0x826F, 0x8000 },
  68. {{ 0x62003, 0x0, 0x0, { 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46 } }, 0x8100, 0x813F, 0x8070 },
  69. {{ 0x62004, 0x0, 0x0, { 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46 } }, 0x8000, 0x80EF, 0x80B0 },
  70. {{ 0x62008, 0x0, 0x0, { 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46 } }, 0x8500, 0x85FF, 0x81A0 },
  71. {{ 0x6200A, 0x0, 0x0, { 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46 } }, 0x8700, 0x871F, 0x82A0 },
  72. {{ 0x6200B, 0x0, 0x0, { 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46 } }, 0x8800, 0x881F, 0x82C0 },
  73. {{ 0x6200E, 0x0, 0x0, { 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46 } }, 0x8B00, 0x8B1F, 0x82E0 },
  74. {{ 0x62013, 0x0, 0x0, { 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46 } }, 0x8D00, 0x8D1F, 0x8300 },
  75. {{ 0x62014, 0x0, 0x0, { 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46 } }, 0x8F00, 0x8F1F, 0x8320 },
  76. {{ 0x6ED8DA90, 0x450B, 0x101B, { 0x98, 0xDA, 0x00, 0xAA, 0x00, 0x3F, 0x13, 0x05} } , 0x0000, 0x003F, 0x8340}};
  77. #define SERVER_NAMED_OFFSET 0x8500
  78. ECNamedProp::ECNamedProp(WSTransport *lpTransport)
  79. {
  80. this->lpTransport = lpTransport;
  81. lpTransport->AddRef();
  82. }
  83. ECNamedProp::~ECNamedProp()
  84. {
  85. // Clear all the cached names
  86. for (const auto &p : mapNames)
  87. if (p.first)
  88. ECFreeBuffer(p.first);
  89. if(lpTransport)
  90. lpTransport->Release();
  91. }
  92. HRESULT ECNamedProp::GetNamesFromIDs(LPSPropTagArray *lppPropTags, LPGUID lpPropSetGuid, ULONG ulFlags, ULONG *lpcPropNames, LPMAPINAMEID **lpppPropNames)
  93. {
  94. HRESULT hr = hrSuccess;
  95. unsigned int i = 0;
  96. LPSPropTagArray lpsPropTags = NULL;
  97. ecmem_ptr<MAPINAMEID *> lppPropNames, lppResolved;
  98. ecmem_ptr<SPropTagArray> lpsUnresolved;
  99. ULONG cResolved = 0;
  100. ULONG cUnresolved = 0;
  101. // Exchange doesn't support this, so neither do we
  102. if(lppPropTags == NULL || *lppPropTags == NULL) {
  103. hr = MAPI_E_TOO_BIG;
  104. goto exit;
  105. }
  106. lpsPropTags = *lppPropTags;
  107. // Allocate space for properties
  108. hr = ECAllocateBuffer(sizeof(LPMAPINAMEID) * lpsPropTags->cValues, &~lppPropNames);
  109. if (hr != hrSuccess)
  110. goto exit;
  111. // Pass 1, local reverse mapping (FAST)
  112. for (i = 0; i < lpsPropTags->cValues; ++i)
  113. if (ResolveReverseLocal(PROP_ID(lpsPropTags->aulPropTag[i]),
  114. lpPropSetGuid, ulFlags, lppPropNames,
  115. &lppPropNames[i]) != hrSuccess)
  116. lppPropNames[i] = NULL;
  117. // Pass 2, cache reverse mapping (FAST)
  118. for (i = 0; i < lpsPropTags->cValues; ++i) {
  119. if (lppPropNames[i] != NULL)
  120. continue;
  121. if (PROP_ID(lpsPropTags->aulPropTag[i]) > SERVER_NAMED_OFFSET)
  122. ResolveReverseCache(PROP_ID(lpsPropTags->aulPropTag[i]), lpPropSetGuid, ulFlags, lppPropNames, &lppPropNames[i]);
  123. // else { Hmmm, so here is a named property, which is < SERVER_NAMED_OFFSET, but CANNOT be
  124. // resolved internally. Looks like somebody's pulling our leg ... We just leave it unknown }
  125. }
  126. hr = ECAllocateBuffer(CbNewSPropTagArray(lpsPropTags->cValues), &~lpsUnresolved);
  127. if (hr != hrSuccess)
  128. goto exit;
  129. cUnresolved = 0;
  130. // Pass 3, server reverse lookup (SLOW)
  131. for (i = 0; i < lpsPropTags->cValues; ++i)
  132. if (lppPropNames[i] == NULL)
  133. if(PROP_ID(lpsPropTags->aulPropTag[i]) > SERVER_NAMED_OFFSET) {
  134. lpsUnresolved->aulPropTag[cUnresolved] = PROP_ID(lpsPropTags->aulPropTag[i]) - SERVER_NAMED_OFFSET;
  135. ++cUnresolved;
  136. }
  137. lpsUnresolved->cValues = cUnresolved;
  138. if(cUnresolved > 0) {
  139. hr = lpTransport->HrGetNamesFromIDs(lpsUnresolved, &~lppResolved, &cResolved);
  140. if(hr != hrSuccess)
  141. goto exit;
  142. // Put the resolved values from the server into the cache
  143. if(cResolved != cUnresolved) {
  144. hr = MAPI_E_CALL_FAILED;
  145. goto exit;
  146. }
  147. for (i = 0; i < cResolved; ++i)
  148. if(lppResolved[i] != NULL)
  149. UpdateCache(lpsUnresolved->aulPropTag[i] + SERVER_NAMED_OFFSET, lppResolved[i]);
  150. // re-scan the cache
  151. for (i = 0; i < lpsPropTags->cValues; ++i)
  152. if (lppPropNames[i] == NULL)
  153. if (PROP_ID(lpsPropTags->aulPropTag[i]) > SERVER_NAMED_OFFSET)
  154. ResolveReverseCache(PROP_ID(lpsPropTags->aulPropTag[i]), lpPropSetGuid, ulFlags, lppPropNames, &lppPropNames[i]);
  155. }
  156. // Check for errors
  157. for (i = 0; i < lpsPropTags->cValues; ++i)
  158. if(lppPropNames[i] == NULL)
  159. hr = MAPI_W_ERRORS_RETURNED;
  160. *lpppPropNames = lppPropNames.release();
  161. *lpcPropNames = lpsPropTags->cValues;
  162. exit:
  163. return hr;
  164. }
  165. HRESULT ECNamedProp::GetIDsFromNames(ULONG cPropNames, LPMAPINAMEID *lppPropNames, ULONG ulFlags, LPSPropTagArray *lppPropTags)
  166. {
  167. HRESULT hr = hrSuccess;
  168. unsigned int i=0;
  169. LPSPropTagArray lpsPropTagArray = NULL;
  170. std::unique_ptr<MAPINAMEID *[]> lppPropNamesUnresolved;
  171. ULONG cUnresolved = 0;
  172. ULONG* lpServerIDs = NULL;
  173. // Exchange doesn't support this, so neither do we
  174. if(cPropNames == 0 || lppPropNames == NULL) {
  175. hr = MAPI_E_TOO_BIG;
  176. goto exit;
  177. }
  178. // Sanity check input
  179. for (i = 0; i < cPropNames; ++i) {
  180. if(lppPropNames[i] == NULL) {
  181. hr = MAPI_E_INVALID_PARAMETER;
  182. goto exit;
  183. }
  184. }
  185. // Allocate memory for the return structure
  186. hr = ECAllocateBuffer(CbNewSPropTagArray(cPropNames), (void **)&lpsPropTagArray);
  187. if(hr != hrSuccess)
  188. goto exit;
  189. lpsPropTagArray->cValues = cPropNames;
  190. // Pass 1, resolve static (local) names (FAST)
  191. for (i = 0; i < cPropNames; ++i)
  192. if(lppPropNames[i] == NULL || ResolveLocal(lppPropNames[i], &lpsPropTagArray->aulPropTag[i]) != hrSuccess)
  193. lpsPropTagArray->aulPropTag[i] = PROP_TAG(PT_ERROR, 0);
  194. // Pass 2, resolve names from local cache (FAST)
  195. for (i = 0; i < cPropNames; ++i)
  196. if (lppPropNames[i] != NULL && lpsPropTagArray->aulPropTag[i] == PROP_TAG(PT_ERROR, 0))
  197. ResolveCache(lppPropNames[i], &lpsPropTagArray->aulPropTag[i]);
  198. // Pass 3, resolve names from server (SLOW, but decreases in frequency with store lifetime)
  199. lppPropNamesUnresolved.reset(new MAPINAMEID *[lpsPropTagArray->cValues]); // over-allocated
  200. // Get a list of unresolved names
  201. for (i = 0; i < cPropNames; ++i)
  202. if(lpsPropTagArray->aulPropTag[i] == PROP_TAG(PT_ERROR, 0) && lppPropNames[i] != NULL ) {
  203. lppPropNamesUnresolved[cUnresolved] = lppPropNames[i];
  204. ++cUnresolved;
  205. }
  206. if(cUnresolved) {
  207. // Let the server resolve these names
  208. hr = lpTransport->HrGetIDsFromNames(lppPropNamesUnresolved.get(), cUnresolved, ulFlags, &lpServerIDs);
  209. if(hr != hrSuccess)
  210. goto exit;
  211. // Put the names into the local cache for all the IDs the server gave us
  212. for (i = 0; i < cUnresolved; ++i)
  213. if(lpServerIDs[i] != 0)
  214. UpdateCache(lpServerIDs[i] + SERVER_NAMED_OFFSET, lppPropNamesUnresolved[i]);
  215. // Pass 4, re-resolve from local cache (FAST)
  216. for (i = 0; i < cPropNames; ++i)
  217. if (lppPropNames[i] != NULL &&
  218. lpsPropTagArray->aulPropTag[i] == PROP_TAG(PT_ERROR, 0))
  219. ResolveCache(lppPropNames[i], &lpsPropTagArray->aulPropTag[i]);
  220. }
  221. // Finally, check for any errors left in the returned structure
  222. hr = hrSuccess;
  223. for (i = 0; i < cPropNames; ++i)
  224. if(lpsPropTagArray->aulPropTag[i] == PROP_TAG(PT_ERROR, 0)) {
  225. hr = MAPI_W_ERRORS_RETURNED;
  226. break;
  227. }
  228. *lppPropTags = lpsPropTagArray;
  229. lpsPropTagArray = NULL;
  230. exit:
  231. if(lpsPropTagArray)
  232. ECFreeBuffer(lpsPropTagArray);
  233. if(lpServerIDs)
  234. ECFreeBuffer(lpServerIDs);
  235. return hr;
  236. }
  237. HRESULT ECNamedProp::ResolveLocal(MAPINAMEID *lpName, ULONG *ulPropTag)
  238. {
  239. // We can only locally resolve MNID_ID types of named properties
  240. if (lpName->ulKind != MNID_ID)
  241. return MAPI_E_NOT_FOUND;
  242. // Loop through our local names to see if the named property is in there
  243. for (size_t i = 0; i < ARRAY_SIZE(sLocalNames); ++i) {
  244. if(memcmp(&sLocalNames[i].guid,lpName->lpguid,sizeof(GUID))==0 && sLocalNames[i].ulMin <= lpName->Kind.lID && sLocalNames[i].ulMax >= lpName->Kind.lID) {
  245. // Found it, calculate the ID and return it.
  246. *ulPropTag = PROP_TAG(PT_UNSPECIFIED, sLocalNames[i].ulMappedId + lpName->Kind.lID - sLocalNames[i].ulMin);
  247. return hrSuccess;
  248. }
  249. }
  250. // Couldn't find it ...
  251. return MAPI_E_NOT_FOUND;
  252. }
  253. HRESULT ECNamedProp::ResolveReverseCache(ULONG ulId, LPGUID lpGuid, ULONG ulFlags, void *lpBase, MAPINAMEID **lppName)
  254. {
  255. HRESULT hr = MAPI_E_NOT_FOUND;
  256. // Loop through the map to find the reverse-lookup of the named property. This could be speeded up by
  257. // used a bimap (bi-directional map)
  258. for (const auto &p : mapNames)
  259. if (p.second == ulId) { // FIXME match GUID
  260. if (lpGuid != nullptr)
  261. assert(memcmp(lpGuid, p.first->lpguid, sizeof(GUID)) == 0); // TEST michel
  262. // found it
  263. hr = HrCopyNameId(p.first, lppName, lpBase);
  264. break;
  265. }
  266. return hr;
  267. }
  268. HRESULT ECNamedProp::ResolveReverseLocal(ULONG ulId, LPGUID lpGuid, ULONG ulFlags, void *lpBase, MAPINAMEID **lppName)
  269. {
  270. MAPINAMEID* lpName = NULL;
  271. // Local mapping is only for MNID_ID
  272. if (ulFlags & MAPI_NO_IDS)
  273. return MAPI_E_NOT_FOUND;
  274. // Loop through the local names to see if we can reverse-map the id
  275. for (size_t i = 0; i < ARRAY_SIZE(sLocalNames); ++i) {
  276. if((lpGuid == NULL || memcmp(&sLocalNames[i].guid, lpGuid, sizeof(GUID)) == 0) && ulId >= sLocalNames[i].ulMappedId && ulId < sLocalNames[i].ulMappedId + (sLocalNames[i].ulMax - sLocalNames[i].ulMin + 1)) {
  277. // Found it !
  278. auto hr = ECAllocateMore(sizeof(MAPINAMEID), lpBase, reinterpret_cast<void **>(&lpName));
  279. if (hr != hrSuccess)
  280. return hr;
  281. hr = ECAllocateMore(sizeof(GUID), lpBase, reinterpret_cast<void **>(&lpName->lpguid));
  282. if (hr != hrSuccess)
  283. return hr;
  284. lpName->ulKind = MNID_ID;
  285. memcpy(lpName->lpguid, &sLocalNames[i].guid, sizeof(GUID));
  286. lpName->Kind.lID = sLocalNames[i].ulMin + (ulId - sLocalNames[i].ulMappedId);
  287. break;
  288. }
  289. }
  290. if (lpName == NULL)
  291. return MAPI_E_NOT_FOUND;
  292. *lppName = lpName;
  293. return hrSuccess;
  294. }
  295. // Update the cache with the given data
  296. HRESULT ECNamedProp::UpdateCache(ULONG ulId, MAPINAMEID *lpName)
  297. {
  298. HRESULT hr = hrSuccess;
  299. MAPINAMEID* lpNameCopy = NULL;
  300. if(mapNames.find(lpName) != mapNames.end()) {
  301. // Already in the cache!
  302. hr = MAPI_E_NOT_FOUND;
  303. goto exit;
  304. }
  305. hr = HrCopyNameId(lpName, &lpNameCopy, NULL);
  306. if(hr != hrSuccess)
  307. goto exit;
  308. mapNames[lpNameCopy] = ulId;
  309. exit:
  310. if(hr != hrSuccess && lpNameCopy)
  311. ECFreeBuffer(lpNameCopy);
  312. return hr;
  313. }
  314. HRESULT ECNamedProp::ResolveCache(MAPINAMEID *lpName, ULONG *lpulPropTag)
  315. {
  316. std::map<MAPINAMEID *, ULONG, ltmap>::const_iterator iterMap;
  317. iterMap = mapNames.find(lpName);
  318. if (iterMap == mapNames.cend())
  319. return MAPI_E_NOT_FOUND;
  320. *lpulPropTag = PROP_TAG(PT_UNSPECIFIED, iterMap->second);
  321. return hrSuccess;
  322. }
  323. /* This copies a MAPINAMEID struct using ECAllocate* functions. Therefore, the
  324. * memory allocated here is *not* traced by the debug functions. Make sure you
  325. * release all memory allocated from this function! (or make sure the client application
  326. * does)
  327. */
  328. HRESULT ECNamedProp::HrCopyNameId(LPMAPINAMEID lpSrc, LPMAPINAMEID *lppDst, void *lpBase)
  329. {
  330. HRESULT hr = hrSuccess;
  331. LPMAPINAMEID lpDst = NULL;
  332. if(lpBase == NULL)
  333. hr = ECAllocateBuffer(sizeof(MAPINAMEID), (void **) &lpDst);
  334. else
  335. hr = ECAllocateMore(sizeof(MAPINAMEID), lpBase, (void **) &lpDst);
  336. if(hr != hrSuccess)
  337. goto exit;
  338. lpDst->ulKind = lpSrc->ulKind;
  339. if(lpSrc->lpguid) {
  340. if(lpBase)
  341. hr = ECAllocateMore(sizeof(GUID), lpBase, (void **) &lpDst->lpguid);
  342. else
  343. hr = ECAllocateMore(sizeof(GUID), lpDst, (void **) &lpDst->lpguid);
  344. if(hr != hrSuccess)
  345. goto exit;
  346. memcpy(lpDst->lpguid, lpSrc->lpguid, sizeof(GUID));
  347. } else {
  348. lpDst->lpguid = NULL;
  349. }
  350. switch(lpSrc->ulKind) {
  351. case MNID_ID:
  352. lpDst->Kind.lID = lpSrc->Kind.lID;
  353. break;
  354. case MNID_STRING:
  355. if(lpBase)
  356. hr = ECAllocateMore(wcslen(lpSrc->Kind.lpwstrName) * sizeof(wchar_t) + sizeof(wchar_t),
  357. lpBase, reinterpret_cast<void **>(&lpDst->Kind.lpwstrName));
  358. else
  359. hr = ECAllocateMore(wcslen(lpSrc->Kind.lpwstrName) * sizeof(wchar_t) + sizeof(wchar_t),
  360. lpDst, reinterpret_cast<void **>(&lpDst->Kind.lpwstrName));
  361. if (hr != hrSuccess)
  362. return hr;
  363. wcscpy(lpDst->Kind.lpwstrName, lpSrc->Kind.lpwstrName);
  364. break;
  365. default:
  366. hr = MAPI_E_INVALID_TYPE;
  367. goto exit;
  368. }
  369. *lppDst = lpDst;
  370. exit:
  371. if(hr != hrSuccess && !lpBase && lpDst)
  372. ECFreeBuffer(lpDst);
  373. return hr;
  374. }