/*
 * Wininet
 *
 * Copyright 1999 Corel Corporation
 * Copyright 2002 CodeWeavers Inc.
 * Copyright 2002 Jaco Greeff
 * Copyright 2002 TransGaming Technologies Inc.
 * Copyright 2004 Mike McCormack for CodeWeavers
 *
 * Ulrich Czekalla
 * Aric Stewart
 * David Hammerton
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "winsock2.h"
#include "ws2ipdef.h"

#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include <wchar.h>

#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "winuser.h"
#include "wininet.h"
#include "winnls.h"
#include "wine/debug.h"
#include "winerror.h"
#define NO_SHLWAPI_STREAM
#include "shlwapi.h"
#include "ws2tcpip.h"
#include "winternl.h"
#include "iphlpapi.h"
#include "dhcpcsdk.h"

#include "wine/exception.h"

#include "internet.h"
#include "resource.h"

WINE_DEFAULT_DEBUG_CHANNEL(wininet);

typedef struct
{
    DWORD  dwError;
    CHAR   response[MAX_REPLY_LEN];
} WITHREADERROR, *LPWITHREADERROR;

static DWORD g_dwTlsErrIndex = TLS_OUT_OF_INDEXES;
HMODULE WININET_hModule;

static CRITICAL_SECTION WININET_cs;
static CRITICAL_SECTION_DEBUG WININET_cs_debug = 
{
    0, 0, &WININET_cs,
    { &WININET_cs_debug.ProcessLocksList, &WININET_cs_debug.ProcessLocksList },
      0, 0, { (DWORD_PTR)(__FILE__ ": WININET_cs") }
};
static CRITICAL_SECTION WININET_cs = { &WININET_cs_debug, -1, 0, 0, 0, 0 };

static object_header_t **handle_table;
static UINT_PTR next_handle;
static UINT_PTR handle_table_size;

typedef struct
{
    DWORD  proxyEnabled;
    LPWSTR proxy;
    LPWSTR proxyBypass;
    LPWSTR proxyUsername;
    LPWSTR proxyPassword;
} proxyinfo_t;

static ULONG max_conns = 2, max_1_0_conns = 4;
static ULONG connect_timeout = 60000;

static const WCHAR szInternetSettings[] =
    L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";

void *alloc_object(object_header_t *parent, const object_vtbl_t *vtbl, size_t size)
{
    UINT_PTR handle = 0, num;
    object_header_t *ret;
    object_header_t **p;
    BOOL res = TRUE;

    ret = heap_alloc_zero(size);
    if(!ret)
        return NULL;

    list_init(&ret->children);

    EnterCriticalSection( &WININET_cs );

    if(!handle_table_size) {
        num = 16;
        p = heap_alloc_zero(sizeof(handle_table[0]) * num);
        if(p) {
            handle_table = p;
            handle_table_size = num;
            next_handle = 1;
        }else {
            res = FALSE;
        }
    }else if(next_handle == handle_table_size) {
        num = handle_table_size * 2;
        p = heap_realloc_zero(handle_table, sizeof(handle_table[0]) * num);
        if(p) {
            handle_table = p;
            handle_table_size = num;
        }else {
            res = FALSE;
        }
    }

    if(res) {
        handle = next_handle;
        if(handle_table[handle])
            ERR("handle isn't free but should be\n");
        handle_table[handle] = ret;
        ret->valid_handle = TRUE;

        while(next_handle < handle_table_size && handle_table[next_handle])
            next_handle++;
    }

    LeaveCriticalSection( &WININET_cs );

    if(!res) {
        heap_free(ret);
        return NULL;
    }

    ret->vtbl = vtbl;
    ret->refs = 1;
    ret->hInternet = (HINTERNET)handle;

    if(parent) {
        ret->lpfnStatusCB = parent->lpfnStatusCB;
        ret->dwInternalFlags = parent->dwInternalFlags & INET_CALLBACKW;
    }

    return ret;
}

object_header_t *WININET_AddRef( object_header_t *info )
{
    ULONG refs = InterlockedIncrement(&info->refs);
    TRACE("%p -> refcount = %d\n", info, refs );
    return info;
}

object_header_t *get_handle_object( HINTERNET hinternet )
{
    object_header_t *info = NULL;
    UINT_PTR handle = (UINT_PTR) hinternet;

    EnterCriticalSection( &WININET_cs );

    if(handle > 0 && handle < handle_table_size && handle_table[handle] && handle_table[handle]->valid_handle)
        info = WININET_AddRef(handle_table[handle]);

    LeaveCriticalSection( &WININET_cs );

    TRACE("handle %ld -> %p\n", handle, info);

    return info;
}

static void invalidate_handle(object_header_t *info)
{
    object_header_t *child, *next;

    if(!info->valid_handle)
        return;
    info->valid_handle = FALSE;

    /* Free all children as native does */
    LIST_FOR_EACH_ENTRY_SAFE( child, next, &info->children, object_header_t, entry )
    {
        TRACE("invalidating child handle %p for parent %p\n", child->hInternet, info);
        invalidate_handle( child );
    }

    WININET_Release(info);
}

BOOL WININET_Release( object_header_t *info )
{
    ULONG refs = InterlockedDecrement(&info->refs);
    TRACE( "object %p refcount = %d\n", info, refs );
    if( !refs )
    {
        invalidate_handle(info);
        if ( info->vtbl->CloseConnection )
        {
            TRACE( "closing connection %p\n", info);
            info->vtbl->CloseConnection( info );
        }
        /* Don't send a callback if this is a session handle created with InternetOpenUrl */
        if ((info->htype != WH_HHTTPSESSION && info->htype != WH_HFTPSESSION)
            || !(info->dwInternalFlags & INET_OPENURL))
        {
            INTERNET_SendCallback(info, info->dwContext,
                                  INTERNET_STATUS_HANDLE_CLOSING, &info->hInternet,
                                  sizeof(HINTERNET));
        }
        TRACE( "destroying object %p\n", info);
        if ( info->htype != WH_HINIT )
            list_remove( &info->entry );
        info->vtbl->Destroy( info );

        if(info->hInternet) {
            UINT_PTR handle = (UINT_PTR)info->hInternet;

            EnterCriticalSection( &WININET_cs );

            handle_table[handle] = NULL;
            if(next_handle > handle)
                next_handle = handle;

            LeaveCriticalSection( &WININET_cs );
        }

        heap_free(info);
    }
    return TRUE;
}

/***********************************************************************
 * DllMain [Internal] Initializes the internal 'WININET.DLL'.
 *
 * PARAMS
 *     hinstDLL    [I] handle to the DLL's instance
 *     fdwReason   [I]
 *     lpvReserved [I] reserved, must be NULL
 *
 * RETURNS
 *     Success: TRUE
 *     Failure: FALSE
 */

BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    TRACE("%p,%x,%p\n", hinstDLL, fdwReason, lpvReserved);

    switch (fdwReason) {
        case DLL_PROCESS_ATTACH:

            g_dwTlsErrIndex = TlsAlloc();

            if (g_dwTlsErrIndex == TLS_OUT_OF_INDEXES)
                return FALSE;

            if(!init_urlcache())
            {
                TlsFree(g_dwTlsErrIndex);
                return FALSE;
            }

            WININET_hModule = hinstDLL;
            break;

        case DLL_THREAD_ATTACH:
            break;

        case DLL_THREAD_DETACH:
            if (g_dwTlsErrIndex != TLS_OUT_OF_INDEXES)
            {
                heap_free(TlsGetValue(g_dwTlsErrIndex));
            }
            break;

        case DLL_PROCESS_DETACH:
            if (lpvReserved) break;
            collect_connections(COLLECT_CLEANUP);
            NETCON_unload();
            free_urlcache();
            free_cookie();

            if (g_dwTlsErrIndex != TLS_OUT_OF_INDEXES)
            {
                heap_free(TlsGetValue(g_dwTlsErrIndex));
                TlsFree(g_dwTlsErrIndex);
            }
            break;
    }
    return TRUE;
}

/***********************************************************************
 *		DllInstall (WININET.@)
 */
HRESULT WINAPI DllInstall(BOOL bInstall, LPCWSTR cmdline)
{
    FIXME("(%x %s): stub\n", bInstall, debugstr_w(cmdline));
    return S_OK;
}

/***********************************************************************
 *           INTERNET_SaveProxySettings
 *
 * Stores the proxy settings given by lpwai into the registry
 *
 * RETURNS
 *     ERROR_SUCCESS if no error, or error code on fail
 */
static LONG INTERNET_SaveProxySettings( proxyinfo_t *lpwpi )
{
    HKEY key;
    LONG ret;

    if ((ret = RegOpenKeyW( HKEY_CURRENT_USER, szInternetSettings, &key )))
        return ret;

    if ((ret = RegSetValueExW( key, L"ProxyEnable", 0, REG_DWORD, (BYTE*)&lpwpi->proxyEnabled, sizeof(DWORD))))
    {
        RegCloseKey( key );
        return ret;
    }

    if (lpwpi->proxy)
    {
        if ((ret = RegSetValueExW( key, L"ProxyServer", 0, REG_SZ, (BYTE*)lpwpi->proxy, sizeof(WCHAR) * (lstrlenW(lpwpi->proxy) + 1))))
        {
            RegCloseKey( key );
            return ret;
        }
    }
    else
    {
        if ((ret = RegDeleteValueW( key, L"ProxyServer" )) && ret != ERROR_FILE_NOT_FOUND)
        {
            RegCloseKey( key );
            return ret;
        }
    }

    RegCloseKey(key);
    return ERROR_SUCCESS;
}

/***********************************************************************
 *           INTERNET_FindProxyForProtocol
 *
 * Searches the proxy string for a proxy of the given protocol.
 * Returns the found proxy, or the default proxy if none of the given
 * protocol is found.
 *
 * PARAMETERS
 *     szProxy       [In]     proxy string to search
 *     proto         [In]     protocol to search for, e.g. "http"
 *     foundProxy    [Out]    found proxy
 *     foundProxyLen [In/Out] length of foundProxy buffer, in WCHARs
 *
 * RETURNS
 *     TRUE if a proxy is found, FALSE if not.  If foundProxy is too short,
 *     *foundProxyLen is set to the required size in WCHARs, including the
 *     NULL terminator, and the last error is set to ERROR_INSUFFICIENT_BUFFER.
 */
WCHAR *INTERNET_FindProxyForProtocol(LPCWSTR szProxy, LPCWSTR proto)
{
    WCHAR *ret = NULL;
    const WCHAR *ptr;

    TRACE("(%s, %s)\n", debugstr_w(szProxy), debugstr_w(proto));

    /* First, look for the specified protocol (proto=scheme://host:port) */
    for (ptr = szProxy; ptr && *ptr; )
    {
        LPCWSTR end, equal;

        if (!(end = wcschr(ptr, ' ')))
            end = ptr + lstrlenW(ptr);
        if ((equal = wcschr(ptr, '=')) && equal < end &&
             equal - ptr == lstrlenW(proto) &&
             !wcsnicmp(proto, ptr, lstrlenW(proto)))
        {
            ret = heap_strndupW(equal + 1, end - equal - 1);
            TRACE("found proxy for %s: %s\n", debugstr_w(proto), debugstr_w(ret));
            return ret;
        }
        if (*end == ' ')
            ptr = end + 1;
        else
            ptr = end;
    }

    /* It wasn't found: look for no protocol */
    for (ptr = szProxy; ptr && *ptr; )
    {
        LPCWSTR end;

        if (!(end = wcschr(ptr, ' ')))
            end = ptr + lstrlenW(ptr);
        if (!wcschr(ptr, '='))
        {
            ret = heap_strndupW(ptr, end - ptr);
            TRACE("found proxy for %s: %s\n", debugstr_w(proto), debugstr_w(ret));
            return ret;
        }
        if (*end == ' ')
            ptr = end + 1;
        else
            ptr = end;
    }

    return NULL;
}

/***********************************************************************
 *           InternetInitializeAutoProxyDll   (WININET.@)
 *
 * Setup the internal proxy
 *
 * PARAMETERS
 *     dwReserved
 *
 * RETURNS
 *     FALSE on failure
 *
 */
BOOL WINAPI InternetInitializeAutoProxyDll(DWORD dwReserved)
{
    FIXME("STUB\n");
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return FALSE;
}

/***********************************************************************
 *           DetectAutoProxyUrl   (WININET.@)
 *
 * Auto detect the proxy url
 *
 * RETURNS
 *     FALSE on failure
 *
 */
BOOL WINAPI DetectAutoProxyUrl(LPSTR lpszAutoProxyUrl,
	DWORD dwAutoProxyUrlLength, DWORD dwDetectFlags)
{
    FIXME("STUB\n");
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return FALSE;
}

static void FreeProxyInfo( proxyinfo_t *lpwpi )
{
    heap_free(lpwpi->proxy);
    heap_free(lpwpi->proxyBypass);
    heap_free(lpwpi->proxyUsername);
    heap_free(lpwpi->proxyPassword);
}

static proxyinfo_t *global_proxy;

static void free_global_proxy( void )
{
    EnterCriticalSection( &WININET_cs );
    if (global_proxy)
    {
        FreeProxyInfo( global_proxy );
        heap_free( global_proxy );
    }
    LeaveCriticalSection( &WININET_cs );
}

static BOOL parse_proxy_url( proxyinfo_t *info, const WCHAR *url )
{
    URL_COMPONENTSW uc = {sizeof(uc)};

    uc.dwHostNameLength = 1;
    uc.dwUserNameLength = 1;
    uc.dwPasswordLength = 1;

    if (!InternetCrackUrlW( url, 0, 0, &uc )) return FALSE;
    if (!uc.dwHostNameLength)
    {
        if (!(info->proxy = heap_strdupW( url ))) return FALSE;
        info->proxyUsername = NULL;
        info->proxyPassword = NULL;
        return TRUE;
    }
    if (!(info->proxy = heap_alloc( (uc.dwHostNameLength + 12) * sizeof(WCHAR) ))) return FALSE;
    swprintf( info->proxy, uc.dwHostNameLength + 12, L"%.*s:%u", uc.dwHostNameLength, uc.lpszHostName, uc.nPort );

    if (!uc.dwUserNameLength) info->proxyUsername = NULL;
    else if (!(info->proxyUsername = heap_strndupW( uc.lpszUserName, uc.dwUserNameLength )))
    {
        heap_free( info->proxy );
        return FALSE;
    }
    if (!uc.dwPasswordLength) info->proxyPassword = NULL;
    else if (!(info->proxyPassword = heap_strndupW( uc.lpszPassword, uc.dwPasswordLength )))
    {
        heap_free( info->proxyUsername );
        heap_free( info->proxy );
        return FALSE;
    }
    return TRUE;
}

/***********************************************************************
 *          INTERNET_LoadProxySettings
 *
 * Loads proxy information from process-wide global settings, the registry,
 * or the environment into lpwpi.
 *
 * The caller should call FreeProxyInfo when done with lpwpi.
 *
 * FIXME:
 * The proxy may be specified in the form 'http=proxy.my.org'
 * Presumably that means there can be ftp=ftpproxy.my.org too.
 */
static LONG INTERNET_LoadProxySettings( proxyinfo_t *lpwpi )
{
    HKEY key;
    DWORD type, len;
    const WCHAR *envproxy;
    LONG ret;

    memset( lpwpi, 0, sizeof(*lpwpi) );

    EnterCriticalSection( &WININET_cs );
    if (global_proxy)
    {
        lpwpi->proxyEnabled = global_proxy->proxyEnabled;
        lpwpi->proxy = heap_strdupW( global_proxy->proxy );
        lpwpi->proxyBypass = heap_strdupW( global_proxy->proxyBypass );
    }
    LeaveCriticalSection( &WININET_cs );

    if ((ret = RegOpenKeyW( HKEY_CURRENT_USER, szInternetSettings, &key )))
    {
        FreeProxyInfo( lpwpi );
        return ret;
    }

    len = sizeof(DWORD);
    if (RegQueryValueExW( key, L"ProxyEnable", NULL, &type, (BYTE *)&lpwpi->proxyEnabled, &len ) || type != REG_DWORD)
    {
        lpwpi->proxyEnabled = 0;
        if((ret = RegSetValueExW( key, L"ProxyEnable", 0, REG_DWORD, (BYTE *)&lpwpi->proxyEnabled, sizeof(DWORD) )))
        {
            FreeProxyInfo( lpwpi );
            RegCloseKey( key );
            return ret;
        }
    }

    if (!(envproxy = _wgetenv( L"http_proxy" )) || lpwpi->proxyEnabled)
    {
        /* figure out how much memory the proxy setting takes */
        if (!RegQueryValueExW( key, L"ProxyServer", NULL, &type, NULL, &len ) && len && (type == REG_SZ))
        {
            LPWSTR szProxy, p;

            if (!(szProxy = heap_alloc(len)))
            {
                RegCloseKey( key );
                FreeProxyInfo( lpwpi );
                return ERROR_OUTOFMEMORY;
            }
            RegQueryValueExW( key, L"ProxyServer", NULL, &type, (BYTE*)szProxy, &len );

            /* find the http proxy, and strip away everything else */
            p = wcsstr( szProxy, L"http=" );
            if (p)
            {
                p += lstrlenW( L"http=" );
                lstrcpyW( szProxy, p );
            }
            p = wcschr( szProxy, ';' );
            if (p) *p = 0;

            FreeProxyInfo( lpwpi );
            lpwpi->proxy = szProxy;
            lpwpi->proxyBypass = NULL;

            TRACE("http proxy (from registry) = %s\n", debugstr_w(lpwpi->proxy));
        }
        else
        {
            TRACE("No proxy server settings in registry.\n");
            FreeProxyInfo( lpwpi );
            lpwpi->proxy = NULL;
            lpwpi->proxyBypass = NULL;
        }
    }
    else if (envproxy)
    {
        FreeProxyInfo( lpwpi );
        if (parse_proxy_url( lpwpi, envproxy ))
        {
            TRACE("http proxy (from environment) = %s\n", debugstr_w(lpwpi->proxy));
            lpwpi->proxyEnabled = 1;
            lpwpi->proxyBypass = NULL;
        }
        else
        {
            WARN("failed to parse http_proxy value %s\n", debugstr_w(envproxy));
            lpwpi->proxyEnabled = 0;
            lpwpi->proxy = NULL;
            lpwpi->proxyBypass = NULL;
        }
    }

    if (lpwpi->proxyEnabled)
    {
        TRACE("Proxy is enabled.\n");

        if (!(envproxy = _wgetenv( L"no_proxy" )))
        {
            /* figure out how much memory the proxy setting takes */
            if (!RegQueryValueExW( key, L"ProxyOverride", NULL, &type, NULL, &len ) && len && (type == REG_SZ))
            {
                LPWSTR szProxy;

                if (!(szProxy = heap_alloc(len)))
                {
                    RegCloseKey( key );
                    return ERROR_OUTOFMEMORY;
                }
                RegQueryValueExW( key, L"ProxyOverride", NULL, &type, (BYTE*)szProxy, &len );

                heap_free( lpwpi->proxyBypass );
                lpwpi->proxyBypass = szProxy;

                TRACE("http proxy bypass (from registry) = %s\n", debugstr_w(lpwpi->proxyBypass));
            }
            else
            {
                heap_free( lpwpi->proxyBypass );
                lpwpi->proxyBypass = NULL;

                TRACE("No proxy bypass server settings in registry.\n");
            }
        }
        else
        {
            WCHAR *envproxyW;

            if (!(envproxyW = heap_alloc(lstrlenW(envproxy) * sizeof(WCHAR))))
            {
                RegCloseKey( key );
                return ERROR_OUTOFMEMORY;
            }
            lstrcpyW( envproxyW, envproxy );

            heap_free( lpwpi->proxyBypass );
            lpwpi->proxyBypass = envproxyW;

            TRACE("http proxy bypass (from environment) = %s\n", debugstr_w(lpwpi->proxyBypass));
        }
    }
    else TRACE("Proxy is disabled.\n");

    RegCloseKey( key );
    return ERROR_SUCCESS;
}

/***********************************************************************
 *           INTERNET_ConfigureProxy
 */
