Source code of Windows XP (NT5)
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.

3374 lines
98 KiB

  1. /*++
  2. Copyright (c) 2000 Microsoft Corporation
  3. Module Name:
  4. compression.cxx
  5. Abstract:
  6. Do Http compression
  7. Author:
  8. Anil Ruia (AnilR) 10-Apr-2000
  9. --*/
  10. #include "precomp.hxx"
  11. #define HTTP_COMPRESSION_KEY L"/LM/w3svc/Filters/Compression"
  12. #define HTTP_COMPRESSION_PARAMETERS L"Parameters"
  13. #define COMP_FILE_PREFIX L"$^_"
  14. #define TEMP_COMP_FILE_SUFFIX L"~TMP~"
  15. // mask for clearing bits when comparing file acls
  16. #define ACL_CLEAR_BIT_MASK (~(SE_DACL_AUTO_INHERITED | \
  17. SE_SACL_AUTO_INHERITED | \
  18. SE_DACL_PROTECTED | \
  19. SE_SACL_PROTECTED))
  20. #define HEX_TO_ASCII(c) ((CHAR)((c) < 10 ? ((c) + '0') : ((c) + 'a' - 10)))
  21. //
  22. // static variables
  23. //
  24. COMPRESSION_SCHEME *HTTP_COMPRESSION::sm_pCompressionSchemes[MAX_SERVER_SCHEMES];
  25. LIST_ENTRY HTTP_COMPRESSION::sm_CompressionThreadWorkQueue;
  26. CRITICAL_SECTION HTTP_COMPRESSION::sm_CompressionThreadLock;
  27. CRITICAL_SECTION HTTP_COMPRESSION::sm_CompressionDirectoryLock;
  28. DWORD HTTP_COMPRESSION::sm_dwNumberOfSchemes = 0;
  29. STRU *HTTP_COMPRESSION::sm_pstrCompressionDirectory = NULL;
  30. STRA *HTTP_COMPRESSION::sm_pstrCacheControlHeader = NULL;
  31. STRA *HTTP_COMPRESSION::sm_pstrExpiresHeader = NULL;
  32. BOOL HTTP_COMPRESSION::sm_fDoStaticCompression = FALSE;
  33. BOOL HTTP_COMPRESSION::sm_fDoDynamicCompression = FALSE;
  34. BOOL HTTP_COMPRESSION::sm_fDoOnDemandCompression = TRUE;
  35. BOOL HTTP_COMPRESSION::sm_fDoDiskSpaceLimiting = FALSE;
  36. BOOL HTTP_COMPRESSION::sm_fNoCompressionForHttp10 = TRUE;
  37. BOOL HTTP_COMPRESSION::sm_fNoCompressionForProxies = FALSE;
  38. BOOL HTTP_COMPRESSION::sm_fNoCompressionForRange = TRUE;
  39. BOOL HTTP_COMPRESSION::sm_fSendCacheHeaders = TRUE;
  40. DWORD HTTP_COMPRESSION::sm_dwMaxDiskSpaceUsage = COMPRESSION_DEFAULT_DISK_SPACE_USAGE;
  41. DWORD HTTP_COMPRESSION::sm_dwIoBufferSize = COMPRESSION_DEFAULT_BUFFER_SIZE;
  42. DWORD HTTP_COMPRESSION::sm_dwCompressionBufferSize = COMPRESSION_DEFAULT_BUFFER_SIZE;
  43. DWORD HTTP_COMPRESSION::sm_dwMaxQueueLength = COMPRESSION_DEFAULT_QUEUE_LENGTH;
  44. DWORD HTTP_COMPRESSION::sm_dwFilesDeletedPerDiskFree = COMPRESSION_DEFAULT_FILES_DELETED_PER_DISK_FREE;
  45. DWORD HTTP_COMPRESSION::sm_dwMinFileSizeForCompression = COMPRESSION_DEFAULT_FILE_SIZE_FOR_COMPRESSION;
  46. PBYTE HTTP_COMPRESSION::sm_pIoBuffer = NULL;
  47. PBYTE HTTP_COMPRESSION::sm_pCompressionBuffer = NULL;
  48. DWORD HTTP_COMPRESSION::sm_dwCurrentDiskSpaceUsage = 0;
  49. BOOL HTTP_COMPRESSION::sm_fCompressionVolumeIsFat = FALSE;
  50. HANDLE HTTP_COMPRESSION::sm_hThreadEvent = NULL;
  51. HANDLE HTTP_COMPRESSION::sm_hCompressionThreadHandle = NULL;
  52. DWORD HTTP_COMPRESSION::sm_dwCurrentQueueLength = 0;
  53. BOOL HTTP_COMPRESSION::sm_fHttpCompressionInitialized = FALSE;
  54. BOOL HTTP_COMPRESSION::sm_fIsTerminating = FALSE;
  55. ALLOC_CACHE_HANDLER *COMPRESSION_BUFFER::allocHandler;
  56. ALLOC_CACHE_HANDLER *COMPRESSION_CONTEXT::allocHandler;
  57. // static
  58. HRESULT HTTP_COMPRESSION::Initialize()
  59. /*++
  60. Synopsis
  61. Initialize function called during Server startup
  62. Returns
  63. HRESULT
  64. --*/
  65. {
  66. //
  67. // Construct some static variables
  68. //
  69. sm_pstrCompressionDirectory = new STRU;
  70. sm_pstrCacheControlHeader = new STRA;
  71. sm_pstrExpiresHeader = new STRA;
  72. if (sm_pstrCompressionDirectory == NULL ||
  73. sm_pstrCacheControlHeader == NULL ||
  74. sm_pstrExpiresHeader == NULL)
  75. {
  76. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  77. }
  78. HRESULT hr;
  79. if (FAILED(hr = sm_pstrCompressionDirectory->Copy(
  80. L"%windir%\\IIS Temporary Compressed Files")) ||
  81. FAILED(hr = sm_pstrCacheControlHeader->Copy("maxage=86400")) ||
  82. FAILED(hr = sm_pstrExpiresHeader->Copy(
  83. "Wed, 01 Jan 1997 12:00:00 GMT")))
  84. {
  85. return hr;
  86. }
  87. MB mb(g_pW3Server->QueryMDObject());
  88. if (!mb.Open(HTTP_COMPRESSION_KEY))
  89. {
  90. return HRESULT_FROM_WIN32(GetLastError());
  91. }
  92. //
  93. // Read the global configuration from the metabase
  94. //
  95. if (FAILED(hr = ReadMetadata(&mb)))
  96. {
  97. return hr;
  98. }
  99. //
  100. // Read in all the compression schemes and initialize them
  101. //
  102. if (FAILED(hr = InitializeCompressionSchemes(&mb)))
  103. {
  104. return hr;
  105. }
  106. mb.Close();
  107. if (FAILED(hr = COMPRESSION_CONTEXT::Initialize()))
  108. {
  109. return hr;
  110. }
  111. if (FAILED(hr = COMPRESSION_BUFFER::Initialize()))
  112. {
  113. return hr;
  114. }
  115. //
  116. // Initialize other stuff
  117. //
  118. sm_pIoBuffer = new BYTE[sm_dwIoBufferSize];
  119. sm_pCompressionBuffer = new BYTE[sm_dwCompressionBufferSize];
  120. if (!sm_pIoBuffer || !sm_pCompressionBuffer)
  121. {
  122. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  123. }
  124. INITIALIZE_CRITICAL_SECTION(&sm_CompressionDirectoryLock);
  125. if (FAILED(hr = InitializeCompressionDirectory()))
  126. {
  127. return hr;
  128. }
  129. if (FAILED(hr = InitializeCompressionThread()))
  130. {
  131. return hr;
  132. }
  133. sm_fHttpCompressionInitialized = TRUE;
  134. return S_OK;
  135. }
  136. DWORD WINAPI HTTP_COMPRESSION::CompressionThread(LPVOID)
  137. /*++
  138. Synopsis
  139. Entry point for the thread which takes Compression work-items off the
  140. queue and processes them
  141. Arguments and Return Values are ignored
  142. --*/
  143. {
  144. BOOL fTerminate = FALSE;
  145. while (!fTerminate)
  146. {
  147. //
  148. // Wait for some item to appear on the work queue
  149. //
  150. if (WaitForSingleObject(sm_hThreadEvent, INFINITE) == WAIT_FAILED)
  151. {
  152. DBG_ASSERT(FALSE);
  153. }
  154. EnterCriticalSection(&sm_CompressionThreadLock);
  155. while (!IsListEmpty(&sm_CompressionThreadWorkQueue))
  156. {
  157. LIST_ENTRY *listEntry =
  158. RemoveHeadList(&sm_CompressionThreadWorkQueue);
  159. LeaveCriticalSection(&sm_CompressionThreadLock);
  160. COMPRESSION_WORK_ITEM *workItem =
  161. CONTAINING_RECORD(listEntry,
  162. COMPRESSION_WORK_ITEM,
  163. ListEntry);
  164. //
  165. // Look at what the work item exactly is
  166. //
  167. if(workItem->WorkItemType == COMPRESSION_WORK_ITEM_TERMINATE)
  168. {
  169. fTerminate = TRUE;
  170. }
  171. else if (!fTerminate)
  172. {
  173. if (workItem->WorkItemType == COMPRESSION_WORK_ITEM_DELETE)
  174. {
  175. //
  176. // special scheme to indicate that this item is for
  177. // deletion, not compression
  178. //
  179. DeleteFile(workItem->strPhysicalPath.QueryStr());
  180. }
  181. else
  182. {
  183. DBG_ASSERT(workItem->WorkItemType == COMPRESSION_WORK_ITEM_COMPRESS);
  184. CompressFile(workItem->scheme,
  185. workItem->strPhysicalPath);
  186. }
  187. }
  188. delete workItem;
  189. EnterCriticalSection(&sm_CompressionThreadLock);
  190. }
  191. LeaveCriticalSection(&sm_CompressionThreadLock);
  192. }
  193. //
  194. // We are terminating, close the Event handle
  195. //
  196. CloseHandle(sm_hThreadEvent);
  197. sm_hThreadEvent = NULL;
  198. return 0;
  199. }
  200. // static
  201. HRESULT HTTP_COMPRESSION::InitializeCompressionThread()
  202. /*++
  203. Initialize stuff related to the Compression Thread
  204. --*/
  205. {
  206. InitializeListHead(&sm_CompressionThreadWorkQueue);
  207. INITIALIZE_CRITICAL_SECTION(&sm_CompressionThreadLock);
  208. sm_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  209. if (sm_hThreadEvent == NULL)
  210. {
  211. return HRESULT_FROM_WIN32(GetLastError());
  212. }
  213. DWORD threadId;
  214. sm_hCompressionThreadHandle = CreateThread(NULL, 0,
  215. CompressionThread,
  216. NULL, 0,
  217. &threadId);
  218. if (sm_hCompressionThreadHandle == NULL)
  219. {
  220. return HRESULT_FROM_WIN32(GetLastError());
  221. }
  222. // CODEWORK: configurable?
  223. SetThreadPriority(sm_hCompressionThreadHandle, THREAD_PRIORITY_LOWEST);
  224. return S_OK;
  225. }
  226. // static
  227. HRESULT HTTP_COMPRESSION::InitializeCompressionDirectory()
  228. /*++
  229. Setup stuff related to the compression directory
  230. --*/
  231. {
  232. WIN32_FILE_ATTRIBUTE_DATA fileInformation;
  233. //
  234. // Find if the directory exists, if not create it
  235. //
  236. if (!GetFileAttributesEx(sm_pstrCompressionDirectory->QueryStr(),
  237. GetFileExInfoStandard,
  238. &fileInformation))
  239. {
  240. if (!CreateDirectory(sm_pstrCompressionDirectory->QueryStr(), NULL))
  241. {
  242. return HRESULT_FROM_WIN32(GetLastError());
  243. }
  244. }
  245. else if (!(fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  246. {
  247. return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
  248. }
  249. if (sm_fDoDiskSpaceLimiting)
  250. {
  251. sm_dwCurrentDiskSpaceUsage = 0;
  252. //
  253. // Find usage in the directory
  254. //
  255. STACK_STRU (CompDirWildcard, 512);
  256. HRESULT hr;
  257. if (FAILED(hr = CompDirWildcard.Copy(*sm_pstrCompressionDirectory)) ||
  258. FAILED(hr = CompDirWildcard.Append(L"\\", 1)) ||
  259. FAILED(hr = CompDirWildcard.Append(COMP_FILE_PREFIX)) ||
  260. FAILED(hr = CompDirWildcard.Append(L"*", 1)))
  261. {
  262. return hr;
  263. }
  264. WIN32_FIND_DATA win32FindData;
  265. HANDLE hDirectory = FindFirstFile(CompDirWildcard.QueryStr(),
  266. &win32FindData);
  267. if (hDirectory != INVALID_HANDLE_VALUE)
  268. {
  269. do
  270. {
  271. sm_dwCurrentDiskSpaceUsage += win32FindData.nFileSizeLow;
  272. }
  273. while (FindNextFile(hDirectory, &win32FindData));
  274. FindClose(hDirectory);
  275. }
  276. }
  277. //
  278. // Find if the Volume is FAT or NTFS, check for file change differs
  279. //
  280. WCHAR volumeRoot[10];
  281. volumeRoot[0] = sm_pstrCompressionDirectory->QueryStr()[0];
  282. volumeRoot[1] = L':';
  283. volumeRoot[2] = L'\\';
  284. volumeRoot[3] = L'\0';
  285. DWORD maximumComponentLength;
  286. DWORD fileSystemFlags;
  287. WCHAR fileSystemName[256];
  288. if (!GetVolumeInformation(volumeRoot, NULL, 0, NULL,
  289. &maximumComponentLength,
  290. &fileSystemFlags,
  291. fileSystemName,
  292. sizeof(fileSystemName)/sizeof(WCHAR)) ||
  293. !wcsncmp(L"FAT", fileSystemName, 3))
  294. {
  295. sm_fCompressionVolumeIsFat = TRUE;
  296. }
  297. else
  298. {
  299. sm_fCompressionVolumeIsFat = FALSE;
  300. }
  301. for (DWORD i=0; i<sm_dwNumberOfSchemes; i++)
  302. {
  303. STRU &filePrefix = sm_pCompressionSchemes[i]->m_strFilePrefix;
  304. HRESULT hr;
  305. if (FAILED(hr = filePrefix.Copy(*sm_pstrCompressionDirectory)) ||
  306. FAILED(hr = filePrefix.Append(L"\\", 1)) ||
  307. FAILED(hr = filePrefix.Append(COMP_FILE_PREFIX)) ||
  308. FAILED(hr = filePrefix.Append(
  309. sm_pCompressionSchemes[i]->m_strCompressionSchemeName)) ||
  310. FAILED(hr = filePrefix.Append(L"_", 1)))
  311. {
  312. return hr;
  313. }
  314. }
  315. return S_OK;
  316. }
  317. // static
  318. HRESULT HTTP_COMPRESSION::InitializeCompressionSchemes(MB *pmb)
  319. /*++
  320. Synopsis:
  321. Read in all the compression schemes and initialize them
  322. Arguments:
  323. pmb: pointer to the Metabase object
  324. Return Value
  325. HRESULT
  326. --*/
  327. {
  328. COMPRESSION_SCHEME *scheme;
  329. BOOL fExistStaticScheme = FALSE;
  330. BOOL fExistDynamicScheme = FALSE;
  331. BOOL fExistOnDemandScheme = FALSE;
  332. HRESULT hr;
  333. //
  334. // Enumerate all the scheme names under the main Compression key
  335. //
  336. WCHAR schemeName[METADATA_MAX_NAME_LEN + 1];
  337. for (DWORD schemeIndex = 0;
  338. pmb->EnumObjects(L"", schemeName, schemeIndex);
  339. schemeIndex++)
  340. {
  341. if (_wcsicmp(schemeName, HTTP_COMPRESSION_PARAMETERS) == 0)
  342. {
  343. continue;
  344. }
  345. scheme = new COMPRESSION_SCHEME;
  346. if (scheme == NULL)
  347. {
  348. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  349. }
  350. if (FAILED(hr = scheme->Initialize(pmb, schemeName)))
  351. {
  352. DBGPRINTF((DBG_CONTEXT, "Error initializing scheme, error %x\n", hr));
  353. delete scheme;
  354. continue;
  355. }
  356. if (scheme->m_fDoStaticCompression)
  357. {
  358. fExistStaticScheme = TRUE;
  359. }
  360. if (scheme->m_fDoDynamicCompression)
  361. {
  362. fExistDynamicScheme = TRUE;
  363. }
  364. if (scheme->m_fDoOnDemandCompression)
  365. {
  366. fExistOnDemandScheme = TRUE;
  367. }
  368. sm_pCompressionSchemes[sm_dwNumberOfSchemes++] = scheme;
  369. if (sm_dwNumberOfSchemes == MAX_SERVER_SCHEMES)
  370. {
  371. break;
  372. }
  373. }
  374. //
  375. // Sort the schemes by priority
  376. //
  377. for (DWORD i=0; i<sm_dwNumberOfSchemes; i++)
  378. {
  379. for (DWORD j=i+1; j<sm_dwNumberOfSchemes; j++)
  380. {
  381. if (sm_pCompressionSchemes[j]->m_dwPriority >
  382. sm_pCompressionSchemes[i]->m_dwPriority)
  383. {
  384. scheme = sm_pCompressionSchemes[j];
  385. sm_pCompressionSchemes[j] = sm_pCompressionSchemes[i];
  386. sm_pCompressionSchemes[i] = scheme;
  387. }
  388. }
  389. }
  390. if (!fExistStaticScheme)
  391. {
  392. sm_fDoStaticCompression = FALSE;
  393. }
  394. if (!fExistDynamicScheme)
  395. {
  396. sm_fDoDynamicCompression = FALSE;
  397. }
  398. if (!fExistOnDemandScheme)
  399. {
  400. sm_fDoOnDemandCompression = FALSE;
  401. }
  402. return S_OK;
  403. }
  404. //static
  405. HRESULT HTTP_COMPRESSION::ReadMetadata(MB *pmb)
  406. /*++
  407. Read all the global compression configuration
  408. --*/
  409. {
  410. BUFFER TempBuff;
  411. DWORD dwNumMDRecords;
  412. DWORD dwDataSetNumber;
  413. METADATA_GETALL_RECORD *pMDRecord;
  414. DWORD i;
  415. BOOL fExpandCompressionDirectory = TRUE;
  416. HRESULT hr;
  417. if (!pmb->GetAll(HTTP_COMPRESSION_PARAMETERS,
  418. METADATA_INHERIT | METADATA_PARTIAL_PATH,
  419. IIS_MD_UT_SERVER,
  420. &TempBuff,
  421. &dwNumMDRecords,
  422. &dwDataSetNumber))
  423. {
  424. hr = HRESULT_FROM_WIN32(GetLastError());
  425. goto ErrorExit;
  426. }
  427. pMDRecord = (METADATA_GETALL_RECORD *)TempBuff.QueryPtr();
  428. for (i=0; i < dwNumMDRecords; i++, pMDRecord++)
  429. {
  430. PVOID pDataPointer = (PVOID) ((PCHAR)TempBuff.QueryPtr() +
  431. pMDRecord->dwMDDataOffset);
  432. DBG_ASSERT(pMDRecord->dwMDDataTag == 0);
  433. switch (pMDRecord->dwMDIdentifier)
  434. {
  435. case MD_HC_COMPRESSION_DIRECTORY:
  436. if (pMDRecord->dwMDDataType != STRING_METADATA &&
  437. pMDRecord->dwMDDataType != EXPANDSZ_METADATA)
  438. {
  439. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  440. goto ErrorExit;
  441. }
  442. if (pMDRecord->dwMDDataType == STRING_METADATA)
  443. {
  444. fExpandCompressionDirectory = FALSE;
  445. }
  446. if (FAILED(hr = sm_pstrCompressionDirectory->Copy(
  447. (LPWSTR)pDataPointer)))
  448. {
  449. goto ErrorExit;
  450. }
  451. break;
  452. case MD_HC_CACHE_CONTROL_HEADER:
  453. if (pMDRecord->dwMDDataType != STRING_METADATA)
  454. {
  455. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  456. goto ErrorExit;
  457. }
  458. if (FAILED(hr = sm_pstrCacheControlHeader->CopyWTruncate(
  459. (LPWSTR)pDataPointer)))
  460. {
  461. goto ErrorExit;
  462. }
  463. break;
  464. case MD_HC_EXPIRES_HEADER:
  465. if (pMDRecord->dwMDDataType != STRING_METADATA)
  466. {
  467. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  468. goto ErrorExit;
  469. }
  470. if (FAILED(hr = sm_pstrExpiresHeader->CopyWTruncate(
  471. (LPWSTR)pDataPointer)))
  472. {
  473. goto ErrorExit;
  474. }
  475. break;
  476. case MD_HC_DO_DYNAMIC_COMPRESSION:
  477. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  478. {
  479. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  480. goto ErrorExit;
  481. }
  482. sm_fDoDynamicCompression = *((BOOL *)pDataPointer);
  483. break;
  484. case MD_HC_DO_STATIC_COMPRESSION:
  485. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  486. {
  487. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  488. goto ErrorExit;
  489. }
  490. sm_fDoStaticCompression = *((BOOL *)pDataPointer);
  491. break;
  492. case MD_HC_DO_ON_DEMAND_COMPRESSION:
  493. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  494. {
  495. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  496. goto ErrorExit;
  497. }
  498. sm_fDoOnDemandCompression = *((BOOL *)pDataPointer);
  499. break;
  500. case MD_HC_DO_DISK_SPACE_LIMITING:
  501. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  502. {
  503. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  504. goto ErrorExit;
  505. }
  506. sm_fDoDiskSpaceLimiting = *((BOOL *)pDataPointer);
  507. break;
  508. case MD_HC_NO_COMPRESSION_FOR_HTTP_10:
  509. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  510. {
  511. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  512. goto ErrorExit;
  513. }
  514. sm_fNoCompressionForHttp10 = *((BOOL *)pDataPointer);
  515. break;
  516. case MD_HC_NO_COMPRESSION_FOR_PROXIES:
  517. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  518. {
  519. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  520. goto ErrorExit;
  521. }
  522. sm_fNoCompressionForProxies = *((BOOL *)pDataPointer);
  523. break;
  524. case MD_HC_NO_COMPRESSION_FOR_RANGE:
  525. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  526. {
  527. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  528. goto ErrorExit;
  529. }
  530. sm_fNoCompressionForRange = *((BOOL *)pDataPointer);
  531. break;
  532. case MD_HC_SEND_CACHE_HEADERS:
  533. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  534. {
  535. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  536. goto ErrorExit;
  537. }
  538. sm_fSendCacheHeaders = *((BOOL *)pDataPointer);
  539. break;
  540. case MD_HC_MAX_DISK_SPACE_USAGE:
  541. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  542. {
  543. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  544. goto ErrorExit;
  545. }
  546. sm_dwMaxDiskSpaceUsage = *((DWORD *)pDataPointer);
  547. break;
  548. case MD_HC_IO_BUFFER_SIZE:
  549. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  550. {
  551. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  552. goto ErrorExit;
  553. }
  554. sm_dwIoBufferSize = *((DWORD *)pDataPointer);
  555. sm_dwIoBufferSize = max(COMPRESSION_MIN_IO_BUFFER_SIZE,
  556. sm_dwIoBufferSize);
  557. sm_dwIoBufferSize = min(COMPRESSION_MAX_IO_BUFFER_SIZE,
  558. sm_dwIoBufferSize);
  559. break;
  560. case MD_HC_COMPRESSION_BUFFER_SIZE:
  561. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  562. {
  563. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  564. goto ErrorExit;
  565. }
  566. sm_dwCompressionBufferSize = *((DWORD *)pDataPointer);
  567. sm_dwCompressionBufferSize = max(COMPRESSION_MIN_COMP_BUFFER_SIZE,
  568. sm_dwCompressionBufferSize);
  569. sm_dwCompressionBufferSize = min(COMPRESSION_MAX_COMP_BUFFER_SIZE,
  570. sm_dwCompressionBufferSize);
  571. break;
  572. case MD_HC_MAX_QUEUE_LENGTH:
  573. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  574. {
  575. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  576. goto ErrorExit;
  577. }
  578. sm_dwMaxQueueLength = *((DWORD *)pDataPointer);
  579. sm_dwMaxQueueLength = min(COMPRESSION_MAX_QUEUE_LENGTH,
  580. sm_dwMaxQueueLength);
  581. break;
  582. case MD_HC_FILES_DELETED_PER_DISK_FREE:
  583. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  584. {
  585. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  586. goto ErrorExit;
  587. }
  588. sm_dwFilesDeletedPerDiskFree = *((DWORD *)pDataPointer);
  589. sm_dwFilesDeletedPerDiskFree =
  590. max(COMPRESSION_MIN_FILES_DELETED_PER_DISK_FREE,
  591. sm_dwFilesDeletedPerDiskFree);
  592. sm_dwFilesDeletedPerDiskFree =
  593. min(COMPRESSION_MAX_FILES_DELETED_PER_DISK_FREE,
  594. sm_dwFilesDeletedPerDiskFree);
  595. break;
  596. case MD_HC_MIN_FILE_SIZE_FOR_COMP:
  597. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  598. {
  599. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  600. goto ErrorExit;
  601. }
  602. sm_dwMinFileSizeForCompression = *((DWORD *)pDataPointer);
  603. break;
  604. default:
  605. ;
  606. }
  607. }
  608. //
  609. // The compression directory name contains an environment vairable,
  610. // expand it
  611. //
  612. if (fExpandCompressionDirectory)
  613. {
  614. WCHAR pszCompressionDir[MAX_PATH + 1];
  615. if (!ExpandEnvironmentStrings(sm_pstrCompressionDirectory->QueryStr(),
  616. pszCompressionDir,
  617. sizeof pszCompressionDir/sizeof WCHAR))
  618. {
  619. hr = HRESULT_FROM_WIN32(GetLastError());
  620. goto ErrorExit;
  621. }
  622. sm_pstrCompressionDirectory->Copy(pszCompressionDir);
  623. }
  624. return S_OK;
  625. ErrorExit:
  626. return hr;
  627. }
  628. HRESULT COMPRESSION_SCHEME::Initialize(
  629. IN MB *pmb,
  630. IN LPWSTR schemeName)
  631. /*++
  632. Initialize all the scheme specific data for the given scheme
  633. --*/
  634. {
  635. BUFFER TempBuff;
  636. DWORD dwNumMDRecords;
  637. DWORD dwDataSetNumber;
  638. HRESULT hr;
  639. METADATA_GETALL_RECORD *pMDRecord;
  640. DWORD i;
  641. STACK_STRU (strCompressionDll, 256);
  642. HMODULE compressionDll = NULL;
  643. //
  644. // Copy the scheme name
  645. //
  646. if (FAILED(hr = m_strCompressionSchemeName.Copy(schemeName)) ||
  647. FAILED(hr = m_straCompressionSchemeName.CopyWTruncate(schemeName)))
  648. {
  649. return hr;
  650. }
  651. //
  652. // First get all the metabase data
  653. //
  654. if (!pmb->GetAll(schemeName,
  655. METADATA_INHERIT | METADATA_PARTIAL_PATH,
  656. IIS_MD_UT_SERVER,
  657. &TempBuff,
  658. &dwNumMDRecords,
  659. &dwDataSetNumber))
  660. {
  661. hr = HRESULT_FROM_WIN32(GetLastError());
  662. goto ErrorExit;
  663. }
  664. pMDRecord = (METADATA_GETALL_RECORD *)TempBuff.QueryPtr();
  665. for (i=0; i < dwNumMDRecords; i++, pMDRecord++)
  666. {
  667. PVOID pDataPointer = (PVOID) ((PCHAR)TempBuff.QueryPtr() +
  668. pMDRecord->dwMDDataOffset);
  669. DBG_ASSERT( pMDRecord->dwMDDataTag == 0);
  670. switch (pMDRecord->dwMDIdentifier)
  671. {
  672. case MD_HC_COMPRESSION_DLL:
  673. if (pMDRecord->dwMDDataType != STRING_METADATA &&
  674. pMDRecord->dwMDDataType != EXPANDSZ_METADATA)
  675. {
  676. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  677. goto ErrorExit;
  678. }
  679. if (pMDRecord->dwMDDataType == EXPANDSZ_METADATA)
  680. {
  681. WCHAR CompressionDll[MAX_PATH + 1];
  682. if (!ExpandEnvironmentStrings((LPWSTR)pDataPointer,
  683. CompressionDll,
  684. sizeof CompressionDll/sizeof WCHAR))
  685. {
  686. hr = HRESULT_FROM_WIN32(GetLastError());
  687. goto ErrorExit;
  688. }
  689. hr = strCompressionDll.Copy(CompressionDll);
  690. }
  691. else
  692. {
  693. hr = strCompressionDll.Copy((LPWSTR)pDataPointer);
  694. }
  695. if (FAILED(hr))
  696. {
  697. goto ErrorExit;
  698. }
  699. break;
  700. case MD_HC_DO_DYNAMIC_COMPRESSION:
  701. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  702. {
  703. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  704. goto ErrorExit;
  705. }
  706. m_fDoDynamicCompression = *((BOOL *)pDataPointer);
  707. break;
  708. case MD_HC_DO_STATIC_COMPRESSION:
  709. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  710. {
  711. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  712. goto ErrorExit;
  713. }
  714. m_fDoStaticCompression = *((BOOL *)pDataPointer);
  715. break;
  716. case MD_HC_DO_ON_DEMAND_COMPRESSION:
  717. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  718. {
  719. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  720. goto ErrorExit;
  721. }
  722. m_fDoOnDemandCompression = *((BOOL *)pDataPointer);
  723. break;
  724. case MD_HC_FILE_EXTENSIONS:
  725. if (pMDRecord->dwMDDataType != MULTISZ_METADATA)
  726. {
  727. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  728. goto ErrorExit;
  729. }
  730. {
  731. MULTISZ mszTemp((LPWSTR)pDataPointer);
  732. m_mszFileExtensions.Copy(mszTemp);
  733. }
  734. break;
  735. case MD_HC_SCRIPT_FILE_EXTENSIONS:
  736. if (pMDRecord->dwMDDataType != MULTISZ_METADATA)
  737. {
  738. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  739. goto ErrorExit;
  740. }
  741. {
  742. MULTISZ mszTemp((LPWSTR)pDataPointer);
  743. m_mszScriptFileExtensions.Copy(mszTemp);
  744. }
  745. break;
  746. case MD_HC_PRIORITY:
  747. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  748. {
  749. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  750. goto ErrorExit;
  751. }
  752. m_dwPriority = *((DWORD *)pDataPointer);
  753. break;
  754. case MD_HC_DYNAMIC_COMPRESSION_LEVEL:
  755. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  756. {
  757. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  758. goto ErrorExit;
  759. }
  760. m_dwDynamicCompressionLevel = *((DWORD *)pDataPointer);
  761. m_dwDynamicCompressionLevel =
  762. min(COMPRESSION_MAX_COMPRESSION_LEVEL,
  763. m_dwDynamicCompressionLevel);
  764. break;
  765. case MD_HC_ON_DEMAND_COMP_LEVEL:
  766. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  767. {
  768. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  769. goto ErrorExit;
  770. }
  771. m_dwOnDemandCompressionLevel = *((DWORD *)pDataPointer);
  772. m_dwOnDemandCompressionLevel =
  773. min(COMPRESSION_MAX_COMPRESSION_LEVEL,
  774. m_dwOnDemandCompressionLevel);
  775. break;
  776. case MD_HC_CREATE_FLAGS:
  777. if (pMDRecord->dwMDDataType != DWORD_METADATA)
  778. {
  779. hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  780. goto ErrorExit;
  781. }
  782. m_dwCreateFlags = *((DWORD *)pDataPointer);
  783. break;
  784. default:
  785. ;
  786. }
  787. }
  788. //
  789. // Now, get the dll and the entry-points
  790. //
  791. compressionDll = LoadLibrary(strCompressionDll.QueryStr());
  792. if (compressionDll == NULL)
  793. {
  794. hr = HRESULT_FROM_WIN32(GetLastError());
  795. goto ErrorExit;
  796. }
  797. m_pfnInitCompression = (PFNCODEC_INIT_COMPRESSION)
  798. GetProcAddress(compressionDll, "InitCompression");
  799. m_pfnDeInitCompression = (PFNCODEC_DEINIT_COMPRESSION)
  800. GetProcAddress(compressionDll, "DeInitCompression");
  801. m_pfnCreateCompression = (PFNCODEC_CREATE_COMPRESSION)
  802. GetProcAddress(compressionDll, "CreateCompression");
  803. m_pfnCompress = (PFNCODEC_COMPRESS)
  804. GetProcAddress(compressionDll, "Compress");
  805. m_pfnDestroyCompression = (PFNCODEC_DESTROY_COMPRESSION)
  806. GetProcAddress(compressionDll, "DestroyCompression");
  807. m_pfnResetCompression = (PFNCODEC_RESET_COMPRESSION)
  808. GetProcAddress(compressionDll, "ResetCompression");
  809. if (!m_pfnInitCompression ||
  810. !m_pfnDeInitCompression ||
  811. !m_pfnCreateCompression ||
  812. !m_pfnCompress ||
  813. !m_pfnDestroyCompression ||
  814. !m_pfnResetCompression)
  815. {
  816. hr = HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND);
  817. goto ErrorExit;
  818. }
  819. //
  820. // Call the initialize entry-point
  821. //
  822. if (FAILED(hr = m_pfnInitCompression()) ||
  823. FAILED(hr = m_pfnCreateCompression(&m_pCompressionContext,
  824. m_dwCreateFlags)))
  825. {
  826. goto ErrorExit;
  827. }
  828. m_hCompressionDll = compressionDll;
  829. return S_OK;
  830. ErrorExit:
  831. if (compressionDll)
  832. {
  833. FreeLibrary(compressionDll);
  834. }
  835. return hr;
  836. }
  837. // static
  838. BOOL HTTP_COMPRESSION::QueueWorkItem (
  839. IN COMPRESSION_WORK_ITEM *WorkItem,
  840. IN BOOL fOverrideMaxQueueLength,
  841. IN BOOL fQueueAtHead)
  842. /*++
  843. Routine Description:
  844. This is the routine that handles queuing work items to the compression
  845. thread.
  846. Arguments:
  847. WorkRoutine - the routine that the compression thread should call
  848. to do work. If NULL, then the call is an indication to the
  849. compression thread that it should terminate.
  850. Context - a context pointer which is passed to WorkRoutine.
  851. WorkItem - if not NULL, this is a pointer to the work item to use
  852. for this request. If NULL, then this routine will allocate a
  853. work item to use. Note that by passing in a work item, the
  854. caller agrees to give up control of the memory: we will free it
  855. as necessary, either here or in the compression thread.
  856. MustSucceed - if TRUE, then this request is not subject to the
  857. limits on the number of work items that can be queued at any one
  858. time.
  859. QueueAtHead - if TRUE, then this work item is placed at the head
  860. of the queue to be serviced immediately.
  861. Return Value:
  862. TRUE if the queuing succeeded.
  863. --*/
  864. {
  865. DBG_ASSERT(WorkItem != NULL);
  866. //
  867. // Acquire the lock that protects the work queue list test to see
  868. // how many items we have on the queue. If this is not a "must
  869. // succeed" request and if we have reached the configured queue size
  870. // limit, then fail this request. "Must succeed" requests are used
  871. // for thread shutdown and other things which we really want to
  872. // work.
  873. //
  874. EnterCriticalSection(&sm_CompressionThreadLock);
  875. if (!fOverrideMaxQueueLength &&
  876. (sm_dwCurrentQueueLength >= sm_dwMaxQueueLength))
  877. {
  878. LeaveCriticalSection(&sm_CompressionThreadLock);
  879. return FALSE;
  880. }
  881. //
  882. // All looks good, so increment the count of items on the queue and
  883. // add this item to the queue.
  884. //
  885. sm_dwCurrentQueueLength++;
  886. if (fQueueAtHead)
  887. {
  888. InsertHeadList(&sm_CompressionThreadWorkQueue, &WorkItem->ListEntry);
  889. }
  890. else
  891. {
  892. InsertTailList(&sm_CompressionThreadWorkQueue, &WorkItem->ListEntry);
  893. }
  894. LeaveCriticalSection(&sm_CompressionThreadLock);
  895. //
  896. // Signal the event that will cause the compression thread to wake
  897. // up and process this work item.
  898. //
  899. SetEvent(sm_hThreadEvent);
  900. return TRUE;
  901. }
  902. // static
  903. VOID HTTP_COMPRESSION::Terminate()
  904. /*++
  905. Called on server shutdown
  906. --*/
  907. {
  908. DBG_ASSERT(sm_fHttpCompressionInitialized);
  909. sm_fIsTerminating = TRUE;
  910. //
  911. // Make the CompressionThread terminate by queueing a work item indicating
  912. // that
  913. //
  914. COMPRESSION_WORK_ITEM *WorkItem = new COMPRESSION_WORK_ITEM;
  915. if (WorkItem == NULL)
  916. {
  917. // if we can't even allocate this much memory, skip the rest of
  918. // the termination and exit
  919. return;
  920. }
  921. WorkItem->WorkItemType = COMPRESSION_WORK_ITEM_TERMINATE;
  922. QueueWorkItem(WorkItem, TRUE, TRUE);
  923. WaitForSingleObject(sm_hCompressionThreadHandle, INFINITE);
  924. CloseHandle(sm_hCompressionThreadHandle);
  925. sm_hCompressionThreadHandle = NULL;
  926. COMPRESSION_BUFFER::Terminate();
  927. COMPRESSION_CONTEXT::Terminate();
  928. //
  929. // Free static objects
  930. //
  931. delete sm_pstrCompressionDirectory;
  932. sm_pstrCompressionDirectory = NULL;
  933. delete sm_pstrCacheControlHeader;
  934. sm_pstrCacheControlHeader = NULL;
  935. delete sm_pstrExpiresHeader;
  936. sm_pstrExpiresHeader = NULL;
  937. delete sm_pIoBuffer;
  938. sm_pIoBuffer = NULL;
  939. delete sm_pCompressionBuffer;
  940. sm_pCompressionBuffer = NULL;
  941. DeleteCriticalSection(&sm_CompressionDirectoryLock);
  942. DeleteCriticalSection(&sm_CompressionThreadLock);
  943. //
  944. // For each compression scheme, unload the compression dll and free
  945. // the space that holds info about the scheme
  946. //
  947. for (DWORD i=0; i<sm_dwNumberOfSchemes; i++)
  948. {
  949. delete sm_pCompressionSchemes[i];
  950. }
  951. }
  952. // static
  953. HRESULT HTTP_COMPRESSION::DoStaticFileCompression(
  954. IN W3_CONTEXT *pW3Context,
  955. IN OUT W3_FILE_INFO **ppOpenFile)
  956. /*++
  957. Synopsis:
  958. Handle compression of static file request by either sending back the
  959. compression version if present and applicable or queueing a work-item
  960. to compress it for future requests. Called by
  961. W3_STATIC_FILE_HANDLER::FileDoWork
  962. Arguments:
  963. pW3Context: The W3_CONTEXT for the request
  964. ppOpenFile: On entry contains the cache entry to the physical path.
  965. If a suitable file is found, on exit contains cach entry to the
  966. compressed file
  967. Returns:
  968. HRESULT
  969. --*/
  970. {
  971. //
  972. // If compression is not initialized, return
  973. //
  974. if (!sm_fHttpCompressionInitialized)
  975. {
  976. pW3Context->SetDoneWithCompression();
  977. return S_OK;
  978. }
  979. //
  980. // If the client has not sent an Accept-Encoding header, or an empty
  981. // Accept-Encoding header, return
  982. //
  983. W3_REQUEST *pRequest = pW3Context->QueryRequest();
  984. CHAR *pszAcceptEncoding = pRequest->GetHeader(HttpHeaderAcceptEncoding);
  985. if (pszAcceptEncoding == NULL || *pszAcceptEncoding == '\0')
  986. {
  987. pW3Context->SetDoneWithCompression();
  988. return S_OK;
  989. }
  990. //
  991. // If we are configured to not compress for 1.0, and version is not 1.1,
  992. // return
  993. //
  994. if (sm_fNoCompressionForHttp10 &&
  995. ((pRequest->QueryVersion().MajorVersion == 0) ||
  996. ((pRequest->QueryVersion().MajorVersion == 1) &&
  997. (pRequest->QueryVersion().MinorVersion == 0))))
  998. {
  999. pW3Context->SetDoneWithCompression();
  1000. return S_OK;
  1001. }
  1002. //
  1003. // If we are configured to not compress for proxies and it is a proxy
  1004. // request, return
  1005. //
  1006. if (sm_fNoCompressionForProxies && pRequest->IsProxyRequest())
  1007. {
  1008. pW3Context->SetDoneWithCompression();
  1009. return S_OK;
  1010. }
  1011. //
  1012. // If we are configured to not Compress for range requests and Range
  1013. // is present, return
  1014. // BUGBUG: Is the correct behavior to take range on the original request
  1015. // and compress those chunks or take range on the compressed file? We do
  1016. // the latter (same as IIS 5.0), figure out if that is correct.
  1017. //
  1018. if (sm_fNoCompressionForRange &&
  1019. pRequest->GetHeader(HttpHeaderRange))
  1020. {
  1021. pW3Context->SetDoneWithCompression();
  1022. return S_OK;
  1023. }
  1024. //
  1025. // If file is too small, return
  1026. //
  1027. W3_FILE_INFO *pOrigFile = *ppOpenFile;
  1028. LARGE_INTEGER originalFileSize;
  1029. pOrigFile->QuerySize(&originalFileSize);
  1030. if (originalFileSize.QuadPart < sm_dwMinFileSizeForCompression)
  1031. {
  1032. pW3Context->SetDoneWithCompression();
  1033. return S_OK;
  1034. }
  1035. //
  1036. // Break the accept-encoding header into all the encoding accepted by
  1037. // the client sorting using the quality value
  1038. //
  1039. STACK_STRA( strAcceptEncoding, 512);
  1040. HRESULT hr;
  1041. if (FAILED(hr = strAcceptEncoding.Copy(pszAcceptEncoding)))
  1042. {
  1043. return hr;
  1044. }
  1045. STRU *pstrPhysical = pW3Context->QueryUrlContext()->QueryPhysicalPath();
  1046. LPWSTR pszExtension = wcsrchr(pstrPhysical->QueryStr(), L'.');
  1047. if (pszExtension != NULL)
  1048. {
  1049. pszExtension++;
  1050. }
  1051. DWORD dwClientCompressionCount;
  1052. DWORD matchingSchemes[MAX_SERVER_SCHEMES];
  1053. //
  1054. // Find out all schemes which will compress for this file
  1055. //
  1056. FindMatchingSchemes(strAcceptEncoding.QueryStr(),
  1057. pszExtension,
  1058. DO_STATIC_COMPRESSION,
  1059. matchingSchemes,
  1060. &dwClientCompressionCount);
  1061. //
  1062. // Try to find a static scheme, which already has the file
  1063. // pre-compressed
  1064. //
  1065. COMPRESSION_SCHEME *firstOnDemandScheme = NULL;
  1066. STACK_STRU (strCompressedFileName, 256);
  1067. for (DWORD i=0; i<dwClientCompressionCount; i++)
  1068. {
  1069. COMPRESSION_SCHEME *scheme = sm_pCompressionSchemes[matchingSchemes[i]];
  1070. //
  1071. // Now, see if there exists a version of the requested file
  1072. // that has been compressed with that scheme. First, calculate
  1073. // the name the file would have. The compressed file will live
  1074. // in the special compression directory with a special converted
  1075. // name. The file name starts with the compression scheme used,
  1076. // then the fully qualified file name where slashes and colons
  1077. // have been converted to underscores. For example, the gzip
  1078. // version of "c:\inetpub\wwwroot\file.htm" would be
  1079. // "c:\compdir\$^_gzip^c^^inetpub^wwwroot^file.htm".
  1080. //
  1081. if (FAILED(hr = ConvertPhysicalPathToCompressedPath(
  1082. scheme,
  1083. pstrPhysical,
  1084. &strCompressedFileName)))
  1085. {
  1086. return hr;
  1087. }
  1088. W3_FILE_INFO *pCompFile;
  1089. if (CheckForExistenceOfCompressedFile(
  1090. pOrigFile,
  1091. &strCompressedFileName,
  1092. &pCompFile))
  1093. {
  1094. //
  1095. // Bingo--we have a compressed version of the file in a
  1096. // format that the client understands. Add the appropriate
  1097. // Content-Encoding header so that the client knows it is
  1098. // getting compressed data and change the server's mapping
  1099. // to the compressed version of the file.
  1100. //
  1101. W3_RESPONSE *pResponse = pW3Context->QueryResponse();
  1102. if (FAILED(hr = pResponse->SetHeaderByReference(
  1103. HttpHeaderContentEncoding,
  1104. scheme->m_straCompressionSchemeName.QueryStr(),
  1105. scheme->m_straCompressionSchemeName.QueryCCH())))
  1106. {
  1107. pCompFile->DereferenceCacheEntry();
  1108. return hr;
  1109. }
  1110. if (sm_fSendCacheHeaders)
  1111. {
  1112. if (FAILED(hr = pResponse->SetHeaderByReference(
  1113. HttpHeaderExpires,
  1114. sm_pstrExpiresHeader->QueryStr(),
  1115. sm_pstrExpiresHeader->QueryCCH())) ||
  1116. FAILED(hr = pResponse->SetHeaderByReference(
  1117. HttpHeaderCacheControl,
  1118. sm_pstrCacheControlHeader->QueryStr(),
  1119. sm_pstrCacheControlHeader->QueryCCH())))
  1120. {
  1121. pCompFile->DereferenceCacheEntry();
  1122. return hr;
  1123. }
  1124. }
  1125. if (FAILED(hr = pResponse->SetHeaderByReference(
  1126. HttpHeaderVary,
  1127. "Accept-Encoding", 15)))
  1128. {
  1129. pCompFile->DereferenceCacheEntry();
  1130. return hr;
  1131. }
  1132. pW3Context->SetDoneWithCompression();
  1133. //
  1134. // Change the cache entry to point to the new file and close
  1135. // the original file
  1136. //
  1137. *ppOpenFile = pCompFile;
  1138. pOrigFile->DereferenceCacheEntry();
  1139. return S_OK;
  1140. }
  1141. //
  1142. // We found a scheme, but we don't have a matching file for it.
  1143. // Remember whether this was the first matching scheme that
  1144. // supports on-demand compression. In the event that we do not
  1145. // find any acceptable files, we'll attempt to do an on-demand
  1146. // compression for this file so that future requests get a
  1147. // compressed version.
  1148. //
  1149. if (firstOnDemandScheme == NULL &&
  1150. scheme->m_fDoOnDemandCompression)
  1151. {
  1152. firstOnDemandScheme = scheme;
  1153. }
  1154. //
  1155. // Loop to see if there is another scheme that is supported
  1156. // by both client and server.
  1157. //
  1158. }
  1159. if (sm_fDoOnDemandCompression && firstOnDemandScheme != NULL)
  1160. {
  1161. //
  1162. // if we are here means scheme was found but no compressed
  1163. // file matching any scheme. So schedule file to compress
  1164. //
  1165. QueueCompressFile(firstOnDemandScheme,
  1166. *pstrPhysical);
  1167. }
  1168. //
  1169. // No static compression for this request, will try dynamic compression
  1170. // if so configured while sending response
  1171. //
  1172. return S_OK;
  1173. }
  1174. // static
  1175. VOID HTTP_COMPRESSION::FindMatchingSchemes(
  1176. IN CHAR * pszAcceptEncoding,
  1177. IN LPWSTR pszExtension,
  1178. IN COMPRESSION_TO_PERFORM performCompr,
  1179. OUT DWORD matchingSchemes[],
  1180. OUT DWORD *pdwClientCompressionCount)
  1181. {
  1182. struct
  1183. {
  1184. LPSTR schemeName;
  1185. float quality;
  1186. } parsedAcceptEncoding[MAX_SERVER_SCHEMES];
  1187. DWORD NumberOfParsedSchemes = 0;
  1188. //
  1189. // First parse the Accept-Encoding header
  1190. //
  1191. BOOL fAddStar = FALSE;
  1192. while (*pszAcceptEncoding != '\0')
  1193. {
  1194. LPSTR schemeEnd;
  1195. BOOL fStar = FALSE;
  1196. while (*pszAcceptEncoding == ' ')
  1197. {
  1198. pszAcceptEncoding++;
  1199. }
  1200. if (isalnum(*pszAcceptEncoding))
  1201. {
  1202. parsedAcceptEncoding[NumberOfParsedSchemes].schemeName =
  1203. pszAcceptEncoding;
  1204. parsedAcceptEncoding[NumberOfParsedSchemes].quality = 1;
  1205. while (isalnum(*pszAcceptEncoding))
  1206. {
  1207. pszAcceptEncoding++;
  1208. }
  1209. // Mark the end of the scheme name
  1210. schemeEnd = pszAcceptEncoding;
  1211. }
  1212. else if (*pszAcceptEncoding == '*')
  1213. {
  1214. fStar = TRUE;
  1215. parsedAcceptEncoding[NumberOfParsedSchemes].quality = 1;
  1216. pszAcceptEncoding++;
  1217. }
  1218. else
  1219. {
  1220. // incorrect syntax
  1221. break;
  1222. }
  1223. while (*pszAcceptEncoding == ' ')
  1224. {
  1225. pszAcceptEncoding++;
  1226. }
  1227. if (*pszAcceptEncoding == ';')
  1228. {
  1229. // quality specifier: looks like q=0.7
  1230. pszAcceptEncoding++;
  1231. while (*pszAcceptEncoding == ' ')
  1232. {
  1233. pszAcceptEncoding++;
  1234. }
  1235. if (*pszAcceptEncoding == 'q')
  1236. {
  1237. pszAcceptEncoding++;
  1238. }
  1239. else
  1240. {
  1241. break;
  1242. }
  1243. while (*pszAcceptEncoding == ' ')
  1244. {
  1245. pszAcceptEncoding++;
  1246. }
  1247. if (*pszAcceptEncoding == '=')
  1248. {
  1249. pszAcceptEncoding++;
  1250. }
  1251. else
  1252. {
  1253. break;
  1254. }
  1255. while (*pszAcceptEncoding == ' ')
  1256. {
  1257. pszAcceptEncoding++;
  1258. }
  1259. parsedAcceptEncoding[NumberOfParsedSchemes].quality =
  1260. atof(pszAcceptEncoding);
  1261. while (*pszAcceptEncoding && *pszAcceptEncoding != ',')
  1262. {
  1263. pszAcceptEncoding++;
  1264. }
  1265. }
  1266. if (*pszAcceptEncoding == ',')
  1267. {
  1268. pszAcceptEncoding++;
  1269. }
  1270. if (fStar)
  1271. {
  1272. //
  1273. // A star with non-zero quality means that all schemes are
  1274. // acceptable except those explicitly unacceptable
  1275. //
  1276. if (parsedAcceptEncoding[NumberOfParsedSchemes].quality != 0)
  1277. {
  1278. fAddStar = TRUE;
  1279. }
  1280. }
  1281. else
  1282. {
  1283. *schemeEnd = '\0';
  1284. NumberOfParsedSchemes++;
  1285. if (NumberOfParsedSchemes >= MAX_SERVER_SCHEMES)
  1286. {
  1287. break;
  1288. }
  1289. }
  1290. }
  1291. //
  1292. // Now sort by quality
  1293. //
  1294. LPSTR tempName;
  1295. float tempQuality;
  1296. for (DWORD i=0; i<NumberOfParsedSchemes; i++)
  1297. {
  1298. for (DWORD j=i+1; j<NumberOfParsedSchemes; j++)
  1299. {
  1300. if (parsedAcceptEncoding[j].quality <
  1301. parsedAcceptEncoding[i].quality)
  1302. {
  1303. tempName = parsedAcceptEncoding[i].schemeName;
  1304. parsedAcceptEncoding[i].schemeName = parsedAcceptEncoding[j].schemeName;
  1305. parsedAcceptEncoding[j].schemeName = tempName;
  1306. tempQuality = parsedAcceptEncoding[i].quality;
  1307. parsedAcceptEncoding[i].quality = parsedAcceptEncoding[j].quality;
  1308. parsedAcceptEncoding[j].quality = tempQuality;
  1309. }
  1310. }
  1311. }
  1312. //
  1313. // Now convert the names to indexes into actual schemes
  1314. //
  1315. BOOL fAddedScheme[MAX_SERVER_SCHEMES];
  1316. for (i=0; i<sm_dwNumberOfSchemes; i++)
  1317. {
  1318. fAddedScheme[i] = FALSE;
  1319. }
  1320. DWORD dwNumberOfActualSchemes = 0;
  1321. for (i=0; i<NumberOfParsedSchemes; i++)
  1322. {
  1323. //
  1324. // Find this scheme
  1325. //
  1326. for (DWORD j=0; j<sm_dwNumberOfSchemes; j++)
  1327. {
  1328. if (!fAddedScheme[j] &&
  1329. !_stricmp(parsedAcceptEncoding[i].schemeName,
  1330. sm_pCompressionSchemes[j]->
  1331. m_straCompressionSchemeName.QueryStr()))
  1332. {
  1333. // found a match
  1334. fAddedScheme[j] = TRUE;
  1335. if (parsedAcceptEncoding[i].quality == 0)
  1336. {
  1337. break;
  1338. }
  1339. //
  1340. // Check if the given scheme does the required kind of
  1341. // compression. Also, check that either there is no list
  1342. // of restricted extensions or that the given file extension
  1343. // matches one in the list
  1344. //
  1345. if (performCompr == DO_STATIC_COMPRESSION)
  1346. {
  1347. if (!sm_pCompressionSchemes[j]->m_fDoStaticCompression ||
  1348. (sm_pCompressionSchemes[j]->m_mszFileExtensions.QueryStringCount() &&
  1349. (!pszExtension ||
  1350. !sm_pCompressionSchemes[j]->m_mszFileExtensions.FindString(pszExtension))))
  1351. {
  1352. break;
  1353. }
  1354. }
  1355. else
  1356. {
  1357. if (!sm_pCompressionSchemes[j]->m_fDoDynamicCompression ||
  1358. (sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.QueryStringCount() &&
  1359. (!pszExtension ||
  1360. !sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.FindString(pszExtension))))
  1361. {
  1362. break;
  1363. }
  1364. }
  1365. matchingSchemes[dwNumberOfActualSchemes++] = j;
  1366. break;
  1367. }
  1368. }
  1369. }
  1370. //
  1371. // If * was specified, add all unadded applicable schemes
  1372. //
  1373. if (fAddStar)
  1374. {
  1375. for (DWORD j=0; j<sm_dwNumberOfSchemes; j++)
  1376. {
  1377. if (!fAddedScheme[j])
  1378. {
  1379. fAddedScheme[j] = TRUE;
  1380. //
  1381. // Check if the given scheme does the required kind of
  1382. // compression. Also, check that either there is no list
  1383. // of restricted extensions or that the given file extension
  1384. // matches one in the list
  1385. //
  1386. if (performCompr == DO_STATIC_COMPRESSION)
  1387. {
  1388. if (!sm_pCompressionSchemes[j]->m_fDoStaticCompression ||
  1389. (sm_pCompressionSchemes[j]->m_mszFileExtensions.QueryStringCount() &&
  1390. (!pszExtension ||
  1391. !sm_pCompressionSchemes[j]->m_mszFileExtensions.FindString(pszExtension))))
  1392. {
  1393. continue;
  1394. }
  1395. }
  1396. else
  1397. {
  1398. if (!sm_pCompressionSchemes[j]->m_fDoDynamicCompression ||
  1399. (sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.QueryStringCount() &&
  1400. (!pszExtension ||
  1401. !sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.FindString(pszExtension))))
  1402. {
  1403. continue;
  1404. }
  1405. }
  1406. matchingSchemes[dwNumberOfActualSchemes++] = j;
  1407. }
  1408. }
  1409. }
  1410. *pdwClientCompressionCount = dwNumberOfActualSchemes;
  1411. }
  1412. // static
  1413. HRESULT HTTP_COMPRESSION::ConvertPhysicalPathToCompressedPath(
  1414. IN COMPRESSION_SCHEME *scheme,
  1415. IN STRU *pstrPhysicalPath,
  1416. OUT STRU *pstrCompressedPath)
  1417. /*++
  1418. Routine Description:
  1419. Builds a string that has the directory for the specified compression
  1420. scheme, followed by the file name with all slashes and colons
  1421. converted to underscores. This allows for a flat directory which
  1422. contains all the compressed files.
  1423. Arguments:
  1424. Scheme - the compression scheme to use.
  1425. pstrPhysicalPath - the physical file name that we want to convert.
  1426. pstrCompressedPath - the resultant string.
  1427. Return Value:
  1428. HRESULT
  1429. --*/
  1430. {
  1431. HRESULT hr;
  1432. EnterCriticalSection(&sm_CompressionDirectoryLock);
  1433. hr = pstrCompressedPath->Copy(scheme->m_strFilePrefix);
  1434. LeaveCriticalSection(&sm_CompressionDirectoryLock);
  1435. if (FAILED(hr))
  1436. {
  1437. return hr;
  1438. }
  1439. //
  1440. // Copy over the actual file name, converting slashes and colons
  1441. // to underscores.
  1442. //
  1443. DWORD cchPathLength = pstrCompressedPath->QueryCCH();
  1444. if (FAILED(hr = pstrCompressedPath->Append(*pstrPhysicalPath)))
  1445. {
  1446. return hr;
  1447. }
  1448. for (LPWSTR s = pstrCompressedPath->QueryStr() + cchPathLength;
  1449. *s != L'\0';
  1450. s++)
  1451. {
  1452. if (*s == L'\\' || *s == L':')
  1453. {
  1454. *s = L'^';
  1455. }
  1456. }
  1457. return S_OK;
  1458. }
  1459. BOOL HTTP_COMPRESSION::CheckForExistenceOfCompressedFile(
  1460. IN W3_FILE_INFO *pOrigFile,
  1461. IN STRU *pstrFileName,
  1462. OUT W3_FILE_INFO **ppCompFile,
  1463. IN BOOL fDeleteAllowed)
  1464. {
  1465. HRESULT hr;
  1466. FILE_CACHE_USER fileUser;
  1467. DBG_ASSERT( g_pW3Server->QueryFileCache() != NULL );
  1468. hr = g_pW3Server->QueryFileCache()->GetFileInfo(
  1469. *pstrFileName,
  1470. NULL,
  1471. &fileUser,
  1472. TRUE,
  1473. ppCompFile );
  1474. if (FAILED(hr))
  1475. {
  1476. return FALSE;
  1477. }
  1478. //
  1479. // So far so good. Determine whether the compressed version
  1480. // of the file is out of date. If the compressed file is
  1481. // out of date, delete it and remember that we did not get a
  1482. // good match. Note that there's really nothing we can do
  1483. // if the delete fails, so ignore any failures from it.
  1484. //
  1485. // The last write times must differ by exactly two seconds
  1486. // to constitute a match. The two-second difference results
  1487. // from the fact that we set the time on the compressed file
  1488. // to be two seconds behind the uncompressed version in
  1489. // order to ensure unique ETag: header values.
  1490. //
  1491. W3_FILE_INFO *pCompFile = *ppCompFile;
  1492. LARGE_INTEGER compressedFileSize;
  1493. FILETIME compressedFileTime;
  1494. LARGE_INTEGER *pli = (LARGE_INTEGER *)&compressedFileTime;
  1495. FILETIME originalFileTime;
  1496. BOOL success = FALSE;
  1497. pCompFile->QuerySize(&compressedFileSize);
  1498. pCompFile->QueryLastWriteTime(&compressedFileTime);
  1499. pOrigFile->QueryLastWriteTime(&originalFileTime);
  1500. pli->QuadPart += 2*10*1000*1000;
  1501. LONG timeResult = CompareFileTime(&compressedFileTime, &originalFileTime);
  1502. if ( timeResult != 0 )
  1503. {
  1504. //
  1505. // That check failed. If the compression directory is
  1506. // on a FAT volume, then see if they are within two
  1507. // seconds of one another. If they are, then consider
  1508. // things valid. We have to do this because FAT file
  1509. // times get truncated in weird ways sometimes: despite
  1510. // the fact that we request setting the file time
  1511. // different by an exact amount, it gets rounded
  1512. // sometimes.
  1513. //
  1514. if (sm_fCompressionVolumeIsFat)
  1515. {
  1516. pli->QuadPart -= 2*10*1000*1000 + 1;
  1517. timeResult += CompareFileTime(&compressedFileTime, &originalFileTime);
  1518. }
  1519. }
  1520. if (timeResult == 0)
  1521. {
  1522. //
  1523. // The filetime passed, also check if ACLs are the same
  1524. //
  1525. PSECURITY_DESCRIPTOR compressedFileAcls =
  1526. pCompFile->QuerySecDesc();
  1527. PSECURITY_DESCRIPTOR originalFileAcls =
  1528. pOrigFile->QuerySecDesc();
  1529. if (compressedFileAcls != NULL && originalFileAcls != NULL )
  1530. {
  1531. DWORD compressedFileAclsLen =
  1532. GetSecurityDescriptorLength(compressedFileAcls);
  1533. DWORD originalFileAclsLen =
  1534. GetSecurityDescriptorLength(originalFileAcls);
  1535. if (originalFileAclsLen == compressedFileAclsLen)
  1536. {
  1537. SECURITY_DESCRIPTOR_CONTROL compressedFileCtl =
  1538. ((PISECURITY_DESCRIPTOR)compressedFileAcls)->Control;
  1539. SECURITY_DESCRIPTOR_CONTROL originalFileCtl =
  1540. ((PISECURITY_DESCRIPTOR)originalFileAcls)->Control;
  1541. ((PISECURITY_DESCRIPTOR)compressedFileAcls)->Control &=
  1542. ACL_CLEAR_BIT_MASK;
  1543. ((PISECURITY_DESCRIPTOR)originalFileAcls)->Control &=
  1544. ACL_CLEAR_BIT_MASK;
  1545. success = (memcmp((PVOID)originalFileAcls,(PVOID)compressedFileAcls, originalFileAclsLen) == 0);
  1546. ((PISECURITY_DESCRIPTOR)compressedFileAcls)->Control =
  1547. compressedFileCtl;
  1548. ((PISECURITY_DESCRIPTOR)originalFileAcls)->Control =
  1549. originalFileCtl;
  1550. }
  1551. }
  1552. }
  1553. //
  1554. // The original file has changed since the compression, close this cache
  1555. // entry
  1556. //
  1557. if (!success)
  1558. {
  1559. pCompFile->DereferenceCacheEntry();
  1560. *ppCompFile = NULL;
  1561. }
  1562. //
  1563. // If the compressed file exists but is stale, queue for deletion
  1564. //
  1565. if (!success && fDeleteAllowed)
  1566. {
  1567. // don't delete if call came from compression thread because then
  1568. // delete request will be in a queue after compression request
  1569. // and will delete a file which was just moment ago compressed
  1570. COMPRESSION_WORK_ITEM *WorkItem = new COMPRESSION_WORK_ITEM;
  1571. if (WorkItem == NULL)
  1572. {
  1573. return FALSE;
  1574. }
  1575. WorkItem->WorkItemType = COMPRESSION_WORK_ITEM_DELETE;
  1576. if (FAILED(WorkItem->strPhysicalPath.Copy(*pstrFileName)))
  1577. {
  1578. delete WorkItem;
  1579. return FALSE;
  1580. }
  1581. if (!QueueWorkItem(WorkItem,
  1582. FALSE,
  1583. FALSE))
  1584. {
  1585. delete WorkItem;
  1586. return FALSE;
  1587. }
  1588. //
  1589. // If we are configured to limit the amount of disk
  1590. // space we use for compressed files, then, in a
  1591. // thread-safe manner, update the tally of disk
  1592. // space used by compression.
  1593. //
  1594. if (sm_fDoDiskSpaceLimiting)
  1595. {
  1596. InterlockedExchangeAdd((PLONG)&sm_dwCurrentDiskSpaceUsage,
  1597. -1 * compressedFileSize.LowPart);
  1598. }
  1599. }
  1600. return success;
  1601. }
  1602. BOOL HTTP_COMPRESSION::QueueCompressFile(
  1603. IN COMPRESSION_SCHEME *scheme,
  1604. IN STRU &strPhysicalPath)
  1605. /*++
  1606. Routine Description:
  1607. Queues a compress file request to the compression thread.
  1608. Arguments:
  1609. Scheme - a pointer to the compression scheme to use in compressing
  1610. the file.
  1611. pszPhysicalPath - the current physical path to the file.
  1612. Return Value:
  1613. TRUE if the queuing succeeded.
  1614. --*/
  1615. {
  1616. COMPRESSION_WORK_ITEM *WorkItem = new COMPRESSION_WORK_ITEM;
  1617. if ( WorkItem == NULL )
  1618. {
  1619. return FALSE;
  1620. }
  1621. //
  1622. // Initialize this structure with the necessary information.
  1623. //
  1624. WorkItem->WorkItemType = COMPRESSION_WORK_ITEM_COMPRESS;
  1625. WorkItem->scheme = scheme;
  1626. if (FAILED(WorkItem->strPhysicalPath.Copy(strPhysicalPath)))
  1627. {
  1628. delete WorkItem;
  1629. return FALSE;
  1630. }
  1631. //
  1632. // Queue a work item and we're done.
  1633. //
  1634. if (!QueueWorkItem(WorkItem,
  1635. FALSE,
  1636. FALSE))
  1637. {
  1638. delete WorkItem;
  1639. return FALSE;
  1640. }
  1641. return TRUE;
  1642. }
  1643. VOID HTTP_COMPRESSION::CompressFile(IN COMPRESSION_SCHEME *scheme,
  1644. IN STRU &strPhysicalPath)
  1645. /*++
  1646. Routine Description:
  1647. This routine does the real work of compressing a static file and
  1648. storing it to the compression directory with a unique name.
  1649. Arguments:
  1650. Context - a pointer to context information for the request,
  1651. including the compression scheme to use for compression and the
  1652. physical path to the file that we need to compress.
  1653. Return Value:
  1654. None. If the compression fails, we just press on and attempt to
  1655. compress the file the next time it is requested.
  1656. --*/
  1657. {
  1658. HANDLE hOriginalFile = NULL;
  1659. HANDLE hCompressedFile = NULL;
  1660. STACK_STRU ( compressedFileName, 256);
  1661. STACK_STRU ( realCompressedFileName, 256);
  1662. BOOL success = FALSE;
  1663. DWORD cbIo = 0;
  1664. DWORD bytesCompressed = 0;
  1665. SECURITY_ATTRIBUTES securityAttributes;
  1666. DWORD totalBytesWritten = 0;
  1667. BOOL usedScheme = FALSE;
  1668. LARGE_INTEGER *pli = NULL;
  1669. W3_FILE_INFO *pofiOriginalFile = NULL;
  1670. W3_FILE_INFO *pofiCompressedFile = NULL;
  1671. FILETIME originalFileTime;
  1672. PSECURITY_DESCRIPTOR originalFileAcls = NULL;
  1673. DWORD originalFileAclsLen = 0;
  1674. OVERLAPPED ovlForRead;
  1675. DWORD readStatus;
  1676. ULARGE_INTEGER readOffset;
  1677. SYSTEMTIME systemTime;
  1678. FILETIME fileTime;
  1679. WCHAR pszPid[16];
  1680. DWORD dwPid;
  1681. BYTE *pCachedFileBuffer;
  1682. BOOL fHaveCachedFileBuffer = FALSE;
  1683. DWORD dwOrigFileSize;
  1684. LARGE_INTEGER liOrigFileSize;
  1685. DIRMON_CONFIG dirConfig;
  1686. FILE_CACHE_USER fileUser;
  1687. //
  1688. // Determine the name of the file to which we will write compression
  1689. // file data. Note that we use a bogus file name initially: this
  1690. // allows us to rename it later and ensure an atomic update to the
  1691. // file system, thereby preventing other threads from returning the
  1692. // compressed file when it has only been partially written.
  1693. //
  1694. // If the caller specified a specific output file name, then use that
  1695. // instead of the calculated name.
  1696. //
  1697. if (FAILED(ConvertPhysicalPathToCompressedPath(
  1698. scheme,
  1699. &strPhysicalPath,
  1700. &realCompressedFileName)))
  1701. {
  1702. goto exit;
  1703. }
  1704. dwPid = GetCurrentProcessId();
  1705. _itow(dwPid, pszPid, 10);
  1706. if (FAILED(compressedFileName.Copy(realCompressedFileName)) ||
  1707. FAILED(compressedFileName.Append(pszPid)) ||
  1708. FAILED(compressedFileName.Append(TEMP_COMP_FILE_SUFFIX)))
  1709. {
  1710. goto exit;
  1711. }
  1712. DBG_ASSERT( g_pW3Server->QueryFileCache() != NULL );
  1713. if (FAILED(g_pW3Server->QueryFileCache()->GetFileInfo(
  1714. strPhysicalPath,
  1715. NULL,
  1716. &fileUser,
  1717. TRUE,
  1718. &pofiOriginalFile)))
  1719. {
  1720. goto exit;
  1721. }
  1722. success = CheckForExistenceOfCompressedFile(pofiOriginalFile,
  1723. &realCompressedFileName,
  1724. &pofiCompressedFile,
  1725. FALSE);
  1726. if (!success)
  1727. {
  1728. originalFileAcls = pofiOriginalFile->QuerySecDesc();
  1729. if (originalFileAcls == NULL)
  1730. {
  1731. goto exit;
  1732. }
  1733. originalFileAclsLen = GetSecurityDescriptorLength(originalFileAcls);
  1734. pofiOriginalFile->QueryLastWriteTime(&originalFileTime);
  1735. pCachedFileBuffer = pofiOriginalFile->QueryFileBuffer();
  1736. pofiOriginalFile->QuerySize(&liOrigFileSize);
  1737. dwOrigFileSize = liOrigFileSize.LowPart;
  1738. securityAttributes.nLength = originalFileAclsLen;
  1739. securityAttributes.lpSecurityDescriptor = originalFileAcls;
  1740. securityAttributes.bInheritHandle = FALSE;
  1741. //
  1742. // Do the actual file open. We open the file for exclusive access,
  1743. // and we assume that the file will not already exist.
  1744. //
  1745. hCompressedFile = CreateFile(
  1746. compressedFileName.QueryStr(),
  1747. GENERIC_WRITE,
  1748. 0,
  1749. &securityAttributes,
  1750. CREATE_ALWAYS,
  1751. FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_SEQUENTIAL_SCAN,
  1752. NULL);
  1753. if (hCompressedFile == INVALID_HANDLE_VALUE)
  1754. {
  1755. goto exit;
  1756. }
  1757. //
  1758. // Loop through the file data, reading it, then compressing it, then
  1759. // writing out the compressed data.
  1760. //
  1761. if (pCachedFileBuffer)
  1762. {
  1763. fHaveCachedFileBuffer = TRUE;
  1764. }
  1765. else
  1766. {
  1767. hOriginalFile = pofiOriginalFile->QueryFileHandle();
  1768. if ( hOriginalFile == INVALID_HANDLE_VALUE )
  1769. {
  1770. hOriginalFile = NULL;
  1771. goto exit;
  1772. }
  1773. ovlForRead.Offset = 0;
  1774. ovlForRead.OffsetHigh = 0;
  1775. ovlForRead.hEvent = NULL;
  1776. readOffset.QuadPart = 0;
  1777. }
  1778. while (TRUE)
  1779. {
  1780. if (!fHaveCachedFileBuffer)
  1781. {
  1782. success = ReadFile(hOriginalFile, sm_pIoBuffer,
  1783. sm_dwIoBufferSize, &cbIo, &ovlForRead);
  1784. if (!success)
  1785. {
  1786. switch (readStatus = GetLastError())
  1787. {
  1788. case ERROR_HANDLE_EOF:
  1789. cbIo = 0;
  1790. success = TRUE;
  1791. break;
  1792. case ERROR_IO_PENDING:
  1793. success = GetOverlappedResult(hOriginalFile, &ovlForRead, &cbIo, TRUE);
  1794. if (!success)
  1795. {
  1796. switch (readStatus = GetLastError())
  1797. {
  1798. case ERROR_HANDLE_EOF:
  1799. cbIo = 0;
  1800. success = TRUE;
  1801. break;
  1802. default:
  1803. break;
  1804. }
  1805. }
  1806. break;
  1807. default:
  1808. break;
  1809. }
  1810. }
  1811. if (!success)
  1812. {
  1813. goto exit;
  1814. }
  1815. if (cbIo)
  1816. {
  1817. readOffset.QuadPart += cbIo;
  1818. ovlForRead.Offset = readOffset.LowPart;
  1819. ovlForRead.OffsetHigh = readOffset.HighPart;
  1820. }
  1821. else
  1822. {
  1823. //
  1824. // ReadFile returns zero bytes read at the end of the
  1825. // file. If we hit that, then break out of this loop.
  1826. //
  1827. break;
  1828. }
  1829. }
  1830. //
  1831. // Remember that we used this compression scheme and that we
  1832. // will need to reset it on exit.
  1833. //
  1834. usedScheme = TRUE;
  1835. //
  1836. // Write the compressed data to the output file.
  1837. //
  1838. success = CompressAndWriteData(
  1839. scheme,
  1840. fHaveCachedFileBuffer ? pCachedFileBuffer : sm_pIoBuffer,
  1841. fHaveCachedFileBuffer ? dwOrigFileSize : cbIo,
  1842. &totalBytesWritten,
  1843. hCompressedFile
  1844. );
  1845. if (!success)
  1846. {
  1847. goto exit;
  1848. }
  1849. if (fHaveCachedFileBuffer)
  1850. {
  1851. break;
  1852. }
  1853. } // end while(TRUE)
  1854. if (!success)
  1855. {
  1856. goto exit;
  1857. }
  1858. //
  1859. // Tell the compression DLL that we're done with this file. It may
  1860. // return a last little bit of data for us to write to the the file.
  1861. // This is because most compression schemes store an end-of-file
  1862. // code in the compressed data stream. Using "0" as the number of
  1863. // bytes to compress handles this case.
  1864. //
  1865. success = CompressAndWriteData(
  1866. scheme,
  1867. NULL,
  1868. 0,
  1869. &totalBytesWritten,
  1870. hCompressedFile
  1871. );
  1872. if (!success)
  1873. {
  1874. goto exit;
  1875. }
  1876. //
  1877. // Set the compressed file's creation time to be identical to the
  1878. // original file. This allows a more granular test for things being
  1879. // out of date. If we just did a greater-than-or-equal time
  1880. // comparison, then copied or renamed files might not get registered
  1881. // as changed.
  1882. //
  1883. //
  1884. // Subtract two seconds from the file time to get the file time that
  1885. // we actually want to put on the file. We do this to make sure
  1886. // that the server will send a different Etag: header for the
  1887. // compressed file than for the uncompressed file, and the server
  1888. // uses the file time to calculate the Etag: it uses.
  1889. //
  1890. // We set it in the past so that if the original file changes, it
  1891. // should never happen to get the same value as the compressed file.
  1892. // We pick two seconds instead of one second because the FAT file
  1893. // system stores file times at a granularity of two seconds.
  1894. //
  1895. pli = (PLARGE_INTEGER)(&originalFileTime);
  1896. pli->QuadPart -= 2*10*1000*1000;
  1897. success = SetFileTime(
  1898. hCompressedFile,
  1899. NULL,
  1900. NULL,
  1901. &originalFileTime
  1902. );
  1903. if (!success)
  1904. {
  1905. goto exit;
  1906. }
  1907. CloseHandle(hCompressedFile);
  1908. hCompressedFile = NULL;
  1909. //
  1910. // Almost done now. Just rename the file to the proper name.
  1911. //
  1912. success = MoveFileEx(
  1913. compressedFileName.QueryStr(),
  1914. realCompressedFileName.QueryStr(),
  1915. MOVEFILE_REPLACE_EXISTING);
  1916. if (!success)
  1917. {
  1918. goto exit;
  1919. }
  1920. //
  1921. // If we are configured to limit the amount of disk space we use for
  1922. // compressed files, then update the tally of disk space used by
  1923. // compression. If the value is too high, then free up some space.
  1924. //
  1925. // Use InterlockedExchangeAdd to update this value because other
  1926. // threads may be deleting files from the compression directory
  1927. // because they have gone out of date.
  1928. //
  1929. if (sm_fDoDiskSpaceLimiting)
  1930. {
  1931. InterlockedExchangeAdd((PLONG)&sm_dwCurrentDiskSpaceUsage,
  1932. totalBytesWritten);
  1933. if (sm_dwCurrentDiskSpaceUsage > sm_dwMaxDiskSpaceUsage)
  1934. {
  1935. FreeDiskSpace();
  1936. }
  1937. }
  1938. }
  1939. //
  1940. // Free the context structure and return.
  1941. //
  1942. exit:
  1943. if (pofiOriginalFile != NULL)
  1944. {
  1945. pofiOriginalFile->DereferenceCacheEntry();
  1946. pofiOriginalFile = NULL;
  1947. }
  1948. if (pofiCompressedFile != NULL)
  1949. {
  1950. pofiCompressedFile->DereferenceCacheEntry();
  1951. pofiCompressedFile = NULL;
  1952. }
  1953. //
  1954. // Reset the compression context for reuse the next time through.
  1955. // This is more optimal than recreating the compression context for
  1956. // every file--it avoids allocations, etc.
  1957. //
  1958. if (usedScheme)
  1959. {
  1960. scheme->m_pfnResetCompression(scheme->m_pCompressionContext);
  1961. }
  1962. if (hCompressedFile != NULL)
  1963. {
  1964. CloseHandle(hCompressedFile);
  1965. }
  1966. if (!success)
  1967. {
  1968. DeleteFile(compressedFileName.QueryStr());
  1969. }
  1970. return;
  1971. }
  1972. VOID HTTP_COMPRESSION::FreeDiskSpace()
  1973. /*++
  1974. Routine Description:
  1975. If disk space limiting is in effect, this routine frees up the
  1976. oldest compressed files to make room for new files.
  1977. Arguments:
  1978. None.
  1979. Return Value:
  1980. None. This routine makes a best-effort attempt to free space, but
  1981. if it doesn't work, oh well.
  1982. --*/
  1983. {
  1984. WIN32_FIND_DATA **filesToDelete;
  1985. WIN32_FIND_DATA *currentFindData;
  1986. WIN32_FIND_DATA *findDataHolder;
  1987. BOOL success;
  1988. DWORD i;
  1989. HANDLE hDirectory = INVALID_HANDLE_VALUE;
  1990. STACK_STRU (strFile, MAX_PATH);
  1991. //
  1992. // Allocate space to hold the array of files to delete and the
  1993. // WIN32_FIND_DATA structures that we will need. We will find the
  1994. // least-recently-used files in the compression directory to delete.
  1995. // The reason we delete multpiple files is to reduce the number of
  1996. // times that we have to go through the process of freeing up disk
  1997. // space, since this is a fairly expensive operation.
  1998. //
  1999. filesToDelete = (WIN32_FIND_DATA **)LocalAlloc(
  2000. LMEM_FIXED,
  2001. sizeof(filesToDelete)*sm_dwFilesDeletedPerDiskFree +
  2002. sizeof(WIN32_FIND_DATA)*(sm_dwFilesDeletedPerDiskFree + 1));
  2003. if (filesToDelete == NULL)
  2004. {
  2005. return;
  2006. }
  2007. //
  2008. // Parcel out the allocation to the various uses. The initial
  2009. // currentFindData will follow the array, and then the
  2010. // WIN32_FIND_DATA structures that start off in the sorted array.
  2011. // Initialize the last access times of the entries in the array to
  2012. // 0xFFFFFFFF so that they are considered recently used and quickly
  2013. // get tossed from the array with real files.
  2014. //
  2015. currentFindData = (PWIN32_FIND_DATA)(filesToDelete + sm_dwFilesDeletedPerDiskFree);
  2016. for (i = 0; i < sm_dwFilesDeletedPerDiskFree; i++)
  2017. {
  2018. filesToDelete[i] = currentFindData + 1 + i;
  2019. filesToDelete[i]->ftLastAccessTime.dwLowDateTime = 0xFFFFFFFF;
  2020. filesToDelete[i]->ftLastAccessTime.dwHighDateTime = 0x7FFFFFFF;
  2021. }
  2022. //
  2023. // Start enumerating the files in the compression directory. Do
  2024. // this while holding the lock that protects the
  2025. // CompressionDirectoryWildcard variable, since it is possible for
  2026. // that string pointer to get freed if there is a metabase
  2027. // configuration change. Note that holding the critical section for
  2028. // a long time is not a perf issue because, in general, only this
  2029. // thread ever acquires this lock, except for the rare configuration
  2030. // change.
  2031. //
  2032. EnterCriticalSection(&sm_CompressionDirectoryLock);
  2033. STACK_STRU (CompDirWildcard, 512);
  2034. if (FAILED(CompDirWildcard.Copy(*sm_pstrCompressionDirectory)) ||
  2035. FAILED(CompDirWildcard.Append(L"\\", 1)) ||
  2036. FAILED(CompDirWildcard.Append(COMP_FILE_PREFIX)) ||
  2037. FAILED(CompDirWildcard.Append(L"*", 1)))
  2038. {
  2039. LeaveCriticalSection(&sm_CompressionDirectoryLock);
  2040. goto exit;
  2041. }
  2042. hDirectory = FindFirstFile(CompDirWildcard.QueryStr(), currentFindData);
  2043. LeaveCriticalSection(&sm_CompressionDirectoryLock);
  2044. if (hDirectory == INVALID_HANDLE_VALUE)
  2045. {
  2046. goto exit;
  2047. }
  2048. while (TRUE)
  2049. {
  2050. //
  2051. // Ignore this entry if it is a directory.
  2052. //
  2053. if (!(currentFindData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  2054. {
  2055. //
  2056. // Walk down the sorted array of files, comparing the time
  2057. // of this file against the times of the files currently in
  2058. // the array. We need to find whether this file belongs in
  2059. // the array at all, and, if so, where in the array it
  2060. // belongs.
  2061. //
  2062. for (i = 0;
  2063. i < sm_dwFilesDeletedPerDiskFree &&
  2064. CompareFileTime(&currentFindData->ftLastAccessTime,
  2065. &filesToDelete[i]->ftLastAccessTime) > 0;
  2066. i++)
  2067. {}
  2068. //
  2069. // If this file needs to get inserted in the array, put it
  2070. // in and move the other entries forward.
  2071. //
  2072. while (i < sm_dwFilesDeletedPerDiskFree)
  2073. {
  2074. findDataHolder = currentFindData;
  2075. currentFindData = filesToDelete[i];
  2076. filesToDelete[i] = findDataHolder;
  2077. i++;
  2078. }
  2079. }
  2080. //
  2081. // Get the next file in the directory.
  2082. //
  2083. if (!FindNextFile(hDirectory, currentFindData))
  2084. {
  2085. break;
  2086. }
  2087. }
  2088. //
  2089. // Now walk through the array of files to delete and get rid of
  2090. // them.
  2091. //
  2092. for (i = 0; i < sm_dwFilesDeletedPerDiskFree; i++)
  2093. {
  2094. if (filesToDelete[i]->ftLastAccessTime.dwHighDateTime != 0x7FFFFFFF)
  2095. {
  2096. if (FAILED(strFile.Copy(*sm_pstrCompressionDirectory)) ||
  2097. FAILED(strFile.Append(L"\\", 1)) ||
  2098. FAILED(strFile.Append(filesToDelete[i]->cFileName)))
  2099. {
  2100. goto exit;
  2101. }
  2102. if (DeleteFile(strFile.QueryStr()))
  2103. {
  2104. InterlockedExchangeAdd((LPLONG)&sm_dwCurrentDiskSpaceUsage,
  2105. -(LONG)filesToDelete[i]->nFileSizeLow);
  2106. }
  2107. }
  2108. }
  2109. exit:
  2110. if (filesToDelete)
  2111. {
  2112. LocalFree(filesToDelete);
  2113. }
  2114. if (hDirectory != INVALID_HANDLE_VALUE)
  2115. {
  2116. FindClose(hDirectory);
  2117. }
  2118. return;
  2119. }
  2120. BOOL HTTP_COMPRESSION::CompressAndWriteData(
  2121. COMPRESSION_SCHEME *scheme,
  2122. PBYTE InputBuffer,
  2123. DWORD BytesToCompress,
  2124. PDWORD BytesWritten,
  2125. HANDLE hCompressedFile)
  2126. /*++
  2127. Routine Description:
  2128. Takes uncompressed data, compresses it with the specified compression
  2129. scheme, and writes the result to the specified file.
  2130. Arguments:
  2131. Scheme - the compression scheme to use.
  2132. InputBuffer - the data we need to compress.
  2133. BytesToCompress - the size of the input buffer, or 0 if we should
  2134. flush the compression buffers to the file at the end of the
  2135. input file. Note that this routine DOES NOT handle compressing
  2136. a zero-byte file; we assume that the input file has some data.
  2137. BytesWritten - the number of bytes written to the output file.
  2138. hCompressedFile - a handle to the file to which we should write the
  2139. compressed results.
  2140. Return Value:
  2141. None. This routine makes a best-effort attempt to free space, but
  2142. if it doesn't work, oh well.
  2143. --*/
  2144. {
  2145. DWORD inputBytesUsed;
  2146. DWORD bytesCompressed;
  2147. HRESULT hResult;
  2148. BOOL keepGoing;
  2149. BOOL success;
  2150. DWORD cbIo;
  2151. if (sm_fIsTerminating)
  2152. {
  2153. return FALSE;
  2154. }
  2155. //
  2156. // Perform compression on the actual file data. Note that it is
  2157. // possible that the compressed data is actually larger than the
  2158. // input data, so we might need to call the compression routine
  2159. // multiple times.
  2160. //
  2161. do
  2162. {
  2163. bytesCompressed = sm_dwCompressionBufferSize;
  2164. hResult = scheme->m_pfnCompress(
  2165. scheme->m_pCompressionContext,
  2166. InputBuffer,
  2167. BytesToCompress,
  2168. sm_pCompressionBuffer,
  2169. sm_dwCompressionBufferSize,
  2170. (PLONG)&inputBytesUsed,
  2171. (PLONG)&bytesCompressed,
  2172. scheme->m_dwOnDemandCompressionLevel);
  2173. if (FAILED(hResult))
  2174. {
  2175. return FALSE;
  2176. }
  2177. if (hResult == S_OK && BytesToCompress == 0)
  2178. {
  2179. keepGoing = TRUE;
  2180. }
  2181. else
  2182. {
  2183. keepGoing = FALSE;
  2184. }
  2185. //
  2186. // If the compressor gave us any data, then write the result to
  2187. // disk. Some compression schemes buffer up data in order to
  2188. // perform better compression, so not every compression call
  2189. // will result in output data.
  2190. //
  2191. if (bytesCompressed > 0)
  2192. {
  2193. if (!WriteFile(hCompressedFile,
  2194. sm_pCompressionBuffer,
  2195. bytesCompressed,
  2196. &cbIo,
  2197. NULL))
  2198. {
  2199. return FALSE;
  2200. }
  2201. *BytesWritten += cbIo;
  2202. }
  2203. //
  2204. // Update the number of input bytes that we have compressed
  2205. // so far, and adjust the input buffer pointer accordingly.
  2206. //
  2207. BytesToCompress -= inputBytesUsed;
  2208. InputBuffer += inputBytesUsed;
  2209. }
  2210. while ( BytesToCompress > 0 || keepGoing );
  2211. return TRUE;
  2212. }
  2213. BOOL DoesCacheControlNeedMaxAge(IN PCHAR CacheControlHeaderValue)
  2214. /*++
  2215. Routine Description:
  2216. This function determines whether the Cache-Control header on a
  2217. compressed response needs to have the max-age directive added.
  2218. If there is already a max-age, or if there is a no-cache directive,
  2219. then we should not add max-age.
  2220. Arguments:
  2221. CacheControlHeaderValue - the value of the cache control header to
  2222. scan. The string should be zero-terminated.
  2223. Return Value:
  2224. TRUE if we need to add max-age; FALSE if we should not add max-age.
  2225. --*/
  2226. {
  2227. if (strstr(CacheControlHeaderValue, "max-age"))
  2228. {
  2229. return FALSE;
  2230. }
  2231. PCHAR s;
  2232. while (s = strstr(CacheControlHeaderValue, "no-cache"))
  2233. {
  2234. //
  2235. // If it is a no-cache=foo then it only refers to a specific header
  2236. // Continue
  2237. //
  2238. if (s[8] != '=')
  2239. {
  2240. return FALSE;
  2241. }
  2242. CacheControlHeaderValue = s + 8;
  2243. }
  2244. //
  2245. // We didn't find any directives that would prevent us from adding
  2246. // max-age.
  2247. //
  2248. return TRUE;
  2249. }
  2250. // static
  2251. HRESULT HTTP_COMPRESSION::OnSendResponse(
  2252. IN W3_CONTEXT *pW3Context,
  2253. IN BOOL fMoreData)
  2254. {
  2255. W3_RESPONSE *pResponse = pW3Context->QueryResponse();
  2256. W3_REQUEST *pRequest = pW3Context->QueryRequest();
  2257. //
  2258. // If compression is not initialized, return
  2259. //
  2260. if (!sm_fHttpCompressionInitialized)
  2261. {
  2262. pW3Context->SetDoneWithCompression();
  2263. return S_OK;
  2264. }
  2265. //
  2266. // If Response status is not 200 (let us not try compressing 206, 3xx or
  2267. // error responses), return
  2268. //
  2269. if (pResponse->QueryStatusCode() != HttpStatusOk.statusCode &&
  2270. pResponse->QueryStatusCode() != HttpStatusMultiStatus.statusCode)
  2271. {
  2272. pW3Context->SetDoneWithCompression();
  2273. return S_OK;
  2274. }
  2275. //
  2276. // If the client has not sent an Accept-Encoding header, or an empty
  2277. // Accept-Encoding header, return
  2278. //
  2279. CHAR *pszAcceptEncoding = pRequest->GetHeader(HttpHeaderAcceptEncoding);
  2280. if (pszAcceptEncoding == NULL || *pszAcceptEncoding == L'\0')
  2281. {
  2282. pW3Context->SetDoneWithCompression();
  2283. return S_OK;
  2284. }
  2285. //
  2286. // Don't compress for TRACEs
  2287. //
  2288. HTTP_VERB VerbType = pW3Context->QueryRequest()->QueryVerbType();
  2289. if (VerbType == HttpVerbTRACE ||
  2290. VerbType == HttpVerbTRACK)
  2291. {
  2292. pW3Context->SetDoneWithCompression();
  2293. return S_OK;
  2294. }
  2295. //
  2296. // If we are configured to not compress for 1.0, and version is not 1.1,
  2297. // return
  2298. //
  2299. if (sm_fNoCompressionForHttp10 &&
  2300. ((pRequest->QueryVersion().MajorVersion == 0) ||
  2301. ((pRequest->QueryVersion().MajorVersion == 1) &&
  2302. (pRequest->QueryVersion().MinorVersion == 0))))
  2303. {
  2304. pW3Context->SetDoneWithCompression();
  2305. return S_OK;
  2306. }
  2307. //
  2308. // If we are configured to not compress for proxies and it is a proxy
  2309. // request, return
  2310. //
  2311. if (sm_fNoCompressionForProxies && pRequest->IsProxyRequest())
  2312. {
  2313. pW3Context->SetDoneWithCompression();
  2314. return S_OK;
  2315. }
  2316. //
  2317. // If the response already has a Content-Encoding header, return
  2318. //
  2319. if (pResponse->GetHeader(HttpHeaderContentEncoding))
  2320. {
  2321. pW3Context->SetDoneWithCompression();
  2322. return S_OK;
  2323. }
  2324. //
  2325. // Now see if we have any matching scheme
  2326. //
  2327. STACK_STRA( strAcceptEncoding, 512);
  2328. HRESULT hr;
  2329. if (FAILED(hr = strAcceptEncoding.Copy(pszAcceptEncoding)))
  2330. {
  2331. return hr;
  2332. }
  2333. STRU *pstrPhysical = pW3Context->QueryUrlContext()->QueryPhysicalPath();
  2334. LPWSTR pszExtension = wcsrchr(pstrPhysical->QueryStr(), L'.');
  2335. if (pszExtension != NULL)
  2336. {
  2337. pszExtension++;
  2338. }
  2339. DWORD dwClientCompressionCount;
  2340. DWORD matchingSchemes[MAX_SERVER_SCHEMES];
  2341. //
  2342. // Find out all schemes which will compress for this url
  2343. //
  2344. FindMatchingSchemes(strAcceptEncoding.QueryStr(),
  2345. pszExtension,
  2346. DO_DYNAMIC_COMPRESSION,
  2347. matchingSchemes,
  2348. &dwClientCompressionCount);
  2349. if (dwClientCompressionCount == 0)
  2350. {
  2351. pW3Context->SetDoneWithCompression();
  2352. return S_OK;
  2353. }
  2354. //
  2355. // All tests passed, we are GO for dynamic compression
  2356. //
  2357. COMPRESSION_CONTEXT *pCompressionContext = new COMPRESSION_CONTEXT;
  2358. if (pCompressionContext == NULL)
  2359. {
  2360. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  2361. }
  2362. pW3Context->SetCompressionContext(pCompressionContext);
  2363. pCompressionContext->m_pScheme = sm_pCompressionSchemes[matchingSchemes[0]];
  2364. PCHAR xferEncoding = pResponse->GetHeader(HttpHeaderTransferEncoding);
  2365. if (xferEncoding &&
  2366. _stricmp(xferEncoding, "chunked") == 0)
  2367. {
  2368. pCompressionContext->m_fTransferChunkEncoded = TRUE;
  2369. }
  2370. //
  2371. // Remove the Content-Length header, set the Content-Encoding, Vary and
  2372. // Transfer-Encoding headers
  2373. //
  2374. if (FAILED(hr = pResponse->SetHeaderByReference(HttpHeaderContentLength,
  2375. NULL, 0)) ||
  2376. FAILED(hr = pResponse->SetHeaderByReference(
  2377. HttpHeaderContentEncoding,
  2378. pCompressionContext->m_pScheme->m_straCompressionSchemeName.QueryStr(),
  2379. pCompressionContext->m_pScheme->m_straCompressionSchemeName.QueryCCH())) ||
  2380. FAILED(hr = pResponse->SetHeader(HttpHeaderVary,
  2381. "Accept-Encoding", 15,
  2382. TRUE)) ||
  2383. FAILED(hr = pResponse->SetHeaderByReference(HttpHeaderTransferEncoding,
  2384. "chunked", 7)))
  2385. {
  2386. return hr;
  2387. }
  2388. if (sm_fSendCacheHeaders)
  2389. {
  2390. if (FAILED(hr = pResponse->SetHeaderByReference(
  2391. HttpHeaderExpires,
  2392. sm_pstrExpiresHeader->QueryStr(),
  2393. sm_pstrExpiresHeader->QueryCCH())))
  2394. {
  2395. return hr;
  2396. }
  2397. PCHAR cacheControl = pResponse->GetHeader(HttpHeaderCacheControl);
  2398. if (!cacheControl || DoesCacheControlNeedMaxAge(cacheControl))
  2399. {
  2400. if (FAILED(hr = pResponse->SetHeader(
  2401. HttpHeaderCacheControl,
  2402. sm_pstrCacheControlHeader->QueryStr(),
  2403. sm_pstrCacheControlHeader->QueryCCH(),
  2404. TRUE)))
  2405. {
  2406. return hr;
  2407. }
  2408. }
  2409. }
  2410. //
  2411. // Get a compression context
  2412. //
  2413. if (FAILED(hr = pCompressionContext->m_pScheme->m_pfnCreateCompression(
  2414. &pCompressionContext->m_pCompressionContext,
  2415. pCompressionContext->m_pScheme->m_dwCreateFlags)))
  2416. {
  2417. return hr;
  2418. }
  2419. //
  2420. // Ok, done with all the header stuff, now actually start compressing
  2421. // the entity
  2422. //
  2423. if (VerbType == HttpVerbHEAD)
  2424. {
  2425. pCompressionContext->m_fRequestIsHead = TRUE;
  2426. }
  2427. //
  2428. // BUGBUG: UL does not know about compression right now, so
  2429. // disable UL caching for this response
  2430. //
  2431. pW3Context->DisableUlCache();
  2432. return DoDynamicCompression(pW3Context,
  2433. fMoreData);
  2434. }
  2435. // static
  2436. HRESULT HTTP_COMPRESSION::DoDynamicCompression(
  2437. IN W3_CONTEXT *pW3Context,
  2438. IN BOOL fMoreData)
  2439. {
  2440. COMPRESSION_CONTEXT *pCompressionContext = pW3Context->QueryCompressionContext();
  2441. if (pCompressionContext == NULL)
  2442. {
  2443. pW3Context->SetDoneWithCompression();
  2444. return S_OK;
  2445. }
  2446. pCompressionContext->FreeBuffers();
  2447. //
  2448. // Get hold of the response chunks
  2449. //
  2450. W3_RESPONSE *pResponse = pW3Context->QueryResponse();
  2451. HRESULT hr;
  2452. if (FAILED(hr = pResponse->GetChunks(&pCompressionContext->m_ULChunkBuffer,
  2453. &pCompressionContext->m_cULChunks)))
  2454. {
  2455. return hr;
  2456. }
  2457. if (pCompressionContext->m_cULChunks > 0)
  2458. {
  2459. pCompressionContext->m_fOriginalBodyEmpty = FALSE;
  2460. }
  2461. //
  2462. // If the request was a HEAD request and we haven't seen any
  2463. // entity body, no point in going any further (if the output
  2464. // is already suppressed, we do not want to compress it and make it
  2465. // non-empty)
  2466. //
  2467. if (pCompressionContext->m_fRequestIsHead &&
  2468. pCompressionContext->m_fOriginalBodyEmpty)
  2469. {
  2470. return S_OK;
  2471. }
  2472. pCompressionContext->m_cCurrentULChunk = 0;
  2473. pCompressionContext->m_pCurrentULChunk =
  2474. (HTTP_DATA_CHUNK *)pCompressionContext->m_ULChunkBuffer.QueryPtr();
  2475. //
  2476. // Do whatever is necessary to setup the current chunk of response
  2477. // e.g. do an I/O for FileHandle chunk
  2478. //
  2479. if (FAILED(hr = pCompressionContext->SetupCurrentULChunk()))
  2480. {
  2481. return hr;
  2482. }
  2483. BOOL fKeepGoing;
  2484. do
  2485. {
  2486. fKeepGoing = FALSE;
  2487. if (pCompressionContext->m_fTransferChunkEncoded)
  2488. {
  2489. //
  2490. // If the input data is being chunk-transfered, initially,
  2491. // we'll be looking at the chunk header. This is a hex
  2492. // representation of the number of bytes in this chunk.
  2493. // Translate this number from ASCII to a DWORD and remember
  2494. // it. Also advance the chunk pointer to the start of the
  2495. // actual data.
  2496. //
  2497. if (FAILED(hr = pCompressionContext->ProcessEncodedChunkHeader()))
  2498. {
  2499. return hr;
  2500. }
  2501. }
  2502. //
  2503. // Try to compress all the contiguous bytes
  2504. //
  2505. DWORD bytesToCompress = 0;
  2506. if (pCompressionContext->m_cCurrentULChunk < pCompressionContext->m_cULChunks)
  2507. {
  2508. bytesToCompress = pCompressionContext->QueryBytesAvailable();
  2509. if (pCompressionContext->m_fTransferChunkEncoded)
  2510. {
  2511. bytesToCompress =
  2512. min(bytesToCompress,
  2513. pCompressionContext->m_dwBytesInCurrentEncodedChunk);
  2514. }
  2515. }
  2516. if (!fMoreData || bytesToCompress > 0)
  2517. {
  2518. DWORD inputBytesUsed = 0;
  2519. DWORD bytesCompressed = 0;
  2520. PBYTE compressionBuffer = pCompressionContext->GetNewBuffer();
  2521. hr = pCompressionContext->m_pScheme->m_pfnCompress(
  2522. pCompressionContext->m_pCompressionContext,
  2523. bytesToCompress ? pCompressionContext->QueryBytePtr() : NULL,
  2524. bytesToCompress,
  2525. compressionBuffer + 6,
  2526. DYNAMIC_COMPRESSION_BUFFER_SIZE,
  2527. (PLONG)&inputBytesUsed,
  2528. (PLONG)&bytesCompressed,
  2529. pCompressionContext->m_pScheme->m_dwDynamicCompressionLevel);
  2530. if (FAILED(hr))
  2531. {
  2532. return hr;
  2533. }
  2534. if (hr == S_OK)
  2535. {
  2536. fKeepGoing = TRUE;
  2537. }
  2538. if (FAILED(hr =
  2539. pCompressionContext->IncrementPointerInULChunk(inputBytesUsed)))
  2540. {
  2541. return hr;
  2542. }
  2543. if (pCompressionContext->m_fTransferChunkEncoded)
  2544. {
  2545. pCompressionContext->m_dwBytesInCurrentEncodedChunk -=
  2546. inputBytesUsed;
  2547. }
  2548. DWORD startSendLocation = 8;
  2549. DWORD bytesToSend = 0;
  2550. if (bytesCompressed > 0)
  2551. {
  2552. //
  2553. // Add the CRLF just before and after the chunk data
  2554. //
  2555. compressionBuffer[4] = '\r';
  2556. compressionBuffer[5] = '\n';
  2557. compressionBuffer[bytesCompressed + 6] = '\r';
  2558. compressionBuffer[bytesCompressed + 7] = '\n';
  2559. //
  2560. // Now create the chunk header which is basically the chunk
  2561. // size written out in hex
  2562. //
  2563. if (bytesCompressed < 0x10 )
  2564. {
  2565. startSendLocation = 3;
  2566. bytesToSend = 3 + bytesCompressed + 2;
  2567. compressionBuffer[3] = HEX_TO_ASCII(bytesCompressed);
  2568. }
  2569. else if (bytesCompressed < 0x100)
  2570. {
  2571. startSendLocation = 2;
  2572. bytesToSend = 4 + bytesCompressed + 2;
  2573. compressionBuffer[2] = HEX_TO_ASCII(bytesCompressed >> 4);
  2574. compressionBuffer[3] = HEX_TO_ASCII(bytesCompressed & 0xF);
  2575. }
  2576. else if (bytesCompressed < 0x1000)
  2577. {
  2578. startSendLocation = 1;
  2579. bytesToSend = 5 + bytesCompressed + 2;
  2580. compressionBuffer[1] = HEX_TO_ASCII(bytesCompressed >> 8);
  2581. compressionBuffer[2] = HEX_TO_ASCII((bytesCompressed >> 4) & 0xF);
  2582. compressionBuffer[3] = HEX_TO_ASCII(bytesCompressed & 0xF);
  2583. }
  2584. else
  2585. {
  2586. DBG_ASSERT( bytesCompressed < 0x10000 );
  2587. startSendLocation = 0;
  2588. bytesToSend = 6 + bytesCompressed + 2;
  2589. compressionBuffer[0] = HEX_TO_ASCII(bytesCompressed >> 12);
  2590. compressionBuffer[1] = HEX_TO_ASCII((bytesCompressed >> 8) & 0xF);
  2591. compressionBuffer[2] = HEX_TO_ASCII((bytesCompressed >> 4) & 0xF);
  2592. compressionBuffer[3] = HEX_TO_ASCII(bytesCompressed & 0xF);
  2593. }
  2594. }
  2595. if (!fKeepGoing)
  2596. {
  2597. //
  2598. // If this is the last send, add the trailer 0 length chunk
  2599. //
  2600. memcpy(compressionBuffer + bytesCompressed + 8, "0\r\n\r\n", 5);
  2601. bytesToSend += 5;
  2602. }
  2603. if (!fKeepGoing || bytesCompressed > 0)
  2604. {
  2605. if (FAILED(hr = pResponse->AddMemoryChunkByReference(
  2606. compressionBuffer + startSendLocation,
  2607. bytesToSend)))
  2608. {
  2609. return hr;
  2610. }
  2611. }
  2612. }
  2613. }
  2614. while (fKeepGoing);
  2615. return S_OK;
  2616. }
  2617. HRESULT COMPRESSION_CONTEXT::SetupCurrentULChunk()
  2618. /*++
  2619. Set up this new chunk so that compression has some bytes to read. If
  2620. the current chunk is a 0 length chunk or we have reached the end-of-file
  2621. on the file handle, this function will recurse
  2622. Return Value:
  2623. HRESULT
  2624. --*/
  2625. {
  2626. if (m_cCurrentULChunk >= m_cULChunks)
  2627. {
  2628. return S_OK;
  2629. }
  2630. if (m_pCurrentULChunk->DataChunkType == HttpDataChunkFromMemory)
  2631. {
  2632. if (m_pCurrentULChunk->FromMemory.BufferLength == 0)
  2633. {
  2634. m_cCurrentULChunk++;
  2635. m_pCurrentULChunk++;
  2636. return SetupCurrentULChunk();
  2637. }
  2638. m_fCurrentULChunkFromMemory = TRUE;
  2639. return S_OK;
  2640. }
  2641. // We do not (nor do we plan to) handle filename chunks
  2642. DBG_ASSERT(m_pCurrentULChunk->DataChunkType == HttpDataChunkFromFileHandle);
  2643. m_fCurrentULChunkFromMemory = FALSE;
  2644. if (m_pIoBuffer == NULL)
  2645. {
  2646. m_pIoBuffer = new BYTE[DYNAMIC_COMPRESSION_BUFFER_SIZE];
  2647. if (m_pIoBuffer == NULL)
  2648. {
  2649. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  2650. }
  2651. }
  2652. DWORD bytesToRead =
  2653. (DWORD)min(DYNAMIC_COMPRESSION_BUFFER_SIZE,
  2654. m_pCurrentULChunk->FromFileHandle.ByteRange.Length.QuadPart);
  2655. OVERLAPPED ovl;
  2656. ZeroMemory(&ovl, sizeof ovl);
  2657. ovl.Offset =
  2658. m_pCurrentULChunk->FromFileHandle.ByteRange.StartingOffset.LowPart;
  2659. ovl.OffsetHigh =
  2660. m_pCurrentULChunk->FromFileHandle.ByteRange.StartingOffset.HighPart;
  2661. DWORD bytesRead = 0;
  2662. if (!ReadFile(m_pCurrentULChunk->FromFileHandle.FileHandle,
  2663. m_pIoBuffer,
  2664. bytesToRead,
  2665. &bytesRead,
  2666. &ovl))
  2667. {
  2668. DWORD dwErr = GetLastError();
  2669. switch (dwErr)
  2670. {
  2671. case ERROR_IO_PENDING:
  2672. if (!GetOverlappedResult(
  2673. m_pCurrentULChunk->FromFileHandle.FileHandle,
  2674. &ovl,
  2675. &bytesRead,
  2676. TRUE))
  2677. {
  2678. dwErr = GetLastError();
  2679. switch(dwErr)
  2680. {
  2681. case ERROR_HANDLE_EOF:
  2682. m_pCurrentULChunk++;
  2683. m_cCurrentULChunk++;
  2684. return SetupCurrentULChunk();
  2685. default:
  2686. return HRESULT_FROM_WIN32(dwErr);
  2687. }
  2688. }
  2689. break;
  2690. case ERROR_HANDLE_EOF:
  2691. m_pCurrentULChunk++;
  2692. m_cCurrentULChunk++;
  2693. return SetupCurrentULChunk();
  2694. default:
  2695. return HRESULT_FROM_WIN32(dwErr);
  2696. }
  2697. }
  2698. m_pCurrentULChunk->FromFileHandle.ByteRange.Length.QuadPart -= bytesRead;
  2699. m_pCurrentULChunk->FromFileHandle.ByteRange.StartingOffset.QuadPart +=
  2700. bytesRead;
  2701. m_currentLocationInIoBuffer = 0;
  2702. m_bytesInIoBuffer = bytesRead;
  2703. return S_OK;
  2704. }
  2705. HRESULT COMPRESSION_CONTEXT::ProcessEncodedChunkHeader()
  2706. {
  2707. HRESULT hr;
  2708. while ((m_dwBytesInCurrentEncodedChunk == 0 ||
  2709. m_encodedChunkState != IN_CHUNK_DATA) &&
  2710. m_cCurrentULChunk < m_cULChunks)
  2711. {
  2712. switch (m_encodedChunkState)
  2713. {
  2714. case IN_CHUNK_LENGTH:
  2715. if (FAILED(hr = CalculateEncodedChunkByteCount()))
  2716. {
  2717. return hr;
  2718. }
  2719. break;
  2720. case IN_CHUNK_EXTENSION:
  2721. if (FAILED(hr = DeleteEncodedChunkExtension()))
  2722. {
  2723. return hr;
  2724. }
  2725. break;
  2726. case IN_CHUNK_HEADER_NEW_LINE:
  2727. if (FAILED(hr = IncrementPointerInULChunk()))
  2728. {
  2729. return hr;
  2730. }
  2731. m_encodedChunkState = IN_CHUNK_DATA;
  2732. break;
  2733. case AT_CHUNK_DATA_NEW_LINE:
  2734. if (FAILED(hr = IncrementPointerInULChunk()))
  2735. {
  2736. return hr;
  2737. }
  2738. m_encodedChunkState = IN_CHUNK_DATA_NEW_LINE;
  2739. break;
  2740. case IN_CHUNK_DATA_NEW_LINE:
  2741. if (FAILED(hr = IncrementPointerInULChunk()))
  2742. {
  2743. return hr;
  2744. }
  2745. m_encodedChunkState = IN_CHUNK_LENGTH;
  2746. break;
  2747. case IN_CHUNK_DATA:
  2748. m_encodedChunkState = AT_CHUNK_DATA_NEW_LINE;
  2749. break;
  2750. default:
  2751. DBG_ASSERT(FALSE);
  2752. }
  2753. }
  2754. return S_OK;
  2755. }
  2756. HRESULT COMPRESSION_CONTEXT::IncrementPointerInULChunk(IN DWORD dwIncr)
  2757. {
  2758. while (dwIncr &&
  2759. (m_cCurrentULChunk < m_cULChunks))
  2760. {
  2761. DWORD bytesLeft = QueryBytesAvailable();
  2762. if (bytesLeft > dwIncr)
  2763. {
  2764. if (m_fCurrentULChunkFromMemory)
  2765. {
  2766. m_pCurrentULChunk->FromMemory.pBuffer =
  2767. (PBYTE)m_pCurrentULChunk->FromMemory.pBuffer + dwIncr;
  2768. m_pCurrentULChunk->FromMemory.BufferLength -= dwIncr;
  2769. }
  2770. else
  2771. {
  2772. // We do not (nor do we plan to) handle filename chunks
  2773. DBG_ASSERT(m_pCurrentULChunk->DataChunkType == HttpDataChunkFromFileHandle);
  2774. m_currentLocationInIoBuffer += dwIncr;
  2775. }
  2776. return S_OK;
  2777. }
  2778. else
  2779. {
  2780. dwIncr -= bytesLeft;
  2781. if (m_fCurrentULChunkFromMemory ||
  2782. m_pCurrentULChunk->FromFileHandle.ByteRange.Length.QuadPart == 0)
  2783. {
  2784. m_cCurrentULChunk++;
  2785. m_pCurrentULChunk++;
  2786. }
  2787. HRESULT hr;
  2788. if (FAILED(hr = SetupCurrentULChunk()))
  2789. {
  2790. return hr;
  2791. }
  2792. }
  2793. }
  2794. return S_OK;
  2795. }
  2796. HRESULT COMPRESSION_CONTEXT::CalculateEncodedChunkByteCount()
  2797. {
  2798. CHAR c;
  2799. HRESULT hr;
  2800. //
  2801. // Walk to the first '\r' or ';' which signifies the end of the chunk
  2802. // byte count
  2803. //
  2804. while (m_cCurrentULChunk < m_cULChunks &&
  2805. SAFEIsXDigit(c = (CHAR)*QueryBytePtr()))
  2806. {
  2807. m_dwBytesInCurrentEncodedChunk <<= 4;
  2808. if (c >= '0' && c <= '9')
  2809. {
  2810. m_dwBytesInCurrentEncodedChunk += c - '0';
  2811. }
  2812. else
  2813. {
  2814. m_dwBytesInCurrentEncodedChunk += (c | 0x20) - 'a' + 10;
  2815. }
  2816. if (FAILED(hr = IncrementPointerInULChunk()))
  2817. {
  2818. return hr;
  2819. }
  2820. }
  2821. if (m_cCurrentULChunk < m_cULChunks)
  2822. {
  2823. if (c == ';')
  2824. {
  2825. m_encodedChunkState = IN_CHUNK_EXTENSION;
  2826. }
  2827. else if (c == '\r')
  2828. {
  2829. m_encodedChunkState = IN_CHUNK_HEADER_NEW_LINE;
  2830. }
  2831. else
  2832. {
  2833. DBG_ASSERT(!"Malformed chunk header");
  2834. return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
  2835. }
  2836. if (FAILED(hr = IncrementPointerInULChunk()))
  2837. {
  2838. return hr;
  2839. }
  2840. }
  2841. return S_OK;
  2842. }
  2843. HRESULT COMPRESSION_CONTEXT::DeleteEncodedChunkExtension()
  2844. {
  2845. CHAR c;
  2846. HRESULT hr;
  2847. //
  2848. // Walk to the first '\r' which signifies the end of the chunk extension
  2849. //
  2850. while (m_cCurrentULChunk < m_cULChunks &&
  2851. (c = (CHAR)*QueryBytePtr()) != '\r')
  2852. {
  2853. if (FAILED(hr = IncrementPointerInULChunk()))
  2854. {
  2855. return hr;
  2856. }
  2857. }
  2858. if (m_cCurrentULChunk < m_cULChunks)
  2859. {
  2860. m_encodedChunkState = IN_CHUNK_HEADER_NEW_LINE;
  2861. if (FAILED(hr = IncrementPointerInULChunk()))
  2862. {
  2863. return hr;
  2864. }
  2865. }
  2866. return S_OK;
  2867. }