/*
 * isurl.cpp - IUniformResourceLocator implementation for Intshcut class.
 */
#include "priv.h"
#include "ishcut.h"
#include "urlprop.h"
#include "assocurl.h"
#include "shlwapi.h"
#include "infotip.h"
#include "resource.h"
#include <intshctp.h>

#include <mluisupp.h>

#define DM_PLUGGABLE DM_TRACE
#define DM_SHELLEXECOBJECT         0x80000000

extern HRESULT CreateTargetFrame(LPCOLESTR pszTargetName, LPUNKNOWN /*IN,OUT*/ *ppunk);

const TCHAR c_szDefaultVerbSubKeyFmt[]  = TEXT("%s\\Shell");

const TCHAR c_szAppCmdLineFmt[]         = TEXT(" %s");
const TCHAR c_szQuotesAppCmdLineFmt[]   = TEXT(" \"%s\"");


/***************************** Private Functions *****************************/


/* input flags to MyExecute() */

typedef enum myexecute_in_flags
{
    /*
     * Adds double quotes around the given argument string on the generated
     * command line if the argument string contains any white space.
     */

    ME_IFL_QUOTE_ARGS    = 0x0001,

    /* flag combinations */

    ALL_ME_IN_FLAGS      = ME_IFL_QUOTE_ARGS
}
MYEXECUTE_IN_FLAGS;