static BOOL INTERNET_ConfigureProxy( appinfo_t *lpwai )
{
    proxyinfo_t wpi;

    if (INTERNET_LoadProxySettings( &wpi ))
        return FALSE;

    if (wpi.proxyEnabled)
    {
        TRACE("http proxy = %s bypass = %s\n", debugstr_w(wpi.proxy), debugstr_w(wpi.proxyBypass));

        lpwai->accessType    = INTERNET_OPEN_TYPE_PROXY;
        lpwai->proxy         = wpi.proxy;
        lpwai->proxyBypass   = wpi.proxyBypass;
        lpwai->proxyUsername = wpi.proxyUsername;
        lpwai->proxyPassword = wpi.proxyPassword;
        return TRUE;
    }

    lpwai->accessType = INTERNET_OPEN_TYPE_DIRECT;
    FreeProxyInfo(&wpi);
    return FALSE;
}

/***********************************************************************
 *           dump_INTERNET_FLAGS
 *
 * Helper function to TRACE the internet flags.
 *
 * RETURNS
 *    None
 *
 */
static void dump_INTERNET_FLAGS(DWORD dwFlags) 
{
#define FE(x) { x, #x }
    static const wininet_flag_info flag[] = {
        FE(INTERNET_FLAG_RELOAD),
        FE(INTERNET_FLAG_RAW_DATA),
        FE(INTERNET_FLAG_EXISTING_CONNECT),
        FE(INTERNET_FLAG_ASYNC),
        FE(INTERNET_FLAG_PASSIVE),
        FE(INTERNET_FLAG_NO_CACHE_WRITE),
        FE(INTERNET_FLAG_MAKE_PERSISTENT),
        FE(INTERNET_FLAG_FROM_CACHE),
        FE(INTERNET_FLAG_SECURE),
        FE(INTERNET_FLAG_KEEP_CONNECTION),
        FE(INTERNET_FLAG_NO_AUTO_REDIRECT),
        FE(INTERNET_FLAG_READ_PREFETCH),
        FE(INTERNET_FLAG_NO_COOKIES),
        FE(INTERNET_FLAG_NO_AUTH),
        FE(INTERNET_FLAG_CACHE_IF_NET_FAIL),
        FE(INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP),
        FE(INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS),
        FE(INTERNET_FLAG_IGNORE_CERT_DATE_INVALID),
        FE(INTERNET_FLAG_IGNORE_CERT_CN_INVALID),
        FE(INTERNET_FLAG_RESYNCHRONIZE),
        FE(INTERNET_FLAG_HYPERLINK),
        FE(INTERNET_FLAG_NO_UI),
        FE(INTERNET_FLAG_PRAGMA_NOCACHE),
        FE(INTERNET_FLAG_CACHE_ASYNC),
        FE(INTERNET_FLAG_FORMS_SUBMIT),
        FE(INTERNET_FLAG_NEED_FILE),
        FE(INTERNET_FLAG_TRANSFER_ASCII),
        FE(INTERNET_FLAG_TRANSFER_BINARY)
    };
#undef FE
    unsigned int i;

    for (i = 0; i < ARRAY_SIZE(flag); i++) {
	if (flag[i].val & dwFlags) {
	    TRACE(" %s", flag[i].name);
	    dwFlags &= ~flag[i].val;
	}
    }	
    if (dwFlags)
        TRACE(" Unknown flags (%08x)\n", dwFlags);
    else
        TRACE("\n");
}

/***********************************************************************
 *           INTERNET_CloseHandle (internal)
 *
 * Close internet handle
 *
 */
static VOID APPINFO_Destroy(object_header_t *hdr)
{
    appinfo_t *lpwai = (appinfo_t*)hdr;

    TRACE("%p\n",lpwai);

    heap_free(lpwai->agent);
    heap_free(lpwai->proxy);
    heap_free(lpwai->proxyBypass);
    heap_free(lpwai->proxyUsername);
    heap_free(lpwai->proxyPassword);
}

static DWORD APPINFO_QueryOption(object_header_t *hdr, DWORD option, void *buffer, DWORD *size, BOOL unicode)
{
    appinfo_t *ai = (appinfo_t*)hdr;

    switch(option) {
    case INTERNET_OPTION_HANDLE_TYPE:
        TRACE("INTERNET_OPTION_HANDLE_TYPE\n");

        if (*size < sizeof(ULONG))
            return ERROR_INSUFFICIENT_BUFFER;

        *size = sizeof(DWORD);
        *(DWORD*)buffer = INTERNET_HANDLE_TYPE_INTERNET;
        return ERROR_SUCCESS;

    case INTERNET_OPTION_USER_AGENT: {
        DWORD bufsize;

        TRACE("INTERNET_OPTION_USER_AGENT\n");

        bufsize = *size;

        if (unicode) {
            DWORD len = ai->agent ? lstrlenW(ai->agent) : 0;

            *size = (len + 1) * sizeof(WCHAR);
            if(!buffer || bufsize < *size)
                return ERROR_INSUFFICIENT_BUFFER;

            if (ai->agent)
                lstrcpyW(buffer, ai->agent);
            else
                *(WCHAR *)buffer = 0;
            /* If the buffer is copied, the returned length doesn't include
             * the NULL terminator.
             */
            *size = len;
        }else {
            if (ai->agent)
                *size = WideCharToMultiByte(CP_ACP, 0, ai->agent, -1, NULL, 0, NULL, NULL);
            else
                *size = 1;
            if(!buffer || bufsize < *size)
                return ERROR_INSUFFICIENT_BUFFER;

            if (ai->agent)
                WideCharToMultiByte(CP_ACP, 0, ai->agent, -1, buffer, *size, NULL, NULL);
            else
                *(char *)buffer = 0;
            /* If the buffer is copied, the returned length doesn't include
             * the NULL terminator.
             */
            *size -= 1;
        }

        return ERROR_SUCCESS;
    }

    case INTERNET_OPTION_PROXY:
        if(!size) return ERROR_INVALID_PARAMETER;
        if (unicode) {
            INTERNET_PROXY_INFOW *pi = (INTERNET_PROXY_INFOW *)buffer;
            DWORD proxyBytesRequired = 0, proxyBypassBytesRequired = 0;
            LPWSTR proxy, proxy_bypass;

            if (ai->proxy)
                proxyBytesRequired = (lstrlenW(ai->proxy) + 1) * sizeof(WCHAR);
            if (ai->proxyBypass)
                proxyBypassBytesRequired = (lstrlenW(ai->proxyBypass) + 1) * sizeof(WCHAR);
            if (!pi || *size < sizeof(INTERNET_PROXY_INFOW) + proxyBytesRequired + proxyBypassBytesRequired)
            {
                *size = sizeof(INTERNET_PROXY_INFOW) + proxyBytesRequired + proxyBypassBytesRequired;
                return ERROR_INSUFFICIENT_BUFFER;
            }
            proxy = (LPWSTR)((LPBYTE)buffer + sizeof(INTERNET_PROXY_INFOW));
            proxy_bypass = (LPWSTR)((LPBYTE)buffer + sizeof(INTERNET_PROXY_INFOW) + proxyBytesRequired);

            pi->dwAccessType = ai->accessType;
            pi->lpszProxy = NULL;
            pi->lpszProxyBypass = NULL;
            if (ai->proxy) {
                lstrcpyW(proxy, ai->proxy);
                pi->lpszProxy = proxy;
            }

            if (ai->proxyBypass) {
                lstrcpyW(proxy_bypass, ai->proxyBypass);
                pi->lpszProxyBypass = proxy_bypass;
            }

            *size = sizeof(INTERNET_PROXY_INFOW) + proxyBytesRequired + proxyBypassBytesRequired;
            return ERROR_SUCCESS;
        }else {
            INTERNET_PROXY_INFOA *pi = (INTERNET_PROXY_INFOA *)buffer;
            DWORD proxyBytesRequired = 0, proxyBypassBytesRequired = 0;
            LPSTR proxy, proxy_bypass;

            if (ai->proxy)
                proxyBytesRequired = WideCharToMultiByte(CP_ACP, 0, ai->proxy, -1, NULL, 0, NULL, NULL);
            if (ai->proxyBypass)
                proxyBypassBytesRequired = WideCharToMultiByte(CP_ACP, 0, ai->proxyBypass, -1,
                        NULL, 0, NULL, NULL);
            if (!pi || *size < sizeof(INTERNET_PROXY_INFOA) + proxyBytesRequired + proxyBypassBytesRequired)
            {
                *size = sizeof(INTERNET_PROXY_INFOA) + proxyBytesRequired + proxyBypassBytesRequired;
                return ERROR_INSUFFICIENT_BUFFER;
            }
            proxy = (LPSTR)((LPBYTE)buffer + sizeof(INTERNET_PROXY_INFOA));
            proxy_bypass = (LPSTR)((LPBYTE)buffer + sizeof(INTERNET_PROXY_INFOA) + proxyBytesRequired);

            pi->dwAccessType = ai->accessType;
            pi->lpszProxy = NULL;
            pi->lpszProxyBypass = NULL;
            if (ai->proxy) {
                WideCharToMultiByte(CP_ACP, 0, ai->proxy, -1, proxy, proxyBytesRequired, NULL, NULL);
                pi->lpszProxy = proxy;
            }

            if (ai->proxyBypass) {
                WideCharToMultiByte(CP_ACP, 0, ai->proxyBypass, -1, proxy_bypass,
                        proxyBypassBytesRequired, NULL, NULL);
                pi->lpszProxyBypass = proxy_bypass;
            }

            *size = sizeof(INTERNET_PROXY_INFOA) + proxyBytesRequired + proxyBypassBytesRequired;
            return ERROR_SUCCESS;
        }

    case INTERNET_OPTION_CONNECT_TIMEOUT:
        TRACE("INTERNET_OPTION_CONNECT_TIMEOUT\n");

        if (*size < sizeof(ULONG))
            return ERROR_INSUFFICIENT_BUFFER;

        *(ULONG*)buffer = ai->connect_timeout;
        *size = sizeof(ULONG);

        return ERROR_SUCCESS;
    }

    return INET_QueryOption(hdr, option, buffer, size, unicode);
}

static DWORD APPINFO_SetOption(object_header_t *hdr, DWORD option, void *buf, DWORD size)
{
    appinfo_t *ai = (appinfo_t*)hdr;

    switch(option) {
    case INTERNET_OPTION_CONNECT_TIMEOUT:
        TRACE("INTERNET_OPTION_CONNECT_TIMEOUT\n");

        if(size != sizeof(connect_timeout))
            return ERROR_INTERNET_BAD_OPTION_LENGTH;
        if(!*(ULONG*)buf)
            return ERROR_BAD_ARGUMENTS;

        ai->connect_timeout = *(ULONG*)buf;
        return ERROR_SUCCESS;
    case INTERNET_OPTION_USER_AGENT:
        heap_free(ai->agent);
        if (!(ai->agent = heap_strdupW(buf))) return ERROR_OUTOFMEMORY;
        return ERROR_SUCCESS;
    case INTERNET_OPTION_REFRESH:
        FIXME("INTERNET_OPTION_REFRESH\n");
        return ERROR_SUCCESS;
    }

    return INET_SetOption(hdr, option, buf, size);
}

static const object_vtbl_t APPINFOVtbl = {
    APPINFO_Destroy,
    NULL,
    APPINFO_QueryOption,
    APPINFO_SetOption,
    NULL,
    NULL,
    NULL
};


/***********************************************************************
 *           InternetOpenW   (WININET.@)
 *
 * Per-application initialization of wininet
 *
 * RETURNS
 *    HINTERNET on success
 *    NULL on failure
 *
 */
HINTERNET WINAPI InternetOpenW(LPCWSTR lpszAgent, DWORD dwAccessType,
    LPCWSTR lpszProxy, LPCWSTR lpszProxyBypass, DWORD dwFlags)
{
    appinfo_t *lpwai = NULL;

    if (TRACE_ON(wininet)) {
#define FE(x) { x, #x }
	static const wininet_flag_info access_type[] = {
	    FE(INTERNET_OPEN_TYPE_PRECONFIG),
	    FE(INTERNET_OPEN_TYPE_DIRECT),
	    FE(INTERNET_OPEN_TYPE_PROXY),
	    FE(INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY)
	};
#undef FE
	DWORD i;
	const char *access_type_str = "Unknown";
	
	TRACE("(%s, %i, %s, %s, %i)\n", debugstr_w(lpszAgent), dwAccessType,
	      debugstr_w(lpszProxy), debugstr_w(lpszProxyBypass), dwFlags);
        for (i = 0; i < ARRAY_SIZE(access_type); i++) {
	    if (access_type[i].val == dwAccessType) {
		access_type_str = access_type[i].name;
		break;
	    }
	}
	TRACE("  access type : %s\n", access_type_str);
	TRACE("  flags       :");
	dump_INTERNET_FLAGS(dwFlags);
    }

    /* Clear any error information */
    INTERNET_SetLastError(0);

    if((dwAccessType == INTERNET_OPEN_TYPE_PROXY) && !lpszProxy) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return NULL;
    }

    lpwai = alloc_object(NULL, &APPINFOVtbl, sizeof(appinfo_t));
    if (!lpwai) {
        SetLastError(ERROR_OUTOFMEMORY);
        return NULL;
    }

    lpwai->hdr.htype = WH_HINIT;
    lpwai->hdr.dwFlags = dwFlags;
    lpwai->accessType = dwAccessType;
    lpwai->proxyUsername = NULL;
    lpwai->proxyPassword = NULL;
    lpwai->connect_timeout = connect_timeout;

    lpwai->agent = heap_strdupW(lpszAgent);
    if(dwAccessType == INTERNET_OPEN_TYPE_PRECONFIG)
        INTERNET_ConfigureProxy( lpwai );
    else if(dwAccessType == INTERNET_OPEN_TYPE_PROXY) {
        lpwai->proxy = heap_strdupW(lpszProxy);
        lpwai->proxyBypass = heap_strdupW(lpszProxyBypass);
    }

    TRACE("returning %p\n", lpwai);

    return lpwai->hdr.hInternet;
}


/***********************************************************************
 *           InternetOpenA   (WININET.@)
 *
 * Per-application initialization of wininet
 *
 * RETURNS
 *    HINTERNET on success
 *    NULL on failure
 *
 */
HINTERNET WINAPI InternetOpenA(LPCSTR lpszAgent, DWORD dwAccessType,
    LPCSTR lpszProxy, LPCSTR lpszProxyBypass, DWORD dwFlags)
{
    WCHAR *szAgent, *szProxy, *szBypass;
    HINTERNET rc;

    TRACE("(%s, 0x%08x, %s, %s, 0x%08x)\n", debugstr_a(lpszAgent),
       dwAccessType, debugstr_a(lpszProxy), debugstr_a(lpszProxyBypass), dwFlags);

    szAgent = heap_strdupAtoW(lpszAgent);
    szProxy = heap_strdupAtoW(lpszProxy);
    szBypass = heap_strdupAtoW(lpszProxyBypass);

    rc = InternetOpenW(szAgent, dwAccessType, szProxy, szBypass, dwFlags);

    heap_free(szAgent);
    heap_free(szProxy);
    heap_free(szBypass);
    return rc;
}

/***********************************************************************
 *           InternetGetLastResponseInfoA (WININET.@)
 *
 * Return last wininet error description on the calling thread
 *
 * RETURNS
 *    TRUE on success of writing to buffer
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetGetLastResponseInfoA(LPDWORD lpdwError,
    LPSTR lpszBuffer, LPDWORD lpdwBufferLength)
{
    LPWITHREADERROR lpwite = TlsGetValue(g_dwTlsErrIndex);

    TRACE("\n");

    if (lpwite)
    {
        *lpdwError = lpwite->dwError;
        if (lpwite->dwError)
        {
            memcpy(lpszBuffer, lpwite->response, *lpdwBufferLength);
            *lpdwBufferLength = strlen(lpszBuffer);
        }
        else
            *lpdwBufferLength = 0;
    }
    else
    {
        *lpdwError = 0;
        *lpdwBufferLength = 0;
    }

    return TRUE;
}

/***********************************************************************
 *           InternetGetLastResponseInfoW (WININET.@)
 *
 * Return last wininet error description on the calling thread
 *
 * RETURNS
 *    TRUE on success of writing to buffer
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetGetLastResponseInfoW(LPDWORD lpdwError,
    LPWSTR lpszBuffer, LPDWORD lpdwBufferLength)
{
    LPWITHREADERROR lpwite = TlsGetValue(g_dwTlsErrIndex);

    TRACE("\n");

    if (lpwite)
    {
        *lpdwError = lpwite->dwError;
        if (lpwite->dwError)
        {
            memcpy(lpszBuffer, lpwite->response, *lpdwBufferLength);
            *lpdwBufferLength = lstrlenW(lpszBuffer);
        }
        else
            *lpdwBufferLength = 0;
    }
    else
    {
        *lpdwError = 0;
        *lpdwBufferLength = 0;
    }

    return TRUE;
}

/***********************************************************************
 *           InternetGetConnectedState (WININET.@)
 *
 * Return connected state
 *
 * RETURNS
 *    TRUE if connected
 *    if lpdwStatus is not null, return the status (off line,
 *    modem, lan...) in it.
 *    FALSE if not connected
 */
BOOL WINAPI InternetGetConnectedState(LPDWORD lpdwStatus, DWORD dwReserved)
{
    TRACE("(%p, 0x%08x)\n", lpdwStatus, dwReserved);

    return InternetGetConnectedStateExW(lpdwStatus, NULL, 0, dwReserved);
}


/***********************************************************************
 *           InternetGetConnectedStateExW (WININET.@)
 *
 * Return connected state
 *
 * PARAMS
 *
 * lpdwStatus         [O] Flags specifying the status of the internet connection.
 * lpszConnectionName [O] Pointer to buffer to receive the friendly name of the internet connection.
 * dwNameLen          [I] Size of the buffer, in characters.
 * dwReserved         [I] Reserved. Must be set to 0.
 *
 * RETURNS
 *    TRUE if connected
 *    if lpdwStatus is not null, return the status (off line,
 *    modem, lan...) in it.
 *    FALSE if not connected
 *
 * NOTES
 *   If the system has no available network connections, an empty string is
 *   stored in lpszConnectionName. If there is a LAN connection, a localized
 *   "LAN Connection" string is stored. Presumably, if only a dial-up
 *   connection is available then the name of the dial-up connection is
 *   returned. Why any application, other than the "Internet Settings" CPL,
 *   would want to use this function instead of the simpler InternetGetConnectedStateW
 *   function is beyond me.
 */
BOOL WINAPI InternetGetConnectedStateExW(LPDWORD lpdwStatus, LPWSTR lpszConnectionName,
                                         DWORD dwNameLen, DWORD dwReserved)
{
    TRACE("(%p, %p, %d, 0x%08x)\n", lpdwStatus, lpszConnectionName, dwNameLen, dwReserved);

    /* Must be zero */
    if(dwReserved)
        return FALSE;

    if (lpdwStatus) {
        WARN("always returning LAN connection.\n");
        *lpdwStatus = INTERNET_CONNECTION_LAN;
    }

    /* When the buffer size is zero LoadStringW fills the buffer with a pointer to
     * the resource, avoid it as we must not change the buffer in this case */
    if(lpszConnectionName && dwNameLen) {
        *lpszConnectionName = '\0';
        LoadStringW(WININET_hModule, IDS_LANCONNECTION, lpszConnectionName, dwNameLen);
    }

    return TRUE;
}


/***********************************************************************
 *           InternetGetConnectedStateExA (WININET.@)
 */
BOOL WINAPI InternetGetConnectedStateExA(LPDWORD lpdwStatus, LPSTR lpszConnectionName,
                                         DWORD dwNameLen, DWORD dwReserved)
{
    LPWSTR lpwszConnectionName = NULL;
    BOOL rc;

    TRACE("(%p, %p, %d, 0x%08x)\n", lpdwStatus, lpszConnectionName, dwNameLen, dwReserved);

    if (lpszConnectionName && dwNameLen > 0)
        lpwszConnectionName = heap_alloc(dwNameLen * sizeof(WCHAR));

    rc = InternetGetConnectedStateExW(lpdwStatus,lpwszConnectionName, dwNameLen,
                                      dwReserved);
    if (rc && lpwszConnectionName)
        WideCharToMultiByte(CP_ACP,0,lpwszConnectionName,-1,lpszConnectionName,
                            dwNameLen, NULL, NULL);

    heap_free(lpwszConnectionName);
    return rc;
}


