/*
 * urlexec.cpp - IUnknown implementation for Intshcut class.
 */

#include "project.hpp"
#include "urlshell.h"
#include "clsfact.h"
#include "resource.h"

#include <mluisupp.h>

// URL Exec Hook

class CURLExec : public IShellExecuteHookA, public IShellExecuteHookW
{
private:

    ULONG       m_cRef;

    ~CURLExec(void);    // Prevent this class from being allocated on the stack or it will fault.

public:
    CURLExec(void);

    // IShellExecuteHook methods

	// Ansi
    STDMETHODIMP Execute(LPSHELLEXECUTEINFOA pei);
    // Unicode
    STDMETHODIMP Execute(LPSHELLEXECUTEINFOW pei);

    // IUnknown methods
    
    STDMETHODIMP  QueryInterface(REFIID riid, PVOID *ppvObj);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);
    
#ifdef DEBUG
    friend BOOL IsValidPCURLExec(const CURLExec * pue);
#endif
};


#ifdef DEBUG

BOOL IsValidPCURLExec(CURLExec * pue)
{
    return (IS_VALID_READ_PTR(pue, CURLExec));
}

#endif


CURLExec::CURLExec(void) : m_cRef(1)
{
    // CURLExec objects should always be allocated

    ASSERT(IS_VALID_STRUCT_PTR(this, CURLExec));

    DLLAddRef();
}

CURLExec::~CURLExec(void)
{
    ASSERT(IS_VALID_STRUCT_PTR(this, CURLExec));

    DLLRelease();
}


/*----------------------------------------------------------
Purpose: IUnknown::QueryInterface handler for CURLExec

*/
STDMETHODIMP CURLExec::QueryInterface(REFIID riid, PVOID *ppvObj)
{
    if (IsEqualIID(riid, IID_IUnknown) ||
        IsEqualIID(riid, IID_IShellExecuteHookA))
    {
        *ppvObj = SAFECAST(this, IShellExecuteHookA *);
    }
    else if (IsEqualIID(riid, IID_IShellExecuteHookW))
    {
    	*ppvObj = SAFECAST(this, IShellExecuteHookW *);
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }
    AddRef();
    return NOERROR;
}


STDMETHODIMP_(ULONG) CURLExec::AddRef()
{
    return ++m_cRef;
}


STDMETHODIMP_(ULONG) CURLExec::Release()
{
    m_cRef--;
    if (m_cRef > 0)
        return m_cRef;

    delete this;
    return 0;
}


// from shlexec.c
#define SEE_MASK_CLASS (SEE_MASK_CLASSNAME|SEE_MASK_CLASSKEY)
/*----------------------------------------------------------
Purpose: IShellExecuteHook::Execute handler for CURLExec

*/
STDMETHODIMP CURLExec::Execute(LPSHELLEXECUTEINFOA pei)
{
    HRESULT hres;
    
    ASSERT(IS_VALID_STRUCT_PTR(this, CURLExec));
    ASSERT(IS_VALID_READ_PTR(pei, SHELLEXECUTEINFO));
    
    if (! pei->lpVerb ||
        ! lstrcmpi(pei->lpVerb, TEXT("open")))
    {
        if (pei->lpFile)
        {
            LPTSTR pszURL;

            // This should succeed only for real URLs.  We should fail
            // for file paths and let the shell handle those.

            hres = TranslateURL(pei->lpFile, 
                                TRANSLATEURL_FL_GUESS_PROTOCOL | TRANSLATEURL_FL_CANONICALIZE,
                                &pszURL);
            
            if (SUCCEEDED(hres))
            {
                LPCTSTR pszURLToUse;
                
                pszURLToUse = (hres == S_OK) ? pszURL : pei->lpFile;
                
                hres = ValidateURL(pszURLToUse);
                
                if (SUCCEEDED(hres))
                {
                    IUniformResourceLocator * purl;

                    hres = SHCoCreateInstance(NULL, &CLSID_InternetShortcut, NULL, IID_IUniformResourceLocator, (void **)&purl);
                    if (SUCCEEDED(hres))
                    {
                        hres = purl->SetURL(pszURLToUse, 0);
                        if (hres == S_OK)
                        {
                            IShellLink * psl;

                            hres = purl->QueryInterface(IID_IShellLink, (void **)&psl);
                            if (SUCCEEDED(hres))
                            {
                                URLINVOKECOMMANDINFO urlici;

                                EVAL(psl->SetShowCmd(pei->nShow) == S_OK);
                                
                                urlici.dwcbSize = SIZEOF(urlici);
                                urlici.hwndParent = pei->hwnd;
                                urlici.pcszVerb = NULL;
                                
                                urlici.dwFlags = IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB;
                                
                                if (IsFlagClear(pei->fMask, SEE_MASK_FLAG_NO_UI))
                                    SetFlag(urlici.dwFlags, IURL_INVOKECOMMAND_FL_ALLOW_UI);
                                
                                hres = purl->InvokeCommand(&urlici);
                                
                                if (hres != S_OK)
                                    SetFlag(pei->fMask, SEE_MASK_FLAG_NO_UI);

                                psl->Release();
                            }
                        }
                        purl->Release();
                    }
                }

                if (pszURL)
                    LocalFree(pszURL);
            }
        }
        else
            // (scotth): This hook only handles execution of file string, not IDList.
            hres = S_FALSE;
    }
    else
        // Unrecognized verb.
        hres = S_FALSE;
    
    if (hres == S_OK)
        pei->hInstApp = (HINSTANCE)42;  // huh??
    else if (FAILED(hres))
    {
        switch (hres)
        {
        case URL_E_INVALID_SYNTAX:
        case URL_E_UNREGISTERED_PROTOCOL:
            hres = S_FALSE;
            break;
            
        case E_OUTOFMEMORY:
            pei->hInstApp = (HINSTANCE)SE_ERR_OOM;
            hres = E_FAIL;
            break;
            
        case IS_E_EXEC_FAILED:
            // Translate execution failure into "file not found".
            pei->hInstApp = (HINSTANCE)SE_ERR_FNF;
            hres = E_FAIL;
            break;
            
        default:
            // pei->lpFile is bogus.  Treat as file not found.
            ASSERT(hres == E_POINTER);

            pei->hInstApp = (HINSTANCE)SE_ERR_FNF;
            hres = E_FAIL;
            break;
        }
    }
    else
        ASSERT(hres == S_FALSE);
    
    ASSERT(hres == S_OK ||
        hres == S_FALSE ||
        hres == E_FAIL);
    
    return hres;
}

