//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
#include <windows.h>

#include "HttpRequest.h"

// static members
ce::string* HttpRequest::m_pstrAgentName = NULL;

// Initialize
bool HttpRequest::Initialize(LPCSTR pszAgent, ...)
{
    assert(pszAgent != NULL);
    assert(m_pstrAgentName == NULL);
    
    if(m_pstrAgentName = new ce::string)
    {
        va_list arguments;
        va_start(arguments, pszAgent);
        
#ifdef DEBUG
        m_pstrAgentName->reserve(1);
#else        
        m_pstrAgentName->reserve(strlen(pszAgent));
#endif        
        
        while(-1 == _vsnprintf(m_pstrAgentName->get_buffer(), m_pstrAgentName->capacity(), pszAgent, arguments))
        {
            ce::string::size_type n = m_pstrAgentName->capacity();
            
            if(n > INTERNET_MAX_URL_LENGTH || !m_pstrAgentName->reserve(2 * n))
                break;
        }
        
        va_end(arguments);
        
        return true;
    }
    else
        return false;
}


// Uninitialize
void HttpRequest::Uninitialize()
{
    delete m_pstrAgentName;
    m_pstrAgentName = NULL;
}


// HttpRequest
HttpRequest::HttpRequest(DWORD dwTimeout/* = 30 * 1000*/)
    : m_dwTimeout(dwTimeout),
      m_dwError(ERROR_SUCCESS)
{
    assert(m_pstrAgentName != NULL);

    m_hInternet = InternetOpenA(*m_pstrAgentName, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, INTERNET_FLAG_ASYNC);
    
    InternetSetOption(m_hInternet, INTERNET_OPTION_RECEIVE_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
    InternetSetOption(m_hInternet, INTERNET_OPTION_SEND_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
    InternetSetOption(m_hInternet, INTERNET_OPTION_DATA_RECEIVE_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
    InternetSetOption(m_hInternet, INTERNET_OPTION_DATA_SEND_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
    InternetSetOption(m_hInternet, INTERNET_OPTION_CONNECT_TIMEOUT , &dwTimeout, sizeof(dwTimeout));
}


// Open
bool HttpRequest::Open(LPCSTR pszVerb, LPCSTR pszUrl, LPCSTR pszVersion/* = NULL*/)
{
    char pszServer[INTERNET_MAX_HOST_NAME_LENGTH];
    char pszUrlPath[INTERNET_MAX_URL_LENGTH];
    char pszUserName[INTERNET_MAX_USER_NAME_LENGTH];
    char pszPassword[INTERNET_MAX_PASSWORD_LENGTH];
    
    URL_COMPONENTSA urlComp = {0};
    
    urlComp.dwStructSize = sizeof(URL_COMPONENTS);
    
    // server
    urlComp.lpszHostName = pszServer;
    urlComp.dwHostNameLength = INTERNET_MAX_HOST_NAME_LENGTH;
    
    // URL Path
    urlComp.lpszUrlPath = pszUrlPath;
    urlComp.dwUrlPathLength = INTERNET_MAX_URL_LENGTH;

    // user
    urlComp.lpszUserName = pszUserName;
    urlComp.dwUserNameLength = INTERNET_MAX_USER_NAME_LENGTH;

    // password
    urlComp.lpszPassword = pszPassword;
    urlComp.dwPasswordLength = INTERNET_MAX_PASSWORD_LENGTH;
    
    if(!InternetCrackUrlA(pszUrl, 0, ICU_DECODE, &urlComp))
        return false;
    
    if(urlComp.nScheme != INTERNET_SCHEME_HTTP)
    {
        m_dwError = ERROR_INTERNET_UNRECOGNIZED_SCHEME;
        return false;
    }
    
	m_hConnect = InternetConnectA(m_hInternet, urlComp.lpszHostName, urlComp.nPort, 
                                 urlComp.lpszUserName, urlComp.lpszPassword, 
                                 INTERNET_SERVICE_HTTP, 0, reinterpret_cast<DWORD>(&m_hConnect));

    if(m_hConnect == NULL)
        return false;

#ifdef UTIL_HTTPLITE
    m_hRequest = HttpOpenRequestA(m_hConnect, pszVerb, urlComp.lpszUrlPath, pszVersion, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, reinterpret_cast<DWORD>(&m_hRequest));
#else
    m_hRequest = HttpOpenRequestA(m_hConnect, pszVerb, urlComp.lpszUrlPath, pszVersion, NULL, NULL, INTERNET_FLAG_RESYNCHRONIZE | INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, reinterpret_cast<DWORD>(&m_hRequest));
#endif
    
    return m_hRequest != NULL;
}


// AddHeader
bool HttpRequest::AddHeader(LPCSTR pszHeaderName, int nHeaderValue, DWORD dwModifiers/* = HTTP_ADDREQ_FLAG_REPLACE | HTTP_ADDREQ_FLAG_ADD*/)
{
    char pszHeaderValue[11];

    _itoa(nHeaderValue, pszHeaderValue, 10);

    return AddHeader(pszHeaderName, pszHeaderValue, dwModifiers);
}


// AddHeader
bool HttpRequest::AddHeader(LPCSTR pszHeaderName, LPCSTR pszHeaderValue, DWORD dwModifiers/* = HTTP_ADDREQ_FLAG_REPLACE | HTTP_ADDREQ_FLAG_ADD*/)
{
    ce::string strHeader;

    strHeader.reserve(strlen(pszHeaderName) + strlen(pszHeaderValue) + 3);
    
    strHeader = pszHeaderName;
    strHeader += ": ";
    strHeader += pszHeaderValue;

    return TRUE == HttpAddRequestHeadersA(m_hRequest, strHeader, -1, dwModifiers);
}


// Write
void HttpRequest::Write(LPCSTR pszData)
{
    m_strMessage += pszData;
}


// Write
void HttpRequest::Write(LPCWSTR pwszData, UINT CodePage/* = CP_UTF8*/)
{
    int nSizeCurrent = m_strMessage.size();
    int nSizeAdd = WideCharToMultiByte(CodePage, 0, pwszData, -1, NULL, 0, NULL, NULL);

    if(!nSizeAdd)
        if(nSizeAdd = WideCharToMultiByte(CP_ACP, 0, pwszData, -1, NULL, 0, NULL, NULL))
            CodePage = CP_ACP;

    m_strMessage.reserve(nSizeCurrent + nSizeAdd);
    
    // regardless of allocation failure or integer overflow in reserve call above,
    // WideCharToMultiByte will only write up to actual capacity of the buffer
    PREFAST_SUPPRESS(12009, "");
    WideCharToMultiByte(CodePage, 0, pwszData, -1, m_strMessage.get_buffer() + nSizeCurrent, m_strMessage.capacity() - nSizeCurrent, NULL, NULL);
}


// Send
bool HttpRequest::Send()
{
    LPCSTR  pszMessage = NULL;
    DWORD   dwLength = 0;

    if(m_strMessage.size())
    {
        pszMessage = m_strMessage;
        dwLength = m_strMessage.size();
    }

    if(!HttpSendRequestA(m_hRequest, NULL, 0, const_cast<LPSTR>(pszMessage), dwLength))
        m_dwError = m_hRequest.wait(m_dwTimeout);
    else
        m_dwError = ERROR_SUCCESS;

    m_strMessage.resize(0);
    
    return m_dwError == ERROR_SUCCESS;
}


// Read
bool HttpRequest::Read(LPVOID pBuffer, DWORD cbBytesToRead, DWORD* pcbBytesRead)
{
    if(!InternetReadFile(m_hRequest, pBuffer, cbBytesToRead, pcbBytesRead))
        m_dwError = m_hRequest.wait(m_dwTimeout);
    else
        m_dwError = ERROR_SUCCESS;

    return m_dwError == ERROR_SUCCESS;
}


// GetStatus
DWORD HttpRequest::GetStatus()
{
    DWORD dwStatus, cb = sizeof(dwStatus);
    
    if(HttpQueryInfo(m_hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwStatus, &cb, NULL))
        return dwStatus;
    else
        return 0;
}


// GetHeader
bool HttpRequest::GetHeader(LPCSTR pszHeaderName, LPSTR pszBuffer, LPDWORD lpdwBufferLength)
{
    DWORD nLen = strlen(pszHeaderName) + 1;

    if(nLen > *lpdwBufferLength)
        *lpdwBufferLength = nLen;
    
    strcpy(pszBuffer, pszHeaderName);
    
    return TRUE == HttpQueryInfoA(m_hRequest, HTTP_QUERY_CUSTOM, pszBuffer, lpdwBufferLength, NULL);
}


// wait
DWORD async_internet_handle::wait(int nTimeout)
{
    DWORD dwError = GetLastError();
        
    if(dwError != ERROR_IO_PENDING)
    {
        ResetEvent(m_hEventComplete);
        
        return m_dwError = dwError;
    }

    if(WAIT_TIMEOUT == WaitForSingleObject(m_hEventComplete, nTimeout))
    {
        close();

        ResetEvent(m_hEventComplete);

        return m_dwError = ERROR_INTERNET_TIMEOUT;
    }

    return m_dwError;
}



// Callback
// Note, this ANSI callback function (Unicode callbacks don't work).
// Status callbacks that contain strings will have ANSI strings

void CALLBACK async_internet_handle::Callback(HINTERNET hInternet,
                                              DWORD_PTR dwContext,
                                              DWORD dwInternetStatus,
                                              LPVOID lpvStatusInformation,
                                              DWORD dwStatusInformationLength)
{
    async_internet_handle* pThis = reinterpret_cast<async_internet_handle*>(dwContext);

    switch(dwInternetStatus)
    {
        case INTERNET_STATUS_REQUEST_COMPLETE:
                {
                    INTERNET_ASYNC_RESULT* pAsyncResult = reinterpret_cast<INTERNET_ASYNC_RESULT*>(lpvStatusInformation);

                    pThis->m_dwError = pAsyncResult->dwError;
                    SetEvent(pThis->m_hEventComplete);
                }
                break;

        case INTERNET_STATUS_HANDLE_CLOSING:
                
                SetEvent(pThis->m_hEventClosed);
                pThis->m_handle = NULL;
                break;
    }
}