/*----------------------------------------------------------
Purpose: Calls CreateProcess() politely

Returns:
Cond:    --
*/
HRESULT
MyExecute(
    LPCTSTR pcszApp,
    LPCTSTR pcszArgs,
    DWORD dwInFlags)
{
    HRESULT hr;
    TCHAR szFullApp[MAX_PATH];

    ASSERT(IS_VALID_STRING_PTR(pcszApp, -1));
    ASSERT(IS_VALID_STRING_PTR(pcszArgs, -1));
    ASSERT(FLAGS_ARE_VALID(dwInFlags, ALL_ME_IN_FLAGS));

    hr = PathSearchAndQualify(pcszApp, szFullApp, SIZECHARS(szFullApp));
    if (hr == S_OK)
    {
        DWORD cbSize;
        LPTSTR pszCmdLine;

        // (+ 1) for null terminator.
        cbSize = max(SIZEOF(c_szAppCmdLineFmt),
                         SIZEOF(c_szQuotesAppCmdLineFmt)) +
                         + CbFromCch(lstrlen(szFullApp) + lstrlen(pcszArgs) + 1);

        pszCmdLine = (LPTSTR)LocalAlloc(LPTR, cbSize);

        if (pszCmdLine)
        {
            LPCTSTR pcszFmt;
            STARTUPINFO si;
            PROCESS_INFORMATION pi;

            // Execute URL via one-shot app.
            pcszFmt = (IsFlagSet(dwInFlags, ME_IFL_QUOTE_ARGS) &&
                       StrPBrk(pcszArgs, TEXT(" \t")) != NULL)
                       ? c_szQuotesAppCmdLineFmt : c_szAppCmdLineFmt;

            wnsprintf(pszCmdLine, cbSize / sizeof(TCHAR), pcszFmt, pcszArgs);

            ZeroMemory(&si, SIZEOF(si));
            si.cb = SIZEOF(si);

            // Specify command line exactly as given to app.
            if (CreateProcess(szFullApp, pszCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
            {
                CloseHandle(pi.hProcess);
                CloseHandle(pi.hThread);

                hr = S_OK;

                TraceMsg(TF_INTSHCUT, "MyExecute(): CreateProcess() \"%s\" succeeded.", pszCmdLine);
            }
            else
            {
                hr = E_FAIL;

                TraceMsg(TF_WARNING, "MyExecute(): CreateProcess() \"%s\" failed.", pszCmdLine);
            }

            LocalFree(pszCmdLine);
            pszCmdLine = NULL;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "MyExecute(): Unable to find app %s.", pcszApp);
    }

    return(hr);
}


/*----------------------------------------------------------
Purpose: Returns TRUE if the given internet shortcut points
         to a website (as opposed to an ftp site, etc).

Returns: see above
*/
BOOL IsWebsite(IN Intshcut * pintshcut)
{
    ASSERT(pintshcut);
    
    //   (scotth): we are assuming that file: schemes are
    //   generally web pages.  This is not true.  For file: schemes,
    //   we should first verify that it is an htm filetype.)
    
    return (URL_SCHEME_HTTP == pintshcut->GetScheme() ||
        URL_SCHEME_FILE == pintshcut->GetScheme());
}



BOOL
GetClassDefaultVerb(
    LPCTSTR pcszClass,
    LPTSTR  pszDefaultVerbBuf,
    UINT    cchBufLen)
{
    // No; get the default verb
    TCHAR szKey[MAX_PATH];

    StrCpyN(szKey, pcszClass, SIZECHARS(szKey));
    StrCatBuff(szKey, TEXT("\\"), SIZECHARS(szKey));
    StrCatBuff(szKey, TEXT("shell"), SIZECHARS(szKey));
    DWORD cbSize = CbFromCch(cchBufLen);

    if (NO_ERROR != SHGetValue(HKEY_CLASSES_ROOT, szKey, NULL, NULL, pszDefaultVerbBuf, &cbSize) 
    || !*pszDefaultVerbBuf)
    {
        // Default to "open" if the registry doesn't specify one
        StrCpyN(pszDefaultVerbBuf, TEXT("open"), cchBufLen);
    }

    return TRUE;
}


#ifdef DEBUG

BOOL
IsValidPCPARSEDURL(
    LPCTSTR pcszURL,
    PCPARSEDURL pcpu)
{
    return(IS_VALID_READ_PTR(pcpu, CPARSEDURL) &&
           (IS_VALID_STRING_PTR(pcpu->pszProtocol, -1) &&
            EVAL(IsStringContained(pcszURL, pcpu->pszProtocol)) &&
            EVAL(pcpu->cchProtocol < (UINT)lstrlen(pcpu->pszProtocol))) &&
           (IS_VALID_STRING_PTR(pcpu->pszSuffix, -1) &&
            EVAL(IsStringContained(pcszURL, pcpu->pszSuffix)) &&
            EVAL(pcpu->cchSuffix <= (UINT)lstrlen(pcpu->pszSuffix))) &&
           EVAL(pcpu->cchProtocol + pcpu->cchSuffix < (UINT)lstrlen(pcszURL)));
}


BOOL
IsValidPCURLINVOKECOMMANDINFO(
    PCURLINVOKECOMMANDINFO pcurlici)
{
    return(IS_VALID_READ_PTR(pcurlici, CURLINVOKECOMMANDINFO) &&
           EVAL(pcurlici->dwcbSize >= SIZEOF(*pcurlici)) &&
           FLAGS_ARE_VALID(pcurlici->dwFlags, ALL_IURL_INVOKECOMMAND_FLAGS) &&
           (IsFlagClear(pcurlici->dwFlags, IURL_INVOKECOMMAND_FL_ALLOW_UI) ||
            NULL == pcurlici->hwndParent || 
            IS_VALID_HANDLE(pcurlici->hwndParent, WND)) &&
           (IsFlagSet(pcurlici->dwFlags, IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB) ||
            IS_VALID_STRING_PTR(pcurlici->pcszVerb, -1)));
}

#endif

BOOL IsValidProtocolChar(TCHAR ch)
{
    if ((ch>=TEXT('a') && ch<=TEXT('z')) ||
        (ch>=TEXT('A') && ch<=TEXT('Z')) ||
        (ch>=TEXT('0') && ch<=TEXT('9')) ||
        (ch == TEXT('+')) ||
        (ch == TEXT('-')) ||
        (ch == TEXT('.'))   )
    {
        return TRUE;
    }
    return FALSE;
}


/********************************** Methods **********************************/

typedef struct
{
    UINT idsVerb;
    UINT idsMenuHelp;
    LPCTSTR pszVerb;
} ISCM;

const static ISCM g_rgiscm[] =
{
    { IDS_MENUOPEN,         IDS_MH_OPEN,            TEXT("open") },         //  IDCMD_ISCM_OPEN 
    { IDS_SYNCHRONIZE,      IDS_MH_SYNCHRONIZE,     TEXT("update now")},    //  IDCMD_ISCM_SYNC 
    { IDS_MAKE_OFFLINE,     IDS_MH_MAKE_OFFLINE,    TEXT("subscribe")},     //  IDCMD_ISCM_SUB  
};

//  WARNING - these must match their index into g_rgiscm
#define IDCMD_ISCM_OPEN   0
#define IDCMD_ISCM_SYNC   1
#define IDCMD_ISCM_SUB    2

BOOL _IsSubscribed(LPCWSTR pszUrl, BOOL *pfSubscribable)
{
    BOOL fRet = FALSE;
    ISubscriptionMgr * pMgr;
    
    *pfSubscribable = FALSE;
    
    if (SUCCEEDED(CoCreateInstance(CLSID_SubscriptionMgr, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(ISubscriptionMgr, &pMgr))))
    {
        pMgr->IsSubscribed(pszUrl, &fRet);

                        
        pMgr->Release();
    }

    if (!fRet)
    {
        //test if we CAN subscribe to this thing
        if (!SHRestricted2W(REST_NoAddingSubscriptions, pszUrl, 0) &&
            IsFeaturePotentiallyAvailable(CLSID_SubscriptionMgr))
        {
            *pfSubscribable = IsSubscribableW(pszUrl);
        }
    }
    else
        *pfSubscribable = TRUE;
    
    return fRet;
}

void _InsertISCM(UINT indexISCM, HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT uFlags)
{
    TCHAR szMenu[CCH_MENUMAX];
    uFlags |= MF_BYPOSITION | MF_STRING;

    MLLoadShellLangString(g_rgiscm[indexISCM].idsVerb, szMenu, SIZECHARS(szMenu));
    InsertMenu_PrivateNoMungeW(hmenu, indexMenu, uFlags, idCmdFirst + indexISCM, szMenu);
}

// IContextMenu::QueryContextMenu handler for Intshcut
// The context menu handler adds the open verb for .url
// files.  This is because we remove the shell\open\command
// key in Nashville for this file type.

STDMETHODIMP Intshcut::QueryContextMenu(
    IN HMENU hmenu,
    IN UINT  indexMenu,
    IN UINT  idCmdFirst,
    IN UINT  idCmdLast,
    IN UINT  uFlags)
{
    //
    //  LEGACY - .URL files have to maintain an open verb in the registry - ZekeL - 14-APR-99
    //  we would like to just use the "open" verb here in the context menu extension,
    //  but we need to not duplicate the open verb that is added by DefCM
    //  on NT5+ shell32 we disable that verb so we can add it here.
    //  on earlier shell32 we want to add "open" any time we arent
    //  initialized by DefCM.  if we think that DefCM added us, 
    //  then we go ahead and allow the DefCM's open from the registry.
    //
    if (!m_fProbablyDefCM || GetUIVersion() >= 5)
    {
        _InsertISCM(IDCMD_ISCM_OPEN, hmenu, indexMenu, idCmdFirst, 0);
        if (-1 == GetMenuDefaultItem(hmenu, MF_BYCOMMAND, 0))
            SetMenuDefaultItem(hmenu, indexMenu, MF_BYPOSITION);
        indexMenu++;
    }

#ifndef UNIX
    /* v-sriran: 12/8/97
     * disabling the context menu item for subscribe, separators etc.
     * because we are not supporting subscriptions right now
     */

    // skip this if we only want default or if there is no room for more.
    if (!(uFlags & CMF_DEFAULTONLY) && (idCmdLast - idCmdFirst >= ARRAYSIZE(g_rgiscm)))
    {
        WCHAR *pwszURL;
        if (SUCCEEDED(GetURLW(&pwszURL)))
        {
            BOOL bSubscribable = FALSE;             //can be subscribed to
            BOOL bSub = _IsSubscribed(pwszURL, &bSubscribable);
            m_bCheckForDelete = bSub && m_pszFile;

            if (bSubscribable || bSub)
            {
                //  add a separator for our subscription stuff
                InsertMenu(hmenu, indexMenu++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);
                UINT uMenuFlags = 0;

                if (bSub)
                {
                    uMenuFlags |= MF_CHECKED;

                    if (SHRestricted2W(REST_NoRemovingSubscriptions, pwszURL, 0))
                    {
                        uMenuFlags |= MF_GRAYED;
                    }
                }

                _InsertISCM(IDCMD_ISCM_SUB, hmenu, indexMenu++, idCmdFirst, uMenuFlags);

                if (bSub)
                {
                    uMenuFlags = 0;

                    if (SHRestricted2W(REST_NoManualUpdates, NULL, 0))
                    {
                        uMenuFlags |= MF_GRAYED;
                    }
                    _InsertISCM(IDCMD_ISCM_SYNC, hmenu, indexMenu++, idCmdFirst, uMenuFlags);
                } 
            }
            
            SHFree(pwszURL);
        }
    }

#endif /* UNIX */

    return ResultFromShort(ARRAYSIZE(g_rgiscm));
}

STDMETHODIMP Intshcut::InvokeCommand(IN LPCMINVOKECOMMANDINFO pici)
{
    HRESULT hres = E_INVALIDARG;

    ASSERT(pici);

    if (pici && SIZEOF(*pici) <= pici->cbSize)
    {
        UINT idCmd;

        if (0 == HIWORD(pici->lpVerb))      // Is the ID cmd given?
        {
            idCmd = LOWORD(pici->lpVerb);   // Yes

            //  Old versions of ShellExec() didnt get the right default command - Zekel - 15-MAR-99
            //  since our QCM implementation doesnt add anything to the menu
            //  if we fix the QCM to work correctly, then this problem will go away.
            //  it sent 0xfffe instead.  so just adjust here.
            if (idCmd == 0xfffe && GetUIVersion() <= 4)
                idCmd = IDCMD_ISCM_OPEN;
        }
        else
        {
            // No; a language-independent verb was supplied
            int i;
            LPCTSTR pszVerb;
            LPCMINVOKECOMMANDINFOEX piciex = (LPCMINVOKECOMMANDINFOEX)pici;
            ASSERT(SIZEOF(*piciex) <= piciex->cbSize);

            WCHAR szVerb[40];

            if (piciex->lpVerbW)
            {
                pszVerb = piciex->lpVerbW;
            }
            else
            {
                if (piciex->lpVerb)
                {
                    ASSERT(lstrlenA(piciex->lpVerb) < ARRAYSIZE(szVerb));
                    SHAnsiToUnicode(piciex->lpVerb, szVerb, ARRAYSIZE(szVerb));    
                }
                else
                {
                    szVerb[0] = L'\0';
                }
                    
                pszVerb = szVerb;
            }

            idCmd = (UINT)-1;
            for (i = 0; i < ARRAYSIZE(g_rgiscm); i++)
            {
                if (0 == StrCmpI(g_rgiscm[i].pszVerb, pszVerb))
                {
                    idCmd = i;
                    break;
                }
            }
        }

        switch (idCmd)
        {
        case IDCMD_ISCM_OPEN: 
            {
                URLINVOKECOMMANDINFO urlici;

                urlici.dwcbSize = SIZEOF(urlici);
                urlici.hwndParent = pici->hwnd;
                urlici.pcszVerb = NULL;
                urlici.dwFlags = IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB;

                if (IsFlagClear(pici->fMask, CMIC_MASK_FLAG_NO_UI))
                {
                    SetFlag(urlici.dwFlags, IURL_INVOKECOMMAND_FL_ALLOW_UI);
                }
                if (IsFlagSet(pici->fMask, SEE_MASK_FLAG_DDEWAIT))
                {
                    SetFlag(urlici.dwFlags, IURL_INVOKECOMMAND_FL_DDEWAIT);
                }
                hres = InvokeCommand(&urlici);
                m_bCheckForDelete = FALSE;
            }
            break;

        case IDCMD_ISCM_SUB:
        case IDCMD_ISCM_SYNC:
        {
            hres = S_OK;

            WCHAR *pwszURL;
            if (SUCCEEDED(GetURLW(&pwszURL)))
            {
                ISubscriptionMgr * pMgr;
                if (SUCCEEDED(JITCoCreateInstance(CLSID_SubscriptionMgr, 
                                                  NULL, 
                                                  CLSCTX_INPROC_SERVER, 
                                                  IID_PPV_ARG(ISubscriptionMgr, &pMgr),
                                                  pici->hwnd,
                                                  FIEF_FLAG_FORCE_JITUI))) 
                {
                    if (idCmd == IDCMD_ISCM_SUB)  
                    {
                        BOOL bSubscribed;

                        pMgr->IsSubscribed(pwszURL, &bSubscribed);

                        if (!bSubscribed)
                        {
                            SHFILEINFO  sfi = {0};
                            WCHAR wszName[MAX_PATH];
                            wszName[0] = 0;
                            if (SHGetFileInfo(m_pszFile, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME))
                            {
                                SHTCharToUnicode(sfi.szDisplayName, wszName, ARRAYSIZE(wszName));
                            }

                            if (!wszName[0])
                                StrCpyNW(wszName, pwszURL, ARRAYSIZE(wszName));

                            //all subscriptions to local .urls are treated as subscribing something
                            //that's already in Favorites, so user isn't forced to add it to their
                            //favorites as they subscribe.
                            if (SUCCEEDED(pMgr->CreateSubscription(pici->hwnd, pwszURL, wszName,
                                                                   CREATESUBS_FROMFAVORITES, 
                                                                   SUBSTYPE_URL, 
                                                                   NULL)))
                            {
                                pMgr->UpdateSubscription(pwszURL);
                            }
                        }
                        else
                        {
                            pMgr->DeleteSubscription(pwszURL, pici->hwnd);
                        }
                    } 
                    else if (idCmd == IDCMD_ISCM_SYNC)
                    {
                        pMgr->UpdateSubscription(pwszURL);
                    }
                    pMgr->Release();    
                }
                SHFree(pwszURL);
                m_bCheckForDelete = FALSE;
            }
            break;
        }

        default:
            hres = E_INVALIDARG;
            break;
        }
    }

    return hres;
}


/*----------------------------------------------------------
Purpose: IContextMenu::GetCommandString handler for Intshcut

*/
STDMETHODIMP Intshcut::GetCommandString(
    IN     UINT_PTR idCmd,
    IN     UINT     uType,
    IN OUT UINT*    puReserved,
    IN OUT LPSTR    pszName,
    IN     UINT     cchMax)
{
    HRESULT hres;
    TCHAR szMenu[CCH_MENUMAX];

    ASSERT(NULL == puReserved);
    ASSERT(IS_VALID_WRITE_BUFFER(pszName, char, cchMax));

    switch (uType)
    {
    case GCS_HELPTEXTA:
    case GCS_HELPTEXTW:
        if (idCmd < ARRAYSIZE(g_rgiscm))
        {
            MLLoadString(g_rgiscm[idCmd].idsMenuHelp, szMenu, SIZECHARS(szMenu));

            if (GCS_HELPTEXTA == uType)
            {
                UnicodeToAnsi(szMenu, pszName, cchMax);
            }
            else
            {
                StrCpyN((LPWSTR)pszName, szMenu, cchMax);
            }
            hres = NOERROR;
        }
        else
        {
            ASSERT(0);
            hres = E_INVALIDARG;
        }
        break;

    case GCS_VALIDATEA:
    case GCS_VALIDATEW:
        hres = idCmd < ARRAYSIZE(g_rgiscm) ? S_OK : S_FALSE;
        break;

    case GCS_VERBA:
    case GCS_VERBW:
        if (idCmd < ARRAYSIZE(g_rgiscm))
        {
            LPCTSTR pszVerb = g_rgiscm[idCmd].pszVerb;

            if (GCS_VERBA == uType)
            {
                UnicodeToAnsi(pszVerb, pszName, cchMax);
            }
            else
            {
                StrCpyN((LPWSTR)pszName, pszVerb, cchMax);
            }
            hres = NOERROR;
        }
        else
        {
            ASSERT(0);
            hres = E_INVALIDARG;
        }
        break;

    default:
        hres = E_NOTIMPL;
        break;
    }

    return hres;
}


// IContextMenu2::HandleMenuMsg handler for Intshcut
STDMETHODIMP Intshcut::HandleMenuMsg(IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam)
{
    return S_OK;
}


/*----------------------------------------------------------
Purpose: Bring up UI to ask the user what to associate this 
         URL protocol to.

*/
STDMETHODIMP
Intshcut::RegisterProtocolHandler(
    HWND hwndParent,
    LPTSTR pszAppBuf,
    UINT cchBuf)
{
    HRESULT hr;
    DWORD dwFlags = 0;
    TCHAR szURL[MAX_URL_STRING];

    ASSERT(! hwndParent ||
           IS_VALID_HANDLE(hwndParent, WND));
    ASSERT(IS_VALID_WRITE_BUFFER(pszAppBuf, TCHAR, cchBuf));
    ASSERT(m_pprop);

    hr = InitProp();
    if (SUCCEEDED(hr))
    {
        hr = m_pprop->GetProp(PID_IS_URL, szURL, SIZECHARS(szURL));
        ASSERT(S_OK == hr);
        
        SetFlag(dwFlags, URLASSOCDLG_FL_REGISTER_ASSOC);

        if (! m_pszFile)
            SetFlag(dwFlags, URLASSOCDLG_FL_USE_DEFAULT_NAME);

        hr = AssociateURL(hwndParent, dwFlags, m_pszFile, szURL, pszAppBuf, cchBuf);

        switch (hr)
        {
        case S_FALSE:
            TraceMsg(TF_INTSHCUT, "Intshcut::RegisterProtocolHandler(): One time execution of %s via %s requested.",
                        szURL, pszAppBuf);
            break;

        case S_OK:
            TraceMsg(TF_INTSHCUT, "Intshcut::RegisterProtocolHandler(): Protocol handler registered for %s.",
                        szURL);
            break;

        default:
            ASSERT(FAILED(hr));
            break;
        }

        ASSERT(! cchBuf ||
               (IS_VALID_STRING_PTR(pszAppBuf, -1) &&
                (UINT)lstrlen(pszAppBuf) < cchBuf));
    }
    return(hr);
}


// Returns the protocol scheme value (URL_SCHEME_*).

STDMETHODIMP_(DWORD)
Intshcut::GetScheme(void)
{
    DWORD dwScheme = URL_SCHEME_UNKNOWN;

    if (SUCCEEDED(InitProp()))
    {
        m_pprop->GetProp(PID_IS_SCHEME, &dwScheme);
    }
    return dwScheme;
}


// IUniformResourceLocator::SetURL handler for Intshcut
//
// Note:
//    1. SetURL clears the IDList, so that when we launch this shortcut,
//        we will use the URL.

STDMETHODIMP
Intshcut::SetURL(
    IN LPCTSTR pszURL,      OPTIONAL
    IN DWORD   dwFlags)
{
    HRESULT hres = E_FAIL;

    ASSERT(IS_VALID_STRUCT_PTR(this, CIntshcut));
    ASSERT(! pszURL ||
           IS_VALID_STRING_PTR(pszURL, -1));
    ASSERT(FLAGS_ARE_VALID(dwFlags, ALL_IURL_SETURL_FLAGS));

    hres = InitProp();
    if (SUCCEEDED(hres))
    {
        hres = m_pprop->SetURLProp(pszURL, dwFlags);
        if (SUCCEEDED(hres))
        {
            // if the path was set successfully, clear the pidl.
            m_pprop->SetIDListProp(NULL);
        }
    }

    return hres;
}



/*----------------------------------------------------------
Purpose: IUniformResourceLocatorA::SetURL handler for Intshcut

         Ansi version

*/
STDMETHODIMP
Intshcut::SetURL(
    IN LPCSTR pcszURL,      OPTIONAL
    IN DWORD  dwInFlags)
{
    if ( !pcszURL )
    {
        return SetURL((LPCTSTR)NULL, dwInFlags);
    }
    else
    {
        WCHAR wszURL[MAX_URL_STRING];

        ASSERT(IS_VALID_STRING_PTRA(pcszURL, -1));

        AnsiToUnicode(pcszURL, wszURL, SIZECHARS(wszURL));

        return SetURL(wszURL, dwInFlags);
    }
}


STDMETHODIMP Intshcut::GetURLW(WCHAR **ppwsz)
{
    LPTSTR  pszURL;
    HRESULT hres = GetURL(&pszURL);
    if (S_OK == hres)
    {
        hres = SHStrDup(pszURL, ppwsz);
        SHFree(pszURL);
    }
    else
        hres = E_FAIL;  // map S_FALSE to FAILED()
    return hres;
}

// IUniformResourceLocator::GetURL handler for Intshcut

STDMETHODIMP Intshcut::GetURL(LPTSTR * ppszURL)
{
    HRESULT hres;
    TCHAR szURL[MAX_URL_STRING];

    ASSERT(IS_VALID_STRUCT_PTR(this, CIntshcut));
    ASSERT(IS_VALID_WRITE_PTR(ppszURL, PTSTR));

    *ppszURL = NULL;

    hres = InitProp();
    if (SUCCEEDED(hres))
    {
        hres = m_pprop->GetProp(PID_IS_URL, szURL, SIZECHARS(szURL));
        if (S_OK == hres)
        {
            // (+ 1) for null terminator.
            int cch = lstrlen(szURL) + 1;
            *ppszURL = (PTSTR)SHAlloc(CbFromCch(cch));
            if (*ppszURL)
                StrCpyN(*ppszURL, szURL, cch);
            else
                hres = E_OUTOFMEMORY;
        }
    }

    ASSERT(IS_VALID_STRUCT_PTR(this, CIntshcut));
    ASSERT((hres == S_OK &&
            IS_VALID_STRING_PTR(*ppszURL, -1)) ||
           ((hres == S_FALSE ||
             hres == E_OUTOFMEMORY) &&
            ! *ppszURL));

    return hres;
}



/*----------------------------------------------------------
Purpose: IUniformResourceLocatorA::GetURL handler for Intshcut

         Ansi version

*/
STDMETHODIMP Intshcut::GetURL(LPSTR * ppszURL)
{
    HRESULT hres;
    TCHAR szURL[MAX_URL_STRING];

    ASSERT(IS_VALID_WRITE_PTR(ppszURL, PSTR));

    *ppszURL = NULL;

    hres = InitProp();
    if (SUCCEEDED(hres))
    {
        hres = m_pprop->GetProp(PID_IS_URL, szURL, SIZECHARS(szURL));

        if (S_OK == hres)
        {
            DWORD cch = WideCharToMultiByte(CP_ACP, 0, szURL, -1, NULL, 0, NULL, NULL);
            *ppszURL = (LPSTR)SHAlloc(CbFromCchA(cch + 1));

            if (*ppszURL)
                UnicodeToAnsi(szURL, *ppszURL, cch);
            else
                hres = E_OUTOFMEMORY;
        }
    }

    return hres;
}


HRESULT HandlePluggableProtocol(LPCTSTR pszURL, LPCTSTR pszProtocol)
{
    HRESULT hres = E_UNEXPECTED;
    HKEY hkey;
    TraceMsg(DM_PLUGGABLE, "HandlePluggableProtocol called");

    if (RegOpenKey(HKEY_CLASSES_ROOT, TEXT("PROTOCOLS\\Handler"), &hkey) == ERROR_SUCCESS) {
        HKEY hkeyProtocol;
        if (RegOpenKey(hkey, pszProtocol, &hkeyProtocol) == ERROR_SUCCESS) {
            TraceMsg(DM_PLUGGABLE, "HandlePluggableProtocol found %s", pszProtocol);
            IUnknown* punk = NULL; // CreateTargetFrame's ppunk is [IN][OUT]
            hres = CreateTargetFrame(NULL, &punk);
            if (SUCCEEDED(hres)) {
                IWebBrowser2* pauto;
                hres = punk->QueryInterface(IID_IWebBrowser2, (LPVOID*)&pauto);
                if (SUCCEEDED(hres))
                {
                    TraceMsg(DM_PLUGGABLE, "HandlePluggableProtocol calling navigate with %s", pszURL);

                    LBSTR::CString          strUrl;

                    LPTSTR          pstrUrl = strUrl.GetBuffer( MAX_URL_STRING );

                    if ( strUrl.GetAllocLength() < MAX_URL_STRING )
                    {
                        TraceMsg( TF_WARNING, "HandlePluggableProtocol() - strUrl Allocation Failed!" );

                        strUrl.Empty();
                    }
                    else
                    {
                        SHTCharToUnicode( pszURL, pstrUrl, MAX_URL_STRING );

                        // Let CString class own the buffer again.
                        strUrl.ReleaseBuffer();
                    }

                    pauto->Navigate( strUrl, PVAREMPTY, PVAREMPTY, PVAREMPTY, PVAREMPTY );
                    pauto->put_Visible(TRUE);
                    pauto->Release();
                }
                punk->Release();
            }
            RegCloseKey(hkeyProtocol);
        } else {
            TraceMsg(DM_WARNING, "HandlePluggableProtocol can't find %s", pszProtocol);
        }
        RegCloseKey(hkey);
    } else {
        ASSERT(0);
    }
    return hres;
}

HRESULT _IEExecFile_TryRunningWindow(VARIANT *pvarIn, DWORD cid)
{
    HRESULT hr = E_FAIL;
    ASSERT(pvarIn);

    IShellWindows *psw = WinList_GetShellWindows(TRUE);
    if (psw)
    {
        IUnknown *punk;
        if (SUCCEEDED(psw->_NewEnum(&punk)))
        {
            VARIANT var = {0};
            IEnumVARIANT *penum;

            //
            //  its too bad _NewEnum doesnt return an penum....
            //  this should never fail.
            //
            punk->QueryInterface(IID_PPV_ARG(IEnumVARIANT, &penum));
            ASSERT(penum);

            //
            //  this can be super spendy since every one of these
            //  items is marshalled.
            //
            //  should we clone the stream here??
            //
            while (FAILED(hr) && S_OK == penum->Next(1, &var, NULL))
            {
                ASSERT(var.vt == VT_DISPATCH);
                ASSERT(var.pdispVal);
                IOleCommandTarget *poct;
                
                if (SUCCEEDED(var.pdispVal->QueryInterface(IID_PPV_ARG(IOleCommandTarget, &poct))))
                {
                    CoAllowSetForegroundWindow(poct, NULL);
                    
                    hr = poct->Exec(&CGID_Explorer, cid, 0, pvarIn, NULL);

                    poct->Release();
                }
                
                //  this should release the pdisp
                VariantClear(&var);
            }

            punk->Release();
            penum->Release();
        }
        
        psw->Release();
    }


    TraceMsgW(DM_SHELLEXECOBJECT, "IEExecFile_Running returns 0x%X", hr);
    return hr;
}

BOOL IsIESchemeHandler(LPTSTR pszVerb, LPTSTR pszScheme)
{
    //  if we fail to get any value at all, the we must assume that it
    //  is some protocol like about: or res: that is not in the registry
    //  so we default to success.
    BOOL fRet = FALSE;
    TCHAR szExe[MAX_PATH];

    if (SUCCEEDED(AssocQueryString(0, ASSOCSTR_EXECUTABLE, pszScheme, pszVerb, szExe, (LPDWORD)MAKEINTRESOURCE(SIZECHARS(szExe)))))
    {
        //  if we find something and it aint us, then fail.
        if ((StrStrI(szExe, TEXT("iexplore.exe")) || StrStrI(szExe, TEXT("explorer.exe"))))
        {
            fRet = TRUE;

            TraceMsg(DM_SHELLEXECOBJECT, "IsIEScheme() found %s", szExe);
        }
    }
    else
    {
        // these are unregistered schemes, we are the only ones that 
        //  should ever even use the unregistered schemes like
        //  res: or shell: so return TRUE here too.
        fRet = *pszScheme && *pszScheme != TEXT('.');
    }
    
    TraceMsg(DM_SHELLEXECOBJECT, "IsIEScheme() returns %d for %s", fRet, pszScheme);
    return fRet;
}    

HRESULT IEExecFile(LPTSTR pszVerb, LPTSTR pszScheme, DWORD cid, LPTSTR pszPath)
{
    HRESULT hr = E_FAIL;
    ASSERT(pszVerb);
    ASSERT(pszScheme);
    ASSERT(pszPath);
    
    if (IsIESchemeHandler(pszVerb, pszScheme))
    {
        VARIANT varIn = {0};
        varIn.vt = VT_BSTR;

        SHSTRW str;
        str.SetStr(pszPath);
        varIn.bstrVal = SysAllocString(str.GetStr());
        if (varIn.bstrVal)
        {
            if (!SHRegGetBoolUSValue(REGSTR_PATH_MAIN, TEXT("AllowWindowReuse"), FALSE, TRUE)
            || FAILED(hr = _IEExecFile_TryRunningWindow(&varIn, cid)))
            {
                IOleCommandTarget *poct;
    
                if (SUCCEEDED(CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_ALL, 
                        IID_PPV_ARG(IOleCommandTarget, &poct))))
                {
                    hr = poct->Exec(&CGID_Explorer, cid, 0, &varIn, NULL);
                    poct->Release();
                }
            }

            SysFreeString(varIn.bstrVal);
        }

    }

    TraceMsg(DM_SHELLEXECOBJECT, "IEExecFile returns 0x%X for %s", hr, pszPath);

    return hr;
}
                
            
/*----------------------------------------------------------
Purpose: IUniformResourceLocator::InvokeCommand for Intshcut

Note:
    1. If the internet shortcut comes with a pidl, use it to ShellExec,
        otherwise use the URL.

*/
STDMETHODIMP Intshcut::InvokeCommand(PURLINVOKECOMMANDINFO purlici)
{
    HRESULT hr = E_INVALIDARG;
    BOOL bExecFailedWhine = FALSE;

    ASSERT(IS_VALID_STRUCT_PTR(this, CIntshcut));
    ASSERT(IS_VALID_STRUCT_PTR(purlici, CURLINVOKECOMMANDINFO));

    if (purlici && EVAL(SIZEOF(*purlici) == purlici->dwcbSize))
    {
        //
        // App compat.  Don't use stack space for the URL.  We use up 16-bit app
        // stack space when we they shell exec urls.
        //

        LPWSTR pszURL = (LPWSTR)LocalAlloc(LPTR, MAX_URL_STRING * sizeof(WCHAR));

        if (pszURL)
        {
            hr = InitProp();
            if (SUCCEEDED(hr))
            {
                //
                // App Compat: Don't use up stack space.
                //

                LPWSTR pszT = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));

                if (pszT)
                {
                    SHELLEXECUTEINFO sei = {0};
                    LPITEMIDLIST pidl = NULL;
                    LPTSTR pszProtocol = NULL;
                    PARSEDURL pu;
                    pu.nScheme = 0; // init to avoid bogus C4701 warning

                    sei.fMask = SEE_MASK_NO_HOOKS;


                    // check if we have a pidl for the target.        
                    hr = GetIDListInternal(&pidl);
                    if ((hr == S_OK) && pidl)
                    {
                        // yse, use the pidl to ShellExec.
                        sei.fMask |= SEE_MASK_INVOKEIDLIST;
                        sei.lpIDList = pidl;
                    }
                    else
                    {
                        // no, get the URL and invoke class handler.
                        hr = InitProp();
                        if (SUCCEEDED(hr))
                        {
                            hr = m_pprop->GetProp(PID_IS_URL, pszURL, MAX_URL_STRING);
                        }
                        if (S_OK == hr)
                        {
                            hr = CopyURLProtocol(pszURL, &pszProtocol, &pu);
               
                            if (hr == S_OK)
                            {
                                hr = IsProtocolRegistered(pszProtocol);
               
                                if (hr == URL_E_UNREGISTERED_PROTOCOL &&
                                    IsFlagSet(purlici->dwFlags, IURL_INVOKECOMMAND_FL_ALLOW_UI))
                                {
                                    TraceMsg(TF_INTSHCUT, "Intshcut::InvokeCommand(): Unregistered URL protocol %s.  Invoking URL protocol handler association dialog.",
                                               pszProtocol);
               
                                    hr = RegisterProtocolHandler(purlici->hwndParent, pszT,
                                                                 MAX_PATH);
                                    if (FAILED(hr))
                                        hr = URL_E_UNREGISTERED_PROTOCOL;
                                }

                                //
                                // I have no idea what this RegisterProtocolHandler
                                // does (it looks too complicated). I, however, know
                                // that we come here if the user type one of pluggable
                                // protocol. And RegisterProtocolHandler returns E_FAIL.
                                // (SatoNa)
                                //
                                if (FAILED(hr)) {
                                    if (SUCCEEDED(HandlePluggableProtocol(pszURL, pszProtocol))) {
                                        hr = S_OK;
                                        goto done;
                                    }
                                }

                                if (SUCCEEDED(hr))
                                {
                                    hr = ResultFromWin32(RegOpenKeyExW(HKEY_CLASSES_ROOT, pszProtocol, 0, KEY_READ, &sei.hkeyClass));
                                    sei.fMask |= SEE_MASK_CLASSKEY;
                                }
                            }
                        }
                    }
                
                    switch (hr)
                    {
                        case S_OK:
                        {
                            //
                            // App Compat: Don't use up stack space.
                            //

                            LPWSTR pszVerb = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));

                            if (pszVerb)
                            {
                                int nShowCmd;
   
                                // Execute URL via registered protocol handler.
   
                                if (IsFlagClear(purlici->dwFlags,
                                                IURL_INVOKECOMMAND_FL_ALLOW_UI))
                                    SetFlag(sei.fMask, SEE_MASK_FLAG_NO_UI);

                                if (purlici->dwFlags & IURL_INVOKECOMMAND_FL_DDEWAIT)
                                    SetFlag(sei.fMask, SEE_MASK_FLAG_DDEWAIT);
                        
                                if (IsFlagClear(purlici->dwFlags,
                                                IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB))
                                {
                                    sei.lpVerb = purlici->pcszVerb;
                                }
                                else
                                {
                                    if (pszProtocol &&
                                        GetClassDefaultVerb(pszProtocol, pszVerb,
                                                            MAX_PATH))
                                        sei.lpVerb = pszVerb;
                                    else
                                        ASSERT(! sei.lpVerb);
                                }

                                ASSERT(m_pprop);
                                hr = InitProp();
                                if (SUCCEEDED(hr))
                                {
                                    m_pprop->GetProp(PID_IS_WORKINGDIR, pszT, MAX_PATH);
                                    m_pprop->GetProp(PID_IS_SHOWCMD, &nShowCmd); // inits to zero if not found
                                
                                    //  if we have a file try using a direct connection
                                    //  to the shell to give the whole shortcut
                                    if (m_pszFile && ((IsIEDefaultBrowser()) || (_IsInFavoritesFolder())))
                                    {
                                        LPTSTR pszType = pszProtocol;
                                        if (pu.nScheme == URL_SCHEME_FILE)
                                            pszType = PathFindExtension(pszURL);
                                            
                                        hr = IEExecFile(pszVerb, pszType, SBCMDID_IESHORTCUT, m_pszFile);
                                    }
                                    else 
                                        hr = E_FAIL;

                                    //  if we failed to pass it to IE, then we should just default 
                                    //  to the old behavior
                                    if (FAILED(hr))
                                    {

                                        sei.cbSize = SIZEOF(sei);
                                        sei.hwnd = purlici->hwndParent;
                                        sei.lpFile = pszURL;
                                        sei.lpDirectory = pszT;
                                        sei.nShow = nShowCmd ? nShowCmd : SW_NORMAL;
                                        // We have to special case "file:" URLs,
                                        // because Nashville's Explorer typically handles 
                                        // file: URLs via DDE, which fails for executables
                                        // (eg, "file://c:\windows\notepad.exe") and
                                        // non-hostable docs (like text files).
                                        //
                                        // So in this case, we remove the protocol class
                                        // and execute the suffix.

                                        // App Compat: Don't use up stack space.
                                        DWORD cchPath = MAX_PATH;
                                        LPWSTR  pszPath = (LPWSTR)LocalAlloc(LPTR, cchPath * sizeof(WCHAR));

                                        if (pszPath)
                                        {
                                            if (IsFlagSet(sei.fMask, SEE_MASK_CLASSKEY) &&
                                                (URL_SCHEME_FILE == pu.nScheme) &&
                                                SUCCEEDED(PathCreateFromUrl(pszURL, pszPath, &cchPath, 0)))
                                            {
                                                sei.hkeyClass = NULL;
                                                ClearFlag(sei.fMask, SEE_MASK_CLASSKEY);
                                                sei.lpFile = pszPath;
                                                
                                            }

                                            if (m_pszFile && IsOS(OS_WHISTLERORGREATER))
                                            {
                                                //  this is the security context
                                                //  so that shellexec() can do zone checks
                                                sei.lpClass = m_pszFile;
                                                sei.fMask |= SEE_MASK_HASTITLE | SEE_MASK_HASLINKNAME;
                                            }


                                            TraceMsg(TF_INTSHCUT, "Intshcut::InvokeCommand(): Invoking %s verb on URL %s.",
                                                       sei.lpVerb ? sei.lpVerb : TEXT("open"),
                                                       sei.lpFile);
           
                                            hr = ShellExecuteEx(&sei) ? S_OK : IS_E_EXEC_FAILED;

                                            LocalFree(pszPath);
                                            pszPath = NULL;
                                        }
                                        else
                                        {
                                            hr = E_OUTOFMEMORY;
                                        }
                                    }
                                }
                                if (hr != S_OK)
                                    TraceMsg(TF_WARNING, "Intshcut::InvokeCommand(): ShellExecuteEx() via registered protcol handler failed for %s.",
                                             pszURL);

                                LocalFree(pszVerb);
                                pszVerb = NULL;
                            }
                            else
                            {
                                hr = E_OUTOFMEMORY;
                            }
   
                            break;
                        }
   
                        case S_FALSE:
                            hr = MyExecute(pszT, pszURL, 0);
                            switch (hr)
                            {
                                case E_FAIL:
                                    bExecFailedWhine = TRUE;
                                    hr = IS_E_EXEC_FAILED;
                                    break;
   
                                default:
                                    break;
                            }
                            break;
   
                        default:
                            ASSERT(FAILED(hr));
                            break;
                    }

        done:
                    if (pszProtocol)
                    {
                        LocalFree(pszProtocol);
                        pszProtocol = NULL;
                    }
                
                    if (pidl)
                        ILFree(pidl);

                    if (sei.hkeyClass)
                        RegCloseKey(sei.hkeyClass);
                    
                    if (FAILED(hr) &&
                        IsFlagSet(purlici->dwFlags, IURL_INVOKECOMMAND_FL_ALLOW_UI))
                    {
                        switch (hr)
                        {
                            case IS_E_EXEC_FAILED:
                                if (bExecFailedWhine)
                                {
                                    ASSERT(IS_VALID_STRING_PTR(pszT, -1));

                                    MLShellMessageBox(
                                                    purlici->hwndParent,
                                                    MAKEINTRESOURCE(IDS_IS_EXEC_FAILED),
                                                    MAKEINTRESOURCE(IDS_SHORTCUT_ERROR_TITLE),
                                                    (MB_OK | MB_ICONEXCLAMATION),
                                                    pszT);
                                }
                                break;
            
                            case URL_E_INVALID_SYNTAX:
                                MLShellMessageBox(
                                                purlici->hwndParent,
                                                MAKEINTRESOURCE(IDS_IS_EXEC_INVALID_SYNTAX),
                                                MAKEINTRESOURCE(IDS_SHORTCUT_ERROR_TITLE),
                                                (MB_OK | MB_ICONEXCLAMATION),
                                                pszURL);
            
                                break;
            
                            case URL_E_UNREGISTERED_PROTOCOL:
                            {
                                LPTSTR pszProtocol;
            
                                if (CopyURLProtocol(pszURL, &pszProtocol, NULL) == S_OK)
                                {
                                    MLShellMessageBox(
                                                    purlici->hwndParent,
                                                    MAKEINTRESOURCE(IDS_IS_EXEC_UNREGISTERED_PROTOCOL),
                                                    MAKEINTRESOURCE(IDS_SHORTCUT_ERROR_TITLE),
                                                    (MB_OK | MB_ICONEXCLAMATION),
                                                    pszProtocol);
            
                                    LocalFree(pszProtocol);
                                    pszProtocol = NULL;
                                }
            
                                break;
                            }
            
                            case E_OUTOFMEMORY:
                                MLShellMessageBox(
                                                purlici->hwndParent,
                                                MAKEINTRESOURCE(IDS_IS_EXEC_OUT_OF_MEMORY),
                                                MAKEINTRESOURCE(IDS_SHORTCUT_ERROR_TITLE),
                                                (MB_OK | MB_ICONEXCLAMATION));
                                break;
            
                            default:
                                ASSERT(hr == E_ABORT);
                                break;
                        }
                    }

                    LocalFree(pszT);
                    pszT = NULL;
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }
            }

           LocalFree(pszURL);
           pszURL = NULL;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }

    ASSERT(IS_VALID_STRUCT_PTR(this, CIntshcut));
    ASSERT(hr == S_OK ||
           hr == E_ABORT ||
           hr == E_OUTOFMEMORY ||
           hr == URL_E_INVALID_SYNTAX ||
           hr == URL_E_UNREGISTERED_PROTOCOL ||
           hr == IS_E_EXEC_FAILED ||
           hr == E_INVALIDARG);
    
    return(hr);
}



