windows-nt/Source/XPSP1/NT/inetsrv/iis/iisrearc/iisplus/ulw3/staticfile.cxx
2020-09-26 16:20:57 +08:00

1347 lines
34 KiB
C++

/*++
Copyright (c) 1999 Microsoft Corporation
Module Name :
staticfile.cxx
Abstract:
Handle static file request
Author:
Bilal Alam (balam) 7-Jan-2000
Environment:
Win32 - User Mode
Project:
ULW3.DLL
--*/
#include "precomp.hxx"
#include "staticfile.hxx"
ALLOC_CACHE_HANDLER * W3_STATIC_FILE_HANDLER::sm_pachStaticFileHandlers;
HRESULT
W3_STATIC_FILE_HANDLER::HandleDefaultLoad(
W3_CONTEXT * pW3Context,
BOOL * pfHandled,
BOOL * pfAsyncPending
)
/*++
Routine Description:
Attempts to find a default load file applicable for this request. If it
does, it will switch the URL of the request and back track.
Arguments:
pW3Context - Context
pfHandled - Set to TRUE if this function has set a response or switched URL
(in other words, no more processing is required)
pfAsyncPending - Set to TRUE if async is pending so bail
Return Value:
HRESULT - If not NO_ERROR, then *pfHandled is irrelevent
--*/
{
URL_CONTEXT * pUrlContext;
W3_METADATA * pMetaData;
STACK_STRU( strDefaultFiles, MAX_PATH );
HRESULT hr = NO_ERROR;
W3_REQUEST * pRequest = pW3Context->QueryRequest();
STRU * pstrPhysical;
STACK_STRU( strNextFile, MAX_PATH );
WCHAR * pszNextFile;
WCHAR * pszEndFile;
W3_FILE_INFO * pOpenFile = NULL;
BOOL fFound = FALSE;
STACK_STRU( strNewUrl, MAX_PATH );
WCHAR * pszQuery;
CONTEXT_STATUS status;
FILE_CACHE_USER FileUser;
DBG_ASSERT( pW3Context != NULL );
DBG_ASSERT( pRequest != NULL );
DBG_ASSERT( pfHandled != NULL );
DBG_ASSERT( pfAsyncPending != NULL );
*pfHandled = FALSE;
*pfAsyncPending = FALSE;
//
// Get the configuration info
//
pUrlContext = pW3Context->QueryUrlContext();
DBG_ASSERT( pUrlContext != NULL );
pMetaData = pUrlContext->QueryMetaData();
DBG_ASSERT( pMetaData != NULL );
pstrPhysical = pUrlContext->QueryPhysicalPath();
DBG_ASSERT( pstrPhysical != NULL );
//
// First ensure the path is / suffixed. Otherwise, redirect to such
//
if (pstrPhysical->QueryStr()[pstrPhysical->QueryCCH() - 1] != L'\\')
{
//
// Before redirecting, first make sure it is a GET or a HEAD
//
HTTP_VERB VerbType = pRequest->QueryVerbType();
if ( VerbType != HttpVerbGET &&
VerbType != HttpVerbHEAD )
{
pW3Context->QueryResponse()->SetStatus( HttpStatusMethodNotAllowed );
hr = pW3Context->SetupAllowHeader();
if ( FAILED( hr ) )
{
return hr;
}
return S_OK;
}
STACK_STRU (strRedirect, MAX_PATH );
//
// Append the suffix '/'
//
if (FAILED(hr = pRequest->GetUrl(&strRedirect)) ||
FAILED(hr = strRedirect.Append(L"/")))
{
return hr;
}
//
// Do the HTTP redirect
//
if (FAILED(hr = pW3Context->SetupHttpRedirect(strRedirect,
TRUE,
HttpStatusMovedPermanently)))
{
return hr;
}
//
// Tell callers we are finished
//
*pfHandled = TRUE;
return S_OK;
}
//
// Look for default load files
//
hr = strDefaultFiles.Copy( *pMetaData->QueryDefaultLoadFiles() );
if ( FAILED( hr ) )
{
return hr;
}
pszNextFile = strDefaultFiles.QueryStr();
while ( pszNextFile != NULL &&
*pszNextFile != L'\0' )
{
pszEndFile = wcschr( pszNextFile, L',' );
if ( pszEndFile != NULL )
{
*pszEndFile = L'\0';
}
//
// Append portion to directory to create a filename to check for
//
hr = strNextFile.Copy( *pstrPhysical );
if ( FAILED( hr ) )
{
return hr;
}
//
// Remove any query string
//
pszQuery = wcschr( pszNextFile, L'?' );
if ( pszQuery != NULL )
{
hr = strNextFile.Append( pszNextFile,
DIFF( pszQuery - pszNextFile ) );
}
else
{
hr = strNextFile.Append( pszNextFile );
}
if ( FAILED( hr ) )
{
return hr;
}
//
// Make a FS path
//
FlipSlashes( strNextFile.QueryStr() );
//
// Open the file
//
pW3Context->QueryFileCacheUser( &FileUser );
DBG_ASSERT( g_pW3Server->QueryFileCache() != NULL );
hr = g_pW3Server->QueryFileCache()->GetFileInfo(
strNextFile,
pMetaData->QueryDirmonConfig(),
&FileUser,
!( pMetaData->QueryNoCache() ),
&pOpenFile );
if ( FAILED( hr ) )
{
DWORD dwError = WIN32_FROM_HRESULT( hr );
DBG_ASSERT( pOpenFile == NULL );
//
// If not found, or name invalid, that's ok -> proceed to next file
//
if ( dwError != ERROR_FILE_NOT_FOUND &&
dwError != ERROR_PATH_NOT_FOUND &&
dwError != ERROR_INVALID_NAME )
{
return hr;
}
hr = NO_ERROR;
}
else
{
DWORD dwAttributes;
//
// Great, we can open the file. We only need it for attributes.
//
DBG_ASSERT( pOpenFile != NULL );
dwAttributes = pOpenFile->QueryAttributes();
pOpenFile->DereferenceCacheEntry();
if ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY )
{
//
// For legacy, if we see a directory, our default load file
// search is over, and we act like we never found one
//
return NO_ERROR;
}
fFound = TRUE;
break;
}
//
// Goto next file
//
pszNextFile = pszEndFile ? pszEndFile + 1 : NULL;
}
//
// Change the url and retrack
//
if ( fFound )
{
//
// Ok. We can change the URL and retrack. Do so.
//
hr = pW3Context->QueryRequest()->GetUrl( &strNewUrl );
if ( FAILED( hr ) )
{
return hr;
}
hr = strNewUrl.Append( pszNextFile );
if ( FAILED( hr ) )
{
return hr;
}
//
// Change the URL
//
hr = pW3Context->QueryRequest()->SetUrl( strNewUrl, FALSE );
if ( FAILED( hr ) )
{
return hr;
}
hr = pW3Context->ExecuteChildRequest( pW3Context->QueryRequest(),
FALSE,
W3_FLAG_ASYNC );
if ( FAILED( hr ) )
{
return hr;
}
else
{
*pfHandled = TRUE;
*pfAsyncPending = TRUE;
return NO_ERROR;
}
}
else
{
//
// If not found, the caller will continue since *pfHandled == FALSE
// if we're here
//
}
return NO_ERROR;
}
HRESULT
W3_STATIC_FILE_HANDLER::DirectoryDoWork(
W3_CONTEXT * pW3Context,
BOOL * pfAsyncPending
)
/*++
Routine Description:
Handle directories. This means default loads and directory listings
Arguments:
pW3Context - Context
pfAsyncPending - Set to TRUE if async pending
Return Value:
HRESULT
--*/
{
DWORD dwDirBrowseFlags;
URL_CONTEXT * pUrlContext;
HRESULT hr;
BOOL fHandled = FALSE;
FILE_CACHE_USER fileUser;
BOOL fImpersonated = FALSE;
DBG_ASSERT( pW3Context != NULL );
DBG_ASSERT( pfAsyncPending != NULL );
*pfAsyncPending = FALSE;
W3_REQUEST *pRequest = pW3Context->QueryRequest();
DBG_ASSERT(pRequest != NULL);
W3_RESPONSE *pResponse = pW3Context->QueryResponse();
DBG_ASSERT(pResponse != NULL);
pUrlContext = pW3Context->QueryUrlContext();
DBG_ASSERT( pUrlContext != NULL );
STRU *pstrPhysical = pUrlContext->QueryPhysicalPath();
DBG_ASSERT( pstrPhysical != NULL );
//
// Get the directory browsing flags for this directory
//
dwDirBrowseFlags = pUrlContext->QueryMetaData()->QueryDirBrowseFlags();
//
// First check for a default load (by first checking whether we are
// allowed to serve default load)
//
if ( dwDirBrowseFlags & MD_DIRBROW_LOADDEFAULT )
{
//
// OK. Look for a default load
//
hr = HandleDefaultLoad( pW3Context,
&fHandled,
pfAsyncPending );
if ( FAILED( hr ) || fHandled || *pfAsyncPending )
{
return hr;
}
}
//
// If doing directory listing, first make sure it is a GET or a HEAD
//
HTTP_VERB VerbType = pRequest->QueryVerbType();
if ( VerbType != HttpVerbGET &&
VerbType != HttpVerbHEAD )
{
pW3Context->QueryResponse()->SetStatus( HttpStatusMethodNotAllowed );
hr = pW3Context->SetupAllowHeader();
if ( FAILED( hr ) )
{
return hr;
}
return S_OK;
}
//
// OK. Check for whether directory listings are enabled
//
if ( dwDirBrowseFlags & MD_DIRBROW_ENABLED )
{
//
// We may need to impersonate some other user to open the file
//
pW3Context->QueryFileCacheUser( &fileUser );
if ( fileUser._hToken != NULL )
{
if ( !SetThreadToken( NULL, fileUser._hToken ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
fImpersonated = TRUE;
}
hr = HandleDirectoryListing( pW3Context,
&fHandled );
if( fImpersonated )
{
RevertToSelf();
fImpersonated = FALSE;
}
if ( FAILED( hr ) || fHandled )
{
return hr;
}
}
//
// If we are here, then neither browsing nor default loads are enabled.
// There is nothing we can do but return a 403.
//
pW3Context->QueryResponse()->SetStatus( HttpStatusForbidden,
Http403DirBrowsingDenied );
return NO_ERROR;
}
HRESULT
GetTypeAndSubType(
CHAR * pszType,
STRA * pstrMainType,
STRA * pstrSubType,
BOOL * pfTypeOk
)
/*++
Routine Description:
Given a mimetype of "foobar/barfoo", return "foobar" as the main type
and "barfoo" as the subtype.
Arguments:
pszType - Whole mime type
pstrMainType - Filled with main type
pstrSubType - Filled with sub type
pfTypeOk - Is this mime type ok?
Return Value:
HRESULT
--*/
{
HRESULT hr;
CHAR * pszSlash = strchr( pszType, '/' );
if (pszSlash == NULL)
{
*pfTypeOk = FALSE;
return S_OK;
}
hr = pstrMainType->Copy( pszType,
DIFF( pszSlash - pszType ) );
hr = pstrSubType->Copy( pszSlash + 1 );
*pfTypeOk = TRUE;
return hr;
}
HRESULT
IsAcceptable(
CHAR * pszContentType,
CHAR * pszAcceptHeader,
BOOL * pfIsAcceptAble
)
/*++
Routine Description:
Return whether given content type is acceptable for the given
Accept: header
Arguments:
pszContentType - Content type
pszAcceptHeader - Accept header to check
pfIsAcceptAble - Filled with bool indicating whether type is acceptable
Return Value:
HRESULT
--*/
{
HRESULT hr;
BOOL fTypeOk;
//
// Quickly handle the */* case
//
if ( pszAcceptHeader[ 0 ] == '*' &&
pszAcceptHeader[ 1 ] == '/' &&
pszAcceptHeader[ 2 ] == '*' &&
pszAcceptHeader[ 3 ] == '\0' )
{
*pfIsAcceptAble = TRUE;
return S_OK;
}
//
// Break the Content-Type into the main- and sub-content-type
//
STACK_STRA ( strMainContentType, 32);
STACK_STRA ( strSubContentType, 32);
if ( FAILED( hr = GetTypeAndSubType( pszContentType,
&strMainContentType,
&strSubContentType,
&fTypeOk ) ) )
{
return hr;
}
if ( !fTypeOk )
{
*pfIsAcceptAble = FALSE;
return S_OK;
}
//
// Skip over any spaces
//
while ( *pszAcceptHeader == ' ' )
{
pszAcceptHeader++;
}
STACK_STRA (strAcceptType, 64);
STACK_STRA (strMainAcceptType, 32);
STACK_STRA (strSubAcceptType, 32);
while (TRUE)
{
//
// Multiple Acceptable Types are ',' separated, get the next one
//
CHAR * pszComma = strchr( pszAcceptHeader, L',' );
if ( pszComma == NULL )
{
if ( FAILED( hr = strAcceptType.Copy( pszAcceptHeader ) ) )
{
return hr;
}
}
else
{
if ( FAILED( hr = strAcceptType.Copy( pszAcceptHeader,
DIFF( pszComma - pszAcceptHeader ) ) ) )
{
return hr;
}
}
//
// Trim out any quality specifier specified after a ';'
//
CHAR * pszQuality = strchr( strAcceptType.QueryStr(), ';' );
if ( pszQuality != NULL )
{
strAcceptType.SetLen(DIFF(pszQuality - strAcceptType.QueryStr()));
}
//
// Trim any spaces at the end
//
INT iSpace = strAcceptType.QueryCCH() - 1;
while ( iSpace >= 0 &&
strAcceptType.QueryStr()[iSpace] == ' ' )
{
iSpace--;
}
strAcceptType.SetLen( iSpace + 1 );
//
// Just check if this Type is */*
//
if ( !strcmp( strAcceptType.QueryStr(), "*/*" ) )
{
*pfIsAcceptAble = TRUE;
return S_OK;
}
//
// Get the main- and sub-Accept types for this type
//
if ( FAILED(hr = GetTypeAndSubType( strAcceptType.QueryStr(),
&strMainAcceptType,
&strSubAcceptType,
&fTypeOk ) ) )
{
return hr;
}
if ( !fTypeOk )
{
*pfIsAcceptAble = TRUE;
return S_OK;
}
//
// Now actually find out if this type is acceptable
//
if ( !_stricmp( strMainAcceptType.QueryStr(),
strMainContentType.QueryStr() ) )
{
if ( !strcmp( strSubAcceptType.QueryStr(), "*" ) ||
!_stricmp( strSubAcceptType.QueryStr(),
strSubContentType.QueryStr() ) )
{
*pfIsAcceptAble = TRUE;
return S_OK;
}
}
//
// Set AcceptHeader to the start of the next type
//
if (pszComma == NULL)
{
*pfIsAcceptAble = FALSE;
return S_OK;
}
pszAcceptHeader = pszComma + 1;
while ( *pszAcceptHeader == ' ' )
{
pszAcceptHeader++;
}
}
}
HRESULT
W3_STATIC_FILE_HANDLER::FileDoWork(
W3_CONTEXT * pW3Context,
W3_FILE_INFO * pOpenFile
)
/*++
Routine Description:
Handle files (non-directories).
Arguments:
pW3Context - Context
pOpenFile - W3_FILE_INFO with the file to send
Return Value:
HRESULT
--*/
{
LARGE_INTEGER liFileSize;
W3_RESPONSE * pResponse;
W3_REQUEST * pRequest;
W3_URL_INFO * pUrlInfo;
W3_METADATA * pMetaData;
BOOL fRet;
HRESULT hr;
STACK_STRU ( strUrl, MAX_PATH );
CHAR * pszRange;
BOOL fHandled = FALSE;
FILE_CACHE_USER fileUser;
DBG_ASSERT( pW3Context != NULL );
DBG_ASSERT( pOpenFile != NULL );
pResponse = pW3Context->QueryResponse();
DBG_ASSERT( pResponse != NULL );
pRequest = pW3Context->QueryRequest();
DBG_ASSERT( pRequest != NULL );
pUrlInfo = pW3Context->QueryUrlContext()->QueryUrlInfo();
DBG_ASSERT( pUrlInfo != NULL );
pMetaData = pW3Context->QueryUrlContext()->QueryMetaData();
DBG_ASSERT( pMetaData != NULL );
//
// First make sure it a GET or a HEAD
//
HTTP_VERB VerbType = pRequest->QueryVerbType();
if ( VerbType != HttpVerbGET &&
VerbType != HttpVerbHEAD )
{
pW3Context->QueryResponse()->SetStatus( HttpStatusMethodNotAllowed );
hr = pW3Context->SetupAllowHeader();
if ( FAILED( hr ) )
{
return hr;
}
return S_OK;
}
//
// If this an image-map file, do the image-map stuff
//
if (pUrlInfo->QueryGateway() == GATEWAY_MAP)
{
fHandled = FALSE;
hr = MapFileDoWork(pW3Context, pOpenFile, &fHandled);
if (FAILED(hr) ||
fHandled)
{
return hr;
}
//
// fHandled was false, so this is a .map file which wasn't really
// an image-map file, handle it as any other static file
//
}
//
// Do compression, if so configured
//
if (pMetaData->QueryDoStaticCompression() &&
!pW3Context->QueryDoneWithCompression())
{
//
// If this file is compressible, don't let UL store the uncompressed
// version in its cache
//
pW3Context->DisableUlCache();
if (FAILED(hr = HTTP_COMPRESSION::DoStaticFileCompression(
pW3Context, &pOpenFile)))
{
return hr;
}
m_pOpenFile = pOpenFile;
}
//
// First see if the Content-Type is acceptable to the client
//
STRA *pstrContentType = pUrlInfo->QueryContentType();
CHAR * pszAccept = pRequest->GetHeader( HttpHeaderAccept );
if ( pszAccept != NULL && *pszAccept != L'\0' )
{
BOOL fIsAcceptAble;
if ( FAILED( hr = IsAcceptable( pstrContentType->QueryStr(),
pszAccept,
&fIsAcceptAble ) ) )
{
return hr;
}
if ( !fIsAcceptAble )
{
pResponse->ClearHeaders();
pResponse->SetStatus( HttpStatusNotAcceptable );
return S_OK;
}
}
//
// Setup the response headers. First ETag
//
hr = pResponse->SetHeaderByReference( HttpHeaderEtag,
pOpenFile->QueryETag(),
pOpenFile->QueryETagSize() );
if ( FAILED( hr ) )
{
goto Failure;
}
//
// Next is Last-Modified
//
hr = pResponse->SetHeaderByReference( HttpHeaderLastModified,
pOpenFile->QueryLastModifiedString(),
GMT_STRING_SIZE - 1 );
if ( FAILED( hr ) )
{
goto Failure;
}
//
// Next is Content-Location. We only need to send this header if
// we have internally changed the URL of the request. In other words,
// if this is a child execute
//
if ( pW3Context->QuerySendLocation() )
{
STACK_STRA (strContentLocation, MAX_PATH);
STACK_STRA (strRawUrl, MAX_PATH);
if (FAILED(hr = pRequest->GetRawUrl(&strRawUrl)) ||
FAILED(hr = pRequest->BuildFullUrl(strRawUrl,
&strContentLocation,
FALSE)) ||
FAILED(hr = pResponse->SetHeader(HttpHeaderContentLocation,
strContentLocation.QueryStr(),
strContentLocation.QueryCCH())))
{
return hr;
}
}
//
// Next is Accept-Ranges
//
if ( FAILED( hr = pResponse->SetHeaderByReference( HttpHeaderAcceptRanges,
"bytes", 5 ) ) )
{
goto Failure;
}
//
// Handle the If-* (except If-Range) headers if present
//
fHandled = FALSE;
if ( FAILED( hr = CacheValidationDoWork( pW3Context,
pOpenFile,
&fHandled ) ) )
{
goto Failure;
}
if ( fHandled )
{
return hr;
}
//
// Now handle If-Range and Range headers
//
pszRange = pRequest->GetHeader( HttpHeaderRange );
if ( ( pszRange != NULL ) &&
( !_strnicmp ( pszRange, "bytes", 5 ) ) )
{
//
// Handle range request
//
fHandled = FALSE;
if ( FAILED( hr = RangeDoWork( pW3Context, pOpenFile, &fHandled ) ) )
{
goto Failure;
}
if ( fHandled )
{
return hr;
}
}
//
// If we fell thru, then we are sending out the entire file
//
//
// Setup Content-Type
//
if ( FAILED( hr = pResponse->SetHeaderByReference(
HttpHeaderContentType,
pstrContentType->QueryStr(),
pstrContentType->QueryCCH() ) ) )
{
goto Failure;
}
//
// Setup the response chunks
//
pOpenFile->QuerySize( &liFileSize );
if (liFileSize.QuadPart > 0)
{
if ( pOpenFile->QueryFileBuffer() != NULL &&
liFileSize.HighPart == 0 )
{
hr = pResponse->AddMemoryChunkByReference(
pOpenFile->QueryFileBuffer(),
liFileSize.LowPart );
}
else
{
hr = pResponse->AddFileHandleChunk( pOpenFile->QueryFileHandle(),
0,
liFileSize.QuadPart );
}
if ( FAILED( hr ) )
{
goto Failure;
}
}
// perf ctr
pW3Context->QuerySite()->IncFilesSent();
// Setup the document footer
if (pMetaData->QueryIsFooterEnabled())
{
if (!pMetaData->QueryFooterString()->IsEmpty() )
{
STRA *pFooterString = pMetaData->QueryFooterString();
if (pFooterString->QueryCCH())
{
if (FAILED(hr = pResponse->AddMemoryChunkByReference(
pFooterString->QueryStr(),
pFooterString->QueryCCH())))
{
goto Failure;
}
}
}
else if (!pMetaData->QueryFooterDocument()->IsEmpty() )
{
DBG_ASSERT( m_pFooterDocument == NULL );
DBG_ASSERT( g_pW3Server->QueryFileCache() );
hr = g_pW3Server->QueryFileCache()->GetFileInfo(
*(pMetaData->QueryFooterDocument()),
NULL,
&fileUser,
TRUE,
&m_pFooterDocument );
if ( SUCCEEDED( hr ) )
{
DBG_ASSERT( m_pFooterDocument != NULL );
m_pFooterDocument->QuerySize( &liFileSize );
if (liFileSize.QuadPart > 0)
{
if ( m_pFooterDocument->QueryFileBuffer() != NULL &&
liFileSize.HighPart == 0 )
{
hr = pResponse->AddMemoryChunkByReference(
m_pFooterDocument->QueryFileBuffer(),
liFileSize.LowPart );
}
else
{
hr = pResponse->AddFileHandleChunk(
m_pFooterDocument->QueryFileHandle(),
0,
liFileSize.QuadPart );
}
if ( FAILED( hr ) )
{
goto Failure;
}
}
}
else
{
//
// Could not open the footer document. Sub in a error string
//
CHAR achErrorString[ 512 ];
DWORD cbErrorString = sizeof( achErrorString );
hr = g_pW3Server->LoadString( IDS_ERROR_FOOTER,
achErrorString,
&cbErrorString );
if ( FAILED( hr ) )
{
goto Failure;
}
hr = m_strFooterString.Copy( achErrorString, cbErrorString );
if ( FAILED( hr ) )
{
goto Failure;
}
hr = pResponse->AddMemoryChunkByReference(
m_strFooterString.QueryStr(),
m_strFooterString.QueryCCH() );
if ( FAILED( hr ) )
{
goto Failure;
}
}
}
}
return S_OK;
Failure:
//
// It is our responsibility to ensure that there is no incomplete response
//
pResponse->Clear();
return hr;
}
CONTEXT_STATUS
W3_STATIC_FILE_HANDLER::DoWork(
VOID
)
/*++
Routine Description:
Execute the static file handler
Return Value:
CONTEXT_STATUS_PENDING or CONTEXT_STATUS_CONTINUE
--*/
{
W3_CONTEXT *pW3Context = QueryW3Context();
DBG_ASSERT( pW3Context != NULL );
HRESULT hr = NO_ERROR;
W3_RESPONSE * pResponse = pW3Context->QueryResponse();
W3_REQUEST * pRequest = pW3Context->QueryRequest();
W3_METADATA * pMetaData;
URL_CONTEXT * pUrlContext;
W3_FILE_INFO * pOpenFile = NULL;
BOOL fRet;
DWORD dwFilePerms;
BOOL fAccess;
BOOL fAsyncPending = FALSE;
FILE_CACHE_USER fileUser;
//
// Get the metadata, in particular the cached W3_URL_INFO off which we
// we attempt to open the file
//
pUrlContext = pW3Context->QueryUrlContext();
DBG_ASSERT( pUrlContext != NULL );
pMetaData = pUrlContext->QueryMetaData();
DBG_ASSERT( pMetaData != NULL );
//
// Check web permissions.
// Will fail, if no VROOT_MASK_READ, or if we forbid remote access and
// the request is remote
//
dwFilePerms = pMetaData->QueryAccessPerms();
if ( !IS_ACCESS_ALLOWED(pRequest, dwFilePerms, READ) )
{
pResponse->SetStatus( HttpStatusForbidden,
Http403ReadAccessDenied );
goto Failure;
}
//
// Now try to open the file
//
pW3Context->QueryFileCacheUser( &fileUser );
hr = pUrlContext->OpenFile( &fileUser, &pOpenFile );
if (FAILED(hr))
{
DWORD dwError;
IF_DEBUG( STATICFILE )
{
DBGPRINTF(( DBG_CONTEXT,
"Error opening file %ws. hr = %x\n",
pUrlContext->QueryPhysicalPath()->QueryStr(),
hr ));
}
dwError = WIN32_FROM_HRESULT( hr );
switch( dwError )
{
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_NAME:
hr = NO_ERROR;
pResponse->SetStatus( HttpStatusNotFound );
break;
case ERROR_LOGON_FAILURE:
case ERROR_ACCOUNT_DISABLED:
case ERROR_ACCESS_DENIED:
hr = NO_ERROR;
pResponse->SetStatus( HttpStatusUnauthorized,
Http401Resource );
break;
case ERROR_INSUFFICIENT_BUFFER:
hr = NO_ERROR;
pResponse->SetStatus( HttpStatusUrlTooLong );
break;
}
goto Failure;
}
DBG_ASSERT( pOpenFile != NULL );
//
// Is the file hidden? If so, don't serve it out for legacy reasons
//
if ( pOpenFile->QueryAttributes() & FILE_ATTRIBUTE_HIDDEN )
{
pOpenFile->DereferenceCacheEntry();
pResponse->SetStatus( HttpStatusNotFound );
goto Failure;
}
//
// Is this a file or directory?
//
if ( pOpenFile->QueryAttributes() & FILE_ATTRIBUTE_DIRECTORY )
{
//
// At this point, we will do one of the following:
// a) Send a directory listing
// b) Send a default load file
// c) Send a 302 (to redirect to a slash suffixed URL)
// d) Send a 403 (forbidden)
//
pOpenFile->DereferenceCacheEntry();
pOpenFile = NULL;
hr = DirectoryDoWork( pW3Context,
&fAsyncPending );
if ( fAsyncPending )
{
return CONTEXT_STATUS_PENDING;
}
//
// If access denied, then send the response now
//
if ( WIN32_FROM_HRESULT( hr ) == ERROR_ACCESS_DENIED )
{
pW3Context->SetErrorStatus( hr );
pW3Context->QueryResponse()->SetStatus( HttpStatusUnauthorized,
Http401Resource );
hr = NO_ERROR;
}
}
else
{
//
// This is just a regular file. Serve it out
//
//
// Save away the file now. We will clean it up at the end of the
// request when this current context is cleaned up
//
m_pOpenFile = pOpenFile;
hr = FileDoWork( pW3Context,
pOpenFile );
}
//
// If there was an error here, then generate a 500. If successful, it
// is assumed that the response status is already set
//
Failure:
if ( FAILED( hr ) )
{
pResponse->Clear();
pW3Context->SetErrorStatus( hr );
pResponse->SetStatus( HttpStatusServerError );
}
hr = pW3Context->SendResponse( W3_FLAG_ASYNC );
if ( FAILED( hr ) )
{
pW3Context->SetErrorStatus( hr );
return CONTEXT_STATUS_CONTINUE;
}
return CONTEXT_STATUS_PENDING;
}
HRESULT
W3_STATIC_FILE_HANDLER::SetupUlCachedResponse(
W3_CONTEXT * pW3Context
)
/*++
Routine Description:
Setup a response to be cached by UL. In this case we will muck with
the cached file object to
a) Remove its TTL
b) Associate the current request's URL with the file object so that when
the file object goes away, we will be called with enough info to
flush the appropriate UL cache entry
Arguments:
pW3Context - Context
Return Value:
HRESULT
--*/
{
STACK_STRU( strFlushUrl, MAX_PATH );
STACK_STRU( strPhysicalPath, MAX_PATH );
TOKEN_CACHE_ENTRY * pToken;
HRESULT hr;
FILE_CACHE_USER fileUser;
W3_METADATA * pMetaData;
if ( pW3Context == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
if ( m_pOpenFile == NULL )
{
return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
}
//
// If the file wasn't cached, then don't use UL cache
//
if ( m_pOpenFile->QueryCached() == FALSE )
{
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
}
//
// If this file was not accessed anonymously, then we need to do access
// check anonymously before putting into cache
//
if ( pW3Context->QueryUserContext()->QueryAuthType() != MD_AUTH_ANONYMOUS )
{
pMetaData = pW3Context->QueryUrlContext()->QueryMetaData();
DBG_ASSERT( pMetaData != NULL );
pToken = pMetaData->QueryVrAccessToken();
if ( pToken == NULL )
{
pToken = pMetaData->QueryAnonymousToken();
}
if ( pToken == NULL )
{
return HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
}
fileUser._hToken = pToken->QueryImpersonationToken();
fileUser._pSid = pToken->QuerySid();
hr = m_pOpenFile->DoAccessCheck( &fileUser );
if ( FAILED( hr ) )
{
return hr;
}
}
//
// Get the exact URL used to flush UL cache
//
hr = pW3Context->QueryMainContext()->QueryRequest()->GetOriginalFullUrl(
&strFlushUrl );
if ( FAILED( hr ) )
{
return hr;
}
//
// Get the physical path
//
hr = strPhysicalPath.Copy( m_pOpenFile->QueryPhysicalPath() );
if ( FAILED( hr ) )
{
return hr;
}
//
// Setup UL cache response token
//
DBG_ASSERT( g_pW3Server->QueryUlCache() != NULL );
hr = g_pW3Server->QueryUlCache()->SetupUlCachedResponse(
pW3Context,
strFlushUrl,
strPhysicalPath );
return hr;
}