/*****************************************************************************\
* MODULE: anycon.cxx
*
* The module contains the base class for connections
*
* Copyright (C) 1997-1998 Microsoft Corporation
*
* History:
*   07/31/98    Weihaic     Created
*
\*****************************************************************************/


#include "precomp.h"
#include "priv.h"

/******************************************************************************
* Class Data Static Members
*****************************************************************************/
const DWORD CAnyConnection::gm_dwConnectTimeout = 30000;   // Thirty second timeout on connect
const DWORD CAnyConnection::gm_dwSendTimeout    = 30000;   // Thirty timeout on send timeout
const DWORD CAnyConnection::gm_dwReceiveTimeout = 60000;   // Thirty seconds on receive timeout
const DWORD CAnyConnection::gm_dwSendSize       = 0x10000; // We use a 16K sections when sending
                                                           // data through WININET
extern BOOL Ping (LPTSTR pszServerName);

CAnyConnection::CAnyConnection (
    BOOL    bSecure,
    INTERNET_PORT nServerPort,
    BOOL bIgnoreSecurityDlg,
    DWORD dwAuthMethod):

    m_lpszPassword (NULL),
    m_lpszUserName (NULL),
    m_hSession (NULL),
    m_hConnect (NULL),
    m_dwAccessFlag (INTERNET_OPEN_TYPE_PRECONFIG),
    m_bSecure (bSecure),
    m_bShowSecDlg (FALSE),
    m_dwAuthMethod (dwAuthMethod),
    m_bIgnoreSecurityDlg (bIgnoreSecurityDlg),
    m_bValid (FALSE)

{
    if (!nServerPort) {
        if (bSecure) {
            m_nServerPort = INTERNET_DEFAULT_HTTPS_PORT;
        }
        else {
            m_nServerPort = INTERNET_DEFAULT_HTTP_PORT;
        }
    }
    else
        m_nServerPort = nServerPort;


    m_bValid = TRUE;
}

CAnyConnection::~CAnyConnection ()
{
    if (m_hConnect) {
        (void) CAnyConnection::Disconnect ();
    }

    if (m_hSession) {
        (void) CAnyConnection::CloseSession ();
    }
    LocalFree (m_lpszPassword);
    m_lpszPassword = NULL;
    LocalFree (m_lpszUserName);
    m_lpszUserName = NULL;
}

HINTERNET
CAnyConnection::OpenSession ()
{

    m_hSession = InetInternetOpen(g_szUserAgent,
                                  m_dwAccessFlag,
                                  NULL,
                                  NULL,
#ifndef WINNT32
                                  INTERNET_FLAG_ASYNC);
#else
                                  0);
#endif



    if (m_hSession) {  // Set up the callback function if successful


#ifndef WINNT32
        INTERNET_STATUS_CALLBACK dwISC;

        dwISC = InternetSetStatusCallback( m_hSession, CAsyncContext::InternetCallback );

        if (dwISC != NULL) {
            // We do not support chaining down to a previous callback, there should not
            // be one and if it fails it will also be non NULL, Set last error to invalid handle and
            // Clean Up
            SetLastError (ERROR_INVALID_HANDLE);
            goto Cleanup;
        }
#endif

        // Also set an internet connection timeout for the session for when we try the
        // connection, should we do this instead of a ping?
        DWORD dwTimeout = gm_dwConnectTimeout;

        if (!InetInternetSetOption( m_hSession,
                                    INTERNET_OPTION_CONNECT_TIMEOUT,
                                    (LPVOID)&dwTimeout,
                                    sizeof(dwTimeout)
                                  ))
            goto Cleanup;

        // Now set the Send and Receive Timeout values

        dwTimeout = gm_dwSendTimeout;

        if (!InetInternetSetOption( m_hSession,
                                    INTERNET_OPTION_SEND_TIMEOUT,
                                    (LPVOID)&dwTimeout,
                                    sizeof(dwTimeout)
                                  ))
            goto Cleanup;

        dwTimeout = gm_dwReceiveTimeout;

        if (!InetInternetSetOption( m_hSession,
                                    INTERNET_OPTION_RECEIVE_TIMEOUT,
                                    (LPVOID)&dwTimeout,
                                    sizeof(dwTimeout)
                                  ))
            goto Cleanup;
    }

    return m_hSession;

