mirror of https://github.com/tongzx/nt5src
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.
6555 lines
164 KiB
6555 lines
164 KiB
/**********************************************************************/
|
|
/** Microsoft Windows NT **/
|
|
/** Copyright(c) Microsoft Corp., 1994 **/
|
|
/**********************************************************************/
|
|
|
|
/*
|
|
httpreq.cxx
|
|
|
|
This module contains the http request class implementation
|
|
|
|
|
|
FILE HISTORY:
|
|
Johnl 24-Aug-1994 Created
|
|
MuraliK 16-May-1995 Modified LogInformation structure
|
|
after adding additional fields.
|
|
MuraliK 22-Jan-1996 Cache & use UNC impersonation.
|
|
*/
|
|
|
|
|
|
#include "w3p.hxx"
|
|
#include <inetinfo.h>
|
|
|
|
#include <ole2.h>
|
|
#include <issperr.h>
|
|
|
|
#include <imd.h>
|
|
#include <mb.hxx>
|
|
#include <mbstring.h>
|
|
#include "wamexec.hxx"
|
|
|
|
extern "C"
|
|
{
|
|
#include "md5.h"
|
|
}
|
|
|
|
|
|
#pragma warning( disable:4355 ) // 'this' used in base member initialization
|
|
|
|
extern LPSYSTEMTIME
|
|
MinSystemTime(
|
|
LPSYSTEMTIME Now,
|
|
LPSYSTEMTIME Other
|
|
);
|
|
|
|
|
|
//
|
|
// Hash defines
|
|
|
|
//
|
|
//
|
|
// Size of read during cert renegotiation phase
|
|
//
|
|
|
|
#define CERT_RENEGO_READ_SIZE (1024*4)
|
|
|
|
//
|
|
// CGI Header Prefix
|
|
//
|
|
#define CGI_HEADER_PREFIX_SZ "HTTP_"
|
|
#define CGI_HEADER_PREFIX_CCH (sizeof(CGI_HEADER_PREFIX_SZ) - 1)
|
|
|
|
//
|
|
// Private globals.
|
|
//
|
|
|
|
CHAR Slash[] = "/";
|
|
|
|
BOOL HTTP_REQUEST::_fGlobalInit = FALSE;
|
|
|
|
HTTP_REQUEST::PFN_GET_INFO HTTP_REQUEST::sm_GetInfoFuncs[26];
|
|
|
|
|
|
//
|
|
// This table contains the verbs we recognize
|
|
//
|
|
|
|
struct _HTTP_VERBS
|
|
{
|
|
TCHAR * pchVerb; // Verb name
|
|
UINT cchVerb; // Count of characters in verb
|
|
HTTP_VERB httpVerb;
|
|
HTTP_REQUEST::PMFN_DOVERB pmfnVerb; // Pointer to member function
|
|
}
|
|
DoVerb[] =
|
|
{
|
|
"GET", 3, HTV_GET, &HTTP_REQUEST::DoGet,
|
|
"HEAD", 4, HTV_HEAD, &HTTP_REQUEST::DoGet,
|
|
"TRACE", 5, HTV_TRACE, &HTTP_REQUEST::DoTrace,
|
|
"PUT", 3, HTV_PUT, &HTTP_REQUEST::DoUnknown,
|
|
"DELETE", 6, HTV_DELETE, &HTTP_REQUEST::DoUnknown,
|
|
"TRACK", 5, HTV_TRACECK, &HTTP_REQUEST::DoTraceCk,
|
|
"POST", 4, HTV_POST, &HTTP_REQUEST::DoUnknown,
|
|
"OPTIONS", 7, HTV_OPTIONS, &HTTP_REQUEST::DoOptions,
|
|
NULL, 0, HTV_UNKNOWN, NULL
|
|
};
|
|
|
|
//
|
|
// Private Prototypes.
|
|
//
|
|
BOOL
|
|
BuildCGIHeaderListInSTR( STR * pstr,
|
|
HTTP_HEADERS * pHeaderList
|
|
);
|
|
|
|
/*******************************************************************
|
|
|
|
Maco support for ref/deref of CLIENT_CONN
|
|
|
|
HISTORY:
|
|
DaveK 22-Sep-1997 Added ref logging to HTTP_REQUEST
|
|
|
|
NOTE callers to HR_LOG_REF_COUNT will not change ref count
|
|
directly because HTTP_REQUEST can only change ref count by
|
|
calling CLIENT_CONN::Ref/Deref. We use negative ref count here
|
|
to indicate no change to ref count.
|
|
|
|
********************************************************************/
|
|
#if DBG
|
|
extern PTRACE_LOG g_pDbgCCRefTraceLog;
|
|
#endif
|
|
|
|
#define HR_LOG_REF_COUNT() \
|
|
\
|
|
SHARED_LOG_REF_COUNT( \
|
|
- (int) QueryRefCount() \
|
|
, _pClientConn \
|
|
, this \
|
|
, _pWamRequest \
|
|
, _htrState \
|
|
); \
|
|
|
|
|
|
|
|
//
|
|
// Functions.
|
|
//
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::HTTP_REQUEST
|
|
|
|
SYNOPSIS: Http request object constructor
|
|
|
|
ENTRY: pClientConn - Client connection the request is being made on
|
|
|
|
NOTES: Constructor can't fail
|
|
|
|
HISTORY:
|
|
Johnl 24-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
HTTP_REQUEST::HTTP_REQUEST(
|
|
CLIENT_CONN * pClientConn,
|
|
PVOID pvInitialBuff,
|
|
DWORD cbInitialBuff
|
|
)
|
|
: HTTP_REQ_BASE( pClientConn,
|
|
pvInitialBuff,
|
|
cbInitialBuff ),
|
|
_pGetFile ( NULL ),
|
|
_pWamRequest ( NULL )
|
|
{
|
|
}
|
|
|
|
HTTP_REQUEST::~HTTP_REQUEST( VOID )
|
|
{
|
|
DBG_ASSERT( _pGetFile == NULL );
|
|
DBG_ASSERT( _pURIInfo == NULL );
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: W3MetaDataFree
|
|
|
|
SYNOPSIS: Frees a formatted meta data object when it's not in use.
|
|
|
|
ENTRY: pObject - Pointer to the meta data object.
|
|
|
|
RETURNS:
|
|
|
|
|
|
NOTES:
|
|
|
|
|
|
********************************************************************/
|
|
|
|
VOID
|
|
W3MetaDataFree(
|
|
PVOID pObject
|
|
)
|
|
{
|
|
PW3_METADATA pMD;
|
|
|
|
pMD = (PW3_METADATA)pObject;
|
|
|
|
delete pMD;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: ValidateURL
|
|
|
|
SYNOPSIS: WinSE 14424, added a function to check and make sure
|
|
the URL is not being used to bypass the metabase settings
|
|
|
|
ENTRY: mb - Metabase Object
|
|
szPath - The full metabase path
|
|
|
|
RETURNS: TRUE - URL is good
|
|
FALSE - URL contains characters sequence which is meant
|
|
to confuse the metabase. ie. "/foo./bar.asp"
|
|
|
|
NOTES:
|
|
|
|
********************************************************************/
|
|
|
|
BOOL
|
|
HTTP_REQUEST::ValidateURL( MB & mb, LPSTR szPath )
|
|
{
|
|
UCHAR ch;
|
|
FILETIME ft;
|
|
LPSTR szPeriod = szPath;
|
|
LPSTR szFirstPeriod;
|
|
|
|
while ( ( szPeriod ) && ( *szPeriod != '\0' ) )
|
|
{
|
|
szPeriod = (PCHAR) _mbschr( (PUCHAR) szPeriod, '.' );
|
|
while ( ( szPeriod ) &&
|
|
( *(szPeriod+1) != '/' ) &&
|
|
( *(szPeriod+1) != '\\' ) &&
|
|
( *(szPeriod+1) != '\0' )
|
|
)
|
|
{
|
|
szPeriod = (PCHAR) _mbschr( (PUCHAR) szPeriod+1, '.' );
|
|
}
|
|
|
|
// This string does not contain a './' or '.\'
|
|
if (!szPeriod)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
// find first period in this row of dots, /test.../test.htm
|
|
szFirstPeriod = szPeriod;
|
|
while (*CharPrev(szPath,szFirstPeriod)=='.')
|
|
{
|
|
szFirstPeriod--;
|
|
}
|
|
|
|
// Terminate Mini-String
|
|
ch = *(szPeriod + 1);
|
|
*(szPeriod + 1) = '\0';
|
|
|
|
// Search for item in Metabase
|
|
if (FAILED(mb.QueryPMDCOM()->ComMDGetLastChangeTime(mb.QueryHandle(),(PUCHAR) szPath,&ft)))
|
|
{
|
|
// Item was not found, lets see if we can find it without the '.'
|
|
*szFirstPeriod = '\0';
|
|
|
|
if (SUCCEEDED(mb.QueryPMDCOM()->ComMDGetLastChangeTime(mb.QueryHandle(),(PUCHAR) szPath,&ft)))
|
|
{
|
|
// They are trying to bypass the name in the metabase, by using the '.'
|
|
*(szPeriod + 1) = ch;
|
|
*szFirstPeriod = '.';
|
|
|
|
SetLastError( ERROR_FILE_NOT_FOUND );
|
|
return FALSE;
|
|
}
|
|
} // if (FAILED(mb.QueryPMDCOM()->ComMDGetLastChangeTime(mb.QueryHandle(),(PUCHAR) pszURL,&ft)))
|
|
|
|
*(szPeriod + 1) = ch;
|
|
*szFirstPeriod = '.';
|
|
szPeriod++;
|
|
} // while ( ( szPeriod ) && ( *szPeriod != '\0' ) )
|
|
|
|
return TRUE;
|
|
} // ValidateURL
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::ReadMetaData
|
|
|
|
SYNOPSIS: Reads metadata for a URI, and formats it appropriately.
|
|
|
|
ENTRY: pszURL - URL to find metadata for
|
|
pstrPhysicalPath - Physical path of strURL
|
|
ppMetadata - Receives pointer to metadata object
|
|
|
|
RETURNS: TRUE if successful, FALSE if an error occurred (call
|
|
GetLastError())
|
|
|
|
NOTES:
|
|
|
|
|
|
********************************************************************/
|
|
|
|
#define RMD_ASSERT(x) if (!(x)) {DBG_ASSERT(FALSE); delete pMD; return FALSE; }
|
|
|
|
|
|
BOOL HTTP_REQUEST::ReadMetaData(
|
|
LPSTR pszURL,
|
|
STR * pstrPhysicalPath,
|
|
PW3_METADATA * ppMetaData
|
|
){
|
|
PW3_METADATA pMD;
|
|
DWORD cbPath;
|
|
DWORD dwDataSetNumber;
|
|
DWORD dwDataSetSize;
|
|
PVOID pCacheInfo;
|
|
MB mb( (IMDCOM*) g_pInetSvc->QueryMDObject() );
|
|
LPSTR pszVrPath = NULL;
|
|
INT ch;
|
|
LPSTR pszInVr;
|
|
LPSTR pszMinInVr;
|
|
DWORD dwNeed;
|
|
BUFFER bufTemp1;
|
|
DWORD dwL;
|
|
STACK_STR( strFullPath, MAX_PATH );
|
|
METADATA_ERROR_INFO MDErrorInfo;
|
|
|
|
|
|
DBG_ASSERT( ppMetaData != NULL );
|
|
|
|
*ppMetaData = NULL;
|
|
|
|
// First read the data set number, and see if we already have it
|
|
// cached. We don't do a full open in this case.
|
|
|
|
if (*pszURL == '*')
|
|
{
|
|
pszURL = Slash;
|
|
}
|
|
|
|
if ( !strFullPath.Copy( QueryW3Instance()->QueryMDVRPath() ) ||
|
|
!strFullPath.Append( ( *pszURL == '/' ) ? pszURL + 1 : pszURL ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !ValidateURL( mb, strFullPath.QueryStr() ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!mb.GetDataSetNumber( strFullPath.QueryStr(),
|
|
&dwDataSetNumber ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// See if we can find a matching data set already formatted.
|
|
pMD = (PW3_METADATA)TsFindMetaData(dwDataSetNumber, METACACHE_W3_SERVER_ID);
|
|
|
|
if (pMD == NULL)
|
|
{
|
|
pMD = new W3_METADATA;
|
|
|
|
if (pMD == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !pMD->ReadMetaData( QueryW3Instance(),
|
|
&mb,
|
|
pszURL,
|
|
&MDErrorInfo) )
|
|
{
|
|
delete pMD;
|
|
mb.Close();
|
|
|
|
SendMetaDataError(&MDErrorInfo);
|
|
return FALSE;
|
|
}
|
|
|
|
// We were succesfull, so try and add this metadata. There is a race
|
|
// condition where someone else could have added it while we were
|
|
// formatting. This is OK - we'll have two cached, but they should be
|
|
// consistent, and one of them will eventually time out. We could have
|
|
// AddMetaData check for this, and free the new one while returning a
|
|
// pointer to the old one if it finds one, but that isn't worthwhile
|
|
// now.
|
|
|
|
pCacheInfo = TsAddMetaData(pMD, W3MetaDataFree,
|
|
dwDataSetNumber, METACACHE_W3_SERVER_ID
|
|
);
|
|
|
|
}
|
|
|
|
*ppMetaData = pMD;
|
|
|
|
//
|
|
// Build physical path from VR_PATH & portion of URI not used to define VR_PATH
|
|
//
|
|
|
|
return pMD->BuildPhysicalPath( pszURL, pstrPhysicalPath );
|
|
}
|
|
|
|
BOOL
|
|
W3_METADATA::BuildProviderList(
|
|
CHAR *pszProviders
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Builds an array of SSPI Authentication providers
|
|
|
|
Arguments:
|
|
|
|
pszProviders - Comma separated list of providers
|
|
|
|
Returns:
|
|
|
|
TRUE if we succeed, FALSE if we don't.
|
|
|
|
--*/
|
|
{
|
|
BOOL fRet = TRUE;
|
|
INET_PARSER Parser( pszProviders );
|
|
DWORD cProv = 0;
|
|
|
|
while ( m_apszNTProviders[cProv] )
|
|
{
|
|
TCP_FREE( m_apszNTProviders[cProv] );
|
|
m_apszNTProviders[cProv++] = NULL;
|
|
}
|
|
|
|
Parser.SetListMode( TRUE );
|
|
|
|
cProv = 0;
|
|
while ( cProv < (MAX_SSPI_PROVIDERS-1) && *Parser.QueryToken() )
|
|
{
|
|
m_apszNTProviders[cProv] = (CHAR *) LocalAlloc( LMEM_FIXED,
|
|
strlen( Parser.QueryToken() ) + sizeof(CHAR));
|
|
|
|
if ( !m_apszNTProviders[cProv] )
|
|
{
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
fRet = FALSE;
|
|
break;
|
|
}
|
|
|
|
strcpy( m_apszNTProviders[cProv++], Parser.QueryToken() );
|
|
Parser.NextItem();
|
|
}
|
|
|
|
Parser.RestoreBuffer();
|
|
|
|
return fRet;
|
|
}
|
|
|
|
BOOL
|
|
W3_METADATA::CheckSSPPackage(
|
|
IN LPCSTR pszAuthString
|
|
)
|
|
{
|
|
DWORD i = 0;
|
|
|
|
while ( m_apszNTProviders[i] )
|
|
{
|
|
if ( !_stricmp( pszAuthString, m_apszNTProviders[i++] ))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
} // W3_SERVER_INSTANCE::CheckSSPPackage
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::Reset
|
|
|
|
SYNOPSIS: Resets the request object getting it ready for the next
|
|
client request. Called at the beginning of a request.
|
|
|
|
RETURNS: TRUE if successful, FALSE if an error occurred (call
|
|
GetLastError())
|
|
|
|
HISTORY:
|
|
Johnl 04-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::Reset( BOOL fResetPipelineInfo )
|
|
{
|
|
//
|
|
// Must reset the base object first
|
|
//
|
|
|
|
TCP_REQUIRE( HTTP_REQ_BASE::Reset(fResetPipelineInfo) );
|
|
|
|
_fAcceptsAll = FALSE;
|
|
_fAnyParams = FALSE;
|
|
_fSendToDav = FALSE;
|
|
_fDisableScriptmap = FALSE;
|
|
|
|
_GatewayType = GATEWAY_UNKNOWN;
|
|
_dwScriptMapFlags = 0;
|
|
_dwFileSystemType = FS_FAT;
|
|
_hTempFileHandle = INVALID_HANDLE_VALUE;
|
|
|
|
_strGatewayImage.Reset();
|
|
_strContentType.Reset();
|
|
_strReturnMimeType.Reset();
|
|
_strTempFileName.Reset();
|
|
|
|
_pFileNameLock = NULL;
|
|
|
|
CloseGetFile();
|
|
|
|
QueryClientConn()->UnbindAccessCheckList();
|
|
|
|
// Free the URI and/or metadata information if we have any. We know that
|
|
// if we have URI info, then it points at meta data and will free the
|
|
// metadata info when the URI info is free. Otherwise, we need to free
|
|
// the metadata information here.
|
|
|
|
ReleaseCacheInfo();
|
|
|
|
// reset execution descriptor block
|
|
|
|
_Exec.Reset();
|
|
_dwCallLevel = 0;
|
|
|
|
// DLC reset
|
|
|
|
_strDLCString.Reset();
|
|
|
|
// Default execute document
|
|
|
|
_fPossibleDefaultExecute = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID
|
|
HTTP_REQUEST::ReleaseCacheInfo(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks in any cache info we have out at the end of a
|
|
request
|
|
|
|
This is a virtual method from HTTP_REQ_BASE.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Let's release the base object first
|
|
//
|
|
|
|
HTTP_REQ_BASE::ReleaseCacheInfo();
|
|
_Exec.ReleaseCacheInfo();
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::DoWork
|
|
|
|
SYNOPSIS: Calls the appropriate work item based on our state
|
|
|
|
ENTRY: pfFinished - Gets set to TRUE if the client request has
|
|
been completed
|
|
|
|
RETURNS: TRUE if successful, FALSE if an error occurred (call
|
|
GetLastError())
|
|
|
|
NOTES: If a failure occurs because of bad info from the client (bad
|
|
URL, syntax error etc) then the code that found the problem
|
|
should call SendErrorResponse( error ), set the state to
|
|
HTR_DONE and return TRUE (when send completes, HTR_DONE will
|
|
cleanup).
|
|
|
|
If an error occurs in the server (out of memory etc) then
|
|
LastError should be set and FALSE should be returned. A server
|
|
error will be sent then the client will be disconnected.
|
|
|
|
HISTORY:
|
|
Johnl 26-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::DoWork(
|
|
BOOL * pfFinished
|
|
)
|
|
{
|
|
BOOL fRet = TRUE;
|
|
BOOL fContinueProcessingRequest;
|
|
BOOL fHandled;
|
|
BOOL fDone = FALSE;
|
|
BOOL fCompleteRequest;
|
|
DWORD dwOffset;
|
|
DWORD dwSizeToSend;
|
|
BOOL fEntireFile;
|
|
BOOL fIsNxRange;
|
|
BOOL fIsLastRange;
|
|
|
|
IF_DEBUG( CONNECTION )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[http_request::DoWork] Object %lx. State = %d, Bytes Written = %d\n",
|
|
QueryClientConn(),
|
|
QueryState(),
|
|
QueryBytesWritten() ));
|
|
|
|
}
|
|
|
|
DBG_CODE( HR_LOG_REF_COUNT(); );
|
|
|
|
switch ( QueryState() )
|
|
{
|
|
case HTR_GATEWAY_ASYNC_IO:
|
|
|
|
fRet = ProcessAsyncGatewayIO();
|
|
break;
|
|
|
|
case HTR_READING_CLIENT_REQUEST:
|
|
|
|
_cbBytesReceived += QueryBytesWritten();
|
|
|
|
fRet = OnFillClientReq( &fCompleteRequest,
|
|
pfFinished,
|
|
&fContinueProcessingRequest);
|
|
|
|
if ( !fRet )
|
|
break;
|
|
|
|
if ( *pfFinished || !fContinueProcessingRequest)
|
|
break;
|
|
|
|
if ( fCompleteRequest )
|
|
{
|
|
goto ProcessClientRequest;
|
|
}
|
|
break;
|
|
|
|
case HTR_READING_GATEWAY_DATA:
|
|
|
|
_cbBytesReceived += QueryBytesWritten();
|
|
|
|
fRet = ReadEntityBody( &fDone );
|
|
|
|
if ( !fRet || !fDone )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Otherwise we're done reading the first piece of the entity body.
|
|
// PUT (and possibly other verbs in the future) come through here to
|
|
// read their intial entity body. We need to set the state to DOVERB
|
|
// here in order to make them work, so that subsequent reads they might
|
|
// issue don't come back through this path. Then fall through to the
|
|
// DOVERB handlesr.
|
|
|
|
SetState(HTR_DOVERB);
|
|
|
|
case HTR_DOVERB:
|
|
|
|
ProcessClientRequest:
|
|
|
|
DBG_ASSERT(QueryState() == HTR_DOVERB);
|
|
|
|
// WinNT 379450
|
|
if ( _GatewayType == GATEWAY_MALFORMED )
|
|
{
|
|
SetState( HTR_DONE, HT_NOT_FOUND, ERROR_INVALID_PARAMETER );
|
|
Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished );
|
|
fRet = TRUE;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Check to see if encryption is required before we do any processing
|
|
//
|
|
|
|
if ( (GetFilePerms() & VROOT_MASK_SSL) && !IsSecurePort() )
|
|
{
|
|
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
|
|
Disconnect( HT_FORBIDDEN, IDS_SSL_REQUIRED, FALSE, pfFinished );
|
|
fRet = TRUE;
|
|
|
|
DBG_CODE( HR_LOG_REF_COUNT(); );
|
|
|
|
break;
|
|
}
|
|
//
|
|
// Check for short name as they break metabase equivalency
|
|
//
|
|
|
|
if ( memchr( _strPhysicalPath.QueryStr(), '~', _strPhysicalPath.QueryCB() ))
|
|
{
|
|
BOOL fShort;
|
|
DWORD err;
|
|
|
|
if ( err = CheckIfShortFileName( (UCHAR *) _strPhysicalPath.QueryStr(),
|
|
QueryImpersonationHandle(),
|
|
&fShort ))
|
|
{
|
|
if ( err != ERROR_FILE_NOT_FOUND &&
|
|
err != ERROR_PATH_NOT_FOUND )
|
|
{
|
|
fRet = FALSE;
|
|
break;
|
|
}
|
|
|
|
goto PathNotFound;
|
|
}
|
|
|
|
if ( fShort )
|
|
{
|
|
PathNotFound:
|
|
SetState( HTR_DONE, HT_NOT_FOUND, ERROR_FILE_NOT_FOUND );
|
|
Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished );
|
|
fRet = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( IsProbablyGatewayRequest() )
|
|
{
|
|
|
|
//
|
|
// Optionally check whether the PATH_INFO exists and is openable
|
|
//
|
|
|
|
if ( *(_Exec._pdwScriptMapFlags) & MD_SCRIPTMAPFLAG_CHECK_PATH_INFO )
|
|
{
|
|
STACK_STR( strTemp, MAX_PATH );
|
|
TS_OPEN_FILE_INFO * pGetFile = NULL;
|
|
DWORD err;
|
|
|
|
if ( !LookupVirtualRoot( &strTemp,
|
|
_Exec._pstrPathInfo->QueryStr(),
|
|
_Exec._pstrPathInfo->QueryCCH() ) )
|
|
{
|
|
fRet = FALSE;
|
|
break;
|
|
}
|
|
|
|
if ( !ImpersonateUser() )
|
|
{
|
|
fRet = FALSE;
|
|
break;
|
|
}
|
|
|
|
pGetFile = TsCreateFile( QueryW3Instance()->GetTsvcCache(),
|
|
strTemp.QueryStr(),
|
|
QueryImpersonationHandle(),
|
|
(_fClearTextPass || _fAnonymous) ?
|
|
TS_CACHING_DESIRED : 0 );
|
|
|
|
err = GetLastError();
|
|
|
|
RevertUser();
|
|
|
|
if ( pGetFile == NULL )
|
|
{
|
|
fRet = TRUE;
|
|
|
|
if ( err == ERROR_FILE_NOT_FOUND ||
|
|
err == ERROR_PATH_NOT_FOUND )
|
|
{
|
|
SetState( HTR_DONE, HT_NOT_FOUND, err );
|
|
Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished );
|
|
break;
|
|
}
|
|
else if (err == ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
// Really means the file name was too long.
|
|
|
|
SetState(HTR_DONE, HT_URL_TOO_LONG, err);
|
|
Disconnect(HT_URL_TOO_LONG, IDS_URL_TOO_LONG, FALSE, pfFinished );
|
|
break;
|
|
}
|
|
else if ( err == ERROR_INVALID_NAME )
|
|
{
|
|
SetState( HTR_DONE, HT_BAD_REQUEST, err );
|
|
Disconnect( HT_BAD_REQUEST, NO_ERROR, FALSE, pfFinished );
|
|
break;
|
|
}
|
|
else if ( err == ERROR_ACCESS_DENIED )
|
|
{
|
|
fRet = FALSE;
|
|
SetDeniedFlags( SF_DENIED_RESOURCE );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
fRet = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBG_REQUIRE( TsCloseHandle( QueryW3Instance()->GetTsvcCache(),
|
|
pGetFile ) );
|
|
}
|
|
}
|
|
|
|
fHandled = FALSE;
|
|
|
|
if ( _GatewayType == GATEWAY_BGI )
|
|
{
|
|
|
|
DBG_CODE( HR_LOG_REF_COUNT(); );
|
|
|
|
fRet = ProcessBGI( &_Exec,
|
|
&fHandled,
|
|
pfFinished,
|
|
_dwScriptMapFlags & MD_SCRIPTMAPFLAG_SCRIPT,
|
|
_dwScriptMapFlags & MD_SCRIPTMAPFLAG_WILDCARD);
|
|
|
|
DBG_CODE( HR_LOG_REF_COUNT(); );
|
|
|
|
}
|
|
else
|
|
{
|
|
fRet = ProcessGateway( &_Exec,
|
|
&fHandled,
|
|
pfFinished,
|
|
_dwScriptMapFlags
|
|
& MD_SCRIPTMAPFLAG_SCRIPT );
|
|
}
|
|
|
|
if ( !fRet) {
|
|
break;
|
|
}
|
|
|
|
if ( fHandled || *pfFinished )
|
|
break;
|
|
|
|
//
|
|
// Either an error ocurred or the gateways should indicate they
|
|
// handled this
|
|
//
|
|
|
|
DBGPRINTF((
|
|
DBG_CONTEXT
|
|
, "HTTP_REQUEST(%08x)::DoWork - "
|
|
"Un-finished, un-handled, succeeded request. "
|
|
"_pWamRequest(%08x) "
|
|
"fRet(%d) "
|
|
"fHandled(%d) "
|
|
"*pfFinished(%d) "
|
|
"\n"
|
|
, this
|
|
, _pWamRequest
|
|
, fRet
|
|
, fHandled
|
|
, *pfFinished
|
|
));
|
|
|
|
DBG_ASSERT( FALSE );
|
|
}
|
|
// SteveBr: Removing MD_SCRIPTMAPFLAG_NOTRANSMIT_ON_READ_DIR
|
|
else if ( _dwScriptMapFlags )
|
|
{
|
|
//
|
|
// Disallow GET on requests with script mappings. This ensures that the only
|
|
// way to get source code access is via DAV (translate:f header)
|
|
//
|
|
|
|
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
Disconnect( HT_FORBIDDEN, IDS_EXECUTE_ACCESS_DENIED, FALSE, pfFinished );
|
|
break;
|
|
}
|
|
|
|
fRet = (this->*_pmfnVerb)( pfFinished );
|
|
break;
|
|
|
|
case HTR_CGI:
|
|
fRet = TRUE;
|
|
break;
|
|
|
|
case HTR_RANGE:
|
|
dwOffset = _dwRgNxOffset;
|
|
dwSizeToSend = _dwRgNxSizeToSend;
|
|
DBG_REQUIRE(ScanRange( &_dwRgNxOffset, &_dwRgNxSizeToSend, &fEntireFile, &fIsLastRange));
|
|
fRet = SendRange( 0, dwOffset, dwSizeToSend, fIsLastRange );
|
|
break;
|
|
|
|
case HTR_CERT_RENEGOTIATE:
|
|
_cbBytesReceived += QueryBytesWritten();
|
|
|
|
fRet = HandleCertRenegotiation( pfFinished,
|
|
&fContinueProcessingRequest,
|
|
QueryBytesWritten() );
|
|
|
|
if ( !fRet || !fContinueProcessingRequest || *pfFinished )
|
|
{
|
|
break;
|
|
}
|
|
|
|
goto ProcessClientRequest;
|
|
|
|
case HTR_RESTART_REQUEST:
|
|
|
|
DBG_CODE( HR_LOG_REF_COUNT(); );
|
|
|
|
fRet = OnRestartRequest( (char *)_bufClientRequest.QueryPtr(),
|
|
_cbBytesWritten = _cbRestartBytesWritten,
|
|
pfFinished,
|
|
&fContinueProcessingRequest);
|
|
|
|
DBG_CODE( HR_LOG_REF_COUNT(); );
|
|
|
|
if ( !fRet || !fContinueProcessingRequest || *pfFinished )
|
|
{
|
|
break;
|
|
}
|
|
goto ProcessClientRequest;
|
|
|
|
case HTR_REDIRECT:
|
|
|
|
DBG_CODE( HR_LOG_REF_COUNT(); );
|
|
|
|
_cbBytesReceived += QueryBytesWritten();
|
|
|
|
fRet = ReadEntityBody( &fDone, FALSE, QueryClientContentLength() );
|
|
|
|
if ( !fRet || !fDone )
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Now we can do the redirect
|
|
//
|
|
|
|
fRet = DoRedirect( pfFinished );
|
|
break;
|
|
|
|
case HTR_ACCESS_DENIED:
|
|
|
|
DBG_CODE( HR_LOG_REF_COUNT(); );
|
|
|
|
_cbBytesReceived += QueryBytesWritten();
|
|
|
|
fRet = ReadEntityBody( &fDone, FALSE, QueryClientContentLength() );
|
|
if ( !fRet || !fDone )
|
|
{
|
|
break;
|
|
}
|
|
|
|
fRet = FALSE;
|
|
SetLastError( ERROR_ACCESS_DENIED );
|
|
|
|
break;
|
|
|
|
case HTR_DONE:
|
|
fRet = TRUE;
|
|
*pfFinished = TRUE;
|
|
|
|
break;
|
|
|
|
default:
|
|
DBG_ASSERT( FALSE );
|
|
fRet = FALSE;
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
break;
|
|
}
|
|
|
|
IF_DEBUG( CONNECTION )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"[http_request::DoWork] Leaving, Object %lx. State = %d\n",
|
|
QueryClientConn(),
|
|
QueryState() ));
|
|
|
|
}
|
|
|
|
DBG_CODE( HR_LOG_REF_COUNT(); );
|
|
|
|
return fRet;
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::FindHost(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Find the appropriate host from a request. This involves looking at
|
|
the host header field and/or the URL itself, and selecting the
|
|
appropriate value. For down level clients we may invoke the munging
|
|
code.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return value:
|
|
|
|
TRUE if no error, otherwise FALSE
|
|
|
|
--*/
|
|
{
|
|
CHAR *pszURL;
|
|
CHAR *pszHost;
|
|
BOOL bFoundColon;
|
|
DWORD dwHostNameLength;
|
|
BOOL fHTTPS = FALSE;
|
|
BOOL fStartsWithHTTP = FALSE;
|
|
|
|
pszURL = (CHAR *)_HeaderList.FastMapQueryValue(HM_URL);
|
|
|
|
DBG_ASSERT(pszURL != NULL);
|
|
|
|
if ( *pszURL != '/' )
|
|
{
|
|
//
|
|
// We probably have an absolute URL. Verify that, then save
|
|
// the host name part of the URL, and update the URL to point
|
|
// beyond the host name.
|
|
|
|
if ( _strnicmp("http://", pszURL, sizeof("http://") - 1) == 0)
|
|
{
|
|
fHTTPS = FALSE;
|
|
fStartsWithHTTP = TRUE;
|
|
}
|
|
else if ( _strnicmp("https://", pszURL, sizeof("https://") - 1) == 0)
|
|
{
|
|
fHTTPS = TRUE;
|
|
fStartsWithHTTP = TRUE;
|
|
}
|
|
|
|
if ( fStartsWithHTTP )
|
|
{
|
|
pszHost = pszURL + ( fHTTPS ? sizeof("https://") : sizeof("http://") ) - 1;
|
|
|
|
// pszHost points to the host name. Walk forward from there.
|
|
// If we find a colon we'll want to remember the length at
|
|
// that point, and we'll keep searching for a termination slash.
|
|
|
|
pszURL = pszHost;
|
|
|
|
bFoundColon = FALSE;
|
|
|
|
while (*pszURL != '\0')
|
|
{
|
|
|
|
if (*pszURL == ':')
|
|
{
|
|
// Found a colon. If we haven't already seen one,
|
|
// calculate the length of the host name now.
|
|
|
|
if (!bFoundColon)
|
|
{
|
|
dwHostNameLength = DIFF(pszURL - pszHost);
|
|
}
|
|
|
|
// Convert the colon to a termianting NULL, remember we
|
|
// saw it, and keep going.
|
|
|
|
bFoundColon = TRUE;
|
|
pszURL++;
|
|
}
|
|
else
|
|
{
|
|
// If we find a slash, we're done.
|
|
|
|
if (*pszURL == '/')
|
|
{
|
|
break;
|
|
}
|
|
|
|
pszURL++;
|
|
}
|
|
}
|
|
|
|
if (*pszURL == '\0')
|
|
{
|
|
// A URL like http://www.microsoft.com is invalid, so
|
|
// fail it here.
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!bFoundColon)
|
|
{
|
|
// Haven't computed host name length yet, do it now.
|
|
dwHostNameLength = DIFF(pszURL - pszHost);
|
|
}
|
|
|
|
if (!_strHostAddr.Copy(pszHost, dwHostNameLength))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
else
|
|
{
|
|
// This was not a valid HTTP scheme, nor is it an absolute path.
|
|
// Fail the request, choosing the correct error depending on
|
|
// whether or not this looks like a scheme.
|
|
|
|
if (*pszURL != '*' || pszURL[1] != '\0')
|
|
{
|
|
if (strchr(pszURL, ':') != NULL)
|
|
{
|
|
// unknown scheme ( e.g FTP )
|
|
// This is not an error as this may be handled by a filter
|
|
// such as the proxy filter
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// If we get here we know we didn't have an absolute URI. Check
|
|
// for a Host: header, and if we have one save that as the host name.
|
|
//
|
|
|
|
pszHost = (CHAR *)_HeaderList.FastMapQueryValue(HM_HST);
|
|
|
|
if (pszHost != NULL)
|
|
{
|
|
CHAR *pszTemp = pszHost;
|
|
|
|
// Have a host value. Scan it for a colon, and calculate the
|
|
// length based on the colon or the terminating null.
|
|
|
|
while (*pszTemp != '\0' && *pszTemp != ':')
|
|
{
|
|
pszTemp++;
|
|
}
|
|
|
|
if (!_strHostAddr.Copy(pszHost, DIFF(pszTemp - pszHost) ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Right now we have neither an absolute URI or a Host: header. Invoke
|
|
// the down level client support if we're supposed to.
|
|
//
|
|
|
|
if ( g_fDLCSupport )
|
|
{
|
|
if ( !DLCMungeSimple() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Used to extract fast-map parameter, and parse it using sub-function.
|
|
# define CheckAndParseField( fld, pstr, func) \
|
|
if ( ((pstr = (LPSTR ) _HeaderList.FastMapQueryValue( fld)) != NULL) && \
|
|
!(this->func( pstr))) { \
|
|
return ( FALSE); \
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::Parse
|
|
|
|
SYNOPSIS: Gathers all of the interesting information in the client
|
|
request
|
|
|
|
ENTRY: pchRequest - raw Latin-1 request received from the
|
|
client, zero terminated
|
|
pfFinished - Set to TRUE if no further processing is needed
|
|
pfHandled - Set to TRUE if request has been handled
|
|
|
|
RETURNS: APIERR if an error occurred parsing the header
|
|
|
|
HISTORY:
|
|
Johnl 24-Aug-1994 Created
|
|
MuraliK 19-Nov-1996 Created new HTTP HEADERS object and
|
|
modified parsing
|
|
|
|
********************************************************************/
|
|
|
|
BOOL
|
|
HTTP_REQUEST::Parse(
|
|
const CHAR* pchRequest,
|
|
DWORD cbData,
|
|
DWORD * pcbExtraData,
|
|
BOOL * pfHandled,
|
|
BOOL * pfFinished
|
|
)
|
|
{
|
|
PMFN_ONGRAMMAR pmfn;
|
|
BOOL fRet;
|
|
BOOL fIsTrace = FALSE;
|
|
BOOL fSecondTry = FALSE;
|
|
PW3_SERVER_INSTANCE pInstance;
|
|
BOOL fMaxConnExceeded;
|
|
LPSTR pszV;
|
|
|
|
//
|
|
// 1. Eliminate all the leading spaces
|
|
// Also eliminate \r\n since some clients are sending
|
|
// extra \r\n\r\n at the end of the POST
|
|
// sending extra characters is incorrect but RFC recommends
|
|
// to handle it
|
|
|
|
while ( cbData &&
|
|
( isspace( (UCHAR)(*(PCHAR)pchRequest) ) ||
|
|
(*pchRequest == '\r') ||
|
|
(*pchRequest == '\n')
|
|
)
|
|
)
|
|
{
|
|
--cbData;
|
|
++pchRequest;
|
|
}
|
|
|
|
//
|
|
// 2. Find if Trace operation was requested.
|
|
//
|
|
if (*pchRequest == 'T' ) {
|
|
if ( (cbData > 6) && !memcmp( pchRequest, "TRAC", sizeof("TRAC")-1) )
|
|
{
|
|
if ( ( pchRequest[sizeof("TRAC")-1] == 'E' &&
|
|
_HTTP_IS_LINEAR_SPACE( ((PBYTE)pchRequest)[sizeof("TRACE")-1] ) ) ||
|
|
( pchRequest[sizeof("TRAC")-1] == 'K' &&
|
|
_HTTP_IS_LINEAR_SPACE( ((PBYTE)pchRequest)[sizeof("TRACK")-1] ) ) )
|
|
{
|
|
if ( !QueryRespBuf()->Resize( cbData + sizeof( CHAR ) ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
CopyMemory( QueryRespBufPtr(), pchRequest, cbData );
|
|
QueryRespBufPtr()[ cbData ] = '\0';
|
|
fIsTrace = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !_HeaderList.ParseInput( pchRequest, cbData, pcbExtraData)) {
|
|
|
|
return ( FALSE);
|
|
}
|
|
|
|
// WinSE 26482 - reject if request header has the character nul in it
|
|
//
|
|
DWORD cchHeader = cbData - *pcbExtraData;
|
|
if ( memchr(pchRequest, 0, cchHeader)!=NULL )
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Parse out the version number now. We do this so that we can send
|
|
// the appropriate Connection: header in the error response if
|
|
// FindHost() fails.
|
|
//
|
|
|
|
CheckAndParseField( HM_VER, pszV, HTTP_REQUEST::OnVersion);
|
|
|
|
//
|
|
// If this client didn't send a host header, and down level client support
|
|
// is required, then munge the URL if necessary.
|
|
//
|
|
|
|
if (!FindHost())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
RetryQueryInstance:
|
|
|
|
fMaxConnExceeded = FALSE;
|
|
|
|
//
|
|
// Get the server instance
|
|
//
|
|
|
|
pInstance = QueryW3Instance();
|
|
|
|
if ( pInstance == NULL )
|
|
{
|
|
|
|
pInstance = (PW3_SERVER_INSTANCE)
|
|
QueryClientConn()->QueryW3Endpoint()->FindAndReferenceInstance(
|
|
_strHostAddr.QueryStr(),
|
|
QueryClientConn()->QueryLocalIPAddress(),
|
|
&fMaxConnExceeded
|
|
);
|
|
|
|
#if 0
|
|
if( pInstance != NULL && fMaxConnExceeded ) {
|
|
pInstance->DecrementCurrentConnections();
|
|
pInstance->Dereference();
|
|
pInstance = NULL;
|
|
SetLastError( ERROR_TOO_MANY_SESS );
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// set the instance. We know that fMaxConnExceeded can't be true
|
|
// unless we found an instance.
|
|
//
|
|
|
|
DBG_ASSERT(!fMaxConnExceeded || pInstance != NULL);
|
|
|
|
if ( pInstance == NULL)
|
|
{
|
|
DWORD err;
|
|
|
|
if ( g_fDLCSupport )
|
|
{
|
|
if ( !fSecondTry &&
|
|
_strHostAddr.IsEmpty() &&
|
|
DLCHandleRequest( pfFinished ) )
|
|
{
|
|
if ( *pfFinished )
|
|
{
|
|
return TRUE;
|
|
}
|
|
fSecondTry = TRUE;
|
|
goto RetryQueryInstance;
|
|
}
|
|
}
|
|
|
|
err = GetLastError();
|
|
|
|
IF_DEBUG(ERROR) {
|
|
DBGPRINTF((DBG_CONTEXT,"FindInstance failed with %d\n",err));
|
|
}
|
|
|
|
//
|
|
// map access denied to something else so we don't request
|
|
// for authentication
|
|
//
|
|
|
|
if ( err == ERROR_ACCESS_DENIED ) {
|
|
SetLastError(ERROR_LOGIN_WKSTA_RESTRICTION);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
DBG_ASSERT(pInstance != NULL);
|
|
|
|
//
|
|
// Set the timeout for future IOs on this context
|
|
//
|
|
|
|
AtqContextSetInfo( QueryClientConn()->QueryAtqContext(),
|
|
ATQ_INFO_TIMEOUT,
|
|
(ULONG_PTR) pInstance->QueryConnectionTimeout() );
|
|
|
|
//
|
|
// Setup bandwidth throttling for this context
|
|
//
|
|
|
|
if ( pInstance->QueryBandwidthInfo() )
|
|
{
|
|
AtqContextSetInfo( QueryClientConn()->QueryAtqContext(),
|
|
ATQ_INFO_BANDWIDTH_INFO,
|
|
(ULONG_PTR) pInstance->QueryBandwidthInfo() );
|
|
}
|
|
|
|
//
|
|
// FindInstance sets a reference to pInstance which
|
|
// we use here.
|
|
//
|
|
|
|
QueryClientConn()->SetW3Instance(pInstance);
|
|
SetW3Instance(pInstance);
|
|
|
|
//
|
|
// Set statistics object to point to pInstance's statistics object
|
|
//
|
|
|
|
QueryClientConn()->SetW3StatsObj(pInstance->QueryStatsObj());
|
|
SetW3StatsObj(pInstance->QueryStatsObj());
|
|
|
|
//
|
|
// Set and reference the filter list for this instance and then copy
|
|
// any global filter context pointers to the per-instance filter list
|
|
//
|
|
|
|
pInstance->LockThisForRead();
|
|
if ( !_Filter.SetFilterList( pInstance->QueryFilterList() ) )
|
|
{
|
|
pInstance->UnlockThis();
|
|
return FALSE;
|
|
}
|
|
pInstance->UnlockThis();
|
|
|
|
_Filter.CopyContextPointers();
|
|
}
|
|
|
|
//
|
|
// Keep track of what URL was before we passed it to any filters that might modify
|
|
// it [other than READ_RAW filters]
|
|
//
|
|
_strOriginalURL.Copy( _HeaderList.FastMapQueryValue( HM_URL ) );
|
|
|
|
if ( _Filter.IsNotificationNeeded( SF_NOTIFY_PREPROC_HEADERS,
|
|
IsSecurePort() ))
|
|
{
|
|
//
|
|
// Notify any filters interested in the request and headers before
|
|
// we do any processing
|
|
//
|
|
|
|
if ( !_Filter.NotifyPreProcHeaderFilters( pfFinished ))
|
|
{
|
|
if ( GetLastError() == ERROR_ACCESS_DENIED )
|
|
{
|
|
//
|
|
// we need to read metadata so that WWW authentication
|
|
// headers can be properly generated.
|
|
//
|
|
|
|
OnURL( (LPSTR)_HeaderList.FastMapQueryValue( HM_URL ) );
|
|
|
|
//
|
|
// restore error ( may have been modified by OnUrl )
|
|
// we disregard any error from OnUrl as we are already
|
|
// doing error processing.
|
|
//
|
|
|
|
SetLastError( ERROR_ACCESS_DENIED );
|
|
SetDeniedFlags( SF_DENIED_FILTER );
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if ( *pfFinished )
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Now scan for any RFC822 field names that we recognize
|
|
//
|
|
|
|
//
|
|
// Check individual items of interest and execute the parse function
|
|
// for that item
|
|
// NYI: It will be great if we can defer the execution and do it on
|
|
// demand basis. We need to modify entire use of the values from
|
|
// HTTP_REQUEST object to make this happen.
|
|
|
|
|
|
//
|
|
//WinSE 24317: URL and Verb are required
|
|
{
|
|
LPSTR pszVerb;
|
|
LPSTR pszURL;
|
|
|
|
pszVerb = (LPSTR)_HeaderList.FastMapQueryValue(HM_MET);
|
|
pszURL = (LPSTR)_HeaderList.FastMapQueryValue(HM_URL);
|
|
|
|
if ( pszVerb == NULL || pszURL == NULL )
|
|
{
|
|
SetState( HTR_DONE, HT_SERVER_ERROR, NO_ERROR );
|
|
Disconnect( HT_SERVER_ERROR, 0, FALSE, pfFinished );
|
|
|
|
*pfHandled = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
if (!(this->HTTP_REQUEST::OnVerb( pszVerb)))
|
|
return ( FALSE);
|
|
|
|
if (!(this->HTTP_REQUEST::OnURL( pszURL)))
|
|
return ( FALSE);
|
|
}
|
|
|
|
|
|
// Now that we've accompished a minimum amount of header processing,
|
|
// check to see if we've exceeded the maximum connection count.
|
|
|
|
if (fMaxConnExceeded)
|
|
{
|
|
|
|
// We've exceeded the max, so we're done now.
|
|
SetLastError( ERROR_TOO_MANY_SESS );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// See if the site is stopped
|
|
//
|
|
|
|
DBG_ASSERT( pInstance != NULL);
|
|
|
|
if (pInstance->IsSiteCPUPaused())
|
|
{
|
|
SetState( HTR_DONE, HT_SVC_UNAVAILABLE, ERROR_NOT_ENOUGH_QUOTA );
|
|
|
|
Disconnect( HT_SVC_UNAVAILABLE, IDS_SITE_RESOURCE_BLOCKED, FALSE, pfFinished );
|
|
|
|
*pfHandled = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
CheckAndParseField( HM_ACC, pszV, HTTP_REQUEST::OnAccept);
|
|
CheckAndParseField( HM_CON, pszV, HTTP_REQUEST::OnConnection);
|
|
// CheckAndParseField( HM_HST, pszV, HTTP_REQ_BASE::OnHost);
|
|
// HM_AUT is deferred
|
|
// CheckAndParseField( HM_AUT, pszV, HTTP_REQ_BASE::OnAuthorization);
|
|
CheckAndParseField( HM_IMS, pszV, HTTP_REQ_BASE::OnIfModifiedSince);
|
|
CheckAndParseField( HM_IFM, pszV, HTTP_REQ_BASE::OnIfMatch);
|
|
CheckAndParseField( HM_INM, pszV, HTTP_REQ_BASE::OnIfNoneMatch);
|
|
CheckAndParseField( HM_IFR, pszV, HTTP_REQ_BASE::OnIfRange);
|
|
CheckAndParseField( HM_UMS, pszV, HTTP_REQ_BASE::OnUnlessModifiedSince);
|
|
CheckAndParseField( HM_IUM, pszV, HTTP_REQ_BASE::OnIfUnmodifiedSince);
|
|
CheckAndParseField( HM_CLE, pszV, HTTP_REQ_BASE::OnContentLength);
|
|
CheckAndParseField( HM_CTY, pszV, HTTP_REQUEST::OnContentType);
|
|
CheckAndParseField( HM_PRA, pszV, HTTP_REQUEST::OnProxyAuthorization);
|
|
CheckAndParseField( HM_RNG, pszV, HTTP_REQ_BASE::OnRange);
|
|
CheckAndParseField( HM_TEC, pszV, HTTP_REQUEST::OnTransferEncoding);
|
|
CheckAndParseField( HM_LCK, pszV, HTTP_REQUEST::OnLockToken);
|
|
|
|
//
|
|
// Optionally ignore the Translate header
|
|
//
|
|
|
|
if ( !_pMetaData || !_pMetaData->QueryIgnoreTranslate() )
|
|
{
|
|
CheckAndParseField( HM_TRN, pszV, HTTP_REQUEST::OnTranslate);
|
|
}
|
|
|
|
CheckAndParseField( HM_ISM, pszV, HTTP_REQUEST::OnIf);
|
|
|
|
// Make sure we're see a host header if this is a 1.1 request.
|
|
|
|
if (IsOneOne() && _HeaderList.FastMapQueryValue(HM_HST) == NULL)
|
|
{
|
|
_HeaderList.FastMapCancel( HM_AUT );
|
|
|
|
SetState( HTR_DONE, HT_BAD_REQUEST, ERROR_INVALID_PARAMETER );
|
|
Disconnect( HT_BAD_REQUEST, IDS_HOST_REQUIRED, FALSE, pfFinished );
|
|
|
|
*pfHandled = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// OK. Now we have determined whether or not to keep the connection
|
|
// alive. Now we can check when the VROOT allows it.
|
|
//
|
|
|
|
if ( !_pMetaData->QueryKeepAlives() )
|
|
{
|
|
SetKeepConn( FALSE );
|
|
}
|
|
|
|
//
|
|
// If we picked up some gateway data in the headers, adjust for that
|
|
// now
|
|
//
|
|
|
|
_cbClientRequest -= *pcbExtraData;
|
|
|
|
//
|
|
// store available input byte count for OnRestartRequest
|
|
// necessary because _cbBytesWritten will be overwritten
|
|
// by IO completion
|
|
//
|
|
|
|
if ( _fHaveContentLength )
|
|
{
|
|
_cbBytesWritten = _cbRestartBytesWritten = *pcbExtraData;
|
|
}
|
|
else
|
|
{
|
|
_cbRestartBytesWritten = _cbBytesWritten;
|
|
}
|
|
|
|
SetState( HTR_DOVERB );
|
|
|
|
return (fIsTrace ? TRUE : ProcessURL( pfFinished, pfHandled ));
|
|
} // HTTP_REQUEST::Parse()
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::OnVerb
|
|
|
|
SYNOPSIS: Parses the verb from an HTTP request
|
|
|
|
ENTRY: pszValue - Pointer to zero terminated string
|
|
|
|
|
|
RETURNS: TRUE if successful, FALSE if an error occurred
|
|
|
|
HISTORY:
|
|
Johnl 24-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::OnVerb( CHAR * pszValue )
|
|
{
|
|
UINT i = 0;
|
|
UINT cchMethod;
|
|
|
|
if ( !_strMethod.Copy( pszValue ) )
|
|
return FALSE;
|
|
|
|
//
|
|
// Look for the verbs we recognize
|
|
//
|
|
|
|
cchMethod = _strMethod.QueryCCH();
|
|
|
|
while ( DoVerb[i].pchVerb )
|
|
{
|
|
if ( (cchMethod == DoVerb[i].cchVerb) &&
|
|
(!memcmp( pszValue,
|
|
DoVerb[i].pchVerb,
|
|
DoVerb[i].cchVerb )))
|
|
{
|
|
_verb = DoVerb[i].httpVerb;
|
|
|
|
switch ( _verb )
|
|
{
|
|
case HTV_GET:
|
|
QueryW3StatsObj()->IncrTotalGets();
|
|
break;
|
|
|
|
case HTV_HEAD:
|
|
QueryW3StatsObj()->IncrTotalHeads();
|
|
break;
|
|
|
|
case HTV_TRACE:
|
|
QueryW3StatsObj()->IncrTotalTraces();
|
|
break;
|
|
|
|
case HTV_TRACECK:
|
|
break;
|
|
|
|
case HTV_PUT:
|
|
QueryW3StatsObj()->IncrTotalPuts();
|
|
_putstate = PSTATE_START;
|
|
_fSendToDav = TRUE; // Verb is handled by DAV.
|
|
#if 0 // Not used anywhere /SAB
|
|
_fIsWrite = TRUE;
|
|
#endif
|
|
break;
|
|
|
|
case HTV_DELETE:
|
|
QueryW3StatsObj()->IncrTotalDeletes();
|
|
_fSendToDav = TRUE; // Verb is handled by DAV.
|
|
break;
|
|
|
|
case HTV_POST:
|
|
QueryW3StatsObj()->IncrTotalPosts();
|
|
break;
|
|
|
|
case HTV_OPTIONS:
|
|
QueryW3StatsObj()->IncrTotalOthers();
|
|
_fSendToDav = TRUE; // Verb is handled by DAV.
|
|
break;
|
|
|
|
default:
|
|
DBG_ASSERT( FALSE );
|
|
break;
|
|
}
|
|
|
|
_pmfnVerb = DoVerb[i].pmfnVerb;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
//
|
|
// The verb may be a verb a gateway knows how to deal with so
|
|
// all hope isn't lost
|
|
//
|
|
|
|
QueryW3StatsObj()->IncrTotalOthers();
|
|
|
|
_pmfnVerb = &HTTP_REQUEST::DoUnknown;
|
|
_verb = HTV_UNKNOWN;
|
|
_fSendToDav = TRUE; // Send unknown verbs to DAV.
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::NormalizeUrl(
|
|
LPSTR pszStart
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Normalize URL
|
|
|
|
Arguments:
|
|
|
|
strUrl - URL to be updated to a canonical URI
|
|
|
|
Return value:
|
|
|
|
TRUE if no error, otherwise FALSE
|
|
|
|
--*/
|
|
{
|
|
TCHAR * pchParams;
|
|
TCHAR * pch;
|
|
TCHAR * pszACU;
|
|
TCHAR chParams;
|
|
LPSTR pszSlash;
|
|
LPSTR pszURL;
|
|
LPSTR pszValue;
|
|
BOOL fSt;
|
|
STACK_STR( strChgUrl, MAX_PATH );
|
|
|
|
|
|
if ( *pszStart != '/' )
|
|
{
|
|
//
|
|
// assume HTTP URL, skip protocol & host name by
|
|
// searching for 1st '/' following "//"
|
|
//
|
|
// We handle this information as a "Host:" header.
|
|
// It will be overwritten by the real header if it is
|
|
// present.
|
|
//
|
|
// We do not check for a match in this case.
|
|
//
|
|
|
|
if ( (pszSlash = strchr( pszStart, '/' )) && pszSlash[1] == '/' )
|
|
{
|
|
pszSlash += 2;
|
|
if ( pszURL = strchr( pszSlash, '/' ) )
|
|
{
|
|
//
|
|
// update pointer to URL to point to the 1st slash
|
|
// following host name
|
|
//
|
|
|
|
pszValue = pszURL;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// if no single slash following host name
|
|
// consider the URL to be empty.
|
|
//
|
|
|
|
pszValue = pszSlash + strlen( pszSlash );
|
|
}
|
|
|
|
memmove( pszStart, pszValue, strlen(pszValue)+1 );
|
|
}
|
|
|
|
//
|
|
// if no double slash, this is not a fully qualified URL
|
|
// and we leave it alone.
|
|
//
|
|
}
|
|
|
|
//
|
|
// references to /_[W3_AUTH_CHANGE_URL] will be redirected
|
|
// to the configured URL. This URL will be accessed from
|
|
// the system context ( i.e with system access rights )
|
|
//
|
|
|
|
if ( pszStart[0] == '/' && pszStart[1] == '_'
|
|
&& !memcmp( pszStart+2, W3_AUTH_CHANGE_URL, sizeof(W3_AUTH_CHANGE_URL)-1 ) )
|
|
{
|
|
QueryW3Instance()->LockThisForRead();
|
|
fSt = strChgUrl.Copy( pszACU = (TCHAR*)QueryW3Instance()->QueryAuthChangeUrl() );
|
|
QueryW3Instance()->UnlockThis();
|
|
|
|
if ( pszACU )
|
|
{
|
|
if ( fSt )
|
|
{
|
|
strcpy( pszStart, strChgUrl.QueryStr() );
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check for a question mark which indicates this URL contains some
|
|
// parameters and break the two apart if found
|
|
//
|
|
|
|
if ( (pchParams = ::_tcschr( pszStart, TEXT('?') )) )
|
|
{
|
|
*pchParams = TEXT('\0');
|
|
}
|
|
|
|
//
|
|
// Unescape wants a STR ( sigh )
|
|
//
|
|
|
|
strChgUrl.Copy( (TCHAR*)pszStart );
|
|
strChgUrl.Unescape();
|
|
strcpy( pszStart, strChgUrl.QueryStr() );
|
|
|
|
//
|
|
// Canonicalize the URL
|
|
//
|
|
|
|
CanonURL( pszStart, g_pInetSvc->IsSystemDBCS() );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::OnURL
|
|
|
|
SYNOPSIS: Parses the URL from an HTTP request
|
|
|
|
ENTRY: pszValue - URL on http request line
|
|
|
|
|
|
RETURNS: TRUE if successful, FALSE if an error occurred
|
|
|
|
NOTES: The URL and Path info are unescaped in this method.
|
|
Parameters coming after the "?" are *not* unescaped as they
|
|
contain encoded '&' or other meaningful items.
|
|
|
|
HISTORY:
|
|
Johnl 24-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::OnURL( CHAR * pszValue )
|
|
{
|
|
TCHAR * pchParams;
|
|
TCHAR * pchStart;
|
|
TCHAR * pch;
|
|
TCHAR * pszACU;
|
|
TCHAR chParams;
|
|
LPSTR pszSlash;
|
|
BOOL fSt;
|
|
LPSTR pszArg = NULL;
|
|
STACK_STR( strChgUrl, MAX_PATH );
|
|
PW3_URI_INFO pURIBlob;
|
|
BOOL fHTTPS = FALSE;
|
|
BOOL fHTTP = FALSE;
|
|
|
|
if ( *pszValue != '/' )
|
|
{
|
|
//
|
|
// skip protocol & host name by
|
|
// searching for 1st '/' following "http://" or "https://"
|
|
//
|
|
|
|
if ( _strnicmp("http://", pszValue, sizeof("http://") - 1) == 0)
|
|
{
|
|
fHTTP = TRUE;
|
|
}
|
|
else if ( _strnicmp("https://", pszValue, sizeof("https://") - 1) == 0)
|
|
{
|
|
fHTTPS = TRUE;
|
|
}
|
|
|
|
if ( fHTTP || fHTTPS )
|
|
{
|
|
pszSlash = pszValue + ( fHTTP ? sizeof("http://") - 1 :
|
|
sizeof("https://") - 1 );
|
|
|
|
if ( !(pszValue = strchr( pszSlash, '/' )) )
|
|
{
|
|
//
|
|
// if no single slash following host name
|
|
// then URL is empty and this is an invalid request
|
|
//
|
|
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( (*pszValue != '*' || pszValue[1] != '\0') &&
|
|
strchr(pszValue, ':') != NULL )
|
|
{
|
|
// unknown scheme ( e.g FTP )
|
|
|
|
SetLastError( ERROR_NOT_SUPPORTED );
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// references to /_[W3_AUTH_CHANGE_URL] will be redirected
|
|
// to the configured URL. This URL will be accessed from
|
|
// the system context ( i.e with system access rights )
|
|
//
|
|
|
|
if ( pszValue[0] == '/' && pszValue[1] == '_'
|
|
&& !strncmp( pszValue+2, W3_AUTH_CHANGE_URL, sizeof(W3_AUTH_CHANGE_URL)-1 ) )
|
|
{
|
|
QueryW3Instance()->LockThisForRead();
|
|
fSt = strChgUrl.Copy( pszACU = (TCHAR*)QueryW3Instance()->QueryAuthChangeUrl() );
|
|
QueryW3Instance()->UnlockThis();
|
|
|
|
// store ptr to arg to append it to URL
|
|
pszArg = strchr( pszValue, '?' );
|
|
|
|
if ( pszACU )
|
|
{
|
|
if ( fSt && LogonAsSystem() )
|
|
{
|
|
pszValue = strChgUrl.QueryStr();
|
|
_strUnmappedUserName.Copy( _strUserName );
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Check for the asterisk URL. If it's an asterisk with trailing stuff
|
|
// it's illegal.
|
|
|
|
if (*pszValue == '*')
|
|
{
|
|
if (pszValue[1] != '\0')
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if ( !_strRawURL.Copy( pszValue ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
if ( pszArg != NULL && !_strRawURL.Append( pszArg ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pchStart = pszValue;
|
|
|
|
//
|
|
// Check for a question mark which indicates this URL contains some
|
|
// parameters and break the two apart if found
|
|
//
|
|
|
|
if ( (pchParams = pszArg) || (pchParams = ::_tcschr( pchStart, TEXT('?') )) )
|
|
{
|
|
if ( !pszArg )
|
|
{
|
|
chParams = *pchParams;
|
|
*pchParams = TEXT('\0');
|
|
}
|
|
|
|
_fAnyParams = TRUE;
|
|
|
|
if ( !_strURL.Copy( pchStart ) ||
|
|
!_strURL.Unescape() ||
|
|
!_strURLParams.Copy( pchParams + 1 ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_fAnyParams = FALSE;
|
|
|
|
if ( !_strURL.Copy( _strRawURL ) ||
|
|
!_strURL.Unescape() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
_strURLParams.Reset();
|
|
}
|
|
|
|
//
|
|
// Canonicalize the URL and make sure it's valid
|
|
//
|
|
|
|
INT cchURL = CanonURL( _strURL.QueryStr(), g_pInetSvc->IsSystemDBCS() );
|
|
_strURL.SetLen( cchURL );
|
|
|
|
if( !_strURLPathInfo.Copy( _strURL ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( pchParams && !pszArg )
|
|
{
|
|
*pchParams = chParams;
|
|
}
|
|
|
|
//
|
|
// Now that we have the canonicalized URL we need to get metadata
|
|
// information about it. Call the cache to get this. If it's not
|
|
// in the cache, we'll call the metadata API to get it and add
|
|
// it to the cache.
|
|
//
|
|
if ( !TsCheckOutCachedBlob( QueryW3Instance()->GetTsvcCache(),
|
|
_strURL.QueryStr(),
|
|
_strURL.QueryCCH(),
|
|
RESERVED_DEMUX_URI_INFO,
|
|
(VOID **) &pURIBlob,
|
|
NULL ))
|
|
{
|
|
|
|
// We don't have URI info available for this yet. We need to read
|
|
// the metadata for this URI and format it into a usable form, and
|
|
// save that with the request. If this request turns out to be
|
|
// valid later on we'll add it to the cache.
|
|
//
|
|
|
|
if ( !ReadMetaData( _strURL.QueryStr(),
|
|
&_strPhysicalPath,
|
|
&_pMetaData ) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
} else
|
|
{
|
|
// We have a cached URI blob. Save it in the HTTP request.
|
|
|
|
_pURIInfo = pURIBlob;
|
|
|
|
if ( _pURIInfo->pszUnmappedName )
|
|
{
|
|
_strPhysicalPath.Copy( _pURIInfo->pszUnmappedName );
|
|
}
|
|
else
|
|
{
|
|
_strPhysicalPath.Copy( _pURIInfo->pszName, _pURIInfo->cchName );
|
|
}
|
|
|
|
_pMetaData = _pURIInfo->pMetaData;
|
|
}
|
|
|
|
if ( _pMetaData->QueryVrError() )
|
|
{
|
|
SetLastError( _pMetaData->QueryVrError() );
|
|
return FALSE;
|
|
}
|
|
|
|
if ( _pMetaData->QueryIpDnsAccessCheckPtr() )
|
|
{
|
|
QueryClientConn()->BindAccessCheckList( (LPBYTE)_pMetaData->QueryIpDnsAccessCheckPtr(),
|
|
_pMetaData->QueryIpDnsAccessCheckSize() );
|
|
}
|
|
|
|
//
|
|
// If we're already logged on as an anonymouse user, make sure that
|
|
// the anonymous user for this URL is compatible.
|
|
//
|
|
|
|
if (_fLoggedOn && _fAnonymous)
|
|
{
|
|
if (_cbLastAnonAcctDesc != _pMetaData->QueryAuthentInfo()->cbAnonAcctDesc ||
|
|
memcmp(_bufLastAnonAcctDesc.QueryPtr(),
|
|
_pMetaData->QueryAuthentInfo()->bAnonAcctDesc.QueryPtr(),
|
|
_cbLastAnonAcctDesc))
|
|
{
|
|
QueryW3StatsObj()->DecrCurrentAnonymousUsers();
|
|
|
|
ResetAuth(FALSE);
|
|
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::ExecuteChildCGIBGI(
|
|
IN CHAR * pszURL,
|
|
IN DWORD dwChildExecFlags,
|
|
IN CHAR * pszVerb
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Provides ISAPI apps with ability to synchronously execute
|
|
CGI scripts.
|
|
|
|
Arguments:
|
|
|
|
pszURL - URL of request to be executed (must be executable)
|
|
dwChildExecFlags - HSE_EXEC_???? flags
|
|
pszVerb - Verb of request
|
|
|
|
Return value:
|
|
|
|
TRUE if no error, otherwise FALSE
|
|
|
|
--*/
|
|
{
|
|
EXEC_DESCRIPTOR Exec;
|
|
STACK_STR( strChildURL, MAX_PATH + 1 );
|
|
STACK_STR( strChildPhysicalPath, MAX_PATH + 1 );
|
|
STACK_STR( strChildGatewayImage, MAX_PATH + 1 );
|
|
STACK_STR( strChildPathInfo, MAX_PATH + 1 );
|
|
STACK_STR( strChildURLParams, MAX_PATH + 1 );
|
|
STACK_STR( strChildUnmappedPhysicalPath, MAX_PATH + 1 );
|
|
DWORD dwChildScriptMapFlags = 0;
|
|
GATEWAY_TYPE ChildGatewayType = GATEWAY_UNKNOWN;
|
|
BOOL fHandled;
|
|
BOOL fFinished;
|
|
CHAR * pchParams = NULL;
|
|
MB mb( (IMDCOM*) g_pInetSvc->QueryMDObject() );
|
|
BOOL fRet;
|
|
PW3_URI_INFO pURIInfo = NULL;
|
|
BOOL fMustCache = FALSE;
|
|
BOOL fVerbExcluded;
|
|
enum HTTP_VERB RequestVerb;
|
|
enum HTTP_VERB OldVerb;
|
|
CHAR * pszRequestVerb;
|
|
STACK_STR( strOldVerb, 16 );
|
|
|
|
if ( _dwCallLevel > EXEC_MAX_NESTED_LEVELS )
|
|
{
|
|
SetLastError( ERROR_NOT_SUPPORTED );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Populate the execution descriptor block with our own "allocated"
|
|
// members (instead of those from HTTP_REQUEST)
|
|
//
|
|
|
|
Exec._pstrURL = &strChildURL;
|
|
Exec._pstrPhysicalPath = &strChildPhysicalPath;
|
|
Exec._pstrUnmappedPhysicalPath = &strChildUnmappedPhysicalPath;
|
|
Exec._pstrGatewayImage = &strChildGatewayImage;
|
|
Exec._pstrPathInfo = &strChildPathInfo;
|
|
Exec._pstrURLParams = &strChildURLParams;
|
|
Exec._pdwScriptMapFlags = &dwChildScriptMapFlags;
|
|
Exec._pGatewayType = &ChildGatewayType;
|
|
Exec._pRequest = this;
|
|
Exec._pPathInfoMetaData = NULL;
|
|
Exec._pPathInfoURIBlob = NULL;
|
|
Exec._pAppPathURIBlob = NULL;
|
|
|
|
Exec._dwExecFlags = ( EXEC_FLAG_CHILD |
|
|
( dwChildExecFlags &
|
|
( HSE_EXEC_NO_HEADERS |
|
|
HSE_EXEC_REDIRECT_ONLY |
|
|
HSE_EXEC_NO_ISA_WILDCARDS |
|
|
HSE_EXEC_CUSTOM_ERROR ) ) );
|
|
|
|
|
|
if ( !strChildURL.Copy( pszURL ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Tiny bit of "duplicate" code to parse out a query string from the
|
|
// original URL request.
|
|
//
|
|
|
|
pchParams = ::_tcschr( strChildURL.QueryStr(), TEXT('?') );
|
|
if ( pchParams != NULL )
|
|
{
|
|
if ( !strChildURL.SetLen( DIFF(pchParams - strChildURL.QueryStr()) ) ||
|
|
!strChildURLParams.Copy( pchParams + 1 ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if ( !strChildURL.Unescape() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Canonicalize the URL and make sure it's valid
|
|
//
|
|
|
|
CanonURL( strChildURL.QueryStr(), g_pInetSvc->IsSystemDBCS() );
|
|
strChildURL.SetLen( strlen( strChildURL.QueryStr() ));
|
|
|
|
if ( !LookupVirtualRoot( &strChildPhysicalPath,
|
|
strChildURL.QueryStr(),
|
|
strChildURL.QueryCCH(),
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
&(Exec._pMetaData),
|
|
& pURIInfo) )
|
|
{
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Change the verb if requested
|
|
//
|
|
|
|
if ( pszVerb )
|
|
{
|
|
DWORD i = 0;
|
|
DWORD cchVerb = strlen( pszVerb );
|
|
|
|
while ( DoVerb[i].pchVerb )
|
|
{
|
|
if ( ( cchVerb == DoVerb[ i ].cchVerb ) &&
|
|
( !memcmp( pszVerb,
|
|
DoVerb[ i ].pchVerb,
|
|
DoVerb[ i ].cchVerb ) ) )
|
|
{
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if ( !DoVerb[i].pchVerb )
|
|
{
|
|
fRet = FALSE;
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
goto Exit;
|
|
}
|
|
|
|
RequestVerb = DoVerb[ i ].httpVerb;
|
|
pszRequestVerb = pszVerb;
|
|
}
|
|
else
|
|
{
|
|
RequestVerb = QueryVerb();
|
|
pszRequestVerb = _strMethod.QueryStr();
|
|
}
|
|
|
|
//
|
|
// Fill in members of execution descriptor block
|
|
//
|
|
|
|
if ( !ParseExecute( &Exec,
|
|
TRUE,
|
|
&fVerbExcluded,
|
|
pURIInfo,
|
|
RequestVerb,
|
|
pszRequestVerb
|
|
) )
|
|
{
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
DBG_ASSERT(!(dwChildScriptMapFlags & MD_SCRIPTMAPFLAG_WILDCARD));
|
|
|
|
if (ChildGatewayType != GATEWAY_UNKNOWN && ChildGatewayType != GATEWAY_NONE &&
|
|
fVerbExcluded)
|
|
{
|
|
ChildGatewayType = GATEWAY_NONE;
|
|
}
|
|
|
|
_dwCallLevel++;
|
|
|
|
//
|
|
// Remember the original verb to be restored after handling execute
|
|
//
|
|
|
|
if ( pszVerb )
|
|
{
|
|
OldVerb = QueryVerb();
|
|
|
|
if ( !strOldVerb.Copy( _strMethod ) )
|
|
{
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
if ( !_strMethod.Copy( pszVerb ) )
|
|
{
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
_verb = RequestVerb;
|
|
}
|
|
|
|
if ( ChildGatewayType == GATEWAY_CGI )
|
|
{
|
|
fRet = ProcessGateway( &Exec,
|
|
&fHandled,
|
|
&fFinished,
|
|
dwChildScriptMapFlags &
|
|
MD_SCRIPTMAPFLAG_SCRIPT );
|
|
}
|
|
else if ( ChildGatewayType == GATEWAY_BGI )
|
|
{
|
|
if ( fRet = Exec.CreateChildEvent() ) {
|
|
|
|
Exec._pParentWamRequest = _pWamRequest;
|
|
|
|
// process ISAPI request under System
|
|
RevertUser();
|
|
|
|
fRet = ProcessBGI( &Exec,
|
|
&fHandled,
|
|
&fFinished,
|
|
dwChildScriptMapFlags &
|
|
MD_SCRIPTMAPFLAG_SCRIPT );
|
|
|
|
// restore impersonation
|
|
DBG_REQUIRE( ImpersonateUser() );
|
|
|
|
Exec.WaitForChildEvent();
|
|
|
|
} else {
|
|
|
|
DBGPRINTF((
|
|
DBG_CONTEXT
|
|
, "HTTP_REQUEST(%08x)::ExecuteChildCGIBGI() "
|
|
"CreateChildEvent() failed. "
|
|
"\n"
|
|
, this
|
|
));
|
|
|
|
//
|
|
// fRet is failure, so just fall through
|
|
//
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
fRet = FALSE;
|
|
}
|
|
|
|
_dwCallLevel--;
|
|
|
|
//
|
|
// Restore the verb if necessary
|
|
//
|
|
|
|
if ( pszVerb )
|
|
{
|
|
_verb = OldVerb;
|
|
DBG_REQUIRE( _strMethod.Copy( strOldVerb ) );
|
|
}
|
|
|
|
Exit:
|
|
|
|
if ( pURIInfo != NULL)
|
|
{
|
|
if (pURIInfo->bIsCached)
|
|
{
|
|
TsCheckInCachedBlob( pURIInfo );
|
|
}
|
|
else
|
|
{
|
|
TsFree(QueryW3Instance()->GetTsvcCache(), pURIInfo );
|
|
}
|
|
pURIInfo = NULL;
|
|
}
|
|
else
|
|
{
|
|
if ( Exec._pMetaData != NULL)
|
|
{
|
|
TsFreeMetaData( Exec._pMetaData->QueryCacheInfo() );
|
|
}
|
|
}
|
|
Exec.Reset();
|
|
|
|
return fRet;
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::ExecuteChildCommand(
|
|
IN CHAR * pszCommand,
|
|
IN DWORD dwChildExecFlags
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Provides ISAPI apps with ability to synchronously execute a shell command
|
|
(useful for SSI functionality #exec cmd=)
|
|
|
|
Arguments:
|
|
|
|
pszCommand - Command to be executed
|
|
dwChildExecFlags - HSE_EXEC_???? flags
|
|
|
|
Return value:
|
|
|
|
TRUE if no error, otherwise FALSE
|
|
|
|
--*/
|
|
{
|
|
STACK_STR( strCommand, MAX_PATH );
|
|
BOOL fHandled;
|
|
DWORD dwOldFlags = _Exec._dwExecFlags;
|
|
BOOL fRet;
|
|
|
|
if ( !strCommand.Copy( pszCommand ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Since there is no URL associated with a shell command, there is no
|
|
// metadata. Just use the HTTP_REQUEST Exec block. But first set its
|
|
// execFlags correctly
|
|
//
|
|
|
|
_Exec._dwExecFlags |= ( EXEC_FLAG_CHILD |
|
|
( dwChildExecFlags &
|
|
( HSE_EXEC_NO_HEADERS |
|
|
HSE_EXEC_REDIRECT_ONLY ) ) );
|
|
|
|
fRet = ProcessCGI( &_Exec,
|
|
NULL,
|
|
NULL,
|
|
&fHandled,
|
|
NULL,
|
|
&strCommand );
|
|
|
|
//
|
|
// Restore original _Exec flags
|
|
//
|
|
|
|
_Exec._dwExecFlags = dwOldFlags;
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::ParseExecute(
|
|
IN OUT EXEC_DESCRIPTOR * pExec,
|
|
IN BOOL fExecChildCGIBGI,
|
|
OUT BOOL * pfVerbExcluded,
|
|
IN PW3_URI_INFO pURIInfo,
|
|
IN enum HTTP_VERB Verb,
|
|
IN CHAR * pszVerb
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Parse the request now that we know it is an execute.
|
|
|
|
Arguments:
|
|
|
|
pExec - Execution Descriptor Block
|
|
fExecChildCGIBGI - TRUE if this is a #exec request for a
|
|
CGI or ISAPI from inside an .stm page
|
|
or other isapi app. FALSE in the normal
|
|
case of a top level request to execute
|
|
a gateway of some sort.
|
|
pfVerbExcluded - Is the verb excluded, regardless of extension match
|
|
pURIInfo - URI Blob for this 'request'
|
|
Verb - Verb (enum form) of request
|
|
pszVerb - Verb (string form)
|
|
|
|
Returns:
|
|
|
|
TRUE on success, FALSE on failure
|
|
|
|
--*/
|
|
{
|
|
TCHAR * pchStart = pExec->_pstrURL->QueryStr();
|
|
TCHAR * pchtmp = pchStart;
|
|
TCHAR * pchSlash = NULL;
|
|
INT cchToEnd = 0;
|
|
DWORD cchExt = 0;
|
|
GATEWAY_TYPE GatewayType = GATEWAY_UNKNOWN;
|
|
BOOL fImageInURL = FALSE;
|
|
TCHAR * pch = NULL;
|
|
PVOID pvExtMapInfo;
|
|
BOOL fUseURIInfo = pURIInfo && pURIInfo->pvExtMapInfo;
|
|
DWORD cDots = 0;
|
|
|
|
*pfVerbExcluded = FALSE;
|
|
BOOL ExitedHere = FALSE;
|
|
|
|
while ( *pchtmp )
|
|
{
|
|
pvExtMapInfo = NULL;
|
|
|
|
pchtmp = strchr( pchtmp + 1, '.' );
|
|
|
|
//
|
|
// Is this file extension mapped to a script? _GatewayType is
|
|
// set to unknown if a mapping isn't found
|
|
//
|
|
|
|
if ( !pExec->_pMetaData->LookupExtMap( pchtmp,
|
|
pExec->NoIsaWildcards(),
|
|
pExec->_pstrGatewayImage,
|
|
pExec->_pGatewayType,
|
|
&cchExt,
|
|
&fImageInURL,
|
|
pfVerbExcluded,
|
|
pExec->_pdwScriptMapFlags,
|
|
pszVerb,
|
|
Verb,
|
|
fUseURIInfo ?
|
|
&(pURIInfo->pvExtMapInfo) :
|
|
&pvExtMapInfo
|
|
))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
GatewayType = *(pExec->_pGatewayType);
|
|
|
|
if ( pchtmp == NULL )
|
|
{
|
|
break;
|
|
}
|
|
|
|
cDots++;
|
|
|
|
if ( GatewayType != GATEWAY_UNKNOWN )
|
|
{
|
|
|
|
if ( GatewayType == GATEWAY_NONE )
|
|
{
|
|
_fAnyParams = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// If this is a regular CGI script, check for an "nph-"
|
|
//
|
|
|
|
if ( GatewayType == GATEWAY_CGI )
|
|
{
|
|
//
|
|
// Walk backwards till we find the '/' that begins
|
|
// this segment
|
|
//
|
|
|
|
pch = pchtmp;
|
|
|
|
while ( pch >= pchStart && *pch != '/' )
|
|
{
|
|
pch--;
|
|
}
|
|
|
|
if ( !_strnicmp( (*pch == '/' ? pch+1 : pch),
|
|
"nph-",
|
|
4 ))
|
|
{
|
|
pExec->_dwExecFlags |= EXEC_FLAG_CGI_NPH;
|
|
}
|
|
}
|
|
|
|
cchToEnd = DIFF(pchtmp - pchStart) + cchExt;
|
|
break;
|
|
}
|
|
else if ( fUseURIInfo )
|
|
{
|
|
ExitedHere = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// WinNT 379450
|
|
|
|
if (!ExitedHere) // with URIInfo the extension map is got from cache
|
|
{
|
|
if (pchtmp)
|
|
{
|
|
|
|
TCHAR *pNextDot = strchr(pchtmp+1,'.');
|
|
|
|
if (pNextDot && (pchtmp != pNextDot)) // two dot, bad we have to check
|
|
{
|
|
TCHAR * pchStart2 = pExec->_pstrURL->QueryStr();
|
|
DWORD dwLenURL = lstrlen((TCHAR *)pchStart2);
|
|
TCHAR * pCopyUrl = (TCHAR *)LocalAlloc(LPTR,dwLenURL+1);
|
|
|
|
if (!pCopyUrl)
|
|
{
|
|
return FALSE;
|
|
};
|
|
|
|
lstrcpy(pCopyUrl,pchStart2);
|
|
|
|
// terminate string
|
|
pCopyUrl[DIFF(pchtmp - pchStart2)+cchExt] = 0;
|
|
|
|
STACK_STR( StrPhys , MAX_PATH );
|
|
|
|
// we are not going to enlarge string
|
|
if ( !pExec->_pMetaData->BuildPhysicalPath( pCopyUrl, //shortened via '/' -> '\0'
|
|
&StrPhys) )
|
|
{
|
|
// free memory and fail
|
|
LocalFree(pCopyUrl);
|
|
*(pExec->_pGatewayType) = GATEWAY_MALFORMED;
|
|
return TRUE;
|
|
};
|
|
|
|
DWORD dwAttributes = 0xffffffff;
|
|
TSVC_CACHE bogus;
|
|
|
|
TS_OPEN_FILE_INFO* pOpenFile = TsCreateFile(bogus,
|
|
StrPhys.QueryStr(),
|
|
0,
|
|
TS_CACHING_DESIRED );
|
|
|
|
if (pOpenFile != NULL)
|
|
{
|
|
dwAttributes = pOpenFile->QueryAttributes();
|
|
TsCloseHandle(bogus, pOpenFile);
|
|
};
|
|
|
|
if ( dwAttributes != 0xffffffff )
|
|
{
|
|
if ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY )
|
|
{
|
|
LocalFree(pCopyUrl);
|
|
*(pExec->_pGatewayType) = GATEWAY_MALFORMED ;
|
|
return TRUE;
|
|
}
|
|
};
|
|
|
|
LocalFree(pCopyUrl);
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------
|
|
|
|
//
|
|
// We should update the URI blob with the matched EXT_MAP_ITEM if
|
|
//
|
|
// a) We have a URIInfo to update
|
|
// b) LookupExtMap() found a matching EXT_MAP_ITEM (or EXTMAP_UNKNOWN_PTR)
|
|
// c) We didn't previously have a value for pvExtMapInfo
|
|
// d) If the match occurred on the first dot encountered OR if this
|
|
// is a static file (and thus an EXTMAP_UNKNOWN_PTR)
|
|
//
|
|
|
|
if ( !fUseURIInfo &&
|
|
pURIInfo &&
|
|
pvExtMapInfo &&
|
|
( ( cDots == 1 ) || ( pvExtMapInfo == EXTMAP_UNKNOWN_PTR ) ) )
|
|
{
|
|
InterlockedExchangePointer( &(pURIInfo->pvExtMapInfo), pvExtMapInfo );
|
|
}
|
|
|
|
if ( GatewayType & GT_CGI_BGI )
|
|
{
|
|
|
|
//
|
|
// If this is a top level request for an executable (not
|
|
// #exec-ing from inside of an .stm or other isapi),
|
|
// and the image was found in the URL, but the vroot
|
|
// doesn't have execute permission set, then this isn't
|
|
// really a gateway and the client is trying to download
|
|
// the file, so let him.
|
|
//
|
|
|
|
if (!fExecChildCGIBGI && fImageInURL && !(GetFilePerms() & VROOT_MASK_EXECUTE))
|
|
{
|
|
*(pExec->_pGatewayType) = GATEWAY_NONE;
|
|
_fAnyParams = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
if (*(pExec->_pdwScriptMapFlags) & MD_SCRIPTMAPFLAG_WILDCARD)
|
|
{
|
|
cchToEnd = pExec->_pstrURL->QueryCB();
|
|
}
|
|
|
|
//
|
|
// Save the path info and remove it from the URL. If this is a
|
|
// script by association and there isn't any path info, then
|
|
// copy the base URL as the path info (reflects what the URL
|
|
// would like without an association (i.e., "/foo/bar.idc?a=b"
|
|
// is really "/scripts/httpodbc.dll/foo/bar.idc?a=b"))
|
|
//
|
|
// If the binary image is actually in the URL, then always copy
|
|
// the path info.
|
|
//
|
|
|
|
if ( !pExec->_pstrPathInfo->Copy( ( fImageInURL ||
|
|
(*(pchStart + cchToEnd) &&
|
|
QueryW3Instance()->QueryAllowPathInfoForScriptMappings() ))
|
|
?
|
|
pchStart + cchToEnd :
|
|
pchStart ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( pExec->_pstrURL->QueryCCH() != (UINT)cchToEnd )
|
|
{
|
|
pExec->_pstrURL->SetLen( cchToEnd );
|
|
if ( !pExec->_pMetaData->BuildPhysicalPath( pExec->_pstrURL->QueryStr(),
|
|
pExec->_pstrPhysicalPath ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
pExec->_pstrUnmappedPhysicalPath->Reset();
|
|
}
|
|
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"[OnURL] Possible script \"%s\" with path info \"%s\", parms \"%s\"\n",
|
|
pExec->_pstrURL->QueryStr(),
|
|
pExec->_pstrPathInfo->QueryStr(),
|
|
pExec->_pstrURLParams->QueryStr()));
|
|
}
|
|
|
|
}
|
|
else if ( ( QueryDirBrowseFlags() & DIRBROW_LOADDEFAULT ) &&
|
|
( GetFilePerms() & VROOT_MASK_EXECUTE ) &&
|
|
( ( QueryVerb() == HTV_GET ) || ( QueryVerb() == HTV_HEAD ) ) )
|
|
{
|
|
//
|
|
// The vroot is EXECUTE but it isn't a CGI or BGI.
|
|
// This could be a directory for which we will later
|
|
// load a default document
|
|
//
|
|
|
|
#if 0
|
|
DWORD dwAttributes = GetFileAttributes( _strPhysicalPath.QueryStr() );
|
|
#else
|
|
DWORD dwAttributes = 0xffffffff;
|
|
TSVC_CACHE bogus;
|
|
TS_OPEN_FILE_INFO* pOpenFile =
|
|
TsCreateFile(bogus, _strPhysicalPath.QueryStr(),
|
|
0, TS_CACHING_DESIRED | TS_NO_ACCESS_CHECK );
|
|
|
|
if (pOpenFile != NULL)
|
|
{
|
|
dwAttributes = pOpenFile->QueryAttributes();
|
|
TsCloseHandle(bogus, pOpenFile);
|
|
}
|
|
#endif
|
|
|
|
if ( ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY ) &&
|
|
( dwAttributes != 0xffffffff ) )
|
|
{
|
|
_fPossibleDefaultExecute = TRUE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
HTTP_REQUEST::SetupDAVExecute(
|
|
EXEC_DESCRIPTOR *pExec,
|
|
CHAR *szDavDll
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Setups up the exec descriptor to call DAV to process the request. Assumes
|
|
it has already been setup for a call to ProcessExecute().
|
|
|
|
Arguments:
|
|
|
|
pExec - Pointer to the exec descriptor
|
|
|
|
Returns
|
|
|
|
TRUE on success, FALSE on failure
|
|
|
|
--*/
|
|
{
|
|
if (pExec->_pstrGatewayImage->Copy(szDavDll) &&
|
|
pExec->_pstrPathInfo->Copy(pExec->_pstrURL->QueryStr()))
|
|
{
|
|
*pExec->_pGatewayType = GATEWAY_BGI;
|
|
pExec->_dwExecFlags |= EXEC_FLAG_RUNNING_DAV;
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::ProcessURL(
|
|
BOOL * pfFinished,
|
|
BOOL * pfHandled
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Finally converts the URL to a physical path, checking for extension mappings
|
|
|
|
Arguments:
|
|
|
|
pfFinished - Set to TRUE if no further processing is needed and no IOs
|
|
are pending
|
|
pfHandled - If !NULL, set to TRUE if request is handled
|
|
|
|
Returns:
|
|
|
|
TRUE on success, FALSE on failure
|
|
|
|
--*/
|
|
{
|
|
TCHAR chParams;
|
|
BOOL fVerbExcluded;
|
|
BOOL fRet;
|
|
|
|
//
|
|
// First check if a redirect is in order. If so, just do it.
|
|
//
|
|
|
|
if ( _pMetaData->QueryRedirectionBlob() != NULL )
|
|
{
|
|
BOOL fDone = FALSE;
|
|
HTR_STATE OldState = QueryState();
|
|
|
|
SetState( HTR_REDIRECT );
|
|
|
|
if ( !ReadEntityBody( &fDone, TRUE, QueryClientContentLength() ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( fDone )
|
|
{
|
|
SetState( OldState );
|
|
if ( DoRedirect( pfFinished ) )
|
|
{
|
|
if ( pfHandled )
|
|
{
|
|
*pfHandled = TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pfHandled )
|
|
{
|
|
*pfHandled = TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if ( _Filter.IsNotificationNeeded( SF_NOTIFY_URL_MAP,
|
|
IsSecurePort() ))
|
|
{
|
|
DWORD dwDenied = SF_DENIED_RESOURCE;
|
|
BOOL fTmp = FALSE;
|
|
|
|
_strUnmappedPhysicalPath.Copy( _strPhysicalPath );
|
|
|
|
if ( !_strPhysicalPath.Resize( MAX_PATH+sizeof(TCHAR) ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If the caller is going to ignore the Finished request flag, supply
|
|
// a value ourselves
|
|
//
|
|
|
|
if ( !pfFinished )
|
|
{
|
|
pfFinished = &fTmp;
|
|
}
|
|
|
|
fRet = _Filter.NotifyUrlMap( _strURL.QueryStr(),
|
|
_strPhysicalPath.QueryStr(),
|
|
_strPhysicalPath.QuerySize(),
|
|
pfFinished );
|
|
if ( !fRet )
|
|
{
|
|
dwDenied |= SF_DENIED_FILTER;
|
|
if ( GetLastError() == ERROR_ACCESS_DENIED )
|
|
{
|
|
SetDeniedFlags( dwDenied );
|
|
}
|
|
}
|
|
_strPhysicalPath.SetLen( strlen(_strPhysicalPath.QueryStr()) );
|
|
|
|
//
|
|
// If the filters didn't change the physical path, zero out the unmapped
|
|
// path we saved before the filter call - this will save us work later
|
|
//
|
|
|
|
if ( _strPhysicalPath.QueryCCH() == _strUnmappedPhysicalPath.QueryCCH() &&
|
|
!memcmp( _strPhysicalPath.QueryStr(),
|
|
_strUnmappedPhysicalPath.QueryStr(),
|
|
_strPhysicalPath.QueryCCH() ))
|
|
{
|
|
_strUnmappedPhysicalPath.Reset();
|
|
}
|
|
|
|
//
|
|
// If the new mapped physical path does not match what we have in the URI
|
|
// cache entry we need to delete this entry.
|
|
//
|
|
|
|
if ( _pURIInfo && strcmp( _pURIInfo->pszName, _strPhysicalPath.QueryStr() ) )
|
|
{
|
|
//
|
|
// We keep a reference to metadata in the HTTP_REQUEST object, so
|
|
// we increment the reference count to metadata, as freeing the URI
|
|
// cache entry will decrement the reference count.
|
|
//
|
|
|
|
DBG_ASSERT( _pURIInfo->pMetaData != NULL );
|
|
|
|
TsAddRefMetaData( _pURIInfo->pMetaData->QueryCacheInfo() );
|
|
|
|
if ( _pURIInfo->bIsCached )
|
|
{
|
|
//
|
|
// This will dereference the URI cache entry and
|
|
// kick it out of the cache.
|
|
//
|
|
|
|
TsDeCacheCachedBlob( (PVOID)_pURIInfo );
|
|
|
|
// TsCheckInCachedBlob( _pURIInfo );
|
|
}
|
|
else
|
|
{
|
|
TsFree(QueryW3Instance()->GetTsvcCache(), _pURIInfo );
|
|
}
|
|
|
|
_pURIInfo = NULL;
|
|
}
|
|
}
|
|
|
|
if ( *pfFinished )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
#if 0 // This block does nothing in effect. If execute permissions are
|
|
// set, then the first 'if' is false. If they are not set, then the
|
|
// script map code below it will not execute, resulting in a return
|
|
// of TRUE anyway. /SAB
|
|
//
|
|
// If the read bit is set, the execute bit is not set, we recognize the
|
|
// verb, there are no parameters and no ext is allowed on read dir,
|
|
// then don't bother looking at the execute mask
|
|
//
|
|
|
|
if ( (_verb != HTV_UNKNOWN) &&
|
|
!(GetFilePerms() & VROOT_MASK_EXECUTE ) &&
|
|
!_fAnyParams &&
|
|
!fCheckExt )
|
|
{
|
|
if (_verb == HTV_GET && (GetFilePerms() & VROOT_MASK_READ) ) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (IS_WRITE_VERB(_verb) && (GetFilePerms() & VROOT_MASK_WRITE) ) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
#endif // 0
|
|
//
|
|
// If this is on a virtual root with execute permissions ||
|
|
// this might be an ismap request || should be handled by DAV
|
|
// {
|
|
// Check for a possible .exe, .com, or .dll gateway (CGI or BGI)
|
|
// If none, send to DAV if required.
|
|
// }
|
|
//
|
|
DBG_ASSERT(_GatewayType == GATEWAY_UNKNOWN);
|
|
|
|
// Populate an EXECUTION descriptor block and check the scriptmap.
|
|
_Exec._pstrURL = &_strURL;
|
|
_Exec._pstrPhysicalPath = &_strPhysicalPath;
|
|
_Exec._pstrUnmappedPhysicalPath = &_strUnmappedPhysicalPath;
|
|
_Exec._pstrGatewayImage = &_strGatewayImage;
|
|
_Exec._pstrPathInfo = &_strPathInfo;
|
|
_Exec._pstrURLParams = &_strURLParams;
|
|
_Exec._pdwScriptMapFlags = &_dwScriptMapFlags;
|
|
_Exec._pGatewayType = &_GatewayType;
|
|
_Exec._pMetaData = _pMetaData;
|
|
_Exec._pRequest = this;
|
|
_Exec._pPathInfoMetaData = NULL;
|
|
_Exec._pPathInfoURIBlob = NULL;
|
|
_Exec._pAppPathURIBlob = NULL;
|
|
|
|
if ( _bProcessingCustomError )
|
|
{
|
|
_Exec._dwExecFlags |= EXEC_FLAG_CUSTOM_ERROR;
|
|
}
|
|
|
|
//
|
|
// If we know this is a static file, then we don't have to go thru
|
|
// ParseExecute()
|
|
//
|
|
|
|
/*
|
|
|
|
// This optimization causes a regression for directories that use
|
|
// asp/isapi as the default page when only execute/script access
|
|
// is set. It should be fairly simple to reimplement, but given the
|
|
// lateness in the release cycle I'm removing it as a safer fix.
|
|
// Bug 379306 - taylorw
|
|
|
|
if ( !_pURIInfo ||
|
|
_pURIInfo->pvExtMapInfo != EXTMAP_UNKNOWN_PTR )
|
|
{
|
|
*/
|
|
if (!ParseExecute(&_Exec,
|
|
FALSE,
|
|
&fVerbExcluded,
|
|
_pURIInfo,
|
|
QueryVerb(),
|
|
_strMethod.QueryStr()
|
|
))
|
|
{
|
|
return FALSE;
|
|
}
|
|
/*
|
|
}
|
|
*/
|
|
|
|
// Did we find a scriptmap entry at all?
|
|
if (_GatewayType != GATEWAY_UNKNOWN &&
|
|
_GatewayType != GATEWAY_NONE &&
|
|
_GatewayType != GATEWAY_MAP)
|
|
{
|
|
// Is it a wildcard?
|
|
if (_dwScriptMapFlags & MD_SCRIPTMAPFLAG_WILDCARD ||
|
|
|
|
// or is it a regular scriptmap entry and we allow these?
|
|
!_fDisableScriptmap &&
|
|
!fVerbExcluded &&
|
|
(
|
|
(_dwScriptMapFlags & MD_SCRIPTMAPFLAG_SCRIPT) && IS_ACCESS_ALLOWED(SCRIPT) ||
|
|
IS_ACCESS_ALLOWED(EXECUTE)
|
|
)
|
|
)
|
|
{
|
|
// Call this scriptmap.
|
|
DBG_ASSERT(!fVerbExcluded); // Should not return this.
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// If DAV isn't going to handle it, this script doesn't have execute permissions
|
|
//
|
|
if ( !_fSendToDav )
|
|
{
|
|
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
Disconnect( HT_FORBIDDEN, IDS_EXECUTE_ACCESS_DENIED, FALSE, pfFinished );
|
|
if ( pfHandled )
|
|
{
|
|
*pfHandled = TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
_GatewayType = GATEWAY_NONE;
|
|
}
|
|
}
|
|
|
|
// If no script map is assigned, and if this is not a verb that IIS should
|
|
// handle, setup for execution by the DAV .dll.
|
|
if (_fSendToDav)
|
|
{
|
|
W3_IIS_SERVICE * pservice = (W3_IIS_SERVICE *) QueryW3Instance()->m_Service;
|
|
|
|
if (pservice->FDavDll())
|
|
SetupDAVExecute(&_Exec, pservice->SzDavDllGet());
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::OnVersion
|
|
|
|
SYNOPSIS: Parses the version from an HTTP request
|
|
|
|
ENTRY: pszValue - Pointer to zero terminated string
|
|
|
|
|
|
RETURNS: TRUE if successful, FALSE if an error occurred
|
|
|
|
HISTORY:
|
|
Johnl 24-Aug-1994 Created
|
|
MuraliK 29-Jan-1996 Optimized for 1.1
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::OnVersion( CHAR * pszValue )
|
|
{
|
|
//
|
|
// Did the client specify a version string? If not, assume 0.9
|
|
//
|
|
|
|
if ( strncmp( "HTTP/", pszValue, 5 ) == 0 )
|
|
{
|
|
//
|
|
// Move past "HTTP/"
|
|
//
|
|
|
|
// Optimize for Version 1.x in this release - 1/15/97 - MuraliK
|
|
if ((pszValue[5] == '1') && (pszValue[6] == '.')) {
|
|
|
|
_VersionMajor = 1;
|
|
pszValue += 6;
|
|
} else {
|
|
_VersionMajor = (BYTE ) atol( pszValue + 5);
|
|
pszValue = strchr( pszValue + 5, '.' );
|
|
}
|
|
|
|
if ( pszValue != NULL )
|
|
{
|
|
DBG_ASSERT( pszValue[0] == '.');
|
|
if ((pszValue[1] == '1') && (pszValue[2] == '\0')) {
|
|
|
|
if ( g_ReplyWith11 ) {
|
|
_VersionMinor = 1;
|
|
}
|
|
else {
|
|
_VersionMinor = 0;
|
|
}
|
|
} else if ((pszValue[1] == '0') && (pszValue[2] == '\0') ) {
|
|
_VersionMinor = 0;
|
|
} else {
|
|
_VersionMinor = (BYTE ) atol( pszValue + 1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is an HTTP 1.1 request, make KeepConn the default.
|
|
//
|
|
|
|
if ( (_VersionMinor == 1) && (_VersionMajor == 1) ) {
|
|
SetKeepConn( TRUE );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Make sure this really is .9, and not garbage.
|
|
if (*pszValue == '\0')
|
|
{
|
|
_VersionMajor = 0;
|
|
_VersionMinor = 9;
|
|
}
|
|
else
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
} // HTTP_REQUEST::OnVersion()
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::OnAccept
|
|
|
|
SYNOPSIS: Adds the MIME type to our accept list
|
|
|
|
ENTRY: CHAR * pszValue
|
|
|
|
|
|
RETURNS: TRUE if successful, FALSE otherwise
|
|
|
|
NOTES: Accept fields can look like:
|
|
|
|
Accept: text/html
|
|
Accept: image/gif; audio/wav
|
|
Accept: image/jpeg, q=.8, mxb=10000, mxt=5.0; image/gif
|
|
|
|
q - Quality (between zero and one)
|
|
mxb - Maximum bytes acceptable
|
|
mxs - Maximum seconds acceptable
|
|
|
|
We currently ignore the parameters
|
|
|
|
HISTORY:
|
|
Johnl 21-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::OnAccept( CHAR * pszValue )
|
|
{
|
|
//
|
|
// Keep an eye out for "*/*". If it's sent then we
|
|
// don't have to search the list for acceptable client
|
|
// types later on. Note it won't catch the case if the "*" occurs
|
|
// after the first item in the list.
|
|
//
|
|
|
|
if ( *pszValue == '*'
|
|
|| strstr( pszValue, TEXT("*/*") ) )
|
|
{
|
|
_fAcceptsAll = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::DoesClientAccept
|
|
|
|
SYNOPSIS: Searches the client accept list for the specified
|
|
MIME type
|
|
|
|
ENTRY: str - MIME type to search for
|
|
|
|
RETURNS: TRUE if found, FALSE if not found
|
|
|
|
HISTORY:
|
|
Johnl 22-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::DoesClientAccept( PCSTR pstr )
|
|
{
|
|
TCHAR * pchSlash;
|
|
TCHAR * pchType;
|
|
INT cchToSlash;
|
|
|
|
//
|
|
// If the client indicated "*/*" in their accept list, then
|
|
// we don't need to check
|
|
//
|
|
|
|
if ( IsAcceptAllSet() )
|
|
return TRUE;
|
|
|
|
LPSTR pszAcc = (LPSTR ) _HeaderList.FastMapQueryStrValue(HM_ACC);
|
|
|
|
//
|
|
// If no accept headers were passed, then assume client
|
|
// accepts "text/plain" and "text/html"
|
|
//
|
|
|
|
if ( *pszAcc == '\0' )
|
|
{
|
|
//
|
|
// Accept everything if no header was sent.
|
|
//
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Find out where the slash is so we can do a prefix compare
|
|
//
|
|
|
|
pchSlash = _tcschr( pstr, TEXT('/') );
|
|
|
|
if ( !pchSlash )
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"[DoesClientAccept] Bad accept type - \"%s\"",
|
|
pstr ));
|
|
return FALSE;
|
|
}
|
|
|
|
cchToSlash = DIFF(pchSlash - pstr);
|
|
|
|
//
|
|
// Scan through the list for entries that match up to the slash
|
|
//
|
|
|
|
INET_PARSER Parser( pszAcc );
|
|
Parser.SetListMode( TRUE );
|
|
|
|
pchType = Parser.QueryToken();
|
|
|
|
while ( *pchType )
|
|
{
|
|
if ( !::_tcscmp( TEXT("*/*"), pchType ) ||
|
|
!::_tcscmp( TEXT("*"), pchType ))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if ( !_tcsnicmp( pstr,
|
|
pchType,
|
|
cchToSlash ))
|
|
{
|
|
//
|
|
// We matched to the slash. Is the second part a '*'
|
|
// or a real match?
|
|
//
|
|
|
|
if ( *(pchType + cchToSlash + 1) == TEXT('*') ||
|
|
!_tcsicmp( pstr + cchToSlash + 1,
|
|
pchType + cchToSlash + 1 ))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
pchType = Parser.NextItem();
|
|
}
|
|
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"[DoesClientAccept] Client doesn't accept %s\n",
|
|
pstr ));
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::OnContentType
|
|
|
|
SYNOPSIS: Saves the content type
|
|
|
|
ENTRY: pszValue - Pointer to zero terminated string
|
|
|
|
RETURNS: TRUE if successful, FALSE on error
|
|
|
|
NOTES: Client's will generally specify this only for gateway data
|
|
|
|
HISTORY:
|
|
Johnl 10-Oct-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::OnContentType( CHAR * pszValue )
|
|
{
|
|
return _strContentType.Copy( pszValue );
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::OnConnection(
|
|
CHAR * pszValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Looks to see if this connection is a keep-alive connection
|
|
|
|
Arguments:
|
|
|
|
pszValue - Pointer to zero terminated string
|
|
|
|
--*/
|
|
{
|
|
|
|
//
|
|
// Length should be greater than 9
|
|
//
|
|
|
|
if ( (*pszValue == 'K') || (*pszValue == 'k') ) {
|
|
|
|
if ( _stricmp( pszValue+1, "eep-Alive") == 0 ) {
|
|
SetKeepConn( TRUE );
|
|
return(TRUE);
|
|
}
|
|
}
|
|
else if ( (*pszValue == 'C') || (*pszValue == 'c') ) {
|
|
if ( _stricmp( pszValue+1, "lose") == 0 ) {
|
|
SetKeepConn( FALSE );
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do it the long way
|
|
//
|
|
|
|
{
|
|
|
|
INET_PARSER Parser( pszValue );
|
|
|
|
Parser.SetListMode( TRUE );
|
|
|
|
while ( *Parser.QueryToken() )
|
|
{
|
|
if ( !_stricmp( "Keep-Alive", Parser.QueryToken() ))
|
|
{
|
|
SetKeepConn( TRUE );
|
|
}
|
|
else
|
|
{
|
|
if ( !_stricmp( "Close", Parser.QueryToken() ))
|
|
{
|
|
SetKeepConn( FALSE );
|
|
}
|
|
}
|
|
Parser.NextItem();
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::OnTransferEncoding(
|
|
CHAR * pszValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles the Transfer-Encoding header.
|
|
|
|
Arguments:
|
|
|
|
pszValue - Pointer to zero terminated string
|
|
|
|
--*/
|
|
{
|
|
INET_PARSER Parser( pszValue );
|
|
|
|
Parser.SetListMode( TRUE );
|
|
|
|
while ( *Parser.QueryToken() )
|
|
{
|
|
if ( !_stricmp( "Chunked", Parser.QueryToken() ))
|
|
{
|
|
SetChunked( );
|
|
_ChunkState = READ_CHUNK_SIZE;
|
|
_dwChunkSize = -1;
|
|
_cbChunkHeader = 0;
|
|
_cbChunkBytesRead = 0;
|
|
_cbContentLength = 0xffffffff;
|
|
_fHaveContentLength = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
SetLastError(ERROR_NOT_SUPPORTED);
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::OnLockToken(
|
|
CHAR * pszValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles the LockToken header.
|
|
|
|
Arguments:
|
|
|
|
pszValue - Pointer to zero terminated string
|
|
|
|
--*/
|
|
{
|
|
_fSendToDav = TRUE; // Send all requests with lock tokens to DAV
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::OnTranslate(
|
|
CHAR * pszValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles the Translate header.
|
|
|
|
Arguments:
|
|
|
|
pszValue - Pointer to zero terminated string
|
|
|
|
--*/
|
|
{
|
|
if (*pszValue == 'F' || *pszValue == 'f')
|
|
{
|
|
_fSendToDav = TRUE;
|
|
_fDisableScriptmap = TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::OnIf(
|
|
CHAR * pszValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handles the LockToken header.
|
|
|
|
Arguments:
|
|
|
|
pszValue - Pointer to zero terminated string
|
|
|
|
--*/
|
|
{
|
|
_fSendToDav = TRUE; // Send all requests with lock tokens to DAV
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::CacheUri(
|
|
PW3_SERVER_INSTANCE pInstance,
|
|
PW3_URI_INFO* ppURIInfo,
|
|
PW3_METADATA pMetaData,
|
|
LPCSTR pszURL,
|
|
ULONG cchURL,
|
|
STR* pstrPhysicalPath,
|
|
STR* pstrUnmappedPhysicalPath
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cache a URI info structure
|
|
|
|
Arguments:
|
|
|
|
pInstance - instance for this request
|
|
ppURIInfo - updated with ptr to URI info if success
|
|
pMetaData - metadata to associate with URI info
|
|
pszURL - URL for which to cache URI info
|
|
pstrPhysicalPath - physical path associated with pszURL
|
|
pstrUnmappedPhysicalPath - unmapped physical path ( before calling filters )
|
|
associated with pszURL. Can be empty if filter not called.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwStringSize;
|
|
PW3_URI_INFO pURIInfo;
|
|
|
|
// The URI information is NULL. Create a new URI blob, and try to open
|
|
// the file.
|
|
|
|
if (!TsAllocateEx(pInstance->GetTsvcCache(),
|
|
sizeof(W3_URI_INFO),
|
|
(PVOID *)&pURIInfo,
|
|
DisposeOpenURIFileInfo))
|
|
{
|
|
// Not enough memory to create the needed strucure, so fail the
|
|
// request.
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#if 0
|
|
pURIInfo->hFileEvent = IIS_CREATE_EVENT(
|
|
"W3_URI_INFO::hFileEvent",
|
|
pURIInfo,
|
|
TRUE,
|
|
FALSE
|
|
);
|
|
|
|
if ( pURIInfo->hFileEvent == NULL ) {
|
|
TsFree( pInstance->GetTsvcCache(), pURIInfo );
|
|
return FALSE;
|
|
}
|
|
#endif //!oplock
|
|
|
|
// pURIInfo->bFileInfoValid = FALSE;
|
|
pURIInfo->dwFileOpenError = ERROR_FILE_NOT_FOUND;
|
|
pURIInfo->pOpenFileInfo = NULL;
|
|
pURIInfo->bIsCached = TRUE;
|
|
pURIInfo->pMetaData = pMetaData;
|
|
pURIInfo->bInProcOnly = FALSE;
|
|
pURIInfo->bUseAppPathChecked = FALSE;
|
|
|
|
// Copy the name of the physical path, so we have it for change
|
|
// notifies.
|
|
dwStringSize = pstrPhysicalPath->QueryCCH() + 1;
|
|
|
|
pURIInfo->cchName = dwStringSize - 1;
|
|
pURIInfo->pszName = (PCHAR)TCP_ALLOC(dwStringSize * sizeof(CHAR));
|
|
|
|
if (pURIInfo->pszName == NULL)
|
|
{
|
|
|
|
// Not enough memory, fail.
|
|
|
|
TsFree(pInstance->GetTsvcCache(), pURIInfo);
|
|
return FALSE;
|
|
}
|
|
|
|
memcpy( pURIInfo->pszName, pstrPhysicalPath->QueryStr(), dwStringSize);
|
|
|
|
//
|
|
// Copy unmapped physical path ( before filter notification )
|
|
// if different from physical path
|
|
//
|
|
|
|
if ( !pstrUnmappedPhysicalPath->IsEmpty() &&
|
|
strcmp( pstrPhysicalPath->QueryStr(), pstrUnmappedPhysicalPath->QueryStr() ) )
|
|
{
|
|
dwStringSize = pstrUnmappedPhysicalPath->QueryCCH() + 1;
|
|
pURIInfo->pszUnmappedName = (PCHAR)TCP_ALLOC(dwStringSize * sizeof(CHAR));
|
|
|
|
if ( pURIInfo->pszUnmappedName == NULL)
|
|
{
|
|
|
|
// Not enough memory, fail.
|
|
|
|
TsFree(pInstance->GetTsvcCache(), pURIInfo);
|
|
return FALSE;
|
|
}
|
|
|
|
memcpy( pURIInfo->pszUnmappedName, pstrUnmappedPhysicalPath->QueryStr(), dwStringSize);
|
|
}
|
|
else
|
|
{
|
|
pURIInfo->pszUnmappedName = NULL;
|
|
}
|
|
|
|
//
|
|
// Cache the extension map info for this URI
|
|
//
|
|
|
|
pURIInfo->pvExtMapInfo = NULL;
|
|
|
|
// It's set up, so add it to the cache.
|
|
|
|
if ( !TsCacheDirectoryBlob( pInstance->GetTsvcCache(),
|
|
pszURL,
|
|
cchURL,
|
|
RESERVED_DEMUX_URI_INFO,
|
|
pURIInfo,
|
|
TRUE))
|
|
{
|
|
pURIInfo->bIsCached = FALSE;
|
|
}
|
|
|
|
*ppURIInfo = pURIInfo;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Verb worker methods
|
|
//
|
|
|
|
BOOL
|
|
HTTP_REQUEST::DoUnknown(
|
|
BOOL * pfFinished
|
|
)
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"OnDoUnknown - Unknown method - %s\n",
|
|
_strMethod.QueryStr()));
|
|
|
|
if (!_stricmp("POST", _strMethod.QueryStr()))
|
|
{
|
|
SetState( HTR_DONE, HT_METHOD_NOT_ALLOWED, ERROR_INVALID_FUNCTION );
|
|
Disconnect( HT_METHOD_NOT_ALLOWED, NO_ERROR, FALSE, pfFinished );
|
|
}
|
|
else
|
|
{
|
|
SetState( HTR_DONE, HT_NOT_SUPPORTED, ERROR_NOT_SUPPORTED );
|
|
Disconnect( HT_NOT_SUPPORTED, IDS_METHOD_NOT_SUPPORTED, FALSE, pfFinished );
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::LookupVirtualRoot(
|
|
OUT STR * pstrPath,
|
|
IN const CHAR * pszURL,
|
|
IN ULONG cchURL,
|
|
OUT DWORD * pcchDirRoot,
|
|
OUT DWORD * pcchVRoot,
|
|
OUT DWORD * pdwMask,
|
|
OUT BOOL * pfFinished,
|
|
IN BOOL fGetAcl,
|
|
OUT PW3_METADATA* ppMetaData,
|
|
OUT PW3_URI_INFO* ppURIBlob
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Looks up the virtual root to find the physical drive mapping. If an
|
|
Accept-Language header was sent by the client, we look for a virtual
|
|
root prefixed by the language tag
|
|
|
|
Arguments:
|
|
|
|
pstrPath - Receives physical drive path
|
|
pszURL - URL to look for
|
|
pcchDirRoot - Number of characters in the found physical path
|
|
pcchVRoot - Number of characters in the found virtual root
|
|
pdwMask - Access mask for the specified URL
|
|
pfFinished - Set to TRUE if a filter indicated the request should end
|
|
fGetAcl - TRUE to retrieve ACL for this virtual root
|
|
ppMetaData - Pointer to metadata object for URL. If this parameter is
|
|
set (!= NULL), then MetaData/URIBlob is not freed/checked in
|
|
ppURIBlob - Pointer to URIBlob for URL. If this parameter is set
|
|
(!= NULL), then MetaData/URIBlob is not freed/checked in
|
|
--*/
|
|
{
|
|
DWORD cbPath;
|
|
BOOL fRet = TRUE;
|
|
DWORD dwDenied = SF_DENIED_RESOURCE;
|
|
BOOL fAnyFilters = FALSE;
|
|
PW3_URI_INFO pURIBlob = NULL;
|
|
PW3_METADATA pMetaData = NULL;
|
|
BOOL fMustCache = FALSE;
|
|
STR strUnmappedPhysicalPath;
|
|
|
|
if ( !TsCheckOutCachedBlob( QueryW3Instance()->GetTsvcCache(),
|
|
pszURL,
|
|
cchURL,
|
|
RESERVED_DEMUX_URI_INFO,
|
|
(VOID **) &pURIBlob,
|
|
NULL ))
|
|
{
|
|
|
|
// We don't have URI info available for this yet. We need to read
|
|
// the metadata for this URI and format it into a usable form, and
|
|
// add it to the cache
|
|
|
|
if ( !ReadMetaData( (LPSTR)pszURL,
|
|
pstrPath,
|
|
&pMetaData ) )
|
|
{
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
fMustCache = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if ( pURIBlob->pszUnmappedName )
|
|
{
|
|
fRet = pstrPath->Copy( pURIBlob->pszUnmappedName );
|
|
}
|
|
else
|
|
{
|
|
fRet = pstrPath->Copy( pURIBlob->pszName, pURIBlob->cchName );
|
|
}
|
|
|
|
pMetaData = pURIBlob->pMetaData;
|
|
}
|
|
|
|
if ( pMetaData->QueryVrError() )
|
|
{
|
|
SetLastError( pMetaData->QueryVrError() );
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
if ( pcchVRoot )
|
|
{
|
|
*pcchVRoot = pMetaData->QueryVrLen();
|
|
}
|
|
|
|
if ( pdwMask )
|
|
{
|
|
*pdwMask = pMetaData->QueryAccessPerms();
|
|
}
|
|
|
|
if ( fRet && _Filter.IsNotificationNeeded( SF_NOTIFY_URL_MAP,
|
|
IsSecurePort() ))
|
|
{
|
|
BOOL fTmp;
|
|
|
|
fAnyFilters = TRUE;
|
|
|
|
if ( !strUnmappedPhysicalPath.Copy( *pstrPath ) ||
|
|
!pstrPath->Resize( MAX_PATH + sizeof(TCHAR) ) )
|
|
{
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// If the caller is going to ignore the Finished request flag, supply
|
|
// a value ourselves
|
|
//
|
|
|
|
if ( !pfFinished )
|
|
{
|
|
pfFinished = &fTmp;
|
|
}
|
|
|
|
fRet = _Filter.NotifyUrlMap( pszURL,
|
|
pstrPath->QueryStr(),
|
|
pstrPath->QuerySize(),
|
|
pfFinished );
|
|
if ( !fRet )
|
|
{
|
|
dwDenied |= SF_DENIED_FILTER;
|
|
}
|
|
|
|
//
|
|
// Reset length because filter may have resized the string
|
|
//
|
|
pstrPath->SetLen( strlen( pstrPath->QueryStr() ) );
|
|
}
|
|
|
|
//
|
|
// Check for short name as they break metabase equivalency
|
|
//
|
|
|
|
if ( fRet &&
|
|
memchr( pstrPath->QueryStr(), '~', pstrPath->QueryCB() ))
|
|
{
|
|
BOOL fShort;
|
|
DWORD err;
|
|
|
|
if ( err = CheckIfShortFileName( (UCHAR *) pstrPath->QueryStr(),
|
|
pMetaData->QueryVrAccessToken(),
|
|
&fShort ))
|
|
{
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
if ( fShort )
|
|
{
|
|
fRet = FALSE;
|
|
SetLastError( ERROR_FILE_NOT_FOUND );
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if ( !fRet && fAnyFilters && GetLastError() == ERROR_ACCESS_DENIED )
|
|
{
|
|
SetDeniedFlags( dwDenied );
|
|
}
|
|
|
|
if ( pcchDirRoot )
|
|
{
|
|
*pcchDirRoot = pMetaData->QueryVrPath()->QueryCB();
|
|
}
|
|
|
|
Exit:
|
|
|
|
if ( fRet && fMustCache && !CacheUri( QueryW3Instance(),
|
|
&pURIBlob,
|
|
pMetaData,
|
|
pszURL,
|
|
cchURL,
|
|
pstrPath,
|
|
&strUnmappedPhysicalPath ) )
|
|
{
|
|
fRet = FALSE;
|
|
}
|
|
|
|
//
|
|
// A caller will set ppURIBlob and ppMetaData when it wants to use the
|
|
// URI cache for the URL, but takes the reponsibility of freeing the
|
|
// URIBlob/Metadata after it is done using the URL's metadata object.
|
|
//
|
|
|
|
if ( ppURIBlob )
|
|
{
|
|
*ppURIBlob = pURIBlob;
|
|
}
|
|
|
|
if ( ppMetaData )
|
|
{
|
|
*ppMetaData = pMetaData;
|
|
}
|
|
|
|
if ( ppMetaData || ppURIBlob )
|
|
{
|
|
return fRet;
|
|
}
|
|
|
|
if ( pURIBlob != NULL )
|
|
{
|
|
if (pURIBlob->bIsCached)
|
|
{
|
|
TsCheckInCachedBlob( pURIBlob );
|
|
}
|
|
else
|
|
{
|
|
TsFree(QueryW3Instance()->GetTsvcCache(), pURIBlob );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pMetaData != NULL)
|
|
{
|
|
TsFreeMetaData( pMetaData->QueryCacheInfo() );
|
|
}
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::ReprocessURL
|
|
|
|
SYNOPSIS: Called when a map file or gateway has redirected us
|
|
to a different URL. An async completion will be posted
|
|
if TRUE is returned.
|
|
|
|
ENTRY: pchURL - URL we've been redirected to
|
|
|
|
htverb - New verb to use (or unknown to leave as is)
|
|
|
|
RETURNS: TRUE if successful, FALSE otherwise
|
|
|
|
HISTORY:
|
|
Johnl 04-Oct-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::ReprocessURL( TCHAR * pchURL,
|
|
enum HTTP_VERB htverb )
|
|
{
|
|
BOOL fFinished = FALSE;
|
|
BOOL fAcceptReneg = FALSE;
|
|
BOOL fHandled = FALSE;
|
|
|
|
//
|
|
// Reset the gateway type
|
|
//
|
|
|
|
_GatewayType = GATEWAY_UNKNOWN;
|
|
_strGatewayImage.Reset();
|
|
_dwScriptMapFlags = 0;
|
|
_fPossibleDefaultExecute = FALSE;
|
|
|
|
// Need to send a Content-Location header, unless the caller
|
|
// doesn't want us to.
|
|
|
|
ToggleSendCL();
|
|
|
|
switch ( htverb )
|
|
{
|
|
case HTV_GET:
|
|
_verb = HTV_GET;
|
|
_pmfnVerb = DoGet;
|
|
break;
|
|
|
|
case HTV_HEAD:
|
|
_verb = HTV_HEAD;
|
|
_pmfnVerb = DoGet;
|
|
break;
|
|
|
|
case HTV_TRACE:
|
|
_verb = HTV_TRACE;
|
|
_pmfnVerb = DoTrace;
|
|
break;
|
|
|
|
case HTV_TRACECK:
|
|
_verb = HTV_TRACECK;
|
|
_pmfnVerb = DoTraceCk;
|
|
break;
|
|
|
|
case HTV_POST:
|
|
case HTV_UNKNOWN:
|
|
break;
|
|
|
|
default:
|
|
DBG_ASSERT( !"[ReprocessURL] Unknown verb type" );
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
ReleaseCacheInfo();
|
|
|
|
SetState( HTR_DOVERB );
|
|
|
|
_acIpAccess = AC_NOT_CHECKED;
|
|
|
|
if ( !OnURL( pchURL ) ||
|
|
!ProcessURL( &fFinished, &fHandled ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If ProcessUrl() handled the request, but we are not finished yet, then
|
|
// ProcessUrl() must have asynchronously send a response
|
|
//
|
|
|
|
if ( !fFinished && fHandled )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// <Begin explanantion of client cert renegotiation voodoo>
|
|
// Check if we need to request a client cert. RequestRenegotiate() will call down
|
|
// to sspifilt to send the necessary SSPI/SSL blob to start the renegotiation
|
|
//
|
|
|
|
if ( QueryState() != HTR_CERT_RENEGOTIATE )
|
|
{
|
|
if ( !RequestRenegotiate( &fAcceptReneg ) )
|
|
{
|
|
if ( GetLastError() == SEC_E_INCOMPLETE_MESSAGE )
|
|
{
|
|
fAcceptReneg = FALSE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If renegotiation was requested/accepted, begin reading data. We issue an async read
|
|
// for the blobs from the client [triggered by the call to RequestRenegotiate above] and
|
|
// back out all the way [to CLIENT_CONN::DoWork] to wait for the async completion to come
|
|
// in with the blob from the client. From there, HandleCertRenegotiation() will take
|
|
// care of the rest of the renegotiation.
|
|
// </End explanation of client cert renegotiation voodoo>
|
|
//
|
|
|
|
if ( fAcceptReneg )
|
|
{
|
|
//
|
|
// _cbOldData is the number of bytes of the client request that have already
|
|
// been seen by the READ_RAW filter(s)
|
|
//
|
|
_cbOldData = _cbClientRequest + _cbEntityBody;
|
|
DWORD cbNextRead = CERT_RENEGO_READ_SIZE;
|
|
|
|
if ( !_bufClientRequest.Resize( _cbOldData + cbNextRead ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
if ( !ReadFile( (BYTE *) _bufClientRequest.QueryPtr() + _cbOldData,
|
|
cbNextRead,
|
|
NULL,
|
|
IO_FLAG_ASYNC|IO_FLAG_NO_FILTER ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// If need to check IP access, do so now
|
|
//
|
|
|
|
if ( !fFinished )
|
|
{
|
|
//
|
|
// Check to see if encryption is required before we do any processing
|
|
//
|
|
|
|
if ( ( GetFilePerms() & VROOT_MASK_SSL )
|
|
&& !IsSecurePort() )
|
|
{
|
|
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
Disconnect( HT_FORBIDDEN, IDS_SSL_REQUIRED, FALSE, &fFinished );
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Check if encryption key size should be at least 128 bits
|
|
//
|
|
|
|
if ( ( GetFilePerms() & VROOT_MASK_SSL128 ) )
|
|
{
|
|
DWORD dwKeySize;
|
|
BOOL fNoCert;
|
|
|
|
if ( !_tcpauth.QueryEncryptionKeySize(&dwKeySize, &fNoCert) || (dwKeySize < 128) )
|
|
{
|
|
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
Disconnect( HT_FORBIDDEN, IDS_SSL128_REQUIRED, FALSE, &fFinished );
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if ( !IsIpDnsAccessCheckPresent() )
|
|
{
|
|
_acIpAccess = AC_IN_GRANT_LIST;
|
|
}
|
|
else if ( _acIpAccess == AC_NOT_CHECKED )
|
|
{
|
|
_acIpAccess = QueryClientConn()->CheckIpAccess( &_fNeedDnsCheck );
|
|
|
|
if ( (_acIpAccess == AC_IN_DENY_LIST) ||
|
|
((_acIpAccess == AC_NOT_IN_GRANT_LIST) && !_fNeedDnsCheck) )
|
|
{
|
|
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
Disconnect( HT_FORBIDDEN, IDS_ADDR_REJECT, FALSE, &fFinished );
|
|
goto Exit;
|
|
}
|
|
|
|
if ( _fNeedDnsCheck && !QueryClientConn()->IsDnsResolved() )
|
|
{
|
|
BOOL fSync;
|
|
LPSTR pDns;
|
|
AC_RESULT acDnsAccess;
|
|
|
|
if ( !QueryClientConn()->QueryDnsName( &fSync,
|
|
(ADDRCHECKFUNCEX)NULL,
|
|
(ADDRCHECKARG)QueryClientConn(),
|
|
&pDns ) )
|
|
{
|
|
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
Disconnect( HT_FORBIDDEN, IDS_ADDR_REJECT, FALSE, &fFinished );
|
|
goto Exit;
|
|
}
|
|
|
|
acDnsAccess = QueryClientConn()->CheckDnsAccess();
|
|
|
|
_fNeedDnsCheck = FALSE;
|
|
|
|
if ( acDnsAccess == AC_IN_DENY_LIST ||
|
|
acDnsAccess == AC_NOT_IN_GRANT_LIST ||
|
|
(_acIpAccess == AC_NOT_IN_GRANT_LIST && acDnsAccess != AC_IN_GRANT_LIST) )
|
|
{
|
|
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
Disconnect( HT_FORBIDDEN, IDS_ADDR_REJECT, FALSE, &fFinished );
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CheckValidAuth();
|
|
|
|
if ( !IsLoggedOn() && !LogonUser( &fFinished ) )
|
|
{
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !fFinished )
|
|
{
|
|
if ( !DoWork( &fFinished ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
//
|
|
// If no further processing is needed, set our state to done and post
|
|
// an async completion. We do this as the caller expects an async
|
|
// completion to clean things up
|
|
//
|
|
|
|
if ( fFinished )
|
|
{
|
|
DBG_ASSERT( QueryLogHttpResponse() != HT_DONT_LOG );
|
|
|
|
SetState( HTR_DONE, QueryLogHttpResponse(), QueryLogWinError() );
|
|
return PostCompletionStatus( 0 );
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::GetInfo
|
|
|
|
SYNOPSIS: Pulls out various bits of information from this request.
|
|
|
|
ENTRY: pszValName - Value to retrieve
|
|
pstr - Receives information in a string format
|
|
pfFound - Option, Set to TRUE if a value was found, FALSE
|
|
otherwise
|
|
|
|
NOTES:
|
|
|
|
HISTORY:
|
|
Johnl 25-Sep-1994 Created
|
|
MuraliK 3-July-1996 Rewrote for efficiency - used switch-case
|
|
MuraliK 21-Nov-1996 Rewrote again for efficiency
|
|
- use sub-function pointers
|
|
|
|
********************************************************************/
|
|
|
|
BOOL
|
|
HTTP_REQUEST::GetInfo(
|
|
const TCHAR * pszValName,
|
|
STR * pstr,
|
|
BOOL * pfFound
|
|
)
|
|
{
|
|
if ( !pszValName )
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
if ( pfFound )
|
|
{
|
|
*pfFound = TRUE;
|
|
}
|
|
|
|
//
|
|
// terminate the string
|
|
//
|
|
|
|
pstr->Reset();
|
|
|
|
if ( !strcmp( "ALL_HTTP", pszValName ))
|
|
return BuildCGIHeaderListInSTR( pstr, &_HeaderList );
|
|
|
|
//
|
|
// Use the GetInfoForName() function to generate the required value
|
|
// All callers of GetInfo() should be directly calling GetInfoForName()
|
|
// for efficiency sake
|
|
//
|
|
|
|
BOOL fRet;
|
|
DWORD cb = pstr->QuerySize();
|
|
fRet = GetInfoForName( pszValName, pstr->QueryStr(), &cb);
|
|
|
|
if ( !fRet) {
|
|
|
|
switch ( GetLastError()) {
|
|
|
|
case ERROR_INSUFFICIENT_BUFFER:
|
|
DBG_ASSERT( cb > pstr->QuerySize());
|
|
if ( !pstr->Resize( cb + 1)) {
|
|
|
|
return ( FALSE);
|
|
}
|
|
|
|
// Try to get the value again.
|
|
fRet = GetInfoForName( pszValName, pstr->QueryStr(), &cb);
|
|
if ( fRet) {
|
|
pstr->SetLen( strlen( pstr->QueryStr()));
|
|
} else {
|
|
DBG_ASSERT( GetLastError() != ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
|
|
break;
|
|
|
|
case ERROR_INVALID_PARAMETER:
|
|
case ERROR_INVALID_INDEX:
|
|
|
|
if ( pfFound ) {
|
|
*pfFound = FALSE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// there is a failure. Return the error to caller
|
|
break;
|
|
} // switch()
|
|
} else {
|
|
|
|
// Set the appropriate length
|
|
pstr->SetLen( strlen( pstr->QueryStr()));
|
|
}
|
|
|
|
return (fRet);
|
|
} // HTTP_REQUEST::GetInfo()
|
|
|
|
|
|
|
|
BOOL
|
|
BuildCGIHeaderListInSTR( STR * pstr,
|
|
HTTP_HEADERS * pHeaderList
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Builds a list of all client passed headers in the form of
|
|
|
|
//
|
|
// Builds a list of all client HTTP headers in the form of:
|
|
//
|
|
// HTTP_<up-case header>: <field>\n
|
|
// HTTP_<up-case header>: <field>\n
|
|
// ...
|
|
//
|
|
|
|
Arguments:
|
|
|
|
pstr - Receives full list
|
|
pHeaderList - List of headers
|
|
|
|
--*/
|
|
{
|
|
|
|
CHAR ach[MAX_HEADER_LENGTH + CGI_HEADER_PREFIX_CCH + 1];
|
|
CHAR * pch;
|
|
DWORD i;
|
|
HH_ITERATOR hhi;
|
|
NAME_VALUE_PAIR * pnp = NULL;
|
|
|
|
CopyMemory( ach, CGI_HEADER_PREFIX_SZ, CGI_HEADER_PREFIX_CCH );
|
|
|
|
pHeaderList->InitIterator( &hhi);
|
|
|
|
while (pHeaderList->NextPair( &hhi, &pnp)) {
|
|
|
|
if ( pnp->cchName + CGI_HEADER_PREFIX_CCH > sizeof( ach)) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Ignore some headers like "method", "url" and "version" -
|
|
// ones without the ':' at the end
|
|
//
|
|
|
|
if ( pnp->pchName[pnp->cchName - 1] != ':' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Convert the destination to upper and replace all '-' with '_'
|
|
//
|
|
|
|
pch = ach + CGI_HEADER_PREFIX_CCH;
|
|
for ( i = 0; i < pnp->cchName; i++) {
|
|
pch[i] = (( pnp->pchName[i] == '-') ? '_' :
|
|
toupper( pnp->pchName[i])
|
|
);
|
|
} // for
|
|
|
|
pch[i] = '\0';
|
|
|
|
if ( !pstr->Append( ach ) ||
|
|
!pstr->Append( pnp->pchValue, pnp->cchValue ) ||
|
|
!pstr->Append( "\n", sizeof("\n")-1 ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
} // for iterator over headers
|
|
|
|
return TRUE;
|
|
} // BuildCGIHeaderListInSTR()
|
|
|
|
|
|
|
|
|
|
DWORD
|
|
HTTP_REQUEST::Initialize(
|
|
VOID
|
|
)
|
|
{
|
|
DBG_REQUIRE( HTTP_HEADERS::Initialize());
|
|
|
|
_fGlobalInit = TRUE;
|
|
|
|
//
|
|
// Initialize the GetInfo function table
|
|
//
|
|
|
|
//
|
|
// 1. init the values to standard function GetInfoMisc() to start with
|
|
//
|
|
for( CHAR ch = 'A'; ch <= 'Z'; ch++) {
|
|
sm_GetInfoFuncs[IndexOfChar(ch)] = (PFN_GET_INFO ) &GetInfoMisc;
|
|
}
|
|
|
|
//
|
|
// 2. Initialize all known functions as appropriate
|
|
//
|
|
sm_GetInfoFuncs[IndexOfChar('A')] = (PFN_GET_INFO ) &GetInfoA;
|
|
sm_GetInfoFuncs[IndexOfChar('C')] = (PFN_GET_INFO ) &GetInfoC;
|
|
|
|
sm_GetInfoFuncs[IndexOfChar('H')] = (PFN_GET_INFO ) &GetInfoH;
|
|
sm_GetInfoFuncs[IndexOfChar('I')] = (PFN_GET_INFO ) &GetInfoI;
|
|
sm_GetInfoFuncs[IndexOfChar('L')] = (PFN_GET_INFO ) &GetInfoL;
|
|
|
|
sm_GetInfoFuncs[IndexOfChar('P')] = (PFN_GET_INFO ) &GetInfoP;
|
|
sm_GetInfoFuncs[IndexOfChar('R')] = (PFN_GET_INFO ) &GetInfoR;
|
|
|
|
sm_GetInfoFuncs[IndexOfChar('S')] = (PFN_GET_INFO ) &GetInfoS;
|
|
sm_GetInfoFuncs[IndexOfChar('U')] = (PFN_GET_INFO ) &GetInfoU;
|
|
|
|
return (NO_ERROR);
|
|
|
|
} // HTTP_REQUEST::Initialize()
|
|
|
|
|
|
|
|
|
|
VOID
|
|
HTTP_REQUEST::Terminate(
|
|
VOID
|
|
)
|
|
{
|
|
if ( !_fGlobalInit )
|
|
return;
|
|
|
|
HTTP_HEADERS::Cleanup();
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::RequestRenegotiate(
|
|
LPBOOL pfAccepted
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This method is invoked to request a SSL cert renegotiation
|
|
|
|
Arguments:
|
|
|
|
pfAccepted - updated with TRUE if renegotiation accepted
|
|
|
|
Returns:
|
|
|
|
TRUE if no error, otherwise FALSE
|
|
|
|
--*/
|
|
{
|
|
if ( !IsSecurePort() ||
|
|
!(GetFilePerms() & VROOT_MASK_NEGO_CERT) )
|
|
{
|
|
*pfAccepted = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if ( _dwRenegotiated &&
|
|
(GetFilePerms() & VROOT_MASK_MAP_CERT) ==
|
|
(UINT)((_dwSslNegoFlags & SSLNEGO_MAP) ? VROOT_MASK_MAP_CERT : 0) )
|
|
{
|
|
*pfAccepted = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
_dwRenegotiated = 0;
|
|
|
|
//
|
|
// Ask the filter to handle renegotiation
|
|
//
|
|
|
|
if ( !_Filter.NotifyRequestRenegotiate( &_Filter,
|
|
pfAccepted,
|
|
GetFilePerms() & VROOT_MASK_MAP_CERT )
|
|
)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( *pfAccepted )
|
|
{
|
|
if ( GetFilePerms() & VROOT_MASK_MAP_CERT )
|
|
{
|
|
_dwSslNegoFlags |= SSLNEGO_MAP;
|
|
}
|
|
else
|
|
{
|
|
_dwSslNegoFlags &= ~SSLNEGO_MAP;
|
|
}
|
|
|
|
SetState( HTR_CERT_RENEGOTIATE );
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::DoneRenegotiate(
|
|
BOOL fSuccess
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This method is invoked on SSL cert renegotiation completion
|
|
|
|
Arguments:
|
|
|
|
fSuccess - TRUE if renegotiation successfully retrieve a certificate
|
|
|
|
Returns:
|
|
|
|
TRUE if no error, otherwise FALSE
|
|
|
|
--*/
|
|
{
|
|
_dwRenegotiated = fSuccess ? CERT_NEGO_SUCCESS : CERT_NEGO_FAILURE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
HTTP_REQUEST::EndOfRequest(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This method does the necessary cleanup for the end of this request - note
|
|
on error conditions this method may not be called, so there is some
|
|
duplication with SessionTerminated which is guaranteed to be called
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Returns:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Notify filters this is the end of the request
|
|
//
|
|
|
|
if ( _Filter.IsNotificationNeeded( SF_NOTIFY_END_OF_REQUEST,
|
|
IsSecurePort() ))
|
|
{
|
|
_Filter.NotifyEndOfRequest();
|
|
}
|
|
|
|
CloseGetFile();
|
|
|
|
//
|
|
// Reset the authentication if
|
|
// - not in authentication phase of connection
|
|
// - SSL connection; connection can't be reused [eg by proxy] because it's encrypted
|
|
// - flag single request per auth is set
|
|
// - flag single proxy request per auth is set and request coming from a proxy
|
|
// ( as determined by checking for 'Via:' header ). Note that this method
|
|
// cannot guarantee proxy detection, but this is the best we can do.
|
|
// There are 2 submodes : always reset auth for proxies, or reset auth only
|
|
// if not in proxy mode ( as set by ISAPI app, default )
|
|
//
|
|
|
|
if ( _pMetaData &&
|
|
!_fAuthenticating &&
|
|
!_fAnonymous &&
|
|
!IsSecurePort() &&
|
|
( (_pMetaData->QueryAuthenticationPersistence() & MD_AUTH_SINGLEREQUEST) ||
|
|
( (_pMetaData->QueryAuthenticationPersistence() & MD_AUTH_SINGLEREQUESTALWAYSIFPROXY) &&
|
|
IsClientProxy() ) ||
|
|
( (_pMetaData->QueryAuthenticationPersistence() & MD_AUTH_SINGLEREQUESTIFPROXY) &&
|
|
IsClientProxy() &&
|
|
!IsProxyRequest() )
|
|
)
|
|
)
|
|
{
|
|
_fSingleRequestAuth = TRUE;
|
|
}
|
|
|
|
// Free the URI and/or metadata information if we have any. We know that
|
|
// if we have URI info, then it points at meta data and will free the
|
|
// metadata info when the URI info is free. Otherwise, we need to free
|
|
// the metadata information here.
|
|
|
|
CleanupWriteState();
|
|
|
|
ReleaseCacheInfo();
|
|
|
|
return;
|
|
|
|
} // HTTP_REQUEST::EndOfRequest()
|
|
|
|
|
|
VOID
|
|
HTTP_REQUEST::SessionTerminated(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This method does the necessary cleanup for the connected session
|
|
for this request object.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Returns:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
switch ( QueryState()) {
|
|
|
|
case HTR_GATEWAY_ASYNC_IO:
|
|
|
|
// does the necessary actions to cleanup outstanding IO operation
|
|
(VOID ) ProcessAsyncGatewayIO();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
} // switch()
|
|
|
|
CloseGetFile();
|
|
|
|
// Free the URI and/or metadata information if we have any. We know that
|
|
// if we have URI info, then it points at meta data and will free the
|
|
// metadata info when the URI info is free. Otherwise, we need to free
|
|
// the metadata information here.
|
|
|
|
ReleaseCacheInfo();
|
|
|
|
//
|
|
// do the cleanup for base object
|
|
//
|
|
HTTP_REQ_BASE::SessionTerminated();
|
|
|
|
return;
|
|
|
|
} // HTTP_REQUEST::SessionTerminated()
|
|
|
|
#define EXTRA_ETAG_PRECOND_SIZE (sizeof("ETag: W/\r\n") - 1 + \
|
|
MAX_ETAG_BUFFER_LENGTH + \
|
|
sizeof("Date: \r\n") - 1+ \
|
|
sizeof("Mon, 00 Jan 1997: 00:00:00 GMT") - 1 +\
|
|
sizeof("Last-Modified: \r\n") - 1+ \
|
|
sizeof("Mon, 00 Jan 1997 00:00:00 GMT") - 1)
|
|
|
|
#define EXTRA_PRECOND_SIZE (sizeof("Connection: keep-alive\r\n") - 1 +\
|
|
sizeof("Content-Length: 4294967295\r\n\r\n") - 1)
|
|
|
|
BOOL
|
|
HTTP_REQUEST::SendPreconditionResponse(
|
|
DWORD HT_Response,
|
|
DWORD dwIOFlags,
|
|
BOOL *pfFinished
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Utility routine to send the appropriate header for a precondition
|
|
failure.
|
|
|
|
Arguments:
|
|
|
|
HT_Response - The response to be sent.
|
|
|
|
pfFinished - Boolean indicating whether or not we're finished.
|
|
|
|
|
|
Returns:
|
|
|
|
TRUE if we sent a response to the request, FALSE if we didn't.
|
|
|
|
--*/
|
|
{
|
|
CHAR *pszTail;
|
|
STACK_STR(strTemp, 80);
|
|
DWORD dwCurrentSize;
|
|
CHAR ach[64];
|
|
|
|
//
|
|
// Build the response with support for keep-alives
|
|
//
|
|
|
|
if ( !BuildStatusLine( &strTemp,
|
|
HT_Response,
|
|
NO_ERROR ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// If this is a 304 response we need to send an ETag, and Expires.
|
|
//
|
|
if (HT_Response == HT_NOT_MODIFIED)
|
|
{
|
|
|
|
if (!BuildBaseResponseHeader(QueryRespBuf(),
|
|
pfFinished,
|
|
&strTemp,
|
|
HTTPH_SEND_GLOBAL_EXPIRE))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
dwCurrentSize = strlen(QueryRespBufPtr());
|
|
|
|
if (!QueryRespBuf()->Resize(dwCurrentSize + EXTRA_ETAG_PRECOND_SIZE +
|
|
EXTRA_PRECOND_SIZE))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pszTail = QueryRespBufPtr() + dwCurrentSize;
|
|
|
|
DBG_ASSERT(_pGetFile != NULL);
|
|
|
|
if ( !QueryNoCache() )
|
|
{
|
|
//
|
|
// Don't send a Last-Modified time, per the 1.1 spec.
|
|
//
|
|
|
|
//
|
|
// ETag: <Etag>
|
|
//
|
|
|
|
if (_pGetFile->WeakETag())
|
|
{
|
|
APPEND_PSZ_HEADER(pszTail, "ETag: W/", _pGetFile->QueryETag(),
|
|
"\r\n");
|
|
} else
|
|
{
|
|
APPEND_PSZ_HEADER(pszTail, "ETag: ", _pGetFile->QueryETag(),
|
|
"\r\n");
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (!BuildBaseResponseHeader(QueryRespBuf(),
|
|
pfFinished,
|
|
&strTemp,
|
|
HTTPH_NO_CUSTOM))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pszTail = QueryRespBufPtr();
|
|
dwCurrentSize = strlen(pszTail);
|
|
|
|
if (!QueryRespBuf()->Resize(dwCurrentSize + EXTRA_PRECOND_SIZE))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pszTail = QueryRespBufPtr() + dwCurrentSize;
|
|
}
|
|
|
|
//
|
|
// Don't need to add Connection: header, that's done by
|
|
// BuildBaseResponseHeader.
|
|
|
|
if ( IsKeepConnSet() )
|
|
{
|
|
if (!_fHaveContentLength &&
|
|
(_cbBytesReceived == _cbClientRequest))
|
|
{
|
|
dwIOFlags |= IO_FLAG_AND_RECV;
|
|
}
|
|
}
|
|
|
|
if (HT_Response != HT_NOT_MODIFIED)
|
|
{
|
|
BYTE cMsg[128];
|
|
BUFFER bufMsg(cMsg, sizeof(cMsg));
|
|
DWORD dwMsgSize;
|
|
|
|
if (CheckCustomError(&bufMsg, HT_Response, 0, pfFinished, &dwMsgSize, FALSE))
|
|
{
|
|
//
|
|
// Now that we know the content length, add a content length
|
|
// header, and copy the custom error message to the buffer.
|
|
//
|
|
|
|
APPEND_NUMERIC_HEADER( pszTail, "Content-Length: ", dwMsgSize, "\r\n" );
|
|
|
|
dwCurrentSize = DIFF(pszTail - QueryRespBufPtr());
|
|
|
|
if (!QueryRespBuf()->Resize(dwCurrentSize + bufMsg.QuerySize()))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pszTail = QueryRespBufPtr() + dwCurrentSize;
|
|
|
|
strcat(pszTail, (CHAR *)bufMsg.QueryPtr());
|
|
}
|
|
else
|
|
{
|
|
APPEND_STRING(pszTail, "Content-Length: 0\r\n\r\n");
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Need to send a content length of 0 because of HTTP/1.0 clients
|
|
//
|
|
|
|
APPEND_STRING(pszTail, "Content-Length: 0\r\n\r\n");
|
|
}
|
|
|
|
|
|
SetState( HTR_DONE, HT_Response, NO_ERROR );
|
|
|
|
return SendHeader( QueryRespBufPtr(),
|
|
QueryRespBufCB(),
|
|
dwIOFlags,
|
|
pfFinished );
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::FindInETagList(
|
|
PCHAR pLocalETag,
|
|
PCHAR pETagList,
|
|
BOOL bWeakCompare
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Search and input list of ETag for one that matches our local ETag.
|
|
|
|
Arguments:
|
|
|
|
pLocalETag - The local ETag we're using.
|
|
|
|
pETagList - The ETag list we've received from the client.
|
|
|
|
bWeakCompare - TRUE if we're doing a weak compare.
|
|
|
|
Returns:
|
|
|
|
TRUE if we found a matching ETag, FALSE otherwise.
|
|
|
|
--*/
|
|
{
|
|
UINT QuoteCount;
|
|
PCHAR pFileETag;
|
|
BOOL Matched;
|
|
|
|
// Otherwise, we'll loop through the ETag string, looking for ETag to
|
|
// compare, as long as we have an ETag to look at.
|
|
|
|
do {
|
|
|
|
while (isspace((UCHAR)(*pETagList)))
|
|
{
|
|
pETagList++;
|
|
}
|
|
|
|
if (!*pETagList)
|
|
{
|
|
// Ran out of ETag.
|
|
return FALSE;
|
|
}
|
|
|
|
// If this ETag is '*', it's a match.
|
|
if (*pETagList == '*')
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
// See if this ETag is weak.
|
|
if (*pETagList == 'W' && *(pETagList+1) == '/')
|
|
{
|
|
// This is a weak validator. If we're not doing the weak comparison,
|
|
// fail.
|
|
|
|
if (!bWeakCompare)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// Skip over the 'W/', and any intervening whitespace.
|
|
pETagList += 2;
|
|
|
|
while (isspace((UCHAR)(*pETagList)))
|
|
{
|
|
pETagList++;
|
|
}
|
|
|
|
if (!*pETagList)
|
|
{
|
|
// Ran out of ETag.
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (*pETagList != '"')
|
|
{
|
|
// This isn't a quoted string, so fail.
|
|
return FALSE;
|
|
}
|
|
|
|
// OK, right now we should be at the start of a quoted string that
|
|
// we can compare against our current ETag.
|
|
|
|
QuoteCount = 0;
|
|
|
|
Matched = TRUE;
|
|
pFileETag = pLocalETag;
|
|
|
|
// Do the actual compare. We do this by scanning the current ETag,
|
|
// which is a quoted string. We look for two quotation marks, the
|
|
// the delimiters if the quoted string. If after we find two quotes
|
|
// in the ETag everything has matched, then we've matched this ETag.
|
|
// Otherwise we'll try the next one.
|
|
|
|
do
|
|
{
|
|
CHAR Temp;
|
|
|
|
Temp = *pETagList;
|
|
|
|
if (Temp == '"')
|
|
{
|
|
QuoteCount++;
|
|
}
|
|
|
|
if (*pFileETag != Temp)
|
|
{
|
|
Matched = FALSE;
|
|
}
|
|
|
|
if (!Temp)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pETagList++;
|
|
|
|
if (*pFileETag == '\0')
|
|
{
|
|
break;
|
|
}
|
|
|
|
pFileETag++;
|
|
|
|
|
|
} while (QuoteCount != 2);
|
|
|
|
if (Matched)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
// Otherwise, at this point we need to look at the next ETag.
|
|
|
|
while (QuoteCount != 2)
|
|
{
|
|
if (*pETagList == '"')
|
|
{
|
|
QuoteCount++;
|
|
}
|
|
else
|
|
{
|
|
if (*pETagList == '\0')
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
pETagList++;
|
|
}
|
|
|
|
while (isspace((UCHAR)(*pETagList)))
|
|
{
|
|
pETagList++;
|
|
}
|
|
|
|
if (*pETagList == ',')
|
|
{
|
|
pETagList++;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
} while ( *pETagList );
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::CancelPreconditions(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cancel preconditions for this request
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
TRUE if success, otherwise FALSE
|
|
|
|
--*/
|
|
{
|
|
_strRange.Reset();
|
|
_HeaderList.FastMapCancel(HM_IFM);
|
|
_liUnmodifiedSince.QuadPart = 0;
|
|
_HeaderList.FastMapCancel(HM_INM);
|
|
_liModifiedSince.QuadPart = 0;
|
|
_dwModifiedSinceLength = 0;
|
|
_HeaderList.FastMapCancel(HM_IFR);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::CheckPreconditions(
|
|
LPTS_OPEN_FILE_INFO pFile,
|
|
BOOL *pfFinished,
|
|
BOOL *bReturn
|
|
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle all of the If Modifiers on a request, and do the right thing
|
|
with them. Note that this version implicitly assumes that the caller
|
|
is performing a GET or HEAD or something compatible.
|
|
|
|
Arguments:
|
|
|
|
pFile - Pointer to file information for file we're checking.
|
|
|
|
pfFinished - pass through BOOLEAN.
|
|
|
|
bReturn - Where to store the response value for this routine.
|
|
|
|
Returns:
|
|
|
|
TRUE if we sent a response to the request, FALSE if we didn't.
|
|
|
|
--*/
|
|
{
|
|
LPSTR pszETagList;
|
|
BOOL bWeakCompare;
|
|
|
|
//
|
|
// There are currently 5 possible If-* modifiers: If-Match,
|
|
// If-Unmodified-Since, If-Non-Match, If-Modified-Since, and
|
|
// If-Range. We handle them in that order if all are present,
|
|
// and as soon as one condition fails we stop processing and return
|
|
// the appropriate header.
|
|
//
|
|
// If-Range is an exception. It only modifies the behavior of an incoming
|
|
// Range: request, and it only applies if the response would otherwise
|
|
// be 2xx. If it succeeds nothing is done, but if it fails we (essentially)
|
|
// delete the Range header and force sending of the whole header.
|
|
//
|
|
|
|
|
|
//
|
|
// First see if we can use the weak comparison function. We can use
|
|
// the weak comparison function iff this is for a conditional GET with
|
|
// no range request. Note that If-Match always requires the strong
|
|
// comparison function.
|
|
|
|
bWeakCompare = _strRange.IsEmpty();
|
|
|
|
// Now handle the If-Match header, if we have one.
|
|
|
|
pszETagList = (LPSTR ) _HeaderList.FastMapQueryValue(HM_IFM);
|
|
|
|
if (pszETagList != NULL)
|
|
{
|
|
// Have an If-Match header. If we can match the ETag we have in
|
|
// the list we'll continue, otherwise send back
|
|
// 412 Precondition Failed. If-Match requires the strong
|
|
// comparison function, so it fails if the local ETag is weak or
|
|
// the incoming ETags are weak.
|
|
|
|
if ( pFile->WeakETag() ||
|
|
!FindInETagList(pFile->QueryETag(), pszETagList, FALSE))
|
|
{
|
|
*bReturn = SendPreconditionResponse(HT_PRECOND_FAILED,
|
|
IO_FLAG_ASYNC,
|
|
pfFinished);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
// Made it through that, handle If-Unmodified-Since if we have that.
|
|
|
|
if ( _liUnmodifiedSince.QuadPart)
|
|
{
|
|
FILETIME tm;
|
|
TCP_REQUIRE( pFile->QueryLastWriteTime( &tm ));
|
|
|
|
// If our last write time is greater than their UnmodifiedSince
|
|
// time, the precondition fails.
|
|
if (*(LONGLONG*)&tm > _liUnmodifiedSince.QuadPart )
|
|
{
|
|
*bReturn = SendPreconditionResponse(HT_PRECOND_FAILED,
|
|
IO_FLAG_ASYNC,
|
|
pfFinished);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now see if we have an If-None-Match, and if so handle that.
|
|
//
|
|
|
|
pszETagList = (LPSTR ) _HeaderList.FastMapQueryValue(HM_INM);
|
|
|
|
if (pszETagList != NULL)
|
|
{
|
|
// Have an If-None-Match header. We send 304 if the If-None-Match
|
|
// condition is false. The If-None-Match condition is false if
|
|
// it's not true that we're doing a strong compare against a weak
|
|
// local ETag and we can find a match in the ETag list.
|
|
|
|
if (!(!bWeakCompare && pFile->WeakETag()) &&
|
|
FindInETagList(pFile->QueryETag(), pszETagList,
|
|
bWeakCompare))
|
|
{
|
|
*bReturn = SendPreconditionResponse(HT_NOT_MODIFIED,
|
|
IO_FLAG_ASYNC,
|
|
pfFinished);
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
// And check for last modified since.
|
|
if ( _liModifiedSince.QuadPart)
|
|
{
|
|
FILETIME tm;
|
|
TCP_REQUIRE( pFile->QueryLastWriteTime( &tm ));
|
|
|
|
if ( *(LONGLONG*)&tm <= _liModifiedSince.QuadPart )
|
|
{
|
|
// Need to check and see if the Modified-Since time is greater than
|
|
// our current time. If it is, we ignore it.
|
|
|
|
::GetSystemTimeAsFileTime(&tm);
|
|
|
|
if (*(LONGLONG *)&tm >= _liModifiedSince.QuadPart)
|
|
{
|
|
LARGE_INTEGER liFileSize;
|
|
TCP_REQUIRE( pFile->QuerySize( liFileSize ));
|
|
|
|
if (_dwModifiedSinceLength == 0 ||
|
|
(liFileSize.HighPart == 0 &&
|
|
(liFileSize.LowPart == _dwModifiedSinceLength)))
|
|
{
|
|
*bReturn = SendPreconditionResponse(HT_NOT_MODIFIED,
|
|
IO_FLAG_ASYNC,
|
|
pfFinished);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Finally, we can handle If-Range: if it exists. If we've gotten this
|
|
// far presumably we're going to send a 2xx response. We'll ignore the
|
|
// If-Range if this is not a range request. If it is a range request and
|
|
// the If-Range matches we don't do anything. If the If-Range doesn't
|
|
// match then we force retrieval of the whole file.
|
|
|
|
pszETagList = (LPSTR ) _HeaderList.FastMapQueryValue(HM_IFR);
|
|
|
|
if (pszETagList != NULL)
|
|
{
|
|
if (!_strRange.IsEmpty())
|
|
{
|
|
|
|
// Need to determine if what we have is a date or an ETag.
|
|
|
|
// What we have can be either an ETag or a date string. An ETag
|
|
// may start with a W/ or a quote. A date may start with a W
|
|
// but will never have the second character be a /.
|
|
|
|
if ( *pszETagList == '"' ||
|
|
(*pszETagList == 'W' && pszETagList[1] == '/'))
|
|
{
|
|
// This is an ETag.
|
|
if (pFile->WeakETag() ||
|
|
!FindInETagList(pFile->QueryETag(), pszETagList,
|
|
FALSE))
|
|
{
|
|
// The If-Range failed, so we can't send a range. Force
|
|
// sending the whole thing.
|
|
_strRange.SetLen(0);
|
|
}
|
|
} else
|
|
{
|
|
LARGE_INTEGER liRangeTime;
|
|
|
|
// This must be a date. Convert it to a time, and see if it's
|
|
// less than or equal to our last write time. If it is, the
|
|
// file's changed, and we can't perform the range.
|
|
|
|
if ( !StringTimeToFileTime( pszETagList, &liRangeTime))
|
|
{
|
|
// Couldn't convert it, so don't send the range.
|
|
_strRange.SetLen(0);
|
|
|
|
} else
|
|
{
|
|
FILETIME tm;
|
|
TCP_REQUIRE( pFile->QueryLastWriteTime( &tm ));
|
|
|
|
if (*(LONGLONG*)&tm > liRangeTime.QuadPart )
|
|
{
|
|
_strRange.SetLen(0);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::CheckPreconditions(
|
|
HANDLE hFile,
|
|
BOOL bExisted,
|
|
BOOL *pfFinished,
|
|
BOOL *bReturn
|
|
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle the If Modifiers on a request, and do the right thing
|
|
with them. This routine is very similar to the other CheckPreconditions
|
|
routine. The difference is that the other one is intended to be called
|
|
by someone doing a GET or HEAD, and this one is intended for other
|
|
methods. This version of the routine takes as input a file handle
|
|
instead of a pointer to a TS_OPEN_FILE_INFO class. Also, this version
|
|
always uses the strong comparison function for ETags, doesn't check
|
|
If-Modified-Since or If-Range headers, and sends 412 instead of 304
|
|
for If-None-Match failures. We don't handle the If-Match and If-Range
|
|
headers because the spec. calls them out as being for use with a GET
|
|
and this version of the routine isn't called for GETs.
|
|
|
|
|
|
Arguments:
|
|
|
|
hFile - Handle for file we're checking.
|
|
|
|
bExists - TRUE if the file we're checking existed before this call.
|
|
|
|
pfFinished - pass through BOOLEAN.
|
|
|
|
bReturn - Where to store the response value for this routine.
|
|
|
|
Returns:
|
|
|
|
TRUE if we sent a response to the request, FALSE if we didn't.
|
|
|
|
--*/
|
|
{
|
|
LPSTR pszETagList;
|
|
CHAR ETag[MAX_ETAG_BUFFER_LENGTH];
|
|
BOOL bETagIsValid = FALSE;
|
|
BOOL bWeakETag;
|
|
FILETIME tm;
|
|
|
|
//
|
|
// There are currently 3 possible If-* modifiers we handle here: If-Match,
|
|
// If-Unmodified-Since, and If-Non-Match. We handle them in that order if
|
|
// all are present, and as soon as one condition fails we stop processing
|
|
// and return the appropriate header.
|
|
//
|
|
// Now handle the If-Match header, if we have one.
|
|
|
|
pszETagList = (LPSTR ) _HeaderList.FastMapQueryValue(HM_IFM);
|
|
|
|
if (pszETagList != NULL)
|
|
{
|
|
// Have an If-Match header. If we can match the ETag we have in
|
|
// the list we'll continue, otherwise send back
|
|
// 412 Precondition Failed. If-Match requires the strong comparison
|
|
// function. The first thing we have to do is call the tsunami cache to
|
|
// create an ETag for us. If we can't do that, we assume the
|
|
// precondition failed, and return the appropriate error.
|
|
|
|
// Check for the special case of '*'.
|
|
if (*pszETagList == '*')
|
|
{
|
|
if (!bExisted)
|
|
{
|
|
*bReturn = SendPreconditionResponse(HT_PRECOND_FAILED,
|
|
IO_FLAG_SYNC,
|
|
pfFinished);
|
|
return TRUE;
|
|
}
|
|
} else
|
|
{
|
|
bETagIsValid = TsCreateETagFromHandle(hFile, ETag, &bWeakETag);
|
|
if (!bETagIsValid ||
|
|
bWeakETag ||
|
|
!FindInETagList(ETag, pszETagList, FALSE))
|
|
{
|
|
*bReturn = SendPreconditionResponse(HT_PRECOND_FAILED,
|
|
IO_FLAG_SYNC,
|
|
pfFinished);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// Made it through that, handle If-Unmodified-Since if we have that.
|
|
|
|
if ( _liUnmodifiedSince.QuadPart)
|
|
{
|
|
|
|
// If our last write time is greater than their UnmodifiedSince
|
|
// time, the precondition fails. We'll need to call tsunami to
|
|
// get the file time, if that fails we can't do the compare, so
|
|
// we err on the safe side and return the error.
|
|
|
|
|
|
if (!TsLastWriteTimeFromHandle(hFile, &tm) ||
|
|
*(LONGLONG*)&tm > _liUnmodifiedSince.QuadPart )
|
|
{
|
|
*bReturn = SendPreconditionResponse(HT_PRECOND_FAILED,
|
|
IO_FLAG_SYNC,
|
|
pfFinished);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now see if we have an If-None-Match, and if so handle that.
|
|
//
|
|
|
|
pszETagList = (LPSTR ) _HeaderList.FastMapQueryValue(HM_INM);
|
|
|
|
if (pszETagList != NULL)
|
|
{
|
|
// Have an If-None-Match header. We send 412 if the If-None-Match
|
|
// condition is false. The If-None-Match condition is false if
|
|
// it's not true that we're doing a strong compare against a weak
|
|
// local ETag and we can find a match in the ETag list. We may
|
|
// still need to create an ETag. In this case we need the
|
|
// ETag to be valid, if we can't create one we won't go with
|
|
// the 412 response.
|
|
|
|
// Check for the special case of '*'.
|
|
if (*pszETagList == '*')
|
|
{
|
|
if (bExisted)
|
|
{
|
|
*bReturn = SendPreconditionResponse(HT_PRECOND_FAILED,
|
|
IO_FLAG_SYNC,
|
|
pfFinished);
|
|
return TRUE;
|
|
}
|
|
} else
|
|
{
|
|
if (!bETagIsValid)
|
|
{
|
|
bETagIsValid = TsCreateETagFromHandle(hFile, ETag, &bWeakETag);
|
|
}
|
|
|
|
if (bETagIsValid && !bWeakETag &&
|
|
FindInETagList(ETag, pszETagList, FALSE))
|
|
{
|
|
*bReturn = SendPreconditionResponse(HT_PRECOND_FAILED,
|
|
IO_FLAG_SYNC,
|
|
pfFinished);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::DLCMungeSimple(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Handle the URL before (optionally) looking up instance. This means
|
|
removing any "/HostMenu" and embeeded hosts.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
TRUE if successful, else FALSE
|
|
|
|
--*/
|
|
{
|
|
const CHAR * pchURL = _HeaderList.FastMapQueryValue( HM_URL );
|
|
|
|
if ( !_strnicmp( pchURL,
|
|
g_pszDLCMenu,
|
|
g_cbDLCMenu ) )
|
|
{
|
|
if ( !_strHostAddr.Copy( g_pszDLCHostName, g_cbDLCHostName ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( *( pchURL + g_cbDLCMenu ) == '\0' ||
|
|
*( pchURL + g_cbDLCMenu ) == '?' )
|
|
{
|
|
STACK_STR( strHostCookie, MAX_PATH );
|
|
STACK_STR( strMenuURL, MAX_PATH );
|
|
|
|
const CHAR * pchCookie = _HeaderList.FastMapQueryValue( HM_COK );
|
|
|
|
if ( pchCookie != NULL && DLCGetCookie( (CHAR*) pchCookie,
|
|
g_pszDLCCookieName,
|
|
g_cbDLCCookieName,
|
|
&strHostCookie ) )
|
|
{
|
|
if ( !strMenuURL.Copy( g_pszDLCCookieMenuDocument,
|
|
g_cbDLCCookieMenuDocument ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !strMenuURL.Copy( g_pszDLCMungeMenuDocument,
|
|
g_cbDLCMungeMenuDocument ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if ( !strMenuURL.Append( pchURL + g_cbDLCMenu ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
_HeaderList.FastMapStore( HM_URL,
|
|
NULL );
|
|
|
|
return _HeaderList.FastMapStoreWithConcat( HM_URL,
|
|
strMenuURL.QueryStr(),
|
|
strMenuURL.QueryCB() );
|
|
}
|
|
else
|
|
{
|
|
_HeaderList.FastMapStore( HM_URL,
|
|
pchURL + g_cbDLCMenu );
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if ( *pchURL == '/' )
|
|
{
|
|
pchURL++;
|
|
}
|
|
|
|
if ( *pchURL == '*' )
|
|
{
|
|
CHAR * pchNextSlash = strchr( pchURL, '/' );
|
|
|
|
if ( pchNextSlash != NULL )
|
|
{
|
|
if ( !_strDLCString.Copy( pchURL + 1,
|
|
DIFF(pchNextSlash - pchURL) - 1 ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
_HeaderList.FastMapStore( HM_URL,
|
|
pchNextSlash );
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::DLCHandleRequest(
|
|
IN OUT BOOL * pfFinished
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check for whether a down level client has sent a cookie for use as a
|
|
host header. If so, set the host header. If no cookie is sent, redirect
|
|
the client to the register configured host menu.
|
|
|
|
Arguments:
|
|
|
|
pfFinished - Set to TRUE if request finished.
|
|
|
|
Returns:
|
|
|
|
TRUE if successful, else FALSE
|
|
|
|
--*/
|
|
{
|
|
const CHAR * pch = NULL;
|
|
const CHAR * pchURL = NULL;
|
|
STACK_STR( strHostCookie, MAX_PATH );
|
|
|
|
//
|
|
// Check for a pseudoheader cookie
|
|
//
|
|
|
|
pch = _HeaderList.FastMapQueryValue( HM_COK );
|
|
pchURL = _HeaderList.FastMapQueryValue( HM_URL );
|
|
|
|
if ( pch != NULL && DLCGetCookie( (CHAR*) _HeaderList.FastMapQueryValue( HM_COK ),
|
|
g_pszDLCCookieName,
|
|
g_cbDLCCookieName,
|
|
&strHostCookie ) )
|
|
{
|
|
return _strHostAddr.Copy( strHostCookie );
|
|
|
|
}
|
|
else if ( !_strDLCString.IsEmpty() )
|
|
{
|
|
return _strHostAddr.Copy( _strDLCString );
|
|
}
|
|
else
|
|
{
|
|
STACK_STR( strRedirect, MAX_PATH );
|
|
|
|
//
|
|
// No cookie, no munged string. Do a redirect!
|
|
//
|
|
|
|
//
|
|
// Send a cookie with the redirect so we can tell how capable the
|
|
// user's browser really is.
|
|
//
|
|
|
|
if ( !strRedirect.Copy( "Set-Cookie: path=/; domain=" ) ||
|
|
!strRedirect.Append( g_pszDLCHostName ) ||
|
|
!strRedirect.Append( "; " ) ||
|
|
!strRedirect.Append( g_pszDLCCookieName ) ||
|
|
!strRedirect.Append( "=" ) ||
|
|
!strRedirect.Append( g_pszDLCHostName ) ||
|
|
!strRedirect.Append( "\r\n" ) ||
|
|
!strRedirect.Append( "Location: http://" ) ||
|
|
!strRedirect.Append( g_pszDLCHostName ) ||
|
|
!strRedirect.Append( g_pszDLCMenu ) ||
|
|
!strRedirect.Append( "?" ) ||
|
|
!strRedirect.Append( _HeaderList.FastMapQueryValue( HM_URL ) ) ||
|
|
!strRedirect.Append( "\r\n\r\n" ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !SendHeader( "302 Temporary Redirect",
|
|
strRedirect.QueryStr(),
|
|
IO_FLAG_SYNC,
|
|
pfFinished ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*pfFinished = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::DLCGetCookie(
|
|
IN CHAR * pszCookieString,
|
|
IN CHAR * pszCookieName,
|
|
IN DWORD cbCookieName,
|
|
OUT STR * pstrCookieValue
|
|
)
|
|
{
|
|
CHAR * pchPos = NULL;
|
|
|
|
DBG_ASSERT( pszCookieString != NULL );
|
|
DBG_ASSERT( pszCookieName != NULL );
|
|
DBG_ASSERT( pstrCookieValue != NULL );
|
|
|
|
pchPos = strstr( pszCookieString, pszCookieName );
|
|
|
|
if ( pchPos != NULL )
|
|
{
|
|
CHAR * pchEnd = NULL;
|
|
BOOL fCopyOK;
|
|
|
|
pchPos += cbCookieName + 1;
|
|
|
|
// Handle case where multiple cookies are sent
|
|
|
|
pchEnd = strchr( pchPos, ';' );
|
|
if ( pchEnd == NULL )
|
|
{
|
|
fCopyOK = pstrCookieValue->Copy( pchPos );
|
|
}
|
|
else
|
|
{
|
|
fCopyOK = pstrCookieValue->Copy( pchPos,
|
|
DIFF(pchEnd - pchPos) );
|
|
}
|
|
|
|
//
|
|
// Unescape the value if we got it
|
|
//
|
|
if ( fCopyOK ) {
|
|
return pstrCookieValue->Unescape();
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::RequestAbortiveClose(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Request for abortive close on disconnect
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
TRUE if successful, else FALSE
|
|
|
|
--*/
|
|
{
|
|
SetKeepConn( FALSE );
|
|
|
|
return QueryClientConn()->RequestAbortiveClose();
|
|
}
|
|
|
|
BOOL
|
|
HTTP_REQUEST::CloseConnection(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Closes the socket connection
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
TRUE if successful, else FALSE
|
|
|
|
--*/
|
|
{
|
|
CLIENT_CONN *pConn;
|
|
BOOL fSuccess = FALSE;
|
|
|
|
pConn = QueryClientConn();
|
|
|
|
DBG_ASSERT( pConn );
|
|
|
|
//
|
|
// If client is not already disconnected itself,
|
|
// clear _fKeepConn flag and close connection
|
|
//
|
|
|
|
if( pConn->QueryState() != CCS_DISCONNECTING ) {
|
|
|
|
//
|
|
// RACE ALERT: it is still possible that client disconnects
|
|
// while we are getting down to ATQ.
|
|
|
|
SetKeepConn( FALSE );
|
|
fSuccess = pConn->CloseConnection();
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// at this point we know that client did disconnect itself
|
|
//
|
|
|
|
fSuccess = FALSE;
|
|
}
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
APIERR
|
|
WriteConfiguration( VOID )
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determination certain configuration items of server and write them out
|
|
to the metabase so that admin can access
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
0 if successful, else Win32 Error Code
|
|
|
|
--*/
|
|
{
|
|
CHAR achFilename[ MAX_PATH ];
|
|
CHAR * pchFilePart;
|
|
DWORD dwRet;
|
|
DWORD dwHandle;
|
|
UINT dwLen;
|
|
HKEY hKey = NULL;
|
|
DWORD dwServerConfiguration = 0;
|
|
MB mb( (IMDCOM*) g_pInetSvc->QueryMDObject() );
|
|
|
|
|
|
//
|
|
// With the availability of Server gated crypto, all servers
|
|
// can use 128 bits encryption
|
|
//
|
|
|
|
#if !defined(CHECK_SCHANNEL_FOR_128_BITS)
|
|
|
|
dwServerConfiguration |= MD_SERVER_CONFIG_SSL_128;
|
|
|
|
#else
|
|
|
|
//
|
|
// Determine what type of SCHANNEL.DLL is installed
|
|
//
|
|
|
|
dwRet = SearchPath( NULL,
|
|
"schannel.dll",
|
|
NULL,
|
|
MAX_PATH,
|
|
achFilename,
|
|
&pchFilePart );
|
|
|
|
if ( dwRet != 0 )
|
|
{
|
|
VOID * pBuf;
|
|
VOID * pValue;
|
|
WORD * pVerTransInfo;
|
|
CHAR achBlockName[ MAX_PATH ];
|
|
|
|
dwRet = GetFileVersionInfoSize( achFilename,
|
|
&dwHandle );
|
|
|
|
if ( dwRet == 0 )
|
|
{
|
|
goto Continue;
|
|
}
|
|
|
|
pBuf = LocalAlloc( LPTR,
|
|
dwRet );
|
|
|
|
if ( pBuf == NULL )
|
|
{
|
|
return GetLastError();
|
|
}
|
|
|
|
if ( !GetFileVersionInfo( achFilename,
|
|
dwHandle,
|
|
dwRet,
|
|
pBuf ) )
|
|
{
|
|
LocalFree( pBuf );
|
|
goto Continue;
|
|
}
|
|
|
|
if ( !VerQueryValue( pBuf,
|
|
"\\VarFileInfo\\Translation",
|
|
&pValue,
|
|
&dwLen ) )
|
|
{
|
|
LocalFree( pBuf );
|
|
goto Continue;
|
|
}
|
|
|
|
DBG_ASSERT( dwLen == sizeof( WORD * ) );
|
|
|
|
pVerTransInfo = (WORD*) pValue;
|
|
|
|
wsprintf( achBlockName,
|
|
"\\StringFileInfo\\%04hx%04hx\\FileDescription",
|
|
pVerTransInfo[ 0 ],
|
|
pVerTransInfo[ 1 ] );
|
|
|
|
if ( !VerQueryValue( pBuf,
|
|
achBlockName,
|
|
&pValue,
|
|
&dwLen ) )
|
|
{
|
|
LocalFree( pBuf );
|
|
goto Continue;
|
|
}
|
|
|
|
if ( strstr( (CHAR*) pValue, "Not for Export" ) )
|
|
{
|
|
dwServerConfiguration |= MD_SERVER_CONFIG_SSL_128;
|
|
}
|
|
else
|
|
{
|
|
dwServerConfiguration |= MD_SERVER_CONFIG_SSL_40;
|
|
}
|
|
|
|
LocalFree( pBuf );
|
|
}
|
|
|
|
Continue:
|
|
|
|
#endif
|
|
|
|
//
|
|
// Is encryption disabled due to locality?
|
|
//
|
|
|
|
if ( IsEncryptionPermitted() )
|
|
{
|
|
dwServerConfiguration |= MD_SERVER_CONFIG_ALLOW_ENCRYPT;
|
|
}
|
|
|
|
//
|
|
// Now determine whether the sub-authenticator is installed
|
|
//
|
|
|
|
if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
W3_AUTHENTICATOR_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&hKey ) == NO_ERROR )
|
|
{
|
|
DWORD lRet;
|
|
CHAR * pchNext;
|
|
HKEY hSubKey = NULL;
|
|
DWORD dwType;
|
|
BUFFER buf( MAX_PATH );
|
|
DWORD dwBufLen;
|
|
BOOL fSubAuthenticate = FALSE;
|
|
|
|
Retry:
|
|
dwBufLen = buf.QuerySize();
|
|
|
|
lRet = RegQueryValueEx( hKey,
|
|
"Authentication Packages",
|
|
NULL,
|
|
&dwType,
|
|
(BYTE*) buf.QueryPtr(),
|
|
&dwBufLen );
|
|
|
|
if ( lRet == ERROR_MORE_DATA )
|
|
{
|
|
if ( !buf.Resize( dwBufLen ) )
|
|
{
|
|
RegCloseKey( hKey );
|
|
return GetLastError();
|
|
}
|
|
goto Retry;
|
|
}
|
|
else if ( lRet == ERROR_SUCCESS )
|
|
{
|
|
|
|
DBG_ASSERT( dwType == REG_MULTI_SZ );
|
|
pchNext = (CHAR*) buf.QueryPtr();
|
|
|
|
while ( *pchNext != '\0' )
|
|
{
|
|
if ( RegOpenKeyEx( hKey,
|
|
pchNext,
|
|
0,
|
|
KEY_READ,
|
|
&hSubKey ) == NO_ERROR )
|
|
{
|
|
BYTE achBuffer[ MAX_PATH ];
|
|
DWORD dwBufSize = MAX_PATH;
|
|
DWORD dwType;
|
|
|
|
if ( RegQueryValueEx( hSubKey,
|
|
"Auth132",
|
|
NULL,
|
|
&dwType,
|
|
achBuffer,
|
|
&dwBufSize ) == ERROR_SUCCESS)
|
|
{
|
|
DBG_ASSERT( dwType == REG_SZ );
|
|
|
|
if ( strstr( (CHAR*) achBuffer, "iissuba" ) != NULL )
|
|
{
|
|
fSubAuthenticate = TRUE;
|
|
}
|
|
}
|
|
|
|
RegCloseKey( hSubKey );
|
|
|
|
if ( fSubAuthenticate )
|
|
{
|
|
dwServerConfiguration |= MD_SERVER_CONFIG_AUTO_PW_SYNC;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
pchNext += strlen( pchNext );
|
|
}
|
|
}
|
|
|
|
RegCloseKey( hKey );
|
|
}
|
|
|
|
|
|
if ( !mb.Open( "/LM/W3SVC/",
|
|
METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE ) )
|
|
{
|
|
return GetLastError();
|
|
}
|
|
|
|
if ( !mb.SetDword( IIS_MD_SVC_INFO_PATH,
|
|
MD_SERVER_CONFIGURATION_INFO,
|
|
IIS_MD_UT_SERVER,
|
|
dwServerConfiguration,
|
|
0 ) )
|
|
{
|
|
DBG_REQUIRE( mb.Close() );
|
|
return GetLastError();
|
|
}
|
|
|
|
DBG_REQUIRE( mb.Close() );
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
VOID
|
|
HTTP_REQUEST::CloseGetFile(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Closes our tsunami file handle structure if we have one.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
Nothing
|
|
|
|
--*/
|
|
{
|
|
TS_OPEN_FILE_INFO * pGetFile;
|
|
|
|
//
|
|
// This function is sometimes called after an async I/O
|
|
// has been initiated, so to prevent a race we the
|
|
// completion routine we have to do this interlocked
|
|
// operation.
|
|
//
|
|
pGetFile = (TS_OPEN_FILE_INFO *) InterlockedExchangePointer(
|
|
(PVOID *)& _pGetFile,
|
|
NULL
|
|
);
|
|
if ( pGetFile ) {
|
|
DBG_REQUIRE( TsCloseURIFile(pGetFile) );
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EXEC_DESCRIPTOR::ReleaseCacheInfo(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees just the items we have checked out in the cache
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
Nothing
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// Always release AppPathURIBlob info.
|
|
//
|
|
if ( _pAppPathURIBlob != NULL)
|
|
{
|
|
if (_pAppPathURIBlob->bIsCached)
|
|
{
|
|
TsCheckInCachedBlob( _pAppPathURIBlob );
|
|
}
|
|
else
|
|
{
|
|
TsFree(_pRequest->QueryW3Instance()->GetTsvcCache(), _pAppPathURIBlob );
|
|
}
|
|
_pAppPathURIBlob = NULL;
|
|
}
|
|
|
|
if ( _pPathInfoURIBlob != NULL)
|
|
{
|
|
|
|
if (_pPathInfoURIBlob->bIsCached)
|
|
{
|
|
TsCheckInCachedBlob( _pPathInfoURIBlob );
|
|
} else
|
|
{
|
|
TsFree(_pRequest->QueryW3Instance()->GetTsvcCache(), _pPathInfoURIBlob );
|
|
}
|
|
_pPathInfoMetaData = NULL;
|
|
_pPathInfoURIBlob = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (_pPathInfoMetaData != NULL)
|
|
{
|
|
TsFreeMetaData(_pPathInfoMetaData->QueryCacheInfo() );
|
|
_pPathInfoMetaData = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EXEC_DESCRIPTOR::Reset(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reset structure after usage
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
Nothing
|
|
|
|
--*/
|
|
{
|
|
ReleaseCacheInfo();
|
|
|
|
_pstrURL = NULL;
|
|
_pstrPhysicalPath = NULL;
|
|
_pstrUnmappedPhysicalPath = NULL;
|
|
_pstrGatewayImage = NULL;
|
|
_pstrPathInfo = NULL;
|
|
_pstrURLParams = NULL;
|
|
_pdwScriptMapFlags = NULL;
|
|
_pGatewayType = NULL;
|
|
_pRequest = NULL;
|
|
_pParentWamRequest = NULL;
|
|
_pMetaData = NULL;
|
|
|
|
//
|
|
// if this is a child request, preserve its childishness
|
|
// (which is specified in flags member), since we need
|
|
// to set child event after this reset has occurred.
|
|
//
|
|
|
|
if ( !IsChild() ) {
|
|
|
|
_dwExecFlags = 0;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
HANDLE
|
|
EXEC_DESCRIPTOR::QueryImpersonationHandle(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
returns impersonation access token for this request
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
HANDLE value
|
|
|
|
--*/
|
|
{
|
|
if ( _pPathInfoMetaData &&
|
|
_pPathInfoMetaData->QueryVrAccessToken() &&
|
|
( !_pPathInfoMetaData->QueryVrPassThrough()
|
|
|| _pRequest->QueryAuthenticationObj()->IsForwardable()) )
|
|
{
|
|
return _pPathInfoMetaData->QueryVrAccessToken();
|
|
}
|
|
else
|
|
{
|
|
return _pRequest->QueryImpersonationHandle();
|
|
}
|
|
}
|
|
|
|
HANDLE
|
|
EXEC_DESCRIPTOR::QueryPrimaryHandle(
|
|
HANDLE* phDel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
returns primary access token for this request
|
|
|
|
Arguments:
|
|
|
|
phDel - updated with token to delete when usage of returned token is complete.
|
|
can be NULL if no token to delete.
|
|
|
|
Returns:
|
|
|
|
HANDLE value
|
|
|
|
--*/
|
|
{
|
|
HANDLE hDel;
|
|
HANDLE hRet;
|
|
|
|
if ( _pPathInfoMetaData &&
|
|
_pPathInfoMetaData->QueryVrAccessToken() &&
|
|
( !_pPathInfoMetaData->QueryVrPassThrough()
|
|
|| _pRequest->QueryAuthenticationObj()->IsForwardable()) )
|
|
{
|
|
hRet = _pPathInfoMetaData->QueryVrPrimaryAccessToken();
|
|
*phDel = NULL;
|
|
}
|
|
else
|
|
{
|
|
hRet = _pRequest->QueryPrimaryToken( phDel );
|
|
}
|
|
|
|
return hRet;
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
EXEC_DESCRIPTOR::CreateChildEvent(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Creates the child event, which will be used to signal
|
|
that a child request has completed.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
BOOL
|
|
|
|
--*/
|
|
{
|
|
|
|
DBG_ASSERT( IsChild() );
|
|
DBG_ASSERT( _hChildEvent == NULL );
|
|
|
|
_hChildEvent =
|
|
CreateEvent(
|
|
NULL, // LPSECURITY_ATTRIBUTES ptr to security attributes
|
|
TRUE, // BOOL bManualReset - flag for manual-reset event
|
|
FALSE,// BOOL bInitialState - flag for initial state
|
|
NULL // LPCTSTR lpName - ptr to event-object name
|
|
);
|
|
|
|
|
|
// Only wait for this event if marked so (after WamRequest.Bind())
|
|
|
|
_fMustWaitForChildEvent = FALSE;
|
|
|
|
|
|
return ( _hChildEvent != NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
EXEC_DESCRIPTOR::WaitForChildEvent(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Waits for a child request to complete.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
Nothing
|
|
|
|
--*/
|
|
{
|
|
|
|
DBG_ASSERT( IsChild() );
|
|
DBG_ASSERT( _hChildEvent != NULL );
|
|
|
|
|
|
if ( _hChildEvent ) {
|
|
|
|
if ( _fMustWaitForChildEvent ) {
|
|
|
|
WaitForSingleObject( _hChildEvent, INFINITE );
|
|
}
|
|
|
|
//
|
|
// After we emerge from wait, close and null the event.
|
|
//
|
|
// Because we force a child request to execute
|
|
// synchronously to completion, we have no
|
|
// further need for the event once it becomes
|
|
// signaled.
|
|
//
|
|
|
|
CloseHandle( _hChildEvent );
|
|
_hChildEvent = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
EXEC_DESCRIPTOR::SetChildEvent(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the child event, which signals that a child request
|
|
has completed.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
Nothing
|
|
|
|
--*/
|
|
{
|
|
|
|
DBG_ASSERT( IsChild() );
|
|
DBG_ASSERT( _hChildEvent != NULL );
|
|
|
|
if ( _hChildEvent ) {
|
|
|
|
DBG_ASSERT( _fMustWaitForChildEvent ); // someone better be waiting
|
|
|
|
SetEvent( _hChildEvent );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
IncrErrorCount(
|
|
IMDCOM* pCom,
|
|
DWORD dwProp,
|
|
LPCSTR pszPath,
|
|
LPBOOL pbOverTheLimit
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Increment DWORD counter in metabase
|
|
|
|
Arguments:
|
|
|
|
pCom - ptr to metabase I/F
|
|
dwProp - property ID to increment, must be MD_UT_SERVER
|
|
pszPath - path in metabase,
|
|
pbOverTheLimit - updated with TRUE if counter over limit, otherwise FALSE
|
|
|
|
Returns:
|
|
|
|
Nothing
|
|
|
|
--*/
|
|
{
|
|
MB mb( pCom );
|
|
DWORD dwCnt;
|
|
|
|
*pbOverTheLimit = FALSE;
|
|
|
|
if ( mb.Open( pszPath, METADATA_PERMISSION_READ|METADATA_PERMISSION_WRITE ) )
|
|
{
|
|
if ( !mb.GetDword(
|
|
"",
|
|
dwProp,
|
|
IIS_MD_UT_SERVER,
|
|
&dwCnt
|
|
) )
|
|
{
|
|
dwCnt = 0;
|
|
}
|
|
|
|
mb.SetDword(
|
|
"",
|
|
dwProp,
|
|
IIS_MD_UT_SERVER,
|
|
dwCnt + 1
|
|
);
|
|
|
|
mb.Close();
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
HTTP_REQUEST::CheckValidAuth(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check that current authentication method valid for the current URL
|
|
|
|
Arguments:
|
|
None
|
|
|
|
--*/
|
|
{
|
|
BOOL fEnab;
|
|
|
|
//
|
|
// Check valid auth type for this URI
|
|
//
|
|
|
|
if ( IsLoggedOn() )
|
|
{
|
|
if ( _fAnonymous )
|
|
{
|
|
fEnab = MD_AUTH_ANONYMOUS & QueryAuthentication();
|
|
}
|
|
else if ( _fClearTextPass )
|
|
{
|
|
fEnab = MD_AUTH_BASIC & QueryAuthentication();
|
|
}
|
|
else if ( _fAuthTypeDigest )
|
|
{
|
|
fEnab = MD_AUTH_MD5 & QueryAuthentication();
|
|
}
|
|
else if ( _fAuthSystem )
|
|
{
|
|
fEnab = TRUE;
|
|
}
|
|
else if ( _fAuthCert )
|
|
{
|
|
fEnab = (((HTTP_REQUEST*)this)->GetFilePerms()) & MD_ACCESS_MAP_CERT;
|
|
}
|
|
else if ( _fMappedAcct )
|
|
{
|
|
fEnab = MD_AUTH_MAPBASIC & QueryAuthentication();
|
|
}
|
|
else
|
|
{
|
|
if ( fEnab = MD_AUTH_NT & QueryAuthentication() )
|
|
{
|
|
// check SSPI package is valid
|
|
|
|
fEnab = FALSE;
|
|
|
|
const LPSTR* apszNTProviders = _pMetaData->QueryNTProviders();
|
|
DWORD i;
|
|
|
|
for ( i = 0 ; apszNTProviders[i] ; ++i )
|
|
{
|
|
if ( !strcmp( _strAuthType.QueryStr(), apszNTProviders[i] ) )
|
|
{
|
|
fEnab = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( !fEnab )
|
|
{
|
|
if ( _fAnonymous )
|
|
{
|
|
QueryW3StatsObj()->DecrCurrentAnonymousUsers();
|
|
}
|
|
else
|
|
{
|
|
QueryW3StatsObj()->DecrCurrentNonAnonymousUsers();
|
|
}
|
|
|
|
ResetAuth( FALSE );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
returns ApplicationPath for this EXEC. For those ISAPI.dll that
|
|
are only runnable in process, _pAppPathURIBlob->bInProcOnly will be true.
|
|
Therefore, the default application path is returned.
|
|
Example: .STM file.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
a pointer to STR that contains application path.
|
|
|
|
--*/
|
|
STR * EXEC_DESCRIPTOR::QueryAppPath
|
|
(
|
|
void
|
|
)
|
|
{
|
|
STR * pstr = NULL;
|
|
|
|
DBG_REQUIRE(_pAppPathURIBlob != NULL);
|
|
DBG_REQUIRE(_pMetaData != NULL);
|
|
|
|
//
|
|
// InProcOnly ISAPI gets the default application path.
|
|
// Otherwise, use the application path come from MetaData.
|
|
//
|
|
if (_pAppPathURIBlob->bInProcOnly)
|
|
{
|
|
pstr = g_pWamDictator->QueryDefaultAppPath();
|
|
}
|
|
else
|
|
{
|
|
pstr = _pMetaData->QueryAppPath();
|
|
}
|
|
|
|
DBG_ASSERT(pstr != NULL);
|
|
|
|
return pstr;
|
|
}
|
|
|
|
|