/*
 * Copyright 2008 Damjan Jovanovic
 *
 * ShellLink's barely documented cousin that handles URLs.
 *
 * 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
 */

/*
 * TODO:
 * Implement the IShellLinkA/W interfaces
 * Handle the SetURL flags
 * Implement any other interfaces? Does any software actually use them?
 *
 * The installer for the Zuma Deluxe Popcap game is good for testing.
 */

#include <stdio.h>

#include "ieframe.h"

#include "shlobj.h"
#include "shobjidl.h"
#include "intshcut.h"
#include "shellapi.h"
#include "winreg.h"
#include "shlwapi.h"
#include "shlguid.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(ieframe);

typedef struct
{
    IUniformResourceLocatorA IUniformResourceLocatorA_iface;
    IUniformResourceLocatorW IUniformResourceLocatorW_iface;
    IPersistFile IPersistFile_iface;
    IPropertySetStorage IPropertySetStorage_iface;

    LONG refCount;

    IPropertySetStorage *property_set_storage;
    WCHAR *url;
    BOOLEAN isDirty;
    LPOLESTR currentFile;
} InternetShortcut;

/* utility functions */

static inline InternetShortcut* impl_from_IUniformResourceLocatorA(IUniformResourceLocatorA *iface)
{
    return CONTAINING_RECORD(iface, InternetShortcut, IUniformResourceLocatorA_iface);
}

static inline InternetShortcut* impl_from_IUniformResourceLocatorW(IUniformResourceLocatorW *iface)
{
    return CONTAINING_RECORD(iface, InternetShortcut, IUniformResourceLocatorW_iface);
}

static inline InternetShortcut* impl_from_IPersistFile(IPersistFile *iface)
{
    return CONTAINING_RECORD(iface, InternetShortcut, IPersistFile_iface);
}

static inline InternetShortcut* impl_from_IPropertySetStorage(IPropertySetStorage *iface)
{
    return CONTAINING_RECORD(iface, InternetShortcut, IPropertySetStorage_iface);
}

static BOOL run_winemenubuilder( const WCHAR *args )
{
    static const WCHAR menubuilder[] = {'\\','w','i','n','e','m','e','n','u','b','u','i','l','d','e','r','.','e','x','e',0};
    LONG len;
    LPWSTR buffer;
    STARTUPINFOW si;
    PROCESS_INFORMATION pi;
    BOOL ret;
    WCHAR app[MAX_PATH];
    void *redir;

    GetSystemDirectoryW( app, MAX_PATH - sizeof(menubuilder)/sizeof(WCHAR) );
    strcatW( app, menubuilder );

    len = (strlenW( app ) + strlenW( args ) + 1) * sizeof(WCHAR);
    buffer = heap_alloc( len );
    if( !buffer )
        return FALSE;

    strcpyW( buffer, app );
    strcatW( buffer, args );

    TRACE("starting %s\n",debugstr_w(buffer));

    memset(&si, 0, sizeof(si));
    si.cb = sizeof(si);

    Wow64DisableWow64FsRedirection( &redir );
    ret = CreateProcessW( app, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi );
    Wow64RevertWow64FsRedirection( redir );

    heap_free( buffer );

    if (ret)
    {
        CloseHandle( pi.hProcess );
        CloseHandle( pi.hThread );
    }

    return ret;
}

static BOOL StartLinkProcessor( LPCOLESTR szLink )
{
    static const WCHAR szFormat[] = { ' ','-','w',' ','-','u',' ','"','%','s','"',0 };
    LONG len;
    LPWSTR buffer;
    BOOL ret;

    len = sizeof(szFormat) + lstrlenW( szLink ) * sizeof(WCHAR);
    buffer = heap_alloc( len );
    if( !buffer )
        return FALSE;

    sprintfW( buffer, szFormat, szLink );
    ret = run_winemenubuilder( buffer );
    heap_free( buffer );
    return ret;
}

/* interface functions */

