
/*
 * url.cpp - IUniformResourceLocator implementation for InternetShortcut class.
 */


/* Headers
 **********/

#include "priv.h"
#pragma hdrstop
#define INC_OLE2
#include "intshcut.h"


/* Module Constants
 *******************/

const TCHAR c_szURLPrefixesKey[]        = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\URL\\Prefixes");
const TCHAR c_szDefaultURLPrefixKey[]   = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\URL\\DefaultPrefix");

// DPA array that holds the IURLSearchHook Pointers
static HDPA g_hdpaHooks = NULL;

// CURRENT_USER
static const TCHAR c_szURLSearchHook[] = TSZIEPATH TEXT("\\URLSearchHooks");


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


int DPA_DestroyURLSearchHooksCallback(LPVOID p, LPVOID d)
{
    IURLSearchHook * psuh = (IURLSearchHook *)p;
    ASSERT(psuh);
    ATOMICRELEASET(psuh, IURLSearchHook);

    return 1; 
}

extern "C" {
    
void DestroyHdpaHooks()
{
    if (g_hdpaHooks)
    {
        ENTERCRITICAL;
        //---------------------------- Critical Section -------------------------
        HDPA hdpa = g_hdpaHooks;
        g_hdpaHooks = NULL;
        //-----------------------------------------------------------------------
        LEAVECRITICAL;
        if (hdpa)
        {
            DPA_DestroyCallback(hdpa, DPA_DestroyURLSearchHooksCallback, 0);
            hdpa = NULL;
        }
    }
}

}

HRESULT InvokeURLSearchHook(IURLSearchHook * pusHook, LPCTSTR pcszQuery, LPTSTR pszResult, ISearchContext * pSC)
{
    HRESULT hr = E_FAIL;
    
    ASSERT(pusHook);
    WCHAR szSearchURL[MAX_URL_STRING]; 

    SHTCharToUnicode(pcszQuery, szSearchURL, ARRAYSIZE(szSearchURL));

    // if we can get an IURLSearchHook2, we'll pass in the
    // search context, otherwise we'll just do without

    IURLSearchHook2 * pUSH2 = NULL;
    hr = pusHook->QueryInterface(IID_IURLSearchHook2, (void **)&pUSH2);
    if (SUCCEEDED(hr))
    {
        RIP(pUSH2 != NULL);
        hr = pUSH2->TranslateWithSearchContext(szSearchURL, ARRAYSIZE(szSearchURL), pSC);
        pUSH2->Release();
    }
    else
    {
        hr = pusHook->Translate(szSearchURL, ARRAYSIZE(szSearchURL));
    }
            
    // In case the URLSearchHook worked, convert result to TCHAR
    // This includes two cases: S_OK and S_FALSE
    if (SUCCEEDED(hr))
    {
        //WARNING: (dli) Assuming pszResult size = MAX_URL_STRING 
        SHUnicodeToTChar(szSearchURL, pszResult, MAX_URL_STRING);
    }

    return hr;    
}


/* 
 * Returns: 
 * S_OK         Search handled completely, pszResult has the full URL to browse to. 
 * 0x00000000   Stop running any further IURLSearchHooks and pass this URL back to 
 *              the browser for browsing.
 *
 * S_FALSE      Query has been preprocessed, pszResult has the result of the preprocess, 
 * 0x00000001   further search still needed. Go on executing the rest of the IURLSearchHooks 
 *              The preprocessing steps can be: 1. replaced certain characters
 *                                              2. added more hints 
 *
 * E_ABORT      Search handled completely, stop running any further IURLSearchHooks, 
 * 0x80004004   but NO BROWSING NEEDED as a result, pszResult is a copy of pcszQuery. 
 *              FEATURE: This is not fully implemented, yet, making IURLQualify return this
 *              involves too much change. 
 * 
 * E_FAIL       This Hook was unsuccessful. Search not handled at all, pcszQueryURL has the 
 * 0x80004005   query string. Please go on running other IURLSearchHooks. 
 * return
 */

