//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/*--
Module Name: filters.cpp
Abstract: ISAPI Filter handling classes
--*/

#include "httpd.h"

CISAPIFilterCon* g_pFilterCon;

const DWORD g_fIsapiFilterModule = TRUE;


const LPCWSTR cwszFilterSep = L",";   // what seperates filter ids in the registry

//  Used to increment the filter, goes up 1 normally, down 1 for RAW DATA prio inversion
#define NEXTFILTER(dwNotifyType, i)                          \
{                                                            \
    (dwNotifyType == SF_NOTIFY_SEND_RAW_DATA) ? i-- : i++;   \
}

void FreeLogParams(PHTTP_FILTER_LOG pLog);

//   Creates new filter info used globally
BOOL InitFilters() 
{
    g_pFilterCon = new CISAPIFilterCon();
    return (g_pFilterCon && g_pFilterCon->m_nFilters!=0);
}

//  Destroys the global filter information
void CleanupFilters() 
{    
    if (g_pFilterCon)
    {
        delete g_pFilterCon;
    }
    g_pFilterCon = 0;
}

CFilterInfo* CreateCFilterInfo(void) 
{
    if (0 == g_pFilterCon->m_nFilters)
    {
        return NULL;
    }

    return new CFilterInfo;
}

//  CFilterInfo is an internal member of CHttpRequest.  Allocate buffers for it
CFilterInfo::CFilterInfo() 
{
    ZEROMEM(this);

    m_pdwEnable = MyRgAllocNZ(DWORD,g_pFilterCon->m_nFilters);
    if (!m_pdwEnable) 
    {
        DEBUGMSG(ZONE_ERROR, (L"HTTPD: CFilterInfo::Init died on Alloc!\r\n"));
        return;
    }

    m_ppvContext = MyRgAllocZ(PVOID,g_pFilterCon->m_nFilters);
    if (!m_ppvContext) 
    {
        MyFree(m_pdwEnable);
        DEBUGMSG(ZONE_ERROR, (L"HTTPD: CFilterInfo::Init died on Alloc!\r\n"));
        return;
    }

    //  A filter can disable itself for events in a session, m_pdwEnable stores this.
    memset(m_pdwEnable,0xFFFF,g_pFilterCon->m_nFilters*sizeof(DWORD));  // all true at first

    m_dwStartTick = GetTickCount();
    m_fFAccept = TRUE;
    return;
}

//  Notes:  If http request is to be persisted, this is called. 
//  Do NOT Free the Allocated Mem or the context structure here, these are persisted
//  across requests, delete at end of session.


BOOL CFilterInfo::ReInit() 
{
    MyFree(m_pszDenyHeader);

    if (m_pFLog) 
    {
        MyFree(m_pFLog);  
    }
    
    //  Reset the enable structure to all 1's, starting over.
    memset(m_pdwEnable,0xFFFF,g_pFilterCon->m_nFilters*sizeof(DWORD));  // all true at first

    // The context struct and AllocMem data lasts through the session, not just a request.
    m_dwStartTick = GetTickCount();
    m_fFAccept = TRUE;
    m_dwNextReadSize = 0;
    m_dwBytesSent = 0;
    m_dwBytesReceived = 0;

    return TRUE;
}

CFilterInfo::~CFilterInfo() 
{
    MyFree(m_pszDenyHeader);

    if (m_pFLog) 
    {
        MyFree(m_pFLog);
    }
    MyFree(m_pdwEnable);    
    MyFree(m_ppvContext);

    
    FreeAllocMem();    // frees m_pAllocBlock
}

//  Initilization routine for global filters.  This looks in the registry for the
//  filters, loads any dlls into memory, and puts them into a list that is 
//  ordered from Highest prio filters to lowest.

BOOL CISAPIFilterCon::Init() 
{
    DEBUG_CODE_INIT;
    BOOL ret = FALSE;
    BOOL fChange = FALSE;
    CISAPI *pCISAPI = NULL;
    const WCHAR *wcszReg;
    WCHAR *wszFilterNames;
    DWORD dwFilterLen = 0;
    PWSTR wszToken = NULL;
    DWORD i;
    int j;

    CReg topreg(HKEY_LOCAL_MACHINE,RK_HTTPD);

    wcszReg = topreg.ValueSZ(RV_FILTER);
    wszFilterNames = MySzDupW(wcszReg);

    DEBUGMSG(ZONE_ISAPI, (L"HTTPD: Filter DLLs Reg Value <<%s>>\r\n",wszFilterNames));

    if (NULL == wszFilterNames || 0 == (dwFilterLen = wcslen(wszFilterNames))) 
    {
        DEBUGMSG(ZONE_ISAPI,(L"HTTPD: No filters listed in registry\r\n"));
        myleave(0);  // no values => no registered filters
    }

    // count # of commas to figure how many dlls there are
    m_nFilters = 1;   // there's at least 1 if we're this far

    for (i = 0; i < dwFilterLen && wszFilterNames[i] != L'\0'; i++) 
    {
        if (wszFilterNames[i] == L',')
        {
            m_nFilters++;
        }
    }
    DEBUGMSG(ZONE_ISAPI, (L"HTTPD: # of filters = %d\r\n",m_nFilters));

    if (! (m_pFilters = MyRgAllocZ(FILTERINFO,m_nFilters)))
    {
        myleave(203);
    }

    // now tokenize the string and load filter libs as we do it
    j = 0;
    wszToken = wcstok(wszFilterNames,cwszFilterSep);
    while (wszToken != NULL) 
    {
        svsutil_SkipWWhiteSpace(wszToken);

        // Handles the case where there's a comma but nothing after it.
        if (*wszToken == L'\0') 
        {
            m_nFilters--;
            break;
        }
        
        pCISAPI = m_pFilters[j].pCISAPI = new CISAPI(SCRIPT_TYPE_FILTER);
        DEBUGMSG(ZONE_ISAPI, (L"HTTPD: Initiating filter library %s\r\n",wszToken));
        if (!pCISAPI) 
        {
            m_nFilters = j;
            if (m_nFilters == 0)
            {
                MyFree(m_pFilters);
            }
            
            myleave(204);
        }

        if (TRUE == pCISAPI->Load(wszToken)) 
        {
            m_pFilters[j].wszDLLName = MySzDupW(wszToken);    
            m_pFilters[j].dwFlags = pCISAPI->GetFilterFlags();
            if (m_pFilters[j].dwFlags & SF_NOTIFY_READ_RAW_DATA)
            {
                g_pVars->m_fReadFilters = TRUE;
            }
            j++;
        }
        else 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: Filter <<%s>> failed to load!\r\n",wszToken));
            // only messed up this filter, others may work so keep on moving
            m_nFilters--;
            delete pCISAPI;
        }
            
        wszToken = wcstok(NULL,cwszFilterSep);
    }
    DEBUGCHK(j == m_nFilters);

    // Case where every filter fails to load
    if (0 == m_nFilters)  
    {
    //    Cleanup();
        MyFree(m_pFilters);
        myleave(205);
    }

    // Reorder list based on priorities of filters.
    do     
    {
        fChange = FALSE;
        for(j=0; j<m_nFilters-1; j++) 
        {
            if ( (m_pFilters[j].dwFlags & SF_NOTIFY_ORDER_MASK) < (m_pFilters[j+1].dwFlags & SF_NOTIFY_ORDER_MASK)) 
            {
                // swap the 2 filter infos
                FILTERINFO ftemp = m_pFilters[j+1];
                m_pFilters[j+1] = m_pFilters[j];
                m_pFilters[j] = ftemp;
                fChange = TRUE;
            }
        }
    } while(fChange);

    ret = TRUE;
