#include "wininetp.h"

static const CHAR szRegPathConnections[] = REGSTR_PATH_INTERNET_SETTINGS "\\Connections";




// some winsock stacks fault if we do a gethostbyname(NULL).  If we come
// accross one of these, don't do any more autodetecting.
BOOL g_fGetHostByNameNULLFails = FALSE;

//
// IsConnectionMatch - a worker function to simply some logic elsewhere,
//  it just handles Connection Name Matching.
//

BOOL 
IsConnectionMatch(
    LPCSTR lpszConnection1,
    LPCSTR lpszConnection2)
{
    if ( lpszConnection1 == NULL && 
         lpszConnection2 == NULL) 
    {
        return TRUE;
    }

    if ( lpszConnection1 && lpszConnection2 &&
         stricmp(lpszConnection1, lpszConnection2) == 0 )
    {
        return TRUE;
    }

    return FALSE;
}

///////////////////////////////////////////////////////////////////////////
//
// CRegBlob implementation
//
///////////////////////////////////////////////////////////////////////////

CRegBlob::CRegBlob(
    BOOL fWrite
    )
{
    // initialize members
    _fWrite = fWrite;
    _fCommit = TRUE;
    _dwOffset = 0;
    _pBuffer = NULL;
    _dwBufferLimit = 0;
    _hkey = NULL;
}


CRegBlob::~CRegBlob(
    )
{
    Commit();

    if(_hkey)
        REGCLOSEKEY(_hkey);

    if(_pBuffer)
        FREE_FIXED_MEMORY(_pBuffer);

    // caller owns _pszValue pointer
}


DWORD
CRegBlob::Init(
    HKEY hBaseKey,
    LPCSTR pszSubKey,
    LPCSTR pszValue
    )
{
    long lRes;
    REGSAM  regsam = KEY_QUERY_VALUE;
    DWORD dwDisposition;

    // If we're writing, save reg value name and set access
    if(_fWrite)
    {
        _pszValue = pszValue;
        regsam = KEY_SET_VALUE;

        lRes = REGCREATEKEYEX(hBaseKey, pszSubKey, 0, "", 0,
                    regsam, NULL, &_hkey, &dwDisposition);
    }
    else
    {
        // If not writing, then use RegOpenKeyEx so we don't need
        // registry write permissions.
        lRes = REGOPENKEYEX(hBaseKey, pszSubKey, 0, regsam, &_hkey);
    }

    if(lRes != ERROR_SUCCESS)
    {
        return lRes;
    }

    // figure out buffer size
    _dwBufferLimit = BLOB_BUFF_GRANULARITY;
    if(FALSE == _fWrite)
    {
        // get size of registry blob
        lRes = RegQueryValueEx(_hkey, pszValue, NULL, NULL, NULL, &_dwBufferLimit);
        if(lRes != ERROR_SUCCESS)
        {
            // nothing there - make zero size buffer
            _dwBufferLimit = 0;
        }
    }

    // allocate buffer if necessary
    if(_dwBufferLimit)
    {
        _pBuffer = (BYTE *)ALLOCATE_FIXED_MEMORY(_dwBufferLimit);
        if(NULL == _pBuffer)
            return GetLastError();
    }

    // if we're reading, fill in buffer
    if(FALSE == _fWrite && _dwBufferLimit)
    {
        // read reg key
        DWORD dwSize = _dwBufferLimit;
        lRes = RegQueryValueEx(_hkey, pszValue, NULL, NULL, _pBuffer, &dwSize);
        if(lRes != ERROR_SUCCESS)
        {
            return lRes;
        }
    }

    // reset pointer to beginning of blob
    _dwOffset = 0;

    return 0;
}

DWORD
CRegBlob::Abandon(
    VOID
    )
{
    // don't commit changes when the time comes
    _fCommit = FALSE;

    return 0;
}

DWORD
CRegBlob::Commit(
    )
{
    long lres = 0;

    if(_fCommit && _fWrite && _pszValue && _pBuffer)
    {
        // save blob to reg key
        lres = RegSetValueEx(_hkey, _pszValue, 0, REG_BINARY, _pBuffer, _dwOffset);
    }

    return lres;
}


DWORD
CRegBlob::Encrpyt(
    )
{
    return 0;
}


DWORD
CRegBlob::Decrypt(
    )
{
    return 0;
}


DWORD
CRegBlob::WriteString(
    LPCSTR pszString
    )
{
    DWORD dwBytes, dwLen = 0;

    if(pszString)
    {
        dwLen = lstrlen(pszString);
    }

    dwBytes = WriteBytes(&dwLen, sizeof(DWORD));
    if(dwLen && dwBytes == sizeof(DWORD))
        dwBytes = WriteBytes(pszString, dwLen);

    return dwBytes;
}