/*----------------------------------------------------------
Purpose: IUniformResourceLocatorA::InvokeCommand for Intshcut

         Ansi version

*/
STDMETHODIMP
Intshcut::InvokeCommand(
    IN PURLINVOKECOMMANDINFOA purlici)

{
    HRESULT hres = E_INVALIDARG;

    ASSERT(purlici);
    ASSERT(SIZEOF(*purlici) == purlici->dwcbSize);

    if (SIZEOF(*purlici) == purlici->dwcbSize)
    {
        URLINVOKECOMMANDINFOW ici;

        ici.dwcbSize = SIZEOF(ici);
        ici.dwFlags  = purlici->dwFlags;
        ici.hwndParent = purlici->hwndParent;

        ici.pcszVerb = NULL;

        if (purlici->pcszVerb)
        {
            //
            // App compat hack.
            //
            // Note: use local alloc here instead of the stack since 16-bit code
            // can shell exec urls and we don't want to use up their stack.
            //

            int cch = lstrlenA(purlici->pcszVerb) + 1;

            ici.pcszVerb = (LPWSTR)LocalAlloc(LPTR, cch * sizeof(WCHAR));

            if (ici.pcszVerb)
            {
                AnsiToUnicode(purlici->pcszVerb, (LPWSTR)ici.pcszVerb, cch);
            }
        }

        hres = InvokeCommand(&ici);

        if (ici.pcszVerb)
        {
            LocalFree((void*)ici.pcszVerb);
            ici.pcszVerb = NULL;
        }
    }

    return hres;
}