done:
    DEBUGMSG_ERR(ZONE_ERROR, (L"HTTPD: CIsapiFilterCon::Init FAILED: GLE=%d err=%d\r\n", 
            GetLastError(), err));
    
    MyFree(wszFilterNames);
    return ret;
}

void CISAPIFilterCon::Cleanup() 
{
    if (0 == m_pFilters)
    {
        return;
    }
    
    for (int i = 0; i < m_nFilters; i++) 
    {
        m_pFilters[i].pCISAPI->Unload(m_pFilters[i].wszDLLName);
        MyFree(m_pFilters[i].wszDLLName);

        delete m_pFilters[i].pCISAPI;
    }
    MyFree(m_pFilters);
}

// Determines whether or not a filter will be called based on current set of flags
BOOL CHttpRequest::IsCurrentFilterCalled(DWORD dwNotifyType, int iCurrentFilter) 
{
    // Filter has not requested to be notified for current call or has disabled itself through SF_REQ_DISABLE_NOTIFICATIONS.
    if (0 == ((dwNotifyType & g_pFilterCon->m_pFilters[iCurrentFilter].dwFlags) & m_pFInfo->m_pdwEnable[iCurrentFilter]))
    {
        return FALSE;
    }

    // Filter only wants secure notifications but we're receiving on an insecure port, or vice versa.
    if ((m_fIsSecurePort && ! (g_pFilterCon->m_pFilters[iCurrentFilter].dwFlags & SF_NOTIFY_SECURE_PORT)) || 
        (!m_fIsSecurePort && ! (g_pFilterCon->m_pFilters[iCurrentFilter].dwFlags & SF_NOTIFY_NONSECURE_PORT)))
    {
        return FALSE;
    }

    return TRUE;
}

// BOOL CHttpRequest::CallFilter

// Function:   Httpd calls this function on specified events, it's goes through
// the list and calls the registered filters for that event.

// PARAMETERS:
//         DWORD dwNotifyType - what SF_NOTIFIY type occured

//        The last 3 parameters are optional; only 3 filter calls use them.
//         They are used when extra data needs to be passed to the filter that isn't
//         part of the CHttpRequest structure.  

//        PSTR *ppszBuf1  ---> SF_NOTIFY_SEND_RAW_DATA   --> The buffer about to be sent
//                        ---> SF_NOTIFY_URL_MAP            --> The virtual path (only
//                                on ServerSupportFunction with HSE_REQ_MAP_URL_TO_PATH,
//                                otherwise use CHttpRequest values.)
//                         ---> SF_NOTIFY_AUTHENTECATION  --> The remote user name
//                        ---> SF_NOTIFY_READ_RAW_DATA   --> Buffer about to be read

//        PSTR *ppszBuf2  ---> SF_NOTIFY_SEND_RAW_DATA   --> Unused
//                        ---> SF_NOTIFY_URL_MAP           --> The physical mapping
//                        ---> SF_NOTIFY_AUTHENTECATION  --> The user's password
//                        ---> SF_NOTIFY_READ_RAW_DATA   --> Buffer about to be read
//
//        int *pcbBuf        ---> SF_NOTIFY_SEND_RAW_DATA   --> Length of buffer to be sent
//                        ---> SF_NOTIFY_URL_MAP           --> Length of physical path buf
//                        ---> SF_NOTIFY_AUTHENTECATION  --> Unused
//                        ---> SF_NOTIFY_READ_RAW_DATA   --> Buffer about to be read

//      int *pcbBuf2    ---> SF_NOTIFY_READ_RAW_DATA   --> size of the buffer reading into

// return values
//         TRUE  tells calling fcn to continue normal execution
//         FALSE tells calling fcn to terminate this request.

// Notes:
// if FALSE is returned, m_pFilterInfo->m_fAccept is also set to false.  This
// helps the filter handle unwinding from nested filter calls.