HRESULT TryURLSearchHooks(LPCTSTR pcszQuery, LPTSTR pszResult, ISearchContext * pSC)
{
    HRESULT hr = E_FAIL;
    
    TCHAR szNewQuery[MAX_URL_STRING];
    StrCpyN(szNewQuery, pcszQuery, ARRAYSIZE(szNewQuery));

    int ihdpa;
    for (ihdpa = 0; ihdpa < (g_hdpaHooks ? DPA_GetPtrCount(g_hdpaHooks) : 0); ihdpa++)
    {
        IURLSearchHook * pusHook;
        pusHook = (IURLSearchHook *) DPA_GetPtr(g_hdpaHooks, ihdpa);
        if (!pusHook)
            return E_FAIL;
        hr = InvokeURLSearchHook(pusHook, szNewQuery, pszResult, pSC);
        if ((hr == S_OK) || (hr == E_ABORT))
            break;
        else if (hr == S_FALSE)
            StrCpyN(szNewQuery, pszResult, ARRAYSIZE(szNewQuery));
    }

    return hr;
}


void InitURLSearchHooks()
{
    HDPA hdpa = DPA_Create(4);
    
    // We need to look in LOCAL_MACHINE if this registry entry doesn't exist in CURRENT_USER.
    // The installer needs to install the values into LOCAL_MACHINE so they are accessable
    // to all users.  Then anyone wanting to modify the value, will need to determine if they
    // want to add it to a specific user's CURRENT_USER or modify the LOCAL_MACHINE value to 
    // apply the change to all users.  (bryanst - #6722)
    HUSKEY hkeyHooks;
    if ((hdpa) && (SHRegOpenUSKey(c_szURLSearchHook, KEY_READ, NULL, &hkeyHooks, FALSE) == ERROR_SUCCESS))
    {    
        TCHAR szCLSID[GUIDSTR_MAX];
        DWORD dwccCLSIDLen;
        LONG lEnumReturn;
        DWORD dwiValue = 0;
        
        do {
            dwccCLSIDLen = ARRAYSIZE(szCLSID);
            lEnumReturn = SHRegEnumUSValue(hkeyHooks, dwiValue, szCLSID, &dwccCLSIDLen, 
                                       NULL, NULL, NULL, SHREGENUM_DEFAULT);
            if (lEnumReturn == ERROR_SUCCESS)
            {
                CLSID clsidHook;
                if (SUCCEEDED(SHCLSIDFromString(szCLSID, &clsidHook)))
                {
                    IURLSearchHook * pusHook;

                    HRESULT hr = CoCreateInstance(clsidHook, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, 
                                                  IID_IURLSearchHook, (LPVOID *)&pusHook);
        
                    if (SUCCEEDED(hr))
                        DPA_AppendPtr(hdpa, pusHook);
                }   
            }
            dwiValue++;            
        } while (lEnumReturn == ERROR_SUCCESS);
        
        SHRegCloseUSKey(hkeyHooks);
    }
    
    ENTERCRITICAL;
    //---------------------------- Critical Section --------------------------
    if (!g_hdpaHooks)
    {
        g_hdpaHooks = hdpa;
        hdpa = NULL;
    }
    //------------------------------------------------------------------------
    LEAVECRITICAL;
    
    if (hdpa)
    {
        DPA_DestroyCallback(hdpa, DPA_DestroyURLSearchHooksCallback, 0);
        hdpa = NULL;
    }
}

    
HRESULT ApplyURLSearch(LPCTSTR pcszQuery, LPTSTR pszTranslatedUrl, ISearchContext * pSC)
{
    if (!g_hdpaHooks)
        InitURLSearchHooks();
    
    return TryURLSearchHooks(pcszQuery, pszTranslatedUrl, pSC);
}

