/*++ 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~" // mask for clearing bits when comparing file acls #define ACL_CLEAR_BIT_MASK (~(SE_DACL_AUTO_INHERITED | \ SE_SACL_AUTO_INHERITED | \ SE_DACL_PROTECTED | \ SE_SACL_PROTECTED)) #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; BOOL HTTP_COMPRESSION::sm_fHttpCompressionInitialized = FALSE; BOOL HTTP_COMPRESSION::sm_fIsTerminating = FALSE; ALLOC_CACHE_HANDLER *COMPRESSION_BUFFER::allocHandler; ALLOC_CACHE_HANDLER *COMPRESSION_CONTEXT::allocHandler; // static HRESULT HTTP_COMPRESSION::Initialize() /*++ Synopsis Initialize function called during Server startup Returns HRESULT --*/ { // // 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) { return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } HRESULT hr; if (FAILED(hr = sm_pstrCompressionDirectory->Copy( L"%windir%\\IIS Temporary Compressed Files")) || FAILED(hr = sm_pstrCacheControlHeader->Copy("maxage=86400")) || FAILED(hr = sm_pstrExpiresHeader->Copy( "Wed, 01 Jan 1997 12:00:00 GMT"))) { return hr; } MB mb(g_pW3Server->QueryMDObject()); if (!mb.Open(HTTP_COMPRESSION_KEY)) { return HRESULT_FROM_WIN32(GetLastError()); } // // Read the global configuration from the metabase // if (FAILED(hr = ReadMetadata(&mb))) { return hr; } // // Read in all the compression schemes and initialize them // if (FAILED(hr = InitializeCompressionSchemes(&mb))) { return hr; } mb.Close(); if (FAILED(hr = COMPRESSION_CONTEXT::Initialize())) { return hr; } if (FAILED(hr = COMPRESSION_BUFFER::Initialize())) { return hr; } // // Initialize other stuff // sm_pIoBuffer = new BYTE[sm_dwIoBufferSize]; sm_pCompressionBuffer = new BYTE[sm_dwCompressionBufferSize]; if (!sm_pIoBuffer || !sm_pCompressionBuffer) { return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } INITIALIZE_CRITICAL_SECTION(&sm_CompressionDirectoryLock); if (FAILED(hr = InitializeCompressionDirectory())) { return hr; } if (FAILED(hr = InitializeCompressionThread())) { return hr; } sm_fHttpCompressionInitialized = TRUE; return S_OK; } 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); 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 { DBG_ASSERT(workItem->WorkItemType == COMPRESSION_WORK_ITEM_COMPRESS); CompressFile(workItem->scheme, workItem->strPhysicalPath); } } 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) { return HRESULT_FROM_WIN32(GetLastError()); } DWORD threadId; sm_hCompressionThreadHandle = CreateThread(NULL, 0, CompressionThread, NULL, 0, &threadId); if (sm_hCompressionThreadHandle == NULL) { 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; // // Find if the directory exists, if not create it // if (!GetFileAttributesEx(sm_pstrCompressionDirectory->QueryStr(), GetFileExInfoStandard, &fileInformation)) { if (!CreateDirectory(sm_pstrCompressionDirectory->QueryStr(), NULL)) { 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 // STACK_STRU (CompDirWildcard, 512); HRESULT hr; if (FAILED(hr = CompDirWildcard.Copy(*sm_pstrCompressionDirectory)) || FAILED(hr = CompDirWildcard.Append(L"\\", 1)) || FAILED(hr = CompDirWildcard.Append(COMP_FILE_PREFIX)) || FAILED(hr = CompDirWildcard.Append(L"*", 1))) { return hr; } WIN32_FIND_DATA win32FindData; HANDLE hDirectory = FindFirstFile(CompDirWildcard.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; im_strFilePrefix; HRESULT hr; 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; im_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) { WCHAR pszCompressionDir[MAX_PATH + 1]; if (!ExpandEnvironmentStrings(sm_pstrCompressionDirectory->QueryStr(), pszCompressionDir, sizeof pszCompressionDir/sizeof WCHAR)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto ErrorExit; } sm_pstrCompressionDirectory->Copy(pszCompressionDir); } 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 --*/ { DBG_ASSERT(sm_fHttpCompressionInitialized); sm_fIsTerminating = TRUE; // // 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; COMPRESSION_BUFFER::Terminate(); COMPRESSION_CONTEXT::Terminate(); // // Free static objects // delete sm_pstrCompressionDirectory; sm_pstrCompressionDirectory = NULL; delete sm_pstrCacheControlHeader; sm_pstrCacheControlHeader = NULL; delete sm_pstrExpiresHeader; sm_pstrExpiresHeader = NULL; delete sm_pIoBuffer; sm_pIoBuffer = NULL; delete sm_pCompressionBuffer; sm_pCompressionBuffer = NULL; DeleteCriticalSection(&sm_CompressionDirectoryLock); DeleteCriticalSection(&sm_CompressionThreadLock); // // For each compression scheme, unload the compression dll and free // the space that holds info about the scheme // for (DWORD i=0; iSetDoneWithCompression(); 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(); CHAR *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; LARGE_INTEGER originalFileSize; pOrigFile->QuerySize(&originalFileSize); if (originalFileSize.QuadPart < sm_dwMinFileSizeForCompression) { 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); // // 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; iQueryResponse(); if (FAILED(hr = pResponse->SetHeaderByReference( HttpHeaderContentEncoding, scheme->m_straCompressionSchemeName.QueryStr(), scheme->m_straCompressionSchemeName.QueryCCH()))) { pCompFile->DereferenceCacheEntry(); return hr; } if (sm_fSendCacheHeaders) { if (FAILED(hr = pResponse->SetHeaderByReference( HttpHeaderExpires, sm_pstrExpiresHeader->QueryStr(), sm_pstrExpiresHeader->QueryCCH())) || FAILED(hr = pResponse->SetHeaderByReference( HttpHeaderCacheControl, sm_pstrCacheControlHeader->QueryStr(), sm_pstrCacheControlHeader->QueryCCH()))) { pCompFile->DereferenceCacheEntry(); return hr; } } if (FAILED(hr = pResponse->SetHeaderByReference( HttpHeaderVary, "Accept-Encoding", 15))) { pCompFile->DereferenceCacheEntry(); return hr; } 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, *pstrPhysical); } // // 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; 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 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.FindString(pszExtension)))) { break; } } else { if (!sm_pCompressionSchemes[j]->m_fDoDynamicCompression || (sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.QueryStringCount() && (!pszExtension || !sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.FindString(pszExtension)))) { break; } } matchingSchemes[dwNumberOfActualSchemes++] = j; break; } } } // // If * was specified, add all unadded applicable schemes // if (fAddStar) { for (DWORD j=0; jm_fDoStaticCompression || (sm_pCompressionSchemes[j]->m_mszFileExtensions.QueryStringCount() && (!pszExtension || !sm_pCompressionSchemes[j]->m_mszFileExtensions.FindString(pszExtension)))) { continue; } } else { if (!sm_pCompressionSchemes[j]->m_fDoDynamicCompression || (sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.QueryStringCount() && (!pszExtension || !sm_pCompressionSchemes[j]->m_mszScriptFileExtensions.FindString(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; FILE_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; LARGE_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) { // // The filetime passed, also check if ACLs are the same // PSECURITY_DESCRIPTOR compressedFileAcls = pCompFile->QuerySecDesc(); PSECURITY_DESCRIPTOR originalFileAcls = pOrigFile->QuerySecDesc(); if (compressedFileAcls != NULL && originalFileAcls != NULL ) { DWORD compressedFileAclsLen = GetSecurityDescriptorLength(compressedFileAcls); DWORD originalFileAclsLen = GetSecurityDescriptorLength(originalFileAcls); if (originalFileAclsLen == compressedFileAclsLen) { SECURITY_DESCRIPTOR_CONTROL compressedFileCtl = ((PISECURITY_DESCRIPTOR)compressedFileAcls)->Control; SECURITY_DESCRIPTOR_CONTROL originalFileCtl = ((PISECURITY_DESCRIPTOR)originalFileAcls)->Control; ((PISECURITY_DESCRIPTOR)compressedFileAcls)->Control &= ACL_CLEAR_BIT_MASK; ((PISECURITY_DESCRIPTOR)originalFileAcls)->Control &= ACL_CLEAR_BIT_MASK; success = (memcmp((PVOID)originalFileAcls,(PVOID)compressedFileAcls, originalFileAclsLen) == 0); ((PISECURITY_DESCRIPTOR)compressedFileAcls)->Control = compressedFileCtl; ((PISECURITY_DESCRIPTOR)originalFileAcls)->Control = originalFileCtl; } } } // // 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 STRU &strPhysicalPath) /*++ 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; if (FAILED(WorkItem->strPhysicalPath.Copy(strPhysicalPath))) { delete WorkItem; return FALSE; } // // 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 STRU &strPhysicalPath) /*++ 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 ( compressedFileName, 256); STACK_STRU ( realCompressedFileName, 256); BOOL success = FALSE; DWORD cbIo = 0; DWORD bytesCompressed = 0; SECURITY_ATTRIBUTES securityAttributes; DWORD totalBytesWritten = 0; BOOL usedScheme = FALSE; LARGE_INTEGER *pli = NULL; W3_FILE_INFO *pofiOriginalFile = NULL; W3_FILE_INFO *pofiCompressedFile = NULL; FILETIME originalFileTime; PSECURITY_DESCRIPTOR originalFileAcls = NULL; DWORD originalFileAclsLen = 0; OVERLAPPED ovlForRead; DWORD readStatus; ULARGE_INTEGER readOffset; SYSTEMTIME systemTime; FILETIME fileTime; WCHAR pszPid[16]; DWORD dwPid; BYTE *pCachedFileBuffer; BOOL fHaveCachedFileBuffer = FALSE; DWORD dwOrigFileSize; LARGE_INTEGER liOrigFileSize; DIRMON_CONFIG dirConfig; FILE_CACHE_USER fileUser; // // 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(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))) { goto exit; } DBG_ASSERT( g_pW3Server->QueryFileCache() != NULL ); if (FAILED(g_pW3Server->QueryFileCache()->GetFileInfo( strPhysicalPath, NULL, &fileUser, TRUE, &pofiOriginalFile))) { goto exit; } success = CheckForExistenceOfCompressedFile(pofiOriginalFile, &realCompressedFileName, &pofiCompressedFile, FALSE); if (!success) { originalFileAcls = pofiOriginalFile->QuerySecDesc(); if (originalFileAcls == NULL) { goto exit; } originalFileAclsLen = GetSecurityDescriptorLength(originalFileAcls); pofiOriginalFile->QueryLastWriteTime(&originalFileTime); pCachedFileBuffer = pofiOriginalFile->QueryFileBuffer(); pofiOriginalFile->QuerySize(&liOrigFileSize); dwOrigFileSize = liOrigFileSize.LowPart; securityAttributes.nLength = originalFileAclsLen; securityAttributes.lpSecurityDescriptor = originalFileAcls; securityAttributes.bInheritHandle = FALSE; // // Do the actual file open. We open the file for exclusive access, // and we assume that the file will not already exist. // hCompressedFile = CreateFile( compressedFileName.QueryStr(), GENERIC_WRITE, 0, &securityAttributes, 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 (TRUE) { 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(TRUE) if (!success) { goto exit; } // // 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( compressedFileName.QueryStr(), realCompressedFileName.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 (pofiOriginalFile != NULL) { pofiOriginalFile->DereferenceCacheEntry(); pofiOriginalFile = NULL; } 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; BOOL success; 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); if (FAILED(CompDirWildcard.Copy(*sm_pstrCompressionDirectory)) || 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; } while (TRUE) { // // 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(¤tFindData->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) { if (FAILED(strFile.Copy(*sm_pstrCompressionDirectory)) || 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; BOOL success; 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 { bytesCompressed = sm_dwCompressionBufferSize; 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 && BytesToCompress == 0) { 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 || keepGoing ); return TRUE; } BOOL DoesCacheControlNeedMaxAge(IN PCHAR 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")) { // // 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, IN BOOL fMoreData) { W3_RESPONSE *pResponse = pW3Context->QueryResponse(); W3_REQUEST *pRequest = pW3Context->QueryRequest(); // // If compression is not initialized, return // if (!sm_fHttpCompressionInitialized) { pW3Context->SetDoneWithCompression(); 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 // CHAR *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 || VerbType == HttpVerbTRACK) { 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]]; PCHAR 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(), 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(), sm_pstrExpiresHeader->QueryCCH()))) { return hr; } PCHAR cacheControl = pResponse->GetHeader(HttpHeaderCacheControl); if (!cacheControl || DoesCacheControlNeedMaxAge(cacheControl)) { if (FAILED(hr = pResponse->SetHeader( HttpHeaderCacheControl, sm_pstrCacheControlHeader->QueryStr(), 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 DoDynamicCompression(pW3Context, fMoreData); } // static HRESULT HTTP_COMPRESSION::DoDynamicCompression( IN W3_CONTEXT *pW3Context, IN BOOL fMoreData) { COMPRESSION_CONTEXT *pCompressionContext = pW3Context->QueryCompressionContext(); if (pCompressionContext == NULL) { pW3Context->SetDoneWithCompression(); return S_OK; } pCompressionContext->FreeBuffers(); // // Get hold of the response chunks // W3_RESPONSE *pResponse = pW3Context->QueryResponse(); HRESULT hr; if (FAILED(hr = pResponse->GetChunks(&pCompressionContext->m_ULChunkBuffer, &pCompressionContext->m_cULChunks))) { return hr; } if (pCompressionContext->m_cULChunks > 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; } pCompressionContext->m_cCurrentULChunk = 0; pCompressionContext->m_pCurrentULChunk = (HTTP_DATA_CHUNK *)pCompressionContext->m_ULChunkBuffer.QueryPtr(); // // Do whatever is necessary to setup the current chunk of response // e.g. do an I/O for FileHandle chunk // if (FAILED(hr = pCompressionContext->SetupCurrentULChunk())) { return hr; } 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 = 0; if (pCompressionContext->m_cCurrentULChunk < pCompressionContext->m_cULChunks) { bytesToCompress = pCompressionContext->QueryBytesAvailable(); if (pCompressionContext->m_fTransferChunkEncoded) { bytesToCompress = min(bytesToCompress, pCompressionContext->m_dwBytesInCurrentEncodedChunk); } } if (!fMoreData || bytesToCompress > 0) { DWORD inputBytesUsed = 0; DWORD bytesCompressed = 0; PBYTE compressionBuffer = pCompressionContext->GetNewBuffer(); hr = pCompressionContext->m_pScheme->m_pfnCompress( pCompressionContext->m_pCompressionContext, bytesToCompress ? pCompressionContext->QueryBytePtr() : 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; } if (FAILED(hr = pCompressionContext->IncrementPointerInULChunk(inputBytesUsed))) { return hr; } 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) { if (FAILED(hr = pResponse->AddMemoryChunkByReference( compressionBuffer + startSendLocation, bytesToSend))) { return hr; } } } } while (fKeepGoing); return S_OK; } HRESULT COMPRESSION_CONTEXT::SetupCurrentULChunk() /*++ Set up this new chunk so that compression has some bytes to read. If the current chunk is a 0 length chunk or we have reached the end-of-file on the file handle, this function will recurse Return Value: HRESULT --*/ { if (m_cCurrentULChunk >= m_cULChunks) { return S_OK; } if (m_pCurrentULChunk->DataChunkType == HttpDataChunkFromMemory) { if (m_pCurrentULChunk->FromMemory.BufferLength == 0) { m_cCurrentULChunk++; m_pCurrentULChunk++; return SetupCurrentULChunk(); } m_fCurrentULChunkFromMemory = TRUE; return S_OK; } // We do not (nor do we plan to) handle filename chunks DBG_ASSERT(m_pCurrentULChunk->DataChunkType == HttpDataChunkFromFileHandle); m_fCurrentULChunkFromMemory = FALSE; if (m_pIoBuffer == NULL) { m_pIoBuffer = new BYTE[DYNAMIC_COMPRESSION_BUFFER_SIZE]; if (m_pIoBuffer == NULL) { return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } } DWORD bytesToRead = (DWORD)min(DYNAMIC_COMPRESSION_BUFFER_SIZE, m_pCurrentULChunk->FromFileHandle.ByteRange.Length.QuadPart); OVERLAPPED ovl; ZeroMemory(&ovl, sizeof ovl); ovl.Offset = m_pCurrentULChunk->FromFileHandle.ByteRange.StartingOffset.LowPart; ovl.OffsetHigh = m_pCurrentULChunk->FromFileHandle.ByteRange.StartingOffset.HighPart; DWORD bytesRead = 0; if (!ReadFile(m_pCurrentULChunk->FromFileHandle.FileHandle, m_pIoBuffer, bytesToRead, &bytesRead, &ovl)) { DWORD dwErr = GetLastError(); switch (dwErr) { case ERROR_IO_PENDING: if (!GetOverlappedResult( m_pCurrentULChunk->FromFileHandle.FileHandle, &ovl, &bytesRead, TRUE)) { dwErr = GetLastError(); switch(dwErr) { case ERROR_HANDLE_EOF: m_pCurrentULChunk++; m_cCurrentULChunk++; return SetupCurrentULChunk(); default: return HRESULT_FROM_WIN32(dwErr); } } break; case ERROR_HANDLE_EOF: m_pCurrentULChunk++; m_cCurrentULChunk++; return SetupCurrentULChunk(); default: return HRESULT_FROM_WIN32(dwErr); } } m_pCurrentULChunk->FromFileHandle.ByteRange.Length.QuadPart -= bytesRead; m_pCurrentULChunk->FromFileHandle.ByteRange.StartingOffset.QuadPart += bytesRead; m_currentLocationInIoBuffer = 0; m_bytesInIoBuffer = bytesRead; return S_OK; } HRESULT COMPRESSION_CONTEXT::ProcessEncodedChunkHeader() { HRESULT hr; while ((m_dwBytesInCurrentEncodedChunk == 0 || m_encodedChunkState != IN_CHUNK_DATA) && m_cCurrentULChunk < m_cULChunks) { switch (m_encodedChunkState) { case IN_CHUNK_LENGTH: if (FAILED(hr = CalculateEncodedChunkByteCount())) { return hr; } break; case IN_CHUNK_EXTENSION: if (FAILED(hr = DeleteEncodedChunkExtension())) { return hr; } break; case IN_CHUNK_HEADER_NEW_LINE: if (FAILED(hr = IncrementPointerInULChunk())) { return hr; } m_encodedChunkState = IN_CHUNK_DATA; break; case AT_CHUNK_DATA_NEW_LINE: if (FAILED(hr = IncrementPointerInULChunk())) { return hr; } m_encodedChunkState = IN_CHUNK_DATA_NEW_LINE; break; case IN_CHUNK_DATA_NEW_LINE: if (FAILED(hr = IncrementPointerInULChunk())) { return hr; } 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::IncrementPointerInULChunk(IN DWORD dwIncr) { while (dwIncr && (m_cCurrentULChunk < m_cULChunks)) { DWORD bytesLeft = QueryBytesAvailable(); if (bytesLeft > dwIncr) { if (m_fCurrentULChunkFromMemory) { m_pCurrentULChunk->FromMemory.pBuffer = (PBYTE)m_pCurrentULChunk->FromMemory.pBuffer + dwIncr; m_pCurrentULChunk->FromMemory.BufferLength -= dwIncr; } else { // We do not (nor do we plan to) handle filename chunks DBG_ASSERT(m_pCurrentULChunk->DataChunkType == HttpDataChunkFromFileHandle); m_currentLocationInIoBuffer += dwIncr; } return S_OK; } else { dwIncr -= bytesLeft; if (m_fCurrentULChunkFromMemory || m_pCurrentULChunk->FromFileHandle.ByteRange.Length.QuadPart == 0) { m_cCurrentULChunk++; m_pCurrentULChunk++; } HRESULT hr; if (FAILED(hr = SetupCurrentULChunk())) { return hr; } } } return S_OK; } HRESULT COMPRESSION_CONTEXT::CalculateEncodedChunkByteCount() { CHAR c; HRESULT hr; // // Walk to the first '\r' or ';' which signifies the end of the chunk // byte count // while (m_cCurrentULChunk < m_cULChunks && SAFEIsXDigit(c = (CHAR)*QueryBytePtr())) { m_dwBytesInCurrentEncodedChunk <<= 4; if (c >= '0' && c <= '9') { m_dwBytesInCurrentEncodedChunk += c - '0'; } else { m_dwBytesInCurrentEncodedChunk += (c | 0x20) - 'a' + 10; } if (FAILED(hr = IncrementPointerInULChunk())) { return hr; } } if (m_cCurrentULChunk < m_cULChunks) { 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); } if (FAILED(hr = IncrementPointerInULChunk())) { return hr; } } return S_OK; } HRESULT COMPRESSION_CONTEXT::DeleteEncodedChunkExtension() { CHAR c; HRESULT hr; // // Walk to the first '\r' which signifies the end of the chunk extension // while (m_cCurrentULChunk < m_cULChunks && (c = (CHAR)*QueryBytePtr()) != '\r') { if (FAILED(hr = IncrementPointerInULChunk())) { return hr; } } if (m_cCurrentULChunk < m_cULChunks) { m_encodedChunkState = IN_CHUNK_HEADER_NEW_LINE; if (FAILED(hr = IncrementPointerInULChunk())) { return hr; } } return S_OK; }