// For example, the http server calls a filter with URL_MAP flags.  The filter
// then calls a WriteClient, which will call the filter again with event
// SF_NOTIFY_SEND_RAW_DATA.  If on the SEND_RAW_DATA call the filter returns a 
// code to stop the session, we need to store it for the original MAP_URL call,
// so that it knows to stop the session too.


BOOL CHttpRequest::CallFilter(DWORD dwNotifyType, PSTR *ppszBuf1, 
                              int *pcbBuf, PSTR *ppszBuf2, int *pcbBuf2)

{
    DEBUG_CODE_INIT;
    HTTP_FILTER_CONTEXT fc;
    LPVOID pStFilter;        // 3rd param passed on HttpFilterProc
    LPVOID pStFilterOrg;     // stores a copy of pStFilter, so we remember alloc'd mem to cleanup, if any
    BOOL fFillStructs = TRUE;
    BOOL fReqReadNext;
    BOOL ret = FALSE;
    DWORD dwFilterCode;
    int i;

    if (0 == g_pFilterCon->m_nFilters)
    {
        return TRUE;
    }

    // m_pFInfo->m_fFAccept = FALSE implies no more filter calls, except on the "cleanup" calls
    DEBUGMSG(ZONE_ISAPI,(L"HTTPD: Filter notify type = 0x%08x\r\n",dwNotifyType));
    if ( (! m_pFInfo->m_fFAccept) &&
          ! (dwNotifyType & (SF_NOTIFY_END_OF_REQUEST | SF_NOTIFY_LOG | SF_NOTIFY_END_OF_NET_SESSION | SF_NOTIFY_SEND_RESPONSE | SF_NOTIFY_SEND_RAW_DATA)))
    {
        return FALSE;
    }
    m_pFInfo->m_dwSFEvent = dwNotifyType;

    do 
    {
        pStFilter = pStFilterOrg = NULL;
        fReqReadNext = FALSE;
        
        // Filters implement priority inversion for SEND_RAW_DATA events.
        i = (dwNotifyType == SF_NOTIFY_SEND_RAW_DATA) ? g_pFilterCon->m_nFilters - 1 : 0;
        
        while ( (dwNotifyType == SF_NOTIFY_SEND_RAW_DATA) ? i >= 0 : i < g_pFilterCon->m_nFilters ) 
        {
            // we still reference g_pFilterCon just in case a filter failed in another request
            if (! IsCurrentFilterCalled(dwNotifyType,i)) 
            {            
                // ith filter didn't request this notification, move along
                NEXTFILTER(dwNotifyType, i);
                continue;
            }

            m_pFInfo->m_iFIndex = i;
            if (fFillStructs && (!FillFC(&fc,dwNotifyType,&pStFilter,&pStFilterOrg,
                                         ppszBuf1, pcbBuf, ppszBuf2, pcbBuf2)))
            {
                myretleave(FALSE,210);  // memory error on FillFC
            }
            fFillStructs = FALSE;    // after structs or filled don't refill them

            fc.pFilterContext = m_pFInfo->m_ppvContext[i];

            __try  
            {
                dwFilterCode =     g_pFilterCon->m_pFilters[i].pCISAPI->CallFilter(&fc,dwNotifyType,pStFilter);
            }
            __except(ReportFault(GetExceptionInformation(),0), EXCEPTION_EXECUTE_HANDLER) 
            {
                DEBUGMSG(ZONE_ERROR, (L"HTTPD: ISAPI Filter DLL <<%s>> caused exception 0x%08x\r\n", 
                                            g_pFilterCon->m_pFilters[i].wszDLLName, GetExceptionCode()));

                g_pVars->m_pLog->WriteEvent(IDS_HTTPD_FILT_EXCEPTION,g_pFilterCon->m_pFilters[i].wszDLLName,GetExceptionCode(),L"HttpFilterProc",GetLastError());
                SetConnectionPersist(CONN_FORCECLOSE);
                myleave(216);
            }

            // bail out if another filter call said to, but not on certain events (for compat with IIS)
            // This would happen if a filter triggered an event to occur which caused another filter call
            // to be made, and if that nested filter returned an error code indicating the end of session.
            if ( (! m_pFInfo->m_fFAccept) &&
              ! (dwNotifyType & (SF_NOTIFY_END_OF_REQUEST | SF_NOTIFY_LOG | SF_NOTIFY_END_OF_NET_SESSION | SF_NOTIFY_SEND_RESPONSE | SF_NOTIFY_SEND_RAW_DATA)))
            
            {
                DEBUGMSG(ZONE_ISAPI,(L"HTTPD: Filter indirectly set us to end, ending CallFilter for event= %x,i=%d\r\n",dwNotifyType,i));
                myretleave(FALSE,0);
            }
            
            m_pFInfo->m_ppvContext[i] = fc.pFilterContext;

            DEBUGMSG(ZONE_ISAPI, (L"HTTPD: Filter returned response code of 0x%08x\r\n",dwFilterCode));
            switch (dwFilterCode) 
            {
                // alert calling class that this request + net session is over
                case SF_STATUS_REQ_FINISHED:
                    SetConnectionPersist(CONN_WANTCLOSE);
                    m_pFInfo->m_fSentHeaders = TRUE;  // don't send back headers in these cases.
                    myretleave(FALSE,0);   
                break;

                // alert calling class that this request but not the net session is over
                case SF_STATUS_REQ_FINISHED_KEEP_CONN:
                    SetConnectionPersist(CONN_WANTKEEP);
                    m_pFInfo->m_fSentHeaders = TRUE;  // don't send back headers in these cases.
                    myretleave(FALSE,0);   
                break;

                // goes to top of loop, handle next filter in line
                case SF_STATUS_REQ_NEXT_NOTIFICATION:
                    NEXTFILTER(dwNotifyType, i);
                break;

                // not an error, just done handling current httpd event
                case SF_STATUS_REQ_HANDLED_NOTIFICATION:
                    myretleave(TRUE,0);           
                break;

                // alert calling class that this request is over
                case SF_STATUS_REQ_ERROR:
                    SetConnectionPersist(CONN_FORCECLOSE);
                    m_rs = STATUS_INTERNALERR;
                    myretleave(FALSE,214);
                break;

                case SF_STATUS_REQ_READ_NEXT:
                    // only valid for read raw data events.  IIS ignores if returned by other filters
                    // Continue running through remaining filters before reading more data
                    if (dwNotifyType == SF_NOTIFY_READ_RAW_DATA) 
                    {
                        fReqReadNext = TRUE;                    
                    }
                    NEXTFILTER(dwNotifyType, i);
                break;
                
                default:    // treat like SF_STATUS_REQ_NEXT_NOTIFICATION
                    DEBUGMSG(ZONE_ERROR, (L"HTTPD: Filter returned unknown/unhandled return code\r\n"));
                    NEXTFILTER(dwNotifyType, i);
                break;
            }
        }

        // Only after all filters have been serviced to we handle READ_NEXT event
        // Note that having a filter call SF_READ_REQ_NEXT during a call to an 
        // ISAPI ext ReadClient is completly unsupported.  This has been docced.
        if (fReqReadNext) 
        {
            PHTTP_FILTER_RAW_DATA pRaw = (PHTTP_FILTER_RAW_DATA) pStFilter;
            fFillStructs = TRUE; // set here in case we don't read any data and 
                                 // exit loop, so we don't call CleanupFC 2 times.

            DEBUGCHK(dwNotifyType == SF_NOTIFY_READ_RAW_DATA);

            CleanupFC(dwNotifyType, &pStFilter,&pStFilterOrg,ppszBuf1, pcbBuf, ppszBuf2);
            m_bufRequest.RecvBody(m_socket,m_pFInfo->m_dwNextReadSize ? m_pFInfo->m_dwNextReadSize : g_pVars->m_dwPostReadSize,TRUE, FALSE, this, FALSE);

            // only stop reading if we didn't get any data.  Timeouts are OK, we'll call read filter again
            if (m_bufRequest.UnaccessedCount() == 0)
            {
                fReqReadNext = FALSE;  
            }
            else
            {
                m_pFInfo->m_dwBytesReceived += pRaw->cbInData; // from HTTP recv
            }
        }
    } while (fReqReadNext);

    ret = TRUE;
done:
    DEBUGMSG_ERR(ZONE_ERROR, (L"HTTPD: CIsapiFilterCon::CallFilter FAILED: GLE=%d err=%d\r\n", 
            GetLastError(), err));


    // Udpate our bytes sent/received if need be.  
    // Send bytes update must after we do our cleanup, Read bytes must happen before.
    if (dwNotifyType == SF_NOTIFY_READ_RAW_DATA) 
    {
        // We only allocate pRaw if we need to (ie someone notified for read),
        // if this is so use that value for rx bytes.  If no one notefied for read filt, use
        // the value we were passed for bytes read by calling fcn.
    
        PHTTP_FILTER_RAW_DATA pRaw = (PHTTP_FILTER_RAW_DATA) pStFilter;
        if (pRaw)
        {
            m_pFInfo->m_dwBytesReceived += pRaw->cbInData;
        }
        else if (ppszBuf1)
        {
            m_pFInfo->m_dwBytesReceived += *pcbBuf;  // from ISAPI ReadClient
        }
        else
        {
            m_pFInfo->m_dwBytesReceived += m_bufRequest.UnaccessedCount(); // from HTTP recv
        }
    }            

    //  fFillStructs will be false if no filter registered for this event, which means
    //  we have no cleanup
    if (FALSE == fFillStructs)
    {
        ret = CleanupFC(dwNotifyType, &pStFilter,&pStFilterOrg,ppszBuf1, pcbBuf, ppszBuf2);    
    }

    if (dwNotifyType == SF_NOTIFY_SEND_RAW_DATA)
    {
        m_pFInfo->m_dwBytesSent += *pcbBuf;
    }

    if (!ret) 
    {
        m_pFInfo->m_fFAccept = FALSE;
    }
    return ret;    
}

