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

Copyright (c) 2000 Microsoft Corporation

Module Name:
    HttpContext.cpp

Abstract:
    This file contains the implementation of the MPCHttpContext class,
    which handles the interface with IIS.

Revision History:
    Davide Massarenti   (Dmassare)  04/20/99
        created

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

#include "stdafx.h"


#define BUFFER_SIZE_TMP      (64)


static const char szStatus [] = "200 OK";
static const char szNewLine[] = "\r\n";


/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
//
// Static functions.
//
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////


static void SupportAddHeader( /*[in/out]*/ MPC::string& szHeaders     ,
                              /*[in]    */ const char*  szHeaderName  ,
                              /*[in]    */ const char*  szHeaderValue )
{
    __ULT_FUNC_ENTRY("SupportAddHeader");


    szHeaders.append( szHeaderName  );
    szHeaders.append( ": "          );
    szHeaders.append( szHeaderValue );
    szHeaders.append( szNewLine     );
}

static void SupportAddHeader( /*[in/out]*/ MPC::string& szHeaders     ,
                              /*[in]    */ const char*  szHeaderName  ,
                              /*[in]    */ DWORD        dwHeaderValue )
{
    __ULT_FUNC_ENTRY("SupportAddHeader");

    char rgBuf[BUFFER_SIZE_TMP];


    sprintf( rgBuf, "%lu", dwHeaderValue );

    SupportAddHeader( szHeaders, szHeaderName, rgBuf );
}

static void SupportEndHeaders( /*[in/out]*/ MPC::string& szHeaders )
{
    __ULT_FUNC_ENTRY("SupportEndHeaders");


    szHeaders.append( szNewLine );
}

//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//
// Construction/Destruction
//
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

MPCHttpContext::MPCHttpContext() : m_hsInput (g_Heap),
                                   m_hsOutput(g_Heap)
{
    __ULT_FUNC_ENTRY("MPCHttpContext::MPCHttpContext");


    m_pECB              = NULL;

    m_mpcsServer        = NULL;
    m_dwSkippedInput    = 0;
    m_fRequestProcessed = FALSE;
    m_fKeepConnection   = TRUE;

    m_fAsync            = FALSE;
    m_FSMstate          = FSM_REGISTER;
    m_IOstate           = IO_IDLE;


#ifdef DEBUG
    m_Debug_NO_RESPONSE_TO_OPEN           = false;

    m_Debug_NO_RESPONSE_TO_WRITE          = false;

    m_Debug_RESPONSE_TO_OPEN              = false;
    m_Debug_RESPONSE_TO_OPEN_response     = 0;
    m_Debug_RESPONSE_TO_OPEN_position     = -1;
    m_Debug_RESPONSE_TO_OPEN_protocol     = UPLOAD_LIBRARY_PROTOCOL_VERSION_SRV;

    m_Debug_RESPONSE_TO_WRITE             = false;
    m_Debug_RESPONSE_TO_WRITE_response    = 0;
    m_Debug_RESPONSE_TO_WRITE_position    = -1;
    m_Debug_RESPONSE_TO_WRITE_protocol    = UPLOAD_LIBRARY_PROTOCOL_VERSION_SRV;

    m_Debug_RANDOM_POINTER_ERROR          = false;
    m_Debug_RANDOM_POINTER_ERROR_pos_low  = 0;
    m_Debug_RANDOM_POINTER_ERROR_pos_high = -1;

    m_Debug_FIXED_POINTER_ERROR           = false;
    m_Debug_FIXED_POINTER_ERROR_pos       = 0;
#endif
}

MPCHttpContext::~MPCHttpContext()
{
    __ULT_FUNC_ENTRY("MPCHttpContext::~MPCHttpContext");


    if(m_mpcsServer) delete m_mpcsServer;

    if(m_fAsync && m_pECB)
    {
        //
        //  Close session.
        //
        m_pECB->ServerSupportFunction( m_pECB->ConnID            ,
                                       HSE_REQ_DONE_WITH_SESSION ,
                                       NULL                      ,
                                       NULL                      ,
                                       NULL                      );
    }
}


//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//
// Callbacks
//
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

