📄 ftpfoldr.cpp
字号:
return hr;
}
/*****************************************************************************\
FUNCTION: GetUIObjectOfHfpl
DESCRIPTION:
_UNDOCUMENTED_: Nowhere is there a list of interfaces
that "should be" supported. You just have to add lots of
squirties and see what interfaces are asked for.
_UNDOCUMENTED_: Nowhere is it mentioned that passing
cidl = 0 (or the various other weird variants) means to
get a UI object on the folder itself.
_UNDOCUMENTED_: It is not mentioned whether the folder should
be expected to handle cidl != 1 when asked for an IExtractIcon.
I code defensively and handle the situation properly.
IExtractIcon(0) extracts the icon for the folder itself.
IExtractIcon(1) extracts the icon for the indicated pidl.
IExtractIcon(n) extracts a generic "multi-document" icon.
IContextMenu(0) produces a context menu for the folder itself.
(Not used by the shell, but used by ourselves internally.)
IContextMenu(n) produces a context menu for the multi-selection.
IDataObject(0) ?? doesn't do anything
IDataObject(n) produces a data object for the multi-selection.
IDropTarget(0) produces a droptarget for the folder itself.
(Not used by the shell, but used by ourselves internally.)
IDropTarget(1) produces a droptarget for the single item.
IShellView(0) ?? doesn't do anything
IShellView(1) produces a shellview for the single item.
(Nobody tries this yet, but I'm ready for it.)
\*****************************************************************************/
HRESULT CFtpFolder::GetUIObjectOfHfpl(HWND hwndOwner, CFtpPidlList * pflHfpl, REFIID riid, LPVOID * ppvObj, BOOL fFromCreateViewObject)
{
HRESULT hr = E_INVALIDARG;
if (IsEqualIID(riid, IID_IExtractIconA) ||
IsEqualIID(riid, IID_IExtractIconW) ||
IsEqualIID(riid, IID_IQueryInfo))
{
hr = CFtpIcon_Create(this, pflHfpl, riid, ppvObj);
//TraceMsg(TF_FTPISF, "CFtpFolder::GetUIObjectOfHfpl() CFtpIcon_Create() hr=%#08lx", hr);
ASSERT(SUCCEEDED(hr));
}
else if (IsEqualIID(riid, IID_IContextMenu))
{
hr = CFtpMenu_Create(this, pflHfpl, hwndOwner, riid, ppvObj, fFromCreateViewObject);
TraceMsg(TF_FTPISF, "CFtpFolder::GetUIObjectOfHfpl() CFtpMenu_Create() hr=%#08lx", hr);
ASSERT(SUCCEEDED(hr));
}
else if (IsEqualIID(riid, IID_IDataObject))
{
hr = CFtpObj_Create(this, pflHfpl, riid, ppvObj);
TraceMsg(TF_FTPISF, "CFtpFolder::GetUIObjectOfHfpl() CFtpObj_Create() hr=%#08lx", hr);
ASSERT(SUCCEEDED(hr));
}
else if (IsEqualIID(riid, IID_IDropTarget))
{
// This will fail when someone gets a property sheet on an FTP PIDL Shortcut
// that has a file as the destination.
hr = CreateSubViewObject(hwndOwner, pflHfpl, riid, ppvObj);
TraceMsg(TF_FTPISF, "CFtpFolder::GetUIObjectOfHfpl() CreateSubViewObject() hr=%#08lx", hr);
}
else if (IsEqualIID(riid, IID_IShellView))
{
ASSERT(0); // Shouldn't happen
}
else if (IsEqualIID(riid, IID_IQueryAssociations))
{
IQueryAssociations * pqa;
hr = AssocCreate(CLSID_QueryAssociations, IID_IQueryAssociations, (void **)&pqa);
if (SUCCEEDED(hr))
{
hr = pqa->Init(0, L"Folder", NULL, NULL);
if (SUCCEEDED(hr))
*ppvObj = (void *)pqa;
else
pqa->Release();
}
}
else
{
//TraceMsg(TF_FTPISF, "CFtpFolder::GetUIObjectOfHfpl() E_NOINTERFACE");
hr = E_NOINTERFACE;
}
if (FAILED(hr))
*ppvObj = NULL;
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
return hr;
}
static const LPCTSTR pszBadAppArray[] = {TEXT("aol.exe"), TEXT("waol.exe"), TEXT("msnviewr.exe"), TEXT("cs3.exe"), TEXT("msdev.exe")};
/*****************************************************************************\
FUNCTION: IsAppFTPCompatible
DESCRIPTION:
Some apps (WebOC hosts) fail to navigate to FTP directories.
We check the app here and see if it's one of those incompatible apps.
I don't worry about perf because we can do the work only once and cache
the result because our globals will be re-inited for each process.
GOOD:
========================================================================
iexplore.exe: Good of course.
explorer.exe: Good of course.
msdev.exe (v6): The HTML help works but folder navigations happen in
a new window. I don't care because the same happens in
the shell (File System case).
<Default Case>: These are apps built with VB's WebOC that work fine, but
they also have the open in new folder behavior.
BAD and UGLY:
========================================================================
msdev.exe (v5): You can navigate their Moniker help to FTP which will
cause a hang.
[MSN] (msnviewr.exe): For some reason MSN calls IPersistFolder::Initialize with an invalid value.
Navigating to the folder works but launching other folders cause them
to appear in their own window and they immediately close. This was
on browser only so it may be because internet delegate folders aren't
supported.
[aol]: (waol.exe) This doesn't work either.
cs3.exe (CompuServ): ????
[ATT WorldNet]: ????
[Protigy]: ????
[SNAP]: ????
\*****************************************************************************/
BOOL IsAppFTPCompatible(void)
{
static BOOL s_fIsAppCompatible;
static BOOL s_fIsResultCached = FALSE;
//
if (!s_fIsResultCached)
{
TCHAR szAppPath[MAX_PATH];
s_fIsAppCompatible = TRUE; // Assume all Web OC Hosts are fine...
if (EVAL(GetModuleFileName(NULL, szAppPath, ARRAYSIZE(szAppPath))))
{
int nIndex;
LPTSTR pszAppFileName = PathFindFileName(szAppPath);
for (nIndex = 0; nIndex < ARRAYSIZE(pszBadAppArray); nIndex++)
{
if (!StrCmpI(pszAppFileName, pszBadAppArray[nIndex]))
{
// This one is bad/
s_fIsAppCompatible = FALSE;
break;
}
}
}
s_fIsResultCached = TRUE;
}
return s_fIsAppCompatible;
}
/*****************************************************************************\
FUNCTION: CreateSubViewObject
DESCRIPTION:
Somebody is asking for a UI object of a subobject, which is
better handled by the subobject than by the parent.
Bind to the subobject and get the requested UI object thence.
If the pidl list is empty, then we are talking about ourselves again.
\*****************************************************************************/
HRESULT CFtpFolder::CreateSubViewObject(HWND hwndOwner, CFtpPidlList * pflHfpl, REFIID riid, LPVOID * ppvObj)
{
HRESULT hr = E_INVALIDARG;
DWORD dwItemsSelected = pflHfpl->GetCount();
IShellFolder * psf = NULL;
if (EVAL(ppvObj)) // I wouldn't be surprised if
*ppvObj = NULL; // somebody relied on this
if (1 == dwItemsSelected)
{
LPITEMIDLIST pidl = pflHfpl->GetPidl(0); // This doesn't clone the pidl so we don't need to free it.
if (EVAL(pidl))
hr = BindToObject(pidl, 0, IID_IShellFolder, (LPVOID *)&psf);
}
else if (EVAL(0 == dwItemsSelected))
hr = this->QueryInterface(IID_IShellFolder, (void **) &psf);
ASSERT_POINTER_MATCHES_HRESULT(psf, hr);
if (EVAL(SUCCEEDED(hr)))
{
// CreateViewObject will AddRef the psfT if it wants it
hr = psf->CreateViewObject(hwndOwner, riid, ppvObj);
}
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
ATOMICRELEASE(psf);
return hr;
}
/*****************************************************************************\
GetSiteMotd
\*****************************************************************************/
CFtpGlob * CFtpFolder::GetSiteMotd(void)
{
CFtpGlob * pGlob = NULL;
_InitFtpSite(); // Okay if it fails.
if (m_pfs)
pGlob = m_pfs->GetMotd();
return pGlob;
}
HRESULT CFtpFolder::_Initialize(LPCITEMIDLIST pidlTarget, LPCITEMIDLIST pidlRoot, int nBytesToPrivate)
{
IUnknown_Set(&m_pfs, NULL);
return CBaseFolder::_Initialize(pidlTarget, pidlRoot, nBytesToPrivate);
}
// Sometimes the user will enter incorrect information without knowing.
// We would catch this if we verified everything that was entered, but
// we don't, we just take it on faith until we do the IEnumIDList.
// This is great for perf but sucks for catching these kinds of things.
// An example of this is the user using the File.Open dialog and going to
// "ftp://myserver/dir/". They then enter "ftp://myserver/dir/file.txt"
// which will try to parse relative but it's an absolute path.
HRESULT CFtpFolder::_FilterBadInput(LPCTSTR pszUrl, LPITEMIDLIST * ppidl)
{
HRESULT hr = S_OK;
// If pidlPrivate isn't empty, then we aren't at the
// root, so reject any urls that are absolute (i.e. have
// ftp: scheme).
if (!IsRoot() && (URL_SCHEME_FTP == GetUrlScheme(pszUrl)))
hr = E_FAIL;
// More may come here...
if (FAILED(hr) && *ppidl)
Pidl_Set(ppidl, NULL);
return hr;
}
/*****************************************************************************\
FUNCTION: _ForPopulateAndEnum
DESCRIPTION:
This function exists to detect the following case and if it's true,
populate the cache (pfd) and return the pidl from that cache in ppidl.
There is one last thing we need to try, we need to detect if:
1) the URL has an URL path, and
2) the last item in the path doesn't have an extension and doesn't
end in a slash ('/') to indicate it's a directory.
If this case is true, we then need to find out if it is a directory
or file by hitting the server. This is needed because by the time
we bind, it's too late to fall back to the other thing (IEnumIDList).
The one thing we might need to be careful about is AutoComplete because
they may call :: ParseDisplayName() for every character a user types.
This won't be so bad because it's on a background thread, asynch, and
the first enum within a segment will cause the cache to be populated
within a that segment so subsequent enums will be fast. The problem
it that it's not uncommon for users to enter between 2 and 5 segments,
and there would be 1 enum per segment.
\*****************************************************************************/
HRESULT CFtpFolder::_ForPopulateAndEnum(CFtpDir * pfd, LPCITEMIDLIST pidlBaseDir, LPCTSTR pszUrl, LPCWIRESTR pwLastDir, LPITEMIDLIST * ppidl)
{
HRESULT hr = E_FAIL;
*ppidl = NULL;
// We only care if the URL Path isn't empty AND it doesn't end in a '/' AND
// it doesn't have an extension.
if (!ILIsEmpty(pfd->GetPathPidlReference()) && (0 == *PathFindExtensionA(pwLastDir)))
{
IEnumIDList * penumIDList;
// NULL hwnd needs to suppress all UI.
hr = CFtpEidl_Create(pfd, this, NULL, (SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN), &penumIDList);
if (EVAL(SUCCEEDED(hr)))
{
hr = penumIDList->Reset();
ASSERT(SUCCEEDED(hr));
// We are working off of the assumption that calling Reset will force it to hit the server and pull down all of the contents.
LPITEMIDLIST pidlFromCache = (LPITEMIDLIST) pfd->GetPidlFromWireName(pwLastDir);
if (pidlFromCache)
{
// It was found, this means that it exists now in the cache after we
// forced it to be populated.
*ppidl = ILCombine(pidlBaseDir, pidlFromCache);
ILFree(pidlFromCache);
}
else
hr = E_FAIL;
penumIDList->Release();
}
}
return hr;
}
HRESULT CFtpFolder::_GetCachedPidlFromDisplayName(LPCTSTR pszDisplayName, LPITEMIDLIST * ppidl)
{
HRESULT hr = E_FAIL;
if (ppidl)
{
CFtpDir * pfd = GetFtpDir();
if (pfd)
{
// We may have a pointer but the cache may still be empty, as in case NT #353324
CFtpPidlList * pfl = pfd->GetHfpl();
if (pfl)
{
// Yes, so we will continue to use the cache. Now let's get rid of that
// temp pointer.
pfl->Release();
}
else
{
// No we don't have it cashed, so pretend the pfd was returned NULL.
pfd->Release();
pfd = NULL;
}
}
*ppidl = NULL;
if (!pfd)
{
LPITEMIDLIST pidlBaseDir;
hr = CreateFtpPidlFromUrl(pszDisplayName, GetCWireEncoding(), NULL, &pidlBaseDir, m_pm, FALSE);
if (SUCCEEDED(hr)) // May fail because of AutoComplete.
{
// If it's not pointing to just a server, then we can enum the contents and
// find out if it's is a file or directory.
if (!ILIsEmpty(pidlBaseDir) && !FtpID_IsServerItemID(ILFindLastID(pidlBaseDir)))
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -