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.

471 lines
15 KiB

  1. /*++
  2. Copyright (c) 2001 Microsoft Corporation
  3. Module Name:
  4. partial.cxx
  5. Abstract:
  6. Routines for manipulating partial cache entries
  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 "..\http\proc.h"
  16. #include "internalapi.hxx"
  17. PRIVATE VOID HTTPCACHE_REQUEST::DeletePartialCacheFile(VOID)
  18. {
  19. if (_pCacheEntryInfo->CacheEntryType & SPARSE_CACHE_ENTRY)
  20. {
  21. // We can't use the partial cache entry because it is
  22. // stale, so delete the data file we got from cache.
  23. if (_hSparseFileReadHandle != INVALID_HANDLE_VALUE)
  24. CloseHandle(_hSparseFileReadHandle);
  25. _hSparseFileReadHandle = INVALID_HANDLE_VALUE;
  26. DeleteFile(_pCacheEntryInfo->lpszLocalFileName);
  27. }
  28. }
  29. PRIVATE BOOL HTTPCACHE_REQUEST::IsPartialCacheEntry(VOID)
  30. // Given an entry in the cache, is the cache entry a partial cache entry
  31. // Side effect: _fIsPartialCache;
  32. // Precondition: CheckIfInCache() returns TRUE
  33. {
  34. INET_ASSERT(_pCacheEntryInfo != NULL);
  35. if (_pCacheEntryInfo->CacheEntryType & SPARSE_CACHE_ENTRY)
  36. _fIsPartialCache = TRUE;
  37. else
  38. _fIsPartialCache = FALSE;
  39. return _fIsPartialCache;
  40. }
  41. PRIVATE VOID HTTPCACHE_REQUEST::LockPartialCacheEntry(VOID)
  42. // Side effect: The file pointer of the cache entry got moved to the last byte
  43. // _fIsPartialCache may change status if the file seems corrupted
  44. {
  45. INET_ASSERT(_fIsPartialCache == TRUE);
  46. // open the file so it can't be deleted
  47. _hSparseFileReadHandle = CreateFile(_pCacheEntryInfo->lpszLocalFileName,
  48. GENERIC_WRITE,
  49. FILE_SHARE_READ,
  50. NULL,
  51. OPEN_EXISTING,
  52. FILE_ATTRIBUTE_NORMAL,
  53. NULL);
  54. // Check the file size
  55. if (_hSparseFileReadHandle == INVALID_HANDLE_VALUE ||
  56. (SetFilePointer (_hSparseFileReadHandle, 0, NULL, FILE_END)
  57. != _pCacheEntryInfo->dwSizeLow))
  58. {
  59. CloseHandle(_hSparseFileReadHandle);
  60. _fIsPartialCache = FALSE;
  61. }
  62. }
  63. // What are we doing here?
  64. // Three things
  65. // 1. We have a partial cache entry for this URL. No need to check
  66. // for expiry or correct user. Add a Range: header to get the
  67. // rest of the data.
  68. //
  69. // 2. If there was Last-Modified-Time, add Unless-Modified-Since header,
  70. // which assures coherency in the event the URL data changed since
  71. // the partial download.
  72. //
  73. // 3. If the entry has a 1.1 ETag, add the If-Range header.
  74. //
  75. PRIVATE BOOL HTTPCACHE_REQUEST::AddRangeRequestHeaders()
  76. {
  77. DEBUG_ENTER((DBG_CACHE,
  78. Dword,
  79. "HTTPCACHE_REQUEST::AddRangeRequestHeaders",
  80. NULL));
  81. INET_ASSERT(_pCacheEntryInfo->CacheEntryType & SPARSE_CACHE_ENTRY);
  82. TCHAR szBuf[64+HTTP_RANGE_LEN];
  83. TCHAR szBuf2[64+HTTP_UMS_LEN];
  84. DWORD cbBuf = wsprintf (szBuf, "%s: bytes=%d-", HTTP_RANGE_SZ, _pCacheEntryInfo->dwSizeLow);
  85. DWORD cbBuf2;
  86. BOOL fResult = FALSE;
  87. if (!HttpAddRequestHeadersA(_hRequest,
  88. szBuf,
  89. cbBuf,
  90. ADD_HEADER))
  91. goto quit;
  92. if (FT2LL(_pCacheEntryInfo->LastModifiedTime) != LONGLONG_ZERO)
  93. {
  94. cbBuf = sizeof(szBuf);
  95. FFileTimetoHttpDateTime(&(_pCacheEntryInfo->LastModifiedTime),
  96. szBuf, &cbBuf);
  97. cbBuf2 = wsprintf(szBuf2, "%s: %s", HTTP_UMS_SZ, szBuf);
  98. if (!HttpAddRequestHeadersA(_hRequest,
  99. szBuf2,
  100. cbBuf2,
  101. ADD_HEADER))
  102. goto quit;
  103. }
  104. // Only HTTP 1.1 support the ETag header
  105. if (!(_pCacheEntryInfo->CacheEntryType & HTTP_1_1_CACHE_ENTRY))
  106. {
  107. fResult = TRUE;
  108. }
  109. else
  110. {
  111. // Look for the ETag header
  112. TCHAR szOutBuf[256];
  113. TCHAR szHeader[256 + HTTP_IF_RANGE_LEN];
  114. DWORD dwOutBufLen = 256;
  115. DWORD dwHeaderLen;
  116. // If the ETag header is present, then add the "if-range: <etag>" header
  117. if (HttpQueryInfoA(_hRequest, WINHTTP_QUERY_ETAG, NULL, szOutBuf, &dwOutBufLen, NULL))
  118. {
  119. dwHeaderLen = wsprintf(szHeader, "%s %s", HTTP_IF_NONE_MATCH_SZ, szOutBuf);
  120. fResult = HttpAddRequestHeadersA(_hRequest,
  121. szHeader,
  122. dwHeaderLen,
  123. WINHTTP_ADDREQ_FLAG_ADD_IF_NEW);
  124. }
  125. }
  126. quit:
  127. if ((fResult == TRUE) ||
  128. (fResult == FALSE && GetLastError() == ERROR_WINHTTP_HEADER_ALREADY_EXISTS))
  129. fResult = TRUE;
  130. DEBUG_LEAVE(fResult);
  131. return fResult;
  132. }
  133. PRIVATE PRIVATE BOOL HTTPCACHE_REQUEST::IsPartialResponseCacheable(VOID)
  134. /*++
  135. Routine Description:
  136. The transfer of the content of the HTTP response has been
  137. interrupted. This call finds out if the currently (incomplete)
  138. content can be commited to the cache.
  139. The main criteria for this decision is whether the HTTP server
  140. from which the request was originated can handle range
  141. request. If the server returns a "Accept-ranges" header, then
  142. the content is cacheable.
  143. Arguments:
  144. None.
  145. Return Value:
  146. BOOL
  147. --*/
  148. {
  149. DEBUG_ENTER((DBG_CACHE,
  150. Dword,
  151. "HTTPCACHE_REQUEST::IsPartialResponseCacheable",
  152. NULL));
  153. TCHAR szHeader[256];
  154. DWORD dwSize = 256;
  155. DWORD dwIndex;
  156. BOOL fRet = FALSE;
  157. DWORD dwErr;
  158. // Until chunked transfer has been implemented, we don't care
  159. // about this bit of code
  160. /*
  161. if (_RealCacheFileSize >= _ContentLength)
  162. {
  163. // We don't handle chunked transfer upon resuming a partial
  164. // download, so we require a Content-Length header. Also,
  165. // if download file is actually complete, yet not committed
  166. // to cache (possibly because the client didn't read to eof)
  167. // then we don't want to save a partial download. Otherwise
  168. // we might later start a range request for the file starting
  169. // one byte beyond eof. MS Proxy 1.0 will return an invalid
  170. // 206 response containing the last byte of the file. Other
  171. // servers or proxies that follow the latest http spec might
  172. // return a 416 response which is equally useless to us.
  173. INET_ASSERT(fRet == FALSE);
  174. goto quit;
  175. }
  176. */
  177. // For HTTP/1.0, must have last-modified time, otherwise we
  178. // don't have a way to tell if the partial downloads are coherent.
  179. if (!InternalIsResponseHttp1_1(_hRequest) && (FT2LL(_ftLastModTime) == 0))
  180. {
  181. INET_ASSERT(fRet == FALSE);
  182. goto quit;
  183. }
  184. // First check if there's a Content-Range header from the response
  185. // headers.
  186. if (!HttpQueryInfoA(_hRequest, WINHTTP_QUERY_CONTENT_RANGE, NULL, szHeader, &dwSize, NULL))
  187. {
  188. // We didn't get a Content-Range header which implies the server
  189. // supports byte range for this URL, so we must look for the
  190. // explicit invitation of "Accept-Ranges: bytes" response header.
  191. dwSize = 256;
  192. dwIndex = 0;
  193. if (!HttpQueryInfoA(_hRequest, WINHTTP_QUERY_ACCEPT_RANGES, NULL, szHeader, &dwSize, &dwIndex)
  194. || !(dwSize == sizeof(BYTES_SZ) - 1 && !strnicmp(szHeader, BYTES_SZ, dwSize)))
  195. {
  196. INET_ASSERT(fRet == FALSE);
  197. goto quit;
  198. }
  199. }
  200. dwSize = 256;
  201. if (!InternalIsResponseHttp1_1(_hRequest))
  202. {
  203. // For HTTP/1.0, only cache responses from Server: Microsoft-???/*
  204. // Microsoft-PWS-95/*.* will respond with a single range but
  205. // with incorrect Content-Length and Content-Range headers.
  206. // Other 1.0 servers may return single range in multipart response.
  207. const static char szPrefix[] = "Microsoft-";
  208. const static DWORD ibSlashOffset = sizeof(szPrefix)-1 + 3;
  209. if (!HttpQueryInfoA(_hRequest, HTTP_QUERY_SERVER, NULL, szHeader, &dwSize, 0)
  210. || dwSize <= ibSlashOffset
  211. || szHeader[ibSlashOffset] != '/'
  212. || memcmp(szHeader, szPrefix, sizeof(szPrefix) - 1)
  213. )
  214. {
  215. INET_ASSERT(fRet == FALSE);
  216. goto quit;
  217. }
  218. }
  219. else // if (IsResponseHttp1_1())
  220. {
  221. // For http 1.1, must have strong etag. A weak etag starts with
  222. // a character other than a quote mark and cannot be used as a
  223. // coherency validator. IIS returns a weak etag when content is
  224. // modified within a single file system time quantum.
  225. if (!HttpQueryInfoA(_hRequest, HTTP_QUERY_ETAG, NULL, szHeader, &dwSize, 0)
  226. || szHeader[0] != '\"')
  227. {
  228. INET_ASSERT(fRet == FALSE);
  229. goto quit;
  230. }
  231. }
  232. fRet = TRUE;
  233. quit:
  234. DEBUG_LEAVE(fRet);
  235. return fRet;
  236. }
  237. PRIVATE PRIVATE BOOL HTTPCACHE_REQUEST::FakePartialCacheResponseHeaders(VOID)
  238. /*++
  239. Routine Description:
  240. Resume partial download.
  241. First thing we do here is we verify the Content-Range header is
  242. a valid one, and then parse it to get the numerical range values
  243. After that's verified, the second thing we do is we fake the
  244. response headers. The intent is to fix up the response headers
  245. to appear same as a 200 response so that the partial caching
  246. becomes completely invisible to the user app
  247. Arguments:
  248. None.
  249. Return Value:
  250. BOOL
  251. --*/
  252. {
  253. DEBUG_ENTER((DBG_CACHE,
  254. Dword,
  255. "HTTPCACHE_REQUEST::FakePartialCacheResponseHeaders",
  256. NULL));
  257. TCHAR szBuffer[256];
  258. DWORD dwBufferLength = sizeof(szBuffer);
  259. DWORD dwIndex = 0;
  260. TCHAR szTempBuf[256];
  261. DWORD dwErr = ERROR_SUCCESS;
  262. INET_ASSERT (!_fCacheWriteInProgress);
  263. // Retrieve the start and end of Content-Range header
  264. if (!HttpQueryInfoA(_hRequest,
  265. HTTP_QUERY_CONTENT_RANGE,
  266. NULL,
  267. szBuffer,
  268. &dwBufferLength,
  269. &dwIndex))
  270. {
  271. goto quit;
  272. }
  273. INET_ASSERT (szBuffer);
  274. PSTR pszLimit;
  275. LPSTR pszHeader;
  276. DWORD cbHeader;
  277. pszHeader = szBuffer;
  278. pszLimit = szBuffer + dwBufferLength;
  279. INET_ASSERT (pszLimit);
  280. *pszLimit = 0;
  281. // Extract the document length as a string and number.
  282. // The http 1.1 spec is very explicit that we MUST parse
  283. // the header and return an error if invalid. We expect
  284. // it to be of the form Content-Range: bytes xxx-yyy/zzz.
  285. PSTR pszStart, pszEnd, pszLength;
  286. DWORD dwStart, dwEnd, dwLength;
  287. // Ensure that value is prefixed with "bytes"
  288. PSTR pszBytes = pszHeader;
  289. if (strnicmp (pszBytes, BYTES_SZ, sizeof(BYTES_SZ) - 1))
  290. goto quit;
  291. // Parse and validate start of range.
  292. pszStart = pszBytes + sizeof(BYTES_SZ) - 1;
  293. SKIPWS (pszStart);
  294. dwStart = StrToInt (pszStart);
  295. if (dwStart != _pCacheEntryInfo->dwSizeLow)
  296. goto quit;
  297. // Parse and validate end of range.
  298. pszEnd = StrChr (pszStart, '-');
  299. if (!pszEnd++)
  300. goto quit;
  301. dwEnd = StrToInt (pszEnd);
  302. if (dwStart > dwEnd)
  303. goto quit;
  304. // Parse and validate length.
  305. pszLength = StrChr (pszEnd, '/');
  306. if (!pszLength)
  307. goto quit;
  308. pszLength++;
  309. dwLength = StrToInt (pszLength);
  310. if (dwEnd + 1 != dwLength)
  311. goto quit;
  312. // Which headers are we exactly changing here?
  313. // 1. Change the content-length header to reflect the length of
  314. // the total amount of data, not just the partial content.
  315. // 2. Add an implicit "Accept-Ranges: bytes" header
  316. // 3. Change the status code from 206 to 200
  317. // 4. Remove the "Content-Range" header
  318. // Step 1: Modify the content-length header
  319. wsprintf (szTempBuf, "%d", dwLength);
  320. dwErr = InternalReplaceResponseHeader(
  321. _hRequest,
  322. HTTP_QUERY_CONTENT_LENGTH,
  323. szTempBuf,
  324. strlen(szTempBuf),
  325. 0,
  326. WINHTTP_ADDREQ_FLAG_REPLACE);
  327. /// _pRequest->SetContentLength(dwLength); // why do we need to do this????
  328. INET_ASSERT(dwErr == ERROR_SUCCESS);
  329. // Step 2: Some servers omit "Accept-Ranges: bytes" since it is implicit
  330. // in a 206 response. This is important to Adobe Amber ActiveX
  331. // control and other clients that may issue their own range
  332. // requests. Add the header if not present.
  333. if (!InternalIsResponseHeaderPresent(_hRequest, HTTP_QUERY_ACCEPT_RANGES))
  334. {
  335. const static char szAccept[] = "bytes";
  336. dwErr = InternalReplaceResponseHeader(
  337. _hRequest,
  338. HTTP_QUERY_ACCEPT_RANGES,
  339. (LPSTR) szAccept,
  340. sizeof(szAccept)-1,
  341. 0,
  342. ADD_HEADER);
  343. INET_ASSERT (dwErr == ERROR_SUCCESS);
  344. }
  345. // Step 3: Fake the status code
  346. /* this code is replaced by the call to ReplaceStatusHeader()
  347. dwErr = InternalReplaceResponseHeader(
  348. _hRequest,
  349. HTTP_QUERY_STATUS_TEXT,
  350. NULL,
  351. 0,
  352. 0,
  353. WINHTTP_ADDREQ_FLAG_REPLACE);
  354. dwErr = InternalReplaceResponseHeader(
  355. _hRequest,
  356. HTTP_QUERY_STATUS_CODE,
  357. NULL,
  358. 0,
  359. 0,
  360. WINHTTP_ADDREQ_FLAG_REPLACE);
  361. INET_ASSERT(dwErr == ERROR_SUCCESS);
  362. */
  363. BOOL bOK;
  364. ((HTTP_REQUEST_HANDLE_OBJECT *)_hRequest)->ReplaceStatusHeader("200 OK");
  365. ((HTTP_REQUEST_HANDLE_OBJECT *)_hRequest)->SetStatusCode(200);
  366. ((HTTP_REQUEST_HANDLE_OBJECT *)_hRequest)->UpdateResponseHeaders(&bOK); //BUGBUG do we need this?
  367. // Step 4: Remove the Content-Range header. We'll just hope that this
  368. // operation really works. (no Error code is returned by the caller)
  369. dwErr = InternalReplaceResponseHeader(
  370. _hRequest,
  371. HTTP_QUERY_CONTENT_RANGE,
  372. NULL,
  373. 0,
  374. 0,
  375. WINHTTP_ADDREQ_FLAG_REPLACE);
  376. // Adjust the file size
  377. _VirtualCacheFileSize = dwStart;
  378. _RealCacheFileSize = dwStart;
  379. DEBUG_LEAVE (dwErr);
  380. return TRUE;
  381. quit:
  382. DEBUG_LEAVE (dwErr);
  383. return FALSE;
  384. }