VOID WINAPI MPCHttpContext::IOCompletion( /*[in]*/ EXTENSION_CONTROL_BLOCK* pECB     ,
                                          /*[in]*/ PVOID                    pContext ,
                                          /*[in]*/ DWORD                    cbIO     ,
                                          /*[in]*/ DWORD                    dwError  )
{
    __ULT_FUNC_ENTRY("MPCHttpContext::IOCompletion");

    MPCHttpContext* ptr = NULL;

    try
    {
        ptr = reinterpret_cast<MPCHttpContext*>(pContext);

        ptr->m_pECB = pECB;

        if(dwError != ERROR_SUCCESS)
        {
            delete ptr; ptr = NULL;
        }
        else
        {
            switch( ptr->m_IOstate )
            {
            case IO_IDLE:
                break;

            case IO_READING:
                //
                // If the request has already been processed, simply count the number of bytes received.
                //
                if(ptr->m_fRequestProcessed)
                {
                    ptr->m_dwSkippedInput += cbIO;
                }
                else
                {
                    ptr->m_hsInput.write( ptr->m_rgBuffer, cbIO );
                }

                //
                // If this is the last request (cbIO==0) or the number of bytes skipped is equal to the number of missing bytes,
                // proceed to the next phase.
                //
                if(cbIO == 0 || ptr->m_dwSkippedInput == ptr->m_hsInput.GetAvailableForWrite())
                {
                    ptr->m_IOstate = IO_IDLE;
                }
                else
                {
                    if(ptr->Fsm_Process() == HSE_STATUS_ERROR)
                    {
                        delete ptr; ptr = NULL;
                    }
                    else
                    {
                        ptr->AsyncRead();
                    }
                }
                break;

            case IO_WRITING:
                if(cbIO == 0 || ptr->m_hsOutput.IsEOR())
                {
                    ptr->m_IOstate = IO_IDLE;
                }
                else
                {
                    ptr->AsyncWrite();
                }
                break;
            }

            if(ptr->m_IOstate == IO_IDLE)
            {
                ptr->AdvanceFSM();
            }
        }
    }
    catch(...)
    {
        __ULT_TRACE_ERROR( UPLOADLIBID, "Upload Server raised an exception. Gracefully exiting..." );

        (void)g_NTEvents.LogEvent( EVENTLOG_ERROR_TYPE, PCHUL_ERR_EXCEPTION,
                                   L""            , // %1 = SERVER
                                   L"IOCompletion", // %2 = CLIENT
                                   NULL           );

        if(ptr)
        {
            delete ptr; ptr = NULL;
        }
    }
}


//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//
// Protected Methods.
//
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

DWORD MPCHttpContext::AsyncRead()
{
    __ULT_FUNC_ENTRY("MPCHttpContext::AsyncRead");

    DWORD dwRes  = HSE_STATUS_SUCCESS;
    DWORD dwSize = m_hsInput.GetAvailableForWrite();


    //
    // If not all the data has been read, ask for async I/O operation.
    //
    if(dwSize)
    {
        DWORD dwTmp = HSE_IO_ASYNC;

        m_fAsync  = TRUE;
        m_IOstate = IO_READING;

        dwSize = min( dwSize, sizeof(m_rgBuffer) );

        if(!m_pECB->ServerSupportFunction(  m_pECB->ConnID           ,
                                            HSE_REQ_ASYNC_READ_CLIENT,
                                            m_rgBuffer               ,
                                           &dwSize                   ,
                                           &dwTmp                    ))
        {
            dwRes = HSE_STATUS_ERROR; __ULT_FUNC_LEAVE;
        }
        else
        {
            dwRes = HSE_STATUS_PENDING; __ULT_FUNC_LEAVE;
        }
    }


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(dwRes);
}

DWORD MPCHttpContext::AsyncWrite()
{
    __ULT_FUNC_ENTRY("MPCHttpContext::AsyncWrite");

    DWORD dwRes  = HSE_STATUS_SUCCESS;
    DWORD dwSize = m_hsOutput.GetAvailableForRead();


    //
    // If not all the data has been read, ask for async I/O operation.
    //
    if(dwSize)
    {
        m_fAsync  = TRUE;
        m_IOstate = IO_WRITING;

        dwSize = min( dwSize, sizeof(m_rgBuffer) );

        if(FAILED(m_hsOutput.read( m_rgBuffer, dwSize )))
        {
            dwRes = HSE_STATUS_ERROR; __ULT_FUNC_LEAVE;
        }

        if(m_pECB->WriteClient( m_pECB->ConnID, m_rgBuffer, &dwSize, HSE_IO_ASYNC ) == FALSE)
        {
            dwRes = HSE_STATUS_ERROR; __ULT_FUNC_LEAVE;
        }

        dwRes = HSE_STATUS_PENDING; __ULT_FUNC_LEAVE;
    }


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(dwRes);
}


