//*********************************************************************
//*                  Microsoft Windows                               **
//*            Copyright(c) Microsoft Corp., 1996                    **
//*********************************************************************

/*Included Files------------------------------------------------------------*/
#include "msrating.h"
#pragma hdrstop

#include <npassert.h>
#include <buffer.h>
#include "ratings.h"
#include "mslubase.h"
#include "parselbl.h"

#include "rors.h"
#include "wininet.h"


typedef HINTERNET (WINAPI *PFNInternetOpen)(
    IN LPCTSTR lpszCallerName,
    IN DWORD dwAccessType,
    IN LPCTSTR lpszServerName OPTIONAL,
    IN INTERNET_PORT nServerPort,
    IN DWORD dwFlags
    );
typedef BOOL (WINAPI *PFNInternetCloseHandle)(
    IN HINTERNET hInternet
    );
typedef HINTERNET (WINAPI *PFNInternetConnect)(
    IN HINTERNET hInternet,
    IN LPCTSTR lpszServerName,
    IN INTERNET_PORT nServerPort,
    IN LPCTSTR lpszUsername OPTIONAL,
    IN LPCTSTR lpszPassword OPTIONAL,
    IN DWORD dwService,
    IN DWORD dwFlags,
    IN DWORD_PTR dwContext
    );
typedef BOOL (WINAPI *PFNInternetReadFile)(
    IN HINTERNET hFile,
    IN LPVOID lpBuffer,
    IN DWORD dwNumberOfBytesToRead,
    OUT LPDWORD lpdwNumberOfBytesRead
    );
typedef INTERNET_STATUS_CALLBACK (WINAPI *PFNInternetSetStatusCallback)(
    IN HINTERNET hInternet,
    IN INTERNET_STATUS_CALLBACK lpfnInternetCallback
    );
typedef HINTERNET (WINAPI *PFNHttpOpenRequest)(
    IN HINTERNET hHttpSession,
    IN LPCTSTR lpszVerb,
    IN LPCTSTR lpszObjectName,
    IN LPCTSTR lpszVersion,
    IN LPCTSTR lpszReferrer OPTIONAL,
    IN LPCTSTR FAR * lplpszAcceptTypes OPTIONAL,
    IN DWORD dwFlags,
    IN DWORD_PTR dwContext
    );
typedef BOOL (WINAPI *PFNHttpSendRequest)(
    IN HINTERNET hHttpRequest,
    IN LPCTSTR lpszHeaders OPTIONAL,
    IN DWORD dwHeadersLength,
    IN LPVOID lpOptional OPTIONAL,
    IN DWORD dwOptionalLength
    );
typedef BOOL (WINAPI *PFNInternetCrackUrl)(
    IN LPCTSTR lpszUrl,
    IN DWORD dwUrlLength,
    IN DWORD dwFlags,
    IN OUT LPURL_COMPONENTS lpUrlComponents
    );
typedef BOOL (WINAPI *PFNInternetCanonicalizeUrl)(
    IN LPCSTR lpszUrl,
    OUT LPSTR lpszBuffer,
    IN OUT LPDWORD lpdwBufferLength,
    IN DWORD dwFlags
    );


PFNInternetReadFile pfnInternetReadFile = NULL;
PFNHttpSendRequest pfnHttpSendRequest = NULL;
PFNInternetOpen pfnInternetOpen = NULL;
PFNInternetSetStatusCallback pfnInternetSetStatusCallback = NULL;
PFNInternetConnect pfnInternetConnect = NULL;
PFNHttpOpenRequest pfnHttpOpenRequest = NULL;
PFNInternetCloseHandle pfnInternetCloseHandle = NULL;
PFNInternetCrackUrl pfnInternetCrackUrl = NULL;
PFNInternetCanonicalizeUrl pfnInternetCanonicalizeUrl = NULL;

#undef InternetReadFile
#undef HttpSendRequest
#undef InternetOpen
#undef InternetSetStatusCallback
#undef InternetConnect
#undef HttpOpenRequest
#undef InternetCloseHandle
#undef InternetCrackUrl
#undef InternetCanonicalizeUrl

#define InternetReadFile pfnInternetReadFile
#define HttpSendRequest pfnHttpSendRequest
#define InternetOpen pfnInternetOpen
#define InternetSetStatusCallback pfnInternetSetStatusCallback
#define InternetConnect pfnInternetConnect
#define HttpOpenRequest pfnHttpOpenRequest
#define InternetCloseHandle pfnInternetCloseHandle
#define InternetCrackUrl pfnInternetCrackUrl
#define InternetCanonicalizeUrl pfnInternetCanonicalizeUrl