/***********************************************************************
 *           InternetConnectW (WININET.@)
 *
 * Open a ftp, gopher or http session
 *
 * RETURNS
 *    HINTERNET a session handle on success
 *    NULL on failure
 *
 */
HINTERNET WINAPI InternetConnectW(HINTERNET hInternet,
    LPCWSTR lpszServerName, INTERNET_PORT nServerPort,
    LPCWSTR lpszUserName, LPCWSTR lpszPassword,
    DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext)
{
    appinfo_t *hIC;
    HINTERNET rc = NULL;
    DWORD res = ERROR_SUCCESS;

    TRACE("(%p, %s, %u, %s, %p, %u, %x, %lx)\n", hInternet, debugstr_w(lpszServerName),
          nServerPort, debugstr_w(lpszUserName), lpszPassword, dwService, dwFlags, dwContext);

    if (!lpszServerName)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return NULL;
    }

    hIC = (appinfo_t*)get_handle_object( hInternet );
    if ( (hIC == NULL) || (hIC->hdr.htype != WH_HINIT) )
    {
        res = ERROR_INVALID_HANDLE;
        goto lend;
    }

    switch (dwService)
    {
        case INTERNET_SERVICE_FTP:
            rc = FTP_Connect(hIC, lpszServerName, nServerPort,
            lpszUserName, lpszPassword, dwFlags, dwContext, 0);
            if(!rc)
                res = INTERNET_GetLastError();
            break;

        case INTERNET_SERVICE_HTTP:
	    res = HTTP_Connect(hIC, lpszServerName, nServerPort,
                    lpszUserName, lpszPassword, dwFlags, dwContext, 0, &rc);
            break;

        case INTERNET_SERVICE_GOPHER:
        default:
            break;
    }
lend:
    if( hIC )
        WININET_Release( &hIC->hdr );

    TRACE("returning %p\n", rc);
    SetLastError(res);
    return rc;
}


/***********************************************************************
 *           InternetConnectA (WININET.@)
 *
 * Open a ftp, gopher or http session
 *
 * RETURNS
 *    HINTERNET a session handle on success
 *    NULL on failure
 *
 */
HINTERNET WINAPI InternetConnectA(HINTERNET hInternet,
    LPCSTR lpszServerName, INTERNET_PORT nServerPort,
    LPCSTR lpszUserName, LPCSTR lpszPassword,
    DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext)
{
    HINTERNET rc = NULL;
    LPWSTR szServerName;
    LPWSTR szUserName;
    LPWSTR szPassword;

    szServerName = heap_strdupAtoW(lpszServerName);
    szUserName = heap_strdupAtoW(lpszUserName);
    szPassword = heap_strdupAtoW(lpszPassword);

    rc = InternetConnectW(hInternet, szServerName, nServerPort,
        szUserName, szPassword, dwService, dwFlags, dwContext);

    heap_free(szServerName);
    heap_free(szUserName);
    heap_free(szPassword);
    return rc;
}


/***********************************************************************
 *           InternetFindNextFileA (WININET.@)
 *
 * Continues a file search from a previous call to FindFirstFile
 *
 * RETURNS
 *    TRUE on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetFindNextFileA(HINTERNET hFind, LPVOID lpvFindData)
{
    BOOL ret;
    WIN32_FIND_DATAW fd;
    
    ret = InternetFindNextFileW(hFind, lpvFindData?&fd:NULL);
    if(lpvFindData)
        WININET_find_data_WtoA(&fd, (LPWIN32_FIND_DATAA)lpvFindData);
    return ret;
}

/***********************************************************************
 *           InternetFindNextFileW (WININET.@)
 *
 * Continues a file search from a previous call to FindFirstFile
 *
 * RETURNS
 *    TRUE on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetFindNextFileW(HINTERNET hFind, LPVOID lpvFindData)
{
    object_header_t *hdr;
    DWORD res;

    TRACE("\n");

    hdr = get_handle_object(hFind);
    if(!hdr) {
        WARN("Invalid handle\n");
        SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    if(hdr->vtbl->FindNextFileW) {
        res = hdr->vtbl->FindNextFileW(hdr, lpvFindData);
    }else {
        WARN("Handle doesn't support NextFile\n");
        res = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
    }

    WININET_Release(hdr);

    if(res != ERROR_SUCCESS)
        SetLastError(res);
    return res == ERROR_SUCCESS;
}

/***********************************************************************
 *           InternetCloseHandle (WININET.@)
 *
 * Generic close handle function
 *
 * RETURNS
 *    TRUE on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetCloseHandle(HINTERNET hInternet)
{
    object_header_t *obj;
    
    TRACE("%p\n", hInternet);

    obj = get_handle_object( hInternet );
    if (!obj) {
        SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    invalidate_handle(obj);
    WININET_Release(obj);

    return TRUE;
}

static BOOL set_url_component(WCHAR **component, DWORD *component_length, const WCHAR *value, DWORD len)
{
    TRACE("%s (%d)\n", debugstr_wn(value, len), len);

    if (!*component_length)
        return TRUE;

    if (!*component) {
        *(const WCHAR**)component = value;
        *component_length = len;
        return TRUE;
    }

    if (*component_length < len+1) {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    *component_length = len;
    if(len)
        memcpy(*component, value, len*sizeof(WCHAR));
    (*component)[len] = 0;
    return TRUE;
}

static BOOL set_url_component_WtoA(const WCHAR *comp_w, DWORD length, const WCHAR *url_w, char **comp, DWORD *ret_length,
                                   const char *url_a)
{
    size_t size, ret_size = *ret_length;

    if (!*ret_length)
        return TRUE;
    size = WideCharToMultiByte(CP_ACP, 0, comp_w, length, NULL, 0, NULL, NULL);

    if (!*comp) {
        *comp = comp_w ? (char*)url_a + WideCharToMultiByte(CP_ACP, 0, url_w, comp_w-url_w, NULL, 0, NULL, NULL) : NULL;
        *ret_length = size;
        return TRUE;
    }

    if (size+1 > ret_size) {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        *ret_length = size+1;
        return FALSE;
    }

    *ret_length = size;
    WideCharToMultiByte(CP_ACP, 0, comp_w, length, *comp, ret_size-1, NULL, NULL);
    (*comp)[size] = 0;
    return TRUE;
}

static BOOL set_url_component_AtoW(const char *comp_a, DWORD len_a, WCHAR **comp_w, DWORD *len_w, WCHAR **buf)
{
    *len_w = len_a;

    if(!comp_a) {
        *comp_w = NULL;
        return TRUE;
    }

    if(!(*comp_w = *buf = heap_alloc(len_a*sizeof(WCHAR)))) {
        SetLastError(ERROR_OUTOFMEMORY);
        return FALSE;
    }

    return TRUE;
}

/***********************************************************************
 *           InternetCrackUrlA (WININET.@)
 *
 * See InternetCrackUrlW.
 */
