4585 lines
116 KiB
C++
4585 lines
116 KiB
C++
/*++
|
|
|
|
Copyright (c) 1999 Microsoft Corporation
|
|
|
|
Module Name :
|
|
w3context.cxx
|
|
|
|
Abstract:
|
|
Drive the state machine
|
|
|
|
Author:
|
|
Bilal Alam (balam) 10-Jan-2000
|
|
|
|
Environment:
|
|
Win32 - User Mode
|
|
|
|
Project:
|
|
ULW3.DLL
|
|
--*/
|
|
|
|
#include "precomp.hxx"
|
|
#include "anonymousprovider.hxx"
|
|
#include "customprovider.hxx"
|
|
#include "staticfile.hxx"
|
|
#include "isapi_handler.h"
|
|
#include "cgi_handler.h"
|
|
#include "trace_handler.h"
|
|
#include "dav_handler.h"
|
|
#include "options_handler.hxx"
|
|
#include "generalhandler.hxx"
|
|
|
|
//
|
|
// Global context list
|
|
//
|
|
|
|
CHAR W3_CONTEXT::sm_achRedirectMessage[ 512 ];
|
|
DWORD W3_CONTEXT::sm_cbRedirectMessage;
|
|
CHAR * W3_CONTEXT::sm_pszAccessDeniedMessage;
|
|
|
|
ALLOC_CACHE_HANDLER * EXECUTE_CONTEXT::sm_pachExecuteContexts;
|
|
|
|
HRESULT
|
|
EXECUTE_CONTEXT::InitializeFromExecUrlInfo(
|
|
HSE_EXEC_URL_INFO * pExecUrlInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize an execution context from an HSE_EXEC_URL_INFO structure.
|
|
This function does the necessary copying
|
|
|
|
Arguments:
|
|
|
|
pExecUrlInfo - Describes the child request to execute
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr;
|
|
|
|
if ( pExecUrlInfo == NULL )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// Copy entity if (any)
|
|
//
|
|
|
|
if ( pExecUrlInfo->pEntity != NULL )
|
|
{
|
|
_EntityInfo.cbAvailable = pExecUrlInfo->pEntity->cbAvailable;
|
|
_EntityInfo.lpbData = pExecUrlInfo->pEntity->lpbData;
|
|
_ExecUrlInfo.pEntity = &_EntityInfo;
|
|
}
|
|
|
|
//
|
|
// Copy user (if any)
|
|
//
|
|
|
|
if ( pExecUrlInfo->pUserInfo != NULL )
|
|
{
|
|
_UserInfo.hImpersonationToken = pExecUrlInfo->pUserInfo->hImpersonationToken;
|
|
|
|
hr = _HeaderBuffer.AllocateSpace(
|
|
pExecUrlInfo->pUserInfo->pszCustomUserName,
|
|
strlen( pExecUrlInfo->pUserInfo->pszCustomUserName ),
|
|
&( _UserInfo.pszCustomUserName ) );
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
hr = _HeaderBuffer.AllocateSpace(
|
|
pExecUrlInfo->pUserInfo->pszCustomAuthType,
|
|
strlen( pExecUrlInfo->pUserInfo->pszCustomAuthType ),
|
|
&( _UserInfo.pszCustomAuthType ) );
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
_ExecUrlInfo.pUserInfo = &_UserInfo;
|
|
}
|
|
|
|
//
|
|
// Copy URL (if any)
|
|
//
|
|
|
|
if ( pExecUrlInfo->pszUrl != NULL )
|
|
{
|
|
hr = _HeaderBuffer.AllocateSpace(
|
|
pExecUrlInfo->pszUrl,
|
|
strlen( pExecUrlInfo->pszUrl ),
|
|
&( _ExecUrlInfo.pszUrl ) );
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy method (if any)
|
|
//
|
|
|
|
if ( pExecUrlInfo->pszMethod != NULL )
|
|
{
|
|
hr = _HeaderBuffer.AllocateSpace(
|
|
pExecUrlInfo->pszMethod,
|
|
strlen( pExecUrlInfo->pszMethod ),
|
|
&( _ExecUrlInfo.pszMethod ) );
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy child headers
|
|
//
|
|
|
|
if ( pExecUrlInfo->pszChildHeaders != NULL )
|
|
{
|
|
hr = _HeaderBuffer.AllocateSpace(
|
|
pExecUrlInfo->pszChildHeaders,
|
|
strlen( pExecUrlInfo->pszChildHeaders ),
|
|
&( _ExecUrlInfo.pszChildHeaders ) );
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
_ExecUrlInfo.dwExecUrlFlags = pExecUrlInfo->dwExecUrlFlags;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//static
|
|
HRESULT
|
|
EXECUTE_CONTEXT::Initialize(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize execute_context lookaside
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
ALLOC_CACHE_CONFIGURATION acConfig;
|
|
|
|
//
|
|
// Setup allocation lookaside
|
|
//
|
|
|
|
acConfig.nConcurrency = 1;
|
|
acConfig.nThreshold = 100;
|
|
acConfig.cbSize = sizeof( EXECUTE_CONTEXT );
|
|
|
|
DBG_ASSERT( sm_pachExecuteContexts == NULL );
|
|
|
|
sm_pachExecuteContexts = new ALLOC_CACHE_HANDLER( "EXECUTE_CONTEXT",
|
|
&acConfig );
|
|
if ( sm_pachExecuteContexts == NULL )
|
|
{
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//static
|
|
VOID
|
|
EXECUTE_CONTEXT::Terminate(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cleanup execute_context lookaside
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
if ( sm_pachExecuteContexts != NULL )
|
|
{
|
|
delete sm_pachExecuteContexts;
|
|
sm_pachExecuteContexts = NULL;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::IsapiExecuteUrl(
|
|
HSE_EXEC_URL_INFO * pExecUrlInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Do an ISAPI execute URL request.
|
|
|
|
SO WHY THE HELL IS THIS CODE NOT IN THE ISAPI_CORE?
|
|
|
|
Because I don't want to preclude the same child execute API exposed to
|
|
ISAPI filters (though of course just the synchronous version)
|
|
|
|
Arguments:
|
|
|
|
pExecUrlInfo - Describes the child request to execute
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
DWORD dwExecFlags = 0;
|
|
WCHAR * pszRequiredAppPool = NULL;
|
|
BOOL fIgnoreValidation = FALSE;
|
|
W3_REQUEST * pChildRequest = NULL;
|
|
W3_CHILD_CONTEXT * pChildContext = NULL;
|
|
HRESULT hr = NO_ERROR;
|
|
DWORD dwCloneRequestFlags = 0;
|
|
BOOL fFinished = FALSE;
|
|
STACK_STRU( strNewUrl, 256 );
|
|
STACK_STRA( strNewVerb, 10 );
|
|
BOOL fIsSSICommandExecution = FALSE;
|
|
W3_HANDLER * pSSICommandHandler = NULL;
|
|
HANDLE hImpersonationToken = NULL;
|
|
CUSTOM_USER_CONTEXT* pCustomUser = NULL;
|
|
STACK_STRA( strAuthorization, 24 );
|
|
STACK_STRA( strCustomAuthType, 32 );
|
|
STACK_STRA( strCustomUserName, 64 );
|
|
BOOL fEnableWildcardMapping = TRUE;
|
|
|
|
if ( pExecUrlInfo == NULL )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Is this a crappy SSI hack
|
|
//
|
|
|
|
if ( pExecUrlInfo->dwExecUrlFlags & HSE_EXEC_URL_SSI_CMD )
|
|
{
|
|
//
|
|
// Issue-10-11-2000-BAlam
|
|
//
|
|
// This is a really dangerous code path (executing a raw command)
|
|
// so verify that SSI is calling us
|
|
//
|
|
|
|
fIsSSICommandExecution = TRUE;
|
|
}
|
|
|
|
//
|
|
// Should we execute the request synchronously?
|
|
//
|
|
|
|
if ( pExecUrlInfo->dwExecUrlFlags & HSE_EXEC_URL_SYNC )
|
|
{
|
|
dwExecFlags |= W3_FLAG_SYNC;
|
|
}
|
|
else
|
|
{
|
|
dwExecFlags |= W3_FLAG_ASYNC;
|
|
}
|
|
|
|
//
|
|
// Should we suppress headers in the response (useful for SSI)
|
|
//
|
|
|
|
if ( pExecUrlInfo->dwExecUrlFlags & HSE_EXEC_URL_NO_HEADERS )
|
|
{
|
|
dwExecFlags |= W3_FLAG_NO_HEADERS;
|
|
}
|
|
|
|
//
|
|
// Should we disable custom errors
|
|
//
|
|
|
|
if ( pExecUrlInfo->dwExecUrlFlags & HSE_EXEC_URL_DISABLE_CUSTOM_ERROR )
|
|
{
|
|
dwExecFlags |= W3_FLAG_NO_ERROR_BODY;
|
|
}
|
|
|
|
//
|
|
// Should we disable * script map?
|
|
//
|
|
|
|
if ( pExecUrlInfo->dwExecUrlFlags & HSE_EXEC_URL_IGNORE_CURRENT_INTERCEPTOR )
|
|
{
|
|
fEnableWildcardMapping = FALSE;
|
|
dwCloneRequestFlags |= W3_REQUEST_CLONE_NO_DAV;
|
|
}
|
|
|
|
//
|
|
// In every case we will clone the basics
|
|
//
|
|
|
|
dwCloneRequestFlags = W3_REQUEST_CLONE_BASICS;
|
|
|
|
//
|
|
// If the client specified headers, then we don't want to clone any
|
|
// headers
|
|
//
|
|
|
|
if ( pExecUrlInfo->pszChildHeaders == NULL )
|
|
{
|
|
dwCloneRequestFlags |= W3_REQUEST_CLONE_HEADERS;
|
|
}
|
|
|
|
//
|
|
// Now, should we also clone the precondition headers?
|
|
//
|
|
|
|
if ( pExecUrlInfo->dwExecUrlFlags & HSE_EXEC_URL_IGNORE_VALIDATION_AND_RANGE )
|
|
{
|
|
dwCloneRequestFlags |= W3_REQUEST_CLONE_NO_PRECONDITION;
|
|
}
|
|
|
|
//
|
|
// Now, should we also clone the preloaded entity?
|
|
//
|
|
|
|
if ( pExecUrlInfo->pEntity == NULL )
|
|
{
|
|
dwCloneRequestFlags |= W3_REQUEST_CLONE_ENTITY;
|
|
}
|
|
|
|
//
|
|
// OK. Start the fun by cloning the current request (as much as needed)
|
|
//
|
|
|
|
DBG_ASSERT( QueryRequest() != NULL );
|
|
|
|
hr = QueryRequest()->CloneRequest( dwCloneRequestFlags,
|
|
&pChildRequest );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
DBG_ASSERT( pChildRequest != NULL );
|
|
|
|
//
|
|
// If the parent specified headers, add them now
|
|
//
|
|
|
|
if ( pExecUrlInfo->pszChildHeaders != NULL )
|
|
{
|
|
hr = pChildRequest->SetHeadersByStream( pExecUrlInfo->pszChildHeaders );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we have entity to stuff in, do so now
|
|
//
|
|
|
|
if ( pExecUrlInfo->pEntity != NULL )
|
|
{
|
|
hr = pChildRequest->AppendEntityBody( pExecUrlInfo->pEntity->lpbData,
|
|
pExecUrlInfo->pEntity->cbAvailable );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Setup the URL for the request, if specified (it can contain a
|
|
// query string)
|
|
//
|
|
// If this is a command execution, then ignore the URL since it really
|
|
// isn't a URL in this case (it is a command to execute)
|
|
//
|
|
|
|
if ( pExecUrlInfo->pszUrl != NULL &&
|
|
!fIsSSICommandExecution )
|
|
{
|
|
hr = strNewUrl.CopyA( pExecUrlInfo->pszUrl );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Now set the new URL/Querystring
|
|
//
|
|
|
|
hr = pChildRequest->SetUrl( strNewUrl );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the verb for this request if specified
|
|
//
|
|
|
|
if ( pExecUrlInfo->pszMethod != NULL )
|
|
{
|
|
hr = strNewVerb.Copy( pExecUrlInfo->pszMethod );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
hr = pChildRequest->SetVerb( strNewVerb );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If a user context was set for this child request, then create a
|
|
// custom user context and do the necessary hookup
|
|
//
|
|
|
|
if ( pExecUrlInfo->pUserInfo != NULL )
|
|
{
|
|
if ( pExecUrlInfo->pUserInfo->hImpersonationToken != NULL )
|
|
{
|
|
hImpersonationToken = pExecUrlInfo->pUserInfo->hImpersonationToken;
|
|
}
|
|
else
|
|
{
|
|
hImpersonationToken = QueryImpersonationToken();
|
|
}
|
|
|
|
DBG_ASSERT( hImpersonationToken != NULL );
|
|
|
|
//
|
|
// Create the user context
|
|
//
|
|
|
|
pCustomUser = new CUSTOM_USER_CONTEXT(
|
|
W3_STATE_AUTHENTICATION::QueryCustomProvider() );
|
|
if ( pCustomUser == NULL )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
hr = pCustomUser->Create( hImpersonationToken,
|
|
pExecUrlInfo->pUserInfo->pszCustomUserName,
|
|
QueryUserContext()->QueryAuthType() );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Make sure REMOTE_USER is calculated properly
|
|
//
|
|
|
|
hr = strCustomUserName.Copy( pExecUrlInfo->pUserInfo->pszCustomUserName );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
hr = pChildRequest->SetRequestUserName( strCustomUserName );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Note that AUTH_TYPE server variable is a request'ism. So stuff it
|
|
// into the request now indirectly by setting authorization header
|
|
//
|
|
|
|
hr = strAuthorization.Copy( "Authorization" );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
hr = strCustomAuthType.Copy( pExecUrlInfo->pUserInfo->pszCustomAuthType );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
hr = pChildRequest->SetHeader( strAuthorization,
|
|
strCustomAuthType,
|
|
FALSE );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No custom user
|
|
//
|
|
|
|
pCustomUser = NULL;
|
|
}
|
|
|
|
//
|
|
// Now we can create a child context
|
|
//
|
|
|
|
pChildContext = new W3_CHILD_CONTEXT( QueryMainContext(),
|
|
this, // parent
|
|
pChildRequest,
|
|
TRUE, // child owns
|
|
pCustomUser,
|
|
dwExecFlags );
|
|
if ( pChildContext == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Get the URL context for this new context
|
|
//
|
|
|
|
hr = pChildContext->RetrieveUrlContext( &fFinished );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
if ( fFinished )
|
|
{
|
|
//
|
|
// Filter wants to bail.
|
|
//
|
|
|
|
hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Now validate that the app pool name is correct, if necessary
|
|
//
|
|
|
|
if ( !( pExecUrlInfo->dwExecUrlFlags & HSE_EXEC_URL_IGNORE_APPPOOL ) )
|
|
{
|
|
if ( QueryUrlContext() == NULL )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
goto Finished;
|
|
}
|
|
|
|
DBG_ASSERT( QueryUrlContext()->QueryMetaData() );
|
|
|
|
pszRequiredAppPool = QueryUrlContext()->QueryMetaData()->QueryAppPoolId();
|
|
|
|
if ( wcscmp( pszRequiredAppPool,
|
|
pChildContext->QueryUrlContext()->QueryMetaData()->QueryAppPoolId() ) )
|
|
{
|
|
//
|
|
// App pool name doesn't match. Bail
|
|
//
|
|
|
|
hr = HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
//
|
|
// ULTRA-GROSS. If this is an explicit command execution from SSI, then
|
|
// we explicitly setup the CGI handler. Otherwise, we determine the
|
|
// handler using sane rules
|
|
//
|
|
|
|
if ( fIsSSICommandExecution )
|
|
{
|
|
pSSICommandHandler = new W3_CGI_HANDLER( pChildContext,
|
|
NULL,
|
|
pExecUrlInfo->pszUrl );
|
|
if ( pSSICommandHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
goto Finished;
|
|
}
|
|
|
|
pChildContext->SetSSICommandHandler( pSSICommandHandler );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Properly find a handler for this request
|
|
//
|
|
|
|
hr = pChildContext->DetermineHandler( fEnableWildcardMapping );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
DBG_ASSERT( pChildContext->QueryHandler() != NULL );
|
|
|
|
hr = pChildContext->ExecuteHandler( dwExecFlags );
|
|
|
|
//
|
|
// If we failed, then we should just cleanup and report the error.
|
|
//
|
|
// NOTE: Failure means failure to spawn the child request. Not that
|
|
// the child request encountered a failure.
|
|
//
|
|
|
|
Finished:
|
|
|
|
//
|
|
// If we spawned this async, and there was no failure, then return out
|
|
//
|
|
|
|
if ( !FAILED( hr ) && ( dwExecFlags & W3_FLAG_ASYNC ) )
|
|
{
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// If we're here, we either failed, or succeeded synchronously
|
|
//
|
|
|
|
//
|
|
// If the urlcontext/child-request was attached to the child context,
|
|
// the child context will clean it up
|
|
//
|
|
|
|
if ( pChildContext != NULL )
|
|
{
|
|
delete pChildContext;
|
|
}
|
|
else
|
|
{
|
|
if ( pChildRequest != NULL )
|
|
{
|
|
delete pChildRequest;
|
|
}
|
|
|
|
if ( pCustomUser != NULL )
|
|
{
|
|
pCustomUser->DereferenceUserContext();
|
|
pCustomUser = NULL;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::ExecuteChildRequest(
|
|
W3_REQUEST * pNewRequest,
|
|
BOOL fOwnRequest,
|
|
DWORD dwFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Execute a child request (for internal W3CORE use only)
|
|
|
|
Arguments:
|
|
|
|
pNewRequest - W3_REQUEST * representing the new request
|
|
fOwnRequest - Should the child context be responsible for cleaning up
|
|
request?
|
|
dwFlags - W3_FLAG_ASYNC async
|
|
W3_FLAG_SYNC sync
|
|
W3_FLAG_MORE_DATA caller needs to send data too,
|
|
W3_FLAG_NO_CUSTOM_ERROR do not execute custom errors
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr;
|
|
W3_CHILD_CONTEXT * pChildContext = NULL;
|
|
BOOL fFinished = FALSE;
|
|
|
|
if ( !VALID_W3_FLAGS( dwFlags ) )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// Ignore the fFinished value on a child
|
|
//
|
|
|
|
pChildContext = new W3_CHILD_CONTEXT( QueryMainContext(),
|
|
this,
|
|
pNewRequest,
|
|
fOwnRequest,
|
|
NULL,
|
|
dwFlags );
|
|
if ( pChildContext == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// First read the metadata for this new request
|
|
//
|
|
|
|
hr = pChildContext->RetrieveUrlContext( &fFinished );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
if ( fFinished )
|
|
{
|
|
//
|
|
// Filter wants to bail.
|
|
//
|
|
|
|
hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Find handler
|
|
//
|
|
|
|
hr = pChildContext->DetermineHandler( TRUE );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
DBG_ASSERT( pChildContext->QueryHandler() );
|
|
|
|
//
|
|
// Execute the handler and take the error in any non-pending case
|
|
// (we can't get the status on pending since the value we would be
|
|
// getting is instable)
|
|
//
|
|
|
|
hr = pChildContext->ExecuteHandler( dwFlags );
|
|
|
|
Finished:
|
|
|
|
//
|
|
// If we spawned this async, and there was no failure, then return out
|
|
//
|
|
|
|
if ( !FAILED( hr ) && ( dwFlags & W3_FLAG_ASYNC ) )
|
|
{
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// If we got here, then either something bad happened OR a synchronous
|
|
// child execute is complete. Either way, we can delete the context
|
|
// here
|
|
//
|
|
|
|
if ( pChildContext != NULL )
|
|
{
|
|
delete pChildContext;
|
|
}
|
|
else
|
|
{
|
|
if ( pNewRequest && fOwnRequest )
|
|
{
|
|
delete pNewRequest;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::SetupAllowHeader(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Setup a 405 response. This means setting the response status as well
|
|
as setting up an appropriate Allow: header
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
STACK_STRA( strAllowHeader, 256 );
|
|
URL_CONTEXT * pUrlContext;
|
|
HRESULT hr;
|
|
W3_URL_INFO * pUrlInfo;
|
|
W3_METADATA * pMetaData;
|
|
MULTISZA * pAllowedVerbs;
|
|
const CHAR * pszAllowedVerb;
|
|
|
|
pUrlContext = QueryUrlContext();
|
|
if ( pUrlContext == NULL )
|
|
{
|
|
//
|
|
// If we have no metadata, then don't send an Allow: header. The
|
|
// only case this would happen is if a filter decided to send the
|
|
// response and in that case it is up to it to send an appropriate
|
|
// Allow: header
|
|
//
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
pUrlInfo = pUrlContext->QueryUrlInfo();
|
|
if ( pUrlInfo == NULL )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
pMetaData = pUrlContext->QueryMetaData();
|
|
if ( pMetaData == NULL )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// We always support OPTIONS and TRACE
|
|
//
|
|
|
|
hr = strAllowHeader.Append( "OPTIONS, TRACE" );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Is this URL script mapped? If so, then the verbs allowed is equal to
|
|
// to the verbs specified in the script map, provided that we have
|
|
// script execute permissions
|
|
//
|
|
|
|
if ( pUrlInfo->QueryScriptMapEntry() != NULL )
|
|
{
|
|
if ( IS_ACCESS_ALLOWED( QueryRequest(),
|
|
pMetaData->QueryAccessPerms(),
|
|
SCRIPT ) )
|
|
{
|
|
pAllowedVerbs = pUrlInfo->QueryScriptMapEntry()->QueryAllowedVerbs();
|
|
DBG_ASSERT( pAllowedVerbs != NULL );
|
|
|
|
pszAllowedVerb = pAllowedVerbs->First();
|
|
while ( pszAllowedVerb != NULL )
|
|
{
|
|
hr = strAllowHeader.Append( ", " );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
hr = strAllowHeader.Append( pszAllowedVerb );
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
pszAllowedVerb = pAllowedVerbs->Next( pszAllowedVerb );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Must be a static file or a explicit gateway
|
|
//
|
|
|
|
switch( pUrlInfo->QueryGateway() )
|
|
{
|
|
case GATEWAY_UNKNOWN:
|
|
case GATEWAY_MAP:
|
|
hr = strAllowHeader.Append( ", GET, HEAD" );
|
|
break;
|
|
case GATEWAY_CGI:
|
|
case GATEWAY_ISAPI:
|
|
hr = strAllowHeader.Append( ", GET, HEAD, POST" );
|
|
break;
|
|
}
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now add some DAV verbs (man, this is broken)
|
|
//
|
|
|
|
if ( IS_ACCESS_ALLOWED( QueryRequest(),
|
|
pMetaData->QueryAccessPerms(),
|
|
WRITE ) &&
|
|
!pMetaData->QueryIsDavDisabled() )
|
|
{
|
|
hr = strAllowHeader.Append( ", DELETE, PUT" );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Finally, set the header
|
|
//
|
|
|
|
hr = QueryResponse()->SetHeader( HttpHeaderAllow,
|
|
strAllowHeader.QueryStr(),
|
|
strAllowHeader.QueryCCH() );
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::SetupCustomErrorFileResponse(
|
|
STRU & strErrorFile
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Open the custom error file (nor URL) from cache if possible and setup
|
|
the response (which means, fill in entity body with file)
|
|
|
|
Arguments:
|
|
|
|
strErrorFile - Custom Error File
|
|
|
|
Return Value:
|
|
|
|
HRESULT (HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND) to indicate to caller
|
|
that it should just generate hard coded custom error.
|
|
|
|
--*/
|
|
{
|
|
W3_FILE_INFO * pOpenFile = NULL;
|
|
HRESULT hr = NO_ERROR;
|
|
DWORD dwFlags;
|
|
W3_RESPONSE * pResponse = QueryResponse();
|
|
FILE_CACHE_USER fileUser;
|
|
LARGE_INTEGER liFileSize;
|
|
W3_METADATA * pMetaData;
|
|
URL_CONTEXT * pUrlContext;
|
|
STACK_STRA( strContentType, 32 );
|
|
|
|
DBG_ASSERT( pResponse != NULL );
|
|
DBG_ASSERT( !pResponse->QueryEntityExists() );
|
|
DBG_ASSERT( pResponse->QueryStatusCode() >= 400 );
|
|
|
|
//
|
|
// Get the file from the cache. We will enable deferred directory
|
|
// monitor changes. This means that we will register for the appropriate
|
|
// parent directory changes as needed and remove as the cached file
|
|
// object goes away.
|
|
//
|
|
|
|
DBG_ASSERT( g_pW3Server->QueryFileCache() != NULL );
|
|
|
|
hr = g_pW3Server->QueryFileCache()->GetFileInfo( strErrorFile,
|
|
NULL,
|
|
&fileUser,
|
|
TRUE,
|
|
&pOpenFile );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
//
|
|
// Switch some known errors back to file not found
|
|
//
|
|
|
|
switch( hr )
|
|
{
|
|
case HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED ):
|
|
case HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ):
|
|
|
|
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
|
|
break;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
DBG_ASSERT( pOpenFile != NULL );
|
|
|
|
//
|
|
// We might already have a custom error file IF we encountered an error
|
|
// setting up the last custom error response
|
|
//
|
|
|
|
if ( _pCustomErrorFile != NULL )
|
|
{
|
|
_pCustomErrorFile->DereferenceCacheEntry();
|
|
_pCustomErrorFile = NULL;
|
|
}
|
|
|
|
DBG_ASSERT( pOpenFile != NULL );
|
|
_pCustomErrorFile = pOpenFile;
|
|
|
|
//
|
|
// Determine the content-type and set the header
|
|
//
|
|
|
|
pUrlContext = QueryUrlContext();
|
|
DBG_ASSERT( pUrlContext != NULL );
|
|
|
|
pMetaData = pUrlContext->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
hr = SelectMimeMappingForFileExt( strErrorFile.QueryStr(),
|
|
pMetaData->QueryMimeMap(),
|
|
&strContentType );
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = pResponse->SetHeader( HttpHeaderContentType,
|
|
strContentType.QueryStr(),
|
|
strContentType.QueryCCH() );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now setup the response chunk
|
|
//
|
|
|
|
pOpenFile->QuerySize( &liFileSize );
|
|
|
|
if ( pOpenFile->QueryFileBuffer() != NULL )
|
|
{
|
|
hr = pResponse->AddMemoryChunkByReference(
|
|
pOpenFile->QueryFileBuffer(),
|
|
liFileSize.LowPart );
|
|
}
|
|
else
|
|
{
|
|
hr = pResponse->AddFileHandleChunk(pOpenFile->QueryFileHandle(),
|
|
0,
|
|
liFileSize.LowPart );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HANDLE
|
|
W3_CONTEXT::QueryImpersonationToken(
|
|
BOOL * pfIsVrToken
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get the impersonation token for this request. This routine will
|
|
choose correctly based on whether there is a VRoot token, passthru is
|
|
enabled, etc.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
HANDLE
|
|
|
|
--*/
|
|
{
|
|
W3_METADATA * pMetaData;
|
|
TOKEN_CACHE_ENTRY * pVrToken;
|
|
W3_USER_CONTEXT * pUserContext;
|
|
|
|
DBG_ASSERT( QueryUrlContext() != NULL );
|
|
|
|
pMetaData = QueryUrlContext()->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
pUserContext = QueryUserContext();
|
|
DBG_ASSERT( pUserContext != NULL );
|
|
|
|
pVrToken = pMetaData->QueryVrAccessToken();
|
|
if ( pVrToken == NULL ||
|
|
( pMetaData->QueryVrPassThrough() &&
|
|
pUserContext->QueryDelegatable() ) )
|
|
{
|
|
if ( pfIsVrToken )
|
|
{
|
|
*pfIsVrToken = FALSE;
|
|
}
|
|
|
|
return pUserContext->QueryImpersonationToken();
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT( pVrToken != NULL );
|
|
|
|
if ( pfIsVrToken )
|
|
{
|
|
*pfIsVrToken = TRUE;
|
|
}
|
|
|
|
return pVrToken->QueryImpersonationToken();
|
|
}
|
|
}
|
|
|
|
VOID
|
|
W3_CONTEXT::QueryFileCacheUser(
|
|
FILE_CACHE_USER * pFileCacheUser
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get a user descriptor suitable for use with file cache
|
|
|
|
Arguments:
|
|
|
|
pFileCacheUser - Filled with file cache user information
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
W3_METADATA * pMetaData;
|
|
TOKEN_CACHE_ENTRY * pVrToken;
|
|
W3_USER_CONTEXT * pUserContext;
|
|
HANDLE hReturnToken = NULL;
|
|
PSID pReturnSid = NULL;
|
|
|
|
DBG_ASSERT( pFileCacheUser != NULL );
|
|
|
|
DBG_ASSERT( QueryUrlContext() != NULL );
|
|
|
|
pMetaData = QueryUrlContext()->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
pUserContext = QueryUserContext();
|
|
DBG_ASSERT( pUserContext != NULL );
|
|
|
|
pVrToken = pMetaData->QueryVrAccessToken();
|
|
if ( pVrToken == NULL ||
|
|
( pMetaData->QueryVrPassThrough() &&
|
|
pUserContext->QueryDelegatable() ) )
|
|
{
|
|
hReturnToken = pUserContext->QueryImpersonationToken();
|
|
pReturnSid = pUserContext->QuerySid();
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT( pVrToken != NULL );
|
|
|
|
pReturnSid = pVrToken->QuerySid();
|
|
hReturnToken = pVrToken->QueryImpersonationToken();
|
|
}
|
|
|
|
//
|
|
// Setup the file cache user
|
|
//
|
|
|
|
pFileCacheUser->_hToken = hReturnToken;
|
|
pFileCacheUser->_pSid = pReturnSid;
|
|
}
|
|
|
|
HANDLE
|
|
W3_CONTEXT::QueryPrimaryToken(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get the primary token for this request. This routine will
|
|
choose correctly based on whether there is a VRoot token, passthru is
|
|
enabled, etc.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
HANDLE
|
|
|
|
--*/
|
|
{
|
|
W3_METADATA * pMetaData;
|
|
TOKEN_CACHE_ENTRY * pVrToken;
|
|
W3_USER_CONTEXT * pUserContext;
|
|
|
|
DBG_ASSERT( QueryUrlContext() != NULL );
|
|
|
|
pMetaData = QueryUrlContext()->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
pUserContext = QueryUserContext();
|
|
DBG_ASSERT( pUserContext != NULL );
|
|
|
|
pVrToken = pMetaData->QueryVrAccessToken();
|
|
if ( pVrToken == NULL ||
|
|
( pMetaData->QueryVrPassThrough() &&
|
|
pUserContext->QueryDelegatable() ) )
|
|
{
|
|
return pUserContext->QueryPrimaryToken();
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT( pVrToken != NULL );
|
|
return pVrToken->QueryPrimaryToken();
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::GetCertificateInfoEx(
|
|
IN DWORD cbAllocated,
|
|
OUT DWORD * pdwCertEncodingType,
|
|
OUT unsigned char * pbCertEncoded,
|
|
OUT DWORD * pcbCertEncoded,
|
|
OUT DWORD * pdwCertificateFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Retrieve certificate info if available
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
CERTIFICATE_CONTEXT * pCertContext = NULL;
|
|
DWORD cbCert = 0;
|
|
PVOID pvCert = NULL;
|
|
|
|
if ( pdwCertEncodingType == NULL ||
|
|
pbCertEncoded == NULL ||
|
|
pcbCertEncoded == NULL ||
|
|
pdwCertificateFlags == NULL )
|
|
{
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
pCertContext = QueryCertificateContext();
|
|
|
|
if ( pCertContext == NULL )
|
|
{
|
|
//
|
|
// If we don't have a certificate, then just succeed with nothing
|
|
// to keep in line with IIS 5 behaviour
|
|
//
|
|
|
|
*pbCertEncoded = NULL;
|
|
*pcbCertEncoded = 0;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
pCertContext->QueryEncodedCertificate( &pvCert, &cbCert );
|
|
DBG_ASSERT( pvCert != NULL );
|
|
DBG_ASSERT( cbCert != 0 );
|
|
|
|
//
|
|
// Fill in the parameters
|
|
//
|
|
|
|
*pcbCertEncoded = cbCert;
|
|
|
|
if ( cbAllocated < *pcbCertEncoded )
|
|
{
|
|
//
|
|
// Not enough space
|
|
//
|
|
|
|
return HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER );
|
|
}
|
|
else
|
|
{
|
|
memcpy( pbCertEncoded,
|
|
pvCert,
|
|
*pcbCertEncoded );
|
|
|
|
*pdwCertificateFlags = pCertContext->QueryFlags();
|
|
*pdwCertEncodingType = X509_ASN_ENCODING;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::SendResponse(
|
|
DWORD dwFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends a response to the client thru ULATQ.
|
|
|
|
Arguments:
|
|
|
|
dwFlags - Flags
|
|
W3_FLAG_ASYNC - Send response asynchronously
|
|
W3_FLAG_SYNC - Send response synchronously
|
|
W3_FLAG_MORE_DATA - Send more data
|
|
W3_FLAG_NO_ERROR_BODY - Don't generate error body
|
|
W3_FLAG_GENERATE_CONTENT_LENGTH - generate content length
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = NO_ERROR;
|
|
BOOL fIsFileError = FALSE;
|
|
BOOL fFinished = FALSE;
|
|
DWORD cbSent = 0;
|
|
BOOL fAddContentLength = FALSE;
|
|
BOOL fSendHeaders = FALSE;
|
|
BOOL fHandlerManagesHead = FALSE;
|
|
BOOL fEnableUlCache = FALSE;
|
|
BOOL fSuppressEntity = FALSE;
|
|
DWORD dwResponseFlags = 0;
|
|
AUTH_PROVIDER * pAnonymousProvider = NULL;
|
|
W3_RESPONSE * pResponse;
|
|
W3_FILTER_CONTEXT * pFilterContext;
|
|
|
|
if ( !VALID_W3_FLAGS( dwFlags ) )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
pResponse = QueryResponse();
|
|
DBG_ASSERT( pResponse != NULL );
|
|
|
|
//
|
|
// Should we be sending headers at all?
|
|
//
|
|
|
|
fSendHeaders = QuerySendHeaders();
|
|
|
|
//
|
|
// Do a quick precheck to see whether we require an END_OF_REQUEST
|
|
// notification. If so, then this won't be our last send
|
|
//
|
|
|
|
if ( fSendHeaders &&
|
|
( IsNotificationNeeded( SF_NOTIFY_END_OF_REQUEST ) ||
|
|
IsNotificationNeeded( SF_NOTIFY_LOG ) ) )
|
|
{
|
|
if ( !( dwFlags & W3_FLAG_MORE_DATA ) &&
|
|
!QueryNeedFinalDone() )
|
|
{
|
|
//
|
|
// If the only reason this is NOT the final send is because of
|
|
// an END_OF_REQUEST or LOG filter, then act like IIS 5.0 and
|
|
// still send a content length
|
|
//
|
|
|
|
fAddContentLength = TRUE;
|
|
}
|
|
|
|
dwFlags |= W3_FLAG_MORE_DATA;
|
|
}
|
|
|
|
//
|
|
// Remember whether we need to send an empty done on our own at the end
|
|
//
|
|
|
|
if ( dwFlags & W3_FLAG_MORE_DATA )
|
|
{
|
|
SetNeedFinalDone();
|
|
}
|
|
|
|
//
|
|
// Was this an access denial not handled by an authentication provider?
|
|
//
|
|
|
|
if ( pResponse->QueryStatusCode() == HttpStatusUnauthorized.statusCode &&
|
|
!QueryProviderHandled() &&
|
|
QueryUrlContext() != NULL )
|
|
{
|
|
//
|
|
// First call access denied filters
|
|
//
|
|
|
|
if ( IsNotificationNeeded( SF_NOTIFY_ACCESS_DENIED ) )
|
|
{
|
|
STACK_STRA( straUrl, 256 );
|
|
STACK_STRA( straPhys, 256 );
|
|
STACK_STRU( strUrl, 256 );
|
|
STRU * pstrPhysicalPath;
|
|
|
|
hr = QueryRequest()->GetUrl( &strUrl );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
pstrPhysicalPath = QueryUrlContext()->QueryPhysicalPath();
|
|
DBG_ASSERT( pstrPhysicalPath != NULL );
|
|
|
|
if (FAILED(hr = straUrl.CopyW(strUrl.QueryStr())) ||
|
|
FAILED(hr = straPhys.CopyW(pstrPhysicalPath->QueryStr())))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
if ( !QueryFilterContext()->NotifyAccessDenied(
|
|
straUrl.QueryStr(),
|
|
straPhys.QueryStr(),
|
|
&fFinished ) )
|
|
{
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
if ( fFinished )
|
|
{
|
|
if ( dwFlags & W3_FLAG_ASYNC )
|
|
{
|
|
ThreadPoolPostCompletion( 0,
|
|
W3_MAIN_CONTEXT::OnPostedCompletion,
|
|
(LPOVERLAPPED) QueryMainContext() );
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now, notify all authentication providers so they can add headers
|
|
// if necessary
|
|
//
|
|
|
|
hr = W3_STATE_AUTHENTICATION::CallAllAccessDenied( QueryMainContext() );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Send a custom error for this response if all of following are true:
|
|
//
|
|
// a) The caller wants an error body
|
|
// b) This request (and all children) actually allow an error body
|
|
// c) The response code is greater than 400 (error)
|
|
//
|
|
|
|
if ( !(dwFlags & W3_FLAG_NO_ERROR_BODY ) &&
|
|
QuerySendErrorBody() &&
|
|
pResponse->QueryStatusCode() >= 400 )
|
|
{
|
|
STACK_STRU( strError, 64 );
|
|
STACK_STRU( strFullUrl, 64 );
|
|
WCHAR achNum[ 32 ];
|
|
HTTP_SUB_ERROR subError;
|
|
W3_REQUEST * pChildRequest = NULL;
|
|
|
|
pResponse->Clear( TRUE );
|
|
|
|
DisableUlCache();
|
|
|
|
//
|
|
// Get the sub error for this response (if any)
|
|
//
|
|
|
|
hr = pResponse->QuerySubError( &subError );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Check for a configured custom error. This check is only possible
|
|
// if we have read metadata. We may not have read metadata if
|
|
// some fatal error occurred early in the pipeline (for example,
|
|
// an out-of-mem error in the URLINFO state)
|
|
//
|
|
|
|
if ( QueryUrlContext() != NULL &&
|
|
QuerySendCustomError() )
|
|
{
|
|
W3_METADATA * pMetaData = NULL;
|
|
|
|
pMetaData = QueryUrlContext()->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
hr = pMetaData->FindCustomError( pResponse->QueryStatusCode(),
|
|
subError.mdSubError,
|
|
&fIsFileError,
|
|
&strError );
|
|
|
|
//
|
|
// If this is a file error, check for file existence now. If it
|
|
// does not exist, then act as if there is no custom error set
|
|
//
|
|
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
if ( fIsFileError )
|
|
{
|
|
hr = SetupCustomErrorFileResponse( strError );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is a custom error URL
|
|
//
|
|
|
|
//
|
|
// If there is no user context, then executing a child
|
|
// request would be tough.
|
|
//
|
|
// But we can still try to get the anonymous token, if
|
|
// it exists and use it
|
|
//
|
|
|
|
if ( QueryUserContext() == NULL )
|
|
{
|
|
pAnonymousProvider = W3_STATE_AUTHENTICATION::QueryAnonymousProvider();
|
|
DBG_ASSERT( pAnonymousProvider != NULL );
|
|
|
|
hr = pAnonymousProvider->DoAuthenticate( QueryMainContext() );
|
|
if ( FAILED( hr ) ||
|
|
QueryMainContext()->QueryUserContext() == NULL )
|
|
{
|
|
//
|
|
// Ok. We really can't get anonymous user.
|
|
// No custom error URL
|
|
//
|
|
|
|
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
|
|
}
|
|
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
//
|
|
// Great. Send the configured custom error
|
|
//
|
|
|
|
if ( !fIsFileError )
|
|
{
|
|
//
|
|
// This is a custom error URL. Reset and launch custom URL
|
|
//
|
|
// The URL is of the form:
|
|
//
|
|
// /customerrorurl?<status>;http://<server>/<original url>
|
|
//
|
|
|
|
hr = QueryRequest()->CloneRequest(
|
|
W3_REQUEST_CLONE_BASICS |
|
|
W3_REQUEST_CLONE_HEADERS |
|
|
W3_REQUEST_CLONE_NO_PRECONDITION |
|
|
W3_REQUEST_CLONE_NO_DAV,
|
|
&pChildRequest );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
DBG_ASSERT( pChildRequest != NULL );
|
|
|
|
hr = strError.Append( L"?" );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
_itow( pResponse->QueryStatusCode(), achNum, 10 );
|
|
|
|
hr = strError.Append( achNum );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
hr = strError.Append( L";" );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
hr = QueryRequest()->GetFullUrl( &strFullUrl );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
hr = strError.Append( strFullUrl );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Change the URL and verb of the request
|
|
//
|
|
|
|
hr = pChildRequest->SetUrl( strError );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Remove DAVFS'ness
|
|
//
|
|
|
|
pChildRequest->RemoveDav();
|
|
|
|
//
|
|
// All custom error URLs are GET/HEADs
|
|
//
|
|
|
|
if ( pChildRequest->QueryVerbType() == HttpVerbHEAD )
|
|
{
|
|
pChildRequest->SetVerbType( HttpVerbHEAD );
|
|
}
|
|
else
|
|
{
|
|
pChildRequest->SetVerbType( HttpVerbGET );
|
|
}
|
|
|
|
pResponse->SetStatus( HttpStatusOk );
|
|
|
|
hr = ExecuteChildRequest( pChildRequest,
|
|
TRUE, // child context cleans up
|
|
dwFlags | W3_FLAG_NO_CUSTOM_ERROR );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Not finding a custom error is OK, but any other error is
|
|
// fatal
|
|
//
|
|
|
|
DBG_ASSERT( FAILED( hr ) );
|
|
|
|
if ( hr != HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
CHAR *achImageError;
|
|
DWORD cbImageError = 512;
|
|
|
|
if (FAILED(hr = QueryHeaderBuffer()->AllocateSpace(
|
|
cbImageError,
|
|
&achImageError)))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Check the image for a resource string which may apply.
|
|
//
|
|
|
|
if ( subError.dwStringId != 0 )
|
|
{
|
|
hr = g_pW3Server->LoadString( subError.dwStringId,
|
|
achImageError,
|
|
&cbImageError );
|
|
}
|
|
else if ( pResponse->QueryStatusCode() ==
|
|
HttpStatusUnauthorized.statusCode &&
|
|
sm_pszAccessDeniedMessage != NULL )
|
|
{
|
|
//
|
|
// Note: 401 message is actually configured in the registry
|
|
//
|
|
|
|
strncpy( achImageError,
|
|
sm_pszAccessDeniedMessage,
|
|
cbImageError - 1 );
|
|
|
|
achImageError[ cbImageError - 1 ] = '\0';
|
|
cbImageError = strlen( achImageError );
|
|
hr = NO_ERROR;
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
|
}
|
|
|
|
if (FAILED(hr) &&
|
|
FAILED(QueryErrorStatus()))
|
|
{
|
|
cbImageError = 512;
|
|
//
|
|
// If there is a particular error status set, find the
|
|
// message for that error and send it. This is that last
|
|
// thing we can do to attempt to send a useful message to
|
|
// client
|
|
//
|
|
|
|
cbImageError = FormatMessageA(
|
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
QueryErrorStatus(),
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
achImageError,
|
|
cbImageError,
|
|
NULL);
|
|
|
|
if (cbImageError == 0)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
else
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
//
|
|
// Add content type
|
|
//
|
|
|
|
hr = pResponse->SetHeader( HttpHeaderContentType,
|
|
"text/html",
|
|
9 );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
hr = pResponse->AddMemoryChunkByReference(
|
|
achImageError,
|
|
cbImageError );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Send custom headers if any
|
|
//
|
|
|
|
if ( fSendHeaders &&
|
|
QueryUrlContext() != NULL )
|
|
{
|
|
W3_METADATA *pMetaData = QueryUrlContext()->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
//
|
|
// Add Custom HTTP Headers if defined in the metabase.
|
|
//
|
|
|
|
if ( !pMetaData->QueryCustomHeaders()->IsEmpty() )
|
|
{
|
|
hr = pResponse->AppendResponseHeaders( *(pMetaData->QueryCustomHeaders()) );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
pResponse->Clear();
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add Cache-Control and Expires header if so configured
|
|
//
|
|
STRA *pstrCacheControlHeader = pMetaData->QueryCacheControlHeader();
|
|
if (!pstrCacheControlHeader->IsEmpty())
|
|
{
|
|
if (FAILED(hr = pResponse->SetHeader(
|
|
HttpHeaderCacheControl,
|
|
pstrCacheControlHeader->QueryStr(),
|
|
pstrCacheControlHeader->QueryCCH(),
|
|
TRUE)))
|
|
{
|
|
pResponse->Clear();
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
if (pMetaData->QueryExpireMode() == EXPIRE_MODE_STATIC)
|
|
{
|
|
STRA *pstrExpireHeader = pMetaData->QueryExpireHeader();
|
|
|
|
if (FAILED(hr = pResponse->SetHeaderByReference(
|
|
HttpHeaderExpires,
|
|
pstrExpireHeader->QueryStr(),
|
|
pstrExpireHeader->QueryCCH())))
|
|
{
|
|
pResponse->Clear();
|
|
return hr;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Does the current handler manage content length on its own?
|
|
// We'll assume (i.e. the choice which means we do nothing to supply
|
|
// a content-length header or suppress HEADs)
|
|
//
|
|
|
|
if ( QueryHandler() != NULL )
|
|
{
|
|
fHandlerManagesHead = QueryHandler()->QueryManagesOwnHead();
|
|
}
|
|
|
|
//
|
|
// Should we be adding a content-length header?
|
|
//
|
|
// Note we get the original verb (not the current request's verb).
|
|
// This is because the original verb determines whether or not UL
|
|
// will be sending a content-length for us.
|
|
//
|
|
|
|
if ( ( QueryMainContext()->QueryShouldGenerateContentLength() &&
|
|
!fHandlerManagesHead &&
|
|
!QueryNeedFinalDone() ) ||
|
|
fAddContentLength ||
|
|
(dwFlags & W3_FLAG_GENERATE_CONTENT_LENGTH) )
|
|
{
|
|
CHAR achNum[ 32 ];
|
|
|
|
//
|
|
// If there is already a Content-Length header, then no need to
|
|
// make one (this must mean the handler handled HEAD themselves)
|
|
//
|
|
|
|
if ( pResponse->GetHeader( HttpHeaderContentLength ) == NULL )
|
|
{
|
|
_ui64toa( pResponse->QueryContentLength(),
|
|
achNum,
|
|
10 );
|
|
|
|
hr = pResponse->SetHeader( HttpHeaderContentLength,
|
|
achNum,
|
|
strlen( achNum ) );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
SetErrorStatus( hr );
|
|
pResponse->SetStatus( HttpStatusServerError );
|
|
return hr;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Should we be suppressing entity. This should be done if this is a
|
|
// HEAD request and the current handler doesn't take responsibility
|
|
// to handle HEADs
|
|
//
|
|
|
|
if ( QueryRequest()->QueryVerbType() == HttpVerbHEAD &&
|
|
!fHandlerManagesHead )
|
|
{
|
|
DisableUlCache();
|
|
fSuppressEntity = TRUE;
|
|
}
|
|
|
|
//
|
|
// Remember what type of operation this is, so that the completion
|
|
// can do the proper book keeping
|
|
//
|
|
|
|
if ( dwFlags & W3_FLAG_ASYNC )
|
|
{
|
|
SetLastIOPending( LOG_WRITE_IO );
|
|
}
|
|
|
|
//
|
|
// Setup logging information
|
|
//
|
|
|
|
BOOL fDoLogging = FALSE;
|
|
if ( fSendHeaders &&
|
|
!QueryNeedFinalDone() &&
|
|
QueryDoUlLogging() )
|
|
{
|
|
fDoLogging = TRUE;
|
|
if ( FAILED( hr = QueryMainContext()->CollectLoggingData( TRUE ) ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If a filter added response headers (either for regular responses or
|
|
// for denial responses), then add them now
|
|
//
|
|
|
|
pFilterContext = QueryFilterContext( FALSE );
|
|
if ( pFilterContext != NULL )
|
|
{
|
|
//
|
|
// Add denial headers if this is a denial
|
|
//
|
|
|
|
if ( pResponse->QueryStatusCode() == HttpStatusUnauthorized.statusCode )
|
|
{
|
|
//
|
|
// Add filter denied headers
|
|
//
|
|
|
|
hr = pResponse->AppendResponseHeaders(
|
|
*pFilterContext->QueryAddDenialHeaders() );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
pFilterContext->QueryAddDenialHeaders()->Reset();
|
|
}
|
|
|
|
//
|
|
// Add regular headers
|
|
//
|
|
|
|
hr = pResponse->AppendResponseHeaders(
|
|
*pFilterContext->QueryAddResponseHeaders() );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
pFilterContext->QueryAddResponseHeaders()->Reset();
|
|
}
|
|
|
|
//
|
|
// Notify Response header filters
|
|
//
|
|
|
|
if ( fSendHeaders &&
|
|
IsNotificationNeeded( SF_NOTIFY_SEND_RESPONSE ) )
|
|
{
|
|
HTTP_FILTER_SEND_RESPONSE sendResponse;
|
|
|
|
sendResponse.GetHeader = GetSendHeader;
|
|
sendResponse.SetHeader = SetSendHeader;
|
|
sendResponse.AddHeader = AddSendHeader;
|
|
sendResponse.HttpStatus = pResponse->QueryStatusCode();
|
|
|
|
if ( !NotifyFilters( SF_NOTIFY_SEND_RESPONSE,
|
|
&sendResponse,
|
|
&fFinished ) )
|
|
{
|
|
return HRESULT_FROM_WIN32( GetLastError() ); // BUGBUG
|
|
}
|
|
|
|
if ( fFinished )
|
|
{
|
|
if ( dwFlags & W3_FLAG_ASYNC )
|
|
{
|
|
ThreadPoolPostCompletion( 0,
|
|
W3_MAIN_CONTEXT::OnPostedCompletion,
|
|
(LPOVERLAPPED) QueryMainContext() );
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
// perf ctr
|
|
if (QuerySite() != NULL)
|
|
{
|
|
if ( pResponse->QueryStatusCode() == HttpStatusNotFound.statusCode )
|
|
{
|
|
QuerySite()->IncNotFound();
|
|
}
|
|
else if ( pResponse->QueryStatusCode() == HttpStatusLockedError.statusCode )
|
|
{
|
|
QuerySite()->IncLockedError();
|
|
}
|
|
}
|
|
|
|
//
|
|
// If any one sees a reason why this response and this kernel mode cache
|
|
// should not be together, speak now or forever hold your peace (or
|
|
// at least until the UL cache flushes the response)
|
|
//
|
|
|
|
DBG_ASSERT( g_pW3Server->QueryUlCache() != NULL );
|
|
|
|
if ( g_pW3Server->QueryUlCache()->CheckUlCacheability( this ) )
|
|
{
|
|
//
|
|
// The response is UL cacheable, so setup a ul cache entry token
|
|
//
|
|
|
|
if ( _pHandler != NULL )
|
|
{
|
|
DBG_ASSERT( _pHandler->QueryIsUlCacheable() );
|
|
|
|
hr = _pHandler->SetupUlCachedResponse( this );
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
fEnableUlCache = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Generate response flags now
|
|
//
|
|
|
|
if ( QueryNeedFinalDone() )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_MORE_DATA;
|
|
}
|
|
|
|
if ( QueryDisconnect() )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_DISCONNECT;
|
|
}
|
|
|
|
if ( fEnableUlCache )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_UL_CACHEABLE;
|
|
}
|
|
|
|
if ( !fSendHeaders )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_SUPPRESS_HEADERS;
|
|
}
|
|
|
|
if ( fSuppressEntity )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_SUPPRESS_ENTITY;
|
|
}
|
|
|
|
if ( dwFlags & W3_FLAG_ASYNC )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_ASYNC;
|
|
}
|
|
|
|
//
|
|
// Send out the full response now
|
|
//
|
|
|
|
hr = pResponse->SendResponse( this,
|
|
dwResponseFlags,
|
|
&cbSent,
|
|
fDoLogging ? QueryUlLogData() : NULL );
|
|
|
|
if (!(dwFlags & W3_FLAG_ASYNC))
|
|
{
|
|
IncrementBytesSent(cbSent);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
if (_pHandler == NULL)
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"SendResponse failed: no handler, hr %x\n",
|
|
hr));
|
|
}
|
|
else
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"SendResponse failed: handler %S, hr %x\n",
|
|
_pHandler->QueryName(),
|
|
hr));
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::SendEntity(
|
|
DWORD dwFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sends a response entity to the client thru ULATQ.
|
|
|
|
Arguments:
|
|
|
|
dwFlags - Flags
|
|
W3_FLAG_ASYNC - Send response asynchronously
|
|
W3_FLAG_SYNC - Send response synchronously
|
|
W3_FLAG_PAST_END_OF_REQ - Send after END_OF_REQUEST notification
|
|
to let UL know we are done
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
DWORD dwResponseFlags = 0;
|
|
|
|
if ( !VALID_W3_FLAGS( dwFlags ) ||
|
|
( dwFlags & W3_FLAG_NO_CUSTOM_ERROR ) )
|
|
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// Set the final send just in case we never did a SendResponse (which
|
|
// would normally have set this).
|
|
//
|
|
SetNeedFinalDone();
|
|
|
|
if (dwFlags & W3_FLAG_ASYNC)
|
|
{
|
|
SetLastIOPending(LOG_WRITE_IO);
|
|
}
|
|
|
|
BOOL fDoLogging = FALSE;
|
|
HRESULT hr;
|
|
if ((dwFlags & W3_FLAG_PAST_END_OF_REQ) &&
|
|
QueryDoUlLogging())
|
|
{
|
|
fDoLogging = TRUE;
|
|
if (FAILED(hr = QueryMainContext()->CollectLoggingData(TRUE)))
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Setup response flags
|
|
//
|
|
|
|
if ( !( dwFlags & W3_FLAG_PAST_END_OF_REQ ) )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_MORE_DATA;
|
|
}
|
|
|
|
if ( dwFlags & W3_FLAG_ASYNC )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_ASYNC;
|
|
}
|
|
|
|
if ( QueryDisconnect() )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_DISCONNECT;
|
|
}
|
|
|
|
if ( QueryRequest()->QueryVerbType() == HttpVerbHEAD &&
|
|
( QueryHandler() == NULL ||
|
|
!QueryHandler()->QueryManagesOwnHead() ) )
|
|
{
|
|
dwResponseFlags |= W3_RESPONSE_SUPPRESS_ENTITY;
|
|
}
|
|
|
|
//
|
|
// Do the send now
|
|
//
|
|
DWORD cbSent = 0;
|
|
hr = QueryResponse()->SendEntity(
|
|
this,
|
|
dwResponseFlags,
|
|
&cbSent,
|
|
fDoLogging ? QueryUlLogData() : NULL);
|
|
|
|
if (!(dwFlags & W3_FLAG_ASYNC))
|
|
{
|
|
IncrementBytesSent(cbSent);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
if (_pHandler == NULL)
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"SendEntity failed: no handler, hr %x\n",
|
|
hr));
|
|
}
|
|
else
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"SendEntity failed: handler %S, hr %x\n",
|
|
_pHandler->QueryName(),
|
|
hr));
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
DWORD
|
|
W3_CONTEXT::QueryRemainingEntityFromUl(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns how much entity can be read from UL
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
Number of bytes available (INFINITE if chunked)
|
|
|
|
--*/
|
|
{
|
|
return QueryMainContext()->QueryRemainingEntityFromUl();
|
|
}
|
|
|
|
VOID
|
|
W3_CONTEXT::SetRemainingEntityFromUl(
|
|
DWORD cbRemaining
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets how much entity can be read from UL
|
|
|
|
Arguments:
|
|
|
|
DWORD
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
QueryMainContext()->SetRemainingEntityFromUl(cbRemaining);
|
|
}
|
|
|
|
VOID
|
|
W3_CONTEXT::QueryAlreadyAvailableEntity(
|
|
VOID ** ppvBuffer,
|
|
DWORD * pcbBuffer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns the already preloaded entity found in the current request
|
|
|
|
Arguments:
|
|
|
|
ppvBuffer - filled with pointer to available entity
|
|
pcbBuffer - filled with size of buffer pointed to by *ppvBuffer
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
W3_REQUEST * pRequest;
|
|
|
|
DBG_ASSERT( ppvBuffer != NULL );
|
|
DBG_ASSERT( pcbBuffer != NULL );
|
|
|
|
pRequest = QueryRequest();
|
|
DBG_ASSERT( pRequest != NULL );
|
|
|
|
*ppvBuffer = pRequest->QueryEntityBody();
|
|
*pcbBuffer = pRequest->QueryAvailableBytes();
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::ReceiveEntity(
|
|
DWORD dwFlags,
|
|
VOID * pBuffer,
|
|
DWORD cbBuffer,
|
|
DWORD * pBytesReceived
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Receives request entity from the client thru ULATQ.
|
|
|
|
Arguments:
|
|
|
|
dwFlags - W3_FLAG_ASYNC or W3_FLAG_SYNC
|
|
pBuffer - Buffer to contain the data
|
|
cbBuffer - The size of the buffer
|
|
pBytesReceived - Upon return, the number of bytes
|
|
copied to the buffer
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
W3_MAIN_CONTEXT * pMainContext;
|
|
HRESULT hr;
|
|
|
|
if (dwFlags & W3_FLAG_ASYNC)
|
|
{
|
|
SetLastIOPending(LOG_READ_IO);
|
|
}
|
|
|
|
pMainContext = QueryMainContext();
|
|
DBG_ASSERT( pMainContext != NULL );
|
|
|
|
hr = pMainContext->ReceiveEntityBody( !!(dwFlags & W3_FLAG_ASYNC),
|
|
pBuffer,
|
|
cbBuffer,
|
|
pBytesReceived );
|
|
|
|
if (!(dwFlags & W3_FLAG_ASYNC))
|
|
{
|
|
IncrementBytesRecvd(*pBytesReceived);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
if (_pHandler == NULL)
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"ReceiveEntity failed: no handler, hr %x\n",
|
|
hr));
|
|
}
|
|
else if (hr != HRESULT_FROM_WIN32(ERROR_HANDLE_EOF))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"ReceiveEntity failed: handler %S, hr %x\n",
|
|
_pHandler->QueryName(),
|
|
hr));
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::CleanIsapiExecuteUrl(
|
|
HSE_EXEC_URL_INFO * pExecUrlInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Wrapper for IsapiExecuteUrl() which insured it is called on a clean
|
|
thread (non-coinited). COM bites
|
|
|
|
Arguments:
|
|
|
|
pExecUrlInfo - HSE_EXEC_URL_INFO
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
EXECUTE_CONTEXT * pExecuteContext;
|
|
HRESULT hr;
|
|
BOOL fRet;
|
|
|
|
if ( pExecUrlInfo == NULL )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
pExecuteContext = new EXECUTE_CONTEXT( this );
|
|
if ( pExecuteContext == NULL )
|
|
{
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
hr = pExecuteContext->InitializeFromExecUrlInfo( pExecUrlInfo );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
delete pExecuteContext;
|
|
return hr;
|
|
}
|
|
|
|
fRet = ThreadPoolPostCompletion( 0,
|
|
W3_CONTEXT::OnCleanIsapiExecuteUrl,
|
|
(LPOVERLAPPED) pExecuteContext );
|
|
if ( !fRet )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
delete pExecuteContext;
|
|
return hr;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::CleanIsapiSendCustomError(
|
|
HSE_CUSTOM_ERROR_INFO * pCustomErrorInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Wrapper for IsapiSendCustomError() which insured it is called on a clean
|
|
thread (non-coinited). COM bites
|
|
|
|
Arguments:
|
|
|
|
pCustomErrorInfo - Custom error info
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
EXECUTE_CONTEXT * pExecuteContext;
|
|
HRESULT hr;
|
|
HANDLE hEvent = NULL;
|
|
BOOL fRet;
|
|
W3_RESPONSE * pResponse = QueryResponse();
|
|
W3_METADATA * pMetaData = NULL;
|
|
STACK_STRU( strError, 256 );
|
|
BOOL fIsFileError;
|
|
HTTP_SUB_ERROR subError;
|
|
|
|
if ( pCustomErrorInfo == NULL )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
DBG_ASSERT( pResponse != NULL );
|
|
|
|
//
|
|
// If we're already sending a custom error, then stop the recursion
|
|
//
|
|
|
|
if ( !QuerySendCustomError() )
|
|
{
|
|
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
|
|
}
|
|
|
|
pResponse->Clear();
|
|
|
|
//
|
|
// We don't expect an empty status line here (we expect an error)
|
|
//
|
|
|
|
if ( pCustomErrorInfo->pszStatus == NULL ||
|
|
pCustomErrorInfo->pszStatus[ 0 ] == '\0' )
|
|
{
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// Setup the response status/substatus
|
|
//
|
|
|
|
hr = pResponse->BuildStatusFromIsapi( pCustomErrorInfo->pszStatus );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
subError.mdSubError = pCustomErrorInfo->uHttpSubError;
|
|
subError.dwStringId = 0;
|
|
pResponse->SetSubError( &subError );
|
|
|
|
//
|
|
// An error better have been sent
|
|
//
|
|
|
|
if ( pResponse->QueryStatusCode() < 400 )
|
|
{
|
|
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
|
|
}
|
|
|
|
//
|
|
// Now try to find a custom error for the given response. If it doesn't
|
|
// exist then we error our
|
|
//
|
|
|
|
DBG_ASSERT( QueryUrlContext() != NULL );
|
|
|
|
pMetaData = QueryUrlContext()->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
hr = pMetaData->FindCustomError( QueryResponse()->QueryStatusCode(),
|
|
subError.mdSubError,
|
|
&fIsFileError,
|
|
&strError );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
|
|
}
|
|
|
|
//
|
|
// Now start executing the custom error
|
|
//
|
|
|
|
if ( pCustomErrorInfo->fAsync == FALSE )
|
|
{
|
|
hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
if ( hEvent == NULL )
|
|
{
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
}
|
|
|
|
pExecuteContext = new EXECUTE_CONTEXT( this );
|
|
if ( pExecuteContext == NULL )
|
|
{
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
pExecuteContext->SetCompleteEvent( hEvent );
|
|
|
|
//
|
|
// Post the call
|
|
//
|
|
|
|
fRet = ThreadPoolPostCompletion( 0,
|
|
W3_CONTEXT::OnCleanIsapiSendCustomError,
|
|
(LPOVERLAPPED) pExecuteContext );
|
|
if ( !fRet )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
CloseHandle( hEvent );
|
|
pExecuteContext->SetCompleteEvent( NULL );
|
|
delete pExecuteContext;
|
|
return hr;
|
|
}
|
|
|
|
|
|
if ( pCustomErrorInfo->fAsync == FALSE )
|
|
{
|
|
WaitForSingleObject( hEvent, INFINITE );
|
|
CloseHandle( hEvent );
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::ExecuteHandler(
|
|
DWORD dwFlags,
|
|
BOOL * pfDidImmediateFinish
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Run the context's handler
|
|
|
|
Arguments:
|
|
|
|
dwFlags - W3_FLAG_ASYNC async
|
|
W3_FLAG_SYNC sync
|
|
W3_FLAG_MORE_DATA caller needs to send data too
|
|
|
|
pfDidImmediateFinish - If the caller sets this value (i.e. non NULL) AND
|
|
the callers asks for async execution, then in the
|
|
case where the handler executes synchronously, we
|
|
will set *pfDidImmediateFinish to TRUE and not
|
|
bother with faking a completion. This is an
|
|
optimization to handle the synchronous ISAPI
|
|
execution (non-child) case.
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
CONTEXT_STATUS status;
|
|
W3_HANDLER * pHandler;
|
|
HTTP_SUB_ERROR subError;
|
|
HRESULT hr;
|
|
|
|
if ( pfDidImmediateFinish != NULL )
|
|
{
|
|
*pfDidImmediateFinish = FALSE;
|
|
}
|
|
|
|
//
|
|
// If this is synchronous execution, then setup an event to signal
|
|
// upon the handler completion
|
|
//
|
|
|
|
if ( dwFlags & W3_FLAG_SYNC )
|
|
{
|
|
SetupCompletionEvent();
|
|
}
|
|
|
|
//
|
|
// Clear any response lingering
|
|
//
|
|
|
|
hr = QueryResponse()->Clear();
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Redirect all completions to the current context (this)
|
|
//
|
|
|
|
QueryMainContext()->PushCurrentContext( this );
|
|
|
|
//
|
|
// Execute the handler
|
|
//
|
|
|
|
status = ExecuteCurrentHandler();
|
|
|
|
if ( status == CONTEXT_STATUS_CONTINUE )
|
|
{
|
|
//
|
|
// Remember not to execute completion routine for handler
|
|
//
|
|
|
|
DBG_ASSERT( _pHandler != NULL );
|
|
|
|
//
|
|
// We don't need the handler any more
|
|
//
|
|
|
|
delete _pHandler;
|
|
_pHandler = NULL;
|
|
|
|
//
|
|
// The handler completed synchronously. If async call was required
|
|
// then post a fake completion, unless the caller set
|
|
// pfDidImmediateFinish in which case we'll finish now and set the
|
|
// flag to TRUE
|
|
//
|
|
|
|
if ( dwFlags & W3_FLAG_ASYNC &&
|
|
pfDidImmediateFinish == NULL )
|
|
{
|
|
if ( ThreadPoolPostCompletion( 0,
|
|
W3_MAIN_CONTEXT::OnPostedCompletion,
|
|
(LPOVERLAPPED) QueryMainContext() ) )
|
|
{
|
|
return NO_ERROR;
|
|
}
|
|
|
|
DBG_ASSERT( FALSE );
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We indicate the "success/failure" of this child context
|
|
// to the parent context, so that the parent can send this
|
|
// info to ISAPIs if needed
|
|
//
|
|
|
|
if ( QueryParentContext() != NULL )
|
|
{
|
|
QueryResponse()->QuerySubError( &subError );
|
|
|
|
QueryParentContext()->SetChildStatusAndError(
|
|
QueryResponse()->QueryStatusCode(),
|
|
subError.mdSubError,
|
|
QueryErrorStatus() );
|
|
}
|
|
|
|
//
|
|
// Don't both signalling completion event since there is nothing
|
|
// to wait for. (Might revisit this decision later)
|
|
//
|
|
|
|
//
|
|
// Current context is no longer needed in completion stack
|
|
//
|
|
|
|
QueryMainContext()->PopCurrentContext();
|
|
|
|
//
|
|
// If caller wanted status, tell them now
|
|
//
|
|
|
|
if ( pfDidImmediateFinish != NULL )
|
|
{
|
|
*pfDidImmediateFinish = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT( status == CONTEXT_STATUS_PENDING );
|
|
|
|
//
|
|
// The handler will complete asynchronsouly. But we are asked for
|
|
// synchronous execution. So wait here until complete
|
|
//
|
|
|
|
if ( dwFlags & W3_FLAG_SYNC )
|
|
{
|
|
WaitForCompletion();
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
CONTEXT_STATUS
|
|
W3_CONTEXT::ExecuteCurrentHandler(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Execute the handler for this context
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
CONTEXT_STATUS_PENDING if async pending, else CONTEXT_STATUS_CONTINUE
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr;
|
|
|
|
DBG_ASSERT( _pHandler != NULL );
|
|
|
|
//
|
|
// Now check if we should be doing redirection for this request
|
|
//
|
|
if (QueryRequest()->QueryVerbType() != HttpVerbTRACE &&
|
|
QueryRequest()->QueryVerbType() != HttpVerbTRACK)
|
|
{
|
|
BOOL fRedirected = FALSE;
|
|
if (FAILED(DoUrlRedirection(&fRedirected)) ||
|
|
fRedirected)
|
|
{
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Access is allowed and no URL redirection. Let'r rip!
|
|
//
|
|
|
|
return _pHandler->MainDoWork();
|
|
}
|
|
|
|
CONTEXT_STATUS
|
|
W3_CONTEXT::ExecuteHandlerCompletion(
|
|
DWORD cbCompletion,
|
|
DWORD dwCompletionStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Execute the current handler's completion.
|
|
|
|
Arguments:
|
|
|
|
cbCompletion - Completion bytes
|
|
dwCompletionStatus - Completion error
|
|
|
|
Return Value:
|
|
|
|
CONTEXT_STATUS_PENDING if async pending, else CONTEXT_STATUS_CONTINUE
|
|
|
|
--*/
|
|
{
|
|
CONTEXT_STATUS status;
|
|
W3_HANDLER * pHandler;
|
|
W3_CONTEXT * pParentContext;
|
|
DWORD dwChildStatus;
|
|
BOOL fAccessAllowed = FALSE;
|
|
HRESULT hr;
|
|
HTTP_SUB_ERROR subError;
|
|
|
|
//
|
|
// This is a completion for the handler.
|
|
//
|
|
// If this is a faked completion, the handler is already cleaned up
|
|
//
|
|
|
|
if ( _pHandler != NULL )
|
|
{
|
|
status = _pHandler->MainOnCompletion( cbCompletion,
|
|
dwCompletionStatus );
|
|
|
|
}
|
|
else
|
|
{
|
|
status = CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
|
|
if ( status == CONTEXT_STATUS_PENDING )
|
|
{
|
|
return status;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Cleanup the current context if necessary
|
|
//
|
|
|
|
DBG_ASSERT( status == CONTEXT_STATUS_CONTINUE );
|
|
|
|
//
|
|
// Current handler execute is complete. Delete it
|
|
//
|
|
|
|
if ( _pHandler != NULL )
|
|
{
|
|
delete _pHandler;
|
|
_pHandler = NULL;
|
|
}
|
|
|
|
//
|
|
// The current handler is complete. But if this is a child
|
|
// request we must first indicate handler completion to the caller
|
|
//
|
|
|
|
pParentContext = QueryParentContext();
|
|
if ( pParentContext != NULL )
|
|
{
|
|
//
|
|
// We indicate the "success/failure" of this child context
|
|
// to the parent context, so that the parent can send this
|
|
// info to ISAPIs if needed
|
|
//
|
|
|
|
QueryResponse()->QuerySubError( &subError );
|
|
|
|
pParentContext->SetChildStatusAndError(
|
|
QueryResponse()->QueryStatusCode(),
|
|
subError.mdSubError,
|
|
QueryErrorStatus() );
|
|
|
|
//
|
|
// Setup all future completions to indicate to parent
|
|
//
|
|
|
|
QueryMainContext()->PopCurrentContext();
|
|
|
|
//
|
|
// We are a child execute
|
|
//
|
|
|
|
if ( QueryIsSynchronous() )
|
|
{
|
|
IndicateCompletion();
|
|
|
|
//
|
|
// We assume the thread waiting on completion will be
|
|
// responsible for advancing the state machine
|
|
//
|
|
|
|
//
|
|
// The waiting thread will also cleanup the child context
|
|
//
|
|
|
|
return CONTEXT_STATUS_PENDING;
|
|
}
|
|
else
|
|
{
|
|
dwChildStatus = WIN32_FROM_HRESULT( QueryErrorStatus() );
|
|
|
|
//
|
|
// We can destroy the current context now. In the case of
|
|
// synchronous execution, it is the caller that cleans up
|
|
// the new context
|
|
//
|
|
|
|
delete this;
|
|
|
|
return pParentContext->ExecuteHandlerCompletion(
|
|
0,
|
|
dwChildStatus );
|
|
}
|
|
}
|
|
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
}
|
|
|
|
W3_CONTEXT::W3_CONTEXT( DWORD dwExecFlags )
|
|
: _pHandler ( NULL ),
|
|
_hCompletion ( NULL ),
|
|
_errorStatus ( S_OK ),
|
|
_pCustomErrorFile ( NULL ),
|
|
_dwExecFlags ( dwExecFlags ),
|
|
_accessState ( ACCESS_STATE_START ),
|
|
_fDNSRequiredForAccess ( FALSE ),
|
|
_fAuthAccessCheckRequired ( TRUE ),
|
|
_childStatusCode ( 200 ),
|
|
_childSubErrorCode ( 0 ),
|
|
_childError ( S_OK )
|
|
{
|
|
_dwSignature = W3_CONTEXT_SIGNATURE;
|
|
}
|
|
|
|
W3_CONTEXT::~W3_CONTEXT()
|
|
{
|
|
_dwSignature = W3_CONTEXT_SIGNATURE_FREE;
|
|
|
|
if ( _pHandler != NULL )
|
|
{
|
|
delete _pHandler;
|
|
_pHandler = NULL;
|
|
}
|
|
|
|
if ( _hCompletion != NULL )
|
|
{
|
|
CloseHandle( _hCompletion );
|
|
_hCompletion = NULL;
|
|
}
|
|
|
|
if ( _pCustomErrorFile != NULL )
|
|
{
|
|
_pCustomErrorFile->DereferenceCacheEntry();
|
|
_pCustomErrorFile = NULL;
|
|
}
|
|
}
|
|
|
|
// static
|
|
HRESULT
|
|
W3_CONTEXT::Initialize(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Global initialization routine for W3_CONTEXTs
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = NO_ERROR;
|
|
HKEY hKey = NULL;
|
|
DWORD dwError;
|
|
DWORD dwType;
|
|
DWORD cbBuffer;
|
|
BYTE bUnused;
|
|
|
|
//
|
|
// Read in the 302 message once
|
|
//
|
|
|
|
DBG_ASSERT( g_pW3Server != NULL );
|
|
|
|
sm_cbRedirectMessage = sizeof( sm_achRedirectMessage );
|
|
|
|
hr = g_pW3Server->LoadString( IDS_URL_MOVED,
|
|
sm_achRedirectMessage,
|
|
&sm_cbRedirectMessage );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
DBG_ASSERT( sm_cbRedirectMessage > 0 );
|
|
|
|
//
|
|
// Read the Access-Denied message from registry
|
|
//
|
|
|
|
dwError = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
W3_PARAMETERS_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&hKey );
|
|
if ( dwError == ERROR_SUCCESS )
|
|
{
|
|
DBG_ASSERT( hKey != NULL );
|
|
|
|
cbBuffer = 0;
|
|
dwType = 0;
|
|
|
|
dwError = RegQueryValueExA( hKey,
|
|
"AccessDeniedMessage",
|
|
NULL,
|
|
&dwType,
|
|
&bUnused,
|
|
&cbBuffer );
|
|
|
|
if ( dwError == ERROR_MORE_DATA && dwType == REG_SZ )
|
|
{
|
|
DBG_ASSERT( cbBuffer > 0 );
|
|
|
|
sm_pszAccessDeniedMessage = (CHAR*) LocalAlloc( LPTR, cbBuffer );
|
|
if ( sm_pszAccessDeniedMessage == NULL )
|
|
{
|
|
return HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
dwError = RegQueryValueExA( hKey,
|
|
"AccessDeniedMessage",
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE) sm_pszAccessDeniedMessage,
|
|
&cbBuffer );
|
|
|
|
if ( dwError != ERROR_SUCCESS )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
|
|
LocalFree( sm_pszAccessDeniedMessage );
|
|
sm_pszAccessDeniedMessage = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Setup child contexts
|
|
//
|
|
|
|
hr = W3_CHILD_CONTEXT::Initialize();
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Setup main contexts
|
|
//
|
|
|
|
hr = W3_MAIN_CONTEXT::Initialize();
|
|
if ( FAILED( hr ) )
|
|
{
|
|
W3_CHILD_CONTEXT::Terminate();
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// Setup execute contexts
|
|
//
|
|
|
|
hr = EXECUTE_CONTEXT::Initialize();
|
|
if ( FAILED( hr ) )
|
|
{
|
|
W3_MAIN_CONTEXT::Terminate();
|
|
W3_CHILD_CONTEXT::Terminate();
|
|
return hr;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// static
|
|
VOID
|
|
W3_CONTEXT::Terminate(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Terminate W3_CONTEXT globals
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
EXECUTE_CONTEXT::Terminate();
|
|
|
|
W3_CHILD_CONTEXT::Terminate();
|
|
|
|
W3_MAIN_CONTEXT::Terminate();
|
|
|
|
if ( sm_pszAccessDeniedMessage != NULL )
|
|
{
|
|
LocalFree( sm_pszAccessDeniedMessage );
|
|
sm_pszAccessDeniedMessage = NULL;
|
|
}
|
|
}
|
|
|
|
//static
|
|
VOID
|
|
W3_CONTEXT::OnCleanIsapiExecuteUrl(
|
|
DWORD dwCompletionStatus,
|
|
DWORD cbWritten,
|
|
LPOVERLAPPED lpo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Actually calls ExecuteHandler() on behalf of a thread which may have
|
|
already been CoInited(). The thread this this guy runs on should not
|
|
have been (COM sucks)
|
|
|
|
Arguments:
|
|
|
|
dwCompletionStatus - Completion status (ignored)
|
|
cbWritten - Bytes written (ignored)
|
|
lpo - Pointer to EXECUTE_CONTEXT which contains info needed for
|
|
ExecuteHandler() call
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
EXECUTE_CONTEXT * pExecuteContext;
|
|
W3_CONTEXT * pW3Context;
|
|
HRESULT hr;
|
|
|
|
DBG_ASSERT( lpo != NULL );
|
|
|
|
pExecuteContext = (EXECUTE_CONTEXT*) lpo;
|
|
DBG_ASSERT( pExecuteContext->CheckSignature() );
|
|
|
|
pW3Context = pExecuteContext->QueryW3Context();
|
|
DBG_ASSERT( pW3Context != NULL );
|
|
DBG_ASSERT( pW3Context->CheckSignature() );
|
|
|
|
//
|
|
// Make the call (this is an async call)
|
|
//
|
|
|
|
hr = pW3Context->IsapiExecuteUrl( pExecuteContext->QueryExecUrlInfo() );
|
|
|
|
delete pExecuteContext;
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
//
|
|
// We failed before posting async operation. It is up to us to
|
|
// fake a completion
|
|
//
|
|
|
|
DBG_ASSERT( pW3Context->QueryHandler() != NULL );
|
|
|
|
pW3Context->SetErrorStatus( hr );
|
|
|
|
pW3Context->QueryHandler()->MainOnCompletion( 0,
|
|
WIN32_FROM_HRESULT( hr ) );
|
|
}
|
|
}
|
|
|
|
//static
|
|
VOID
|
|
W3_CONTEXT::OnCleanIsapiSendCustomError(
|
|
DWORD dwCompletionStatus,
|
|
DWORD cbWritten,
|
|
LPOVERLAPPED lpo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Actually calls ExecuteHandler() on behalf of a thread which may have
|
|
already been CoInited(). The thread this this guy runs on should not
|
|
have been (COM sucks)
|
|
|
|
Arguments:
|
|
|
|
dwCompletionStatus - Completion status (ignored)
|
|
cbWritten - Bytes written (ignored)
|
|
lpo - Pointer to SEND_CUSTOM_ERROR_CONTEXT which contains info needed for
|
|
ExecuteHandler() call
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
EXECUTE_CONTEXT * pExecuteContext;
|
|
W3_CONTEXT * pW3Context;
|
|
HRESULT hr;
|
|
BOOL fAsync;
|
|
|
|
DBG_ASSERT( lpo != NULL );
|
|
|
|
pExecuteContext = (EXECUTE_CONTEXT*) lpo;
|
|
DBG_ASSERT( pExecuteContext->CheckSignature() );
|
|
|
|
pW3Context = pExecuteContext->QueryW3Context();
|
|
DBG_ASSERT( pW3Context != NULL );
|
|
DBG_ASSERT( pW3Context->CheckSignature() );
|
|
|
|
fAsync = pExecuteContext->QueryCompleteEvent() == NULL;
|
|
|
|
hr = pW3Context->SendResponse( fAsync ? W3_FLAG_ASYNC : W3_FLAG_SYNC );
|
|
|
|
delete pExecuteContext;
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
if ( fAsync )
|
|
{
|
|
//
|
|
// We failed before posting async operation. It is up to us to
|
|
// fake a completion
|
|
//
|
|
|
|
DBG_ASSERT( pW3Context->QueryHandler() != NULL );
|
|
|
|
pW3Context->SetErrorStatus( hr );
|
|
|
|
pW3Context->QueryHandler()->MainOnCompletion( 0,
|
|
WIN32_FROM_HRESULT( hr ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
CHUNK_BUFFER *
|
|
W3_CONTEXT::QueryHeaderBuffer(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get header buffer to store data which must be around until
|
|
request is finished
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
CHUNK_BUFFER pointer
|
|
|
|
--*/
|
|
{
|
|
DBG_ASSERT( QueryMainContext() != NULL );
|
|
return QueryMainContext()->QueryHeaderBuffer();
|
|
}
|
|
|
|
CONTEXT_STATUS
|
|
W3_CONTEXT::CheckAccess(
|
|
BOOL fCompletion,
|
|
DWORD dwCompletionStatus,
|
|
BOOL * pfAccessAllowed
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check whether the given request is allowed for the current metadata
|
|
settings. In particular check:
|
|
1) Is SSL required?
|
|
2) Are IP address restrictions in force?
|
|
3) Is a client certificate required?
|
|
4) Is the authentication mechanism allowed? Optionally
|
|
|
|
If access is not allowed, then this routine will send the appropriate
|
|
response asychronously
|
|
|
|
Arguments:
|
|
|
|
fCompletion - Are we being called on a completion (i.e.
|
|
is this a subsequent call to CheckAccess())
|
|
dwCompletionStatus - Completion status (if fCompletion is TRUE)
|
|
pfAccessAllowed - Set to TRUE if access is allowed, else FALSE
|
|
|
|
Return Value:
|
|
|
|
CONTEXT_STATUS_PENDING if we're not finished the check yet
|
|
CONTEXT_STATUS_CONTINUE if we are finished.
|
|
|
|
--*/
|
|
{
|
|
W3_METADATA * pMetaData;
|
|
URL_CONTEXT * pUrlContext;
|
|
HTTP_SSL_INFO * pSslInfo;
|
|
HTTP_SSL_CLIENT_CERT_INFO * pClientCertInfo = NULL;
|
|
DWORD dwAccessPerms;
|
|
HRESULT hr = NO_ERROR;
|
|
BOOL fAccessAllowed = TRUE;
|
|
ADDRESS_CHECK * pAddressCheck = NULL;
|
|
AC_RESULT acResult;
|
|
BOOL fSyncDNS = FALSE;
|
|
LPSTR pszTemp;
|
|
sockaddr_in remoteAddress;
|
|
BOOL fDoCertMap;
|
|
|
|
if ( pfAccessAllowed == NULL )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
SetErrorStatus( HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ) );
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
*pfAccessAllowed = FALSE;
|
|
|
|
pUrlContext = QueryUrlContext();
|
|
DBG_ASSERT( pUrlContext != NULL );
|
|
|
|
pMetaData = pUrlContext->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
dwAccessPerms = pMetaData->QueryAccessPerms();
|
|
|
|
switch ( _accessState )
|
|
{
|
|
case ACCESS_STATE_SENDING_ERROR:
|
|
|
|
//
|
|
// We have sent an error. Just chill
|
|
//
|
|
|
|
fAccessAllowed = FALSE;
|
|
break;
|
|
|
|
case ACCESS_STATE_START:
|
|
|
|
//
|
|
// Is SSL required?
|
|
//
|
|
|
|
if ( dwAccessPerms & VROOT_MASK_SSL &&
|
|
!QueryRequest()->IsSecureRequest() )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403SSLRequired );
|
|
goto AccessDenied;
|
|
}
|
|
|
|
//
|
|
// Is 128bit required?
|
|
//
|
|
|
|
if ( dwAccessPerms & VROOT_MASK_SSL128 )
|
|
{
|
|
pSslInfo = QueryRequest()->QuerySslInfo();
|
|
|
|
if ( pSslInfo == NULL ||
|
|
pSslInfo->ConnectionKeySize < 128 )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403SSL128Required );
|
|
goto AccessDenied;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Fall through to next phase in access check
|
|
//
|
|
|
|
case ACCESS_STATE_CLIENT_CERT:
|
|
|
|
_accessState = ACCESS_STATE_CLIENT_CERT;
|
|
|
|
//
|
|
// Is a client certificate possible? First check is we even allow
|
|
// a client cert to be negotiated and that this is a secure request
|
|
//
|
|
|
|
if ( dwAccessPerms & VROOT_MASK_NEGO_CERT &&
|
|
QueryRequest()->IsSecureRequest() )
|
|
{
|
|
//
|
|
// Try for a client cert if we don't already have one associated
|
|
// with the request
|
|
//
|
|
|
|
if ( QueryCertificateContext() == NULL )
|
|
{
|
|
if ( fCompletion )
|
|
{
|
|
fCompletion = FALSE;
|
|
|
|
//
|
|
// If we got an error, that's OK. We will fall thru
|
|
// and check whether we actually need a client cert
|
|
//
|
|
|
|
if ( dwCompletionStatus == NO_ERROR )
|
|
{
|
|
//
|
|
// Are we cert mapping?
|
|
//
|
|
|
|
fDoCertMap = !!(dwAccessPerms & VROOT_MASK_MAP_CERT);
|
|
|
|
//
|
|
// All is well. Make a synchronous call to get
|
|
// the certificate
|
|
//
|
|
|
|
hr = UlAtqReceiveClientCertificate(
|
|
QueryUlatqContext(),
|
|
FALSE, // sync
|
|
fDoCertMap,
|
|
&pClientCertInfo );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusServerError );
|
|
goto AccessDenied;
|
|
}
|
|
|
|
DBG_ASSERT( pClientCertInfo != NULL );
|
|
|
|
//
|
|
// Setup a client cert context for this request
|
|
//
|
|
|
|
hr = QueryMainContext()->SetupCertificateContext( pClientCertInfo );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusServerError );
|
|
goto AccessDenied;
|
|
}
|
|
|
|
//
|
|
// Just because we got a client cert, doesn't mean
|
|
// it is acceptable. It might be revoked, time
|
|
// expired, etc. The policy of what to do when
|
|
// certs are non-totally-valid is metabase driven.
|
|
// (in case you are wondering why the stream filter
|
|
// doesn't do these checks and just fail the
|
|
// renegotiation)
|
|
//
|
|
|
|
if ( !CheckClientCertificateAccess() )
|
|
{
|
|
goto AccessDenied;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Are we cert mapping?
|
|
//
|
|
|
|
fDoCertMap = !!(dwAccessPerms & VROOT_MASK_MAP_CERT);
|
|
|
|
//
|
|
// First time asking for a client cert. Do it
|
|
// async
|
|
//
|
|
|
|
fCompletion = FALSE;
|
|
|
|
hr = UlAtqReceiveClientCertificate(
|
|
QueryUlatqContext(),
|
|
TRUE, // async
|
|
fDoCertMap,
|
|
&pClientCertInfo );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusServerError );
|
|
goto AccessDenied;
|
|
}
|
|
|
|
return CONTEXT_STATUS_PENDING;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Ok. We're done the certificate crud. If we needed a client
|
|
// cert and didn't get it, then setup 403.7
|
|
//
|
|
|
|
if ( dwAccessPerms & VROOT_MASK_NEGO_MANDATORY &&
|
|
QueryCertificateContext() == NULL )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403CertRequired );
|
|
goto AccessDenied;
|
|
}
|
|
|
|
//
|
|
// Fall through to next phase in access check
|
|
//
|
|
|
|
case ACCESS_STATE_RDNS:
|
|
|
|
_accessState = ACCESS_STATE_RDNS;
|
|
|
|
pAddressCheck = QueryMainContext()->QueryAddressCheck();
|
|
DBG_ASSERT( pAddressCheck != NULL );
|
|
|
|
if ( !fCompletion )
|
|
{
|
|
if ( pMetaData->QueryIpAccessCheckSize() != 0 ||
|
|
pMetaData->QueryDoReverseDNS() )
|
|
{
|
|
//
|
|
// Setup the RDNS crud (joy)
|
|
//
|
|
|
|
remoteAddress.sin_family = AF_INET;
|
|
remoteAddress.sin_port = QueryRequest()->QueryRemotePort();
|
|
remoteAddress.sin_addr.s_addr = ntohl( QueryRequest()->QueryRemoteAddress() );
|
|
ZeroMemory( remoteAddress.sin_zero, sizeof( remoteAddress.sin_zero ) );
|
|
|
|
pAddressCheck->BindAddr( (sockaddr*) &remoteAddress );
|
|
}
|
|
|
|
//
|
|
// Ok. If there is an access check set in metabase, then
|
|
// do the check.
|
|
//
|
|
|
|
if ( pMetaData->QueryIpAccessCheckSize() != 0 )
|
|
{
|
|
//
|
|
// Set the metadata IP blob
|
|
//
|
|
|
|
pAddressCheck->BindCheckList(
|
|
pMetaData->QueryIpAccessCheckBuffer(),
|
|
pMetaData->QueryIpAccessCheckSize() );
|
|
|
|
//
|
|
// Check access
|
|
//
|
|
|
|
acResult = pAddressCheck->CheckIpAccess( &_fDNSRequiredForAccess );
|
|
|
|
if ( !_fDNSRequiredForAccess )
|
|
{
|
|
pAddressCheck->UnbindCheckList();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Fake a valid access check since there was no checks
|
|
// configured in metabase
|
|
//
|
|
|
|
acResult = AC_NO_LIST;
|
|
}
|
|
|
|
//
|
|
// Check if we now know our access status now
|
|
//
|
|
|
|
if ( acResult == AC_IN_DENY_LIST ||
|
|
( acResult == AC_NOT_IN_GRANT_LIST &&
|
|
!_fDNSRequiredForAccess ) )
|
|
{
|
|
//
|
|
// We know we're rejected
|
|
//
|
|
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403IPAddressReject );
|
|
goto AccessDenied;
|
|
}
|
|
|
|
//
|
|
// Do we need to do a DNS check to determine access?
|
|
// Do we need to do DNS check for logging/servervar purposes?
|
|
//
|
|
// In either case, we will do an async DNS check
|
|
//
|
|
|
|
if ( _fDNSRequiredForAccess ||
|
|
pMetaData->QueryDoReverseDNS() )
|
|
{
|
|
fSyncDNS = TRUE;
|
|
|
|
if ( !pAddressCheck->QueryDnsName( &fSyncDNS,
|
|
W3_MAIN_CONTEXT::AddressResolutionCallback,
|
|
QueryMainContext(),
|
|
&pszTemp ) )
|
|
{
|
|
//
|
|
// Only error if DNS was required for access check
|
|
// purposes
|
|
//
|
|
|
|
if ( _fDNSRequiredForAccess )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403IPAddressReject );
|
|
goto AccessDenied;
|
|
}
|
|
}
|
|
|
|
if ( fSyncDNS )
|
|
{
|
|
//
|
|
// Fake a completion if needed. This just prevents us from
|
|
// posting one more time to the thread pool
|
|
//
|
|
|
|
fCompletion = TRUE;
|
|
}
|
|
else
|
|
{
|
|
return CONTEXT_STATUS_PENDING;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( fCompletion )
|
|
{
|
|
fCompletion = FALSE;
|
|
|
|
//
|
|
// This is the completion for DNS check
|
|
//
|
|
|
|
if ( _fDNSRequiredForAccess )
|
|
{
|
|
_fDNSRequiredForAccess = FALSE;
|
|
|
|
acResult = pAddressCheck->CheckDnsAccess();
|
|
|
|
pAddressCheck->UnbindCheckList();
|
|
|
|
if ( acResult == AC_NOT_CHECKED ||
|
|
acResult == AC_IN_DENY_LIST ||
|
|
acResult == AC_NOT_IN_GRANT_LIST )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403IPAddressReject );
|
|
goto AccessDenied;
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// Fall through to next phase in access check
|
|
//
|
|
|
|
case ACCESS_STATE_AUTHENTICATION:
|
|
|
|
//
|
|
// Is the authentication method valid? We may not have a user
|
|
// context available at this point if this was an early
|
|
// custom error URL invocation
|
|
//
|
|
|
|
_accessState = ACCESS_STATE_AUTHENTICATION;
|
|
|
|
if ( QueryAuthAccessCheckRequired() &&
|
|
QueryUserContext() != NULL )
|
|
{
|
|
if ( !( pMetaData->QueryAuthentication() &
|
|
QueryUserContext()->QueryAuthType() ) )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusUnauthorized,
|
|
Http401Config );
|
|
goto AccessDenied;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// We know the access status (allowed or denied)
|
|
//
|
|
|
|
_accessState = ACCESS_STATE_DONE;
|
|
*pfAccessAllowed = fAccessAllowed;
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
|
|
AccessDenied:
|
|
*pfAccessAllowed = FALSE;
|
|
_accessState = ACCESS_STATE_SENDING_ERROR;
|
|
|
|
//
|
|
// Send back the bad access response
|
|
//
|
|
|
|
hr = SendResponse( W3_FLAG_ASYNC );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
_accessState = ACCESS_STATE_DONE;
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
return CONTEXT_STATUS_PENDING;
|
|
}
|
|
}
|
|
|
|
CERTIFICATE_CONTEXT *
|
|
W3_CONTEXT::QueryCertificateContext(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get client cert info object. We get it from the main context
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
Pointer to client cert object
|
|
|
|
--*/
|
|
{
|
|
return QueryMainContext()->QueryCertificateContext();
|
|
}
|
|
|
|
BOOL
|
|
W3_CONTEXT::CheckClientCertificateAccess(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check the context's certificate for validity
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
TRUE if the cert is valid, else FALSE
|
|
|
|
--*/
|
|
{
|
|
W3_MAIN_CONTEXT * pMainContext;
|
|
CERTIFICATE_CONTEXT * pCertificateContext;
|
|
DWORD dwCertFlags;
|
|
|
|
pMainContext = QueryMainContext();
|
|
DBG_ASSERT( pMainContext != NULL );
|
|
|
|
pCertificateContext = pMainContext->QueryCertificateContext();
|
|
if ( pCertificateContext == NULL )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
dwCertFlags = pCertificateContext->QueryFlags();
|
|
|
|
if ( dwCertFlags & CERT_TRUST_IS_UNTRUSTED_ROOT ||
|
|
dwCertFlags & CERT_TRUST_IS_NOT_SIGNATURE_VALID ||
|
|
dwCertFlags & CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID ||
|
|
dwCertFlags & CERT_TRUST_IS_NOT_VALID_FOR_USAGE )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403CertInvalid );
|
|
return FALSE;
|
|
}
|
|
else if ( dwCertFlags & CERT_TRUST_IS_NOT_TIME_VALID ||
|
|
dwCertFlags & CERT_TRUST_CTL_IS_NOT_TIME_VALID )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403CertTimeInvalid );
|
|
return FALSE;
|
|
}
|
|
else if ( dwCertFlags & CERT_TRUST_IS_REVOKED )
|
|
{
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403CertRevoked );
|
|
return FALSE;
|
|
}
|
|
else if ( dwCertFlags != 0 )
|
|
{
|
|
//
|
|
// this case is to prevent new error flags added by CAPI to be ignored by IIS
|
|
// if there is a flag IIS doesn't explicitly recognize, let's return Http403CertInvalid
|
|
//
|
|
|
|
QueryResponse()->SetStatus( HttpStatusForbidden,
|
|
Http403CertInvalid );
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::CheckPathInfoExists(
|
|
W3_HANDLER ** ppHandler
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Utility to check whether path info exists. If it does exist, this
|
|
function succeeds without setting the handler.
|
|
|
|
If the file doesn't exist, this function succeeds but sets a general
|
|
handler to send the error
|
|
|
|
Arguments:
|
|
|
|
ppHandler - Set to handler
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr = NO_ERROR;
|
|
W3_FILE_INFO * pOpenFile = NULL;
|
|
W3_HANDLER * pHandler = NULL;
|
|
URL_CONTEXT * pUrlContext;
|
|
FILE_CACHE_USER fileUser;
|
|
|
|
if ( ppHandler == NULL )
|
|
{
|
|
DBG_ASSERT( FALSE );
|
|
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
*ppHandler = NULL;
|
|
|
|
pUrlContext = QueryUrlContext();
|
|
DBG_ASSERT( pUrlContext != NULL );
|
|
|
|
QueryFileCacheUser( &fileUser );
|
|
|
|
hr = pUrlContext->OpenFile( &fileUser, &pOpenFile );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
DBG_ASSERT( pOpenFile == NULL );
|
|
|
|
switch ( WIN32_FROM_HRESULT( hr ) )
|
|
{
|
|
case ERROR_FILE_NOT_FOUND:
|
|
case ERROR_PATH_NOT_FOUND:
|
|
pHandler = new W3_GENERAL_HANDLER( this,
|
|
HttpStatusNotFound );
|
|
break;
|
|
|
|
case ERROR_INVALID_PARAMETER:
|
|
pHandler = new W3_GENERAL_HANDLER( this,
|
|
HttpStatusUrlTooLong );
|
|
break;
|
|
|
|
case ERROR_ACCESS_DENIED:
|
|
case ERROR_ACCOUNT_DISABLED:
|
|
case ERROR_LOGON_FAILURE:
|
|
pHandler = new W3_GENERAL_HANDLER( this,
|
|
HttpStatusUnauthorized,
|
|
Http401Application );
|
|
break;
|
|
|
|
default:
|
|
pHandler = new W3_GENERAL_HANDLER( this,
|
|
HttpStatusServerError );
|
|
break;
|
|
}
|
|
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
|
|
}
|
|
else
|
|
{
|
|
hr = NO_ERROR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT( pOpenFile != NULL );
|
|
|
|
pOpenFile->DereferenceCacheEntry();
|
|
}
|
|
|
|
*ppHandler = pHandler;
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::DetermineHandler(
|
|
BOOL fEnableWildcardMapping
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine the handler for a given request, and setup it.
|
|
|
|
How to determine handler is driven by the Magic Flow Chart (TM)
|
|
|
|
Arguments:
|
|
|
|
fEnableWildcardMapping - Enable wildcard mapping
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
W3_REQUEST * pRequest;
|
|
URL_CONTEXT * pUrlContext;
|
|
W3_METADATA * pMetaData;
|
|
HRESULT hr = NO_ERROR;
|
|
W3_HANDLER * pHandler = NULL;
|
|
META_SCRIPT_MAP_ENTRY * pScriptMapEntry = NULL;
|
|
STACK_STRA( strHeaderValue, 10 );
|
|
STACK_STRA( strVerb, 10 );
|
|
HTTP_VERB VerbType;
|
|
BOOL fKnownVerb = FALSE;
|
|
DWORD dwFilePerms;
|
|
GATEWAY_TYPE GatewayType;
|
|
BOOL fAccessAllowed = FALSE;
|
|
BOOL fSuspectUrl = FALSE;
|
|
CHAR * szHeaderValue = NULL;
|
|
|
|
//
|
|
// We shouldn't have a handler set for this request
|
|
//
|
|
|
|
DBG_ASSERT( _pHandler == NULL );
|
|
|
|
pRequest = QueryRequest();
|
|
DBG_ASSERT( pRequest != NULL );
|
|
|
|
pUrlContext = QueryUrlContext();
|
|
DBG_ASSERT( pUrlContext != NULL );
|
|
|
|
pMetaData = pUrlContext->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
//
|
|
// First check for a * script map. If one exists, use it if allowed
|
|
//
|
|
|
|
VerbType = pRequest->QueryVerbType();
|
|
|
|
if ( VerbType == HttpVerbTRACE ||
|
|
VerbType == HttpVerbTRACK )
|
|
{
|
|
pHandler = new W3_TRACE_HANDLER( this );
|
|
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
|
|
}
|
|
|
|
goto Finished;
|
|
}
|
|
|
|
pScriptMapEntry = pMetaData->QueryScriptMap()->QueryStarScriptMap();
|
|
if ( pScriptMapEntry != NULL &&
|
|
fEnableWildcardMapping )
|
|
{
|
|
//
|
|
// If there is a ./ in the URL, then always check for existence. This
|
|
// prevents a trailing . metabase equivilency problem
|
|
//
|
|
|
|
fSuspectUrl = pRequest->IsSuspectUrl();
|
|
|
|
if ( pScriptMapEntry->QueryCheckPathInfoExists() ||
|
|
fSuspectUrl )
|
|
{
|
|
hr = CheckPathInfoExists( &pHandler );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
if ( pHandler != NULL )
|
|
{
|
|
//
|
|
// We already have an error handler for this request.
|
|
// That means path info didn't really exist
|
|
//
|
|
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create the appropriate handler for the script mapping
|
|
//
|
|
|
|
if ( pScriptMapEntry->QueryGateway() == GATEWAY_CGI )
|
|
{
|
|
pHandler = new W3_CGI_HANDLER( this, pScriptMapEntry );
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT( pScriptMapEntry->QueryGateway() == GATEWAY_ISAPI );
|
|
pHandler = new W3_ISAPI_HANDLER( this, pScriptMapEntry );
|
|
}
|
|
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Check for Translate: f
|
|
//
|
|
// If there, it goes to DAVFS unless DAV is disabled
|
|
//
|
|
|
|
if (!pMetaData->QueryIsDavDisabled())
|
|
{
|
|
szHeaderValue = pRequest->GetHeader( HttpHeaderTranslate );
|
|
if ( szHeaderValue &&
|
|
toupper( szHeaderValue[ 0 ] ) == 'F' &&
|
|
szHeaderValue[ 1 ] == '\0' &&
|
|
!pMetaData->QueryIgnoreTranslate() )
|
|
{
|
|
//
|
|
// This is a DAV request
|
|
//
|
|
|
|
pHandler = new W3_DAV_HANDLER( this );
|
|
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Does this request map to an extension
|
|
//
|
|
// Map can mean one of two things
|
|
// a) A script map (as in MD_SCRIPT_MAP)
|
|
// b) An explicit .DLL/.EXE/.COM/etc.
|
|
//
|
|
|
|
pScriptMapEntry = pUrlContext->QueryUrlInfo()->QueryScriptMapEntry();
|
|
if ( pScriptMapEntry != NULL ||
|
|
pUrlContext->QueryUrlInfo()->QueryGateway() == GATEWAY_ISAPI ||
|
|
pUrlContext->QueryUrlInfo()->QueryGateway() == GATEWAY_CGI )
|
|
{
|
|
dwFilePerms = pMetaData->QueryAccessPerms();
|
|
|
|
if ( pScriptMapEntry != NULL )
|
|
{
|
|
GatewayType = pScriptMapEntry->QueryGateway();
|
|
|
|
//
|
|
// We have a script map. Check access rights
|
|
//
|
|
|
|
if ( pScriptMapEntry->QueryAllowScriptAccess() &&
|
|
IS_ACCESS_ALLOWED( pRequest, dwFilePerms, SCRIPT ) )
|
|
{
|
|
fAccessAllowed = TRUE;
|
|
}
|
|
|
|
if ( !fAccessAllowed &&
|
|
IS_ACCESS_ALLOWED( pRequest, dwFilePerms, EXECUTE ) )
|
|
{
|
|
fAccessAllowed = TRUE;
|
|
}
|
|
|
|
if ( !fAccessAllowed )
|
|
{
|
|
pHandler = new W3_GENERAL_HANDLER( this,
|
|
HttpStatusForbidden,
|
|
Http403ExecAccessDenied );
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// If there is a ./ in the URL, then always check for existence. This
|
|
// prevents a trailing . metabase equivilency problem
|
|
//
|
|
|
|
fSuspectUrl = pRequest->IsSuspectUrl();
|
|
|
|
//
|
|
// Should we verify that path info does exist?
|
|
//
|
|
|
|
if ( pScriptMapEntry->QueryCheckPathInfoExists() ||
|
|
fSuspectUrl )
|
|
{
|
|
hr = CheckPathInfoExists( &pHandler );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
if ( pHandler != NULL )
|
|
{
|
|
//
|
|
// We already have an error handler for this request.
|
|
// That means path info didn't really exist
|
|
//
|
|
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Does the script map support the verb?
|
|
//
|
|
|
|
hr = pRequest->GetVerbString( &strVerb );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
goto Finished;
|
|
}
|
|
|
|
if ( !pScriptMapEntry->IsVerbAllowed( strVerb ) )
|
|
{
|
|
pHandler = new W3_GENERAL_HANDLER( this,
|
|
HttpStatusForbidden,
|
|
Http403ExecAccessDenied );
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
goto Finished;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GatewayType = pUrlContext->QueryUrlInfo()->QueryGateway();
|
|
}
|
|
|
|
//
|
|
// OK. If we got to here, we can setup an executable handler
|
|
//
|
|
|
|
if ( GatewayType == GATEWAY_CGI )
|
|
{
|
|
pHandler = new W3_CGI_HANDLER( this, pScriptMapEntry );
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT( GatewayType == GATEWAY_ISAPI );
|
|
pHandler = new W3_ISAPI_HANDLER( this, pScriptMapEntry );
|
|
}
|
|
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// Ok. No script map applied. Is this an unknown verb?
|
|
// (i.e. not GET, HEAD, POST, TRACE, TRACK)
|
|
//
|
|
|
|
if ( VerbType == HttpVerbGET ||
|
|
VerbType == HttpVerbPOST ||
|
|
VerbType == HttpVerbHEAD )
|
|
{
|
|
fKnownVerb = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fKnownVerb = FALSE;
|
|
}
|
|
|
|
//
|
|
// If this verb is unknown, then it goes to DAV
|
|
//
|
|
|
|
if ( fKnownVerb == FALSE )
|
|
{
|
|
if (!pMetaData->QueryIsDavDisabled())
|
|
{
|
|
pHandler = new W3_DAV_HANDLER( this );
|
|
}
|
|
else if ( VerbType == HttpVerbOPTIONS )
|
|
{
|
|
//
|
|
// We handle OPTIONS if DAV is disabled
|
|
//
|
|
pHandler = new W3_OPTIONS_HANDLER( this );
|
|
}
|
|
else
|
|
{
|
|
pHandler = new W3_GENERAL_HANDLER( this,
|
|
HttpStatusNotImplemented );
|
|
}
|
|
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
goto Finished;
|
|
}
|
|
|
|
//
|
|
// If there are special DAV verbs like If: and Lock-Token:, then it also
|
|
// go to DAV unless it is disabled
|
|
//
|
|
|
|
if (!pMetaData->QueryIsDavDisabled())
|
|
{
|
|
if ( SUCCEEDED( pRequest->GetHeader( "If",
|
|
2,
|
|
&strHeaderValue,
|
|
TRUE ) ) ||
|
|
SUCCEEDED( pRequest->GetHeader( "Lock-Token",
|
|
10,
|
|
&strHeaderValue,
|
|
TRUE ) ) )
|
|
{
|
|
pHandler = new W3_DAV_HANDLER( this );
|
|
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
goto Finished;
|
|
}
|
|
}
|
|
|
|
//
|
|
// OK. Exchange/DAV-providers have had their chance. Now its our turn!
|
|
//
|
|
|
|
//
|
|
// Call the static file handler
|
|
//
|
|
|
|
pHandler = new W3_STATIC_FILE_HANDLER( this );
|
|
|
|
if ( pHandler == NULL )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
|
|
goto Finished;
|
|
}
|
|
|
|
Finished:
|
|
if ( FAILED( hr ) )
|
|
{
|
|
DBG_ASSERT( pHandler == NULL );
|
|
}
|
|
|
|
_pHandler = pHandler;
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::SetupHttpRedirect(
|
|
STRA & strPath,
|
|
BOOL fIncludeParameters,
|
|
HTTP_STATUS & httpStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Do an HTTP redirect (301 or 302)
|
|
|
|
Arguments:
|
|
|
|
strPath - New path component of destination
|
|
fIncludeParameters - Include query string in Location: header
|
|
httpStatus - Status for redirect (i.e. HttpStatusRedirect)
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
HRESULT hr;
|
|
STACK_STRA( strRedirect, MAX_PATH);
|
|
STRA *pstrRedirect;
|
|
|
|
W3_RESPONSE *pResponse = QueryResponse();
|
|
W3_REQUEST *pRequest = QueryRequest();
|
|
|
|
//
|
|
// If it an absolute path add the protocol, host name and QueryString
|
|
// if specified, otherwise just assume it is a fully qualified URL.
|
|
//
|
|
if (strPath.QueryStr()[0] == '/')
|
|
{
|
|
// build the redirect URL (with http://) into strRedirect
|
|
hr = pRequest->BuildFullUrl( strPath,
|
|
&strRedirect,
|
|
fIncludeParameters );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
pstrRedirect = &strRedirect;
|
|
}
|
|
else
|
|
{
|
|
pstrRedirect = &strPath;
|
|
}
|
|
|
|
//
|
|
// Setup the response, starting with status and location:
|
|
//
|
|
|
|
pResponse->SetStatus( httpStatus );
|
|
|
|
//
|
|
// Be careful to reset the response on subsequent failures so we don't
|
|
// end up with an incomplete response!
|
|
//
|
|
|
|
hr = pResponse->SetHeader( HttpHeaderLocation,
|
|
pstrRedirect->QueryStr(),
|
|
pstrRedirect->QueryCCH() );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
pResponse->Clear();
|
|
return hr;
|
|
}
|
|
|
|
pResponse->SetHeaderByReference( HttpHeaderContentType,
|
|
"text/html", 9 );
|
|
|
|
//
|
|
// Now add any metabase configured "redirect" headers (lame)
|
|
//
|
|
|
|
STRA *pstrRedirectHeaders = QueryUrlContext()->QueryMetaData()->QueryRedirectHeaders();
|
|
if ( pstrRedirectHeaders != NULL )
|
|
{
|
|
hr = pResponse->AppendResponseHeaders( *pstrRedirectHeaders );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add the canned redirect body. This means taking the format string,
|
|
// and inserting the URL into it
|
|
//
|
|
|
|
CHAR *pvRedirect;
|
|
DWORD cbRedirect = sm_cbRedirectMessage + pstrRedirect->QueryCCH();
|
|
|
|
//
|
|
// Keep a buffer around
|
|
//
|
|
|
|
hr = QueryHeaderBuffer()->AllocateSpace( cbRedirect + 1,
|
|
&pvRedirect );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
cbRedirect = _snprintf( pvRedirect,
|
|
cbRedirect,
|
|
sm_achRedirectMessage,
|
|
pstrRedirect->QueryStr() );
|
|
|
|
//
|
|
// Setup the response
|
|
//
|
|
|
|
hr = pResponse->AddMemoryChunkByReference( pvRedirect,
|
|
cbRedirect );
|
|
if ( FAILED( hr ) )
|
|
{
|
|
pResponse->Clear();
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
W3_CONTEXT::SetupHttpRedirect(
|
|
STRU & strPath,
|
|
BOOL fIncludeParameters,
|
|
HTTP_STATUS & httpStatus
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Do an HTTP redirect (301 or 302)
|
|
|
|
Arguments:
|
|
|
|
strPath - New path component of destination
|
|
fIncludeParameters - Include query string in Location: header
|
|
httpStatus - Status for redirect (i.e. HttpStatusRedirect)
|
|
|
|
Return Value:
|
|
|
|
HRESULT
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Convert the unicode path to ansi
|
|
//
|
|
HRESULT hr;
|
|
STACK_STRA (straPath, MAX_PATH);
|
|
if (FAILED(hr = straPath.CopyWToUTF8Unescaped(strPath)))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
return SetupHttpRedirect(straPath,
|
|
fIncludeParameters,
|
|
httpStatus);
|
|
}
|
|
|
|
BOOL W3_CONTEXT::QueryDoUlLogging()
|
|
{
|
|
if (QuerySite() == NULL ||
|
|
!QuerySite()->QueryDoUlLogging())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (QueryUrlContext() == NULL ||
|
|
QueryUrlContext()->QueryMetaData() == NULL)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return !QueryUrlContext()->QueryMetaData()->QueryDontLog();
|
|
}
|
|
|
|
BOOL W3_CONTEXT::QueryDoCustomLogging()
|
|
{
|
|
if (QuerySite() == NULL ||
|
|
!QuerySite()->QueryDoCustomLogging())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (QueryUrlContext() == NULL ||
|
|
QueryUrlContext()->QueryMetaData() == NULL)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return !QueryUrlContext()->QueryMetaData()->QueryDontLog();
|
|
}
|
|
|
|
VOID
|
|
W3_CONTEXT::SetSSICommandHandler(
|
|
W3_HANDLER * pHandler
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Explicitly sets the CGI_HANDLER for this request. This is the handler
|
|
which executes explicit command lines on behalf of SSI
|
|
|
|
The function is named SetSSICommandHandler (instead of just SetHandler())
|
|
to discourage others from using this function.
|
|
|
|
Arguments:
|
|
|
|
pHandler - Handler to set. Must be the CGI handler
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
DBG_ASSERT( pHandler != NULL );
|
|
DBG_ASSERT( _pHandler == NULL );
|
|
DBG_ASSERT( wcscmp( pHandler->QueryName(), L"CGIHandler" ) == 0 );
|
|
|
|
_pHandler = pHandler;
|
|
}
|