530 lines
17 KiB
C++
530 lines
17 KiB
C++
#include "priv.h"
|
|
#include "dochost.h"
|
|
#include "resource.h"
|
|
#include "urlprop.h"
|
|
#include "ishcut.h"
|
|
#include "shlguid.h"
|
|
#include "mlang.h"
|
|
|
|
#include <mluisupp.h>
|
|
|
|
#define DM_HISTORY 0
|
|
|
|
HRESULT PersistShortcut(IUniformResourceLocator * purl, LPCWSTR pwszFile)
|
|
{
|
|
IPersistFile *ppf;
|
|
HRESULT hres = purl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
hres = ppf->Save(pwszFile, TRUE);
|
|
|
|
if (SUCCEEDED(hres))
|
|
ppf->SaveCompleted(pwszFile); // return value always S_OK
|
|
|
|
ppf->Release();
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/*************************************************************\
|
|
FUNCTION: GenerateUnknownShortcutName
|
|
|
|
PARAMETERS:
|
|
pwzSourceFilename - TCHAR Source Path and Filename that cannot be created.
|
|
This value will be changed to a valid path\filename
|
|
pwzDestFilename - After pwzSourceFilename is converted to a valid filename,
|
|
the valid path will be returned here in a UNICODE string.
|
|
dwSize - Size of the pwzDestFilename buffer in chars.
|
|
|
|
DESCRIPTION:
|
|
This function will replace the filename at the end of
|
|
the path in pwzFilename with "Untitled.url". If that file
|
|
exists, it will try, "Untitled1.url" and so on until it can
|
|
be unique.
|
|
|
|
WARNING:
|
|
This function will only allow the incoming value be in ANSI
|
|
because these helper functions (like PathRemoveFileSpecW) won't
|
|
work on Win95 when compiled in UNICODE. (CharNextW isn't supported
|
|
on Win95)
|
|
\*************************************************************/
|
|
|
|
|
|
#define MAX_GEN_TRIES 100
|
|
#define GEN_EXTION_LEN (7 * sizeof(TCHAR)) // size == L"000.url" in chars
|
|
|
|
BOOL GenerateUnknownShortcutName(
|
|
IN LPCTSTR pszSourceFilename,
|
|
IN LPWSTR pwzDestFilename,
|
|
IN DWORD dwSize)
|
|
{
|
|
TCHAR szTempFilename[MAX_PATH];
|
|
TCHAR szUntitledStr[MAX_PATH];
|
|
LONG lTry = 1;
|
|
|
|
if (MLLoadString(IDS_UNTITLE_SHORTCUT, szUntitledStr, ARRAYSIZE(szUntitledStr)))
|
|
{
|
|
StrCpyN(szTempFilename, pszSourceFilename, ARRAYSIZE(szTempFilename));
|
|
PathRemoveFileSpec(szTempFilename); // "Path"
|
|
PathAddBackslash(szTempFilename); // "Path\"
|
|
|
|
// Make sure the string is large enough (including terminator). (Counting chars, not bytes)
|
|
if (dwSize > (DWORD)(lstrlen(szTempFilename) + lstrlen(szUntitledStr) + GEN_EXTION_LEN))
|
|
{
|
|
PathCombine(szUntitledStr, szTempFilename, szUntitledStr); // "Path\untitled"
|
|
wnsprintf(szTempFilename, ARRAYSIZE(szTempFilename), TEXT("%s.url"), szUntitledStr); // "Path\untitled.url"
|
|
|
|
// Make a reasonable number of tries (MAX_GEN_TRIES) to find a unique
|
|
// filename. "path\Untitled.url", "path\Untitled1.url", ...
|
|
while ((PathFileExists(szTempFilename)) && (lTry < MAX_GEN_TRIES))
|
|
wnsprintf(szTempFilename, ARRAYSIZE(szTempFilename), TEXT("%s%ld.url"), szUntitledStr, lTry++);
|
|
|
|
if (!PathFileExists(szTempFilename))
|
|
{
|
|
if (SHTCharToUnicode(szTempFilename, pwzDestFilename, dwSize) > 0)
|
|
return(TRUE);
|
|
}
|
|
}
|
|
}
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// If no directory is specified then it is simply made into a path name without any dir attached
|
|
//
|
|
STDAPI_(BOOL) GetShortcutFileName(LPCTSTR pszTarget, LPCTSTR pszTitle, LPCTSTR pszDir, LPTSTR pszOut, int cchOut)
|
|
{
|
|
TCHAR szFullName[MAX_PATH];
|
|
BOOL fAddDotUrl = TRUE;
|
|
UINT cchMax;
|
|
|
|
static const TCHAR c_szDotURL[] = TEXT(".url");
|
|
|
|
TraceMsg(DM_HISTORY, "GetShortcutFileName pszDir = %s", pszDir);
|
|
|
|
// Check if the title has some characters that just cannot be
|
|
// displayed
|
|
if(IsOS(OS_WIN95ORGREATER) || (IsOS(OS_NT) && !IsOS(OS_WIN2000ORGREATER)))
|
|
{
|
|
IMultiLanguage2 *pMultiLanguage = NULL;
|
|
if (pszTitle &&
|
|
S_OK == CoCreateInstance(
|
|
CLSID_CMultiLanguage,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_IMultiLanguage2,
|
|
(void**)&pMultiLanguage))
|
|
{
|
|
ASSERT(pMultiLanguage);
|
|
DWORD dwMode = 0;
|
|
UINT uSrcSize = lstrlenW(pszTitle);
|
|
UINT uDestSize;
|
|
if (S_OK != pMultiLanguage->ConvertStringFromUnicodeEx(&dwMode, GetACP(), (LPTSTR)pszTitle, &uSrcSize,
|
|
NULL, &uDestSize, MLCONVCHARF_NOBESTFITCHARS, NULL))
|
|
|
|
{
|
|
pszTitle = NULL; // Don't Use the title
|
|
}
|
|
|
|
pMultiLanguage->Release();
|
|
}
|
|
}
|
|
|
|
cchMax = ARRAYSIZE(szFullName) - lstrlen(c_szDotURL);
|
|
|
|
if (pszTitle && pszTitle[0])
|
|
StrCpyN(szFullName, pszTitle, cchMax);
|
|
else if (pszTarget && pszTarget[0])
|
|
{
|
|
UINT cchLen;
|
|
StrCpyN(szFullName, PathFindFileName(pszTarget), cchMax);
|
|
cchLen = lstrlen(szFullName);
|
|
if(szFullName[cchLen -1] == TEXT('/')) // Catch the common case of ftp://foo/
|
|
szFullName[cchLen -1] = TEXT('\0');
|
|
PathRemoveExtension(szFullName);
|
|
}
|
|
else
|
|
{
|
|
fAddDotUrl = FALSE;
|
|
MLLoadString(IDS_NEW_INTSHCUT, szFullName, SIZECHARS(szFullName));
|
|
}
|
|
|
|
// We need at least this many characters for the directory + extension + " (nn)" + the null terminator
|
|
|
|
// If there are multiple shortcuts with the same beginning, we'll append " (nn)", where
|
|
// nn represents a two-digit maxiumum
|
|
DWORD cc = (DWORD)(lstrlen(pszDir) + (fAddDotUrl ? ARRAYSIZE(c_szDotURL) : 1) + 5);
|
|
// We want to allow for at least a one letter filename
|
|
if ((cc + 1) > ARRAYSIZE(szFullName))
|
|
{
|
|
return FALSE;
|
|
}
|
|
szFullName[ARRAYSIZE(szFullName)-cc] = TEXT('\0');
|
|
|
|
if(fAddDotUrl)
|
|
StrCatBuff(szFullName, c_szDotURL, ARRAYSIZE(szFullName));
|
|
|
|
if(pszDir)
|
|
{
|
|
if (PathCleanupSpec(pszDir, szFullName) & PCS_FATAL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
PathCombine(pszOut, pszDir, szFullName);
|
|
}
|
|
else
|
|
{
|
|
StrCpyN(pszOut, szFullName, cchOut);
|
|
}
|
|
|
|
TraceMsg(DM_HISTORY, "GetShortcutFileName pszOut = %s", pszOut);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Unfortunately we do not already have something like this around
|
|
// If you find duplicate, please nuke this (dli)
|
|
// Warning: This function does not consider all possible URL cases.
|
|
BOOL _GetPrettyURLName(LPCTSTR pcszURL, LPCTSTR pcszDir, LPTSTR pszUrlFile, int cchUrlFile)
|
|
{
|
|
BOOL bRet = FALSE;
|
|
PARSEDURL pu = {0};
|
|
pu.cbSize = SIZEOF(PARSEDURL);
|
|
|
|
if (SUCCEEDED(ParseURL(pcszURL, &pu)))
|
|
{
|
|
LPCTSTR pszPrettyName = pu.pszSuffix;
|
|
|
|
// Get rid of the forward '/'
|
|
while (*pszPrettyName && *pszPrettyName == TEXT('/'))
|
|
pszPrettyName++;
|
|
|
|
if (!StrCmpN(pszPrettyName, TEXT("www."), 4))
|
|
pszPrettyName += 4;
|
|
|
|
if (*pszPrettyName)
|
|
bRet = GetShortcutFileName(pcszURL, pszPrettyName, pcszDir, pszUrlFile, cchUrlFile);
|
|
}
|
|
return bRet;
|
|
}
|
|
/*
|
|
* pcszURL -> "ftp://ftp.microsoft.com"
|
|
* pcszPath -> "c:\windows\desktop\internet\Microsoft FTP.url"
|
|
*/
|
|
HRESULT
|
|
CreateNewURLShortcut(
|
|
IN LPCTSTR pcszURL,
|
|
IN LPCITEMIDLIST pidlURL,
|
|
IN LPCTSTR pcszURLFile,
|
|
IN LPCTSTR pcszDir,
|
|
OUT LPTSTR pszOut,
|
|
IN int cchOut,
|
|
IN BOOL bUpdateProperties,
|
|
IN BOOL bUpdateIcon,
|
|
IN IOleCommandTarget *pCommandTarget)
|
|
{
|
|
HRESULT hr;
|
|
|
|
WCHAR wszFile[MAX_URL_STRING];
|
|
|
|
if (SHTCharToUnicode(pcszURLFile, wszFile, ARRAYSIZE(wszFile)))
|
|
{
|
|
IUniformResourceLocator *purl;
|
|
|
|
hr = CoCreateInstance(CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IUniformResourceLocator, &purl));
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (pidlURL)
|
|
{
|
|
// if we're given a pidl, try to set pidl first.
|
|
|
|
IShellLink *psl;
|
|
hr = purl->QueryInterface(IID_PPV_ARG(IShellLink, &psl));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = psl->SetIDList(pidlURL);
|
|
psl->Release();
|
|
}
|
|
}
|
|
|
|
if (!pidlURL || FAILED(hr))
|
|
hr = purl->SetURL(pcszURL, 0);
|
|
|
|
if (S_OK == hr)
|
|
IUnknown_SetSite(purl, pCommandTarget);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Persist the internet shortcut
|
|
hr = PersistShortcut(purl, wszFile);
|
|
|
|
// If the previous call fails, try again with a new Filename.
|
|
// This is needed because the other filename could have been invalid,
|
|
// which will happen if the web page's title was stored in DBCS with
|
|
// a non-English code page.
|
|
|
|
// (dli) First try a file name related to the URL, then the default untitled
|
|
if (FAILED(hr))
|
|
{
|
|
TCHAR tszFile[MAX_PATH];
|
|
BOOL bURLname = _GetPrettyURLName(pcszURL, pcszDir, tszFile, ARRAYSIZE(tszFile));
|
|
if ((bURLname && SHTCharToUnicode(tszFile, wszFile, ARRAYSIZE(wszFile)) > 0) ||
|
|
(!bURLname && GenerateUnknownShortcutName(pcszURLFile, wszFile, ARRAYSIZE(wszFile))))
|
|
{
|
|
hr = PersistShortcut(purl, wszFile);
|
|
}
|
|
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
TCHAR szFile[MAX_PATH];
|
|
VARIANT varIn = {0};
|
|
|
|
if (bUpdateIcon)
|
|
{
|
|
HRESULT hrTemp = IUnknown_Exec(purl, &CGID_ShortCut, ISHCUTCMDID_DOWNLOADICON, 0, NULL, NULL);
|
|
ASSERT(SUCCEEDED(hrTemp));
|
|
}
|
|
|
|
varIn.vt = VT_UNKNOWN;
|
|
varIn.punkVal = purl;
|
|
|
|
SHUnicodeToTChar(wszFile, szFile, ARRAYSIZE(szFile));
|
|
#ifndef UNIX
|
|
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, szFile, NULL);
|
|
#else
|
|
// IEUNIX : Synchronous notifications for unix.
|
|
SHChangeNotify(SHCNE_CREATE, ( SHCNF_PATH | SHCNF_FLUSH ), szFile, NULL);
|
|
#endif
|
|
if (pszOut)
|
|
{
|
|
StrCpyN(pszOut, wszFile, cchOut);
|
|
}
|
|
}
|
|
}
|
|
purl->Release();
|
|
}
|
|
}
|
|
else
|
|
hr = E_FAIL;
|
|
|
|
return(hr);
|
|
}
|
|
|
|
|
|
BOOL ILCanCreateLNK(LPCITEMIDLIST pidl)
|
|
{
|
|
HRESULT hr = S_FALSE;
|
|
DWORD dwAttributes = SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_FILESYSANCESTOR;
|
|
|
|
// Should call IsBrowserFrameOptionsPidlSet(BIF_PREFER_INTERNET_SHORTCUT) instead. Some URL delegate
|
|
// NSEs (FTP for one) may want .lnks instead of .url files.
|
|
// This would be great for CDocObjFolder to not set this bit so
|
|
// for .doc files so they will use the .lnk versions.
|
|
if (!pidl || IsURLChild(pidl, TRUE))
|
|
return FALSE;
|
|
|
|
hr = IEGetAttributesOf(pidl, &dwAttributes);
|
|
return (SUCCEEDED(hr) &&
|
|
(IsFlagSet(dwAttributes, SFGAO_FOLDER) ||
|
|
IsFlagSet(dwAttributes, SFGAO_FILESYSANCESTOR) )
|
|
);
|
|
}
|
|
|
|
|
|
// This API makes a callback via the IN parameter
|
|
// pCommand to inform that shortcut creation is over.
|
|
// The callback it currently sends back are :
|
|
//
|
|
|
|
|
|
STDAPI
|
|
CreateShortcutInDirEx(ISHCUT_PARAMS *pParams)
|
|
{
|
|
LPCITEMIDLIST pidlTarget = pParams->pidlTarget;
|
|
TCHAR szFileName[MAX_PATH];
|
|
TCHAR szTarget[MAX_URL_STRING];
|
|
HRESULT hres;
|
|
BOOL bIsURL = IsURLChild(pidlTarget, TRUE);
|
|
|
|
if (!ILCanCreateLNK(pidlTarget) &&
|
|
SUCCEEDED(IEGetDisplayName(pidlTarget, szTarget, SHGDN_FORPARSING)) &&
|
|
_ValidateURL(szTarget, UQF_DEFAULT))
|
|
{
|
|
BOOL bUsePidl;
|
|
|
|
SHCleanupUrlForDisplay(szTarget);
|
|
|
|
// Note that _ValidateURL() calls IURLQualify() which adds "file://"
|
|
// prefix to szTarget as appropriate.
|
|
|
|
if (bIsURL ||
|
|
(GetUrlScheme(szTarget) == URL_SCHEME_FILE))
|
|
{
|
|
bUsePidl = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// use pidl if it's not URL or file: compatible.
|
|
bUsePidl = TRUE;
|
|
}
|
|
|
|
GetShortcutFileName(szTarget, pParams->pszTitle, pParams->pszDir, szFileName, ARRAYSIZE(szFileName));
|
|
if(pParams->bUniqueName)
|
|
PathYetAnotherMakeUniqueName(szFileName, szFileName, NULL, NULL);
|
|
hres = CreateNewURLShortcut(szTarget, bUsePidl ? pidlTarget : NULL, szFileName, pParams->pszDir,
|
|
pParams->pszOut, pParams->cchOut, pParams->bUpdateProperties,
|
|
pParams->bUpdateIcon, pParams->pCommand);
|
|
|
|
|
|
} else {
|
|
hres = CreateLinkToPidl(pidlTarget, pParams->pszDir, pParams->pszTitle, pParams->pszOut, pParams->cchOut);
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
// pidlTarget ... the thing the shortcut is going to point to
|
|
// pszDir .. the directory that should hold the shortcut
|
|
|
|
// WARNING: if you change any parameters for this function, you
|
|
// need to fix up explorer.exe
|
|
STDAPI CreateShortcutInDirA(
|
|
IN LPCITEMIDLIST pidlTarget,
|
|
IN LPSTR pszTitle,
|
|
IN LPCSTR pszDir,
|
|
OUT LPSTR pszOut,
|
|
IN BOOL bUpdateProperties)
|
|
{
|
|
HRESULT hres = E_FAIL;
|
|
TCHAR szTitle[MAX_PATH];
|
|
TCHAR szDir[MAX_PATH];
|
|
TCHAR szOut[MAX_URL_STRING];
|
|
ISHCUT_PARAMS ShCutParams = {0};
|
|
SHAnsiToTChar(pszTitle, szTitle, ARRAYSIZE(szTitle));
|
|
SHAnsiToTChar(pszDir, szDir, ARRAYSIZE(szDir));
|
|
|
|
ShCutParams.pidlTarget = pidlTarget;
|
|
ShCutParams.pszTitle = szTitle;
|
|
ShCutParams.pszDir = szDir;
|
|
ShCutParams.pszOut = (pszOut ? szOut : NULL);
|
|
ShCutParams.cchOut = (int)((pszOut ? ARRAYSIZE(szOut) : 0));
|
|
ShCutParams.bUpdateProperties = bUpdateProperties;
|
|
ShCutParams.bUniqueName = FALSE;
|
|
ShCutParams.bUpdateIcon = FALSE;
|
|
ShCutParams.pCommand = NULL;
|
|
ShCutParams.pDoc = NULL;
|
|
|
|
hres = CreateShortcutInDirEx(&ShCutParams);
|
|
|
|
if (pszOut && SUCCEEDED(hres))
|
|
SHTCharToAnsi(szOut, pszOut, MAX_URL_STRING);
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
STDAPI CreateShortcutInDirW(
|
|
IN LPCITEMIDLIST pidlTarget,
|
|
IN LPWSTR pwszTitle,
|
|
IN LPCWSTR pwszDir,
|
|
OUT LPWSTR pwszOut,
|
|
IN BOOL bUpdateProperties)
|
|
{
|
|
HRESULT hres = E_FAIL;
|
|
TCHAR szTitle[MAX_PATH];
|
|
TCHAR szDir[MAX_PATH];
|
|
TCHAR szOut[MAX_URL_STRING];
|
|
ISHCUT_PARAMS ShCutParams = {0};
|
|
|
|
SHUnicodeToTChar(pwszTitle, szTitle, ARRAYSIZE(szTitle));
|
|
SHUnicodeToTChar(pwszDir, szDir, ARRAYSIZE(szDir));
|
|
|
|
ShCutParams.pidlTarget = pidlTarget;
|
|
ShCutParams.pszTitle = szTitle;
|
|
ShCutParams.pszDir = szDir;
|
|
ShCutParams.pszOut = (pwszOut ? szOut : NULL);
|
|
ShCutParams.cchOut = (int)((pwszOut ? ARRAYSIZE(szOut) : 0));
|
|
ShCutParams.bUpdateProperties = bUpdateProperties;
|
|
ShCutParams.bUniqueName = FALSE;
|
|
ShCutParams.bUpdateIcon = FALSE;
|
|
ShCutParams.pCommand = NULL;
|
|
ShCutParams.pDoc = NULL;
|
|
hres = CreateShortcutInDirEx(&ShCutParams);
|
|
|
|
if (pwszOut && SUCCEEDED(hres))
|
|
SHTCharToUnicode(szOut, pwszOut, MAX_URL_STRING);
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// Adds the given URL to the history storage
|
|
//
|
|
// pwzTitle may be NULL if no title exists
|
|
//
|
|
// Note this function may be called multiple times in a single
|
|
// page-visit. bUpdateProperties is TRUE only once during
|
|
// those sequence of calls.
|
|
//
|
|
HRESULT
|
|
AddUrlToUrlHistoryStg(
|
|
IN LPCWSTR pwszUrl,
|
|
IN LPCWSTR pwszTitle,
|
|
IN LPUNKNOWN punk,
|
|
IN BOOL fWriteHistory,
|
|
IN IOleCommandTarget *poctNotify,
|
|
IN IUnknown *punkSFHistory,
|
|
OUT UINT* pcodepage)
|
|
{
|
|
TraceMsg(DM_HISTORY, "AddUrlToUrlHistoryStg() entered url = %s, title = %s, punk = %X, fwrite = %d, poct = %X, punkHist = %X, cp = %d",
|
|
pwszUrl, pwszTitle, punk,fWriteHistory,poctNotify,punkSFHistory,pcodepage);
|
|
|
|
|
|
IUrlHistoryPriv *pUrlHistStg;
|
|
HRESULT hr;
|
|
|
|
if (!pwszUrl)
|
|
return E_POINTER;
|
|
|
|
if (punk == NULL)
|
|
{
|
|
|
|
hr = CoCreateInstance(CLSID_CUrlHistory, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_IUrlHistoryPriv, (void **)&pUrlHistStg);
|
|
}
|
|
else
|
|
{
|
|
|
|
// query the pointer for IServiceProvider so we can get the IUrlHistoryStg
|
|
hr = IUnknown_QueryService(punk,SID_SUrlHistory, IID_IUrlHistoryPriv, (LPVOID *)&pUrlHistStg);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// This demostrate the mechanism to get the codepage for URL.
|
|
//
|
|
hr = pUrlHistStg->AddUrlAndNotifyCP(pwszUrl,
|
|
pwszTitle,
|
|
0,
|
|
fWriteHistory,
|
|
poctNotify,
|
|
punkSFHistory,
|
|
pcodepage);
|
|
pUrlHistStg->Release();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|