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

2906 lines
82 KiB

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// ECB.CPP
//
// Implementation of CEcb methods and non-member functions
//
// Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
//
#include "_davprs.h"
#include "ecb.h"
#include "instdata.h"
#include "ecbimpl.h"
// ========================================================================
//
// CLASS IEcb
//
// ------------------------------------------------------------------------
//
// IEcb::~IEcb()
//
// Out of line virtual destructor necessary for proper deletion
// of objects of derived classes via this class.
//
IEcb::~IEcb()
{
}
#ifdef DBG // ECB logging
const CHAR gc_szDbgECBLogging[] = "ECB Logging";
// ========================================================================
//
// CLASS CEcbLog (DBG only)
//
class CEcbLog : private Singleton<CEcbLog>
{
//
// Friend declarations required by Singleton template
//
friend class Singleton<CEcbLog>;
//
// Critical section to serialize writes to
// the log file
//
CCriticalSection m_cs;
//
// Handle to the log file
//
auto_handle<HANDLE> m_hfLog;
//
// Monotonically increasing unique identifier
// for ECB logging;
//
LONG m_lMethodID;
// CREATORS
//
// Declared private to ensure that arbitrary instances
// of this class cannot be created. The Singleton
// template (declared as a friend above) controls
// the sole instance of this class.
//
CEcbLog();
// NOT IMPLEMENTED
//
CEcbLog( const CEcbLog& );
CEcbLog& operator=( const CEcbLog& );
public:
// STATICS
//
//
// Instance creating/destroying routines provided
// by the Singleton template.
//
using Singleton<CEcbLog>::CreateInstance;
using Singleton<CEcbLog>::DestroyInstance;
static void LogString( const EXTENSION_CONTROL_BLOCK * pecb,
LONG lMethodID,
LPCSTR szLocation );
static LONG LNextMethodID();
};
// ------------------------------------------------------------------------
//
// CEcbLog::CEcbLog()
//
CEcbLog::CEcbLog() :
m_lMethodID(0)
{
CHAR rgch[MAX_PATH];
// Init our ECB log file.
if (GetPrivateProfileString( gc_szDbgECBLogging,
gc_szDbgLogFile,
"",
rgch,
sizeof(rgch),
gc_szDbgIni ))
{
m_hfLog = CreateFile( rgch,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, // | FILE_FLAG_SEQUENTIAL_SCAN
NULL );
}
else
m_hfLog = INVALID_HANDLE_VALUE;
}
// ------------------------------------------------------------------------
//
// CEcbLog::LogString()
//
void
CEcbLog::LogString( const EXTENSION_CONTROL_BLOCK * pecb,
LONG lMethodID,
LPCSTR szLocation )
{
if ( INVALID_HANDLE_VALUE == Instance().m_hfLog )
return;
Assert( pecb );
CHAR rgch[MAX_PATH];
int cch;
// Dump a line to the log:
// Thread: <tid> pECB <ecb> MethodID: <id> <meth name> <szLocation>
//
cch = _snprintf( rgch, CElems(rgch), "Thread: %08x pECB: 0x%08p MethodID: 0x%08x %hs %hs %hs\n",
GetCurrentThreadId(),
pecb,
lMethodID,
gc_szSignature,
pecb->lpszMethod,
szLocation );
// If we hit the buffer length then we won't be NULL terminated
//
rgch[CElems(rgch)-1] = '\0';
// If the trace is bigger than the buffer then cch will be negative
// but the buffer will have been filled.
//
if ( 0 > cch )
cch = CElems(rgch)-1;
DWORD cbActual;
CSynchronizedBlock sb(Instance().m_cs);
WriteFile( Instance().m_hfLog,
rgch,
cch,
&cbActual,
NULL );
}
// ------------------------------------------------------------------------
//
// CEcbLog::LNextMethodID()
//
LONG
CEcbLog::LNextMethodID()
{
return InterlockedIncrement(&Instance().m_lMethodID);
}
void InitECBLogging()
{
CEcbLog::CreateInstance();
}
void DeinitECBLogging()
{
CEcbLog::DestroyInstance();
}
#endif // DBG ECB logging
// ========================================================================
//
// CLASS IIISAsyncIOCompleteObserver
//
// ------------------------------------------------------------------------
//
// IIISAsyncIOCompleteObserver::~IIISAsyncIOCompleteObserver()
//
// Out of line virtual destructor necessary for proper deletion
// of objects of derived classes via this class
//
IIISAsyncIOCompleteObserver::~IIISAsyncIOCompleteObserver() {}
// ========================================================================
//
// CLASS CAsyncErrorResponseInterlock
//
class CAsyncErrorResponseInterlock
{
enum
{
STATE_ENABLED,
STATE_DISABLED,
STATE_TRIGGERED
};
// Interlock state
//
LONG m_lState;
// NOT IMPLEMENTED
//
CAsyncErrorResponseInterlock( const CAsyncErrorResponseInterlock& );
CAsyncErrorResponseInterlock& operator=( const CAsyncErrorResponseInterlock& );
public:
CAsyncErrorResponseInterlock() :
m_lState(STATE_ENABLED)
{
}
// ------------------------------------------------------------------------
//
// CAsyncErrorResponseInterlock::FDisable()
//
// Tries to disable the interlock. Returns TRUE if successful; subsequent
// calls to FTrigger() will return FALSE.
//
BOOL FDisable()
{
// Return TRUE if the lock is already disabled OR if the lock is
// still enabled and we succeeded in disabling it. Return FALSE
// otherwise.
//
return STATE_DISABLED == m_lState ||
(STATE_ENABLED == m_lState &&
STATE_ENABLED == InterlockedCompareExchange(
&m_lState,
STATE_DISABLED,
STATE_ENABLED));
}
// ------------------------------------------------------------------------
//
// CAsyncErrorResponseInterlock::FTrigger()
//
// Tries to trigger the interlock. Returns TRUE if successful; subsequent
// calls to FDisable() will return FALSE.
//
BOOL FTrigger()
{
// We can only trigger the lock once.
//
Assert(STATE_TRIGGERED != m_lState);
// Return TRUE if the lock is still enabled and we succeed in
// triggering it. Return FALSE otherwise.
//
return STATE_ENABLED == m_lState &&
STATE_ENABLED == InterlockedCompareExchange(
&m_lState,
STATE_TRIGGERED,
STATE_ENABLED);
}
};
// ========================================================================
//
// CLASS CEcb
//
// Implementation of the caching ECB
//
class CEcb : public CEcbBaseImpl<IEcb>
{
// Cached user impersonation token
//
mutable HANDLE m_hTokUser;
// Cached instance data -- owned by the instance cache,
// not us, so don't free it (not an auto-ptr!!).
//
mutable CInstData * m_pInstData;
// Cached HTTP version (e.g. "HTTP/1.1")
//
mutable CHAR m_rgchVersion[10];
// Cached Connection: header
//
mutable auto_heap_ptr<WCHAR> m_pwszConnectionHeader;
// Cached metadata
//
auto_ref_ptr<IMDData> m_pMD;
// State in which we leave the connection
// when we're done.
//
mutable enum
{
UNKNOWN, // Don't know yet
CLOSE, // Close it
KEEP_ALIVE // Keep it open
} m_connState;
// Brief'ness
//
enum { BRIEF_UNKNOWN = -1, BRIEF_NO, BRIEF_YES };
mutable LONG m_lBrief;
// Acceptable transfer coding method:
//
// TC_UNKNOWN - Acceptable transfer coding has not yet been determined.
// TC_CHUNKED - Chunked transfer coding is acceptable.
// TC_IDENTITY - No transfer coding is acceptable.
//
mutable TRANSFER_CODINGS m_tcAccepted;
// Authentication State information:
// Bit Means
// ============================
// 31 Queried against ECB
// 30-4 Unused
// 3 Kerberos
// 2 NTLM
// 1 Basic
// 0 Authenticated
mutable DWORD m_rgbAuthState;
// Init flag set to TRUE once we've registered our
// I/O completion routine with IIS.
//
enum { NO_COMPLETION, IO_COMPLETION, CUSTERR_COMPLETION, EXECURL_COMPLETION };
LONG m_lSetIISIOCompleteCallback;
// Flag stating whether a child ISAPI has been successfully executed. If this is the
// case, we don't want to reset dwHttpStatusCode later or we will lose whatever
// status code they set.
//
BOOL m_fChildISAPIExecSuccess;
//
// Interlock used to prevent a race condition between a thread
// sending a normal response and a thread sending an error in response
// to an async event such as an exception or epoxy shutdown.
//
CAsyncErrorResponseInterlock m_aeri;
// Status string for async custom error response.
// Format "nnn reason".
//
auto_heap_ptr<CHAR> m_pszStatus;
//
// Refcount to track number of outstanding async I/O operations.
// There should never be more than one.
//
LONG m_cRefAsyncIO;
//
// Pointer to the current async I/O completion observer
//
IIISAsyncIOCompleteObserver * m_pobsAsyncIOComplete;
#ifdef DBG
LONG m_lEcbLogMethodID;
#endif
// ECB tracing (not to be confused with ECB logging!)
//
#ifdef DBG
void TraceECB() const;
#else
void TraceECB() const {}
#endif
//
// Async I/O
//
SCODE ScSetIOCompleteCallback(LONG lCompletion);
static VOID WINAPI IISIOComplete( const EXTENSION_CONTROL_BLOCK * pecbIIS,
CEcb * pecb,
DWORD dwcbIO,
DWORD dwLastError );
static VOID WINAPI CustomErrorIOCompletion( const EXTENSION_CONTROL_BLOCK * pecbIIS,
CEcb * pecb,
DWORD dwcbIO,
DWORD dwLastError );
static VOID WINAPI ExecuteUrlIOCompletion( const EXTENSION_CONTROL_BLOCK * pecbIIS,
CEcb * pecb,
DWORD dwcbIO,
DWORD dwLastError );
// NOT IMPLEMENTED
//
CEcb( const CEcb& );
CEcb& operator=( const CEcb& );
SCODE ScSyncExecuteChildWide60Before( LPCWSTR pwszUrl,
LPCSTR pszQueryString,
BOOL fCustomErrorUrl );
SCODE ScAsyncExecUrlWide60After( LPCWSTR pwszUrl,
LPCSTR pszQueryString,
BOOL fCustomErrorUrl );
public:
CEcb( EXTENSION_CONTROL_BLOCK& ecb );
BOOL FInitialize( BOOL fUseRawUrlMappings );
~CEcb();
// URL prefix
//
UINT CchUrlPortW( LPCWSTR * ppwszPort ) const;
// Instance data access
//
CInstData& InstData() const;
// Impersonation token access
//
HANDLE HitUser() const;
// ACCESSORS
//
LPCSTR LpszVersion() const;
BOOL FKeepAlive() const;
BOOL FCanChunkResponse() const;
BOOL FAuthenticated() const;
BOOL FProcessingCEUrl() const;
BOOL FIIS60OrAfter() const
{
return (m_pecb->dwVersion >= IIS_VERSION_6_0);
}
BOOL FSyncTransmitHeaders( const HSE_SEND_HEADER_EX_INFO& shei );
SCODE ScAsyncRead( BYTE * pbBuf,
UINT * pcbBuf,
IIISAsyncIOCompleteObserver& obs );
SCODE ScAsyncWrite( BYTE * pbBuf,
DWORD dwcbBuf,
IIISAsyncIOCompleteObserver& obs );
SCODE ScAsyncTransmitFile( const HSE_TF_INFO& tfi,
IIISAsyncIOCompleteObserver& obs );
SCODE ScAsyncCustomError60After( const HSE_CUSTOM_ERROR_INFO& cei,
LPSTR pszStatus );
SCODE ScAsyncExecUrl60After( const HSE_EXEC_URL_INFO& eui );
SCODE ScExecuteChild( LPCWSTR pwszURI, LPCSTR pszQueryString, BOOL fCustomErrorUrl )
{
// IIS 6.0 or after has a different way of executing child
//
if (m_pecb->dwVersion >= IIS_VERSION_6_0)
{
return ScAsyncExecUrlWide60After (pwszURI, pszQueryString, fCustomErrorUrl);
}
else
{
return ScSyncExecuteChildWide60Before (pwszURI, pszQueryString, fCustomErrorUrl);
}
}
SCODE ScSendRedirect( LPCSTR lpszURI );
IMDData& MetaData() const
{
Assert( m_pMD.get() );
return *m_pMD;
}
BOOL FBrief () const;
LPCWSTR PwszMDPathVroot() const
{
Assert( m_pInstData );
return m_pInstData->GetNameW();
}
#ifdef DBG
virtual void LogString( LPCSTR lpszLocation ) const
{
if ( DEBUG_TRACE_TEST(ECBLogging) )
CEcbLog::LogString( m_pecb, m_lEcbLogMethodID, lpszLocation );
}
#endif
// MANIPULATORS
//
VOID SendAsyncErrorResponse( DWORD dwStatusCode,
LPCSTR pszStatusDescription,
DWORD cchzStatusDescription,
LPCSTR pszBody,
DWORD cchzBody );
DWORD HSEHandleException();
// Session handling
//
VOID DoneWithSession( BOOL fKeepAlive );
// To be used ONLY by request/response.
//
void SetStatusCode( UINT iStatusCode );
void SetConnectionHeader( LPCWSTR pwszValue );
void SetAcceptLanguageHeader( LPCSTR pszValue );
void CloseConnection();
};
// ------------------------------------------------------------------------
//
// CEcb Constructor/Destructor
//
CEcb::CEcb( EXTENSION_CONTROL_BLOCK& ecb ) :
CEcbBaseImpl<IEcb>(ecb),
m_hTokUser(NULL),
m_pInstData(NULL),
m_connState(UNKNOWN),
m_tcAccepted(TC_UNKNOWN),
m_rgbAuthState(0),
m_cRefAsyncIO(0),
m_lSetIISIOCompleteCallback(NO_COMPLETION),
m_fChildISAPIExecSuccess(FALSE),
m_lBrief(BRIEF_UNKNOWN)
{
#ifdef DBG
if ( DEBUG_TRACE_TEST(ECBLogging) )
m_lEcbLogMethodID = CEcbLog::LNextMethodID();
#endif
// Auto-pointers will be init'd by their own ctors.
//
// Zero the first char of the m_rgchVersion.
//
*m_rgchVersion = '\0';
// Clear out the status code in the EXTENSION_CONTROL_BLOCK so that
// we will be able to tell whether we should try to send a 500
// Server Error response in the event of an exception.
//
SetStatusCode(0);
// Set up our instance data now. We need it for the perf counters below.
//
m_pInstData = &g_inst.GetInstData( *this );
// And trace out the ECB info (If we're debug, if we're tracing....)
//
TraceECB();
}
// ------------------------------------------------------------------------
//
// CEcb::FInitialize()
//
BOOL
CEcb::FInitialize( BOOL fUseRawUrlMappings )
{
auto_heap_ptr<WCHAR> pwszMDUrlOnHeap;
//
// Fault in a few things like the vroot (LPSTR) and its length
// and the corresponding path information.
// The mapex info is already "faulted in" during the CEcb constructor.
// (ctor calls GetInstData, which calls CEcbBaseImpl<>::GetMapExInfo)
// However, fault in other pieces, like our translated request URI
// and our MD path.
//
//
// Cache the metabase paths for both the vroot and the request URI.
//
// Note that the metabase path for the vroot is just the instance name.
//
// Special case: If '*' is the request URI.
//
// IMPORTANT: this is only valid for an OPTIONS request. The handling
// of the validitiy of the request is handled later. For now, lookup
// the data for the default site root.
//
// IMPORTANT:
// LpszRequestUrl() will return FALSE in the case of a bad URL (in
// DAVEX, if the ScNormalizeUrl() call fails). If that happens,
// set our status code to HSC_BAD_REQUEST and return FALSE from here.
// The calling code (NewEcb()) will handle this gracefully.
//
LPCWSTR pwszRequestUrl = LpwszRequestUrl();
if (!pwszRequestUrl)
{
SetStatusCode(HSC_BAD_REQUEST);
return FALSE;
}
LPCWSTR pwszMDUrl;
if ( L'*' == pwszRequestUrl[0] &&
L'\0' == pwszRequestUrl[1] )
{
pwszMDUrl = PwszMDPathVroot();
}
else
{
pwszMDUrlOnHeap = static_cast<LPWSTR>(ExAlloc(CbMDPathW(*this, pwszRequestUrl)));
if (NULL == pwszMDUrlOnHeap.get())
return FALSE;
pwszMDUrl = pwszMDUrlOnHeap.get();
MDPathFromURIW( *this, pwszRequestUrl, const_cast<LPWSTR>(pwszMDUrl) );
}
//
//$REVIEW It would be nice to propagate out the specific HRESULT
//$REVIEW so that we could send back an appropriate suberror,
//$REVIEW but sending a suberror could be difficult if we can't
//$REVIEW get to the metadata that contains the suberror mappings....
//
return SUCCEEDED(HrMDGetData( *this,
pwszMDUrl,
PwszMDPathVroot(),
m_pMD.load() ));
}
// ------------------------------------------------------------------------
//
// CEcb::~CEcb()
//
CEcb::~CEcb()
{
//
// If we've already given back the EXTENSION_CONTROL_BLOCK then
// we don't need to do anything else here. Otherwise we should
// return it (call HSE_REQ_DONE_WITH_SESSION) with the appropriate
// keep-alive.
//
if ( m_pecb )
{
//
// At this point someone should have generated a response,
// even in the case of an exception (see HSEHandleException()).
//
Assert( m_pecb->dwHttpStatusCode != 0 );
//
// Tell IIS that we're done for this request.
//
DoneWithSession( FKeepAlive() );
}
}
// ========================================================================
//
// PRIVATE CEcb methods
//
// ------------------------------------------------------------------------
//
// CEcb::DoneWithSession()
//
// Called whenever we are done with the raw EXTENSION_CONTROL_BLOCK.
//
VOID
CEcb::DoneWithSession( BOOL fKeepAlive )
{
//
// We should only call DoneWithSession() once. We null out m_pecb
// at the end, so we can assert that we are only called once by
// checking m_pecb here.
//
Assert( m_pecb );
//
// We should never release the EXTENSION_CONTROL_BLOCK if there
// is async I/O outstanding.
//
Assert( 0 == m_cRefAsyncIO );
//
// "Release" the raw EXTENSION_CONTROL_BLOCK inherited from IEcb.
//
static const DWORD sc_dwKeepConn = HSE_STATUS_SUCCESS_AND_KEEP_CONN;
(VOID) m_pecb->ServerSupportFunction(
m_pecb->ConnID,
HSE_REQ_DONE_WITH_SESSION,
fKeepAlive ? const_cast<DWORD *>(&sc_dwKeepConn) : NULL,
NULL,
NULL );
//
// We can no longer use the EXTENSION_CONTROL_BLOCK so remove any
// temptation to do so by nulling out the pointer.
//
m_pecb = NULL;
}
// ------------------------------------------------------------------------
//
// CEcb::SendAsyncErrorResponse()
//
VOID
CEcb::SendAsyncErrorResponse( DWORD dwStatusCode,
LPCSTR pszStatusDescription,
DWORD cchzStatusDescription,
LPCSTR pszBody,
DWORD cchzBody )
{
// Try to trigger the async error response mechanism. If successful
// then we are responsible for sending the entire repsonse. If not
// then we are already sending a response on some other thread, so
// don't confuse things by sending any thing here.
//
if (!m_aeri.FTrigger())
{
DebugTrace( "CEcb::SendAsyncErrorResponse() - Non-error response already in progress\n" );
return;
}
HSE_SEND_HEADER_EX_INFO shei;
CHAR rgchStatusDescription[256];
// Blow away any previously set status code in favor of
// the requested status code. Even though there may have
// been an old status code set, it was never sent -- the
// fact that our interlock triggered proves that no other
// response has been sent.
//
SetStatusCode(dwStatusCode);
// If we don't have a status description then fetch the default
// for the given status code.
//
if ( !pszStatusDescription )
{
LpszLoadString( dwStatusCode,
LcidAccepted(),
rgchStatusDescription,
sizeof(rgchStatusDescription) );
pszStatusDescription = rgchStatusDescription;
}
shei.pszStatus = pszStatusDescription;
shei.cchStatus = cchzStatusDescription;
// Don't send any body unless we are given one.
//
shei.pszHeader = pszBody;
shei.cchHeader = cchzBody;
// Always close the connection on errors -- and we should
// only ever be called for serious server errors.
//
Assert(dwStatusCode >= 400);
shei.fKeepConn = FALSE;
// Send the response. We don't care at all about the return
// value because there's nothing we can do if the response
// cannot be sent.
//
(VOID) m_pecb->ServerSupportFunction(
m_pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER_EX,
&shei,
NULL,
NULL );
}
// ------------------------------------------------------------------------
//
// CEcb::HSEHandleException()
//
DWORD
CEcb::HSEHandleException()
{
//
// !!! IMPORTANT !!!
//
// This function is called after an exception has occurred.
// Don't do ANYTHING outside of a try/catch block or a secondary
// exception could take out the whole IIS process.
//
try
{
//
// Translate async Win32 exceptions into thrown C++ exceptions.
// This must be placed inside the try block!
//
CWin32ExceptionHandler win32ExceptionHandler;
//
// Send a 500 Server Error response. We use the async error
// response mechanism, because we may have been in the middle
// of sending some other response on another thread.
//
SendAsyncErrorResponse(500,
gc_szDefErrStatusLine,
gc_cchszDefErrStatusLine,
gc_szDefErrBody,
gc_cchszDefErrBody);
}
catch ( CDAVException& )
{
//
// We blew up trying to send a response. Oh well.
//
}
//
// Tell IIS that we are done with the EXTENSION_CONTROL_BLOCK that
// it gave us. We must do this or IIS will not be able to shut down.
// We would normally do this from within our destructor, but since
// we are handling an exception, there is no guarantee that our
// destructor will ever be called -- that is, there may be outstanding
// refs that will never be released (i.e. we will leak).
//
DWORD dwHSEStatusRet;
try
{
//
// Translate async Win32 exceptions into thrown C++ exceptions.
// This must be placed inside the try block!
//
CWin32ExceptionHandler win32ExceptionHandler;
DoneWithSession( FALSE );
//
// If this call succeeds, we MUST return HSE_STATUS_PENDING to
// let IIS know that we claimed a ref on the EXTENSION_CONTROL_BLOCK.
//
dwHSEStatusRet = HSE_STATUS_PENDING;
}
catch ( CDAVException& )
{
//
// We blew up trying to tell IIS that we were done with
// the EXTENSION_CONTROL_BLOCK. There is absolutely nothing
// we can do at this point. IIS will probably hang on shutdown.
//
dwHSEStatusRet = HSE_STATUS_ERROR;
}
return dwHSEStatusRet;
}
// ------------------------------------------------------------------------
//
// CEcb::TraceECB()
//
// Traces out the EXTENSION_CONTROL_BLOCK
//
#ifdef DBG
void
CEcb::TraceECB() const
{
EcbTrace( "ECB Contents:\n" );
EcbTrace( "\tcbSize = %lu\n", m_pecb->cbSize );
EcbTrace( "\tdwVersion = %lu\n", m_pecb->dwVersion );
EcbTrace( "\tlpszMethod = \"%s\"\n", m_pecb->lpszMethod );
EcbTrace( "\tlpszQueryString = \"%s\"\n", m_pecb->lpszQueryString );
EcbTrace( "\tlpszPathInfo = \"%s\"\n", m_pecb->lpszPathInfo );
EcbTrace( "\tlpszPathTranslated = \"%s\"\n", m_pecb->lpszPathTranslated );
EcbTrace( "\tcbTotalBytes = %lu\n", m_pecb->cbTotalBytes );
EcbTrace( "\tcbAvailable = %lu\n", m_pecb->cbAvailable );
EcbTrace( "\tlpszContentType = \"%s\"\n", m_pecb->lpszContentType );
EcbTrace( "\n" );
{
char rgch[256];
DWORD dwCbRgch;
dwCbRgch = sizeof(rgch);
(void) m_pecb->GetServerVariable( m_pecb->ConnID, "SCRIPT_NAME", rgch, &dwCbRgch );
EcbTrace( "Script name = \"%s\"\n", rgch );
dwCbRgch = sizeof(rgch);
(void) m_pecb->GetServerVariable( m_pecb->ConnID, "SCRIPT_MAP", rgch, &dwCbRgch );
EcbTrace( "Script map = \"%s\"\n", rgch );
dwCbRgch = sizeof(rgch);
(void) m_pecb->GetServerVariable( m_pecb->ConnID, "HTTP_REQUEST_URI", rgch, &dwCbRgch );
EcbTrace( "Request URI = \"%s\"\n", rgch );
dwCbRgch = sizeof(rgch);
(void) m_pecb->ServerSupportFunction( m_pecb->ConnID, HSE_REQ_MAP_URL_TO_PATH, rgch, &dwCbRgch, NULL );
EcbTrace( "Path from request URI = \"%s\"\n", rgch );
}
}
#endif // defined(DBG)
// ========================================================================
//
// PUBLIC CEcb methods
//
// ------------------------------------------------------------------------
//
// CEcb::CchUrlPortW
//
// Get the string with the port based on the fact if we are secure
//
UINT
CEcb::CchUrlPortW( LPCWSTR * ppwszPort ) const
{
Assert (ppwszPort);
// If we are secure...
//
if (FSsl())
{
*ppwszPort = gc_wszUrl_Port_443;
return gc_cchUrl_Port_443;
}
*ppwszPort = gc_wszUrl_Port_80;
return gc_cchUrl_Port_80;
}
// ------------------------------------------------------------------------
//
// CEcb::InstData
//
// Fetch and cache our per-vroot instance data
//
CInstData&
CEcb::InstData() const
{
Assert( m_pInstData );
return *m_pInstData;
}
// ------------------------------------------------------------------------
//
// CEcb::HitUser
//
// Fetch and cache our impersonation token
//
HANDLE
CEcb::HitUser() const
{
if ( m_hTokUser == NULL )
{
ULONG cb = sizeof(HANDLE);
m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_GET_IMPERSONATION_TOKEN,
&m_hTokUser,
&cb,
NULL );
}
return m_hTokUser;
}
// ------------------------------------------------------------------------
//
// CEcb::LpszVersion()
//
LPCSTR
CEcb::LpszVersion() const
{
if ( !*m_rgchVersion )
{
DWORD cbVersion = sizeof(m_rgchVersion);
if ( !FGetServerVariable( gc_szHTTP_Version,
m_rgchVersion,
&cbVersion ) )
{
//
// If we are unable to get a value for HTTP_VERSION then
// the string is probably longer than the buffer we gave.
// Rather than deal with a potentially arbitrarily long
// string, default to HTTP/1.1. This is consistent with
// observed IIS behavior (cf. NT5:247826).
//
memcpy( m_rgchVersion,
gc_szHTTP_1_1,
sizeof(gc_szHTTP_1_1) );
}
else if ( !*m_rgchVersion )
{
//
// No value for HTTP_VERSION means that nothing was
// specified on the request line, which means HTTP/0.9
//
memcpy( m_rgchVersion,
gc_szHTTP_0_9,
sizeof(gc_szHTTP_0_9) );
}
}
return m_rgchVersion;
}
// ------------------------------------------------------------------------
//
// CEcb::FKeepAlive()
//
// Returns whether to keep alive the client connection after sending
// the response.
//
// The connection logic has changed over the various HTTP versions;
// this function uses the logic appropriate to the HTTP version
// of the request.
//
BOOL
CEcb::FKeepAlive() const
{
//
// If we haven't already determined what we want, then begin
// the process of figuring it out...
//
if ( m_connState == UNKNOWN )
{
//
// If someone set a Connection: header then pay attention to it
//
if (m_pwszConnectionHeader.get())
{
//
// Set the connection state based on the current value of
// the request's Connection: header, and the HTTP version.
// NOTE: The request MUST forward us any updates to the
// Connection: header for this to work!
// NOTE: Comparing the HTTP version strings using C-runtime strcmp,
// because the version string is pure ASCII.
//
//
// HTTP/1.1
//
// (Consider the HTTP/1.1 case FIRST to minimize the number
// of string compares in this most common case).
//
if ( !strcmp( LpszVersion(), gc_szHTTP_1_1 ) )
{
http_1_1:
//
// The default for HTTP/1.1 is to keep the connection alive
//
m_connState = KEEP_ALIVE;
//
// But if the request's Connection: header says close,
// then close.
//
// This compare should be case-insensitive.
//
// Using CRT skinny-string func here 'cause this header is pure ASCII,
// AND because _stricmp (and his brother _strcmpi) doesn't cause us
// a context-switch!
//
if ( !_wcsicmp( m_pwszConnectionHeader.get(), gc_wszClose ) )
m_connState = CLOSE;
}
//
// HTTP/1.0
//
else if ( !strcmp( LpszVersion(), gc_szHTTP_1_0 ) )
{
//
// For HTTP/1.0 requests, the default is to close the connection
// unless a "Connection: Keep-Alive" header exists.
//
m_connState = CLOSE;
if ( !_wcsicmp( m_pwszConnectionHeader.get(), gc_wszKeep_Alive ) )
m_connState = KEEP_ALIVE;
}
//
// HTTP/0.9
//
else if ( !strcmp( LpszVersion(), gc_szHTTP_0_9 ) )
{
//
// For HTTP/0.9, always close the connection. There was no
// other option for HTTP/0.9.
//
m_connState = CLOSE;
}
//
// Other (future) HTTP versions
//
else
{
//
// We really are only guessing what to do here, but assuming
// that the HTTP spec doesn't change the Connection behavior
// again, we should behave like HTTP/1.1
//
goto http_1_1;
}
}
//
// If no one set a Connection: header, than use whatever IIS
// tells us to use.
//
// NOTE: Currently, this value can only be ADDED, never DELETED.
// If that fact changes, FIX this code!
//
else
{
BOOL fKeepAlive;
if ( !m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_IS_KEEP_CONN,
&fKeepAlive,
NULL,
NULL ))
{
DebugTrace( "CEcb::FKeepAlive--Failure (0x%08x) from SSF(IsKeepConn).\n",
GetLastError() );
// No big deal? If we're getting errors like this
// we probably want to close the connection anyway....
//
m_connState = CLOSE;
}
m_connState = fKeepAlive ? KEEP_ALIVE : CLOSE;
}
}
//
// By now we must know what we want
//
Assert( m_connState == KEEP_ALIVE || m_connState == CLOSE );
return m_connState == KEEP_ALIVE;
}
// ------------------------------------------------------------------------
//
// CEcb::FCanChunkResponse()
//
// Returns TRUE if the client will accept a chunked response.
//
BOOL
CEcb::FCanChunkResponse() const
{
if ( TC_UNKNOWN == m_tcAccepted )
{
//
// According to the HTTP/1.1 draft, section 14.39 TE:
//
// "A server tests whether a transfer-coding is acceptable,
// acording to a TE field, using these rules:
//
// 1.
// The "chunked" transfer-coding is always acceptable.
//
// [...]"
//
// and section 3.6 Transfer Codings, last paragraph:
//
// "A server MUST NOT send transfer-codings to an HTTP/1.0
// client."
//
// Therefore, deciding whether a client accepts a chunked
// transfer coding is simple: If the request is an HTTP/1.1
// request, then it accepts chunked coding. Otherwise it doesn't.
//
m_tcAccepted = strcmp( gc_szHTTP_1_1, LpszVersion() ) ?
TC_IDENTITY :
TC_CHUNKED;
}
Assert( m_tcAccepted != TC_UNKNOWN );
return TC_CHUNKED == m_tcAccepted;
}
BOOL
CEcb::FBrief() const
{
// If we don't have a value yet...
//
if (BRIEF_UNKNOWN == m_lBrief)
{
CHAR rgchBrief[8] = {0};
ULONG cbBrief = 8;
// Brief is expected when:
//
// The "brief" header has a value of "t"
//
// NOTE: The default is brief to false.
//
// We addapt overwrite checking model here. Just first letter for true case.
//
// NOTE also: the default value if there is no Brief: header
// is FALSE -- give the full response.
//
FGetServerVariable("HTTP_BRIEF", rgchBrief, &cbBrief);
if ((rgchBrief[0] != 't') && (rgchBrief[0] != 'T'))
m_lBrief = BRIEF_NO;
else
m_lBrief = BRIEF_YES;
}
return (BRIEF_YES == m_lBrief);
}
// ------------------------------------------------------------------------
//
// CEcb::FAuthenticated()
//
const DWORD c_AuthStateQueried = 0x80000000;
const DWORD c_AuthStateAuthenticated = 0x00000001;
const DWORD c_AuthStateBasic = 0x00000002;
const DWORD c_AuthStateNTLM = 0x00000004;
const DWORD c_AuthStateKerberos = 0x00000008;
const DWORD c_AuthStateUnknown = 0x00000010;
const CHAR c_szBasic[] = "Basic";
const CHAR c_szNTLM[] = "NTLM";
const CHAR c_szKerberos[] = "Kerberos";
BOOL
CEcb::FAuthenticated() const
{
if (!(m_rgbAuthState & c_AuthStateQueried))
{
CHAR szAuthType[32];
ULONG cb = sizeof(szAuthType);
Assert(m_rgbAuthState == 0);
if (FGetServerVariable(gc_szAuth_Type, szAuthType, &cb))
{
// For now, lets just check the first character (it's cheaper).
// If this proves problematic then we can do a full string
// compair. Also, SSL by itself is not to be considered a form
// of domain authentication. The only time that SSL does imply
// and authenticated connection is when Cert Mapping is enabled
// and I don't think this is an interesting scenario. (russsi)
if (*szAuthType == 'B')
m_rgbAuthState = (c_AuthStateAuthenticated | c_AuthStateBasic);
else if (*szAuthType == 'N')
m_rgbAuthState = (c_AuthStateAuthenticated | c_AuthStateNTLM);
else if (*szAuthType == 'K')
m_rgbAuthState = (c_AuthStateAuthenticated | c_AuthStateKerberos);
else
m_rgbAuthState = c_AuthStateUnknown; // it could be "SSL/PCT"
}
m_rgbAuthState |= c_AuthStateQueried;
}
return (m_rgbAuthState & c_AuthStateAuthenticated);
}
// ------------------------------------------------------------------------
//
// CEcb::SetStatusCode()
//
// Sets the HTTP status code that IIS uses in logging
//
void
CEcb::SetStatusCode( UINT iStatusCode )
{
// If we have executed a child ISAPI successfully, we don't want to overwrite the
// status code in the ECB. This will end up causing IIS to log this status code
// rather than the one left in the ECB by the ISAPI.
//
if (!m_fChildISAPIExecSuccess)
m_pecb->dwHttpStatusCode = iStatusCode;
}
// MANIPULATORS
// To be used ONLY by request/response.
//
// NOTE: These member vars start out NULL. Inside CEcb, we are using NULL
// as a special value that means the data has NEVER been set, so if
// we get an lpszValue of NULL (meaning to delete the header), store
// an empty string instead, so that we know the data has been forcefully erased.
//
void CEcb::SetConnectionHeader( LPCWSTR pwszValue )
{
auto_heap_ptr<WCHAR> pwszOld;
pwszOld.take_ownership(m_pwszConnectionHeader.relinquish());
// If they want to delete the value, set an empty string.
//
if (!pwszValue)
pwszValue = gc_wszEmpty;
m_pwszConnectionHeader = WszDupWsz( pwszValue );
}
void CEcb::CloseConnection()
{
m_connState = CLOSE;
}
void CEcb::SetAcceptLanguageHeader( LPCSTR pszValue )
{
auto_heap_ptr<CHAR> pszOld;
pszOld.take_ownership(m_pszAcceptLanguage.relinquish());
// If they want to delete the value, set an empty string.
//
if (!pszValue)
pszValue = gc_szEmpty;
m_pszAcceptLanguage = LpszAutoDupSz( pszValue );
}
SCODE CEcb::ScAsyncRead( BYTE * pbBuf,
UINT * pcbBuf,
IIISAsyncIOCompleteObserver& obs )
{
SCODE sc = S_OK;
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncRead() called...\n", GetCurrentThreadId(), this );
//
// If there is another async IO outstanding we do not want to start one more. IIS will fail
// us out, and we ourselves will not be able to handle the completion of initial async IO
// properly. So just kill the connection and return. This may happen when we attempt to
// send the response before the read is finished.
//
if (0 != InterlockedCompareExchange(&m_cRefAsyncIO,
1,
0))
{
// The function bellow is not supported starting from IIS 6.0 but let us call it anyway
// just in case support becomes available - and we want to call it if the binary is
// running on IIS 5.0. It does not matter that much, as the bad side of not closing the
// connection may hang the client, or error out on subsequent request. That is ok as
// the path is supposed to be hit in abnormal/error conditions when clients for example
// send in invalid requests trying to cause denial of service or similar things.
// So on IIS 6.0 the connection will not be closed, we will just error out. We have
// not seen this path hit on IIS 6.0 anyway when runing denial of service scripts as it
// handles custom errors differently.
//
if (m_pecb->ServerSupportFunction(m_pecb->ConnID,
HSE_REQ_CLOSE_CONNECTION,
NULL,
NULL,
NULL))
{
EcbTrace( "CEcb::ScAsyncRead() - More than 1 async operation. Connection closed. Failing out with error 0x%08lX\n", E_ABORT );
sc = E_ABORT;
goto ret;
}
else
{
EcbTrace( "CEcb::ScAsyncRead() - More than 1 async operation. ServerSupportFunction(HSE_REQ_CLOSE_CONNECTION) "
"failed with last error 0x%08lX. Overriding with fatal error 0x%08lX\n", GetLastError(), E_FAIL );
sc = E_FAIL;
goto ret;
}
}
//
// IIS allows only one async I/O operation at a time. But for performance reasons it
// leaves it up to the ISAPI to heed the restriction. For the same reasons, we push
// that responsibility off to the DAV impl. A simple refcount tells us whether
// the impl has done so.
//
AssertSz( 1 == m_cRefAsyncIO,
"CEcb::ScAsyncRead() - m_cRefAsyncIO wrong on entry" );
//
// We need to hold a ref on the process-wide instance data for the duration of the I/O
// so that if IIS tells us to shut down while the I/O is still pending we will keep
// the instance data alive until we're done with the I/O.
//
AddRefImplInst();
//
// Set the async I/O completion observer
//
m_pobsAsyncIOComplete = &obs;
//
// Set up the async I/O completion routine and start reading.
// Add a ref for the I/O completion thread. Use auto_ref_ptr
// to make things exception-proof.
//
{
auto_ref_ptr<CEcb> pRef(this);
sc = ScSetIOCompleteCallback(IO_COMPLETION);
if (SUCCEEDED(sc))
{
if (m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_ASYNC_READ_CLIENT,
pbBuf,
reinterpret_cast<LPDWORD>(pcbBuf),
NULL ))
{
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncRead() I/O pending...\n", GetCurrentThreadId(), this );
pRef.relinquish();
}
else
{
EcbTrace( "CEcb::ScAsyncRead() - ServerSupportFunction(HSE_REQ_ASYNC_READ_CLIENT) failed with last error 0x%08lX\n", GetLastError() );
sc = HRESULT_FROM_WIN32(GetLastError());
}
}
else
{
EcbTrace( "CEcb::ScAsyncRead() - ScSetIOCompleteCallback() failed with error 0x%08lX\n", sc );
}
if (FAILED(sc))
{
LONG cRefAsyncIO;
//
// Release the instance ref we added above.
//
ReleaseImplInst();
//
// Decrement the async I/O refcount added above.
//
cRefAsyncIO = InterlockedDecrement(&m_cRefAsyncIO);
AssertSz( 0 == cRefAsyncIO,
"CEcb::ScAsyncRead() - m_cRefAsyncIO wrong after failed async read" );
goto ret;
}
}
ret:
return sc;
}
BOOL CEcb::FSyncTransmitHeaders( const HSE_SEND_HEADER_EX_INFO& shei )
{
//
// At this point someone should have generated a response.
//
Assert( m_pecb->dwHttpStatusCode != 0 );
//
// Try to disable the async error response mechanism. If we succeed, then we
// can send a response. If we fail then we must not send a response -- the
// async error response mechanism already sent one.
//
if ( !m_aeri.FDisable() )
{
DebugTrace( "CEcb::FSyncTransmitHeaders() - Async error response already in progress\n" );
// Do not forget to set the error, as callers will be confused if the function
// returns FALSE, but GetLastError() will return S_OK.
//
SetLastError(static_cast<ULONG>(E_FAIL));
return FALSE;
}
//
// Send the response
//
if ( !m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER_EX,
const_cast<HSE_SEND_HEADER_EX_INFO *>(&shei),
NULL,
NULL ) )
{
DebugTrace( "CEcb::FSyncTransmitHeaders() - SSF::HSE_REQ_SEND_RESPONSE_HEADER_EX failed (%d)\n", GetLastError() );
return FALSE;
}
return TRUE;
}
SCODE CEcb::ScAsyncWrite( BYTE * pbBuf,
DWORD dwcbBuf,
IIISAsyncIOCompleteObserver& obs )
{
SCODE sc = S_OK;
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncWrite() called...\n", GetCurrentThreadId(), this );
//
// At this point someone should have generated a response.
//
Assert( m_pecb->dwHttpStatusCode != 0 );
//
// Try to disable the async error response mechanism. If we succeed, then we
// can send a response. If we fail then we must not send a response -- the
// async error response mechanism already sent one.
//
if ( !m_aeri.FDisable() )
{
EcbTrace( "CEcb::ScAsyncWrite() - Async error response already in progress. Failing out with 0x%08lX\n", E_FAIL );
// Do not forget to set the error, as callers will be confused if the function
// returns FALSE, but GetLastError() will return S_OK.
//
sc = E_FAIL;
goto ret;
}
//
// If there is another async IO outstanding we do not want to start one more. IIS will fail
// us out, and we ourselves will not be able to handle the completion of initial async IO
// properly. So just kill the connection and return. This may happen when we attempt to
// send the response before the read is finished.
//
if (0 != InterlockedCompareExchange(&m_cRefAsyncIO,
1,
0))
{
// The function bellow is not supported starting from IIS 6.0 but let us call it anyway
// just in case support becomes available - and we want to call it if the binary is
// running on IIS 5.0. It does not matter that much, as the bad side of not closing the
// connection may hang the client, or error out on subsequent request. That is ok as
// the path is supposed to be hit in abnormal/error conditions when clients for example
// send in invalid requests trying to cause denial of service or similar things.
// So on IIS 6.0 the connection will not be closed, we will just error out. We have
// not seen this path hit on IIS 6.0 anyway when runing denial of service scripts as it
// handles custom errors differently.
//
if (m_pecb->ServerSupportFunction(m_pecb->ConnID,
HSE_REQ_CLOSE_CONNECTION,
NULL,
NULL,
NULL))
{
EcbTrace( "CEcb::ScAsyncWrite() - More than 1 async operation. Connection closed. Failing out with error 0x%08lX\n", E_ABORT );
sc = E_ABORT;
goto ret;
}
else
{
EcbTrace( "CEcb::ScAsyncWrite() - More than 1 async operation. ServerSupportFunction(HSE_REQ_CLOSE_CONNECTION) "
"failed with last error 0x%08lX. Overriding with fatal error 0x%08lX\n", GetLastError(), E_FAIL );
sc = E_FAIL;
goto ret;
}
}
//
// IIS allows only one async I/O operation at a time. But for performance reasons it
// leaves it up to the ISAPI to heed the restriction. For the same reasons, we push
// that responsibility off to the DAV impl. A simple refcount tells us whether
// the impl has done so.
//
AssertSz( 1 == m_cRefAsyncIO,
"CEcb::ScAsyncWrite() - m_cRefAsyncIO wrong on entry" );
//
// We need to hold a ref on the process-wide instance data for the duration of the I/O
// so that if IIS tells us to shut down while the I/O is still pending we will keep
// the instance data alive until we're done with the I/O.
//
AddRefImplInst();
//
// Set the async I/O completion observer
//
m_pobsAsyncIOComplete = &obs;
//
// Set up the async I/O completion routine and start writing.
// Add a ref for the I/O completion thread. Use auto_ref_ptr
// to make things exception-proof.
//
{
auto_ref_ptr<CEcb> pRef(this);
sc = ScSetIOCompleteCallback(IO_COMPLETION);
if (SUCCEEDED(sc))
{
if (m_pecb->WriteClient( m_pecb->ConnID, pbBuf, &dwcbBuf, HSE_IO_ASYNC ))
{
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncWrite() I/O pending...\n", GetCurrentThreadId(), this );
pRef.relinquish();
}
else
{
EcbTrace( "CEcb::ScAsyncWrite() - _EXTENSION_CONTROL_BLOCK::WriteClient() failed with last error 0x%08lX\n", GetLastError() );
sc = HRESULT_FROM_WIN32(GetLastError());
}
}
else
{
EcbTrace( "CEcb::ScAsyncWrite() - ScSetIOCompleteCallback() failed with error 0x%08lX\n", sc );
}
if (FAILED(sc))
{
LONG cRefAsyncIO;
//
// Release the instance ref we added above.
//
ReleaseImplInst();
//
// Decrement the async I/O refcount added above.
//
cRefAsyncIO = InterlockedDecrement(&m_cRefAsyncIO);
AssertSz( 0 == cRefAsyncIO,
"CEcb::ScAsyncWrite() - m_cRefAsyncIO wrong after failed async write" );
goto ret;
}
}
ret:
return sc;
}
SCODE CEcb::ScAsyncTransmitFile( const HSE_TF_INFO& tfi,
IIISAsyncIOCompleteObserver& obs )
{
SCODE sc = S_OK;
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncTransmitFile() called...\n", GetCurrentThreadId(), this );
//
// At this point someone should have generated a response.
//
Assert( m_pecb->dwHttpStatusCode != 0 );
//
// Try to disable the async error response mechanism. If we succeed, then we
// can send a response. If we fail then we must not send a response -- the
// async error response mechanism already sent one.
//
if ( !m_aeri.FDisable() )
{
EcbTrace( "CEcb::ScAsyncTransmitFile() - Async error response already in progress. Failing out with 0x%08lX\n", E_FAIL );
// Do not forget to set the error, as callers will be confused if the function
// returns FALSE, but GetLastError() will return S_OK.
//
sc = E_FAIL;
goto ret;
}
//
// If there is another async IO outstanding we do not want to start one more. IIS will fail
// us out, and we ourselves will not be able to handle the completion of initial async IO
// properly. So just kill the connection and return. This may happen when we attempt to
// send the response before the read is finished.
//
if (0 != InterlockedCompareExchange(&m_cRefAsyncIO,
1,
0))
{
// The function bellow is not supported starting from IIS 6.0 but let us call it anyway
// just in case support becomes available - and we want to call it if the binary is
// running on IIS 5.0. It does not matter that much, as the bad side of not closing the
// connection may hang the client, or error out on subsequent request. That is ok as
// the path is supposed to be hit in abnormal/error conditions when clients for example
// send in invalid requests trying to cause denial of service or similar things.
// So on IIS 6.0 the connection will not be closed, we will just error out. We have
// not seen this path hit on IIS 6.0 anyway when runing denial of service scripts as it
// handles custom errors differently.
//
if (m_pecb->ServerSupportFunction(m_pecb->ConnID,
HSE_REQ_CLOSE_CONNECTION,
NULL,
NULL,
NULL))
{
EcbTrace( "CEcb::ScAsyncTransmitFile() - More than 1 async operation. Connection closed. Failing out with error 0x%08lX\n", E_ABORT );
sc = E_ABORT;
goto ret;
}
else
{
EcbTrace( "CEcb::ScAsyncTransmitFile() - More than 1 async operation. ServerSupportFunction(HSE_REQ_CLOSE_CONNECTION) "
"failed with last error 0x%08lX. Overriding with fatal error 0x%08lX\n", GetLastError(), E_FAIL );
sc = E_FAIL;
goto ret;
}
}
//
// IIS allows only one async I/O operation at a time. But for performance reasons it
// leaves it up to the ISAPI to heed the restriction. For the same reasons, we push
// that responsibility off to the DAV impl. A simple refcount tells us whether
// the impl has done so.
//
AssertSz( 1 == m_cRefAsyncIO,
"CEcb::ScAsyncTransmitFile() - m_cRefAsyncIO wrong on entry" );
//
// We need to hold a ref on the process-wide instance data for the duration of the I/O
// so that if IIS tells us to shut down while the I/O is still pending we will keep
// the instance data alive until we're done with the I/O.
//
AddRefImplInst();
//
// Async I/O completion routine and context should have been passed
// in as parameters. Callers should NOT use the corresponding members
// of the HSE_TF_INFO structure. IIS has to call CEcb::IISIOComplete()
// so that it can release the critsec.
//
Assert( !tfi.pfnHseIO );
Assert( !tfi.pContext );
//
// Verify that the caller has set the async I/O flag
//
Assert( tfi.dwFlags & HSE_IO_ASYNC );
//
// Set the async I/O completion observer
//
m_pobsAsyncIOComplete = &obs;
//
// Set up the async I/O completion routine and start transmitting.
// Add a ref for the I/O completion thread. Use auto_ref_ptr
// to make things exception-proof.
//
{
auto_ref_ptr<CEcb> pRef(this);
sc = ScSetIOCompleteCallback(IO_COMPLETION);
if (SUCCEEDED(sc))
{
if (m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_TRANSMIT_FILE,
const_cast<HSE_TF_INFO *>(&tfi),
NULL,
NULL ))
{
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncTransmitFile() I/O pending...\n", GetCurrentThreadId(), this );
pRef.relinquish();
}
else
{
EcbTrace( "CEcb::ScAsyncTransmitFile() - ServerSupportFunction(HSE_REQ_TRANSMIT_FILE) failed with last error 0x%08lX\n", GetLastError() );
sc = HRESULT_FROM_WIN32(GetLastError());
}
}
else
{
EcbTrace( "CEcb::ScAsyncTransmitFile() - ScSetIOCompleteCallback() failed with error 0x%08lX\n", sc );
}
if (FAILED(sc))
{
LONG cRefAsyncIO;
//
// Release the instance ref we added above.
//
ReleaseImplInst();
//
// Decrement the async I/O refcount added above.
//
cRefAsyncIO = InterlockedDecrement(&m_cRefAsyncIO);
AssertSz( 0 == cRefAsyncIO,
"CEcb::ScAsyncTransmitFile() - m_cRefAsyncIO wrong after failed async transmit file" );
goto ret;
}
}
ret:
return sc;
}
// Other functions that start async IO with IIS. These functions are for IIS 6.0 or later
// only. We will not even use observers on them, as the completion esentially will serve
// just to signal some cleanup on a single string, which does not make much sense to wrap
// it up as the observer.
//
SCODE CEcb::ScAsyncCustomError60After( const HSE_CUSTOM_ERROR_INFO& cei,
LPSTR pszStatus )
{
SCODE sc = S_OK;
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncCustomError60After() called...\n", GetCurrentThreadId(), this );
//
// At this point someone should have generated a response.
//
Assert( m_pecb->dwHttpStatusCode != 0 );
//
// Try to disable the async error response mechanism. If we succeed, then we
// can send a response. If we fail then we must not send a response -- the
// async error response mechanism already sent one.
//
if ( !m_aeri.FDisable() )
{
EcbTrace( "CEcb::ScAsyncCustomError60After() - Async error response already in progress. Failing out with 0x%08lX\n", E_FAIL );
// Do not forget to set the error, as callers will be confused if the function
// returns FALSE, but GetLastError() will return S_OK.
//
sc = E_FAIL;
goto ret;
}
//
// If there is another async IO outstanding we do not want to start one more. IIS will fail
// us out, and we ourselves will not be able to handle the completion of initial async IO
// properly. So just kill the connection and return. This may happen when we attempt to
// send the response before the read is finished.
//
if (0 != InterlockedCompareExchange(&m_cRefAsyncIO,
1,
0))
{
// The function bellow is not supported starting from IIS 6.0 but let us call it anyway
// just in case support becomes available - and we want to call it if the binary is
// running on IIS 5.0. It does not matter that much, as the bad side of not closing the
// connection may hang the client, or error out on subsequent request. That is ok as
// the path is supposed to be hit in abnormal/error conditions when clients for example
// send in invalid requests trying to cause denial of service or similar things.
// So on IIS 6.0 the connection will not be closed, we will just error out. We have
// not seen this path hit on IIS 6.0 anyway when runing denial of service scripts as it
// handles custom errors differently.
//
if (m_pecb->ServerSupportFunction(m_pecb->ConnID,
HSE_REQ_CLOSE_CONNECTION,
NULL,
NULL,
NULL))
{
EcbTrace( "CEcb::ScAsyncCustomError60After() - More than 1 async operation. Connection closed. Failing out with error 0x%08lX\n", E_ABORT );
sc = E_ABORT;
goto ret;
}
else
{
EcbTrace( "CEcb::ScAsyncCustomError60After() - More than 1 async operation. ServerSupportFunction(HSE_REQ_CLOSE_CONNECTION) "
"failed with last error 0x%08lX. Overriding with fatal error 0x%08lX\n", GetLastError(), E_FAIL );
sc = E_FAIL;
goto ret;
}
}
//
// IIS allows only one async I/O operation at a time. But for performance reasons it
// leaves it up to the ISAPI to heed the restriction. For the same reasons, we push
// that responsibility off to the DAV impl. A simple refcount tells us whether
// the impl has done so.
//
AssertSz( 1 == m_cRefAsyncIO,
"CEcb::ScAsyncCustomError60After() - m_cRefAsyncIO wrong on entry" );
//
// We need to hold a ref on the process-wide instance data for the duration of the I/O
// so that if IIS tells us to shut down while the I/O is still pending we will keep
// the instance data alive until we're done with the I/O.
//
AddRefImplInst();
//
// Verify that the caller has set the async I/O flag
//
Assert( TRUE == cei.fAsync );
//
// Set up the async I/O completion routine and start transmitting.
// Add a ref for the I/O completion thread. Use auto_ref_ptr
// to make things exception-proof.
//
{
auto_ref_ptr<CEcb> pRef(this);
sc = ScSetIOCompleteCallback(CUSTERR_COMPLETION);
if (SUCCEEDED(sc))
{
if (m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_SEND_CUSTOM_ERROR,
const_cast<HSE_CUSTOM_ERROR_INFO *>(&cei),
NULL,
NULL ))
{
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncCustomError60After() I/O pending...\n", GetCurrentThreadId(), this );
pRef.relinquish();
}
else
{
EcbTrace( "CEcb::ScAsyncCustomError60After() - ServerSupportFunction(HSE_REQ_SEND_CUSTOM_ERROR) failed with last error 0x%08lX\n", GetLastError() );
sc = HRESULT_FROM_WIN32(GetLastError());
}
}
else
{
EcbTrace( "CEcb::ScAsyncCustomError60After() - ScSetIOCompleteCallback() failed with error 0x%08lX\n", sc );
}
if (FAILED(sc))
{
LONG cRefAsyncIO;
//
// Release the instance ref we added above.
//
ReleaseImplInst();
//
// Decrement the async I/O refcount added above.
//
cRefAsyncIO = InterlockedDecrement(&m_cRefAsyncIO);
AssertSz( 0 == cRefAsyncIO,
"CEcb::ScAsyncCustomError60After() - m_cRefAsyncIO wrong after failed async custom error" );
goto ret;
}
}
// We need to take ownership of the status string that was passed in in the case of success
// From looking in the code in IIS it does not mater if we keep it alive until completion,
// as it is anyway realocated before going onto another thread. But just in case something
// changes and to be doing what IIS people asked us to do we will keep it alive. String has
// the format of "nnn reason".
//
m_pszStatus.take_ownership(pszStatus);
ret:
return sc;
}
SCODE CEcb::ScAsyncExecUrl60After( const HSE_EXEC_URL_INFO& eui )
{
SCODE sc = S_OK;
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncExecUrl60After() called...\n", GetCurrentThreadId(), this );
//
// At this point someone should have generated a response.
//
Assert( m_pecb->dwHttpStatusCode != 0 );
//
// Try to disable the async error response mechanism. If we succeed, then we
// can send a response. If we fail then we must not send a response -- the
// async error response mechanism already sent one.
//
if ( !m_aeri.FDisable() )
{
EcbTrace( "CEcb::ScAsyncExecUrl60After() - Async error response already in progress. Failing out with 0x%08lX\n", E_FAIL );
sc = E_FAIL;
goto ret;
}
//
// If there is another async IO outstanding we do not want to start one more. IIS will fail
// us out, and we ourselves will not be able to handle the completion of initial async IO
// properly. So just kill the connection and return. This may happen when we attempt to
// send the response before the read is finished.
//
if (0 != InterlockedCompareExchange(&m_cRefAsyncIO,
1,
0))
{
// The function bellow is not supported starting from IIS 6.0 but let us call it anyway
// just in case support becomes available - and we want to call it if the binary is
// running on IIS 5.0. It does not matter that much, as the bad side of not closing the
// connection may hang the client, or error out on subsequent request. That is ok as
// the path is supposed to be hit in abnormal/error conditions when clients for example
// send in invalid requests trying to cause denial of service or similar things.
// So on IIS 6.0 the connection will not be closed, we will just error out. We have
// not seen this path hit on IIS 6.0 anyway when runing denial of service scripts as it
// handles custom errors differently.
//
if (m_pecb->ServerSupportFunction(m_pecb->ConnID,
HSE_REQ_CLOSE_CONNECTION,
NULL,
NULL,
NULL))
{
EcbTrace( "CEcb::ScAsyncExecUrl60After() - More than 1 async operation. Connection closed. Failing out with error 0x%08lX\n", E_ABORT );
sc = E_ABORT;
goto ret;
}
else
{
EcbTrace( "CEcb::ScAsyncExecUrl60After() - More than 1 async operation. ServerSupportFunction(HSE_REQ_CLOSE_CONNECTION) "
"failed with last error 0x%08lX. Overriding with fatal error 0x%08lX\n", GetLastError(), E_FAIL );
sc = E_FAIL;
goto ret;
}
}
//
// IIS allows only one async I/O operation at a time. But for performance reasons it
// leaves it up to the ISAPI to heed the restriction. For the same reasons, we push
// that responsibility off to the DAV impl. A simple refcount tells us whether
// the impl has done so.
//
AssertSz( 1 == m_cRefAsyncIO,
"CEcb::ScAsyncExecUrl60After() - m_cRefAsyncIO wrong on entry" );
//
// We need to hold a ref on the process-wide instance data for the duration of the I/O
// so that if IIS tells us to shut down while the I/O is still pending we will keep
// the instance data alive until we're done with the I/O.
//
AddRefImplInst();
//
// Set up the async I/O completion routine and start transmitting.
// Add a ref for the I/O completion thread. Use auto_ref_ptr
// to make things exception-proof.
//
{
auto_ref_ptr<CEcb> pRef(this);
sc = ScSetIOCompleteCallback(EXECURL_COMPLETION);
if (SUCCEEDED(sc))
{
if (m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_EXEC_URL,
const_cast<HSE_EXEC_URL_INFO *>(&eui),
NULL,
NULL ))
{
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::ScAsyncExecUrl60After() I/O pending...\n", GetCurrentThreadId(), this );
pRef.relinquish();
}
else
{
EcbTrace( "CEcb::ScAsyncExecUrl60After() - ServerSupportFunction(HSE_REQ_EXEC_URL) failed with last error 0x%08lX\n", GetLastError() );
sc = HRESULT_FROM_WIN32(GetLastError());
}
}
else
{
EcbTrace( "CEcb::ScAsyncExecUrl60After() - ScSetIOCompleteCallback() failed with error 0x%08lX\n", sc );
}
if (FAILED(sc))
{
LONG cRefAsyncIO;
//
// Release the instance ref we added above.
//
ReleaseImplInst();
//
// Decrement the async I/O refcount added above.
//
cRefAsyncIO = InterlockedDecrement(&m_cRefAsyncIO);
AssertSz( 0 == cRefAsyncIO,
"CEcb::ScAsyncExecUrl60After() - m_cRefAsyncIO wrong after failed async exec url" );
goto ret;
}
}
ret:
return sc;
}
SCODE CEcb::ScSetIOCompleteCallback( LONG lCompletion )
{
SCODE sc = S_OK;
//
// Do not reset the completion function if it is already set to the
// same one we want to set. There is no need to protect the member
// variable against multithreaded access as the callers of this
// function are already protecting against the overlap of 2 async
// IO-s and this function is the only one changing variable value
// and is always called within protected zone.
//
if ( lCompletion != m_lSetIISIOCompleteCallback )
{
//
// Figure out what completion function we need
//
PFN_HSE_IO_COMPLETION pfnCallback;
if (IO_COMPLETION == lCompletion)
{
pfnCallback = reinterpret_cast<PFN_HSE_IO_COMPLETION>(IISIOComplete);
}
else if (CUSTERR_COMPLETION == lCompletion)
{
pfnCallback = reinterpret_cast<PFN_HSE_IO_COMPLETION>(CustomErrorIOCompletion);
}
else if (EXECURL_COMPLETION == lCompletion)
{
pfnCallback = reinterpret_cast<PFN_HSE_IO_COMPLETION>(ExecuteUrlIOCompletion);
}
else
{
EcbTrace( "CEcb::ScSetIOCompleteCallback() - attempting to set unknown completion function. Failing out with 0x%08lX\n", E_FAIL );
sc = E_FAIL;
goto ret;
}
// Set the IIS I/O completion routine to the requested one. Some of those
// routines will simply handle the completion, others will forward to the
// right observer.
//
if (!m_pecb->ServerSupportFunction(m_pecb->ConnID,
HSE_REQ_IO_COMPLETION,
pfnCallback,
NULL,
reinterpret_cast<LPDWORD>(this)))
{
EcbTrace( "CEcb::ScSetIOCompleteCallback() - ServerSupportFunction(HSE_REQ_IO_COMPLETION) failed with last error 0x%08lX\n", GetLastError() );
sc = HRESULT_FROM_WIN32(GetLastError());
goto ret;
}
m_lSetIISIOCompleteCallback = lCompletion;
}
ret:
return sc;
}
VOID WINAPI
CEcb::IISIOComplete( const EXTENSION_CONTROL_BLOCK * pecbIIS,
CEcb * pecb,
DWORD dwcbIO,
DWORD dwLastError )
{
BOOL fCaughtException = FALSE;
// PLEASE SEE *** EXTREMELY IMPORTANT NOTE *** near the bottom of this
// function for more information on the proper way to unwind (deinit())
// this auto_ref_ptr!
//
auto_ref_ptr<CEcb> pThis;
//
// Don't let thrown C++ exceptions propagate out of this entrypoint.
//
try
{
//
// Translate async Win32 exceptions into thrown C++ exceptions.
// This must be placed inside the try block!
//
CWin32ExceptionHandler win32ExceptionHandler;
LONG cRefAsyncIO;
//
// Take ownership of the reference added
// on our behalf by the thread that started the async I/O.
//
pThis.take_ownership(pecb);
EcbTrace( "DAV: TID %3d: 0x%08lX: CEcb::IISIOComplete() called\n", GetCurrentThreadId(), pecb );
//
// A quick sanity check to make sure the context
// is really us...
//
Assert( !IsBadReadPtr( pecb, sizeof(CEcb) ) );
Assert( pecb->m_pecb == pecbIIS );
IIISAsyncIOCompleteObserver * pobsAsyncIOComplete = pThis->m_pobsAsyncIOComplete;
//
// Decrement the async I/O refcount added by the routine that
// started the async I/O. Do this before calling the I/O
// completion routine which can start new async I/O.
//
cRefAsyncIO = InterlockedDecrement(&pThis->m_cRefAsyncIO);
AssertSz( 0 == cRefAsyncIO,
"CEcb::IISIOComplete() - m_cRefAsyncIO wrong after async I/O complete" );
// Tell the observer that the I/O is complete
//
pobsAsyncIOComplete->IISIOComplete( dwcbIO, dwLastError );
}
catch ( CDAVException& )
{
fCaughtException = TRUE;
}
//
// If we caught an exception then handle it as best we can
//
if ( fCaughtException )
{
//
// If we have a CEcb then use it to handle the exception.
// If we don't have one then there's nothing we can do --
// there is no way to return any status from this function.
//
if ( pThis.get() )
(VOID) pThis->HSEHandleException();
}
//
// Release the instance ref added by the routine that started the async I/O.
// We must do this as the VERY LAST THING(tm) before returning control back
// to IIS because during shutdown, this could be the last reference to the
// instance data.
//
// EXTREMELY IMPORTANT NOTE: If this is the last reference on the instance
// data, everything will get torn down (we're finished with everything, so
// we can clean up everything). Specifically, our HEAPS will be DESTROYED
// here in this situation. Hence, we need to clear out the auto_ref_ptr
// from above ********** BEFORE ********** we call ReleaseImplInst().
// Otherwise, we could end up trying to touch the reference count on the
// CEcb object pointed to by the auto_ref_ptr AFTER we have destroyed the
// heap it was allocated on. This is A BAD THING(tm).
//
// This bug was found in IIS stress on 18 June 1999, and was filed as NTRAID
// bug #358578.
//
// Per "EXTREMELY IMPORTANT NOTE" above: CLEAR the auto_ref_ptr
// ********** BEFORE ********** calling ReleaseImplInst().
//
pThis.clear();
// Now it is safe to call ReleaseImplInst().
//
ReleaseImplInst();
}
VOID WINAPI
CEcb::CustomErrorIOCompletion ( const EXTENSION_CONTROL_BLOCK * pecbIIS,
CEcb * pecb,
DWORD dwcbIO,
DWORD dwLastError )
{
auto_ref_ptr<CEcb> pThis;
LONG cRefAsyncIO;
//
// Take ownership of the reference added
// on our behalf by the thread that started the async I/O.
//
pThis.take_ownership(pecb);
//
// Decrement the async I/O refcount added by the routine that
// started the async I/O.
//
cRefAsyncIO = InterlockedDecrement(&pThis->m_cRefAsyncIO);
AssertSz( 0 == cRefAsyncIO,
"CEcb::CustomErrorIOCompletion() - m_cRefAsyncIO wrong after async I/O complete" );
EcbTrace( "Custom Error finished with dwcbIO = %d, error = %d\n", dwcbIO, dwLastError);
EcbTrace( "More info about the request:\n");
EcbTrace( "\tcbSize = %lu\n", pecbIIS->cbSize );
EcbTrace( "\tdwVersion = %lu\n", pecbIIS->dwVersion );
EcbTrace( "\tlpszMethod = \"%s\"\n", pecbIIS->lpszMethod );
EcbTrace( "\tlpszQueryString = \"%s\"\n", pecbIIS->lpszQueryString );
EcbTrace( "\tlpszPathInfo = \"%s\"\n", pecbIIS->lpszPathInfo );
EcbTrace( "\tlpszPathTranslated = \"%s\"\n", pecbIIS->lpszPathTranslated );
EcbTrace( "\tcbTotalBytes = %lu\n", pecbIIS->cbTotalBytes );
EcbTrace( "\tcbAvailable = %lu\n", pecbIIS->cbAvailable );
EcbTrace( "\tlpszContentType = \"%s\"\n", pecbIIS->lpszContentType );
EcbTrace( "\n" );
// We need to make sure that last release of memory is finished before we release
// ref on CImplInst (as when CImplInst goes away so does our heap and we do not
// want to do operations on memory if the heap itself is gone).
//
pThis.clear();
ReleaseImplInst();
}
VOID WINAPI
CEcb::ExecuteUrlIOCompletion( const EXTENSION_CONTROL_BLOCK * pecbIIS,
CEcb * pecb,
DWORD dwcbIO,
DWORD dwLastError )
{
auto_ref_ptr<CEcb> pThis;
LONG cRefAsyncIO;
//
// Take ownership of the reference added
// on our behalf by the thread that started the async I/O.
//
pThis.take_ownership(pecb);
//
// Decrement the async I/O refcount added by the routine that
// started the async I/O.
//
cRefAsyncIO = InterlockedDecrement(&pThis->m_cRefAsyncIO);
AssertSz( 0 == cRefAsyncIO,
"CEcb::CustomErrorIOCompletion() - m_cRefAsyncIO wrong after async I/O complete" );
EcbTrace( "Exec_URL finished with dwcbIO = %d, error = %d\n", dwcbIO, dwLastError);
EcbTrace( "More info about the request:\n");
EcbTrace( "\tcbSize = %lu\n", pecbIIS->cbSize );
EcbTrace( "\tdwVersion = %lu\n", pecbIIS->dwVersion );
EcbTrace( "\tlpszMethod = \"%s\"\n", pecbIIS->lpszMethod );
EcbTrace( "\tlpszQueryString = \"%s\"\n", pecbIIS->lpszQueryString );
EcbTrace( "\tlpszPathInfo = \"%s\"\n", pecbIIS->lpszPathInfo );
EcbTrace( "\tlpszPathTranslated = \"%s\"\n", pecbIIS->lpszPathTranslated );
EcbTrace( "\tcbTotalBytes = %lu\n", pecbIIS->cbTotalBytes );
EcbTrace( "\tcbAvailable = %lu\n", pecbIIS->cbAvailable );
EcbTrace( "\tlpszContentType = \"%s\"\n", pecbIIS->lpszContentType );
EcbTrace( "\n" );
// We need to make sure that last release of memory is finished before we release
// ref on CImplInst (as when CImplInst goes away so does our heap and we do not
// want to do operations on memory if the heap itself is gone).
//
pThis.clear();
ReleaseImplInst();
}
// This is how we execute a child in any IIS version before IIS 6.0
//
SCODE CEcb::ScSyncExecuteChildWide60Before( LPCWSTR pwszUrl,
LPCSTR pszQueryString,
BOOL fCustomErrorUrl )
{
SCODE sc = S_OK;
auto_heap_ptr<CHAR> pszUrlEscaped;
CStackBuffer<CHAR, MAX_PATH> pszUrl;
DWORD dwExecFlags;
LPCSTR pszUrlToForward;
LPCSTR pszVerb = NULL;
UINT cch;
UINT cb;
UINT cbQueryString = 0;
Assert( m_pecb );
Assert( pwszUrl );
cch = static_cast<UINT>(wcslen(pwszUrl));
Assert(L'\0' == pwszUrl[cch]);
cb = cch * 3;
if (pszQueryString)
{
cbQueryString = static_cast<UINT>(strlen(pszQueryString));
}
// Resize the buffer to the sufficient size, leave place for '\0' termination.
// We also add the length of the query string there, although it is not necessary
// at this step - but in many cases it will save us the allocation afterwards,
// as we already will have sufficient buffer even for escaped version of the string.
//
if (!pszUrl.resize(cb + cbQueryString + 1))
{
sc = E_OUTOFMEMORY;
DebugTrace("CEcb::ScSyncExecuteChildWide60Before() - Error while allocating memory 0x%08lX\n", sc);
goto ret;
}
// Convert URL to skinny including '\0' termination.
//
cb = WideCharToMultiByte(CP_UTF8,
0,
pwszUrl,
cch + 1,
pszUrl.get(),
cb + 1,
0,
0);
if (0 == cb)
{
sc = HRESULT_FROM_WIN32(GetLastError());
DebugTrace("CEcb::ScSyncExecuteChildWide60Before() - WideCharToMultiByte() failed 0x%08lX\n", sc);
goto ret;
}
// Escape the URL
//
HttpUriEscape( pszUrl.get(), pszUrlEscaped );
// Handle the query string
//
if (cbQueryString)
{
// Find out the length of new URL
//
cb = static_cast<UINT>(strlen(pszUrlEscaped.get()));
// Resize the buffer to the sufficient size, leave place for '\0' termination.
//
if (!pszUrl.resize(cb + cbQueryString + 1))
{
sc = E_OUTOFMEMORY;
DebugTrace("CEcb::ScSyncExecuteChildWide60Before() - Error while allocating memory 0x%08lX\n", sc);
goto ret;
}
// Copy the escaped version of the URL
//
memcpy(pszUrl.get(), pszUrlEscaped.get(), cb);
// Copy the query string at the end together with it's '\0' termination.
//
memcpy(pszUrl.get() + cb, pszQueryString, cbQueryString + 1);
// Point to the constructed URL
//
pszUrlToForward = pszUrl.get();
}
else
{
// In the case we do not have query string then the URL to forward to
// is the same as the escaped URL.
//
pszUrlToForward = pszUrlEscaped.get();
}
// Depending on the fact if we are doing custom error or executing script
// determine the execution flags and the verb.
//
if ( fCustomErrorUrl )
{
// We enable wildcard processing here, since we want
// to give one chance to all CE URLs.
// Calling into ourselves is fine here and we prevent
// recusrion using another scheme!
//
dwExecFlags = HSE_EXEC_CUSTOM_ERROR;
if (!strcmp(LpszMethod(), gc_szHEAD))
{
// If this is a HEAD request, tell whomever we
// forward this to to only pass back the status
// line and headers.
//
pszVerb = gc_szHEAD;
}
else
{
pszVerb = gc_szGET;
}
// If we're doing a custom error then someone has
// already set the status code.
//
Assert( m_pecb->dwHttpStatusCode != 0 );
}
else
{
// When we are executing scripts, we disable wild card
// execution to prevent recursion.
// Also the verb field is allowed to be NULL, it is optional and
// used only for custom error processing.
//
dwExecFlags = HSE_EXEC_NO_ISA_WILDCARDS;
pszVerb = NULL;
// We need to set the status code here to 200 (like it
// was set when we were first called) just in case
// child ISAPIs depend on it.
//
SetStatusCode(200);
}
if (!m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_EXECUTE_CHILD,
const_cast<LPSTR>(pszUrlToForward),
reinterpret_cast<LPDWORD>(const_cast<LPSTR>(pszVerb)),
&dwExecFlags ))
{
// Reset the status code back to 0 if we failed to execute
// the child ISAPI because we will be handling the request
// ourselves later (probably by sending an error).
//
if (!fCustomErrorUrl)
{
SetStatusCode(0);
}
sc = HRESULT_FROM_WIN32(GetLastError());
DebugTrace("CEcb::ScSyncExecuteChildWide60Before() - ServerSupportFunction(HSE_REQ_EXECUTE_CHILD) failed with error 0x%08lX\n", sc);
goto ret;
}
// Set the flag stating that we have successfully executed a child ISAPI.
//
m_fChildISAPIExecSuccess = TRUE;
ret:
return sc;
}
SCODE CEcb::ScAsyncExecUrlWide60After( LPCWSTR pwszUrl,
LPCSTR pszQueryString,
BOOL fCustomErrorUrl )
{
SCODE sc = S_OK;
HSE_EXEC_URL_INFO execUrlInfo;
auto_heap_ptr<CHAR> pszUrlEscaped;
CStackBuffer<CHAR, MAX_PATH> pszUrl;
UINT cch;
UINT cb;
UINT cbQueryString = 0;
Assert( m_pecb );
Assert( pwszUrl );
Assert(!fCustomErrorUrl); // In IIS60 custom error is NOT done by execute URL
cch = static_cast<UINT>(wcslen(pwszUrl));
Assert(L'\0' == pwszUrl[cch]);
cb = cch * 3;
if (pszQueryString)
{
cbQueryString = static_cast<UINT>(strlen(pszQueryString));
}
// Resize the buffer to the sufficient size, leave place for '\0' termination.
// We also add the length of the query string there, although it is not necessary
// at this step - but in many cases it will save us the allocation afterwards,
// as we already will have sufficient buffer even for escaped version of the string.
//
if (!pszUrl.resize(cb + cbQueryString + 1))
{
sc = E_OUTOFMEMORY;
DebugTrace("CEcb::ScAsyncExecUrlWide60After() - Error while allocating memory 0x%08lX\n", sc);
goto ret;
}
// Convert to skinny including '\0' termination
//
cb = WideCharToMultiByte(CP_UTF8,
0,
pwszUrl,
cch + 1,
pszUrl.get(),
cb + 1,
0,
0);
if (0 == cb)
{
sc = HRESULT_FROM_WIN32(GetLastError());
DebugTrace("CEcb::ScAsyncExecUrlWide60After() - WideCharToMultiByte() failed 0x%08lX\n", sc);
goto ret;
}
// Escape the URL
//
HttpUriEscape( pszUrl.get(), pszUrlEscaped );
// Handle the query string
//
if (cbQueryString)
{
// Find out the length of new URL
//
cb = static_cast<UINT>(strlen(pszUrlEscaped.get()));
// Resize the buffer to the sufficient size, leave place for '\0' termination.
//
if (!pszUrl.resize(cb + cbQueryString + 1))
{
sc = E_OUTOFMEMORY;
DebugTrace("CEcb::ScAsyncExecUrlWide60After() - Error while allocating memory 0x%08lX\n", sc);
goto ret;
}
// Copy the escaped version of the URL
//
memcpy(pszUrl.get(), pszUrlEscaped.get(), cb);
// Copy the query string at the end together with it's '\0' termination.
//
memcpy(pszUrl.get() + cb, pszQueryString, cbQueryString + 1);
// Point to the constructed URL
//
execUrlInfo.pszUrl = pszUrl.get();
}
else
{
// In the case we do not have query string then the URL to forward to
// is the same as the escaped URL.
//
execUrlInfo.pszUrl = pszUrlEscaped.get();
}
// Initialize method name
//
execUrlInfo.pszMethod = NULL;
// Initialize child headers
//
execUrlInfo.pszChildHeaders = NULL;
// We don't need a new user context,
//
execUrlInfo.pUserInfo = NULL;
// We don't need a new entity either
//
execUrlInfo.pEntity = NULL;
// Pick up the execution flags
//
execUrlInfo.dwExecUrlFlags = HSE_EXEC_URL_DISABLE_CUSTOM_ERROR;
// We need to set the status code here to 200 (like it
// was set when we were first called) just in case
// child ISAPIs depend on it.
//
SetStatusCode(200);
sc = ScAsyncExecUrl60After( execUrlInfo );
if (FAILED(sc))
{
// Reset the status code back to 0 if we failed to execute
// the child ISAPI because we will be handling the request
// ourselves later (probably by sending an error).
//
SetStatusCode(0);
DebugTrace("CEcb::ScAsyncExecUrlWide60After() - CEcb::ScAsyncExecUrl60After() failed with error 0x%08lX\n", sc);
goto ret;
}
// Set the flag stating that we have successfully executed a child ISAPI.
//
m_fChildISAPIExecSuccess = TRUE;
ret:
return sc;
}
SCODE CEcb::ScSendRedirect( LPCSTR pszURI )
{
SCODE sc = S_OK;
//
// We cannot assert that the dwHttpStatusCode status code in ECB is
// some particular value. On IIS 5.X it will be 0, IIS 6.0 has changed
// the behaviour and will shuffle 200 into it upon calling us. Also we
// have seen ourselves be called from IIS 6.0 when If-Modified-Since,
// Translate: t request is given which is the bug in IIS (WB 277208),
// as in that case IIS must be handling that. So untill we could assert
// for the response code to be 0 (IIS 5.X) or 200 (IIS 6.0) we will have
// to wait till IIS 6.0 is fixed up.
//
//
// Fill in the appropriate status code in the ECB
// (for IIS logging).
//
SetStatusCode(HSC_MOVED_TEMPORARILY);
//
// Attempt to send a redirection response. If successful then
// the response will be handled by IIS. If unsuccessful
// then we will handle the response later.
//
Assert( pszURI );
DWORD cbURI = static_cast<DWORD>(strlen( pszURI ) * sizeof(CHAR));
if ( !m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_SEND_URL_REDIRECT_RESP,
const_cast<LPSTR>(pszURI),
&cbURI,
NULL ) )
{
//
// Reset the status code back to 0 if we failed to send
// the redirect because we will be handling the request
// ourselves later (probably by sending an error).
//
SetStatusCode(0);
sc = HRESULT_FROM_WIN32(GetLastError());
DebugTrace( "CEcb::ScSendRedirect() - ServerSupportFunction(HSE_REQ_SEND_URL_REDIRECT_RESP) failed with error 0x%08lX\n", sc );
goto ret;
}
ret:
return sc;
}
// ------------------------------------------------------------------------
//
// CEcb::FProcessingCEUrl()
//
// Find out if we are called with a CE URL.
// Important to avoid recursive invocation while
// doing custom error URLs.
//
BOOL
CEcb::FProcessingCEUrl( ) const
{
// Assume that we are not doing custom error processing.
// For IIS 6.0 and after it is always the right thing to assume,
// due to the changes in behaviour from IIS 5.x
//
BOOL fCustErr = FALSE;
// In the case of IIS 5.x we do the usual determinatin.
//
if (m_pecb->dwVersion < IIS_VERSION_6_0)
{
// By default assume custom error processing.
// Why? Suppose somebody forgets to tell us that
// it is a custom error url. We dont want recursive
// calls in that case. So it is safer to assume that
// we are doing custom error processing. This is only
// used to determine if we want to invoke custom
// error URLs and hence has no other side effects.
//
DWORD dwExecFlags = HSE_EXEC_CUSTOM_ERROR;
if (!(m_pecb->ServerSupportFunction( m_pecb->ConnID,
HSE_REQ_GET_EXECUTE_FLAGS,
NULL,
NULL,
&dwExecFlags )))
{
DebugTrace("CEcb::FProcessingCEUrl Server supportFunction call failed.\n");
DebugTrace("CEcb::FProcessingCEUrl Assuming custom error processing.\n");
}
fCustErr = !!(dwExecFlags & HSE_EXEC_CUSTOM_ERROR);
}
return fCustErr;
}
// ========================================================================
//
// FREE FUNCTIONS
//
// ------------------------------------------------------------------------
//
// NewEcb
//
IEcb * NewEcb( EXTENSION_CONTROL_BLOCK& ecbRaw,
BOOL fUseRawUrlMappings,
DWORD * pdwHSEStatusRet )
{
Assert( !IsBadWritePtr(pdwHSEStatusRet, sizeof(DWORD)) );
auto_ref_ptr<CEcb> pecb;
pecb.take_ownership(new CEcb(ecbRaw));
if ( pecb->FInitialize(fUseRawUrlMappings) )
return pecb.relinquish();
//
// If we couldn't init, there are two cases: If there is a status code
// set into the (raw) ECB, then the error was that LpszRequestUrl() returned
// NULL (bad URL). In this case, we need to go ahead and send a response to
// the client that is appropriate (400 Bad Request) and then continue on
// the way we normally would. Since the CEcb was already constructed, we can
// do everything just as we would for a normal request, calling
// DoneWithSession() and then returning HSE_STATUS_PENDING to IIS.
//
// If there was NOT a status code in the (raw) ECB, we just treat it as an
// exception. Think of this as functionally equal to throwing from a
// constructor.
//
if (ecbRaw.dwHttpStatusCode)
{
//
// The only path that can get here right now is if LpszRequestUrl()
// returns NULL during the CEcb FInitialize() call. Assert that
// we had 400 (Bad Request) in the ECB.
//
Assert(HSC_BAD_REQUEST == ecbRaw.dwHttpStatusCode);
//
// Send a 400 Bad Request response. We use the async error
// response mechanism, but that technically doesn't matter since we
// are for sure are not in the middle of sending some other response on
// another thread (we've not even dispatched out to the actual methods
// (DAV*) yet).
//
Assert(pecb.get());
pecb->SendAsyncErrorResponse(HSC_BAD_REQUEST,
gc_szDefErr400StatusLine,
CchConstString(gc_szDefErr400StatusLine),
NULL,
0);
//
// Tell IIS that we are done with the EXTENSION_CONTROL_BLOCK that
// it gave us. We must do this or IIS will not be able to shut down.
// If this call succeeds (doesn't except), we MUST return
// HSE_STATUS_PENDING to let IIS know that we claimed a ref on the
// EXTENSION_CONTROL_BLOCK.
//
pecb->DoneWithSession( FALSE );
*pdwHSEStatusRet = HSE_STATUS_PENDING;
}
else
{
*pdwHSEStatusRet = pecb->HSEHandleException();
}
return NULL;
}
// ------------------------------------------------------------------------
//
// CbMDPathW
//
ULONG CbMDPathW (const IEcb& ecb, LPCWSTR pwszURI)
{
// The number of bytes we returned could be more than the path needs
//
return static_cast<UINT>((wcslen(ecb.InstData().GetNameW()) + wcslen(pwszURI) + 1) * sizeof(WCHAR));
}
// ------------------------------------------------------------------------
//
// MDPathFromURIW
//
VOID MDPathFromURIW (const IEcb& ecb, LPCWSTR pwszURI, LPWSTR pwszMDPath)
{
LPCWSTR pwszVroot;
// If the URI is fully qualified, then somebody is not
// playing fair. Gently nudge them.
//
Assert (pwszURI);
Assert (pwszURI == PwszUrlStrippedOfPrefix (pwszURI));
// Copy the root name the instance -- MINUS the vroot
//
UINT cch = static_cast<UINT>(wcslen(ecb.InstData().GetNameW()));
cch -= ecb.CchGetVirtualRootW(&pwszVroot);
memcpy (pwszMDPath, ecb.InstData().GetNameW(), sizeof(WCHAR) * cch);
// Copy the rest that is after the vroot path.
//
wcscpy (pwszMDPath + cch, pwszURI);
}