Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

521 lines
18 KiB

  1. /*++
  2. Copyright (c) 1996 Microsoft Corp.
  3. Module Name: hashutil.cxx
  4. Abstract:
  5. Implementation of linked list of hash tables for cache index lookup.
  6. Author:
  7. Rajeev Dujari (rajeevd) 22-Oct-96
  8. --*/
  9. #include <wininetp.h>
  10. #include <cache.hxx>
  11. #define SIG_HASH ('H'|('A'<<8)|('S'<<16)|('H'<<24))
  12. typedef LIST_FILEMAP_ENTRY HASH_FILEMAP_ENTRY;
  13. // hash table parameters
  14. #define BYTES_PER_PAGE 4096
  15. #define ITEMS_PER_BUCKET ((BYTES_PER_PAGE - sizeof(HASH_FILEMAP_ENTRY))\
  16. / (SLOT_COUNT * sizeof(HASH_ITEM)))
  17. #define BYTES_PER_TABLE (sizeof(HASH_FILEMAP_ENTRY) \
  18. + SLOT_COUNT * ITEMS_PER_BUCKET * sizeof(HASH_ITEM))
  19. //
  20. // Hash Function: Pearson's method
  21. //
  22. PRIVATE DWORD HashKey (LPCSTR lpsz, DWORD dwAddedHash)
  23. {
  24. union
  25. {
  26. DWORD dw;
  27. BYTE c[4];
  28. }
  29. Hash, Hash2;
  30. const static BYTE bTranslate[256] =
  31. {
  32. 1, 14,110, 25, 97,174,132,119,138,170,125,118, 27,233,140, 51,
  33. 87,197,177,107,234,169, 56, 68, 30, 7,173, 73,188, 40, 36, 65,
  34. 49,213,104,190, 57,211,148,223, 48,115, 15, 2, 67,186,210, 28,
  35. 12,181,103, 70, 22, 58, 75, 78,183,167,238,157,124,147,172,144,
  36. 176,161,141, 86, 60, 66,128, 83,156,241, 79, 46,168,198, 41,254,
  37. 178, 85,253,237,250,154,133, 88, 35,206, 95,116,252,192, 54,221,
  38. 102,218,255,240, 82,106,158,201, 61, 3, 89, 9, 42,155,159, 93,
  39. 166, 80, 50, 34,175,195,100, 99, 26,150, 16,145, 4, 33, 8,189,
  40. 121, 64, 77, 72,208,245,130,122,143, 55,105,134, 29,164,185,194,
  41. 193,239,101,242, 5,171,126, 11, 74, 59,137,228,108,191,232,139,
  42. 6, 24, 81, 20,127, 17, 91, 92,251,151,225,207, 21, 98,113,112,
  43. 84,226, 18,214,199,187, 13, 32, 94,220,224,212,247,204,196, 43,
  44. 249,236, 45,244,111,182,153,136,129, 90,217,202, 19,165,231, 71,
  45. 230,142, 96,227, 62,179,246,114,162, 53,160,215,205,180, 47,109,
  46. 44, 38, 31,149,135, 0,216, 52, 63, 23, 37, 69, 39,117,146,184,
  47. 163,200,222,235,248,243,219, 10,152,131,123,229,203, 76,120,209
  48. };
  49. // Seed the hash values based on the first character.
  50. Hash.c[0] = bTranslate[ *lpsz];
  51. Hash.c[1] = bTranslate[(*lpsz+1) & 255];
  52. Hash.c[2] = bTranslate[(*lpsz+2) & 255];
  53. Hash.c[3] = bTranslate[(*lpsz+3) & 255];
  54. Hash.dw += dwAddedHash;
  55. while (*++lpsz)
  56. {
  57. // Allow URLs differing only by trailing slash to collide.
  58. if (lpsz[0] == '/' && lpsz[1] == 0)
  59. break;
  60. Hash2.c[0] = Hash.c[0] ^ *lpsz;
  61. Hash2.c[1] = Hash.c[1] ^ *lpsz;
  62. Hash2.c[2] = Hash.c[2] ^ *lpsz;
  63. Hash2.c[3] = Hash.c[3] ^ *lpsz;
  64. Hash.c[0] = bTranslate[Hash2.c[0]];
  65. Hash.c[1] = bTranslate[Hash2.c[1]];
  66. Hash.c[2] = bTranslate[Hash2.c[2]];
  67. Hash.c[3] = bTranslate[Hash2.c[3]];
  68. }
  69. return Hash.dw;
  70. }
  71. //
  72. // HashLookupItem support functions specific to urlcache:
  73. // AllocTable
  74. // IsMatch
  75. //
  76. PRIVATE HASH_FILEMAP_ENTRY* AllocTable
  77. (LPVOID pAllocObj, LPBYTE* ppBase, LPDWORD* ppdwOffset)
  78. {
  79. // Save the offset to the table offset.
  80. DWORD_PTR dpOffsetToTableOffset = (LPBYTE)*ppdwOffset - *ppBase; // 64BIT
  81. // Ask for BYTES_PER_PAGE instead of BYTES_PER_TABLE
  82. // so the allocator knows to align on a page boundary.
  83. INET_ASSERT (BYTES_PER_PAGE >= BYTES_PER_TABLE);
  84. MEMMAP_FILE* pmmf = (MEMMAP_FILE*) pAllocObj;
  85. HASH_FILEMAP_ENTRY* pTable =
  86. (HASH_FILEMAP_ENTRY *) pmmf->AllocateEntry (BYTES_PER_PAGE);
  87. if (!pTable)
  88. return NULL;
  89. INET_ASSERT (! (((LPBYTE) pTable - *pmmf->GetHeapStart()) & (BYTES_PER_PAGE-1)) );
  90. // Chain new table to previous table.
  91. *ppBase = *pmmf->GetHeapStart();
  92. *ppdwOffset = (DWORD*) (*ppBase + dpOffsetToTableOffset);
  93. **ppdwOffset = (DWORD) ((LPBYTE)pTable - *ppBase); // 64BIT
  94. // Initialize the header.
  95. pTable->dwSig = SIG_HASH;
  96. pTable->dwNext = 0;
  97. // Fill the rest of the entry with HASH_END
  98. DWORD* pdw = (DWORD *) (pTable + 1);
  99. DWORD cdw = SLOT_COUNT * ITEMS_PER_BUCKET * (sizeof(HASH_ITEM)/sizeof(DWORD));
  100. INET_ASSERT (!(sizeof(HASH_ITEM) % sizeof(DWORD)));
  101. while (cdw--)
  102. *pdw++ = HASH_END;
  103. // Return the new table.
  104. return pTable;
  105. }
  106. //
  107. // IsMatch: determine if hash table item with a matching 32-bit hash value
  108. // is an actual match or return NULL if a collision.
  109. //
  110. PRIVATE HASH_ITEM* URL_CONTAINER::IsMatch
  111. (HASH_ITEM *pItem, LPCSTR pszKey, DWORD dwFlags)
  112. {
  113. MEMMAP_FILE* pmmf = _UrlObjStorage;
  114. dwFlags &= (LOOKUP_BIT_REDIR | LOOKUP_BIT_CREATE);
  115. if (pmmf->IsBadOffset (pItem->dwOffset))
  116. {
  117. // Fix up a bad hash table item. This could happen if a thread
  118. // died between allocating a hash table item and setting the offset.
  119. pItem->MarkFree();
  120. return NULL;
  121. }
  122. FILEMAP_ENTRY* pEntry = (FILEMAP_ENTRY*)
  123. (*pmmf->GetHeapStart() + pItem->dwOffset);
  124. switch (pEntry->dwSig)
  125. {
  126. case SIG_URL:
  127. {
  128. // Fail if lookup flags are inconsistent with url entry type.
  129. INET_ASSERT (!(pItem->dwHash & HASH_BIT_NOTURL));
  130. // Get pointer to URL.
  131. URL_FILEMAP_ENTRY *pUrlEntry = (URL_FILEMAP_ENTRY *) pEntry;
  132. LPSTR pszUrl = ((LPSTR) pUrlEntry) + pUrlEntry->UrlNameOffset;
  133. LPCSTR pszKey2 = pszKey, pszUrl2 = pszUrl;
  134. while ( *pszKey2 && *pszUrl2 && *pszKey2 == *pszUrl2 )
  135. {
  136. pszKey2++;
  137. pszUrl2++;
  138. }
  139. if (!*pszKey2 && ! *pszUrl2)
  140. {
  141. // Found exact match.
  142. if (dwFlags == LOOKUP_REDIR_CREATE)
  143. {
  144. // We are have a cache entry for a URL which is now
  145. // redirecting. Delete the cache entry.
  146. DeleteUrlEntry (pUrlEntry, pItem, SIG_DELETE);
  147. return NULL;
  148. }
  149. return pItem;
  150. }
  151. // If redirects allowed, check for trailing slash match.
  152. if ((dwFlags == LOOKUP_URL_TRANSLATE)
  153. && (pItem->dwHash & HASH_BIT_REDIR))
  154. {
  155. DWORD cbUrl = strlen (pszUrl);
  156. DWORD cbKey = strlen (pszKey);
  157. INET_ASSERT (cbUrl && pszUrl[cbUrl - 1] == '/');
  158. if (cbUrl == (cbKey + 1) && !memcmp (pszUrl, pszKey, cbKey))
  159. return pItem;
  160. }
  161. return NULL;
  162. }
  163. case SIG_REDIR:
  164. {
  165. // When online, filter out offline redirect entries.
  166. if (dwFlags == LOOKUP_URL_NOCREATE)
  167. return NULL;
  168. // Check that redirect URL matches exactly.
  169. REDIR_FILEMAP_ENTRY* pRedir = (REDIR_FILEMAP_ENTRY *) pEntry;
  170. if (lstrcmp (pszKey, pRedir->szUrl))
  171. return NULL;
  172. switch (dwFlags)
  173. {
  174. case LOOKUP_URL_CREATE:
  175. // We are creating a new entry for a URL that once
  176. // redirected. Delete the stale redirect entry.
  177. pmmf->FreeEntry (pRedir);
  178. pItem->MarkFree();
  179. return NULL;
  180. case LOOKUP_REDIR_CREATE:
  181. // Return the redirect item if we're looking for it.
  182. return pItem;
  183. case LOOKUP_URL_TRANSLATE:
  184. // Otherwise, translate through the redirect item.
  185. pItem = (HASH_ITEM *)
  186. (*pmmf->GetHeapStart() + pRedir->dwItemOffset);
  187. // Perform some consistency checks.
  188. if (pItem->dwHash & HASH_BIT_NOTURL)
  189. return NULL; // not an URL entry
  190. if ((pItem->dwHash & ~SLOT_MASK) != pRedir->dwHashValue)
  191. return NULL; // not a matching URL entry
  192. return pItem;
  193. default:
  194. INET_ASSERT (FALSE);
  195. }
  196. }
  197. default:
  198. {
  199. // Fix up a bad hash table entry. This can happen if a thread
  200. // died between allocating a hash table item and setting the offset.
  201. pItem->MarkFree();
  202. return NULL;
  203. }
  204. }
  205. }
  206. //
  207. // HashFindItem: finds a matching entry or else the first free slot
  208. //
  209. BOOL URL_CONTAINER::HashFindItem
  210. (LPCSTR pszKey, DWORD dwFlags, HASH_ITEM** ppItem)
  211. {
  212. INET_ASSERT(!((dwFlags & LOOKUP_URL_DONT_FOLLOW) && (dwFlags & LOOKUP_BIT_CREATE)));
  213. DWORD dwFind = 0;
  214. BOOL fLookAgain = !(dwFlags & LOOKUP_URL_DONT_FOLLOW) && GlobalIdentity;
  215. again:
  216. LPVOID pAllocObj = (LPVOID) _UrlObjStorage;
  217. LPBYTE pBase = *_UrlObjStorage->GetHeapStart();
  218. LPDWORD pdwTableOffset = _UrlObjStorage->GetPtrToHashTableOffset();
  219. // Scan flags.
  220. BOOL fCreate = dwFlags & LOOKUP_BIT_CREATE;
  221. HASH_ITEM* pFree = NULL;
  222. DWORD nBlock = 0;
  223. // Hash the URL and calculate the slot.
  224. DWORD dwHash = HashKey(pszKey, dwFind);
  225. DWORD iSlot = dwHash & SLOT_MASK;
  226. dwHash &= ~SLOT_MASK;
  227. // Walk through the list of hash tables.
  228. while (*pdwTableOffset && !_UrlObjStorage->IsBadOffset(*pdwTableOffset))
  229. {
  230. // Calculate offset to next hash table and validate signature.
  231. HASH_FILEMAP_ENTRY* pTable =
  232. (HASH_FILEMAP_ENTRY*) (pBase + *pdwTableOffset);
  233. if (pTable->dwSig != SIG_HASH || pTable->nBlock != nBlock++)
  234. break;
  235. // Calculate offset to bucket in this table.
  236. HASH_ITEM* pItem = ((HASH_ITEM*) (pTable + 1)) + iSlot * ITEMS_PER_BUCKET;
  237. // Scan the bucket.
  238. for (DWORD iSeat=0; iSeat<ITEMS_PER_BUCKET; iSeat++, pItem++)
  239. {
  240. // No reserved bits should ever be set on an item.
  241. INET_ASSERT (!(pItem->dwHash & HASH_BIT_RESERVED));
  242. switch (pItem->dwHash)
  243. {
  244. case HASH_FREE: // free item but more items might follow
  245. {
  246. INET_ASSERT (!(pItem->dwHash & ~SLOT_MASK));
  247. // If caller wants a free item, record the first one we find.
  248. if (!pFree && fCreate)
  249. pFree = pItem;
  250. }
  251. continue;
  252. case HASH_END: // first previously unused free item; no more to follow
  253. {
  254. INET_ASSERT (!(pItem->dwHash & ~SLOT_MASK));
  255. if (!fCreate)
  256. *ppItem = NULL;
  257. else
  258. {
  259. // Hand out the first free slot.
  260. if (pFree)
  261. {
  262. // Invalidate offset in case caller neglects to set it.
  263. pFree->dwOffset = HASH_END;
  264. *ppItem = pFree;
  265. }
  266. else
  267. {
  268. // The first free slot has never been used before.
  269. INET_ASSERT (pItem->dwOffset == HASH_END);
  270. *ppItem = pItem;
  271. }
  272. (*ppItem)->dwHash = dwHash;
  273. }
  274. }
  275. return FALSE;
  276. default:
  277. {
  278. // Check if the key matches.
  279. if (dwHash == (pItem->dwHash & ~SLOT_MASK))
  280. {
  281. if (dwFlags & INTERNET_CACHE_FLAG_ALLOW_COLLISIONS)
  282. {
  283. *ppItem = pItem;
  284. return TRUE;
  285. }
  286. HASH_ITEM* pItem2 = IsMatch(pItem, pszKey, dwFlags);
  287. if (pItem2)
  288. {
  289. LPURL_FILEMAP_ENTRY pEntry =
  290. (URL_FILEMAP_ENTRY*)(*(((MEMMAP_FILE*)_UrlObjStorage)->GetHeapStart())
  291. + pItem2->dwOffset);
  292. // This will check for a ~U: header or IDENTITY_CACHE_ENTRY
  293. // We use the first for compatibility with Wininet5
  294. // We use IDENTITY_CACHE_ENTRY for entries we wish to hide from Wininet5
  295. if ((pEntry->dwSig==SIG_URL)
  296. && (!(dwFlags & LOOKUP_URL_DONT_FOLLOW)
  297. && ((pEntry->CacheEntryType & IDENTITY_CACHE_ENTRY)
  298. || IsPerUserEntry(pEntry))))
  299. {
  300. // We'll search again for an entry corresponding to GlobalIdentity
  301. if (fLookAgain)
  302. {
  303. fLookAgain = FALSE;
  304. dwFind = GlobalIdentity;
  305. goto again;
  306. }
  307. // Guarantee that this is what we want
  308. if (pEntry->GetIdentity()!=GlobalIdentity)
  309. {
  310. continue;
  311. }
  312. // If we're looking for an identity-0 cache entry
  313. // and there is no filename, we need to do the following:
  314. // 1. if we're trying to create an entry, then return this
  315. // 2. otherwise, say No, the entry is not present
  316. if (!pEntry->InternalFileNameOffset && !fCreate)
  317. {
  318. *ppItem = NULL;
  319. return FALSE;
  320. }
  321. }
  322. *ppItem = pItem2;
  323. return TRUE;
  324. }
  325. }
  326. }
  327. continue;
  328. } // end switch
  329. } // end for loop to scan seats in bucket
  330. // Follow the link to the next table.
  331. pdwTableOffset = &pTable->dwNext;
  332. } // end while (*pdwTableOffset)
  333. // If we've encountered a corrupt table, we'll have to recover
  334. if (*pdwTableOffset)
  335. {
  336. INET_ASSERT(FALSE);
  337. *pdwTableOffset = 0;
  338. }
  339. // We are out a buckets, so an item hasn't been found.
  340. if (fCreate && !pFree)
  341. {
  342. // Caller wanted a free item but we didn't find one.
  343. HASH_FILEMAP_ENTRY* pTable = AllocTable
  344. (pAllocObj, &pBase, &pdwTableOffset);
  345. //////////////////////////////////////////////////////////////////////
  346. // WARNING: the file might have grown and remapped, so any pointers //
  347. // hereafter must be recalculated by offsets from the new base. //
  348. //////////////////////////////////////////////////////////////////////
  349. if (pTable)
  350. {
  351. pTable->nBlock = nBlock;
  352. // Calculate next free slot.
  353. pFree = ((HASH_ITEM*) (pTable + 1)) + iSlot * ITEMS_PER_BUCKET;
  354. INET_ASSERT (pFree->dwHash == HASH_END);
  355. INET_ASSERT (pFree->dwOffset == HASH_END);
  356. }
  357. }
  358. // Return free item if desired and indicate no item found.
  359. if (pFree)
  360. {
  361. INET_ASSERT (fCreate);
  362. pFree->dwHash = dwHash;
  363. pFree->dwOffset = HASH_END; // invalid in case caller neglects to set it
  364. }
  365. *ppItem = pFree;
  366. return FALSE;
  367. }
  368. //
  369. // HashFindNextItem: scans the table for the next valid URL item
  370. //
  371. PUBLIC
  372. HASH_ITEM*
  373. HashGetNextItem
  374. (
  375. IN LPVOID pAllocObj, // allocator object
  376. IN LPBYTE pBase, // base for all offsets
  377. IN OUT LPDWORD pdwItemOffset, // current item offset
  378. IN DWORD dwFlags // include redirects?
  379. )
  380. {
  381. INET_ASSERT (!(dwFlags & ~LOOKUP_BIT_REDIR));
  382. // Check if there if the hash table is empty (or we are at the end already.)
  383. if (!*pdwItemOffset)
  384. return NULL;
  385. HASH_ITEM* pItem = (HASH_ITEM*) (pBase + *pdwItemOffset);
  386. // Calculate current table offset, assuming it's the previous page boundary.
  387. INET_ASSERT (BYTES_PER_TABLE <= BYTES_PER_PAGE);
  388. HASH_FILEMAP_ENTRY* pTable =
  389. (HASH_FILEMAP_ENTRY*) (((DWORD_PTR)pItem) & ~(BYTES_PER_PAGE - 1));
  390. // Advance item pointer to next location.
  391. if (pItem == (HASH_ITEM*) pTable)
  392. pItem = (HASH_ITEM*) (pTable + 1); // first location in table
  393. else
  394. pItem++; // next location in table
  395. do // Scan the list of tables.
  396. {
  397. if (pTable->dwSig != SIG_HASH)
  398. break;
  399. // Scan the current table.
  400. for (; (LPBYTE) pItem < ((LPBYTE) pTable) + BYTES_PER_TABLE; pItem++)
  401. {
  402. // No reserved bits should be set.
  403. INET_ASSERT (!(pItem->dwHash & HASH_BIT_RESERVED));
  404. if (!(pItem->dwHash & HASH_BIT_NOTURL)
  405. || (dwFlags /* & LOOKUP_BIT_REDIR */)
  406. && ((pItem->dwHash & HASH_FLAG_MASK) == HASH_REDIR))
  407. {
  408. // Found a valid entry.
  409. *pdwItemOffset = (DWORD) ((LPBYTE)pItem - pBase); // 64BIT
  410. return pItem;
  411. }
  412. }
  413. // Follow the link to the next table.
  414. if (!pTable->dwNext)
  415. pTable = NULL;
  416. else
  417. {
  418. // Validate the table signature and sequence number.
  419. DWORD nBlock = pTable->nBlock;
  420. pTable = (HASH_FILEMAP_ENTRY*) (pBase + pTable->dwNext);
  421. if (pTable->dwSig != SIG_HASH || pTable->nBlock != nBlock + 1)
  422. pTable = NULL;
  423. // Set pointer to first location in table.
  424. pItem = (HASH_ITEM*) (pTable + 1);
  425. }
  426. }
  427. while (pTable);
  428. // We reached the end of the last table.
  429. *pdwItemOffset = 0;
  430. return NULL;
  431. }