/*----------------------------------------------------------
Purpose: This function qualifies a string as a URL.  Strings
         such as "www.foo.com" would have the scheme guessed
         if the correct flags are given.  Local paths are 
         converted to "file:" URLs.

         pszTranslatedURL may point to the same buffer as 
         pcszURL.

         If the given string is already a URL (not necessarily
         canonicalized, though), this function will not touch it, 
         unless UQF_CANONICALIZE is set, in which case the string 
         will be canonicalized.

Returns: S_OK or S_FALSE means we filled in pszTranslatedURL.
         S_OK means we altered the URL to qualify it too.
         various failure codes too

Cond:    --
*/
SHDOCAPI
IURLQualifyWithContext(
    IN  LPCWSTR pcszURL, 
    IN  DWORD   dwFlags,         // UQF_*
    OUT LPWSTR  pszTranslatedURL,
    LPBOOL      pbWasSearchURL,
    LPBOOL      pbWasCorrected,
    ISearchContext *  pSC)
{
    HRESULT hres = S_FALSE;
    DWORD cchSize;

    SHSTR strOut;
    BOOL bWasCorrected = FALSE; 

    ASSERT(IS_VALID_STRING_PTR(pcszURL, -1));
    ASSERT(IS_VALID_WRITE_BUFFER(pszTranslatedURL, TCHAR, MAX_URL_STRING));

    if (pbWasSearchURL)
        *pbWasSearchURL = FALSE;

    // Special cases: URLs of the form <drive>:<filename>
    //                URLs of the form \<filename>
    // we'll assume that if the second character is a : or |, this is an url of
    // that form, and we will guess "file://" for the prefix.
    // we'll assume any url that begins with a single \ is a file: url
 
    // NOTE: We do this here because these are cases where the protocol is 
    // left off, and is likely to be incorrectly guessed, such as a 
    // relative path \data\ftp\docs, would wrongly be turned 
    // into "ftp://\data\ftp\docs".
 

    // Note: PathIsURL returns TRUE for non-canonicalized URLs too
    if (PathIsURL(pcszURL))
    {
        LPCWSTR pcszTemp = pcszURL;
        cchSize = MAX_URL_STRING;
        if (IsFlagSet(dwFlags, UQF_AUTOCORRECT))
        {
            hres = UrlFixup(pcszURL, pszTranslatedURL, cchSize);
            if (hres == S_OK)
            {
                bWasCorrected = TRUE;
                pcszTemp = pszTranslatedURL;
            }
        }

        if (dwFlags & UQF_CANONICALIZE)
            hres = UrlCanonicalize(pcszTemp, pszTranslatedURL, &cchSize, 0);
        else if (pszTranslatedURL != pcszTemp)
            StrCpyN(pszTranslatedURL, pcszTemp, MAX_URL_STRING);

        hres = S_OK;
    }
    else
    {
        // Look for file paths
        if (IsFlagClear(dwFlags, UQF_IGNORE_FILEPATHS) && (
#ifdef UNIX
            pcszURL[0] == TEXT('/') ||
#endif
            pcszURL[1] == TEXT(':') || pcszURL[1] == TEXT('|') || pcszURL[0] == TEXT('\\')))
        {
            hres = strOut.SetSize(MAX_PATH);

            if(SUCCEEDED(hres))
            {
                //  SHSTRs have a size granularity, so the size
                //  will be equal to or greater than what was set.
                //  this means we need to get it our self.
                DWORD cchOut = strOut.GetSize();
                TCHAR szCurrentDir[MAX_PATH];

                //
                //  APPCOMPAT - IE30 compatibility - zekel 8-Jan-97
                //  we need to GetCurrentDirectory() in order to
                //  put a default drive letter on the path
                //  if necessary.  
                //

                if(GetCurrentDirectory(ARRAYSIZE(szCurrentDir), szCurrentDir))
                    PathCombine(strOut.GetInplaceStr(), szCurrentDir, pcszURL);
                else
                    hres = strOut.SetStr(pcszURL);

                if(SUCCEEDED(hres))
                {
                    hres = UrlCreateFromPath(strOut, strOut.GetInplaceStr(), &cchOut, 0);
                    if (E_POINTER == hres && SUCCEEDED(hres = strOut.SetSize(cchOut)))
                    {
                        cchOut = strOut.GetSize();
                        hres = UrlCreateFromPath(strOut, strOut.GetInplaceStr(), &cchOut, 0);
                    }
                }
            }
        }
        else if (SUCCEEDED(hres = strOut.SetSize(MAX_URL_STRING)))
        {
            //  all the Apply*() below rely on MAX_URL_STRING

            // No; begin processing general-case URLs.  Try to guess the
            // protocol or resort to the default protocol.

            DWORD cchOut = strOut.GetSize();
            if (IsFlagSet(dwFlags, UQF_GUESS_PROTOCOL))
                hres = UrlApplyScheme(pcszURL, strOut.GetInplaceStr(), &cchOut, URL_APPLY_GUESSSCHEME);

            //
            // Try to auto-correct the protocol
            //
            if (hres == S_FALSE &&
                IsFlagSet(dwFlags, UQF_AUTOCORRECT))
            {
                hres = UrlFixup(pcszURL, strOut.GetInplaceStr(), strOut.GetSize());
                bWasCorrected = (hres == S_OK);
            }

            if (hres == S_FALSE &&
                IsFlagSet(dwFlags, UQF_USE_DEFAULT_PROTOCOL)) 
            {
                // run the search with or without the search context
                hres = ApplyURLSearch(pcszURL, strOut.GetInplaceStr(), pSC);
                if (SUCCEEDED(hres) && pbWasSearchURL) {
                    *pbWasSearchURL = TRUE;
                }
                
                // If that fails, then tack on the default protocol
                if (FAILED(hres) || hres == S_FALSE)
                {
                    cchOut = strOut.GetSize();
                    hres = UrlApplyScheme(pcszURL, strOut.GetInplaceStr(), &cchOut, URL_APPLY_DEFAULT);
                }
            }

            // Did the above fail?
            if (S_FALSE == hres)
            {
                // Yes; return the real reason why the URL is bad
                hres = URL_E_INVALID_SYNTAX;
            }
            else if (dwFlags & UQF_CANONICALIZE)
            {
                // No; canonicalize
                cchSize = strOut.GetSize();
                hres = UrlCanonicalize(strOut, strOut.GetInplaceStr(), &cchSize, 0);
            }
        }

        if (SUCCEEDED(hres))
        {
            StrCpyN(pszTranslatedURL, strOut, MAX_URL_STRING);
        }
    }

    if (pbWasCorrected)
        *pbWasCorrected = bWasCorrected;

    return hres;
}

