mirror of https://github.com/lianthony/NT4.0
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.
2355 lines
59 KiB
2355 lines
59 KiB
/**********************************************************************/
|
|
/** Microsoft Windows NT **/
|
|
/** Copyright(c) Microsoft Corp., 1994 **/
|
|
/**********************************************************************/
|
|
|
|
/*
|
|
doget.cxx
|
|
|
|
This module contains the code for the GET and HEAD verb
|
|
|
|
|
|
FILE HISTORY:
|
|
Johnl 23-Aug-1994 Created
|
|
Phillich 24-Jan-1996 Added support for NCSA map files
|
|
Phillich 20-Feb-1996 Added support for byte ranges
|
|
|
|
*/
|
|
|
|
#include "w3p.hxx"
|
|
|
|
//
|
|
// Private constants.
|
|
//
|
|
|
|
//
|
|
// Computes the square of a number. Used for circle image maps
|
|
//
|
|
|
|
#define SQR(x) ((x) * (x))
|
|
|
|
//
|
|
// Maximum number of vertices in image map polygon
|
|
//
|
|
|
|
#define MAXVERTS 100
|
|
|
|
//
|
|
// Point offset of x and y
|
|
//
|
|
|
|
#define X 0
|
|
#define Y 1
|
|
|
|
//
|
|
// Private globals.
|
|
//
|
|
|
|
#define BOUNDARY_STRING_DEFINITION "[lka9uw3et5vxybtp87ghq23dpu7djv84nhls9p]"
|
|
|
|
// The boundary string is preceded by a line delimiter ( cf RFC 1521 )
|
|
// This can be set to "\n" instead of "\r\n" as Navigator 2.0 apparently handles
|
|
// all bytes before the "\n" as part of the reply body.
|
|
|
|
#define BOUNDARY_STRING "\r\n--" BOUNDARY_STRING_DEFINITION "\r\n"
|
|
#define LAST_BOUNDARY_STRING "\r\n--" BOUNDARY_STRING_DEFINITION "--\r\n\r\n"
|
|
|
|
// addition to 1st delimiter of boundary string ( can be "\r" if not included
|
|
// in BOUNDARY_STRING )
|
|
|
|
#define DELIMIT_FIRST ""
|
|
#define ADJ_FIRST (2-(sizeof(DELIMIT_FIRST)-1))
|
|
|
|
#define MMIME_TYPE_1 "Content-Type: "
|
|
#define MMIME_TYPE_2 "\r\n"
|
|
#define MMIME_TYPE MMIME_TYPE_1 \
|
|
"%s" \
|
|
MMIME_TYPE_2
|
|
char g_achMMimeTypeFmt[] = MMIME_TYPE_1 MMIME_TYPE_2;
|
|
|
|
#define MMIME_RANGE_1 "Content-Range: bytes "
|
|
#define MMIME_RANGE_2 "-"
|
|
#define MMIME_RANGE_3 "/"
|
|
#define MMIME_RANGE_4 "\r\n\r\n"
|
|
#define MMIME_RANGE MMIME_RANGE_1 \
|
|
"%u" \
|
|
MMIME_RANGE_2 \
|
|
"%u" \
|
|
MMIME_RANGE_3 \
|
|
"%u" \
|
|
MMIME_RANGE_4
|
|
|
|
char g_achMMimeRangeFmt[] = MMIME_RANGE_1
|
|
MMIME_RANGE_2
|
|
MMIME_RANGE_3
|
|
MMIME_RANGE_4;
|
|
|
|
|
|
//
|
|
// Private prototypes.
|
|
//
|
|
|
|
BOOL SearchMapFile( CHAR * pchFile,
|
|
TSVC_CACHE * pTsvcCache,
|
|
HANDLE hToken,
|
|
INT x,
|
|
INT y,
|
|
STR * pstrURL,
|
|
BOOL * pfFound,
|
|
BOOL fIsAnon );
|
|
|
|
int pointinpoly(int x, int y, double pgon[MAXVERTS][2]);
|
|
|
|
INT GetNumber( CHAR * * ppch );
|
|
|
|
DWORD NbDigit( DWORD dw );
|
|
|
|
//
|
|
// Public functions.
|
|
//
|
|
|
|
|
|
//
|
|
// Private functions.
|
|
//
|
|
|
|
|
|
DWORD NbDigit( DWORD dw )
|
|
{
|
|
if ( dw < 10 )
|
|
return 1;
|
|
else if ( dw < 100 )
|
|
return 2;
|
|
else if ( dw < 1000 )
|
|
return 3;
|
|
else if ( dw < 10000 )
|
|
return 4;
|
|
else if ( dw < 100000 )
|
|
return 5;
|
|
else if ( dw < 1000000 )
|
|
return 6;
|
|
|
|
DWORD cD = 7;
|
|
for ( dw /= 10000000 ; dw ; ++cD )
|
|
dw /= 10;
|
|
|
|
return cD;
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::DoGet
|
|
|
|
SYNOPSIS: Implements the Get verb
|
|
|
|
RETURNS: TRUE if successful, FALSE on error
|
|
|
|
HISTORY:
|
|
Johnl 29-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::DoGet( VOID )
|
|
{
|
|
if ( !DoGetHeadAux( TRUE ) )
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::DoHead
|
|
|
|
SYNOPSIS: Implements the Head verb (same as get but doesn't return
|
|
the contents of the file)
|
|
|
|
RETURNS: TRUE if successful, FALSE on error
|
|
|
|
HISTORY:
|
|
Johnl 29-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::DoHead( VOID )
|
|
{
|
|
if ( !DoGetHeadAux( FALSE ) )
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::DoGetHeadAux
|
|
|
|
SYNOPSIS: Implements the Get and Head verbs
|
|
|
|
RETURNS: TRUE if successful, FALSE on error
|
|
|
|
HISTORY:
|
|
Johnl 29-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::DoGetHeadAux( BOOL fSendFile )
|
|
{
|
|
DWORD dwMask;
|
|
|
|
if ( !(_dwRootMask & VROOT_MASK_READ) )
|
|
{
|
|
SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
|
|
Disconnect( HT_FORBIDDEN, IDS_READ_ACCESS_DENIED );
|
|
return TRUE;
|
|
}
|
|
|
|
if ( !SendFileOrDir( &_strPhysicalPath,
|
|
fSendFile ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::SendFileOrDir
|
|
|
|
SYNOPSIS: Transmits a the specified file or directory in response
|
|
to a Get or Head request
|
|
|
|
RETURNS: TRUE if successful, FALSE on error
|
|
|
|
FALSE should be returned for fatal errors (memory etc),
|
|
a server error response will be sent with error text from
|
|
GetLastError()
|
|
|
|
For other errors (access denied, path not found etc)
|
|
disconnect with status should be called and TRUE should be
|
|
returned.
|
|
|
|
NOTES: The file handle gets closed during destruction
|
|
|
|
We never retrieve a hidden file or directory. We will process
|
|
hidden map files however.
|
|
|
|
HISTORY:
|
|
Johnl 29-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::SendFileOrDir( STR * pstrPath,
|
|
BOOL fSendFile )
|
|
{
|
|
BOOL fHandled = FALSE;
|
|
DWORD cbSizeLow;
|
|
DWORD cbSizeHigh;
|
|
BOOL fHidden;
|
|
BOOL fMatches;
|
|
|
|
//
|
|
// Open the file (or directory)
|
|
//
|
|
|
|
IF_DEBUG( REQUEST )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"DoGetHeadAux: Openning %s\n",
|
|
pstrPath->QueryStr()));
|
|
}
|
|
|
|
#ifdef CHICAGO
|
|
|
|
int err;
|
|
unsigned short perms;
|
|
|
|
if (TsIsUserLevelPresent()) {
|
|
|
|
//
|
|
// As file system on Chicago does not check access for us , we need to do it
|
|
// manually ( yack ). I will move this check into Tsunami eventually , but user name
|
|
// is not available there ,as impersonation is also not available.
|
|
const CHAR * pszUser = _strUserName.QueryStr();
|
|
|
|
if (!pszUser || !*pszUser) {
|
|
if ( _fAnonymous ) {
|
|
g_pTsvcInfo->LockThisForRead();
|
|
pszUser = g_pTsvcInfo->QueryAnonUserName();
|
|
g_pTsvcInfo->UnlockThis();
|
|
}
|
|
}
|
|
|
|
if (err = NetAccessGetUserPerms(NULL,
|
|
(CHAR *)pszUser,
|
|
pstrPath->QueryStr(),
|
|
&perms))
|
|
{
|
|
// Could not get access list - what to do ?
|
|
Disconnect( HT_NOT_FOUND );
|
|
return TRUE;
|
|
}
|
|
|
|
if ( !(perms & ACCESS_READ ) ) {
|
|
SetDeniedFlags( SF_DENIED_RESOURCE );
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
return FALSE;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
|
|
_pGetFile = TsCreateFile( g_pTsvcInfo->GetTsvcCache(),
|
|
pstrPath->QueryStr(),
|
|
QueryImpersonationHandle( FALSE ),
|
|
((_fClearTextPass || _fAnonymous) ? TS_CACHING_DESIRED : 0)
|
|
| TS_NOT_IMPERSONATED );
|
|
|
|
if ( QueryFileHandle() == INVALID_HANDLE_VALUE )
|
|
{
|
|
DWORD err = GetLastError();
|
|
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"DoGetHeadAux: Failed to open %s, error %d\n",
|
|
pstrPath->QueryStr(),
|
|
err ));
|
|
|
|
if ( err == ERROR_FILE_NOT_FOUND ||
|
|
err == ERROR_PATH_NOT_FOUND )
|
|
{
|
|
SetState( HTR_DONE, HT_NOT_FOUND, GetLastError() );
|
|
Disconnect( HT_NOT_FOUND );
|
|
return TRUE;
|
|
}
|
|
|
|
if ( err == ERROR_INVALID_NAME )
|
|
{
|
|
SetState( HTR_DONE, HT_BAD_REQUEST, GetLastError() );
|
|
Disconnect( HT_BAD_REQUEST );
|
|
return TRUE;
|
|
}
|
|
else if ( err == ERROR_ACCESS_DENIED )
|
|
{
|
|
SetDeniedFlags( SF_DENIED_RESOURCE );
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
fHidden = ((_pGetFile->QueryAttributes() & FILE_ATTRIBUTE_HIDDEN) != 0);
|
|
|
|
|
|
//
|
|
// If the file is a directory, then we may need to do a directory listing
|
|
//
|
|
|
|
if ( _pGetFile->QueryAttributes() & FILE_ATTRIBUTE_DIRECTORY &&
|
|
(QueryVerb() == HTV_GET || QueryVerb() == HTV_HEAD) )
|
|
{
|
|
TCP_REQUIRE( TsCloseHandle( g_pTsvcInfo->GetTsvcCache(),
|
|
_pGetFile ));
|
|
_pGetFile = NULL;
|
|
|
|
if ( fHidden )
|
|
{
|
|
SetState( HTR_DONE, HT_NOT_FOUND, ERROR_PATH_NOT_FOUND );
|
|
Disconnect( HT_NOT_FOUND );
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef CHICAGO
|
|
if (TsIsUserLevelPresent()) {
|
|
|
|
// For directories we we also need to check scan rights
|
|
if ( !(perms & ACCESS_FINDFIRST ) ) {
|
|
SetDeniedFlags( SF_DENIED_RESOURCE );
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
return FALSE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// If a default file is in the directory and the feature is enabled,
|
|
// then return the default file to the user
|
|
//
|
|
|
|
if ( DirBrowFlags & DIRBROW_LOADDEFAULT )
|
|
{
|
|
if ( !CheckDefaultLoad( pstrPath, &fHandled ))
|
|
{
|
|
if ( GetLastError() == ERROR_ACCESS_DENIED )
|
|
{
|
|
SetDeniedFlags( SF_DENIED_RESOURCE );
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if ( fHandled )
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// We're doing a directory listing, so send the directory list
|
|
// with the response headers. The request is finished at that
|
|
// point.
|
|
//
|
|
|
|
SetState( HTR_DONE, HT_OK, NO_ERROR );
|
|
|
|
if ( DirBrowFlags & DIRBROW_ENABLED )
|
|
{
|
|
return DoDirList( *pstrPath, QueryRespBuf() );
|
|
}
|
|
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[DoDirList] Denying request for directory browsing\n"));
|
|
|
|
SetLogStatus( HT_FORBIDDEN, ERROR_ACCESS_DENIED );
|
|
|
|
Disconnect( HT_FORBIDDEN );
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// We're dealing with a file. Is it an ismap request?
|
|
//
|
|
|
|
if ( _GatewayType == GATEWAY_MAP )
|
|
{
|
|
BOOL fFound = FALSE;
|
|
|
|
//
|
|
// This may be an ISMAP request so check the parameters and process
|
|
// the map file if it is. _hGetFile is a map file if it is.
|
|
//
|
|
|
|
if ( !ProcessISMAP( pstrPath->QueryStr(),
|
|
QueryRespBuf(),
|
|
&fFound,
|
|
&fHandled ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( fHandled )
|
|
return TRUE;
|
|
|
|
if ( fFound )
|
|
{
|
|
SetState( HTR_DONE, HT_OK, NO_ERROR );
|
|
|
|
return WriteFile( QueryRespBufPtr(),
|
|
QueryRespBufCB(),
|
|
NULL,
|
|
IO_FLAG_ASYNC );
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point we know the user wants to retrieve the file
|
|
//
|
|
|
|
//
|
|
// If the client sent an If-Modified-Since header, check the date on the
|
|
// resource
|
|
//
|
|
|
|
if ( _liModifiedSince.QuadPart && !fHidden )
|
|
{
|
|
FILETIME tm;
|
|
|
|
TCP_REQUIRE( _pGetFile->QueryLastWriteTime( &tm ));
|
|
|
|
if ( *(LONGLONG*)&tm <= _liModifiedSince.QuadPart )
|
|
{
|
|
//
|
|
// Build the not modified response with support for keep-alives
|
|
//
|
|
|
|
if ( !BuildStatusLine( QueryRespBuf(),
|
|
HT_NOT_MODIFIED,
|
|
NO_ERROR ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( IsKeepConnSet() )
|
|
{
|
|
strcat( QueryRespBufPtr(), "Connection: keep-alive\r\n"
|
|
"Content-Length: 0\r\n\r\n" );
|
|
}
|
|
else
|
|
{
|
|
strcat( QueryRespBufPtr(), "\r\n" );
|
|
}
|
|
|
|
SetState( HTR_DONE, HT_NOT_MODIFIED, NO_ERROR );
|
|
|
|
if ( !WriteFile( QueryRespBufPtr(),
|
|
QueryRespBufCB(),
|
|
&cbSizeLow, // dummy
|
|
IO_FLAG_ASYNC ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if ( fHidden )
|
|
{
|
|
SetState( HTR_DONE, HT_NOT_FOUND, ERROR_FILE_NOT_FOUND );
|
|
Disconnect( HT_NOT_FOUND );
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Check to see if this is a server side include file and server side
|
|
// includes are enabled
|
|
//
|
|
|
|
if ( fSSIEnabled )
|
|
{
|
|
LockAdminForRead();
|
|
|
|
fMatches = !_stricmp( pstrPath->QueryStr() + pstrPath->QueryCCH() -
|
|
strlen( pszSSIExt ),
|
|
pszSSIExt );
|
|
|
|
UnlockAdmin();
|
|
|
|
if ( fMatches )
|
|
{
|
|
//
|
|
// The extension matches, do the processing
|
|
//
|
|
|
|
if ( !ProcessSSI( pstrPath,
|
|
&fHandled ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( fHandled )
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// check if range requested
|
|
|
|
DWORD dwOffset;
|
|
DWORD dwSizeToSend;
|
|
BOOL fIsNxRange;
|
|
|
|
_fAcceptRange = g_fAcceptRangesBytes;
|
|
|
|
if ( g_fAcceptRangesBytes && !_strRange.IsEmpty() && fSendFile )
|
|
{
|
|
ProcessRangeRequest( pstrPath,
|
|
&dwOffset,
|
|
&dwSizeToSend,
|
|
&fIsNxRange );
|
|
|
|
if ( !BuildResponseHeader( QueryRespBuf(),
|
|
pstrPath,
|
|
_pGetFile,
|
|
&fHandled ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Build the header response based on file type and client
|
|
// requests
|
|
//
|
|
// It's possible we may not want to send the file (if the client
|
|
// doesn't have a needed MIME type for example)
|
|
//
|
|
|
|
else if ( !BuildFileResponseHeader( QueryRespBuf(),
|
|
pstrPath,
|
|
_pGetFile,
|
|
&fHandled ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( fHandled )
|
|
return TRUE;
|
|
|
|
//
|
|
// Send the header response and file and cleanup the request
|
|
//
|
|
|
|
if ( !fSendFile )
|
|
{
|
|
SetState( HTR_DONE, HT_OK, NO_ERROR );
|
|
|
|
if ( !WriteFile( QueryRespBufPtr(),
|
|
QueryRespBufCB(),
|
|
&cbSizeLow, // dummy
|
|
IO_FLAG_ASYNC ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
TCP_REQUIRE( _pGetFile->QuerySize( &cbSizeLow,
|
|
&cbSizeHigh ));
|
|
|
|
//
|
|
// Refuse requests for files greater then four gigs
|
|
//
|
|
|
|
if ( cbSizeHigh )
|
|
{
|
|
SetState( HTR_DONE, HT_NOT_SUPPORTED, ERROR_NOT_SUPPORTED );
|
|
Disconnect( HT_NOT_SUPPORTED );
|
|
return TRUE;
|
|
}
|
|
|
|
if ( _fProcessByteRange )
|
|
{
|
|
if ( fIsNxRange )
|
|
SetState( HTR_RANGE, HT_RANGE, NO_ERROR );
|
|
return SendRange( QueryRespBufCB(), dwOffset, dwSizeToSend, !fIsNxRange );
|
|
}
|
|
else
|
|
{
|
|
BOOL fKeepAlive = (dwAllNotifFlags & SF_NOTIFY_END_OF_REQUEST) ||
|
|
IsKeepConnSet();
|
|
|
|
SetState( HTR_DONE, HT_OK, NO_ERROR );
|
|
|
|
if ( !TransmitFile( QueryFileHandle(),
|
|
cbSizeLow,
|
|
IO_FLAG_ASYNC |
|
|
(fKeepAlive ? 0 :
|
|
TF_DISCONNECT | TF_REUSE_SOCKET),
|
|
QueryRespBufPtr(),
|
|
QueryRespBufCB() ))
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"DoGetHeadAux: TransmitFile failed sending header, error %d\n",
|
|
GetLastError() ));
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
DWORD AToDW(
|
|
LPSTR *ppRng,
|
|
BOOL *pfIsB
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Convert ASCII to DWORD, set flag stating presence
|
|
of a numeric value, update pointer to character stream
|
|
|
|
Returns:
|
|
DWORD value converted from ASCII
|
|
|
|
Arguments:
|
|
|
|
ppRng PSTR to numeric value, updated on return
|
|
pfIsB flag set to TRUE if numeric value present on return
|
|
|
|
History:
|
|
Phillich 08-Feb-1996 Created
|
|
|
|
--*/
|
|
{
|
|
LPSTR pRng = *ppRng;
|
|
DWORD dwV = 0;
|
|
|
|
if ( isdigit( *pRng ) )
|
|
{
|
|
int c;
|
|
while ( (c = *pRng) && isdigit( c ) )
|
|
{
|
|
dwV = dwV * 10 + c - '0';
|
|
++pRng;
|
|
}
|
|
*pfIsB = TRUE;
|
|
*ppRng = pRng;
|
|
}
|
|
else
|
|
*pfIsB = FALSE;
|
|
|
|
return dwV;
|
|
}
|
|
|
|
|
|
void
|
|
HTTP_REQUEST::ProcessRangeRequest(
|
|
STR * pstrPath,
|
|
DWORD * pdwOffset,
|
|
DWORD * pdwSizeToSend,
|
|
BOOL * pfIsNxRange )
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Process a range request, updating member variables
|
|
|
|
Returns:
|
|
|
|
VOID
|
|
|
|
Arguments:
|
|
|
|
pstrPath File being requested
|
|
pdwOffset Range offset
|
|
pdwSizeToSend Range size
|
|
pfIsNxRange TRUE if valid next range exists
|
|
|
|
History:
|
|
Phillich 08-Feb-1996 Created
|
|
|
|
--*/
|
|
{
|
|
BOOL fEntireFile;
|
|
BOOL fIsLastRange;
|
|
DWORD cbSizeLow;
|
|
DWORD cbSizeHigh;
|
|
FILETIME tm;
|
|
|
|
// Check range specified & optional UnlessModifiedSince
|
|
|
|
TCP_REQUIRE( _pGetFile->QueryLastWriteTime( &tm ));
|
|
|
|
if ( !_liUnlessModifiedSince.QuadPart
|
|
|| *(LONGLONG*)&tm
|
|
<= _liUnlessModifiedSince.QuadPart )
|
|
{
|
|
if ( ScanRange( pdwOffset,
|
|
pdwSizeToSend,
|
|
&fEntireFile,
|
|
&fIsLastRange )
|
|
&& !fEntireFile )
|
|
{
|
|
_fProcessByteRange = TRUE;
|
|
|
|
// default Mime length is range length
|
|
// will be used if only one range
|
|
|
|
_cbMimeMultipart = *pdwSizeToSend;
|
|
|
|
// compute Content-Length:
|
|
|
|
if ( IsKeepConnSet() && !fIsLastRange )
|
|
{
|
|
DWORD dwR = 0;
|
|
DWORD dwI = _iRangeIdx;
|
|
DWORD dwL = 0;
|
|
DWORD dwFx;
|
|
|
|
_dwRgNxOffset = *pdwOffset;
|
|
_dwRgNxSizeToSend = *pdwSizeToSend;
|
|
|
|
TCP_REQUIRE( _pGetFile->QuerySize( &cbSizeLow,
|
|
&cbSizeHigh ));
|
|
SelectMimeMapping( &_strReturnMimeType,
|
|
pstrPath->QueryStr() );
|
|
|
|
// For each segment in the MIME multipart message
|
|
// the size of the MIME type and the number of
|
|
// digits in document total size will be constant,
|
|
// so compute them now.
|
|
|
|
dwFx = strlen( _strReturnMimeType.QueryStr() )
|
|
+ NbDigit( cbSizeLow );
|
|
|
|
do {
|
|
// The size of each segment in a MIME
|
|
// multipart message is determined by :
|
|
// Boundary String
|
|
// The range itself
|
|
// the MIME type string
|
|
// the range string
|
|
// the number of digits in the range string
|
|
|
|
dwL += sizeof(BOUNDARY_STRING) - 1
|
|
+ _dwRgNxSizeToSend
|
|
+ sizeof(g_achMMimeTypeFmt) - 1
|
|
+ sizeof(g_achMMimeRangeFmt) - 1
|
|
+ NbDigit( _dwRgNxOffset )
|
|
+ NbDigit( _dwRgNxOffset
|
|
+ _dwRgNxSizeToSend - 1 )
|
|
+ dwFx;
|
|
|
|
// number of ranges
|
|
|
|
++dwR;
|
|
|
|
} while ( ScanRange(&_dwRgNxOffset,
|
|
&_dwRgNxSizeToSend,
|
|
&fEntireFile,
|
|
&fIsLastRange ) );
|
|
|
|
// adjust length because 1st boundary
|
|
// initial delimiter is part of the header delimiter
|
|
|
|
dwL += sizeof(LAST_BOUNDARY_STRING) - ADJ_FIRST - 1;
|
|
_iRangeIdx = dwI;
|
|
|
|
// Update mime multipart length
|
|
|
|
if ( dwR > 1 )
|
|
_cbMimeMultipart = dwL;
|
|
}
|
|
|
|
// scan next range now, this way we know
|
|
// if the current range is the last one.
|
|
|
|
*pfIsNxRange = ScanRange( &_dwRgNxOffset,
|
|
&_dwRgNxSizeToSend,
|
|
&fEntireFile,
|
|
&fIsLastRange );
|
|
|
|
if ( *pfIsNxRange )
|
|
{
|
|
_fMimeMultipart = TRUE;
|
|
|
|
// no keep-alive : no need to send Content-Length
|
|
if ( !IsKeepConnSet() )
|
|
_cbMimeMultipart = 0;
|
|
}
|
|
}
|
|
// else send entire file
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::ScanRange(
|
|
LPDWORD pdwOffset,
|
|
LPDWORD pdwSizeToSend,
|
|
BOOL *pfEntireFile,
|
|
BOOL *pfIsLastRange
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Scan the next range in strRange
|
|
|
|
Returns:
|
|
TRUE if a range was found, else FALSE
|
|
|
|
Arguments:
|
|
|
|
pdwOffset update range offset on return
|
|
pdwSizeToSend update range size on return
|
|
pfEntireFile set to TRUE on return if entire file to be send
|
|
pfIsLastRange set to TRUE on return if this is the last range
|
|
|
|
History:
|
|
Phillich 08-Feb-1996 Created
|
|
|
|
--*/
|
|
{
|
|
DWORD cbSizeLow;
|
|
DWORD cbSizeHigh;
|
|
DWORD dwOffset;
|
|
DWORD cbSizeToSend;
|
|
BOOL fEndOfRange = FALSE;
|
|
BOOL fInvalidRange;
|
|
BOOL fEntireFile = FALSE;
|
|
int c;
|
|
|
|
TCP_REQUIRE( _pGetFile->QuerySize( &cbSizeLow,
|
|
&cbSizeHigh ));
|
|
LPSTR pRng = _strRange.QueryStr() + _iRangeIdx;
|
|
|
|
do {
|
|
fInvalidRange = FALSE;
|
|
|
|
// Skip to begining of next range
|
|
while ( (c=*pRng) && c!='-' && !isdigit(c) )
|
|
++pRng;
|
|
|
|
// test for no range
|
|
if ( *pRng == '\0' )
|
|
{
|
|
_iRangeIdx = pRng - _strRange.QueryStr();
|
|
*pfEntireFile = fEntireFile;
|
|
*pfIsLastRange = TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
// determine Offset & Size to send
|
|
DWORD dwB, dwE;
|
|
BOOL fIsB, fIsE;
|
|
dwB = AToDW( &pRng, &fIsB );
|
|
if ( *pRng == '-' )
|
|
{
|
|
++pRng;
|
|
dwE = AToDW( &pRng, &fIsE );
|
|
if ( !fIsB && !fIsE )
|
|
fInvalidRange = TRUE;
|
|
else
|
|
{
|
|
fEntireFile = FALSE;
|
|
|
|
if ( fIsB )
|
|
{
|
|
if ( fIsE )
|
|
{
|
|
if ( dwB <= dwE )
|
|
{
|
|
if ( dwE < cbSizeLow )
|
|
{
|
|
dwOffset = dwB;
|
|
cbSizeToSend = dwE - dwB + 1;
|
|
}
|
|
else
|
|
{
|
|
if ( dwB < cbSizeLow )
|
|
{
|
|
dwOffset = dwB;
|
|
cbSizeToSend = cbSizeLow - dwB;
|
|
}
|
|
else
|
|
fInvalidRange = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// E < B : entire file if this is
|
|
// the only range
|
|
fEntireFile = TRUE;
|
|
fInvalidRange = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// starting at B until end
|
|
if ( dwB < cbSizeLow )
|
|
{
|
|
dwOffset = dwB;
|
|
cbSizeToSend = cbSizeLow - dwB;
|
|
}
|
|
else
|
|
fInvalidRange = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// E last bytes
|
|
if ( dwE < cbSizeLow )
|
|
{
|
|
dwOffset = cbSizeLow - dwE;
|
|
cbSizeToSend = dwE;
|
|
}
|
|
else
|
|
{
|
|
// entire file
|
|
// returned as range if not the 1st requested range
|
|
// else as entire file
|
|
dwOffset = 0;
|
|
cbSizeToSend = cbSizeLow;
|
|
fEntireFile = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
fInvalidRange = TRUE;
|
|
|
|
// Skip to begining of next range
|
|
while ( (c=*pRng) && c!='-' && !isdigit(c) )
|
|
++pRng;
|
|
|
|
} while ( fInvalidRange );
|
|
|
|
_iRangeIdx = pRng - _strRange.QueryStr();
|
|
|
|
*pfIsLastRange = *pRng == '\0';
|
|
*pfEntireFile = fEntireFile;
|
|
*pdwOffset = dwOffset;
|
|
*pdwSizeToSend = cbSizeToSend;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::SendRange(
|
|
DWORD dwBufLen,
|
|
DWORD dwOffset,
|
|
DWORD dwSizeToSend,
|
|
BOOL fIsLast
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Send a byte range to the client
|
|
|
|
Returns:
|
|
TRUE if TransmitFile OK, FALSE on error
|
|
|
|
Arguments:
|
|
|
|
dwBufLen length of the header already created in pbufResponse
|
|
dwOffset range offset in file
|
|
dwSizeToSend range size
|
|
fIsLast TRUE if this is the last range to send
|
|
|
|
History:
|
|
Phillich 08-Feb-1996 Created
|
|
|
|
--*/
|
|
{
|
|
CHAR * pszResp;
|
|
CHAR * pszTail;
|
|
BUFFER * pbufResponse = QueryRespBuf();
|
|
DWORD cbSizeLow;
|
|
DWORD cbSizeHigh;
|
|
DWORD dwFlags = IO_FLAG_ASYNC;
|
|
|
|
pszTail = pszResp = (CHAR *) pbufResponse->QueryPtr() + dwBufLen;
|
|
|
|
if ( _fMimeMultipart )
|
|
pszTail += wsprintf( pszTail,
|
|
MMIME_TYPE,
|
|
_strReturnMimeType.QueryStr() );
|
|
|
|
if ( fIsLast )
|
|
SetState( HTR_DONE, HT_RANGE, NO_ERROR );
|
|
|
|
TCP_REQUIRE( _pGetFile->QuerySize( &cbSizeLow,
|
|
&cbSizeHigh ));
|
|
pszTail += wsprintf( pszTail,
|
|
MMIME_RANGE,
|
|
dwOffset, dwOffset + dwSizeToSend - 1,
|
|
cbSizeLow
|
|
);
|
|
|
|
if ( fIsLast && !IsKeepConnSet() &&
|
|
(dwAllNotifFlags & SF_NOTIFY_END_OF_REQUEST) )
|
|
{
|
|
dwFlags |= TF_DISCONNECT | TF_REUSE_SOCKET;
|
|
}
|
|
|
|
if ( !TransmitFileEx( QueryFileHandle(),
|
|
dwOffset,
|
|
dwSizeToSend,
|
|
dwFlags,
|
|
QueryRespBufPtr(),
|
|
QueryRespBufCB(),
|
|
_fMimeMultipart ? (fIsLast ? LAST_BOUNDARY_STRING : BOUNDARY_STRING) : NULL,
|
|
_fMimeMultipart ? (fIsLast ? sizeof(LAST_BOUNDARY_STRING)-1 : sizeof(BOUNDARY_STRING)-1 ) : 0 ) )
|
|
{
|
|
SetState( HTR_DONE );
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQ_BASE::BuildResponseHeader
|
|
|
|
SYNOPSIS: Builds a successful response header
|
|
|
|
ENTRY: pstrResponse - Receives reply headers
|
|
pstrPath - Fully qualified path to file, may be NULL
|
|
pFile - File information about pstrPath, may be NULL
|
|
pfHandled - Does processing need to continue? Will be
|
|
set to TRUE if no further processing is needed by
|
|
the caller
|
|
pstrStatus - Alternate status string to use
|
|
|
|
RETURNS: TRUE if successful, FALSE on error
|
|
|
|
NOTES: if pstrPath is NULL, then the base header is put into
|
|
pstrResponse without the header termination or file
|
|
information
|
|
|
|
HISTORY:
|
|
Johnl 30-Aug-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL
|
|
HTTP_REQUEST::BuildResponseHeader(
|
|
BUFFER * pbufResponse,
|
|
STR * pstrPath,
|
|
TS_OPEN_FILE_INFO * pFile,
|
|
BOOL * pfHandled,
|
|
STR * pstrStatus
|
|
)
|
|
{
|
|
FILETIME FileTime;
|
|
DWORD cbSizeLow;
|
|
SYSTEMTIME SysTime;
|
|
CHAR * pszResp;
|
|
CHAR * pszTail;
|
|
CHAR achTime[64];
|
|
|
|
if ( pfHandled )
|
|
*pfHandled = FALSE;
|
|
|
|
//
|
|
// HTTP 0.9 clients don't use MIME headers, they just expect the data
|
|
//
|
|
|
|
if ( IsPointNine() )
|
|
{
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[BuildResponseHeader] Skipping headers, 0.9 client\n"));
|
|
}
|
|
|
|
*((CHAR *)pbufResponse->QueryPtr()) = '\0';
|
|
return TRUE;
|
|
}
|
|
|
|
if ( !BuildBaseResponseHeader( pbufResponse,
|
|
pfHandled,
|
|
pstrStatus,
|
|
TRUE ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If no file, then don't add the content type headers
|
|
//
|
|
|
|
if ( !pstrPath )
|
|
return TRUE;
|
|
|
|
pszResp = (CHAR *) pbufResponse->QueryPtr();
|
|
pszTail = pszResp + strlen(pszResp);
|
|
|
|
//
|
|
// "Content-Type: xxx/xxx"
|
|
//
|
|
// We check to make sure the client can accept the type we
|
|
// want to send
|
|
//
|
|
|
|
if ( !::SelectMimeMapping( &_strReturnMimeType, pstrPath->QueryStr() ) )
|
|
return FALSE;
|
|
|
|
if ( !DoesClientAccept( _strReturnMimeType.QueryStr() ) )
|
|
{
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[BuildResponseHeader] Client doesn't accept %s\n",
|
|
_strReturnMimeType.QueryStr() ));
|
|
}
|
|
|
|
SetState( HTR_DONE, HT_NONE_ACCEPTABLE, NO_ERROR );
|
|
Disconnect( HT_NONE_ACCEPTABLE );
|
|
|
|
//
|
|
// No further processing is needed
|
|
//
|
|
|
|
if ( pfHandled )
|
|
*pfHandled = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if ( _fMimeMultipart && _fProcessByteRange )
|
|
{
|
|
APPEND_STRING( pszTail, "Content-Type: multipart/x-byteranges; boundary="
|
|
BOUNDARY_STRING_DEFINITION "\r\n" );
|
|
}
|
|
else
|
|
{
|
|
pszTail += wsprintf( pszTail,
|
|
"Content-Type: %s\r\n",
|
|
_strReturnMimeType.QueryStr() );
|
|
|
|
if ( _fAcceptRange && !_fProcessByteRange )
|
|
APPEND_STRING( pszTail, "Accept-Ranges: bytes\r\n" );
|
|
}
|
|
|
|
if ( !pFile )
|
|
return TRUE;
|
|
|
|
//
|
|
// "Last-Modified: <GMT time>"
|
|
//
|
|
|
|
if ( !pFile->QueryLastWriteTime( &FileTime ) ||
|
|
!::FileTimeToSystemTime( &FileTime, &SysTime ) ||
|
|
!::SystemTimeToGMT( SysTime, achTime, sizeof(achTime) ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pszTail += wsprintf( pszTail,
|
|
"Last-Modified: %s\r\n",
|
|
achTime );
|
|
|
|
//
|
|
// "Content-Length: nnnn" and end of headers
|
|
//
|
|
|
|
|
|
if ( _fMimeMultipart )
|
|
{
|
|
if ( _cbMimeMultipart )
|
|
pszTail += wsprintf( pszTail,
|
|
"Content-Length: %lu\r\n",
|
|
_cbMimeMultipart );
|
|
|
|
|
|
//
|
|
// first boundary string
|
|
//
|
|
|
|
APPEND_STRING( pszTail, DELIMIT_FIRST BOUNDARY_STRING );
|
|
}
|
|
else
|
|
{
|
|
if ( _fProcessByteRange )
|
|
cbSizeLow = _cbMimeMultipart;
|
|
else
|
|
TCP_REQUIRE( pFile->QuerySize( &cbSizeLow ));
|
|
|
|
pszTail += wsprintf( pszTail,
|
|
_fProcessByteRange ? "Content-Length: %lu\r\n"
|
|
: "Content-Length: %lu\r\n\r\n",
|
|
cbSizeLow );
|
|
}
|
|
|
|
IF_DEBUG( REQUEST )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"BuildResponseHeader: Built the following header:\n%s",
|
|
pszResp ));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
HTTP_REQUEST::BuildFileResponseHeader(
|
|
BUFFER * pbufResponse,
|
|
STR * pstrPath,
|
|
TS_OPEN_FILE_INFO * pFile,
|
|
BOOL * pfHandled
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Builds a successful response header for a file request
|
|
without byte ranges.
|
|
|
|
Returns:
|
|
TRUE if successful, FALSE on error
|
|
|
|
Arguments:
|
|
|
|
pstrResponse - Receives reply headers
|
|
pstrPath - Fully qualified path to file, may be NULL
|
|
pFile - File information about pstrPath, may be NULL
|
|
pfHandled - Does processing need to continue? Will be
|
|
set to TRUE if no further processing is needed by
|
|
the caller
|
|
|
|
History:
|
|
Phillich 26-Feb-1996 Created
|
|
|
|
--*/
|
|
{
|
|
FILETIME FileTime;
|
|
DWORD cbSizeLow;
|
|
SYSTEMTIME SysTime;
|
|
CHAR * pszResp;
|
|
CHAR * pszTail;
|
|
CHAR * pszVariant;
|
|
CHAR achTime[64];
|
|
int cMod;
|
|
|
|
if ( pfHandled )
|
|
*pfHandled = FALSE;
|
|
|
|
//
|
|
// HTTP 0.9 clients don't use MIME headers, they just expect the data
|
|
//
|
|
|
|
if ( IsPointNine() )
|
|
{
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[BuildResponseHeader] Skipping headers, 0.9 client\n"));
|
|
}
|
|
|
|
*((CHAR *)pbufResponse->QueryPtr()) = '\0';
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
pszResp = (CHAR *) pbufResponse->QueryPtr();
|
|
|
|
if ( !BuildBaseResponseHeader( pbufResponse,
|
|
pfHandled,
|
|
NULL,
|
|
HTTPH_SEND_GLOBAL_EXPIRE
|
|
|HTTPH_NO_DATE ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pszTail = pszResp + strlen(pszResp);
|
|
|
|
// build Date: uses Date/Time cache
|
|
::GetSystemTime( &SysTime );
|
|
pszTail += IslFormatDateTime( &SysTime, dftGmt, pszTail );
|
|
|
|
if ( pFile->RetrieveHttpInfo( pszTail, &cMod ) )
|
|
{
|
|
// 1st line is Content-Type:, check client accepts it
|
|
|
|
PSTR pDelim = strchr( pszTail, '\r' );
|
|
|
|
if ( pDelim )
|
|
*pDelim = '\0';
|
|
|
|
if ( !DoesClientAccept( pszTail + sizeof( "Content-Type: " ) - sizeof(CHAR) ) )
|
|
goto no_match_type;
|
|
|
|
if ( pDelim )
|
|
*pDelim = '\r';
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
pszVariant = pszTail;
|
|
|
|
//
|
|
// "Content-Type: xxx/xxx"
|
|
//
|
|
// We check to make sure the client can accept the type we
|
|
// want to send
|
|
//
|
|
|
|
if ( !::SelectMimeMapping( &_strReturnMimeType, pstrPath->QueryStr() ) )
|
|
return FALSE;
|
|
|
|
if ( !DoesClientAccept( _strReturnMimeType.QueryStr() ) )
|
|
{
|
|
no_match_type:
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[BuildResponseHeader] Client doesn't accept %s\n",
|
|
_strReturnMimeType.QueryStr() ));
|
|
}
|
|
|
|
SetState( HTR_DONE, HT_NONE_ACCEPTABLE, NO_ERROR );
|
|
Disconnect( HT_NONE_ACCEPTABLE );
|
|
|
|
//
|
|
// No further processing is needed
|
|
//
|
|
|
|
if ( pfHandled )
|
|
*pfHandled = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
pszTail += wsprintf( pszTail,
|
|
"Content-Type: %s\r\n",
|
|
_strReturnMimeType.QueryStr() );
|
|
|
|
if ( _fAcceptRange )
|
|
APPEND_STRING( pszTail, "Accept-Ranges: bytes\r\n" );
|
|
|
|
//
|
|
// "Last-Modified: <GMT time>"
|
|
//
|
|
|
|
if ( !pFile->QueryLastWriteTime( &FileTime ) ||
|
|
!::FileTimeToSystemTime( &FileTime, &SysTime ) ||
|
|
!::SystemTimeToGMT( SysTime, achTime, sizeof(achTime) ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pszTail += wsprintf( pszTail,
|
|
"Last-Modified: %s\r\n",
|
|
achTime );
|
|
|
|
//
|
|
// "Content-Length: nnnn" and end of headers
|
|
//
|
|
|
|
|
|
TCP_REQUIRE( pFile->QuerySize( &cbSizeLow ));
|
|
|
|
pszTail += wsprintf( pszTail,
|
|
"Content-Length: %lu\r\n\r\n",
|
|
cbSizeLow );
|
|
|
|
pFile->SetHttpInfo( pszVariant, pszTail - pszVariant );
|
|
|
|
IF_DEBUG( REQUEST )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"BuildResponseHeader: Built the following header:\n%s",
|
|
pszResp ));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQUEST::ProcessISMAP
|
|
|
|
SYNOPSIS: Checks of the URL and passed parameters specify an
|
|
image mapping file
|
|
|
|
RETURNS: TRUE if successful, FALSE on error
|
|
|
|
NOTES: pchFile - Fully qualified name of file
|
|
pstrResp - Response to send if *pfHandled is FALSE
|
|
pfFound - TRUE if a mapping was found
|
|
pfHandled - Set to TRUE if no further processing is needed,
|
|
FALSE if pstrResp should be sent to the client
|
|
|
|
HISTORY:
|
|
Johnl 17-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQUEST::ProcessISMAP( CHAR * pchFile,
|
|
BUFFER * pbufResp,
|
|
BOOL * pfFound,
|
|
BOOL * pfHandled )
|
|
{
|
|
INT x, y;
|
|
TCHAR * pch = _strURLParams.QueryStr();
|
|
STR strURL;
|
|
|
|
*pfHandled = FALSE;
|
|
*pfFound = FALSE;
|
|
|
|
//
|
|
// Get the x and y cooridinates of the mouse click on the image
|
|
//
|
|
|
|
x = _tcstoul( pch,
|
|
NULL,
|
|
10 );
|
|
|
|
//
|
|
// Move past x and any intervening delimiters
|
|
//
|
|
|
|
while ( isdigit( *pch ))
|
|
pch++;
|
|
|
|
while ( *pch && !isdigit( *pch ))
|
|
pch++;
|
|
|
|
y = _tcstoul( pch,
|
|
NULL,
|
|
10 );
|
|
|
|
if ( !ImpersonateUser() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
if ( !SearchMapFile( pchFile,
|
|
&g_pTsvcInfo->GetTsvcCache(),
|
|
_tcpauth.GetUserHandle(),
|
|
x,
|
|
y,
|
|
&strURL,
|
|
pfFound,
|
|
_fAnonymous ))
|
|
{
|
|
RevertUser();
|
|
return FALSE;
|
|
}
|
|
|
|
RevertUser();
|
|
|
|
if ( !*pfFound )
|
|
{
|
|
if ( (pch = _HeaderList.FindValue( "Referer:" )) )
|
|
{
|
|
#if 0
|
|
// handle relative URL ( i.e. not fully qualified
|
|
// and not specifying an absolute path ).
|
|
// disabled for now.
|
|
PSTR pColon = strchr( pch, ':' );
|
|
PSTR pDelim = strchr( pch, '/' );
|
|
BOOL fValid;
|
|
|
|
// check for relative URL
|
|
|
|
if ( *pch != '/' && (pColon == NULL || pDelim < pColon) )
|
|
{
|
|
strURL.Copy( _strURL.QueryStr() );
|
|
PSTR pL = strURL.QueryStr();
|
|
|
|
// look for last '/'
|
|
int iL = strlen( pL );
|
|
while ( iL && pL[iL-1] != '/' )
|
|
--iL;
|
|
|
|
// combine request URL & relative referer URL
|
|
strURL.Resize( iL + strlen( pch ) + 1 );
|
|
strcpy( strURL.QueryStr() + iL, pch );
|
|
if ( !CanonURL( &strURL, &fValid ) )
|
|
return TRUE; // as if not found
|
|
}
|
|
else
|
|
#endif
|
|
strURL.Copy( (TCHAR *) pch );
|
|
}
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
TCP_REQUIRE( ::TsCloseHandle( g_pTsvcInfo->GetTsvcCache(),
|
|
_pGetFile ));
|
|
_pGetFile = NULL;
|
|
|
|
//
|
|
// If the found URL starts with a forward slash ("/foo/bar/doc.htm")
|
|
// and it doesn't contain a bookmark ('#')
|
|
// then the URL is local and we build a fully qualified URL to send
|
|
// back to the client.
|
|
// we assume it's a fully qualified URL ("http://foo/bar/doc.htm")
|
|
// and send the client a redirection notice to the mapped URL
|
|
//
|
|
|
|
if ( *strURL.QueryStr() == TEXT('/') )
|
|
{
|
|
#if 0
|
|
// disabled :
|
|
// we now always send a redirect
|
|
|
|
TCHAR * pch = strURL.QueryStr();
|
|
|
|
//
|
|
// Make sure there's no bookmark
|
|
//
|
|
if ( !(strchr( strURL.QueryStr(), '#' )) )
|
|
{
|
|
//
|
|
// Call OnURL to reparse the URL and set the members then
|
|
// call the verb again
|
|
//
|
|
|
|
if ( !ReprocessURL( pch ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*pfHandled = TRUE;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
//
|
|
// fully qualify the URL and send a
|
|
// redirect. Some browsers (emosaic) don't like doc relative
|
|
// URLs with bookmarks
|
|
//
|
|
|
|
STR strOldURL( strURL );
|
|
|
|
if ( !strOldURL.IsValid() ||
|
|
!strURL.Resize( strOldURL.QueryCB() + 50 ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// NOTE: We fully qualify the URL with the protocol (http or
|
|
// https) based on the port this request came in on. This means
|
|
// you cannot have a partial URL with a bookmark (which is how
|
|
// we got here) go from a secure part of the server to a
|
|
// nonsecure part of the server.
|
|
//
|
|
|
|
if ( IsSecurePort() ? (INT) QueryClientConn()->QueryPort()
|
|
!= HTTP_SSL_PORT
|
|
: (INT) QueryClientConn()->QueryPort() != 80 )
|
|
wsprintf( strURL.QueryStr(),
|
|
(IsSecurePort() ? "https://%s:%d%s" : "http://%s:%d%s"),
|
|
QueryHostAddr(),
|
|
(INT) QueryClientConn()->QueryPort(),
|
|
strOldURL.QueryStr() );
|
|
else
|
|
wsprintf( strURL.QueryStr(),
|
|
(IsSecurePort() ? "https://%s%s" : "http://%s%s"),
|
|
QueryHostAddr(),
|
|
strOldURL.QueryStr() );
|
|
if ( !BuildURLMovedResponse( pbufResp, &strURL ))
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !BuildURLMovedResponse( pbufResp, &strURL ))
|
|
return FALSE;
|
|
}
|
|
|
|
*pfFound = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: HTTP_REQ_BASE::BuildURLMovedResponse
|
|
|
|
SYNOPSIS: Builds a full request indicating an object has moved to
|
|
the location specified by URL
|
|
|
|
ENTRY: pbufResp - String to receive built response
|
|
pstrURL - New location of object, gets escaped
|
|
|
|
RETURNS: TRUE if successful, FALSE on error
|
|
|
|
NOTES: This routine doesn't support sending a Unicode doc moved
|
|
message
|
|
|
|
The Log status is set to HT_REDIRECT
|
|
|
|
HISTORY:
|
|
Johnl 17-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
BOOL HTTP_REQ_BASE::BuildURLMovedResponse( BUFFER * pbufResp,
|
|
STR * pstrURL )
|
|
{
|
|
STR strMovedMessage;
|
|
UINT cb;
|
|
CHAR * pszTail;
|
|
CHAR * pszResp;
|
|
DWORD cbURL;
|
|
|
|
|
|
if ( !g_pTsvcInfo->LoadStr( strMovedMessage, IDS_URL_MOVED ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure the response buffer is large enough
|
|
//
|
|
|
|
#define CB_FIXED_RESP 500
|
|
|
|
cbURL = pstrURL->QueryCB();
|
|
|
|
if ( (2 * cbURL + CB_FIXED_RESP) > pbufResp->QuerySize() )
|
|
{
|
|
if ( !pbufResp->Resize( 2 * cbURL + CB_FIXED_RESP ))
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// "HTTP/<ver> 302 Redirect"
|
|
//
|
|
|
|
if ( !BuildStatusLine( pbufResp,
|
|
HT_REDIRECT,
|
|
NO_ERROR ))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set the status to log here
|
|
//
|
|
|
|
SetLogStatus( HT_REDIRECT, NO_ERROR );
|
|
|
|
pszResp = (CHAR *) pbufResp->QueryPtr();
|
|
pszTail = pszResp + strlen( pszResp );
|
|
|
|
//
|
|
// "Location: <URL>"
|
|
//
|
|
|
|
pszTail += wsprintf( pszTail,
|
|
"Location: %s\r\n",
|
|
pstrURL->QueryStr() );
|
|
|
|
//
|
|
// "Server: <Server>/<version>
|
|
//
|
|
|
|
APPEND_VER_STR( pszTail );
|
|
|
|
//
|
|
// Content-Type, it's OK to assume all clients accept text/html
|
|
//
|
|
|
|
strcpy( pszTail,
|
|
"Content-Type: text/html\r\n" );
|
|
|
|
pszTail += sizeof( "Content-Type: text/html\r\n" ) - sizeof(CHAR);
|
|
|
|
//
|
|
// Calculate the bytes in the body of the message for Content-Length
|
|
//
|
|
|
|
cb = cbURL +
|
|
strMovedMessage.QueryCB() -
|
|
2; // Subtrace '%s' in strMovedMessage
|
|
|
|
if ( IsKeepConnSet() )
|
|
{
|
|
strcpy( pszTail,
|
|
"Connection: keep-alive\r\n" );
|
|
|
|
pszTail += sizeof( "Connection: keep-alive\r\n" ) - sizeof(CHAR);
|
|
}
|
|
|
|
|
|
//
|
|
// "Content-Length: <length>"
|
|
//
|
|
|
|
pszTail += wsprintf( pszTail,
|
|
"Content-Length: %lu\r\n\r\n",
|
|
cb );
|
|
|
|
//
|
|
// Figure the total length to see if we have enough room. Note we've
|
|
// already added the message body length. Add in the terminator.
|
|
//
|
|
|
|
cb += pszTail - pszResp + sizeof(TCHAR);
|
|
|
|
//
|
|
// Add the short HTML doc indicating the new location of the URL, watch
|
|
// for pointer shift if a resize is necessary
|
|
//
|
|
|
|
if ( !pbufResp->Resize( cb ) )
|
|
return FALSE;
|
|
|
|
if ( pbufResp->QueryPtr() != pszResp )
|
|
{
|
|
pszResp = (CHAR *) pbufResp->QueryPtr();
|
|
pszTail = pszResp + strlen( pszResp );
|
|
}
|
|
|
|
::wsprintf( pszTail,
|
|
strMovedMessage.QueryStr(),
|
|
pstrURL->QueryStr() );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: SearchMapFile
|
|
|
|
SYNOPSIS: Searches the given mapfile for a shape that contains
|
|
the passed cooridinates
|
|
|
|
ENTRY: pchFile - Fully qualified path to file
|
|
pTsvcCache - Cache ID
|
|
hToken - Impersonation token
|
|
x - x cooridinate
|
|
y - y cooridinate
|
|
pstrURL - receives URL indicated in the map file
|
|
pfFound - Set to TRUE if a mapping was found
|
|
|
|
RETURNS: TRUE if successful, FALSE on error
|
|
|
|
NOTES: This routine will attempt to cache the file. You must call
|
|
this function while impersonating the appropriate user
|
|
|
|
HISTORY:
|
|
Johnl 19-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
#define SKIPNONWHITE( pch ) while ( *pch && \
|
|
!ISWHITEA( *pch )) \
|
|
pch++;
|
|
|
|
#define SKIPWHITE( pch ) while ( ISWHITE( *pch ) || \
|
|
*pch == ')' || \
|
|
*pch == '(' ) \
|
|
pch++;
|
|
|
|
BOOL SearchMapFile( CHAR * pchFile,
|
|
TSVC_CACHE * pTsvcCache,
|
|
HANDLE hToken,
|
|
INT x,
|
|
INT y,
|
|
STR * pstrURL,
|
|
BOOL * pfFound,
|
|
BOOL fIsAnon )
|
|
{
|
|
DWORD BytesRead;
|
|
CHAR * pch;
|
|
CACHE_FILE_INFO CacheFileInfo;
|
|
CHAR * pchDefault = NULL;
|
|
CHAR * pchPoint = NULL;
|
|
CHAR * pchStart;
|
|
STR strDefaultURL;
|
|
BOOL fRet = TRUE;
|
|
DWORD cchUrl;
|
|
UINT dis;
|
|
UINT bdis = UINT(-1);
|
|
|
|
*pfFound = FALSE;
|
|
|
|
//
|
|
// Retrieve the '\0' terminated map file
|
|
//
|
|
|
|
if ( !CheckOutCachedFile( pchFile,
|
|
pTsvcCache,
|
|
hToken,
|
|
(BYTE **) &pch,
|
|
&BytesRead,
|
|
fIsAnon,
|
|
#ifdef JAPAN
|
|
&CacheFileInfo,
|
|
0 )) // no code conversion
|
|
#else
|
|
&CacheFileInfo ))
|
|
#endif
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Loop through the contents of the buffer and see what we've got
|
|
//
|
|
|
|
BOOL fComment = FALSE;
|
|
BOOL fIsNCSA = FALSE;
|
|
LPSTR pURL; // valid only if fIsNCSA is TRUE
|
|
|
|
while ( *pch )
|
|
{
|
|
fIsNCSA = FALSE;
|
|
|
|
//
|
|
// note: _tolower doesn't check case (tolower does)
|
|
//
|
|
|
|
switch ( (*pch >= 'A' && *pch <= 'Z') ? _tolower( *pch ) : *pch )
|
|
{
|
|
case '#':
|
|
fComment = TRUE;
|
|
break;
|
|
|
|
case '\r':
|
|
case '\n':
|
|
fComment = FALSE;
|
|
break;
|
|
|
|
//
|
|
// Rectangle
|
|
//
|
|
|
|
case 'r':
|
|
case 'o':
|
|
if ( !fComment &&
|
|
(!_strnicmp( "rect", pch, 4 )
|
|
// BUGBUG handles oval as a rect, as they are using
|
|
// the same specification format. Should do better.
|
|
|| !_strnicmp( "oval", pch, 4 )) )
|
|
{
|
|
INT x1, y1, x2, y2;
|
|
|
|
SKIPNONWHITE( pch );
|
|
pURL = pch;
|
|
SKIPWHITE( pch );
|
|
|
|
if ( !isdigit(*pch) && *pch!='(' )
|
|
{
|
|
fIsNCSA = TRUE;
|
|
SKIPNONWHITE( pch );
|
|
}
|
|
|
|
x1 = GetNumber( &pch );
|
|
y1 = GetNumber( &pch );
|
|
x2 = GetNumber( &pch );
|
|
y2 = GetNumber( &pch );
|
|
|
|
if ( x >= x1 && x < x2 &&
|
|
y >= y1 && y < y2 )
|
|
{
|
|
if ( fIsNCSA )
|
|
pch = pURL;
|
|
goto Found;
|
|
}
|
|
|
|
//
|
|
// Skip the URL
|
|
//
|
|
|
|
if ( !fIsNCSA )
|
|
{
|
|
SKIPWHITE( pch );
|
|
SKIPNONWHITE( pch );
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
//
|
|
// Circle
|
|
//
|
|
|
|
case 'c':
|
|
if ( !fComment &&
|
|
!_strnicmp( "circ", pch, 4 ))
|
|
{
|
|
INT xCenter, yCenter, xEdge, yEdge;
|
|
INT r1, r2;
|
|
|
|
SKIPNONWHITE( pch );
|
|
pURL = pch;
|
|
SKIPWHITE( pch );
|
|
|
|
if ( !isdigit(*pch) && *pch!='(' )
|
|
{
|
|
fIsNCSA = TRUE;
|
|
SKIPNONWHITE( pch );
|
|
}
|
|
|
|
//
|
|
// Get the center and edge of the circle
|
|
//
|
|
|
|
xCenter = GetNumber( &pch );
|
|
yCenter = GetNumber( &pch );
|
|
|
|
xEdge = GetNumber( &pch );
|
|
yEdge = GetNumber( &pch );
|
|
|
|
//
|
|
// If there's a yEdge, then we have the NCSA format, otherwise
|
|
// we have the CERN format, which specifies a radius
|
|
//
|
|
|
|
if ( yEdge != -1 )
|
|
{
|
|
r1 = ((yCenter - yEdge) * (yCenter - yEdge)) +
|
|
((xCenter - xEdge) * (xCenter - xEdge));
|
|
|
|
r2 = ((yCenter - y) * (yCenter - y)) +
|
|
((xCenter - x) * (xCenter - x));
|
|
|
|
if ( r2 <= r1 )
|
|
{
|
|
if ( fIsNCSA )
|
|
pch = pURL;
|
|
goto Found;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
INT radius;
|
|
|
|
//
|
|
// CERN format, third param is the radius
|
|
//
|
|
|
|
radius = xEdge;
|
|
|
|
if ( SQR( xCenter - x ) + SQR( yCenter - y ) <=
|
|
SQR( radius ))
|
|
{
|
|
if ( fIsNCSA )
|
|
pch = pURL;
|
|
goto Found;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Skip the URL
|
|
//
|
|
|
|
if ( !fIsNCSA )
|
|
{
|
|
SKIPWHITE( pch );
|
|
SKIPNONWHITE( pch );
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
//
|
|
// Polygon
|
|
//
|
|
|
|
case 'p':
|
|
if ( !fComment &&
|
|
!_strnicmp( "poly", pch, 4 ))
|
|
{
|
|
double pgon[MAXVERTS][2];
|
|
DWORD i = 0;
|
|
CHAR * pchLast;
|
|
|
|
SKIPNONWHITE( pch );
|
|
pURL = pch;
|
|
SKIPWHITE( pch );
|
|
|
|
if ( !isdigit(*pch) && *pch!='(' )
|
|
{
|
|
fIsNCSA = TRUE;
|
|
SKIPNONWHITE( pch );
|
|
}
|
|
|
|
//
|
|
// Build the array of points
|
|
//
|
|
|
|
while ( *pch && *pch != '\r' && *pch != '\n' )
|
|
{
|
|
pgon[i][0] = GetNumber( &pch );
|
|
|
|
//
|
|
// Did we hit the end of the line (and go past the URL)?
|
|
//
|
|
|
|
if ( pgon[i][0] != -1 )
|
|
{
|
|
pgon[i][1] = GetNumber( &pch );
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
pgon[i][X] = -1;
|
|
|
|
if ( pointinpoly( x, y, pgon ))
|
|
{
|
|
if ( fIsNCSA )
|
|
pch = pURL;
|
|
goto Found;
|
|
}
|
|
|
|
//
|
|
// Skip the URL
|
|
//
|
|
|
|
if ( !fIsNCSA )
|
|
{
|
|
SKIPWHITE( pch );
|
|
SKIPNONWHITE( pch );
|
|
}
|
|
continue;
|
|
}
|
|
else if ( !fComment &&
|
|
!_strnicmp( "point", pch, 5 ))
|
|
{
|
|
INT x1,y1;
|
|
|
|
SKIPNONWHITE( pch );
|
|
pURL = pch;
|
|
SKIPWHITE( pch );
|
|
SKIPNONWHITE( pch );
|
|
|
|
x1 = GetNumber( &pch );
|
|
y1 = GetNumber( &pch );
|
|
|
|
x1 -= x;
|
|
y1 -= y;
|
|
dis = x1*x1 + y1*y1;
|
|
if ( dis < bdis )
|
|
{
|
|
pchPoint = pURL;
|
|
bdis = dis;
|
|
}
|
|
}
|
|
break;
|
|
|
|
//
|
|
// Default URL
|
|
//
|
|
|
|
case 'd':
|
|
if ( !fComment &&
|
|
!_strnicmp( "def", pch, 3 ) )
|
|
{
|
|
//
|
|
// Skip "default" (don't skip white space)
|
|
//
|
|
|
|
SKIPNONWHITE( pch );
|
|
|
|
pchDefault = pch;
|
|
|
|
//
|
|
// Skip URL
|
|
//
|
|
|
|
SKIPWHITE( pch );
|
|
SKIPNONWHITE( pch );
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
pch++;
|
|
SKIPWHITE( pch );
|
|
}
|
|
|
|
//
|
|
// If we didn't find a mapping and a default was specified, use
|
|
// the default URL
|
|
//
|
|
|
|
if ( pchPoint )
|
|
{
|
|
pch = pchPoint;
|
|
goto Found;
|
|
}
|
|
|
|
if ( pchDefault )
|
|
{
|
|
pch = pchDefault;
|
|
goto Found;
|
|
}
|
|
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
TCP_PRINT(( DBG_CONTEXT,
|
|
"[SearchMapFile] No mapping found for (%d,%d)\n",
|
|
x,
|
|
y ));
|
|
}
|
|
|
|
goto Exit;
|
|
|
|
Found:
|
|
|
|
//
|
|
// pch should point to the white space immediately before the URL
|
|
//
|
|
|
|
SKIPWHITE( pch );
|
|
|
|
pchStart = pch;
|
|
|
|
SKIPNONWHITE( pch );
|
|
|
|
//
|
|
// Determine the length of the URL and copy it out
|
|
//
|
|
|
|
cchUrl = pch - pchStart;
|
|
|
|
if ( !pstrURL->Resize( (cchUrl + 1) * sizeof(TCHAR) ))
|
|
{
|
|
fRet = FALSE;
|
|
|
|
goto Exit;
|
|
}
|
|
|
|
memcpy( pstrURL->QueryStr(),
|
|
pchStart,
|
|
cchUrl );
|
|
|
|
pstrURL->QueryStr()[ cchUrl ] = '\0';
|
|
|
|
if ( !pstrURL->Unescape() )
|
|
{
|
|
fRet = FALSE;
|
|
goto Exit;
|
|
}
|
|
|
|
IF_DEBUG( PARSING )
|
|
{
|
|
TCP_PRINT((DBG_CONTEXT,
|
|
"[SearchMapFile] Mapping for (%d,%d) is %s\n",
|
|
x,
|
|
y,
|
|
pstrURL->QueryStr() ));
|
|
}
|
|
|
|
*pfFound = TRUE;
|
|
|
|
Exit:
|
|
TCP_REQUIRE( CheckInCachedFile( pTsvcCache,
|
|
&CacheFileInfo ));
|
|
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
int pointinpoly(int point_x, int point_y, double pgon[MAXVERTS][2])
|
|
{
|
|
int i, numverts, inside_flag, xflag0;
|
|
int crossings;
|
|
double *p, *stop;
|
|
double tx, ty, y;
|
|
|
|
for (i = 0; pgon[i][X] != -1 && i < MAXVERTS; i++)
|
|
;
|
|
|
|
numverts = i;
|
|
crossings = 0;
|
|
|
|
tx = (double) point_x;
|
|
ty = (double) point_y;
|
|
y = pgon[numverts - 1][Y];
|
|
|
|
p = (double *) pgon + 1;
|
|
|
|
if ((y >= ty) != (*p >= ty))
|
|
{
|
|
if ((xflag0 = (pgon[numverts - 1][X] >= tx)) == (*(double *) pgon >= tx))
|
|
{
|
|
if (xflag0)
|
|
crossings++;
|
|
}
|
|
else
|
|
{
|
|
crossings += (pgon[numverts - 1][X] - (y - ty) *
|
|
(*(double *) pgon - pgon[numverts - 1][X]) /
|
|
(*p - y)) >= tx;
|
|
}
|
|
}
|
|
|
|
stop = pgon[numverts];
|
|
|
|
for (y = *p, p += 2; p < stop; y = *p, p += 2)
|
|
{
|
|
if (y >= ty)
|
|
{
|
|
while ((p < stop) && (*p >= ty))
|
|
p += 2;
|
|
|
|
if (p >= stop)
|
|
break;
|
|
|
|
if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx))
|
|
{
|
|
if (xflag0)
|
|
crossings++;
|
|
}
|
|
else
|
|
{
|
|
crossings += (*(p - 3) - (*(p - 2) - ty) *
|
|
(*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while ((p < stop) && (*p < ty))
|
|
p += 2;
|
|
|
|
if (p >= stop)
|
|
break;
|
|
|
|
if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx))
|
|
{
|
|
if (xflag0)
|
|
crossings++;
|
|
}
|
|
else
|
|
{
|
|
crossings += (*(p - 3) - (*(p - 2) - ty) *
|
|
(*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
|
|
}
|
|
}
|
|
}
|
|
|
|
inside_flag = crossings & 0x01;
|
|
return (inside_flag);
|
|
}
|
|
|
|
/*******************************************************************
|
|
|
|
NAME: GetNumber
|
|
|
|
SYNOPSIS: Scans for the beginning of a number and places the
|
|
pointer after the found number
|
|
|
|
|
|
ENTRY: ppch - Place to begin. Will be set to character after
|
|
the last digit of the found number
|
|
|
|
RETURNS: Integer value of found number (or -1 if not found)
|
|
|
|
HISTORY:
|
|
Johnl 19-Sep-1994 Created
|
|
|
|
********************************************************************/
|
|
|
|
INT GetNumber( CHAR * * ppch )
|
|
{
|
|
CHAR * pch = *ppch;
|
|
INT n;
|
|
|
|
//
|
|
// Make sure we don't get into the URL
|
|
//
|
|
|
|
while ( *pch &&
|
|
!isdigit( *pch ) &&
|
|
!isalpha( *pch ) &&
|
|
*pch != '/' &&
|
|
*pch != '\r' &&
|
|
*pch != '\n' )
|
|
{
|
|
pch++;
|
|
}
|
|
|
|
if ( !isdigit( *pch ) )
|
|
return -1;
|
|
|
|
n = atoi( pch );
|
|
|
|
while ( isdigit( *pch ))
|
|
pch++;
|
|
|
|
*ppch = pch;
|
|
|
|
return n;
|
|
}
|
|
|