Cleanup:

    if (m_hSession) {
        InetInternetCloseHandle (m_hSession);
        m_hSession = NULL;
    }

    return m_hSession;
}

BOOL CAnyConnection::CloseSession ()
{
    BOOL bRet =  InetInternetCloseHandle (m_hSession);

    m_hSession = NULL;

    return bRet;
}

HINTERNET
CAnyConnection::Connect(
    LPTSTR lpszServerName)
{

    if (m_hSession) {
        // Ping the server if it is in the intranet to make sure that the server is online

        if (lpszServerName &&

            (_tcschr ( lpszServerName, TEXT ('.')) || Ping (lpszServerName) )) {

            m_hConnect = InetInternetConnect(m_hSession,
                                             lpszServerName,
                                             m_nServerPort,
                                             NULL,//m_lpszUserName,
                                             NULL, //m_lpszPassword,
                                             INTERNET_SERVICE_HTTP,
                                             0,
                                             0);
        }
    }

    return m_hConnect;
}

BOOL
CAnyConnection::Disconnect ()
{
    BOOL bRet = InetInternetCloseHandle (m_hConnect);

    m_hConnect = NULL;

    return bRet;
}


HINTERNET
CAnyConnection::OpenRequest (
    LPTSTR      lpszUrl)
{
    HINTERNET hReq = NULL;
    DWORD dwFlags;

    if (m_hConnect) {
        // We need to create an Asynchronous Context for the Rest of the operations to use,
        // passing this in of course makes this request also asynchronous

        WIN9X_NEW_ASYNC( pacSync );

        WIN9X_IF_ASYNC( pacSync )
            WIN9X_IF_ASYNC( pacSync->bValid() ) {
                hReq = InetHttpOpenRequest(m_hConnect,
                                           g_szPOST,
                                           lpszUrl,
                                           g_szHttpVersion,
                                           NULL,
                                           NULL,
                                           INETPP_REQ_FLAGS | (m_bSecure? INTERNET_FLAG_SECURE:0),
                                           WIN9X_CONTEXT_ASYNC(pacSync));

            } WIN9X_ELSE_ASYNC( delete pacSync );

    }
    return hReq;
}

BOOL
CAnyConnection::CloseRequest (HINTERNET hReq)
{   // BUG: We have to close the handle manually, since WININET seems not to be giving us
    // an INTERNET_STATUS_HANDLE_CLOSING message
    BOOL bSuccess;

    WIN9X_GET_ASYNC( pacSync, hReq );

    bSuccess = InetInternetCloseHandle (hReq); // When this handle is closed, the context will be closed

    WIN9X_IF_ASYNC(pacSync) WIN9X_OP_ASYNC(delete pacSync;)

    return bSuccess;
}



BOOL
CAnyConnection::SendRequest(
    HINTERNET      hReq,
    LPCTSTR        lpszHdr,
    DWORD          cbData,
    LPBYTE         pidi)
{
    BOOL        bRet = FALSE;
    CMemStream  *pStream;

    pStream = new CMemStream (pidi, cbData);

    if (pStream && pStream->bValid ()){

        bRet = SendRequest (hReq, lpszHdr, pStream);
    }

    if (pStream) {
        delete pStream;
    }
    return bRet;
}