BOOL WINAPI InternetCrackUrlA(const char *url, DWORD url_length, DWORD flags, URL_COMPONENTSA *ret_comp)
{
    WCHAR *host = NULL, *user = NULL, *pass = NULL, *path = NULL, *scheme = NULL, *extra = NULL;
    URL_COMPONENTSW comp;
    WCHAR *url_w = NULL;
    BOOL ret;

    TRACE("(%s %u %x %p)\n", url_length ? debugstr_an(url, url_length) : debugstr_a(url), url_length, flags, ret_comp);

    if (!url || !*url || !ret_comp || ret_comp->dwStructSize != sizeof(URL_COMPONENTSA)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    comp.dwStructSize = sizeof(comp);

    ret = set_url_component_AtoW(ret_comp->lpszHostName, ret_comp->dwHostNameLength,
                                 &comp.lpszHostName, &comp.dwHostNameLength, &host)
        && set_url_component_AtoW(ret_comp->lpszUserName, ret_comp->dwUserNameLength,
                                  &comp.lpszUserName, &comp.dwUserNameLength, &user)
        && set_url_component_AtoW(ret_comp->lpszPassword, ret_comp->dwPasswordLength,
                                  &comp.lpszPassword, &comp.dwPasswordLength, &pass)
        && set_url_component_AtoW(ret_comp->lpszUrlPath, ret_comp->dwUrlPathLength,
                                  &comp.lpszUrlPath, &comp.dwUrlPathLength, &path)
        && set_url_component_AtoW(ret_comp->lpszScheme, ret_comp->dwSchemeLength,
                                  &comp.lpszScheme, &comp.dwSchemeLength, &scheme)
        && set_url_component_AtoW(ret_comp->lpszExtraInfo, ret_comp->dwExtraInfoLength,
                                  &comp.lpszExtraInfo, &comp.dwExtraInfoLength, &extra);

    if(ret && !(url_w = heap_strndupAtoW(url, url_length ? url_length : -1, &url_length))) {
        SetLastError(ERROR_OUTOFMEMORY);
        ret = FALSE;
    }

    if (ret && (ret = InternetCrackUrlW(url_w, url_length, flags, &comp))) {
        ret_comp->nScheme = comp.nScheme;
        ret_comp->nPort = comp.nPort;

        ret = set_url_component_WtoA(comp.lpszHostName, comp.dwHostNameLength, url_w,
                                     &ret_comp->lpszHostName, &ret_comp->dwHostNameLength, url)
            && set_url_component_WtoA(comp.lpszUserName, comp.dwUserNameLength, url_w,
                                      &ret_comp->lpszUserName, &ret_comp->dwUserNameLength, url)
            && set_url_component_WtoA(comp.lpszPassword, comp.dwPasswordLength, url_w,
                                      &ret_comp->lpszPassword, &ret_comp->dwPasswordLength, url)
            && set_url_component_WtoA(comp.lpszUrlPath, comp.dwUrlPathLength, url_w,
                                      &ret_comp->lpszUrlPath, &ret_comp->dwUrlPathLength, url)
            && set_url_component_WtoA(comp.lpszScheme, comp.dwSchemeLength, url_w,
                                      &ret_comp->lpszScheme, &ret_comp->dwSchemeLength, url)
            && set_url_component_WtoA(comp.lpszExtraInfo, comp.dwExtraInfoLength, url_w,
                                      &ret_comp->lpszExtraInfo, &ret_comp->dwExtraInfoLength, url);

        if(ret)
            TRACE("%s: scheme(%s) host(%s) path(%s) extra(%s)\n", debugstr_a(url),
                  debugstr_an(ret_comp->lpszScheme, ret_comp->dwSchemeLength),
                  debugstr_an(ret_comp->lpszHostName, ret_comp->dwHostNameLength),
                  debugstr_an(ret_comp->lpszUrlPath, ret_comp->dwUrlPathLength),
                  debugstr_an(ret_comp->lpszExtraInfo, ret_comp->dwExtraInfoLength));
    }

    heap_free(host);
    heap_free(user);
    heap_free(pass);
    heap_free(path);
    heap_free(scheme);
    heap_free(extra);
    heap_free(url_w);
    return ret;
}

static const WCHAR *url_schemes[] =
{
    L"ftp",
    L"gopher",
    L"http",
    L"https",
    L"file",
    L"news",
    L"mailto",
    L"socks",
    L"javascript",
    L"vbscript",
    L"res"
};

/***********************************************************************
 *           GetInternetSchemeW (internal)
 *
 * Get scheme of url
 *
 * RETURNS
 *    scheme on success
 *    INTERNET_SCHEME_UNKNOWN on failure
 *
 */
static INTERNET_SCHEME GetInternetSchemeW(LPCWSTR lpszScheme, DWORD nMaxCmp)
{
    int i;

    TRACE("%s %d\n",debugstr_wn(lpszScheme, nMaxCmp), nMaxCmp);

    if(lpszScheme==NULL)
        return INTERNET_SCHEME_UNKNOWN;

    for (i = 0; i < ARRAY_SIZE(url_schemes); i++)
        if (!wcsnicmp(lpszScheme, url_schemes[i], nMaxCmp))
            return INTERNET_SCHEME_FIRST + i;

    return INTERNET_SCHEME_UNKNOWN;
}

/***********************************************************************
 *           InternetCrackUrlW   (WININET.@)
 *
 * Break up URL into its components
 *
 * RETURNS
 *    TRUE on success
 *    FALSE on failure
 */
BOOL WINAPI InternetCrackUrlW(const WCHAR *lpszUrl, DWORD dwUrlLength, DWORD dwFlags, URL_COMPONENTSW *lpUC)
{
  /*
   * RFC 1808
   * <protocol>:[//<net_loc>][/path][;<params>][?<query>][#<fragment>]
   *
   */
    LPCWSTR lpszParam    = NULL;
    BOOL  found_colon = FALSE;
    LPCWSTR lpszap;
    LPCWSTR lpszcp = NULL, lpszNetLoc;

    TRACE("(%s %u %x %p)\n",
          lpszUrl ? debugstr_wn(lpszUrl, dwUrlLength ? dwUrlLength : lstrlenW(lpszUrl)) : "(null)",
          dwUrlLength, dwFlags, lpUC);

    if (!lpszUrl || !*lpszUrl || !lpUC)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (!dwUrlLength)
        dwUrlLength = lstrlenW(lpszUrl);
    else {
        /* Windows stops at a null, regardless of what dwUrlLength says. */
        dwUrlLength = wcsnlen(lpszUrl, dwUrlLength);
    }

    if (dwFlags & ICU_DECODE)
    {
        WCHAR *url_tmp, *buffer;
        DWORD len = dwUrlLength + 1;
        BOOL ret;

        if (!(url_tmp = heap_strndupW(lpszUrl, dwUrlLength)))
        {
            SetLastError(ERROR_OUTOFMEMORY);
            return FALSE;
        }

        buffer = url_tmp;
        ret = InternetCanonicalizeUrlW(url_tmp, buffer, &len, ICU_DECODE | ICU_NO_ENCODE);
        if (!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
        {
            buffer = heap_alloc(len * sizeof(WCHAR));
            if (!buffer)
            {
                SetLastError(ERROR_OUTOFMEMORY);
                heap_free(url_tmp);
                return FALSE;
            }
            ret = InternetCanonicalizeUrlW(url_tmp, buffer, &len, ICU_DECODE | ICU_NO_ENCODE);
        }
        if (ret)
            ret = InternetCrackUrlW(buffer, len, dwFlags & ~ICU_DECODE, lpUC);

        if (buffer != url_tmp) heap_free(buffer);
        heap_free(url_tmp);
        return ret;
    }
    lpszap = lpszUrl;
    
    /* Determine if the URI is absolute. */
    while (lpszap - lpszUrl < dwUrlLength)
    {
        if (iswalnum(*lpszap) || *lpszap == '+' || *lpszap == '.' || *lpszap == '-')
        {
            lpszap++;
            continue;
        }
        if (*lpszap == ':')
        {
            found_colon = TRUE;
            lpszcp = lpszap;
        }
        else
        {
            lpszcp = lpszUrl; /* Relative url */
        }

        break;
    }

    if(!found_colon){
        SetLastError(ERROR_INTERNET_UNRECOGNIZED_SCHEME);
        return FALSE;
    }

    lpUC->nScheme = INTERNET_SCHEME_UNKNOWN;
    lpUC->nPort = INTERNET_INVALID_PORT_NUMBER;

    /* Parse <params> */
    lpszParam = wmemchr(lpszap, '?', dwUrlLength - (lpszap - lpszUrl));
    if(!lpszParam)
        lpszParam = wmemchr(lpszap, '#', dwUrlLength - (lpszap - lpszUrl));

    if(!set_url_component(&lpUC->lpszExtraInfo, &lpUC->dwExtraInfoLength,
                          lpszParam, lpszParam ? dwUrlLength-(lpszParam-lpszUrl) : 0))
        return FALSE;


    /* Get scheme first. */
    lpUC->nScheme = GetInternetSchemeW(lpszUrl, lpszcp - lpszUrl);
    if(!set_url_component(&lpUC->lpszScheme, &lpUC->dwSchemeLength, lpszUrl, lpszcp - lpszUrl))
        return FALSE;

    /* Eat ':' in protocol. */
    lpszcp++;

    /* double slash indicates the net_loc portion is present */
    if ((lpszcp[0] == '/') && (lpszcp[1] == '/'))
    {
        lpszcp += 2;

        lpszNetLoc = wmemchr(lpszcp, '/', dwUrlLength - (lpszcp - lpszUrl));
        if (lpszParam)
        {
            if (lpszNetLoc)
                lpszNetLoc = min(lpszNetLoc, lpszParam);
            else
                lpszNetLoc = lpszParam;
        }
        else if (!lpszNetLoc)
            lpszNetLoc = lpszcp + dwUrlLength-(lpszcp-lpszUrl);

        /* Parse net-loc */
        if (lpszNetLoc)
        {
            LPCWSTR lpszHost;
            LPCWSTR lpszPort;

            /* [<user>[<:password>]@]<host>[:<port>] */
            /* First find the user and password if they exist */

            lpszHost = wmemchr(lpszcp, '@', dwUrlLength - (lpszcp - lpszUrl));
            if (lpszHost == NULL || lpszHost > lpszNetLoc)
            {
                /* username and password not specified. */
                set_url_component(&lpUC->lpszUserName, &lpUC->dwUserNameLength, NULL, 0);
                set_url_component(&lpUC->lpszPassword, &lpUC->dwPasswordLength, NULL, 0);
            }
            else /* Parse out username and password */
            {
                LPCWSTR lpszUser = lpszcp;
                LPCWSTR lpszPasswd = lpszHost;

                while (lpszcp < lpszHost)
                {
                    if (*lpszcp == ':')
                        lpszPasswd = lpszcp;

                    lpszcp++;
                }

                if(!set_url_component(&lpUC->lpszUserName, &lpUC->dwUserNameLength, lpszUser, lpszPasswd - lpszUser))
                    return FALSE;

                if (lpszPasswd != lpszHost)
                    lpszPasswd++;
                if(!set_url_component(&lpUC->lpszPassword, &lpUC->dwPasswordLength,
                                      lpszPasswd == lpszHost ? NULL : lpszPasswd, lpszHost - lpszPasswd))
                    return FALSE;

                lpszcp++; /* Advance to beginning of host */
            }

            /* Parse <host><:port> */

            lpszHost = lpszcp;
            lpszPort = lpszNetLoc;

            /* special case for res:// URLs: there is no port here, so the host is the
               entire string up to the first '/' */
            if(lpUC->nScheme==INTERNET_SCHEME_RES)
            {
                if(!set_url_component(&lpUC->lpszHostName, &lpUC->dwHostNameLength, lpszHost, lpszPort - lpszHost))
                    return FALSE;
                lpszcp=lpszNetLoc;
            }
            else
            {
                while (lpszcp < lpszNetLoc)
                {
                    if (*lpszcp == ':')
                        lpszPort = lpszcp;

                    lpszcp++;
                }

                /* If the scheme is "file" and the host is just one letter, it's not a host */
                if(lpUC->nScheme==INTERNET_SCHEME_FILE && lpszPort <= lpszHost+1)
                {
                    lpszcp=lpszHost;
                    set_url_component(&lpUC->lpszHostName, &lpUC->dwHostNameLength, NULL, 0);
                }
                else
                {
                    if(!set_url_component(&lpUC->lpszHostName, &lpUC->dwHostNameLength, lpszHost, lpszPort - lpszHost))
                        return FALSE;
                    if (lpszPort != lpszNetLoc)
                        lpUC->nPort = wcstol(++lpszPort, NULL, 10);
                    else switch (lpUC->nScheme)
                    {
                    case INTERNET_SCHEME_HTTP:
                        lpUC->nPort = INTERNET_DEFAULT_HTTP_PORT;
                        break;
                    case INTERNET_SCHEME_HTTPS:
                        lpUC->nPort = INTERNET_DEFAULT_HTTPS_PORT;
                        break;
                    case INTERNET_SCHEME_FTP:
                        lpUC->nPort = INTERNET_DEFAULT_FTP_PORT;
                        break;
                    default:
                        break;
                    }
                }
            }
        }
    }
    else
    {
        set_url_component(&lpUC->lpszUserName, &lpUC->dwUserNameLength, NULL, 0);
        set_url_component(&lpUC->lpszPassword, &lpUC->dwPasswordLength, NULL, 0);
        set_url_component(&lpUC->lpszHostName, &lpUC->dwHostNameLength, NULL, 0);
    }

    /* Here lpszcp points to:
     *
     * <protocol>:[//<net_loc>][/path][;<params>][?<query>][#<fragment>]
     *                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     */
    if (lpszcp != 0 && lpszcp - lpszUrl < dwUrlLength && (!lpszParam || lpszcp <= lpszParam))
    {
        DWORD len;

        /* Only truncate the parameter list if it's already been saved
         * in lpUC->lpszExtraInfo.
         */
        if (lpszParam && lpUC->dwExtraInfoLength && lpUC->lpszExtraInfo)
            len = lpszParam - lpszcp;
        else
        {
            /* Leave the parameter list in lpszUrlPath.  Strip off any trailing
             * newlines if necessary.
             */
            LPWSTR lpsznewline = wmemchr(lpszcp, '\n', dwUrlLength - (lpszcp - lpszUrl));
            if (lpsznewline != NULL)
                len = lpsznewline - lpszcp;
            else
                len = dwUrlLength-(lpszcp-lpszUrl);
        }
        if (lpUC->dwUrlPathLength && lpUC->lpszUrlPath &&
                lpUC->nScheme == INTERNET_SCHEME_FILE)
        {
            WCHAR tmppath[MAX_PATH];
            if (*lpszcp == '/')
            {
                len = MAX_PATH;
                PathCreateFromUrlW(lpszUrl, tmppath, &len, 0);
            }
            else
            {
                WCHAR *iter;
                memcpy(tmppath, lpszcp, len * sizeof(WCHAR));
                tmppath[len] = '\0';

                iter = tmppath;
                while (*iter) {
                    if (*iter == '/')
                        *iter = '\\';
                    ++iter;
                }
            }
            /* if ends in \. or \.. append a backslash */
            if (tmppath[len - 1] == '.' &&
                    (tmppath[len - 2] == '\\' ||
                     (tmppath[len - 2] == '.' && tmppath[len - 3] == '\\')))
            {
                if (len < MAX_PATH - 1)
                {
                    tmppath[len] = '\\';
                    tmppath[len+1] = '\0';
                    ++len;
                }
            }
            if(!set_url_component(&lpUC->lpszUrlPath, &lpUC->dwUrlPathLength, tmppath, len))
                return FALSE;
        }
        else if(!set_url_component(&lpUC->lpszUrlPath, &lpUC->dwUrlPathLength, lpszcp, len))
            return FALSE;
    }
    else
    {
        set_url_component(&lpUC->lpszUrlPath, &lpUC->dwUrlPathLength, lpszcp, 0);
    }

    TRACE("%s: scheme(%s) host(%s) path(%s) extra(%s)\n", debugstr_wn(lpszUrl,dwUrlLength),
             debugstr_wn(lpUC->lpszScheme,lpUC->dwSchemeLength),
             debugstr_wn(lpUC->lpszHostName,lpUC->dwHostNameLength),
             debugstr_wn(lpUC->lpszUrlPath,lpUC->dwUrlPathLength),
             debugstr_wn(lpUC->lpszExtraInfo,lpUC->dwExtraInfoLength));

    return TRUE;
}

/***********************************************************************
 *           InternetAttemptConnect (WININET.@)
 *
 * Attempt to make a connection to the internet
 *
 * RETURNS
 *    ERROR_SUCCESS on success
 *    Error value   on failure
 *
 */
DWORD WINAPI InternetAttemptConnect(DWORD dwReserved)
{
    FIXME("Stub\n");
    return ERROR_SUCCESS;
}


/***********************************************************************
 *           convert_url_canonicalization_flags
 *
 * Helper for InternetCanonicalizeUrl
 *
 * PARAMS
 *     dwFlags [I] Flags suitable for InternetCanonicalizeUrl
 *
 * RETURNS
 *     Flags suitable for UrlCanonicalize
 */
static DWORD convert_url_canonicalization_flags(DWORD dwFlags)
{
    DWORD dwUrlFlags = URL_WININET_COMPATIBILITY | URL_ESCAPE_UNSAFE;

    if (dwFlags & ICU_BROWSER_MODE)        dwUrlFlags |= URL_BROWSER_MODE;
    if (dwFlags & ICU_DECODE)              dwUrlFlags |= URL_UNESCAPE;
    if (dwFlags & ICU_ENCODE_PERCENT)      dwUrlFlags |= URL_ESCAPE_PERCENT;
    if (dwFlags & ICU_ENCODE_SPACES_ONLY)  dwUrlFlags |= URL_ESCAPE_SPACES_ONLY;
    /* Flip this bit to correspond to URL_ESCAPE_UNSAFE */
    if (dwFlags & ICU_NO_ENCODE)           dwUrlFlags ^= URL_ESCAPE_UNSAFE;
    if (dwFlags & ICU_NO_META)             dwUrlFlags |= URL_NO_META;

    return dwUrlFlags;
}

/***********************************************************************
 *           InternetCanonicalizeUrlA (WININET.@)
 *
 * Escape unsafe characters and spaces
 *
 * RETURNS
 *    TRUE on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetCanonicalizeUrlA(LPCSTR lpszUrl, LPSTR lpszBuffer,
	LPDWORD lpdwBufferLength, DWORD dwFlags)
{
    HRESULT hr;

    TRACE("(%s, %p, %p, 0x%08x) buffer length: %d\n", debugstr_a(lpszUrl), lpszBuffer,
        lpdwBufferLength, dwFlags, lpdwBufferLength ? *lpdwBufferLength : -1);

    dwFlags = convert_url_canonicalization_flags(dwFlags);
    hr = UrlCanonicalizeA(lpszUrl, lpszBuffer, lpdwBufferLength, dwFlags);
    if (hr == E_POINTER) SetLastError(ERROR_INSUFFICIENT_BUFFER);
    if (hr == E_INVALIDARG) SetLastError(ERROR_INVALID_PARAMETER);

    return hr == S_OK;
}

/***********************************************************************
 *           InternetCanonicalizeUrlW (WININET.@)
 *
 * Escape unsafe characters and spaces
 *
 * RETURNS
 *    TRUE on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetCanonicalizeUrlW(LPCWSTR lpszUrl, LPWSTR lpszBuffer,
    LPDWORD lpdwBufferLength, DWORD dwFlags)
{
    HRESULT hr;

    TRACE("(%s, %p, %p, 0x%08x) buffer length: %d\n", debugstr_w(lpszUrl), lpszBuffer,
          lpdwBufferLength, dwFlags, lpdwBufferLength ? *lpdwBufferLength : -1);

    dwFlags = convert_url_canonicalization_flags(dwFlags);
    hr = UrlCanonicalizeW(lpszUrl, lpszBuffer, lpdwBufferLength, dwFlags);
    if (hr == E_POINTER) SetLastError(ERROR_INSUFFICIENT_BUFFER);
    if (hr == E_INVALIDARG) SetLastError(ERROR_INVALID_PARAMETER);

    return hr == S_OK;
}

/* #################################################### */

static INTERNET_STATUS_CALLBACK set_status_callback(
    object_header_t *lpwh, INTERNET_STATUS_CALLBACK callback, BOOL unicode)
{
    INTERNET_STATUS_CALLBACK ret;

    if (unicode) lpwh->dwInternalFlags |= INET_CALLBACKW;
    else lpwh->dwInternalFlags &= ~INET_CALLBACKW;

    ret = lpwh->lpfnStatusCB;
    lpwh->lpfnStatusCB = callback;

    return ret;
}

/***********************************************************************
 *           InternetSetStatusCallbackA (WININET.@)
 *
 * Sets up a callback function which is called as progress is made
 * during an operation.
 *
 * RETURNS
 *    Previous callback or NULL 	on success
 *    INTERNET_INVALID_STATUS_CALLBACK  on failure
 *
 */
INTERNET_STATUS_CALLBACK WINAPI InternetSetStatusCallbackA(
	HINTERNET hInternet ,INTERNET_STATUS_CALLBACK lpfnIntCB)
{
    INTERNET_STATUS_CALLBACK retVal;
    object_header_t *lpwh;

    TRACE("%p\n", hInternet);

    if (!(lpwh = get_handle_object(hInternet)))
        return INTERNET_INVALID_STATUS_CALLBACK;

    retVal = set_status_callback(lpwh, lpfnIntCB, FALSE);

    WININET_Release( lpwh );
    return retVal;
}

/***********************************************************************
 *           InternetSetStatusCallbackW (WININET.@)
 *
 * Sets up a callback function which is called as progress is made
 * during an operation.
 *
 * RETURNS
 *    Previous callback or NULL 	on success
 *    INTERNET_INVALID_STATUS_CALLBACK  on failure
 *
 */
INTERNET_STATUS_CALLBACK WINAPI InternetSetStatusCallbackW(
	HINTERNET hInternet ,INTERNET_STATUS_CALLBACK lpfnIntCB)
{
    INTERNET_STATUS_CALLBACK retVal;
    object_header_t *lpwh;

    TRACE("%p\n", hInternet);

    if (!(lpwh = get_handle_object(hInternet)))
        return INTERNET_INVALID_STATUS_CALLBACK;

    retVal = set_status_callback(lpwh, lpfnIntCB, TRUE);

    WININET_Release( lpwh );
    return retVal;
}

/***********************************************************************
 *           InternetSetFilePointer (WININET.@)
 */
DWORD WINAPI InternetSetFilePointer(HINTERNET hFile, LONG lDistanceToMove,
    PVOID pReserved, DWORD dwMoveContext, DWORD_PTR dwContext)
{
    FIXME("(%p %d %p %d %lx): stub\n", hFile, lDistanceToMove, pReserved, dwMoveContext, dwContext);

    SetLastError(ERROR_INTERNET_INVALID_OPERATION);
    return INVALID_SET_FILE_POINTER;
}

/***********************************************************************
 *           InternetWriteFile (WININET.@)
 *
 * Write data to an open internet file
 *
 * RETURNS
 *    TRUE  on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetWriteFile(HINTERNET hFile, LPCVOID lpBuffer,
	DWORD dwNumOfBytesToWrite, LPDWORD lpdwNumOfBytesWritten)
{
    object_header_t *lpwh;
    BOOL res;

    TRACE("(%p %p %d %p)\n", hFile, lpBuffer, dwNumOfBytesToWrite, lpdwNumOfBytesWritten);

    lpwh = get_handle_object( hFile );
    if (!lpwh) {
        WARN("Invalid handle\n");
        SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    if(lpwh->vtbl->WriteFile) {
        res = lpwh->vtbl->WriteFile(lpwh, lpBuffer, dwNumOfBytesToWrite, lpdwNumOfBytesWritten);
    }else {
        WARN("No Writefile method.\n");
        res = ERROR_INVALID_HANDLE;
    }

    WININET_Release( lpwh );

    if(res != ERROR_SUCCESS)
        SetLastError(res);
    return res == ERROR_SUCCESS;
}


/***********************************************************************
 *           InternetReadFile (WININET.@)
 *
 * Read data from an open internet file
 *
 * RETURNS
 *    TRUE  on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetReadFile(HINTERNET hFile, LPVOID lpBuffer,
        DWORD dwNumOfBytesToRead, LPDWORD pdwNumOfBytesRead)
{
    object_header_t *hdr;
    DWORD res = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;

    TRACE("%p %p %d %p\n", hFile, lpBuffer, dwNumOfBytesToRead, pdwNumOfBytesRead);

    hdr = get_handle_object(hFile);
    if (!hdr) {
        INTERNET_SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    if(hdr->vtbl->ReadFile) {
        res = hdr->vtbl->ReadFile(hdr, lpBuffer, dwNumOfBytesToRead, pdwNumOfBytesRead, 0, 0);
        if(res == ERROR_IO_PENDING)
            *pdwNumOfBytesRead = 0;
    }

    WININET_Release(hdr);

    TRACE("-- %s (%u) (bytes read: %d)\n", res == ERROR_SUCCESS ? "TRUE": "FALSE", res,
          pdwNumOfBytesRead ? *pdwNumOfBytesRead : -1);

    SetLastError(res);
    return res == ERROR_SUCCESS;
}

/***********************************************************************
 *           InternetReadFileExA (WININET.@)
 *
 * Read data from an open internet file
 *
 * PARAMS
 *  hFile         [I] Handle returned by InternetOpenUrl or HttpOpenRequest.
 *  lpBuffersOut  [I/O] Buffer.
 *  dwFlags       [I] Flags. See notes.
 *  dwContext     [I] Context for callbacks.
 *
 * RETURNS
 *    TRUE  on success
 *    FALSE on failure
 *
 * NOTES
 *  The parameter dwFlags include zero or more of the following flags:
 *|IRF_ASYNC - Makes the call asynchronous.
 *|IRF_SYNC - Makes the call synchronous.
 *|IRF_USE_CONTEXT - Forces dwContext to be used.
 *|IRF_NO_WAIT - Don't block if the data is not available, just return what is available.
 *
 * However, in testing IRF_USE_CONTEXT seems to have no effect - dwContext isn't used.
 *
 * SEE
 *  InternetOpenUrlA(), HttpOpenRequestA()
 */
BOOL WINAPI InternetReadFileExA(HINTERNET hFile, LPINTERNET_BUFFERSA lpBuffersOut,
	DWORD dwFlags, DWORD_PTR dwContext)
{
    object_header_t *hdr;
    DWORD res = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;

    TRACE("(%p %p 0x%x 0x%lx)\n", hFile, lpBuffersOut, dwFlags, dwContext);

    if (lpBuffersOut->dwStructSize != sizeof(*lpBuffersOut)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    hdr = get_handle_object(hFile);
    if (!hdr) {
        INTERNET_SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    if(hdr->vtbl->ReadFile)
        res = hdr->vtbl->ReadFile(hdr, lpBuffersOut->lpvBuffer, lpBuffersOut->dwBufferLength,
                &lpBuffersOut->dwBufferLength, dwFlags, dwContext);

    WININET_Release(hdr);

    TRACE("-- %s (%u, bytes read: %d)\n", res == ERROR_SUCCESS ? "TRUE": "FALSE",
          res, lpBuffersOut->dwBufferLength);

    if(res != ERROR_SUCCESS)
        SetLastError(res);
    return res == ERROR_SUCCESS;
}

/***********************************************************************
 *           InternetReadFileExW (WININET.@)
 * SEE
 *  InternetReadFileExA()
 */
BOOL WINAPI InternetReadFileExW(HINTERNET hFile, LPINTERNET_BUFFERSW lpBuffer,
	DWORD dwFlags, DWORD_PTR dwContext)
{
    object_header_t *hdr;
    DWORD res = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;

    TRACE("(%p %p 0x%x 0x%lx)\n", hFile, lpBuffer, dwFlags, dwContext);

    if (!lpBuffer || lpBuffer->dwStructSize != sizeof(*lpBuffer)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    hdr = get_handle_object(hFile);
    if (!hdr) {
        INTERNET_SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    if(hdr->vtbl->ReadFile)
        res = hdr->vtbl->ReadFile(hdr, lpBuffer->lpvBuffer, lpBuffer->dwBufferLength, &lpBuffer->dwBufferLength,
                dwFlags, dwContext);

    WININET_Release(hdr);

    TRACE("-- %s (%u, bytes read: %d)\n", res == ERROR_SUCCESS ? "TRUE": "FALSE",
          res, lpBuffer->dwBufferLength);

    if(res != ERROR_SUCCESS)
        SetLastError(res);
    return res == ERROR_SUCCESS;
}

static IP_ADAPTER_ADDRESSES *get_adapters(void)
{
    ULONG err, size = 1024, flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
                                    GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME;
    IP_ADAPTER_ADDRESSES *tmp, *ret;

    if (!(ret = heap_alloc( size ))) return NULL;
    err = GetAdaptersAddresses( AF_UNSPEC, flags, NULL, ret, &size );
    while (err == ERROR_BUFFER_OVERFLOW)
    {
        if (!(tmp = heap_realloc( ret, size ))) break;
        ret = tmp;
        err = GetAdaptersAddresses( AF_UNSPEC, flags, NULL, ret, &size );
    }
    if (err == ERROR_SUCCESS) return ret;
    heap_free( ret );
    return NULL;
}

static WCHAR *detect_proxy_autoconfig_url_dhcp(void)
{
    IP_ADAPTER_ADDRESSES *adapters, *ptr;
    DHCPCAPI_PARAMS_ARRAY send_params, recv_params;
    DHCPCAPI_PARAMS param;
    WCHAR name[MAX_ADAPTER_NAME_LENGTH + 1], *ret = NULL;
    DWORD err, size;
    BYTE *tmp, *buf = NULL;

    if (!(adapters = get_adapters())) return NULL;

    memset( &send_params, 0, sizeof(send_params) );
    memset( &param, 0, sizeof(param) );
    param.OptionId = OPTION_MSFT_IE_PROXY;
    recv_params.nParams = 1;
    recv_params.Params  = &param;

    for (ptr = adapters; ptr; ptr = ptr->Next)
    {
        MultiByteToWideChar( CP_ACP, 0, ptr->AdapterName, -1, name, ARRAY_SIZE(name) );
        TRACE( "adapter '%s' type %u dhcpv4 enabled %d\n", wine_dbgstr_w(name), ptr->IfType, ptr->Dhcpv4Enabled );

        if (ptr->IfType == IF_TYPE_SOFTWARE_LOOPBACK) continue;
        /* FIXME: also skip adapters where DHCP is disabled */

        size = 256;
        if (!(buf = heap_alloc( size ))) goto done;
        err = DhcpRequestParams( DHCPCAPI_REQUEST_SYNCHRONOUS, NULL, name, NULL, send_params, recv_params,
                                 buf, &size, NULL );
        while (err == ERROR_MORE_DATA)
        {
            if (!(tmp = heap_realloc( buf, size ))) goto done;
            buf = tmp;
            err = DhcpRequestParams( DHCPCAPI_REQUEST_SYNCHRONOUS, NULL, name, NULL, send_params, recv_params,
                                     buf, &size, NULL );
        }
        if (err == ERROR_SUCCESS && param.nBytesData)
        {
            int len = MultiByteToWideChar( CP_ACP, 0, (const char *)param.Data, param.nBytesData, NULL, 0 );
            if ((ret = heap_alloc( (len + 1) * sizeof(WCHAR) )))
            {
                MultiByteToWideChar( CP_ACP, 0,  (const char *)param.Data, param.nBytesData, ret, len );
                ret[len] = 0;
            }
            TRACE("returning %s\n", debugstr_w(ret));
            break;
        }
    }

done:
    heap_free( buf );
    heap_free( adapters );
    return ret;
}

static char *get_computer_name( COMPUTER_NAME_FORMAT format )
{
    char *ret;
    DWORD size = 0;

    GetComputerNameExA( format, NULL, &size );
    if (GetLastError() != ERROR_MORE_DATA) return NULL;
    if (!(ret = heap_alloc( size ))) return NULL;
    if (!GetComputerNameExA( format, ret, &size ))
    {
        heap_free( ret );
        return NULL;
    }
    return ret;
}

static BOOL is_domain_suffix( const char *domain, const char *suffix )
{
    int len_domain = strlen( domain ), len_suffix = strlen( suffix );

    if (len_suffix > len_domain) return FALSE;
    if (!stricmp( domain + len_domain - len_suffix, suffix )) return TRUE;
    return FALSE;
}

static int reverse_lookup( const struct addrinfo *ai, char *hostname, size_t len )
{
    return getnameinfo( ai->ai_addr, ai->ai_addrlen, hostname, len, NULL, 0, 0 );
}

static WCHAR *build_wpad_url( const char *hostname, const struct addrinfo *ai )
{
    char name[NI_MAXHOST];
    WCHAR *ret, *p;
    int len;

    while (ai && ai->ai_family != AF_INET && ai->ai_family != AF_INET6) ai = ai->ai_next;
    if (!ai) return NULL;

    if (!reverse_lookup( ai, name, sizeof(name) )) hostname = name;

    len = lstrlenW( L"http://" ) + strlen( hostname ) + lstrlenW( L"/wpad.dat" );
    if (!(ret = p = GlobalAlloc( 0, (len + 1) * sizeof(WCHAR) ))) return NULL;
    lstrcpyW( p, L"http://" );
    p += lstrlenW( L"http://" );
    while (*hostname) { *p++ = *hostname++; }
    lstrcpyW( p, L"/wpad.dat" );
    return ret;
}

static WCHAR *detect_proxy_autoconfig_url_dns(void)
{
    char *fqdn, *domain, *p;
    WCHAR *ret;

    if (!(fqdn = get_computer_name( ComputerNamePhysicalDnsFullyQualified ))) return NULL;
    if (!(domain = get_computer_name( ComputerNamePhysicalDnsDomain )))
    {
        heap_free( fqdn );
        return NULL;
    }
    p = fqdn;
    while ((p = strchr( p, '.' )) && is_domain_suffix( p + 1, domain ))
    {
        char *name;
        struct addrinfo *ai;
        int res;

        if (!(name = heap_alloc( sizeof("wpad") + strlen(p) )))
        {
            heap_free( fqdn );
            heap_free( domain );
            return NULL;
        }
        strcpy( name, "wpad" );
        strcat( name, p );
        res = getaddrinfo( name, NULL, NULL, &ai );
        if (!res)
        {
            ret = build_wpad_url( name, ai );
            freeaddrinfo( ai );
            if (ret)
            {
                TRACE("returning %s\n", debugstr_w(ret));
                heap_free( name );
                break;
            }
        }
       heap_free( name );
       p++;
    }
    heap_free( domain );
    heap_free( fqdn );
    return ret;
}

static WCHAR *get_proxy_autoconfig_url(void)
{
    WCHAR *ret = detect_proxy_autoconfig_url_dhcp();
    if (!ret) ret = detect_proxy_autoconfig_url_dns();
    return ret;
}

static DWORD query_global_option(DWORD option, void *buffer, DWORD *size, BOOL unicode)
{
    /* FIXME: This function currently handles more options than it should. Options requiring
     * proper handles should be moved to proper functions */
    switch(option) {
    case INTERNET_OPTION_HTTP_VERSION:
        if (*size < sizeof(HTTP_VERSION_INFO))
            return ERROR_INSUFFICIENT_BUFFER;

        /*
         * Presently hardcoded to 1.1
         */
        ((HTTP_VERSION_INFO*)buffer)->dwMajorVersion = 1;
        ((HTTP_VERSION_INFO*)buffer)->dwMinorVersion = 1;
        *size = sizeof(HTTP_VERSION_INFO);

        return ERROR_SUCCESS;

    case INTERNET_OPTION_CONNECTED_STATE:
        FIXME("INTERNET_OPTION_CONNECTED_STATE: semi-stub\n");

        if (*size < sizeof(ULONG))
            return ERROR_INSUFFICIENT_BUFFER;

        *(ULONG*)buffer = INTERNET_STATE_CONNECTED;
        *size = sizeof(ULONG);

        return ERROR_SUCCESS;

    case INTERNET_OPTION_PROXY: {
        appinfo_t ai;
        BOOL ret;

        TRACE("Getting global proxy info\n");
        memset(&ai, 0, sizeof(appinfo_t));
        INTERNET_ConfigureProxy(&ai);

        ret = APPINFO_QueryOption(&ai.hdr, INTERNET_OPTION_PROXY, buffer, size, unicode); /* FIXME */
        APPINFO_Destroy(&ai.hdr);
        return ret;
    }

    case INTERNET_OPTION_MAX_CONNS_PER_SERVER:
        TRACE("INTERNET_OPTION_MAX_CONNS_PER_SERVER\n");

        if (*size < sizeof(ULONG))
            return ERROR_INSUFFICIENT_BUFFER;

        *(ULONG*)buffer = max_conns;
        *size = sizeof(ULONG);

        return ERROR_SUCCESS;

    case INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER:
            TRACE("INTERNET_OPTION_MAX_CONNS_1_0_SERVER\n");

            if (*size < sizeof(ULONG))
                return ERROR_INSUFFICIENT_BUFFER;

            *(ULONG*)buffer = max_1_0_conns;
            *size = sizeof(ULONG);

            return ERROR_SUCCESS;

    case INTERNET_OPTION_SECURITY_FLAGS:
        FIXME("INTERNET_OPTION_SECURITY_FLAGS: Stub\n");
        return ERROR_SUCCESS;

    case INTERNET_OPTION_VERSION: {
        static const INTERNET_VERSION_INFO info = { 1, 2 };

        TRACE("INTERNET_OPTION_VERSION\n");

        if (*size < sizeof(INTERNET_VERSION_INFO))
            return ERROR_INSUFFICIENT_BUFFER;

        memcpy(buffer, &info, sizeof(info));
        *size = sizeof(info);

        return ERROR_SUCCESS;
    }

    case INTERNET_OPTION_PER_CONNECTION_OPTION: {
        WCHAR *url;
        INTERNET_PER_CONN_OPTION_LISTW *con = buffer;
        INTERNET_PER_CONN_OPTION_LISTA *conA = buffer;
        DWORD res = ERROR_SUCCESS, i;
        proxyinfo_t pi;
        LONG ret;

        TRACE("Getting global proxy info\n");
        if((ret = INTERNET_LoadProxySettings(&pi)))
            return ret;

        FIXME("INTERNET_OPTION_PER_CONNECTION_OPTION stub\n");

        if (*size < sizeof(INTERNET_PER_CONN_OPTION_LISTW)) {
            FreeProxyInfo(&pi);
            return ERROR_INSUFFICIENT_BUFFER;
        }

        url = get_proxy_autoconfig_url();

        for (i = 0; i < con->dwOptionCount; i++) {
            INTERNET_PER_CONN_OPTIONW *optionW = con->pOptions + i;
            INTERNET_PER_CONN_OPTIONA *optionA = conA->pOptions + i;

            switch (optionW->dwOption) {
            case INTERNET_PER_CONN_FLAGS:
                if(pi.proxyEnabled)
                    optionW->Value.dwValue = PROXY_TYPE_PROXY;
                else
                    optionW->Value.dwValue = PROXY_TYPE_DIRECT;
                if (url)
                    /* native includes PROXY_TYPE_DIRECT even if PROXY_TYPE_PROXY is set */
                    optionW->Value.dwValue |= PROXY_TYPE_DIRECT|PROXY_TYPE_AUTO_PROXY_URL;
                break;

            case INTERNET_PER_CONN_PROXY_SERVER:
                if (unicode)
                    optionW->Value.pszValue = heap_strdupW(pi.proxy);
                else
                    optionA->Value.pszValue = heap_strdupWtoA(pi.proxy);
                break;

            case INTERNET_PER_CONN_PROXY_BYPASS:
                if (unicode)
                    optionW->Value.pszValue = heap_strdupW(pi.proxyBypass);
                else
                    optionA->Value.pszValue = heap_strdupWtoA(pi.proxyBypass);
                break;

            case INTERNET_PER_CONN_AUTOCONFIG_URL:
                if (!url)
                    optionW->Value.pszValue = NULL;
                else if (unicode)
                    optionW->Value.pszValue = heap_strdupW(url);
                else
                    optionA->Value.pszValue = heap_strdupWtoA(url);
                break;

            case INTERNET_PER_CONN_AUTODISCOVERY_FLAGS:
                optionW->Value.dwValue = AUTO_PROXY_FLAG_ALWAYS_DETECT;
                break;

            case INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL:
            case INTERNET_PER_CONN_AUTOCONFIG_RELOAD_DELAY_MINS:
            case INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_TIME:
            case INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL:
                FIXME("Unhandled dwOption %d\n", optionW->dwOption);
                memset(&optionW->Value, 0, sizeof(optionW->Value));
                break;

            default:
                FIXME("Unknown dwOption %d\n", optionW->dwOption);
                res = ERROR_INVALID_PARAMETER;
                break;
            }
        }
        heap_free(url);
        FreeProxyInfo(&pi);

        return res;
    }
    case INTERNET_OPTION_REQUEST_FLAGS:
    case INTERNET_OPTION_USER_AGENT:
        *size = 0;
        return ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
    case INTERNET_OPTION_POLICY:
        return ERROR_INVALID_PARAMETER;
    case INTERNET_OPTION_CONNECT_TIMEOUT:
        TRACE("INTERNET_OPTION_CONNECT_TIMEOUT\n");

        if (*size < sizeof(ULONG))
            return ERROR_INSUFFICIENT_BUFFER;

        *(ULONG*)buffer = connect_timeout;
        *size = sizeof(ULONG);

        return ERROR_SUCCESS;
    }

    FIXME("Stub for %d\n", option);
    return ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
}

DWORD INET_QueryOption(object_header_t *hdr, DWORD option, void *buffer, DWORD *size, BOOL unicode)
{
    switch(option) {
    case INTERNET_OPTION_CONTEXT_VALUE:
        if (!size)
            return ERROR_INVALID_PARAMETER;

        if (*size < sizeof(DWORD_PTR)) {
            *size = sizeof(DWORD_PTR);
            return ERROR_INSUFFICIENT_BUFFER;
        }
        if (!buffer)
            return ERROR_INVALID_PARAMETER;

        *(DWORD_PTR *)buffer = hdr->dwContext;
        *size = sizeof(DWORD_PTR);
        return ERROR_SUCCESS;

    case INTERNET_OPTION_REQUEST_FLAGS:
        WARN("INTERNET_OPTION_REQUEST_FLAGS\n");
        *size = sizeof(DWORD);
        return ERROR_INTERNET_INCORRECT_HANDLE_TYPE;

    case INTERNET_OPTION_MAX_CONNS_PER_SERVER:
    case INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER:
        WARN("Called on global option %u\n", option);
        return ERROR_INTERNET_INVALID_OPERATION;
    }

    /* FIXME: we shouldn't call it here */
    return query_global_option(option, buffer, size, unicode);
}

/***********************************************************************
 *           InternetQueryOptionW (WININET.@)
 *
 * Queries an options on the specified handle
 *
 * RETURNS
 *    TRUE  on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetQueryOptionW(HINTERNET hInternet, DWORD dwOption,
                                 LPVOID lpBuffer, LPDWORD lpdwBufferLength)
{
    object_header_t *hdr;
    DWORD res = ERROR_INVALID_HANDLE;

    TRACE("%p %d %p %p\n", hInternet, dwOption, lpBuffer, lpdwBufferLength);

    if(hInternet) {
        hdr = get_handle_object(hInternet);
        if (hdr) {
            res = hdr->vtbl->QueryOption(hdr, dwOption, lpBuffer, lpdwBufferLength, TRUE);
            WININET_Release(hdr);
        }
    }else {
        res = query_global_option(dwOption, lpBuffer, lpdwBufferLength, TRUE);
    }

    if(res != ERROR_SUCCESS)
        SetLastError(res);
    return res == ERROR_SUCCESS;
}

/***********************************************************************
 *           InternetQueryOptionA (WININET.@)
 *
 * Queries an options on the specified handle
 *
 * RETURNS
 *    TRUE  on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetQueryOptionA(HINTERNET hInternet, DWORD dwOption,
                                 LPVOID lpBuffer, LPDWORD lpdwBufferLength)
{
    object_header_t *hdr;
    DWORD res = ERROR_INVALID_HANDLE;

    TRACE("%p %d %p %p\n", hInternet, dwOption, lpBuffer, lpdwBufferLength);

    if(hInternet) {
        hdr = get_handle_object(hInternet);
        if (hdr) {
            res = hdr->vtbl->QueryOption(hdr, dwOption, lpBuffer, lpdwBufferLength, FALSE);
            WININET_Release(hdr);
        }
    }else {
        res = query_global_option(dwOption, lpBuffer, lpdwBufferLength, FALSE);
    }

    if(res != ERROR_SUCCESS)
        SetLastError(res);
    return res == ERROR_SUCCESS;
}

DWORD INET_SetOption(object_header_t *hdr, DWORD option, void *buf, DWORD size)
{
    switch(option) {
    case INTERNET_OPTION_SETTINGS_CHANGED:
        FIXME("INTERNETOPTION_SETTINGS_CHANGED semi-stub\n");
        collect_connections(COLLECT_CONNECTIONS);
        return ERROR_SUCCESS;
    case INTERNET_OPTION_CALLBACK:
        WARN("Not settable option %u\n", option);
        return ERROR_INTERNET_OPTION_NOT_SETTABLE;
    case INTERNET_OPTION_MAX_CONNS_PER_SERVER:
    case INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER:
        WARN("Called on global option %u\n", option);
        return ERROR_INTERNET_INVALID_OPERATION;
    case INTERNET_OPTION_REFRESH:
        return ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
    }

    return ERROR_INTERNET_INVALID_OPTION;
}

static DWORD set_global_option(DWORD option, void *buf, DWORD size)
{
    switch(option) {
    case INTERNET_OPTION_CALLBACK:
        WARN("Not global option %u\n", option);
        return ERROR_INTERNET_INCORRECT_HANDLE_TYPE;

    case INTERNET_OPTION_MAX_CONNS_PER_SERVER:
        TRACE("INTERNET_OPTION_MAX_CONNS_PER_SERVER\n");

        if(size != sizeof(max_conns))
            return ERROR_INTERNET_BAD_OPTION_LENGTH;
        if(!*(ULONG*)buf)
            return ERROR_BAD_ARGUMENTS;

        max_conns = *(ULONG*)buf;
        return ERROR_SUCCESS;

    case INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER:
        TRACE("INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER\n");

        if(size != sizeof(max_1_0_conns))
            return ERROR_INTERNET_BAD_OPTION_LENGTH;
        if(!*(ULONG*)buf)
            return ERROR_BAD_ARGUMENTS;

        max_1_0_conns = *(ULONG*)buf;
        return ERROR_SUCCESS;

    case INTERNET_OPTION_CONNECT_TIMEOUT:
        TRACE("INTERNET_OPTION_CONNECT_TIMEOUT\n");

        if(size != sizeof(connect_timeout))
            return ERROR_INTERNET_BAD_OPTION_LENGTH;
        if(!*(ULONG*)buf)
            return ERROR_BAD_ARGUMENTS;

        connect_timeout = *(ULONG*)buf;
        return ERROR_SUCCESS;

    case INTERNET_OPTION_SUPPRESS_BEHAVIOR:
        FIXME("INTERNET_OPTION_SUPPRESS_BEHAVIOR stub\n");

        if(size != sizeof(ULONG))
            return ERROR_INTERNET_BAD_OPTION_LENGTH;

        FIXME("%08x\n", *(ULONG*)buf);
        return ERROR_SUCCESS;
    }

    return INET_SetOption(NULL, option, buf, size);
}

/***********************************************************************
 *           InternetSetOptionW (WININET.@)
 *
 * Sets an options on the specified handle
 *
 * RETURNS
 *    TRUE  on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetSetOptionW(HINTERNET hInternet, DWORD dwOption,
                           LPVOID lpBuffer, DWORD dwBufferLength)
{
    object_header_t *lpwhh;
    BOOL ret = TRUE;
    DWORD res;

    TRACE("(%p %d %p %d)\n", hInternet, dwOption, lpBuffer, dwBufferLength);

    lpwhh = (object_header_t*) get_handle_object( hInternet );
    if(lpwhh)
        res = lpwhh->vtbl->SetOption(lpwhh, dwOption, lpBuffer, dwBufferLength);
    else
        res = set_global_option(dwOption, lpBuffer, dwBufferLength);

    if(res != ERROR_INTERNET_INVALID_OPTION) {
        if(lpwhh)
            WININET_Release(lpwhh);

        if(res != ERROR_SUCCESS)
            SetLastError(res);

        return res == ERROR_SUCCESS;
    }

    switch (dwOption)
    {
    case INTERNET_OPTION_HTTP_VERSION:
      {
        HTTP_VERSION_INFO* pVersion=(HTTP_VERSION_INFO*)lpBuffer;
        FIXME("Option INTERNET_OPTION_HTTP_VERSION(%d,%d): STUB\n",pVersion->dwMajorVersion,pVersion->dwMinorVersion);
      }
      break;
    case INTERNET_OPTION_ERROR_MASK:
      {
        if(!lpwhh) {
            SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
            return FALSE;
        } else if(*(ULONG*)lpBuffer & (~(INTERNET_ERROR_MASK_INSERT_CDROM|
                        INTERNET_ERROR_MASK_COMBINED_SEC_CERT|
                        INTERNET_ERROR_MASK_LOGIN_FAILURE_DISPLAY_ENTITY_BODY))) {
            SetLastError(ERROR_INVALID_PARAMETER);
            ret = FALSE;
        } else if(dwBufferLength != sizeof(ULONG)) {
            SetLastError(ERROR_INTERNET_BAD_OPTION_LENGTH);
            ret = FALSE;
        } else
            TRACE("INTERNET_OPTION_ERROR_MASK: %x\n", *(ULONG*)lpBuffer);
            lpwhh->ErrorMask = *(ULONG*)lpBuffer;
      }
      break;
    case INTERNET_OPTION_PROXY:
    {
        INTERNET_PROXY_INFOW *info = lpBuffer;

        if (!lpBuffer || dwBufferLength < sizeof(INTERNET_PROXY_INFOW))
        {
            SetLastError(ERROR_INVALID_PARAMETER);
            return FALSE;
        }
        if (!hInternet)
        {
            EnterCriticalSection( &WININET_cs );
            free_global_proxy();
            global_proxy = heap_alloc( sizeof(proxyinfo_t) );
            if (global_proxy)
            {
                if (info->dwAccessType == INTERNET_OPEN_TYPE_PROXY)
                {
                    global_proxy->proxyEnabled = 1;
                    global_proxy->proxy = heap_strdupW( info->lpszProxy );
                    global_proxy->proxyBypass = heap_strdupW( info->lpszProxyBypass );
                }
                else
                {
                    global_proxy->proxyEnabled = 0;
                    global_proxy->proxy = global_proxy->proxyBypass = NULL;
                }
            }
            LeaveCriticalSection( &WININET_cs );
        }
        else
        {
            /* In general, each type of object should handle
             * INTERNET_OPTION_PROXY directly.  This FIXME ensures it doesn't
             * get silently dropped.
             */
            FIXME("INTERNET_OPTION_PROXY unimplemented\n");
            SetLastError(ERROR_INTERNET_INVALID_OPTION);
            ret = FALSE;
        }
        break;
    }
    case INTERNET_OPTION_CODEPAGE:
      {
        ULONG codepage = *(ULONG *)lpBuffer;
        FIXME("Option INTERNET_OPTION_CODEPAGE (%d): STUB\n", codepage);
      }
      break;
    case INTERNET_OPTION_REQUEST_PRIORITY:
      {
        ULONG priority = *(ULONG *)lpBuffer;
        FIXME("Option INTERNET_OPTION_REQUEST_PRIORITY (%d): STUB\n", priority);
      }
      break;
    case INTERNET_OPTION_CONNECT_TIMEOUT:
      {
        ULONG connecttimeout = *(ULONG *)lpBuffer;
        FIXME("Option INTERNET_OPTION_CONNECT_TIMEOUT (%d): STUB\n", connecttimeout);
      }
      break;
    case INTERNET_OPTION_DATA_RECEIVE_TIMEOUT:
      {
        ULONG receivetimeout = *(ULONG *)lpBuffer;
        FIXME("Option INTERNET_OPTION_DATA_RECEIVE_TIMEOUT (%d): STUB\n", receivetimeout);
      }
      break;
    case INTERNET_OPTION_RESET_URLCACHE_SESSION:
        FIXME("Option INTERNET_OPTION_RESET_URLCACHE_SESSION: STUB\n");
        break;
    case INTERNET_OPTION_END_BROWSER_SESSION:
        FIXME("Option INTERNET_OPTION_END_BROWSER_SESSION: semi-stub\n");
        free_cookie();
        free_authorization_cache();
        break;
    case INTERNET_OPTION_CONNECTED_STATE:
        FIXME("Option INTERNET_OPTION_CONNECTED_STATE: STUB\n");
        break;
    case INTERNET_OPTION_DISABLE_PASSPORT_AUTH:
	TRACE("Option INTERNET_OPTION_DISABLE_PASSPORT_AUTH: harmless stub, since not enabled\n");
	break;
    case INTERNET_OPTION_IGNORE_OFFLINE:
        FIXME("Option INTERNET_OPTION_IGNORE_OFFLINE: STUB\n");
        break;
    case INTERNET_OPTION_SEND_TIMEOUT:
    case INTERNET_OPTION_RECEIVE_TIMEOUT:
    case INTERNET_OPTION_DATA_SEND_TIMEOUT:
    {
        ULONG timeout = *(ULONG *)lpBuffer;
        FIXME("INTERNET_OPTION_SEND/RECEIVE_TIMEOUT/DATA_SEND_TIMEOUT %d\n", timeout);
        break;
    }
    case INTERNET_OPTION_CONNECT_RETRIES:
    {
        ULONG retries = *(ULONG *)lpBuffer;
        FIXME("INTERNET_OPTION_CONNECT_RETRIES %d\n", retries);
        break;
    }
    case INTERNET_OPTION_CONTEXT_VALUE:
    {
        if (!lpwhh)
        {
            SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
            return FALSE;
        }
        if (!lpBuffer || dwBufferLength != sizeof(DWORD_PTR))
        {
            SetLastError(ERROR_INVALID_PARAMETER);
            ret = FALSE;
        }
        else
            lpwhh->dwContext = *(DWORD_PTR *)lpBuffer;
        break;
    }
    case INTERNET_OPTION_SECURITY_FLAGS:
	 FIXME("Option INTERNET_OPTION_SECURITY_FLAGS; STUB\n");
	 break;
    case INTERNET_OPTION_DISABLE_AUTODIAL:
	 FIXME("Option INTERNET_OPTION_DISABLE_AUTODIAL; STUB\n");
	 break;
    case INTERNET_OPTION_HTTP_DECODING:
        if (!lpwhh)
        {
            SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
            return FALSE;
        }
        if (!lpBuffer || dwBufferLength != sizeof(BOOL))
        {
            SetLastError(ERROR_INVALID_PARAMETER);
            ret = FALSE;
        }
        else
            lpwhh->decoding = *(BOOL *)lpBuffer;
        break;
    case INTERNET_OPTION_COOKIES_3RD_PARTY:
        FIXME("INTERNET_OPTION_COOKIES_3RD_PARTY; STUB\n");
        SetLastError(ERROR_INTERNET_INVALID_OPTION);
        ret = FALSE;
        break;
    case INTERNET_OPTION_SEND_UTF8_SERVERNAME_TO_PROXY:
        FIXME("INTERNET_OPTION_SEND_UTF8_SERVERNAME_TO_PROXY; STUB\n");
        SetLastError(ERROR_INTERNET_INVALID_OPTION);
        ret = FALSE;
        break;
    case INTERNET_OPTION_CODEPAGE_PATH:
        FIXME("INTERNET_OPTION_CODEPAGE_PATH; STUB\n");
        SetLastError(ERROR_INTERNET_INVALID_OPTION);
        ret = FALSE;
        break;
    case INTERNET_OPTION_CODEPAGE_EXTRA:
        FIXME("INTERNET_OPTION_CODEPAGE_EXTRA; STUB\n");
        SetLastError(ERROR_INTERNET_INVALID_OPTION);
        ret = FALSE;
        break;
    case INTERNET_OPTION_IDN:
        FIXME("INTERNET_OPTION_IDN; STUB\n");
        SetLastError(ERROR_INTERNET_INVALID_OPTION);
        ret = FALSE;
        break;
    case INTERNET_OPTION_POLICY:
        SetLastError(ERROR_INVALID_PARAMETER);
        ret = FALSE;
        break;
    case INTERNET_OPTION_PER_CONNECTION_OPTION: {
        INTERNET_PER_CONN_OPTION_LISTW *con = lpBuffer;
        LONG res;
        unsigned int i;
        proxyinfo_t pi;

        if (INTERNET_LoadProxySettings(&pi)) return FALSE;

        for (i = 0; i < con->dwOptionCount; i++) {
            INTERNET_PER_CONN_OPTIONW *option = con->pOptions + i;

            switch (option->dwOption) {
            case INTERNET_PER_CONN_PROXY_SERVER:
                heap_free(pi.proxy);
                pi.proxy = heap_strdupW(option->Value.pszValue);
                break;

            case INTERNET_PER_CONN_FLAGS:
                if(option->Value.dwValue & PROXY_TYPE_PROXY)
                    pi.proxyEnabled = 1;
                else
                {
                    if(option->Value.dwValue != PROXY_TYPE_DIRECT)
                        FIXME("Unhandled flags: 0x%x\n", option->Value.dwValue);
                    pi.proxyEnabled = 0;
                }
                break;

            case INTERNET_PER_CONN_PROXY_BYPASS:
                heap_free(pi.proxyBypass);
                pi.proxyBypass = heap_strdupW(option->Value.pszValue);
                break;

            case INTERNET_PER_CONN_AUTOCONFIG_URL:
            case INTERNET_PER_CONN_AUTODISCOVERY_FLAGS:
            case INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL:
            case INTERNET_PER_CONN_AUTOCONFIG_RELOAD_DELAY_MINS:
            case INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_TIME:
            case INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL:
                FIXME("Unhandled dwOption %d\n", option->dwOption);
                break;

            default:
                FIXME("Unknown dwOption %d\n", option->dwOption);
                SetLastError(ERROR_INVALID_PARAMETER);
                break;
            }
        }

        if ((res = INTERNET_SaveProxySettings(&pi)))
            SetLastError(res);

        FreeProxyInfo(&pi);

        ret = (res == ERROR_SUCCESS);
        break;
        }
    default:
        FIXME("Option %d STUB\n",dwOption);
        SetLastError(ERROR_INTERNET_INVALID_OPTION);
        ret = FALSE;
        break;
    }

    if(lpwhh)
        WININET_Release( lpwhh );

    return ret;
}


/***********************************************************************
 *           InternetSetOptionA (WININET.@)
 *
 * Sets an options on the specified handle.
 *
 * RETURNS
 *    TRUE  on success
 *    FALSE on failure
 *
 */
BOOL WINAPI InternetSetOptionA(HINTERNET hInternet, DWORD dwOption,
                           LPVOID lpBuffer, DWORD dwBufferLength)
{
    LPVOID wbuffer;
    DWORD wlen;
    BOOL r;

    switch( dwOption )
    {
    case INTERNET_OPTION_PROXY:
        {
        LPINTERNET_PROXY_INFOA pi = (LPINTERNET_PROXY_INFOA) lpBuffer;
        LPINTERNET_PROXY_INFOW piw;
        DWORD proxlen, prbylen;
        LPWSTR prox, prby;

        proxlen = MultiByteToWideChar( CP_ACP, 0, pi->lpszProxy, -1, NULL, 0);
        prbylen= MultiByteToWideChar( CP_ACP, 0, pi->lpszProxyBypass, -1, NULL, 0);
        wlen = sizeof(*piw) + proxlen + prbylen;
        wbuffer = heap_alloc(wlen*sizeof(WCHAR) );
        piw = (LPINTERNET_PROXY_INFOW) wbuffer;
        piw->dwAccessType = pi->dwAccessType;
        prox = (LPWSTR) &piw[1];
        prby = &prox[proxlen+1];
        MultiByteToWideChar( CP_ACP, 0, pi->lpszProxy, -1, prox, proxlen);
        MultiByteToWideChar( CP_ACP, 0, pi->lpszProxyBypass, -1, prby, prbylen);
        piw->lpszProxy = prox;
        piw->lpszProxyBypass = prby;
        }
        break;
    case INTERNET_OPTION_USER_AGENT:
    case INTERNET_OPTION_USERNAME:
    case INTERNET_OPTION_PASSWORD:
    case INTERNET_OPTION_PROXY_USERNAME:
    case INTERNET_OPTION_PROXY_PASSWORD:
        wlen = MultiByteToWideChar( CP_ACP, 0, lpBuffer, -1, NULL, 0 );
        if (!(wbuffer = heap_alloc( wlen * sizeof(WCHAR) ))) return ERROR_OUTOFMEMORY;
        MultiByteToWideChar( CP_ACP, 0, lpBuffer, -1, wbuffer, wlen );
        break;
    case INTERNET_OPTION_PER_CONNECTION_OPTION: {
        unsigned int i;
        INTERNET_PER_CONN_OPTION_LISTW *listW;
        INTERNET_PER_CONN_OPTION_LISTA *listA = lpBuffer;
        wlen = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
        wbuffer = heap_alloc(wlen);
        listW = wbuffer;

        listW->dwSize = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
        if (listA->pszConnection)
        {
            wlen = MultiByteToWideChar( CP_ACP, 0, listA->pszConnection, -1, NULL, 0 );
            listW->pszConnection = heap_alloc(wlen*sizeof(WCHAR));
            MultiByteToWideChar( CP_ACP, 0, listA->pszConnection, -1, listW->pszConnection, wlen );
        }
        else
            listW->pszConnection = NULL;
        listW->dwOptionCount = listA->dwOptionCount;
        listW->dwOptionError = listA->dwOptionError;
        listW->pOptions = heap_alloc(sizeof(INTERNET_PER_CONN_OPTIONW) * listA->dwOptionCount);

        for (i = 0; i < listA->dwOptionCount; ++i) {
            INTERNET_PER_CONN_OPTIONA *optA = listA->pOptions + i;
            INTERNET_PER_CONN_OPTIONW *optW = listW->pOptions + i;

            optW->dwOption = optA->dwOption;

            switch (optA->dwOption) {
            case INTERNET_PER_CONN_AUTOCONFIG_URL:
            case INTERNET_PER_CONN_PROXY_BYPASS:
            case INTERNET_PER_CONN_PROXY_SERVER:
            case INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL:
            case INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL:
                if (optA->Value.pszValue)
                {
                    wlen = MultiByteToWideChar( CP_ACP, 0, optA->Value.pszValue, -1, NULL, 0 );
                    optW->Value.pszValue = heap_alloc(wlen*sizeof(WCHAR));
                    MultiByteToWideChar( CP_ACP, 0, optA->Value.pszValue, -1, optW->Value.pszValue, wlen );
                }
                else
                    optW->Value.pszValue = NULL;
                break;
            case INTERNET_PER_CONN_AUTODISCOVERY_FLAGS:
            case INTERNET_PER_CONN_FLAGS:
            case INTERNET_PER_CONN_AUTOCONFIG_RELOAD_DELAY_MINS:
                optW->Value.dwValue = optA->Value.dwValue;
                break;
            case INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_TIME:
                optW->Value.ftValue = optA->Value.ftValue;
                break;
            default:
                WARN("Unknown PER_CONN dwOption: %d, guessing at conversion to Wide\n", optA->dwOption);
                optW->Value.dwValue = optA->Value.dwValue;
                break;
            }
        }
        }
        break;
    default:
        wbuffer = lpBuffer;
        wlen = dwBufferLength;
    }

    r = InternetSetOptionW(hInternet,dwOption, wbuffer, wlen);

    if( lpBuffer != wbuffer )
    {
        if (dwOption == INTERNET_OPTION_PER_CONNECTION_OPTION)
        {
            INTERNET_PER_CONN_OPTION_LISTW *list = wbuffer;
            unsigned int i;
            for (i = 0; i < list->dwOptionCount; ++i) {
                INTERNET_PER_CONN_OPTIONW *opt = list->pOptions + i;
                switch (opt->dwOption) {
                case INTERNET_PER_CONN_AUTOCONFIG_URL:
                case INTERNET_PER_CONN_PROXY_BYPASS:
                case INTERNET_PER_CONN_PROXY_SERVER:
                case INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL:
                case INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL:
                    heap_free( opt->Value.pszValue );
                    break;
                default:
                    break;
                }
            }
            heap_free( list->pOptions );
        }
        heap_free( wbuffer );
    }

    return r;
}


/***********************************************************************
 *           InternetSetOptionExA (WININET.@)
 */
BOOL WINAPI InternetSetOptionExA(HINTERNET hInternet, DWORD dwOption,
                           LPVOID lpBuffer, DWORD dwBufferLength, DWORD dwFlags)
{
    FIXME("Flags %08x ignored\n", dwFlags);
    return InternetSetOptionA( hInternet, dwOption, lpBuffer, dwBufferLength );
}

/***********************************************************************
 *           InternetSetOptionExW (WININET.@)
 */
BOOL WINAPI InternetSetOptionExW(HINTERNET hInternet, DWORD dwOption,
                           LPVOID lpBuffer, DWORD dwBufferLength, DWORD dwFlags)
{
    FIXME("Flags %08x ignored\n", dwFlags);
    if( dwFlags & ~ISO_VALID_FLAGS )
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }
    return InternetSetOptionW( hInternet, dwOption, lpBuffer, dwBufferLength );
}

static const WCHAR WININET_wkday[7][4] =
    { L"Sun", L"Mon", L"Tue", L"Wed",
      L"Thu", L"Fri", L"Sat"};
static const WCHAR WININET_month[12][4] =
    { L"Jan", L"Feb", L"Mar", L"Apr",
      L"May", L"Jun", L"Jul", L"Aug",
      L"Sep", L"Oct", L"Nov", L"Dec"};

/***********************************************************************
 *           InternetTimeFromSystemTimeA (WININET.@)
 */
BOOL WINAPI InternetTimeFromSystemTimeA( const SYSTEMTIME* time, DWORD format, LPSTR string, DWORD size )
{
    BOOL ret;
    WCHAR stringW[INTERNET_RFC1123_BUFSIZE];

    TRACE( "%p 0x%08x %p 0x%08x\n", time, format, string, size );

    if (!time || !string || format != INTERNET_RFC1123_FORMAT)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (size < INTERNET_RFC1123_BUFSIZE * sizeof(*string))
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    ret = InternetTimeFromSystemTimeW( time, format, stringW, sizeof(stringW) );
    if (ret) WideCharToMultiByte( CP_ACP, 0, stringW, -1, string, size, NULL, NULL );

    return ret;
}

/***********************************************************************
 *           InternetTimeFromSystemTimeW (WININET.@)
 */
BOOL WINAPI InternetTimeFromSystemTimeW( const SYSTEMTIME* time, DWORD format, LPWSTR string, DWORD size )
{
    TRACE( "%p 0x%08x %p 0x%08x\n", time, format, string, size );

    if (!time || !string || format != INTERNET_RFC1123_FORMAT)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (size < INTERNET_RFC1123_BUFSIZE * sizeof(*string))
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    swprintf( string, size, L"%s, %02d %s %4d %02d:%02d:%02d GMT",
              WININET_wkday[time->wDayOfWeek],
              time->wDay,
              WININET_month[time->wMonth - 1],
              time->wYear,
              time->wHour,
              time->wMinute,
              time->wSecond );

    return TRUE;
}

/***********************************************************************
 *           InternetTimeToSystemTimeA (WININET.@)
 */
BOOL WINAPI InternetTimeToSystemTimeA( LPCSTR string, SYSTEMTIME* time, DWORD reserved )
{
    BOOL ret = FALSE;
    WCHAR *stringW;

    TRACE( "%s %p 0x%08x\n", debugstr_a(string), time, reserved );

    stringW = heap_strdupAtoW(string);
    if (stringW)
    {
        ret = InternetTimeToSystemTimeW( stringW, time, reserved );
        heap_free( stringW );
    }
    return ret;
}

/***********************************************************************
 *           InternetTimeToSystemTimeW (WININET.@)
 */
BOOL WINAPI InternetTimeToSystemTimeW( LPCWSTR string, SYSTEMTIME* time, DWORD reserved )
{
    unsigned int i;
    const WCHAR *s = string;
    WCHAR       *end;

    TRACE( "%s %p 0x%08x\n", debugstr_w(string), time, reserved );

    if (!string || !time) return FALSE;

    /* Windows does this too */
    GetSystemTime( time );

    /*  Convert an RFC1123 time such as 'Fri, 07 Jan 2005 12:06:35 GMT' into
     *  a SYSTEMTIME structure.
     */

    while (*s && !iswalpha( *s )) s++;
    if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0') return TRUE;
    time->wDayOfWeek = 7;

    for (i = 0; i < 7; i++)
    {
        if (!wcsnicmp( WININET_wkday[i], s, 3 ))
        {
            time->wDayOfWeek = i;
            break;
        }
    }

    if (time->wDayOfWeek > 6) return TRUE;
    while (*s && !iswdigit( *s )) s++;
    time->wDay = wcstol( s, &end, 10 );
    s = end;

    while (*s && !iswalpha( *s )) s++;
    if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0') return TRUE;
    time->wMonth = 0;

    for (i = 0; i < 12; i++)
    {
        if (!wcsnicmp( WININET_month[i], s, 3 ))
        {
            time->wMonth = i + 1;
            break;
        }
    }
    if (time->wMonth == 0) return TRUE;

    while (*s && !iswdigit( *s )) s++;
    if (*s == '\0') return TRUE;
    time->wYear = wcstol( s, &end, 10 );
    s = end;

    while (*s && !iswdigit( *s )) s++;
    if (*s == '\0') return TRUE;
    time->wHour = wcstol( s, &end, 10 );
    s = end;

    while (*s && !iswdigit( *s )) s++;
    if (*s == '\0') return TRUE;
    time->wMinute = wcstol( s, &end, 10 );
    s = end;

    while (*s && !iswdigit( *s )) s++;
    if (*s == '\0') return TRUE;
    time->wSecond = wcstol( s, &end, 10 );
    s = end;

    time->wMilliseconds = 0;
    return TRUE;
}

/***********************************************************************
 *	InternetCheckConnectionW (WININET.@)
 *
 * Pings a requested host to check internet connection
 *
 * RETURNS
 *   TRUE on success and FALSE on failure. If a failure then
 *   ERROR_NOT_CONNECTED is placed into GetLastError
 *
 */
BOOL WINAPI InternetCheckConnectionW( LPCWSTR lpszUrl, DWORD dwFlags, DWORD dwReserved )
{
/*
 * this is a kludge which runs the resident ping program and reads the output.
 *
 * Anyone have a better idea?
 */

  BOOL   rc = FALSE;
  static const CHAR ping[] = "ping -c 1 ";
  static const CHAR redirect[] = " >/dev/null 2>/dev/null";
  WCHAR *host;
  DWORD len, host_len;
  INTERNET_PORT port;
  int status = -1;

  FIXME("(%s %x %x)\n", debugstr_w(lpszUrl), dwFlags, dwReserved);

  /*
   * Crack or set the Address
   */
  if (lpszUrl == NULL)
  {
     /*
      * According to the doc we are supposed to use the ip for the next
      * server in the WnInet internal server database. I have
      * no idea what that is or how to get it.
      *
      * So someone needs to implement this.
      */
     FIXME("Unimplemented with URL of NULL\n");
     return TRUE;
  }
  else
  {
     URL_COMPONENTSW components = {sizeof(components)};

     components.dwHostNameLength = 1;

     if (!InternetCrackUrlW(lpszUrl,0,0,&components))
       goto End;

     host = components.lpszHostName;
     host_len = components.dwHostNameLength;
     port = components.nPort;
     TRACE("host name: %s port: %d\n",debugstr_wn(host, host_len), port);
  }

  if (dwFlags & FLAG_ICC_FORCE_CONNECTION)
  {
      struct sockaddr_storage saddr;
      int sa_len = sizeof(saddr);
      WCHAR *host_z;
      int fd;
      BOOL b;

      host_z = heap_strndupW(host, host_len);
      if (!host_z)
          return FALSE;

      b = GetAddress(host_z, port, (struct sockaddr *)&saddr, &sa_len, NULL);
      heap_free(host_z);
      if(!b)
          goto End;
      init_winsock();
      fd = socket(saddr.ss_family, SOCK_STREAM, 0);
      if (fd != -1)
      {
          if (connect(fd, (struct sockaddr *)&saddr, sa_len) == 0)
              rc = TRUE;
          closesocket(fd);
      }
  }
  else
  {
      /*
       * Build our ping command
       */
      char *command;

      len = WideCharToMultiByte(CP_UNIXCP, 0, host, host_len, NULL, 0, NULL, NULL);
      command = heap_alloc(strlen(ping)+len+strlen(redirect)+1);
      strcpy(command, ping);
      WideCharToMultiByte(CP_UNIXCP, 0, host, host_len, command+sizeof(ping)-1, len, NULL, NULL);
      strcpy(command+sizeof(ping)-1+len, redirect);

      TRACE("Ping command is : %s\n",command);

      status = system(command);
      heap_free( command );

      TRACE("Ping returned a code of %i\n",status);

      /* Ping return code of 0 indicates success */
      if (status == 0)
         rc = TRUE;
  }

End:
  if (rc == FALSE)
    INTERNET_SetLastError(ERROR_NOT_CONNECTED);

  return rc;
}


/***********************************************************************
 *	InternetCheckConnectionA (WININET.@)
 *
 * Pings a requested host to check internet connection
 *
 * RETURNS
 *   TRUE on success and FALSE on failure. If a failure then
 *   ERROR_NOT_CONNECTED is placed into GetLastError
 *
 */
BOOL WINAPI InternetCheckConnectionA(LPCSTR lpszUrl, DWORD dwFlags, DWORD dwReserved)
{
    WCHAR *url = NULL;
    BOOL rc;

    if(lpszUrl) {
        url = heap_strdupAtoW(lpszUrl);
        if(!url)
            return FALSE;
    }

    rc = InternetCheckConnectionW(url, dwFlags, dwReserved);

    heap_free(url);
    return rc;
}


/**********************************************************
 *	INTERNET_InternetOpenUrlW (internal)
 *
 * Opens an URL
 *
 * RETURNS
 *   handle of connection or NULL on failure
 */
static HINTERNET INTERNET_InternetOpenUrlW(appinfo_t *hIC, LPCWSTR lpszUrl,
    LPCWSTR lpszHeaders, DWORD dwHeadersLength, DWORD dwFlags, DWORD_PTR dwContext)
{
    URL_COMPONENTSW urlComponents = { sizeof(urlComponents) };
    WCHAR *host, *user = NULL, *pass = NULL, *path;
    HINTERNET client = NULL, client1 = NULL;
    DWORD res;
    
    TRACE("(%p, %s, %s, %08x, %08x, %08lx)\n", hIC, debugstr_w(lpszUrl), debugstr_w(lpszHeaders),
	  dwHeadersLength, dwFlags, dwContext);
    
    urlComponents.dwHostNameLength = 1;
    urlComponents.dwUserNameLength = 1;
    urlComponents.dwPasswordLength = 1;
    urlComponents.dwUrlPathLength = 1;
    urlComponents.dwExtraInfoLength = 1;
    if(!InternetCrackUrlW(lpszUrl, lstrlenW(lpszUrl), 0, &urlComponents))
	return NULL;

    if ((urlComponents.nScheme == INTERNET_SCHEME_HTTP || urlComponents.nScheme == INTERNET_SCHEME_HTTPS) &&
        urlComponents.dwExtraInfoLength)
    {
        assert(urlComponents.lpszUrlPath + urlComponents.dwUrlPathLength == urlComponents.lpszExtraInfo);
        urlComponents.dwUrlPathLength += urlComponents.dwExtraInfoLength;
    }

    host = heap_strndupW(urlComponents.lpszHostName, urlComponents.dwHostNameLength);
    path = heap_strndupW(urlComponents.lpszUrlPath, urlComponents.dwUrlPathLength);
    if(urlComponents.dwUserNameLength)
        user = heap_strndupW(urlComponents.lpszUserName, urlComponents.dwUserNameLength);
    if(urlComponents.dwPasswordLength)
        pass = heap_strndupW(urlComponents.lpszPassword, urlComponents.dwPasswordLength);

    switch(urlComponents.nScheme) {
    case INTERNET_SCHEME_FTP:
	client = FTP_Connect(hIC, host, urlComponents.nPort,
			     user, pass, dwFlags, dwContext, INET_OPENURL);
	if(client == NULL)
	    break;
	client1 = FtpOpenFileW(client, path, GENERIC_READ, dwFlags, dwContext);
	if(client1 == NULL) {
	    InternetCloseHandle(client);
	    break;
	}
	break;
	
    case INTERNET_SCHEME_HTTP:
    case INTERNET_SCHEME_HTTPS: {
        LPCWSTR accept[2] = { L"*/*", NULL };

        if (urlComponents.nScheme == INTERNET_SCHEME_HTTPS) dwFlags |= INTERNET_FLAG_SECURE;

        /* FIXME: should use pointers, not handles, as handles are not thread-safe */
	res = HTTP_Connect(hIC, host, urlComponents.nPort,
                           user, pass, dwFlags, dwContext, INET_OPENURL, &client);
        if(res != ERROR_SUCCESS) {
            INTERNET_SetLastError(res);
	    break;
        }

        client1 = HttpOpenRequestW(client, NULL, path, NULL, NULL, accept, dwFlags, dwContext);
	if(client1 == NULL) {
	    InternetCloseHandle(client);
	    break;
	}
	HttpAddRequestHeadersW(client1, lpszHeaders, dwHeadersLength, HTTP_ADDREQ_FLAG_ADD);
	if (!HttpSendRequestW(client1, NULL, 0, NULL, 0) &&
            GetLastError() != ERROR_IO_PENDING) {
	    InternetCloseHandle(client1);
	    client1 = NULL;
	    break;
	}
    }
    case INTERNET_SCHEME_GOPHER:
	/* gopher doesn't seem to be implemented in wine, but it's supposed
	 * to be supported by InternetOpenUrlA. */
    default:
        SetLastError(ERROR_INTERNET_UNRECOGNIZED_SCHEME);
	break;
    }

    TRACE(" %p <--\n", client1);

    heap_free(host);
    heap_free(path);
    heap_free(user);
    heap_free(pass);
    return client1;
}

/**********************************************************
 *	InternetOpenUrlW (WININET.@)
 *
 * Opens an URL
 *
 * RETURNS
 *   handle of connection or NULL on failure
 */
typedef struct {
    task_header_t hdr;
    WCHAR *url;
    WCHAR *headers;
    DWORD headers_len;
    DWORD flags;
    DWORD_PTR context;
} open_url_task_t;

static void AsyncInternetOpenUrlProc(task_header_t *hdr)
{
    open_url_task_t *task = (open_url_task_t*)hdr;

    TRACE("%p\n", task->hdr.hdr);

    INTERNET_InternetOpenUrlW((appinfo_t*)task->hdr.hdr, task->url, task->headers,
            task->headers_len, task->flags, task->context);
    heap_free(task->url);
    heap_free(task->headers);
}

HINTERNET WINAPI InternetOpenUrlW(HINTERNET hInternet, LPCWSTR lpszUrl,
    LPCWSTR lpszHeaders, DWORD dwHeadersLength, DWORD dwFlags, DWORD_PTR dwContext)
{
    HINTERNET ret = NULL;
    appinfo_t *hIC = NULL;

    if (TRACE_ON(wininet)) {
	TRACE("(%p, %s, %s, %08x, %08x, %08lx)\n", hInternet, debugstr_w(lpszUrl), debugstr_w(lpszHeaders),
	      dwHeadersLength, dwFlags, dwContext);
	TRACE("  flags :");
	dump_INTERNET_FLAGS(dwFlags);
    }

    if (!lpszUrl)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        goto lend;
    }

    hIC = (appinfo_t*)get_handle_object( hInternet );
    if (NULL == hIC ||  hIC->hdr.htype != WH_HINIT) {
	SetLastError(ERROR_INTERNET_INCORRECT_HANDLE_TYPE);
 	goto lend;
    }
    
    if (hIC->hdr.dwFlags & INTERNET_FLAG_ASYNC) {
	open_url_task_t *task;

        task = alloc_async_task(&hIC->hdr, AsyncInternetOpenUrlProc, sizeof(*task));
        task->url = heap_strdupW(lpszUrl);
        task->headers = heap_strdupW(lpszHeaders);
        task->headers_len = dwHeadersLength;
        task->flags = dwFlags;
        task->context = dwContext;
	
        INTERNET_AsyncCall(&task->hdr);
        SetLastError(ERROR_IO_PENDING);
    } else {
	ret = INTERNET_InternetOpenUrlW(hIC, lpszUrl, lpszHeaders, dwHeadersLength, dwFlags, dwContext);
    }
    
  lend:
    if( hIC )
        WININET_Release( &hIC->hdr );
    TRACE(" %p <--\n", ret);
    
    return ret;
}