// sets up the FilterContext data structure so it makes sense to filterr being called.
// Only called once per filter event.

// ppStFilter is sent to filter dlls.
// ppStFilterOrg is a copy, it stores what we've allocated and is used to free up mem, as
// the filter would cause server mem leaks without it as a reference

// Last three parameters are the same as CallFilter

BOOL CHttpRequest::FillFC(PHTTP_FILTER_CONTEXT pfc, DWORD dwNotifyType, 
                          LPVOID *ppStFilter, LPVOID *ppStFilterOrg,
                          PSTR *ppszBuf1, int *pcbBuf, PSTR *ppszBuf2, int *pcbBuf2)
{
    DEBUG_CODE_INIT;
    BOOL ret = FALSE;

    DEBUGCHK((*ppStFilterOrg) == NULL);
    DEBUGCHK((*ppStFilter)    == NULL);
    
    switch (dwNotifyType) 
    {
        case SF_NOTIFY_END_OF_NET_SESSION:
        case SF_NOTIFY_END_OF_REQUEST:
            break;

        case SF_NOTIFY_READ_RAW_DATA:
        {
            PHTTP_FILTER_RAW_DATA pRawData = NULL;
            *ppStFilter = pRawData = MyAllocNZ(HTTP_FILTER_RAW_DATA);
            if (!pRawData)
            {
                myleave(220);
            }

            // We use UnaccessedCount() rather than Count() member of Buffio class
            // because we want the entire buffer for the Filter to read.  For instance,
            // if we had http headers and POST data, Count would only return the size
            // of the headers, while UnaccessedCount returns the size of everything.
            // We give filter the whole buffer.  For IIS compat.

            if (!ppszBuf1)  
            { // Use buffer in CHttpResponse class
                pRawData->cbInData =   m_bufRequest.UnaccessedCount();
                pRawData->cbInBuffer = m_bufRequest.AvailableBufferSize();
                pRawData->pvInData =   m_bufRequest.FilterRawData();  
            }    
            else  
            { //  Called from ISAPI ReadClient()
                pRawData->cbInBuffer  = *pcbBuf2;
                pRawData->cbInData = *pcbBuf;
                pRawData->pvInData = (PVOID) *ppszBuf1;
            }

            pRawData->dwReserved = 0;
            *ppStFilterOrg = MyAllocNZ(HTTP_FILTER_RAW_DATA);

            if (!(*ppStFilterOrg)) 
            {
                MyFree(pRawData);
                *ppStFilter = NULL;
                myleave(240);
            }
            memcpy(*ppStFilterOrg,*ppStFilter,sizeof(HTTP_FILTER_RAW_DATA));        
        }
        break;

        case SF_NOTIFY_PREPROC_HEADERS:
        {
            PHTTP_FILTER_PREPROC_HEADERS pPreProc = NULL;
            *ppStFilter = pPreProc = MyAllocNZ(HTTP_FILTER_PREPROC_HEADERS);
            *ppStFilterOrg = NULL;

            if (!pPreProc)
            {
                myleave(221);
            }
            pPreProc->GetHeader = ::GetHeader;
            pPreProc->SetHeader = ::SetHeader;
            pPreProc->AddHeader = ::SetHeader;
            pPreProc->HttpStatus = 0;        // no response status code this return 
            pPreProc->dwReserved = 0;    
        }
        break;

        case SF_NOTIFY_URL_MAP:
        {
            PHTTP_FILTER_URL_MAP pUrlMap = NULL;
            *ppStFilter = pUrlMap = MyAllocNZ(HTTP_FILTER_URL_MAP);

            if (!pUrlMap)
            {
                myleave(222);
            }

            if (NULL == ppszBuf1) 
            {  // usual case, use data in CHttpRequest
                pUrlMap->pszURL = m_pszURL;
                pUrlMap->pszPhysicalPath = MySzDupWtoA(m_wszPath);

                if (pUrlMap->pszPhysicalPath)
                {
                    pUrlMap->cbPathBuff = MyStrlenA(pUrlMap->pszPhysicalPath);
                }
            }
            else  
            { // called from ISAPI ext with HSE_REQ_MAP_URL_TO_PATH
                pUrlMap->pszURL = (PSTR) *ppszBuf1;
                pUrlMap->pszPhysicalPath = (PSTR) *ppszBuf2;
                pUrlMap->cbPathBuff = *pcbBuf;
            }
            *ppStFilterOrg = MyAllocNZ(HTTP_FILTER_URL_MAP);

            if ( !pUrlMap->pszURL || ! pUrlMap->pszPhysicalPath || ! (*ppStFilterOrg))  
            {
                if (NULL == ppszBuf1) // only in this case do we have to clean this up.
                {
                    MyFree(pUrlMap->pszPhysicalPath);
                }

                MyFree((*ppStFilterOrg));
                *ppStFilterOrg = NULL;

                MyFree(pUrlMap);
                *ppStFilter = NULL;
                myleave(320);
            }

            memcpy(*ppStFilterOrg,*ppStFilter,sizeof(HTTP_FILTER_URL_MAP));
        }
        break;

        case SF_NOTIFY_AUTHENTICATION:
        {
            DEBUGCHK(NULL != ppszBuf1 && NULL != ppszBuf2);
            
            PHTTP_FILTER_AUTHENT pAuth = NULL;
            *ppStFilter = pAuth = MyAllocNZ(HTTP_FILTER_AUTHENT);

            if (!pAuth)
            {
                myleave(223);
            }

            // pszUser + pszPassword are static buffers made in AuthenticateFilter fcn
            pAuth->pszUser = (PSTR) *ppszBuf1; 
            pAuth->cbUserBuff = SF_MAX_USERNAME;
            pAuth->pszPassword = (PSTR) *ppszBuf2;         
            pAuth->cbPasswordBuff = SF_MAX_PASSWORD;
        }
        break;

        case SF_NOTIFY_ACCESS_DENIED:
        {
            PHTTP_FILTER_ACCESS_DENIED pDenied = NULL;
            *ppStFilter = pDenied = MyAllocNZ(HTTP_FILTER_ACCESS_DENIED);

            if (!pDenied)
            {
                myleave(224);
            }

            pDenied->pszURL = m_pszURL;
            pDenied->pszPhysicalPath = MySzDupWtoA(m_wszPath);    
            pDenied->dwReason = SF_DENIED_LOGON;    
            *ppStFilterOrg = MyAllocNZ(HTTP_FILTER_ACCESS_DENIED);

            if (! pDenied->pszURL || ! pDenied->pszPhysicalPath || !(*ppStFilterOrg)) 
            {
                MyFree(pDenied->pszPhysicalPath);
                MyFree((*ppStFilterOrg));
                MyFree(pDenied);
                *ppStFilter = NULL;
                myleave(322);
            }
            memcpy(*ppStFilterOrg,*ppStFilter,sizeof(HTTP_FILTER_ACCESS_DENIED));
        }
        break;

        case SF_NOTIFY_SEND_RESPONSE:
        {
            PHTTP_FILTER_SEND_RESPONSE pSendRes = NULL;
            *ppStFilter = pSendRes = MyAllocNZ(HTTP_FILTER_PREPROC_HEADERS);
            
            if (!pSendRes)
            {
                myleave(225);
            }

            pSendRes->GetHeader  = ::GetHeader;
            pSendRes->SetHeader  = ::SetHeader;
            pSendRes->AddHeader  = ::SetHeader;
            pSendRes->HttpStatus = rgStatus[m_rs].dwStatusNumber;    
            pSendRes->dwReserved = 0;
        }
        break;

        case SF_NOTIFY_SEND_RAW_DATA:
        {
            PHTTP_FILTER_RAW_DATA pRawData = NULL;
            *ppStFilter = pRawData = MyAllocNZ(HTTP_FILTER_RAW_DATA);
            *ppStFilterOrg = MyAllocNZ(HTTP_FILTER_RAW_DATA);

            if (!pRawData || !(*ppStFilterOrg)) 
            {
                MyFree(pRawData);
                MyFree((*ppStFilterOrg));
                *ppStFilter = *ppStFilterOrg = NULL;
                myleave(220);
            }

            pRawData->pvInData = *ppszBuf1;  
            pRawData->cbInData = *pcbBuf;
            pRawData->cbInBuffer = *pcbBuf;
            pRawData->dwReserved = 0;

            memcpy(*ppStFilterOrg,*ppStFilter,sizeof(HTTP_FILTER_RAW_DATA));
        }
        break;

        case SF_NOTIFY_LOG:
        {
            PHTTP_FILTER_LOG pLog = NULL;
            CHAR szHostBuf[MAX_PATH];
            
            *ppStFilter = pLog = MyAllocZ(HTTP_FILTER_LOG);
            if (!pLog)
            {
                myleave(226);
            }

            if (NULL == (pLog->pszClientHostName = (CHAR*)svsutil_AllocZ(LOG_REMOTE_ADDR_SIZE*sizeof(CHAR))))
            {
                goto cleanupFCLog;
            }

            GetRemoteAddress(m_socket,(PSTR) pLog->pszClientHostName,FALSE,LOG_REMOTE_ADDR_SIZE);

            if ( 0 == gethostname(szHostBuf, sizeof(szHostBuf))) 
            {
                if (NULL == (pLog->pszServerName = MySzDupA(szHostBuf)))
                {
                    goto cleanupFCLog;
                }
            }
            else
            {
                pLog->pszServerName = cszEmpty;
            }

            pLog->pszClientUserName = m_pszRemoteUser;
            pLog->pszOperation = m_pszMethod;  
            pLog->pszTarget = m_pszURL;
            pLog->pszParameters = m_pszQueryString;
            pLog->dwWin32Status = GetLastError();
            pLog->dwBytesSent = m_pFInfo->m_dwBytesSent;        
            pLog->dwBytesRecvd = m_pFInfo->m_dwBytesReceived;
            pLog->msTimeForProcessing = GetTickCount() - m_pFInfo->m_dwStartTick;

            pLog->dwHttpStatus = rgStatus[m_rs].dwStatusNumber;

            // don't pass NULL, give 'em empty string on certain cases
            if ( ! pLog->pszClientUserName)  
            {
                pLog->pszClientUserName = cszEmpty;
            }

            if ( ! pLog->pszParameters )
            {
                pLog->pszParameters = cszEmpty;
            }

            *ppStFilterOrg = MyAllocNZ(HTTP_FILTER_LOG);

cleanupFCLog:
            if (    !pLog->pszClientUserName  || !pLog->pszServerName  
                ||  !pLog->pszOperation       || !pLog->pszTarget      
                ||  !pLog->pszParameters      || !pLog->pszClientHostName  
                ||   !(*ppStFilterOrg))
            {
                MyFree((*ppStFilterOrg));
                MyFree(pLog->pszClientHostName);

                if (pLog->pszServerName != cszEmpty) 
                {
                    MyFree(pLog->pszServerName);
                }

                MyFree(pLog);
                *ppStFilter = NULL;
                myleave(344);
            }

            memcpy(*ppStFilterOrg,*ppStFilter,sizeof(HTTP_FILTER_LOG));
        }
        break;

        default:
            DEBUGCHK(0);
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: FillFC received unknown notification type = %d\r\n",dwNotifyType));
            *ppStFilterOrg = *ppStFilter = NULL;
            myleave(246);
        break;
    }

    // the pfc is always the same regardless of dwNotifyType
    pfc->cbSize = sizeof(*pfc);
    pfc->Revision = HTTP_FILTER_REVISION;
    pfc->ServerContext = (PVOID) this;
    pfc->ulReserved = 0;
    pfc->fIsSecurePort = m_fIsSecurePort;
    //  pfc->pFilterContext is filled in calling loop in CallFilter()
    pfc->GetServerVariable = ::GetServerVariable;
    pfc->AddResponseHeaders = ::AddResponseHeaders;
    pfc->WriteClient = ::WriteClient;
    pfc->AllocMem = ::AllocMem;
    pfc->ServerSupportFunction = ::ServerSupportFunction;

    ret = TRUE;
done:
    DEBUGMSG_ERR(ZONE_ERROR,(L"HTTPD: FillFC failed on mem alloc, GLE = %d, err= %d\r\n",GetLastError(),err));
    
    return ret;
}


