www.pudn.com > httpserve.rar > Reqsock.cpp, change:2001-07-17,size:36209b
// ReqSock.cpp : implementation of the CRequestSocket class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1997-1998 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.
#include "stdafx.h"
#include "HttpSvr.h"
#include "HttpDoc.h"
#include "Http.h"
#include "ReqSock.h"
#include "Request.h"
#include "resource.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_DYNCREATE(CRequestSocket, CAsyncSocket)
#define CRLF "\x0d\x0a"
SECURITY_ATTRIBUTES g_sa = {sizeof(SECURITY_ATTRIBUTES),NULL,TRUE};
CRequestSocket::CRequestSocket( void )
{
}
CRequestSocket::CRequestSocket( CHttpSvrDoc* pDoc )
{
#ifdef IMPL_CGI
m_pThread = NULL;
m_pCancel = NULL;
#endif // IMPL_CGI
m_bKilled = FALSE;
m_nRefs = 1;
m_reqStatus = REQ_REQUEST;
m_buf.SetSize( 1024 );
m_cbOut = 0;
m_hFile = INVALID_HANDLE_VALUE;
m_pRequest = NULL;
m_pDoc = pDoc;
}
CRequestSocket::~CRequestSocket( void )
{
// JIC....
#ifdef IMPL_CGI
if ( m_pCancel )
{
if ( m_pThread )
{
DWORD dwCode;
// signal a cancel if still running....
if ( ::GetExitCodeThread( m_pThread->m_hThread, &dwCode )
&& dwCode == STILL_ACTIVE )
{
// signal a cancel....
m_pCancel->SetEvent();
// wait for the thread to die....
WaitForSingleObject( m_pThread->m_hThread, INFINITE );
}
// kill the object...
delete m_pThread;
}
delete m_pCancel;
}
#endif // IMPL_CGI
if ( m_hFile )
CloseHandle( m_hFile );
if ( m_pRequest )
{
// release our hold on the request object....
m_pRequest->m_bDone = TRUE;
m_pRequest->Release();
}
}
void CRequestSocket::OnReceive(int nErrorCode)
{
if ( m_pRequest == NULL )
{
// new request....
m_pRequest = new CRequest;
m_bKeepOpen = m_bWantKeepOpen = FALSE;
}
if ( m_pRequest )
{
// get the bytes....
int nBytes = Receive( m_buf.GetData(), m_buf.GetSize() );
if ( nBytes != SOCKET_ERROR )
{
int ndx = 0;
switch ( m_reqStatus )
{
case REQ_REQUEST:
case REQ_HEADER:
while( GetLine( m_buf, nBytes, ndx ) == TRUE )
{
if ( !m_strLine.IsEmpty() )
ProcessLine();
else
{
m_reqStatus = REQ_BODY;
break;
}
}
// break if we're not looking for the body....
if ( m_reqStatus != REQ_BODY )
break;
// stop if no body sent....
if ( !BodySent() )
{
m_reqStatus = REQ_DONE;
break;
}
// else fall through....
case REQ_BODY:
AddToBody( nBytes, ndx );
break;
}
if ( m_reqStatus == REQ_DONE )
{
m_buf.SetSize(0);
if ( !StartResponse() )
AsyncSelect( FD_WRITE | FD_CLOSE );
}
}
else
nBytes = GetLastError();
}
else
{
// couldn't allocate request object....
ShutDown( both );
m_bKilled = TRUE;
Release();
}
}
void CRequestSocket::OnSend(int nErrorCode)
{
int nBytes = Send( m_buf.GetData(), m_cbOut );
if ( nBytes == SOCKET_ERROR )
{
if ( GetLastError() != WSAEWOULDBLOCK )
{
ShutDown( both );
m_bKilled = TRUE;
Release();
}
else
AsyncSelect( FD_WRITE | FD_CLOSE );
}
else if ( nBytes < m_cbOut )
{
// still got some left....
m_buf.RemoveAt( 0, nBytes );
m_cbOut -= nBytes;
// adjust the bytes-sent value for the request....
m_pRequest->m_cbSent += nBytes;
// set up for next write....
AsyncSelect( FD_WRITE | FD_CLOSE );
}
else
{
// adjust the bytes-sent value for the request....
m_pRequest->m_cbSent += nBytes;
// if we're not done with the file....
if ( m_hFile != INVALID_HANDLE_VALUE )
{
DWORD dwRead = 0;
// read next chunk....
ReadFile( m_hFile, m_buf.GetData(),
m_buf.GetSize(), &dwRead, NULL );
if ( dwRead > 0 )
m_cbOut = dwRead;
else
{
// no more data to send....
CloseHandle( m_hFile );
m_hFile = INVALID_HANDLE_VALUE;
}
}
// see if we need to keep going....
if ( m_hFile != INVALID_HANDLE_VALUE )
AsyncSelect( FD_WRITE | FD_CLOSE );
else
{
// eh, we're outta here....
ShutDown( both );
m_bKilled = TRUE;
Release();
}
}
}
void CRequestSocket::OnClose(int nErrorCode)
{
m_bKilled = TRUE;
Release();
}
BOOL CRequestSocket::GetLine( const CByteArray& bytes, int nBytes, int& ndx )
{
BOOL bLine = FALSE;
while ( bLine == FALSE && ndx < nBytes )
{
char ch = (char)(bytes.GetAt( ndx ));
switch( ch )
{
case '\r': // ignore
break;
case '\n': // end-of-line
bLine = TRUE;
break;
default: // other....
m_strLine += ch;
break;
}
++ndx;
}
return bLine;
}
void CRequestSocket::ProcessLine( void )
{
int ndx;
switch( m_reqStatus )
{
case REQ_REQUEST:
ndx = m_strLine.Find( ' ' );
if ( ndx != -1 )
{
m_pRequest->m_strMethod = m_strLine.Left( ndx );
m_strLine = m_strLine.Mid( ndx+1 );
m_strLine.TrimLeft();
ndx = m_strLine.Find( ' ' );
if ( ndx == -1 )
{
m_pRequest->m_strURL = m_strLine;
m_pRequest->m_strURL.TrimRight();
m_reqStatus = REQ_SIMPLE;
}
else
{
m_pRequest->m_strURL = m_strLine.Left( ndx );
m_pRequest->m_strVersion = m_strLine.Mid( ndx+1 );
m_pRequest->m_strVersion.TrimLeft();
}
// check for execution arguments....
ndx = m_pRequest->m_strURL.Find( '?' );
if ( ndx != -1 )
{
// yup; save the args....
m_pRequest->m_strArgs = m_pRequest->m_strURL.Mid( ndx+1 );
// strip from file name....
m_pRequest->m_strURL = m_pRequest->m_strURL.Left( ndx );
m_pRequest->m_dwExecute = CRequest::APP_EXECUTE;
}
// change any "%xx"s to the appropriate char....
m_pRequest->m_strURL = Decode( m_pRequest->m_strURL );
}
m_reqStatus = REQ_HEADER;
break;
case REQ_HEADER:
ndx = m_strLine.Find( ':' );
if ( ndx != -1 )
{
CString strName = m_strLine.Left( ndx );
CString strValue = m_strLine.Mid( ndx+1 );
strName.MakeLower();
strValue.TrimLeft();
m_pRequest->m_mapHeaders.SetAt( strName, strValue );
}
break;
};
m_strLine.Empty();
}
BOOL CRequestSocket::BodySent( void )
{
BOOL bSent = FALSE;
CString strValue = m_pRequest->GetHeaderValue( "Content-Length" );
if ( !strValue.IsEmpty() )
{
m_pRequest->m_cbBody = atoi( strValue );
bSent = TRUE;
}
return bSent;
}
void CRequestSocket::AddToBody( int nBytes, int ndx )
{
// save the buffer size....
int nOldSize = m_buf.GetSize();
// get rid of old stuff; append to body data....
m_buf.RemoveAt( 0, ndx );
m_buf.SetSize( nBytes - ndx );
m_pRequest->m_baBody.Append( m_buf );
// restore the buffer size....
m_buf.SetSize( nOldSize );
// see if we're done....
if ( m_pRequest->m_baBody.GetSize() >= m_pRequest->m_cbBody )
{
m_pRequest->m_baBody.SetSize( m_pRequest->m_cbBody );
m_reqStatus = REQ_DONE;
}
}
BOOL CRequestSocket::StartResponse( void )
{
BOOL bWait = FALSE;
CString strFile;
UINT uPort;
// save the host address....
GetPeerName( m_pRequest->m_strHost, uPort );
// switch on the method....
if ( m_pRequest->m_cbBody == 0 &&
m_pRequest->m_strMethod.CompareNoCase( "GET" ) == 0 )
{
FindTarget( strFile );
if( m_pRequest->m_uStatus == 0 )
{
if ( m_pRequest->m_dwExecute )
bWait=StartSvrApp();
else
{
if ( StuffHeading() )
StartTargetStuff();
}
}
}
else if ( m_pRequest->m_cbBody == 0 && m_reqStatus == REQ_DONE &&
m_pRequest->m_strMethod.CompareNoCase( "HEAD" ) == 0 )
{
FindTarget( strFile );
if( m_pRequest->m_uStatus == 0 )
{
if ( m_pRequest->m_dwExecute )
bWait=StartSvrApp();
else
{
StuffHeading();
// we don't send the file for HEAD reqs....
if ( m_hFile != INVALID_HANDLE_VALUE)
{
CloseHandle( m_hFile );
m_hFile = INVALID_HANDLE_VALUE;
}
}
}
}
else if ( m_pRequest->m_cbBody > 0 && m_reqStatus == REQ_DONE &&
m_pRequest->m_strMethod.CompareNoCase( "POST" ) == 0 )
{
// assume an executable....
m_pRequest->m_dwExecute = CRequest::APP_EXECUTE;
FindTarget( strFile );
if ( m_pRequest->m_uStatus == 0 )
{
bWait=StartSvrApp();
}
}
else
StuffError( IDS_STATUS_NOTIMPL );
// notify the active view of the hit....
m_pDoc->DocHit( m_pRequest );
return bWait;
}
BOOL CRequestSocket::FindTarget( CString& strFile )
{
BOOL bFound = FALSE;
strFile = m_pRequest->m_strURL;
// change from URL to local file system path....
if ( URLtoPath( strFile ) )
{
CString strExtra; // extra path info
m_pRequest->m_dwAttr = GetFileAttributes( strFile );
if ( m_pRequest->m_dwAttr != -1 )
bFound = TRUE;
else
{
// rip off the last portion....
strExtra = StripLast( strFile );
while( !strFile.IsEmpty() )
{
// anything there?
m_pRequest->m_dwAttr = GetFileAttributes( strFile );
if ( m_pRequest->m_dwAttr != -1 )
{
// found something; better not be a folder....
if( (m_pRequest->m_dwAttr&FILE_ATTRIBUTE_DIRECTORY) == 0 )
bFound = TRUE;
break;
}
// rip off the next portion....
strExtra = StripLast( strFile ) + strExtra;
}
}
if ( bFound )
{
// strip any trailing SEPCHAR....
if ( strFile.GetAt( strFile.GetLength()-1) == SEPCHAR )
m_pRequest->m_strFullPath = strFile.Left( strFile.GetLength()-1 );
else
m_pRequest->m_strFullPath = strFile;
// see if we need to set the extra path info....
if ( !strExtra.IsEmpty() )
{
m_pRequest->m_strPathInfo = strExtra;
if ( URLtoPath( strExtra ) )
m_pRequest->m_strPathTranslated = strExtra;
}
// if it's a folder, see if we can redirect to
// on of the default docs or apps....
if ( m_pRequest->m_dwAttr & FILE_ATTRIBUTE_DIRECTORY )
{
// check for existence of a default doc or app....
if ( !CheckDefault( IDS_DEFAULTDOC, FALSE ) )
CheckDefault( IDS_DEFAULTAPP, TRUE );
}
else if ( m_pRequest->m_dwExecute && !IsSvrApp() )
{
StuffError( IDS_STATUS_BADREQUEST );
}
}
else
StuffError( IDS_STATUS_NOTFOUND );
}
else
StuffError( IDS_STATUS_BADREQUEST );
return bFound;
}
BOOL CRequestSocket::URLtoPath( CString& strFile )
{
BOOL bLegal = FALSE;
CString& strRoot = m_pDoc->m_strRoot;
// start with the root, append the abs path....
CString strTemp = strRoot + strFile;
// now canonicalize it....
DWORD dwSize = GetFullPathName( strTemp, MAX_PATH, strFile.GetBuffer(MAX_PATH+1), NULL );
strFile.ReleaseBuffer();
// get the full path okay?
if ( dwSize )
{
int cchRoot = strRoot.GetLength();
int cchFile = strFile.GetLength();
// must be the same or longer than the root....
if ( cchRoot == cchFile )
{
// must be exactly the same....
if ( strRoot.Compare( strFile ) == 0 )
bLegal = TRUE;
}
else if ( cchRoot < cchFile )
{
// must have the root as the base....
if ( strRoot.Compare( strFile.Left(cchRoot) ) == 0
&& strFile.GetAt( cchRoot ) == SEPCHAR )
bLegal = TRUE;
}
}
return bLegal;
}
BOOL CRequestSocket::PathToURL( CString& strFile )
{
int ndx;
// a local reference to the root....
CString& strRoot = m_pDoc->m_strRoot;
// change all SEPCHARs to forward slashes....
while ( (ndx=strFile.Find( SEPCHAR )) != -1 )
strFile = strFile.Left( ndx ) + '/' + strFile.Mid(ndx+1);
// now add the prefix and server, and cut the root....
CString strPort;
UINT uPort = m_pDoc->m_uPort;
if ( uPort != PORT_HTTP )
strPort.Format( ":%d", uPort );
strFile = CString("http://")
+ m_pDoc->m_strServer
+ strPort
+ strFile.Mid(strRoot.GetLength());
return TRUE;
}
int CRequestSocket::StuffString( const CString& strData )
{
int nLen = strData.GetLength()*sizeof(TCHAR);
// make sure there's enough room....
if ( m_cbOut + nLen > m_buf.GetSize() )
{
int nChunks = nLen/1024 + 1;
m_buf.SetSize( m_cbOut + nChunks*1024 );
}
// copy the data....
MoveMemory( m_buf.GetData() + m_cbOut, (LPCSTR)strData, nLen );
m_cbOut += nLen;
// return amount of space left....
return (m_buf.GetSize() - m_cbOut);
}
int CRequestSocket::StuffString( UINT uId )
{
CString str;
str.LoadString( uId );
return StuffString( str );
}
int CRequestSocket::StuffStatus( const CString& strStatus )
{
CString strVer = "HTTP/1.0 ";
StuffString( strVer );
StuffString( strStatus );
StuffString( CRLF );
// stuff the server name....
CString strServer;
if ( strServer.LoadString( IDS_SERVER_NAME ) && !strServer.IsEmpty() )
StuffHeader( "Server", strServer );
// stuff the date....
return StuffHeader( "Date", GetHttpDate() );
}
int CRequestSocket::StuffStatus( UINT uStatus )
{
CString strStatus;
strStatus.LoadString( uStatus );
// save the status for this request....
m_pRequest->m_uStatus = uStatus;
// stuff the HTTP status line....
return StuffStatus( strStatus );
}
int CRequestSocket::StuffError( UINT uMsg )
{
StuffStatus( uMsg );
return StuffString( CRLF );
}
int CRequestSocket::StuffHeader( CString strName, CString strValue )
{
StuffString( strName );
StuffString( ": " );
StuffString( strValue );
return StuffString( CRLF );
}
int CRequestSocket::StuffHeader( CString strName, int nValue )
{
CString strValue;
StuffString( strName );
StuffString( ": " );
strValue.Format( "%d", nValue );
StuffString( strValue );
return StuffString( CRLF );
}
BOOL CRequestSocket::StuffHeading( void )
{
BOOL bContinue = FALSE;
if ( m_pRequest->m_dwAttr & FILE_ATTRIBUTE_HIDDEN )
{
// never show hidden files....
StuffError( IDS_STATUS_FORBIDDEN );
}
else if ( m_pRequest->m_dwAttr & FILE_ATTRIBUTE_DIRECTORY )
{
if ( m_pDoc->m_bAllowListing )
{
// create a directory listing....
StuffStatus( IDS_STATUS_OK );
StuffString( CRLF );
bContinue = TRUE;
}
else
StuffError( IDS_STATUS_FORBIDDEN );
}
#ifdef IMPL_CGI
else if ( m_hFile != INVALID_HANDLE_VALUE )
{
// cgi's output file will be opened already....
CString strStatus, strHeaders;
// loop until we find a blank line....
DWORD dwRead = 0;
CByteArray baFile;
baFile.SetSize( 1024 );
// read next chunk....
BOOL bRead = ReadFile( m_hFile, baFile.GetData(),
baFile.GetSize(), &dwRead, NULL );
while ( dwRead > 0 )
{
int ndx = 0;
while( GetLine( baFile, dwRead, ndx ) == TRUE )
{
BOOL bSave = TRUE;
// stuff any non-empty lines.....
if ( m_strLine.IsEmpty() )
{
// we found our empty line;
// back up to where we left off....
DWORD dwPos = SetFilePointer( m_hFile,
ndx - dwRead,
NULL, FILE_CURRENT );
// and we're off....
bContinue = TRUE;
break;
}
else
{
int nPos = m_strLine.Find( ':' );
if ( nPos != -1 )
{
CString strName = m_strLine.Left( nPos );
strName.TrimLeft();
strName.TrimRight();
CString strVal = m_strLine.Mid( nPos+1 );
strVal.TrimLeft();
strVal.TrimRight();
if ( strName.CompareNoCase("Status") == 0 )
{
strStatus = strVal;
bSave = FALSE;
}
else if ( strName.CompareNoCase("Location") == 0 )
{
strStatus.LoadString( IDS_STATUS_MOVEDTEMP );
}
}
}
// save the header (if we want to)....
if ( bSave )
strHeaders += m_strLine + CRLF;
m_strLine.Empty();
}
// read next chunk if we're not done....
if ( bContinue )
break;
else
ReadFile( m_hFile, baFile.GetData(),
baFile.GetSize(), &dwRead, NULL );
}
if ( strStatus.IsEmpty() )
StuffStatus( IDS_STATUS_OK );
else
StuffStatus( strStatus );
// stuff the headers....
StuffString( strHeaders );
// stuff the blank line....
StuffString( CRLF );
}
#endif // IMPL_CGI
else
{
// open the file....
m_hFile = CreateFile( m_pRequest->m_strFullPath,
GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL );
if ( m_hFile != INVALID_HANDLE_VALUE )
{
if ( m_reqStatus != REQ_SIMPLE )
{
CTime timeIfMod;
CString strIfMod = m_pRequest->GetHeaderValue( "If-Modified-Since" );
if ( strIfMod.GetLength() > 0 &&
FromHttpTime( strIfMod, timeIfMod ) &&
!IfModSince( timeIfMod ) )
{
// eh, it hasn't been modified....
StuffStatus( IDS_STATUS_NOTMODIFIED );
// don't need it anymore....
CloseHandle( m_hFile );
m_hFile = INVALID_HANDLE_VALUE;
}
else
{
// send it off....
StuffStatus( IDS_STATUS_OK );
// any other header info....
StuffFileType();
StuffHeader( "Content-length", GetFileSize( m_hFile, NULL ) );
// get the last modified time....
FILETIME ft;
if ( GetFileTime( m_hFile, NULL, NULL, &ft ) )
{
StuffHeader( "Last-Modified", GetHttpDate( &ft ) );
}
bContinue = TRUE;
}
// blank line....
StuffString( CRLF );
}
else
bContinue = TRUE;
}
else
{
// couldn't open; try again later....
StuffError( IDS_STATUS_SVCUNAVAIL );
}
}
return bContinue;
}
void CRequestSocket::StartTargetStuff( void )
{
if ( m_hFile != INVALID_HANDLE_VALUE)
{
DWORD dwRead = 0;
// read the first chunk....
ReadFile( m_hFile, m_buf.GetData() + m_cbOut,
m_buf.GetSize()-m_cbOut, &dwRead, NULL );
if ( dwRead > 0 )
m_cbOut += dwRead;
else
{
// nothing read.... close the file....
CloseHandle( m_hFile );
m_hFile = INVALID_HANDLE_VALUE;
}
}
else if ( m_pRequest->m_dwAttr & FILE_ATTRIBUTE_DIRECTORY )
StuffListing();
else
StuffString( CRLF );
}
void CRequestSocket::StuffListing( void )
{
BOOL bRoot = FALSE;
BOOL bIcons = m_pDoc->m_bListIcon;
CString strIcon;
CString strLine = CString("http://")
+ m_pDoc->m_strServer
+ m_pRequest->m_strURL;
CString strDir = m_pRequest->m_strURL;
CString strMask = m_pRequest->m_strFullPath;
// make sure URL ends in a slash....
if ( strDir.GetAt( strDir.GetLength()-1 ) != '/' )
strDir += '/';
// is this the server's root folder?
else if ( strDir.Compare( "/" ) == 0 )
bRoot = TRUE;
// create the file search mask....
AddFile( strMask, IDS_DIRMASK );
StuffString( IDS_CONTENTS_PRE );
StuffString( strLine );
StuffString( IDS_CONTENTS_POST );
if ( bRoot )
StuffString( IDS_CONTENTS_DESC );
if ( bIcons )
strIcon.LoadString( IDS_ICON_BLANK );
strLine.Format( IDS_CONTENTS_HEADING, strIcon );
StuffString( strLine );
int nFiles = 0;
WIN32_FIND_DATA fd;
// find the first file that matches the mask....
HANDLE fh = FindFirstFile( strMask, &fd );
if ( fh != INVALID_HANDLE_VALUE )
{
// create a line for the found file....
nFiles += StuffListingFile( &fd, strDir, bIcons );
// loop through all other files....
while ( FindNextFile( fh, &fd ) )
nFiles += StuffListingFile( &fd, strDir, bIcons );
}
if ( nFiles == 0 )
StuffString( IDS_CONTENTS_EMPTY );
StuffString( IDS_CONTENTS_FOOTER );
// only add the parent link if there is one....
if ( !bRoot )
{
if ( bIcons )
strIcon.LoadString( IDS_ICON_PARENT );
strLine.Format( IDS_CONTENTS_PARENT, strIcon );
StuffString( strLine );
}
// add the note and end it....
StuffString( IDS_CONTENTS_NOTE );
StuffString( CRLF );
}
int CRequestSocket::StuffListingFile( WIN32_FIND_DATA* pfd, const CString& strDir, BOOL bIcons )
{
int nFile = 0;
// don't include '.', '..' or hidden files....
if ( lstrcmp( pfd->cFileName, "." ) && lstrcmp( pfd->cFileName, ".." )
&& (pfd->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) == 0 )
{
CString strSize, strIcon = "";
CString strLine, strFile = pfd->cFileName;
CTime timeFile( pfd->ftLastWriteTime );
BOOL bFolder = ((pfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
if ( bIcons && bFolder )
strIcon.LoadString( IDS_ICON_FOLDER );
else if ( bIcons )
strIcon.LoadString( IDS_ICON_FILE );
// create the link string....
CString strLink = strDir + strFile;
// make sure spaces are replaced with '%20'...
int ndx;
while ( (ndx=strLink.Find(' ')) != -1 )
strLink = strLink.Left(ndx) + "%20" + strLink.Mid( ndx+1 );
// format the size string....
if ( bFolder )
strSize = " Folder";
else if ( pfd->nFileSizeHigh > 0 )
strSize = " > 4GB"; // yeah, right.
else if ( pfd->nFileSizeLow < 1024 )
strSize = " < 1K";
else
strSize.Format( "%7dK", pfd->nFileSizeLow/1024 );
strLine.Format( IDS_CONTENTS_FORMAT,
timeFile.Format( IDS_FILETIMEFMT ),
strSize, strLink, strIcon, strFile );
StuffString( strLine );
nFile = 1;
}
return nFile;
}
BOOL CRequestSocket::StartSvrApp( void )
{
#ifdef IMPL_CGI
if ( m_pRequest->m_dwExecute != CRequest::APP_ISAPI )
return CGIStart();
else
{
StuffError( IDS_STATUS_NOTIMPL );
return FALSE;
}
#else // IMPL_CGI
StuffError( IDS_STATUS_NOTIMPL );
return FALSE;
#endif // IMPL_CGI
}
#ifdef IMPL_CGI
BOOL CRequestSocket::CGIStart( void )
{
BOOL bOk = FALSE;
// get the temp path...
CString strTempPath;
GetTempPath( MAX_PATH, strTempPath.GetBuffer(MAX_PATH) );
strTempPath.ReleaseBuffer();
// create a temporary file for the output....
CString strTempName;
GetTempFileName( strTempPath, "CGI", 0, strTempName.GetBuffer(MAX_PATH) );
strTempName.ReleaseBuffer();
m_hFile = CreateFile( strTempName, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, &g_sa,
CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, NULL );
if ( m_hFile != INVALID_HANDLE_VALUE )
{
// create the cancel event....
m_pCancel = new CEvent;
if ( m_pCancel )
{
// make sure the event is reset....
m_pCancel->ResetEvent();
// create the CGI thread suspended....
m_pThread = AfxBeginThread( (AFX_THREADPROC)CGIThread,
(LPVOID)this, THREAD_PRIORITY_NORMAL, 0,
CREATE_SUSPENDED, NULL );
if ( m_pThread )
{
// don't self-destruct (we must delete)....
m_pThread->m_bAutoDelete = FALSE;
// resume...
m_pThread->ResumeThread();
bOk = TRUE;
}
}
}
if ( bOk == FALSE )
{
StuffError( IDS_STATUS_SVRERROR );
if( m_hFile != INVALID_HANDLE_VALUE )
{ // JIC....
CloseHandle( m_hFile );
m_hFile = INVALID_HANDLE_VALUE;
}
}
return bOk;
}
void AddEnvVar( CString& strEnv, CString strName, CString strVal )
{
// add the name=val pair to the env in alphabetical order....
strEnv += strName + '=' + strVal + '\a';
}
UINT CGIThread( LPVOID pvParam )
{
CRequestSocket* pReqSock = (CRequestSocket*)pvParam;
CRequest* pRequest = pReqSock->m_pRequest;
BOOL bOk = FALSE;
DWORD dwErr;
HANDLE hWritePipe, hReadPipe;
// create a pipe we'll use for STDIN....
if ( CreatePipe( &hReadPipe, &hWritePipe, &g_sa, 0 ) )
{
// get the command line together....
CString strCmdLine = pRequest->m_strFullPath
+ ' '
+ Decode( pRequest->m_strArgs, TRUE );
// get the directory....
CString strDir = pRequest->m_strFullPath;
int ndx = strDir.ReverseFind( SEPCHAR );
// assume we found it....
strDir = strDir.Left( ndx+1 );
// create an environment for the CGI process....
DWORD dwCreateFlags = 0;
#ifdef UNICODE
dwCreateFlags = CREATE_UNICODE_ENVIRONMENT;
#endif // UNICODE
CEnvironment cEnv;
CString strValue;
strValue.LoadString( IDS_SERVER_NAME );
cEnv.Add( "SERVER_SOFTWARE", strValue );
cEnv.Add( "SERVER_NAME", pReqSock->m_pDoc->m_strServer );
cEnv.Add( "GATEWAY_INTERFACE", "CGI/1.1" );
cEnv.Add( "SERVER_PROTOCOL", "HTTP/1.0" );
strValue.Format( "%d", pReqSock->m_pDoc->m_uPort );
cEnv.Add( "SERVER_PORT", strValue );
cEnv.Add( "REQUEST_METHOD", pRequest->m_strMethod );
cEnv.Add( "SCRIPT_NAME", pRequest->m_strURL );
cEnv.Add( "QUERY_STRING", pRequest->m_strArgs );
cEnv.Add( "REMOTE_ADDR", pRequest->m_strHost );
if ( pRequest->m_cbBody > 0 )
{
cEnv.Add( "CONTENT_LENGTH", pRequest->GetHeaderValue("Content-Length") );
cEnv.Add( "CONTENT_TYPE", pRequest->GetHeaderValue("Content-Type") );
}
if ( !pRequest->m_strPathInfo.IsEmpty() )
{
cEnv.Add( "PATH_INFO", pRequest->m_strPathInfo );
cEnv.Add( "PATH_TRANSLATED", pRequest->m_strPathTranslated );
}
// all the passed headers prefixed with "HTTP_"....
POSITION pos = pRequest->m_mapHeaders.GetStartPosition();
while ( pos != NULL )
{
// get the name/value pair....
CString strName, strValue;
pRequest->m_mapHeaders.GetNextAssoc( pos, strName, strValue );
HeaderToEnvVar( strName );
// set the environment variable....
cEnv.Add( strName, strValue );
}
// create the process....
LPVOID pEnv = (LPVOID)cEnv.GetBlock();
PROCESS_INFORMATION pi;
STARTUPINFO si = {0};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = hReadPipe;
si.hStdOutput = pReqSock->m_hFile;
si.hStdError = pReqSock->m_hFile;
bOk = CreateProcess( NULL, strCmdLine.GetBuffer(1),
NULL, NULL, TRUE,
dwCreateFlags, pEnv,
strDir, &si, &pi );
strCmdLine.ReleaseBuffer();
// if created....
if ( bOk )
{
// release our hold on the thread....
CloseHandle( pi.hThread );
// send the body of the post to the stdin....
if ( pRequest->m_cbBody > 0 )
{
DWORD dwWritten = 0;
WriteFile( hWritePipe, pRequest->m_baBody.GetData(),
pRequest->m_cbBody, &dwWritten, NULL );
}
// wait for either cancel or process done....
HANDLE aHandles[2];
aHandles[0] = pi.hProcess;
aHandles[1] = pReqSock->m_pCancel->m_hObject;
if ( WaitForMultipleObjects( 2, aHandles, FALSE, INFINITE ) == WAIT_OBJECT_0 )
{
// process finished; notify main thread....
AfxGetApp()->m_pMainWnd->PostMessage( WSM_CGIDONE, 0, (LPARAM)pReqSock );
}
else
{
// canceled or some other error....
bOk = FALSE;
}
// close our hold on it....
CloseHandle( pi.hProcess );
}
else
dwErr = GetLastError();
// close the stdin pipe....
CloseHandle( hWritePipe );
CloseHandle( hReadPipe );
delete pEnv;
}
if ( bOk == FALSE && pReqSock->m_hFile != INVALID_HANDLE_VALUE )
{ // JIC....
CloseHandle( pReqSock->m_hFile );
pReqSock->m_hFile = INVALID_HANDLE_VALUE;
}
return (bOk?0:1);
}
void CRequestSocket::CGIDone( void )
{
if ( !m_bKilled )
{
// flush the temp file's buffers....
BOOL bSucceed = FlushFileBuffers( m_hFile );
// go to start of file....
DWORD dwPos = SetFilePointer( m_hFile, 0, NULL, FILE_BEGIN );
// output the header....
StuffHeading();
if ( m_pRequest->m_strMethod.Compare( "HEAD" ) )
StartTargetStuff();
else
{
CloseHandle( m_hFile );
m_hFile = INVALID_HANDLE_VALUE;
}
AsyncSelect( FD_WRITE | FD_CLOSE );
}
else
{
CloseHandle( m_hFile );
m_hFile = INVALID_HANDLE_VALUE;
}
}
void HeaderToEnvVar( CString& strVar )
{
int ndx;
// make upper case, change '-' to '_', and prefix....
strVar.MakeUpper();
while( (ndx = strVar.Find('-')) != -1 )
strVar = strVar.Left(ndx) + '_' + strVar.Mid(ndx+1);
strVar = "HTTP_" + strVar;
}
CEnvironment::CEnvironment( void )
{
m_nSize = 2;
}
CEnvironment::~CEnvironment( void )
{
}
BOOL CEnvironment::Add( CString name, CString value )
{
BOOL bOk = TRUE;
// create the entry pair string....
CString strPair = name + __TEXT('=') + value;
m_nSize += strPair.GetLength() + 1;
POSITION pos = m_list.GetHeadPosition();
// find the first item bigger than this string....
while( pos != NULL )
{
if ( m_list.GetAt(pos).CompareNoCase(strPair) > 0 )
{
m_list.InsertBefore( pos, strPair );
break;
}
m_list.GetNext( pos );
}
if ( pos == NULL )
m_list.AddTail( strPair );
return bOk;
}
LPVOID CEnvironment::GetBlock( void )
{
// allocate a block....
PTCHAR pBlock = new TCHAR[m_nSize];
if ( pBlock )
{
// iterate through the list....
PTCHAR pPos = pBlock;
POSITION pos = m_list.GetHeadPosition();
while( pos != NULL )
{
CString& str = m_list.GetNext( pos );
// copy the string....
lstrcpy( pPos, str );
pPos += str.GetLength() + 1;
}
// NULL for the whole list....
*pPos = __TEXT('\0');
}
return pBlock;
}
#endif // IMPL_CGI
CString CRequestSocket::GetHttpDate( LPFILETIME pft )
{
SYSTEMTIME st;
if ( pft )
FileTimeToSystemTime( pft, &st );
else
GetSystemTime( &st );
CTime timeHttp( st );
return timeHttp.Format( IDS_HTTPTIME );
}
BOOL CRequestSocket::IfModSince( const CTime& timeIfMod )
{
// assume it has been modified....
BOOL bOk = TRUE;
FILETIME ft;
if ( GetFileTime( m_hFile, NULL, NULL, &ft ) )
{
SYSTEMTIME st;
if ( FileTimeToSystemTime( &ft, &st ) )
{
CTime timeFile( st );
if ( timeFile <= timeIfMod )
bOk = FALSE;
}
}
return bOk;
}
static int IntVal( CString strVal )
{
int nVal = 0;
strVal.TrimLeft();
for( int ndx = 0; ndx < strVal.GetLength(); ++ndx )
nVal = nVal*10 + strVal.GetAt(ndx) - '0';
return nVal;
}
static int MonthFromStr( const CString& str )
{
LPSTR aMonths[] = {
"xxx", "jan", "feb", "mar", "apr", "may", "jun",
"jul", "aug", "sep", "oct", "nov", "dec" };
for( int nMonth=1; nMonth <= 12; ++nMonth )
{
if ( str.CompareNoCase( aMonths[nMonth] ) == 0 )
break;
}
return nMonth;
}
// Dow, dd Mon year hh:mm:ss GMT
BOOL CRequestSocket::FromHttpTime( const CString& strHttp, CTime& timeHttp )
{
// assume we couldn't get a good time conversion....
BOOL bOk = FALSE;
SYSTEMTIME st = {0};
int ndx;
switch( strHttp.GetAt(3) )
{
case ',':
// read RFC-1123 (preferred)....
st.wDay = IntVal( strHttp.Mid(5,2) );
st.wMonth = MonthFromStr( strHttp.Mid(8,3) );
st.wYear = IntVal( strHttp.Mid(12,4) );
st.wHour = IntVal( strHttp.Mid(17,2) );
st.wMinute = IntVal( strHttp.Mid(20,2) );
st.wSecond = IntVal( strHttp.Mid(23,2) );
break;
case ' ':
// read ANSI-C time format....
st.wDay = IntVal( strHttp.Mid(8,2) );
st.wMonth = MonthFromStr( strHttp.Mid(4,3) );
st.wYear = IntVal( strHttp.Mid(20,4) );
st.wHour = IntVal( strHttp.Mid(11,2) );
st.wMinute = IntVal( strHttp.Mid(14,2) );
st.wSecond = IntVal( strHttp.Mid(17,2) );
break;
default:
if ( (ndx = strHttp.Find( ", " )) != -1 )
{
st.wDay = IntVal( strHttp.Mid(ndx+2,2) );
st.wMonth = MonthFromStr( strHttp.Mid(ndx+5,3) );
st.wYear = IntVal( strHttp.Mid(ndx+9,2) );
st.wHour = IntVal( strHttp.Mid(ndx+12,2) );
st.wMinute = IntVal( strHttp.Mid(ndx+15,2) );
st.wSecond = IntVal( strHttp.Mid(ndx+18,2) );
// add the correct century....
st.wYear += (st.wYear > 50)?1900:2000;
}
break;
}
// if year not zero, we pulled the info out of the string....
if ( st.wYear != 0 )
{
// assume GMT....
CTime strTime( st );
// check to see if the minutes are the same....
if ( strTime.GetMinute() == st.wMinute )
{
// assume it worked....
timeHttp = strTime;
bOk = TRUE;
}
}
return bOk;
}
int CRequestSocket::AddRef( void )
{
return ++m_nRefs;
}
int CRequestSocket::Release( void )
{
int nRefs = --m_nRefs;
if ( nRefs == 0 )
delete this;
return nRefs;
}
void CRequestSocket::StuffFileType( void )
{
// get the extension....
CString strExt = m_pRequest->m_strFullPath.Mid(
m_pRequest->m_strFullPath.ReverseFind('.') );
// find it in the registry....
HKEY hKey = NULL;
if ( RegOpenKeyEx( HKEY_CLASSES_ROOT, strExt,
0, KEY_READ, &hKey ) == ERROR_SUCCESS )
{
DWORD dwSize = 0;
// see how long the data is....
if ( RegQueryValueEx( hKey, "Content Type", NULL, NULL,
NULL, &dwSize ) == ERROR_SUCCESS )
{
CString strType;
LONG lRet = RegQueryValueEx( hKey, "Content Type", NULL, NULL,
(LPBYTE)strType.GetBuffer( dwSize ), &dwSize );
strType.ReleaseBuffer();
if ( lRet == ERROR_SUCCESS )
StuffHeader( "Content-type", strType );
}
RegCloseKey( hKey );
}
}
CString Decode( const CString& str, BOOL bQuery )
{
int ndx;
CString strDecoded = str;
// special processing or query strings....
if ( bQuery )
{
// change all '+' to ' '....
while( (ndx=strDecoded.Find('+')) != -1 )
strDecoded = strDecoded.Left(ndx) + ' ' + strDecoded.Mid(ndx+1);
}
// first see if there are any %s to decode....
if ( strDecoded.Find( '%' ) != -1 )
{
// iterate through the string, changing %dd to special char....
for( ndx=0; ndx < strDecoded.GetLength(); ndx++ )
{
char ch = strDecoded.GetAt( ndx );
if ( ch == '%' )
{
if ( strDecoded.GetAt( ndx+1 ) == '%' )
{
// wanna keep one percent sign....
strDecoded = strDecoded.Left(ndx) + strDecoded.Mid(ndx+1);
}
else
{
// assume we have a hex value....
char ch1 = strDecoded.GetAt(ndx+1);
char ch2 = strDecoded.GetAt(ndx+2);
ch1 = ch1 >= 'A' ? (ch1&0xdf)-'A' : ch1-'0';
ch2 = ch2 >= 'A' ? (ch2&0xdf)-'A' : ch2-'0';
// replace the escape sequence with the char....
strDecoded = strDecoded.Left(ndx)
+ (char)(ch1*16 + ch2)
+ strDecoded.Mid( ndx+3 );
}
}
}
}
return strDecoded;
}
CString CRequestSocket::StripLast( CString& strPath )
{
CString strExtra;
if ( !strPath.IsEmpty() )
{
int ndx = strPath.ReverseFind( SEPCHAR );
if ( ndx < 0 )
ndx = 0;
strExtra = strPath.Mid( ndx );
strPath = strPath.Left( ndx );
}
return strExtra;
}
BOOL CRequestSocket::CheckDefault( UINT uList, BOOL bExecute )
{
BOOL bFound = FALSE;
DWORD dwAttr;
CString strDefault, strDefList;
strDefList.LoadString( uList );
while ( !strDefList.IsEmpty() )
{
int ndx;
strDefault = m_pRequest->m_strFullPath;
if ( (ndx=strDefList.Find('\n')) == -1 )
{
AddFile( strDefault, strDefList );
strDefList.Empty();
}
else
{
AddFile( strDefault, strDefList.Left(ndx) );
strDefList = strDefList.Mid( ndx+1 );
}
if ( (dwAttr=GetFileAttributes(strDefault)) != -1 &&
(dwAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 )
{
bFound = TRUE;
break;
}
}
if ( bFound )
{
// redirect to the default file....
PathToURL( strDefault );
if ( bExecute )
strDefault += '?';
StuffStatus( IDS_STATUS_MOVEDTEMP );
StuffHeader( "Location", strDefault );
StuffString( CRLF );
}
return bFound;
}
BOOL CRequestSocket::IsSvrApp( void )
{
BOOL bOk = FALSE;
int ndx = m_pRequest->m_strFullPath.ReverseFind( '.' );
if ( ndx != -1 )
{
CString strExt = m_pRequest->m_strFullPath.Mid( ndx+1 );
CString strAvail;
// check if CGI app....
strAvail.LoadString( IDS_APP_CGI );
bOk = CheckExt( strExt, strAvail, CRequest::APP_CGI );
if ( !bOk )
{
strAvail.LoadString( IDS_APP_ISAPI );
bOk = CheckExt( strExt, strAvail, CRequest::APP_ISAPI );
}
}
return bOk;
}
BOOL CRequestSocket::CheckExt( const CString& strExt, CString& strAvail, DWORD dwType )
{
BOOL bMatch = FALSE;
CString strPossible;
// loop through all possible exts....
while( !strAvail.IsEmpty() )
{
int ndx = strAvail.ReverseFind('\n');
if ( ndx == -1 )
{
strPossible = strAvail;
strAvail.Empty();
}
else
{
strPossible = strAvail.Mid( ndx+1 );
strAvail = strAvail.Left( ndx );
}
if ( strExt.CompareNoCase( strPossible ) == 0 )
{
m_pRequest->m_dwExecute = dwType;
bMatch = TRUE;
break;
}
}
return bMatch;
}