static HRESULT Unknown_QueryInterface(InternetShortcut *This, REFIID riid, PVOID *ppvObject)
{
    TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppvObject);
    *ppvObject = NULL;
    if (IsEqualGUID(&IID_IUnknown, riid))
        *ppvObject = &This->IUniformResourceLocatorA_iface;
    else if (IsEqualGUID(&IID_IUniformResourceLocatorA, riid))
        *ppvObject = &This->IUniformResourceLocatorA_iface;
    else if (IsEqualGUID(&IID_IUniformResourceLocatorW, riid))
        *ppvObject = &This->IUniformResourceLocatorW_iface;
    else if (IsEqualGUID(&IID_IPersistFile, riid))
        *ppvObject = &This->IPersistFile_iface;
    else if (IsEqualGUID(&IID_IPropertySetStorage, riid))
        *ppvObject = &This->IPropertySetStorage_iface;
    else if (IsEqualGUID(&IID_IShellLinkA, riid))
    {
        FIXME("The IShellLinkA interface is not yet supported by InternetShortcut\n");
        return E_NOINTERFACE;
    }
    else if (IsEqualGUID(&IID_IShellLinkW, riid))
    {
        FIXME("The IShellLinkW interface is not yet supported by InternetShortcut\n");
        return E_NOINTERFACE;
    }
    else
    {
        FIXME("Interface with GUID %s not yet implemented by InternetShortcut\n", debugstr_guid(riid));
        return E_NOINTERFACE;
    }
    IUnknown_AddRef((IUnknown*)*ppvObject);
    return S_OK;
}

static ULONG Unknown_AddRef(InternetShortcut *This)
{
    TRACE("(%p)\n", This);
    return InterlockedIncrement(&This->refCount);
}

static ULONG Unknown_Release(InternetShortcut *This)
{
    ULONG count;
    TRACE("(%p)\n", This);
    count = InterlockedDecrement(&This->refCount);
    if (count == 0)
    {
        CoTaskMemFree(This->url);
        CoTaskMemFree(This->currentFile);
        IPropertySetStorage_Release(This->property_set_storage);
        heap_free(This);
        unlock_module();
    }
    return count;
}

static HRESULT WINAPI UniformResourceLocatorW_QueryInterface(IUniformResourceLocatorW *url, REFIID riid, PVOID *ppvObject)
{
    InternetShortcut *This = impl_from_IUniformResourceLocatorW(url);
    TRACE("(%p, %s, %p)\n", url, debugstr_guid(riid), ppvObject);
    return Unknown_QueryInterface(This, riid, ppvObject);
}

static ULONG WINAPI UniformResourceLocatorW_AddRef(IUniformResourceLocatorW *url)
{
    InternetShortcut *This = impl_from_IUniformResourceLocatorW(url);
    TRACE("(%p)\n", url);
    return Unknown_AddRef(This);
}

static ULONG WINAPI UniformResourceLocatorW_Release(IUniformResourceLocatorW *url)
{
    InternetShortcut *This = impl_from_IUniformResourceLocatorW(url);
    TRACE("(%p)\n", url);
    return Unknown_Release(This);
}

static HRESULT WINAPI UniformResourceLocatorW_SetUrl(IUniformResourceLocatorW *url, LPCWSTR pcszURL, DWORD dwInFlags)
{
    WCHAR *newURL = NULL;
    InternetShortcut *This = impl_from_IUniformResourceLocatorW(url);
    TRACE("(%p, %s, 0x%x)\n", url, debugstr_w(pcszURL), dwInFlags);
    if (dwInFlags != 0)
        FIXME("ignoring unsupported flags 0x%x\n", dwInFlags);
    if (pcszURL != NULL)
    {
        newURL = co_strdupW(pcszURL);
        if (newURL == NULL)
            return E_OUTOFMEMORY;
    }
    CoTaskMemFree(This->url);
    This->url = newURL;
    This->isDirty = TRUE;
    return S_OK;
}

static HRESULT WINAPI UniformResourceLocatorW_GetUrl(IUniformResourceLocatorW *url, LPWSTR *ppszURL)
{
    InternetShortcut *This = impl_from_IUniformResourceLocatorW(url);

    TRACE("(%p, %p)\n", url, ppszURL);

    if (!This->url) {
        *ppszURL = NULL;
        return S_FALSE;
    }

    *ppszURL = co_strdupW(This->url);
    if (!*ppszURL)
        return E_OUTOFMEMORY;

    return S_OK;
}