STDMETHODIMP Intshcut::Create(REFFMTID fmtid, const CLSID *pclsid,
                              DWORD grfFlags, DWORD grfMode, IPropertyStorage **pppropstg)
{
    *pppropstg = NULL;
    return E_NOTIMPL;
}


STDMETHODIMP Intshcut::Open(REFFMTID fmtid, DWORD grfMode, IPropertyStorage **pppropstg)
{
    HRESULT hres = E_FAIL;      // assume failure

    *pppropstg = NULL;

    if (IsEqualGUID(fmtid, FMTID_Intshcut))
    {
        // Create a URLProp object for this format ID
        hres = CIntshcutProp_CreateInstance(NULL, IID_PPV_ARG(IPropertyStorage, pppropstg));
        if (SUCCEEDED(hres))
        {
            // Initialize this object
            IntshcutProp * pisprop = (IntshcutProp *)*pppropstg;
            hres = pisprop->InitFromFile(m_pszFile);
        }
    }
    else if (IsEqualGUID(fmtid, FMTID_InternetSite))
    {
        // Create a URLProp object for this format ID
        hres = CIntsiteProp_CreateInstance(NULL, IID_PPV_ARG(IPropertyStorage, pppropstg));
        if (SUCCEEDED(hres))
        {
            hres = InitProp();
            if (SUCCEEDED(hres))
            {
                TCHAR szURL[MAX_URL_STRING];
                hres = m_pprop->GetProp(PID_IS_URL, szURL, SIZECHARS(szURL));
                if (SUCCEEDED(hres))
                {
                    IntsiteProp * pisprop = (IntsiteProp *)*pppropstg;
                    hres = pisprop->InitFromDB(szURL, this, FALSE);
                }
            }

            if (FAILED(hres))
            {
                (*pppropstg)->Release();
                *pppropstg = NULL;
            }
        }
    }

    return hres;
}