BOOL CAnyConnection::SendRequest(
    HINTERNET      hReq,
    LPCTSTR        lpszHdr,
    CStream        *pStream)
{
    BOOL  bRet = FALSE;
    DWORD dwStatus;
    DWORD cbStatus = sizeof(dwStatus);
    BOOL  bRetry = FALSE;
    DWORD dwRetryCount = 0;
    BOOL  bShowUI = FALSE;

    DWORD cbData;
    PBYTE pBuf = NULL;
    DWORD cbRead;


    if (!pStream->GetTotalSize (&cbData))
        return FALSE;

    pBuf = new BYTE[gm_dwSendSize];
    if (!pBuf)
        return FALSE;

#define MAX_RETRY 3
    do {
        BOOL bSuccess = FALSE;
        BOOL bLeave;

        WIN9X_GET_ASYNC( pacSync, hReq );

        WIN9X_IF_ASYNC (!pacSync) WIN9X_BREAK_ASYNC(FALSE);

        if (cbData < gm_dwSendSize) {

            if (pStream->Reset() &&
                pStream->Read (pBuf, cbData, &cbRead) && cbRead == cbData) {

                // If what we want to send is small, we send it with HttpSendRequest and not
                // HttpSendRequestEx, this is to wotk around a problem where we get timeouts on
                // receive on very small data transactions
                bSuccess = InetHttpSendRequest(hReq,
                                               lpszHdr,
                                               (lpszHdr ? (DWORD)-1 : 0),
                                               pBuf,
                                               cbData);

                (void) WIN9X_TIMEOUT_ASYNC(pacSync, bSuccess);
            }

        } else {
            do {
                BOOL bSuccessSend;
                // The timeout value for the packets applies for an entire session, so, instead of sending in
                // one chuck, we have to send in smaller chunks
                INTERNET_BUFFERS BufferIn;

                bLeave = TRUE;

                BufferIn.dwStructSize = sizeof( INTERNET_BUFFERS );
                BufferIn.Next = NULL;
                BufferIn.lpcszHeader = lpszHdr;
                if (lpszHdr)
                    BufferIn.dwHeadersLength = sizeof(TCHAR)*lstrlen(lpszHdr);
                else
                    BufferIn.dwHeadersLength = 0;
                BufferIn.dwHeadersTotal = 1;    // There is one header to send
                BufferIn.lpvBuffer = NULL;      // We defer this to the multiple write side
                BufferIn.dwBufferLength = 0;    // The total buffer length
                BufferIn.dwBufferTotal = cbData; // This is the size of the data we are about to send
                BufferIn.dwOffsetLow = 0;       // No offset into the buffers
                BufferIn.dwOffsetHigh = 0;

                // Since we will only ever be sending one request per hReq handle, we can associate
                // the context with all of these operations

                bSuccess = InetHttpSendRequestEx (hReq,
                                                  &BufferIn,
                                                  NULL,
                                                  0,
                                                  WIN9X_CONTEXT_ASYNC(pacSync));

                (void) WIN9X_TIMEOUT_ASYNC(pacSync, bSuccess );

                if (bSuccess) {
                    bSuccess = pStream->Reset();
                }

                DWORD   dwBufPos    = 0;      // This is our current point in the buffer
                DWORD   dwRemaining = cbData;  // These are the number of bytes left to send

                bSuccessSend = bSuccess;

                while (bSuccess && dwRemaining) {  // While we have data to send and the operations are
                                                   // successful
                    DWORD dwWrite = min( dwRemaining, gm_dwSendSize); // The amount to write
                    DWORD dwWritten;               // The amount actually written

                    if (pStream->Read (pBuf, dwWrite, &cbRead) && cbRead == dwWrite) {

                        bSuccess = InetInternetWriteFile (hReq, pBuf, dwWrite, &dwWritten);

                        (void) WIN9X_TIMEOUT_ASYNC(pacSync, bSuccess );  // Wait for the operation to actually happen

                        if (bSuccess) {
                            bSuccess = dwWritten ? TRUE : FALSE;

                            dwRemaining -= dwWritten;            // Remaining amount decreases by this
                            dwBufPos += dwWritten;                // Advance through the buffer

                            if (dwWritten != dwWrite) {
                                // We need to adjust the pointer, since not all the bytes are
                                // successfully sent to the server
                                //
                                bSuccess = pStream->SetPtr (dwBufPos);
                            }
                        }
                    }
                    else
                        bSuccess = FALSE;
                }

                BOOL bEndSuccess = FALSE;

                if (bSuccessSend) {  // We started the request successfully, so we can end it successfully
                   bEndSuccess = InetHttpEndRequest (hReq,
                                                     NULL,
                                                     0,
                                                     WIN9X_CONTEXT_ASYNC(pacSync));

                   (void) WIN9X_TIMEOUT_ASYNC(pacSync, bEndSuccess );
                 }

                if (!bEndSuccess && GetLastError() == ERROR_INTERNET_FORCE_RETRY)
                        bLeave = FALSE;


                bSuccess = bSuccess  && bEndSuccess && bSuccessSend;

            }  while (!bLeave);
        }

        if (bSuccess) {

            if ( InetHttpQueryInfo(hReq,
                                   HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE,
                                   &dwStatus,
                                   &cbStatus,
                                   NULL) ) {
                switch (dwStatus) {
                case HTTP_STATUS_DENIED:
                case HTTP_STATUS_PROXY_AUTH_REQ:
                    SetLastError (ERROR_ACCESS_DENIED);
                    break;
                case HTTP_STATUS_FORBIDDEN:
                    SetLastError (HTTP_STATUS_FORBIDDEN);
                    break;
                case HTTP_STATUS_OK:
                    bRet = TRUE;
                    break;
                case HTTP_STATUS_SERVER_ERROR:

                    DBG_MSG(DBG_LEV_ERROR, (TEXT("CAnyConnection::SendRequest : HTTP_STATUS_SERVER_ERROR")));

                    SetLastError (ERROR_INVALID_PRINTER_NAME);
                    break;
                default:

                    if ((dwStatus >= HTTP_STATUS_BAD_REQUEST) &&
                        (dwStatus < HTTP_STATUS_SERVER_ERROR)) {

                        SetLastError(ERROR_INVALID_PRINTER_NAME);

                    } else {

                        // We get some other errors, but don't know how to handle it
                        //
                        DBG_MSG(DBG_LEV_ERROR, (TEXT("CAnyConnection::SendRequest : Unknown Error (%d)"), dwStatus));

                        SetLastError (ERROR_INVALID_HANDLE);
                    }
                    break;
                }
            }
        }
        else {

            if (m_bSecure) {
#if NEVER
                //
                // In the future, we need to change this part to call InternetQueryOption on
                //  INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT
                // and pass it back to the client
                //
                LPTSTR szBuf[1024];
                DWORD dwSize = 1024;

                switch (GetLastError ()) {
                case ERROR_INTERNET_INVALID_CA:
                case ERROR_INTERNET_SEC_CERT_DATE_INVALID:
                case ERROR_INTERNET_SEC_CERT_CN_INVALID:


                    if (InternetQueryOption(hReq,
                                                INTERNET_OPTION_SECURITY_CERTIFICATE,
                                                szBuf, &dwSize)) {


                        DBG_MSG(DBG_LEV_WARN, (TEXT("Cert: %ws\n"), szBuf));


                        break;
                    }
                }
#endif
                DWORD dwFlags = 0;
                DWORD dwRet;

                if (m_bShowSecDlg) {
                    bShowUI = TRUE;
                    dwRet = InetInternetErrorDlg (GetTopWindow (NULL),
                                              hReq,
                                              GetLastError(),
                                              FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS, NULL);

                    if (dwRet == ERROR_SUCCESS || dwRet == ERROR_INTERNET_FORCE_RETRY) {
                        bRetry = TRUE;
                    }
                }
                else {
                    switch (GetLastError ()) {
                    case ERROR_INTERNET_INVALID_CA:
                        dwFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA;
                        break;
                    default:
                        // All other failure, try to ignore everything and retry
                        dwFlags = SECURITY_FLAG_IGNORE_REVOCATION |
                                  SECURITY_FLAG_IGNORE_UNKNOWN_CA |
                                  SECURITY_FLAG_IGNORE_WRONG_USAGE |
                                  SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
                                  SECURITY_FLAG_IGNORE_CERT_DATE_INVALID|
                                  SECURITY_FLAG_IGNORE_REDIRECT_TO_HTTPS |
                                  SECURITY_FLAG_IGNORE_REDIRECT_TO_HTTP ;
                        break;
                    }

                    if (InetInternetSetOption(hReq,
                                           INTERNET_OPTION_SECURITY_FLAGS,
                                           &dwFlags,
                                           sizeof (DWORD))) {
                        bRetry = TRUE;
                    }
                }
            }
        }
    }
    while (bRetry && ++dwRetryCount < MAX_RETRY);

    if (!bRet && GetLastError () ==  ERROR_INTERNET_LOGIN_FAILURE)
    {
        SetLastError (ERROR_ACCESS_DENIED);
    }

    if (bShowUI) {
        // We only show the dialog once.
        m_bShowSecDlg = FALSE;
    }

    if (pBuf) {
        delete [] pBuf;
    }

    return bRet;
}