DWORD MPCHttpContext::AdvanceFSM()
{
    __ULT_FUNC_ENTRY("MPCHttpContext::AdvanceFSM");

    DWORD dwRes  = HSE_STATUS_SUCCESS;
    bool  fClean = false;


    while(dwRes == HSE_STATUS_SUCCESS)
    {
        switch(m_FSMstate)
        {
        case FSM_REGISTER: m_FSMstate = FSM_INPUT  ; dwRes = Fsm_Register    (); break; // Register IO callback.
        case FSM_INPUT   : m_FSMstate = FSM_PROCESS; dwRes = Fsm_ReceiveInput(); break; // Read all the input.
        case FSM_PROCESS : m_FSMstate = FSM_OUTPUT ; dwRes = Fsm_Process     (); break; // Process request.
        case FSM_OUTPUT  : m_FSMstate = FSM_DELETE ; dwRes = Fsm_SendOutput  (); break; // Send output.
        case FSM_DELETE  : fClean     = true;        __ULT_FUNC_LEAVE;                  // Delete the request object.
        }
    }

    if(dwRes != HSE_STATUS_SUCCESS &&
       dwRes != HSE_STATUS_PENDING  )
    {
        fClean = true;
    }


    __ULT_FUNC_CLEANUP;

    if(fClean) delete this;

    __ULT_FUNC_EXIT(dwRes);
}


DWORD MPCHttpContext::Fsm_Register()
{
    __ULT_FUNC_ENTRY("MPCHttpContext::Fsm_Register");

    DWORD dwRes = HSE_STATUS_SUCCESS;


    if(!m_pECB->ServerSupportFunction( m_pECB->ConnID       ,
                                       HSE_REQ_IO_COMPLETION,
                                       IOCompletion         ,
                                       NULL                 ,
                                       (LPDWORD)this        ))
    {
        dwRes = HSE_STATUS_ERROR; __ULT_FUNC_LEAVE;
    }


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(dwRes);
}

DWORD MPCHttpContext::Fsm_ReceiveInput()
{
    __ULT_FUNC_ENTRY("MPCHttpContext::Fsm_ReceiveInput");

    DWORD dwRes = HSE_STATUS_SUCCESS;


    //
    // Alloc a buffer large enough to hold the request data.
    //
    if(FAILED(m_hsInput.SetSize(                  m_pECB->cbTotalBytes ))) { dwRes = HSE_STATUS_ERROR; __ULT_FUNC_LEAVE; }
    if(FAILED(m_hsInput.write  ( m_pECB->lpbData, m_pECB->cbAvailable  ))) { dwRes = HSE_STATUS_ERROR; __ULT_FUNC_LEAVE; }

    dwRes = AsyncRead();


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(dwRes);
}

DWORD MPCHttpContext::Fsm_Process()
{
    __ULT_FUNC_ENTRY("MPCHttpContext::Fsm_Process");

    HRESULT hr;
    DWORD   dwRes = HSE_STATUS_SUCCESS;


    if(m_fRequestProcessed == TRUE) __ULT_FUNC_LEAVE;

    m_hsInput .Rewind();
    m_hsOutput.Reset ();

    if(FAILED(hr = m_mpcsServer->Process( m_fKeepConnection )))
    {
        if(hr == E_PENDING)
        {
            dwRes = HSE_STATUS_PENDING; __ULT_FUNC_LEAVE;
        }

        m_fRequestProcessed = TRUE;

        dwRes = HSE_STATUS_ERROR; __ULT_FUNC_LEAVE;
    }

    m_fRequestProcessed = TRUE;


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(dwRes);
}