static HRESULT WINAPI UniformResourceLocatorW_InvokeCommand(IUniformResourceLocatorW *url, PURLINVOKECOMMANDINFOW pCommandInfo)
{
    InternetShortcut *This = impl_from_IUniformResourceLocatorW(url);
    WCHAR app[64];
    HKEY hkey;
    static const WCHAR wszURLProtocol[] = {'U','R','L',' ','P','r','o','t','o','c','o','l',0};
    SHELLEXECUTEINFOW sei;
    DWORD res, type;
    HRESULT hres;

    TRACE("%p %p\n", This, pCommandInfo );

    if (pCommandInfo->dwcbSize < sizeof (URLINVOKECOMMANDINFOW))
        return E_INVALIDARG;

    if (pCommandInfo->dwFlags != IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB)
    {
        FIXME("(%p, %p): non-default verbs not implemented\n", url, pCommandInfo);
        return E_NOTIMPL;
    }

    hres = CoInternetParseUrl(This->url, PARSE_SCHEMA, 0, app, sizeof(app)/sizeof(WCHAR), NULL, 0);
    if(FAILED(hres))
        return E_FAIL;

    res = RegOpenKeyW(HKEY_CLASSES_ROOT, app, &hkey);
    if(res != ERROR_SUCCESS)
        return E_FAIL;

    res = RegQueryValueExW(hkey, wszURLProtocol, NULL, &type, NULL, NULL);
    RegCloseKey(hkey);
    if(res != ERROR_SUCCESS || type != REG_SZ)
        return E_FAIL;

    memset(&sei, 0, sizeof(sei));
    sei.cbSize = sizeof(sei);
    sei.lpFile = This->url;
    sei.nShow = SW_SHOW;

    if( ShellExecuteExW(&sei) )
        return S_OK;
    else
        return E_FAIL;
}

static HRESULT WINAPI UniformResourceLocatorA_QueryInterface(IUniformResourceLocatorA *url, REFIID riid, PVOID *ppvObject)
{
    InternetShortcut *This = impl_from_IUniformResourceLocatorA(url);
    TRACE("(%p, %s, %p)\n", url, debugstr_guid(riid), ppvObject);
    return Unknown_QueryInterface(This, riid, ppvObject);
}

static ULONG WINAPI UniformResourceLocatorA_AddRef(IUniformResourceLocatorA *url)
{
    InternetShortcut *This = impl_from_IUniformResourceLocatorA(url);
    TRACE("(%p)\n", url);
    return Unknown_AddRef(This);
}

static ULONG WINAPI UniformResourceLocatorA_Release(IUniformResourceLocatorA *url)
{
    InternetShortcut *This = impl_from_IUniformResourceLocatorA(url);
    TRACE("(%p)\n", url);
    return Unknown_Release(This);
}

static HRESULT WINAPI UniformResourceLocatorA_SetUrl(IUniformResourceLocatorA *url, LPCSTR pcszURL, DWORD dwInFlags)
{
    WCHAR *newURL = NULL;
    InternetShortcut *This = impl_from_IUniformResourceLocatorA(url);
    TRACE("(%p, %s, 0x%x)\n", url, debugstr_a(pcszURL), dwInFlags);
    if (dwInFlags != 0)
        FIXME("ignoring unsupported flags 0x%x\n", dwInFlags);
    if (pcszURL != NULL)
    {
        newURL = co_strdupAtoW(pcszURL);
        if (newURL == NULL)
            return E_OUTOFMEMORY;
    }
    CoTaskMemFree(This->url);
    This->url = newURL;
    This->isDirty = TRUE;
    return S_OK;
}

static HRESULT WINAPI UniformResourceLocatorA_GetUrl(IUniformResourceLocatorA *url, LPSTR *ppszURL)
{
    InternetShortcut *This = impl_from_IUniformResourceLocatorA(url);

    TRACE("(%p, %p)\n", url, ppszURL);

    if (!This->url) {
        *ppszURL = NULL;
        return S_FALSE;

    }

    *ppszURL = co_strdupWtoA(This->url);
    if (!*ppszURL)
        return E_OUTOFMEMORY;

    return S_OK;
}

