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

3294 lines
99 KiB

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
compression.cxx
Abstract:
Do Http compression
Author:
Anil Ruia (AnilR) 10-Apr-2000
--*/
#include "precomp.hxx"
#define HTTP_COMPRESSION_KEY L"/LM/w3svc/Filters/Compression"
#define HTTP_COMPRESSION_PARAMETERS L"Parameters"
#define COMP_FILE_PREFIX L"$^_"
#define TEMP_COMP_FILE_SUFFIX L"~TMP~"
#define HEX_TO_ASCII(c) ((CHAR)((c) < 10 ? ((c) + '0') : ((c) + 'a' - 10)))
//
// static variables
//
COMPRESSION_SCHEME *HTTP_COMPRESSION::sm_pCompressionSchemes[MAX_SERVER_SCHEMES];
LIST_ENTRY HTTP_COMPRESSION::sm_CompressionThreadWorkQueue;
CRITICAL_SECTION HTTP_COMPRESSION::sm_CompressionThreadLock;
CRITICAL_SECTION HTTP_COMPRESSION::sm_CompressionDirectoryLock;
DWORD HTTP_COMPRESSION::sm_dwNumberOfSchemes = 0;
STRU *HTTP_COMPRESSION::sm_pstrCompressionDirectory = NULL;
STRA *HTTP_COMPRESSION::sm_pstrCacheControlHeader = NULL;
STRA *HTTP_COMPRESSION::sm_pstrExpiresHeader = NULL;
BOOL HTTP_COMPRESSION::sm_fDoStaticCompression = FALSE;
BOOL HTTP_COMPRESSION::sm_fDoDynamicCompression = FALSE;
BOOL HTTP_COMPRESSION::sm_fDoOnDemandCompression = TRUE;
BOOL HTTP_COMPRESSION::sm_fDoDiskSpaceLimiting = FALSE;
BOOL HTTP_COMPRESSION::sm_fNoCompressionForHttp10 = TRUE;
BOOL HTTP_COMPRESSION::sm_fNoCompressionForProxies = FALSE;
BOOL HTTP_COMPRESSION::sm_fNoCompressionForRange = TRUE;
BOOL HTTP_COMPRESSION::sm_fSendCacheHeaders = TRUE;
DWORD HTTP_COMPRESSION::sm_dwMaxDiskSpaceUsage = COMPRESSION_DEFAULT_DISK_SPACE_USAGE;
DWORD HTTP_COMPRESSION::sm_dwIoBufferSize = COMPRESSION_DEFAULT_BUFFER_SIZE;
DWORD HTTP_COMPRESSION::sm_dwCompressionBufferSize = COMPRESSION_DEFAULT_BUFFER_SIZE;
DWORD HTTP_COMPRESSION::sm_dwMaxQueueLength = COMPRESSION_DEFAULT_QUEUE_LENGTH;
DWORD HTTP_COMPRESSION::sm_dwFilesDeletedPerDiskFree = COMPRESSION_DEFAULT_FILES_DELETED_PER_DISK_FREE;
DWORD HTTP_COMPRESSION::sm_dwMinFileSizeForCompression = COMPRESSION_DEFAULT_FILE_SIZE_FOR_COMPRESSION;
PBYTE HTTP_COMPRESSION::sm_pIoBuffer = NULL;
PBYTE HTTP_COMPRESSION::sm_pCompressionBuffer = NULL;
DWORD HTTP_COMPRESSION::sm_dwCurrentDiskSpaceUsage = 0;
BOOL HTTP_COMPRESSION::sm_fCompressionVolumeIsFat = FALSE;
HANDLE HTTP_COMPRESSION::sm_hThreadEvent = NULL;
HANDLE HTTP_COMPRESSION::sm_hCompressionThreadHandle = NULL;
DWORD HTTP_COMPRESSION::sm_dwCurrentQueueLength = 0;
COMP_INIT_STATUS HTTP_COMPRESSION::sm_InitStatus = COMP_INIT_NONE;
BOOL HTTP_COMPRESSION::sm_fIsTerminating = FALSE;
ALLOC_CACHE_HANDLER *COMPRESSION_CONTEXT::allocHandler;
// static
HRESULT HTTP_COMPRESSION::Initialize()
/*++
Synopsis
Initialize function called during Server startup
Returns
HRESULT
--*/
{
HRESULT hr;
MB mb(g_pW3Server->QueryMDObject());
sm_InitStatus = COMP_INIT_NONE;
//
// Construct some static variables
//
sm_pstrCompressionDirectory = new STRU;
sm_pstrCacheControlHeader = new STRA;
sm_pstrExpiresHeader = new STRA;
if (sm_pstrCompressionDirectory == NULL ||
sm_pstrCacheControlHeader == NULL ||
sm_pstrExpiresHeader == NULL)
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto Finished;
}
if (FAILED(hr = sm_pstrCompressionDirectory->Copy(
L"%windir%\\IIS Temporary Compressed Files")) ||
FAILED(hr = sm_pstrCacheControlHeader->Copy("max-age=86400")) ||
FAILED(hr = sm_pstrExpiresHeader->Copy(
"Wed, 01 Jan 1997 12:00:00 GMT")))
{
goto Finished;
}
if (!mb.Open(HTTP_COMPRESSION_KEY))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
//
// Read the global configuration from the metabase
//
if (FAILED(hr = ReadMetadata(&mb)))
{
mb.Close();
goto Finished;
}
//
// Read in all the compression schemes and initialize them
//
if (FAILED(hr = InitializeCompressionSchemes(&mb)))
{
mb.Close();
goto Finished;
}
mb.Close();
sm_InitStatus = COMP_INIT_SCHEMES;
//
// Initialize other stuff
//
sm_pIoBuffer = new BYTE[sm_dwIoBufferSize];
sm_pCompressionBuffer = new BYTE[sm_dwCompressionBufferSize];
if (!sm_pIoBuffer || !sm_pCompressionBuffer)
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
goto Finished;
}
if ( !INITIALIZE_CRITICAL_SECTION(&sm_CompressionDirectoryLock) )
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
sm_InitStatus = COMP_INIT_DIRLOCK;
if (FAILED(hr = InitializeCompressionDirectory()))
{
goto Finished;
}
if (FAILED(hr = COMPRESSION_CONTEXT::Initialize()))
{
goto Finished;
}
sm_InitStatus = COMP_INIT_CONTEXT;
if (FAILED(hr = InitializeCompressionThread()))
{
goto Finished;
}
sm_InitStatus = COMP_INIT_DONE;
return S_OK;
Finished:
Terminate();
return hr;
}
DWORD WINAPI HTTP_COMPRESSION::CompressionThread(LPVOID)
/*++
Synopsis
Entry point for the thread which takes Compression work-items off the
queue and processes them
Arguments and Return Values are ignored
--*/
{
BOOL fTerminate = FALSE;
while (!fTerminate)
{
//
// Wait for some item to appear on the work queue
//
if (WaitForSingleObject(sm_hThreadEvent, INFINITE) == WAIT_FAILED)
{
DBG_ASSERT(FALSE);
}
EnterCriticalSection(&sm_CompressionThreadLock);
while (!IsListEmpty(&sm_CompressionThreadWorkQueue))
{
LIST_ENTRY *listEntry =
RemoveHeadList(&sm_CompressionThreadWorkQueue);
sm_dwCurrentQueueLength--;
LeaveCriticalSection(&sm_CompressionThreadLock);
COMPRESSION_WORK_ITEM *workItem =
CONTAINING_RECORD(listEntry,
COMPRESSION_WORK_ITEM,
ListEntry);
//
// Look at what the work item exactly is
//
if(workItem->WorkItemType == COMPRESSION_WORK_ITEM_TERMINATE)
{
fTerminate = TRUE;
}
else if (!fTerminate)
{
if (workItem->WorkItemType == COMPRESSION_WORK_ITEM_DELETE)
{
//
// special scheme to indicate that this item is for
// deletion, not compression
//
DeleteFile(workItem->strPhysicalPath.QueryStr());
}
else if (workItem->WorkItemType == COMPRESSION_WORK_ITEM_COMPRESS)
{
CompressFile(workItem->scheme,
workItem->pFileInfo);
}
else
{
DBG_ASSERT(FALSE);
}
}
delete workItem;
EnterCriticalSection(&sm_CompressionThreadLock);
}
LeaveCriticalSection(&sm_CompressionThreadLock);
}
//
// We are terminating, close the Event handle
//
CloseHandle(sm_hThreadEvent);
sm_hThreadEvent = NULL;
return 0;
}
// static
HRESULT HTTP_COMPRESSION::InitializeCompressionThread()
/*++
Initialize stuff related to the Compression Thread
--*/
{
InitializeListHead(&sm_CompressionThreadWorkQueue);
INITIALIZE_CRITICAL_SECTION(&sm_CompressionThreadLock);
sm_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (sm_hThreadEvent == NULL)
{
DeleteCriticalSection(&sm_CompressionThreadLock);
return HRESULT_FROM_WIN32(GetLastError());
}
DWORD threadId;
sm_hCompressionThreadHandle = CreateThread(NULL,
0, // Use the process default stack size
CompressionThread,
NULL, 0,
&threadId);
if (sm_hCompressionThreadHandle == NULL)
{
CloseHandle(sm_hThreadEvent);
sm_hThreadEvent = NULL;
DeleteCriticalSection(&sm_CompressionThreadLock);
return HRESULT_FROM_WIN32(GetLastError());
}
// CODEWORK: configurable?
SetThreadPriority(sm_hCompressionThreadHandle, THREAD_PRIORITY_LOWEST);
return S_OK;
}
// static
HRESULT HTTP_COMPRESSION::InitializeCompressionDirectory()
/*++
Setup stuff related to the compression directory
--*/
{
WIN32_FILE_ATTRIBUTE_DATA fileInformation;
STACK_STRU (strPath, 256);
HRESULT hr;
//
// Allow long paths to be specified
//
if (FAILED(hr = MakePathCanonicalizationProof(sm_pstrCompressionDirectory->QueryStr(),
&strPath)))
{
return hr;
}
//
// Find if the directory exists, if not create it
//
if (!GetFileAttributesEx(strPath.QueryStr(),
GetFileExInfoStandard,
&fileInformation))
{
if (!CreateDirectory(strPath.QueryStr(), NULL))
{
LPCWSTR apsz[1];
apsz[0] = sm_pstrCompressionDirectory->QueryStr();
g_pW3Server->LogEvent(W3_EVENT_COMPRESSION_DIRECTORY_INVALID,
1,
apsz,
GetLastError());
return HRESULT_FROM_WIN32(GetLastError());
}
}
else if (!(fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
}
if (sm_fDoDiskSpaceLimiting)
{
sm_dwCurrentDiskSpaceUsage = 0;
//
// Find usage in the directory
//
if (FAILED(hr = strPath.Append(L"\\", 1)) ||
FAILED(hr = strPath.Append(COMP_FILE_PREFIX)) ||
FAILED(hr = strPath.Append(L"*", 1)))
{
return hr;
}
WIN32_FIND_DATA win32FindData;
HANDLE hDirectory = FindFirstFile(strPath.QueryStr(),
&win32FindData);
if (hDirectory != INVALID_HANDLE_VALUE)
{
do
{
sm_dwCurrentDiskSpaceUsage += win32FindData.nFileSizeLow;
}
while (FindNextFile(hDirectory, &win32FindData));
FindClose(hDirectory);
}
}
//
// Find if the Volume is FAT or NTFS, check for file change differs
//
WCHAR volumeRoot[10];
volumeRoot[0] = sm_pstrCompressionDirectory->QueryStr()[0];
volumeRoot[1] = L':';
volumeRoot[2] = L'\\';
volumeRoot[3] = L'\0';
DWORD maximumComponentLength;
DWORD fileSystemFlags;
WCHAR fileSystemName[256];
if (!GetVolumeInformation(volumeRoot, NULL, 0, NULL,
&maximumComponentLength,
&fileSystemFlags,
fileSystemName,
sizeof(fileSystemName)/sizeof(WCHAR)) ||
!wcsncmp(L"FAT", fileSystemName, 3))
{
sm_fCompressionVolumeIsFat = TRUE;
}
else
{
sm_fCompressionVolumeIsFat = FALSE;
}
for (DWORD i=0; i<sm_dwNumberOfSchemes; i++)
{
STRU &filePrefix = sm_pCompressionSchemes[i]->m_strFilePrefix;
if (FAILED(hr = filePrefix.Copy(*sm_pstrCompressionDirectory)) ||
FAILED(hr = filePrefix.Append(L"\\", 1)) ||
FAILED(hr = filePrefix.Append(COMP_FILE_PREFIX)) ||
FAILED(hr = filePrefix.Append(
sm_pCompressionSchemes[i]->m_strCompressionSchemeName)) ||
FAILED(hr = filePrefix.Append(L"_", 1)))
{
return hr;
}
}
return S_OK;
}
// static
HRESULT HTTP_COMPRESSION::InitializeCompressionSchemes(MB *pmb)
/*++
Synopsis:
Read in all the compression schemes and initialize them
Arguments:
pmb: pointer to the Metabase object
Return Value
HRESULT
--*/
{
COMPRESSION_SCHEME *scheme;
BOOL fExistStaticScheme = FALSE;
BOOL fExistDynamicScheme = FALSE;
BOOL fExistOnDemandScheme = FALSE;
HRESULT hr;
//
// Enumerate all the scheme names under the main Compression key
//
WCHAR schemeName[METADATA_MAX_NAME_LEN + 1];
for (DWORD schemeIndex = 0;
pmb->EnumObjects(L"", schemeName, schemeIndex);
schemeIndex++)
{
if (_wcsicmp(schemeName, HTTP_COMPRESSION_PARAMETERS) == 0)
{
continue;
}
scheme = new COMPRESSION_SCHEME;
if (scheme == NULL)
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
if (FAILED(hr = scheme->Initialize(pmb, schemeName)))
{
DBGPRINTF((DBG_CONTEXT, "Error initializing scheme, error %x\n", hr));
delete scheme;
continue;
}
if (scheme->m_fDoStaticCompression)
{
fExistStaticScheme = TRUE;
}
if (scheme->m_fDoDynamicCompression)
{
fExistDynamicScheme = TRUE;
}
if (scheme->m_fDoOnDemandCompression)
{
fExistOnDemandScheme = TRUE;
}
sm_pCompressionSchemes[sm_dwNumberOfSchemes++] = scheme;
if (sm_dwNumberOfSchemes == MAX_SERVER_SCHEMES)
{
break;
}
}
//
// Sort the schemes by priority
//
for (DWORD i=0; i<sm_dwNumberOfSchemes; i++)
{
for (DWORD j=i+1; j<sm_dwNumberOfSchemes; j++)
{
if (sm_pCompressionSchemes[j]->m_dwPriority >
sm_pCompressionSchemes[i]->m_dwPriority)
{
scheme = sm_pCompressionSchemes[j];
sm_pCompressionSchemes[j] = sm_pCompressionSchemes[i];
sm_pCompressionSchemes[i] = scheme;
}
}
}
if (!fExistStaticScheme)
{
sm_fDoStaticCompression = FALSE;
}
if (!fExistDynamicScheme)
{
sm_fDoDynamicCompression = FALSE;
}
if (!fExistOnDemandScheme)
{
sm_fDoOnDemandCompression = FALSE;
}
return S_OK;
}
//static
HRESULT HTTP_COMPRESSION::ReadMetadata(MB *pmb)
/*++
Read all the global compression configuration
--*/
{
BUFFER TempBuff;
DWORD dwNumMDRecords;
DWORD dwDataSetNumber;
METADATA_GETALL_RECORD *pMDRecord;
DWORD i;
BOOL fExpandCompressionDirectory = TRUE;
HRESULT hr;
if (!pmb->GetAll(HTTP_COMPRESSION_PARAMETERS,
METADATA_INHERIT | METADATA_PARTIAL_PATH,
IIS_MD_UT_SERVER,
&TempBuff,
&dwNumMDRecords,
&dwDataSetNumber))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
pMDRecord = (METADATA_GETALL_RECORD *)TempBuff.QueryPtr();
for (i=0; i < dwNumMDRecords; i++, pMDRecord++)
{
PVOID pDataPointer = (PVOID) ((PCHAR)TempBuff.QueryPtr() +
pMDRecord->dwMDDataOffset);
DBG_ASSERT(pMDRecord->dwMDDataTag == 0);
switch (pMDRecord->dwMDIdentifier)
{
case MD_HC_COMPRESSION_DIRECTORY:
if (pMDRecord->dwMDDataType != STRING_METADATA &&
pMDRecord->dwMDDataType != EXPANDSZ_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
if (pMDRecord->dwMDDataType == STRING_METADATA)
{
fExpandCompressionDirectory = FALSE;
}
if (FAILED(hr = sm_pstrCompressionDirectory->Copy(
(LPWSTR)pDataPointer)))
{
goto ErrorExit;
}
break;
case MD_HC_CACHE_CONTROL_HEADER:
if (pMDRecord->dwMDDataType != STRING_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
if (FAILED(hr = sm_pstrCacheControlHeader->CopyWTruncate(
(LPWSTR)pDataPointer)))
{
goto ErrorExit;
}
break;
case MD_HC_EXPIRES_HEADER:
if (pMDRecord->dwMDDataType != STRING_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
if (FAILED(hr = sm_pstrExpiresHeader->CopyWTruncate(
(LPWSTR)pDataPointer)))
{
goto ErrorExit;
}
break;
case MD_HC_DO_DYNAMIC_COMPRESSION:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_fDoDynamicCompression = *((BOOL *)pDataPointer);
break;
case MD_HC_DO_STATIC_COMPRESSION:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_fDoStaticCompression = *((BOOL *)pDataPointer);
break;
case MD_HC_DO_ON_DEMAND_COMPRESSION:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_fDoOnDemandCompression = *((BOOL *)pDataPointer);
break;
case MD_HC_DO_DISK_SPACE_LIMITING:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_fDoDiskSpaceLimiting = *((BOOL *)pDataPointer);
break;
case MD_HC_NO_COMPRESSION_FOR_HTTP_10:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_fNoCompressionForHttp10 = *((BOOL *)pDataPointer);
break;
case MD_HC_NO_COMPRESSION_FOR_PROXIES:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_fNoCompressionForProxies = *((BOOL *)pDataPointer);
break;
case MD_HC_NO_COMPRESSION_FOR_RANGE:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_fNoCompressionForRange = *((BOOL *)pDataPointer);
break;
case MD_HC_SEND_CACHE_HEADERS:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_fSendCacheHeaders = *((BOOL *)pDataPointer);
break;
case MD_HC_MAX_DISK_SPACE_USAGE:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_dwMaxDiskSpaceUsage = *((DWORD *)pDataPointer);
break;
case MD_HC_IO_BUFFER_SIZE:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_dwIoBufferSize = *((DWORD *)pDataPointer);
sm_dwIoBufferSize = max(COMPRESSION_MIN_IO_BUFFER_SIZE,
sm_dwIoBufferSize);
sm_dwIoBufferSize = min(COMPRESSION_MAX_IO_BUFFER_SIZE,
sm_dwIoBufferSize);
break;
case MD_HC_COMPRESSION_BUFFER_SIZE:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_dwCompressionBufferSize = *((DWORD *)pDataPointer);
sm_dwCompressionBufferSize = max(COMPRESSION_MIN_COMP_BUFFER_SIZE,
sm_dwCompressionBufferSize);
sm_dwCompressionBufferSize = min(COMPRESSION_MAX_COMP_BUFFER_SIZE,
sm_dwCompressionBufferSize);
break;
case MD_HC_MAX_QUEUE_LENGTH:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_dwMaxQueueLength = *((DWORD *)pDataPointer);
sm_dwMaxQueueLength = min(COMPRESSION_MAX_QUEUE_LENGTH,
sm_dwMaxQueueLength);
break;
case MD_HC_FILES_DELETED_PER_DISK_FREE:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_dwFilesDeletedPerDiskFree = *((DWORD *)pDataPointer);
sm_dwFilesDeletedPerDiskFree =
max(COMPRESSION_MIN_FILES_DELETED_PER_DISK_FREE,
sm_dwFilesDeletedPerDiskFree);
sm_dwFilesDeletedPerDiskFree =
min(COMPRESSION_MAX_FILES_DELETED_PER_DISK_FREE,
sm_dwFilesDeletedPerDiskFree);
break;
case MD_HC_MIN_FILE_SIZE_FOR_COMP:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
sm_dwMinFileSizeForCompression = *((DWORD *)pDataPointer);
break;
default:
;
}
}
//
// The compression directory name contains an environment vairable,
// expand it
//
if (fExpandCompressionDirectory)
{
STACK_BUFFER (bufCompressionDir, 256);
DWORD cchRet = ExpandEnvironmentStrings(
sm_pstrCompressionDirectory->QueryStr(),
(LPWSTR)bufCompressionDir.QueryPtr(),
bufCompressionDir.QuerySize()/sizeof(WCHAR));
if (cchRet == 0)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
if (cchRet > bufCompressionDir.QuerySize()/sizeof(WCHAR))
{
if (!bufCompressionDir.Resize(cchRet * sizeof(WCHAR)))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
cchRet = ExpandEnvironmentStrings(
sm_pstrCompressionDirectory->QueryStr(),
(LPWSTR)bufCompressionDir.QueryPtr(),
bufCompressionDir.QuerySize()/sizeof(WCHAR));
if (cchRet == 0 ||
cchRet > bufCompressionDir.QuerySize()/sizeof(WCHAR))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
}
sm_pstrCompressionDirectory->Copy((LPWSTR)bufCompressionDir.QueryPtr());
}
return S_OK;
ErrorExit:
return hr;
}
HRESULT COMPRESSION_SCHEME::Initialize(
IN MB *pmb,
IN LPWSTR schemeName)
/*++
Initialize all the scheme specific data for the given scheme
--*/
{
BUFFER TempBuff;
DWORD dwNumMDRecords;
DWORD dwDataSetNumber;
HRESULT hr;
METADATA_GETALL_RECORD *pMDRecord;
DWORD i;
STACK_STRU (strCompressionDll, 256);
HMODULE compressionDll = NULL;
//
// Copy the scheme name
//
if (FAILED(hr = m_strCompressionSchemeName.Copy(schemeName)) ||
FAILED(hr = m_straCompressionSchemeName.CopyWTruncate(schemeName)))
{
return hr;
}
//
// First get all the metabase data
//
if (!pmb->GetAll(schemeName,
METADATA_INHERIT | METADATA_PARTIAL_PATH,
IIS_MD_UT_SERVER,
&TempBuff,
&dwNumMDRecords,
&dwDataSetNumber))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
pMDRecord = (METADATA_GETALL_RECORD *)TempBuff.QueryPtr();
for (i=0; i < dwNumMDRecords; i++, pMDRecord++)
{
PVOID pDataPointer = (PVOID) ((PCHAR)TempBuff.QueryPtr() +
pMDRecord->dwMDDataOffset);
DBG_ASSERT( pMDRecord->dwMDDataTag == 0);
switch (pMDRecord->dwMDIdentifier)
{
case MD_HC_COMPRESSION_DLL:
if (pMDRecord->dwMDDataType != STRING_METADATA &&
pMDRecord->dwMDDataType != EXPANDSZ_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
if (pMDRecord->dwMDDataType == EXPANDSZ_METADATA)
{
WCHAR CompressionDll[MAX_PATH + 1];
if (!ExpandEnvironmentStrings((LPWSTR)pDataPointer,
CompressionDll,
sizeof CompressionDll/sizeof WCHAR))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
hr = strCompressionDll.Copy(CompressionDll);
}
else
{
hr = strCompressionDll.Copy((LPWSTR)pDataPointer);
}
if (FAILED(hr))
{
goto ErrorExit;
}
break;
case MD_HC_DO_DYNAMIC_COMPRESSION:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
m_fDoDynamicCompression = *((BOOL *)pDataPointer);
break;
case MD_HC_DO_STATIC_COMPRESSION:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
m_fDoStaticCompression = *((BOOL *)pDataPointer);
break;
case MD_HC_DO_ON_DEMAND_COMPRESSION:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
m_fDoOnDemandCompression = *((BOOL *)pDataPointer);
break;
case MD_HC_FILE_EXTENSIONS:
if (pMDRecord->dwMDDataType != MULTISZ_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
{
MULTISZ mszTemp((LPWSTR)pDataPointer);
m_mszFileExtensions.Copy(mszTemp);
}
break;
case MD_HC_SCRIPT_FILE_EXTENSIONS:
if (pMDRecord->dwMDDataType != MULTISZ_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
{
MULTISZ mszTemp((LPWSTR)pDataPointer);
m_mszScriptFileExtensions.Copy(mszTemp);
}
break;
case MD_HC_PRIORITY:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
m_dwPriority = *((DWORD *)pDataPointer);
break;
case MD_HC_DYNAMIC_COMPRESSION_LEVEL:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
m_dwDynamicCompressionLevel = *((DWORD *)pDataPointer);
m_dwDynamicCompressionLevel =
min(COMPRESSION_MAX_COMPRESSION_LEVEL,
m_dwDynamicCompressionLevel);
break;
case MD_HC_ON_DEMAND_COMP_LEVEL:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
m_dwOnDemandCompressionLevel = *((DWORD *)pDataPointer);
m_dwOnDemandCompressionLevel =
min(COMPRESSION_MAX_COMPRESSION_LEVEL,
m_dwOnDemandCompressionLevel);
break;
case MD_HC_CREATE_FLAGS:
if (pMDRecord->dwMDDataType != DWORD_METADATA)
{
hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
goto ErrorExit;
}
m_dwCreateFlags = *((DWORD *)pDataPointer);
break;
default:
;
}
}
//
// Now, get the dll and the entry-points
//
compressionDll = LoadLibrary(strCompressionDll.QueryStr());
if (compressionDll == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto ErrorExit;
}
m_pfnInitCompression = (PFNCODEC_INIT_COMPRESSION)
GetProcAddress(compressionDll, "InitCompression");
m_pfnDeInitCompression = (PFNCODEC_DEINIT_COMPRESSION)
GetProcAddress(compressionDll, "DeInitCompression");
m_pfnCreateCompression = (PFNCODEC_CREATE_COMPRESSION)
GetProcAddress(compressionDll, "CreateCompression");
m_pfnCompress = (PFNCODEC_COMPRESS)
GetProcAddress(compressionDll, "Compress");
m_pfnDestroyCompression = (PFNCODEC_DESTROY_COMPRESSION)
GetProcAddress(compressionDll, "DestroyCompression");
m_pfnResetCompression = (PFNCODEC_RESET_COMPRESSION)
GetProcAddress(compressionDll, "ResetCompression");
if (!m_pfnInitCompression ||
!m_pfnDeInitCompression ||
!m_pfnCreateCompression ||
!m_pfnCompress ||
!m_pfnDestroyCompression ||
!m_pfnResetCompression)
{
hr = HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND);
goto ErrorExit;
}
//
// Call the initialize entry-point
//
if (FAILED(hr = m_pfnInitCompression()) ||
FAILED(hr = m_pfnCreateCompression(&m_pCompressionContext,
m_dwCreateFlags)))
{
goto ErrorExit;
}
m_hCompressionDll = compressionDll;
return S_OK;
ErrorExit:
if (compressionDll)
{
FreeLibrary(compressionDll);
}
return hr;
}
// static
BOOL HTTP_COMPRESSION::QueueWorkItem (
IN COMPRESSION_WORK_ITEM *WorkItem,
IN BOOL fOverrideMaxQueueLength,
IN BOOL fQueueAtHead)
/*++
Routine Description:
This is the routine that handles queuing work items to the compression
thread.
Arguments:
WorkRoutine - the routine that the compression thread should call
to do work. If NULL, then the call is an indication to the
compression thread that it should terminate.
Context - a context pointer which is passed to WorkRoutine.
WorkItem - if not NULL, this is a pointer to the work item to use
for this request. If NULL, then this routine will allocate a
work item to use. Note that by passing in a work item, the
caller agrees to give up control of the memory: we will free it
as necessary, either here or in the compression thread.
MustSucceed - if TRUE, then this request is not subject to the
limits on the number of work items that can be queued at any one
time.
QueueAtHead - if TRUE, then this work item is placed at the head
of the queue to be serviced immediately.
Return Value:
TRUE if the queuing succeeded.
--*/
{
DBG_ASSERT(WorkItem != NULL);
//
// Acquire the lock that protects the work queue list test to see
// how many items we have on the queue. If this is not a "must
// succeed" request and if we have reached the configured queue size
// limit, then fail this request. "Must succeed" requests are used
// for thread shutdown and other things which we really want to
// work.
//
EnterCriticalSection(&sm_CompressionThreadLock);
if (!fOverrideMaxQueueLength &&
(sm_dwCurrentQueueLength >= sm_dwMaxQueueLength))
{
LeaveCriticalSection(&sm_CompressionThreadLock);
return FALSE;
}
//
// All looks good, so increment the count of items on the queue and
// add this item to the queue.
//
sm_dwCurrentQueueLength++;
if (fQueueAtHead)
{
InsertHeadList(&sm_CompressionThreadWorkQueue, &WorkItem->ListEntry);
}
else
{
InsertTailList(&sm_CompressionThreadWorkQueue, &WorkItem->ListEntry);
}
LeaveCriticalSection(&sm_CompressionThreadLock);
//
// Signal the event that will cause the compression thread to wake
// up and process this work item.
//
SetEvent(sm_hThreadEvent);
return TRUE;
}
// static
VOID HTTP_COMPRESSION::Terminate()
/*++
Called on server shutdown
--*/
{
sm_fIsTerminating = TRUE;
switch (sm_InitStatus)
{
case COMP_INIT_DONE:
{
//
// Make the CompressionThread terminate by queueing a work item
// indicating that
//
COMPRESSION_WORK_ITEM *WorkItem = new COMPRESSION_WORK_ITEM;
if (WorkItem == NULL)
{
// if we can't even allocate this much memory, skip the rest of
// the termination and exit
return;
}
WorkItem->WorkItemType = COMPRESSION_WORK_ITEM_TERMINATE;
QueueWorkItem(WorkItem, TRUE, TRUE);
WaitForSingleObject(sm_hCompressionThreadHandle, INFINITE);
CloseHandle(sm_hCompressionThreadHandle);
sm_hCompressionThreadHandle = NULL;
DeleteCriticalSection(&sm_CompressionThreadLock);
}
case COMP_INIT_CONTEXT:
COMPRESSION_CONTEXT::Terminate();
case COMP_INIT_DIRLOCK:
DeleteCriticalSection(&sm_CompressionDirectoryLock);
case COMP_INIT_SCHEMES:
//
// For each compression scheme, unload the compression dll and free
// the space that holds info about the scheme
//
for (DWORD i=0; i<sm_dwNumberOfSchemes; i++)
{
delete sm_pCompressionSchemes[i];
}
case COMP_INIT_NONE:
//
// Free static objects
//
if (sm_pstrCompressionDirectory != NULL)
{
delete sm_pstrCompressionDirectory;
sm_pstrCompressionDirectory = NULL;
}
if (sm_pstrCacheControlHeader != NULL)
{
delete sm_pstrCacheControlHeader;
sm_pstrCacheControlHeader = NULL;
}
if (sm_pstrExpiresHeader != NULL)
{
delete sm_pstrExpiresHeader;
sm_pstrExpiresHeader = NULL;
}
if (sm_pIoBuffer != NULL)
{
delete sm_pIoBuffer;
sm_pIoBuffer = NULL;
}
if (sm_pCompressionBuffer != NULL)
{
delete sm_pCompressionBuffer;
sm_pCompressionBuffer = NULL;
}
}
}
// static
HRESULT HTTP_COMPRESSION::DoStaticFileCompression(
IN W3_CONTEXT *pW3Context,
IN OUT W3_FILE_INFO **ppOpenFile,
OUT BOOL *pfDoCache)
/*++
Synopsis:
Handle compression of static file request by either sending back the
compression version if present and applicable or queueing a work-item
to compress it for future requests. Called by
W3_STATIC_FILE_HANDLER::FileDoWork
Arguments:
pW3Context: The W3_CONTEXT for the request
ppOpenFile: On entry contains the cache entry to the physical path.
If a suitable file is found, on exit contains cach entry to the
compressed file
pfDoCache: On exit, indicates whether this file should be cached
in http.sys, basically we only cache compressible files if they
are compressed
Returns:
HRESULT
--*/
{
*pfDoCache = FALSE;
//
// If compression is not initialized, return
//
if (sm_InitStatus != COMP_INIT_DONE)
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If the client has not sent an Accept-Encoding header, or an empty
// Accept-Encoding header, return
//
W3_REQUEST *pRequest = pW3Context->QueryRequest();
LPCSTR pszAcceptEncoding = pRequest->GetHeader(HttpHeaderAcceptEncoding);
if (pszAcceptEncoding == NULL || *pszAcceptEncoding == '\0')
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If we are configured to not compress for 1.0, and version is not 1.1,
// return
//
if (sm_fNoCompressionForHttp10 &&
((pRequest->QueryVersion().MajorVersion == 0) ||
((pRequest->QueryVersion().MajorVersion == 1) &&
(pRequest->QueryVersion().MinorVersion == 0))))
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If we are configured to not compress for proxies and it is a proxy
// request, return
//
if (sm_fNoCompressionForProxies && pRequest->IsProxyRequest())
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If we are configured to not Compress for range requests and Range
// is present, return
// BUGBUG: Is the correct behavior to take range on the original request
// and compress those chunks or take range on the compressed file? We do
// the latter (same as IIS 5.0), figure out if that is correct.
//
if (sm_fNoCompressionForRange &&
pRequest->GetHeader(HttpHeaderRange))
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If file is too small, return
//
W3_FILE_INFO *pOrigFile = *ppOpenFile;
ULARGE_INTEGER originalFileSize;
pOrigFile->QuerySize(&originalFileSize);
if (originalFileSize.QuadPart < sm_dwMinFileSizeForCompression)
{
*pfDoCache = TRUE;
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If the file is encrypted, return
//
if ( pOrigFile->QueryAttributes() & FILE_ATTRIBUTE_ENCRYPTED )
{
*pfDoCache = TRUE;
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// Break the accept-encoding header into all the encoding accepted by
// the client sorting using the quality value
//
STACK_STRA( strAcceptEncoding, 512);
HRESULT hr;
if (FAILED(hr = strAcceptEncoding.Copy(pszAcceptEncoding)))
{
return hr;
}
STRU *pstrPhysical = pW3Context->QueryUrlContext()->QueryPhysicalPath();
LPWSTR pszExtension = wcsrchr(pstrPhysical->QueryStr(), L'.');
if (pszExtension != NULL)
{
pszExtension++;
}
DWORD dwClientCompressionCount;
DWORD matchingSchemes[MAX_SERVER_SCHEMES];
//
// Find out all schemes which will compress for this file
//
FindMatchingSchemes(strAcceptEncoding.QueryStr(),
pszExtension,
DO_STATIC_COMPRESSION,
matchingSchemes,
&dwClientCompressionCount);
if (dwClientCompressionCount == 0)
{
*pfDoCache = TRUE;
}
//
// Try to find a static scheme, which already has the file
// pre-compressed
//
COMPRESSION_SCHEME *firstOnDemandScheme = NULL;
STACK_STRU (strCompressedFileName, 256);
for (DWORD i=0; i<dwClientCompressionCount; i++)
{
COMPRESSION_SCHEME *scheme = sm_pCompressionSchemes[matchingSchemes[i]];
//
// Now, see if there exists a version of the requested file
// that has been compressed with that scheme. First, calculate
// the name the file would have. The compressed file will live
// in the special compression directory with a special converted
// name. The file name starts with the compression scheme used,
// then the fully qualified file name where slashes and colons
// have been converted to underscores. For example, the gzip
// version of "c:\inetpub\wwwroot\file.htm" would be
// "c:\compdir\$^_gzip^c^^inetpub^wwwroot^file.htm".
//
if (FAILED(hr = ConvertPhysicalPathToCompressedPath(
scheme,
pstrPhysical,
&strCompressedFileName)))
{
return hr;
}
W3_FILE_INFO *pCompFile;
if (CheckForExistenceOfCompressedFile(
pOrigFile,
&strCompressedFileName,
&pCompFile))
{
//
// Bingo--we have a compressed version of the file in a
// format that the client understands. Add the appropriate
// Content-Encoding header so that the client knows it is
// getting compressed data and change the server's mapping
// to the compressed version of the file.
//
W3_RESPONSE *pResponse = pW3Context->QueryResponse();
if (FAILED(hr = pResponse->SetHeaderByReference(
HttpHeaderContentEncoding,
scheme->m_straCompressionSchemeName.QueryStr(),
(USHORT)scheme->m_straCompressionSchemeName.QueryCCH())))
{
pCompFile->DereferenceCacheEntry();
return hr;
}
if (sm_fSendCacheHeaders)
{
if (FAILED(hr = pResponse->SetHeaderByReference(
HttpHeaderExpires,
sm_pstrExpiresHeader->QueryStr(),
(USHORT)sm_pstrExpiresHeader->QueryCCH())) ||
FAILED(hr = pResponse->SetHeader(
HttpHeaderCacheControl,
sm_pstrCacheControlHeader->QueryStr(),
(USHORT)sm_pstrCacheControlHeader->QueryCCH(),
TRUE)))
{
pCompFile->DereferenceCacheEntry();
return hr;
}
}
if (FAILED(hr = pResponse->SetHeaderByReference(
HttpHeaderVary,
"Accept-Encoding", 15)))
{
pCompFile->DereferenceCacheEntry();
return hr;
}
*pfDoCache = TRUE;
pW3Context->SetDoneWithCompression();
//
// Change the cache entry to point to the new file and close
// the original file
//
*ppOpenFile = pCompFile;
pOrigFile->DereferenceCacheEntry();
return S_OK;
}
//
// We found a scheme, but we don't have a matching file for it.
// Remember whether this was the first matching scheme that
// supports on-demand compression. In the event that we do not
// find any acceptable files, we'll attempt to do an on-demand
// compression for this file so that future requests get a
// compressed version.
//
if (firstOnDemandScheme == NULL &&
scheme->m_fDoOnDemandCompression)
{
firstOnDemandScheme = scheme;
}
//
// Loop to see if there is another scheme that is supported
// by both client and server.
//
}
if (sm_fDoOnDemandCompression && firstOnDemandScheme != NULL)
{
//
// if we are here means scheme was found but no compressed
// file matching any scheme. So schedule file to compress
//
QueueCompressFile(firstOnDemandScheme,
pOrigFile);
}
//
// No static compression for this request, will try dynamic compression
// if so configured while sending response
//
return S_OK;
}
// static
VOID HTTP_COMPRESSION::FindMatchingSchemes(
IN CHAR * pszAcceptEncoding,
IN LPWSTR pszExtension,
IN COMPRESSION_TO_PERFORM performCompr,
OUT DWORD matchingSchemes[],
OUT DWORD *pdwClientCompressionCount)
{
struct
{
LPSTR schemeName;
float quality;
} parsedAcceptEncoding[MAX_SERVER_SCHEMES];
DWORD NumberOfParsedSchemes = 0;
//
// First parse the Accept-Encoding header
//
BOOL fAddStar = FALSE;
while (*pszAcceptEncoding != '\0')
{
LPSTR schemeEnd = NULL;
BOOL fStar = FALSE;
while (*pszAcceptEncoding == ' ')
{
pszAcceptEncoding++;
}
if (isalnum(*pszAcceptEncoding))
{
parsedAcceptEncoding[NumberOfParsedSchemes].schemeName =
pszAcceptEncoding;
parsedAcceptEncoding[NumberOfParsedSchemes].quality = 1;
while (isalnum(*pszAcceptEncoding))
{
pszAcceptEncoding++;
}
// Mark the end of the scheme name
schemeEnd = pszAcceptEncoding;
}
else if (*pszAcceptEncoding == '*')
{
fStar = TRUE;
parsedAcceptEncoding[NumberOfParsedSchemes].quality = 1;
pszAcceptEncoding++;
}
else
{
// incorrect syntax
break;
}
while (*pszAcceptEncoding == ' ')
{
pszAcceptEncoding++;
}
if (*pszAcceptEncoding == ';')
{
// quality specifier: looks like q=0.7
pszAcceptEncoding++;
while (*pszAcceptEncoding == ' ')
{
pszAcceptEncoding++;
}
if (*pszAcceptEncoding == 'q')
{
pszAcceptEncoding++;
}
else
{
break;
}
while (*pszAcceptEncoding == ' ')
{
pszAcceptEncoding++;
}
if (*pszAcceptEncoding == '=')
{
pszAcceptEncoding++;
}
else
{
break;
}
while (*pszAcceptEncoding == ' ')
{
pszAcceptEncoding++;
}
parsedAcceptEncoding[NumberOfParsedSchemes].quality =
atof(pszAcceptEncoding);
while (*pszAcceptEncoding && *pszAcceptEncoding != ',')
{
pszAcceptEncoding++;
}
}
if (*pszAcceptEncoding == ',')
{
pszAcceptEncoding++;
}
if (fStar)
{
//
// A star with non-zero quality means that all schemes are
// acceptable except those explicitly unacceptable
//
if (parsedAcceptEncoding[NumberOfParsedSchemes].quality != 0)
{
fAddStar = TRUE;
}
}
else
{
*schemeEnd = '\0';
NumberOfParsedSchemes++;
if (NumberOfParsedSchemes >= MAX_SERVER_SCHEMES)
{
break;
}
}
}
//
// Now sort by quality
//
LPSTR tempName;
float tempQuality;
for (DWORD i=0; i<NumberOfParsedSchemes; i++)
{
for (DWORD j=i+1; j<NumberOfParsedSchemes; j++)
{
if (parsedAcceptEncoding[j].quality >
parsedAcceptEncoding[i].quality)
{
tempName = parsedAcceptEncoding[i].schemeName;
parsedAcceptEncoding[i].schemeName = parsedAcceptEncoding[j].schemeName;
parsedAcceptEncoding[j].schemeName = tempName;
tempQuality = parsedAcceptEncoding[i].quality;
parsedAcceptEncoding[i].quality = parsedAcceptEncoding[j].quality;
parsedAcceptEncoding[j].quality = tempQuality;
}
}
}
//
// Now convert the names to indexes into actual schemes
//
BOOL fAddedScheme[MAX_SERVER_SCHEMES];
for (i=0; i<sm_dwNumberOfSchemes; i++)
{
fAddedScheme[i] = FALSE;
}
DWORD dwNumberOfActualSchemes = 0;
for (i=0; i<NumberOfParsedSchemes; i++)
{
//
// Find this scheme
//
for (DWORD j=0; j<sm_dwNumberOfSchemes; j++)
{
if (!fAddedScheme[j] &&
!_stricmp(parsedAcceptEncoding[i].schemeName,
sm_pCompressionSchemes[j]->
m_straCompressionSchemeName.QueryStr()))
{
// found a match
fAddedScheme[j] = TRUE;
if (parsedAcceptEncoding[i].quality == 0)
{
break;
}
//
// Check if the given scheme does the required kind of
// compression. Also, check that either there is no list
// of restricted extensions or that the given file extension
// matches one in the list
//
if (performCompr == DO_STATIC_COMPRESSION)
{
if (!sm_pCompressionSchemes[j]->m_fDoStaticCompression ||
(sm_pCompressionSchemes[j]->m_mszFileExtensions.QueryStringCount() &&
(!pszExtension ||
!sm_pCompressionSchemes[j]->m_mszFileExtensions.FindStringNoCase(pszExtension))))
{
break;
}
}
else
{
if (!sm_pCompressionSchemes[j]->m_fDoDynamicCompression ||
(sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.QueryStringCount() &&
(!pszExtension ||
!sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.FindStringNoCase(pszExtension))))
{
break;
}
}
matchingSchemes[dwNumberOfActualSchemes++] = j;
break;
}
}
}
//
// If * was specified, add all unadded applicable schemes
//
if (fAddStar)
{
for (DWORD j=0; j<sm_dwNumberOfSchemes; j++)
{
if (!fAddedScheme[j])
{
fAddedScheme[j] = TRUE;
//
// Check if the given scheme does the required kind of
// compression. Also, check that either there is no list
// of restricted extensions or that the given file extension
// matches one in the list
//
if (performCompr == DO_STATIC_COMPRESSION)
{
if (!sm_pCompressionSchemes[j]->m_fDoStaticCompression ||
(sm_pCompressionSchemes[j]->m_mszFileExtensions.QueryStringCount() &&
(!pszExtension ||
!sm_pCompressionSchemes[j]->m_mszFileExtensions.FindStringNoCase(pszExtension))))
{
continue;
}
}
else
{
if (!sm_pCompressionSchemes[j]->m_fDoDynamicCompression ||
(sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.QueryStringCount() &&
(!pszExtension ||
!sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.FindStringNoCase(pszExtension))))
{
continue;
}
}
matchingSchemes[dwNumberOfActualSchemes++] = j;
}
}
}
*pdwClientCompressionCount = dwNumberOfActualSchemes;
}
// static
HRESULT HTTP_COMPRESSION::ConvertPhysicalPathToCompressedPath(
IN COMPRESSION_SCHEME *scheme,
IN STRU *pstrPhysicalPath,
OUT STRU *pstrCompressedPath)
/*++
Routine Description:
Builds a string that has the directory for the specified compression
scheme, followed by the file name with all slashes and colons
converted to underscores. This allows for a flat directory which
contains all the compressed files.
Arguments:
Scheme - the compression scheme to use.
pstrPhysicalPath - the physical file name that we want to convert.
pstrCompressedPath - the resultant string.
Return Value:
HRESULT
--*/
{
HRESULT hr;
EnterCriticalSection(&sm_CompressionDirectoryLock);
hr = pstrCompressedPath->Copy(scheme->m_strFilePrefix);
LeaveCriticalSection(&sm_CompressionDirectoryLock);
if (FAILED(hr))
{
return hr;
}
//
// Copy over the actual file name, converting slashes and colons
// to underscores.
//
DWORD cchPathLength = pstrCompressedPath->QueryCCH();
if (FAILED(hr = pstrCompressedPath->Append(*pstrPhysicalPath)))
{
return hr;
}
for (LPWSTR s = pstrCompressedPath->QueryStr() + cchPathLength;
*s != L'\0';
s++)
{
if (*s == L'\\' || *s == L':')
{
*s = L'^';
}
}
return S_OK;
}
BOOL HTTP_COMPRESSION::CheckForExistenceOfCompressedFile(
IN W3_FILE_INFO *pOrigFile,
IN STRU *pstrFileName,
OUT W3_FILE_INFO **ppCompFile,
IN BOOL fDeleteAllowed)
{
HRESULT hr;
CACHE_USER fileUser;
DBG_ASSERT( g_pW3Server->QueryFileCache() != NULL );
hr = g_pW3Server->QueryFileCache()->GetFileInfo(
*pstrFileName,
NULL,
&fileUser,
TRUE,
ppCompFile );
if (FAILED(hr))
{
return FALSE;
}
//
// So far so good. Determine whether the compressed version
// of the file is out of date. If the compressed file is
// out of date, delete it and remember that we did not get a
// good match. Note that there's really nothing we can do
// if the delete fails, so ignore any failures from it.
//
// The last write times must differ by exactly two seconds
// to constitute a match. The two-second difference results
// from the fact that we set the time on the compressed file
// to be two seconds behind the uncompressed version in
// order to ensure unique ETag: header values.
//
W3_FILE_INFO *pCompFile = *ppCompFile;
ULARGE_INTEGER compressedFileSize;
FILETIME compressedFileTime;
LARGE_INTEGER *pli = (LARGE_INTEGER *)&compressedFileTime;
FILETIME originalFileTime;
BOOL success = FALSE;
pCompFile->QuerySize(&compressedFileSize);
pCompFile->QueryLastWriteTime(&compressedFileTime);
pOrigFile->QueryLastWriteTime(&originalFileTime);
pli->QuadPart += 2*10*1000*1000;
LONG timeResult = CompareFileTime(&compressedFileTime, &originalFileTime);
if ( timeResult != 0 )
{
//
// That check failed. If the compression directory is
// on a FAT volume, then see if they are within two
// seconds of one another. If they are, then consider
// things valid. We have to do this because FAT file
// times get truncated in weird ways sometimes: despite
// the fact that we request setting the file time
// different by an exact amount, it gets rounded
// sometimes.
//
if (sm_fCompressionVolumeIsFat)
{
pli->QuadPart -= 2*10*1000*1000 + 1;
timeResult += CompareFileTime(&compressedFileTime, &originalFileTime);
}
}
if (timeResult == 0)
{
success = TRUE;
}
//
// The original file has changed since the compression, close this cache
// entry
//
if (!success)
{
pCompFile->DereferenceCacheEntry();
*ppCompFile = NULL;
}
//
// If the compressed file exists but is stale, queue for deletion
//
if (!success && fDeleteAllowed)
{
// don't delete if call came from compression thread because then
// delete request will be in a queue after compression request
// and will delete a file which was just moment ago compressed
COMPRESSION_WORK_ITEM *WorkItem = new COMPRESSION_WORK_ITEM;
if (WorkItem == NULL)
{
return FALSE;
}
WorkItem->WorkItemType = COMPRESSION_WORK_ITEM_DELETE;
if (FAILED(WorkItem->strPhysicalPath.Copy(*pstrFileName)))
{
delete WorkItem;
return FALSE;
}
if (!QueueWorkItem(WorkItem,
FALSE,
FALSE))
{
delete WorkItem;
return FALSE;
}
//
// If we are configured to limit the amount of disk
// space we use for compressed files, then, in a
// thread-safe manner, update the tally of disk
// space used by compression.
//
if (sm_fDoDiskSpaceLimiting)
{
InterlockedExchangeAdd((PLONG)&sm_dwCurrentDiskSpaceUsage,
-1 * compressedFileSize.LowPart);
}
}
return success;
}
BOOL HTTP_COMPRESSION::QueueCompressFile(
IN COMPRESSION_SCHEME *scheme,
IN W3_FILE_INFO *pFileInfo)
/*++
Routine Description:
Queues a compress file request to the compression thread.
Arguments:
Scheme - a pointer to the compression scheme to use in compressing
the file.
pszPhysicalPath - the current physical path to the file.
Return Value:
TRUE if the queuing succeeded.
--*/
{
COMPRESSION_WORK_ITEM *WorkItem = new COMPRESSION_WORK_ITEM;
if ( WorkItem == NULL )
{
return FALSE;
}
//
// Initialize this structure with the necessary information.
//
WorkItem->WorkItemType = COMPRESSION_WORK_ITEM_COMPRESS;
WorkItem->scheme = scheme;
pFileInfo->ReferenceCacheEntry();
WorkItem->pFileInfo = pFileInfo;
//
// Queue a work item and we're done.
//
if (!QueueWorkItem(WorkItem,
FALSE,
FALSE))
{
delete WorkItem;
return FALSE;
}
return TRUE;
}
VOID HTTP_COMPRESSION::CompressFile(IN COMPRESSION_SCHEME *scheme,
IN W3_FILE_INFO *pofiOriginalFile)
/*++
Routine Description:
This routine does the real work of compressing a static file and
storing it to the compression directory with a unique name.
Arguments:
Context - a pointer to context information for the request,
including the compression scheme to use for compression and the
physical path to the file that we need to compress.
Return Value:
None. If the compression fails, we just press on and attempt to
compress the file the next time it is requested.
--*/
{
HANDLE hOriginalFile = NULL;
HANDLE hCompressedFile = NULL;
STACK_STRU ( strPhysicalPath, 256);
STACK_STRU ( compressedFileName, 256);
STACK_STRU ( realCompressedFileName, 256);
STACK_STRU ( compressedFileNameCanonical, 256);
STACK_STRU ( realCompressedFileNameCanonical, 256);
BOOL success = FALSE;
DWORD cbIo = 0;
DWORD totalBytesWritten = 0;
BOOL usedScheme = FALSE;
LARGE_INTEGER *pli = NULL;
W3_FILE_INFO *pofiCompressedFile = NULL;
FILETIME originalFileTime;
OVERLAPPED ovlForRead;
DWORD readStatus;
ULARGE_INTEGER readOffset = {0};
WCHAR pszPid[16];
DWORD dwPid;
BYTE *pCachedFileBuffer;
BOOL fHaveCachedFileBuffer = FALSE;
DWORD dwOrigFileSize;
ULARGE_INTEGER liOrigFileSize;
//
// Determine the name of the file to which we will write compression
// file data. Note that we use a bogus file name initially: this
// allows us to rename it later and ensure an atomic update to the
// file system, thereby preventing other threads from returning the
// compressed file when it has only been partially written.
//
// If the caller specified a specific output file name, then use that
// instead of the calculated name.
//
if (FAILED(strPhysicalPath.Copy(pofiOriginalFile->QueryPhysicalPath())) ||
FAILED(ConvertPhysicalPathToCompressedPath(
scheme,
&strPhysicalPath,
&realCompressedFileName)))
{
goto exit;
}
dwPid = GetCurrentProcessId();
_itow(dwPid, pszPid, 10);
if (FAILED(compressedFileName.Copy(realCompressedFileName)) ||
FAILED(compressedFileName.Append(pszPid)) ||
FAILED(compressedFileName.Append(TEMP_COMP_FILE_SUFFIX)) ||
FAILED(MakePathCanonicalizationProof(realCompressedFileName.QueryStr(),
&realCompressedFileNameCanonical)) ||
FAILED(MakePathCanonicalizationProof(compressedFileName.QueryStr(),
&compressedFileNameCanonical)))
{
goto exit;
}
success = CheckForExistenceOfCompressedFile(pofiOriginalFile,
&realCompressedFileName,
&pofiCompressedFile,
FALSE);
if (!success)
{
pofiOriginalFile->QueryLastWriteTime(&originalFileTime);
pCachedFileBuffer = pofiOriginalFile->QueryFileBuffer();
pofiOriginalFile->QuerySize(&liOrigFileSize);
dwOrigFileSize = liOrigFileSize.LowPart;
//
// Do the actual file open. We open the file for exclusive access,
// and we assume that the file will not already exist.
//
hCompressedFile = CreateFile(
compressedFileNameCanonical.QueryStr(),
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (hCompressedFile == INVALID_HANDLE_VALUE)
{
goto exit;
}
//
// Loop through the file data, reading it, then compressing it, then
// writing out the compressed data.
//
if (pCachedFileBuffer)
{
fHaveCachedFileBuffer = TRUE;
}
else
{
hOriginalFile = pofiOriginalFile->QueryFileHandle();
if ( hOriginalFile == INVALID_HANDLE_VALUE )
{
hOriginalFile = NULL;
goto exit;
}
ovlForRead.Offset = 0;
ovlForRead.OffsetHigh = 0;
ovlForRead.hEvent = NULL;
readOffset.QuadPart = 0;
}
while (dwOrigFileSize > 0)
{
if (!fHaveCachedFileBuffer)
{
success = ReadFile(hOriginalFile, sm_pIoBuffer,
sm_dwIoBufferSize, &cbIo, &ovlForRead);
if (!success)
{
switch (readStatus = GetLastError())
{
case ERROR_HANDLE_EOF:
cbIo = 0;
success = TRUE;
break;
case ERROR_IO_PENDING:
success = GetOverlappedResult(hOriginalFile, &ovlForRead, &cbIo, TRUE);
if (!success)
{
switch (readStatus = GetLastError())
{
case ERROR_HANDLE_EOF:
cbIo = 0;
success = TRUE;
break;
default:
break;
}
}
break;
default:
break;
}
}
if (!success)
{
goto exit;
}
if (cbIo)
{
readOffset.QuadPart += cbIo;
ovlForRead.Offset = readOffset.LowPart;
ovlForRead.OffsetHigh = readOffset.HighPart;
}
else
{
//
// ReadFile returns zero bytes read at the end of the
// file. If we hit that, then break out of this loop.
//
break;
}
}
//
// Remember that we used this compression scheme and that we
// will need to reset it on exit.
//
usedScheme = TRUE;
//
// Write the compressed data to the output file.
//
success = CompressAndWriteData(
scheme,
fHaveCachedFileBuffer ? pCachedFileBuffer : sm_pIoBuffer,
fHaveCachedFileBuffer ? dwOrigFileSize : cbIo,
&totalBytesWritten,
hCompressedFile
);
if (!success)
{
goto exit;
}
if (fHaveCachedFileBuffer)
{
break;
}
} // end while(dwOrigFileSize > 0)
//
// Tell the compression DLL that we're done with this file. It may
// return a last little bit of data for us to write to the the file.
// This is because most compression schemes store an end-of-file
// code in the compressed data stream. Using "0" as the number of
// bytes to compress handles this case.
//
success = CompressAndWriteData(
scheme,
NULL,
0,
&totalBytesWritten,
hCompressedFile
);
if (!success)
{
goto exit;
}
//
// Set the compressed file's creation time to be identical to the
// original file. This allows a more granular test for things being
// out of date. If we just did a greater-than-or-equal time
// comparison, then copied or renamed files might not get registered
// as changed.
//
//
// Subtract two seconds from the file time to get the file time that
// we actually want to put on the file. We do this to make sure
// that the server will send a different Etag: header for the
// compressed file than for the uncompressed file, and the server
// uses the file time to calculate the Etag: it uses.
//
// We set it in the past so that if the original file changes, it
// should never happen to get the same value as the compressed file.
// We pick two seconds instead of one second because the FAT file
// system stores file times at a granularity of two seconds.
//
pli = (PLARGE_INTEGER)(&originalFileTime);
pli->QuadPart -= 2*10*1000*1000;
success = SetFileTime(
hCompressedFile,
NULL,
NULL,
&originalFileTime
);
if (!success)
{
goto exit;
}
CloseHandle(hCompressedFile);
hCompressedFile = NULL;
//
// Almost done now. Just rename the file to the proper name.
//
success = MoveFileEx(
compressedFileNameCanonical.QueryStr(),
realCompressedFileNameCanonical.QueryStr(),
MOVEFILE_REPLACE_EXISTING);
if (!success)
{
goto exit;
}
//
// If we are configured to limit the amount of disk space we use for
// compressed files, then update the tally of disk space used by
// compression. If the value is too high, then free up some space.
//
// Use InterlockedExchangeAdd to update this value because other
// threads may be deleting files from the compression directory
// because they have gone out of date.
//
if (sm_fDoDiskSpaceLimiting)
{
InterlockedExchangeAdd((PLONG)&sm_dwCurrentDiskSpaceUsage,
totalBytesWritten);
if (sm_dwCurrentDiskSpaceUsage > sm_dwMaxDiskSpaceUsage)
{
FreeDiskSpace();
}
}
}
//
// Free the context structure and return.
//
exit:
if (pofiCompressedFile != NULL)
{
pofiCompressedFile->DereferenceCacheEntry();
pofiCompressedFile = NULL;
}
//
// Reset the compression context for reuse the next time through.
// This is more optimal than recreating the compression context for
// every file--it avoids allocations, etc.
//
if (usedScheme)
{
scheme->m_pfnResetCompression(scheme->m_pCompressionContext);
}
if (hCompressedFile != NULL)
{
CloseHandle(hCompressedFile);
}
if (!success)
{
DeleteFile(compressedFileName.QueryStr());
}
return;
}
VOID HTTP_COMPRESSION::FreeDiskSpace()
/*++
Routine Description:
If disk space limiting is in effect, this routine frees up the
oldest compressed files to make room for new files.
Arguments:
None.
Return Value:
None. This routine makes a best-effort attempt to free space, but
if it doesn't work, oh well.
--*/
{
WIN32_FIND_DATA **filesToDelete;
WIN32_FIND_DATA *currentFindData;
WIN32_FIND_DATA *findDataHolder;
DWORD i;
HANDLE hDirectory = INVALID_HANDLE_VALUE;
STACK_STRU (strFile, MAX_PATH);
//
// Allocate space to hold the array of files to delete and the
// WIN32_FIND_DATA structures that we will need. We will find the
// least-recently-used files in the compression directory to delete.
// The reason we delete multpiple files is to reduce the number of
// times that we have to go through the process of freeing up disk
// space, since this is a fairly expensive operation.
//
filesToDelete = (WIN32_FIND_DATA **)LocalAlloc(
LMEM_FIXED,
sizeof(filesToDelete)*sm_dwFilesDeletedPerDiskFree +
sizeof(WIN32_FIND_DATA)*(sm_dwFilesDeletedPerDiskFree + 1));
if (filesToDelete == NULL)
{
return;
}
//
// Parcel out the allocation to the various uses. The initial
// currentFindData will follow the array, and then the
// WIN32_FIND_DATA structures that start off in the sorted array.
// Initialize the last access times of the entries in the array to
// 0xFFFFFFFF so that they are considered recently used and quickly
// get tossed from the array with real files.
//
currentFindData = (PWIN32_FIND_DATA)(filesToDelete + sm_dwFilesDeletedPerDiskFree);
for (i = 0; i < sm_dwFilesDeletedPerDiskFree; i++)
{
filesToDelete[i] = currentFindData + 1 + i;
filesToDelete[i]->ftLastAccessTime.dwLowDateTime = 0xFFFFFFFF;
filesToDelete[i]->ftLastAccessTime.dwHighDateTime = 0x7FFFFFFF;
}
//
// Start enumerating the files in the compression directory. Do
// this while holding the lock that protects the
// CompressionDirectoryWildcard variable, since it is possible for
// that string pointer to get freed if there is a metabase
// configuration change. Note that holding the critical section for
// a long time is not a perf issue because, in general, only this
// thread ever acquires this lock, except for the rare configuration
// change.
//
EnterCriticalSection(&sm_CompressionDirectoryLock);
STACK_STRU (CompDirWildcard, 512);
//
// Allow long paths to be specified
//
if (FAILED(MakePathCanonicalizationProof(sm_pstrCompressionDirectory->QueryStr(),
&CompDirWildcard)) ||
FAILED(CompDirWildcard.Append(L"\\", 1)) ||
FAILED(CompDirWildcard.Append(COMP_FILE_PREFIX)) ||
FAILED(CompDirWildcard.Append(L"*", 1)))
{
LeaveCriticalSection(&sm_CompressionDirectoryLock);
goto exit;
}
hDirectory = FindFirstFile(CompDirWildcard.QueryStr(), currentFindData);
LeaveCriticalSection(&sm_CompressionDirectoryLock);
if (hDirectory == INVALID_HANDLE_VALUE)
{
goto exit;
}
for (;;)
{
//
// Ignore this entry if it is a directory.
//
if (!(currentFindData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
//
// Walk down the sorted array of files, comparing the time
// of this file against the times of the files currently in
// the array. We need to find whether this file belongs in
// the array at all, and, if so, where in the array it
// belongs.
//
for (i = 0;
i < sm_dwFilesDeletedPerDiskFree &&
CompareFileTime(&currentFindData->ftLastAccessTime,
&filesToDelete[i]->ftLastAccessTime) > 0;
i++)
{}
//
// If this file needs to get inserted in the array, put it
// in and move the other entries forward.
//
while (i < sm_dwFilesDeletedPerDiskFree)
{
findDataHolder = currentFindData;
currentFindData = filesToDelete[i];
filesToDelete[i] = findDataHolder;
i++;
}
}
//
// Get the next file in the directory.
//
if (!FindNextFile(hDirectory, currentFindData))
{
break;
}
}
//
// Now walk through the array of files to delete and get rid of
// them.
//
for (i = 0; i < sm_dwFilesDeletedPerDiskFree; i++)
{
if (filesToDelete[i]->ftLastAccessTime.dwHighDateTime != 0x7FFFFFFF)
{
//
// Allow long paths to be specified
//
if (FAILED(MakePathCanonicalizationProof(sm_pstrCompressionDirectory->QueryStr(),
&strFile)) ||
FAILED(strFile.Append(L"\\", 1)) ||
FAILED(strFile.Append(filesToDelete[i]->cFileName)))
{
goto exit;
}
if (DeleteFile(strFile.QueryStr()))
{
InterlockedExchangeAdd((LPLONG)&sm_dwCurrentDiskSpaceUsage,
-(LONG)filesToDelete[i]->nFileSizeLow);
}
}
}
exit:
if (filesToDelete)
{
LocalFree(filesToDelete);
}
if (hDirectory != INVALID_HANDLE_VALUE)
{
FindClose(hDirectory);
}
return;
}
BOOL HTTP_COMPRESSION::CompressAndWriteData(
COMPRESSION_SCHEME *scheme,
PBYTE InputBuffer,
DWORD BytesToCompress,
PDWORD BytesWritten,
HANDLE hCompressedFile)
/*++
Routine Description:
Takes uncompressed data, compresses it with the specified compression
scheme, and writes the result to the specified file.
Arguments:
Scheme - the compression scheme to use.
InputBuffer - the data we need to compress.
BytesToCompress - the size of the input buffer, or 0 if we should
flush the compression buffers to the file at the end of the
input file. Note that this routine DOES NOT handle compressing
a zero-byte file; we assume that the input file has some data.
BytesWritten - the number of bytes written to the output file.
hCompressedFile - a handle to the file to which we should write the
compressed results.
Return Value:
None. This routine makes a best-effort attempt to free space, but
if it doesn't work, oh well.
--*/
{
DWORD inputBytesUsed;
DWORD bytesCompressed;
HRESULT hResult;
BOOL keepGoing;
DWORD cbIo;
if (sm_fIsTerminating)
{
return FALSE;
}
//
// Perform compression on the actual file data. Note that it is
// possible that the compressed data is actually larger than the
// input data, so we might need to call the compression routine
// multiple times.
//
do
{
hResult = scheme->m_pfnCompress(
scheme->m_pCompressionContext,
InputBuffer,
BytesToCompress,
sm_pCompressionBuffer,
sm_dwCompressionBufferSize,
(PLONG)&inputBytesUsed,
(PLONG)&bytesCompressed,
scheme->m_dwOnDemandCompressionLevel);
if (FAILED(hResult))
{
return FALSE;
}
if (hResult == S_OK)
{
keepGoing = TRUE;
}
else
{
keepGoing = FALSE;
}
//
// If the compressor gave us any data, then write the result to
// disk. Some compression schemes buffer up data in order to
// perform better compression, so not every compression call
// will result in output data.
//
if (bytesCompressed > 0)
{
if (!WriteFile(hCompressedFile,
sm_pCompressionBuffer,
bytesCompressed,
&cbIo,
NULL))
{
return FALSE;
}
*BytesWritten += cbIo;
}
//
// Update the number of input bytes that we have compressed
// so far, and adjust the input buffer pointer accordingly.
//
BytesToCompress -= inputBytesUsed;
InputBuffer += inputBytesUsed;
}
while ( ((BytesToCompress > 0) || ((BytesToCompress + inputBytesUsed) == 0)) && keepGoing );
return TRUE;
}
BOOL DoesCacheControlNeedMaxAge(IN LPCSTR CacheControlHeaderValue)
/*++
Routine Description:
This function determines whether the Cache-Control header on a
compressed response needs to have the max-age directive added.
If there is already a max-age, or if there is a no-cache directive,
then we should not add max-age.
Arguments:
CacheControlHeaderValue - the value of the cache control header to
scan. The string should be zero-terminated.
Return Value:
TRUE if we need to add max-age; FALSE if we should not add max-age.
--*/
{
if (strstr(CacheControlHeaderValue, "max-age"))
{
return FALSE;
}
PCHAR s;
while ((s = strstr(CacheControlHeaderValue, "no-cache")) != NULL)
{
//
// If it is a no-cache=foo then it only refers to a specific header
// Continue
//
if (s[8] != '=')
{
return FALSE;
}
CacheControlHeaderValue = s + 8;
}
//
// We didn't find any directives that would prevent us from adding
// max-age.
//
return TRUE;
}
// static
HRESULT HTTP_COMPRESSION::OnSendResponse(
IN W3_CONTEXT *pW3Context )
{
W3_RESPONSE *pResponse = pW3Context->QueryResponse();
W3_REQUEST *pRequest = pW3Context->QueryRequest();
//
// If compression is not initialized, return
//
if (sm_InitStatus != COMP_INIT_DONE)
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If we already have a compression context (more than one SendResponse)
// return
//
if (pW3Context->QueryCompressionContext() != NULL)
{
return S_OK;
}
//
// If Response status is not 200 (let us not try compressing 206, 3xx or
// error responses), return
//
if (pResponse->QueryStatusCode() != HttpStatusOk.statusCode &&
pResponse->QueryStatusCode() != HttpStatusMultiStatus.statusCode)
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If the client has not sent an Accept-Encoding header, or an empty
// Accept-Encoding header, return
//
LPCSTR pszAcceptEncoding = pRequest->GetHeader(HttpHeaderAcceptEncoding);
if (pszAcceptEncoding == NULL || *pszAcceptEncoding == L'\0')
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// Don't compress for TRACEs
//
HTTP_VERB VerbType = pW3Context->QueryRequest()->QueryVerbType();
if (VerbType == HttpVerbTRACE)
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If we are configured to not compress for 1.0, and version is not 1.1,
// return
//
if (sm_fNoCompressionForHttp10 &&
((pRequest->QueryVersion().MajorVersion == 0) ||
((pRequest->QueryVersion().MajorVersion == 1) &&
(pRequest->QueryVersion().MinorVersion == 0))))
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If we are configured to not compress for proxies and it is a proxy
// request, return
//
if (sm_fNoCompressionForProxies && pRequest->IsProxyRequest())
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// If the response already has a Content-Encoding header, return
//
if (pResponse->GetHeader(HttpHeaderContentEncoding))
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// Now see if we have any matching scheme
//
STACK_STRA( strAcceptEncoding, 512);
HRESULT hr;
if (FAILED(hr = strAcceptEncoding.Copy(pszAcceptEncoding)))
{
return hr;
}
STRU *pstrPhysical = pW3Context->QueryUrlContext()->QueryPhysicalPath();
LPWSTR pszExtension = wcsrchr(pstrPhysical->QueryStr(), L'.');
if (pszExtension != NULL)
{
pszExtension++;
}
DWORD dwClientCompressionCount;
DWORD matchingSchemes[MAX_SERVER_SCHEMES];
//
// Find out all schemes which will compress for this url
//
FindMatchingSchemes(strAcceptEncoding.QueryStr(),
pszExtension,
DO_DYNAMIC_COMPRESSION,
matchingSchemes,
&dwClientCompressionCount);
if (dwClientCompressionCount == 0)
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// All tests passed, we are GO for dynamic compression
//
COMPRESSION_CONTEXT *pCompressionContext = new COMPRESSION_CONTEXT;
if (pCompressionContext == NULL)
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
pW3Context->SetCompressionContext(pCompressionContext);
pCompressionContext->m_pScheme = sm_pCompressionSchemes[matchingSchemes[0]];
LPCSTR xferEncoding = pResponse->GetHeader(HttpHeaderTransferEncoding);
if (xferEncoding &&
_stricmp(xferEncoding, "chunked") == 0)
{
pCompressionContext->m_fTransferChunkEncoded = TRUE;
}
//
// Remove the Content-Length header, set the Content-Encoding, Vary and
// Transfer-Encoding headers
//
if (FAILED(hr = pResponse->SetHeaderByReference(HttpHeaderContentLength,
NULL, 0)) ||
FAILED(hr = pResponse->SetHeaderByReference(
HttpHeaderContentEncoding,
pCompressionContext->m_pScheme->m_straCompressionSchemeName.QueryStr(),
(USHORT)pCompressionContext->m_pScheme->m_straCompressionSchemeName.QueryCCH())) ||
FAILED(hr = pResponse->SetHeader(HttpHeaderVary,
"Accept-Encoding", 15,
TRUE)) ||
FAILED(hr = pResponse->SetHeaderByReference(HttpHeaderTransferEncoding,
"chunked", 7)))
{
return hr;
}
if (sm_fSendCacheHeaders)
{
if (FAILED(hr = pResponse->SetHeaderByReference(
HttpHeaderExpires,
sm_pstrExpiresHeader->QueryStr(),
(USHORT)sm_pstrExpiresHeader->QueryCCH())))
{
return hr;
}
LPCSTR cacheControl = pResponse->GetHeader(HttpHeaderCacheControl);
if (!cacheControl || DoesCacheControlNeedMaxAge(cacheControl))
{
if (FAILED(hr = pResponse->SetHeader(
HttpHeaderCacheControl,
sm_pstrCacheControlHeader->QueryStr(),
(USHORT)sm_pstrCacheControlHeader->QueryCCH(),
TRUE)))
{
return hr;
}
}
}
//
// Get a compression context
//
if (FAILED(hr = pCompressionContext->m_pScheme->m_pfnCreateCompression(
&pCompressionContext->m_pCompressionContext,
pCompressionContext->m_pScheme->m_dwCreateFlags)))
{
return hr;
}
//
// Ok, done with all the header stuff, now actually start compressing
// the entity
//
if (VerbType == HttpVerbHEAD)
{
pCompressionContext->m_fRequestIsHead = TRUE;
}
//
// BUGBUG: UL does not know about compression right now, so
// disable UL caching for this response
//
pW3Context->DisableUlCache();
return S_OK;
}
BOOL CheckForEndofHeaders(IN LPBYTE pbuff,
IN DWORD cbData,
OUT DWORD *pcbIndexStartOfData)
{
//
// Look for two consecutive newline, \n\r\r\n, \n\r\n or \n\n
//
DWORD index;
for (index = 0; index + 1 < cbData; index++)
{
if (pbuff[index] == '\n')
{
if (pbuff[index + 1] == '\n')
{
*pcbIndexStartOfData = index + 2;
return TRUE;
}
else if (pbuff[index + 1] == '\r' &&
index + 2 < cbData)
{
if (pbuff[index + 2] == '\n')
{
*pcbIndexStartOfData = index + 3;
return TRUE;
}
else if (pbuff[index + 2] == '\r' &&
index + 3 < cbData &&
pbuff[index + 3] == '\n')
{
*pcbIndexStartOfData = index + 4;
return TRUE;
}
}
}
}
return FALSE;
}
// static
HRESULT HTTP_COMPRESSION::DoDynamicCompression(
IN W3_CONTEXT *pW3Context,
IN BOOL fMoreData,
IN HTTP_FILTER_RAW_DATA* pRawData )
{
COMPRESSION_CONTEXT *pCompressionContext = pW3Context->QueryCompressionContext();
DWORD cbChunkOffset = 0;
HRESULT hr;
if (pCompressionContext == NULL)
{
pW3Context->SetDoneWithCompression();
return S_OK;
}
//
// Get hold of the response chunks
//
pCompressionContext->m_pbOrigData = (BYTE *)pRawData->pvInData;
pCompressionContext->m_cbOrigData = pRawData->cbInData;
if (!pCompressionContext->m_fHeadersSent)
{
DWORD cbStartOfEntityBody;
if (!CheckForEndofHeaders(pCompressionContext->m_pbOrigData,
pCompressionContext->m_cbOrigData,
&cbStartOfEntityBody))
{
return S_OK;
}
if (!pCompressionContext->m_bufChunk.Resize(cbStartOfEntityBody))
{
return HRESULT_FROM_WIN32( GetLastError() );
}
memcpy(pCompressionContext->m_bufChunk.QueryPtr(),
pCompressionContext->m_pbOrigData,
cbStartOfEntityBody);
cbChunkOffset = cbStartOfEntityBody;
pCompressionContext->IncrementPointerInULChunk(cbStartOfEntityBody);
pCompressionContext->m_fHeadersSent = TRUE;
}
if (pCompressionContext->m_cbOrigData > 0)
{
pCompressionContext->m_fOriginalBodyEmpty = FALSE;
}
//
// If the request was a HEAD request and we haven't seen any
// entity body, no point in going any further (if the output
// is already suppressed, we do not want to compress it and make it
// non-empty)
//
if (pCompressionContext->m_fRequestIsHead &&
pCompressionContext->m_fOriginalBodyEmpty)
{
return S_OK;
}
BOOL fKeepGoing;
do
{
fKeepGoing = FALSE;
if (pCompressionContext->m_fTransferChunkEncoded)
{
//
// If the input data is being chunk-transfered, initially,
// we'll be looking at the chunk header. This is a hex
// representation of the number of bytes in this chunk.
// Translate this number from ASCII to a DWORD and remember
// it. Also advance the chunk pointer to the start of the
// actual data.
//
if (FAILED(hr = pCompressionContext->ProcessEncodedChunkHeader()))
{
return hr;
}
}
//
// Try to compress all the contiguous bytes
//
DWORD bytesToCompress = pCompressionContext->m_cbOrigData;
if (pCompressionContext->m_fTransferChunkEncoded)
{
bytesToCompress =
min(bytesToCompress,
pCompressionContext->m_dwBytesInCurrentEncodedChunk);
}
if (!fMoreData || bytesToCompress > 0)
{
DWORD inputBytesUsed = 0;
DWORD bytesCompressed = 0;
BYTE compressionBuffer[6 + DYNAMIC_COMPRESSION_BUFFER_SIZE + 7];
hr = pCompressionContext->m_pScheme->m_pfnCompress(
pCompressionContext->m_pCompressionContext,
bytesToCompress ? pCompressionContext->m_pbOrigData : NULL,
bytesToCompress,
compressionBuffer + 6,
DYNAMIC_COMPRESSION_BUFFER_SIZE,
(PLONG)&inputBytesUsed,
(PLONG)&bytesCompressed,
pCompressionContext->m_pScheme->m_dwDynamicCompressionLevel);
if (FAILED(hr))
{
return hr;
}
if (hr == S_OK)
{
fKeepGoing = TRUE;
}
pCompressionContext->IncrementPointerInULChunk(inputBytesUsed);
if (pCompressionContext->m_fTransferChunkEncoded)
{
pCompressionContext->m_dwBytesInCurrentEncodedChunk -=
inputBytesUsed;
}
DWORD startSendLocation = 8;
DWORD bytesToSend = 0;
if (bytesCompressed > 0)
{
//
// Add the CRLF just before and after the chunk data
//
compressionBuffer[4] = '\r';
compressionBuffer[5] = '\n';
compressionBuffer[bytesCompressed + 6] = '\r';
compressionBuffer[bytesCompressed + 7] = '\n';
//
// Now create the chunk header which is basically the chunk
// size written out in hex
//
if (bytesCompressed < 0x10 )
{
startSendLocation = 3;
bytesToSend = 3 + bytesCompressed + 2;
compressionBuffer[3] = HEX_TO_ASCII(bytesCompressed);
}
else if (bytesCompressed < 0x100)
{
startSendLocation = 2;
bytesToSend = 4 + bytesCompressed + 2;
compressionBuffer[2] = HEX_TO_ASCII(bytesCompressed >> 4);
compressionBuffer[3] = HEX_TO_ASCII(bytesCompressed & 0xF);
}
else if (bytesCompressed < 0x1000)
{
startSendLocation = 1;
bytesToSend = 5 + bytesCompressed + 2;
compressionBuffer[1] = HEX_TO_ASCII(bytesCompressed >> 8);
compressionBuffer[2] = HEX_TO_ASCII((bytesCompressed >> 4) & 0xF);
compressionBuffer[3] = HEX_TO_ASCII(bytesCompressed & 0xF);
}
else
{
DBG_ASSERT( bytesCompressed < 0x10000 );
startSendLocation = 0;
bytesToSend = 6 + bytesCompressed + 2;
compressionBuffer[0] = HEX_TO_ASCII(bytesCompressed >> 12);
compressionBuffer[1] = HEX_TO_ASCII((bytesCompressed >> 8) & 0xF);
compressionBuffer[2] = HEX_TO_ASCII((bytesCompressed >> 4) & 0xF);
compressionBuffer[3] = HEX_TO_ASCII(bytesCompressed & 0xF);
}
}
if (!fKeepGoing)
{
//
// If this is the last send, add the trailer 0 length chunk
//
memcpy(compressionBuffer + bytesCompressed + 8, "0\r\n\r\n", 5);
bytesToSend += 5;
}
if (!fKeepGoing || bytesCompressed > 0)
{
//
// Keep appending to buffer
//
if (!pCompressionContext->m_bufChunk.Resize(
cbChunkOffset + bytesToSend))
{
return HRESULT_FROM_WIN32( GetLastError() );
}
memcpy((PBYTE)pCompressionContext->m_bufChunk.QueryPtr() + cbChunkOffset,
compressionBuffer + startSendLocation,
bytesToSend);
cbChunkOffset += bytesToSend;
}
}
}
while (fKeepGoing);
//
// Finally add the chunk
//
if ( cbChunkOffset > 0 )
{
pRawData->pvInData = pCompressionContext->m_bufChunk.QueryPtr();
}
pRawData->cbInData = cbChunkOffset;
return S_OK;
}
HRESULT COMPRESSION_CONTEXT::ProcessEncodedChunkHeader()
{
HRESULT hr;
while ((m_dwBytesInCurrentEncodedChunk == 0 ||
m_encodedChunkState != IN_CHUNK_DATA) &&
m_cbOrigData > 0)
{
switch (m_encodedChunkState)
{
case IN_CHUNK_LENGTH:
if (FAILED(hr = CalculateEncodedChunkByteCount()))
{
return hr;
}
break;
case IN_CHUNK_EXTENSION:
DeleteEncodedChunkExtension();
break;
case IN_CHUNK_HEADER_NEW_LINE:
IncrementPointerInULChunk();
m_encodedChunkState = IN_CHUNK_DATA;
break;
case AT_CHUNK_DATA_NEW_LINE:
IncrementPointerInULChunk();
m_encodedChunkState = IN_CHUNK_DATA_NEW_LINE;
break;
case IN_CHUNK_DATA_NEW_LINE:
IncrementPointerInULChunk();
m_encodedChunkState = IN_CHUNK_LENGTH;
break;
case IN_CHUNK_DATA:
m_encodedChunkState = AT_CHUNK_DATA_NEW_LINE;
break;
default:
DBG_ASSERT(FALSE);
}
}
return S_OK;
}
HRESULT COMPRESSION_CONTEXT::CalculateEncodedChunkByteCount()
{
CHAR c = '\0';
//
// Walk to the first '\r' or ';' which signifies the end of the chunk
// byte count
//
while (m_cbOrigData > 0 &&
SAFEIsXDigit(c = *m_pbOrigData))
{
m_dwBytesInCurrentEncodedChunk <<= 4;
if (c >= '0' && c <= '9')
{
m_dwBytesInCurrentEncodedChunk += c - '0';
}
else
{
m_dwBytesInCurrentEncodedChunk += (c | 0x20) - 'a' + 10;
}
IncrementPointerInULChunk();
}
if (m_cbOrigData > 0)
{
if (c == ';')
{
m_encodedChunkState = IN_CHUNK_EXTENSION;
}
else if (c == '\r')
{
m_encodedChunkState = IN_CHUNK_HEADER_NEW_LINE;
}
else
{
DBG_ASSERT(!"Malformed chunk header");
return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
}
IncrementPointerInULChunk();
}
return S_OK;
}
VOID COMPRESSION_CONTEXT::DeleteEncodedChunkExtension()
{
//
// Walk to the first '\r' which signifies the end of the chunk extension
//
while (m_cbOrigData > 0 &&
*m_pbOrigData != '\r')
{
IncrementPointerInULChunk();
}
if (m_cbOrigData > 0)
{
m_encodedChunkState = IN_CHUNK_HEADER_NEW_LINE;
IncrementPointerInULChunk();
}
}