/*=============================================================================
                           xmlrpc_wininet_transport
===============================================================================
   WinInet-based client transport for Xmlrpc-c.  Copyright information at
   the bottom of this file.
   
=============================================================================*/

#include "xmlrpc_config.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <stddef.h>
#include <windows.h>
#include <wininet.h>

#include "bool.h"
#include "mallocvar.h"
#include "linklist.h"
#include "casprintf.h"
#include "pthreadx.h"

#include "xmlrpc-c/base.h"
#include "xmlrpc-c/base_int.h"
#include "xmlrpc-c/client.h"
#include "xmlrpc-c/client_int.h"
#include "xmlrpc-c/transport.h"

#if defined(_DEBUG)
#   include <crtdbg.h>
#   define new DEBUG_NEW
#   define malloc(size) _malloc_dbg( size, _NORMAL_BLOCK, __FILE__, __LINE__)
#   undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
#endif


static HINTERNET hSyncInternetSession = NULL;

/* Declare WinInet status callback. */
void CALLBACK
statusCallback(HINTERNET     const hInternet,
               unsigned long const dwContext,
               unsigned long const dwInternetStatus,
               void *        const lpvStatusInformation,
               unsigned long const dwStatusInformationLength);


struct xmlrpc_client_transport {
    pthread_mutex_t listLock;
    struct list_head rpcList;
        /* List of all RPCs that exist for this transport.  An RPC exists
           from the time the user requests it until the time the user 
           acknowledges it is done.
        */
    int allowInvalidSSLCerts;
        /* Flag to specify if we ignore invalid SSL Certificates.  If this
           is set to zero, calling a XMLRPC server with an invalid SSL
           certificate will fail.  This is the default behavior of the other
           transports, but invalid certificates were allowed in pre 1.2 
           wininet xmlrpc-c transports.
        */
};

typedef struct {
    unsigned long http_status;
    HINTERNET hHttpRequest;
    HINTERNET hURL;
    INTERNET_PORT nPort;
    char szHostName[255];   
    char szUrlPath[255];
    BOOL bUseSSL;
    char * headerList;
    BYTE * pSendData;
    xmlrpc_mem_block * pResponseData;
} winInetTransaction;

typedef struct {
    struct list_head link;  /* link in transport's list of RPCs */
    winInetTransaction * winInetTransactionP;
        /* The object which does the HTTP transaction, with no knowledge
           of XML-RPC or Xmlrpc-c.
        */
    xmlrpc_mem_block * responseXmlP;
    xmlrpc_bool threadExists;
    pthread_t thread;
    xmlrpc_transport_asynch_complete complete;
        /* Routine to call to complete the RPC after it is complete HTTP-wise.
           NULL if none.
        */
    struct xmlrpc_call_info * callInfoP;
        /* User's identifier for this RPC */
    struct xmlrpc_client_transport * clientTransportP;
} rpc;



static void
createWinInetHeaderList(xmlrpc_env *               const envP,
                        const xmlrpc_server_info * const serverP,
                        char **                    const headerListP) {

    const char * const szContentType = "Content-Type: text/xml\r\n";

    char * szHeaderList;

    /* Send an authorization header if we need one. */
    if (serverP->allowedAuth.basic) {
        /* Make the header with content type and authorization   */
        /* NOTE: A newline is required between each added header */
        szHeaderList = malloc(strlen(szContentType) + 17 +
                              strlen(serverP->basicAuthHdrValue) + 1);
        
        if (szHeaderList == NULL)
            xmlrpc_faultf(envP,
                          "Couldn't allocate memory for authorization header");
        else {
            memcpy(szHeaderList, szContentType, strlen(szContentType));
            memcpy(szHeaderList + strlen(szContentType),"\r\nAuthorization: ",
                   17);
            memcpy(szHeaderList + strlen(szContentType) + 17,
                   serverP->basicAuthHdrValue,
                   strlen(serverP->basicAuthHdrValue) + 1);
        }
    } else {
        /* Just the content type header is needed */
        szHeaderList = malloc(strlen(szContentType) + 1);
        
        if (szHeaderList == NULL)
            xmlrpc_faultf(envP,
                          "Couldn't allocate memory for standard header");
        else 
            memcpy(szHeaderList, szContentType, strlen(szContentType) + 1);
    }
    *headerListP = szHeaderList;
}