static HRESULT WINAPI UniformResourceLocatorA_InvokeCommand(IUniformResourceLocatorA *url, PURLINVOKECOMMANDINFOA pCommandInfo)
{
    URLINVOKECOMMANDINFOW wideCommandInfo;
    int len;
    WCHAR *wideVerb;
    HRESULT res;
    InternetShortcut *This = impl_from_IUniformResourceLocatorA(url);

    wideCommandInfo.dwcbSize = sizeof wideCommandInfo;
    wideCommandInfo.dwFlags = pCommandInfo->dwFlags;
    wideCommandInfo.hwndParent = pCommandInfo->hwndParent;

    len = MultiByteToWideChar(CP_ACP, 0, pCommandInfo->pcszVerb, -1, NULL, 0);
    wideVerb = heap_alloc(len * sizeof(WCHAR));
    MultiByteToWideChar(CP_ACP, 0, pCommandInfo->pcszVerb, -1, wideVerb, len);

    wideCommandInfo.pcszVerb = wideVerb;

    res = UniformResourceLocatorW_InvokeCommand(&This->IUniformResourceLocatorW_iface, &wideCommandInfo);
    heap_free(wideVerb);

    return res;
}

static HRESULT WINAPI PersistFile_QueryInterface(IPersistFile *pFile, REFIID riid, PVOID *ppvObject)
{
    InternetShortcut *This = impl_from_IPersistFile(pFile);
    TRACE("(%p, %s, %p)\n", pFile, debugstr_guid(riid), ppvObject);
    return Unknown_QueryInterface(This, riid, ppvObject);
}

static ULONG WINAPI PersistFile_AddRef(IPersistFile *pFile)
{
    InternetShortcut *This = impl_from_IPersistFile(pFile);
    TRACE("(%p)\n", pFile);
    return Unknown_AddRef(This);
}

static ULONG WINAPI PersistFile_Release(IPersistFile *pFile)
{
    InternetShortcut *This = impl_from_IPersistFile(pFile);
    TRACE("(%p)\n", pFile);
    return Unknown_Release(This);
}

static HRESULT WINAPI PersistFile_GetClassID(IPersistFile *pFile, CLSID *pClassID)
{
    TRACE("(%p, %p)\n", pFile, pClassID);
    *pClassID = CLSID_InternetShortcut;
    return S_OK;
}

static HRESULT WINAPI PersistFile_IsDirty(IPersistFile *pFile)
{
    InternetShortcut *This = impl_from_IPersistFile(pFile);
    TRACE("(%p)\n", pFile);
    return This->isDirty ? S_OK : S_FALSE;
}

/* A helper function:  Allocate and fill rString.  Return number of bytes read. */
static DWORD get_profile_string(LPCWSTR lpAppName, LPCWSTR lpKeyName,
                                LPCWSTR lpFileName, WCHAR **rString )
{
    DWORD r = 0;
    DWORD len = 128;
    WCHAR *buffer;

    buffer = CoTaskMemAlloc(len * sizeof(*buffer));
    if (buffer != NULL)
    {
        r = GetPrivateProfileStringW(lpAppName, lpKeyName, NULL, buffer, len, lpFileName);
        while (r == len-1)
        {
            WCHAR *realloc_buf;

            len *= 2;
            realloc_buf = CoTaskMemRealloc(buffer, len * sizeof(*buffer));
            if (realloc_buf == NULL)
            {
                CoTaskMemFree(buffer);
                *rString = NULL;
                return 0;
            }
            buffer = realloc_buf;

            r = GetPrivateProfileStringW(lpAppName, lpKeyName, NULL, buffer, len, lpFileName);
        }
    }

    *rString = buffer;
    return r;
}