// Final fcn called in CallFilter, this frees any unneeded allocated memory
// and sets the last three values, if valid, to whatever the filter changed them to.
BOOL CHttpRequest::CleanupFC(DWORD dwNotifyType, LPVOID* ppStFilter, LPVOID* ppStFilterOrg,
                            PSTR *ppszBuf1, int *pcbBuf, PSTR *ppszBuf2)

{
    BOOL fRet = TRUE;

    switch (dwNotifyType)
    {
        case SF_NOTIFY_END_OF_NET_SESSION:
        case SF_NOTIFY_END_OF_REQUEST:
        case SF_NOTIFY_PREPROC_HEADERS:
        case SF_NOTIFY_SEND_RESPONSE:
            break;

        case SF_NOTIFY_READ_RAW_DATA:
        {
            PHTTP_FILTER_RAW_DATA pRawData = (PHTTP_FILTER_RAW_DATA) *ppStFilter;            

            if (! ppszBuf1) 
            { // Use buffer in CHttpResponse class
                m_bufRequest.FilterDataUpdate(pRawData->pvInData,pRawData->cbInData,pRawData->pvInData != m_bufRequest.FilterRawData());
            }
            else 
            {
                *ppszBuf1 = (PSTR) pRawData->pvInData;
                *pcbBuf =   pRawData->cbInData;
            }
        }
        break;

        case SF_NOTIFY_AUTHENTICATION: 
        {
            PHTTP_FILTER_AUTHENT pAuth = (PHTTP_FILTER_AUTHENT) *ppStFilter;

            *ppszBuf1 = pAuth->pszUser;
            *ppszBuf2 = pAuth->pszPassword;
        }
        break;
        
        case SF_NOTIFY_SEND_RAW_DATA:
        {
            PHTTP_FILTER_RAW_DATA pRawData = (PHTTP_FILTER_RAW_DATA) *ppStFilter;
            *ppszBuf1 = (PSTR) pRawData->pvInData;
            *pcbBuf  = pRawData->cbInData;
        }
        break;

        case SF_NOTIFY_URL_MAP:
        {
            PHTTP_FILTER_URL_MAP pUrlMap = (PHTTP_FILTER_URL_MAP) *ppStFilter ;
            PHTTP_FILTER_URL_MAP pUrlMapOrg = (PHTTP_FILTER_URL_MAP) *ppStFilterOrg;

            // Case when parsing http headers from a request.
            if (NULL == ppszBuf1) 
            {
                // since data can be modified in place, always re-copy                        
                MyFree(m_wszPath);
                m_wszPath = MySzDupAtoW(pUrlMap->pszPhysicalPath);

                MyFree(pUrlMapOrg->pszPhysicalPath);
                if (!m_wszPath)
                {
                    fRet = FALSE;
                }

                // note: don't set m_ccWPath here because it will be set in ParseHeaders() anyway.
            }
            else 
            { // ServerSupportFunction with HSE_MAP_URL call
                *ppszBuf1 = (PSTR) pUrlMap->pszURL;
                *ppszBuf2 = pUrlMap->pszPhysicalPath;
                *pcbBuf = pUrlMap->cbPathBuff;
            }
        }
        break;

        case SF_NOTIFY_ACCESS_DENIED:
        {
            PHTTP_FILTER_ACCESS_DENIED pDeniedOrg = (PHTTP_FILTER_ACCESS_DENIED) *ppStFilterOrg;
            // If they change pDenied->pszURL we ignore it.  Since the session
            // is coming to an end because of access problems, the only thing changing 
            // pszURL could affect would be logging (which should be set through logging filter)        
            MyFree(pDeniedOrg->pszPhysicalPath);
        }
        break;

        // We use m_pFInfo->m_pFLog to store changes to the log.  When
        // we write out the log, we use the web server value by default
        // unless a particular value is non-NULL; by default all values
        // are NULL in m_pFInfo->m_pFLog unless overwritten by this code.
        case SF_NOTIFY_LOG:
        {
            PHTTP_FILTER_LOG pLog = (PHTTP_FILTER_LOG) *ppStFilter;
            PHTTP_FILTER_LOG pLogOrg = (PHTTP_FILTER_LOG ) *ppStFilterOrg;
            PHTTP_FILTER_LOG pFLog;

            // Free dynamically allocated data.
            MyFreeNZ(pLogOrg->pszClientHostName);

            if (pLogOrg->pszServerName != cszEmpty)
            {
                MyFreeNZ(pLogOrg->pszServerName);
            }

            // If no changes were made, break out early.
            if ( ! memcmp(pLog, pLogOrg, sizeof(HTTP_FILTER_LOG))) 
            {
                break;                  
            }

            if (NULL == (pFLog = m_pFInfo->m_pFLog = MyAllocZ(HTTP_FILTER_LOG))) 
            {
                // Memory errors just mean we won't bother doing logging, non-fatal.
                break;  
            }
            
            if (pLog->pszClientHostName != pLogOrg->pszClientHostName)
            {
                pFLog->pszClientHostName = pLog->pszClientHostName;
            }

            if (pLog->pszServerName  != pLogOrg->pszServerName)
            {
                pFLog->pszServerName = pLog->pszServerName;
            }

            if (pLog->pszClientUserName  != pLogOrg->pszClientUserName)
            {
                pFLog->pszClientUserName = pLog->pszClientUserName;
            }

            if (pLog->pszOperation  != pLogOrg->pszOperation)
            {
                pFLog->pszOperation = pLog->pszOperation;
            }

            if (pLog->pszTarget  != pLogOrg->pszTarget)
            {
                pFLog->pszTarget = pLog->pszTarget;
            }

            if (pLog->pszParameters  != pLogOrg->pszParameters)
            {
                pFLog->pszParameters = pLog->pszParameters ;
            }

            pFLog->dwHttpStatus        = pLog->dwHttpStatus;
            pFLog->dwWin32Status       = pLog->dwWin32Status;
            pFLog->dwBytesSent         = pLog->dwBytesSent;
            pFLog->dwBytesRecvd        = pLog->dwBytesRecvd ;
            pFLog->msTimeForProcessing = pLog->msTimeForProcessing;        
        }
        
        break;

        default:
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: CleanupFCs received unknown code = %d\r\n",dwNotifyType));
        break;

    }

    MyFree(*ppStFilterOrg);
    MyFree(*ppStFilter);
    return fRet;
}