/**********************************************************
 *	InternetOpenUrlA (WININET.@)
 *
 * Opens an URL
 *
 * RETURNS
 *   handle of connection or NULL on failure
 */
HINTERNET WINAPI InternetOpenUrlA(HINTERNET hInternet, LPCSTR lpszUrl,
    LPCSTR lpszHeaders, DWORD dwHeadersLength, DWORD dwFlags, DWORD_PTR dwContext)
{
    HINTERNET rc = NULL;
    LPWSTR szUrl = NULL;
    WCHAR *headers = NULL;

    TRACE("\n");

    if(lpszUrl) {
        szUrl = heap_strdupAtoW(lpszUrl);
        if(!szUrl)
            return NULL;
    }

    if(lpszHeaders) {
        headers = heap_strndupAtoW(lpszHeaders, dwHeadersLength, &dwHeadersLength);
        if(!headers) {
            heap_free(szUrl);
            return NULL;
        }
    }
    
    rc = InternetOpenUrlW(hInternet, szUrl, headers, dwHeadersLength, dwFlags, dwContext);

    heap_free(szUrl);
    heap_free(headers);
    return rc;
}


static LPWITHREADERROR INTERNET_AllocThreadError(void)
{
    LPWITHREADERROR lpwite = heap_alloc(sizeof(*lpwite));

    if (lpwite)
    {
        lpwite->dwError = 0;
        lpwite->response[0] = '\0';
    }

    if (!TlsSetValue(g_dwTlsErrIndex, lpwite))
    {
        heap_free(lpwite);
        return NULL;
    }
    return lpwite;
}


