/**********************************************************************/
/**                       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    160

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


#define MIN_ADDL_BUF_HDR_SIZE_CACHED    (sizeof("Date: ") - 1 + \
                                   sizeof("Mon, 00 Jan 0000 00:00:00 GMT\r\n") - 1 +\
                                   MAX_SIZE_HTTP_INFO)

#define MIN_ADDL_BUF_HDR_SIZE       (sizeof("Content-Type: \r\n") - 1 + \
                                    sizeof("Accept-Ranges: bytes\r\n") - 1 + \
                                    sizeof("Last-Modified: ") - 1 +\
                                    sizeof("Mon, 00 Jan 0000 00:00:00 GMT\r\n") - 1 +\
                                    sizeof("ETag: W/\r\n") - 1 + \
                                    MAX_ETAG_BUFFER_LENGTH + \
                                    sizeof("Content-Length: 4294967295\r\n\r\n") - 1)


#define RANGE_ADDL_BUF_HDR_SIZE     (sizeof("Content-Type: multipart/x-byteranges; boundary=")\
                                    - 1 + sizeof(BOUNDARY_STRING) - 1 + \
                                    sizeof("Date: ") - 1 + \
                                    sizeof("Mon, 00 Jan 0000 00:00:00 GMT\r\n") - 1 +\
                                    sizeof("Accept-Ranges: bytes\r\n") - 1 + \
                                    sizeof("Last-Modified: ") - 1 +\
                                    sizeof("Mon, 00 Jan 0000 00:00:00 GMT\r\n") - 1 +\
                                    sizeof("ETag: W/\r\n") - 1 + \
                                    MAX_ETAG_BUFFER_LENGTH + \
                                    sizeof("Content-Length: 4294967295\r\n\r\n") - 1)

//
//  Private prototypes.
//

BOOL SearchMapFile( LPTS_OPEN_FILE_INFO gFile,
                    CHAR *              pchFile,
                    TSVC_CACHE *        pTsvcCache,
                    HANDLE              hToken,
                    INT                 x,
                    INT                 y,
                    STR *               pstrURL,
                    BOOL *              pfFound,
                    BOOL                fmayCacheAccessToken );

int pointinpoly(int x, int y, double pgon[MAXVERTS][2]);

INT GetNumber( CHAR * * ppch );

DWORD NbDigit( DWORD dw );

//
//  Public functions.
//


//
//  Private functions.
//


// Forward references.
extern BOOL DisposeOpenURIFileInfo(IN  PVOID   pvOldBlock);



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

LPSYSTEMTIME
MinSystemTime(
    LPSYSTEMTIME Now,
    LPSYSTEMTIME Other
    )

/*++

Routine Descriptions:

    Compare to systemtime dates, and return a pointer to the earlier one.

Arguments:

    Now     - The current date.
    Other   - Other date to compare.

Return Value:

    Pointer to the earlier of Now and Other.

--*/
{
    if (Now->wYear > Other->wYear)
    {
        return Other;
    }
    else
    {
        if (Now->wYear == Other->wYear)
        {
            if (Now->wMonth > Other->wMonth)
            {
                return Other;
            }
            else
            {
                if (Now->wMonth == Other->wMonth)
                {
                    if (Now->wDay > Other->wDay)
                    {
                        return Other;
                    }
                    else
                    {
                        if (Now->wDay == Other->wDay)
                        {
                            if (Now->wHour > Other->wHour)
                            {
                                return Other;
                            }
                            else
                            {
                                if (Now->wHour == Other->wHour)
                                {
                                    if (Now->wMinute > Other->wMinute)
                                    {
                                        return Other;
                                    }
                                    else
                                    {
                                        if (Now->wMinute == Other->wMinute)
                                        {
                                            if (Now->wSecond > Other->wSecond)
                                            {
                                                return Other;
                                            }
                                            else
                                            {
                                                if (Now->wSecond == Other->wSecond)
                                                {
                                                    if (Now->wMilliseconds >= Other->wMilliseconds)
                                                    {
                                                        return Other;
                                                    }
                                                    else
                                                    {
                                                        return Now;
                                                    }
                                                }
                                                else
                                                {
                                                    return Now;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            return Now;
                                        }
                                    }
                                }
                                else
                                {
                                return Now;
                                }
                            }
                        }
                        else
                        {
                            return Now;
                        }
                    }
                }
                else
                {
                    return Now;
                }
            }
        }
        else
        {
            return Now;
        }
    }

}


BOOL
HTTP_REQUEST::DoTraceCk(
    BOOL * pfFinished
    )
/*++

Routine Description:

    Handle a TRACECK request ( used by WolfPack as non-logged TRACE request )
    Basically echo the client request as a message body

Arguments:

    None

Return Value:

    TRUE if success, else FALSE

--*/
{
    return DoTrace( pfFinished );
}


BOOL
HTTP_REQUEST::DoTrace(
    BOOL * pfFinished
    )
/*++

Routine Description:

    Handle a TRACE request ( cf HTTP 1.1 spec )
    Basically echo the client request as a message body

Arguments:

    None

Return Value:

    TRUE if success, else FALSE

--*/
{
    BUFFER buHeader;
    BOOL fHandled = FALSE;
    LPSTR pszResp;
    LPSTR pszTail;
    UINT cHeaderSize;
    UINT cMsgSize;
    DWORD dwWrt;
    DWORD cbContentLength;


    //
    // build response header
    //

    if ( !buHeader.Resize( MIN_BUFFER_SIZE_FOR_HEADERS ) )
    {
        return FALSE;
    }

    if ( !BuildBaseResponseHeader( &buHeader,
                                   &fHandled,
                                   NULL,
                                   0 ))
    {
        return FALSE;
    }

    if ( fHandled )
    {
        return TRUE;
    }

    cHeaderSize = strlen( (PSTR)buHeader.QueryPtr() );

    if (!buHeader.Resize(cHeaderSize + sizeof("Content-Type: message/http\r\n")
                        - 1 + sizeof("Content-Length:  4294967295\r\n\r\n") - 1))
    {
        return FALSE;
    }

    pszResp = (PSTR) buHeader.QueryPtr();
    pszTail = pszResp + cHeaderSize;
    cMsgSize = _cbClientRequest + _cbContentLength;

    APPEND_STRING( pszTail, "Content-Type: message/http\r\n" );
    pszTail += wsprintf( pszTail,
             "Content-Length: %lu\r\n\r\n",
              cMsgSize );

    SetState( HTR_DONE, HT_OK, NO_ERROR );

    //
    // send response header
    //

    if ( !SendHeader( (CHAR *) buHeader.QueryPtr(),
                      (DWORD) (pszTail - (PSTR)buHeader.QueryPtr()),
                      IO_FLAG_SYNC,
                      pfFinished ))
    {
        return FALSE;
    }

    if ( *pfFinished )
        return TRUE;

    //
    // send echo of request headers
    // NOTE : we're using the original client buffer because a filter may have
    // modified the server response buffer
    //
    
    cbContentLength = _cbContentLength;

    if (! WriteFile( (PSTR) _bufClientRequest.QueryPtr(),
                     _cbClientRequest,
                     &dwWrt,
                     cbContentLength ? IO_FLAG_SYNC : IO_FLAG_ASYNC ) )
    {
        return FALSE;
    }

    //
    // send echo of request message body
    //

    if ( cbContentLength &&
        !WriteFile( (PSTR)_bufClientRequest.QueryPtr()
                        + _cbClientRequest,
                    cbContentLength,
                    NULL,
                    IO_FLAG_ASYNC ) )
    {
        return FALSE;
    }

    return TRUE;
}

/*******************************************************************

    NAME:       HTTP_REQUEST::DoGet

    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.

                If no further processing is needed (and the caller needs to
                setup for the next request), set *pfFinished to TRUE

    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::DoGet(
    BOOL * pfFinished
    )
{
    BOOL                        fHandled = FALSE;
    DWORD                       cbSizeLow;
    DWORD                       cbSizeHigh;
    BOOL                        fHidden;
    BOOL                        fMatches;
    DWORD                       dwMask;
    BOOL                        fSendFile = (QueryVerb() == HTV_GET);
    PW3_SERVER_INSTANCE         pInstance = QueryW3Instance();
    DWORD                       err;

    //
    //  Open the file (or directory)
    //

    IF_DEBUG( REQUEST )
    {
        DBGPRINTF((DBG_CONTEXT,
                  "SendFileOrDir: Opening %s\n",
                   _strPhysicalPath.QueryStr()));
    }

    if ( !IS_ACCESS_ALLOWED(READ) && !_fPossibleDefaultExecute )
    {
        DBGPRINTF((DBG_CONTEXT,
                    "ACCESS_DENIED. No Read Permission for URL %s (Physical Path: %s)\n",
                   _strURL.QueryStr(),
                   _strPhysicalPath.QueryStr()));

        SetState( HTR_DONE, HT_FORBIDDEN, ERROR_ACCESS_DENIED );

        Disconnect( HT_FORBIDDEN,
                    IDS_READ_ACCESS_DENIED,
                    FALSE,
                    pfFinished );

        return TRUE;
    }

    if (*_strURL.QueryStr() == '*')
    {
        // Don't allow GETs on the server URL.
        SetState( HTR_DONE, HT_BAD_REQUEST, ERROR_INVALID_PARAMETER );

        Disconnect( HT_BAD_REQUEST,
                    NULL,
                    FALSE,
                    pfFinished );

        return TRUE;
    }

    if( _strURL.QueryCCH() > MAX_URI_LENGTH )
    {
        SetState( HTR_DONE, HT_URL_TOO_LONG, ERROR_INSUFFICIENT_BUFFER);
        Disconnect( HT_URL_TOO_LONG, IDS_URL_TOO_LONG, FALSE, pfFinished);
        return TRUE;
    }
        
    if ( _pURIInfo == NULL )
    {
        if ( !CacheUri( QueryW3Instance(),
                        &_pURIInfo,
                        _pMetaData,
                        _strURL.QueryStr(),
                        _strURL.QueryCCH(),
                        &_strPhysicalPath,
                        &_strUnmappedPhysicalPath ) )
        {
            return FALSE;
        }
    }
    
    if ( _pMetaData->QueryDoCache() && !_pMetaData->QueryVrError() )
    {
        _pGetFile = TsCreateFileFromURI(pInstance->GetTsvcCache(),
                                        _pURIInfo,
                                        QueryImpersonationHandle( FALSE ),
                                        TS_NOT_IMPERSONATED
                                        | ((_fClearTextPass || _fAnonymous) ? 0 :
                                            TS_DONT_CACHE_ACCESS_TOKEN)
                                        | (DoAccessCheckOnUrl()
                                            ? 0 : TS_NO_ACCESS_CHECK ),
                                        &err
                                        );
    }
    else
    {
        _pGetFile = TsCreateFile( pInstance->GetTsvcCache(),
                                  _strPhysicalPath.QueryStr(),
                                  QueryImpersonationHandle(),
                                  TS_NOT_IMPERSONATED );

        if (_pGetFile == NULL) {
            err = GetLastError();
        }
    }

    if ( _pGetFile == NULL ) {

        IF_DEBUG(ERROR) {
            DBGPRINTF((DBG_CONTEXT,
                  "DoGetHeadAux: Failed to open %s, error %d\n",
                   _strURL.QueryStr(),
                   err ));
        }

        if ( err == ERROR_FILE_NOT_FOUND ||
             err == ERROR_PATH_NOT_FOUND ||
             err == ERROR_INVALID_NAME )
        {
            SetState( HTR_DONE, HT_NOT_FOUND, err );
            Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished );
            return TRUE;
        }

        if (err == ERROR_INSUFFICIENT_BUFFER)
        {
            // Really means the file name was too long.

            SetState(HTR_DONE, HT_URL_TOO_LONG, err);
            Disconnect(HT_URL_TOO_LONG, IDS_URL_TOO_LONG, FALSE, pfFinished );
            return TRUE;
        }

        if ( err == ERROR_ACCESS_DENIED )
        {
            DBGPRINTF((DBG_CONTEXT,
                        "ACCESS_DENIED. ACLs restricting acess to URL %s (Physical Path: %s)\n",
                        _strURL.QueryStr(), _strPhysicalPath.QueryStr()));

            DBGPRINTF((DBG_CONTEXT,
                        "User: %s, AuthorizationType: %s\n",
                        _strUserName.QueryStr(), _strAuthType.QueryStr()));

            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) )
    {
        DWORD dirBrowFlags;

        if ( fHidden )
        {
            SetState( HTR_DONE, HT_NOT_FOUND, ERROR_PATH_NOT_FOUND );
            Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished );
            return TRUE;
        }

        //
        //  If a default file is in the directory and the feature is enabled,
        //  then return the default file to the user
        //

        dirBrowFlags = QueryDirBrowseFlags();

        if ( dirBrowFlags & DIRBROW_LOADDEFAULT )
        {
            if ( !CheckDefaultLoad( &_strPhysicalPath, &fHandled, pfFinished ))
            {
                if ( GetLastError() == ERROR_ACCESS_DENIED )
                {
                    SetDeniedFlags( SF_DENIED_RESOURCE );
                }

                return FALSE;
            }

            if ( fHandled || *pfFinished )
            {
                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( _strPhysicalPath, QueryRespBuf(), pfFinished );
        }

        DBGPRINTF((DBG_CONTEXT,
                  "[DoDirList] Denying request for directory browsing\n"));

        SetLogStatus( HT_FORBIDDEN, ERROR_ACCESS_DENIED );

        Disconnect( HT_FORBIDDEN, IDS_DIR_LIST_DENIED, FALSE, pfFinished );
        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( _pGetFile,
                            _strPhysicalPath.QueryStr(),
                            QueryRespBuf(),
                            &fFound,
                            &fHandled ))
        {
            return FALSE;
        }

        if ( fHandled )
        {
            return TRUE;
        }

        if ( fFound )
        {
            SetState( HTR_DONE, HT_OK, NO_ERROR );

            return SendHeader( QueryRespBufPtr(),
                               (DWORD) -1,
                               IsKeepConnSet() ?
                               (IO_FLAG_ASYNC | IO_FLAG_AND_RECV) :
                               IO_FLAG_ASYNC,
                               pfFinished );
        }
    }

    if ( fHidden )
    {
        SetState( HTR_DONE, HT_NOT_FOUND, ERROR_FILE_NOT_FOUND );
        Disconnect( HT_NOT_FOUND, NO_ERROR, FALSE, pfFinished );
        return TRUE;
    }

    //
    //  At this point we know the user wants to retrieve the file
    //


    // If we're currently using a weak ETag for this file, try to make it
    // strong.
    if (_pGetFile->WeakETag())
    {
        _pGetFile->MakeStrongETag();
    }

    //
    //  If the client sent an If-* modifier, check it now. Skip if Custom Error
    //

    if ( _fIfModifier && !_bProcessingCustomError)
    {
        BOOL    bReturn;

        if (CheckPreconditions(_pGetFile, pfFinished, &bReturn))
        {
            return bReturn;

        }
    }

    TCP_REQUIRE( _pGetFile->QuerySize( &cbSizeLow,
                                       &cbSizeHigh ));

    // check if range requested

    DWORD dwOffset;
    DWORD dwSizeToSend;
    BOOL fIsNxRange;


    _fAcceptRange = pInstance->IsAcceptByteRanges();

    if ( _fAcceptRange && !_strRange.IsEmpty() && fSendFile )
    {
        ProcessRangeRequest( &_strPhysicalPath,
                &dwOffset,
                &dwSizeToSend,
                &fIsNxRange );

        //
        // Error HT_RANGE_NOT_SATISFIABLE if we saw no valid ranges and we saw an
        // unsatisfiable byte range and no If-Range header was sent.
        //

        if ( (!_fProcessByteRange) &&  _fUnsatisfiableByteRange &&
             ( NULL == _HeaderList.FastMapQueryValue(HM_IFR)))
        {
            CHAR    ach[17];

            STR * pstrAdditionalHeader = QueryAdditionalRespHeaders();

            _itoa( cbSizeLow, ach, 10 );

            pstrAdditionalHeader->Append("Content-Range: bytes */");
            pstrAdditionalHeader->Append(ach);
            pstrAdditionalHeader->Append("\r\n");

            SetState( HTR_DONE, HT_RANGE_NOT_SATISFIABLE, NO_ERROR );
            Disconnect( HT_RANGE_NOT_SATISFIABLE, NO_ERROR, FALSE, pfFinished );
            return TRUE;
        }

        if ( !BuildResponseHeader( QueryRespBuf(),
                                   &_strPhysicalPath,
                                   _pGetFile,
                                   &fHandled,
                                   NULL,
                                   pfFinished ))
        {
            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(),
                               &_strPhysicalPath,
                               _pGetFile,
                               &fHandled,
                               pfFinished ))
    {
        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 );

        return SendHeader( QueryRespBufPtr(),
                           QueryRespBufCB(),
                           (IsKeepConnSet() &&
                            (_cbBytesReceived == _cbClientRequest)) ?
                           (IO_FLAG_ASYNC | IO_FLAG_AND_RECV) :
                           IO_FLAG_ASYNC,
                           pfFinished );
    }

    //
    //  Refuse requests for files greater then four gigs
    //

    if ( cbSizeHigh )
    {
        SetState( HTR_DONE, HT_NOT_SUPPORTED, ERROR_NOT_SUPPORTED );
        Disconnect( HT_NOT_SUPPORTED, NO_ERROR, FALSE, pfFinished );
        return TRUE;
    }

    //
    //  Notify filters of the headers we're about to send
    //

    if ( _Filter.IsNotificationNeeded( SF_NOTIFY_SEND_RESPONSE,
                                       IsSecurePort() ))
    {
        if ( !_Filter.NotifySendHeaders( QueryRespBufPtr(),
                                         pfFinished,
                                         NULL,
                                         QueryRespBuf() ))
        {
            return FALSE;
        }

        if ( *pfFinished )
        {
            return TRUE;
        }
    }


    if ( _fProcessByteRange )
    {
        if ( fIsNxRange )
        {
            SetState( HTR_RANGE, HT_RANGE, NO_ERROR );
        }
        return SendRange( QueryRespBufCB(), dwOffset, dwSizeToSend, !fIsNxRange );
    }
    else
    {
        DWORD dwIOFlags = IO_FLAG_ASYNC;

        //
        //  If a filter needs a send response notification, keep the socket open
        //  and make sure we receive the TransmitFile completion
        //  otherwise if this is not a keep-alive connection, tell ATQ to
        //  disconnect after sending the file
        //

        if ( _Filter.IsNotificationNeeded( SF_NOTIFY_END_OF_REQUEST,
                                           IsSecurePort() ))
        {
            dwIOFlags |= IO_FLAG_NO_RECV;
        }
        else if ( !IsKeepConnSet() )
        {
            dwIOFlags |= TF_DISCONNECT | TF_REUSE_SOCKET;
        }

        SetState( HTR_DONE, HT_OK, NO_ERROR );

        //
        // If we've still got unprocessed data left in the buffer, don't
        // allow a receive after the transmit file.
        //
        if (_cbBytesReceived > _cbClientRequest)
        {
            dwIOFlags |= IO_FLAG_NO_RECV;
        }

        if ( !TransmitFile( _pGetFile,
                            NULL,
                            0,
                            cbSizeLow,
                            dwIOFlags,
                            QueryRespBufPtr(),
                            QueryRespBufCB() ,
                            QueryMetaData()->QueryFooter(),
                            QueryMetaData()->QueryFooterLength())
                            )
        {
            DBGPRINTF((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( (UCHAR)(*pRng) ) )
    {
        int c;
        while ( (c = *pRng) && isdigit( (UCHAR)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

--*/
{

    DWORD       cbSizeLow;
    DWORD       cbSizeHigh;

    FILETIME    tm;

    BOOL        fEntireFile;        // Indicates: Skip all Range headers. Send Entire File
    BOOL        fIsLastRange;       // Indicates: No valid range after this.

    //
    // Check range specified & optional UnlessModifiedSince
    //

    TCP_REQUIRE( _pGetFile->QueryLastWriteTime( &tm ));

    TCP_REQUIRE( _pGetFile->QuerySize( &cbSizeLow,
                                       &cbSizeHigh ));

    if ( !_liUnlessModifiedSince.QuadPart ||
         *(LONGLONG*)&tm <= _liUnlessModifiedSince.QuadPart )
    {
        //
        // Run through all valid ranges
        //

        DWORD cRanges           = 0;
        DWORD dwContentLength   = 0;
        DWORD dwFirstRangeEnd   = 0;
        DWORD dwFx              = 0;

        while ( ScanRange(  &_dwRgNxOffset,
                            &_dwRgNxSizeToSend,
                            &fEntireFile,
                            &fIsLastRange ))
        {
            _fProcessByteRange = TRUE;              // At least one range to process

            if (fEntireFile)
            {
                //
                // Ignore Range headers. Send Full File.
                //

                _fUnsatisfiableByteRange  = FALSE;
                _fProcessByteRange        = FALSE;
                return;
            }

            cRanges++;

            if ( 1 == cRanges)
            {
                //
                // First valid range. Special processing
                //

                *pdwOffset      = _dwRgNxOffset;
                *pdwSizeToSend  =  _cbMimeMultipart = _dwRgNxSizeToSend;

                if (!fIsLastRange)
                {
                    //
                    // There are more ranges left in the header. However they may or
                    // may not be syntactically valid or satisfiable.
                    //

                    SelectMimeMapping( &_strReturnMimeType,
                                       pstrPath->QueryStr(),
                                       _pMetaData);

                    dwFirstRangeEnd = _iRangeIdx;         // Save this for restoring later.

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

                    dwContentLength =  sizeof(BOUNDARY_STRING) - 1
                                        + _dwRgNxSizeToSend
                                        + sizeof(g_achMMimeTypeFmt) - 1
                                        + sizeof(g_achMMimeRangeFmt) - 1
                                        + NbDigit( _dwRgNxOffset )
                                        + NbDigit( _dwRgNxOffset + _dwRgNxSizeToSend - 1 )
                                        + dwFx;
                }
                else
                {
                    break;
                }
            }
            else
            {
                //
                // We need to send a multipart response.
                //

                dwContentLength +=  sizeof(BOUNDARY_STRING) - 1
                                        + _dwRgNxSizeToSend
                                        + sizeof(g_achMMimeTypeFmt) - 1
                                        + sizeof(g_achMMimeRangeFmt) - 1
                                        + NbDigit( _dwRgNxOffset )
                                        + NbDigit( _dwRgNxOffset + _dwRgNxSizeToSend - 1 )
                                        + dwFx;

            }
        }

        if (cRanges > 1)
        {
            //
            // adjust Content-Length because initial delimiter is part of the header delimiter
            //

            dwContentLength += sizeof(LAST_BOUNDARY_STRING) - ADJ_FIRST - 1;
            _fMimeMultipart = TRUE;
            _cbMimeMultipart = dwContentLength;

            *pfIsNxRange = TRUE;

            //
            // Restore the end of Range for subsequent processing
            //

            _iRangeIdx = dwFirstRangeEnd;

        }
        else
        {
            *pfIsNxRange = FALSE;
        }
    }
}

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     = 0;
    DWORD      cbSizeToSend = 0;
    BOOL       fEndOfRange  = FALSE;
    BOOL       fInvalidRange;
    BOOL       fEntireFile  = FALSE;
    int        c;

    TCP_REQUIRE( _pGetFile->QuerySize( &cbSizeLow,
                                       &cbSizeHigh ));
    LPSTR pRng = _strRange.QueryStr() + _iRangeIdx;

    //
    // Rules for processing ranges
    //
    // If there is any Syntactically Invalid Byte-Range in the request, then the header
    // must be ignored. Return code is 200 with entire body (i.e. *pfEntireFile = TRUE).
    //
    // If the request is Syntactically Valid & any element is satisfiable, the partial
    // data for the satisfiable portions should be sent with return code HT_PARTIAL_CONTENT.
    //
    // If the request is Syntactically Valid & all elements are unsatisfiable and there
    // is no If-Range header the return error code is HT_RANGE_NOT_SATISFIABLE (416)

    do
    {
        fInvalidRange = FALSE;

        //
        // Skip to begining of next range
        //

        while ( (c=*pRng) && (' '==c) )
        {
            ++pRng;
        }

        //
        // Test for no range
        //

        if ( *pRng == '\0' )
        {
            _iRangeIdx      = (DWORD)(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 ( *pRng == '-' || (!fIsB && !fIsE) )
            {
                //
                // Syntactically Invalid Range. Skip RANGE Header
                //

                fEntireFile = TRUE;
                break;
            }

            if ( fIsB )
            {
                if ( fIsE )
                {
                    if ( dwB <= dwE )
                    {
                        if ( dwE < cbSizeLow )
                        {
                            dwOffset = dwB;
                            cbSizeToSend = dwE - dwB + 1;
                        }
                        else if (dwE < (cbSizeLow + QueryMetaData()->QueryFooterLength()) )
                        {
                            //
                            // Asking for part of footer, send entire file.
                            //

                            dwOffset = 0;
                            cbSizeToSend = cbSizeLow;
                        }
                        else
                        {
                            if ( dwB < cbSizeLow )
                            {
                                dwOffset = dwB;
                                cbSizeToSend = cbSizeLow - dwB;
                            }
                            else if (dwB < (cbSizeLow + QueryMetaData()->QueryFooterLength()) )
                            {
                                //
                                // Asking for part of footer,send entire file.
                                //

                                dwOffset = 0;
                                cbSizeToSend = cbSizeLow;
                            }
                            else
                            {
                                //
                                // Syntactically Valid but Unsatisfiable range. Zap this range and
                                // skip to the next range.
                                //

                                _fUnsatisfiableByteRange  = TRUE;
                                fInvalidRange             = TRUE;

                                memmove( _strRange.QueryStr()+_iRangeIdx, pRng,
                                 _strRange.QueryCCH() - (size_t)(pRng - _strRange.QueryStr())+1);

                                pRng = _strRange.QueryStr()+_iRangeIdx;
                            }
                        }
                    }
                    else
                    {
                        //
                        // E < B : Syntactically Invalid Range. Skip RANGE Header
                        //

                        fEntireFile = TRUE;
                        break;
                    }
                }
                else
                {
                    //
                    // Starting at B until end.
                    //

                    DWORD dwFooter = QueryMetaData()->QueryFooterLength() ;

                    if ( dwB < cbSizeLow + dwFooter)
                    {
                        if ( 0 != dwFooter)
                        {
                            //
                            // There's a footer on the file, send the whole thing.
                            //

                            dwOffset = 0;
                            cbSizeToSend = cbSizeLow;
                        }
                        else
                        {
                            dwOffset = dwB;
                            cbSizeToSend = cbSizeLow - dwB;
                        }
                    }
                    else
                    {
                        //
                        // Syntactically Valid but Unsatisfiable range. Zap this range and
                        // skip to the next range.
                        //

                        _fUnsatisfiableByteRange  = TRUE;
                        fInvalidRange             = TRUE;

                        memmove( _strRange.QueryStr()+_iRangeIdx, pRng,
                                 _strRange.QueryCCH() - (size_t)(pRng - _strRange.QueryStr())+1);

                        pRng = _strRange.QueryStr()+_iRangeIdx;
                    }
                }
            }
            else
            {
                //
                // E last bytes
                //

                if (    0   != dwE      &&
                        dwE < cbSizeLow &&
                        QueryMetaData()->QueryFooterLength() == 0)
                {
                    dwOffset = cbSizeLow - dwE;
                    cbSizeToSend = dwE;
                }
                else if ( 0 == dwE )
                {
                    //
                    // Syntactically Valid but Unsatisfiable range. Zap this range and
                    // skip to the next range.
                    //

                    _fUnsatisfiableByteRange  = TRUE;
                    fInvalidRange             = TRUE;

                    memmove( _strRange.QueryStr()+_iRangeIdx, pRng,
                             _strRange.QueryCCH() - (size_t)(pRng - _strRange.QueryStr())+1);

                    pRng = _strRange.QueryStr()+_iRangeIdx;
                }
                else
                {
                    //
                    // Return entire file
                    //

                    dwOffset = 0;
                    cbSizeToSend = cbSizeLow;
                }
            }
        }
        else
        {
            //
            // Syntactically Invalid Range. Skip RANGE Header
            //

            fEntireFile = TRUE;
            break;
        }

        //
        // Skip to begining of next range
        //

        while ( (c=*pRng) && c!=',' )
        {
            ++pRng;
        }
        if ( c == ',' )
        {
            ++pRng;
        }
    }
    while ( fInvalidRange );

    _iRangeIdx      = (DWORD)(pRng - _strRange.QueryStr());
    *pfEntireFile   = fEntireFile;
    *pfIsLastRange  = (*pRng == '\0');
    *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 | (fIsLast ? 0 : IO_FLAG_NO_RECV);

    pszTail = pszResp = (CHAR *) pbufResponse->QueryPtr() + dwBufLen;

    if ( _fMimeMultipart )
    {
        pszTail += wsprintf( pszTail,
                             MMIME_TYPE,
                             _strReturnMimeType.QueryStr() );
    }

    if ( fIsLast )
    {
        SetState( HTR_DONE, HT_RANGE, NO_ERROR );

        if (_cbBytesReceived > _cbClientRequest)
        {
            dwFlags |= IO_FLAG_NO_RECV;
        }
    }

    TCP_REQUIRE( _pGetFile->QuerySize( &cbSizeLow,
                                       &cbSizeHigh ));
    pszTail += wsprintf( pszTail,
                         MMIME_RANGE,
                         dwOffset, dwOffset + dwSizeToSend - 1,
                         cbSizeLow
                         );

    if ( fIsLast && (!IsKeepConnSet() &&
                     !_Filter.IsNotificationNeeded( SF_NOTIFY_END_OF_REQUEST,
                                                    IsSecurePort() )))
    {
        dwFlags |= TF_DISCONNECT | TF_REUSE_SOCKET;

    }

    if ( !TransmitFile( _pGetFile,
                        NULL,
                        dwOffset,
                        dwSizeToSend,
                        dwFlags,
                        QueryRespBufPtr(),
                        QueryRespBufCB(),
                        _fMimeMultipart ? (fIsLast ? LAST_BOUNDARY_STRING : BOUNDARY_STRING) : NULL,
                        (DWORD)(_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,
    LPBOOL              pfFinished
    )
{
    FILETIME   FileTime;
    DWORD      cbSizeLow;
    SYSTEMTIME SysTime;
    SYSTEMTIME SysFileTime;
    LPSYSTEMTIME pMinSysTime;
    CHAR *     pszResp;
    CHAR *     pszTail;
    CHAR       ach[64];
    DWORD      cb;
    DWORD      dwHdrLength;

    if ( pfHandled )
    {
        *pfHandled = FALSE;
    }

    //
    //  HTTP 0.9 clients don't use MIME headers, they just expect the data
    //

    if ( IsPointNine() )
    {
        IF_DEBUG( PARSING )
        {
            DBGPRINTF((DBG_CONTEXT,
                      "[BuildResponseHeader] Skipping headers, 0.9 client\n"));
        }

        *((CHAR *)pbufResponse->QueryPtr()) = '\0';
        return TRUE;
    }

    if ( !BuildBaseResponseHeader( pbufResponse,
                                   pfHandled,
                                   pstrStatus,
                                   (HTTPH_SEND_GLOBAL_EXPIRE |
                                    HTTPH_NO_DATE) ))
    {
        return FALSE;
    }

    //
    //  If no file, then don't add the content type headers
    //

    if ( !pstrPath )
    {
        return TRUE;
    }


    //
    //  "Content-Type: xxx/xxx"
    //
    //  We check to make sure the client can accept the type we
    //  want to send
    //

    if ( !::SelectMimeMapping( &_strReturnMimeType,
                                pstrPath->QueryStr(),
                                _pMetaData) )
    {
        return FALSE;
    }

    dwHdrLength = strlen((CHAR *) pbufResponse->QueryPtr());

    if (!pbufResponse->Resize(dwHdrLength + RANGE_ADDL_BUF_HDR_SIZE +
                                _strReturnMimeType.QueryCB()))
    {
        return FALSE;
    }

    pszResp = (CHAR *) pbufResponse->QueryPtr();

     // build Date: uses Date/Time cache
    dwHdrLength += g_pDateTimeCache->GetFormattedCurrentDateTime(
                                    pszResp + dwHdrLength );

    pszTail = pszResp + dwHdrLength;

    if ( !DoesClientAccept( _strReturnMimeType.QueryStr() ) )
    {
        IF_DEBUG( PARSING )
        {
            DBGPRINTF((DBG_CONTEXT,
                      "[BuildResponseHeader] Client doesn't accept %s\n",
                       _strReturnMimeType.QueryStr() ));
        }

        SetState( HTR_DONE, HT_NONE_ACCEPTABLE, NO_ERROR );
        Disconnect( HT_NONE_ACCEPTABLE, NO_ERROR, FALSE, pfFinished );

        //
        //  No further processing is needed
        //

        if ( pfHandled )
        {
            *pfHandled = TRUE;
        }

        return TRUE;
    }

    if ( _fMimeMultipart && _fProcessByteRange )
    {
        if ( _VersionMajor > 1 || (_VersionMajor == 1 && _VersionMinor > 0) )
        {
            APPEND_STRING( pszTail, "Content-Type: multipart/byteranges; boundary="
                    BOUNDARY_STRING_DEFINITION "\r\n" );
        }
        else
        {
            APPEND_STRING( pszTail, "Content-Type: multipart/x-byteranges; boundary="
                    BOUNDARY_STRING_DEFINITION "\r\n" );
        }
    }
    else
    {
        if ( _fAcceptRange && !_fProcessByteRange )
        {
            APPEND_STRING( pszTail, "Accept-Ranges: bytes\r\n" );
        }

        APPEND_STR_HEADER( pszTail, "Content-Type: ", _strReturnMimeType, "\r\n" );
    }

    if ( !pFile )
    {
        return TRUE;
    }

    if ( !QueryNoCache() )
    {
        //
        //  "Last-Modified: <GMT time>". Only do this if we're not
        //   satisfying an If-Range: request.
        //

        if (!_fProcessByteRange ||
            (_HeaderList.FastMapQueryValue(HM_IFR) == NULL) )
        {
            if ( !pFile->QueryLastWriteTime( &FileTime )          ||
                 !::FileTimeToSystemTime( &FileTime, &SysFileTime ))
            {
                return FALSE;
            }

            IISGetCurrentTimeAsSystemTime(&SysTime);
            pMinSysTime = MinSystemTime(&SysTime, &SysFileTime);

            if (!::SystemTimeToGMT( *pMinSysTime, ach, sizeof(ach) ))
            {
                return FALSE;
            }

            APPEND_PSZ_HEADER( pszTail, "Last-Modified: ", ach, "\r\n" );

        }

        //
        // ETag: <Etag>
        //

        if (pFile->WeakETag())
        {
            APPEND_PSZ_HEADER(pszTail, "ETag: W/", pFile->QueryETag(), "\r\n");
        } else
        {
            APPEND_PSZ_HEADER(pszTail, "ETag: ", pFile->QueryETag(), "\r\n");
        }

    }

    //
    //  "Content-Length: nnnn" and end of headers
    //


    if ( _fMimeMultipart )
    {
        if ( _cbMimeMultipart )
        {
            APPEND_NUMERIC_HEADER( pszTail,
                                   "Content-Length: ",
                                   _cbMimeMultipart,
                                   "\r\n" );
        }


        //
        // first boundary string
        //

        APPEND_STRING( pszTail, DELIMIT_FIRST BOUNDARY_STRING );
    }
    else
    {
        if ( _fProcessByteRange )
        {
            cbSizeLow = _cbMimeMultipart;
        }
        else
        {
            TCP_REQUIRE( pFile->QuerySize( &cbSizeLow ));
            cbSizeLow += QueryMetaData()->QueryFooterLength();
        }

        APPEND_NUMERIC_HEADER_TAILVAR( pszTail,
                               "Content-Length: ",
                               cbSizeLow,
                               (_fProcessByteRange ? "\r\n" : "\r\n\r\n") );
    }

    IF_DEBUG( REQUEST )
    {
        DBGPRINTF((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,
    LPBOOL              pfFinished
    )
/*++

  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;
    SYSTEMTIME SysFileTime;
    LPSYSTEMTIME pMinSysTime;
    CHAR *     pszResp;
    CHAR *     pszTail;
    CHAR *     pszVariant;
    CHAR       ach[64];
    int        cMod;
    DWORD      dwCurrentHeaderLength;

    if ( pfHandled )
    {
        *pfHandled = FALSE;
    }

    //
    //  HTTP 0.9 clients don't use MIME headers, they just expect the data
    //

    if ( IsPointNine() )
    {
        IF_DEBUG( PARSING )
        {
            DBGPRINTF((DBG_CONTEXT,
                      "[BuildResponseHeader] Skipping headers, 0.9 client\n"));
        }

        *((CHAR *)pbufResponse->QueryPtr()) = '\0';
        return TRUE;
    }


    if ( !BuildBaseResponseHeader( pbufResponse,
                                   pfHandled,
                                   NULL,
                                   HTTPH_SEND_GLOBAL_EXPIRE
                                        |HTTPH_NO_DATE ))
    {
        return FALSE;
    }

    //
    // Make sure that we have enough room left in the buffer. Note
    // that right here we only account for enough space for the Date:
    // header and the max. amount of cached info. If there is more
    // uncached info that may be inserted here, make sure to change the
    // MIN_ADDL_BUF_HDR_SIZE_CACHED define above.
    //

    dwCurrentHeaderLength = strlen((CHAR *) pbufResponse->QueryPtr());

    if (!pbufResponse->Resize(dwCurrentHeaderLength +
                                MIN_ADDL_BUF_HDR_SIZE_CACHED) )
    {
        return FALSE;
    }

    pszResp = (CHAR *) pbufResponse->QueryPtr();

     // build Date: uses Date/Time cache
    dwCurrentHeaderLength += g_pDateTimeCache->GetFormattedCurrentDateTime(
                                    pszResp + dwCurrentHeaderLength );

    pszTail = pszResp + dwCurrentHeaderLength;

    if ( !QueryNoCache() && 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;
    }

    //
    //  "Content-Type: xxx/xxx"
    //
    //  We check to make sure the client can accept the type we
    //  want to send
    //

    if ( !::SelectMimeMapping( &_strReturnMimeType,
                                pstrPath->QueryStr(),
                                _pMetaData) )
    {
        return FALSE;
    }

    if (!pbufResponse->Resize(dwCurrentHeaderLength +
                                _strReturnMimeType.QueryCB() +
                                MIN_ADDL_BUF_HDR_SIZE))
    {
        return FALSE;
    }

    pszResp = (CHAR *) pbufResponse->QueryPtr();
    pszTail = pszResp + dwCurrentHeaderLength;

    pszVariant = pszTail;


    if ( !DoesClientAccept( _strReturnMimeType.QueryStr() ) )
    {
no_match_type:
        IF_DEBUG( PARSING )
        {
            DBGPRINTF((DBG_CONTEXT,
                      "[BuildResponseHeader] Client doesn't accept %s\n",
                       _strReturnMimeType.QueryStr() ));
        }

        SetState( HTR_DONE, HT_NONE_ACCEPTABLE, NO_ERROR );
        Disconnect( HT_NONE_ACCEPTABLE, NO_ERROR, FALSE, pfFinished );

        //
        //  No further processing is needed
        //

        if ( pfHandled )
        {
            *pfHandled = TRUE;
        }

        return TRUE;
    }


    //
    // Content-Type: MUST be the 1st header, or we get messed up when
    // we use the cached headers and try to pass the mime type to
    // DoesClientAccept().
    //

    APPEND_STR_HEADER( pszTail, "Content-Type: ", _strReturnMimeType, "\r\n" );

    if ( _fAcceptRange )
    {
        APPEND_STRING( pszTail, "Accept-Ranges: bytes\r\n" );
    }

    if ( !QueryNoCache() )
    {
        //
        //  "Last-Modified: <GMT time>"
        //

        if ( !pFile->QueryLastWriteTime( &FileTime )          ||
             !::FileTimeToSystemTime( &FileTime, &SysFileTime ))
        {
            return FALSE;
        }

        IISGetCurrentTimeAsSystemTime(&SysTime);
        pMinSysTime = MinSystemTime(&SysTime, &SysFileTime);

        if (!::SystemTimeToGMT( *pMinSysTime, ach, sizeof(ach) ))
        {
            return FALSE;
        }

        APPEND_PSZ_HEADER( pszTail, "Last-Modified: ", ach, "\r\n" );

        //
        // ETag: <Etag>
        //

        if (pFile->WeakETag())
        {
            APPEND_PSZ_HEADER(pszTail, "ETag: W/", pFile->QueryETag(), "\r\n");
        } else
        {
            APPEND_PSZ_HEADER(pszTail, "ETag: ", pFile->QueryETag(), "\r\n");
        }

    }

    //
    //  "Content-Length: nnnn" and end of headers
    //


    TCP_REQUIRE( pFile->QuerySize( &cbSizeLow ));
    cbSizeLow += QueryMetaData()->QueryFooterLength();

    APPEND_NUMERIC_HEADER( pszTail,
                           "Content-Length: ",
                           cbSizeLow,
                           "\r\n\r\n" );

    if ( !QueryNoCache() )
    {
        pFile->SetHttpInfo( pszVariant, DIFF(pszTail - pszVariant) );
    }

    IF_DEBUG( REQUEST )
    {
        DBGPRINTF((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:      gFile - Opened file Info
                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( LPTS_OPEN_FILE_INFO gFile,
                                 CHAR *   pchFile,
                                 BUFFER * pbufResp,
                                 BOOL *   pfFound,
                                 BOOL *   pfHandled )
{
    INT                 x, y;
    TCHAR *             pch = _strURLParams.QueryStr();
    PW3_SERVER_INSTANCE pInstance = QueryW3Instance();
    STACK_STR( strURL, MAX_PATH);

    *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( (UCHAR)(*pch) ))
        pch++;

    while ( *pch && !isdigit( (UCHAR)(*pch) ))
        pch++;

    y = _tcstoul( pch,
                  NULL,
                  10 );

    if ( !ImpersonateUser() )
    {
        return FALSE;
    }

    if ( !SearchMapFile( gFile,
                         pchFile,
                         &pInstance->GetTsvcCache(),
                         QueryImpersonationHandle(),
                         x,
                         y,
                         &strURL,
                         pfFound,
                         IsAnonymous()||IsClearTextPassword() ))
    {
        RevertUser();
        return FALSE;
    }

    RevertUser();

    if ( !*pfFound )
    {
        if ( (pch = (TCHAR * ) _HeaderList.FastMapQueryValue( HM_REF )) )
        {
#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 );
                fValid = TRUE;
                CanonURL( strURL.QueryStr());
                CanonURL.SetLen( strlen( strURL.QueryStr() ));
            }
            else
#endif
                strURL.Copy( (TCHAR *) pch );
        }
        else
            return TRUE;
    }

    CloseGetFile();
    
    //
    //  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
        {
            CHAR achPort[32];
            STACK_STR( strOldURL, MAX_PATH );

            //
            //  fully qualify the URL and send a
            //  redirect.  Some browsers (emosaic) don't like doc relative
            //  URLs with bookmarks
            //

            if ( !strOldURL.Copy( strURL ) ||
                 !strURL.Resize( strOldURL.QueryCB() + 128 ))
            {
                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.
            //

            strURL.Copy( (IsSecurePort() ? "https://" : "http://" ));
            strURL.Append( QueryHostAddr() );

            if ( IsSecurePort() ? (INT) QueryClientConn()->QueryPort()
                    != HTTP_SSL_PORT
                    : (INT) QueryClientConn()->QueryPort() != 80 )
            {
                strURL.Append( ":" );
                _itoa( (INT) QueryClientConn()->QueryPort(), achPort, 10 );
                strURL.Append( achPort );
            }

            strURL.Append( strOldURL );

            if ( !BuildURLMovedResponse( pbufResp, &strURL, HT_REDIRECT, FALSE ))
                return FALSE;
        }
    }
    else
    {
        if ( !BuildURLMovedResponse( pbufResp, &strURL, HT_REDIRECT, FALSE ))
            return FALSE;
    }

    *pfFound = TRUE;
    return TRUE;
}

/*******************************************************************

    NAME:       EncodeStringToHTML

    SYNOPSIS:   Enode string in HTML format

    ENTRY:      szSrc - Source string to be encoded, in system codepage
                szDest - Space to output HTML (in system codepage)
                cbDest - Size of space in szDest (number of bytes)

    RETURNS:    number of bytes required for HTML text

    NOTES:      If cbDest is less than target HTML, then we fit
                as much characters as possible into szDest. But the
                return value remains the same and is greater than
                cbDest. szDest is not zero terminated in this case.

    HISTORY:
        markzh       17-Jan-2002 Created

********************************************************************/
UINT EncodeStringToHTML(CHAR *szSrc, CHAR *szDest, UINT cbDest)
{
    CHAR *pSrc = szSrc;
    CHAR *pDest = szDest;
    BOOL isSecondByte=FALSE;
    
    do {
        CHAR *szAppend = pSrc;
        UINT cbAppend = 1;

        // We may be running on DBCS. Ideally we should encode them to &#nnnnn
        // but we dont want that many code here

        if (isSecondByte)
        {
            isSecondByte = FALSE;
        }
        else if (IsDBCSLeadByte(*pSrc))
        {
            isSecondByte = TRUE;
        }
        else switch (*pSrc)
        {
        #define SetAppend(s)  (szAppend=(s), cbAppend=sizeof(s)-1)
            case '&': SetAppend("&amp;");
                break;
            case '"': SetAppend("&quot;");
                break;
            case '<': SetAppend("&lt;");
                break;
            case '>': SetAppend("&gt;");
                break;
            default:
                break;
        #undef SetAppend
        }

        if (pDest - szDest + cbAppend <= cbDest)
        {
            memcpy(pDest, szAppend, cbAppend);
        }

        pDest += cbAppend;

    }while (*pSrc++);

    return DIFF(pDest - szDest);
}


/*******************************************************************

    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
                dwServerCode - Server response code
                               (either HT_REDIRECT or HT_MOVED)
                fIncludeParams - TRUE to include params from original request
                                 in redirect

    RETURNS:    TRUE if successful, FALSE on error

    NOTES:      This routine doesn't support sending a Unicode doc moved
                message

    HISTORY:
        Johnl       17-Sep-1994 Created

********************************************************************/

BOOL HTTP_REQ_BASE::BuildURLMovedResponse( BUFFER *    pbufResp,
                                           STR *       pstrURL,
                                           DWORD       dwServerCode,
                                           BOOL        fIncludeParams )
{
    STACK_STR(strMovedMessage, 512);
    STR*    pstrMovedMessage;
    STACK_STR(strUrlWithParams, 512);
    UINT    cb;
    UINT    cbA;
    CHAR *  pszTail;
    CHAR *  pszResp;
    DWORD   cbURL;
    BOOL    fDone;
    BOOL    bHaveCustom;
    DWORD   dwMsgSize;
    DWORD   dwRedirHeaderSize;

    DBG_ASSERT(dwServerCode == HT_REDIRECT || dwServerCode == HT_MOVED);

    if (CheckCustomError(&strMovedMessage, HT_REDIRECT, 0, &fDone, &dwMsgSize, FALSE))
    {
        DBG_ASSERT(!fDone);
        strMovedMessage.SetLen(strlen((CHAR *)strMovedMessage.QueryPtr()));
        bHaveCustom = TRUE;
        pstrMovedMessage = &strMovedMessage;
    }
    else
    {
        pstrMovedMessage = g_pstrMovedMessage;

        DBG_ASSERT( pstrMovedMessage->QueryCB() );

        bHaveCustom = FALSE;
    }

    //
    //  Make sure the response buffer is large enough
    //

#define CB_FIXED_RESP           500

    //
    //  Technically we should escape more characters then just the spaces but
    //  that would probably break some number of client apps
    //

    if ( !pstrURL->EscapeSpaces() )
    {
        return FALSE;
    }

    if ( fIncludeParams &&
         !_strURLParams.IsEmpty() )
    {
        if ( !strUrlWithParams.Copy( *pstrURL ) ||
             !strUrlWithParams.Append( "?" ) ||
             !strUrlWithParams.Append( _strURLParams ) )
        {
            return FALSE;
        }

        pstrURL = &strUrlWithParams;
    }

    cbURL = pstrURL->QueryCB();

    // WinSE 24915
    // Enode string in HTML format
    
    STACK_STR(strUrlInHTML, 512);
    UINT     cbUrlInHTML;
    
    cbUrlInHTML = EncodeStringToHTML(pstrURL->QueryStr(),
                        strUrlInHTML.QueryStr(),
                        strUrlInHTML.QuerySize());
    
    if ( cbUrlInHTML > strUrlInHTML.QuerySize() )
    {
        if (! strUrlInHTML.Resize(cbUrlInHTML))
        {
            return FALSE;
        }

        cbUrlInHTML = EncodeStringToHTML(pstrURL->QueryStr(),
                        strUrlInHTML.QueryStr(),
                        strUrlInHTML.QuerySize());
    }

    if ( (cbUrlInHTML + cbURL + CB_FIXED_RESP) > pbufResp->QuerySize() )
    {
        if ( !pbufResp->Resize( cbUrlInHTML + cbURL + CB_FIXED_RESP ))
            return FALSE;
    }

    //
    //  "HTTP/<ver> 302 Redirect" or
    //  "HTTP/<ver> 301 Redirect"
    //

    if ( !BuildStatusLine( pbufResp,
                           dwServerCode,
                           NO_ERROR ))
    {
        return FALSE;
    }

    //
    //  Set the status to log here
    //

    SetLogStatus( dwServerCode, NO_ERROR );

    pszResp = (CHAR *) pbufResp->QueryPtr();
    pszTail = pszResp + strlen( pszResp );

    //
    //  "Location: <URL>"
    //

    APPEND_STR_HEADER( pszTail, "Location: ", *pstrURL, "\r\n" );

    //
    //  "Server: <Server>/<version>
    //

    APPEND_VER_STR( pszTail );

    //
    //  Content-Type,  it's OK to assume all clients accept text/html
    //

    if (!bHaveCustom)
    {
        APPEND_STRING( pszTail, "Content-Type: text/html\r\n" );
    }

    //
    //  Calculate the bytes in the body of the message for Content-Length
    //
    //  cbUrlInHTML is total bytes in the string (-1 for the null)
    //
    //  pstrMovedMessage->QueryCB does not include the null, but
    //  it may be a format string that will contain the url, so -2 for %s
    //
    //  Wait to subtract the %s away, though since pstrMovedMessage
    //  my be custom error file data - This code is so @#$#%@%$.
    //  Of course none of these calculations are right if %s is actually
    //  in the custom error file, and if it isn't then the error is
    //  useless. Whoever wrote this deserves a beating.
    //

    cb = ( cbUrlInHTML - 1 ) +
         ( pstrMovedMessage->QueryCB() );


    if (!bHaveCustom)
    {
        //
        // Now compensate for %s. It is okay that cb is a little too
        // big in this case dwMsgSize will be what determines the value
        // of the content length header.
        //
        dwMsgSize = cb - 2;
    }

    if ( IsKeepConnSet() )
    {
        if (!IsOneOne())
        {
            APPEND_STRING( pszTail, "Connection: keep-alive\r\n" );
        }
    } else
    {
        if (IsOneOne())
        {
            APPEND_STRING( pszTail, "Connection: close\r\n" );
        }
    }

    //
    // If we have any redirection specific headers, copy them in now.
    //

    dwRedirHeaderSize = QueryMetaData()->QueryRedirectHeaders()->QueryCCH();

    if (dwRedirHeaderSize != 0)
    {
        if (!pbufResp->Resize(DIFF(pszTail - pszResp) + dwRedirHeaderSize ))
        {
            return FALSE;
        }
        pszTail = (CHAR *) pbufResp->QueryPtr() + (pszTail - pszResp);
        pszResp = (CHAR *) pbufResp->QueryPtr();

        memcpy(pszTail, QueryMetaData()->QueryRedirectHeaders()->QueryStr(),
                dwRedirHeaderSize);

        pszTail += dwRedirHeaderSize;
    }

    //
    //  Append any headers specified by filters
    //

    if ( cbA = QueryAdditionalRespHeaders()->QueryCB() )
    {
        if (!pbufResp->Resize(DIFF(pszTail - pszResp) + cbA ))
        {
            return FALSE;
        }
        pszTail = (CHAR *) pbufResp->QueryPtr() + (pszTail - pszResp);
        pszResp = (CHAR *) pbufResp->QueryPtr();

        memcpy( pszTail, QueryAdditionalRespHeaders()->QueryStr(), cbA );

        pszTail += cbA;
    }

    //
    //  Figure out the total length to see if we have enough room for the "Content-Length"
    //  header
    //
    cb += DIFF(pszTail - pszResp) +
        sizeof("Content-Length: ") - 1 +
        30 + // [magic #] max length of string containing numeric value of Content-Length
        ( bHaveCustom ? sizeof("\r\n") - 1 : sizeof("\r\n\r\n") - 1 ) +
        sizeof(TCHAR);

    if ( !pbufResp->Resize( cb ))
    {
        return FALSE;
    }

    if ( pbufResp->QueryPtr() != pszResp )
    {
        pszTail = (CHAR *) pbufResp->QueryPtr() + (pszTail - pszResp);
        pszResp = (CHAR *) pbufResp->QueryPtr();
    }

    //
    //  "Content-Length: <length>"
    //

    APPEND_NUMERIC_HEADER_TAILVAR( pszTail, "Content-Length: ", dwMsgSize, bHaveCustom ? "\r\n" : "\r\n\r\n" );

    //
    // pstrMovedMessage is something along the lines of "This object can be found at %s" and
    // pstrURL is the URL that replaces the %s in pstrMovedMessage, so the max length of the
    // actual HTML doc is the sum of the lengths of pstrMovedMessage and pstrURL
    //

    if (HTV_HEAD != QueryVerb())
    {
        DWORD cbMaxMsg = pstrMovedMessage->QueryCB() + strUrlInHTML.QueryCB();

        if ( !pbufResp->Resize( DIFF(pszTail - pszResp) + cbMaxMsg ) )
        {
            return FALSE;
        }

        //
        // Watch for pointer shift
        //
        if ( pbufResp->QueryPtr() != pszResp )
        {
            pszTail = (CHAR*) pbufResp->QueryPtr() + (pszTail - pszResp);
            pszResp = (CHAR *) pbufResp->QueryPtr();
        }

        //
        // Add the short HTML doc indicating the new location of the URL,
        // making sure we have enough space for it
        // Note we've already added the message body length.  Add in the terminator.
        //

    
        ::sprintf( pszTail,
                   pstrMovedMessage->QueryStr(),
                   strUrlInHTML.QueryStr() );

    }
    
    return TRUE;
}

/*******************************************************************

    NAME:       SearchMapFile

    SYNOPSIS:   Searches the given mapfile for a shape that contains
                the passed cooridinates

    ENTRY:      gFile - Open file Info
                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
                fMayCacheAccessToken  - TRUE access token may be cached

    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( LPTS_OPEN_FILE_INFO gFile,
                    CHAR *              pchFile,
                    TSVC_CACHE *        pTsvcCache,
                    HANDLE              hToken,
                    INT                 x,
                    INT                 y,
                    STR *               pstrURL,
                    BOOL *              pfFound,
                    BOOL                fMayCacheAccessToken )
{
    DWORD                       BytesRead;
    CHAR *                      pch;
    CACHE_FILE_INFO             CacheFileInfo;
    CHAR *                      pchDefault = NULL;
    CHAR *                      pchPoint = NULL;
    CHAR *                      pchStart;
    BOOL                        fRet = TRUE;
    DWORD                       cchUrl;
    UINT                        dis;
    UINT                        bdis = UINT(-1);
    STACK_STR( strDefaultURL, MAX_PATH );

    *pfFound = FALSE;

    //
    //  Retrieve the '\0' terminated map file
    //

    if ( !CheckOutCachedFileFromURI( (PVOID)gFile,
                                     pchFile,
                                     pTsvcCache,
                                     hToken,
                                     (BYTE **) &pch,
                                     &BytesRead,
                                     fMayCacheAccessToken,
                                     &CacheFileInfo,
                                     0 ))   // no code conversion
    {
        if ( !CheckOutCachedFile( pchFile,
                                  pTsvcCache,
                                  hToken,
                                  (BYTE **) &pch,
                                  &BytesRead,
                                  fMayCacheAccessToken,
                                  &CacheFileInfo,
                                  0 ))   // no code conversion
        {
            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 )
                 // 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((UCHAR)(*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((UCHAR)(*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;
                BOOL fOverflow = FALSE;

                SKIPNONWHITE( pch );
                pURL = pch;
                SKIPWHITE( pch );

                if ( !isdigit((UCHAR)(*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;
                    }

                    if ( i < MAXVERTS-1 )
                    {
                        i++;
                    }
                    else
                    {
                        fOverflow = TRUE;
                    }
                }

                pgon[i][X] = -1;

                if ( !fOverflow && 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 )
    {
        DBGPRINTF(( 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 = DIFF(pch - pchStart);

    if ( !pstrURL->Resize( cchUrl + 1 ))
    {
        fRet = FALSE;

        goto Exit;
    }

    memcpy( pstrURL->QueryStr(),
            pchStart,
            cchUrl );

    pstrURL->SetLen(cchUrl);

    if ( !pstrURL->Unescape() )
    {
        fRet = FALSE;
        goto Exit;
    }

    IF_DEBUG( PARSING )
    {
        DBGPRINTF((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( (UCHAR)(*pch) ) &&
            !isalpha( (UCHAR)(*pch) ) &&
            *pch != '/'      &&
            *pch != '\r'     &&
            *pch != '\n' )
    {
        pch++;
    }

    if ( !isdigit( (UCHAR)(*pch) ) )
        return -1;

    n = atoi( pch );

    while ( isdigit( (UCHAR)(*pch) ))
        pch++;

    *ppch = pch;

    return n;
}



BOOL
DisposeOpenURIFileInfo(
    IN  PVOID   pvOldBlock
    )
/*++

    Routine Description

        Close an open URI file information block. This involves closing the
        handle if the file information is valid and freeing and associated
        structures.

    Arguments

        pvOldBlock - pointer to the URI file information block.

    Returns

        TRUE if operation successful.

--*/
{

    PW3_URI_INFO        pURIInfo;
    PVOID pvBlob;
    BOOL bSuccess;

    pURIInfo = (PW3_URI_INFO ) pvOldBlock;

    if (pURIInfo->pszName != NULL) {
        LocalFree(pURIInfo->pszName);
    }

    if (pURIInfo->pszUnmappedName != NULL) {
        LocalFree(pURIInfo->pszUnmappedName);
    }

    if( pURIInfo->pMetaData != NULL ) {
        TsFreeMetaData(pURIInfo->pMetaData->QueryCacheInfo() );
    }

    if ( pURIInfo->pOpenFileInfo != NULL ) {
        TsDerefURIFile(pURIInfo->pOpenFileInfo);
    }

    return( TRUE );

} // DisposeOpenURIFileInfo