static HRESULT WINAPI PersistFile_Load(IPersistFile *pFile, LPCOLESTR pszFileName, DWORD dwMode)
{
    WCHAR str_header[] = {'I','n','t','e','r','n','e','t','S','h','o','r','t','c','u','t',0};
    WCHAR str_URL[] = {'U','R','L',0};
    WCHAR str_iconfile[] = {'i','c','o','n','f','i','l','e',0};
    WCHAR str_iconindex[] = {'i','c','o','n','i','n','d','e','x',0};
    WCHAR *filename = NULL;
    HRESULT hr;
    InternetShortcut *This = impl_from_IPersistFile(pFile);
    TRACE("(%p, %s, 0x%x)\n", pFile, debugstr_w(pszFileName), dwMode);
    if (dwMode != 0)
        FIXME("ignoring unimplemented mode 0x%x\n", dwMode);
    filename = co_strdupW(pszFileName);
    if (filename != NULL)
    {
        DWORD r;
        WCHAR *url;

        r = get_profile_string(str_header, str_URL, pszFileName, &url);

        if (url == NULL)
        {
            hr = E_OUTOFMEMORY;
            CoTaskMemFree(filename);
        }
        else if (r == 0)
        {
            hr = E_FAIL;
            CoTaskMemFree(filename);
        }
        else
        {
            hr = S_OK;
            CoTaskMemFree(This->currentFile);
            This->currentFile = filename;
            CoTaskMemFree(This->url);
            This->url = url;
            This->isDirty = FALSE;
        }

        /* Now we're going to read in the iconfile and iconindex.
           If we don't find them, that's not a failure case -- it's possible
           that they just aren't in there. */
        if (SUCCEEDED(hr))
        {
            IPropertyStorage *pPropStg;
            WCHAR *iconfile;
            WCHAR *iconindexstring;
            hr = IPropertySetStorage_Open(This->property_set_storage, &FMTID_Intshcut,
                                          STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                                          &pPropStg);

            r = get_profile_string(str_header, str_iconfile, pszFileName, &iconfile);
            if (iconfile != NULL)
            {
                PROPSPEC ps;
                PROPVARIANT pv;
                ps.ulKind = PRSPEC_PROPID;
                ps.u.propid = PID_IS_ICONFILE;
                pv.vt = VT_LPWSTR;
                pv.u.pwszVal = iconfile;
                hr = IPropertyStorage_WriteMultiple(pPropStg, 1, &ps, &pv, 0);
                if (FAILED(hr))
                {
                    TRACE("Failed to store the iconfile to our property storage.  hr = 0x%x\n", hr);
                }

                CoTaskMemFree(iconfile);
            }

            r = get_profile_string(str_header, str_iconindex, pszFileName, &iconindexstring);

            if (iconindexstring != NULL)
            {
                int iconindex;
                PROPSPEC ps;
                PROPVARIANT pv;
                char *iconindexastring = co_strdupWtoA(iconindexstring);
                sscanf(iconindexastring, "%d", &iconindex);
                CoTaskMemFree(iconindexastring);
                ps.ulKind = PRSPEC_PROPID;
                ps.u.propid = PID_IS_ICONINDEX;
                pv.vt = VT_I4;
                pv.u.iVal = iconindex;
                hr = IPropertyStorage_WriteMultiple(pPropStg, 1, &ps, &pv, 0);
                if (FAILED(hr))
                {
                    TRACE("Failed to store the iconindex to our property storage.  hr = 0x%x\n", hr);
                }

                CoTaskMemFree(iconindexstring);
            }

            IPropertyStorage_Release(pPropStg);
        }
        else
            hr = E_OUTOFMEMORY;
    }
    else
        hr = E_OUTOFMEMORY;
    return hr;
}