STDMETHODIMP Intshcut::Delete(REFFMTID fmtid)
{
    return STG_E_ACCESSDENIED;
}


STDMETHODIMP Intshcut::Enum(OUT IEnumSTATPROPSETSTG ** ppenum)
{
    *ppenum = NULL;
    return E_NOTIMPL;
}

// get the required propery that indicates the item has been changed

STDAPI GetRecentlyChanged(IPropertyStorage *ppropstg, PROPID propid, LPTSTR pszBuf, DWORD cchBuf)
{
    PROPVARIANT propvar;
    HRESULT hres = E_FAIL;  // assume not there
    PROPSPEC prspec = { PRSPEC_PROPID, propid };

    if (S_OK == ppropstg->ReadMultiple(1, &prspec, &propvar))
    {
        if ((VT_UI4 == propvar.vt) && (PIDISF_RECENTLYCHANGED & propvar.lVal))
            hres = S_FALSE;     // we've got it, skip this property

        PropVariantClear(&propvar);
    }
    return hres;
}

STDAPI GetStringPropURL(IPropertyStorage *ppropstg, PROPID propid, LPTSTR pszBuf, DWORD cchBuf)
{
    HRESULT hres = GetStringProp(ppropstg, propid, pszBuf, cchBuf);
    if (SUCCEEDED(hres))
    {
        // get rid of the query string for display
        if (UrlIs(pszBuf, URLIS_HASQUERY))
            UrlCombine(pszBuf, TEXT("?..."), pszBuf, &cchBuf, 0);
    }
    return hres;
}