static void
createWinInetTransaction(xmlrpc_env *               const envP,
                         const xmlrpc_server_info * const serverP,
                         xmlrpc_mem_block *         const callXmlP,
                         xmlrpc_mem_block *         const responseXmlP,
                         winInetTransaction **      const winInetTranPP) {

    winInetTransaction * winInetTransactionP;

    MALLOCVAR(winInetTransactionP);
    if (winInetTransactionP == NULL)
        xmlrpc_faultf(envP, "No memory to create WinInet transaction.");
    else {
        char szExtraInfo[255];
        char szScheme[100];
        URL_COMPONENTS uc;
        BOOL succeeded;

        /* Init to defaults */
        winInetTransactionP->http_status   = 0;
        winInetTransactionP->hHttpRequest  = NULL;
        winInetTransactionP->hURL          = NULL;
        winInetTransactionP->headerList    = NULL;
        winInetTransactionP->pSendData     = NULL;
        winInetTransactionP->pResponseData = responseXmlP;

        /* Parse the URL and store results into the winInetTransaction */

        memset(&uc, 0, sizeof(uc));
        uc.dwStructSize      = sizeof (uc);
        uc.lpszScheme        = szScheme;
        uc.dwSchemeLength    = 100;
        uc.lpszHostName      = winInetTransactionP->szHostName;
        uc.dwHostNameLength  = 255;
        uc.lpszUrlPath       = winInetTransactionP->szUrlPath;
        uc.dwUrlPathLength   = 255;
        uc.lpszExtraInfo     = szExtraInfo;
        uc.dwExtraInfoLength = 255;
        succeeded = InternetCrackUrl(serverP->serverUrl,
                                     strlen(serverP->serverUrl),
                                     ICU_ESCAPE, &uc);
        if (!succeeded)
            xmlrpc_faultf(envP, "Unable to parse the server URL.");
        else {
            winInetTransactionP->nPort =
                uc.nPort ? uc.nPort : INTERNET_DEFAULT_HTTP_PORT;
            if (_strnicmp(uc.lpszScheme, "https", 5) == 0)
                winInetTransactionP->bUseSSL=TRUE;
            else
                winInetTransactionP->bUseSSL=FALSE;
            createWinInetHeaderList(envP, serverP,
                                    &winInetTransactionP->headerList);

            XMLRPC_MEMBLOCK_APPEND(char, envP, callXmlP, "\0", 1);
            if (!envP->fault_occurred) {
                winInetTransactionP->pSendData =
                    XMLRPC_MEMBLOCK_CONTENTS(char, callXmlP);
            }
        }
            
        if (envP->fault_occurred)
            free(winInetTransactionP);
    }
    *winInetTranPP = winInetTransactionP;
}



static void
destroyWinInetTransaction(winInetTransaction * const winInetTransactionP) {

    XMLRPC_ASSERT_PTR_OK(winInetTransactionP);

    if (winInetTransactionP->hHttpRequest)
        InternetCloseHandle(winInetTransactionP->hHttpRequest);

    if (winInetTransactionP->hURL)
        InternetCloseHandle(winInetTransactionP->hURL);

    if (winInetTransactionP->headerList)
        free(winInetTransactionP->headerList);

    free(winInetTransactionP);
}