static HRESULT WINAPI PersistFile_Save(IPersistFile *pFile, LPCOLESTR pszFileName, BOOL fRemember)
{
    HRESULT hr = S_OK;
    INT len;
    CHAR *url;
    InternetShortcut *This = impl_from_IPersistFile(pFile);

    TRACE("(%p, %s, %d)\n", pFile, debugstr_w(pszFileName), fRemember);

    if (pszFileName != NULL && fRemember)
    {
        LPOLESTR oldFile = This->currentFile;
        This->currentFile = co_strdupW(pszFileName);
        if (This->currentFile == NULL)
        {
            This->currentFile = oldFile;
            return E_OUTOFMEMORY;
        }
        CoTaskMemFree(oldFile);
    }
    if (This->url == NULL)
        return E_FAIL;

    /* Windows seems to always write:
     *   ASCII "[InternetShortcut]" headers
     *   ASCII names in "name=value" pairs
     *   An ASCII (probably UTF8?) value in "URL=..."
     */
    len = WideCharToMultiByte(CP_UTF8, 0, This->url, -1, NULL, 0, 0, 0);
    url = heap_alloc(len);
    if (url != NULL)
    {
        HANDLE file;
        WideCharToMultiByte(CP_UTF8, 0, This->url, -1, url, len, 0, 0);
        file = CreateFileW(pszFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (file != INVALID_HANDLE_VALUE)
        {
            DWORD bytesWritten;
            char *iconfile;
            char str_header[] = "[InternetShortcut]";
            char str_URL[] = "URL=";
            char str_ICONFILE[] = "ICONFILE=";
            char str_eol[] = "\r\n";
            IPropertyStorage *pPropStgRead;
            PROPSPEC ps[2];
            PROPVARIANT pvread[2];
            ps[0].ulKind = PRSPEC_PROPID;
            ps[0].u.propid = PID_IS_ICONFILE;
            ps[1].ulKind = PRSPEC_PROPID;
            ps[1].u.propid = PID_IS_ICONINDEX;

            WriteFile(file, str_header, lstrlenA(str_header), &bytesWritten, NULL);
            WriteFile(file, str_eol, lstrlenA(str_eol), &bytesWritten, NULL);
            WriteFile(file, str_URL, lstrlenA(str_URL), &bytesWritten, NULL);
            WriteFile(file, url, lstrlenA(url), &bytesWritten, NULL);
            WriteFile(file, str_eol, lstrlenA(str_eol), &bytesWritten, NULL);

            hr = IPropertySetStorage_Open(This->property_set_storage, &FMTID_Intshcut, STGM_READ|STGM_SHARE_EXCLUSIVE, &pPropStgRead);
            if SUCCEEDED(hr)
            {
                hr = IPropertyStorage_ReadMultiple(pPropStgRead, 2, ps, pvread);
                if (hr == S_FALSE)
                {
                    /* None of the properties are present, that's ok */
                    hr = S_OK;
                    IPropertyStorage_Release(pPropStgRead);
                }
                else if SUCCEEDED(hr)
                {
                    char indexString[50];
                    len = WideCharToMultiByte(CP_UTF8, 0, pvread[0].u.pwszVal, -1, NULL, 0, 0, 0);
                    iconfile = heap_alloc(len);
                    if (iconfile != NULL)
                    {
                        WideCharToMultiByte(CP_UTF8, 0, pvread[0].u.pwszVal, -1, iconfile, len, 0, 0);
                        WriteFile(file, str_ICONFILE, lstrlenA(str_ICONFILE), &bytesWritten, NULL);
                        WriteFile(file, iconfile, lstrlenA(iconfile), &bytesWritten, NULL);
                        WriteFile(file, str_eol, lstrlenA(str_eol), &bytesWritten, NULL);
                    }

                    sprintf(indexString, "ICONINDEX=%d", pvread[1].u.iVal);
                    WriteFile(file, indexString, lstrlenA(indexString), &bytesWritten, NULL);
                    WriteFile(file, str_eol, lstrlenA(str_eol), &bytesWritten, NULL);

                    IPropertyStorage_Release(pPropStgRead);
                    PropVariantClear(&pvread[0]);
                    PropVariantClear(&pvread[1]);
                }
                else
                {
                    TRACE("Unable to read properties.\n");
                }
            }
            else
            {
               TRACE("Unable to get the IPropertyStorage.\n");
            }

            CloseHandle(file);
            if (pszFileName == NULL || fRemember)
                This->isDirty = FALSE;
            StartLinkProcessor(pszFileName);
        }
        else
            hr = E_FAIL;
        heap_free(url);
    }
    else
        hr = E_OUTOFMEMORY;

    return hr;
}

static HRESULT WINAPI PersistFile_SaveCompleted(IPersistFile *pFile, LPCOLESTR pszFileName)
{
    FIXME("(%p, %p): stub\n", pFile, pszFileName);
    return E_NOTIMPL;
}

static HRESULT WINAPI PersistFile_GetCurFile(IPersistFile *pFile, LPOLESTR *ppszFileName)
{
    HRESULT hr = S_OK;
    InternetShortcut *This = impl_from_IPersistFile(pFile);
    TRACE("(%p, %p)\n", pFile, ppszFileName);
    if (This->currentFile == NULL)
        *ppszFileName = NULL;
    else
    {
        *ppszFileName = co_strdupW(This->currentFile);
        if (*ppszFileName == NULL)
            hr = E_OUTOFMEMORY;
    }
    return hr;
}

static HRESULT WINAPI PropertySetStorage_QueryInterface(IPropertySetStorage *iface, REFIID riid, PVOID *ppvObject)
{
    InternetShortcut *This = impl_from_IPropertySetStorage(iface);
    TRACE("(%p)\n", iface);
    return Unknown_QueryInterface(This, riid, ppvObject);
}

static ULONG WINAPI PropertySetStorage_AddRef(IPropertySetStorage *iface)
{
    InternetShortcut *This = impl_from_IPropertySetStorage(iface);
    TRACE("(%p)\n", iface);
    return Unknown_AddRef(This);
}

static ULONG WINAPI PropertySetStorage_Release(IPropertySetStorage *iface)
{
    InternetShortcut *This = impl_from_IPropertySetStorage(iface);
    TRACE("(%p)\n", iface);
    return Unknown_Release(This);
}

static HRESULT WINAPI PropertySetStorage_Create(
        IPropertySetStorage* iface,
        REFFMTID rfmtid,
        const CLSID *pclsid,
        DWORD grfFlags,
        DWORD grfMode,
        IPropertyStorage **ppprstg)
{
    InternetShortcut *This = impl_from_IPropertySetStorage(iface);
    TRACE("(%s, %p, 0x%x, 0x%x, %p)\n", debugstr_guid(rfmtid), pclsid, grfFlags, grfMode, ppprstg);

    return IPropertySetStorage_Create(This->property_set_storage,
                                      rfmtid,
                                      pclsid,
                                      grfFlags,
                                      grfMode,
                                      ppprstg);
}

static HRESULT WINAPI PropertySetStorage_Open(
        IPropertySetStorage* iface,
        REFFMTID rfmtid,
        DWORD grfMode,
        IPropertyStorage **ppprstg)
{
    InternetShortcut *This = impl_from_IPropertySetStorage(iface);
    TRACE("(%s, 0x%x, %p)\n", debugstr_guid(rfmtid), grfMode, ppprstg);

    /* Note:  The |STGM_SHARE_EXCLUSIVE is to cope with a bug in the implementation.  Should be fixed in ole32. */
    return IPropertySetStorage_Open(This->property_set_storage,
                                    rfmtid,
                                    grfMode|STGM_SHARE_EXCLUSIVE,
                                    ppprstg);
}

static HRESULT WINAPI PropertySetStorage_Delete(IPropertySetStorage *iface, REFFMTID rfmtid)
{
    InternetShortcut *This = impl_from_IPropertySetStorage(iface);
    TRACE("(%s)\n", debugstr_guid(rfmtid));


    return IPropertySetStorage_Delete(This->property_set_storage,
                                      rfmtid);
}

static HRESULT WINAPI PropertySetStorage_Enum(IPropertySetStorage *iface, IEnumSTATPROPSETSTG **ppenum)
{
    FIXME("(%p): stub\n", ppenum);
    return E_NOTIMPL;
}

static const IUniformResourceLocatorWVtbl uniformResourceLocatorWVtbl = {
    UniformResourceLocatorW_QueryInterface,
    UniformResourceLocatorW_AddRef,
    UniformResourceLocatorW_Release,
    UniformResourceLocatorW_SetUrl,
    UniformResourceLocatorW_GetUrl,
    UniformResourceLocatorW_InvokeCommand
};

static const IUniformResourceLocatorAVtbl uniformResourceLocatorAVtbl = {
    UniformResourceLocatorA_QueryInterface,
    UniformResourceLocatorA_AddRef,
    UniformResourceLocatorA_Release,
    UniformResourceLocatorA_SetUrl,
    UniformResourceLocatorA_GetUrl,
    UniformResourceLocatorA_InvokeCommand
};

static const IPersistFileVtbl persistFileVtbl = {
    PersistFile_QueryInterface,
    PersistFile_AddRef,
    PersistFile_Release,
    PersistFile_GetClassID,
    PersistFile_IsDirty,
    PersistFile_Load,
    PersistFile_Save,
    PersistFile_SaveCompleted,
    PersistFile_GetCurFile
};

static const IPropertySetStorageVtbl propertySetStorageVtbl = {
    PropertySetStorage_QueryInterface,
    PropertySetStorage_AddRef,
    PropertySetStorage_Release,
    PropertySetStorage_Create,
    PropertySetStorage_Open,
    PropertySetStorage_Delete,
    PropertySetStorage_Enum
};

static InternetShortcut *create_shortcut(void)
{
    InternetShortcut *newshortcut;

    newshortcut = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(InternetShortcut));
    if (newshortcut)
    {
        HRESULT hr;
        IPropertyStorage *dummy;

        newshortcut->IUniformResourceLocatorA_iface.lpVtbl = &uniformResourceLocatorAVtbl;
        newshortcut->IUniformResourceLocatorW_iface.lpVtbl = &uniformResourceLocatorWVtbl;
        newshortcut->IPersistFile_iface.lpVtbl = &persistFileVtbl;
        newshortcut->IPropertySetStorage_iface.lpVtbl = &propertySetStorageVtbl;
        newshortcut->refCount = 1;
        hr = StgCreateStorageEx(NULL, STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE,
                                STGFMT_STORAGE, 0, NULL, NULL, &IID_IPropertySetStorage, (void **) &newshortcut->property_set_storage);
        if FAILED(hr)
        {
            TRACE("Failed to create the storage object needed for the shortcut.\n");
            heap_free(newshortcut);
            return NULL;
        }

        hr = IPropertySetStorage_Create(newshortcut->property_set_storage, &FMTID_Intshcut, NULL, PROPSETFLAG_DEFAULT, STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, &dummy);
        if FAILED(hr)
        {
            TRACE("Failed to create the property object needed for the shortcut.\n");
            IPropertySetStorage_Release(newshortcut->property_set_storage);
            heap_free(newshortcut);
            return NULL;
        }
        IPropertySetStorage_Release(dummy);
    }

    return newshortcut;
}

