windows-nt/Source/XPSP1/NT/shell/browseui/shellurl.cpp

2698 lines
94 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/**************************************************************\
FILE: shellurl.cpp
DESCRIPTION:
Implements CShellUrl.
\**************************************************************/
#include "priv.h"
#include "resource.h"
#include "util.h"
#include "shellurl.h"
#include "bandprxy.h"
#include "mluisupp.h"
#ifdef UNIX
#include "unixstuff.h"
#endif
// We need to reroute radio urls
#define WZ_RADIO_PROTOCOL L"vnd.ms.radio:"
//#define FEATURE_WILDCARD_SUPPORT
#define CH_DOT TEXT('.')
#define CH_SPACE TEXT(' ')
#define CH_SEPARATOR TEXT('/')
#define CH_FRAGMENT TEXT('#')
#ifdef FEATURE_WILDCARD_SUPPORT
#define CH_ASTRISK TEXT('*')
#define CH_QUESTIONMARK TEXT('?')
#endif // FEATURE_WILDCARD_SUPPORT
#define SZ_SPACE TEXT(" ")
#define SZ_SEPARATOR TEXT("/")
#define SZ_UNC TEXT("\\\\")
#ifndef UNIX
#define CH_FILESEPARATOR TEXT('\\')
#define SZ_FILESEPARATOR TEXT("\\")
#else
#define CH_FILESEPARATOR TEXT('/')
#define SZ_FILESEPARATOR TEXT("/")
#endif
#define CE_PATHGROW 1
#define IS_SHELL_SEPARATOR(ch) ((CH_SEPARATOR == ch) || (CH_FILESEPARATOR == ch))
// Private Functions
BOOL _FixDriveDisplayName(LPCTSTR pszStart, LPCTSTR pszCurrent, LPCITEMIDLIST pidl);
#define TF_CHECKITEM 0 // TF_BAND|TF_GENERAL
/****************************************************\
CShellUrl Constructor
\****************************************************/
CShellUrl::CShellUrl()
{
TraceMsg(TF_SHDLIFE, "ctor CShellUrl %x", this);
// Don't want this object to be on the stack
ASSERT(!m_pszURL);
ASSERT(!m_pszArgs);
ASSERT(!m_pstrRoot);
ASSERT(!m_pidl);
ASSERT(!m_pidlWorkingDir);
ASSERT(!m_hdpaPath);
ASSERT(!m_dwGenType);
ASSERT(!m_hwnd);
}
/****************************************************\
CShellUrl destructor
\****************************************************/
CShellUrl::~CShellUrl()
{
Reset();
if (m_pstrRoot)
{
LocalFree(m_pstrRoot);
m_pstrRoot = NULL;
}
if (m_pidlWorkingDir)
ILFree(m_pidlWorkingDir);
_DeletePidlDPA(m_hdpaPath);
TraceMsg(TF_SHDLIFE, "dtor CShellUrl %x", this);
}
//*** CShellUrl::IUnknown::* {
ULONG CShellUrl::AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG CShellUrl::Release()
{
ASSERT(_cRef > 0);
// n.b. returns <0,=0,>0 (not actual dec result)
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
HRESULT CShellUrl::QueryInterface(REFIID riid, void **ppvObj)
{
static const QITAB qit[] = {
QITABENT(CShellUrl, IAddressBarParser), // IID_IUserAssist
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
//+-------------------------------------------------------------------------
// Creates and instance of CShellUrl
//--------------------------------------------------------------------------
STDAPI CShellUrl_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
// Note - Aggregation checking is handled in class factory
*ppunk = NULL;
CShellUrl* pShellUrl = new CShellUrl();
if (pShellUrl)
{
*ppunk = SAFECAST(pShellUrl, IAddressBarParser *);
(*ppunk)->AddRef();
return S_OK;
}
return E_OUTOFMEMORY;
}
/****************************************************\
FUNCTION: Clone
PARAMETERS
pShellUrl - This is the pointer to object that
we want to clone
DESCRIPTION:
This function will make a deep copy of the passed
object into 'this'
\****************************************************/
HRESULT CShellUrl::Clone(CShellUrl * pShellUrl)
{
HRESULT hr = S_OK;
if (!pShellUrl)
{
hr = E_POINTER;
goto exit;
}
Str_SetPtr(&m_pszURL, pShellUrl->m_pszURL);
Str_SetPtr(&m_pszDisplayName, pShellUrl->m_pszDisplayName);
Str_SetPtr(&m_pszArgs, pShellUrl->m_pszArgs);
Str_SetPtr(&m_pstrRoot, pShellUrl->m_pstrRoot);
m_dwGenType = pShellUrl->m_dwGenType;
m_dwFlags = pShellUrl->m_dwFlags;
m_hwnd = pShellUrl->m_hwnd;
if (m_pidl)
{
ILFree(m_pidl);
m_pidl = NULL;
}
if (pShellUrl->m_pidl)
{
m_pidl = ILClone(pShellUrl->m_pidl);
if (!m_pidl)
{
hr = E_OUTOFMEMORY;
goto exit;
}
}
if (m_pidlWorkingDir)
{
ILFree(m_pidlWorkingDir);
m_pidlWorkingDir = NULL;
}
if (pShellUrl->m_pidlWorkingDir)
{
m_pidlWorkingDir = ILClone(pShellUrl->m_pidlWorkingDir);
if (!m_pidlWorkingDir)
{
hr = E_OUTOFMEMORY;
goto exit;
}
}
_DeletePidlDPA(m_hdpaPath);
m_hdpaPath = NULL;
if (pShellUrl->m_hdpaPath)
{
m_hdpaPath = DPA_Create(CE_PATHGROW);
for(int nPathIndex = 0; nPathIndex < DPA_GetPtrCount(pShellUrl->m_hdpaPath); nPathIndex++)
{
LPITEMIDLIST pidlCurrPath = (LPITEMIDLIST) DPA_GetPtr(pShellUrl->m_hdpaPath, nPathIndex);
LPITEMIDLIST pidlNew = ILClone(pidlCurrPath);
if (pidlNew)
DPA_AppendPtr(m_hdpaPath, pidlNew);
else
{
hr = E_OUTOFMEMORY;
goto exit;
}
}
}
exit:
return hr;
}
/****************************************************\
FUNCTION: Execute
PARAMETERS
pbp - This is the pointer to the interface
which is needed to find a new topmost
window or the associated browser window.
pfDidShellExec (Out Optional) - This parameter
can be NULL. If not NULL, it will be set
to TRUE if this Execute() called ShellExec.
This is needed by callers that wait for
DISPID_NAVIGATECOMPLETE which will never happen
in this case.
DESCRIPTION:
This command will determine if the current
shell url needs to be shell executed or navigated
to. If it needs to be navigated to, it will try
to navigate to the PIDL, otherwise, it will navigate
to the string version.
\****************************************************/
HRESULT CShellUrl::Execute(IBandProxy * pbp, BOOL * pfDidShellExec, DWORD dwExecFlags)
{
HRESULT hr = S_FALSE; // S_FALSE until navigation occurs.
ULONG ulShellExecFMask = (IsFlagSet(dwExecFlags, SHURL_EXECFLAGS_SEPVDM)) ? SEE_MASK_FLAG_SEPVDM : 0;
ASSERT(IS_VALID_CODE_PTR(pbp, IBandProxy *));
ASSERT(!pfDidShellExec || IS_VALID_WRITE_PTR(pfDidShellExec, BOOL));
if (!EVAL(pbp))
return E_INVALIDARG;
#ifdef UNIX
// When trying to execute a shellurl we will first check if it is a local file
// url. If so check if there is a proper file association with it. If not give
// error and bail out.
TCHAR szTmpPath[MAX_URL_STRING];
BOOL bCheckForAssoc = FALSE;
if (m_pidl)
{
// Get Path from pidl
IEGetNameAndFlags(m_pidl, SHGDN_FORPARSING, szTmpPath, SIZECHARS(szTmpPath), NULL);
//SHTCharToAnsi( szTmpPath, szTmpPathAnsi, ARRAYSIZE(szTmpPathAnsi) );
// Path is file path only in Ansi ??
if (PathIsFilePath(szTmpPath) && PathFileExists(szTmpPath) )
bCheckForAssoc = TRUE;
}
else
if (GetUrlScheme(m_pszURL) == URL_SCHEME_FILE )
{
HRESULT hr = S_FALSE;
TCHAR szQualifiedUrl[MAX_URL_STRING];
DWORD cchSize = ARRAYSIZE(szQualifiedUrl);
hr = (ParseURLFromOutsideSource(m_pszURL, szQualifiedUrl, &cchSize, NULL) ? S_OK : E_FAIL);
if (EVAL(SUCCEEDED(hr)))
{
cchSize = ARRAYSIZE(szTmpPath);
hr = PathCreateFromUrl(szQualifiedUrl, szTmpPath, &cchSize, 0);
if (EVAL(SUCCEEDED(hr)) && PathFileExists(szTmpPath))
bCheckForAssoc = TRUE;
}
}
if (bCheckForAssoc)
{
// FileHasProperAssociation returns true for all known
// file types ( even directories )
DWORD cch;
if (!PathIsExe( szTmpPath )
&& !FileHasProperAssociation(szTmpPath))
{
MLShellMessageBox(m_hwnd,
MAKEINTRESOURCE(IDS_SHURL_ERR_NOASSOC),
MAKEINTRESOURCE(IDS_SHURL_ERR_TITLE),
(MB_OK | MB_ICONERROR));
return E_FAIL;
}
}
#endif
// Is the following true: 1) The caller wants other browsers to be able to handle the URLs,
// 2) The ShellUrl is a Web Url, and 3) IE doesn't own HTML files.
// If all of these are true, then we will just ShellExec() the URL String so the
// default handler can handle it.
// Also if the user wants us to browse in a new process and we are currently in the shell process,
// we will launch IE to handle the url.
if ((IsFlagSet(dwExecFlags, SHURL_EXECFLAGS_DONTFORCEIE) && IsWebUrl() && !IsIEDefaultBrowser())
#ifdef BROWSENEWPROCESS_STRICT // "Nav in new process" has become "Launch in new process", so this is no longer needed
|| (IsWebUrl() && IsBrowseNewProcessAndExplorer())
#endif
)
{
hr = _UrlShellExec();
ASSERT(S_OK == hr);
}
if ((S_OK != hr) && m_pidl && _CanUseAdvParsing())
{
// We will only Shell Exec it if:
// 1. We want to Force IE (over other web browsers) and it's not browsable, even by non-default owners.
// 2. It's not browsable by default owners.
if (!ILIsBrowsable(m_pidl, NULL))
{
if (pfDidShellExec)
*pfDidShellExec = TRUE;
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: Execute() Going to _PidlShellExec(>%s<)", Dbg_PidlStr(m_pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
// If NULL == m_pidl, then the String will be used.
hr = _PidlShellExec(m_pidl, ulShellExecFMask);
}
}
if (S_OK != hr)
{
VARIANT vFlags = {0};
vFlags.vt = VT_I4;
vFlags.lVal = navAllowAutosearch;
if (pfDidShellExec)
*pfDidShellExec = FALSE;
// We prefer pidls, thank you
if (m_pidl)
{
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: Execute() Going to pbp->NavigateToPIDL(>%s<)", Dbg_PidlStr(m_pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
hr = pbp->NavigateToPIDL(m_pidl);
}
else
{
ASSERT(m_pszURL);
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: Execute() Going to pbp->NavigateToURL(%s)", m_pszURL);
#ifdef UNICODE
hr = pbp->NavigateToURL(m_pszURL, &vFlags);
#else
WCHAR wszURL[MAX_URL_STRING];
SHTCharToUnicode(m_pszURL, wszURL, ARRAYSIZE(wszURL));
hr = pbp->NavigateToURL(wszURL, &vFlags);
#endif
}
VariantClearLazy(&vFlags);
}
#ifdef UNIX
// PidlExec failed or not done at all.
// Before passing it for navigation check if it is a shell url
// if so, execute it.
// The above comment is nolonger true. This code is moved here
// from before the navigate to fix attempt to createprocess problem
if (S_OK != hr && m_pszURL && IsShellUrl( m_pszURL, TRUE ) )
{
if (pfDidShellExec)
*pfDidShellExec = TRUE;
hr = _UrlShellExec();
ASSERT(S_OK == hr);
}
#endif
return hr;
}
/****************************************************\
FUNCTION: _PidlShellExec
PARAMETERS
pidl - The Pidl to execute.
DESCRIPTION:
This function will call ShellExecEx() on the
pidl specified. It will also fill in the Current
Working Directory and Command Line Arguments if there
are any.
\****************************************************/
HRESULT CShellUrl::_PidlShellExec(LPCITEMIDLIST pidl, ULONG ulShellExecFMask)
{
HRESULT hr = E_FAIL;
SHELLEXECUTEINFO sei = {0};
ASSERT(IS_VALID_PIDL(pidl));
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: _PidlShellExec() Going to execute pidl=>%s<", Dbg_PidlStr(pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
if (m_pidlWorkingDir)
{
// note, this must be MAX_URL_STRING since IEGetDisplayName can return a URL.
WCHAR szCWD[MAX_URL_STRING];
IEGetDisplayName(m_pidlWorkingDir, szCWD, SHGDN_FORPARSING);
if (PathIsFilePath(szCWD))
{
sei.lpDirectory = szCWD;
}
}
/**** TODO: Get the Current Working Directory of top most window
if (!sei.lpDirectory || !sei.lpDirectory[0])
{
GetCurrentDirectory(SIZECHARS(szCurrWorkDir), szCurrWorkDir);
sei.lpDirectory = szCurrWorkDir;
}
*****/
sei.cbSize = sizeof(SHELLEXECUTEINFO);
sei.lpIDList = (LPVOID) pidl;
sei.lpParameters = m_pszArgs;
sei.nShow = SW_SHOWNORMAL;
sei.fMask = SEE_MASK_FLAG_NO_UI | (pidl ? SEE_MASK_INVOKEIDLIST : 0) | ulShellExecFMask;
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: _PidlShellExec() Cmd=>%s<, Args=>%s<, WorkDir=>%s<",
GEN_DEBUGSTR(sei.lpFile), GEN_DEBUGSTR(sei.lpParameters), GEN_DEBUGSTR(sei.lpDirectory));
if (ShellExecuteEx(&sei))
hr = S_OK;
else
{
#ifdef DEBUG
DWORD dwGetLastError = GetLastError();
TraceMsg(TF_ERROR, "ShellUrl: _PidlShellExec() ShellExecuteEx() failed for this item. Cmd=>%s<; dwGetLastError=%lx", GEN_DEBUGSTR(sei.lpParameters), dwGetLastError);
#endif // DEBUG
hr = E_FAIL;
}
return hr;
}
/****************************************************\
FUNCTION: _UrlShellExec
DESCRIPTION:
This function will call ShellExecEx() on the
URL. This is so other popular browsers can handle
the URL if they own HTML and other web files.
\****************************************************/
HRESULT CShellUrl::_UrlShellExec(void)
{
HRESULT hr = E_FAIL;
SHELLEXECUTEINFO sei = {0};
ASSERT(m_pszURL);
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: _UrlShellExec() Going to execute URL=>%s<", m_pszURL);
sei.cbSize = sizeof(sei);
sei.lpFile = m_pszURL;
sei.nShow = SW_SHOWNORMAL;
sei.fMask = SEE_MASK_FLAG_NO_UI;
if (m_pszURL && ShellExecuteEx(&sei))
hr = S_OK;
else
hr = E_FAIL;
return hr;
}
// The following function is identical to ParseURLFromOutsideSource except that it
// enables autocorrect and sets pbWasCorrected to TRUE if the string was corrected
BOOL CShellUrl::_ParseURLFromOutsideSource
(
LPCWSTR psz,
LPWSTR pszOut,
LPDWORD pcchOut,
LPBOOL pbWasSearchURL, // if converted to a search string
LPBOOL pbWasCorrected // if url was autocorrected
)
{
// This is our hardest case. Users and outside applications might
// type fully-escaped, partially-escaped, or unescaped URLs at us.
// We need to handle all these correctly. This API will attempt to
// determine what sort of URL we've got, and provide us a returned URL
// that is guaranteed to be FULLY escaped.
IURLQualify(psz, UQF_DEFAULT | UQF_AUTOCORRECT, pszOut, pbWasSearchURL, pbWasCorrected);
//
// Go ahead and canonicalize this appropriately
//
if (FAILED(UrlCanonicalize(pszOut, pszOut, pcchOut, URL_ESCAPE_SPACES_ONLY)))
{
//
// we cant resize from here.
// NOTE UrlCan will return E_POINTER if it is an insufficient buffer
//
return FALSE;
}
return TRUE;
}
#ifdef UNICODE
HRESULT CShellUrl::ParseFromOutsideSource(LPCSTR pcszUrlIn, DWORD dwParseFlags, PBOOL pfWasCorrected)
{
WCHAR wzUrl[MAX_URL_STRING];
SHAnsiToUnicode(pcszUrlIn, wzUrl, ARRAYSIZE(wzUrl));
return ParseFromOutsideSource(wzUrl, dwParseFlags, pfWasCorrected);
}
#endif // UNICODE
/****************************************************\
FUNCTION: _TryQuickParse
PARAMETERS
pcszUrlIn - String to parse.
dwParseFlags - Flags to modify parsing. (Defined in iedev\inc\shlobj.w)
DESCRIPTION:
We prefer to call g_psfDesktop->ParseDisplayName()
and have it do the parse really quickly and without
enumerating the name space. We need this for things
that are parsed but not enumerated, which includes:
a) hidden files, b) other.
However, we need to not parse URLs if the caller
doesn't want to accept them.
\****************************************************/
HRESULT CShellUrl::_TryQuickParse(LPCTSTR pszUrl, DWORD dwParseFlags)
{
HRESULT hr = E_FAIL; // E_FAIL means we don't know yet.
int nScheme = GetUrlScheme(pszUrl);
// Don't parse unknown schemes because we may
// want to "AutoCorrect" them later.
if (URL_SCHEME_UNKNOWN != nScheme)
{
if ((dwParseFlags & SHURL_FLAGS_NOWEB) &&
(URL_SCHEME_INVALID != nScheme) &&
(URL_SCHEME_UNKNOWN != nScheme) &&
(URL_SCHEME_MK != nScheme) &&
(URL_SCHEME_SHELL != nScheme) &&
(URL_SCHEME_LOCAL != nScheme) &&
(URL_SCHEME_RES != nScheme) &&
(URL_SCHEME_ABOUT != nScheme))
{
// Skip parsing this because it's a web item, and
// the caller wants to filter those out.
}
else
{
hr = IEParseDisplayNameWithBCW(CP_ACP, pszUrl, NULL, &m_pidl);
}
}
return hr;
}
/****************************************************\
FUNCTION: ParseFromOutsideSource
PARAMETERS
pcszUrlIn - String to parse.
dwParseFlags - Flags to modify parsing. (Defined in iedev\inc\shlobj.w)
pfWasCorrected - [out] if url was autocorrected (can be null)
DESCRIPTION:
Convert a string to a fully qualified shell url. Parsing
falls into one of the following categories:
1. If the URL starts with "\\", check if it's a UNC Path.
2. If the URL starts something that appears to indicate that it starts
from the root of the shell name space (Desktop), then check if it
is an absolute ShellUrl.
(Only do #3 and #4 if #2 was false)
3. Check if the string is relative to the Current Working Directory.
4. Check if the string is relative to one of the items in the
"Shell Path".
5. Check if the string is in the system's AppPath or DOS Path.
6. Check if this is a URL to Navigate to. This call will pretty much
always succeeded, because it will accept anything as an AutoSearch
URL.
\****************************************************/
HRESULT CShellUrl::ParseFromOutsideSource(LPCTSTR pcszUrlIn, DWORD dwParseFlags, PBOOL pfWasCorrected)
{
HRESULT hr = E_FAIL; // E_FAIL means we don't know yet.
TCHAR szUrlExpanded[MAX_URL_STRING];
LPTSTR pszUrlInMod = (LPTSTR) szUrlExpanded; // For iteration only
LPTSTR pszErrorURL = NULL;
BOOL fPossibleWebUrl = FALSE;
int nScheme;
BOOL fDisable = SHRestricted(REST_NORUN);
m_dwFlags = dwParseFlags;
if (pfWasCorrected)
*pfWasCorrected = FALSE;
if (!pcszUrlIn[0])
return E_FAIL;
if (!StrCmpNIW(WZ_RADIO_PROTOCOL, pcszUrlIn, ARRAYSIZE(WZ_RADIO_PROTOCOL)-1))
{
// We need to reroute vnd.ms.radio: urls to the regular player, since we don't support the radio bar anymore.
// (Media bar or the external player)
StrCpyN(szUrlExpanded, pcszUrlIn+ARRAYSIZE(WZ_RADIO_PROTOCOL)-1, SIZECHARS(szUrlExpanded));
}
else
{
SHExpandEnvironmentStrings(pcszUrlIn, szUrlExpanded, SIZECHARS(szUrlExpanded));
}
PathRemoveBlanks(pszUrlInMod);
Reset(); // Empty info because we will fill it in if successful or leave empty if we fail.
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: ParseFromOutsideSource() Begin. pszUrlInMod=%s", pszUrlInMod);
// The display Name will be exactly what the user entered.
Str_SetPtr(&m_pszDisplayName, pszUrlInMod);
nScheme = GetUrlScheme(pszUrlInMod);
if ((URL_SCHEME_FILE != nScheme) || !fDisable) // Don't parse FILE: URLs if Start->Run is disabled.
{
// For HTTP and FTP we can make a few minor corrections
if (IsFlagSet(dwParseFlags, SHURL_FLAGS_AUTOCORRECT) &&
(URL_SCHEME_HTTP == nScheme || URL_SCHEME_FTP == nScheme || URL_SCHEME_HTTPS == nScheme))
{
if (S_OK == UrlFixupW(szUrlExpanded, szUrlExpanded, ARRAYSIZE(szUrlExpanded)) &&
pfWasCorrected)
{
*pfWasCorrected = TRUE;
}
}
hr = _TryQuickParse(szUrlExpanded, dwParseFlags);
if (FAILED(hr))
{
// Does this string refer to something in the shell namespace that is
// not a standard URL AND can we do shell namespace parsing AND
// can we use advanced parsing on it?
if (((URL_SCHEME_UNKNOWN == nScheme) ||
(URL_SCHEME_SHELL == nScheme) ||
(URL_SCHEME_INVALID == nScheme)) &&
!(SHURL_FLAGS_NOSNS & dwParseFlags) && _CanUseAdvParsing())
{
fPossibleWebUrl = TRUE;
// Yes; is this URL absolute (e.g., "\foo" or "Desktop\foo")?
if (IS_SHELL_SEPARATOR(pszUrlInMod[0]) ||
(S_OK == StrCmpIWithRoot(pszUrlInMod, FALSE, &m_pstrRoot)))
{
// Yes
// CASE #1.
// It starts with "\\", so it's probably a UNC,
// so _ParseUNC() will call _ParseRelativePidl() with the Network
// Neighborhood PIDL as the relative location. This is needed
// because commands like this "\\bryanst2\public\program.exe Arg1 Arg2"
// that need to be shell executed.
if (PathIsUNC(pszUrlInMod))
{
hr = _ParseUNC(pszUrlInMod, &fPossibleWebUrl, dwParseFlags, FALSE);
// If we got this far, don't pass off to Navigation if _ParseUNC() failed.
fPossibleWebUrl = FALSE;
}
if (FAILED(hr))
{
if (IS_SHELL_SEPARATOR(pszUrlInMod[0]))
{
pszErrorURL = pszUrlInMod; // We want to keep the '\' for the error message.
pszUrlInMod++; // Skip past '\'.
}
// See if we need to advance past a "Desktop".
if (S_OK == StrCmpIWithRoot(pszUrlInMod, FALSE, &m_pstrRoot))
{
pszUrlInMod += lstrlen(m_pstrRoot);
if (IS_SHELL_SEPARATOR(pszUrlInMod[0]))
pszUrlInMod++;
if (!pszUrlInMod[0])
{
// The only thing the user entered was [...]"desktop"[\]
// so just clone the Root pidl.
return _SetPidl(&s_idlNULL);
}
}
// CASE #2. Passing NULL indicates that it should test relative
// to the root.
hr = _ParseRelativePidl(pszUrlInMod, &fPossibleWebUrl, dwParseFlags, NULL, FALSE, FALSE);
}
}
else
{
// No; it is relative
int nPathCount = 0;
int nPathIndex;
if (m_hdpaPath)
nPathCount = DPA_GetPtrCount(m_hdpaPath);
// CASE #3. Parse relative to the Current Working Directory.
// Only valid if this object's ::SetCurrentWorkingDir()
// method was called.
if (m_pidlWorkingDir)
{
hr = _ParseRelativePidl(pszUrlInMod, &fPossibleWebUrl, dwParseFlags, m_pidlWorkingDir, TRUE, TRUE);
#ifdef FEATURE_WILDCARD_SUPPORT
if (FAILED(hr) && m_pidlWorkingDir &&
!StrChr(pszUrlInMod, CH_SEPARATOR) && !StrChr(pszUrlInMod, CH_FILESEPARATOR))
{
LPTSTR pszWildCard = StrChr(pszUrlInMod, CH_ASTRISK);
if (!pszWildCard)
pszWildCard = StrChr(pszUrlInMod, CH_QUESTIONMARK);
if (pszWildCard)
{
IOleWindow * pow;
m_pidlWorkingDir
}
}
#endif // FEATURE_WILDCARD_SUPPORT
if (FAILED(hr))
{
//
// Check if the place we are navigating to is the same as the current
// working directory. If so then there is a good chance that the user just
// pressed the enter key / go button in the addressbar and we should simply
// refresh the current directory.
//
WCHAR szCurrentDir[MAX_URL_STRING];
HRESULT hr2 = IEGetNameAndFlags(m_pidlWorkingDir, SHGDN_FORPARSING | SHGDN_FORADDRESSBAR, szCurrentDir, ARRAYSIZE(szCurrentDir), NULL);
if (FAILED(hr2))
{
// Sometimes SHGDN_FORPARSING fails and the addressbar then tries SHGDN_NORMAL
hr2 = IEGetNameAndFlags(m_pidlWorkingDir, SHGDN_NORMAL | SHGDN_FORADDRESSBAR, szCurrentDir, ARRAYSIZE(szCurrentDir), NULL);
}
if (SUCCEEDED(hr2))
{
if (0 == StrCmpI(pszUrlInMod, szCurrentDir))
{
// It matches so stay in the current working directory
_SetPidl(m_pidlWorkingDir);
hr = S_OK;
}
}
}
}
else
{
// TODO: Get the Current Working Directory of the top most window.
// hr = _ParseRelativePidl(pszUrlInMod, &fPossibleWebUrl, dwParseFlags, pshurlCWD, TRUE, TRUE);
}
// CASE #4. Parse relative to the entries in the "Shell Path".
// Only valid if this object's ::AddPath() method was
// called at least once.
for (nPathIndex = 0; FAILED(hr) && nPathIndex < nPathCount; nPathIndex++)
{
LPITEMIDLIST pidlCurrPath = (LPITEMIDLIST) DPA_GetPtr(m_hdpaPath, nPathIndex);
if (EVAL(pidlCurrPath))
{
ASSERT(IS_VALID_PIDL(pidlCurrPath));
hr = _ParseRelativePidl(pszUrlInMod, &fPossibleWebUrl, dwParseFlags, pidlCurrPath, FALSE, FALSE);
}
}
// CASE #5. We need to see if the beginning of the string matches
// the entry in the AppPaths or DOS Path
if (FAILED(hr) && IsFlagClear(dwParseFlags, SHURL_FLAGS_NOPATHSEARCH))
hr = _QualifyFromPath(pszUrlInMod, dwParseFlags);
}
}
else
{
if (URL_SCHEME_FILE != nScheme)
fPossibleWebUrl = TRUE;
}
}
}
if (FAILED(hr) && !fPossibleWebUrl && !fDisable)
{
// Did the caller want to suppress UI (Error Messages)
if (IsFlagClear(dwParseFlags, SHURL_FLAGS_NOUI))
{
if (!pszErrorURL)
pszErrorURL = pszUrlInMod;
ASSERT(pszErrorURL);
// We were able to parse part of it, but failed parsing the second or
// later segment. This means we need to inform the user of their
// misspelling. They can force AutoSearch with "go xxx" or "? xxx"
// if they are trying to AutoSearch something that appears in their
// Shell Name Space.
MLShellMessageBox(m_hwnd, MAKEINTRESOURCE(IDS_SHURL_ERR_PARSE_FAILED),
MAKEINTRESOURCE(IDS_SHURL_ERR_TITLE),
(MB_OK | MB_ICONERROR), pszErrorURL);
}
}
else if (S_OK != hr)
{
if (!(dwParseFlags & SHURL_FLAGS_NOWEB))
{
TCHAR szQualifiedUrl[MAX_URL_STRING];
DWORD cchSize = SIZECHARS(szQualifiedUrl);
SHExpandEnvironmentStrings(pcszUrlIn, szUrlExpanded, SIZECHARS(szUrlExpanded));
PathRemoveBlanks(szUrlExpanded);
// Unintialized szQualifiedUrl causes junk characters to appear on
// addressbar and causes registry corruption on UNIX.
szQualifiedUrl[0] = TEXT('\0');
// CASE #6. Just check if this is a URL to Navigate to. This call will
// pretty much always succeeded, because it will accept
// anything as a search URL.
if (IsFlagSet(dwParseFlags, SHURL_FLAGS_AUTOCORRECT))
{
hr = (_ParseURLFromOutsideSource(szUrlExpanded, szQualifiedUrl, &cchSize, NULL, pfWasCorrected) ? S_OK : E_FAIL);
}
else
{
hr = (ParseURLFromOutsideSource(szUrlExpanded, szQualifiedUrl, &cchSize, NULL) ? S_OK : E_FAIL);
}
if (SUCCEEDED(hr))
{
SetUrl(szQualifiedUrl, GENTYPE_FROMURL);
Str_SetPtr(&m_pszDisplayName, szQualifiedUrl); // The display Name will be exactly what the user entered.
}
ASSERT(!m_pidl);
if (fDisable && SUCCEEDED(hr))
{
nScheme = GetUrlScheme(szQualifiedUrl);
// We will allow all but the following schemes:
if ((URL_SCHEME_SHELL != nScheme) &&
(URL_SCHEME_FILE != nScheme) &&
(URL_SCHEME_UNKNOWN != nScheme) &&
(URL_SCHEME_INVALID != nScheme))
{
fDisable = FALSE;
}
}
}
}
if (fDisable && ((URL_SCHEME_FILE == nScheme) || (URL_SCHEME_INVALID == nScheme) || (URL_SCHEME_UNKNOWN == nScheme)))
{
if (IsFlagClear(dwParseFlags, SHURL_FLAGS_NOUI))
{
MLShellMessageBox(m_hwnd, MAKEINTRESOURCE(IDS_SHURL_ERR_PARSE_NOTALLOWED),
MAKEINTRESOURCE(IDS_SHURL_ERR_TITLE),
(MB_OK | MB_ICONERROR), pszUrlInMod);
}
hr = E_ACCESSDENIED;
Reset(); // Just in case the caller ignores the return value.
}
return hr;
}
/****************************************************\
FUNCTION: _QualifyFromPath
PARAMETERS:
pcszFilePathIn - String that may be in the Path.
dwFlags - Parse Flags, not currently used.
DESCRIPTION:
This function will call _QualifyFromAppPath()
to see if the item exists in the AppPaths. If not,
it will check in the DOS Path Env. variable with a
call to _QualifyFromDOSPath().
\****************************************************/
HRESULT CShellUrl::_QualifyFromPath(LPCTSTR pcszFilePathIn, DWORD dwFlags)
{
HRESULT hr = _QualifyFromAppPath(pcszFilePathIn, dwFlags);
if (FAILED(hr))
hr = _QualifyFromDOSPath(pcszFilePathIn, dwFlags);
return hr;
}
/****************************************************\
FUNCTION: _QualifyFromDOSPath
PARAMETERS:
pcszFilePathIn - String that may be in the Path.
dwFlags - Parse Flags, not currently used.
DESCRIPTION:
See if pcszFilePathIn exists in the DOS Path Env
variable. If so, set the ShellUrl to that location.
\****************************************************/
HRESULT CShellUrl::_QualifyFromDOSPath(LPCTSTR pcszFilePathIn, DWORD dwFlags)
{
HRESULT hr = E_FAIL;
TCHAR szPath[MAX_PATH];
LPTSTR pszEnd = (LPTSTR) pcszFilePathIn;
BOOL fContinue = TRUE;
do
{
hr = _GetNextPossibleFullPath(pcszFilePathIn, &pszEnd, szPath, SIZECHARS(szPath), &fContinue);
if (SUCCEEDED(hr))
{
if (PathFindOnPathEx(szPath, NULL, (PFOPEX_OPTIONAL | PFOPEX_COM | PFOPEX_BAT | PFOPEX_PIF | PFOPEX_EXE)))
{
_GeneratePidl(szPath, GENTYPE_FROMPATH);
if (!ILIsFileSysFolder(m_pidl))
{
Str_SetPtr(&m_pszArgs, pszEnd); // Set aside Args
break;
}
}
if (fContinue)
pszEnd = CharNext(pszEnd);
hr = E_FAIL;
}
}
while (FAILED(hr) && fContinue);
return hr;
}
/****************************************************\
FUNCTION: _QualifyFromAppPath
PARAMETERS:
pcszFilePathIn - String that may be in the Path.
dwFlags - Parse Flags, not currently used.
DESCRIPTION:
See if pcszFilePathIn exists in the AppPaths
Registry Section. If so, set the ShellUrl to that location.
\****************************************************/
HRESULT CShellUrl::_QualifyFromAppPath(LPCTSTR pcszFilePathIn, DWORD dwFlags)
{
HRESULT hr = E_FAIL;
TCHAR szFileName[MAX_PATH];
TCHAR szRegKey[MAX_PATH];
DWORD dwType;
DWORD cbData = sizeof(szFileName);
DWORD cchNewPathSize;
StrCpyN(szFileName, pcszFilePathIn, SIZECHARS(szFileName));
PathRemoveArgs(szFileName); // Get Rid of Args (Will be added later)
cchNewPathSize = lstrlen(szFileName); // Get size so we known where to find args in pcszFilePathIn
PathAddExtension(szFileName, TEXT(".exe")); // Add extension if needed.
wnsprintf(szRegKey, ARRAYSIZE(szRegKey), TEXT("%s\\%s"), STR_REGKEY_APPPATH, szFileName);
if (NOERROR == SHGetValue(HKEY_LOCAL_MACHINE, szRegKey, TEXT(""), &dwType, (LPVOID) szFileName, &cbData))
{
// 1. Create Pidl from String.
hr = _GeneratePidl(szFileName, GENTYPE_FROMPATH);
// 2. Set aside Args
ASSERT((DWORD)lstrlen(pcszFilePathIn) >= cchNewPathSize);
Str_SetPtr(&m_pszArgs, &(pcszFilePathIn[cchNewPathSize]));
}
return hr;
}
/****************************************************\
FUNCTION: _ParseUNC
PARAMETERS:
pcszUrlIn - URL, which can be a UNC path.
pfPossibleWebUrl - Set to FALSE if we find that the user has attempted
to enter a Shell Url or File url but misspelled one
of the segments.
dwFlags - Parse Flags
fQualifyDispName - If TRUE when we known that we need to force the
URL to be fully qualified if we bind to the destination.
This is needed because we are using state information to
find the destination URL and that state information won't
be available later.
DESCRIPTION:
See if the URL passed in is a valid path
relative to "SHELL:Desktop/Network Neighborhood".
\****************************************************/
HRESULT CShellUrl::_ParseUNC(LPCTSTR pcszUrlIn, BOOL * pfPossibleWebUrl, DWORD dwFlags, BOOL fQualifyDispName)
{
HRESULT hr = E_FAIL;
LPITEMIDLIST pidlNN = NULL;
SHGetSpecialFolderLocation(NULL, CSIDL_NETWORK, &pidlNN); // Get Pidl for "Network Neighborhood"
if (pidlNN)
{
hr = _ParseRelativePidl(pcszUrlIn, pfPossibleWebUrl, dwFlags, pidlNN, FALSE, fQualifyDispName);
ILFree(pidlNN);
}
return hr;
}
/****************************************************\
FUNCTION: _ParseSeparator
PARAMETERS:
pidl - PIDL to ISF that has been parsed so far.
pcszSeg - Str of rest of Url to parse.
pfPossibleWebUrl - Set to FALSE if we know that the user attempted
but failed to enter a correct Shell Url.
fQualifyDispName - If TRUE when we known that we need to force the
URL to be fully qualified if we bind to the
destination. This is needed because we are using
state information to find the destination URL and
that state information won't be available later.
DESCRIPTION:
This function is called after at least one
segment in the SHELL URL has bound to a valid
Shell Item/Folder (i.e., ITEMID). It is called
each time a segment in the Shell Url binds to a PIDL.
It will then evaluate the rest of the string and
determine if:
1. The URL has been completely parsed
and is valid. This will include getting
the command line arguments if appropriate.
2. More segments in the URL exist and ::_ParseNextSegment()
needs to be called to continue the recursive parsing
of the URL.
3. The rest of the URL indicates that it's an invalid url.
This function is always called by ::_ParseNextSegment() and basically
decides if it wants to continue the recursion by calling back into
::_ParseNextSegment() or not. Recursion is used because it's necessary
to back out of parsing something and go down a path if we received
a false positive.
\****************************************************/
HRESULT CShellUrl::_ParseSeparator(LPCITEMIDLIST pidl, LPCTSTR pcszSeg, BOOL * pfPossibleWebUrl, BOOL fAllowRelative, BOOL fQualifyDispName)
{
HRESULT hr = S_OK;
BOOL fIgnoreArgs = FALSE;
ASSERT(pidl && IS_VALID_PIDL(pidl));
// Does anything follow this separator?
if ((CH_FRAGMENT == pcszSeg[0]) || (IS_SHELL_SEPARATOR(pcszSeg[0]) && pcszSeg[1]))
{
// Yes, continue parsing recursively.
// Do we need to skip the '/' or '\' separator?
if (CH_FRAGMENT != pcszSeg[0])
pcszSeg++; // Skip separator
hr = _ParseNextSegment(pidl, pcszSeg, pfPossibleWebUrl, fAllowRelative, fQualifyDispName);
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: _ParseSeparator() Current Level pidl=>%s<", Dbg_PidlStr(pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
if (FAILED(hr) && pfPossibleWebUrl)
{
*pfPossibleWebUrl = FALSE;
// We bound to at least one level when parsing, so don't do a web search because
// of a failure.
}
}
else
{
// No, we will see if we have reached a valid Shell Item.
// Is the remaining string args?
if (CH_SPACE == pcszSeg[0])
{
// If there are still chars left in the string, we need to
// verify the first one is a space to indicate Command line args.
// Also, we need to make sure the PIDL isn't browsable because browsable
// Shell folders/items don't take Cmd Line Args.
if (ILIsBrowsable(pidl, NULL))
{
// No
//
// The remaining chars cannot be Command Line Args if the PIDL
// doesn't point to something that is shell executable. This
// case actually happens often.
// Example: (\\bryanst\... and \\bryanst2\.. both exist and
// user enters \\bryanst2\... but parsing attempts
// to use \\bryanst because it was found first. This
// will cause recursion to crawl back up the stack and try \\bryanst2.
hr = E_FAIL;
}
}
else if (pcszSeg[0])
{
// No
// The only time we allow a char after a folder segment is if it is a Shell Separator
// Example: "E:\dir1\"
if (IS_SHELL_SEPARATOR(*pcszSeg) && 0 == pcszSeg[1])
fIgnoreArgs = TRUE;
else
hr = E_FAIL; // Invalid because there is more to be parsed.
}
if (SUCCEEDED(hr))
{
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: _ParseSeparator() Parsing Finished. pidl=>%s<", Dbg_PidlStr(pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
_SetPidl(pidl);
if (!fIgnoreArgs && pcszSeg[0])
Str_SetPtr(&m_pszArgs, pcszSeg);
if (fQualifyDispName)
_GenDispNameFromPidl(pidl, pcszSeg);
}
}
return hr;
}
//
// Returns TRUE is the pidl is a network server
//
BOOL _IsNetworkServer(LPCITEMIDLIST pidl)
{
BOOL fRet = FALSE;
// First see if this is a network pidl
if (IsSpecialFolderChild(pidl, CSIDL_NETWORK, FALSE))
{
// See if it ends in a share name
WCHAR szUrl[MAX_URL_STRING];
HRESULT hr = IEGetNameAndFlags(pidl, SHGDN_FORPARSING, szUrl, ARRAYSIZE(szUrl), NULL);
if (FAILED(hr))
{
// On non-integrated browsers SHGDN_FORPARSING may fail so try
// again without this flag. The preceeding back slashes will be
// missing so we add them ourselves
szUrl[0] = CH_FILESEPARATOR;
szUrl[1] = CH_FILESEPARATOR;
hr = IEGetNameAndFlags(pidl, SHGDN_NORMAL | SHGDN_FORADDRESSBAR, szUrl+2, ARRAYSIZE(szUrl)-2, NULL);
}
fRet = SUCCEEDED(hr) && PathIsUNCServer(szUrl);
}
return fRet;
}
/****************************************************\
FUNCTION: _ParseNextSegment
PARAMETERS:
pidlParent - Fully Qualified PIDL to ISF to find next ITEMID in pcszStrToParse.
pcszStrToParse - pcszStrToParse will begin with either
a valid display name of a child ITEMID of pidlParent
or the Shell URL is invalid relative to pidlParent.
fAllowRelative - Should relative moves be allowed?
fQualifyDispName - If TRUE when we known that we need to force the
URL to be fully qualified if we bind to the destination.
This is needed because we are using state information to
find the destination URL and that state information won't
be available later.
DESCRIPTION/PERF:
This function exists to take the string (pcszStrToParse)
passed in and attempt to bind to a ITEMID which
has a DisplayName that matches the beginning of
pcszStrToParse. This function will check all the
ITEMIDs under the pidlParent section of the Shell
Name Space.
The only two exceptions to the above method is if
1) the string begins with "..", in which case, we
bind to the pidlParent's Parent ITEMID. - or -
2) The pidlParent passes the ::_IsFilePidl()
test and we are guaranteed the item is in the
File System or a UNC item. This will allow us
to call IShellFolder::ParseDisplayName() to
find the child ITEMID of pidlParent.
This function will iterate through the items under
pidlParent instead of call IShellFolder::ParseDisplayName
for two reasons: 1) The ::ParseDisplayName for "The Internet"
will accept any string because of AutoSearch, and
2) We never know the location of the end of one segment and
the beginning of the next segment in pcszStrToParse. This is
because DisplayNames for ISFs can contain almost any character.
If this function has successfully bind to a child ITEMID
of pidlParent, it will call ::_ParseSeparator() with the
rest of pcszStrToParse to parse. _ParseSeparator() will determine
if the end of the URL has been parsed or call back into this function
recursively to continue parsing segments. In the former case,
_ParseSeparator() will set this object's PIDL and arguments which
can be used later. In the latter case, the recursion stack will
unwind and my take a different path (Cases exists that require this).
\****************************************************/
HRESULT CShellUrl::_ParseNextSegment(LPCITEMIDLIST pidlParent,
LPCTSTR pcszStrToParse, BOOL * pfPossibleWebUrl,
BOOL fAllowRelative, BOOL fQualifyDispName)
{
HRESULT hr = E_FAIL;
if (!pidlParent || !pcszStrToParse)
return E_INVALIDARG;
// Is this ".."?
if (fAllowRelative && CH_DOT == pcszStrToParse[0] && CH_DOT == pcszStrToParse[1])
{
// Yes
LPITEMIDLIST pidl = ILClone(pidlParent);
if (pidl && !ILIsEmpty(pidl))
{
ILRemoveLastID(pidl); // pidl/psfFolder now point to the new shell item, which is the parent in this case.
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: _ParseNextSegment() Nav '..'. PIDL=>%s<", Dbg_PidlStr(pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
// Parse the next segment or finish up if we reached the end
// (we're skipping the ".." here)
hr = _ParseSeparator(pidl, &(pcszStrToParse[2]), pfPossibleWebUrl, fAllowRelative, fQualifyDispName);
ILFree(pidl);
}
}
else
{
// No
LPTSTR pszNext = NULL; // Remove const because we will iterate only.
long i = 0;
// Can we parse this display name quickly?
if (!ILIsRooted(pidlParent) && _IsFilePidl(pidlParent) &&
// Quick way fails for shares right off of the network server
!_IsNetworkServer(pidlParent))
{
// Yes
TCHAR szParseChunk[MAX_PATH+1];
do
{
++i;
hr = _GetNextPossibleSegment(pcszStrToParse, &pszNext, szParseChunk, SIZECHARS(szParseChunk), TRUE);
if (S_OK == hr)
{
hr = _QuickParse(pidlParent, szParseChunk, pszNext, pfPossibleWebUrl, fAllowRelative, fQualifyDispName);
//
// Certain network shares like \\foo\Printers and "\\foo\Scheduled Tasks" will fail the if we
// combine the server and share in a segment. So we try parsing the server separately.
//
if ((S_OK != hr) && (i == 1) && PathIsUNCServerShare(szParseChunk))
{
pszNext = NULL;
hr = _GetNextPossibleSegment(pcszStrToParse, &pszNext, szParseChunk, SIZECHARS(szParseChunk), FALSE);
if (S_OK == hr)
{
hr = _QuickParse(pidlParent, szParseChunk, pszNext, pfPossibleWebUrl, fAllowRelative, fQualifyDispName);
}
}
#ifdef FEATURE_SUPPORT_FRAGS_INFILEURLS
// Did we fail to parse the traditional way and the first char of this
// next chunk indicates it's probably a URL Fragment?
if (FAILED(hr) && (CH_FRAGMENT == pcszStrToParse[0]))
{
TCHAR szUrl[MAX_URL_STRING];
// Yes, so try parsing in another way that will work
// with URL fragments.
hr = ::IEGetDisplayName(pidlParent, szUrl, SHGDN_FORPARSING);
if (EVAL(SUCCEEDED(hr)))
{
TCHAR szFullUrl[MAX_URL_STRING];
DWORD cchFullUrlSize = ARRAYSIZE(szFullUrl);
hr = UrlCombine(szUrl, szParseChunk, szFullUrl, &cchFullUrlSize, 0);
if (EVAL(SUCCEEDED(hr)))
{
LPITEMIDLIST pidl = NULL;
hr = IEParseDisplayName(CP_ACP, szFullUrl, &pidl);
if (SUCCEEDED(hr))
{
_SetPidl(pidl);
if (fQualifyDispName)
_GenDispNameFromPidl(pidl, szFullUrl);
ILFree(pidl);
}
else
ASSERT(!pidl); // Verify IEParseDisplayName() didn't fail but return a pidl.
}
}
}
#endif // FEATURE_SUPPORT_FRAGS_INFILEURLS
}
}
while (FAILED(hr));
if (S_OK != hr)
hr = E_FAIL; // Not Found
}
else if (FAILED(hr))
{
// No; use the slow method
IShellFolder * psfFolder = NULL;
DWORD dwAttrib = SFGAO_FOLDER;
IEGetAttributesOf(pidlParent, &dwAttrib);
if (IsFlagSet(dwAttrib, SFGAO_FOLDER))
{
IEBindToObject(pidlParent, &psfFolder);
ASSERT(psfFolder);
}
if (psfFolder)
{
LPENUMIDLIST penumIDList = NULL;
HWND hwnd = _GetWindow();
// Is this an FTP Pidl?
if (IsFTPFolder(psfFolder))
{
// NT #274795: Yes so, we need to NULL out the hwnd to prevent
// displaying UI because enumerator of that folder may need to display
// UI (to collect passwords, etc.). This is not valid because pcszStrToParse
// may be an absolute path and psfFolder points to the current location which
// isn't valid. This should probaby be done for all IShellFolder::EnumObjects()
// calls, but it's too risky right before ship.
hwnd = NULL;
}
// Warning Docfind returns S_FALSE to indicate no enumerator and returns NULL..
if (S_OK == IShellFolder_EnumObjects(psfFolder, hwnd, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &penumIDList))
{
LPITEMIDLIST pidlRelative; // NOT a FULLY Qualified Pidl
LPITEMIDLIST pidlResult; // PIDL after it has been made Fully Qualified
ULONG cFetched;
LPTSTR pszRemaining = NULL;
while (FAILED(hr) && NOERROR == penumIDList->Next(1, &pidlRelative, &cFetched) && cFetched)
{
// The user will have entered the name in one of the three formats and they need to be
// checked from the longest string to the smallest. This is necessary because the
// parser will check to see if the item's DisplayName is the first part of the user
// string.
//
// #1. (FORPARSING): This will be the full name.
// Example: razzle.lnk on desktop = D:\nt\public\tools\razzle.lnk.
// #2. (FORPARSING | SHGDN_INFOLDER): This will be only the full name w/Extension.
// Example: razzle.lnk on desktop = razzle.lnk
// #3. (SHGDN_INFOLDER): This will be the full name w/o extension if "Hide File Extensions for Known File Types" is on.
// Example: razzle.lnk on desktop = D:\nt\public\tools\razzle.lnk.
// The user may have entered the "SHGDN_FORPARSING" Display Name or the "SHGDN_INFOLDER", so we need
// to check both.
hr = _CheckItem(psfFolder, pidlParent, pidlRelative, &pidlResult, pcszStrToParse, &pszRemaining, SHGDN_FORPARSING);
if (FAILED(hr)) // Used for file items w/extensions. (Like razzle.lnk on the Desktop)
hr = _CheckItem(psfFolder, pidlParent, pidlRelative, &pidlResult, pcszStrToParse, &pszRemaining, SHGDN_FORPARSING | SHGDN_INFOLDER);
if (FAILED(hr))
hr = _CheckItem(psfFolder, pidlParent, pidlRelative, &pidlResult, pcszStrToParse, &pszRemaining, SHGDN_INFOLDER);
if (SUCCEEDED(hr))
{
// See if the Display Name for a Drive ate the separator for the next segment.
if (_FixDriveDisplayName(pcszStrToParse, pszRemaining, pidlResult))
{
// FIX: "E:\dir1\dir2". We expent display names to not claim the '\' separator between
// names. The problem is that drive letters claim to be "E:\" instead
// of "E:". So, we need to back up so we use the '\' as a separator.
pszRemaining--;
}
#ifndef UNIX
// Our root is equal to a separator, so it's N/A on UNIX.
ASSERT(pcszStrToParse != pszRemaining);
#endif
// Parse the next segment or finish up if we reached the end.
hr = _ParseSeparator(pidlResult, pszRemaining, pfPossibleWebUrl, fAllowRelative, fQualifyDispName);
if (pidlResult)
ILFree(pidlResult);
}
ILFree(pidlRelative);
}
penumIDList->Release();
}
psfFolder->Release();
}
}
}
return hr;
}
/****************************************************\
FUNCTION: _GetNextPossibleSegment
PARAMETERS:
pcszFullPath - Full Path
ppszSegIterator - Pointer to iterator to maintain state.
WARNING: This needs to be NULL on first call.
pszSegOut - Of S_OK is returned, this will contain the next possible segment
cchSegOutSize - char Size of pszSegOut buffer
DESCRIPTION:
Generate the next possible segment that can
be parsed. If "one two three/four five" is passed
in, this function will return S_OK three times
with these values in pszSegOut:
1) "one two three",
2) "one two", and
3) "one".
In this example, S_OK will be returned for the first
three calls, and S_FALSE will be returned for the
fourth to indicate that no more possible segments can be obtained
from that string.
\****************************************************/
HRESULT CShellUrl::_GetNextPossibleSegment(LPCTSTR pcszFullPath,
LPTSTR * ppszSegIterator, LPTSTR pszSegOut, DWORD cchSegOutSize, BOOL fSkipShare)
{
HRESULT hr = S_OK;
LPTSTR szStart = (LPTSTR) pcszFullPath;
// We need to treat UNCs Specially.
if (PathIsUNC(szStart))
{
LPTSTR szUNCShare;
// This is a UNC so we need to make the "Segment" include
// the "\\server\share" because Network Neighborhood's
// IShellFolder::ParseDisplayName() is increadibly slow
// and makes mistakes when it parses "server" and then "share"
// separately.
// This if clause will advance szStart past the Server
// section of the UNC path so the rest of the algorithm will
// naturally continue working on the share section of the UNC.
szStart += 2; // Skip past the "\\" UNC header.
// Is there a share?
if (fSkipShare && (szUNCShare = StrChr(szStart, CH_FILESEPARATOR)))
{
// Yes, so advanced to the first char in the share
// name so the algorithm below works correctly.
szStart = szUNCShare + 1;
}
}
// Do we need to initialize the iterator? If so, set it to the
// largest possible segment in the string because we will be
// working backwards.
ASSERT(ppszSegIterator);
if (*ppszSegIterator)
{
*ppszSegIterator = StrRChr(szStart, *ppszSegIterator, CH_SPACE);
if (!*ppszSegIterator)
{
pszSegOut[0] = TEXT('\0'); // Make sure caller doesn't ignore return and recurse infinitely.
return S_FALSE;
}
}
else
{
// We have not yet started the iteration, so set the ppszSegIterator to the end of the possible
// segment. This will be a segment separator character ('\' || '/') or the end of the string
// if either of those don't exist. This will be the first segment to try.
#ifndef UNIX
*ppszSegIterator = StrChr(szStart, CH_FILESEPARATOR);
if (!*ppszSegIterator)
*ppszSegIterator = StrChr(szStart, CH_SEPARATOR);
#else
// On UNIX, we always skip the 1st "/" and go to the 2nd.
if (szStart[0] == CH_FILESEPARATOR)
*ppszSegIterator = StrChr(szStart+1, CH_FILESEPARATOR);
#endif
LPTSTR pszFrag = StrChr(szStart, CH_FRAGMENT);
// Is the next separator a fragment?
if (pszFrag && (!*ppszSegIterator || (pszFrag < *ppszSegIterator)))
{
TCHAR szFile[MAX_URL_STRING];
StrCpyN(szFile, szStart, (int)(pszFrag - szStart + 1));
if (PathIsHTMLFile(szFile))
*ppszSegIterator = pszFrag;
}
if (!*ppszSegIterator)
{
// Go to end of the string because this is the last seg.
*ppszSegIterator = (LPTSTR) &((szStart)[lstrlen(szStart)]);
}
}
// Fill the pszSegOut parameter.
ASSERT(*ppszSegIterator);
// This is weird but correct. pszEnd - pszBeginning results count of chars, not
// count of bytes.
if (cchSegOutSize >= (DWORD)((*ppszSegIterator - pcszFullPath) + 1))
StrCpyN(pszSegOut, pcszFullPath, (int)(*ppszSegIterator - pcszFullPath + 1));
else
StrCpyN(pszSegOut, pcszFullPath, cchSegOutSize-1);
return hr;
}
/****************************************************\
FUNCTION: _GetNextPossibleFullPath
DESCRIPTION:
This function will attempt to see if strParseChunk
is a Parsible DisplayName under pidlParent.
\****************************************************/
HRESULT CShellUrl::_GetNextPossibleFullPath(LPCTSTR pcszFullPath,
LPTSTR * ppszSegIterator, LPTSTR pszSegOut, DWORD cchSegOutSize,
BOOL * pfContinue)
{
HRESULT hr = S_OK;
LPTSTR pszNext = StrChr(*ppszSegIterator, CH_SPACE);
DWORD cchAmountToCopy = cchSegOutSize;
if (TEXT('\0') == (*ppszSegIterator)[0])
{
if (pfContinue)
*pfContinue = FALSE;
return E_FAIL; // Nothing Left.
}
if (!pszNext)
pszNext = &((*ppszSegIterator)[lstrlen(*ppszSegIterator)]); // Go to end of the string because this is the last seg.
// Copy as much of the string as we have room for.
// The compiler will take care of adding '/ sizeof(TCHAR)'.
if ((cchAmountToCopy-1) > (DWORD)(pszNext - pcszFullPath + 1))
cchAmountToCopy = (int)(pszNext - pcszFullPath + 1);
StrCpyN(pszSegOut, pcszFullPath, cchAmountToCopy);
if (CH_SPACE == pszNext[0])
{
*pfContinue = TRUE;
}
else
*pfContinue = FALSE;
*ppszSegIterator = pszNext;
return hr;
}
/****************************************************\
FUNCTION: _QuickParse
PARAMETERS:
pidlParent - Pidl to ISF to parse from.
pszParseChunk - Display Name of item in pidlParent.
pszNext - Rest of string to parse if we succeed at parsing pszParseChunk.
pfPossibleWebUrl - Set to FALSE if we find that the user has attempted to enter
a Shell Url or File url but misspelled one of the segments.
fAllowRelative - Allow relative parsing. ("..")
fQualifyDispName - If TRUE when we known that we need to force the
URL to be fully qualified if we bind to the destination.
This is needed because we are using state information to
find the destination URL and that state information won't
be available later.
DESCRIPTION:
This function will attempt to see if strParseChunk
is a Parsible DisplayName under pidlParent.
\****************************************************/
HRESULT CShellUrl::_QuickParse(LPCITEMIDLIST pidlParent, LPTSTR pszParseChunk,
LPTSTR pszNext, BOOL * pfPossibleWebUrl, BOOL fAllowRelative,
BOOL fQualifyDispName)
{
HRESULT hr;
IShellFolder * psfFolder;
hr = IEBindToObject(pidlParent, &psfFolder);
if (SUCCEEDED(hr))
{
ULONG ulEatten; // Not used.
SHSTRW strParseChunkThunked;
hr = strParseChunkThunked.SetStr(pszParseChunk);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl = NULL;
// TODO: In the future, we may want to cycle through commonly used extensions in case the
// user doesn't add them.
hr = psfFolder->ParseDisplayName(_GetWindow(), NULL, strParseChunkThunked.GetInplaceStr(), &ulEatten, &pidl, NULL);
if (SUCCEEDED(hr))
{
// IShellFolder::ParseDisplayName() only generates PIDLs that are relative to the ISF. We need
// to make them Absolute.
LPITEMIDLIST pidlFull = ILCombine(pidlParent, pidl);
if (pidlFull)
{
// Parse the next segment or finish up if we reached the end.
hr = _ParseSeparator(pidlFull, pszNext, pfPossibleWebUrl, fAllowRelative, fQualifyDispName);
ILFree(pidlFull);
}
ILFree(pidl);
}
}
psfFolder->Release();
}
return hr;
}
/****************************************************\
FUNCTION: _CheckItem
DESCRIPTION:
This function will obtain the Display Name
of the ITEMID (pidlRelative) which is a child of
psfFolder. If it's Display Name matches the first
part of pcszStrToParse, we will return successful
and set ppszRemaining to the section of pcszStrToParse
after the segment just parsed.
This function will also see if the Display Name ends
in something that would indicate it's executable.
(.EXE, .BAT, .COM, ...). If so, we will match if
pcszStrToParse matches the Display Name without the
Extension.
\****************************************************/
HRESULT CShellUrl::_CheckItem(IShellFolder * psfFolder,
LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidlRelative,
LPITEMIDLIST * ppidlChild, LPCTSTR pcszStrToParse,
LPTSTR * ppszRemaining, DWORD dwFlags)
{
HRESULT hr = E_FAIL;
*ppidlChild = NULL;
TCHAR szISFName[MAX_URL_STRING];
if (SUCCEEDED(DisplayNameOf(psfFolder, pidlRelative, dwFlags, szISFName, SIZECHARS(szISFName))))
{
DWORD cchISFLen = lstrlen(szISFName);
DWORD cchStrToParse = lstrlen(pcszStrToParse);
BOOL fEqual = FALSE;
// Either the item needs to match exactly, or it needs to do a partial match
// if the Shell Object is an executable file. For Example: "msdev" should match the
// "msdev.exe" file object.
if (cchISFLen > 0)
{
// We want to see if pcszStrToParse is a match to the first part of szISFName.
// First we will try to see if it's a direct match.
// Example: User="file.exe" Shell Item="file.exe"
// But we DON'T want to match if the StrToParse is longer than
// ISFName, unless the next char in StrToParse is a separator.
// If StrToParse is shorter than ISFName, then it can't be an exact match.
if (cchStrToParse >= cchISFLen &&
0 == StrCmpNI(szISFName, pcszStrToParse, cchISFLen) &&
(cchStrToParse == cchISFLen || IS_SHELL_SEPARATOR(pcszStrToParse[cchISFLen])))
{
fEqual = TRUE;
}
else
{
int cchRoot = (int)((PathFindExtension(szISFName)-szISFName));
// If that failed, we try to see if the Shell Item is
// executable (.EXE, .COM, .BAT, .CMD, ...) and if so,
// we will see if pcszStrToParse matches Shell Item w/o the file
// extension.
// REARCHITECT this will match if there happens to be a space in the user's
// filename that doesn't denote commandline arguments.
// Example: User="foo file.doc" Shell Item="foo.exe"
if (PathIsExe(szISFName) && // shell object is executable
(!((dwFlags & SHGDN_INFOLDER) && !(dwFlags & SHGDN_FORPARSING))) && // we didn't strip extension
((lstrlen(pcszStrToParse) >= cchRoot) && // and user entered at least root chars
((pcszStrToParse[cchRoot] == TEXT('\0')) || // and user entered exact root
(pcszStrToParse[cchRoot] == TEXT(' ')))) && // or possible commandline args
(0 == StrCmpNI(szISFName, pcszStrToParse, cchRoot))) // and the root matches
{
// This wasn't a direct match, but we found that the segment entered
// by the user (pcszStrToParse) matched
// We found that the ISF item is an executable object and the
// string matched w/o the extension.
fEqual = TRUE;
cchISFLen = cchRoot; // So that we generate *ppszRemaining correctly
}
}
}
if (fEqual)
{
hr = S_OK; // We were able to navigate to this shell item token.
*ppszRemaining = (LPTSTR) &(pcszStrToParse[cchISFLen]); // We will only iterate over the string, so it's ok that we loose the const.
*ppidlChild = ILCombine(pidlParent, pidlRelative);
TraceMsg(TF_CHECKITEM, "ShellUrl: _CheckItem() PIDL=>%s< IS EQUAL TO StrIn=>%s<", pcszStrToParse, szISFName);
}
else
TraceMsg(TF_CHECKITEM, "ShellUrl: _CheckItem() PIDL=>%s< not equal to StrIn=>%s<", pcszStrToParse, szISFName);
}
return hr;
}
/****************************************************\
FUNCTION: _IsFilePidl
PARAMETERS:
pidl (IN) - Pidl to check if it is a File Pidl
DESCRIPTION:
The PIDL is a file pidl if:
1. The pidl equals "Network Neighborhood" or descendent
2. The pidl's grandparent or farther removed from "My Computer".
This algorithm only allows "Network Neighborhood" because
that ISF contains a huge number of PIDLs and takes for ever
to enumerate. The second clause will work in any part of the
file system except for the root drive (A:\, C:\). This is
because we need to allow other direct children of "My Computer"
to use the other parsing.
\****************************************************/
BOOL CShellUrl::_IsFilePidl(LPCITEMIDLIST pidl)
{
BOOL fResult = FALSE;
BOOL fNeedToSkip = FALSE;
if (!pidl || ILIsEmpty(pidl))
return fResult;
// Test for Network Neighborhood because it will take forever to enum.
fResult = IsSpecialFolderChild(pidl, CSIDL_NETWORK, FALSE);
if (!fResult)
{
// We only want to do this if we are not the immediate
// child.
if (IsSpecialFolderChild(pidl, CSIDL_DRIVES, FALSE))
{
TCHAR szActualPath[MAX_URL_STRING]; // IEGetDisplayName() needs the buffer to be this large.
IEGetNameAndFlags(pidl, SHGDN_FORPARSING, szActualPath, SIZECHARS(szActualPath), NULL);
DWORD dwOutSize = MAX_URL_STRING;
if (SUCCEEDED(PathCreateFromUrl(szActualPath, szActualPath, &dwOutSize, 0)))
{
PathStripToRoot(szActualPath);
fResult = PathIsRoot(szActualPath);
}
#ifdef UNIX
else
{
fResult = (szActualPath[0]==TEXT('/'));
}
#endif
}
}
return fResult;
}
/****************************************************\
FUNCTION: IsWebUrl
PARAMETERS
none.
DESCRIPTION:
Return TRUE if the URL is a Web Url (http,
ftp, other, ...). Return FALSE if it's a Shell Url
or File Url.
\****************************************************/
BOOL CShellUrl::IsWebUrl(void)
{
if (m_pidl)
{
if (!IsURLChild(m_pidl, TRUE))
return FALSE;
}
else
{
ASSERT(m_pszURL); // This CShellUrl hasn't been set.
if (m_pszURL && IsShellUrl(m_pszURL, TRUE))
return FALSE;
}
return TRUE;
}
/****************************************************\
FUNCTION: SetCurrentWorkingDir
PARAMETERS
pShellUrlNew - Pointer to a CShellUrl that will
be the "Current Working Directory"
DESCRIPTION:
This Shell Url will have a new current working
directory, which will be the CShellUrl passed in.
MEMORY ALLOCATION:
The caller needs to Allocate pShellUrlNew and
this object will take care of freeing it. WARNING:
this means it cannot be on the stack.
\****************************************************/
HRESULT CShellUrl::SetCurrentWorkingDir(LPCITEMIDLIST pidlCWD)
{
Pidl_Set(&m_pidlWorkingDir, pidlCWD);
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: SetCurrentWorkingDir() pidl=>%s<", Dbg_PidlStr(m_pidlWorkingDir, szDbgBuffer, SIZECHARS(szDbgBuffer)));
return S_OK;
}
/****************************************************\
PARAMETERS
pvPidl1 - First pidl to compare
pvPidl2 - Second pidl to compare
DESCRIPTION:
Return if the pidl matches. This doesn't work
for sorted lists (because we can't determine less
than or greater than).
\****************************************************/
int DPAPidlCompare(LPVOID pvPidl1, LPVOID pvPidl2, LPARAM lParam)
{
// return < 0 for pvPidl1 before pvPidl2.
// return == 0 for pvPidl1 equals pvPidl2.
// return > 0 for pvPidl1 after pvPidl2.
return (ILIsEqual((LPCITEMIDLIST)pvPidl1, (LPCITEMIDLIST)pvPidl2) ? 0 : 1);
}
/****************************************************\
PARAMETERS
pShellUrlNew - Pointer to a CShellUrl that will
be added to the "Shell Path"
DESCRIPTION:
This Shell Url will have the ShellUrl that's
passed in added to the "Shell Path", which will be
searched when trying to qualify the Shell Url during
parsing.
MEMORY ALLOCATION:
The caller needs to Allocate pShellUrlNew and
this object will take care of freeing it. WARNING:
this means it cannot be on the stack.
\****************************************************/
HRESULT CShellUrl::AddPath(LPCITEMIDLIST pidl)
{
ASSERT(IS_VALID_PIDL(pidl));
// we dont want to add any paths that arent derived from
// our root.
if (ILIsRooted(m_pidlWorkingDir) && !ILIsParent(m_pidlWorkingDir, pidl, FALSE))
return S_FALSE;
if (!m_hdpaPath)
{
m_hdpaPath = DPA_Create(CE_PATHGROW);
if (!m_hdpaPath)
return E_OUTOFMEMORY;
}
// Does the path already exist in our list?
if (-1 == DPA_Search(m_hdpaPath, (void *)pidl, 0, DPAPidlCompare, NULL, 0))
{
// No, so let's add it.
LPITEMIDLIST pidlNew = ILClone(pidl);
if (pidlNew)
DPA_AppendPtr(m_hdpaPath, pidlNew);
}
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: AddPath() pidl=>%s<", Dbg_PidlStr(pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
return S_OK;
}
/****************************************************\
FUNCTION: Reset
PARAMETERS:
none.
DESCRIPTION:
This function will "Clean" out the object and
reset it. Normally called when the caller is about
to set new values.
\****************************************************/
HRESULT CShellUrl::Reset(void)
{
Pidl_Set(&m_pidl, NULL);
Str_SetPtr(&m_pszURL, NULL);
Str_SetPtr(&m_pszArgs, NULL);
Str_SetPtr(&m_pszDisplayName, NULL);
m_dwGenType = 0;
return S_OK;
}
/****************************************************\
FUNCTION: _CanUseAdvParsing
PARAMETERS:
none.
DESCRIPTION:
This function will return TRUE if Advanced
Parsing (Shell URLs) should be supported. This
function will keep track of whether the user
has turn off Shell Parsing from the Control Panel.
\****************************************************/
#define REGSTR_USEADVPARSING_PATH TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Band\\Address")
#define REGSTR_USEADVPARSING_VALUE TEXT("UseShellParsing")
BOOL CShellUrl::_CanUseAdvParsing(void)
{
// WARNING: Since this is static, changes to the registry entry won't be
// read in until the time the process is launched. This is okay,
// because this feature will probably be removed from the released
// product and can be added back in as power toy.
static TRI_STATE fCanUseAdvParsing = TRI_UNKNOWN;
if (TRI_UNKNOWN == fCanUseAdvParsing)
fCanUseAdvParsing = (TRI_STATE) SHRegGetBoolUSValue(REGSTR_USEADVPARSING_PATH, REGSTR_USEADVPARSING_VALUE, FALSE, TRUE);
return fCanUseAdvParsing;
}
/****************************************************\
FUNCTION: _FixDriveDisplayName
PARAMETERS:
pszStart - Pointer to the beginning of the URL string.
pszCurrent - Pointer into current location in the URL string.
pidl - PIDL pointing to location of Shell Name space that
has been parsed so far.
DESCRIPTION:
This function exists to check if we are parsing
a drive letter. This is necessary because the Display
Name of drive letters end in '\', which will is needed
later to determine the start of the next segment.
\****************************************************/
#ifndef UNIX
#define DRIVE_STRENDING TEXT(":\\")
#define DRIVE_STRSIZE 3 // "C:\"
#else
#define DRIVE_STRSIZE 1 // "/"
#endif
BOOL _FixDriveDisplayName(LPCTSTR pszStart, LPCTSTR pszCurrent, LPCITEMIDLIST pidl)
{
BOOL fResult = FALSE;
ASSERT(pszCurrent >= pszStart);
#ifndef UNIX
// The compiler will take care of adding '/ sizeof(TCHAR)'.
if (((pszCurrent - pszStart) == DRIVE_STRSIZE) &&
(0 == StrCmpN(&(pszStart[1]), DRIVE_STRENDING, SIZECHARS(DRIVE_STRENDING)-1)))
#else
if ((((pszCurrent - pszStart)/sizeof(TCHAR)) == DRIVE_STRSIZE))
#endif
{
if (IsSpecialFolderChild(pidl, CSIDL_DRIVES, TRUE))
fResult = TRUE;
}
return fResult;
}
/****************************************************\
FUNCTION: _ParseRelativePidl
PARAMETERS:
pcszUrlIn - Pointer to URL to Parse.
dwFlags - Flags to modify the way the string is parsed.
pidl - This function will see if pcszUrlIn is a list of display names
relative to this pidl.
fAllowRelative - Do we allow relative parsing, which
means strings containing "..".
fQualifyDispName - If TRUE when we known that we need to force the
URL to be fully qualified if we bind to the destination.
This is needed because we are using state information to
find the destination URL and that state information won't
be available later.
DESCRIPTION:
Start the parsing by getting the pidl of ShellUrlRelative
and call _ParseNextSegment(). _ParseNextSegment() will
recursively parse each segment of the PIDL until either
it fails to fully parse of it finishes.
\****************************************************/
HRESULT CShellUrl::_ParseRelativePidl(LPCTSTR pcszUrlIn,
BOOL * pfPossibleWebUrl, DWORD dwFlags, LPCITEMIDLIST pidl,
BOOL fAllowRelative, BOOL fQualifyDispName)
{
HRESULT hr;
BOOL fFreePidl = FALSE;
if (!pcszUrlIn)
return E_INVALIDARG;
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: _ParseRelativePidl() Begin. pcszUrlIn=%s", pcszUrlIn);
hr = _ParseNextSegment(pidl, pcszUrlIn, pfPossibleWebUrl, fAllowRelative, fQualifyDispName);
if (pidl && fFreePidl)
ILFree((LPITEMIDLIST)pidl);
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: _ParseRelativePidl() m_pidl=>%s<", Dbg_PidlStr(m_pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
return hr;
}
/****************************************************\
FUNCTION: IsShellUrl
PARAMETERS:
LPCTSTR szUrl - URL from Outside Source.
return - Whether the URL is an Internet URL.
DESCRIPTION:
This function will determine if the URL is
a shell URL which includes the following:
1. File Urls (E;\dir1\dir2)
2. Shell Urls (shell:desktop)
\****************************************************/
BOOL IsShellUrl(LPCTSTR pcszUrl, BOOL fIncludeFileUrls)
{
int nSchemeBefore, nSchemeAfter;
TCHAR szParsedUrl[MAX_URL_STRING];
nSchemeBefore = GetUrlScheme(pcszUrl);
IURLQualifyT(pcszUrl, UQF_GUESS_PROTOCOL, szParsedUrl, NULL, NULL);
nSchemeAfter = GetUrlScheme(szParsedUrl);
// This is a "shell url" if it is a file: (and fIncludeFileUrls is
// set), or it is a shell:, or it is an invalid scheme (which
// occurs for things like "My Computer" and "Control Panel").
return ((fIncludeFileUrls && URL_SCHEME_FILE == nSchemeAfter) ||
URL_SCHEME_SHELL == nSchemeAfter ||
URL_SCHEME_INVALID == nSchemeBefore);
}
/****************************************************\
FUNCTION: IsSpecialFolderChild
PARAMETERS:
pidlToTest (In) - Is this PIDL to test and see if it's
a child of SpecialFolder(nFolder).
psfParent (In Optional)- The psf passed to
SHGetSpecialFolderLocation() if needed.
nFolder (In) - Special Folder Number (CSIDL_INTERNET, CSIDL_DRIVES, ...).
pdwLevels (In Optional) - Pointer to DWORD to receive levels between
pidlToTest and it's parent (nFolder) if S_OK is returned.
DESCRIPTION:
This function will see if pidlToTest is a child
of the Special Folder nFolder.
\****************************************************/
BOOL IsSpecialFolderChild(LPCITEMIDLIST pidlToTest, int nFolder, BOOL fImmediate)
{
LPITEMIDLIST pidlThePidl = NULL;
BOOL fResult = FALSE;
if (!pidlToTest)
return FALSE;
ASSERT(IS_VALID_PIDL(pidlToTest));
if (NOERROR == SHGetSpecialFolderLocation(NULL, nFolder, &pidlThePidl))
{
fResult = ILIsParent(pidlThePidl, pidlToTest, fImmediate);
ILFree(pidlThePidl);
}
return fResult; // Shell Items (My Computer, Control Panel)
}
/****************************************************\
FUNCTION: GetPidl
PARAMETERS
ppidl - Pointer that will receive the current PIDL.
DESCRIPTION:
This function will retrieve the pidl that the
Shell Url is set to.
MEMORY ALLOCATION:
This function will allocate the PIDL that ppidl
points to, and the caller needs to free the PIDL when
done with it.
\****************************************************/
HRESULT CShellUrl::GetPidl(LPITEMIDLIST * ppidl)
{
HRESULT hr = S_OK;
if (ppidl)
*ppidl = NULL;
if (!m_pidl)
hr = _GeneratePidl(m_pszURL, m_dwGenType);
if (ppidl)
{
if (m_pidl)
{
*ppidl = ILClone(m_pidl);
if (!*ppidl)
hr = E_FAIL;
}
else
hr = E_FAIL;
}
// Callers only free *ppidl if SUCCEDED(hr), so assert we act this way.
ASSERT((*ppidl && SUCCEEDED(hr)) || (!*ppidl && FAILED(hr)));
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: GetPidl() *ppidl=>%s<", Dbg_PidlStr(*ppidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
return hr;
}
//
// This is a wacky class! If GetPidl member of this class is called and
// a m_pidl is generated from and url and then Execute() assumes we have
// a valid location in our namespace and calls code that will not autoscan.
// This hacky function is used to return a pidl only if we have one to
// avoid the above problem.
//
HRESULT CShellUrl::GetPidlNoGenerate(LPITEMIDLIST * ppidl)
{
HRESULT hr = E_FAIL;
if (m_pidl && ppidl)
{
*ppidl = ILClone(m_pidl);
if (*ppidl)
{
hr = S_OK;
}
}
return hr;
}
/****************************************************\
FUNCTION: _GeneratePidl
PARAMETERS
pcszUrl - This URL will be used to generate the m_pidl.
dwGenType - This is needed to know how to parse pcszUrl
to generate the PIDL.
DESCRIPTION:
This CShellUrl maintains a pointer to the object
in the Shell Name Space by using either the string URL
or the PIDL. When this CShellUrl is set to one, we
delay generating the other one for PERF reasons.
This function generates the PIDL from the string URL
when we do need the string.
\****************************************************/
HRESULT CShellUrl::_GeneratePidl(LPCTSTR pcszUrl, DWORD dwGenType)
{
HRESULT hr;
if (!pcszUrl && m_pidl)
return S_OK; // The caller only wants the PIDL to be created if it doesn't exist.
if (pcszUrl && m_pidl)
{
ILFree(m_pidl);
m_pidl = NULL;
}
switch (dwGenType)
{
case GENTYPE_FROMURL:
if (ILIsRooted(m_pidlWorkingDir))
hr = E_FAIL; // MSN Displays error dialogs on IShellFolder::ParseDisplayName()
// fall through
case GENTYPE_FROMPATH:
hr = IECreateFromPath(pcszUrl, &m_pidl);
// This may fail if it's something like "ftp:/" and not yet valid".
break;
default:
hr = E_INVALIDARG;
break;
}
if (!m_pidl && SUCCEEDED(hr))
hr = E_FAIL;
return hr;
}
/****************************************************\
FUNCTION: SetPidl
PARAMETERS
pidl - New pidl to use.
DESCRIPTION:
The shell url will now consist of the new pidl
passed in.
MEMORY ALLOCATION:
The caller is responsible for Allocating and Freeing
the PIDL parameter.
\****************************************************/
HRESULT CShellUrl::SetPidl(LPCITEMIDLIST pidl)
{
HRESULT hr = S_OK;
ASSERT(!pidl || IS_VALID_PIDL(pidl));
Reset(); // External Calls to this will reset the entire CShellUrl.
return _SetPidl(pidl);
}
/****************************************************\
FUNCTION: _SetPidl
PARAMETERS
pidl - New pidl to use.
DESCRIPTION:
This function will reset the m_pidl member
variable without modifying m_szURL. This is only used
internally, and callers that want to reset the entire
CShellUrl to a PIDL should call the public method
SetPidl().
MEMORY ALLOCATION:
The caller is responsible for Allocating and Freeing
the PIDL parameter.
\****************************************************/
HRESULT CShellUrl::_SetPidl(LPCITEMIDLIST pidl)
{
HRESULT hr = S_OK;
DEBUG_CODE(TCHAR szDbgBuffer[MAX_PATH];)
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: _SetPidl() pidl=>%s<", Dbg_PidlStr(pidl, szDbgBuffer, SIZECHARS(szDbgBuffer)));
Pidl_Set(&m_pidl, pidl);
if (!m_pidl)
hr = E_FAIL;
return hr;
}
/****************************************************\
FUNCTION: GetUrl
PARAMETERS
pszUrlOut (Out Optional) - If the caller wants the string.
cchUrlOutSize (In) - Size of String Buffer Passed in.
DESCRIPTION:
This function will retrieve the string value of
the shell url. This will not include the command line
arguments or other information needed for correct navigation
(AutoSearch=On/Off, ...). Note that this may be of the
form "Shell:/desktop/My Computer/...".
\****************************************************/
HRESULT CShellUrl::GetUrl(LPTSTR pszUrlOut, DWORD cchUrlOutSize)
{
HRESULT hr = S_OK;
if (!m_pszURL)
{
if (m_pidl)
hr = _GenerateUrl(m_pidl);
else
hr = E_FAIL; // User never set the CShellUrl.
}
if (SUCCEEDED(hr) && pszUrlOut)
StrCpyN(pszUrlOut, m_pszURL, cchUrlOutSize);
return hr;
}
/****************************************************\
!!! WARNING - extremely specific to the ShellUrl/AddressBar - ZekeL - 18-NOV-98
!!! it depends on the bizarre pathology of the ShellUrl in order
!!! to be reparsed into a pidl later. cannot be used for anything else
PARAMETERS:
pidlIn - Pointer to PIDL to generate Display Names.
pszUrlOut - String Buffer to store list of Display Names for ITEMIDs
in pidlIn.
cchUrlOutSize - Size of Buffer in characters.
DESCRIPTION:
This function will take the PIDL passed in and
generate a string containing the ILGDN_ITEMONLY Display names
of each ITEMID in the pidl separated by '\'.
\****************************************************/
#define SZ_SEPARATOR TEXT("/")
HRESULT MutantGDNForShellUrl(LPCITEMIDLIST pidlIn, LPTSTR pszUrlOut, int cchUrlOutSize)
{
HRESULT hr = S_OK;
LPCITEMIDLIST pidlCur;
IShellFolder *psfCur = NULL;
if (ILIsRooted(pidlIn))
{
// need to start off with our virtual root
LPITEMIDLIST pidlFirst = ILCloneFirst(pidlIn);
if (pidlFirst)
{
IEBindToObject(pidlFirst, &psfCur);
ILFree(pidlFirst);
}
pidlCur = _ILNext(pidlIn);
}
else
{
SHGetDesktopFolder(&psfCur);
pidlCur = pidlIn;
}
ASSERT(pidlCur && IS_VALID_PIDL(pidlCur));
while (psfCur && SUCCEEDED(hr) && !ILIsEmpty(pidlCur) && (cchUrlOutSize > 0))
{
LPITEMIDLIST pidlCopy = ILCloneFirst(pidlCur);
if (pidlCopy)
{
StrCpyN(pszUrlOut, SZ_SEPARATOR, cchUrlOutSize);
cchUrlOutSize -= SIZECHARS(SZ_SEPARATOR);
TCHAR szCurrDispName[MAX_PATH];
hr = DisplayNameOf(psfCur, pidlCopy, SHGDN_NORMAL, szCurrDispName, SIZECHARS(szCurrDispName));
if (SUCCEEDED(hr))
{
if (TBOOL((int)cchUrlOutSize > lstrlen(szCurrDispName)))
{
StrCatBuff(pszUrlOut, szCurrDispName, cchUrlOutSize);
cchUrlOutSize -= lstrlen(szCurrDispName);
}
// may fail, in that case we terminate the loop
IShellFolder *psfCurNew = NULL; // for buggy BindToObject impls
hr = psfCur->BindToObject(pidlCopy, NULL, IID_IShellFolder, (void **)&psfCurNew);
psfCur->Release();
psfCur = psfCurNew;
}
pidlCur = _ILNext(pidlCur);
ILFree(pidlCopy);
}
else
hr = E_FAIL;
}
if (psfCur)
psfCur->Release();
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: MutantGDNForShellUrl() End. pszUrlOut=%s", pszUrlOut);
return hr;
}
/****************************************************\
FUNCTION: _GenerateUrl
PARAMETERS
pidl - This PIDL will be used to generate the m_pszURL, string URL.
DESCRIPTION:
This CShellUrl maintains a pointer to the object
in the Shell Name Space by using either the string URL
or the PIDL. When this CShellUrl is set to one, we
delay generating the other one for PERF reasons.
This function generates the string URL from the PIDL
when we do need the string.
\****************************************************/
#define SZ_THEINTERNET_PARSENAME TEXT("::{")
HRESULT CShellUrl::_GenerateUrl(LPCITEMIDLIST pidl)
{
HRESULT hr = S_OK;
TCHAR szUrl[MAX_URL_STRING];
ASSERT(IS_VALID_PIDL(pidl));
if (IsURLChild(pidl, TRUE) || _IsFilePidl(pidl))
{
hr = IEGetNameAndFlags(pidl, SHGDN_FORPARSING, szUrl, SIZECHARS(szUrl), NULL);
if (SUCCEEDED(hr))
{
// Was the pidl pointing to "The Internet"?
if (0 == StrCmpN(szUrl, SZ_THEINTERNET_PARSENAME, (ARRAYSIZE(SZ_THEINTERNET_PARSENAME) - 1)))
{
// Yes, so we don't want the SHGDN_FORPARSING name
// because the user doesn't know what the heck it is. Since we
// navigate to the home page, let's display that.
hr = IEGetNameAndFlags(pidl, SHGDN_NORMAL, szUrl, SIZECHARS(szUrl), NULL);
}
}
}
else
{
// hr = MutantGDNForShellUrl(pidl, szUrl, SIZECHARS(szUrl));
hr = IEGetNameAndFlags(pidl, SHGDN_NORMAL, szUrl, SIZECHARS(szUrl), NULL);
}
if (SUCCEEDED(hr))
Str_SetPtr(&m_pszURL, szUrl);
if (!m_pszURL)
hr = E_OUTOFMEMORY;
if (FAILED(hr))
Str_SetPtr(&m_pszURL, NULL); // Clear it
return hr;
}
/****************************************************\
FUNCTION: SetUrl
PARAMETERS
szUrlOut (Out) - Url
DESCRIPTION:
Set the ShellUrl from a string that is parsible from
the root (desktop) ISF. This is normally used for
File Paths.
\****************************************************/
HRESULT CShellUrl::SetUrl(LPCTSTR pcszUrlIn, DWORD dwGenType)
{
Reset(); // External Calls to this will reset the entire CShellUrl.
return _SetUrl(pcszUrlIn, dwGenType);
}
/****************************************************\
FUNCTION: _SetUrl
PARAMETERS
pcszUrlIn (In) - The string URL for this CShellUrl
dwGenType (In) - Method to use when generating the PIDL
from pcszUrlIn.
DESCRIPTION:
This function will reset the m_pszURL member
variable without modifying m_pidl. This is only used
internally, and callers that want to reset the entire
CShellUrl to an URL should call the public method
SetUrl().
\****************************************************/
HRESULT CShellUrl::_SetUrl(LPCTSTR pcszUrlIn, DWORD dwGenType)
{
m_dwGenType = dwGenType;
return Str_SetPtr(&m_pszURL, pcszUrlIn) ? S_OK : E_OUTOFMEMORY;
}
/****************************************************\
FUNCTION: GetDisplayName
PARAMETERS
pszUrlOut (Out) - Get the Shell Url in String Form.
cchUrlOutSize (In) - Size of String Buffer Passed in.
DESCRIPTION:
This function will Fill in pszUrlOut with nice
versions of the Shell Url that can be displayed in
the AddressBar or in the Titles of windows.
\****************************************************/
HRESULT CShellUrl::GetDisplayName(LPTSTR pszUrlOut, DWORD cchUrlOutSize)
{
HRESULT hr = S_OK;
if (!m_pszDisplayName)
{
if (m_pidl)
{
LPITEMIDLIST pidl = NULL;
hr = GetPidl(&pidl);
if (SUCCEEDED(hr))
{
hr = _GenDispNameFromPidl(pidl, NULL);
ILFree(pidl);
}
}
else if (m_pszURL)
{
// In this case, we will just give back the URL.
Str_SetPtr(&m_pszDisplayName, m_pszURL);
if (NULL == m_pszDisplayName)
hr = E_OUTOFMEMORY;
}
else
{
hr = E_FAIL;
}
}
if (SUCCEEDED(hr) && pszUrlOut && m_pszDisplayName)
StrCpyN(pszUrlOut, m_pszDisplayName, cchUrlOutSize);
return hr;
}
/****************************************************\
FUNCTION: _GenDispNameFromPidl
PARAMETERS
pidl (In) - This will be used to generate the Display Name.
pcszArgs (In) - These will be added to the end of the Display Name
DESCRIPTION:
This function will generate the Display Name
from the pidl and pcszArgs parameters. This is
normally not needed when this CShellUrl was parsed
from an outside source, because the Display Name
was generated at that time.
\****************************************************/
HRESULT CShellUrl::_GenDispNameFromPidl(LPCITEMIDLIST pidl, LPCTSTR pcszArgs)
{
HRESULT hr;
TCHAR szDispName[MAX_URL_STRING];
hr = GetUrl(szDispName, SIZECHARS(szDispName));
if (SUCCEEDED(hr))
{
if (pcszArgs)
StrCatBuff(szDispName, pcszArgs, ARRAYSIZE(szDispName));
PathMakePretty(szDispName);
hr = Str_SetPtr(&m_pszDisplayName, szDispName) ? S_OK : E_OUTOFMEMORY;
}
return hr;
}
/****************************************************\
FUNCTION: GetArgs
PARAMETERS
pszArgsOut - The arguments to the Shell Url. (Only
for ShellExec().
cchArgsOutSize - Size of pszArgsOut in chars.
DESCRIPTION:
Get the arguments that will be passed to
ShellExec() if 1) the Pidl is navigated to, 2) it's
a File URL, and 3) it's not navigatable.
\****************************************************/
HRESULT CShellUrl::GetArgs(LPTSTR pszArgsOut, DWORD cchArgsOutSize)
{
ASSERT(pszArgsOut);
if (m_pszArgs)
StrCpyN(pszArgsOut, m_pszArgs, cchArgsOutSize);
else
*pszArgsOut = 0;
TraceMsg(TF_BAND|TF_GENERAL, "ShellUrl: GetArgs() pszArgsOut=%s", pszArgsOut);
return S_OK;
}
/****************************************************\
FUNCTION: SetDefaultShellPath
PARAMETERS
psu - CShellUrl to set path.
DESCRIPTION:
"Desktop";"Desktop/My Computer" is the
most frequently used Shell Path for parsing. This
function will add those two items to the CShellUrl
passed in the paramter.
\****************************************************/
HRESULT SetDefaultShellPath(CShellUrl * psu)
{
ASSERT(psu);
LPITEMIDLIST pidl;
// We need to set the "Shell Path" which will allow
// the user to enter Display Names of items in Shell
// Folders that are frequently used. We add "Desktop"
// and "Desktop/My Computer" to the Shell Path because
// that is what users use most often.
// _pshuUrl will free pshuPath, so we can't.
psu->AddPath(&s_idlNULL);
SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &pidl); // Get Pidl for "My Computer"
if (pidl)
{
// psu will free pshuPath, so we can't.
psu->AddPath(pidl);
ILFree(pidl);
}
// Add favorites folder too
SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &pidl);
if (pidl)
{
// psu will free pshuPath, so we can't.
psu->AddPath(pidl);
ILFree(pidl);
}
return S_OK;
}
void CShellUrl::SetMessageBoxParent(HWND hwnd)
{
// Find the topmost window so that the messagebox disables
// the entire frame
HWND hwndTopmost = NULL;
while (hwnd)
{
hwndTopmost = hwnd;
hwnd = GetParent(hwnd);
}
m_hwnd = hwndTopmost;
};