/***********************************************************************
 *           INTERNET_SetLastError (internal)
 *
 * Set last thread specific error
 *
 * RETURNS
 *
 */
void INTERNET_SetLastError(DWORD dwError)
{
    LPWITHREADERROR lpwite = TlsGetValue(g_dwTlsErrIndex);

    if (!lpwite)
        lpwite = INTERNET_AllocThreadError();

    SetLastError(dwError);
    if(lpwite)
        lpwite->dwError = dwError;
}


/***********************************************************************
 *           INTERNET_GetLastError (internal)
 *
 * Get last thread specific error
 *
 * RETURNS
 *
 */
DWORD INTERNET_GetLastError(void)
{
    LPWITHREADERROR lpwite = TlsGetValue(g_dwTlsErrIndex);
    if (!lpwite) return 0;
    /* TlsGetValue clears last error, so set it again here */
    SetLastError(lpwite->dwError);
    return lpwite->dwError;
}


/***********************************************************************
 *           INTERNET_WorkerThreadFunc (internal)
 *
 * Worker thread execution function
 *
 * RETURNS
 *
 */
static DWORD CALLBACK INTERNET_WorkerThreadFunc(LPVOID lpvParam)
{
    task_header_t *task = lpvParam;

    TRACE("\n");

    task->proc(task);
    WININET_Release(task->hdr);
    heap_free(task);

    if (g_dwTlsErrIndex != TLS_OUT_OF_INDEXES)
    {
        heap_free(TlsGetValue(g_dwTlsErrIndex));
        TlsSetValue(g_dwTlsErrIndex, NULL);
    }
    return TRUE;
}