static void
get_wininet_response(xmlrpc_env *         const envP,
                     winInetTransaction * const winInetTransactionP) {

    unsigned long dwLen;
    INTERNET_BUFFERS inetBuffer;
    unsigned long dwFlags;
    unsigned long dwErr; 
    unsigned long nExpected;
    void * body;
    BOOL bOK;
    PVOID pMsgMem;

    pMsgMem = NULL; /* initial value */
    dwErr = 0; /* initial value */
    body = NULL;  /* initial value */
    dwLen = sizeof(unsigned long);  /* initial value */

    inetBuffer.dwStructSize    = sizeof (INTERNET_BUFFERS);
    inetBuffer.Next            = NULL;
    inetBuffer.lpcszHeader     = NULL;
    inetBuffer.dwHeadersTotal  = 0;
    inetBuffer.dwHeadersLength = 0;
    inetBuffer.dwOffsetHigh    = 0;
    inetBuffer.dwOffsetLow     = 0;
    inetBuffer.dwBufferLength  = 0;

    /* Note that while Content-Length is optional in HTTP 1.1, it is
       required by XML-RPC.  Following fails if server didn't send it.
    */

    bOK = HttpQueryInfo(winInetTransactionP->hHttpRequest, 
                        HTTP_QUERY_CONTENT_LENGTH|HTTP_QUERY_FLAG_NUMBER, 
                        &inetBuffer.dwBufferTotal, &dwLen, NULL);
    if (!bOK) {
        LPTSTR pMsg;
        dwErr = GetLastError ();
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                      FORMAT_MESSAGE_FROM_SYSTEM, 
                      NULL,
                      dwErr,
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      (LPTSTR) &pMsgMem,
                      1024,NULL);

        pMsg = pMsgMem ? (LPTSTR)pMsgMem : "Sync HttpQueryInfo failed.";
        XMLRPC_FAIL(envP, XMLRPC_NETWORK_ERROR, pMsg);
    }

    if (inetBuffer.dwBufferTotal == 0)
        XMLRPC_FAIL(envP, XMLRPC_NETWORK_ERROR, "WinInet returned no data");

    inetBuffer.lpvBuffer = calloc(inetBuffer.dwBufferTotal, sizeof(TCHAR));
    body = inetBuffer.lpvBuffer;
    dwFlags = IRF_SYNC;
    nExpected = inetBuffer.dwBufferTotal;
    inetBuffer.dwBufferLength = nExpected;
    InternetQueryDataAvailable(winInetTransactionP->hHttpRequest,
                               &inetBuffer.dwBufferLength, 0, 0);
    
    /* Read Response from InternetFile */
    do {
        if (inetBuffer.dwBufferLength != 0)
            bOK = InternetReadFileEx(winInetTransactionP->hHttpRequest,
                                     &inetBuffer, dwFlags, 1);

        if (!bOK)
            dwErr = GetLastError();

        if (dwErr) {
            if (dwErr == WSAEWOULDBLOCK || dwErr == ERROR_IO_PENDING) {
                /* Non-block socket operation wait 10 msecs */
                SleepEx(10, TRUE);
                /* Reset dwErr to zero for next pass */
                dwErr = 0;
            } else {
                LPTSTR pMsg;
                FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                               FORMAT_MESSAGE_FROM_SYSTEM, 
                               NULL,
                               dwErr,
                               MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                               (LPTSTR) &pMsgMem,
                               1024,NULL);
                pMsg = pMsgMem ?
                    (LPTSTR)pMsgMem : "ASync InternetReadFileEx failed.";
                XMLRPC_FAIL(envP, XMLRPC_NETWORK_ERROR, pMsg);
            }
        }
        
        if (inetBuffer.dwBufferLength) {
            TCHAR * const oldBufptr = inetBuffer.lpvBuffer;

            inetBuffer.lpvBuffer = oldBufptr + inetBuffer.dwBufferLength;
            nExpected -= inetBuffer.dwBufferLength;
            /* Adjust inetBuffer.dwBufferLength when it is greater than the */
            /* expected end of file */
            if (inetBuffer.dwBufferLength > nExpected)
                inetBuffer.dwBufferLength = nExpected; 

        } else
            inetBuffer.dwBufferLength = nExpected;
        dwErr = 0;
    } while (nExpected != 0);

    /* Add to the response buffer. */ 
    xmlrpc_mem_block_append(envP, winInetTransactionP->pResponseData, body,
                            inetBuffer.dwBufferTotal);
    XMLRPC_FAIL_IF_FAULT(envP);

 cleanup:
    /* Since the XMLRPC_FAIL calls goto cleanup, we must handle */
    /* the free'ing of the memory here. */
    if (pMsgMem != NULL)
        LocalFree(pMsgMem);

    if (body)
        free(body);
}