DWORD
CRegBlob::ReadString(
    LPCSTR * ppszString
    )
{
    DWORD dwLen, dwBytes = 0;
    LPSTR lpszTemp = NULL;

    dwBytes = ReadBytes(&dwLen, sizeof(DWORD));
    if(dwBytes == sizeof(DWORD))
    {
        if(dwLen)
        {
            lpszTemp = (LPSTR)GlobalAlloc(GPTR, dwLen + 1);
            if(lpszTemp)
            {
                dwBytes = ReadBytes(lpszTemp, dwLen);
                lpszTemp[dwBytes] = 0;
            }
        }
    }

    *ppszString = lpszTemp;
    return dwBytes;
}


DWORD
CRegBlob::WriteBytes(
    LPCVOID pBytes,
    DWORD dwByteCount
    )
{
    BYTE * pNewBuffer;

    // can only do this on write blob
    if(FALSE == _fWrite)
        return 0;

    // grow buffer if necessary
    if(_dwBufferLimit - _dwOffset < dwByteCount)
    {
        DWORD dw = _dwBufferLimit + ((dwByteCount / BLOB_BUFF_GRANULARITY)+1)*BLOB_BUFF_GRANULARITY;
        pNewBuffer = (BYTE *)ALLOCATE_FIXED_MEMORY(dw);
        if(NULL == pNewBuffer)
        {
            // failed to get more memory
            return 0;
        }

        memset(pNewBuffer, 0, dw);
        memcpy(pNewBuffer, _pBuffer, _dwBufferLimit);
        FREE_FIXED_MEMORY(_pBuffer);
        _pBuffer = pNewBuffer;
        _dwBufferLimit = dw;
    }

    // copy callers data to buffer
    memcpy(_pBuffer + _dwOffset, pBytes, dwByteCount);
    _dwOffset += dwByteCount;

    // tell caller how much we wrote
    return dwByteCount;
}



DWORD
CRegBlob::ReadBytes(
    LPVOID pBytes,
    DWORD dwByteCount
    )
{
    DWORD   dwActual = _dwBufferLimit - _dwOffset;

    // can only do this on read blob
    if(_fWrite)
        return 0;

    // don't read past end of blob
    if(dwByteCount < dwActual)
        dwActual = dwByteCount;

    // copy bytes and increment offset
    if(dwActual > 0)
    {
        memcpy(pBytes, _pBuffer + _dwOffset, dwActual);
        _dwOffset += dwActual;
    }

    // tell caller how much we actually read
    return dwActual;
}


//
// Function Declarations
//


DWORD
LoadProxySettings()
/*
** Load global proxy info from registry.
** 
*/
{
    DWORD error;

    //
    // Get proxy struct for proxy object
    //
    INTERNET_PROXY_INFO_EX info;

    memset(&info, 0, sizeof(info));

    info.dwStructSize = sizeof(info);
    info.lpszConnectionName = NULL;

    //
    // Read LAN proxy settings and stuff them into the GlobalProxyInfo object.
    //
    error = ReadProxySettings(&info);

    if (error == ERROR_SUCCESS)
    {
        error = g_pGlobalProxyInfo->SetProxySettings(&info, FALSE);

        info.lpszConnectionName = NULL; // we don't allocate this field
        CleanProxyStruct(&info);
    }

    return error;
}



#define INTERNET_SETTINGS_KEY       "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"

HKEY
FindBaseProxyKey(
    VOID
    )
/*
** Determine whether proxy settings live in HKLM or HKCU
**
** WinHttpX is hard-coded to always use HKEY_LOCAL_MACHINE
** 
*/

{
    return HKEY_LOCAL_MACHINE;
}


DWORD
ReadProxySettings(
    LPINTERNET_PROXY_INFO_EX pInfo
    )