void *alloc_async_task(object_header_t *hdr, async_task_proc_t proc, size_t size)
{
    task_header_t *task;

    task = heap_alloc(size);
    if(!task)
        return NULL;

    task->hdr = WININET_AddRef(hdr);
    task->proc = proc;
    return task;
}

/***********************************************************************
 *           INTERNET_AsyncCall (internal)
 *
 * Retrieves work request from queue
 *
 * RETURNS
 *
 */
DWORD INTERNET_AsyncCall(task_header_t *task)
{
    BOOL bSuccess;

    TRACE("\n");

    bSuccess = QueueUserWorkItem(INTERNET_WorkerThreadFunc, task, WT_EXECUTELONGFUNCTION);
    if (!bSuccess)
    {
        heap_free(task);
        return ERROR_INTERNET_ASYNC_THREAD_FAILED;
    }
    return ERROR_SUCCESS;
}


/***********************************************************************
 *          INTERNET_GetResponseBuffer  (internal)
 *
 * RETURNS
 *
 */
LPSTR INTERNET_GetResponseBuffer(void)
{
    LPWITHREADERROR lpwite = TlsGetValue(g_dwTlsErrIndex);
    if (!lpwite)
        lpwite = INTERNET_AllocThreadError();
    TRACE("\n");
    return lpwite->response;
}

/**********************************************************
 *	InternetQueryDataAvailable (WININET.@)
 *
 * Determines how much data is available to be read.
 *
 * RETURNS
 *   TRUE on success, FALSE if an error occurred. If
 *   INTERNET_FLAG_ASYNC was specified in InternetOpen, and
 *   no data is presently available, FALSE is returned with
 *   the last error ERROR_IO_PENDING; a callback with status
 *   INTERNET_STATUS_REQUEST_COMPLETE will be sent when more
 *   data is available.
 */