static void
performWinInetTransaction(
    xmlrpc_env *                     const envP,
    winInetTransaction *             const winInetTransactionP,
    struct xmlrpc_client_transport * const clientTransportP) {

    const char * const acceptTypes[] = {"text/xml", NULL};

    unsigned long queryLen;
    LPVOID pMsgMem;
    BOOL succeeded;

    unsigned long lastErr;
    unsigned long reqFlags;

    pMsgMem = NULL;  /* initial value */

    reqFlags = INTERNET_FLAG_NO_UI;  /* initial value */
    
    winInetTransactionP->hURL =
        InternetConnect(hSyncInternetSession, 
                        winInetTransactionP->szHostName,
                        winInetTransactionP->nPort, 
                        NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);

    /* Start our request running. */
    if (winInetTransactionP->bUseSSL == TRUE)
        reqFlags |=
            INTERNET_FLAG_SECURE | INTERNET_FLAG_IGNORE_CERT_CN_INVALID;

    winInetTransactionP->hHttpRequest =
        HttpOpenRequest(winInetTransactionP->hURL, "POST",
                        winInetTransactionP->szUrlPath, "HTTP/1.1", NULL,
                        (const char **)&acceptTypes,
                        reqFlags, 1);
    
    XMLRPC_FAIL_IF_NULL(winInetTransactionP->hHttpRequest, envP,
                        XMLRPC_INTERNAL_ERROR,
                        "Unable to open the requested URL.");

    succeeded =
        HttpAddRequestHeaders(winInetTransactionP->hHttpRequest,
                              winInetTransactionP->headerList, 
                              strlen (winInetTransactionP->headerList),
                              HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);

    if (!succeeded)
        XMLRPC_FAIL(envP, XMLRPC_INTERNAL_ERROR,
                    "Could not set Content-Type.");

Again:
    /* Send the requested XML remote procedure command */ 
    succeeded = HttpSendRequest(winInetTransactionP->hHttpRequest, NULL, 0, 
                                winInetTransactionP->pSendData, 
                                strlen(winInetTransactionP->pSendData));
    if (!succeeded) {
        LPTSTR pMsg;

        lastErr = GetLastError();

        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_ALLOCATE_BUFFER |
                      FORMAT_MESSAGE_IGNORE_INSERTS, 
                      NULL,
                      lastErr,
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      (LPTSTR) &pMsgMem,
                      0, NULL);

        if (pMsgMem == NULL) {
            switch (lastErr) {
            case ERROR_INTERNET_CANNOT_CONNECT:
                pMsg = "Sync HttpSendRequest failed: Connection refused.";
                break;
            case ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED:
                pMsg = "Sync HttpSendRequest failed: "
                    "Client authorization certificate needed.";
                break;

            /* The following conditions are recommendations that microsoft */
            /* provides in their knowledge base. */

            /* HOWTO: Handle Invalid Certificate Authority Error with
               WinInet (Q182888)
            */
            case ERROR_INTERNET_INVALID_CA:
                if (clientTransportP->allowInvalidSSLCerts){
                    OutputDebugString(
                        "Sync HttpSendRequest failed: "
                        "The function is unfamiliar with the certificate "
                        "authority that generated the server's certificate. ");
                    reqFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA;

                    InternetSetOption(winInetTransactionP->hHttpRequest,
                                      INTERNET_OPTION_SECURITY_FLAGS,
                                      &reqFlags, sizeof(reqFlags));

                    goto Again;
                } else
                    pMsg = "Invalid or unknown/untrusted "
                        "SSL Certificate Authority.";
                break;

            /* HOWTO: Make SSL Requests Using WinInet (Q168151) */
            case ERROR_INTERNET_SEC_CERT_CN_INVALID:
                if (clientTransportP->allowInvalidSSLCerts) {
                    OutputDebugString(
                        "Sync HttpSendRequest failed: "
                        "The SSL certificate common name (host name field) "
                        "is incorrect\r\n "
                        "for example, if you entered www.server.com "
                        "and the common name "
                        "on the certificate says www.different.com. ");
                    
                    reqFlags = INTERNET_FLAG_IGNORE_CERT_CN_INVALID;

                    InternetSetOption(winInetTransactionP->hHttpRequest,
                                      INTERNET_OPTION_SECURITY_FLAGS,
                                      &reqFlags, sizeof(reqFlags));
                    
                    goto Again;
                } else
                    pMsg = "The SSL certificate common name "
                        "(host name field) is incorrect.";
                break;

            case ERROR_INTERNET_SEC_CERT_DATE_INVALID:
                if (clientTransportP->allowInvalidSSLCerts) {
                    OutputDebugString(
                        "Sync HttpSendRequest failed: "
                        "The SSL certificate date that was received "
                        "from the server is "
                        "bad. The certificate is expired. ");

                    reqFlags = INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;

                    InternetSetOption(winInetTransactionP->hHttpRequest,
                                      INTERNET_OPTION_SECURITY_FLAGS,
                                      &reqFlags, sizeof(reqFlags));

                    goto Again;
                } else
                    pMsg = "The SSL certificate date that was received "
                        "from the server is invalid.";
                break;

            default:
                pMsg = (LPTSTR)pMsgMem = LocalAlloc(LPTR, MAX_PATH);
                sprintf(pMsg, "Sync HttpSendRequest failed: "
                        "GetLastError (%d)", lastErr);
                break;

            }
        } else
            pMsg = (LPTSTR)pMsgMem; 

        XMLRPC_FAIL(envP, XMLRPC_NETWORK_ERROR, pMsg);
    }

    queryLen = sizeof(unsigned long);  /* initial value */

    succeeded = HttpQueryInfo(winInetTransactionP->hHttpRequest, 
                              HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE,
                              &winInetTransactionP->http_status,
                              &queryLen, NULL);
    if (!succeeded) {
        LPTSTR pMsg;

        lastErr = GetLastError();
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                      FORMAT_MESSAGE_FROM_SYSTEM, 
                      NULL,
                      lastErr,
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      (LPTSTR) &pMsgMem,
                      1024, NULL);

        pMsg = pMsgMem ? (LPTSTR)pMsgMem : "Sync HttpQueryInfo failed.";
        XMLRPC_FAIL(envP, XMLRPC_NETWORK_ERROR, pMsg);
    }

    /* Make sure we got a "200 OK" message from the remote server. */
    if (winInetTransactionP->http_status != 200) {
        unsigned long msgLen;
        char errMsg[1024];
        errMsg[0] = '\0';
        msgLen = 1024;  /* initial value */

        HttpQueryInfo(winInetTransactionP->hHttpRequest,
                      HTTP_QUERY_STATUS_TEXT, errMsg, &msgLen, NULL);

        xmlrpc_env_set_fault_formatted(
            envP, XMLRPC_NETWORK_ERROR,
            "HTTP error #%d occurred\n %s",
            winInetTransactionP->http_status, errMsg);
        goto cleanup;
    }
    /* Read the response. */    
    get_wininet_response(envP, winInetTransactionP);
    XMLRPC_FAIL_IF_FAULT(envP);

 cleanup:
    /* Since the XMLRPC_FAIL calls goto cleanup, we must handle */
    /* the free'ing of the memory here. */
    if (pMsgMem)
        LocalFree(pMsgMem);
}