HRESULT WINAPI InternetShortcut_Create(IClassFactory *iface, IUnknown *outer, REFIID riid, void **ppv)
{
    InternetShortcut *This;
    HRESULT hres;

    TRACE("(%p, %s, %p)\n", outer, debugstr_guid(riid), ppv);

    *ppv = NULL;

    if(outer)
        return CLASS_E_NOAGGREGATION;

    This = create_shortcut();
    if(!This)
        return E_OUTOFMEMORY;

    hres = Unknown_QueryInterface(This, riid, ppv);
    Unknown_Release(This);
    return hres;
}


/**********************************************************************
 * OpenURL  (ieframe.@)
 */
void WINAPI OpenURL(HWND hWnd, HINSTANCE hInst, LPCSTR lpcstrUrl, int nShowCmd)
{
    InternetShortcut *shortcut;
    WCHAR* urlfilepath = NULL;
    int len;

    shortcut = create_shortcut();

    if(!shortcut)
        return;

    len = MultiByteToWideChar(CP_ACP, 0, lpcstrUrl, -1, NULL, 0);
    urlfilepath = heap_alloc(len * sizeof(WCHAR));
    MultiByteToWideChar(CP_ACP, 0, lpcstrUrl, -1, urlfilepath, len);

    if(SUCCEEDED(IPersistFile_Load(&shortcut->IPersistFile_iface, urlfilepath, 0))) {
        URLINVOKECOMMANDINFOW ici;

        memset( &ici, 0, sizeof ici );
        ici.dwcbSize = sizeof ici;
        ici.dwFlags = IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB;
        ici.hwndParent = hWnd;

        if(FAILED(UniformResourceLocatorW_InvokeCommand(&shortcut->IUniformResourceLocatorW_iface, (PURLINVOKECOMMANDINFOW) &ici)))
            TRACE("failed to open URL: %s\n", debugstr_a(lpcstrUrl));
    }

    heap_free(urlfilepath);
    Unknown_Release(shortcut);
}