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.

3074 lines
88 KiB

  1. /*++
  2. Copyright (c) 1997 Microsoft Corporation
  3. Module Name:
  4. cache.cxx
  5. Abstract:
  6. Contains HTTP cache-related functions. Cache functions pulled out of
  7. read.cxx and send.cxx
  8. Contents:
  9. HTTP_REQUEST_HANDLE_OBJECT::FCanWriteToCache
  10. HTTP_REQUEST_HANDLE_OBJECT::FAddIfModifiedSinceHeader
  11. HTTP_REQUEST_HANDLE_OBJECT::AddHeaderIfEtagFound
  12. HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheRetrieval
  13. HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheWrite
  14. HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePreNetIO
  15. HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePostNetIO
  16. HTTP_REQUEST_HANDLE_OBJECT::ResumePartialDownload
  17. HTTP_REQUEST_HANDLE_OBJECT::AddTimestampsFromCacheToResponseHeaders
  18. HTTP_REQUEST_HANDLE_OBJECT::AddTimeHeader
  19. HTTP_REQUEST_HANDLE_OBJECT::IsPartialResponseCacheable
  20. HTTP_REQUEST_HANDLE_OBJECT::LocalEndCacheWrite
  21. HTTP_REQUEST_HANDLE_OBJECT::GetTimeStampsForCache
  22. (FExcludedMimeType)
  23. (FilterHeaders)
  24. Author:
  25. Richard L Firth (rfirth) 05-Dec-1997
  26. Environment:
  27. Win32 user-mode DLL
  28. Revision History:
  29. 05-Dec-1997 rfirth
  30. Created
  31. --*/
  32. #include <wininetp.h>
  33. #include <perfdiag.hxx>
  34. #include "httpp.h"
  35. //
  36. // private prototypes
  37. //
  38. PRIVATE
  39. BOOL
  40. FExcludedMimeType(
  41. IN LPSTR lpszMimeType,
  42. IN DWORD dwMimeTypeSize
  43. );
  44. PRIVATE
  45. VOID
  46. FilterHeaders(
  47. IN LPSTR lpszHeaderInfo,
  48. OUT LPDWORD lpdwHeaderLen
  49. );
  50. //
  51. // static data
  52. //
  53. LPCSTR rgszExcludedMimeTypes[] = {
  54. "multipart/mixed",
  55. "multipart/x-mixed-replace"
  56. };
  57. const DWORD rgdwExcludedMimeTypeSizes[] = {
  58. sizeof("multipart/mixed") - 1,
  59. sizeof("multipart/x-mixed-replace") - 1
  60. };
  61. static const char szDefaultExtension[] = "txt";
  62. LPSTR rgszExcludeHeaders[] = {
  63. HTTP_SET_COOKIE_SZ,
  64. HTTP_LAST_MODIFIED_SZ,
  65. HTTP_SERVER_SZ,
  66. HTTP_DATE_SZ,
  67. HTTP_EXPIRES_SZ,
  68. HTTP_CONNECTION_SZ,
  69. HTTP_PROXY_CONNECTION_SZ,
  70. HTTP_VIA_SZ,
  71. HTTP_VARY_SZ,
  72. HTTP_AGE_SZ,
  73. HTTP_CACHE_CONTROL_SZ,
  74. HTTP_ACCEPT_RANGES_SZ,
  75. HTTP_CONTENT_DISPOSITION_SZ
  76. };
  77. const char vszUserNameHeader[4] = "~U:";
  78. //
  79. // HTTP Request Handle Object methods
  80. //
  81. BOOL
  82. HTTP_REQUEST_HANDLE_OBJECT::FCanWriteToCache(
  83. VOID
  84. )
  85. /*++
  86. Routine Description:
  87. Determines if we can write this file to the cache
  88. Arguments:
  89. None.
  90. Return Value:
  91. BOOL
  92. --*/
  93. {
  94. DEBUG_ENTER((DBG_HTTP,
  95. Bool,
  96. "HTTP_REQUEST_HANDLE_OBJECT::FCanWriteToCache",
  97. NULL
  98. ));
  99. PERF_LOG(PE_CACHE_WRITE_CHECK_START);
  100. BOOL ok = FALSE;
  101. BOOL fForceToCache = FALSE;
  102. BOOL fCheckNeedFile = FALSE;
  103. BOOL fVary = FALSE;
  104. BOOL fContentEnc = FALSE;
  105. //
  106. // Set fNoCache if there is pragma: no-cache
  107. //
  108. BOOL fNoCache = FALSE;
  109. DWORD length, index;
  110. LPSTR lpszBuf;
  111. _ResponseHeaders.LockHeaders();
  112. if (GetOpenFlags() & INTERNET_FLAG_SECURE)
  113. {
  114. SetPerUserItem(TRUE);
  115. //
  116. // Determine if there are any Pragma: no-cache headers.
  117. //
  118. index = 0;
  119. while ( FastQueryResponseHeader(HTTP_QUERY_PRAGMA,
  120. (LPVOID *) &lpszBuf,
  121. &length,
  122. index) == ERROR_SUCCESS )
  123. {
  124. if (length == NO_CACHE_LEN &&
  125. strnicmp(NO_CACHE_SZ, lpszBuf, NO_CACHE_LEN) == 0)
  126. {
  127. fNoCache = TRUE;
  128. break;
  129. }
  130. index++;
  131. }
  132. if (fNoCache)
  133. {
  134. if(GlobalBypassSSLNoCacheCheck)
  135. {
  136. goto check_need_file;
  137. }
  138. // If server disabled caching over SSL, don't even create
  139. // a file, let alone commit it to the cache. Game over.
  140. BETA_LOG (DOWNLOAD_NO_FILE);
  141. goto quit;
  142. }
  143. //
  144. // If we've disabled caching for SSL servers, consider creating
  145. // a download file if the client insists upon it.
  146. //
  147. if (GlobalDisableSslCaching)
  148. {
  149. goto check_need_file;
  150. }
  151. }
  152. //
  153. // Also set fNoCache if there is Cache-Control: no-cache or no-store header,
  154. // if there is a Cache-Control: private header and we're *not* on NT with user profiles,
  155. // or any Vary: headers. These are only checked for HTTP 1.1 servers.
  156. //
  157. if (IsResponseHttp1_1())
  158. {
  159. CHAR *ptr, *pToken;
  160. index = 0;
  161. // Scan for Cache-Control header.
  162. while (FastQueryResponseHeader(HTTP_QUERY_CACHE_CONTROL,
  163. (LPVOID *) &lpszBuf,
  164. &length,
  165. index) == ERROR_SUCCESS)
  166. {
  167. // Check for no-cache or no-store or private.
  168. CHAR chTemp = lpszBuf[length];
  169. lpszBuf[length] = '\0';
  170. pToken = ptr = lpszBuf;
  171. // Parse a token from the string; test for sub headers.
  172. while (*pToken != '\0')
  173. {
  174. SKIPWS(pToken);
  175. // no-cache, no-store.
  176. if (strnicmp(NO_CACHE_SZ, pToken, NO_CACHE_LEN) == 0)
  177. {
  178. fNoCache = TRUE;
  179. break;
  180. }
  181. if( strnicmp(NO_STORE_SZ, pToken, NO_STORE_LEN) == 0)
  182. {
  183. fNoCache = TRUE;
  184. fCheckNeedFile = TRUE;
  185. }
  186. // private.
  187. if (strnicmp(PRIVATE_SZ, pToken, PRIVATE_LEN) == 0)
  188. {
  189. SetPerUserItem(TRUE);
  190. }
  191. while (*pToken != '\0')
  192. {
  193. if ( *pToken == ',')
  194. {
  195. pToken++;
  196. break;
  197. }
  198. pToken++;
  199. }
  200. } // while (*pToken != '\0')
  201. //
  202. // We've finished parsing it, now return our terminator back to its proper place
  203. //
  204. lpszBuf[length] = chTemp;
  205. // If fNoCache, we're done. Break out of switch.
  206. if (fNoCache)
  207. break;
  208. index++;
  209. } // while FastQueryResponseHeader == ERROR_SUCCESS
  210. if (fNoCache)
  211. {
  212. if( fCheckNeedFile )
  213. {
  214. // cache-control: no store
  215. goto check_need_file;
  216. }
  217. else
  218. if ((GetOpenFlags() & INTERNET_FLAG_SECURE) && !GlobalBypassSSLNoCacheCheck)
  219. {
  220. // If server disabled caching over SSL, don't even create
  221. // a file, let alone commit it to the cache. Game over.
  222. BETA_LOG (DOWNLOAD_NO_FILE);
  223. goto quit;
  224. }
  225. else
  226. {
  227. //
  228. // This is not SSL/PCT, so don't cache but consider creating a
  229. // download file if one was requested.
  230. //
  231. goto check_need_file;
  232. }
  233. }
  234. // Finally, check if any Vary: headers exist, EXCEPT "Vary: User-Agent"
  235. index = 0;
  236. if (FastQueryResponseHeader(HTTP_QUERY_VARY,
  237. (LPVOID *) &lpszBuf,
  238. &length,
  239. index) == ERROR_SUCCESS
  240. && !(length == USER_AGENT_LEN
  241. && !strnicmp (lpszBuf, USER_AGENT_SZ, length)) )
  242. {
  243. // content-encoding?
  244. if (FastQueryResponseHeader(HTTP_QUERY_CONTENT_ENCODING,
  245. (LPVOID *) &lpszBuf,
  246. &length,
  247. index) == ERROR_SUCCESS )
  248. {
  249. fContentEnc = TRUE;
  250. }
  251. fVary = TRUE;
  252. goto check_need_file;
  253. }
  254. }
  255. if (GetCacheFlags() & INTERNET_FLAG_NO_CACHE_WRITE)
  256. {
  257. goto check_need_file;
  258. }
  259. //
  260. // accept HTTP/1.0 or downlevel server responses
  261. //
  262. if ((GetStatusCode() == HTTP_STATUS_OK) || (GetStatusCode() == 0))
  263. {
  264. if (FastQueryResponseHeader(HTTP_QUERY_CONTENT_TYPE, (LPVOID *) &lpszBuf, &length, 0) == ERROR_SUCCESS)
  265. {
  266. if (FExcludedMimeType(lpszBuf, length))
  267. {
  268. DEBUG_PRINT(CACHE,
  269. INFO,
  270. ("%s Mime Excluded from caching\n",
  271. lpszBuf
  272. ));
  273. goto check_need_file;
  274. }
  275. }
  276. //
  277. // BUGBUG should we also check for size and keep an upper bound?
  278. //
  279. ok = TRUE;
  280. goto quit;
  281. }
  282. else
  283. {
  284. INTERNET_SCHEME schemeType = GetSchemeType();
  285. if ((schemeType == INTERNET_SCHEME_FTP)
  286. || (schemeType == INTERNET_SCHEME_GOPHER))
  287. {
  288. ok = TRUE;
  289. goto quit;
  290. }
  291. }
  292. check_need_file:
  293. INET_ASSERT (!ok);
  294. if (GetCacheFlags() & INTERNET_FLAG_NEED_FILE
  295. || GetCacheFlags () & INTERNET_FLAG_HYPERLINK
  296. || (GlobalBypassSSLNoCacheCheck && fNoCache))
  297. {
  298. //
  299. // Create a download file but don't commit it to the cache.
  300. //
  301. BETA_LOG (DOWNLOAD_FILE_NEEDED);
  302. fForceToCache = !ok;
  303. ok = TRUE;
  304. if( !fContentEnc )
  305. {
  306. // if content-enc, the client will write to cache...
  307. // so we should not delete the data file
  308. SetDeleteDataFile();
  309. }
  310. DEBUG_PRINT(CACHE, INFO, ("don't commit file\n"));
  311. }
  312. else
  313. {
  314. BETA_LOG (DOWNLOAD_FILE_NOT_NEEDED);
  315. }
  316. quit:
  317. if ((!ok || fForceToCache) && !fVary)
  318. {
  319. SetCacheWriteDisabled();
  320. // Deleting originally cached files if (& only if) the server sends cache-control headers.
  321. // Bugs 98644 and 101578
  322. if (fNoCache)
  323. {
  324. _RequestHeaders.LockHeaders();
  325. // In 5.0, Rosebud worked like this:
  326. // 1. A call to CommitUrlCacheEntry with a file size of about 11 bytes.
  327. // 2. A call to HttpSendRequest.
  328. // 3. A call to DeleteUrlCacheEntry that succeeds.
  329. // 4. Another call to CommitUrlCacheEntry for the same file, except it's grown.
  330. // 5. Repeat 3 and 4 until file is downloaded.
  331. // However, we made a change so that if the server sends "no-cache", we'll
  332. // delete any cache entry during the call in (2). This caused Rosebud to break
  333. // We don't want to go back to the original behaviour, since that would introduce
  334. // IE 94486. So we look for a Translate: F header to guide us.
  335. DWORD dwIndex = 0;
  336. BOOL fFound = FALSE;
  337. TCHAR szValue[MAX_PATH];
  338. DWORD ccValue = ARRAY_ELEMENTS(szValue);
  339. while (ERROR_SUCCESS==QueryRequestHeader(
  340. "Translate",
  341. ARRAY_ELEMENTS("Translate")-1,
  342. (LPVOID)szValue,
  343. &ccValue,
  344. 0,
  345. &dwIndex))
  346. {
  347. if (!StrCmpI(szValue, "F"))
  348. {
  349. fFound = TRUE;
  350. break;
  351. }
  352. ccValue = ARRAY_ELEMENTS(szValue);
  353. }
  354. _RequestHeaders.UnlockHeaders();
  355. if (!fFound)
  356. {
  357. DeleteUrlCacheEntry(GetOriginalUrl());
  358. }
  359. }
  360. }
  361. _ResponseHeaders.UnlockHeaders();
  362. PERF_LOG(PE_CACHE_WRITE_CHECK_END);
  363. DEBUG_LEAVE(ok);
  364. return ok;
  365. }
  366. BOOL
  367. HTTP_REQUEST_HANDLE_OBJECT::FAddIfModifiedSinceHeader(
  368. IN LPCACHE_ENTRY_INFO lpCEI
  369. )
  370. /*++
  371. Routine Description:
  372. description-of-function.
  373. Arguments:
  374. lpCEI -
  375. Return Value:
  376. BOOL
  377. --*/
  378. {
  379. DEBUG_ENTER((DBG_HTTP,
  380. Bool,
  381. "HTTP_REQUEST_HANDLE_OBJECT::FAddIfModifiedSinceHeader",
  382. "%#x",
  383. lpCEI
  384. ));
  385. PERF_ENTER(FAddIfModifiedSinceHeader);
  386. char buff[64], buffh[256];
  387. DWORD dwLen, error;
  388. BOOL success = FALSE;
  389. INET_ASSERT (FT2LL(lpCEI->LastModifiedTime));
  390. dwLen = sizeof(buff);
  391. if (FFileTimetoHttpDateTime(&(lpCEI->LastModifiedTime), buff, &dwLen))
  392. {
  393. LPSTR pszBuf;
  394. if (lpCEI->CacheEntryType & HTTP_1_1_CACHE_ENTRY)
  395. {
  396. INET_ASSERT (dwLen);
  397. pszBuf = buff;
  398. }
  399. else
  400. {
  401. dwLen = wsprintf(buffh, "%s; length=%d", buff, lpCEI->dwSizeLow);
  402. pszBuf = buffh;
  403. }
  404. DEBUG_PRINT(CACHE,
  405. INFO,
  406. ("%s %s - empty\n",
  407. GlobalKnownHeaders[HTTP_QUERY_IF_MODIFIED_SINCE].Text,
  408. buffh
  409. ));
  410. error = ReplaceRequestHeader(HTTP_QUERY_IF_MODIFIED_SINCE,
  411. pszBuf,
  412. dwLen,
  413. 0, // no index
  414. ADD_HEADER_IF_NEW
  415. );
  416. if ((error == ERROR_SUCCESS) || (error == ERROR_HTTP_HEADER_ALREADY_EXISTS))
  417. {
  418. if (error == ERROR_SUCCESS)
  419. {
  420. // we added the header on behalf of the app. This is equivalent
  421. // to the app having specified INTERNET_FLAG_RESYNCHRONIZE
  422. SetAutoSync();
  423. }
  424. success = TRUE;
  425. }
  426. }
  427. PERF_LEAVE(FAddIfModifiedSinceHeader);
  428. DEBUG_LEAVE(success);
  429. return success;
  430. }
  431. BOOL
  432. HTTP_REQUEST_HANDLE_OBJECT::AddHeaderIfEtagFound(
  433. IN LPCACHE_ENTRY_INFO lpCEI
  434. )
  435. /*++
  436. Routine Description:
  437. Adds if-modified-since header if an etag was present in the cache entry headers.
  438. Arguments:
  439. lpCEI -
  440. Return Value:
  441. BOOL
  442. --*/
  443. {
  444. if (!(lpCEI->CacheEntryType & HTTP_1_1_CACHE_ENTRY))
  445. return TRUE;
  446. // BUGBUG - always parsing, use flag.
  447. CHAR buf[512];
  448. DWORD nIndex = 0, cbBuf = 512, dwError = ERROR_SUCCESS;
  449. HTTP_HEADER_PARSER hp((CHAR*) lpCEI->lpHeaderInfo, lpCEI->dwHeaderInfoSize);
  450. if (hp.FindHeader((CHAR*) lpCEI->lpHeaderInfo, HTTP_QUERY_ETAG,
  451. 0, buf, &cbBuf, &nIndex) == ERROR_SUCCESS)
  452. {
  453. DWORD dwQueryIndex;
  454. if (lpCEI->CacheEntryType & SPARSE_CACHE_ENTRY)
  455. {
  456. dwQueryIndex = HTTP_QUERY_IF_RANGE;
  457. }
  458. else
  459. {
  460. dwQueryIndex = HTTP_QUERY_IF_NONE_MATCH;
  461. }
  462. dwError = ReplaceRequestHeader(dwQueryIndex,
  463. buf,
  464. cbBuf,
  465. 0,
  466. ADD_HEADER_IF_NEW
  467. );
  468. }
  469. if ((dwError == ERROR_SUCCESS) || (dwError == ERROR_HTTP_HEADER_ALREADY_EXISTS))
  470. return TRUE;
  471. return FALSE;
  472. }
  473. DWORD
  474. HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheRetrieval(
  475. IN BOOL bReset,
  476. IN BOOL bOffline,
  477. IN BOOL bNoRetrieveIfExist
  478. )
  479. /*++
  480. Routine Description:
  481. Starts retrieving data for this object from the cache
  482. Arguments:
  483. bReset - if TRUE, forcefully reset the (keep-alive) connection
  484. Return Value:
  485. DWORD
  486. Success - ERROR_SUCCESS
  487. Failure - ERROR_NOT_ENOUGH_MEMORY
  488. --*/
  489. {
  490. DEBUG_ENTER((DBG_HTTP,
  491. Dword,
  492. "HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheRetrieval",
  493. "%B, %B",
  494. bReset,
  495. bOffline
  496. ));
  497. PERF_LOG(PE_CACHE_RETRIEVE_START);
  498. DWORD error;
  499. LPSTR lpHeaders = NULL;
  500. if (bOffline)
  501. {
  502. if (!IsOffline() && IsCacheReadDisabled())
  503. {
  504. error = ERROR_FILE_NOT_FOUND;
  505. goto quit;
  506. }
  507. if( !bNoRetrieveIfExist )
  508. error = UrlCacheRetrieve (TRUE);
  509. else
  510. error = ERROR_SUCCESS;
  511. if (error != ERROR_SUCCESS)
  512. goto quit2;
  513. }
  514. INET_ASSERT(_hCacheStream && _pCacheEntryInfo);
  515. //
  516. // RecordCacheRetrieval() will set end-of-file if it succeeds
  517. //
  518. error = RecordCacheRetrieval (_pCacheEntryInfo);
  519. if (error != ERROR_SUCCESS)
  520. {
  521. UrlCacheUnlock();
  522. goto quit2;
  523. }
  524. if (bOffline && lstrcmp(_pCacheEntryInfo->lpszSourceUrlName, GetCacheKey()))
  525. {
  526. // Simulate a redirect to the client.
  527. InternetIndicateStatusString
  528. (INTERNET_STATUS_REDIRECT, _pCacheEntryInfo->lpszSourceUrlName);
  529. // The cache translated through a redirect.
  530. FreeSecondaryCacheKey (); // POST redirects must be to GET
  531. SetURL (_pCacheEntryInfo->lpszSourceUrlName);
  532. }
  533. if (bOffline &&
  534. (_pCacheEntryInfo->CacheEntryType & MUST_REVALIDATE_CACHE_ENTRY))
  535. {
  536. INET_ASSERT (_pCacheEntryInfo->CacheEntryType & HTTP_1_1_CACHE_ENTRY);
  537. // Offline mode. Check for a Cache-Control: must-revalidate header.
  538. // If so, allow cache data to be retrieved only if not expired.
  539. FILETIME ftCurrentTime;
  540. GetCurrentGmtTime(&ftCurrentTime);
  541. LONGLONG qwExpire = FT2LL(_pCacheEntryInfo->ExpireTime);
  542. LONGLONG qwCurrent = FT2LL(ftCurrentTime);
  543. if (qwCurrent > qwExpire)
  544. {
  545. error = ERROR_FILE_NOT_FOUND;
  546. goto quit;
  547. }
  548. else
  549. {
  550. goto check_if_modified_since;
  551. }
  552. }
  553. check_if_modified_since:
  554. if (!IsOffline() && IsCacheReadDisabled())
  555. {
  556. //
  557. // We are in offline mode. (Really should assert this.)
  558. // Allow cache data to be retrieved only if last-modified time is
  559. // STRICTLY greater than if-modified-since time added by client.
  560. //
  561. LONGLONG qwLastModified = FT2LL(_pCacheEntryInfo->LastModifiedTime);
  562. LONGLONG qwIfModifiedSince;
  563. SYSTEMTIME stIfModifiedSince;
  564. DWORD length = sizeof(stIfModifiedSince);
  565. DWORD index = 0;
  566. _RequestHeaders.LockHeaders();
  567. error = QueryRequestHeader(HTTP_QUERY_IF_MODIFIED_SINCE,
  568. &stIfModifiedSince,
  569. &length,
  570. HTTP_QUERY_FLAG_SYSTEMTIME,
  571. &index
  572. );
  573. _RequestHeaders.UnlockHeaders();
  574. if (error != ERROR_SUCCESS)
  575. {
  576. // It's theoretically possible the client was redirected,
  577. // removed i-m-s on redirect callback, then went offline.
  578. error = ERROR_FILE_NOT_FOUND;
  579. goto quit;
  580. }
  581. if (!SystemTimeToFileTime(&stIfModifiedSince,
  582. (FILETIME*)&qwIfModifiedSince))
  583. {
  584. error = ERROR_FILE_NOT_FOUND;
  585. goto quit;
  586. }
  587. if (qwLastModified <= qwIfModifiedSince)
  588. {
  589. error = ERROR_FILE_NOT_FOUND;
  590. goto quit;
  591. }
  592. }
  593. // allocate buffer for headers
  594. lpHeaders = (LPSTR)ALLOCATE_FIXED_MEMORY(_pCacheEntryInfo->dwHeaderInfoSize);
  595. if (!lpHeaders)
  596. {
  597. error = ERROR_NOT_ENOUGH_MEMORY;
  598. goto quit;
  599. }
  600. memcpy(lpHeaders, _pCacheEntryInfo->lpHeaderInfo, _pCacheEntryInfo->dwHeaderInfoSize);
  601. // we are hijacking the HTTP request object. If there is already
  602. // a pending network operation (we started a network transaction
  603. // but realised that we have the same data in cache (i.e. the
  604. // data has not expired since we last wrote it to cache)) then
  605. // we need to kill it
  606. if (bReset)
  607. {
  608. // if we have a keep-alive connection AND the server returned
  609. // 304 then the parameter can be FALSE so that we don't kill
  610. // the connection, just release it
  611. //
  612. // ResetObject() will reset end-of-file if it succeeds
  613. //
  614. // We DO NOT clear out the request headers so that the app can still
  615. // query them if necessary
  616. //dprintf(">>> resetting %s\n", GetURL());
  617. error = ResetObject(!(IsKeepAlive() && (GetBytesInSocket() == 0)), FALSE);
  618. if (error != ERROR_SUCCESS)
  619. goto quit;
  620. }
  621. error = CreateResponseHeaders(&lpHeaders, _pCacheEntryInfo->dwHeaderInfoSize);
  622. if (error != ERROR_SUCCESS)
  623. goto quit;
  624. error = AddTimestampsFromCacheToResponseHeaders(_pCacheEntryInfo);
  625. if (error != ERROR_SUCCESS)
  626. goto quit;
  627. /* Send notification about past cookie-events on this URL */
  628. InternetCookieHistory history;
  629. DWORD dwEntryType = _pCacheEntryInfo->CacheEntryType;
  630. history.fAccepted = (dwEntryType & COOKIE_ACCEPTED_CACHE_ENTRY);
  631. history.fLeashed = (dwEntryType & COOKIE_LEASHED_CACHE_ENTRY);
  632. history.fDowngraded = (dwEntryType & COOKIE_DOWNGRADED_CACHE_ENTRY);
  633. history.fRejected = (dwEntryType & COOKIE_REJECTED_CACHE_ENTRY);
  634. if (history.fAccepted ||
  635. history.fLeashed ||
  636. history.fDowngraded ||
  637. history.fRejected)
  638. InternetIndicateStatus (INTERNET_STATUS_COOKIE_HISTORY,
  639. (void*) &history,
  640. sizeof(history));
  641. /* Reevaluate P3P policy to generate policy notifications */
  642. ExtractSetCookieHeaders(NULL);
  643. // we have to set end-of-file again: RecordCacheRetrieval() set
  644. // it, then we wiped it out in ResetObject()
  645. // FYI: We set end-of-file because we have all the data in the
  646. // stream available locally; it doesn't mean that our virtual
  647. // stream pointer is positioned at the end of the file
  648. DEBUG_PRINT(CACHE, INFO, ("Found in the cache\n"));
  649. SetState(HttpRequestStateObjectData);
  650. SetFromCache();
  651. SetEndOfFile();
  652. SetHaveContentLength(TRUE);
  653. _VirtualCacheFileSize =
  654. _RealCacheFileSize =
  655. _BytesRemaining =
  656. _ContentLength = _pCacheEntryInfo->dwSizeLow;
  657. INET_ASSERT (error == ERROR_SUCCESS);
  658. quit:
  659. if (error != ERROR_SUCCESS)
  660. EndCacheRetrieval();
  661. if (lpHeaders)
  662. FREE_MEMORY(lpHeaders);
  663. quit2:
  664. PERF_LOG(PE_CACHE_RETRIEVE_END);
  665. DEBUG_LEAVE(error);
  666. return error;
  667. }
  668. BOOL
  669. HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheWrite(
  670. VOID
  671. )
  672. /*++
  673. Routine Description:
  674. Preps the cache for writing. Sets up the cache filename with an extension
  675. based on the MIME type and optionally writes the headers to the cache data
  676. stream
  677. Arguments:
  678. None.
  679. Return Value:
  680. BOOL
  681. TRUE - Success
  682. FALSE - Failure
  683. --*/
  684. {
  685. DEBUG_ENTER((DBG_HTTP,
  686. Bool,
  687. "HTTP_REQUEST_HANDLE_OBJECT::FHttpBeginCacheWrite",
  688. NULL
  689. ));
  690. PERF_ENTER(FHttpBeginCacheWrite);
  691. INET_ASSERT(!IsCacheReadInProgress());
  692. INET_ASSERT(!IsCacheWriteInProgress());
  693. PERF_TRACE(FHttpBeginCacheWrite, 1);
  694. _ResponseHeaders.LockHeaders();
  695. LPSTR lpszFileExtension = NULL;
  696. BOOL fIsUncertainMime = FALSE;
  697. char cExt[DEFAULT_MAX_EXTENSION_LENGTH + 1];
  698. char buf[256];
  699. DWORD dwLen, cbFileName, dwIndex;
  700. CHAR *ptr, *pToken, *pszFileName = NULL;
  701. CHAR szFileName[MAX_PATH];
  702. LPSTR lpszBuf;
  703. // Create a temp local cache file.
  704. // If we find a Content-Disposition header, parse out any filename and use it.
  705. if (QueryResponseHeader (HTTP_QUERY_CONTENT_DISPOSITION,
  706. buf, &(dwLen = sizeof(buf)), 0, &(dwIndex = 0) ) == ERROR_SUCCESS)
  707. {
  708. // Could have multiple tokens in it. Scan for the "filename" token.
  709. ptr = pToken = buf;
  710. while (ptr = StrTokEx2(&pToken, ";"))
  711. {
  712. // Skip any leading ws in token.
  713. SKIPWS(ptr);
  714. // Compare against "filename".
  715. if (!strnicmp(ptr, FILENAME_SZ, FILENAME_LEN))
  716. {
  717. // Found it.
  718. ptr += FILENAME_LEN;
  719. // Skip ws before '='.
  720. SKIPWS(ptr);
  721. // Must have '='
  722. if (*ptr == '=')
  723. {
  724. // Skip any ws after '=' and point
  725. // to beginning of the file name
  726. ptr++;
  727. SKIPWS(ptr);
  728. // Skip past any quotes
  729. if (*ptr == '\"')
  730. ptr++;
  731. SKIPWS(ptr);
  732. cbFileName = strlen(ptr);
  733. if (cbFileName)
  734. {
  735. // Ignore any trailing quote.
  736. if (ptr[cbFileName-1] == '\"')
  737. cbFileName--;
  738. memcpy(szFileName, ptr, cbFileName);
  739. szFileName[cbFileName] = '\0';
  740. pszFileName = szFileName;
  741. }
  742. }
  743. break;
  744. }
  745. }
  746. }
  747. // Either no Content-disposition header or filename not parsed.
  748. if (!pszFileName)
  749. {
  750. DWORD dwMimeLen;
  751. if ((FastQueryResponseHeader(HTTP_QUERY_CONTENT_ENCODING, (LPVOID *) &lpszBuf, &dwMimeLen, 0) == ERROR_SUCCESS) &&
  752. StrCmpNI(lpszBuf,"binary",6) )
  753. {
  754. // if there is content encoding, we should not use
  755. // content-type for file extension
  756. //Modifying this for bug 98611.
  757. //For 'binary' encoding use the Content-Type to find extension
  758. lpszFileExtension = NULL;
  759. }
  760. else if (FastQueryResponseHeader(HTTP_QUERY_CONTENT_TYPE, (LPVOID *) &lpszBuf, &dwMimeLen, 0) == ERROR_SUCCESS)
  761. {
  762. dwLen = sizeof(cExt);
  763. fIsUncertainMime = strnicmp(lpszBuf, "text/plain", dwMimeLen)==0;
  764. PERF_TRACE(FHttpBeginCacheWrite, 2);
  765. if (!fIsUncertainMime &&
  766. GetFileExtensionFromMimeType(lpszBuf, dwMimeLen, cExt, &dwLen))
  767. {
  768. //
  769. // get past the '.' because the cache expects it that way
  770. //
  771. lpszFileExtension = &cExt[1];
  772. }
  773. }
  774. //
  775. // if we couldn't get the MIME type or failed to map it then try to get
  776. // the file extension from the object name requested
  777. //
  778. if (lpszFileExtension == NULL)
  779. {
  780. dwLen = sizeof(cExt);
  781. PERF_TRACE(FHttpBeginCacheWrite, 3);
  782. lpszFileExtension = GetFileExtensionFromUrl(GetURL(), &dwLen);
  783. if (lpszFileExtension != NULL)
  784. {
  785. memcpy(cExt, lpszFileExtension, dwLen);
  786. cExt[dwLen] = '\0';
  787. lpszFileExtension = cExt;
  788. }
  789. PERF_TRACE(FHttpBeginCacheWrite, 4);
  790. }
  791. if ((lpszFileExtension == NULL) && fIsUncertainMime)
  792. {
  793. INET_ASSERT(sizeof(szDefaultExtension) < DEFAULT_MAX_EXTENSION_LENGTH);
  794. strcpy(cExt, szDefaultExtension);
  795. lpszFileExtension = cExt;
  796. }
  797. }
  798. //
  799. // BUGBUG - BeginCacheWrite() wants the estimated file size, but we just
  800. // give it 0 for now
  801. //
  802. //dwError = pRequest->BeginCacheWrite(dwLen, lpszFileExtension);
  803. PERF_TRACE(FHttpBeginCacheWrite, 5);
  804. DWORD dwError = BeginCacheWrite(0, lpszFileExtension, pszFileName);
  805. PERF_TRACE(FHttpBeginCacheWrite, 6);
  806. _ResponseHeaders.UnlockHeaders();
  807. PERF_LEAVE(FHttpBeginCacheWrite);
  808. BOOL success = (dwError == ERROR_SUCCESS);
  809. DEBUG_LEAVE(success);
  810. return success;
  811. }
  812. /*============================================================================
  813. IsExpired (...)
  814. 4/17/00 (RajeevD) Corrected back arrow behavior and wrote detailed comment.
  815. We have a cache entry for the URL. This routine determines whether we should
  816. synchronize, i.e. do an if-modified-since request. This answer depends on 3
  817. factors: navigation mode, expiry on cache entry if any, and syncmode setting.
  818. 1. There are two navigation modes:
  819. a. hyperlinking - clicking on a link, typing a URL, starting browser etc.
  820. b. back/forward - using the back or forward buttons in the browser.
  821. In b/f case we generally want to display what was previously shown. Ideally
  822. wininet would cache multiple versions of a given URL and trident would specify
  823. which one to use when hitting back arrow. For now, the best we can do is use
  824. the latest (only) cache entry or resync with the server.
  825. EXCEPTION: if the cache entry sets http/1.1 cache-control: must-revalidate,
  826. we treat as if we were always hyperlinking to the cache entry. This is
  827. normally used during offline mode to suppress using a cache entry after
  828. expiry. This overloaded usage gives sites a workaround if they dislike our
  829. new back button behavior.
  830. 2. Expiry may fall into one of 3 buckets:
  831. a. no expiry information
  832. b. expiry in past of current time (hyperlink) or last-access time (back/fwd)
  833. c. expiry in future of current time (hyperlink) or-last access time (back/fwd)
  834. 3. Syncmode may have 3 settings
  835. a. always - err on side of freshest data at expense of net perf.
  836. b. never - err on side of best net perf at expense of stale data.
  837. c. once-per-session - middle-of-the-road setting
  838. d. automatic - slight variation of once-per-session where we decay frequency
  839. of i-m-s for images that appear to be static. This is the default.
  840. Based on these factors, there are 5 possible result values in matrices below:
  841. 1 synchronize
  842. 0 don't synchronize
  843. ? synchronize if last-sync time was before start of the current session,
  844. ?- Like per-session except if URL is marked static and has a delay interval.
  845. 0+ Don't sync if URL is marked static, else fall back to per-session
  846. HYPERLINKING
  847. When hyperlinking, expiry takes precedence, then we look at syncmode.
  848. No Expiry Expiry in Future Expiry in Past
  849. Syncmode of Current Time of Current Time
  850. Always 1 0 1
  851. Never 0 0 1
  852. Per-Session ? 0 1
  853. Automatic ?- 0 1
  854. BACK/FORWARD
  855. When going back or forward, we generally don't sync. The exception is if
  856. we should have sync'ed the URL on the previous navigate but didn't. We
  857. deduce this by looking at the last-sync time of the entry.
  858. No Expiry Expiry in Future Expiry in Past
  859. Syncmode of Last-Access Time of Last-Access Time
  860. Always ? 0 ?
  861. Never 0 0 ?
  862. Per-Session ? 0 ?
  863. Automatic 0+ 0 ?
  864. When considering what might have happened when hyperlinking to this URL,
  865. the decision tree has 5 outcomes:
  866. 1. We might have had no cache entry and downloaded to cache for the first time
  867. 2. Else we might have had a cache entry and used it w/o i-m-s
  868. 3. Else we did i-m-s but the download was aborted
  869. 4. Or the i-m-s returned not modified
  870. 5. Or the i-m-s returned new content
  871. Only in case 3 do we want to resync the cache entry.
  872. ============================================================================*/
  873. BOOL IsExpired (
  874. CACHE_ENTRY_INFOEX* pInfo,
  875. DWORD dwCacheFlags,
  876. BOOL* pfLazyUpdate )
  877. {
  878. BOOL fExpired;
  879. FILETIME ftCurrentTime;
  880. GetCurrentGmtTime (&ftCurrentTime);
  881. if ((dwCacheFlags & INTERNET_FLAG_FWD_BACK)
  882. && !(pInfo->CacheEntryType & MUST_REVALIDATE_CACHE_ENTRY))
  883. {
  884. // BACK/FORWARD CASE
  885. if (FT2LL (pInfo->ExpireTime) != LONGLONG_ZERO)
  886. {
  887. // We have an expires time.
  888. if (FT2LL (pInfo->ExpireTime) > FT2LL(pInfo->LastAccessTime))
  889. {
  890. // Expiry was in future of last access time, so don't resync.
  891. fExpired = FALSE;
  892. }
  893. else
  894. {
  895. // Entry was originally expired. Make sure it was sync'ed once.
  896. fExpired = (FT2LL(pInfo->LastSyncTime) < dwdwSessionStartTime);
  897. }
  898. }
  899. else switch (GlobalUrlCacheSyncMode)
  900. {
  901. default:
  902. case WININET_SYNC_MODE_AUTOMATIC:
  903. if (pInfo->CacheEntryType & STATIC_CACHE_ENTRY)
  904. {
  905. fExpired = FALSE;
  906. break;
  907. }
  908. // else intentional fall-through...
  909. case WININET_SYNC_MODE_ALWAYS:
  910. case WININET_SYNC_MODE_ONCE_PER_SESSION:
  911. fExpired = (FT2LL(pInfo->LastSyncTime) < dwdwSessionStartTime);
  912. break;
  913. case WININET_SYNC_MODE_NEVER:
  914. fExpired = FALSE;
  915. break;
  916. } // end switch
  917. }
  918. else
  919. {
  920. // HYPERLINKING CASE
  921. // Always strictly honor expire time from the server.
  922. INET_ASSERT(pfLazyUpdate);
  923. *pfLazyUpdate = FALSE;
  924. if( (pInfo->CacheEntryType & POST_CHECK_CACHE_ENTRY ) &&
  925. !(dwCacheFlags & INTERNET_FLAG_BGUPDATE) )
  926. {
  927. //
  928. // this is the (instlled) post check cache entry, so we will do
  929. // post check on this ietm
  930. //
  931. fExpired = FALSE;
  932. *pfLazyUpdate = TRUE;
  933. }
  934. else if (FT2LL(pInfo->ExpireTime) != LONGLONG_ZERO)
  935. {
  936. // do we have postCheck time?
  937. //
  938. // ftPostCheck ftExpire
  939. // | |
  940. // --------------|----------------------------|-----------> time
  941. // | |
  942. // not expired | not expired (bg update) | expired
  943. //
  944. //
  945. LONGLONG qwPostCheck = FT2LL(pInfo->ftPostCheck);
  946. if( qwPostCheck != LONGLONG_ZERO )
  947. {
  948. LONGLONG qwCurrent = FT2LL(ftCurrentTime);
  949. if( qwCurrent < qwPostCheck )
  950. {
  951. fExpired = FALSE;
  952. }
  953. else
  954. if( qwCurrent < FT2LL(pInfo->ExpireTime) )
  955. {
  956. fExpired = FALSE;
  957. // set background update flag
  958. // (only if we are not doing lazy updating ourselfs)
  959. if ( !(dwCacheFlags & INTERNET_FLAG_BGUPDATE) )
  960. {
  961. *pfLazyUpdate = TRUE;
  962. }
  963. }
  964. else
  965. {
  966. fExpired = TRUE;
  967. }
  968. }
  969. else
  970. fExpired = FT2LL(pInfo->ExpireTime) <= FT2LL(ftCurrentTime);
  971. }
  972. else switch (GlobalUrlCacheSyncMode)
  973. {
  974. case WININET_SYNC_MODE_NEVER:
  975. // Never check, unless the page has expired
  976. fExpired = FALSE;
  977. break;
  978. case WININET_SYNC_MODE_ALWAYS:
  979. fExpired = TRUE;
  980. break;
  981. default:
  982. case WININET_SYNC_MODE_AUTOMATIC:
  983. if (pInfo->CacheEntryType & STATIC_CACHE_ENTRY)
  984. {
  985. // We believe this entry never actually changes.
  986. // Check the entry if interval since last checked
  987. // is less than 25% of the time we had it cached.
  988. LONGLONG qwTimeSinceLastCheck = FT2LL (ftCurrentTime)
  989. - FT2LL(pInfo->LastSyncTime);
  990. LONGLONG qwTimeSinceDownload = FT2LL (ftCurrentTime)
  991. - FT2LL (pInfo->ftDownload);
  992. fExpired = qwTimeSinceLastCheck > qwTimeSinceDownload/4;
  993. break;
  994. }
  995. // else intentional fall through to once-per-session rules.
  996. case WININET_SYNC_MODE_ONCE_PER_SESSION:
  997. fExpired = TRUE;
  998. // Huh. We don't have an expires, so we'll improvise
  999. // but wait! if we are hyperlinking then there is added
  1000. // complication. This semantic has been figured out
  1001. // on Netscape after studying various sites
  1002. // if the server didn't send us expiry time or lastmodifiedtime
  1003. // then this entry expires when hyperlinking
  1004. // this happens on queries
  1005. if (dwCacheFlags & INTERNET_FLAG_HYPERLINK
  1006. && !FT2LL(pInfo->LastModifiedTime))
  1007. {
  1008. // shouldn't need the hyperlink test anymore
  1009. DEBUG_PRINT(UTIL, INFO, ("Hyperlink semantics\n"));
  1010. INET_ASSERT(fExpired==TRUE);
  1011. break;
  1012. }
  1013. // We'll assume the data could change within a day of the last time
  1014. // we sync'ed.
  1015. // We want to refresh UNLESS we've seen the page this session
  1016. // AND the session's upper bound hasn't been exceeded.
  1017. if ((dwdwSessionStartTime < FT2LL(pInfo->LastSyncTime))
  1018. &&
  1019. (FT2LL(ftCurrentTime) < FT2LL(pInfo->LastSyncTime) +
  1020. dwdwHttpDefaultExpiryDelta))
  1021. {
  1022. fExpired = FALSE;
  1023. }
  1024. break;
  1025. } // end switch
  1026. } // end else for hyperlinking case
  1027. #ifdef DBG
  1028. char buff[64];
  1029. PrintFileTimeInInternetFormat(&ftCurrentTime, buff, sizeof(buff));
  1030. DEBUG_PRINT(UTIL, INFO, ("Current Time: %s\n", buff));
  1031. PrintFileTimeInInternetFormat(&(pInfo->ExpireTime), buff, sizeof(buff));
  1032. DEBUG_PRINT(UTIL, INFO, ("Expiry Time: %s\n", buff));
  1033. PrintFileTimeInInternetFormat(&(pInfo->ftPostCheck), buff, sizeof(buff));
  1034. DEBUG_PRINT(UTIL, INFO, ("PostCheck Time: %s\n", buff));
  1035. PrintFileTimeInInternetFormat(&(pInfo->LastSyncTime), buff, sizeof(buff));
  1036. DEBUG_PRINT(UTIL, INFO, ("Last Sync Time: %s\n", buff));
  1037. PrintFileTimeInInternetFormat(&(pInfo->ftDownload), buff, sizeof(buff));
  1038. DEBUG_PRINT(UTIL, INFO, ("Last Download Time: %s\n", buff));
  1039. PrintFileTimeInInternetFormat((FILETIME *)&dwdwSessionStartTime, buff, sizeof(buff));
  1040. DEBUG_PRINT(UTIL, INFO, ("Session start Time: %s\n", buff));
  1041. DEBUG_PRINT(UTIL, INFO, ("CacheFlags=%x\n", dwCacheFlags));
  1042. DEBUG_PRINT(HTTP, INFO,
  1043. ("CheckExpired: Url %s Expired \n", (fExpired ? "" : "Not")));
  1044. #endif //DBG
  1045. return fExpired;
  1046. }
  1047. DWORD
  1048. HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePreNetIO(
  1049. VOID
  1050. )
  1051. /*++
  1052. Routine Description:
  1053. Check if in the cache. If so, check for expired, if expired do if-modified
  1054. -since, else get from the cache.
  1055. Arguments:
  1056. None.
  1057. Return Value:
  1058. DWORD Windows Error Code
  1059. ERROR_SUCCESS: started retrieval from the cache
  1060. ERROR_FILE_NOT_FOUND: go to the wire
  1061. --*/
  1062. {
  1063. DEBUG_ENTER((DBG_HTTP,
  1064. Dword,
  1065. "HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePreNetIO",
  1066. NULL
  1067. ));
  1068. PERF_LOG(PE_CACHE_EXPIRY_CHECK_START);
  1069. DWORD error = ERROR_FILE_NOT_FOUND;
  1070. if (_pCacheEntryInfo || IsCacheReadDisabled())
  1071. {
  1072. INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
  1073. goto done;
  1074. }
  1075. PERF_LOG(PE_TRACE, 0x91);
  1076. //
  1077. // See if we have a cache entry and if so lock it.
  1078. //
  1079. // Check for the cdrom insertion case Set error to
  1080. // ERROR_INTERNET_INSERT_CDROM ONLY only in this
  1081. // case, since this will be handled. Otherwise,
  1082. // error defaults to ERROR_FILE_NOT_FOUND.
  1083. DWORD dwError;
  1084. dwError = UrlCacheRetrieve(FALSE);
  1085. if (dwError != ERROR_SUCCESS)
  1086. {
  1087. if (dwError == ERROR_INTERNET_INSERT_CDROM)
  1088. error = dwError;
  1089. else
  1090. BETA_LOG (CACHE_MISS);
  1091. goto done;
  1092. }
  1093. if (_pCacheEntryInfo->CacheEntryType & SPARSE_CACHE_ENTRY)
  1094. {
  1095. //
  1096. // Open the file so it can't be deleted.
  1097. //
  1098. _CacheFileHandle =
  1099. CreateFile
  1100. (
  1101. _pCacheEntryInfo->lpszLocalFileName,
  1102. GENERIC_WRITE,
  1103. FILE_SHARE_READ,
  1104. NULL,
  1105. OPEN_EXISTING,
  1106. FILE_ATTRIBUTE_NORMAL,
  1107. NULL
  1108. );
  1109. //
  1110. // Check the file size.
  1111. //
  1112. if (_CacheFileHandle == INVALID_HANDLE_VALUE
  1113. || (_pCacheEntryInfo->dwSizeLow !=
  1114. SetFilePointer (_CacheFileHandle, 0, NULL, FILE_END)))
  1115. {
  1116. FREE_MEMORY (_pCacheEntryInfo);
  1117. _pCacheEntryInfo = NULL;
  1118. INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
  1119. goto done;
  1120. }
  1121. // We have a partial cache entry for this URL. No need to check
  1122. // for expiry or correct user. Add a Range: header to get the
  1123. // rest of the data.
  1124. char szBuf[64];
  1125. DWORD cbBuf =
  1126. wsprintf (szBuf, "bytes=%d-", _pCacheEntryInfo->dwSizeLow);
  1127. if (ERROR_SUCCESS != ReplaceRequestHeader
  1128. (HTTP_QUERY_RANGE, szBuf, cbBuf, 0, ADD_HEADER))
  1129. {
  1130. goto done;
  1131. }
  1132. //
  1133. // If there was Last-Modified-Time, add Unless-Modified-Since header,
  1134. // which assures coherency in the event the URL data changed since
  1135. // the partial download.
  1136. //
  1137. if (FT2LL(_pCacheEntryInfo->LastModifiedTime) != LONGLONG_ZERO)
  1138. {
  1139. cbBuf = sizeof(szBuf);
  1140. FFileTimetoHttpDateTime(&(_pCacheEntryInfo->LastModifiedTime),
  1141. szBuf, &cbBuf);
  1142. ReplaceRequestHeader (
  1143. HTTP_QUERY_UNLESS_MODIFIED_SINCE,
  1144. szBuf, cbBuf, 0, ADD_HEADER
  1145. );
  1146. }
  1147. //
  1148. // Similarly, if the entry has a 1.1 ETag, add the If-Range header.
  1149. //
  1150. AddHeaderIfEtagFound(_pCacheEntryInfo);
  1151. }
  1152. else
  1153. {
  1154. PERF_LOG(PE_TRACE, 0x93);
  1155. BOOL fIsExpired = IsExpired
  1156. (_pCacheEntryInfo, GetCacheFlags(), &_fLazyUpdate);
  1157. PERF_LOG(PE_TRACE, 0x94);
  1158. //
  1159. // found the cache entry
  1160. // if it is expired, or we are asked to do if-modified-since
  1161. // then we do it. However, if the entry is an Installed entry,
  1162. // we skip netio unless the entry is also marked EDITED_CACHE_ENTRY
  1163. // and the client has specifically asked for
  1164. // INTERNET_OPTION_BYPASS_EDITED_ENTRY.
  1165. if ((!(_pCacheEntryInfo->CacheEntryType & INSTALLED_CACHE_ENTRY)
  1166. || ((_pCacheEntryInfo->CacheEntryType & EDITED_CACHE_ENTRY)
  1167. && GlobalBypassEditedEntry))
  1168. && (fIsExpired || (GetCacheFlags() & INTERNET_FLAG_RESYNCHRONIZE)
  1169. ))
  1170. {
  1171. //
  1172. // add if-modified-since only if there is lastmodifiedtime
  1173. // sent back by the site. This way you never get into trouble
  1174. // where the sitedoesn't send you an lastmodtime and you
  1175. // send if-modified-since based on a clock which might be ahead
  1176. // of the site. So the site might say nothing is modified even though
  1177. // something might be. www.microsoft.com is one such example
  1178. //
  1179. if (FT2LL(_pCacheEntryInfo->LastModifiedTime) != LONGLONG_ZERO)
  1180. {
  1181. DEBUG_PRINT(HTTP,
  1182. INFO,
  1183. ("%s expired, doing IF-MODIFIED-SINCE\n",
  1184. GetURL()
  1185. ));
  1186. PERF_LOG(PE_TRACE, 0x97);
  1187. FAddIfModifiedSinceHeader(_pCacheEntryInfo);
  1188. PERF_LOG(PE_TRACE, 0x98);
  1189. }
  1190. // If this is an HTTP 1.1 then check to see if an Etag:
  1191. // header is present and add an If-None-Match header.
  1192. AddHeaderIfEtagFound(_pCacheEntryInfo);
  1193. INET_ASSERT(error == ERROR_FILE_NOT_FOUND);
  1194. }
  1195. else
  1196. {
  1197. //
  1198. // The cache entry is not expired, so we don't hit the net.
  1199. //
  1200. BETA_LOG (CACHE_NOT_EXPIRED);
  1201. PERF_LOG(PE_TRACE, 0x99);
  1202. ReuseObject();
  1203. error = FHttpBeginCacheRetrieval(FALSE, FALSE);
  1204. INET_ASSERT (error == ERROR_SUCCESS);
  1205. }
  1206. }
  1207. done:
  1208. PERF_LOG(PE_CACHE_EXPIRY_CHECK_END);
  1209. DEBUG_LEAVE(error);
  1210. return (error);
  1211. }
  1212. DWORD
  1213. HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePostNetIO(
  1214. IN DWORD dwStatusCode,
  1215. IN BOOL fVariation
  1216. )
  1217. /*++
  1218. Routine Description:
  1219. Check if this response needs to be cached or pulled from the cache
  1220. after we have received the response from the net. Here we check for
  1221. if-modified-since.
  1222. Arguments:
  1223. dwStatusCode - HTTP status code
  1224. Return Value:
  1225. DWORD Windows Error Code
  1226. --*/
  1227. {
  1228. DEBUG_ENTER((DBG_HTTP,
  1229. Dword,
  1230. "HTTP_REQUEST_HANDLE_OBJECT::GetFromCachePostNetIO",
  1231. "%d",
  1232. dwStatusCode
  1233. ));
  1234. DWORD error = ERROR_FILE_NOT_FOUND;
  1235. BOOL fLastModTimeFromServer = FALSE;
  1236. BOOL fExpireTimeFromServer = FALSE;
  1237. BOOL fPostCheckTimeFromServer = FALSE;
  1238. //
  1239. // check if
  1240. // we can actually read the data from the cache after all. The idea here is
  1241. // to improve efficiency by returning data from the cache if it hasn't
  1242. // changed at the server. We determine this by the server returning a 304
  1243. // response, or by determining that the date on the response is less than or
  1244. // equal to the expires timestamp on the data we already wrote to cache
  1245. //
  1246. INET_ASSERT((dwStatusCode == HTTP_STATUS_NOT_MODIFIED)
  1247. || (dwStatusCode == HTTP_STATUS_PRECOND_FAILED)
  1248. || (dwStatusCode == HTTP_STATUS_OK)
  1249. || (dwStatusCode == HTTP_STATUS_PARTIAL_CONTENT)
  1250. || (dwStatusCode == 0));
  1251. GetTimeStampsForCache(&_ftExpires,
  1252. &_ftLastModified,
  1253. &_ftPostCheck,
  1254. &fExpireTimeFromServer,
  1255. &fLastModTimeFromServer,
  1256. &fPostCheckTimeFromServer
  1257. );
  1258. if (IsCacheReadDisabled())
  1259. {
  1260. INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
  1261. goto quit;
  1262. }
  1263. //
  1264. // we may have no cache entry info, so no point in continuing - this file
  1265. // was not in the cache when we started. (We could try to retrieve again
  1266. // in case another download has put it in the cache)
  1267. //
  1268. if (_pCacheEntryInfo == NULL)
  1269. {
  1270. if (fVariation)
  1271. {
  1272. error = UrlCacheRetrieve(TRUE);
  1273. }
  1274. if (error!=ERROR_SUCCESS)
  1275. {
  1276. goto quit;
  1277. }
  1278. }
  1279. if (_pCacheEntryInfo->CacheEntryType & SPARSE_CACHE_ENTRY)
  1280. {
  1281. if (dwStatusCode == HTTP_STATUS_PARTIAL_CONTENT)
  1282. {
  1283. BETA_LOG (CACHE_RESUMED);
  1284. error = ResumePartialDownload();
  1285. }
  1286. else
  1287. {
  1288. BETA_LOG (CACHE_NOT_RESUMED);
  1289. INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
  1290. }
  1291. goto quit;
  1292. }
  1293. //
  1294. // we are optimizing further here
  1295. // if the return status is OK and the server sent us a
  1296. // last modified time and this time is the same as
  1297. // what we got earlier then we use the entry from the cache
  1298. //
  1299. if ((dwStatusCode == HTTP_STATUS_NOT_MODIFIED)
  1300. || ((fLastModTimeFromServer)
  1301. && (FT2LL(_ftLastModified)
  1302. == FT2LL(_pCacheEntryInfo->LastModifiedTime))
  1303. && GetMethodType() == HTTP_METHOD_TYPE_GET))
  1304. {
  1305. BETA_LOG (CACHE_NOT_MODIFIED);
  1306. DEBUG_PRINT(HTTP,
  1307. INFO,
  1308. ("response code=%d %s, using cache entry\n",
  1309. GetStatusCode(),
  1310. GetURL()
  1311. ));
  1312. DWORD dwAction = CACHE_ENTRY_SYNCTIME_FC;
  1313. GetCurrentGmtTime(&(_pCacheEntryInfo->LastSyncTime));
  1314. if (fExpireTimeFromServer)
  1315. {
  1316. (_pCacheEntryInfo->ExpireTime).dwLowDateTime = _ftExpires.dwLowDateTime;
  1317. (_pCacheEntryInfo->ExpireTime).dwHighDateTime = _ftExpires.dwHighDateTime;
  1318. dwAction |= CACHE_ENTRY_EXPTIME_FC;
  1319. }
  1320. // update the cache entry type if needed
  1321. DWORD dwType;
  1322. dwType = GetCacheEntryType();
  1323. if (dwType)
  1324. _pCacheEntryInfo->CacheEntryType |= dwType;
  1325. dwAction |= CACHE_ENTRY_TYPE_FC;
  1326. //
  1327. // Update the last sync time to the current time
  1328. // so we can do once_per_session logic
  1329. //
  1330. if (!SetUrlCacheEntryInfoA(_pCacheEntryInfo->lpszSourceUrlName, _pCacheEntryInfo, dwAction))
  1331. {
  1332. // NB if this call fails, the worst that could happen is
  1333. // that next time around we will do an if-modified-since
  1334. // again
  1335. INET_ASSERT(FALSE);
  1336. }
  1337. error = FHttpBeginCacheRetrieval(TRUE, FALSE);
  1338. if (error != ERROR_SUCCESS)
  1339. {
  1340. //
  1341. // if we failed to abort the transaction, or to retrieve the
  1342. // data from the cache, then return the error only if the
  1343. // response if not OK. Otherwise we can continue and
  1344. // get the data
  1345. //
  1346. if (dwStatusCode != HTTP_STATUS_OK)
  1347. {
  1348. // this should never happen to us
  1349. INET_ASSERT(FALSE);
  1350. error = ERROR_FILE_NOT_FOUND;
  1351. }
  1352. }
  1353. }
  1354. else // We could not use the cache entry so release it.
  1355. {
  1356. BETA_LOG (CACHE_MODIFIED);
  1357. INET_ASSERT (error == ERROR_FILE_NOT_FOUND);
  1358. }
  1359. quit:
  1360. if (error != ERROR_SUCCESS)
  1361. UrlCacheUnlock();
  1362. DEBUG_LEAVE(error);
  1363. return error;
  1364. }
  1365. DWORD HTTP_REQUEST_HANDLE_OBJECT::ResumePartialDownload(void)
  1366. /*++
  1367. Routine Description:
  1368. description-of-function.
  1369. Arguments:
  1370. None.
  1371. Return Value:
  1372. DWORD
  1373. --*/
  1374. {
  1375. DEBUG_ENTER((DBG_HTTP,
  1376. Dword,
  1377. "HTTP_REQUEST_HANDLE_OBJECT::ResumePartialDownload",
  1378. NULL));
  1379. _ResponseHeaders.LockHeaders();
  1380. const static char sz200[] = "200";
  1381. INET_ASSERT (!_CacheWriteInProgress);
  1382. LPSTR pszHeader;
  1383. DWORD cbHeader;
  1384. // Retrieve the start and end of Content-Range header
  1385. if (!_iSlotContentRange)
  1386. goto quit;
  1387. pszHeader = _ResponseHeaders.GetHeaderPointer
  1388. (_ResponseBuffer, _iSlotContentRange);
  1389. INET_ASSERT (pszHeader);
  1390. PSTR pszLimit;
  1391. pszLimit = StrChr (pszHeader, '\n');
  1392. INET_ASSERT (pszLimit);
  1393. *pszLimit = 0;
  1394. // Extract the document length as a string and number.
  1395. // The http 1.1 spec is very explicit that we MUST parse
  1396. // the header and return an error if invalid. We expect
  1397. // it to be of the form Content-Range: bytes xxx-yyy/zzz.
  1398. PSTR pszStart, pszEnd, pszLength;
  1399. DWORD dwStart, dwEnd, dwLength;
  1400. // Ensure that value is prefixed with "bytes"
  1401. PSTR pszBytes;
  1402. pszBytes = pszHeader + (GlobalKnownHeaders[HTTP_QUERY_CONTENT_RANGE].Length+1); // +1 for the ':'
  1403. SKIPWS (pszBytes);
  1404. if (strnicmp (pszBytes, BYTES_SZ, BYTES_LEN))
  1405. goto quit;
  1406. // Parse and validate start of range.
  1407. pszStart = pszBytes + BYTES_LEN;
  1408. SKIPWS (pszStart);
  1409. dwStart = StrToInt (pszStart);
  1410. if (dwStart != _pCacheEntryInfo->dwSizeLow)
  1411. goto quit;
  1412. // Parse and validate end of range.
  1413. pszEnd = StrChr (pszStart, '-');
  1414. if (!pszEnd++)
  1415. goto quit;
  1416. dwEnd = StrToInt (pszEnd);
  1417. if (dwStart > dwEnd)
  1418. goto quit;
  1419. // Parse and validate length.
  1420. pszLength = StrChr (pszEnd, '/');
  1421. if (!pszLength)
  1422. goto quit;
  1423. pszLength++;
  1424. dwLength = StrToInt (pszLength);
  1425. if (dwEnd + 1 != dwLength)
  1426. goto quit;
  1427. // The Content-Length header is the amount transmitted, however
  1428. // as far as the client is concerned only the total amount of
  1429. // data matters, so we fix it up from the Content-Range header.
  1430. // Use the Content-Range buffer for the new Content-Length.
  1431. // We know the buffer is big enough because the last number
  1432. // in the Content-Range header is the new Content-Length value.
  1433. _ContentLength = dwLength;
  1434. // Remove the old Content-Length header, if any.
  1435. if (_iSlotContentLength)
  1436. {
  1437. _ResponseHeaders.RemoveAllByIndex(HTTP_QUERY_CONTENT_LENGTH);
  1438. //_ResponseHeaders.RemoveHeader (_iSlotContentLength, HTTP_QUERY_CONTENT_LENGTH, &_bKnownHeaders[dwQueryIndex]);
  1439. }
  1440. cbHeader = wsprintf (pszHeader, "%s: %d", GlobalKnownHeaders[HTTP_QUERY_CONTENT_LENGTH].Text,
  1441. dwLength);
  1442. _ResponseHeaders.ShrinkHeader
  1443. (_ResponseBuffer, _iSlotContentRange, HTTP_QUERY_CONTENT_RANGE, HTTP_QUERY_CONTENT_LENGTH, cbHeader);
  1444. // Fix up the response headers to appear same as a 200 response.
  1445. // Revise the status line from 206 to 200. The status code
  1446. // starts after the first space, e.g. "HTTP/1.0 206 Partial Content"
  1447. pszHeader = _ResponseHeaders.GetHeaderPointer (_ResponseBuffer, 0);
  1448. INET_ASSERT (pszHeader);
  1449. LPSTR pszStatus;
  1450. pszStatus = StrChr (pszHeader, ' ');
  1451. SKIPWS (pszStatus);
  1452. INET_ASSERT (!memcmp(pszStatus, "206", 3));
  1453. memcpy (pszStatus, sz200, sizeof(sz200));
  1454. _ResponseHeaders.ShrinkHeader (_ResponseBuffer, 0,
  1455. HTTP_QUERY_STATUS_TEXT, HTTP_QUERY_STATUS_TEXT,
  1456. (DWORD) (pszStatus - pszHeader) + sizeof(sz200) - 1);
  1457. _StatusCode = HTTP_STATUS_OK;
  1458. // Some servers omit "Accept-Ranges: bytes" since it is implicit
  1459. // in a 206 response. This is important to Adobe Amber ActiveX
  1460. // control and other clients that may issue their own range
  1461. // requests. Add the header if not present.
  1462. if (!IsResponseHeaderPresent(HTTP_QUERY_ACCEPT_RANGES))
  1463. {
  1464. const static char szAccept[] = "Accept-Ranges: bytes";
  1465. DWORD dwErr = AddInternalResponseHeader
  1466. (HTTP_QUERY_ACCEPT_RANGES, (LPSTR) szAccept, CSTRLEN(szAccept));
  1467. INET_ASSERT (dwErr == ERROR_SUCCESS);
  1468. }
  1469. // Adjust the handle values for socket read and file write positions.
  1470. if (IsKeepAlive())
  1471. _BytesRemaining = _ContentLength;
  1472. _VirtualCacheFileSize = dwStart;
  1473. _RealCacheFileSize = dwStart;
  1474. SetAvailableDataLength (dwStart);
  1475. // Save the cache file path.
  1476. INET_ASSERT(!_CacheFileName);
  1477. _CacheFileName = NewString(_pCacheEntryInfo->lpszLocalFileName);
  1478. if (!_CacheFileName)
  1479. goto quit;
  1480. // Open the file for read (should already be open for write)
  1481. INET_ASSERT (_CacheFileHandle != INVALID_HANDLE_VALUE);
  1482. _CacheFileHandleRead = CreateFile (_CacheFileName, GENERIC_READ,
  1483. FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
  1484. FILE_ATTRIBUTE_NORMAL, NULL);
  1485. if (_CacheFileHandleRead == INVALID_HANDLE_VALUE)
  1486. goto quit;
  1487. // We can discard the partial cache entry info.
  1488. FREE_MEMORY (_pCacheEntryInfo);
  1489. _pCacheEntryInfo = NULL;
  1490. _CacheWriteInProgress = TRUE;
  1491. quit:
  1492. _ResponseHeaders.UnlockHeaders();
  1493. DWORD dwErr;
  1494. if (_CacheWriteInProgress)
  1495. {
  1496. dwErr = ERROR_SUCCESS;
  1497. }
  1498. else
  1499. {
  1500. INET_ASSERT (FALSE);
  1501. dwErr = ERROR_HTTP_INVALID_SERVER_RESPONSE;
  1502. if (_CacheFileName)
  1503. {
  1504. FREE_MEMORY (_CacheFileName);
  1505. _CacheFileName = NULL;
  1506. }
  1507. if (_CacheFileHandleRead != INVALID_HANDLE_VALUE)
  1508. {
  1509. CloseHandle (_CacheFileHandleRead);
  1510. _CacheFileHandleRead = INVALID_HANDLE_VALUE;
  1511. }
  1512. }
  1513. DEBUG_LEAVE (dwErr);
  1514. return dwErr;
  1515. }
  1516. DWORD
  1517. HTTP_REQUEST_HANDLE_OBJECT::AddTimestampsFromCacheToResponseHeaders(
  1518. IN LPCACHE_ENTRY_INFO lpCacheEntryInfo
  1519. )
  1520. /*++
  1521. Routine Description:
  1522. description-of-function.
  1523. Arguments:
  1524. lpCacheEntryInfo -
  1525. Return Value:
  1526. DWORD
  1527. --*/
  1528. {
  1529. DEBUG_ENTER((DBG_HTTP,
  1530. Dword,
  1531. "HTTP_REQUEST_HANDLE_OBJECT::AddTimestampsFromCacheToResponseHeaders",
  1532. "%#x",
  1533. lpCacheEntryInfo
  1534. ));
  1535. PERF_LOG(PE_TRACE, 0x701);
  1536. _ResponseHeaders.LockHeaders();
  1537. DWORD error = AddTimeHeader(lpCacheEntryInfo->ExpireTime,
  1538. HTTP_QUERY_EXPIRES
  1539. );
  1540. if (error == ERROR_SUCCESS) {
  1541. error = AddTimeHeader(lpCacheEntryInfo->LastModifiedTime,
  1542. HTTP_QUERY_LAST_MODIFIED
  1543. );
  1544. }
  1545. if (error == ERROR_INVALID_PARAMETER) {
  1546. error = ERROR_SUCCESS;
  1547. }
  1548. _ResponseHeaders.UnlockHeaders();
  1549. PERF_LOG(PE_TRACE, 0x702);
  1550. DEBUG_LEAVE(error);
  1551. return error;
  1552. }
  1553. DWORD
  1554. HTTP_REQUEST_HANDLE_OBJECT::AddTimeHeader(
  1555. IN FILETIME fTime,
  1556. IN DWORD dwHeaderIndex
  1557. )
  1558. /*++
  1559. Routine Description:
  1560. Adds a time header to this object
  1561. Arguments:
  1562. fTime - FILETIME value to add as header value
  1563. dwHeaderIndex - contains an index into a global array of Header strings.
  1564. NOTE: Must be called under Header Critical Section!!!
  1565. Return Value:
  1566. DWORD
  1567. Success - ERROR_SUCCESS
  1568. Failure - ERROR_INVALID_PARAMETER
  1569. Couldn't convert fTime
  1570. --*/
  1571. {
  1572. DEBUG_ENTER((DBG_HTTP,
  1573. None,
  1574. "HTTP_REQUEST_HANDLE_OBJECT::AddTimeHeader",
  1575. "%#x:%#x, %u [%q]",
  1576. fTime.dwLowDateTime,
  1577. fTime.dwHighDateTime,
  1578. dwHeaderIndex,
  1579. GlobalKnownHeaders[dwHeaderIndex].Text
  1580. ));
  1581. char buf[MAX_PATH];
  1582. SYSTEMTIME systemTime;
  1583. DWORD error = ERROR_SUCCESS;
  1584. if (FT2LL(fTime) != LONGLONG_ZERO) {
  1585. if (FileTimeToSystemTime((CONST FILETIME *)&fTime, &systemTime)) {
  1586. memcpy(buf, GlobalKnownHeaders[dwHeaderIndex].Text, GlobalKnownHeaders[dwHeaderIndex].Length);
  1587. buf[GlobalKnownHeaders[dwHeaderIndex].Length] = ':';
  1588. buf[GlobalKnownHeaders[dwHeaderIndex].Length+1] = ' ';
  1589. if (InternetTimeFromSystemTime((CONST SYSTEMTIME *)&systemTime,
  1590. INTERNET_RFC1123_FORMAT,
  1591. buf + (GlobalKnownHeaders[dwHeaderIndex].Length+2),
  1592. sizeof(buf) - (GlobalKnownHeaders[dwHeaderIndex].Length+2)
  1593. )) {
  1594. error = AddInternalResponseHeader(dwHeaderIndex, buf, lstrlen(buf));
  1595. INET_ASSERT(error == ERROR_SUCCESS);
  1596. //
  1597. // if it came upto here and all went well, only then would
  1598. // the error be set to ERROR_SUCCESS
  1599. //
  1600. DEBUG_PRINT(HTTP,
  1601. INFO,
  1602. ("Cache: Adding header %s, errorcode=%d\n",
  1603. buf,
  1604. error
  1605. ));
  1606. } else {
  1607. INET_ASSERT(FALSE);
  1608. error = ERROR_INVALID_PARAMETER;
  1609. }
  1610. } else {
  1611. INET_ASSERT(FALSE);
  1612. error = ERROR_INVALID_PARAMETER;
  1613. }
  1614. }
  1615. DEBUG_LEAVE(error);
  1616. return error;
  1617. }
  1618. BOOL HTTP_REQUEST_HANDLE_OBJECT::IsPartialResponseCacheable(void)
  1619. /*++
  1620. Routine Description:
  1621. description-of-function.
  1622. Arguments:
  1623. None.
  1624. Return Value:
  1625. BOOL
  1626. --*/
  1627. {
  1628. LPSTR lpszHeader;
  1629. DWORD cbHeader;
  1630. DWORD dwIndex;
  1631. BOOL fRet = FALSE;
  1632. DWORD err;
  1633. _ResponseHeaders.LockHeaders();
  1634. if (GlobalDisableReadRange || GetMethodType() != HTTP_METHOD_TYPE_GET)
  1635. {
  1636. INET_ASSERT(fRet == FALSE);
  1637. goto quit;
  1638. }
  1639. if (_RealCacheFileSize >= _ContentLength)
  1640. {
  1641. // We don't handle chunked transfer upon resuming a partial
  1642. // download, so we require a Content-Length header. Also,
  1643. // if download file is actually complete, yet not committed
  1644. // to cache (possibly because the client didn't read to eof)
  1645. // then we don't want to save a partial download. Otherwise
  1646. // we might later start a range request for the file starting
  1647. // one byte beyond eof. MS Proxy 1.0 will return an invalid
  1648. // 206 response containing the last byte of the file. Other
  1649. // servers or proxies that follow the latest http spec might
  1650. // return a 416 response which is equally useless to us.
  1651. INET_ASSERT(fRet == FALSE);
  1652. goto quit;
  1653. }
  1654. // For HTTP/1.0, must have last-modified time, otherwise we
  1655. // don't have a way to tell if the partial downloads are coherent.
  1656. if (!IsResponseHttp1_1() && (FT2LL(_ftLastModified) == 0))
  1657. {
  1658. INET_ASSERT(fRet == FALSE);
  1659. goto quit;
  1660. }
  1661. if (!_iSlotContentRange)
  1662. {
  1663. // We didn't get a Content-Range header which implies the server
  1664. // supports byte range for this URL, so we must look for the
  1665. // explicit invitation of "Accept-Ranges: bytes" response header.
  1666. dwIndex = 0;
  1667. err = FastQueryResponseHeader (HTTP_QUERY_ACCEPT_RANGES,
  1668. (LPVOID *) &lpszHeader,&cbHeader, dwIndex);
  1669. if (err != ERROR_SUCCESS || !(cbHeader == BYTES_LEN && !strnicmp(lpszHeader, BYTES_SZ, cbHeader)) )
  1670. {
  1671. INET_ASSERT(fRet == FALSE);
  1672. goto quit;
  1673. }
  1674. }
  1675. if (!IsResponseHttp1_1())
  1676. {
  1677. // For HTTP/1.0, only cache responses from Server: Microsoft-???/*
  1678. // Microsoft-PWS-95/*.* will respond with a single range but
  1679. // with incorrect Content-Length and Content-Range headers.
  1680. // Other 1.0 servers may return single range in multipart response.
  1681. const static char szPrefix[] = "Microsoft-";
  1682. const static DWORD ibSlashOffset = sizeof(szPrefix)-1 + 3;
  1683. dwIndex = 0;
  1684. if ( ERROR_SUCCESS != FastQueryResponseHeader (HTTP_QUERY_SERVER,
  1685. (LPVOID *) &lpszHeader, &cbHeader, dwIndex)
  1686. || cbHeader <= ibSlashOffset
  1687. || lpszHeader[ibSlashOffset] != '/'
  1688. || memcmp (lpszHeader, szPrefix, sizeof(szPrefix) - 1)
  1689. )
  1690. {
  1691. INET_ASSERT(fRet == FALSE);
  1692. goto quit;
  1693. }
  1694. }
  1695. else // if (IsResponseHttp1_1())
  1696. {
  1697. // For http 1.1, must have strong etag. A weak etag starts with
  1698. // a character other than a quote mark and cannot be used as a
  1699. // coherency validator. IIS returns a weak etag when content is
  1700. // modified within a single file system time quantum.
  1701. if (ERROR_SUCCESS != FastQueryResponseHeader (HTTP_QUERY_ETAG,
  1702. (LPVOID *) &lpszHeader, &cbHeader, 0)
  1703. || *lpszHeader != '\"')
  1704. {
  1705. INET_ASSERT(fRet == FALSE);
  1706. goto quit;
  1707. }
  1708. }
  1709. fRet = TRUE;
  1710. quit:
  1711. _ResponseHeaders.UnlockHeaders();
  1712. return fRet;
  1713. }
  1714. DWORD
  1715. HTTP_REQUEST_HANDLE_OBJECT::LocalEndCacheWrite(
  1716. IN BOOL fNormal
  1717. )
  1718. /*++
  1719. Routine Description:
  1720. Finishes up the cache-write operation, either committing the cache entry or
  1721. deleting it (because we failed)
  1722. Arguments:
  1723. fNormal - TRUE if normal end-cache-write operation (i.e. FALSE if error)
  1724. Return Value:
  1725. DWORD
  1726. Success - ERROR_SUCCESS
  1727. Failure - ERROR_NOT_ENOUGH_MEMORY
  1728. ERROR_INTERNET_INTERNAL_ERROR
  1729. --*/
  1730. {
  1731. DEBUG_ENTER((DBG_HTTP,
  1732. Dword,
  1733. "HTTP_REQUEST_HANDLE_OBJECT::LocalEndCacheWrite",
  1734. "%B",
  1735. fNormal
  1736. ));
  1737. PERF_LOG(PE_TRACE, 0x2001);
  1738. LPSTR lpszBuf = NULL;
  1739. char buff[256];
  1740. DWORD dwBuffLen;
  1741. DWORD dwError;
  1742. DWORD dwUserNameHeader = 0;
  1743. LPSTR lpszHeaderInfo = NULL;
  1744. DWORD dwEntryType = GetCacheEntryType();
  1745. if (!fNormal)
  1746. {
  1747. if (!IsPartialResponseCacheable())
  1748. {
  1749. BETA_LOG (DOWNLOAD_ABORTED);
  1750. }
  1751. else
  1752. {
  1753. //
  1754. // Flush any buffered data to disk.
  1755. // BUGBUG: doesn't work with chunked transfer
  1756. // WriteResponseBufferToCache();
  1757. // WriteQueryBufferToCache();
  1758. //
  1759. // dprintf ("wininet: partial cached %s\n", GetURL());
  1760. BETA_LOG (DOWNLOAD_PARTIAL);
  1761. fNormal = TRUE;
  1762. dwEntryType = SPARSE_CACHE_ENTRY;
  1763. //
  1764. // Disable InternetUnlockRequest
  1765. //
  1766. LOCK_REQUEST_INFO* pLock =
  1767. (LOCK_REQUEST_INFO*) GetLockRequestHandle();
  1768. if (pLock)
  1769. pLock->fNoDelete = TRUE;
  1770. }
  1771. }
  1772. if (fNormal)
  1773. {
  1774. if (GetCacheFlags() & INTERNET_FLAG_MAKE_PERSISTENT)
  1775. dwEntryType |= STICKY_CACHE_ENTRY;
  1776. if (GetSecondaryCacheKey())
  1777. dwEntryType |= POST_RESPONSE_CACHE_ENTRY;
  1778. if (IsResponseHttp1_1())
  1779. {
  1780. dwEntryType |= HTTP_1_1_CACHE_ENTRY;
  1781. if (IsMustRevalidate())
  1782. dwEntryType |= MUST_REVALIDATE_CACHE_ENTRY;
  1783. }
  1784. if (IsPerUserItem())
  1785. {
  1786. //
  1787. // create per-user header, e.g. "~U:JoeBlow\r\n", if it fits in the
  1788. // buffer
  1789. //
  1790. INET_ASSERT(vdwCurrentUserLen);
  1791. dwUserNameHeader = sizeof(vszUserNameHeader) - 1
  1792. + vdwCurrentUserLen
  1793. + sizeof("\r\n");
  1794. if (sizeof(buff) >= dwUserNameHeader)
  1795. {
  1796. memcpy(buff, vszUserNameHeader, sizeof(vszUserNameHeader) - 1);
  1797. DWORD dwSize = lstrlen(vszCurrentUser);
  1798. memcpy(&buff[sizeof(vszUserNameHeader) - 1],
  1799. vszCurrentUser,
  1800. dwSize);
  1801. dwSize += sizeof(vszUserNameHeader) - 1;
  1802. memcpy(&buff[dwSize], "\r\n", sizeof("\r\n"));
  1803. }
  1804. else
  1805. {
  1806. // if it failed, mark it as expired
  1807. dwUserNameHeader = 0;
  1808. GetCurrentGmtTime(&_ftExpires);
  1809. AddLongLongToFT(&_ftExpires, (-1)*(ONE_HOUR_DELTA));
  1810. }
  1811. }
  1812. //
  1813. // start off with 512 byte buffer (used to be 256, but headers are
  1814. // getting larger)
  1815. //
  1816. dwBuffLen = 512;
  1817. do
  1818. {
  1819. DWORD dwPreviousLength;
  1820. lpszBuf = (LPSTR)ResizeBuffer(lpszBuf, dwBuffLen, FALSE);
  1821. if (lpszBuf == NULL)
  1822. {
  1823. dwError = ERROR_NOT_ENOUGH_MEMORY;
  1824. break;
  1825. }
  1826. dwBuffLen -= dwUserNameHeader;
  1827. dwPreviousLength = dwBuffLen;
  1828. lpszHeaderInfo = lpszBuf;
  1829. lpszBuf[0] = '\0';
  1830. dwError = QueryRawResponseHeaders(TRUE,
  1831. (LPVOID)lpszHeaderInfo,
  1832. &dwBuffLen
  1833. );
  1834. if (dwError == ERROR_SUCCESS)
  1835. {
  1836. FilterHeaders(lpszHeaderInfo, &dwBuffLen);
  1837. if (dwUserNameHeader)
  1838. {
  1839. lstrcat(lpszHeaderInfo, buff);
  1840. dwBuffLen += dwUserNameHeader;
  1841. }
  1842. break; // get out
  1843. }
  1844. //
  1845. // we have the right error and we haven't allocated more memory yet
  1846. //
  1847. if (dwError == ERROR_INSUFFICIENT_BUFFER)
  1848. {
  1849. if (dwBuffLen <= dwPreviousLength)
  1850. {
  1851. dwError = ERROR_INTERNET_INTERNAL_ERROR;
  1852. break;
  1853. }
  1854. dwBuffLen += dwUserNameHeader;
  1855. }
  1856. else
  1857. {
  1858. //
  1859. // if HttpQueryInfo() returned ERROR_HTTP_DOWNLEVEL_SERVER
  1860. // or we are making CERN proxy requests for FTP or gopher, then
  1861. // there are no headers from the origin server, but the
  1862. // operation succeeded
  1863. //
  1864. if (dwError == ERROR_HTTP_DOWNLEVEL_SERVER)
  1865. dwError = ERROR_SUCCESS;
  1866. //
  1867. // either allready tried once with allocation or
  1868. // we got some error other than ERROR_INSUFFICIENT_BUFFER
  1869. //
  1870. INET_ASSERT(*lpszHeaderInfo == '\0');
  1871. lpszHeaderInfo = NULL;
  1872. dwBuffLen = 0;
  1873. break;
  1874. }
  1875. } while (TRUE);
  1876. fNormal = (dwError == ERROR_SUCCESS);
  1877. }
  1878. if (!fNormal)
  1879. {
  1880. dwEntryType = 0xffffffff;
  1881. lpszHeaderInfo = NULL;
  1882. }
  1883. //
  1884. // Simulate a redirect if the original object was the root
  1885. // ("" or "/") and we did not otherwise get a redirect.
  1886. //
  1887. if (IsObjectRoot() && _OriginalUrl && !lstrcmpi (_OriginalUrl, GetURL()))
  1888. {
  1889. DWORD dwLen = lstrlen (_OriginalUrl);
  1890. INET_ASSERT (_OriginalUrl[dwLen - 1] == '/');
  1891. _OriginalUrl[dwLen - 1] = 0;
  1892. }
  1893. //
  1894. // Let the cache know if the item is likely to be a static image.
  1895. // 1. No expire time.
  1896. // 2. Has last-modified time.
  1897. // 3. The content-type is image/*
  1898. // 4. No '?' in the URL.
  1899. //
  1900. LPSTR lpszHeader;
  1901. DWORD cbHeader;
  1902. BOOL fImage =
  1903. ( !FT2LL(_ftExpires)
  1904. && FT2LL(_ftLastModified)
  1905. && (ERROR_SUCCESS == FastQueryResponseHeader (HTTP_QUERY_CONTENT_TYPE,
  1906. (LPVOID *) &lpszHeader, &cbHeader, 0))
  1907. && (StrCmpNI (lpszHeader, "image/", sizeof("image/")-1) == 0)
  1908. && (!StrChr (GetURL(), '?'))
  1909. );
  1910. DEBUG_PRINT(CACHE,
  1911. INFO,
  1912. ("Cache write EntryType = %x\r\n",
  1913. dwEntryType
  1914. ));
  1915. dwError = EndCacheWrite(&_ftExpires,
  1916. &_ftLastModified,
  1917. &_ftPostCheck,
  1918. dwEntryType,
  1919. dwBuffLen,
  1920. lpszHeaderInfo,
  1921. NULL,
  1922. fImage
  1923. );
  1924. if (fNormal && !(dwEntryType & SPARSE_CACHE_ENTRY))
  1925. {
  1926. if (dwError == ERROR_SUCCESS)
  1927. {
  1928. BETA_LOG (DOWNLOAD_CACHED);
  1929. }
  1930. else
  1931. {
  1932. BETA_LOG (DOWNLOAD_NOT_CACHED);
  1933. }
  1934. }
  1935. if (lpszBuf != NULL) {
  1936. lpszBuf = (LPSTR)FREE_MEMORY(lpszBuf);
  1937. INET_ASSERT(lpszBuf == NULL);
  1938. }
  1939. PERF_LOG(PE_TRACE, 0x2002);
  1940. DEBUG_LEAVE(dwError);
  1941. return dwError;
  1942. }
  1943. VOID
  1944. HTTP_REQUEST_HANDLE_OBJECT::GetTimeStampsForCache(
  1945. OUT LPFILETIME lpftExpiryTime,
  1946. OUT LPFILETIME lpftLastModTime,
  1947. OUT LPFILETIME lpftPostCheckTime,
  1948. OUT LPBOOL lpfHasExpiry,
  1949. OUT LPBOOL lpfHasLastModTime,
  1950. OUT LPBOOL lpfHasPostCheck
  1951. )
  1952. /*++
  1953. Routine Description:
  1954. extracts timestamps from the http response. If the timestamps don't exist,
  1955. does the default thing. has additional goodies like checking for expiry etc.
  1956. Arguments:
  1957. lpftExpiryTime - returned expiry time
  1958. lpftLatsModTime - returned last-modified time
  1959. lpfHasExpiry - returned TRUE if the response header contains an expiry
  1960. timestamp. This patameter can be NULL
  1961. lpfHasLastModTime - returned TRUE if the response header contains a
  1962. last-modified timestamp. This patameter can be NULL
  1963. Return Value:
  1964. None.
  1965. Comment:
  1966. --*/
  1967. {
  1968. DEBUG_ENTER((DBG_HTTP,
  1969. None,
  1970. "HTTP_REQUEST_HANDLE_OBJECT::GetTimeStampsForCache",
  1971. "%#x, %#x, %#x, %#x",
  1972. lpftExpiryTime,
  1973. lpftLastModTime,
  1974. lpfHasExpiry,
  1975. lpfHasLastModTime
  1976. ));
  1977. PERF_LOG(PE_TRACE, 0x9001);
  1978. char buf[512];
  1979. LPSTR lpszBuf;
  1980. BOOL fRet;
  1981. DWORD length, index = 0;
  1982. BOOL fPostCheck = FALSE;
  1983. BOOL fPreCheck = FALSE;
  1984. FILETIME ftPreCheckTime;
  1985. FILETIME ftPostCheckTime;
  1986. fRet = FALSE;
  1987. _ResponseHeaders.LockHeaders();
  1988. // Determine if a Cache-Control: max-age header exists. If so, calculate expires
  1989. // time from current time + max-age minus any delta indicated by Age:
  1990. //
  1991. // we really want all the post-fetch stuff works with 1.0 proxy
  1992. // so we loose our grip a little bit here: enable all Cache-Control
  1993. // max-age work with 1.0 response.
  1994. //
  1995. //if (IsResponseHttp1_1())
  1996. {
  1997. CHAR *ptr, *pToken;
  1998. INT nDeltaSecsPostCheck = 0;
  1999. INT nDeltaSecsPreCheck = 0;
  2000. while (1)
  2001. {
  2002. // Scan headers for Cache-Control: max-age header.
  2003. length = sizeof(buf);
  2004. switch (QueryResponseHeader(HTTP_QUERY_CACHE_CONTROL,
  2005. buf,
  2006. &length,
  2007. 0,
  2008. &index))
  2009. {
  2010. case ERROR_SUCCESS:
  2011. buf[length] = '\0';
  2012. pToken = ptr = buf;
  2013. // Parse a token from the string; test for sub headers.
  2014. while (pToken = StrTokEx(&ptr, ","))
  2015. {
  2016. SKIPWS(pToken);
  2017. if (strnicmp(POSTCHECK_SZ, pToken, POSTCHECK_LEN) == 0)
  2018. {
  2019. pToken += POSTCHECK_LEN;
  2020. SKIPWS(pToken);
  2021. if (*pToken != '=')
  2022. break;
  2023. pToken++;
  2024. SKIPWS(pToken);
  2025. nDeltaSecsPostCheck = atoi(pToken);
  2026. // Calculate post fetch time
  2027. GetCurrentGmtTime(&ftPostCheckTime);
  2028. AddLongLongToFT(&ftPostCheckTime, (nDeltaSecsPostCheck * (LONGLONG) 10000000));
  2029. fPostCheck = TRUE;
  2030. }
  2031. else if (strnicmp(PRECHECK_SZ, pToken, PRECHECK_LEN) == 0)
  2032. {
  2033. // found
  2034. pToken += PRECHECK_LEN;
  2035. SKIPWS(pToken);
  2036. if (*pToken != '=')
  2037. break;
  2038. pToken++;
  2039. SKIPWS(pToken);
  2040. nDeltaSecsPreCheck = atoi(pToken);
  2041. // Calculate pre fetch time (overwrites ftExpire )
  2042. GetCurrentGmtTime(&ftPreCheckTime);
  2043. AddLongLongToFT(&ftPreCheckTime, (nDeltaSecsPreCheck * (LONGLONG) 10000000));
  2044. fPreCheck = TRUE;
  2045. }
  2046. else if (strnicmp(MAX_AGE_SZ, pToken, MAX_AGE_LEN) == 0)
  2047. {
  2048. // Found max-age. Convert to integer form.
  2049. // Parse out time in seconds, text and convert.
  2050. pToken += MAX_AGE_LEN;
  2051. SKIPWS(pToken);
  2052. if (*pToken != '=')
  2053. break;
  2054. pToken++;
  2055. SKIPWS(pToken);
  2056. INT nDeltaSecs = atoi(pToken);
  2057. INT nAge;
  2058. // See if an Age: header exists.
  2059. // Using a local index variable:
  2060. DWORD indexAge = 0;
  2061. length = sizeof(INT)+1;
  2062. if (QueryResponseHeader(HTTP_QUERY_AGE,
  2063. &nAge,
  2064. &length,
  2065. HTTP_QUERY_FLAG_NUMBER,
  2066. &indexAge) == ERROR_SUCCESS)
  2067. {
  2068. // Found Age header. Convert and subtact from max-age.
  2069. // If less or = 0, attempt to get expires header.
  2070. nAge = ((nAge < 0) ? 0 : nAge);
  2071. nDeltaSecs -= nAge;
  2072. if (nDeltaSecs <= 0)
  2073. // The server (or some caching intermediary) possibly sent an incorrectly
  2074. // calculated header. Use "Expires", if no "max-age" directives at higher indexes.
  2075. // Note: This behaviour could cause a situation where the "pre-check"
  2076. // and "post-check" are picked up from the current index, and "max-age" is
  2077. // picked up from a higher index. "pre-check" and "post-check" are IE 5.x
  2078. // extensions, and generally not bunched together with "max-age", so this
  2079. // should work fine. More info on "pre-check" and "post-check":
  2080. // <http://msdn.microsoft.com/workshop/author/perf/perftips.asp#Use_Cache-Control_Extensions>
  2081. continue;
  2082. }
  2083. // Calculate expires time from max age.
  2084. GetCurrentGmtTime(lpftExpiryTime);
  2085. //*((LONGLONG *)lpftExpiryTime) += (nDeltaSecs * (LONGLONG) 10000000);
  2086. AddLongLongToFT(lpftExpiryTime, (nDeltaSecs * (LONGLONG) 10000000));
  2087. fRet = TRUE;
  2088. }
  2089. else if (strnicmp(MUST_REVALIDATE_SZ, pToken, MUST_REVALIDATE_LEN) == 0)
  2090. {
  2091. pToken += MUST_REVALIDATE_LEN;
  2092. SKIPWS(pToken);
  2093. if (*pToken == 0 || *pToken == ',')
  2094. SetMustRevalidate();
  2095. }
  2096. }
  2097. // If an expires time has been found, break switch.
  2098. if (fRet)
  2099. break;
  2100. // Need to bump up index to prevent possibility of never-ending outer while(1) loop.
  2101. // Otherwise, on exit from inner while, we could be stuck here reading the
  2102. // Cache-Control at the same index.
  2103. // QueryResponseHeader(HTTP_QUERY_CACHE_CONTROL, ...) will return either the next index,
  2104. // or an error, and we'll be good to go:
  2105. index++;
  2106. continue;
  2107. case ERROR_INSUFFICIENT_BUFFER:
  2108. index++;
  2109. continue;
  2110. default:
  2111. break; // no more Cache-Control headers.
  2112. }
  2113. //
  2114. // pre-post fetch headers must come in pair, also
  2115. // pre fetch header overwrites the expire
  2116. // and make sure postcheck < precheck
  2117. //
  2118. if( fPreCheck && fPostCheck &&
  2119. ( nDeltaSecsPostCheck < nDeltaSecsPreCheck ) )
  2120. {
  2121. fRet = TRUE;
  2122. *lpftPostCheckTime = ftPostCheckTime;
  2123. *lpftExpiryTime = ftPreCheckTime;
  2124. if( lpfHasPostCheck )
  2125. *lpfHasPostCheck = TRUE;
  2126. if( nDeltaSecsPostCheck == 0 &&
  2127. !(GetCacheFlags() & INTERNET_FLAG_BGUPDATE) )
  2128. {
  2129. //
  2130. // "post-check = 0"
  2131. // this page has already passed the lazy update time
  2132. // this means server wants us to do background update
  2133. // after the first download
  2134. //
  2135. // (bg fsm will be created at the end of the cache write)
  2136. //
  2137. _fLazyUpdate = TRUE;
  2138. }
  2139. }
  2140. else
  2141. {
  2142. fPreCheck = FALSE;
  2143. fPostCheck = FALSE;
  2144. }
  2145. break; // no more Cache-Control headers.
  2146. }
  2147. } // Is http 1.1
  2148. // If no expires time is calculated from max-age, check for expires header.
  2149. if (!fRet)
  2150. {
  2151. length = sizeof(buf) - 1;
  2152. index = 0;
  2153. if (QueryResponseHeader(HTTP_QUERY_EXPIRES, buf, &length, 0, &index) == ERROR_SUCCESS)
  2154. {
  2155. fRet = FParseHttpDate(lpftExpiryTime, buf);
  2156. //
  2157. // as per HTTP spec, if the expiry time is incorrect, then the page is
  2158. // considered to have expired
  2159. //
  2160. if (!fRet)
  2161. {
  2162. GetCurrentGmtTime(lpftExpiryTime);
  2163. AddLongLongToFT(lpftExpiryTime, (-1)*ONE_HOUR_DELTA); // subtract 1 hour
  2164. fRet = TRUE;
  2165. }
  2166. }
  2167. }
  2168. // We found or calculated a valid expiry time, let us check it against the
  2169. // server date if possible
  2170. FILETIME ft;
  2171. length = sizeof(buf) - 1;
  2172. index = 0;
  2173. if (QueryResponseHeader(HTTP_QUERY_DATE, buf, &length, 0, &index) == ERROR_SUCCESS
  2174. && FParseHttpDate(&ft, buf))
  2175. {
  2176. // we found a valid Data: header
  2177. // if the expires: date is less than or equal to the Date: header
  2178. // then we put an expired timestamp on this item.
  2179. // Otherwise we let it be the same as was returned by the server.
  2180. // This may cause problems due to mismatched clocks between
  2181. // the client and the server, but this is the best that can be done.
  2182. // Calulating an expires offset from server date causes pages
  2183. // coming from proxy cache to expire later, because proxies
  2184. // do not change the date: field even if the reponse has been
  2185. // sitting the proxy cache for days.
  2186. // This behaviour is as-per the HTTP spec.
  2187. if (FT2LL(*lpftExpiryTime) <= FT2LL(ft))
  2188. {
  2189. GetCurrentGmtTime(lpftExpiryTime);
  2190. AddLongLongToFT(lpftExpiryTime, (-1)*ONE_HOUR_DELTA); // subtract 1 hour
  2191. }
  2192. }
  2193. if (lpfHasExpiry)
  2194. {
  2195. *lpfHasExpiry = fRet;
  2196. }
  2197. if (!fRet)
  2198. {
  2199. lpftExpiryTime->dwLowDateTime = 0;
  2200. lpftExpiryTime->dwHighDateTime = 0;
  2201. }
  2202. fRet = FALSE;
  2203. length = sizeof(buf) - 1;
  2204. index = 0;
  2205. if (QueryResponseHeader(HTTP_QUERY_LAST_MODIFIED, buf, &length, 0, &index) == ERROR_SUCCESS)
  2206. {
  2207. DEBUG_PRINT(CACHE,
  2208. INFO,
  2209. ("Last Modified date is: %q\n",
  2210. buf
  2211. ));
  2212. fRet = FParseHttpDate(lpftLastModTime, buf);
  2213. if (!fRet)
  2214. {
  2215. DEBUG_PRINT(CACHE,
  2216. ERROR,
  2217. ("FParseHttpDate() returns FALSE\n"
  2218. ));
  2219. }
  2220. }
  2221. if (lpfHasLastModTime)
  2222. {
  2223. *lpfHasLastModTime = fRet;
  2224. }
  2225. if (!fRet)
  2226. {
  2227. lpftLastModTime->dwLowDateTime = 0;
  2228. lpftLastModTime->dwHighDateTime = 0;
  2229. }
  2230. _ResponseHeaders.UnlockHeaders();
  2231. PERF_LOG(PE_TRACE, 0x9002);
  2232. DEBUG_LEAVE(0);
  2233. }
  2234. //
  2235. // private functions
  2236. //
  2237. PRIVATE
  2238. VOID
  2239. FilterHeaders(
  2240. IN LPSTR lpszHeaderInfo,
  2241. OUT LPDWORD lpdwHeaderLen
  2242. )
  2243. /*++
  2244. Routine Description:
  2245. description-of-function.
  2246. Arguments:
  2247. lpszHeaderInfo -
  2248. lpdwHeaderLen -
  2249. Return Value:
  2250. None.
  2251. --*/
  2252. {
  2253. PERF_LOG(PE_TRACE, 0x3001);
  2254. DWORD i, len, lenT, reduced = 0, dwHeaderTableCount;
  2255. LPSTR lpT, lpMark, lpNext, *lprgszHeaderExcludeTable;
  2256. //
  2257. // skip over the status line
  2258. // NB this assumes that the raw buffer is nullterminated
  2259. //
  2260. if (lpvrgszHeaderExclusionTable) {
  2261. lprgszHeaderExcludeTable = lpvrgszHeaderExclusionTable;
  2262. dwHeaderTableCount = vdwHeaderExclusionTableCount;
  2263. } else {
  2264. lprgszHeaderExcludeTable = rgszExcludeHeaders;
  2265. dwHeaderTableCount = sizeof(rgszExcludeHeaders) / sizeof(LPSTR);
  2266. }
  2267. lpT = strchr(lpszHeaderInfo, '\r');
  2268. if (!lpT) {
  2269. PERF_LOG(PE_TRACE, 0x3002);
  2270. return;
  2271. }
  2272. INET_ASSERT(*(lpT + 1) == '\n');
  2273. lpT += 2;
  2274. do {
  2275. //
  2276. // find the header portion
  2277. //
  2278. lpMark = strchr(lpT, ':');
  2279. if (!lpMark) {
  2280. break;
  2281. }
  2282. //
  2283. // get the end of the header line
  2284. //
  2285. lpNext = strchr(lpMark, '\r');
  2286. if (!lpNext)
  2287. {
  2288. INET_ASSERT(FALSE);
  2289. // A properly formed header _should_ terminate with \r\n, but sometimes
  2290. // that just doesn't happen
  2291. lpNext = lpMark;
  2292. while (*lpNext)
  2293. {
  2294. lpNext++;
  2295. }
  2296. }
  2297. else
  2298. {
  2299. INET_ASSERT(*(lpNext + 1) == '\n');
  2300. lpNext += 2;
  2301. }
  2302. len = (DWORD) PtrDifference(lpMark, lpT) + 1;
  2303. lenT = *lpdwHeaderLen; // doing all this to see it properly in debugger
  2304. BOOL bFound = FALSE;
  2305. for (i = 0; i < dwHeaderTableCount; ++i) {
  2306. if (!strnicmp(lpT, lprgszHeaderExcludeTable[i], len)) {
  2307. bFound = TRUE;
  2308. break;
  2309. }
  2310. }
  2311. if (bFound) {
  2312. //
  2313. // nuke this header
  2314. //
  2315. len = lenT - (DWORD)PtrDifference(lpNext, lpszHeaderInfo) + 1; // for NULL character
  2316. //
  2317. // ACHTUNG memove because of overlapped copies
  2318. //
  2319. memmove(lpT, lpNext, len);
  2320. //
  2321. // keep count of how much we reduced the header by
  2322. //
  2323. reduced += (DWORD) PtrDifference(lpNext, lpT);
  2324. //
  2325. // lpT is already properly positioned because of the move
  2326. //
  2327. } else {
  2328. lpT = lpNext;
  2329. }
  2330. } while (TRUE);
  2331. *lpdwHeaderLen -= reduced;
  2332. PERF_LOG(PE_TRACE, 0x3003);
  2333. }
  2334. PRIVATE
  2335. BOOL
  2336. FExcludedMimeType(
  2337. IN LPSTR lpszMimeType,
  2338. IN DWORD dwMimeTypeSize
  2339. )
  2340. /*++
  2341. Routine Description:
  2342. description-of-function.
  2343. Arguments:
  2344. lpszMimeType -
  2345. Return Value:
  2346. BOOL
  2347. --*/
  2348. {
  2349. PERF_LOG(PE_TRACE, 0x501);
  2350. DWORD i;
  2351. LPCSTR * lprgszMimeExcludeTable = rgszExcludedMimeTypes;
  2352. DWORD dwMimeExcludeCount = (sizeof(rgszExcludedMimeTypes)/sizeof(LPSTR));
  2353. const DWORD *lprgdwMimeExcludeTableOfSizes = rgdwExcludedMimeTypeSizes;
  2354. if (lpvrgszMimeExclusionTable) {
  2355. lprgszMimeExcludeTable = (LPCSTR *)lpvrgszMimeExclusionTable;
  2356. dwMimeExcludeCount = vdwMimeExclusionTableCount;
  2357. lprgdwMimeExcludeTableOfSizes = lpvrgdwMimeExclusionTableOfSizes;
  2358. }
  2359. for (i = 0; i < dwMimeExcludeCount; ++i) {
  2360. if ((dwMimeTypeSize == lprgdwMimeExcludeTableOfSizes[i]) &&
  2361. !strnicmp(lpszMimeType,
  2362. lprgszMimeExcludeTable[i],
  2363. lprgdwMimeExcludeTableOfSizes[i])) {
  2364. PERF_LOG(PE_TRACE, 0x502);
  2365. return TRUE;
  2366. }
  2367. }
  2368. PERF_LOG(PE_TRACE, 0x503);
  2369. return FALSE;
  2370. }