BOOL CAnyConnection::ReadFile (
    HINTERNET hReq,
    LPVOID    lpvBuffer,
    DWORD     cbBuffer,
    LPDWORD   lpcbRd)
{
    BOOL bSuccess;

    bSuccess = InetInternetReadFile(hReq, lpvBuffer, cbBuffer, lpcbRd);

    return WIN9X_WAIT_ASYNC( hReq, bSuccess );
}


BOOL CAnyConnection::SetPassword (
    HINTERNET hReq,
    LPTSTR lpszUserName,
    LPTSTR lpszPassword)
{
    BOOL bRet = FALSE;
    TCHAR szNULL[] = TEXT ("");

    if (!lpszUserName) {
        lpszUserName = szNULL;
    }

    if (!lpszPassword) {
        lpszPassword = szNULL;
    }

    if ( InetInternetSetOption (hReq,
                                INTERNET_OPTION_USERNAME,
                                lpszUserName,
                                (DWORD) (lstrlen(lpszUserName) + 1)) &&
         InetInternetSetOption (hReq,
                                INTERNET_OPTION_PASSWORD,
                                lpszPassword,
                                (DWORD) (lstrlen(lpszPassword) + 1)) ) {
        bRet = TRUE;
    }

    return bRet;
}

BOOL CAnyConnection::GetAuthSchem (
    HINTERNET hReq,
    LPSTR lpszScheme,
    DWORD dwSize)
{
    DWORD dwIndex = 0;

    return InetHttpQueryInfo(hReq, HTTP_QUERY_WWW_AUTHENTICATE, (LPVOID)lpszScheme, &dwSize, &dwIndex);
}

void CAnyConnection::SetShowSecurityDlg (
    BOOL bShowSecDlg)
{
    m_bShowSecDlg = bShowSecDlg;
}


#ifndef WINNT32  // We use asynchronous code in this case
/**********************************************************************************************
** Method       - GetAsyncContext
** Description  - Get the async context from the handle
**********************************************************************************************/
CAnyConnection::CAsyncContext *CAnyConnection::GetAsyncContext( IN HINTERNET hInternet ) {
    CAsyncContext *pacContext;         // This is the context we wish to retrieve

    DWORD dwContextSize = sizeof(pacContext);

    if (InternetQueryOption( hInternet,
                             INTERNET_OPTION_CONTEXT_VALUE,
                             (LPBYTE)&pacContext,
                             &dwContextSize ))
        return pacContext;
    else
        return NULL;
}

/**********************************************************************************************
** Method       - AsynchronousWait
** Description  - This is really a wrapper for the object asynchronous wait, we simply first
**                get the object out of the context before we continue doing anything
**********************************************************************************************/
BOOL CAnyConnection::AsynchronousWait( IN HINTERNET hInternet, IN OUT BOOL &bSuccess) {
    // We get the context value from the internet handle
    CAsyncContext *pacContext;         // This is the context we wish to retrieve

    if (bSuccess) return TRUE;      // Saves having to get the context

    DWORD dwContextSize = sizeof(pacContext);

    if (InternetQueryOption( hInternet,
                             INTERNET_OPTION_CONTEXT_VALUE,
                             (LPBYTE)&pacContext,
                             &dwContextSize ))
        return pacContext->TimeoutWait (bSuccess);
    else
        return bSuccess = FALSE;
}