static unsigned __stdcall 
doAsyncRpc(void * const arg) {
    rpc * const rpcP = arg;
    xmlrpc_env env;
    xmlrpc_env_init(&env);
    performWinInetTransaction(&env, rpcP->winInetTransactionP,
                              rpcP->clientTransportP );
    rpcP->complete(rpcP->callInfoP, rpcP->responseXmlP, env);
    xmlrpc_env_clean(&env);

    return 0;
}



static void
createRpcThread(xmlrpc_env * const envP,
                rpc *        const rpcP,
                pthread_t *  const threadP) {

    int rc;

    rc = pthread_create(threadP, NULL, doAsyncRpc, rpcP);
    switch (rc) {
    case 0: 
        break;
    case EAGAIN:
        xmlrpc_faultf(envP, "pthread_create() failed:  "
                      "System Resources exceeded.");
        break;
    case EINVAL:
        xmlrpc_faultf(envP, "pthread_create() failed:  "
                      "Param Error for attr.");
        break;
    case ENOMEM:
        xmlrpc_faultf(envP, "pthread_create() failed:  "
                      "No memory for new thread.");
        break;
    default:
        xmlrpc_faultf(envP, "pthread_create() failed: "
                      "Unrecognized error code %d.", rc);
        break;
    }
}



static void
rpcCreate(xmlrpc_env *                     const envP,
          struct xmlrpc_client_transport * const clientTransportP,
          const xmlrpc_server_info *       const serverP,
          xmlrpc_mem_block *               const callXmlP,
          xmlrpc_mem_block *               const responseXmlP,
          xmlrpc_transport_asynch_complete       complete, 
          struct xmlrpc_call_info *        const callInfoP,
          rpc **                           const rpcPP) {

    rpc * rpcP;

    MALLOCVAR(rpcP);
    if (rpcP == NULL)
        xmlrpc_faultf(envP, "Couldn't allocate memory for rpc object");
    else {
        rpcP->callInfoP    = callInfoP;
        rpcP->complete     = complete;
        rpcP->responseXmlP = responseXmlP;
        rpcP->threadExists = FALSE;

        createWinInetTransaction(envP, serverP, callXmlP, responseXmlP, 
                                 &rpcP->winInetTransactionP);
        if (!envP->fault_occurred) {
            if (complete) {
                createRpcThread(envP, rpcP, &rpcP->thread);
                if (!envP->fault_occurred)
                    rpcP->threadExists = TRUE;
            }
            if (!envP->fault_occurred) {
                list_init_header(&rpcP->link, rpcP);
                pthread_mutex_lock(&clientTransportP->listLock);
                list_add_head(&clientTransportP->rpcList, &rpcP->link);
                pthread_mutex_unlock(&clientTransportP->listLock);
            }
            if (envP->fault_occurred)
                    destroyWinInetTransaction(rpcP->winInetTransactionP);
        }
        if (envP->fault_occurred)
            free(rpcP);
    }
    *rpcPP = rpcP;
}



static void 
rpcDestroy(rpc * const rpcP) {

    XMLRPC_ASSERT_PTR_OK(rpcP);
    XMLRPC_ASSERT(!rpcP->threadExists);

    destroyWinInetTransaction(rpcP->winInetTransactionP);

    list_remove(&rpcP->link);

    free(rpcP);
}



static void * 
finishRpc(struct list_head * const headerP, 
          void *             const context ATTR_UNUSED) {
    
    rpc * const rpcP = headerP->itemP;

    if (rpcP->threadExists) {
        void * status;
        int result;

        result = pthread_join(rpcP->thread, &status);
        
        rpcP->threadExists = FALSE;
    }

    XMLRPC_MEMBLOCK_FREE(char, rpcP->responseXmlP);

    rpcDestroy(rpcP);

    return NULL;
}


/* Used for debugging purposes to track the status of
   your request.
*/
void CALLBACK
statusCallback (HINTERNET     const hInternet,
                unsigned long const dwContext,
                unsigned long const dwInternetStatus,
                void *        const lpvStatusInformation,
                unsigned long const dwStatusInformationLength) {

    switch (dwInternetStatus) {
    case INTERNET_STATUS_RESOLVING_NAME:
        OutputDebugString("INTERNET_STATUS_RESOLVING_NAME\r\n");
        break;

    case INTERNET_STATUS_NAME_RESOLVED:
        OutputDebugString("INTERNET_STATUS_NAME_RESOLVED\r\n");
        break;

    case INTERNET_STATUS_HANDLE_CREATED:
        OutputDebugString("INTERNET_STATUS_HANDLE_CREATED\r\n");
        break;

    case INTERNET_STATUS_CONNECTING_TO_SERVER:
        OutputDebugString("INTERNET_STATUS_CONNECTING_TO_SERVER\r\n");
        break;

    case INTERNET_STATUS_REQUEST_SENT:
        OutputDebugString("INTERNET_STATUS_REQUEST_SENT\r\n");
        break;

    case INTERNET_STATUS_SENDING_REQUEST:
        OutputDebugString("INTERNET_STATUS_SENDING_REQUEST\r\n");
        break;

    case INTERNET_STATUS_CONNECTED_TO_SERVER:
        OutputDebugString("INTERNET_STATUS_CONNECTED_TO_SERVER\r\n");
        break;

    case INTERNET_STATUS_RECEIVING_RESPONSE:
        OutputDebugString("INTERNET_STATUS_RECEIVING_RESPONSE\r\n");
        break;

    case INTERNET_STATUS_RESPONSE_RECEIVED:
        OutputDebugString("INTERNET_STATUS_RESPONSE_RECEIVED\r\n");
        break;

    case INTERNET_STATUS_CLOSING_CONNECTION:
        OutputDebugString("INTERNET_STATUS_CLOSING_CONNECTION\r\n");
        break;

    case INTERNET_STATUS_CONNECTION_CLOSED:
        OutputDebugString("INTERNET_STATUS_CONNECTION_CLOSED\r\n");
        break;

    case INTERNET_STATUS_HANDLE_CLOSING:
        OutputDebugString("INTERNET_STATUS_HANDLE_CLOSING\r\n");
        break;

    case INTERNET_STATUS_CTL_RESPONSE_RECEIVED:
        OutputDebugString("INTERNET_STATUS_CTL_RESPONSE_RECEIVED\r\n");
        break;

    case INTERNET_STATUS_REDIRECT:
        OutputDebugString("INTERNET_STATUS_REDIRECT\r\n");
        break;

    case INTERNET_STATUS_REQUEST_COMPLETE:
        /* This indicates the data is ready. */
        OutputDebugString("INTERNET_STATUS_REQUEST_COMPLETE\r\n");
        break;

    default:
        OutputDebugString("statusCallback, default case!\r\n");
        break;
     }
}



