2467 lines
85 KiB
C++
2467 lines
85 KiB
C++
|
/*****************************************************************************\
|
||
|
FILE: ftpfoldr.h
|
||
|
|
||
|
DESCRIPTION:
|
||
|
This class inherits from CBaseFolder for a base ShellFolder implementation
|
||
|
of IShellFolder and overrides methods to give Ftp Specific features.
|
||
|
|
||
|
_UNDOCUMENTED_: The shell violates Apartment model threading
|
||
|
when doing background enumeration, so even though this DLL is
|
||
|
marked as Apartment model, IShellFolder and IEnumIDList must
|
||
|
be written with the free threading model with respect to anything
|
||
|
that IEnumIDList can do in the background.
|
||
|
|
||
|
This means that you'll see lots of ENTER_CRITICAL() and
|
||
|
LEAVE_CRITICAL() calls when your brain would say, "I don't
|
||
|
need to do that because I'm Apartment-model." I'll try to
|
||
|
point them out as they occur; look for the marker _MT_.
|
||
|
|
||
|
CAUTION! Internally, our property sheet handler also invokes
|
||
|
methods on CFtpFolder on the wrong thread, so it's not just the
|
||
|
shell that is weird.
|
||
|
\*****************************************************************************/
|
||
|
|
||
|
#include "priv.h"
|
||
|
#include "ftpfoldr.h"
|
||
|
#include "ftpurl.h"
|
||
|
#include "ftppidl.h"
|
||
|
#include "ftpicon.h"
|
||
|
#include "view.h"
|
||
|
#include "proxycache.h"
|
||
|
#include <idhidden.h>
|
||
|
|
||
|
#define FEATURE_SOFTLINK_SHORTCUT_ICONOVERLAY
|
||
|
|
||
|
// {A11501B3-6EA4-11d2-B679-006097DF5BD4} Private to msieftp.dll
|
||
|
const GUID IID_CFtpFolder = { 0xa11501b3, 0x6ea4, 0x11d2, { 0xb6, 0x79, 0x0, 0x60, 0x97, 0xdf, 0x5b, 0xd4 } };
|
||
|
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* More const statics.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
#pragma BEGIN_CONST_DATA
|
||
|
|
||
|
WORD c_wZero = 0; /* As promised in ftpview.h */
|
||
|
|
||
|
/*
|
||
|
* String separator used when building relative names.
|
||
|
*/
|
||
|
char c_szSlash[] = "/";
|
||
|
|
||
|
|
||
|
#pragma END_CONST_DATA
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_AddToUrlHistory(LPCWSTR pwzUrl)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if (!m_puhs)
|
||
|
{
|
||
|
hr = CoCreateInstance(CLSID_CUrlHistory, NULL, CLSCTX_INPROC_SERVER, IID_IUrlHistoryStg, (void **)&m_puhs);
|
||
|
}
|
||
|
|
||
|
if (m_puhs)
|
||
|
{
|
||
|
hr = m_puhs->AddUrl(pwzUrl, pwzUrl, 0);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
/* Not yet needed
|
||
|
HRESULT CFtpFolder::AddToUrlHistory(LPCTSTR pszUrl)
|
||
|
{
|
||
|
return _AddToUrlHistory(wzUrl);
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
HRESULT CFtpFolder::AddToUrlHistory(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
WCHAR wzUrl[MAX_URL_STRING];
|
||
|
HRESULT hr = UrlCreateFromPidlW(pidl, SHGDN_FORPARSING, wzUrl, ARRAYSIZE(wzUrl), (ICU_ESCAPE | ICU_USERNAME), TRUE);
|
||
|
|
||
|
if (EVAL(SUCCEEDED(hr)))
|
||
|
{
|
||
|
hr = _AddToUrlHistory(wzUrl);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
CWireEncoding * CFtpFolder::GetCWireEncoding(void)
|
||
|
{
|
||
|
// GetFtpDir() may return NULL when we aren't rooted in an FTP server.
|
||
|
CFtpDir * pfd = GetFtpDir();
|
||
|
CWireEncoding * pwe = NULL;
|
||
|
|
||
|
if (pfd)
|
||
|
{
|
||
|
pwe = pfd->GetFtpSite()->GetCWireEncoding();
|
||
|
pfd->Release();
|
||
|
}
|
||
|
|
||
|
return pwe;
|
||
|
}
|
||
|
|
||
|
HRESULT CFtpFolder::_FixQuestionablePidl(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
// NOTE: In the future, we may want to hit the server to
|
||
|
// disambiguate this.
|
||
|
/*
|
||
|
BOOL fIsDir = TRUE;
|
||
|
LPCSTR pszName = FtpPidl_GetLastItemName(pidl);
|
||
|
|
||
|
// Can we get the name?
|
||
|
if (EVAL(pszName))
|
||
|
{
|
||
|
// Is the file extension non-NULL? (Meaning it exists)
|
||
|
if ('\0' != *PathFindExtensionA(pszName))
|
||
|
fIsDir = FALSE; // Yes, so asume it's a file.
|
||
|
}
|
||
|
|
||
|
hr = FtpPidl_SetFileItemType((LPITEMIDLIST) pidl, fIsDir);
|
||
|
*/
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CFtpFolder::_IsServerVMS(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
BOOL fIsServerVMS = FALSE; // Assume TRUE
|
||
|
CFtpSite * pfs;
|
||
|
|
||
|
// Some caller's don't pass the Server ID so let's assume
|
||
|
// that they already made it past that point.
|
||
|
if (FtpID_IsServerItemID(pidl) &&
|
||
|
EVAL(SUCCEEDED(SiteCache_PidlLookup(pidl, FALSE, m_pm, &pfs))))
|
||
|
{
|
||
|
fIsServerVMS = pfs->IsServerVMS();
|
||
|
pfs->Release();
|
||
|
}
|
||
|
|
||
|
return fIsServerVMS;
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************\
|
||
|
FUNCTION: _IsProxyBlockingSite
|
||
|
|
||
|
DESCRIPTION:
|
||
|
We need to detect if we cannot connect to the
|
||
|
site because the proxy is a CERN or CERN type proxy
|
||
|
that blocks ALL ftp access. If this is true, we
|
||
|
need to inform the user can fall all
|
||
|
IShellFolder::BindToObject() calls.
|
||
|
|
||
|
We will detect this case by doing the normal
|
||
|
WININET FTP InternetConnect(). If that returns
|
||
|
hr=0x80002EE7 (ERROR_INTERNET_NAME_NOT_RESOLVED)
|
||
|
then it could either be that the name doesn't exist,
|
||
|
or there is a CERN proxy blocking the call. We will
|
||
|
then try connect the CERN method which will tell us
|
||
|
if it's the proxy that is blocking us.
|
||
|
\****************************************************/
|
||
|
BOOL CFtpFolder::_IsProxyBlockingSite(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
BOOL fCacheResult;
|
||
|
BOOL fResult = TRUE; // Assume TRUE
|
||
|
CFtpDir * pfd;
|
||
|
|
||
|
if (ProxyCache_IsProxyBlocking(pidl, &fCacheResult))
|
||
|
return fCacheResult;
|
||
|
|
||
|
if (EVAL(pfd = GetFtpDirFromPidl(pidl)))
|
||
|
{
|
||
|
HRESULT hr = pfd->WithHint(NULL, NULL, NULL, NULL, NULL, this);
|
||
|
|
||
|
// WithHint() often fails if a CERN style proxy blocks REAL wininet
|
||
|
// access to the server. If the server name is a DNS name, the error
|
||
|
// returned will be ERROR_INTERNET_NAME_NOT_RESOLVED because that is
|
||
|
// what is returned by the CERN proxy. If the server name is an IP
|
||
|
// Address, wininet will skip the CERN proxy and try to find it on the
|
||
|
// intranet. If not found (because it's past the firewall), then,
|
||
|
// the attempt will timeout with ERROR_INTERNET_TIMEOUT. We need
|
||
|
// to treat this as a proxy block if and ONLY if the server name is an
|
||
|
// IP address because DNS names can timeout for other reasons. Us
|
||
|
// treating IP server name timeouts as proxy blocks is going to have
|
||
|
// to be tolerated because wininet won't handle this case. It happens
|
||
|
// very infrequently so I don't care that much.
|
||
|
//
|
||
|
// Some authentication proxies fail with: ERROR_INTERNET_CANNOT_CONNECT
|
||
|
// We would like to fall back in that case, however, that may include
|
||
|
// other cases like the server refusing to allow us in.
|
||
|
// (password or too many logged in users?)
|
||
|
// It would be great if ERROR_INTERNET_INVALID_PROXY_REQUEST or
|
||
|
// ERROR_INTERNET_CLIENT_AUTH_NOT_SETUP could be used.
|
||
|
//
|
||
|
if ((HRESULT_FROM_WIN32(ERROR_INTERNET_NAME_NOT_RESOLVED) == hr) ||
|
||
|
(HRESULT_FROM_WIN32(ERROR_INTERNET_CANNOT_CONNECT) == hr) ||
|
||
|
((HRESULT_FROM_WIN32(ERROR_INTERNET_TIMEOUT) == hr) && !FtpPidl_IsDNSServerName(pidl)))
|
||
|
{
|
||
|
TCHAR szUrl[MAX_URL_STRING];
|
||
|
|
||
|
if (EVAL(SUCCEEDED(UrlCreateFromPidl(pidl, SHGDN_FORPARSING, szUrl, ARRAYSIZE(szUrl), ICU_ESCAPE | ICU_USERNAME, FALSE))))
|
||
|
{
|
||
|
HINTERNET hintTemp;
|
||
|
ASSERT(GetWininetSessionHandle());
|
||
|
|
||
|
// For Web Proxies, InternetOpenUrl should work. The problem is that
|
||
|
// some (Netscape's) don't work.
|
||
|
if (SUCCEEDED(InternetOpenUrlWrap(GetWininetSessionHandle(), TRUE, szUrl, NULL, 0, INTERNET_FLAG_NO_UI, NULL, &hintTemp)))
|
||
|
{
|
||
|
InternetCloseHandle(hintTemp); // This did work, so we must have a CERN proxy.
|
||
|
}
|
||
|
else
|
||
|
fResult = FALSE; // We aren't blocked by the proxy. (Wrong IP Addr or Name?)
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
fResult = FALSE; // We aren't blocked by the proxy.
|
||
|
|
||
|
// Cache the result since finding out is so expensive.
|
||
|
ProxyCache_SetProxyBlocking(pidl, fResult);
|
||
|
pfd->Release();
|
||
|
}
|
||
|
|
||
|
return fResult;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* InvalidateCache
|
||
|
*
|
||
|
* Invalidate the pflHfpl cache in the corresponding FtpDir.
|
||
|
*
|
||
|
* _MT_: Note that the background enumerator calls this, so it must be
|
||
|
* multithread-safe.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
void CFtpFolder::InvalidateCache(void)
|
||
|
{
|
||
|
CFtpDir * pfd = GetFtpDir();
|
||
|
|
||
|
if (pfd)
|
||
|
{
|
||
|
// Should have created one on the GetHint()
|
||
|
pfd->SetCache(0);
|
||
|
pfd->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_InitFtpSite(void)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if (!m_pfs) // If we don't already got one...
|
||
|
{
|
||
|
ENTERCRITICAL;
|
||
|
if (!m_pfs) // Did it get created while we were waiting
|
||
|
{
|
||
|
if (EVAL(GetPrivatePidlReference()))
|
||
|
hr = SiteCache_PidlLookup(GetPrivatePidlReference(), TRUE, m_pm, &m_pfs);
|
||
|
else
|
||
|
{
|
||
|
// Not initialized
|
||
|
TraceMsg(TF_FTPISF, "CFtpFolder_GetFtpDir(%08x) NOT INITED", this);
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LEAVECRITICAL;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: GetFtpDir
|
||
|
|
||
|
DESCRIPTION:
|
||
|
Say where our dir info is.
|
||
|
|
||
|
We allocate the pfd only if somebody actually needs it, because
|
||
|
Explorer does a lot of ILCompare's when you open a new folder,
|
||
|
each of which creates a new IShellFolder for the sole purpose
|
||
|
of calling CompareIDs. We don't want to go through all the
|
||
|
hubbub of creating an FtpDir and FtpSite when we don't need one.
|
||
|
|
||
|
_MT_: Note that the background enumerator calls this, so it must be
|
||
|
multithread-safe. In such case, however, the IShellFolder is
|
||
|
marked cBusy, so we don't have to worry about the this->pfd
|
||
|
getting wiped out behind our back by a change of identity.
|
||
|
\*****************************************************************************/
|
||
|
CFtpDir * CFtpFolder::GetFtpDir(void)
|
||
|
{
|
||
|
HRESULT hres = S_OK;
|
||
|
CFtpDir * pfd = NULL;
|
||
|
|
||
|
_InitFtpSite(); // Okay if it fails.
|
||
|
if (m_pfs)
|
||
|
hres = m_pfs->GetFtpDir(GetPrivatePidlReference(), &pfd); // GetFtpDir can fail in out of memory
|
||
|
|
||
|
return pfd;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
CFtpDir * CFtpFolder::GetFtpDirFromPidl(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
HRESULT hres = S_OK;
|
||
|
CFtpDir * pfd = NULL;
|
||
|
CFtpSite * pfs = NULL;
|
||
|
|
||
|
hres = SiteCache_PidlLookup(pidl, FALSE, m_pm, &pfs);
|
||
|
if (pfs)
|
||
|
{
|
||
|
hres = pfs->GetFtpDir(pidl, &pfd);
|
||
|
pfs->Release();
|
||
|
}
|
||
|
|
||
|
return pfd;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
* GetItemAllocator
|
||
|
*
|
||
|
* Return today's pidl allocator.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::GetItemAllocator(IMalloc **ppm)
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
|
||
|
*ppm = NULL;
|
||
|
if (EVAL(m_pm))
|
||
|
{
|
||
|
IUnknown_Set(ppm, m_pm);
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
TraceMsg(TF_FTPISF, "CFtpFolder_GetItemAllocator(%08x) NOT INITED", this);
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
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);
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IDataObject))
|
||
|
{
|
||
|
hr = CFtpObj_Create(this, pflHfpl, riid, ppvObj); // Can fail in out of memory.
|
||
|
TraceMsg(TF_FTPISF, "CFtpFolder::GetUIObjectOfHfpl() CFtpObj_Create() hr=%#08lx", 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);
|
||
|
|
||
|
// Default to TRUE because if it's not in the registry, then default to compatible.
|
||
|
s_fIsAppCompatible = SHRegGetBoolUSValue(SZ_REGKEY_FTPFOLDER_COMPAT, pszAppFileName, FALSE, TRUE);
|
||
|
for (nIndex = 0; nIndex < ARRAYSIZE(pszBadAppArray); nIndex++)
|
||
|
{
|
||
|
if (!StrCmpI(pszAppFileName, pszBadAppArray[nIndex]))
|
||
|
{
|
||
|
// Default to FALSE because if it's not in the registry, then it's incompatible because
|
||
|
// it's in our list.
|
||
|
s_fIsAppCompatible = SHRegGetBoolUSValue(SZ_REGKEY_FTPFOLDER_COMPAT, pszAppFileName, FALSE, 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 (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 (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 is bad 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 (pfd && !ILIsEmpty(pfd->GetPathPidlReference()) && (!pwLastDir || (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 (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)))
|
||
|
{
|
||
|
CFtpSite * pfs;
|
||
|
|
||
|
hr = SiteCache_PidlLookup(pidlBaseDir, TRUE, m_pm, &pfs);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
LPCWIRESTR pwLastDirName;
|
||
|
|
||
|
// If we are using a hidden password, then ::GetDisplayNameOf() hands out
|
||
|
// these "ftp://user@server/dir/" URLs and the password is hidden. If
|
||
|
// :: ParseDisplayName() is given one of these URLs and we are currently in
|
||
|
// that server w/that user name, then :: ParseDisplayNameOf() needs to hand
|
||
|
// out a pidl with the correct hidden password cookie.
|
||
|
//
|
||
|
// Is pidlNav the same as GetPublicRootPidlReference() except pidlNav doesn't
|
||
|
// have a password. The same means that the servers match, and the user names
|
||
|
// match.
|
||
|
EVAL(SUCCEEDED(pfs->UpdateHiddenPassword(pidlBaseDir)));
|
||
|
|
||
|
// This is sneaky because pwLastDirName will point into them itemID
|
||
|
// that will be removed. The memory won't really be removed, it will
|
||
|
// just have the size set to zero.
|
||
|
pwLastDirName = FtpPidl_GetLastItemWireName(pidlBaseDir);
|
||
|
|
||
|
ILRemoveLastID(pidlBaseDir);
|
||
|
pfs->GetFtpDir(pidlBaseDir, &pfd);
|
||
|
|
||
|
if (pfd)
|
||
|
{
|
||
|
LPITEMIDLIST pidlFromCache = (LPITEMIDLIST) pfd->GetPidlFromWireName(pwLastDirName);
|
||
|
if (pidlFromCache)
|
||
|
{
|
||
|
// It was found, this means we were probably in ftp://serverX/Dir1/
|
||
|
// and the user entered something from that directory or another directory
|
||
|
// taht we have alread displayed to the user and it's in our cache.
|
||
|
*ppidl = ILCombine(pidlBaseDir, pidlFromCache);
|
||
|
ILFree(pidlFromCache);
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// 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.
|
||
|
hr = _ForPopulateAndEnum(pfd, pidlBaseDir, pszDisplayName, pwLastDirName, ppidl);
|
||
|
}
|
||
|
|
||
|
|
||
|
pfd->Release();
|
||
|
}
|
||
|
else
|
||
|
hr = E_FAIL;
|
||
|
|
||
|
pfs->Release();
|
||
|
}
|
||
|
else
|
||
|
hr = E_FAIL;
|
||
|
}
|
||
|
else
|
||
|
hr = E_FAIL;
|
||
|
|
||
|
ILFree(pidlBaseDir);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Create a new enumeration object for the caller.
|
||
|
// PERF: log 2 (sizeof(m_pflHfpl))
|
||
|
*ppidl = (LPITEMIDLIST) pfd->GetPidlFromDisplayName(pszDisplayName);
|
||
|
if (*ppidl)
|
||
|
{
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// If we got here, the cache for this directory is populated.
|
||
|
// So if the name doesn't match, then either:
|
||
|
// 1) it doesn't exist,
|
||
|
// 2) the cache is out of date, or
|
||
|
// 3) it's multilevel, (like "dir1\dir2\dir3") or
|
||
|
// 4) It's a weird parsing token that our parent parse should have remoted, like "..", ".", "\", etc.
|
||
|
// We will assome our parent parse takes care of #4, and #2 isn't true.
|
||
|
|
||
|
// Is this multilevel? (Case #3)
|
||
|
if (!StrChr(pszDisplayName, TEXT('/')))
|
||
|
{
|
||
|
// No, so reject it and don't let our caller blindly accept it.
|
||
|
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pfd->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_GetBindCtx(IBindCtx ** ppbc)
|
||
|
{
|
||
|
HRESULT hr = CreateBindCtx(NULL, ppbc);
|
||
|
|
||
|
if (SUCCEEDED(hr)) // Can fail with out of memory
|
||
|
{
|
||
|
hr = (*ppbc)->RegisterObjectParam(STR_SKIP_BINDING_CLSID, SAFECAST(this, IShellIcon *)); // We want IUnknown, not IShellIcon, but this is to disambigiuate.
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
ATOMICRELEASE(*ppbc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_GetLegacyURL(LPCITEMIDLIST pidl, IBindCtx * pbc, LPTSTR pszUrl, DWORD cchSize)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
LPITEMIDLIST pidlWithVRoot;
|
||
|
|
||
|
// We now need to insert the virtual root path into the path section
|
||
|
// of the URL because the old FTP support doesn't follow the correct
|
||
|
// FTP URL spec that says that the virtual root needs to be left out
|
||
|
// of the URL.
|
||
|
hr = _ConvertPidlForRootedFix(pidl, &pidlWithVRoot);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
WCHAR wzFrag[MAX_PATH];
|
||
|
|
||
|
// SECURITY ISSUE: We need to get the URL w/password or it won't work, but
|
||
|
// this will expose the password publicly. We need a way for
|
||
|
// the real FTP URL Pidl to hide the password.
|
||
|
hr = UrlCreateFromPidlW(pidlWithVRoot, SHGDN_FORPARSING, pszUrl, cchSize, (ICU_ESCAPE | ICU_USERNAME), FALSE);
|
||
|
if (ILGetHiddenStringW(pidl, IDLHID_URLFRAGMENT, wzFrag, ARRAYSIZE(wzFrag))) // Add fragment if it exists.
|
||
|
UrlCombineW(pszUrl, wzFrag, pszUrl, &cchSize, 0);
|
||
|
|
||
|
ILFree(pidlWithVRoot);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_GetLegacyPidl(LPCITEMIDLIST pidl, LPITEMIDLIST * ppidlLegacy)
|
||
|
{
|
||
|
IBindCtx * pbc = NULL;
|
||
|
HRESULT hr = _GetBindCtx(&pbc);
|
||
|
|
||
|
*ppidlLegacy = NULL;
|
||
|
if (SUCCEEDED(hr)) // Can fail with out of memory.
|
||
|
{
|
||
|
WCHAR wzUrl[MAX_URL_STRING];
|
||
|
|
||
|
hr = _GetLegacyURL(pidl, pbc, wzUrl, ARRAYSIZE(wzUrl));
|
||
|
if (EVAL(SUCCEEDED(hr)))
|
||
|
{
|
||
|
TraceMsg(TF_FTPISF, "_BindToObject_OriginalFtpSupport() navigating to=%ls", wzUrl);
|
||
|
hr = IEParseDisplayNameWithBCW(CP_ACP, wzUrl, pbc, ppidlLegacy);
|
||
|
}
|
||
|
|
||
|
pbc->Release();
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_InitLegacyShellFolder(IShellFolder * psfLegacy, LPCITEMIDLIST pidlInit)
|
||
|
{
|
||
|
IPersistFolder * ppf;
|
||
|
HRESULT hr = psfLegacy->QueryInterface(IID_IPersistFolder, (void **) &ppf);
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = ppf->Initialize(pidlInit);
|
||
|
ppf->Release();
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_INetBindToObject(LPCITEMIDLIST pidl, IBindCtx * pbc, REFIID riid, LPVOID * ppvObj)
|
||
|
{
|
||
|
HRESULT hr = E_OUTOFMEMORY;
|
||
|
|
||
|
LPITEMIDLIST pidlFirst = GetPublicPidlRootIDClone();
|
||
|
if (pidlFirst)
|
||
|
{
|
||
|
IShellFolder * psfInternetSF;
|
||
|
|
||
|
hr = IEBindToObject(pidlFirst, &psfInternetSF);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = _InitLegacyShellFolder(psfInternetSF, pidlFirst);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// Note the I use ILNext() in order to skip past the Desktop ItemID,
|
||
|
// which is internal knowledge I should not have.
|
||
|
hr = psfInternetSF->BindToObject(_ILNext(pidl), pbc, riid, ppvObj);
|
||
|
}
|
||
|
|
||
|
psfInternetSF->Release();
|
||
|
}
|
||
|
|
||
|
ILFree(pidlFirst);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_BindToObject_OriginalFtpSupport(LPCITEMIDLIST pidl, REFIID riid, LPVOID * ppvObj)
|
||
|
{
|
||
|
LPBC pbc = NULL;
|
||
|
HRESULT hr = CreateBindCtx(NULL, &pbc);
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = pbc->RegisterObjectParam(STR_SKIP_BINDING_CLSID, SAFECAST(this, IShellIcon *)); // We want IUnknown, not IShellIcon, but this is to disambigiuate.
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
LPITEMIDLIST pidlLegacy;
|
||
|
|
||
|
hr = _GetLegacyPidl(pidl, &pidlLegacy);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = _INetBindToObject(pidlLegacy, pbc, riid, ppvObj);
|
||
|
ILFree(pidlLegacy);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pbc->Release();
|
||
|
}
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: _IsValidPidlParameter
|
||
|
|
||
|
DESCRIPTION:
|
||
|
If this IShellFolder is rooted within our name space, then the pidl needs
|
||
|
to be a valid relative pidl. If we are rooted at the base of our name space,
|
||
|
then it needs to be a full pidl.
|
||
|
\*****************************************************************************/
|
||
|
BOOL CFtpFolder::_IsValidPidlParameter(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
BOOL fResult = TRUE;
|
||
|
|
||
|
if (IsRoot())
|
||
|
fResult = FtpPidl_IsValidFull(pidl);
|
||
|
else
|
||
|
fResult = FtpPidl_IsValidRelative(pidl);
|
||
|
|
||
|
return fResult;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder::_BindToObject
|
||
|
|
||
|
DESCRIPTION:
|
||
|
We are now sure that we want to handle the support, so check what they
|
||
|
want.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::_BindToObject(LPCITEMIDLIST pidl, LPCITEMIDLIST pidlFull, IBindCtx * pbc, REFIID riid, LPVOID * ppvObj)
|
||
|
{
|
||
|
HRESULT hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Indicate we want the old functionality to kick in.
|
||
|
|
||
|
if (IsEqualIID(riid, IID_IShellFolder) ||
|
||
|
IsEqualIID(riid, IID_IShellFolder2) ||
|
||
|
IsEqualIID(riid, IID_IBrowserFrameOptions))
|
||
|
{
|
||
|
LPITEMIDLIST pidlTarget = ILCombine(GetPublicTargetPidlReference(), pidl);
|
||
|
LPITEMIDLIST pidlRoot = (GetFolderPidl() ? ILCombine(GetFolderPidl(), pidl) : NULL);
|
||
|
|
||
|
// There's no point trying to verify that it's folders all
|
||
|
// the way down, because it's the caller's job not to combine
|
||
|
// pidls randomly. Furthermore, they might not actually be marked
|
||
|
// as folders if we got them via ParseDisplayName.
|
||
|
|
||
|
// NOTE: Binding will succeed even if the pidl isn't valid on the
|
||
|
// server. In the future we may want to verify now so we
|
||
|
// don't hand out a IEnumIDList that won't work. Currently,
|
||
|
// IEnumIDList will fail and cause a renavigation if it can
|
||
|
// connect to the server in a different way (different username
|
||
|
// password pair). It would be better to do a redirect because
|
||
|
// the renavigation causes the bad entry in the navigation stack.
|
||
|
// We can't verify the item exists on the server if we have a WebProxy
|
||
|
// installed.
|
||
|
|
||
|
hr = CFtpFolder_Create(pidlTarget, pidlRoot, GetPidlByteOffset(), riid, ppvObj);
|
||
|
//TraceMsg(TF_FOLDER_SHRTCUTS, "CFtpFolder::_BindToObject() creating an FTP IShellFolder psf=%#08lx, pidlTarget=%#08lx, pidlRoot=%#08lx", *ppvObj, pidlTarget, pidlRoot);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
IUnknown * punk = (IUnknown *) *ppvObj;
|
||
|
IDelegateFolder * pdf;
|
||
|
|
||
|
hr = punk->QueryInterface(IID_IDelegateFolder, (LPVOID *) &pdf);
|
||
|
if (EVAL(SUCCEEDED(hr)))
|
||
|
{
|
||
|
hr = pdf->SetItemAlloc(m_pm);
|
||
|
pdf->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ILFree(pidlTarget);
|
||
|
ILFree(pidlRoot);
|
||
|
//TraceMsg(TF_FTPISF, "CFtpFolder::BindToObject() IID_IShellFolder hr=%#08lx", hr);
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IMoniker))
|
||
|
{
|
||
|
hr = _PidlToMoniker(pidlFull, (IMoniker **) ppvObj);
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IStream))
|
||
|
{
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
|
||
|
CFtpDir * pfd = GetFtpDir();
|
||
|
if (pfd)
|
||
|
{
|
||
|
DWORD dwAccess = (BindCtx_GetMode(pbc, STGM_READ) & STGM_WRITE) ? GENERIC_WRITE : GENERIC_READ;
|
||
|
ULARGE_INTEGER uliTemp = {0};
|
||
|
|
||
|
hr = CFtpStm_Create(pfd, pidlFull, dwAccess, (IStream **)ppvObj, uliTemp, uliTemp, NULL, FALSE);
|
||
|
|
||
|
pfd->Release();
|
||
|
}
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_CFtpFolder))
|
||
|
{
|
||
|
IShellFolder * psf;
|
||
|
|
||
|
// Nothing like a little recursion to keep the code clean.
|
||
|
// The fact that we use IID_IShellFolder guarantees the breaking
|
||
|
// of the recursion.
|
||
|
hr = BindToObject(pidl, pbc, IID_IShellFolder, (void **) &psf);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = psf->QueryInterface(riid, ppvObj);
|
||
|
psf->Release();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TraceMsg(TF_FTPISF, "CFtpFolder::BindToObject() unsupported interface hr=E_NOINTERFACE");
|
||
|
*ppvObj = NULL;
|
||
|
hr = E_NOINTERFACE;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: _ConvertPidlForRootedFix
|
||
|
|
||
|
DESCRIPTION:
|
||
|
If an FTP URL has a login name, that login may root the user under a directory other than "/".
|
||
|
The FTP URL spec (RFC 1738) says that URL paths need to be relative to the rooted directory. For example:
|
||
|
If UserA's rooted account is in \usr\GroupA\UserA and the url is:
|
||
|
ftp://UserA:FooBar@server/test/file.txt, then the real path is \usr\GroupA\UserA\test\file.txt.
|
||
|
The problem is that the old FTP code doesn't respect this and requires:
|
||
|
ftp://UserA:FooBar@server/usr/GroupA/UserA/test/file.txt, so we fix that here.
|
||
|
|
||
|
PARAMETERS:
|
||
|
pidlBefore [IN]: This will be a public pidl to the item to navigate to.
|
||
|
This means it will be: [TheINet][FtpServerID][...]
|
||
|
*ppidlWithVRoot [OUT]: This will be the same public pidl that was passed in except
|
||
|
any ItemIDs that come from pfs->GetVirtualRootReference() will be inserted
|
||
|
between the ServerID and ItemIDs.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::_ConvertPidlForRootedFix(LPCITEMIDLIST pidlBefore, LPITEMIDLIST * ppidlWithVRoot)
|
||
|
{
|
||
|
CFtpSite * pfs;
|
||
|
HRESULT hr = SiteCache_PidlLookup(pidlBefore, FALSE, m_pm, &pfs);
|
||
|
|
||
|
*ppidlWithVRoot = NULL;
|
||
|
if (SUCCEEDED(hr) && pfs)
|
||
|
{
|
||
|
if (pfs->HasVirtualRoot())
|
||
|
{
|
||
|
LPCITEMIDLIST pidlVirtualRoot = pfs->GetVirtualRootReference();
|
||
|
LPITEMIDLIST pidlUrlPath = (LPITEMIDLIST)pidlBefore;
|
||
|
|
||
|
// Skip past non-FTP Server/ItemIDs. (TheInternet)
|
||
|
while (pidlUrlPath && !ILIsEmpty(pidlUrlPath) && !FtpID_IsServerItemID(pidlUrlPath))
|
||
|
pidlUrlPath = _ILNext(pidlUrlPath);
|
||
|
|
||
|
if (FtpID_IsServerItemID(pidlUrlPath))
|
||
|
pidlUrlPath = _ILNext(pidlUrlPath);
|
||
|
|
||
|
if (EVAL(pidlUrlPath))
|
||
|
{
|
||
|
LPITEMIDLIST pidlFullWithVRoot;
|
||
|
USHORT cb = pidlUrlPath->mkid.cb;
|
||
|
|
||
|
pidlUrlPath->mkid.cb = 0;
|
||
|
pidlFullWithVRoot = ILCombine(pidlBefore, pidlVirtualRoot);
|
||
|
pidlUrlPath->mkid.cb = cb;
|
||
|
|
||
|
if (pidlFullWithVRoot)
|
||
|
{
|
||
|
FtpPidl_InsertVirtualRoot(pidlFullWithVRoot, pidlUrlPath, ppidlWithVRoot);
|
||
|
ILFree(pidlFullWithVRoot);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
else
|
||
|
hr = E_FAIL;
|
||
|
|
||
|
pfs->Release();
|
||
|
}
|
||
|
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
*ppidlWithVRoot = ILClone(pidlBefore);
|
||
|
if (*ppidlWithVRoot)
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CFtpFolder::IsUTF8Supported(void)
|
||
|
{
|
||
|
if (EVAL(m_pfs))
|
||
|
return m_pfs->IsUTF8Supported();
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder::_PidlToMoniker
|
||
|
|
||
|
DESCRIPTION:
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::_PidlToMoniker(LPCITEMIDLIST pidl, IMoniker ** ppmk)
|
||
|
{
|
||
|
HRESULT hr = E_INVALIDARG;
|
||
|
|
||
|
*ppmk = NULL;
|
||
|
if (EVAL(pidl))
|
||
|
{
|
||
|
IBindCtx * pbc;
|
||
|
|
||
|
hr = _GetBindCtx(&pbc);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
WCHAR wzUrl[MAX_URL_STRING];
|
||
|
|
||
|
// URLMON expects incorrectly formatted URLs (where the virtual
|
||
|
// root is included in the url path). We need to fix that
|
||
|
// here.
|
||
|
hr = _GetLegacyURL(pidl, pbc, wzUrl, ARRAYSIZE(wzUrl));
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = CreateURLMoniker(NULL, wzUrl, ppmk);
|
||
|
}
|
||
|
|
||
|
pbc->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppmk, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_CreateShellView(HWND hwndOwner, void ** ppvObj)
|
||
|
{
|
||
|
IShellFolderViewCB * psfvCallBack;
|
||
|
|
||
|
HRESULT hr = CFtpView_Create(this, hwndOwner, IID_IShellFolderViewCB, (LPVOID *) &psfvCallBack);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// GetPublicTargetPidlReference() is used because it's passed to SFVM_GETNOTIFY
|
||
|
// to synch ChangeNotify messages.
|
||
|
hr = CBaseFolder::_CreateShellView(hwndOwner, ppvObj, FTP_SHCNE_EVENTS,
|
||
|
FVM_DETAILS, psfvCallBack, GetPublicTargetPidlReference(), CBaseFolderViewCB::_IShellFolderViewCallBack);
|
||
|
psfvCallBack->Release();
|
||
|
}
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HKEY ClassKeyFromExtension(LPCWIRESTR pszExt)
|
||
|
{
|
||
|
HKEY hkey = NULL;
|
||
|
WIRECHAR szProgID[MAX_PATH];
|
||
|
DWORD cbProgID = sizeof(szProgID);
|
||
|
|
||
|
if (ERROR_SUCCESS == SHGetValueA(HKEY_CLASSES_ROOT, pszExt, NULL, NULL, (void *)szProgID, &cbProgID))
|
||
|
{
|
||
|
// the entension points to a ProgID, use that.
|
||
|
RegOpenKeyA(HKEY_CLASSES_ROOT, szProgID, &hkey);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// No ProgID, use the extension as the program ID.
|
||
|
RegOpenKeyA(HKEY_CLASSES_ROOT, pszExt, &hkey);
|
||
|
}
|
||
|
|
||
|
return hkey;
|
||
|
}
|
||
|
|
||
|
#define SZ_REGVALUE_DOCOBJECT TEXT("DocObject")
|
||
|
#define SZ_REGVALUE_BROWSEINPLACE TEXT("BrowseInPlace")
|
||
|
|
||
|
BOOL _IsDocObjViewerInstalled(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
BOOL fResult = FALSE;
|
||
|
|
||
|
// Return FALSE if it's just pointing to an FTP server.
|
||
|
if (!FtpID_IsServerItemID(ILFindLastID(pidl)))
|
||
|
{
|
||
|
LPCWIRESTR pwWireFileName = FtpPidl_GetLastItemWireName(pidl);
|
||
|
LPCWIRESTR pszExt = PathFindExtensionA(pwWireFileName);
|
||
|
|
||
|
if (pszExt)
|
||
|
{
|
||
|
HKEY hkey = ClassKeyFromExtension(pszExt);
|
||
|
if (hkey)
|
||
|
{
|
||
|
if ((ERROR_SUCCESS == RegQueryValue(hkey, SZ_REGVALUE_DOCOBJECT, 0, NULL)) ||
|
||
|
(ERROR_SUCCESS == RegQueryValue(hkey, SZ_REGVALUE_BROWSEINPLACE, 0, NULL)))
|
||
|
{
|
||
|
fResult = TRUE;
|
||
|
}
|
||
|
|
||
|
RegCloseKey(hkey);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fResult;
|
||
|
}
|
||
|
|
||
|
|
||
|
ULONG FtpGetAttributesOf(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
ASSERT(IsValidPIDL(pidl));
|
||
|
|
||
|
DWORD dwAttributes = FtpPidl_GetAttributes(pidl); // Get File based attributes.
|
||
|
ULONG rgfInOut = Misc_SfgaoFromFileAttributes(dwAttributes); // Turn them into IShellFolder attributes.
|
||
|
return rgfInOut;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//===========================
|
||
|
// *** IShellFolder2 Interface ***
|
||
|
//===========================
|
||
|
|
||
|
STDAPI InitVariantFromBuffer(VARIANT *pvar, const void *pv, UINT cb)
|
||
|
{
|
||
|
HRESULT hres;
|
||
|
SAFEARRAY *psa = SafeArrayCreateVector(VT_UI1, 0, cb); // create a one-dimensional safe array
|
||
|
if (psa)
|
||
|
{
|
||
|
memcpy(psa->pvData, pv, cb);
|
||
|
|
||
|
memset(pvar, 0, sizeof(*pvar)); // VariantInit()
|
||
|
pvar->vt = VT_ARRAY | VT_UI1;
|
||
|
pvar->parray = psa;
|
||
|
hres = S_OK;
|
||
|
}
|
||
|
else
|
||
|
hres = E_OUTOFMEMORY;
|
||
|
return hres;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder2::GetDetailsEx
|
||
|
|
||
|
DESCRIPTION:
|
||
|
This function will be called when the caller wants detailed info about
|
||
|
and item. SHGetDataFromIDList() is one such caller and that is commonly
|
||
|
called by the Shell Object model when using CSDFldrItem::get_Size(LONG *pul)
|
||
|
and other such APIs.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv)
|
||
|
{
|
||
|
HRESULT hr = E_OUTOFMEMORY;
|
||
|
|
||
|
if (IsEqualGUID(pscid->fmtid, FMTID_ShellDetails) && (PID_FINDDATA == pscid->pid))
|
||
|
{
|
||
|
WIN32_FIND_DATAW wfd;
|
||
|
|
||
|
// I can handle this.
|
||
|
LPITEMIDLIST pidlFull = CreateFullPrivatePidl(pidl);
|
||
|
|
||
|
if (pidlFull)
|
||
|
{
|
||
|
hr = Win32FindDataFromPidl(pidlFull, &wfd, TRUE, TRUE);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = InitVariantFromBuffer(pv, (PVOID)&wfd, sizeof(wfd));
|
||
|
}
|
||
|
ILFree(pidlFull);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = CBaseFolder::GetDetailsEx(pidl, pscid, pv);
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//===========================
|
||
|
// *** IShellFolder Interface ***
|
||
|
//===========================
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder:: ParseDisplayName
|
||
|
|
||
|
DESCRIPTION:
|
||
|
The incoming name is %-encoded, but if we see an illegal %-sequence,
|
||
|
just leave the % alone.
|
||
|
|
||
|
For now, we disallow backslash, "*" and "?" from filenames.
|
||
|
Backslashes don't sit well with wininet, and wildcards
|
||
|
mess up the "quick FindFirst to see if the file exists".
|
||
|
|
||
|
We also disallow encoded slashes, because they mess up the way
|
||
|
we manage subpidls.
|
||
|
|
||
|
Annoying feature: You can't pass -1 as the output buffer size.
|
||
|
NLS returns ERROR_INVALID_PARAMETER if you try. So you have to pass
|
||
|
the actual size. Sigh.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::ParseDisplayName(HWND hwnd, LPBC pbcReserved, LPOLESTR pwszDisplayName,
|
||
|
ULONG * pchEaten, LPITEMIDLIST * ppidl, ULONG *pdwAttributes)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
*ppidl = NULL;
|
||
|
if (pchEaten)
|
||
|
*pchEaten = 0;
|
||
|
|
||
|
// PERF: log 2 (sizeof(m_pflHfpl))
|
||
|
hr = _GetCachedPidlFromDisplayName(pwszDisplayName, ppidl);
|
||
|
if (FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) != hr))
|
||
|
{
|
||
|
// Are we are rooted within an FTP Server?
|
||
|
if (IsRoot())
|
||
|
{
|
||
|
// No, so parse the entire thing
|
||
|
|
||
|
// There is only one case where we want to hide the password,
|
||
|
// and that is when the user entered it into the "Login"
|
||
|
// dialog. Since we entering it into the dialog will cause a
|
||
|
// redirect to an URL with that password in it, we need to determie
|
||
|
// if we are being called during this redirect. If so,
|
||
|
// the password just came from the Login dialog and we need to hide it.
|
||
|
|
||
|
// This will work for fully qualified Ftp URLs
|
||
|
hr = CreateFtpPidlFromUrl(pwszDisplayName, GetCWireEncoding(), pchEaten, ppidl, m_pm, FALSE);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
CFtpSite * pfs;
|
||
|
|
||
|
hr = SiteCache_PidlLookup(*ppidl, TRUE, m_pm, &pfs);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// If we are using a hidden password, then ::GetDisplayNameOf() hands out
|
||
|
// these "ftp://user@server/dir/" URLs and the password is hidden. If
|
||
|
// ::ParseDisplayName() is given one of these URLs and we are currently in
|
||
|
// that server w/that user name, then ::ParseDisplayNameOf() needs to hand
|
||
|
// out a pidl with the correct hidden password cookie.
|
||
|
//
|
||
|
// Is pidlNav the same as GetPublicPidlReference() except pidlNav doesn't
|
||
|
// have a password. The same means that the servers match, and the user names
|
||
|
// match.
|
||
|
EVAL(SUCCEEDED(pfs->UpdateHiddenPassword(*ppidl)));
|
||
|
pfs->Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Yes, so do a relative parse
|
||
|
|
||
|
// 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 is bad 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.
|
||
|
hr = _FilterBadInput(pwszDisplayName, ppidl);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
CFtpDir * pfd = GetFtpDir();
|
||
|
if (pfd)
|
||
|
hr = CreateFtpPidlFromDisplayPath(pwszDisplayName, pfd->GetFtpSite()->GetCWireEncoding(), pchEaten, ppidl, FALSE, FALSE);
|
||
|
else
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
|
||
|
pfd->Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED(hr) && pdwAttributes)
|
||
|
{
|
||
|
hr = GetAttributesOf(1, (LPCITEMIDLIST *) ppidl, pdwAttributes);
|
||
|
if (FAILED(hr))
|
||
|
ILFree(*ppidl);
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
TCHAR szUrlDebug[MAX_URL_STRING];
|
||
|
|
||
|
szUrlDebug[0] = 0;
|
||
|
if (*ppidl)
|
||
|
UrlCreateFromPidl(*ppidl, SHGDN_FORPARSING, szUrlDebug, ARRAYSIZE(szUrlDebug), ICU_USERNAME, FALSE);
|
||
|
|
||
|
TraceMsg(TF_FTPISF, "CFtpFolder::ParseDisplayName(%ls) CreateFtpPidlFromUrl() returned hres=%#08lx %ls", pwszDisplayName, hr, szUrlDebug);
|
||
|
ASSERT(FAILED(hr) || IsValidPIDL(*ppidl));
|
||
|
#endif // DEBUG
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppidl, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
IMalloc * CFtpFolder::GetIMalloc(void)
|
||
|
{
|
||
|
IMalloc * pm = NULL;
|
||
|
|
||
|
IUnknown_Set(&pm, m_pm);
|
||
|
ASSERT(pm);
|
||
|
return pm;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder::EnumObjects
|
||
|
|
||
|
DESCRIPTION:
|
||
|
Design subtlety: If we couldn't create an enumeration on the server,
|
||
|
succeed, but return an enumerator that shows no objects.
|
||
|
|
||
|
This is necessary so that our IShellView callback can put
|
||
|
up error UI. If we failed the create, the shell would
|
||
|
destroy the view without giving us a chance to say what's
|
||
|
up.
|
||
|
|
||
|
It's also important for write-only directories like /incoming,
|
||
|
so that the user can drag files into the directory without
|
||
|
necessarily being able to drag files out.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::EnumObjects(HWND hwndOwner, DWORD grfFlags, IEnumIDList ** ppenumIDList)
|
||
|
{
|
||
|
HRESULT hres = E_FAIL;
|
||
|
CFtpDir * pfd = GetFtpDir();
|
||
|
|
||
|
// This will happen if some TARD tries to just CoCreateInstance our
|
||
|
// Name Space extension and see what contents we have. TweakUI
|
||
|
// is an example of one such abuser. Since we can only populate
|
||
|
// our contents after we navigate to a FTP server, we are empty.
|
||
|
*ppenumIDList = NULL;
|
||
|
if (pfd)
|
||
|
{
|
||
|
// Create a new enumeration object for the caller.
|
||
|
ASSERT(m_pm);
|
||
|
hres = CFtpEidl_Create(pfd, this, hwndOwner, grfFlags, ppenumIDList);
|
||
|
TraceMsg(TF_FTPISF, "CFtpFolder::EnumObjects() CFtpEidl_Create() returned hres=%#08lx", hres);
|
||
|
|
||
|
pfd->Release();
|
||
|
}
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppenumIDList, hres);
|
||
|
return hres;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CFtpFolder::_NeedToFallBackRelative(LPCITEMIDLIST pidl, BOOL * pfDisplayProxyFallBackDlg)
|
||
|
{
|
||
|
LPITEMIDLIST pidlFull = CreateFullPrivatePidl(pidl);
|
||
|
BOOL fFallBack = FALSE;
|
||
|
|
||
|
if (pidlFull)
|
||
|
{
|
||
|
fFallBack = _NeedToFallBack(pidl, pfDisplayProxyFallBackDlg);
|
||
|
ILFree(pidlFull);
|
||
|
}
|
||
|
|
||
|
return fFallBack;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: _NeedToFallBack
|
||
|
|
||
|
DESCRIPTION:
|
||
|
We need to fall back to the old URLMON support in these cases:
|
||
|
#1 It's a file, we let the old code use URLMON to do the download.
|
||
|
#2 The app (WebOC host) has bugs that cause us to fail.
|
||
|
#3 The user turned off the New FTP UI. (For whatever reason)
|
||
|
#4 The proxy is a web proxy and allows URLMON but not WININET access,
|
||
|
so fall back to the old support.
|
||
|
#5 WININET doesn't support VMS servers, so we need to fall back in that case.
|
||
|
|
||
|
NOTE: The order is important because we always need to calc
|
||
|
fIsProxyBlockingFTP so we only display the fallback dlg
|
||
|
in the correct case.
|
||
|
\*****************************************************************************/
|
||
|
BOOL CFtpFolder::_NeedToFallBack(LPCITEMIDLIST pidlFull, BOOL * pfDisplayProxyFallBackDlg)
|
||
|
{
|
||
|
BOOL fNeedToFallBack = TRUE;
|
||
|
|
||
|
*pfDisplayProxyFallBackDlg = FALSE;
|
||
|
|
||
|
// TweakUI sends us an Empty pidls so don't fault. NT #396234.
|
||
|
if (pidlFull && !ILIsEmpty(pidlFull))
|
||
|
{
|
||
|
BOOL fIsDirectory;
|
||
|
|
||
|
if (IsFtpPidlQuestionable(pidlFull))
|
||
|
_FixQuestionablePidl(pidlFull);
|
||
|
|
||
|
fIsDirectory = (!FtpPidl_HasPath(pidlFull) || FtpPidl_IsDirectory(pidlFull, FALSE));
|
||
|
if (fIsDirectory) // #1
|
||
|
{
|
||
|
if (IsAppFTPCompatible()) // #2
|
||
|
{
|
||
|
if (!SHRegGetBoolUSValue(SZ_REGKEY_FTPFOLDER, SZ_REGKEY_USE_OLD_UI, FALSE, FALSE)) // #3
|
||
|
{
|
||
|
// The binding code passes us a bind context and that would be a good
|
||
|
// key to determine if were are about to navigate to the site. The
|
||
|
// problem is that we can't skip the proxy checking because we will
|
||
|
// fail later.
|
||
|
//
|
||
|
// #224285 is an example where navigating from ftp://ftp.microsoft.com/ to
|
||
|
// "www.microsoft.com" will cause CShellUrl to call :: BindToObject and then
|
||
|
// our IEnumIDList::Next() which will give an error message.
|
||
|
//
|
||
|
// Are we unable to get access to the server because there is
|
||
|
// a CERN type proxy blocking us?
|
||
|
// PERF: Only check for the proxy if we have a bind context because
|
||
|
// the only place we are called from to navigate is
|
||
|
// CDocObjectFolder:: BindToObject() and we are guaranteed that they
|
||
|
// pass it to us.
|
||
|
if (!_IsProxyBlockingSite(pidlFull)) // #4
|
||
|
{
|
||
|
// Is this a VMS Server? If yes, fallback
|
||
|
// to URLMON support because wininet doesn't work with this kind of server.
|
||
|
if (!_IsServerVMS(pidlFull))
|
||
|
{
|
||
|
// Only continue if the user didn't turn the new UI Off.
|
||
|
fNeedToFallBack = FALSE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
*pfDisplayProxyFallBackDlg = TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fNeedToFallBack;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder:: BindToObject
|
||
|
|
||
|
DESCRIPTION:
|
||
|
First thing we need to do, is see if we want to over ride the default
|
||
|
IE FTP support. If we do, we call otherwise, we just fallback to the old
|
||
|
support. We want the new UI if: a) it's a directory, b) the web proxy doesn't
|
||
|
block us, and c) the user didn't turn us off.
|
||
|
|
||
|
PERF/TODO:
|
||
|
OrderItem_GetSystemImageListIndexFromCache (\shell\lib\dpastuff.cpp)
|
||
|
uses riid=IShellFolder when trying to find out the icon. We don't want
|
||
|
to hit the net in that case, so force them to pass a pbc to indicate skipping
|
||
|
the net in that case.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::BindToObject(LPCITEMIDLIST pidl, IBindCtx * pbc, REFIID riid, LPVOID * ppvObj)
|
||
|
{
|
||
|
HRESULT hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Indicate we want the old functionality to kick in.
|
||
|
|
||
|
if (ppvObj)
|
||
|
*ppvObj = NULL;
|
||
|
|
||
|
if (!pidl || ILIsEmpty(pidl) || !_IsValidPidlParameter(pidl))
|
||
|
{
|
||
|
// Caller, are you smoking crack? What's the idea of passing
|
||
|
// an empty pidl. (Comdlg32 is known to do this)
|
||
|
hr = E_INVALIDARG;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
BOOL fDisplayProxyFallBackDlg = FALSE;
|
||
|
LPITEMIDLIST pidlFull = CreateFullPrivatePidl(pidl);
|
||
|
|
||
|
if (pidlFull)
|
||
|
{
|
||
|
// We need to handle it.
|
||
|
hr = _BindToObject(pidl, pidlFull, pbc, riid, ppvObj);
|
||
|
|
||
|
// Maybe we still need to handle it if
|
||
|
ASSERT(HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr);
|
||
|
ILFree(pidlFull);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder::BindToStorage
|
||
|
|
||
|
DESCRIPTION:
|
||
|
We need to implement this so the user can Open and Save files in
|
||
|
the standard Open Dialog and Save Dialog.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::BindToStorage(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, LPVOID * ppvObj)
|
||
|
{
|
||
|
HRESULT hr = E_INVALIDARG;
|
||
|
|
||
|
if (!EVAL(_IsValidPidlParameter(pidl)))
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
*ppvObj = 0;
|
||
|
if (EVAL(pidl))
|
||
|
{
|
||
|
IMoniker * pmk;
|
||
|
|
||
|
hr = _PidlToMoniker(pidl, &pmk);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = pmk->BindToStorage(pbc, NULL, riid, ppvObj);
|
||
|
pmk->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
|
||
|
TraceMsg(TF_FTPISF, "CFtpFolder::BindToStorage() hr=%#08lx", hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder::CompareIDs
|
||
|
|
||
|
DESCRIPTION:
|
||
|
ici - column on which to sort. Note! that we rely on the fact that
|
||
|
IShellFolders are uniform; we do not need to bind to the shell folder in
|
||
|
order to compare its sub-itemids.
|
||
|
|
||
|
_UNDOCUMENTED_: The documentation does not say whether or not
|
||
|
complex pidls can be received. In fact, they can.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::CompareIDs(LPARAM ici, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
|
||
|
{
|
||
|
ASSERT(IsValidPIDL(pidl1));
|
||
|
ASSERT(IsValidPIDL(pidl2));
|
||
|
|
||
|
return FtpItemID_CompareIDs(ici, pidl1, pidl2, FCMP_GROUPDIRS);
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CFtpFolder::_CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID * ppvObj)
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
CFtpDir * pfd = GetFtpDir();
|
||
|
|
||
|
*ppvObj = NULL; // Explorer relies on this
|
||
|
//TraceMsg(TF_FTPISF, "CFtpObj::CreateViewObject() ");
|
||
|
if (pfd)
|
||
|
{
|
||
|
if (IsEqualIID(riid, IID_IDropTarget))
|
||
|
{
|
||
|
// Don't create a drop target for the root FTP folder.
|
||
|
if (IsRoot())
|
||
|
hr = E_NOINTERFACE;
|
||
|
else
|
||
|
{
|
||
|
CFtpDrop * pfm;
|
||
|
hr = CFtpDrop_Create(this, hwndOwner, &pfm);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = pfm->QueryInterface(riid, ppvObj);
|
||
|
pfm->Release();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
hr = E_NOINTERFACE;
|
||
|
|
||
|
// TODO: IID_IShellDetails
|
||
|
pfd->Release();
|
||
|
}
|
||
|
else
|
||
|
hr = E_FAIL; // Can't do that yet - Never _Initialize'd
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
|
||
|
if (FAILED(hr))
|
||
|
hr = CBaseFolder::CreateViewObject(hwndOwner, riid, ppvObj);
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
IShellFolder * CFtpFolder::_GetLegacyShellFolder(void)
|
||
|
{
|
||
|
IShellFolder * psfLegacy = NULL;
|
||
|
|
||
|
// I assert that this succeeds because I need to make
|
||
|
// sure every install case has this CLSID publicly available.
|
||
|
if (SUCCEEDED(CoCreateInstance(CLSID_CDocObjectFolder, NULL, CLSCTX_INPROC_SERVER, IID_IShellFolder, (void **)&psfLegacy)))
|
||
|
{
|
||
|
LPITEMIDLIST pidl = GetPrivatePidlClone();
|
||
|
|
||
|
if (pidl && !ILIsEmpty(pidl))
|
||
|
{
|
||
|
LPITEMIDLIST pidlLast = (LPITEMIDLIST) ILGetLastID(pidl);
|
||
|
LPITEMIDLIST pidlLegacy;
|
||
|
|
||
|
if (!FtpID_IsServerItemID(pidlLast))
|
||
|
{
|
||
|
// NT #291513: We want to force the last item to always be marked as a file
|
||
|
// because then it will not have the trailing '/' in the URL when we
|
||
|
// pass it to URLMON. This way, we leave wether it's a file or dir
|
||
|
// ambigious for URLMON to figure out. This is because we can't
|
||
|
// disambiguate because the proxy blocks us but URLMON handles it
|
||
|
// correctly.
|
||
|
FtpPidl_SetFileItemType(pidlLast, FALSE);
|
||
|
FtpPidl_SetAttributes(pidl, FILE_ATTRIBUTE_NORMAL);
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED(_GetLegacyPidl(pidl, &pidlLegacy)))
|
||
|
{
|
||
|
if (FAILED(_InitLegacyShellFolder(psfLegacy, pidlLegacy)))
|
||
|
ATOMICRELEASE(psfLegacy);
|
||
|
|
||
|
ILFree(pidlLegacy);
|
||
|
}
|
||
|
|
||
|
ILFree(pidl);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return psfLegacy;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder:: CreateViewObject
|
||
|
|
||
|
DESCRIPTION:
|
||
|
_UNDOCUMENTED_: This entire method is not documented.
|
||
|
|
||
|
_UNDOCUMENTED_: It is not documented that you need to
|
||
|
provide an IDropTarget object if you want the view to
|
||
|
act as a drop target.
|
||
|
|
||
|
IDropTarget produces a droptarget for the folder itself.
|
||
|
|
||
|
_UNDOCUMENTED_: The IShellView interface is not documented.
|
||
|
|
||
|
IShellView produces a shell view for the folder itself.
|
||
|
|
||
|
_UNOBVIOUS_: Not obvious that this is how the shell gets
|
||
|
a context menu for the folder itself. (You might think it
|
||
|
comes from GetUIObjectOf...)
|
||
|
|
||
|
IContextMenu produces a context menu for the folder itself.
|
||
|
This is important for supporting things like New and Paste.
|
||
|
|
||
|
IShellDetails (undocumented) is the direct interface to
|
||
|
GetDetailsOf and ColumnClick, which is now obsolete, replaced
|
||
|
by the DVM_GETDETAILSOF and DVM_COLUMNCLICK notifications.
|
||
|
|
||
|
_UNDOCUMENTED_: SHCreateShellFolderViewEx is not documented.
|
||
|
|
||
|
Yes, it's annoying how some things are handled by CreateViewObject
|
||
|
and some things are handled by GetUIObjectOf(cpidl = 0), so we
|
||
|
keep having to forward the requests back and forth. Particularly
|
||
|
annoying because the shell actually comes through both ways.
|
||
|
|
||
|
For example, if the user drags something onto a folder,
|
||
|
it does a CreateViewObject(IDropTarget), because it might not
|
||
|
be able to bind to the parent to get the IDropTarget (if the
|
||
|
folder is the root of a namespace).
|
||
|
|
||
|
But if you drag an object onto a subfolder of a folder, the shell
|
||
|
asks for a GetUIObjectOf(pidl, IDropTarget) so it can talk to
|
||
|
the drop target of the subobject. It does this to allow the
|
||
|
shell folder to create a quick IDropTarget without necessarily
|
||
|
binding to the subobject first.
|
||
|
|
||
|
We don't do any such optimization, so GetUIObjectOf() simply
|
||
|
binds to the subfolder and uses CreateViewObject().
|
||
|
|
||
|
|
||
|
If the IShellFolder doesn't have an FtpSite (typically because it
|
||
|
has not been IPersistFolder::Initialize'd), then fail any attempt
|
||
|
to create a view object.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID * ppvObj)
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
BOOL fDisplayProxyFallBackDlg = TRUE;
|
||
|
|
||
|
// We don't need to wory about falling back with a few interfaces,
|
||
|
// IResolveShellLink is one of them. There may be others, and we should
|
||
|
// add them if they are used often or in common scenarios because of the
|
||
|
// HUGE PERF IMPACT of _NeedToFallBack() which checks the net for a proxy
|
||
|
// blocking us. TODO: Investigate if IID_IDropTarget, IID_IContextMenu can be added.
|
||
|
if (!IsEqualIID(riid, IID_IResolveShellLink) && !IsEqualIID(riid, IID_IShellDetails) &&
|
||
|
_NeedToFallBack(GetPrivatePidlReference(), &fDisplayProxyFallBackDlg))
|
||
|
{
|
||
|
IShellFolder * psfLegacy = _GetLegacyShellFolder();
|
||
|
|
||
|
// We only want to display the proxy blocking dialog when we are creating the view.
|
||
|
if (fDisplayProxyFallBackDlg && IsEqualIID(riid, IID_IShellView))
|
||
|
{
|
||
|
DisplayBlockingProxyDialog(GetPrivatePidlReference(), hwndOwner);
|
||
|
}
|
||
|
|
||
|
if (psfLegacy)
|
||
|
{
|
||
|
hr = psfLegacy->CreateViewObject(hwndOwner, riid, ppvObj);
|
||
|
psfLegacy->Release();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = _CreateViewObject(hwndOwner, riid, ppvObj);
|
||
|
}
|
||
|
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder::GetAttributesOf
|
||
|
|
||
|
DESCRIPTION:
|
||
|
If we are given cpidl = 0, then we are being asked for attributes
|
||
|
on the folder itself. But note that some people pass slightly
|
||
|
confused versions of cpidl = 0, as noted in the comment block below.
|
||
|
|
||
|
If the SFGAO_VALIDATE bit is set with cpidl = 0, then the view
|
||
|
object is warning us that it is about to refresh, so we should
|
||
|
throw away any cached information.
|
||
|
|
||
|
NOTE! ftpcm.cpp relies heavily on the fact that this routine will
|
||
|
fail when given complex pidls. (This allows ftpcm.cpp to assume
|
||
|
that all the pidls are directly in the affected folder.)
|
||
|
|
||
|
_UNDOCUMENTED_: The documentation does not say whether or not
|
||
|
complex pidls can be received. I don't know whether or not
|
||
|
they can, so I'll code defensively and watch out for them.
|
||
|
|
||
|
Does a server need to return SFGAO_HASSUBFOLDER? We don't currently
|
||
|
do that and it would be a lot of work and incure a huge perf hit
|
||
|
if we did.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::GetAttributesOf(UINT cpidl, LPCITEMIDLIST *apidl, ULONG *rgfInOut)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
DWORD dwMask = *rgfInOut;
|
||
|
|
||
|
// _UNDOCUMENTED_:
|
||
|
// Some people pass cpidl = 1 but ILIsEmpty(apidl[0]),
|
||
|
// intending to pass cpidl = 0. While we're being distracted
|
||
|
// by these sorts of people, may as well catch apidl[0] == 0 also...
|
||
|
// Oh, and defview sometimes passes cpidl = 1 but apidl == 0...
|
||
|
if (cpidl > 0 && apidl && apidl[0] && !ILIsEmpty(apidl[0]))
|
||
|
{
|
||
|
UINT ipidl;
|
||
|
|
||
|
// Can't multi-rename because there's nowhere to pass the new names.
|
||
|
// Can't multi-paste since you don't know where it really goes.
|
||
|
if (cpidl > 1)
|
||
|
{
|
||
|
*rgfInOut &= ~SFGAO_CANRENAME;
|
||
|
*rgfInOut &= ~SFGAO_DROPTARGET;
|
||
|
}
|
||
|
hr = S_OK;
|
||
|
for (ipidl = 0; ipidl < cpidl; ipidl++)
|
||
|
{
|
||
|
// This maybe a fully qualified pidl or relative pidl
|
||
|
LPITEMIDLIST pidlFull;
|
||
|
|
||
|
if (ILIsSimple(apidl[ipidl]))
|
||
|
pidlFull = CreateFullPrivatePidl(apidl[0]);
|
||
|
else
|
||
|
pidlFull = (LPITEMIDLIST) apidl[0];
|
||
|
|
||
|
*rgfInOut &= FtpGetAttributesOf(pidlFull);
|
||
|
|
||
|
// BUG NT #166783: shell32 v3 & v4 (Win95 & NT4 orig) won't allow you
|
||
|
// to allow SFGAO_CANLINK but disallow the link to be created in your
|
||
|
// own folder. So we need to disable this item in browser only.
|
||
|
if (SHELL_VERSION_NT5 != GetShellVersion())
|
||
|
*rgfInOut &= ~SFGAO_CANLINK;
|
||
|
|
||
|
if (apidl[0] != pidlFull)
|
||
|
ILFree(pidlFull); // We alloced it so we free it.
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// At top-level, SFGAO_DROPTARGET is also disabled
|
||
|
if (IsRoot())
|
||
|
*rgfInOut &= ~SFGAO_DROPTARGET;
|
||
|
|
||
|
*rgfInOut &= ~(SFGAO_GHOSTED | SFGAO_LINK | SFGAO_READONLY |
|
||
|
SFGAO_SHARE | SFGAO_REMOVABLE);
|
||
|
|
||
|
if (*rgfInOut & SFGAO_VALIDATE)
|
||
|
InvalidateCache(); // About to refresh...
|
||
|
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
|
||
|
// TraceMsg(TF_FTPISF, "CFtpFolder::GetAttributesOf() *rgfInOut=%#08lx, hr=%#08lx", *rgfInOut, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
DESCRIPTION:
|
||
|
Creates an pflHfpl and asks CFtpFolder_GetUIObjectOfHfpl (qv)
|
||
|
to do the real work.
|
||
|
|
||
|
Note that we always release the pflHfpl. If GetUIObjectOfHfpl
|
||
|
needs to keep the pflHfpl, it will do its own AddRef().
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, LPCITEMIDLIST rgpidl[],
|
||
|
REFIID riid, UINT * prgfInOut, LPVOID * ppvObj)
|
||
|
{
|
||
|
return _GetUIObjectOf(hwndOwner, cidl, rgpidl, riid, prgfInOut, ppvObj, FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
DESCRIPTION:
|
||
|
Creates an pflHfpl and asks CFtpFolder_GetUIObjectOfHfpl (qv)
|
||
|
to do the real work.
|
||
|
|
||
|
Note that we always release the pflHfpl. If GetUIObjectOfHfpl
|
||
|
needs to keep the pflHfpl, it will do its own AddRef().
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::_GetUIObjectOf(HWND hwndOwner, UINT cidl, LPCITEMIDLIST rgpidl[],
|
||
|
REFIID riid, UINT * prgfInOut, LPVOID * ppvObj, BOOL fFromCreateViewObject)
|
||
|
{
|
||
|
CFtpPidlList * pflHfpl = NULL;
|
||
|
HRESULT hr = E_FAIL;
|
||
|
LPITEMIDLIST pidlFull;
|
||
|
|
||
|
if (rgpidl)
|
||
|
pidlFull = CreateFullPrivatePidl(rgpidl[0]);
|
||
|
else
|
||
|
pidlFull = GetPrivatePidlClone();
|
||
|
|
||
|
if (ppvObj)
|
||
|
*ppvObj = NULL;
|
||
|
|
||
|
// Is the proxy blocking us? If yes, don't do anything
|
||
|
// because we don't want our Context Menu to appear for the
|
||
|
// original FTP UI.
|
||
|
// It's not blocking so go ahead.
|
||
|
hr = CFtpPidlList_Create(cidl, rgpidl, &pflHfpl);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
_InitFtpSite(); // GetUIObjectOfHfpl() will later need m_pfs.
|
||
|
hr = GetUIObjectOfHfpl(hwndOwner, pflHfpl, riid, ppvObj, fFromCreateViewObject);
|
||
|
pflHfpl->Release();
|
||
|
}
|
||
|
|
||
|
if (pidlFull)
|
||
|
ILFree(pidlFull);
|
||
|
|
||
|
// TraceMsg(TF_FTPISF, "CFtpFolder::GetUIObjectOf() hres=%#08lx", hr);
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder::GetDisplayNameOf
|
||
|
|
||
|
DESCRIPTION:
|
||
|
Note! that since we do not support junctions (duh), we can
|
||
|
safely walk down the pidl generating goop as we go, secure
|
||
|
in the knowledge that we are in charge of every subpidl.
|
||
|
|
||
|
FTP UNICODE ISSUE:
|
||
|
The FTP spec (RFC 959 (?)) says that FTP uses 8-bit BYTEs as
|
||
|
names. If the 8th bit is zero, these are treated as ANSI.
|
||
|
But it's not specified what the 8th bit means when it's set?
|
||
|
Some FTP clients have been pushing DBCS/MBCS up using the 8th bit
|
||
|
but this incurs data loss because the code page is lost and cross
|
||
|
code page strings are not supported. For that reason, a combination
|
||
|
of UTF-8 (by default) should be used and fall back to DBCS with
|
||
|
code page guessing (maybe need UI to guess code page).
|
||
|
|
||
|
We need to use WININET BYTE APIs (BYTE means ANSI with an ambiguous 8th bit).
|
||
|
We then need to store those bytes in our cache (CFtpDir). When we display
|
||
|
these strings in UI, we need to convert them to unicode and guess weather
|
||
|
it's UTF-8 or DBCS encoded.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD shgno, LPSTRRET pStrRet)
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
|
||
|
// It's invalid for someone to pass us an empty pidl, but some do.
|
||
|
// (comdlg32\GetPathFromLocation) Reject it now.
|
||
|
if (ILIsEmpty(pidl) || !EVAL(_IsValidPidlParameter(pidl)))
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
LPITEMIDLIST pidlFull = CreateFullPrivatePidl(pidl);
|
||
|
if (pidlFull)
|
||
|
{
|
||
|
ASSERT(IsValidPIDL(pidlFull));
|
||
|
|
||
|
hr = StrRetFromFtpPidl(pStrRet, shgno, pidlFull);
|
||
|
ILFree(pidlFull);
|
||
|
}
|
||
|
|
||
|
// TraceMsg(TF_FTPISF, "CFtpFolder::GetDisplayNameOf() szName=%hs, hres=%#08lx", pStrRet->cStr, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellFolder::SetNameOf
|
||
|
|
||
|
DESCRIPTION:
|
||
|
The real work is done by SetNameOf.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::SetNameOf(HWND hwndOwner, LPCITEMIDLIST pidl, LPCOLESTR pwszName,
|
||
|
DWORD dwReserved, LPITEMIDLIST *ppidlOut)
|
||
|
{
|
||
|
HRESULT hr = S_FALSE;
|
||
|
TCHAR szPath[MAX_PATH];
|
||
|
BOOL fContinueToRename = TRUE;
|
||
|
|
||
|
if (!EVAL(_IsValidPidlParameter(pidl)))
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
CFtpDir * pfd = GetFtpDir();
|
||
|
if (!pfd)
|
||
|
{
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
ASSERT(IsValidPIDL(pidl));
|
||
|
|
||
|
SHUnicodeToTChar(pwszName, szPath, ARRAYSIZE(szPath));
|
||
|
// You can not rename a folder or file to have spaces at the beginning or end. This
|
||
|
// is because the path is sent over the wire as "RENAME foobar.txt foobar2.txt"
|
||
|
// so note that the spaces are ambiguis so the server will ignore spaces before or after the
|
||
|
// file. If the caller has spaces before or after the path, remove them. Spaces in
|
||
|
// the middle are acceptable.
|
||
|
PathRemoveBlanks(szPath);
|
||
|
|
||
|
// Does the new item not have an extension and this isn't a directory?
|
||
|
if ((0 == PathFindExtension(szPath)[0]) && !FtpPidl_IsDirectory(pidl, FALSE))
|
||
|
{
|
||
|
LPCWIRESTR pszFileName = FtpPidl_GetLastItemWireName(pidl);
|
||
|
// Yes, then we are scared that they may be loosing an extension.
|
||
|
|
||
|
// Did the original name have an extension?
|
||
|
if (pszFileName && PathFindExtensionA(pszFileName)[0])
|
||
|
{
|
||
|
// Yes, so now we are scared they may loose it and not be able
|
||
|
// to find the src app. Ask the user if they really want to do this
|
||
|
// rename if that will mean the file will no longer have an extension.
|
||
|
|
||
|
// Hey browser, can I display UI?
|
||
|
if (EVAL(hwndOwner))
|
||
|
{
|
||
|
// Hay browser, cover me, I'm going to do UI.
|
||
|
IUnknown_EnableModless(_punkSite, FALSE);
|
||
|
|
||
|
TCHAR szTitle[MAX_PATH];
|
||
|
TCHAR szReplaceMsg[MAX_PATH*4];
|
||
|
|
||
|
EVAL(LoadString(HINST_THISDLL, IDS_FTPERR_RENAME_TITLE, szTitle, ARRAYSIZE(szTitle)));
|
||
|
EVAL(LoadString(HINST_THISDLL, IDS_FTPERR_RENAME_EXT_WRN, szReplaceMsg, ARRAYSIZE(szReplaceMsg)));
|
||
|
if (IDNO == MessageBox(hwndOwner, szReplaceMsg, szTitle, (MB_YESNO | MB_ICONEXCLAMATION)))
|
||
|
fContinueToRename = FALSE; // Cancel the rename.
|
||
|
|
||
|
IUnknown_EnableModless(_punkSite, TRUE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fContinueToRename)
|
||
|
{
|
||
|
if (pfd)
|
||
|
{
|
||
|
hr = pfd->SetNameOf(this, hwndOwner, pidl, szPath, dwReserved, ppidlOut);
|
||
|
if (FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr) && hwndOwner)
|
||
|
{
|
||
|
DisplayWininetError(hwndOwner, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_FILERENAME, IDS_FTPERR_WININET, MB_OK, NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TraceMsg(TF_FTPISF, "CFtpFolder::SetNameOf(%ls) hres=%#08lx", pwszName, hr);
|
||
|
// shell32.dll in IE4 (maybe earlier also) will infinitely call
|
||
|
// CFtpFolder::SetNameOf() over and over if it returns FAILED(hr);
|
||
|
if (FAILED(hr))
|
||
|
hr = S_FALSE;
|
||
|
}
|
||
|
|
||
|
if (ppidlOut)
|
||
|
{
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppidlOut, hr);
|
||
|
}
|
||
|
|
||
|
if (pfd)
|
||
|
{
|
||
|
pfd->Release();
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
//===========================
|
||
|
// *** IPersistFolder Interface ***
|
||
|
//===========================
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IPersistFolder::Initialize
|
||
|
|
||
|
DESCRIPTION:
|
||
|
This is called when the shell creates a new "root".
|
||
|
|
||
|
Note that we pass a fake "null" ID list as the second
|
||
|
pidl to ::_Initialize, q.v., for explanation.
|
||
|
|
||
|
_UNDOCUMENTED_: Undocumented method in undocumented interface.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::Initialize(LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
ASSERT(IsValidPIDL(pidl));
|
||
|
|
||
|
HRESULT hr = _Initialize(pidl, NULL, ILGetSize(pidl) - sizeof(pidl->mkid.cb));
|
||
|
//TraceMsg(TF_FTPISF, "CFtpFolder::Initialize() hres=%#08lx", hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
//===========================
|
||
|
// *** IShellIcon Interface ***
|
||
|
//===========================
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IShellIcon::GetIconOf
|
||
|
|
||
|
DESCRIPTION:
|
||
|
Get the system icon imagelist index for a pidl. Subtlety - If we are
|
||
|
enumerating children of the root, their icon is a computer.
|
||
|
|
||
|
_UNDOCUMENTED_: Undocumented method in undocumented interface.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::GetIconOf(LPCITEMIDLIST pidl, UINT gil, LPINT pnIcon)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if (!EVAL(_IsValidPidlParameter(pidl)))
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
// If we want custom icons, make that change here. We could want to do
|
||
|
// that if we wanted folder icons from FTP sites to look different than
|
||
|
// folder icons from the file system. But we don't currently want that.
|
||
|
*pnIcon = GetFtpIcon(gil, IsRoot());
|
||
|
|
||
|
ASSERT(IsValidPIDL(pidl));
|
||
|
if (EVAL(!IsRoot())) // GetFtpIcon() is wrong so either fix it or verify we never try to use it.
|
||
|
{
|
||
|
SHFILEINFO sfi;
|
||
|
hr = FtpPidl_GetFileInfo(pidl, &sfi, SHGFI_SYSICONINDEX |
|
||
|
((gil & GIL_OPENICON) ? SHGFI_OPENICON : 0));
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
*pnIcon = sfi.iIcon;
|
||
|
if (sfi.hIcon)
|
||
|
DestroyIcon(sfi.hIcon);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TraceMsg(TF_FTPISF, "CFtpFolder::GetIconOf() hres=%#08lx", hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//===========================
|
||
|
// *** IShellIconOverlay Interface ***
|
||
|
//===========================
|
||
|
HRESULT CFtpFolder::GetOverlayIndexHelper(LPCITEMIDLIST pidl, int * pIndex, DWORD dwFlags)
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
|
||
|
*pIndex = 0;
|
||
|
// Is this a soft link? (Meaning it won't have a windows link
|
||
|
// extension (.lnk, .url, ...) but we still want the shortcut cue.
|
||
|
if (pidl && FtpPidl_IsSoftLink(pidl))
|
||
|
{
|
||
|
if (!m_psiom)
|
||
|
{
|
||
|
hr = CoCreateInstance(CLSID_CFSIconOverlayManager, NULL, CLSCTX_INPROC_SERVER, IID_IShellIconOverlayManager, (void **)&m_psiom);
|
||
|
}
|
||
|
|
||
|
if (m_psiom)
|
||
|
{
|
||
|
hr = m_psiom->GetReservedOverlayInfo(L"", FtpPidl_GetAttributes(pidl), pIndex, dwFlags, SIOM_RESERVED_LINK);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
//===========================
|
||
|
// *** IDelegateFolder Interface ***
|
||
|
//===========================
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: IDelegateFolder::SetItemAlloc
|
||
|
|
||
|
DESCRIPTION:
|
||
|
Gives us the pidl allocator.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::SetItemAlloc(IMalloc *pm)
|
||
|
{
|
||
|
IUnknown_Set(&m_pm, pm);
|
||
|
|
||
|
// TraceMsg(TF_FTPISF, "CFtpFolder::SetItemAlloc(IMalloc *pm=%#08lx) hres=%#08lx", pm, S_OK);
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
//===========================
|
||
|
// *** IBrowserFrameOptions Interface ***
|
||
|
//===========================
|
||
|
/*****************************************************************************\
|
||
|
DESCRIPTION:
|
||
|
Tell the browser/host what behaviors we want. This lets the caller
|
||
|
know when we want to act like the shell, the browser, or even unique.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder::GetFrameOptions(IN BROWSERFRAMEOPTIONS dwMask, OUT BROWSERFRAMEOPTIONS * pdwOptions)
|
||
|
{
|
||
|
// This function is called in the follow situations:
|
||
|
// ftp://bryanst/ (w/ & w/o folder shortcut)
|
||
|
// ftp://bryanst/default.htm (w/ & w/o folder shortcut)
|
||
|
// ftp://bryanst/notes.txt (w/ & w/o folder shortcut)
|
||
|
// ftp://bryanst/resume.doc (w/ & w/o folder shortcut)
|
||
|
// ftp://bryanst/ (w/ & w/o folder shortcut)
|
||
|
// ftp://bryanst/ (w/ & w/o folder shortcut)
|
||
|
// ftp://bryanst/ (w/ & w/o folder shortcut)
|
||
|
HRESULT hr = E_INVALIDARG;
|
||
|
|
||
|
if (pdwOptions)
|
||
|
{
|
||
|
// We want both "Internet Options" and "Folder Options".
|
||
|
*pdwOptions = dwMask & (BFO_BOTH_OPTIONS | BFO_BROWSE_NO_IN_NEW_PROCESS |
|
||
|
BFO_NO_REOPEN_NEXT_RESTART |
|
||
|
BFO_ENABLE_HYPERLINK_TRACKING | BFO_USE_IE_LOGOBANDING |
|
||
|
BFO_ADD_IE_TOCAPTIONBAR | BFO_USE_DIALUP_REF);
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
FUNCTION: CFtpFolder_Create
|
||
|
|
||
|
DESCRIPTION:
|
||
|
Note that we release the pff that Common_New created, because we
|
||
|
are done with it. The real refcount is handled by the
|
||
|
CFtpFolder_QueryInterface.
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder_Create(REFIID riid, LPVOID * ppvObj)
|
||
|
{
|
||
|
HRESULT hres = E_OUTOFMEMORY;
|
||
|
CFtpFolder * pff = new CFtpFolder();
|
||
|
|
||
|
*ppvObj = NULL;
|
||
|
if (pff)
|
||
|
{
|
||
|
hres = pff->QueryInterface(riid, ppvObj);
|
||
|
pff->Release();
|
||
|
}
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hres);
|
||
|
return hres;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*****************************************************************************\
|
||
|
DESCRIPTION:
|
||
|
\*****************************************************************************/
|
||
|
HRESULT CFtpFolder_Create(LPCITEMIDLIST pidlTarget, LPCITEMIDLIST pidlRoot, int ib, REFIID riid, LPVOID * ppvObj)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
CFtpFolder * pff = new CFtpFolder();
|
||
|
|
||
|
ASSERT(IsValidPIDL(pidlTarget));
|
||
|
ASSERT(!pidlRoot || IsValidPIDL(pidlRoot));
|
||
|
|
||
|
*ppvObj = NULL;
|
||
|
if (!pff)
|
||
|
{
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = pff->_Initialize(pidlTarget, pidlRoot, ib); // Can fail in out of memory
|
||
|
if (SUCCEEDED(hr))
|
||
|
hr = pff->QueryInterface(riid, ppvObj);
|
||
|
|
||
|
pff->Release();
|
||
|
}
|
||
|
|
||
|
ASSERT_POINTER_MATCHES_HRESULT(*ppvObj, hr);
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************\
|
||
|
Constructor
|
||
|
\****************************************************/
|
||
|
CFtpFolder::CFtpFolder() : CBaseFolder((LPCLSID) &CLSID_FtpFolder)
|
||
|
{
|
||
|
DllAddRef();
|
||
|
|
||
|
// This needs to be allocated in Zero Inited Memory.
|
||
|
// Assert that all Member Variables are inited to Zero.
|
||
|
ASSERT(!m_pfs);
|
||
|
ASSERT(!m_pm);
|
||
|
ASSERT(!m_puhs);
|
||
|
ASSERT(!m_psiom);
|
||
|
|
||
|
// Needed because we need to call CoCreateInstance() on Browser Only.
|
||
|
LEAK_ADDREF(LEAK_CFtpFolder);
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************\
|
||
|
Destructor
|
||
|
\****************************************************/
|
||
|
CFtpFolder::~CFtpFolder()
|
||
|
{
|
||
|
ATOMICRELEASE(m_pfs);
|
||
|
ATOMICRELEASE(m_pm);
|
||
|
ATOMICRELEASE(m_puhs);
|
||
|
ATOMICRELEASE(m_psiom);
|
||
|
|
||
|
if (m_hinstInetCpl)
|
||
|
FreeLibrary(m_hinstInetCpl);
|
||
|
|
||
|
DllRelease();
|
||
|
LEAK_DELREF(LEAK_CFtpFolder);
|
||
|
}
|
||
|
|
||
|
|
||
|
//===========================
|
||
|
// *** IUnknown Interface ***
|
||
|
//===========================
|
||
|
|
||
|
HRESULT CFtpFolder::QueryInterface(REFIID riid, void **ppvObj)
|
||
|
{
|
||
|
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IShellIcon))
|
||
|
{
|
||
|
*ppvObj = SAFECAST(this, IShellIcon*);
|
||
|
}
|
||
|
// This causes all icons to use my custom ftp folder icon, so I will do this when that is fixed.
|
||
|
#ifdef FEATURE_SOFTLINK_SHORTCUT_ICONOVERLAY
|
||
|
else if (IsEqualIID(riid, IID_IShellIconOverlay))
|
||
|
{
|
||
|
*ppvObj = SAFECAST(this, IShellIconOverlay*);
|
||
|
}
|
||
|
#endif // FEATURE_SOFTLINK_SHORTCUT_ICONOVERLAY
|
||
|
else if (IsEqualIID(riid, IID_IPersistFolder))
|
||
|
{
|
||
|
*ppvObj = SAFECAST(this, IPersistFolder*);
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IDelegateFolder))
|
||
|
{
|
||
|
*ppvObj = SAFECAST(this, IDelegateFolder*);
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IObjectWithSite))
|
||
|
{
|
||
|
*ppvObj = SAFECAST(this, IObjectWithSite*);
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IPersistFolder2))
|
||
|
{
|
||
|
*ppvObj = SAFECAST(this, IPersistFolder2*);
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IShellPropSheetExt))
|
||
|
{
|
||
|
*ppvObj = SAFECAST(this, IShellPropSheetExt*);
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_IBrowserFrameOptions))
|
||
|
{
|
||
|
*ppvObj = SAFECAST(this, IBrowserFrameOptions*);
|
||
|
}
|
||
|
else if (IsEqualIID(riid, IID_CFtpFolder))
|
||
|
{
|
||
|
// Only valid if caller lives in msieftp.dll
|
||
|
*ppvObj = (void *)this;
|
||
|
}
|
||
|
else
|
||
|
return CBaseFolder::QueryInterface(riid, ppvObj);
|
||
|
|
||
|
AddRef();
|
||
|
return S_OK;
|
||
|
}
|