//  Initializes buffers for CallFilter with SF_NOTIFY_AUTHENTICATION, and tries
//  to authenticate if the filter changes any data.

//  Note:  Even in the case where security isn't a component or is disabled in 
//         registry, we make this call because filter may theoretically decide to end
//           the session based on the user information it receives.

BOOL CHttpRequest::AuthenticateFilter() 
{
    DEBUG_CODE_INIT;
    BOOL ret = FALSE;
    HRESULT hr = S_OK;

    // IIS docs promise the filter minimum sized buffers to write password
    // and user name into, we provide them
    char szUserName[SF_MAX_USERNAME];
    char szPassword[SF_MAX_PASSWORD];  

    PSTR pszNewUserName = szUserName;
    PSTR pszNewPassword = szPassword;

    // We only make notification if we're using Anonymous or BASIC auth,
    // otherwise we don't know how to decode the text/password.  Like IIS.
    if ( m_pszAuthType && 0 != _stricmp(m_pszAuthType,cszBasic))
    {
        return TRUE;
    }

    // write existing value, if any, into the new buffers

    //  In this case we've cracked the user name and password into ANSI already.
    if (m_pszRemoteUser) 
    {
        // These are safe strcpy's because HandleBasicAuth cracked
        // out the strings and made sure their combined len was < MAX_PATH.
        DEBUGCHK(strlen(m_pszRemoteUser) < sizeof(szUserName));
        DEBUGCHK(strlen(m_pszPassword)   < sizeof(szPassword));
        
        StringCbCopyA(szUserName,sizeof(szUserName),m_pszRemoteUser);
        StringCbCopyA(szPassword,sizeof(szPassword),m_pszPassword);
    }
    //  We have user user name data but it hasn't been Base64 Decoded yet
    else if (m_pszRawRemoteUser) 
    {
        DWORD dwLen = sizeof(szUserName);

        if (strlen(m_pszRawRemoteUser) >= MAXUSERPASS) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: Base64 data > 256 bytes on Basic Auth, failing request\r\n"));
            m_rs = STATUS_FORBIDDEN;
            return FALSE;
        }
    
        if (! svsutil_Base64Decode(m_pszRawRemoteUser,(void*)szUserName,sizeof(szUserName),NULL,TRUE)) 
        {
            DEBUGMSG(ZONE_ERROR,(L"HTTPD: Base64 data is invalid, failing request\r\n"));
            m_rs = STATUS_BADREQ;
            return FALSE;
        }

        PSTR pszDivider =  strchr(szUserName, ':');
        if(NULL == pszDivider)
        {
            myleave(290);
        }
        *pszDivider++ = 0;        // seperate user & pass
    
        //  We need copies of this data for later in the fcn
        m_pszRemoteUser = MySzDupA(szUserName, strlen(szUserName));
        if (NULL == m_pszRemoteUser)
        {
            myleave(291);
        }
                
        m_pszPassword = MySzDupA(pszDivider);
        if (NULL == m_pszPassword)
        {
            myleave(292);
        }
        
        hr = StringCbCopyA(szPassword, sizeof(szPassword), m_pszPassword);
        if(FAILED(hr)) {
            myleave(292);
        }
        hr = StringCbCopyA(szUserName, sizeof(szUserName), m_pszRemoteUser);
        if(FAILED(hr)) {
            myleave(291);
        }
    }
    // Otherwise the browser didn't set any user information.
    else 
    {
        szUserName[0] = '\0';
        szPassword[0] = '\0';
    }

    if ( ! CallFilter(SF_NOTIFY_AUTHENTICATION,&pszNewUserName, NULL, &pszNewPassword))
    {
        myleave(293);
    }


    //  If the filter arbitrarily denied access, then we exit. 
    //  Check if the filter has changed the user name ptr or modified it in place.
    
    if ( pszNewUserName != szUserName ||                            // changed ptr
         (m_pszRemoteUser == NULL  && szUserName[0] != '\0') ||        // modified in place
         (m_pszRemoteUser && strcmp(szUserName, m_pszRemoteUser) )  // modified in place
       )                        
    {
        // Update m_pszRemoteUser with what was set in filter because GetServerVariable
        // may need it.
        ResetString(m_pszRemoteUser, pszNewUserName);
        if (! m_pszRemoteUser)
        {
            myleave(294);
        }
    }

    if ( pszNewPassword != szPassword ||
            (m_pszPassword == NULL  && szPassword[0] != '\0') ||
         (m_pszPassword && strcmp(szPassword, m_pszPassword))
         )
    {
        // GetServerVariables with AUTH_PASSWORD might request this.
        ResetString(m_pszPassword, pszNewPassword);
        if (! m_pszPassword)
        {
            myleave(295);
        }
    }


    ret = TRUE;
done:
    DEBUGMSG_ERR(ZONE_ERROR,(L"HTTPD: Authentication for filters failed, err = %d, GLE = %d\r\n",err,GetLastError()));
    return ret;
}


//  Called on updating data from an ISAPI filter, resets internal structures.  
//  fModifiedPtr is TRUE when 

BOOL CBuffer::FilterDataUpdate(PVOID pvData, DWORD cbData, BOOL fModifiedPtr) 
{
    // a filter can do anything to 2nd HTTP request waiting in buffer, so
    // just mark it as invalid to play it safe.
    InvalidateNextRequestIfAlreadyRead();

    if (fModifiedPtr) 
    {
        if ((int) (cbData - UnaccessedCount()) > 0) 
        {
            if (! AllocMem(cbData - UnaccessedCount() + 1))
            {
                return FALSE;
            }

            memcpy(m_pszBuf + m_iNextInFollow, pvData, cbData);
        }
    }
    // It's possible we modified data in place, so we always update the size
    // information that the filter passed us in case it changed the # of bytes
    // in the request.

    m_iNextIn = (int)cbData + m_iNextInFollow;
    m_iNextInFollow = m_iNextIn;
    
    return TRUE;
}
