// 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; }