struct {
    FARPROC *ppfn;
    LPCSTR pszName;
} aImports[] = {
#ifndef UNICODE
    { (FARPROC *)&pfnInternetReadFile, "InternetReadFile" },
    { (FARPROC *)&pfnHttpSendRequest, "HttpSendRequestA" },
    { (FARPROC *)&pfnInternetOpen, "InternetOpenA" },
    { (FARPROC *)&pfnInternetSetStatusCallback, "InternetSetStatusCallback" },
    { (FARPROC *)&pfnInternetConnect, "InternetConnectA" },
    { (FARPROC *)&pfnHttpOpenRequest, "HttpOpenRequestA" },
    { (FARPROC *)&pfnInternetCloseHandle, "InternetCloseHandle" },
    { (FARPROC *)&pfnInternetCrackUrl, "InternetCrackUrlA" },
    { (FARPROC *)&pfnInternetCanonicalizeUrl, "InternetCanonicalizeUrlA" },
#else
    { (FARPROC *)&pfnInternetReadFile, "InternetReadFile" },
    { (FARPROC *)&pfnHttpSendRequest, "HttpSendRequestW" },
    { (FARPROC *)&pfnInternetOpen, "InternetOpenW" },
    { (FARPROC *)&pfnInternetSetStatusCallback, "InternetSetStatusCallback" },
    { (FARPROC *)&pfnInternetConnect, "InternetConnectW" },
    { (FARPROC *)&pfnHttpOpenRequest, "HttpOpenRequestW" },
    { (FARPROC *)&pfnInternetCloseHandle, "InternetCloseHandle" },
    { (FARPROC *)&pfnInternetCrackUrl, "InternetCrackUrlW" },
    { (FARPROC *)&pfnInternetCanonicalizeUrl, "InternetCanonicalizeUrlW" },
#endif
};

const UINT cImports = sizeof(aImports) / sizeof(aImports[0]);

HINSTANCE hWinINet = NULL;
BOOL fTriedLoad = FALSE;
HINTERNET hI = NULL;

void _stdcall WinInetCallbackProc(HINTERNET hInternet, DWORD_PTR Context, DWORD Status, LPVOID Info, DWORD Length);
#define USER_AGENT_STRING "Batcave(bcrs)"


BOOL LoadWinINet(void)
{
    if (fTriedLoad)
    {
        return (hWinINet != NULL);
    }

    fTriedLoad = TRUE;

    hWinINet = ::LoadLibrary("WININET.DLL");
    if (hWinINet == NULL)
    {
        return FALSE;
    }

    for (UINT i=0; i<cImports; i++)
    {
        *(aImports[i].ppfn) = ::GetProcAddress(hWinINet, aImports[i].pszName);
        if (*(aImports[i].ppfn) == NULL)
        {
            CleanupWinINet();
            return FALSE;
        }
    }

    hI = InternetOpen(USER_AGENT_STRING, PRE_CONFIG_INTERNET_ACCESS, NULL, 0, INTERNET_FLAG_ASYNC);
    if (hI == NULL)
    {
        CleanupWinINet();
        return FALSE;
    }

    InternetSetStatusCallback(hI, WinInetCallbackProc);

    return TRUE;
}


void CleanupWinINet(void)
{
    if (hI != NULL)
    {
        InternetCloseHandle(hI);
        hI = NULL;
    }

    if (hWinINet != NULL)
    {
        for (UINT i=0; i<cImports; i++)
        {
            *(aImports[i].ppfn) = NULL;
        }

        ::FreeLibrary(hWinINet);
        hWinINet = NULL;
    }
}


void _stdcall WinInetCallbackProc(HINTERNET hInternet, DWORD_PTR Context, DWORD Status, LPVOID Info, DWORD Length)
{
    BOOL unknown = FALSE;
    HANDLE  hAsyncEvent = (HANDLE) Context;

    char *type$;
    switch (Status)
    {
        case INTERNET_STATUS_RESOLVING_NAME:
        type$ = "RESOLVING NAME";
        break;

        case INTERNET_STATUS_NAME_RESOLVED:
        type$ = "NAME RESOLVED";
        break;

        case INTERNET_STATUS_CONNECTING_TO_SERVER:
        type$ = "CONNECTING TO SERVER";
        break;

        case INTERNET_STATUS_CONNECTED_TO_SERVER:
        type$ = "CONNECTED TO SERVER";
        break;

        case INTERNET_STATUS_SENDING_REQUEST:
        type$ = "SENDING REQUEST";
        break;

        case INTERNET_STATUS_REQUEST_SENT:
        type$ = "REQUEST SENT";
        break;

        case INTERNET_STATUS_RECEIVING_RESPONSE:
        type$ = "RECEIVING RESPONSE";
        break;

        case INTERNET_STATUS_RESPONSE_RECEIVED:
        type$ = "RESPONSE RECEIVED";
        break;

        case INTERNET_STATUS_CLOSING_CONNECTION:
        type$ = "CLOSING CONNECTION";
        break;

        case INTERNET_STATUS_CONNECTION_CLOSED:
        type$ = "CONNECTION CLOSED";
        break;

        case INTERNET_STATUS_REQUEST_COMPLETE:
        type$ = "REQUEST COMPLETE";
        SetEvent(hAsyncEvent);
        break;

        default:
        type$ = "???";
        unknown = TRUE;
        break;
    }

/*
    printf("callback: handle %x [context %x ] %s \n",
        hInternet,
        Context,
        type$
        );
*/
}

#define ABORT_EVENT 0
#define ASYNC_EVENT 1


BOOL ShouldAbort(HANDLE hAbort)
{
    return (WAIT_OBJECT_0 == WaitForSingleObject(hAbort, 0));
}

BOOL WaitForAsync(HANDLE rgEvents[])
{
    BOOL fAbort;

//  if (ERROR_IO_PENDING != GetLastError()) return FALSE;       

    fAbort = (WAIT_OBJECT_0 == WaitForMultipleObjects(2, rgEvents, FALSE, INFINITE));
//  fAbort = (WAIT_OBJECT_0 == WaitForSingleObject(rgEvents[ABORT_EVENT], 0));

    return !fAbort;
}


void EncodeUrl(LPCTSTR pszTargetUrl, char *pBuf)
{
    while (*pszTargetUrl)
    {
        switch (*pszTargetUrl)
        {
        case ':':
            *pBuf++ = '%';
            *pBuf++ = '3';
            *pBuf++ = 'A';
            break;
        case '/':
            *pBuf++ = '%';
            *pBuf++ = '2';
            *pBuf++ = 'F';
            break;      
        default:
            *pBuf++ = *pszTargetUrl;
            break;
        }

        ++pszTargetUrl; 
    }

    *pBuf = 0;
}


STDMETHODIMP CRORemoteSite::QueryInterface(
    /* [in] */ REFIID riid,
    /* [out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
{
    *ppvObject = NULL;

    if (IsEqualIID(riid, IID_IUnknown) ||
        IsEqualIID(riid, IID_IObtainRating))
    {
        *ppvObject = (LPVOID)this;
        AddRef();
        return NOERROR;
    }

    return ResultFromScode(E_NOINTERFACE);
}


STDMETHODIMP_(ULONG) CRORemoteSite::AddRef(void)
{
    RefThisDLL(TRUE);

    return ++m_cRef;
}


STDMETHODIMP_(ULONG) CRORemoteSite::Release(void)
{
    RefThisDLL(FALSE);

    if (!--m_cRef)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_cRef;
    }
}


LPSTR FindRatingLabel(LPSTR pszResponse)
{
    /* pszResponse is the complete response message from the HTTP server.
     * It could be a simple response (just the PICS label we want) or it
     * could be a full response including headers.  In the former case we
     * just return the label, in the latter we have to skip the headers
     * to the message body.
     *
     * To be extra tolerant of poorly written label bureaus, we start by
     * looking at the start of the data to see if it's a left paren.  If
     * it isn't, we assume we've got some headers, so we skip to the
     * double CRLF which HTTP requires to terminate headers.  We don't
     * require a Status-Line (such as "HTTP/1.1 200 OK") even though
     * technically HTTP does.  If we don't find the double CRLF, then
     * we look for the string "(PICS-" which is usually what begins a
     * PICS label list.  If they've done everything else wrong and they're
     * also perverse enough to insert whitespace there (such as "( PICS-"),
     * tough.
     */

    SkipWhitespace(&pszResponse);       /* skip leading whitespace just in case */
    if (*pszResponse != '(')
    {          /* doesn't seem to start with a label */
        LPSTR pszBody = ::strstrf(pszResponse, ::szDoubleCRLF);
        if (pszBody != NULL)
        {          /* found double CRLF, end of HTTP headers */
            pszResponse = pszBody + 4;  /* length of CRLFCRLF */
        }
        else
        {                          /* no double CRLF, hunt for PICS label */
            pszBody = ::strstrf(pszResponse, ::szPicsOpening);
            if (pszBody != NULL)
            {
                pszResponse = pszBody;  /* beginning of PICS label */
            }
        }
    }

    return pszResponse;
}


const char szRequestTemplate[] = "?opt=normal&u=\"";
const UINT cchRequestTemplate = sizeof(szRequestTemplate) + 1;

STDMETHODIMP CRORemoteSite::ObtainRating(THIS_ LPCTSTR pszTargetUrl, HANDLE hAbortEvent,
                             IMalloc *pAllocator, LPSTR *ppRatingOut)
{
    HINTERNET hIC, hH;
    HANDLE  rgEvents[2];
    BOOL fRet;
    HRESULT hrRet = E_RATING_NOT_FOUND;
    char rgBuf[10000], *pBuf;   // PERF - way too much stack!
    DWORD  nRead, nBuf = sizeof(rgBuf) - 1;
    LPSTR pszRatingServer;

    if (!gPRSI->etstrRatingBureau.fIsInit())
    {
        return hrRet;
    }

    if (!LoadWinINet())
    {
        return hrRet;
    }

    pszRatingServer = gPRSI->etstrRatingBureau.Get();

    BUFFER bufBureauHostName(INTERNET_MAX_HOST_NAME_LENGTH);
    BUFFER bufBureauPath(INTERNET_MAX_PATH_LENGTH);

    if (!bufBureauHostName.QueryPtr() || !bufBureauPath.QueryPtr())
    {
        return E_OUTOFMEMORY;
    }

    URL_COMPONENTS uc;

    uc.dwStructSize = sizeof(uc);
    uc.lpszScheme = NULL;
    uc.dwSchemeLength = 0;
    uc.lpszHostName = (LPSTR)bufBureauHostName.QueryPtr();
    uc.dwHostNameLength = bufBureauHostName.QuerySize();
    uc.lpszUserName = NULL;
    uc.dwUserNameLength = 0;
    uc.lpszPassword = NULL;
    uc.dwPasswordLength = 0;
    uc.lpszUrlPath = (LPSTR)bufBureauPath.QueryPtr();
    uc.dwUrlPathLength = bufBureauPath.QuerySize();
    uc.lpszExtraInfo = NULL;
    uc.dwExtraInfoLength = 0;

    if (!InternetCrackUrl(pszRatingServer, 0, 0, &uc))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    BUFFER bufRequest(INTERNET_MAX_URL_LENGTH + uc.dwUrlPathLength + cchRequestTemplate);

    LPSTR pszRequest = (LPSTR)bufRequest.QueryPtr();
    if (pszRequest == NULL)
    {
        return E_OUTOFMEMORY;
    }

    LPSTR pszCurrent = pszRequest;
    ::strcpyf(pszCurrent, uc.lpszUrlPath);
    pszCurrent += uc.dwUrlPathLength;

    ::strcpyf(pszCurrent, szRequestTemplate);
    pszCurrent += ::strlenf(pszCurrent);

    /* Encode the target URL. */
    EncodeUrl(pszTargetUrl, pszCurrent);

    ::strcatf(pszCurrent, "\"");

    hIC = hH = NULL;
    
    rgEvents[ABORT_EVENT] = hAbortEvent;
    rgEvents[ASYNC_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (!rgEvents[ASYNC_EVENT])
    {
        goto STATE_CLEANUP;
    }

    hIC = InternetConnect(hI, uc.lpszHostName, uc.nPort, NULL, NULL,
                          INTERNET_SERVICE_HTTP, 0, (DWORD_PTR) rgEvents[ASYNC_EVENT]);
    if (hIC == NULL || ShouldAbort(hAbortEvent))
    {
        goto STATE_CLEANUP;
    }

    hH = HttpOpenRequest(hIC, "GET", pszRequest, NULL, NULL, NULL,
                         INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD,
                         (DWORD_PTR) rgEvents[ASYNC_EVENT]);
    if (hH == NULL || ShouldAbort(hAbortEvent))
    {
        goto STATE_CLEANUP;
    }

    fRet = HttpSendRequest(hH, NULL, (DWORD) 0, NULL, 0);
    if (!fRet && !WaitForAsync(rgEvents))
    {
        goto STATE_CLEANUP;
    }

    pBuf  = rgBuf;
    nRead = 0;
    do
    {
        fRet = InternetReadFile(hH, pBuf, nBuf-nRead, &nRead);
        if (!fRet && !WaitForAsync(rgEvents))
        {
            goto STATE_CLEANUP;
        }

        if (nRead)
        {
            pBuf += nRead;
            hrRet = NOERROR;
        }

    } while (nRead);
        

STATE_CLEANUP:
    if (hH)  InternetCloseHandle(hH);
    if (hIC) InternetCloseHandle(hIC);
    if (rgEvents[ASYNC_EVENT])
    {
        CloseHandle(rgEvents[ASYNC_EVENT]);
    }

    if (hrRet == NOERROR)
    {
        (*ppRatingOut) = (char*) pAllocator->Alloc((int)(pBuf - rgBuf + 1));
        if (*ppRatingOut != NULL)
        {
            *pBuf = '\0';
            LPSTR pszLabel = FindRatingLabel(rgBuf);
            strcpyf(*ppRatingOut, pszLabel);
        }
        else
        {
            hrRet = ResultFromScode(E_OUTOFMEMORY);
        }
    }

    if (hrRet == NOERROR)
    {
        hrRet = S_RATING_FOUND;
    }

    return hrRet;
}



STDMETHODIMP_(ULONG) CRORemoteSite::GetSortOrder(THIS)
{
    return RATING_ORDER_REMOTESITE;
}