static void 
create(xmlrpc_env *                      const envP,
       int                               const flags ATTR_UNUSED,
       const char *                      const appname ATTR_UNUSED,
       const char *                      const appversion ATTR_UNUSED,
       const void *                      const transportparmsP,
       size_t                            const parm_size,
       struct xmlrpc_client_transport ** const handlePP) {
/*----------------------------------------------------------------------------
   This does the 'create' operation for a WinInet client transport.
-----------------------------------------------------------------------------*/
    const struct xmlrpc_wininet_xportparms * const wininetXportParmsP = 
        transportparmsP;

    struct xmlrpc_client_transport * transportP;

    MALLOCVAR(transportP);
    if (transportP == NULL)
        xmlrpc_faultf(envP, "Unable to allocate transport descriptor.");
    else {
        pthread_mutex_init(&transportP->listLock, NULL);
        
        list_make_empty(&transportP->rpcList);

        if (hSyncInternetSession == NULL)
            hSyncInternetSession =
                InternetOpen("xmlrpc-c wininet transport",
                             INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);

        if (!wininetXportParmsP ||
            parm_size < XMLRPC_WXPSIZE(allowInvalidSSLCerts))
            transportP->allowInvalidSSLCerts = 0;
        else
            transportP->allowInvalidSSLCerts =
                wininetXportParmsP->allowInvalidSSLCerts;

        *handlePP = transportP;
    }
}