SHDOCAPI
IURLQualify(
    IN  LPCWSTR pcszURL, 
    IN  DWORD   dwFlags,         // UQF_*
    OUT LPWSTR  pszTranslatedURL,
    LPBOOL      pbWasSearchURL,
    LPBOOL      pbWasCorrected)
{
    return IURLQualifyWithContext(pcszURL, dwFlags, pszTranslatedURL, pbWasSearchURL, pbWasCorrected, NULL);
}


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


STDAPI
URLQualifyA(
    LPCSTR pszURL, 
    DWORD  dwFlags,         // UQF_*
    LPSTR *ppszOut)
{
    HRESULT hres;

    ASSERT(IS_VALID_STRING_PTRA(pszURL, -1));
    ASSERT(IS_VALID_WRITE_PTR(ppszOut, LPSTR));

    *ppszOut = NULL;

    WCHAR szTempTranslatedURL[MAX_URL_STRING];
    WCHAR szURL[MAX_URL_STRING];

    SHAnsiToUnicode(pszURL, szURL, ARRAYSIZE(szURL));

    hres = IURLQualify(szURL, dwFlags, szTempTranslatedURL, NULL, NULL);

    if (SUCCEEDED(hres))
    {
        CHAR szOut[MAX_URL_STRING];

        SHUnicodeToAnsi(szTempTranslatedURL, szOut, ARRAYSIZE(szOut));

        *ppszOut = StrDupA(szOut);

        if (!*ppszOut)
            hres = E_OUTOFMEMORY;
    }

    return hres;
}


STDAPI
URLQualifyW(
    LPCWSTR pszURL, 
    DWORD  dwFlags,         // UQF_*
    LPWSTR *ppszOut)
{
    HRESULT hres;

    ASSERT(IS_VALID_STRING_PTRW(pszURL, -1));
    ASSERT(IS_VALID_WRITE_PTR(ppszOut, LPWSTR));

    WCHAR szTempTranslatedURL[MAX_URL_STRING];

    hres = IURLQualify(pszURL, dwFlags, szTempTranslatedURL, NULL, NULL);

    if (SUCCEEDED(hres))
    {
        *ppszOut = StrDup(szTempTranslatedURL);

        if (!*ppszOut)
            hres = E_OUTOFMEMORY;
    }

    return hres;
}