{
    CRegBlob r(FALSE);
    LPCSTR  pszConnectionName;
    LPCSTR  pszSavedConnectionName;
    DWORD   error = ERROR_SUCCESS;
    long    lRes;
    DWORD   i;
    HKEY    hBaseKey;

    DEBUG_ENTER((DBG_DIALUP,
                 Dword,
                 "ReadProxySettings",
                 "%#x",
                 pInfo
                 ));

    // verify pInfo
    if(NULL == pInfo || pInfo->dwStructSize != sizeof(INTERNET_PROXY_INFO_EX))
    {
        DEBUG_LEAVE(ERROR_INVALID_PARAMETER);
        return ERROR_INVALID_PARAMETER;
    }

    // figure out connection name  (NULL == 'network')
    pszConnectionName = pInfo->lpszConnectionName;
    pszSavedConnectionName = pInfo->lpszConnectionName;
    if(NULL == pszConnectionName || 0 == *pszConnectionName)
    {
        pszConnectionName = "WinHttpSettings";
    }

    // figure out base key
    hBaseKey = FindBaseProxyKey();

    // initialize structure
    memset(pInfo, 0, sizeof(*pInfo));
    pInfo->dwStructSize = sizeof(*pInfo);
    pInfo->lpszConnectionName = pszSavedConnectionName;
    pInfo->dwFlags = PROXY_TYPE_DIRECT;

    // init blob
    lRes = r.Init(hBaseKey, szRegPathConnections, pszConnectionName);
    if(lRes)
    {
        error = (lRes == ERROR_FILE_NOT_FOUND) ? ERROR_SUCCESS : lRes;
        goto quit;
    }

    // read fields from blob
    if(0 == r.ReadBytes(&pInfo->dwStructSize, sizeof(DWORD)) ||
         (pInfo->dwStructSize < sizeof(*pInfo)))
    {
        // blob didn't exist or in correct format - set default values
        pInfo->dwStructSize = sizeof(*pInfo);
    }
    else
    {
        // read the rest of the blob
        r.ReadBytes(&pInfo->dwCurrentSettingsVersion, sizeof(DWORD));
        r.ReadBytes(&pInfo->dwFlags, sizeof(DWORD));
        r.ReadString(&pInfo->lpszProxy);
        r.ReadString(&pInfo->lpszProxyBypass);
    }

    //
    // WinHttpX does not support proxy autodection or autoconfig URL's,
    // so make sure those PROXY_TYPE flags are turned off.
    //
    pInfo->dwFlags &= ~(PROXY_TYPE_AUTO_DETECT | PROXY_TYPE_AUTO_PROXY_URL);


    DEBUG_PRINT(DIALUP, INFO, ("conn=%s, vers=%u, flag=%X, prox=%s, by=%s\n", 
                    pszConnectionName,
                    pInfo->dwCurrentSettingsVersion,
                    pInfo->dwFlags,
                    (pInfo->lpszProxy ? pInfo->lpszProxy : "<none>"),
                    (pInfo->lpszProxyBypass ? pInfo->lpszProxyBypass : "<none>")
                    ));

quit:
    DEBUG_LEAVE(error);
    return error;
}


void
CleanProxyStruct(
    LPINTERNET_PROXY_INFO_EX pInfo
    )

{
    if(pInfo->lpszConnectionName)             GlobalFree((LPSTR) pInfo->lpszConnectionName);
    if(pInfo->lpszProxy)                      GlobalFree((LPSTR) pInfo->lpszProxy);
    if(pInfo->lpszProxyBypass)                GlobalFree((LPSTR) pInfo->lpszProxyBypass);
    memset(pInfo, 0, sizeof(INTERNET_PROXY_INFO_EX));
    pInfo->dwFlags = PROXY_TYPE_DIRECT;
}


DWORD
SetPerConnOptions(
    HINTERNET hInternet,
    BOOL fIsAutoProxyThread,
    LPINTERNET_PER_CONN_OPTION_LISTA pList
    )
{
    INTERNET_PROXY_INFO_EX  info;
    DWORD   i, dwError = ERROR_SUCCESS;
    BOOL fCommit = FALSE;
    LPSTR   pszCopy, pszNew;

    INET_ASSERT(fIsAutoProxyThread == FALSE);

    INET_ASSERT(hInternet != NULL);

    memset(&info, 0, sizeof(info));
    info.dwStructSize = sizeof(info);

    // loop through option list and set members
    for(i=0; i<pList->dwOptionCount; i++)
    {
        pszNew = NULL;

        switch(pList->pOptions[i].dwOption)
        {        
        case INTERNET_PER_CONN_PROXY_SERVER:
        case INTERNET_PER_CONN_PROXY_BYPASS:
            // make a copy of the string passed in for these guys
            pszCopy = pList->pOptions[i].Value.pszValue;
            if(pszCopy)
            {
                pszNew = (LPSTR)GlobalAlloc(GPTR, lstrlen(pszCopy) + 1);
                if(pszNew)
                {
                    lstrcpy(pszNew, pszCopy);
                }
                else
                {
                    dwError = ERROR_NOT_ENOUGH_MEMORY;
                    pList->dwOptionError = i;
                }
            }
            break;
        }

        if(dwError)
        {
            fCommit = FALSE;
            break;
        }

        switch(pList->pOptions[i].dwOption)
        {
        case INTERNET_PER_CONN_FLAGS:
            info.dwFlags = pList->pOptions[i].Value.dwValue;
            fCommit = TRUE;
            break;
        case INTERNET_PER_CONN_PROXY_SERVER:
            if(info.lpszProxy)
                GlobalFree((LPSTR)info.lpszProxy);
            info.lpszProxy = pszNew;
            fCommit = TRUE;
            break;
        case INTERNET_PER_CONN_PROXY_BYPASS:
            if(info.lpszProxyBypass)
                GlobalFree((LPSTR)info.lpszProxyBypass);
            info.lpszProxyBypass = pszNew;
            fCommit = TRUE;
            break;
        case INTERNET_PER_CONN_AUTODISCOVERY_FLAGS:
        case INTERNET_PER_CONN_AUTOCONFIG_RELOAD_DELAY_MINS:
        case INTERNET_PER_CONN_AUTOCONFIG_URL:
        case INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL:
        case INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_TIME:
        case INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL:
        default:
            dwError = ERROR_INVALID_PARAMETER;
            pList->dwOptionError = i;
            break;
        }

        if(dwError)
        {
            fCommit = FALSE;
            break;
        }
    }

    if (fCommit && hInternet)
    {
        g_pGlobalProxyInfo->SetProxySettings(&info, TRUE);
    }

    return dwError;
}