BOOL Intshcut::_TryLink(REFIID riid, void **ppvOut)
{
    HRESULT hr = InitProp();

    if (SUCCEEDED(hr) && URL_SCHEME_FILE == GetScheme())
    {
        // This shortcut is not in the favorites folder as far as we know 
        TCHAR szURL[INTERNET_MAX_URL_LENGTH];
        DWORD cch = SIZECHARS(szURL);

        *szURL = 0;

        m_pprop->GetProp(PID_IS_URL, szURL, SIZECHARS(szURL));

        if (*szURL && SUCCEEDED(PathCreateFromUrl(szURL, szURL, &cch, 0)))
        {
            if (!_punkLink)
            {
                hr = _CreateShellLink(szURL, &_punkLink);
            }

            if (_punkLink)
            {
                if (SUCCEEDED(_punkLink->QueryInterface(riid, ppvOut)))
                    return TRUE;
            }
        }

        if (FAILED(hr))
            ATOMICRELEASE(_punkLink);
    }

    return FALSE;
}

STDMETHODIMP Intshcut::GetInfoTip(DWORD dwFlags, WCHAR **ppwszTip)
{
    HRESULT hr = E_FAIL;
    IQueryInfo *pqi;

    if (_TryLink(IID_PPV_ARG(IQueryInfo, &pqi)))
    {
        hr = pqi->GetInfoTip(dwFlags, ppwszTip);
        pqi->Release();
    }
    
    if (FAILED(hr))
    {
        static const ITEM_PROP c_rgTitleAndURL[] = {
            { &FMTID_InternetSite, PID_INTSITE_TITLE,   GetStringProp, IDS_FAV_STRING },
            { &FMTID_Intshcut, PID_IS_URL,              GetStringPropURL, IDS_FAV_STRING },
            { NULL, 0, 0, 0 },
        };

        hr = GetInfoTipFromStorage(SAFECAST(this, IPropertySetStorage *), c_rgTitleAndURL, ppwszTip);
    }

    return hr;

}