STDMETHODIMP CURLExec::Execute(LPSHELLEXECUTEINFOW pei)
{
	// thunk stuff copied from shlexec.c InvokeShellExecuteHook
	SHELLEXECUTEINFOA seia;
    UINT cchVerb = 0;
    UINT cchFile = 0;
    UINT cchParameters = 0;
    UINT cchDirectory  = 0;
    UINT cchClass = 0;
    LPSTR lpszBuffer;
    HRESULT hres = E_FAIL;

    seia = *(SHELLEXECUTEINFOA*)pei;    // Copy all of the binary data

    if (pei->lpVerb)
    {
        cchVerb = WideCharToMultiByte(CP_ACP,0,
                                      pei->lpVerb, -1,
                                      NULL, 0,
                                      NULL, NULL)+1;
    }

    if (pei->lpFile)
        cchFile = WideCharToMultiByte(CP_ACP,0,
                                      pei->lpFile, -1,
                                      NULL, 0,
                                      NULL, NULL)+1;

    if (pei->lpParameters)
        cchParameters = WideCharToMultiByte(CP_ACP,0,
	                                        pei->lpParameters, -1,
                                            NULL, 0,
                                            NULL, NULL)+1;

    if (pei->lpDirectory)
        cchDirectory = WideCharToMultiByte(CP_ACP,0,
                                           pei->lpDirectory, -1,
                                           NULL, 0,
                                           NULL, NULL)+1;
    if (((pei->fMask & SEE_MASK_CLASS) == SEE_MASK_CLASSNAME) && pei->lpClass)
        cchClass = WideCharToMultiByte(CP_ACP,0,
                                       pei->lpClass, -1,
                                       NULL, 0,
                                       NULL, NULL)+1;

	// what is this (alloca)? InvokeShellExecuteHook is not freeing lpszBuffer
    //lpszBuffer = alloca(cchVerb+cchFile+cchParameters+cchDirectory+cchClass);
    lpszBuffer = (LPSTR)LocalAlloc(LPTR, cchVerb+cchFile+cchParameters+cchDirectory+cchClass);
    if (lpszBuffer)
	{
		LPSTR lpsz = lpszBuffer;
		
    	seia.lpVerb = NULL;
	    seia.lpFile = NULL;
    	seia.lpParameters = NULL;
	    seia.lpDirectory = NULL;
    	seia.lpClass = NULL;

	    //
    	// Convert all of the strings to ANSI
	    //
    	if (pei->lpVerb)
	    {
    	    WideCharToMultiByte(CP_ACP, 0, pei->lpVerb, -1,
        	                    lpszBuffer, cchVerb, NULL, NULL);
	        seia.lpVerb = lpszBuffer;
    	    lpszBuffer += cchVerb;
	    }
    	if (pei->lpFile)
	    {
    	    WideCharToMultiByte(CP_ACP, 0, pei->lpFile, -1,
	                            lpszBuffer, cchFile, NULL, NULL);
    	    seia.lpFile = lpszBuffer;
	        lpszBuffer += cchFile;
    	}
	    if (pei->lpParameters)
    	{
	        WideCharToMultiByte(CP_ACP, 0,
    	                        pei->lpParameters, -1,
	                            lpszBuffer, cchParameters, NULL, NULL);
    	    seia.lpParameters = lpszBuffer;
	        lpszBuffer += cchParameters;
    	}
	    if (pei->lpDirectory)
    	{
	        WideCharToMultiByte(CP_ACP, 0,
    	                        pei->lpDirectory, -1,
	                            lpszBuffer, cchDirectory, NULL, NULL);
    	    seia.lpDirectory = lpszBuffer;
	        lpszBuffer += cchDirectory;
    	}
	    if (((pei->fMask & SEE_MASK_CLASS) == SEE_MASK_CLASSNAME) && pei->lpClass)
    	{
        	WideCharToMultiByte(CP_ACP, 0,
	                            pei->lpClass, -1,
    	                        lpszBuffer, cchClass, NULL, NULL);
	        seia.lpClass = lpszBuffer;
    	}

    	hres = Execute(&seia);
    	// now thunk the possible new stuff back
    	pei->hInstApp = seia.hInstApp;
    	if (pei->fMask & SEE_MASK_NOCLOSEPROCESS)
        	pei->hProcess = seia.hProcess;


    	LocalFree(lpsz);
	}

    return hres;
}

STDAPI CreateInstance_URLExec(LPUNKNOWN punkOuter, REFIID riid, void **ppvOut)
{
    *ppvOut = NULL;

    if (punkOuter)
        return CLASS_E_NOAGGREGATION;

    CURLExec *pue = new(CURLExec);
    if (!pue)
        return E_OUTOFMEMORY;

    HRESULT hres = pue->QueryInterface(riid, ppvOut);
    pue->Release();
    return hres;
}