DWORD
QueryPerConnOptions(
    HINTERNET hInternet,
    BOOL fIsAutoProxyThread,
    LPINTERNET_PER_CONN_OPTION_LISTA pList
    )
{
    INTERNET_PROXY_INFO_EX  info;
    LPCSTR   pszCopy;
    LPSTR    pszNew;
    DWORD    i, dwError = ERROR_SUCCESS;
    BOOL     fFreeCopy = FALSE;

    pList->dwOptionError = 0;

    memset(&info, 0, sizeof(info));
    info.dwStructSize = sizeof(info);
    info.lpszConnectionName = pList->pszConnection;

    if ( hInternet == NULL ) 
    {
#ifndef WININET_SERVER_CORE
        if ( ! fIsAutoProxyThread ||                          
             ! g_pGlobalProxyInfo->GetAutoProxyThreadSettings(&info) ||
             ! IsConnectionMatch(info.lpszConnectionName, pList->pszConnection)) 
        { 
            CheckForUpgrade();
            ReadProxySettings(&info);
            fFreeCopy = TRUE;
        }
#endif //!WININET_SERVER_CORE
    }
    else 
    {
        g_pGlobalProxyInfo->GetProxySettings(&info, FALSE);
    }

    // loop through option list and fill in members
    for(i=0; i<pList->dwOptionCount; i++)
    {
        pList->pOptions[i].Value.pszValue = NULL;
        pszCopy = NULL;

        switch(pList->pOptions[i].dwOption)
        {
        case INTERNET_PER_CONN_FLAGS:
            pList->pOptions[i].Value.dwValue = info.dwFlags;
            break;
        case INTERNET_PER_CONN_PROXY_SERVER:
            pszCopy = info.lpszProxy;
            break;
        case INTERNET_PER_CONN_PROXY_BYPASS:
            pszCopy = info.lpszProxyBypass;
            break;
        default:
            dwError = ERROR_INVALID_PARAMETER;
            pList->dwOptionError = i;
            break;
        }

        // if this is a string value, make a copy of the string for the
        // caller
        if(pszCopy)
        {
            // make a copy of the string and stick it in the option
            pszNew = (LPSTR)GlobalAlloc(GPTR, lstrlen(pszCopy) + 1);
            if(pszNew)
            {
                lstrcpy(pszNew, pszCopy);
                pList->pOptions[i].Value.pszValue = pszNew;
            }
            else
            {
                dwError = ERROR_NOT_ENOUGH_MEMORY;
                pList->dwOptionError = i;
            }
        }

        if(dwError)
        {
            break;
        }
    }

    if (dwError)
    {
        // If an error has occurred, we should get rid of any strings that
        // we've allocated.
        for (i=0; i<pList->dwOptionError; i++)
        {
            switch(pList->pOptions[i].dwOption)
            {
            case INTERNET_PER_CONN_PROXY_SERVER:
            case INTERNET_PER_CONN_PROXY_BYPASS:
                if (pList->pOptions[i].Value.pszValue)
                {
                    GlobalFree(pList->pOptions[i].Value.pszValue);
                    pList->pOptions[i].Value.pszValue = NULL;
                }
                break;

            default:
                break;
            }
        }
    }

    if ( fFreeCopy ) {
        info.lpszConnectionName = NULL; // we don't allocate this field
        CleanProxyStruct(&info);
    }

    return dwError;
}