STDMETHODIMP Intshcut::GetInfoFlags(DWORD *pdwFlags)
{
    *pdwFlags = 0;
#if 0    
// This Function is commented out since it has not been tested.
// It can be uncommented if we provide support for providing offline cursor
// for shortucts. I think this needs updates to listview in comctl -- BharatS
        
    LPSTR pszURL;
    if (S_OK == GetURL(&pszURL))
    {
        BOOL fCached = UrlIsCached(pszUrl);
        if (!fCached)
        {
            CHAR szCanonicalizedUrlA[MAX_URL_STRING];
            DWORD dwLen = ARRAYSIZE(szCanonicalizedUrlA);
            InternetCanonicalizeUrlA(pszURL, szCanonicalizedUrlA, &dwLen, 0);
            fCached = UrlIsMappedOrInCache(szCanonicalizedUrlA);
        }
        if (fCached)
            *pdwFlags |= QIF_CACHED;
        SHFree(pszURL);
    }
    return S_OK;
#else
    return E_NOTIMPL;
#endif
}

/*----------------------------------------------------------
IQueryCodePage:
*/
STDMETHODIMP Intshcut::GetCodePage(UINT * puiCodePage)
{
    HRESULT hres = E_FAIL;
    *puiCodePage = 0;     // NULL out the code page. 
    if (IsFlagSet(m_dwFlags, ISF_CODEPAGE))
    {
        *puiCodePage = m_uiCodePage;
        hres = S_OK;
    }

    return hres;
}

STDMETHODIMP Intshcut::SetCodePage(UINT uiCodePage)
{
    SetFlag(m_dwFlags, ISF_CODEPAGE);
    m_uiCodePage = uiCodePage;
    return S_OK;
}

/***************************** Exported Functions ****************************/


// This function was ported from URL.DLL.  Normally, since our
// internet shortcut object has a context menu handler, we don't
// call this function.
//
// Only one thing needs this entry point: Exchange.  Sigh.
//
// Instead of simply calling ShellExecuteEx to handle opening file
// attachments, they grovel thru the registry themselves. Of course,
// their code is incomplete and thinks a file-association needs to
// have an explicit \shell\open\command that works before it executes
// it.  Hmm, it brings to mind a phrase, like:
//
// 
//
// So, we export this API so they will work.  But really the invoke
// occurs in the context menu handler for normal cases.
//


STDAPI_(void) OpenURL(HWND hwndParent, HINSTANCE hinst, LPSTR pszCmdLine, int nShowCmd)
{
   HRESULT hr;
   HRESULT hrCoInit;

   

   Intshcut * pIntshcut = new Intshcut;     // This must be a 0 INITed memory allocation
   WCHAR wszPath[MAX_PATH];

    if (!pIntshcut)
        return;

   hrCoInit = SHCoInitialize(); // gets called from rundll32 in browser only mode - hence we need to
                                // make sure that OLE has been init'ed

 

   ASSERT(IS_VALID_HANDLE(hwndParent, WND));
   ASSERT(IS_VALID_HANDLE(hinst, INSTANCE));
   ASSERT(IS_VALID_STRING_PTRA(pszCmdLine, -1));
   ASSERT(IsValidShowCmd(nShowCmd));

   // Assume the entire command line is an Internet Shortcut file path.

   TrimWhiteSpaceA(pszCmdLine);

   TraceMsgA(TF_INTSHCUT, "OpenURL(): Trying to open Internet Shortcut %s.",
              pszCmdLine);

#ifndef UNIX

   AnsiToUnicode(pszCmdLine, wszPath, SIZECHARS(wszPath));
   hr = pIntshcut->LoadFromFile(wszPath);

#else /* UNIX */

#ifndef ANSI_SHELL32_ON_UNIX
   // IEUNIX : Our Shell32 calls this function with unicode command line
   hr = pIntshcut->LoadFromFile((LPWSTR)pszCmdLine);
#else
   hr = pIntshcut->LoadFromFile(pszCmdLine);
#endif

#endif /* !UNIX */

   if (hr == S_OK)
   {
      URLINVOKECOMMANDINFO urlici;

      urlici.dwcbSize = SIZEOF(urlici);
      urlici.hwndParent = hwndParent;
      urlici.pcszVerb = NULL;
      urlici.dwFlags = (IURL_INVOKECOMMAND_FL_ALLOW_UI |
                        IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB);

      hr = pIntshcut->InvokeCommand(&urlici);
   }

   if (hr != S_OK)
   {
      MLShellMessageBox(
                      hwndParent,
                      MAKEINTRESOURCE(IDS_IS_LOADFROMFILE_FAILED),
                      MAKEINTRESOURCE(IDS_SHORTCUT_ERROR_TITLE),
                      (MB_OK | MB_ICONEXCLAMATION),
                      wszPath);
   }

   pIntshcut->Release();

   SHCoUninitialize(hrCoInit);

}

// INamedPropertyBag Methods
//
// Reads & writes properties from a section in the shortcut ini file


const TCHAR  c_szSizeSuffix[] = TEXT("__Size");