BOOL WINAPI InternetQueryDataAvailable( HINTERNET hFile,
                                LPDWORD lpdwNumberOfBytesAvailable,
                                DWORD dwFlags, DWORD_PTR dwContext)
{
    object_header_t *hdr;
    DWORD res;

    TRACE("(%p %p %x %lx)\n", hFile, lpdwNumberOfBytesAvailable, dwFlags, dwContext);

    hdr = get_handle_object( hFile );
    if (!hdr) {
        SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    if(hdr->vtbl->QueryDataAvailable) {
        res = hdr->vtbl->QueryDataAvailable(hdr, lpdwNumberOfBytesAvailable, dwFlags, dwContext);
    }else {
        WARN("wrong handle\n");
        res = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
    }

    WININET_Release(hdr);

    if(res != ERROR_SUCCESS)
        SetLastError(res);
    return res == ERROR_SUCCESS;
}

DWORD create_req_file(const WCHAR *file_name, req_file_t **ret)
{
    req_file_t *req_file;

    req_file = heap_alloc_zero(sizeof(*req_file));
    if(!req_file)
        return ERROR_NOT_ENOUGH_MEMORY;

    req_file->ref = 1;

    req_file->file_name = heap_strdupW(file_name);
    if(!req_file->file_name) {
        heap_free(req_file);
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    req_file->file_handle = CreateFileW(req_file->file_name, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
              NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(req_file->file_handle == INVALID_HANDLE_VALUE) {
        req_file_release(req_file);
        return GetLastError();
    }

    *ret = req_file;
    return ERROR_SUCCESS;
}

void req_file_release(req_file_t *req_file)
{
    if(InterlockedDecrement(&req_file->ref))
        return;

    if(!req_file->is_committed)
        DeleteFileW(req_file->file_name);
    if(req_file->file_handle && req_file->file_handle != INVALID_HANDLE_VALUE)
        CloseHandle(req_file->file_handle);
    heap_free(req_file->file_name);
    heap_free(req_file->url);
    heap_free(req_file);
}

/***********************************************************************
 *      InternetLockRequestFile (WININET.@)
 */
BOOL WINAPI InternetLockRequestFile(HINTERNET hInternet, HANDLE *lphLockReqHandle)
{
    req_file_t *req_file = NULL;
    object_header_t *hdr;
    DWORD res;

    TRACE("(%p %p)\n", hInternet, lphLockReqHandle);

    hdr = get_handle_object(hInternet);
    if (!hdr) {
        SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    if(hdr->vtbl->LockRequestFile) {
        res = hdr->vtbl->LockRequestFile(hdr, &req_file);
    }else {
        WARN("wrong handle\n");
        res = ERROR_INTERNET_INCORRECT_HANDLE_TYPE;
    }

    WININET_Release(hdr);

    *lphLockReqHandle = req_file;
    if(res != ERROR_SUCCESS)
        SetLastError(res);
    return res == ERROR_SUCCESS;
}

BOOL WINAPI InternetUnlockRequestFile(HANDLE hLockHandle)
{
    TRACE("(%p)\n", hLockHandle);

    req_file_release(hLockHandle);
    return TRUE;
}


/***********************************************************************
 *      InternetAutodial (WININET.@)
 *
 * On windows this function is supposed to dial the default internet
 * connection. We don't want to have Wine dial out to the internet so
 * we return TRUE by default. It might be nice to check if we are connected.
 *
 * RETURNS
 *   TRUE on success
 *   FALSE on failure
 *
 */
BOOL WINAPI InternetAutodial(DWORD dwFlags, HWND hwndParent)
{
    FIXME("STUB\n");

    /* Tell that we are connected to the internet. */
    return TRUE;
}

/***********************************************************************
 *      InternetAutodialHangup (WININET.@)
 *
 * Hangs up a connection made with InternetAutodial
 *
 * PARAM
 *    dwReserved
 * RETURNS
 *   TRUE on success
 *   FALSE on failure
 *
 */
BOOL WINAPI InternetAutodialHangup(DWORD dwReserved)
{
    FIXME("STUB\n");

    /* we didn't dial, we don't disconnect */
    return TRUE;
}

/***********************************************************************
 *      InternetCombineUrlA (WININET.@)
 *
 * Combine a base URL with a relative URL
 *
 * RETURNS
 *   TRUE on success
 *   FALSE on failure
 *
 */

BOOL WINAPI InternetCombineUrlA(LPCSTR lpszBaseUrl, LPCSTR lpszRelativeUrl,
                                LPSTR lpszBuffer, LPDWORD lpdwBufferLength,
                                DWORD dwFlags)
{
    HRESULT hr=S_OK;

    TRACE("(%s, %s, %p, %p, 0x%08x)\n", debugstr_a(lpszBaseUrl), debugstr_a(lpszRelativeUrl), lpszBuffer, lpdwBufferLength, dwFlags);

    /* Flip this bit to correspond to URL_ESCAPE_UNSAFE */
    dwFlags ^= ICU_NO_ENCODE;
    hr=UrlCombineA(lpszBaseUrl,lpszRelativeUrl,lpszBuffer,lpdwBufferLength,dwFlags);

    return (hr==S_OK);
}

/***********************************************************************
 *      InternetCombineUrlW (WININET.@)
 *
 * Combine a base URL with a relative URL
 *
 * RETURNS
 *   TRUE on success
 *   FALSE on failure
 *
 */

BOOL WINAPI InternetCombineUrlW(LPCWSTR lpszBaseUrl, LPCWSTR lpszRelativeUrl,
                                LPWSTR lpszBuffer, LPDWORD lpdwBufferLength,
                                DWORD dwFlags)
{
    HRESULT hr=S_OK;

    TRACE("(%s, %s, %p, %p, 0x%08x)\n", debugstr_w(lpszBaseUrl), debugstr_w(lpszRelativeUrl), lpszBuffer, lpdwBufferLength, dwFlags);

    /* Flip this bit to correspond to URL_ESCAPE_UNSAFE */
    dwFlags ^= ICU_NO_ENCODE;
    hr=UrlCombineW(lpszBaseUrl,lpszRelativeUrl,lpszBuffer,lpdwBufferLength,dwFlags);

    return (hr==S_OK);
}

/* max port num is 65535 => 5 digits */
#define MAX_WORD_DIGITS 5

#define URL_GET_COMP_LENGTH(url, component) ((url)->dw##component##Length ? \
    (url)->dw##component##Length : lstrlenW((url)->lpsz##component))
#define URL_GET_COMP_LENGTHA(url, component) ((url)->dw##component##Length ? \
    (url)->dw##component##Length : strlen((url)->lpsz##component))

static BOOL url_uses_default_port(INTERNET_SCHEME nScheme, INTERNET_PORT nPort)
{
    if ((nScheme == INTERNET_SCHEME_HTTP) &&
        (nPort == INTERNET_DEFAULT_HTTP_PORT))
        return TRUE;
    if ((nScheme == INTERNET_SCHEME_HTTPS) &&
        (nPort == INTERNET_DEFAULT_HTTPS_PORT))
        return TRUE;
    if ((nScheme == INTERNET_SCHEME_FTP) &&
        (nPort == INTERNET_DEFAULT_FTP_PORT))
        return TRUE;
    if ((nScheme == INTERNET_SCHEME_GOPHER) &&
        (nPort == INTERNET_DEFAULT_GOPHER_PORT))
        return TRUE;

    if (nPort == INTERNET_INVALID_PORT_NUMBER)
        return TRUE;

    return FALSE;
}

/* opaque urls do not fit into the standard url hierarchy and don't have
 * two following slashes */
static inline BOOL scheme_is_opaque(INTERNET_SCHEME nScheme)
{
    return (nScheme != INTERNET_SCHEME_FTP) &&
           (nScheme != INTERNET_SCHEME_GOPHER) &&
           (nScheme != INTERNET_SCHEME_HTTP) &&
           (nScheme != INTERNET_SCHEME_HTTPS) &&
           (nScheme != INTERNET_SCHEME_FILE);
}

static LPCWSTR INTERNET_GetSchemeString(INTERNET_SCHEME scheme)
{
    int index;
    if (scheme < INTERNET_SCHEME_FIRST)
        return NULL;
    index = scheme - INTERNET_SCHEME_FIRST;
    if (index >= ARRAY_SIZE(url_schemes))
        return NULL;
    return url_schemes[index];
}

/* we can calculate using ansi strings because we're just
 * calculating string length, not size
 */
static BOOL calc_url_length(LPURL_COMPONENTSW lpUrlComponents,
                            LPDWORD lpdwUrlLength)
{
    INTERNET_SCHEME nScheme;

    *lpdwUrlLength = 0;

    if (lpUrlComponents->lpszScheme)
    {
        DWORD dwLen = URL_GET_COMP_LENGTH(lpUrlComponents, Scheme);
        *lpdwUrlLength += dwLen;
        nScheme = GetInternetSchemeW(lpUrlComponents->lpszScheme, dwLen);
    }
    else
    {
        LPCWSTR scheme;

        nScheme = lpUrlComponents->nScheme;

        if (nScheme == INTERNET_SCHEME_DEFAULT)
            nScheme = INTERNET_SCHEME_HTTP;
        scheme = INTERNET_GetSchemeString(nScheme);
        *lpdwUrlLength += lstrlenW(scheme);
    }

    (*lpdwUrlLength)++; /* ':' */
    if (!scheme_is_opaque(nScheme) || lpUrlComponents->lpszHostName)
        *lpdwUrlLength += strlen("//");

    if (lpUrlComponents->lpszUserName)
    {
        *lpdwUrlLength += URL_GET_COMP_LENGTH(lpUrlComponents, UserName);
        *lpdwUrlLength += strlen("@");
    }
    else
    {
        if (lpUrlComponents->lpszPassword)
        {
            INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
            return FALSE;
        }
    }

    if (lpUrlComponents->lpszPassword)
    {
        *lpdwUrlLength += strlen(":");
        *lpdwUrlLength += URL_GET_COMP_LENGTH(lpUrlComponents, Password);
    }

    if (lpUrlComponents->lpszHostName)
    {
        *lpdwUrlLength += URL_GET_COMP_LENGTH(lpUrlComponents, HostName);

        if (!url_uses_default_port(nScheme, lpUrlComponents->nPort))
        {
            WCHAR port[MAX_WORD_DIGITS + 1];

            _ltow(lpUrlComponents->nPort, port, 10);
            *lpdwUrlLength += lstrlenW(port);
            *lpdwUrlLength += strlen(":");
        }

        if (lpUrlComponents->lpszUrlPath && *lpUrlComponents->lpszUrlPath != '/')
            (*lpdwUrlLength)++; /* '/' */
    }

    if (lpUrlComponents->lpszUrlPath)
        *lpdwUrlLength += URL_GET_COMP_LENGTH(lpUrlComponents, UrlPath);

    if (lpUrlComponents->lpszExtraInfo)
        *lpdwUrlLength += URL_GET_COMP_LENGTH(lpUrlComponents, ExtraInfo);

    return TRUE;
}

static void convert_urlcomp_atow(LPURL_COMPONENTSA lpUrlComponents, LPURL_COMPONENTSW urlCompW)
{
    INT len;

    ZeroMemory(urlCompW, sizeof(URL_COMPONENTSW));

    urlCompW->dwStructSize = sizeof(URL_COMPONENTSW);
    urlCompW->dwSchemeLength = lpUrlComponents->dwSchemeLength;
    urlCompW->nScheme = lpUrlComponents->nScheme;
    urlCompW->dwHostNameLength = lpUrlComponents->dwHostNameLength;
    urlCompW->nPort = lpUrlComponents->nPort;
    urlCompW->dwUserNameLength = lpUrlComponents->dwUserNameLength;
    urlCompW->dwPasswordLength = lpUrlComponents->dwPasswordLength;
    urlCompW->dwUrlPathLength = lpUrlComponents->dwUrlPathLength;
    urlCompW->dwExtraInfoLength = lpUrlComponents->dwExtraInfoLength;

    if (lpUrlComponents->lpszScheme)
    {
        len = URL_GET_COMP_LENGTHA(lpUrlComponents, Scheme) + 1;
        urlCompW->lpszScheme = heap_alloc(len * sizeof(WCHAR));
        MultiByteToWideChar(CP_ACP, 0, lpUrlComponents->lpszScheme,
                            -1, urlCompW->lpszScheme, len);
    }

    if (lpUrlComponents->lpszHostName)
    {
        len = URL_GET_COMP_LENGTHA(lpUrlComponents, HostName) + 1;
        urlCompW->lpszHostName = heap_alloc(len * sizeof(WCHAR));
        MultiByteToWideChar(CP_ACP, 0, lpUrlComponents->lpszHostName,
                            -1, urlCompW->lpszHostName, len);
    }

    if (lpUrlComponents->lpszUserName)
    {
        len = URL_GET_COMP_LENGTHA(lpUrlComponents, UserName) + 1;
        urlCompW->lpszUserName = heap_alloc(len * sizeof(WCHAR));
        MultiByteToWideChar(CP_ACP, 0, lpUrlComponents->lpszUserName,
                            -1, urlCompW->lpszUserName, len);
    }

    if (lpUrlComponents->lpszPassword)
    {
        len = URL_GET_COMP_LENGTHA(lpUrlComponents, Password) + 1;
        urlCompW->lpszPassword = heap_alloc(len * sizeof(WCHAR));
        MultiByteToWideChar(CP_ACP, 0, lpUrlComponents->lpszPassword,
                            -1, urlCompW->lpszPassword, len);
    }

    if (lpUrlComponents->lpszUrlPath)
    {
        len = URL_GET_COMP_LENGTHA(lpUrlComponents, UrlPath) + 1;
        urlCompW->lpszUrlPath = heap_alloc(len * sizeof(WCHAR));
        MultiByteToWideChar(CP_ACP, 0, lpUrlComponents->lpszUrlPath,
                            -1, urlCompW->lpszUrlPath, len);
    }

    if (lpUrlComponents->lpszExtraInfo)
    {
        len = URL_GET_COMP_LENGTHA(lpUrlComponents, ExtraInfo) + 1;
        urlCompW->lpszExtraInfo = heap_alloc(len * sizeof(WCHAR));
        MultiByteToWideChar(CP_ACP, 0, lpUrlComponents->lpszExtraInfo,
                            -1, urlCompW->lpszExtraInfo, len);
    }
}

/***********************************************************************
 *      InternetCreateUrlA (WININET.@)
 *
 * See InternetCreateUrlW.
 */
BOOL WINAPI InternetCreateUrlA(LPURL_COMPONENTSA lpUrlComponents, DWORD dwFlags,
                               LPSTR lpszUrl, LPDWORD lpdwUrlLength)
{
    BOOL ret;
    LPWSTR urlW = NULL;
    URL_COMPONENTSW urlCompW;

    TRACE("(%p,%d,%p,%p)\n", lpUrlComponents, dwFlags, lpszUrl, lpdwUrlLength);

    if (!lpUrlComponents || lpUrlComponents->dwStructSize != sizeof(URL_COMPONENTSW) || !lpdwUrlLength)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    convert_urlcomp_atow(lpUrlComponents, &urlCompW);

    if (lpszUrl)
        urlW = heap_alloc(*lpdwUrlLength * sizeof(WCHAR));

    ret = InternetCreateUrlW(&urlCompW, dwFlags, urlW, lpdwUrlLength);

    if (!ret && (GetLastError() == ERROR_INSUFFICIENT_BUFFER))
        *lpdwUrlLength /= sizeof(WCHAR);

    /* on success, lpdwUrlLength points to the size of urlW in WCHARS
    * minus one, so add one to leave room for NULL terminator
    */
    if (ret)
        WideCharToMultiByte(CP_ACP, 0, urlW, -1, lpszUrl, *lpdwUrlLength + 1, NULL, NULL);

    heap_free(urlCompW.lpszScheme);
    heap_free(urlCompW.lpszHostName);
    heap_free(urlCompW.lpszUserName);
    heap_free(urlCompW.lpszPassword);
    heap_free(urlCompW.lpszUrlPath);
    heap_free(urlCompW.lpszExtraInfo);
    heap_free(urlW);
    return ret;
}

/***********************************************************************
 *      InternetCreateUrlW (WININET.@)
 *
 * Creates a URL from its component parts.
 *
 * PARAMS
 *  lpUrlComponents [I] URL Components.
 *  dwFlags         [I] Flags. See notes.
 *  lpszUrl         [I] Buffer in which to store the created URL.
 *  lpdwUrlLength   [I/O] On input, the length of the buffer pointed to by
 *                        lpszUrl in characters. On output, the number of bytes
 *                        required to store the URL including terminator.
 *
 * NOTES
 *
 * The dwFlags parameter can be zero or more of the following:
 *|ICU_ESCAPE - Generates escape sequences for unsafe characters in the path and extra info of the URL.
 *
 * RETURNS
 *   TRUE on success
 *   FALSE on failure
 *
 */
BOOL WINAPI InternetCreateUrlW(LPURL_COMPONENTSW lpUrlComponents, DWORD dwFlags,
                               LPWSTR lpszUrl, LPDWORD lpdwUrlLength)
{
    DWORD dwLen;
    INTERNET_SCHEME nScheme;

    static const WCHAR slashSlashW[] = {'/','/'};

    TRACE("(%p,%d,%p,%p)\n", lpUrlComponents, dwFlags, lpszUrl, lpdwUrlLength);

    if (!lpUrlComponents || lpUrlComponents->dwStructSize != sizeof(URL_COMPONENTSW) || !lpdwUrlLength)
    {
        INTERNET_SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (!calc_url_length(lpUrlComponents, &dwLen))
        return FALSE;

    if (!lpszUrl || *lpdwUrlLength < dwLen)
    {
        *lpdwUrlLength = (dwLen + 1) * sizeof(WCHAR);
        INTERNET_SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    *lpdwUrlLength = dwLen;
    lpszUrl[0] = 0x00;

    dwLen = 0;

    if (lpUrlComponents->lpszScheme)
    {
        dwLen = URL_GET_COMP_LENGTH(lpUrlComponents, Scheme);
        memcpy(lpszUrl, lpUrlComponents->lpszScheme, dwLen * sizeof(WCHAR));
        lpszUrl += dwLen;

        nScheme = GetInternetSchemeW(lpUrlComponents->lpszScheme, dwLen);
    }
    else
    {
        LPCWSTR scheme;
        nScheme = lpUrlComponents->nScheme;

        if (nScheme == INTERNET_SCHEME_DEFAULT)
            nScheme = INTERNET_SCHEME_HTTP;

        scheme = INTERNET_GetSchemeString(nScheme);
        dwLen = lstrlenW(scheme);
        memcpy(lpszUrl, scheme, dwLen * sizeof(WCHAR));
        lpszUrl += dwLen;
    }

    /* all schemes are followed by at least a colon */
    *lpszUrl = ':';
    lpszUrl++;

    if (!scheme_is_opaque(nScheme) || lpUrlComponents->lpszHostName)
    {
        memcpy(lpszUrl, slashSlashW, sizeof(slashSlashW));
        lpszUrl += ARRAY_SIZE(slashSlashW);
    }

    if (lpUrlComponents->lpszUserName)
    {
        dwLen = URL_GET_COMP_LENGTH(lpUrlComponents, UserName);
        memcpy(lpszUrl, lpUrlComponents->lpszUserName, dwLen * sizeof(WCHAR));
        lpszUrl += dwLen;

        if (lpUrlComponents->lpszPassword)
        {
            *lpszUrl = ':';
            lpszUrl++;

            dwLen = URL_GET_COMP_LENGTH(lpUrlComponents, Password);
            memcpy(lpszUrl, lpUrlComponents->lpszPassword, dwLen * sizeof(WCHAR));
            lpszUrl += dwLen;
        }

        *lpszUrl = '@';
        lpszUrl++;
    }

    if (lpUrlComponents->lpszHostName)
    {
        dwLen = URL_GET_COMP_LENGTH(lpUrlComponents, HostName);
        memcpy(lpszUrl, lpUrlComponents->lpszHostName, dwLen * sizeof(WCHAR));
        lpszUrl += dwLen;

        if (!url_uses_default_port(nScheme, lpUrlComponents->nPort))
        {
            *lpszUrl++ = ':';
            _ltow(lpUrlComponents->nPort, lpszUrl, 10);
            lpszUrl += lstrlenW(lpszUrl);
        }

        /* add slash between hostname and path if necessary */
        if (lpUrlComponents->lpszUrlPath && *lpUrlComponents->lpszUrlPath != '/')
        {
            *lpszUrl = '/';
            lpszUrl++;
        }
    }

    if (lpUrlComponents->lpszUrlPath)
    {
        dwLen = URL_GET_COMP_LENGTH(lpUrlComponents, UrlPath);
        memcpy(lpszUrl, lpUrlComponents->lpszUrlPath, dwLen * sizeof(WCHAR));
        lpszUrl += dwLen;
    }

    if (lpUrlComponents->lpszExtraInfo)
    {
        dwLen = URL_GET_COMP_LENGTH(lpUrlComponents, ExtraInfo);
        memcpy(lpszUrl, lpUrlComponents->lpszExtraInfo, dwLen * sizeof(WCHAR));
        lpszUrl += dwLen;
    }

    *lpszUrl = '\0';

    return TRUE;
}

/***********************************************************************
 *      InternetConfirmZoneCrossingA (WININET.@)
 *
 */
DWORD WINAPI InternetConfirmZoneCrossingA( HWND hWnd, LPSTR szUrlPrev, LPSTR szUrlNew, BOOL bPost )
{
    FIXME("(%p, %s, %s, %x) stub\n", hWnd, debugstr_a(szUrlPrev), debugstr_a(szUrlNew), bPost);
    return ERROR_SUCCESS;
}

/***********************************************************************
 *      InternetConfirmZoneCrossingW (WININET.@)
 *
 */
DWORD WINAPI InternetConfirmZoneCrossingW( HWND hWnd, LPWSTR szUrlPrev, LPWSTR szUrlNew, BOOL bPost )
{
    FIXME("(%p, %s, %s, %x) stub\n", hWnd, debugstr_w(szUrlPrev), debugstr_w(szUrlNew), bPost);
    return ERROR_SUCCESS;
}

static DWORD zone_preference = 3;

/***********************************************************************
 *      PrivacySetZonePreferenceW (WININET.@)
 */
DWORD WINAPI PrivacySetZonePreferenceW( DWORD zone, DWORD type, DWORD template, LPCWSTR preference )
{
    FIXME( "%x %x %x %s: stub\n", zone, type, template, debugstr_w(preference) );

    zone_preference = template;
    return 0;
}

/***********************************************************************
 *      PrivacyGetZonePreferenceW (WININET.@)
 */
DWORD WINAPI PrivacyGetZonePreferenceW( DWORD zone, DWORD type, LPDWORD template,
                                        LPWSTR preference, LPDWORD length )
{
    FIXME( "%x %x %p %p %p: stub\n", zone, type, template, preference, length );

    if (template) *template = zone_preference;
    return 0;
}

/***********************************************************************
 *      InternetGetSecurityInfoByURLA (WININET.@)
 */
BOOL WINAPI InternetGetSecurityInfoByURLA(LPSTR lpszURL, PCCERT_CHAIN_CONTEXT *ppCertChain, DWORD *pdwSecureFlags)
{
    WCHAR *url;
    BOOL res;

    TRACE("(%s %p %p)\n", debugstr_a(lpszURL), ppCertChain, pdwSecureFlags);

    url = heap_strdupAtoW(lpszURL);
    if(!url)
        return FALSE;

    res = InternetGetSecurityInfoByURLW(url, ppCertChain, pdwSecureFlags);
    heap_free(url);
    return res;
}

/***********************************************************************
 *      InternetGetSecurityInfoByURLW (WININET.@)
 */
BOOL WINAPI InternetGetSecurityInfoByURLW(LPCWSTR lpszURL, PCCERT_CHAIN_CONTEXT *ppCertChain, DWORD *pdwSecureFlags)
{
    URL_COMPONENTSW url = {sizeof(url)};
    server_t *server;
    BOOL res;

    TRACE("(%s %p %p)\n", debugstr_w(lpszURL), ppCertChain, pdwSecureFlags);

    if (!ppCertChain && !pdwSecureFlags) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    url.dwHostNameLength = 1;
    res = InternetCrackUrlW(lpszURL, 0, 0, &url);
    if(!res || url.nScheme != INTERNET_SCHEME_HTTPS) {
        SetLastError(ERROR_INTERNET_ITEM_NOT_FOUND);
        return FALSE;
    }

    server = get_server(substr(url.lpszHostName, url.dwHostNameLength), url.nPort, TRUE, FALSE);
    if(!server) {
        SetLastError(ERROR_INTERNET_ITEM_NOT_FOUND);
        return FALSE;
    }

    if(server->cert_chain) {
        if(pdwSecureFlags)
            *pdwSecureFlags = server->security_flags & _SECURITY_ERROR_FLAGS_MASK;

        if(ppCertChain && !(*ppCertChain = CertDuplicateCertificateChain(server->cert_chain)))
            res = FALSE;
    }else {
        SetLastError(ERROR_INTERNET_ITEM_NOT_FOUND);
        res = FALSE;
    }

    server_release(server);
    return res;
}

DWORD WINAPI InternetDialA( HWND hwndParent, LPSTR lpszConnectoid, DWORD dwFlags,
                            DWORD_PTR* lpdwConnection, DWORD dwReserved )
{
    FIXME("(%p, %p, 0x%08x, %p, 0x%08x) stub\n", hwndParent, lpszConnectoid, dwFlags,
          lpdwConnection, dwReserved);
    return ERROR_SUCCESS;
}

DWORD WINAPI InternetDialW( HWND hwndParent, LPWSTR lpszConnectoid, DWORD dwFlags,
                            DWORD_PTR* lpdwConnection, DWORD dwReserved )
{
    FIXME("(%p, %p, 0x%08x, %p, 0x%08x) stub\n", hwndParent, lpszConnectoid, dwFlags,
          lpdwConnection, dwReserved);
    return ERROR_SUCCESS;
}

BOOL WINAPI InternetGoOnlineA( LPSTR lpszURL, HWND hwndParent, DWORD dwReserved )
{
    FIXME("(%s, %p, 0x%08x) stub\n", debugstr_a(lpszURL), hwndParent, dwReserved);
    return TRUE;
}

BOOL WINAPI InternetGoOnlineW( LPWSTR lpszURL, HWND hwndParent, DWORD dwReserved )
{
    FIXME("(%s, %p, 0x%08x) stub\n", debugstr_w(lpszURL), hwndParent, dwReserved);
    return TRUE;
}

DWORD WINAPI InternetHangUp( DWORD_PTR dwConnection, DWORD dwReserved )
{
    FIXME("(0x%08lx, 0x%08x) stub\n", dwConnection, dwReserved);
    return ERROR_SUCCESS;
}

BOOL WINAPI CreateMD5SSOHash( PWSTR pszChallengeInfo, PWSTR pwszRealm, PWSTR pwszTarget,
                              PBYTE pbHexHash )
{
    FIXME("(%s, %s, %s, %p) stub\n", debugstr_w(pszChallengeInfo), debugstr_w(pwszRealm),
          debugstr_w(pwszTarget), pbHexHash);
    return FALSE;
}

BOOL WINAPI ResumeSuspendedDownload( HINTERNET hInternet, DWORD dwError )
{
    FIXME("(%p, 0x%08x) stub\n", hInternet, dwError);
    return FALSE;
}

BOOL WINAPI InternetQueryFortezzaStatus(DWORD *a, DWORD_PTR b)
{
    FIXME("(%p, %08lx) stub\n", a, b);
    return FALSE;
}

DWORD WINAPI ShowClientAuthCerts(HWND parent)
{
    FIXME("%p: stub\n", parent);
    return 0;
}