DWORD MPCHttpContext::Fsm_SendOutput()
{
    __ULT_FUNC_ENTRY("MPCHttpContext::Fsm_SendOutput");

    HSE_SEND_HEADER_EX_INFO headerInfo;
    MPC::string             szHeaders;
    DWORD                   dwRes;


    //
    // Built headers.
    //
    SupportAddHeader( szHeaders, "Content-Length", m_hsOutput.GetSize()        );
    SupportAddHeader( szHeaders, "Content-Type"  , "application/uploadlibrary" );

    SupportEndHeaders( szHeaders );

    //
    //  Populate HSE_SEND_HEADER_EX_INFO struct.
    //
    headerInfo.pszStatus = szStatus;
    headerInfo.cchStatus = strlen( szStatus );
    headerInfo.pszHeader = szHeaders.c_str();
    headerInfo.cchHeader = szHeaders.length();
    headerInfo.fKeepConn = TRUE;

    //
    //  Send response.
    //
    if(!m_pECB->ServerSupportFunction(  m_pECB->ConnID                  ,
                                        HSE_REQ_SEND_RESPONSE_HEADER_EX ,
                                       &headerInfo                      ,
                                        NULL                            ,
                                        NULL                            ))
    {
        dwRes = HSE_STATUS_ERROR; __ULT_FUNC_LEAVE;
    }

    //
    // Send data, if present.
    //
    dwRes = AsyncWrite();


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(dwRes);
}


//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//
// Methods.
//
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

DWORD MPCHttpContext::Init( /*[in]*/ LPEXTENSION_CONTROL_BLOCK pECB )
{
    __ULT_FUNC_ENTRY("MPCHttpContext::Init");

    DWORD        dwRes;
    MPC::wstring szURL;
    MPC::wstring szUser;


    m_pECB = pECB;


    if(FAILED(GetServerVariable( "URL", szURL )))
    {
        szURL = L"DEFAULT";
    }

    if(FAILED(GetServerVariable( "REMOTE_USER", szUser )))
    {
        szUser = L"";
    }


#ifdef DEBUG
    if(pECB->lpszQueryString)
    {
        static MPC::string constNO_RESPONSE_TO_OPEN ( "NO_RESPONSE_TO_OPEN"  );
        static MPC::string constNO_RESPONSE_TO_WRITE( "NO_RESPONSE_TO_WRITE" );
        static MPC::string constRESPONSE_TO_OPEN    ( "RESPONSE_TO_OPEN"     );
        static MPC::string constRESPONSE_TO_WRITE   ( "RESPONSE_TO_WRITE"    );
        static MPC::string constRANDOM_POINTER_ERROR( "RANDOM_POINTER_ERROR" );
        static MPC::string constFIXED_POINTER_ERROR ( "FIXED_POINTER_ERROR"  );


        std::vector<MPC::string> vec;
        std::vector<MPC::string> vec2;
        std::vector<MPC::string> vec3;
        MPC::NocaseCompare       cmp;
        int                      i;

        MPC::SplitAtDelimiter( vec, pECB->lpszQueryString, "&" );

        for(i=0; i<vec.size(); i++)
        {
            MPC::SplitAtDelimiter( vec2, vec[i].c_str(), "=" );

            switch( vec2.size() )
            {
            default:
            case 2 : MPC::SplitAtDelimiter( vec3, vec2[1].c_str(), "," );
            case 1 : break;
            case 0 : continue;
            }

            MPC::string& name = vec2[0];

            if(cmp( name, constNO_RESPONSE_TO_OPEN ))
            {
                m_Debug_NO_RESPONSE_TO_OPEN = true;
            }
            else if(cmp( name, constNO_RESPONSE_TO_WRITE ))
            {
                m_Debug_NO_RESPONSE_TO_WRITE = true;
            }
            else if(cmp( name, constRESPONSE_TO_OPEN ))
            {
                switch( vec3.size() )
                {
                case 3 : m_Debug_RESPONSE_TO_OPEN_protocol = atol( vec3[2].c_str() );
                case 2 : m_Debug_RESPONSE_TO_OPEN_position = atol( vec3[1].c_str() );
                case 1 : m_Debug_RESPONSE_TO_OPEN_response = atol( vec3[0].c_str() );
                         m_Debug_RESPONSE_TO_OPEN          = true;
                }
            }
            else if(cmp( name, constRESPONSE_TO_WRITE ))
            {
                switch( vec3.size() )
                {
                case 3 : m_Debug_RESPONSE_TO_WRITE_protocol = atol( vec3[2].c_str() );
                case 2 : m_Debug_RESPONSE_TO_WRITE_position = atol( vec3[1].c_str() );
                case 1 : m_Debug_RESPONSE_TO_WRITE_response = atol( vec3[0].c_str() );
                         m_Debug_RESPONSE_TO_WRITE          = true;
                }
            }
            else if(cmp( name, constRANDOM_POINTER_ERROR ))
            {
                switch( vec3.size() )
                {
                case 2: m_Debug_RANDOM_POINTER_ERROR_pos_high = atol( vec3[1].c_str() );
                        m_Debug_RANDOM_POINTER_ERROR_pos_low  = atol( vec3[0].c_str() );
                        m_Debug_RANDOM_POINTER_ERROR          = true;
                }
            }
            else if(cmp( name, constFIXED_POINTER_ERROR ))
            {
                switch( vec3.size() )
                {
                case 1 : m_Debug_FIXED_POINTER_ERROR_pos = atol( vec3[0].c_str() );
                         m_Debug_FIXED_POINTER_ERROR     = true;
                }
            }
        }
    }
#endif

    m_mpcsServer = new MPCServer( this, szURL.c_str(), szUser.c_str() );

    if(m_mpcsServer == NULL)
    {
        dwRes = HSE_STATUS_ERROR; __ULT_FUNC_LEAVE;
    }

    dwRes = AdvanceFSM();


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(dwRes);
}


