|
|
/*++
Copyright (c) 1999 Microsoft Corporation
Module Name : urlinfo.cxx
Abstract: Implementation of URL cache Author: Bilal Alam (balam) 8-Jan-2000
Environment: Win32 - User Mode
Project: ULW3.DLL --*/
#include "precomp.hxx"
//
// This is the maximum size for a script map extension
//
#define MAX_EXT_LEN 128
ALLOC_CACHE_HANDLER * W3_URL_INFO::sm_pachW3UrlInfo; DWORD W3_URL_INFO::sm_cMaxDots;
HRESULT W3_URL_INFO_KEY::CreateCacheKey( WCHAR * pszKey, DWORD cchKey, DWORD cchSitePrefix, BOOL fCopy ) /*++
Description:
Setup a URI cache key
Arguments:
pszKey - URL of cache key cchKey - size of URL cchSitePrefix - Size of site prefix ("LM/W3SVC/<n>") fCopy - Set to TRUE if we should copy the URL, else we just keep a ref Return:
HRESULT
--*/ { HRESULT hr; _cchSitePrefix = cchSitePrefix; if ( fCopy ) { hr = _strKey.Copy( pszKey ); if ( FAILED( hr ) ) { return hr; } _pszKey = _strKey.QueryStr(); _cchKey = _strKey.QueryCCH(); } else { _pszKey = pszKey; _cchKey = cchKey; }
return NO_ERROR; }
//static
HRESULT W3_URL_INFO::Initialize( VOID ) /*++
Description:
URI entry lookaside initialization
Arguments:
None Return:
HRESULT
--*/ { ALLOC_CACHE_CONFIGURATION acConfig; HRESULT hr; DWORD dwError; HKEY hKey = NULL; DWORD cbData; DWORD dwType; DWORD dwValue;
//
// Default max dots is 100
//
sm_cMaxDots = 100; //
// Look for a registry override to the max dot value
// (one of the more useful configurable options in IIS)
//
dwError = RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\inetinfo\\Parameters", 0, KEY_READ, &hKey ); if ( dwError == ERROR_SUCCESS ) { DBG_ASSERT( hKey != NULL ); //
// Should we be file caching at all?
//
cbData = sizeof( DWORD ); dwError = RegQueryValueEx( hKey, L"MaxDepthDots", NULL, &dwType, (LPBYTE) &dwValue, &cbData ); if ( dwError == ERROR_SUCCESS && dwType == REG_DWORD ) { sm_cMaxDots = dwValue; } RegCloseKey( hKey ); }
//
// Initialize allocation lookaside
//
acConfig.nConcurrency = 1; acConfig.nThreshold = 100; acConfig.cbSize = sizeof( W3_URL_INFO );
DBG_ASSERT( sm_pachW3UrlInfo == NULL ); sm_pachW3UrlInfo = new ALLOC_CACHE_HANDLER( "W3_URL_INFO", &acConfig );
if ( sm_pachW3UrlInfo == NULL ) { hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
DBGPRINTF(( DBG_CONTEXT, "Error initializing sm_pachW3UrlInfo. hr = 0x%x\n", hr ));
return hr; } return NO_ERROR; }
//static
VOID W3_URL_INFO::Terminate( VOID ) /*++
Description:
URI cache cleanup
Arguments:
None Return:
None
--*/ { if ( sm_pachW3UrlInfo != NULL ) { delete sm_pachW3UrlInfo; sm_pachW3UrlInfo = NULL; } }
HRESULT W3_URL_INFO::Create( STRU & strUrl, STRU & strMetadataPath ) /*++
Description:
Initialize a W3_URL_INFO
Arguments:
strUrl - Url key for this entry strMetadataPath - Full metadata path used for key
Return:
HRESULT
--*/ { HRESULT hr; hr = _cacheKey.CreateCacheKey( strMetadataPath.QueryStr(), strMetadataPath.QueryCCH(), strMetadataPath.QueryCCH() - strUrl.QueryCCH(), TRUE ); if ( FAILED( hr ) ) { return hr; } //
// Process the URL for execution info (and splitting path_info/url)
//
hr = ProcessUrl( strUrl ); if( FAILED( hr ) ) { return hr; } //
// Now create physical path
//
hr = _pMetaData->BuildPhysicalPath( _strProcessedUrl, &_strPhysicalPath ); if ( FAILED( hr ) ) { return hr; } //
// Now create translation of the whole URL. Note: this is not always
// PathTranslated, so we do not name it so
//
hr = _pMetaData->BuildPhysicalPath( strUrl, &_strUrlTranslated ); if ( FAILED( hr ) ) { return hr; } return NO_ERROR; }
HRESULT W3_URL_INFO::GetPathTranslated( W3_CONTEXT * pW3Context, BOOL fUsePathInfo, STRU * pstrPathTranslated ) /*++
Routine Description:
Given the PATH_INFO of this cached entry, determine PATH_TRANSLATED. This involves: 1) Mapping PATH_INFO to a physical path 2) Calling filters if necessary for this path
We cache the output of step 1)
Arguments:
pW3Context - W3Context pstrPathTranslated - Filled with physical path on success
Return Value:
HRESULT
--*/ { HRESULT hr = S_OK; W3_URL_INFO * pUrlInfo = NULL; BOOL fReadLocked = TRUE; if ( pW3Context == NULL || pstrPathTranslated == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); }
//
// Check for cached path translated entry. If its there and flushed,
// then release it (and try again)
//
if ( _pUrlInfoPathTranslated != NULL ) { _PathTranslatedLock.ReadLock(); if ( _pUrlInfoPathTranslated != NULL ) { if ( _pUrlInfoPathTranslated->QueryIsFlushed() ) { _PathTranslatedLock.ConvertSharedToExclusive(); //
// Remember to write unlock when we're done
//
fReadLocked = FALSE; //
// Due to how lock works, someone may have freed it already
//
if ( _pUrlInfoPathTranslated != NULL ) { _pUrlInfoPathTranslated->DereferenceCacheEntry(); _pUrlInfoPathTranslated = NULL; } } else { _pUrlInfoPathTranslated->ReferenceCacheEntry(); pUrlInfo = _pUrlInfoPathTranslated; } } if ( fReadLocked ) { _PathTranslatedLock.ReadUnlock(); } else { _PathTranslatedLock.WriteUnlock(); } }
//
// Use the cached translated entry if there
//
if ( pUrlInfo == NULL ) { //
// Get and keep the metadata and urlinfo for this path
//
DBG_ASSERT( g_pW3Server->QueryUrlInfoCache() != NULL );
if ( fUsePathInfo ) { hr = g_pW3Server->QueryUrlInfoCache()->GetUrlInfo( pW3Context, _strPathInfo, &pUrlInfo ); } else { pUrlInfo = this; pUrlInfo->ReferenceCacheEntry(); }
if ( FAILED( hr ) ) { return hr; } DBG_ASSERT( pUrlInfo != NULL );
//
// Store away the URL info, provided it is an empty string.
//
// Basically, we're trying to optimize the case of "/foobar.dll".
// In this case, path info is empty, and we want to avoid doing the
// extra URL lookup for empty string.
//
if ( pUrlInfo->QueryCached() && pUrlInfo->QueryUrl()[0] == L'\0' ) { _PathTranslatedLock.WriteLock(); if ( _pUrlInfoPathTranslated == NULL ) { _pUrlInfoPathTranslated = pUrlInfo; } else { pUrlInfo->DereferenceCacheEntry(); pUrlInfo = _pUrlInfoPathTranslated; }
pUrlInfo->ReferenceCacheEntry();
_PathTranslatedLock.WriteUnlock(); } } DBG_ASSERT( pUrlInfo != NULL ); //
// Now call into the filter
//
hr = W3_STATE_URLINFO::FilterMapPath( pW3Context, pUrlInfo, pstrPathTranslated ); pUrlInfo->DereferenceCacheEntry(); return hr; }
HRESULT W3_URL_INFO::GetFileInfo( CACHE_USER * pOpeningUser, BOOL fDoCache, W3_FILE_INFO ** ppFileInfo, FILE_CACHE_ASYNC_CONTEXT * pAsyncContext, BOOL * pfHandledSync, BOOL fAllowNoBuffering, BOOL fCheckForExistenceOnly ) /*++
Routine Description:
Get file info associated with this cache entry. If it doesn't exist, then go to file cache directly to open the file
Arguments:
pOpeningUser - Opening user fDoCache - Should we cache the file ppFileInfo - Set to file cache entry on success pAsyncContext - In case an async read is desired, context with callback info rmation pfHandledSync - Did the open complete synchronously fAllowNoBuffering - If opening the file, can we open with no-OS-buffering fCheckFOrExistenceOnly - Check for existence only Return Value:
HRESULT
--*/ { W3_FILE_INFO * pFileInfo = NULL; BOOL fCleanup = FALSE; HRESULT hr; if ( ppFileInfo == NULL || pOpeningUser == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *ppFileInfo = NULL; //
// Do we already have a file entry associated? If so use it, if it was
// not already flushed
//
// BIG NOTE: We will also cache "existence-only" cache entries since
// that makes our "Check-Path-Existence" option work fast.
//
// What that means is if we're not being asked for an existence-check,
// then we need to not use an existence-only cache entry
//
LockCacheEntry(); if ( _pFileInfo != NULL ) { if ( _pFileInfo->Checkout(pOpeningUser) ) { pFileInfo = _pFileInfo; } else { _pFileInfo->DereferenceCacheEntry(); _pFileInfo = NULL; } } UnlockCacheEntry();
//
// Is this an existence-only entry? Is it usable
//
if ( pFileInfo != NULL && pFileInfo->QueryFileHandle() == INVALID_HANDLE_VALUE && pFileInfo->QueryFileBuffer() == NULL && !fCheckForExistenceOnly ) { //
// Doh. This entry isn't very useful for us. Clear it out
//
LockCacheEntry(); if ( _pFileInfo != NULL ) { _pFileInfo = NULL; } UnlockCacheEntry(); pFileInfo->DereferenceCacheEntry(); pFileInfo = NULL; //
// Fall thru and act as if we never had a cached file to begin with
//
}
//
// If we got a file entry, we're done, assuming access check is ok
//
if ( pFileInfo != NULL ) { hr = pFileInfo->DoAccessCheck( pOpeningUser ); if ( SUCCEEDED( hr ) ) { *ppFileInfo = pFileInfo; if (pfHandledSync != NULL) { *pfHandledSync = TRUE; }
return NO_ERROR; } else { pFileInfo->DereferenceCacheEntry(); return hr; } } //
// We'll have to go to file cache directly
//
DBG_ASSERT( g_pW3Server->QueryFileCache() != NULL ); hr = g_pW3Server->QueryFileCache()->GetFileInfo( _strPhysicalPath, _pMetaData->QueryDirmonConfig(), pOpeningUser, fDoCache, &pFileInfo, pAsyncContext, pfHandledSync, fAllowNoBuffering, fCheckForExistenceOnly ); if ( FAILED( hr ) ) { return hr; } //
// Now try to stuff the file descriptor into this object (bearing in mind
// that another thread may try to do the same thing for this W3_URL_INFO)
//
if ( ((pfHandledSync == NULL) || *pfHandledSync) && pFileInfo->QueryCached() ) { LockCacheEntry(); if ( _pFileInfo == NULL ) { pFileInfo->ReferenceCacheEntry(); _pFileInfo = pFileInfo; } UnlockCacheEntry(); }
//
// It is OK if this thread was not able to associate the file entry.
//
*ppFileInfo = pFileInfo;
return NO_ERROR; }
HRESULT W3_URL_INFO::ProcessUrl( STRU & strUrl ) /*++
Routine Description:
Process the URL and assocate execution information with the W3_URL_INFO.
Called before adding a new URL info to the cache.
Look through the url for script-mapped or executable extensions and update the W3_URL_INFO with the actual URL to execute, path-info, gateway type, etc.
Arguments:
strUrl - Original requested URL
Return Value:
SUCCEEDED()/FAILED()
CODEWORK
1. Handle wildcard mappings
--*/ { HRESULT hr = NOERROR; DWORD cDots = 0;
DBG_ASSERT( _pMetaData );
STACK_STRU( strExtension, MAX_EXT_LEN );
//
// Reference the URL_INFO's data
//
STRU * pstrProcessedUrl = &_strProcessedUrl; STRU * pstrPathInfo = &_strPathInfo;
//
// Iterate over pstrProcessedUrl. These always point at the
// pstrProcessedUrl string.
//
WCHAR * pszExtensionIter = NULL; WCHAR * pszPathInfoIter = NULL;
//
// Make a working copy of the URL, this will be modified if an
// exectuable extension is found before the terminal node in the
// path.
//
hr = pstrProcessedUrl->Copy( strUrl ); if( FAILED(hr) ) { goto failure; }
//
// Search the URL for an extension that matches something
// we know how to execute.
//
pszExtensionIter = pstrProcessedUrl->QueryStr(); while( pszExtensionIter = wcschr( pszExtensionIter, L'.' ) ) { //
// Maintain a count of the dots we encounter, any more than
// sm_cMaxDots and we fail the request
//
cDots++; if ( cDots > sm_cMaxDots ) { break; } //
// Save the extension string
//
hr = strExtension.Copy( pszExtensionIter ); if( FAILED(hr) ) { goto failure; } //
// Find the end of the string or the beginning of the path info
//
pszPathInfoIter = wcschr( pszExtensionIter, L'/' ); if( pszPathInfoIter != NULL ) { DBG_REQUIRE( strExtension.SetLen( DIFF(pszPathInfoIter - pszExtensionIter) ) ); } //
// Lowercase the extension string to allow case-insensitive
// comparisons.
//
_wcslwr( strExtension.QueryStr() );
//
// Try to find a matching script map entry
//
META_SCRIPT_MAP * pScriptMap; META_SCRIPT_MAP_ENTRY * pScriptMapEntry;
pScriptMap = _pMetaData->QueryScriptMap(); DBG_ASSERT( pScriptMap ); if( pScriptMap->FindEntry( strExtension, &pScriptMapEntry ) ) { DBG_ASSERT( pScriptMapEntry );
_pScriptMapEntry = pScriptMapEntry;
if( pszPathInfoIter ) { hr = pstrPathInfo->Copy( pszPathInfoIter ); if( FAILED(hr) ) { goto failure; }
//
// Make sure that we truncate the URL so that we don't end
// up downloading a source file by mistake.
//
DBG_REQUIRE( pstrProcessedUrl->SetLen( DIFF(pszPathInfoIter - pstrProcessedUrl->QueryStr()) ) ); } else { pstrPathInfo->Reset(); }
//
// Since we found the entry, we are done
//
break; }
//
// No matching script map, so check if this is a wellknown
// Gateway type.
//
DBG_ASSERT( pScriptMapEntry == NULL ); DBG_ASSERT( _pScriptMapEntry == NULL );
//
// Avoid all the string comps if we will never match.
//
if( strExtension.QueryCCH() == 4 ) { GATEWAY_TYPE Gateway; Gateway = GATEWAY_UNKNOWN; //
// Test extension against known gateway extensions.
//
// Does it make sense to allow the known extensions to be
// configured?
//
if( wcscmp( L".dll", strExtension.QueryStr() ) == 0 || wcscmp( L".isa", strExtension.QueryStr() ) == 0 ) { Gateway = GATEWAY_ISAPI; } else if( !wcscmp( L".exe", strExtension.QueryStr() ) || !wcscmp( L".cgi", strExtension.QueryStr() ) || !wcscmp( L".com", strExtension.QueryStr() ) ) { Gateway = GATEWAY_CGI; } else if( wcscmp( L".map", strExtension.QueryStr() ) == 0 ) { Gateway = GATEWAY_MAP; } //
// OK. Before we continue, if the request was a GATEWAY_ISAPI
// or GATEWAY_CGI and we do NOT have EXECUTE permissions, then
// this really isn't a ISA/CGI after all
//
if ( Gateway == GATEWAY_CGI || Gateway == GATEWAY_ISAPI ) { if ( !( _pMetaData->QueryAccessPerms() & VROOT_MASK_EXECUTE ) ) { Gateway = GATEWAY_UNKNOWN; } } //
// The gateway is specified in the URL and we recognize it.
//
if( Gateway != GATEWAY_UNKNOWN ) { _Gateway = Gateway;
//
// Save everthing after the matching extension as
// path-info and truncate the URL so that it doesn't
// include the path info
//
if( pszPathInfoIter ) {
hr = pstrPathInfo->Copy( pszPathInfoIter ); if( FAILED(hr) ) { goto failure; }
DBG_REQUIRE( pstrProcessedUrl->SetLen( DIFF(pszPathInfoIter - pstrProcessedUrl->QueryStr()) ) ); } else { pstrPathInfo->Reset(); } //
// We have a match so exit the loop
//
break; } } //
// We do not have a matching entry, so continue to look for an
// executable extension.
//
pszExtensionIter++; } // while (pszExtensionIter)
//
// Now associate the ContentType for this entry
//
if (_pScriptMapEntry == NULL && _Gateway != GATEWAY_ISAPI && _Gateway != GATEWAY_CGI) { if (FAILED(hr = SelectMimeMappingForFileExt(pstrProcessedUrl->QueryStr(), _pMetaData->QueryMimeMap(), &_strContentType, &_fDefaultMimeMap))) { goto failure; } } return S_OK;
failure:
DBG_ASSERT( FAILED(hr) ); return FAILED(hr) ? hr : E_FAIL; }
HRESULT W3_URL_INFO_CACHE::Initialize( VOID ) /*++
Description:
Initialize URI cache
Arguments:
None
Return:
HRESULT
--*/ { HRESULT hr; DWORD dwData; DWORD dwType; DWORD cbData = sizeof( DWORD ); DWORD csecTTL = DEFAULT_W3_URL_INFO_CACHE_TTL; HKEY hKey;
//
// What is the TTL for the URI cache
//
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\inetinfo\\Parameters", 0, KEY_READ, &hKey ) == ERROR_SUCCESS ) { DBG_ASSERT( hKey != NULL ); if ( RegQueryValueEx( hKey, L"ObjectCacheTTL", NULL, &dwType, (LPBYTE) &dwData, &cbData ) == ERROR_SUCCESS && dwType == REG_DWORD ) { csecTTL = dwData; } RegCloseKey( hKey ); } //
// We'll use TTL for scavenge period, and expect two inactive periods to
// flush
//
hr = SetCacheConfiguration( csecTTL * 1000, csecTTL * 1000, CACHE_INVALIDATION_METADATA, NULL ); if ( FAILED( hr ) ) { return hr; }
return W3_URL_INFO::Initialize(); }
HRESULT W3_URL_INFO_CACHE::GetUrlInfo( W3_CONTEXT * pW3Context, STRU & strUrl, W3_URL_INFO ** ppUrlInfo ) /*++
Routine Description:
Retrieve a W3_URL_INFO, creating it if necessary
Arguments:
pW3Context - W3 context strUrl - Url to lookup ppUrlInfo - Filled with cache entry if successful Return Value:
HRESULT
--*/ { W3_URL_INFO_KEY uriKey; W3_URL_INFO * pUrlInfo; STACK_STRU( strKey, MAX_PATH ); STRU * pstrMBRoot; HRESULT hr; if ( pW3Context == NULL || ppUrlInfo == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *ppUrlInfo = NULL; //
// The key is the full metadata path. Start with the site prefix minus
// the trailing '/'
//
pstrMBRoot = pW3Context->QuerySite()->QueryMBRoot(); DBG_ASSERT( pstrMBRoot != NULL ); hr = strKey.Copy( pstrMBRoot->QueryStr(), pstrMBRoot->QueryCCH() - 1 ); if ( FAILED( hr ) ) { return hr; } //
// Now add the URL (note that some applications depend on
// case-sensitivity in the URL.)
//
hr = strKey.Append( strUrl ); if ( FAILED( hr ) ) { return hr; } //
// Setup a key to lookup
//
hr = uriKey.CreateCacheKey( strKey.QueryStr(), strKey.QueryCCH(), pW3Context->QuerySite()->QueryMBRoot()->QueryCCH(), FALSE ); if ( FAILED( hr ) ) { return hr; } //
// Look it up
//
hr = FindCacheEntry( &uriKey, (CACHE_ENTRY**) &pUrlInfo ); if ( SUCCEEDED( hr ) ) { DBG_ASSERT( pUrlInfo != NULL ); *ppUrlInfo = pUrlInfo; return NO_ERROR; } //
// We need to create a URI cache entry
//
hr = CreateNewUrlInfo( pW3Context, strUrl, strKey, &pUrlInfo ); if ( FAILED( hr ) ) { return hr; } DBG_ASSERT( pUrlInfo != NULL ); //
// Add to the cache
//
AddCacheEntry( pUrlInfo ); *ppUrlInfo = pUrlInfo; return NO_ERROR; }
HRESULT W3_URL_INFO_CACHE::CreateNewUrlInfo( W3_CONTEXT * pW3Context, STRU & strUrl, STRU & strMetadataPath, W3_URL_INFO ** ppUrlInfo ) /*++
Routine Description:
Create a new URI cache entry
Arguments:
pW3Context - Main context strUrl - Url strMetadataPath - Full metadata path used as key ppUrlInfo - Set to URI cache entry Return Value:
HRESULT
--*/ { W3_METADATA * pMetaData = NULL; W3_URL_INFO * pUrlInfo = NULL; HRESULT hr; if ( pW3Context == NULL || ppUrlInfo == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *ppUrlInfo = NULL; //
// Find a metacache entry
//
DBG_ASSERT( g_pW3Server->QueryMetaCache() != NULL ); hr = g_pW3Server->QueryMetaCache()->GetMetaData( pW3Context, strUrl, &pMetaData ); if ( FAILED( hr ) ) { //
// Check for RPC disconnected error. If that's what happened,
// mark unhealthy.
//
// Oh yeah, make sure we only log one message
//
if ( hr == RPC_E_DISCONNECTED || hr == HRESULT_FROM_WIN32( RPC_S_SERVER_UNAVAILABLE ) || hr == HRESULT_FROM_WIN32( RPC_S_CALL_FAILED ) || hr == HRESULT_FROM_WIN32( RPC_S_CALL_FAILED_DNE ) ) { if ( InterlockedExchange( (LPLONG) &_fMarkedUnhealthy, TRUE ) == FALSE ) { g_pW3Server->LogEvent( W3_EVENT_NO_METABASE, 0, NULL, 0 ); UlAtqSetUnhealthy(); } } return hr; } DBG_ASSERT( pMetaData != NULL );
//
// Create a W3_URL_INFO
//
pUrlInfo = new W3_URL_INFO( this, pMetaData ); if ( pUrlInfo == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); pMetaData->DereferenceCacheEntry(); return hr; }
hr = pUrlInfo->Create( strUrl, strMetadataPath ); if ( FAILED( hr ) ) { pUrlInfo->DereferenceCacheEntry(); return hr; }
*ppUrlInfo = pUrlInfo; return NO_ERROR; }
|