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.

1311 lines
41 KiB

  1. /*++
  2. Copyright (c) 2001 Microsoft Corporation
  3. Module Name:
  4. cacheread.cxx
  5. Abstract:
  6. This file contains the implementation of the HTTPCACHE request object which involve reading from the cache
  7. Author:
  8. Revision History:
  9. --*/
  10. #include <wininetp.h>
  11. #include <string.h>
  12. #define __CACHE_INCLUDE__
  13. #include "..\urlcache\cache.hxx"
  14. #include "cachelogic.hxx"
  15. #include "internalapi.hxx"
  16. #include "..\http\proc.h"
  17. extern const struct KnownHeaderType GlobalKnownHeaders[];
  18. DWORD
  19. UrlCacheRetrieve
  20. (
  21. IN LPSTR pszUrl,
  22. IN BOOL fRedir,
  23. OUT HANDLE* phStream,
  24. OUT CACHE_ENTRY_INFOEX** ppCEI
  25. );
  26. #define ONE_HOUR_DELTA (60 * 60 * (LONGLONG)10000000)
  27. ////////////////////////////////////////////////////////////////////////////////
  28. //
  29. // Cache read related private functions:
  30. //
  31. // EndCacheRead
  32. // ReadDataFromCache
  33. // SendIMSRequest
  34. // CheckIfInCache
  35. // CheckIsExpired
  36. // AddIfModifiedSinceAndETag
  37. // CheckResponseAfterIMS
  38. //
  39. //
  40. PRIVATE PRIVATE VOID HTTPCACHE_REQUEST::ResetCacheReadVariables()
  41. /*++
  42. Routine Description:
  43. Should be call to reset all variables related to cache read for new requests
  44. Return Value:
  45. NONE
  46. --*/
  47. {
  48. _hCacheReadStream = NULL;
  49. _dwCurrentStreamPosition = 0;
  50. }
  51. PRIVATE BOOL HTTPCACHE_REQUEST::CloseCacheReadStream(VOID)
  52. /*++
  53. Routine Description:
  54. Close the cache read stream to fully complete the cache read operation.
  55. Pre-condition:
  56. OpenCacheReadStream() returns TRUE
  57. Side Effects:
  58. _hCacheReadStream = NULL;
  59. _dwCurrentStreamPosition = 0;
  60. Return Value:
  61. BOOL indicating whether the call is successful or not
  62. --*/
  63. {
  64. DEBUG_ENTER((DBG_CACHE,
  65. Bool,
  66. "HTTPCACHE_REQUEST::CloseCacheReadStream",
  67. NULL
  68. ));
  69. BOOL fResult = FALSE;
  70. if (_fIsPartialCache == TRUE)
  71. return TRUE;
  72. INET_ASSERT(_hCacheReadStream != NULL);
  73. if (UnlockUrlCacheEntryStream(_hCacheReadStream, 0))
  74. {
  75. // reinitializes the variables so the new requests won't screw up
  76. fResult = TRUE;
  77. ResetCacheReadVariables();
  78. }
  79. DEBUG_LEAVE(fResult);
  80. return fResult;
  81. }
  82. PRIVATE BOOL HTTPCACHE_REQUEST::ReadDataFromCache(
  83. LPVOID lpBuffer,
  84. DWORD dwNumberOfBytesToRead,
  85. LPDWORD lpdwNumberOfBytesRead
  86. )
  87. /*++
  88. Routine Description:
  89. Try to read dwNumberOfBytesToRead bytes of data from the current file pointer for the
  90. cache entry, and return the data to lpBuffer. Also return the actual number of bytes
  91. read to lpdwNumberOfBytesRead
  92. Similar to HTTP_REQUEST_HANDLE_OBJECT::AttemptReadFromFile in Wininet
  93. Parameters:
  94. Precondition:
  95. OpenCacheReadStream() returns TRUE before is function is being called
  96. Side Effects:
  97. NONE
  98. Return Value:
  99. BOOL indicating whether the call is successful or not
  100. --*/
  101. {
  102. DEBUG_ENTER((DBG_CACHE,
  103. Bool,
  104. "HTTPCACHE_REQUEST::ReadDataFromCache",
  105. NULL
  106. ));
  107. BOOL fSuccess;
  108. DWORD dwBytesToCopy = 0;
  109. if (!dwNumberOfBytesToRead)
  110. {
  111. *lpdwNumberOfBytesRead = 0;
  112. DEBUG_LEAVE(TRUE);
  113. return TRUE;
  114. }
  115. if (_fCacheReadInProgress && !_fIsPartialCache)
  116. {
  117. // INET_ASSERT(_VirtualCacheFileSize == _RealCacheFileSize);
  118. // Entire read should be satisfied from cache.
  119. *lpdwNumberOfBytesRead = dwNumberOfBytesToRead;
  120. if (ReadUrlCacheEntryStream(_hCacheReadStream,
  121. _dwCurrentStreamPosition,
  122. lpBuffer,
  123. lpdwNumberOfBytesRead,
  124. 0))
  125. {
  126. _dwCurrentStreamPosition += *lpdwNumberOfBytesRead;
  127. DEBUG_LEAVE(TRUE);
  128. return TRUE;
  129. }
  130. else
  131. {
  132. *lpdwNumberOfBytesRead = 0;
  133. DEBUG_PRINT(CACHE, ERROR, ("Error in ReadUrlCacheEntryStream: _hCacheReadStream=%d, _dwCurrentStreamPosition=%d\n",
  134. _hCacheReadStream, _dwCurrentStreamPosition));
  135. DEBUG_LEAVE(FALSE);
  136. return FALSE;
  137. }
  138. }
  139. else if (_fCacheWriteInProgress || _fCacheReadInProgress && _fIsPartialCache)
  140. {
  141. // See if the read is completely within the file.
  142. if (_dwCurrentStreamPosition + *lpdwNumberOfBytesRead > _VirtualCacheFileSize) // && !IsEndOfFile() ??
  143. {
  144. DEBUG_PRINT(CACHE, ERROR, ("Error: Current streampos=%d cbToRead=%d, _VirtualCacheFileSize=%d\n",
  145. _dwCurrentStreamPosition, *lpdwNumberOfBytesRead, _VirtualCacheFileSize));
  146. DEBUG_LEAVE(FALSE);
  147. return FALSE;
  148. }
  149. INET_ASSERT((_lpszCacheWriteLocalFilename != NULL) || _fIsPartialCache);
  150. if (_fIsPartialCache)
  151. {
  152. _lpszCacheWriteLocalFilename = NewString(_pCacheEntryInfo->lpszLocalFileName);
  153. INET_ASSERT(_lpszCacheWriteLocalFilename);
  154. }
  155. HANDLE hfRead;
  156. hfRead = CreateFile(_lpszCacheWriteLocalFilename,
  157. GENERIC_READ,
  158. FILE_SHARE_READ | FILE_SHARE_WRITE,
  159. NULL,
  160. OPEN_EXISTING,
  161. FILE_ATTRIBUTE_NORMAL,
  162. NULL);
  163. if (hfRead == INVALID_HANDLE_VALUE)
  164. {
  165. DEBUG_PRINT(CACHE, ERROR, ("CreateFile failed: Local filename = %s", _lpszCacheWriteLocalFilename));
  166. DEBUG_LEAVE(FALSE);
  167. return FALSE;
  168. }
  169. // Read the data from the file.
  170. SetFilePointer (hfRead, _dwCurrentStreamPosition, NULL, FILE_BEGIN);
  171. fSuccess = ReadFile (hfRead, lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead, NULL);
  172. if (fSuccess)
  173. _dwCurrentStreamPosition += *lpdwNumberOfBytesRead;
  174. CloseHandle(hfRead);
  175. return fSuccess;
  176. }
  177. else
  178. {
  179. DEBUG_PRINT(CACHE, ERROR, ("Error: unexpected program path. (possibly uninitalized variables?)\n"));
  180. DEBUG_LEAVE(FALSE);
  181. return FALSE;
  182. }
  183. }
  184. PRIVATE VOID HTTPCACHE_REQUEST::CheckResponseAfterIMS(DWORD dwStatusCode)
  185. /*++
  186. Routine Description:
  187. We get back the response after sending a IMS request. This routine finds out
  188. if the content is modified (in which case we have to go to the net again), not
  189. modfied (in which case we can grab it from the cache), or whether it's a
  190. partial cache entry (it which case we follow the partial cache logic in partial.cxx)
  191. Similar to HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePostNetIO in wininet
  192. Parameters:
  193. dwStatusCode - the HTTP response status code sent back from the server after the IMS request
  194. Precondition:
  195. TransmitRequest() (conditional send request) has been called
  196. Side Effects:
  197. NONE
  198. --*/
  199. {
  200. DEBUG_ENTER((DBG_CACHE,
  201. None,
  202. "HTTPCACHE_REQUEST::CheckResponseAfterIMS",
  203. "%d",
  204. dwStatusCode
  205. ));
  206. // Assume that a conditional send request has already been called and response returned back.
  207. INET_ASSERT((dwStatusCode == HTTP_STATUS_NOT_MODIFIED)
  208. || (dwStatusCode == HTTP_STATUS_PRECOND_FAILED)
  209. || (dwStatusCode == HTTP_STATUS_OK)
  210. || (dwStatusCode == HTTP_STATUS_PARTIAL_CONTENT)
  211. || (dwStatusCode == 0));
  212. // Extract the time stamps from the HTTP headers
  213. CalculateTimeStampsForCache();
  214. // TODO: fVariation??
  215. // We get a 304 (if-modified-since was not modified), then use the entry from the cache
  216. // Optimization: When the return status is OK and the server sent us a last modified time
  217. // that is exactly the same as what's in the cache entry, then we follow the same behavior
  218. // as a 304
  219. if ((dwStatusCode == HTTP_STATUS_NOT_MODIFIED) ||
  220. ((_fHasLastModTime) && (FT2LL(_ftLastModTime) == FT2LL(_pCacheEntryInfo->LastModifiedTime))))
  221. {
  222. DWORD dwAction = CACHE_ENTRY_SYNCTIME_FC;
  223. if (_fHasExpiry)
  224. {
  225. (_pCacheEntryInfo->ExpireTime).dwLowDateTime = _ftExpiryTime.dwLowDateTime;
  226. (_pCacheEntryInfo->ExpireTime).dwHighDateTime = _ftExpiryTime.dwHighDateTime;
  227. dwAction |= CACHE_ENTRY_EXPTIME_FC;
  228. }
  229. // update the cache entry type if needed
  230. DWORD dwType;
  231. dwType = _pCacheEntryInfo->CacheEntryType;
  232. if (dwType)
  233. _pCacheEntryInfo->CacheEntryType |= dwType;
  234. dwAction |= CACHE_ENTRY_TYPE_FC;
  235. // Update the last sync time to the current time
  236. // so we can do once_per_session logic
  237. if (!SetUrlCacheEntryInfoA(_pCacheEntryInfo->lpszSourceUrlName, _pCacheEntryInfo, dwAction))
  238. {
  239. // NB if this call fails, the worst that could happen is
  240. // that next time around we will do an if-modified-since
  241. // again
  242. INET_ASSERT(FALSE);
  243. }
  244. }
  245. DEBUG_LEAVE(0);
  246. }
  247. PRIVATE BOOL HTTPCACHE_REQUEST::TransmitRequest(IN OUT DWORD * pdwStatusCode)
  248. /*++
  249. Routine Description:
  250. A call to WinHttpSendRequest. Use this call to send a I-M-S or a
  251. U-M-S request
  252. Parameter
  253. pdwStatusCode - returns the status code of the Send request
  254. Return Value:
  255. BOOL - whether the call is successful
  256. --*/
  257. {
  258. DEBUG_ENTER((DBG_CACHE,
  259. Bool,
  260. "HTTPCACHE_REQUEST::TransmitRequest",
  261. NULL
  262. ));
  263. DWORD dwSize = sizeof(DWORD);
  264. BOOL fResult;
  265. // Someone will get here only if they're calling WinHttpSendRequest()
  266. // and is using the cache, so...
  267. INET_ASSERT(_hRequest);
  268. // resend the HTTP request
  269. WinHttpSendRequest(_hRequest, NULL, 0, NULL, 0, 0, 0);
  270. WinHttpReceiveResponse(_hRequest, NULL);
  271. // Examine what HTTP status code I get back after the send request
  272. fResult = WinHttpQueryHeaders(_hRequest,
  273. WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
  274. NULL,
  275. pdwStatusCode,
  276. &dwSize,
  277. NULL);
  278. DEBUG_LEAVE(fResult);
  279. return fResult;
  280. }
  281. PRIVATE BOOL HTTPCACHE_REQUEST::FakeCacheResponseHeaders()
  282. /*++
  283. Routine Description:
  284. If a resource is coming from the cache, sets the HTTP Response Headers
  285. so that users apps calling WinHttpQueryHeaders get the right behaviours.
  286. Basically this function is the HTTP_REQUEST_HEADER::FHttpBeginCacheRetreival,
  287. AddTimestampsForCacheToResponseHeaders(), and AddTimeHeader() from
  288. from wininet all packed together
  289. Precondition:
  290. The GET request content can be fulfilled by the cache
  291. Return Value:
  292. BOOL
  293. --*/
  294. {
  295. DEBUG_ENTER((DBG_CACHE,
  296. Bool,
  297. "HTTPCACHE_REQUEST::FakeCacheResponseHeaders",
  298. NULL
  299. ));
  300. LPSTR lpHeaders = NULL;
  301. DWORD dwError = ERROR_INVALID_PARAMETER;
  302. TCHAR szBuf[128];
  303. BOOL fResult = FALSE;
  304. // DO we need to warp this call by a _ResponseHeader.LockHeader() and
  305. // UnlockHeader() pair??
  306. // allocate buffer for headers
  307. lpHeaders = (LPSTR)ALLOCATE_FIXED_MEMORY(_pCacheEntryInfo->dwHeaderInfoSize+512);
  308. if (!lpHeaders)
  309. goto quit;
  310. memcpy(lpHeaders, _pCacheEntryInfo->lpHeaderInfo, _pCacheEntryInfo->dwHeaderInfoSize);
  311. InternalReuseHTTP_Request_Handle_Object(_hRequest);
  312. dwError = InternalCreateResponseHeaders(_hRequest, &lpHeaders, _pCacheEntryInfo->dwHeaderInfoSize);
  313. if (dwError == ERROR_SUCCESS)
  314. {
  315. if (AddTimeResponseHeader(_pCacheEntryInfo->LastModifiedTime, WINHTTP_QUERY_LAST_MODIFIED))
  316. {
  317. if (AddTimeResponseHeader(_pCacheEntryInfo->ExpireTime, WINHTTP_QUERY_EXPIRES))
  318. {
  319. fResult = TRUE;
  320. }
  321. }
  322. }
  323. quit:
  324. if (lpHeaders)
  325. FREE_MEMORY(lpHeaders);
  326. DEBUG_LEAVE(fResult);
  327. return (fResult);
  328. }
  329. PRIVATE PRIVATE BOOL HTTPCACHE_REQUEST::AddTimeResponseHeader(
  330. IN FILETIME fTime,
  331. IN DWORD dwQueryIndex
  332. )
  333. /*++
  334. Routine Description:
  335. Add a response header that has a time-related value.
  336. Used mainly as a helper function for FakeCacheResponseHeaders to add
  337. time-related response headers
  338. Parameters:
  339. fTime - Time in FILTIME format
  340. dwQueryIndex - the type of response header to add
  341. Return Value:
  342. BOOL
  343. --*/
  344. {
  345. DEBUG_ENTER((DBG_CACHE,
  346. Bool,
  347. "HTTPCACHE_REQUEST::AddTimeResponseHeader",
  348. "%#x:%#x, %d",
  349. fTime.dwLowDateTime,
  350. fTime.dwHighDateTime,
  351. dwQueryIndex
  352. ));
  353. BOOL fResult = FALSE;
  354. SYSTEMTIME systemTime;
  355. DWORD dwLen;
  356. TCHAR szBuf[64];
  357. if (FT2LL(fTime) != LONGLONG_ZERO)
  358. {
  359. if (FileTimeToSystemTime((CONST FILETIME *)&fTime, &systemTime))
  360. {
  361. if (InternetTimeFromSystemTimeA((CONST SYSTEMTIME *)&systemTime,
  362. szBuf))
  363. {
  364. fResult = (ERROR_SUCCESS == InternalReplaceResponseHeader(
  365. _hRequest,
  366. dwQueryIndex,
  367. szBuf,
  368. strlen(szBuf),
  369. 0,
  370. WINHTTP_ADDREQ_FLAG_ADD_IF_NEW
  371. ));
  372. }
  373. }
  374. }
  375. DEBUG_LEAVE(fResult);
  376. return fResult;
  377. }
  378. PRIVATE BOOL HTTPCACHE_REQUEST::OpenCacheReadStream()
  379. /*++
  380. Routine Description:
  381. Determines if the URL entry of this object is already in the cache, and
  382. if so, open up the cache read file stream so that cache retrieval
  383. can be done.
  384. Return Value:
  385. BOOL
  386. --*/
  387. {
  388. DEBUG_ENTER((DBG_CACHE,
  389. Bool,
  390. "HTTPCACHE_REQUEST::OpenCacheReadStream",
  391. NULL
  392. ));
  393. BOOL fResult = FALSE;
  394. LPSTR lpszUrl;
  395. HANDLE hReadStream;
  396. lpszUrl = GetUrl();
  397. // look up URL from the cache and if found, get the handle to the cache objects
  398. // We DO NOT take redirection into account. Redirection is handled at a higher layer
  399. if (UrlCacheRetrieve(lpszUrl, FALSE, &hReadStream, &_pCacheEntryInfo) == ERROR_SUCCESS)
  400. {
  401. DEBUG_PRINT (CACHE, INFO, ("%s found in the cache!! Local filename: %s\n", lpszUrl, _pCacheEntryInfo->lpszLocalFileName));
  402. // Note that if this is a partial entry, then _hCacheReadStream will be set
  403. // to NULL. But at this point we don't check whether this is a partial
  404. // entry or not
  405. ResetCacheReadVariables();
  406. _hCacheReadStream = hReadStream;
  407. fResult = TRUE;
  408. }
  409. else
  410. _hCacheReadStream = NULL;
  411. DEBUG_LEAVE(fResult);
  412. return fResult;
  413. }
  414. // --- from wininet\http\cache.cxx
  415. /*============================================================================
  416. IsExpired (...)
  417. 4/17/00 (RajeevD) Corrected back arrow behavior and wrote detailed comment.
  418. We have a cache entry for the URL. This routine determines whether we should
  419. synchronize, i.e. do an if-modified-since request. This answer depends on 3
  420. factors: navigation mode, expiry on cache entry if any, and syncmode setting.
  421. 1. There are two navigation modes:
  422. a. hyperlinking - clicking on a link, typing a URL, starting browser etc.
  423. b. back/forward - using the back or forward buttons in the browser.
  424. In b/f case we generally want to display what was previously shown. Ideally
  425. wininet would cache multiple versions of a given URL and trident would specify
  426. which one to use when hitting back arrow. For now, the best we can do is use
  427. the latest (only) cache entry or resync with the server.
  428. EXCEPTION: if the cache entry sets http/1.1 cache-control: must-revalidate,
  429. we treat as if we were always hyperlinking to the cache entry. This is
  430. normally used during offline mode to suppress using a cache entry after
  431. expiry. This overloaded usage gives sites a workaround if they dislike our
  432. new back button behavior.
  433. 2. Expiry may fall into one of 3 buckets:
  434. a. no expiry information
  435. b. expiry in past of current time (hyperlink) or last-access time (back/fwd)
  436. c. expiry in future of current time (hyperlink) or-last access time (back/fwd)
  437. 3. Syncmode may have 3 settings
  438. a. always - err on side of freshest data at expense of net perf.
  439. b. never - err on side of best net perf at expense of stale data.
  440. c. once-per-session - middle-of-the-road setting
  441. d. automatic - slight variation of once-per-session where we decay frequency
  442. of i-m-s for images that appear to be static. This is the default.
  443. Based on these factors, there are 5 possible result values in matrices below:
  444. 1 synchronize
  445. 0 don't synchronize
  446. ? synchronize if last-sync time was before start of the current session,
  447. ?- Like per-session except if URL is marked static and has a delay interval.
  448. 0+ Don't sync if URL is marked static, else fall back to per-session
  449. HYPERLINKING
  450. When hyperlinking, expiry takes precedence, then we look at syncmode.
  451. No Expiry Expiry in Future Expiry in Past
  452. Syncmode of Current Time of Current Time
  453. Always 1 0 1
  454. Never 0 0 1
  455. Per-Session ? 0 1
  456. Automatic ?- 0 1
  457. BACK/FORWARD
  458. When going back or forward, we generally don't sync. The exception is if
  459. we should have sync'ed the URL on the previous navigate but didn't. We
  460. deduce this by looking at the last-sync time of the entry.
  461. No Expiry Expiry in Future Expiry in Past
  462. Syncmode of Last-Access Time of Last-Access Time
  463. Always ? 0 ?
  464. Never 0 0 ?
  465. Per-Session ? 0 ?
  466. Automatic 0+ 0 ?
  467. When considering what might have happened when hyperlinking to this URL,
  468. the decision tree has 5 outcomes:
  469. 1. We might have had no cache entry and downloaded to cache for the first time
  470. 2. Else we might have had a cache entry and used it w/o i-m-s
  471. 3. Else we did i-m-s but the download was aborted
  472. 4. Or the i-m-s returned not modified
  473. 5. Or the i-m-s returned new content
  474. Only in case 3 do we want to resync the cache entry.
  475. ============================================================================*/
  476. PRIVATE BOOL HTTPCACHE_REQUEST::IsExpired ()
  477. /*++
  478. Routine Description:
  479. Determines whether the current cache entry is expired. If it's
  480. expired then we need to synchronize (i.e. do a i-m-s request)
  481. Parameters:
  482. NONE
  483. Side Effects:
  484. _fLazyUpdate
  485. Return Value:
  486. BOOL
  487. --*/
  488. {
  489. DEBUG_ENTER((DBG_CACHE,
  490. Bool,
  491. "HTTPCACHE_REQUEST::IsExpired",
  492. NULL
  493. ));
  494. BOOL fExpired;
  495. FILETIME ftCurrentTime;
  496. GetCurrentGmtTime (&ftCurrentTime);
  497. if ((_dwCacheFlags & CACHE_FLAG_FWD_BACK)
  498. && !(_pCacheEntryInfo->CacheEntryType & MUST_REVALIDATE_CACHE_ENTRY))
  499. {
  500. // BACK/FORWARD CASE
  501. if (FT2LL (_pCacheEntryInfo->ExpireTime) != LONGLONG_ZERO)
  502. {
  503. // We have an expires time.
  504. if (FT2LL (_pCacheEntryInfo->ExpireTime) > FT2LL(_pCacheEntryInfo->LastAccessTime))
  505. {
  506. // Expiry was in future of last access time, so don't resync.
  507. fExpired = FALSE;
  508. }
  509. else
  510. {
  511. // Entry was originally expired. Make sure it was sync'ed once.
  512. fExpired = (FT2LL(_pCacheEntryInfo->LastSyncTime) < dwdwSessionStartTime);
  513. }
  514. }
  515. else switch (_dwCacheFlags)
  516. {
  517. default:
  518. case CACHE_FLAG_SYNC_MODE_AUTOMATIC:
  519. if (_pCacheEntryInfo->CacheEntryType & STATIC_CACHE_ENTRY)
  520. {
  521. fExpired = FALSE;
  522. break;
  523. }
  524. // else intentional fall-through...
  525. case CACHE_FLAG_SYNC_MODE_ALWAYS:
  526. case CACHE_FLAG_SYNC_MODE_ONCE_PER_SESSION:
  527. fExpired = (FT2LL(_pCacheEntryInfo->LastSyncTime) < dwdwSessionStartTime);
  528. break;
  529. case CACHE_FLAG_SYNC_MODE_NEVER:
  530. fExpired = FALSE;
  531. break;
  532. } // end switch
  533. }
  534. else
  535. {
  536. // HYPERLINKING CASE
  537. // Always strictly honor expire time from the server.
  538. _fLazyUpdate = FALSE;
  539. if( (_pCacheEntryInfo->CacheEntryType & POST_CHECK_CACHE_ENTRY ) &&
  540. !(_dwCacheFlags & INTERNET_FLAG_BGUPDATE) )
  541. {
  542. //
  543. // this is the (instlled) post check cache entry, so we will do
  544. // post check on this ietm
  545. //
  546. fExpired = FALSE;
  547. _fLazyUpdate = TRUE;
  548. }
  549. else if (FT2LL(_pCacheEntryInfo->ExpireTime) != LONGLONG_ZERO)
  550. {
  551. // do we have postCheck time?
  552. //
  553. // ftPostCheck ftExpire
  554. // | |
  555. // --------------|----------------------------|-----------> time
  556. // | |
  557. // not expired | not expired (bg update) | expired
  558. //
  559. //
  560. LONGLONG qwPostCheck = FT2LL(_pCacheEntryInfo->ftPostCheck);
  561. if( qwPostCheck != LONGLONG_ZERO )
  562. {
  563. LONGLONG qwCurrent = FT2LL(ftCurrentTime);
  564. if( qwCurrent < qwPostCheck )
  565. {
  566. fExpired = FALSE;
  567. }
  568. else
  569. if( qwCurrent < FT2LL(_pCacheEntryInfo->ExpireTime) )
  570. {
  571. fExpired = FALSE;
  572. // set background update flag
  573. // (only if we are not doing lazy updating ourselfs)
  574. if ( !(_dwCacheFlags & INTERNET_FLAG_BGUPDATE) )
  575. {
  576. _fLazyUpdate = TRUE;
  577. }
  578. }
  579. else
  580. {
  581. fExpired = TRUE;
  582. }
  583. }
  584. else
  585. fExpired = FT2LL(_pCacheEntryInfo->ExpireTime) <= FT2LL(ftCurrentTime);
  586. }
  587. else switch (_dwCacheFlags)
  588. {
  589. case CACHE_FLAG_SYNC_MODE_NEVER:
  590. // Never check, unless the page has expired
  591. fExpired = FALSE;
  592. break;
  593. case CACHE_FLAG_SYNC_MODE_ALWAYS:
  594. fExpired = TRUE;
  595. break;
  596. default:
  597. case CACHE_FLAG_SYNC_MODE_AUTOMATIC:
  598. if (_pCacheEntryInfo->CacheEntryType & STATIC_CACHE_ENTRY)
  599. {
  600. // We believe this entry never actually changes.
  601. // Check the entry if interval since last checked
  602. // is less than 25% of the time we had it cached.
  603. LONGLONG qwTimeSinceLastCheck = FT2LL (ftCurrentTime)
  604. - FT2LL(_pCacheEntryInfo->LastSyncTime);
  605. LONGLONG qwTimeSinceDownload = FT2LL (ftCurrentTime)
  606. - FT2LL (_pCacheEntryInfo->ftDownload);
  607. fExpired = qwTimeSinceLastCheck > qwTimeSinceDownload/4;
  608. break;
  609. }
  610. // else intentional fall through to once-per-session rules.
  611. case CACHE_FLAG_SYNC_MODE_ONCE_PER_SESSION:
  612. fExpired = TRUE;
  613. // Huh. We don't have an expires, so we'll improvise
  614. // but wait! if we are hyperlinking then there is added
  615. // complication. This semantic has been figured out
  616. // on Netscape after studying various sites
  617. // if the server didn't send us expiry time or lastmodifiedtime
  618. // then this entry expires when hyperlinking
  619. // this happens on queries
  620. if (_dwCacheFlags & INTERNET_FLAG_HYPERLINK
  621. && !FT2LL(_pCacheEntryInfo->LastModifiedTime))
  622. {
  623. // shouldn't need the hyperlink test anymore
  624. DEBUG_PRINT(CACHE, INFO, ("Hyperlink semantics\n"));
  625. INET_ASSERT(fExpired==TRUE);
  626. break;
  627. }
  628. // We'll assume the data could change within a day of the last time
  629. // we sync'ed.
  630. // We want to refresh UNLESS we've seen the page this session
  631. // AND the session's upper bound hasn't been exceeded.
  632. if ((dwdwSessionStartTime < FT2LL(_pCacheEntryInfo->LastSyncTime))
  633. &&
  634. (FT2LL(ftCurrentTime) < FT2LL(_pCacheEntryInfo->LastSyncTime) +
  635. dwdwHttpDefaultExpiryDelta))
  636. {
  637. fExpired = FALSE;
  638. }
  639. break;
  640. } // end switch
  641. } // end else for hyperlinking case
  642. DEBUG_LEAVE(fExpired);
  643. return fExpired;
  644. }
  645. PRIVATE BOOL HTTPCACHE_REQUEST::AddIfModifiedSinceHeaders()
  646. /*++
  647. Routine Description:
  648. Add the necessary IMS request headers to validate whether a cache
  649. entry can still be used to satisfy the GET request.
  650. Code from HTTP_REQUEST_HANDLE_OBJECT::FAddIfModifiedSinceHeader
  651. and HTTP_REQUEST_HANDLE_OBJECT::AddHeaderIfEtagFound from wininet
  652. Return Value:
  653. BOOL
  654. --*/
  655. {
  656. DEBUG_ENTER((DBG_CACHE,
  657. Bool,
  658. "HTTPCACHE_REQUEST::AddIfModifiedSinceHeaders",
  659. NULL
  660. ));
  661. BOOL fResult = FALSE;
  662. // add if-modified-since only if there is last modified time
  663. // sent back by the site. This way you never get into trouble
  664. // where the site doesn't send you an last modified time and you
  665. // send if-modified-since based on a clock which might be ahead
  666. // of the site. So the site might say nothing is modified even though
  667. // something might be. www.microsoft.com is one such example
  668. if (FT2LL(_pCacheEntryInfo->LastModifiedTime) != LONGLONG_ZERO)
  669. {
  670. TCHAR szBuf[64];
  671. TCHAR szHeader[HTTP_IF_MODIFIED_SINCE_LEN + 76];
  672. DWORD dwLen;
  673. DWORD dwError;
  674. BOOL success = FALSE;
  675. INET_ASSERT (FT2LL(_pCacheEntryInfo->LastModifiedTime));
  676. dwLen = sizeof(szBuf);
  677. if (FFileTimetoHttpDateTime(&(_pCacheEntryInfo->LastModifiedTime), szBuf, &dwLen))
  678. {
  679. if (_pCacheEntryInfo->CacheEntryType & HTTP_1_1_CACHE_ENTRY)
  680. {
  681. INET_ASSERT (dwLen);
  682. dwLen = wsprintf(szHeader, "%s %s", HTTP_IF_MODIFIED_SINCE_SZ, szBuf);
  683. }
  684. else
  685. {
  686. dwLen = wsprintf(szHeader, "%s %s; length=%d", HTTP_IF_MODIFIED_SINCE_SZ,
  687. szBuf, _pCacheEntryInfo->dwSizeLow);
  688. }
  689. fResult = HttpAddRequestHeadersA(_hRequest,
  690. szHeader,
  691. dwLen,
  692. WINHTTP_ADDREQ_FLAG_ADD);
  693. }
  694. }
  695. // Only HTTP 1.1 support the ETag header
  696. if (!(_pCacheEntryInfo->CacheEntryType & HTTP_1_1_CACHE_ENTRY))
  697. {
  698. fResult = TRUE;
  699. }
  700. else
  701. {
  702. // Look for the ETag header
  703. TCHAR szOutBuf[256];
  704. TCHAR szHeader[256 + HTTP_IF_NONE_MATCH_LEN];
  705. DWORD dwOutBufLen = 256;
  706. DWORD dwHeaderLen;
  707. // If the ETag header is present, then add the "if-range: <etag>" header
  708. if (HttpQueryInfoA(_hRequest, WINHTTP_QUERY_ETAG, NULL, szOutBuf, &dwOutBufLen, NULL))
  709. {
  710. dwHeaderLen = wsprintf(szHeader, "%s %s", HTTP_IF_NONE_MATCH_SZ, szOutBuf);
  711. fResult = HttpAddRequestHeadersA(_hRequest,
  712. szHeader,
  713. dwHeaderLen,
  714. WINHTTP_ADDREQ_FLAG_ADD_IF_NEW);
  715. }
  716. }
  717. DEBUG_LEAVE(fResult);
  718. return fResult;
  719. }
  720. PRIVATE PRIVATE VOID HTTPCACHE_REQUEST::CalculateTimeStampsForCache()
  721. /*++
  722. Routine Description:
  723. extracts timestamps from the http response. If the timestamps don't exist,
  724. does the default thing. has additional goodies like checking for expiry etc.
  725. Side Effects:
  726. The calculated time stamps values are saved as private members
  727. _ftLastModTime, _ftExpiryTime, _ftPostCheckTime, _fHasExpiry,
  728. _fHasLastModTime, _fHasPostCheck, and _fMustRevalidate.
  729. Return Value:
  730. NONE
  731. --*/
  732. {
  733. DEBUG_ENTER((DBG_CACHE,
  734. None,
  735. "HTTPCACHE_REQUEST::CalculateTimeStampsForCache",
  736. NULL
  737. ));
  738. TCHAR buf[512];
  739. LPSTR lpszBuf;
  740. BOOL fRet = FALSE;
  741. DWORD dwLen, index = 0;
  742. BOOL fPostCheck = FALSE;
  743. BOOL fPreCheck = FALSE;
  744. FILETIME ftPreCheckTime;
  745. FILETIME ftPostCheckTime;
  746. // reset the private variables
  747. _fHasLastModTime = FALSE;
  748. _fHasExpiry = FALSE;
  749. _fHasPostCheck = FALSE;
  750. _fMustRevalidate = FALSE;
  751. // Do we need to enter a critical section?
  752. // _ResponseHeaders.LockHeaders();
  753. // Determine if a Cache-Control: max-age header exists. If so, calculate expires
  754. // time from current time + max-age minus any delta indicated by Age:
  755. //
  756. // we really want all the post-fetch stuff works with 1.0 proxy
  757. // so we loose our grip a little bit here: enable all Cache-Control
  758. // max-age work with 1.0 response.
  759. //
  760. //if (IsResponseHttp1_1())
  761. CHAR *ptr, *pToken;
  762. INT nDeltaSecsPostCheck = 0;
  763. INT nDeltaSecsPreCheck = 0;
  764. BOOL fResult;
  765. DWORD dwError;
  766. while (1)
  767. {
  768. // Scan headers for Cache-Control: max-age header.
  769. dwLen = sizeof(buf);
  770. fResult = HttpQueryInfoA(_hRequest,
  771. WINHTTP_QUERY_CACHE_CONTROL,
  772. NULL,
  773. buf,
  774. &dwLen,
  775. &index);
  776. if (fResult == TRUE)
  777. dwError = ERROR_SUCCESS;
  778. else
  779. dwError = GetLastError();
  780. switch (dwError)
  781. {
  782. case ERROR_SUCCESS:
  783. buf[dwLen] = '\0';
  784. pToken = ptr = buf;
  785. // Parse a token from the string; test for sub headers.
  786. while (pToken = StrTokExA(&ptr, ",")) // <<-- Really test this out, used StrTokEx before
  787. {
  788. SKIPWS(pToken);
  789. if (strnicmp(POSTCHECK_SZ, pToken, POSTCHECK_LEN) == 0)
  790. {
  791. pToken += POSTCHECK_LEN;
  792. SKIPWS(pToken);
  793. if (*pToken != '=')
  794. break;
  795. pToken++;
  796. SKIPWS(pToken);
  797. nDeltaSecsPostCheck = atoi(pToken);
  798. // Calculate post fetch time
  799. GetCurrentGmtTime(&ftPostCheckTime);
  800. AddLongLongToFT(&ftPostCheckTime, (nDeltaSecsPostCheck * (LONGLONG) 10000000));
  801. fPostCheck = TRUE;
  802. }
  803. else if (strnicmp(PRECHECK_SZ, pToken, PRECHECK_LEN) == 0)
  804. {
  805. // found
  806. pToken += PRECHECK_LEN;
  807. SKIPWS(pToken);
  808. if (*pToken != '=')
  809. break;
  810. pToken++;
  811. SKIPWS(pToken);
  812. nDeltaSecsPreCheck = atoi(pToken);
  813. // Calculate pre fetch time (overwrites ftExpire )
  814. GetCurrentGmtTime(&ftPreCheckTime);
  815. AddLongLongToFT(&ftPreCheckTime, (nDeltaSecsPreCheck * (LONGLONG) 10000000));
  816. fPreCheck = TRUE;
  817. }
  818. else if (strnicmp(MAX_AGE_SZ, pToken, MAX_AGE_LEN) == 0)
  819. {
  820. // Found max-age. Convert to integer form.
  821. // Parse out time in seconds, text and convert.
  822. pToken += MAX_AGE_LEN;
  823. SKIPWS(pToken);
  824. if (*pToken != '=')
  825. break;
  826. pToken++;
  827. SKIPWS(pToken);
  828. INT nDeltaSecs = atoi(pToken);
  829. INT nAge;
  830. // See if an Age: header exists.
  831. // Using a local index variable:
  832. DWORD indexAge = 0;
  833. dwLen = sizeof(INT)+1;
  834. if (HttpQueryInfoA(_hRequest,
  835. HTTP_QUERY_AGE | HTTP_QUERY_FLAG_NUMBER,
  836. NULL,
  837. &nAge,
  838. &dwLen,
  839. &indexAge))
  840. {
  841. // Found Age header. Convert and subtact from max-age.
  842. // If less or = 0, attempt to get expires header.
  843. nAge = ((nAge < 0) ? 0 : nAge);
  844. nDeltaSecs -= nAge;
  845. if (nDeltaSecs <= 0)
  846. // The server (or some caching intermediary) possibly sent an incorrectly
  847. // calculated header. Use "Expires", if no "max-age" directives at higher indexes.
  848. // Note: This behaviour could cause a situation where the "pre-check"
  849. // and "post-check" are picked up from the current index, and "max-age" is
  850. // picked up from a higher index. "pre-check" and "post-check" are IE 5.x
  851. // extensions, and generally not bunched together with "max-age", so this
  852. // should work fine. More info on "pre-check" and "post-check":
  853. // <http://msdn.microsoft.com/workshop/author/perf/perftips.asp#Use_Cache-Control_Extensions>
  854. continue;
  855. }
  856. // Calculate expires time from max age.
  857. GetCurrentGmtTime(&_ftExpiryTime);
  858. AddLongLongToFT(&_ftExpiryTime, (nDeltaSecs * (LONGLONG) 10000000));
  859. fRet = TRUE;
  860. }
  861. else if (strnicmp(MUST_REVALIDATE_SZ, pToken, MUST_REVALIDATE_LEN) == 0)
  862. {
  863. pToken += MUST_REVALIDATE_LEN;
  864. SKIPWS(pToken);
  865. if (*pToken == 0 || *pToken == ',')
  866. _fMustRevalidate = TRUE;
  867. }
  868. }
  869. // If an expires time has been found, break switch.
  870. if (fRet)
  871. break;
  872. // Need to bump up index to prevent possibility of never-ending outer while(1) loop.
  873. // Otherwise, on exit from inner while, we could be stuck here reading the
  874. // Cache-Control at the same index.
  875. // HttpQueryInfoA(WINHTTP_QUERY_CACHE_CONTROL, ...) will return either the next index,
  876. // or an error, and we'll be good to go:
  877. index++;
  878. continue;
  879. case ERROR_INSUFFICIENT_BUFFER:
  880. index++;
  881. continue;
  882. default:
  883. break; // no more Cache-Control headers.
  884. }
  885. //
  886. // pre-post fetch headers must come in pair, also
  887. // pre fetch header overwrites the expire
  888. // and make sure postcheck < precheck
  889. //
  890. if( fPreCheck && fPostCheck &&
  891. ( nDeltaSecsPostCheck < nDeltaSecsPreCheck ) )
  892. {
  893. fRet = TRUE;
  894. _ftPostCheckTime = ftPostCheckTime;
  895. _ftExpiryTime = ftPreCheckTime;
  896. _fHasPostCheck = TRUE;
  897. if( nDeltaSecsPostCheck == 0 &&
  898. !(_dwCacheFlags & CACHE_FLAG_BGUPDATE) )
  899. {
  900. //
  901. // "post-check = 0"
  902. // this page has already passed the lazy update time
  903. // this means server wants us to do background update
  904. // after the first download
  905. //
  906. // (bg fsm will be created at the end of the cache write)
  907. //
  908. _fLazyUpdate = TRUE;
  909. }
  910. }
  911. else
  912. {
  913. fPreCheck = FALSE;
  914. fPostCheck = FALSE;
  915. }
  916. break; // no more Cache-Control headers.
  917. }
  918. // If no expires time is calculated from max-age, check for expires header.
  919. if (!fRet)
  920. {
  921. dwLen = sizeof(buf) - 1;
  922. index = 0;
  923. if (HttpQueryInfoA(_hRequest, HTTP_QUERY_EXPIRES, NULL, buf, &dwLen, &index))
  924. {
  925. fRet = FParseHttpDate(&_ftExpiryTime, buf);
  926. //
  927. // as per HTTP spec, if the expiry time is incorrect, then the page is
  928. // considered to have expired
  929. //
  930. if (!fRet)
  931. {
  932. GetCurrentGmtTime(&_ftExpiryTime);
  933. AddLongLongToFT(&_ftExpiryTime, (-1)*ONE_HOUR_DELTA); // subtract 1 hour
  934. fRet = TRUE;
  935. }
  936. }
  937. }
  938. // We found or calculated a valid expiry time, let us check it against the
  939. // server date if possible
  940. FILETIME ft;
  941. dwLen = sizeof(buf) - 1;
  942. index = 0;
  943. if (HttpQueryInfoA(_hRequest, HTTP_QUERY_DATE, NULL, buf, &dwLen, &index) == ERROR_SUCCESS
  944. && FParseHttpDate(&ft, buf))
  945. {
  946. // we found a valid Data: header
  947. // if the expires: date is less than or equal to the Date: header
  948. // then we put an expired timestamp on this item.
  949. // Otherwise we let it be the same as was returned by the server.
  950. // This may cause problems due to mismatched clocks between
  951. // the client and the server, but this is the best that can be done.
  952. // Calulating an expires offset from server date causes pages
  953. // coming from proxy cache to expire later, because proxies
  954. // do not change the date: field even if the reponse has been
  955. // sitting the proxy cache for days.
  956. // This behaviour is as-per the HTTP spec.
  957. if (FT2LL(_ftExpiryTime) <= FT2LL(ft))
  958. {
  959. GetCurrentGmtTime(&_ftExpiryTime);
  960. AddLongLongToFT(&_ftExpiryTime, (-1)*ONE_HOUR_DELTA); // subtract 1 hour
  961. }
  962. }
  963. _fHasExpiry = fRet;
  964. if (!fRet)
  965. {
  966. _ftExpiryTime.dwLowDateTime = 0;
  967. _ftExpiryTime.dwHighDateTime = 0;
  968. }
  969. fRet = FALSE;
  970. dwLen = sizeof(buf) - 1;
  971. index = 0;
  972. if (HttpQueryInfoA(_hRequest, HTTP_QUERY_LAST_MODIFIED, NULL, buf, &dwLen, &index))
  973. {
  974. DEBUG_PRINT(CACHE,
  975. INFO,
  976. ("Last Modified date is: %q\n",
  977. buf
  978. ));
  979. fRet = FParseHttpDate(&_ftLastModTime, buf);
  980. if (!fRet)
  981. {
  982. DEBUG_PRINT(CACHE,
  983. ERROR,
  984. ("FParseHttpDate() returns FALSE\n"
  985. ));
  986. }
  987. }
  988. _fHasLastModTime = fRet;
  989. if (!fRet)
  990. {
  991. _ftLastModTime.dwLowDateTime = 0;
  992. _ftLastModTime.dwHighDateTime = 0;
  993. }
  994. DEBUG_LEAVE(0);
  995. }