HRESULT MPCHttpContext::GetServerVariable( /*[in]*/ LPCSTR szVar, /*[out]*/ MPC::wstring& szValue )
{
    __ULT_FUNC_ENTRY("MPCHttpContext::GetServerVariable");

    USES_CONVERSION;

    HRESULT hr;
    DWORD   dwRes;
    LPSTR   szData = NULL;
    DWORD   dwSize = 0;


    m_pECB->GetServerVariable( m_pECB->ConnID, (LPSTR)szVar, NULL, &dwSize );

    dwRes = ::GetLastError();
    if(dwRes != ERROR_INSUFFICIENT_BUFFER)
    {
        __MPC_SET_WIN32_ERROR_AND_EXIT(hr, dwRes);
    }

    __MPC_EXIT_IF_ALLOC_FAILS(hr, szData, new CHAR[dwSize+1]);

    __MPC_EXIT_IF_CALL_RETURNS_FALSE(hr, m_pECB->GetServerVariable( m_pECB->ConnID, (LPSTR)szVar, szData, &dwSize ));

    szValue = A2W( szData );
    hr      = S_OK;


    __ULT_FUNC_CLEANUP;

    if(szData) delete [] szData;

    __ULT_FUNC_EXIT(hr);

}
HRESULT MPCHttpContext::GetRequestSize( /*[out]*/ DWORD& dwCount )
{
    __ULT_FUNC_ENTRY("MPCHttpContext::GetRequestSize");

    HRESULT hr;


    dwCount = m_hsInput.GetSize();
    hr      = S_OK;


    __ULT_FUNC_EXIT(hr);
}

HRESULT MPCHttpContext::CheckDataAvailable( /*[in] */ DWORD dwCount    ,
                                            /*[out]*/ bool& fAvailable )
{
    __ULT_FUNC_ENTRY("MPCHttpContext::CheckDataAvailable");

    HRESULT hr;


    fAvailable = (m_hsInput.GetAvailableForRead() >= dwCount);
    hr         = S_OK;


    __ULT_FUNC_EXIT(hr);
}

HRESULT MPCHttpContext::Read( /*[in]*/ void* pBuffer ,
                              /*[in]*/ DWORD dwCount )
{
    __ULT_FUNC_ENTRY("MPCHttpContext::Read");

    HRESULT hr = m_hsInput.read( pBuffer, dwCount );


    __ULT_FUNC_EXIT(hr);
}

HRESULT MPCHttpContext::Write( /*[in]*/ const void* pBuffer ,
                               /*[in]*/ DWORD       dwCount )
{
    __ULT_FUNC_ENTRY("MPCHttpContext::Write");

    HRESULT hr = m_hsOutput.write( pBuffer, dwCount );


    __ULT_FUNC_EXIT(hr);
}