STDMETHODIMP Intshcut::WritePropertyNPB(
                                       LPCOLESTR pszSectionNameW, 
                            /* [in] */ LPCOLESTR pszPropNameW, 
                       /* [out][in] */ PROPVARIANT  *pVar)
{
    const TCHAR *pszSectionName;
    const TCHAR *pszPropName;
    HRESULT hr;
    if((NULL == pszSectionNameW) || (NULL == pszPropNameW) || (NULL == pVar))
    {
        return E_FAIL;
    }


    if(S_OK != _CreateTemporaryBackingFile())
    {
        ASSERT(NULL == m_pszTempFileName);
        return E_FAIL;
    }


    ASSERT(m_pszTempFileName);
    
    pszSectionName = pszSectionNameW;
    pszPropName = pszPropNameW;
    // Write the appropriate value in depending on the type

    switch(pVar->vt)
    {
        // NOTE: (andrewgu) these types we also can round-trip using the same code pass as for
        // unsigned types, except bharats in a codereview recommended we comment these out because
        // they'll look goofy in the *.ini file (you wrote -5 but see 4294967290 junk instead).
        // VT_UINT is not listed as "may appear in an OLE property set" in <wtypes.h>.
     /* case VT_I1:
        case VT_I2:
        case VT_I4:
        case VT_INT:
        case VT_UINT: */

        case VT_UI1:
        case VT_UI2:
        case VT_UI4:
            hr = WriteUnsignedToFile(m_pszTempFileName, pszSectionName, pszPropName, pVar->ulVal);
            break;

        case VT_BSTR:
            hr = WriteGenericString(m_pszTempFileName, pszSectionName, pszPropName, pVar->bstrVal);
            break;

        case VT_BLOB:
            {
                TCHAR *pszSizePropName = NULL;
                int  cchPropName = lstrlen(pszPropName) + ARRAYSIZE(c_szSizeSuffix) + 1;
                DWORD dwAllocSize = cchPropName * sizeof(TCHAR);
                
                pszSizePropName = (TCHAR *)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, dwAllocSize);
                if(pszSizePropName)
                {
                    DWORD dwBufferSize;
                    StrCpyN(pszSizePropName, pszPropName, cchPropName);
                    StrCatBuff(pszSizePropName, c_szSizeSuffix, cchPropName);

                    // OK Now - we have the name for the size
                    // we write it out

                    dwBufferSize = pVar->blob.cbSize;
                    hr = WriteBinaryToFile(m_pszTempFileName, pszSectionName, pszSizePropName, 
                                                (LPVOID)(&dwBufferSize), sizeof(DWORD));

                    if(S_OK == hr)
                    {
                        // Write out the buffer
                        hr = WriteBinaryToFile(m_pszTempFileName, pszSectionName, pszPropName, 
                                                (LPVOID)(pVar->blob.pBlobData), dwBufferSize);
                    }

                    LocalFree((LPVOID)pszSizePropName);
                    pszSizePropName = NULL;
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }
                
                break;
            }
        default:
            hr = WriteBinaryToFile(m_pszTempFileName, pszSectionName, pszPropName, (LPVOID)pVar, sizeof(PROPVARIANT));
            break;
    }

   
    return hr;
}

STDMETHODIMP Intshcut::ReadPropertyNPB(
                       /* [in] */ LPCOLESTR pszSectionNameW,
                       /* [in] */ LPCOLESTR pszPropNameW,
                       /* [out][in] */ PROPVARIANT  *pVar)
{
    const TCHAR *pszSectionName;
    const TCHAR *pszPropName;
    TCHAR       *pszFileToReadFrom;
    HRESULT hr;

    if((NULL == pszSectionNameW) || (NULL == pszPropNameW) || (NULL == pVar))
    {
        if (NULL != pVar)
            pVar->vt = VT_ERROR;

        return E_FAIL;
    }


    if(m_pszTempFileName)
    {
        pszFileToReadFrom = m_pszTempFileName;
    } 
    else if(m_pszFile)
    {
        pszFileToReadFrom = m_pszFile;
    }
    else
    {
        pVar->vt = VT_EMPTY;
        return S_FALSE;
    }

    pszSectionName = pszSectionNameW;
    pszPropName = pszPropNameW;

    switch(pVar->vt)
    {
        // NOTE: (andrewgu) these types we also can round-trip using the same code pass as for
        // unsigned types, except bharats in a codereview recommended we comment these out because
        // they'll look goofy in the *.ini file (you wrote -5 but see 4294967290 junk instead).
        // VT_UINT is not listed as "may appear in an OLE property set" in <wtypes.h>.
     /* case VT_I1:
        case VT_I2:
        case VT_I4:
        case VT_INT:
        case VT_UINT: */

        case VT_UI1:
        case VT_UI2:
        case VT_UI4:
            pVar->ulVal = 0;
            hr          = ReadUnsignedFromFile(pszFileToReadFrom, pszSectionName, pszPropName, &(pVar->ulVal));
            break;

        case VT_BSTR:   
             // It is a string
           pVar->vt = VT_BSTR;
           pVar->bstrVal = NULL;
           hr = ReadBStrFromFile(pszFileToReadFrom, pszSectionName, pszPropName, &(pVar->bstrVal));            
           break;

        case VT_BLOB:
            {
                TCHAR *pszSizePropName = NULL;
                int  cchPropName = lstrlen(pszPropName) + ARRAYSIZE(c_szSizeSuffix) + 1;
                DWORD dwAllocSize = cchPropName * sizeof(TCHAR);
                
                pszSizePropName = (TCHAR *)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, dwAllocSize);
                if(pszSizePropName)
                {
                    DWORD dwBufferSize;
                    StrCpyN(pszSizePropName, pszPropName, cchPropName);
                    StrCatBuff(pszSizePropName, c_szSizeSuffix, cchPropName);
                    // Read the Size first
                    hr = ReadBinaryFromFile(pszFileToReadFrom, pszSectionName, pszSizePropName, 
                                            &dwBufferSize, sizeof(DWORD));
                    if(S_OK == hr)
                    {
                        
                        pVar->blob.pBlobData = (unsigned char *)CoTaskMemAlloc(dwBufferSize);
                        if(pVar->blob.pBlobData)
                        {
                            hr = ReadBinaryFromFile(pszFileToReadFrom, pszSectionName, pszPropName, 
                                            pVar->blob.pBlobData, dwBufferSize);

                            if(S_OK == hr)
                            {
                                pVar->blob.cbSize = dwBufferSize;
                            }
                            else
                            {
                                CoTaskMemFree(pVar->blob.pBlobData);
                            }
                        }
                    }

                    LocalFree(pszSizePropName);
                    pszSizePropName = NULL;
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }

               break;
            }
        default:
            {
                // all else
                PROPVARIANT tmpPropvar = {0};
                
                hr = ReadBinaryFromFile(pszFileToReadFrom, pszSectionName, pszPropName, &tmpPropvar, sizeof(PROPVARIANT));
                if((S_OK == hr) && (tmpPropvar.vt == pVar->vt))
                {
                    memcpy(pVar, &tmpPropvar, sizeof(PROPVARIANT));
                }
                else
                {
                    pVar->vt = VT_ERROR;
                }
                break;
            }

    }

   if(hr != S_OK)
   {
        memset(pVar, 0, sizeof(PROPVARIANT));
        pVar->vt = VT_EMPTY;
   }   

   return hr;
}

STDMETHODIMP Intshcut::RemovePropertyNPB (
                            /* [in] */ LPCOLESTR pszSectionNameW,
                            /* [in] */ LPCOLESTR pszPropNameW)
{
    const TCHAR *pszSectionName;
    const TCHAR *pszPropName;
    HRESULT hr;
    TCHAR *pszFileToDeleteFrom;

    // Return if there is no file name
    if((NULL == pszSectionNameW) || (NULL == pszPropNameW)) 
    {
        return E_FAIL;
    }

     if(m_pszTempFileName)
     {
        pszFileToDeleteFrom = m_pszTempFileName;
     }
     else if(m_pszFile)
     {
        pszFileToDeleteFrom = m_pszFile;
     }
     else
     {
        return E_FAIL;
     }
     
    
    // Just delete the key corresponding to this property name
    pszSectionName = pszSectionNameW;
    pszPropName = pszPropNameW;

    hr = SHDeleteIniString(pszSectionName, pszPropName, pszFileToDeleteFrom)? S_OK : E_FAIL;

    return hr;
}