#endif // #ifndef WINNT32


#ifndef WINNT32
/**********************************************************************************************
** Class Implementation  - CAnyConnection::CAsyncContext
**********************************************************************************************/

/**********************************************************************************************
** Constructor   - CAsyncContext
** Description   - Create the Event Handle, set the point to the CAnyConnection and ensure
**                 that the two return values are correctly set
***********************************************************************************************/
CAnyConnection::CAsyncContext::CAsyncContext(void) :
    m_dwRet (0),
    m_dwError (ERROR_SUCCESS),
    m_hEvent (NULL) {

    // Create an event with no inheritable security, automatic reset semantics, a non-signalled
    // initial state and no name
    m_hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

}

/************************************************************************************************
** Destructor    - CAsyncContext
** Description   - Deallocate the event handle
************************************************************************************************/
CAnyConnection::CAsyncContext::~CAsyncContext() {
    if (m_hEvent) CloseHandle(m_hEvent);
}

/************************************************************************************************
** Method        - TimeoutWait
** Description   - Wait on an event callback if the call was asynchronous, clear the event, the
**                 callback routine does the hard work, this one is for a bool
************************************************************************************************/
BOOL CAnyConnection::CAsyncContext::TimeoutWait(IN OUT BOOL &bSuccess) {
    if (!bSuccess && GetLastError() == ERROR_IO_PENDING) {  // The call was asynchronously deferred
        DWORD dwRet;

        dwRet = WaitForSingleObject( m_hEvent , INFINITE );
            // This is not a real infinite wait since the timeout has been set in WININET
            // The callback will signal us back when it is all done

            // The object was signalled
            // The m_dwRet value will have been set to indicate success or failure
            // if the synchronisation was wrong it will be set to 0, so also a failure
        bSuccess = (BOOL)m_dwRet;

        if (!bSuccess) SetLastError (m_dwError);
                  else SetLastError(ERROR_SUCCESS);

    } else ResetEvent(m_hEvent);
       // Some events are synchronous, but still generate a callback, in this case the callback
       // will be in the same thread and set event, leaving the event open for next time,
       // In either case it is safe to do this, since if there is no callback, there will
       // be no data to pass back and no set event.

    return bSuccess;
}


/************************************************************************************************
** Callback    - InternetCallback
** Description - This handles the callback from the Wininet API, it is responsible for asyncronous
**               handle returns as well as other call returns, it also destructs the context
**               when it is eventually deleted
************************************************************************************************/
void CALLBACK CAnyConnection::CAsyncContext::InternetCallback(
    IN HINTERNET  hInternet,
    IN DWORD_PTR  dwContext,
    IN DWORD      dwInternetStatus,
    IN LPVOID     lpvStatusInformation,
    IN DWORD      dwStatusInformationLength) {

    CAsyncContext *pThis = (CAsyncContext *)dwContext;

    // Regardless of whether we are in a critical failure state or not, we want to delete the
    // context of this handle is closing
    switch(dwInternetStatus) {

#if 0
    // BUG: We do not get this notification from WININET, so we have to do this from outside
    // When this is resolved, this code is much neater

    case INTERNET_STATUS_HANDLE_CLOSING:
        delete pThis;
        break;
#endif // #if 0
    case INTERNET_STATUS_REQUEST_COMPLETE: // The request we sent was successful (or timed
                                           // out)
        pThis->m_dwRet = ((LPINTERNET_ASYNC_RESULT)lpvStatusInformation)->dwResult;
        pThis->m_dwError = ((LPINTERNET_ASYNC_RESULT)lpvStatusInformation)->dwError;
        SetEvent (pThis->m_hEvent);
        break;

    }
}

#endif // #ifndef WINNT32


/************************************************************************************************
** End of File (anycon.cxx)
************************************************************************************************/