static void 
destroy(struct xmlrpc_client_transport * const clientTransportP) {
/*----------------------------------------------------------------------------
   This does the 'destroy' operation for a WinInet client transport.
-----------------------------------------------------------------------------*/
    XMLRPC_ASSERT(clientTransportP != NULL);

    XMLRPC_ASSERT(list_is_empty(&clientTransportP->rpcList));

    if (hSyncInternetSession)
        InternetCloseHandle(hSyncInternetSession);
    hSyncInternetSession = NULL;

    pthread_mutex_destroy(&clientTransportP->listLock);

    free(clientTransportP);
}



static void 
sendRequest(xmlrpc_env *                     const envP, 
            struct xmlrpc_client_transport * const clientTransportP,
            const xmlrpc_server_info *       const serverP,
            xmlrpc_mem_block *               const callXmlP,
            xmlrpc_transport_asynch_complete       complete,
            struct xmlrpc_call_info *        const callInfoP) {
/*----------------------------------------------------------------------------
   Initiate an XML-RPC rpc asynchronously.  Don't wait for it to go to
   the server.

   Unless we return failure, we arrange to have complete() called when
   the rpc completes.

   This does the 'send_request' operation for a WinInet client transport.
-----------------------------------------------------------------------------*/
    rpc * rpcP;
    xmlrpc_mem_block * responseXmlP;

    responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
    if (!envP->fault_occurred) {
        rpcCreate(envP, clientTransportP, serverP, callXmlP, responseXmlP,
                  complete, callInfoP,
                  &rpcP);

        if (envP->fault_occurred)
            XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
    }
    /* The user's eventual finish_asynch call will destroy this RPC
       and response buffer
    */
}



static void 
finishAsynch(struct xmlrpc_client_transport * const clientTransportP,
             xmlrpc_timeoutType               const timeoutType ATTR_UNUSED,
             xmlrpc_timeout                   const timeout ATTR_UNUSED) {
/*----------------------------------------------------------------------------
   Wait for the threads of all outstanding RPCs to exit and destroy those
   RPCs.

   This does the 'finish_asynch' operation for a WinInet client transport.
-----------------------------------------------------------------------------*/
    /* We ignore any timeout request.  Some day, we should figure out how
       to set an alarm and interrupt running threads.
    */

    pthread_mutex_lock(&clientTransportP->listLock);

    list_foreach(&clientTransportP->rpcList, finishRpc, NULL);

    pthread_mutex_unlock(&clientTransportP->listLock);
}



static void
call(xmlrpc_env *                     const envP,
     struct xmlrpc_client_transport * const clientTransportP,
     const xmlrpc_server_info *       const serverP,
     xmlrpc_mem_block *               const callXmlP,
     xmlrpc_mem_block **              const responsePP) {

    xmlrpc_mem_block * responseXmlP;
    rpc * rpcP;

    XMLRPC_ASSERT_ENV_OK(envP);
    XMLRPC_ASSERT_PTR_OK(serverP);
    XMLRPC_ASSERT_PTR_OK(callXmlP);
    XMLRPC_ASSERT_PTR_OK(responsePP);

    responseXmlP = XMLRPC_MEMBLOCK_NEW(char, envP, 0);
    if (!envP->fault_occurred) {
        rpcCreate(envP, clientTransportP, serverP, callXmlP, responseXmlP,
                  NULL, NULL, &rpcP);
        if (!envP->fault_occurred) {
            performWinInetTransaction(envP, rpcP->winInetTransactionP,
                                      clientTransportP);
            
            *responsePP = responseXmlP;
            
            rpcDestroy(rpcP);
        }
        if (envP->fault_occurred)
            XMLRPC_MEMBLOCK_FREE(char, responseXmlP);
    }
}



struct xmlrpc_client_transport_ops xmlrpc_wininet_transport_ops = {
    NULL,
    NULL,
    &create,
    &destroy,
    &sendRequest,
    &call,
    &finishAsynch,
    NULL,
};



/* Copyright (C) 2001 by First Peer, Inc. All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission. 
**  
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE. */
