windows-nt/Source/XPSP1/NT/shell/ext/ftp/ftpeidl.cpp
2020-09-26 16:20:57 +08:00

648 lines
21 KiB
C++

/*****************************************************************************
*
* ftpeidl.cpp - IEnumIDList interface
*
* FtpNameCache
*
* Enumerating an FTP site is an expensive operation, because
* it can entail dialing the phone, connecting to an ISP, then
* connecting to the site, logging in, cd'ing to the appropriate
* location, pumping over an "ls" command, parsing the result,
* then closing the connection.
*
* So we cache the results of an enumeration inside a pidl list.
* If the user does a REFRESH, then we toss the list and create
* a new one.
*
* NOTE! that the WinINet API does not allow a FindFirst to be
* interrupted. In other words, once you do an FtpFindFirst,
* you must read the directory to completion and close the
* handle before you can do anything else to the site.
*
* As a result, we cannot use lazy evaluation on the enumerated
* contents. (Not that it helps any, because WinINet will just
* do an "ls", parse the output, and then hand the items back
* one element at a time via FtpFindNext. You may as well retrieve
* them all the moment they're ready.)
*
\*****************************************************************************/
#include "priv.h"
#include "ftpeidl.h"
#include "view.h"
#include "util.h"
/*****************************************************************************
*
* We actually cache the result of the enumeration in the parent
* FtpDir, because FTP enumeration is very expensive.
*
* Since DVM_REFRESH forces us to re-enumerate, but we might have
* outstanding IEnumIDList's, we need to treat the object cache
* as yet another object that needs to be refcounted.
*
*****************************************************************************/
/*****************************************************************************
* _fFilter
*
* Decides whether the file attributes agree with the filter criteria.
*
* If hiddens are excluded, then exclude hiddens. (Duh.)
*
* Else, include or exclude based on folder/nonfolder-ness.
*
* Let's look at that expression in slow motion.
*
* "The attributes pass the filter if both...
* (1) it passes the INCLUDEHIDDEN criterion, and
* (2) it passes the FOLDERS/NONFOLDERS criterion.
*
* The INCLUDEHIDDEN criterion is passed if FILE_ATTRIBUTE_HIDDEN
* implies SHCONTF_INCLUDEHIDDEN.
*
* The FOLDERS/NONFOLDERS criterion is passed if the appropriate bit
* is set in the shcontf, based on the actual type of the file."
*****************************************************************************/
BOOL CFtpEidl::_fFilter(DWORD shcontf, DWORD dwFAFLFlags)
{
BOOL fResult = FALSE;
if (shcontf & SHCONTF_FOLDERS)
fResult |= dwFAFLFlags & FILE_ATTRIBUTE_DIRECTORY;
if (shcontf & SHCONTF_NONFOLDERS)
fResult |= !(dwFAFLFlags & FILE_ATTRIBUTE_DIRECTORY);
if ((dwFAFLFlags & FILE_ATTRIBUTE_HIDDEN) && !(shcontf & SHCONTF_INCLUDEHIDDEN))
fResult = FALSE;
return fResult;
}
/*****************************************************************************\
* _AddFindDataToPidlList
*
* Add information in a WIN32_FIND_DATA to the cache.
* Except that dot and dotdot don't go in.
\*****************************************************************************/
HRESULT CFtpEidl::_AddFindDataToPidlList(LPCITEMIDLIST pidl)
{
HRESULT hr = E_FAIL;
if (EVAL(m_pflHfpl))
{
ASSERT(IsValidPIDL(pidl));
hr = m_pflHfpl->InsertSorted(pidl);
}
return hr;
}
/*****************************************************************************\
FUNCTION: _HandleSoftLinks
DESCRIPTION:
A softlink is a file on an UNIX server that reference another file or
directory. We can detect these by the fact that (pwfd->dwFileAttribes == 0).
If that is true, we have some work to do. First we find out if it's a file
or a directory by trying to ChangeCurrentWorking directories into it. If we
can we turn the dwFileAttributes from 0 to (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT).
If it's just a softlink to a file, then we change it to
(FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_REPARSE_POINT). We later use the
FILE_ATTRIBUTE_REPARSE_POINT attribute to put the shortcut overlay on it to
que the user.
RETURN VALUE:
HRESULT - If FAILED() is returned, the item will not be added to the
list view.
\*****************************************************************************/
HRESULT CFtpEidl::_HandleSoftLinks(HINTERNET hint, LPITEMIDLIST pidl, LPWIRESTR pwCurrentDir, DWORD cchSize)
{
HRESULT hr = S_OK;
// Is it a softlink? It just came in off the wire and wininet returns 0 (zero)
// for softlinks. This function will determine if it's a SoftLink to a file
// or a directory and then set FILE_ATTRIBUTE_REPARSE_POINT or
// (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT) respectively.
if (0 == FtpPidl_GetAttributes(pidl))
{
LPCWIRESTR pwWireFileName = FtpPidl_GetFileWireName(pidl);
// Yes, so I will need to attempt to CD into that directory to test if it's a directory.
// I need to get back because ".." won't work. I will cache the return so I don't keep
// getting it if there is a directory full of them.
// Did we get the current directory yet? This is the bread crums so I can
// find my way back.
if (!pwCurrentDir[0])
EVAL(SUCCEEDED(FtpGetCurrentDirectoryWrap(hint, TRUE, pwCurrentDir, cchSize)));
// Yes, so is it a directory?
if (SUCCEEDED(FtpSetCurrentDirectoryPidlWrap(hint, TRUE, pidl, FALSE, FALSE))) // Relative CD
{
// Does it have a virtual root?
if (m_pfd->GetFtpSite()->HasVirtualRoot())
{
LPCITEMIDLIST pidlVirtualRoot = m_pfd->GetFtpSite()->GetVirtualRootReference();
LPITEMIDLIST pidlSoftLinkDest = NULL;
CWireEncoding * pwe = m_pfd->GetFtpSite()->GetCWireEncoding();
// Yes, so we need to make sure this dir softlink doesn't point
// outside of the virtual root, or it would cause invalid FTP URLs.
// File SoftLinks are fine because the old FTP Code abuses FTP URLs.
// I'm just not ready to drop my morals just yet.
if (SUCCEEDED(FtpGetCurrentDirectoryPidlWrap(hint, TRUE, pwe, &pidlSoftLinkDest)))
{
if (!FtpItemID_IsParent(pidlVirtualRoot, pidlSoftLinkDest))
{
// This is a Softlink or HardLink to a directory outside of the virtual root.
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Skip this one.
}
ILFree(pidlSoftLinkDest);
}
}
// Return to where we came from.
//TraceMsg(TF_WININET_DEBUG, "_HandleSoftLinks FtpSetCurrentDirectory(%hs) worked", pwWireFileName);
EVAL(SUCCEEDED(FtpSetCurrentDirectoryWrap(hint, TRUE, pwCurrentDir))); // Absolute CD
FtpPidl_SetAttributes(pidl, (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT));
FtpPidl_SetFileItemType(pidl, TRUE);
}
else // No, it's one of those files w/o extensions.
{
TraceMsg(TF_WININET_DEBUG, "_HandleSoftLinks FtpSetCurrentDirectory(%s) failed", pwWireFileName);
FtpPidl_SetAttributes(pidl, (FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_REPARSE_POINT));
FtpPidl_SetFileItemType(pidl, FALSE);
}
}
return hr;
}
/*****************************************************************************\
* CFtpEidl::_PopulateItem
*
* Fill a cache with stuff.
*
* EEK! Some ftp servers (e.g., ftp.funet.fi) run with ls -F!
* This means that things get "*" appended to them if they are executable.
\*****************************************************************************/
HRESULT CFtpEidl::_PopulateItem(HINTERNET hint0, HINTPROCINFO * phpi)
{
HRESULT hr = S_OK;
HINTERNET hint;
LPITEMIDLIST pidl;
CMultiLanguageCache cmlc;
CWireEncoding * pwe = m_pfd->GetFtpSite()->GetCWireEncoding();
if (phpi->psb)
{
phpi->psb->SetStatusMessage(IDS_LS, NULL);
EVAL(SUCCEEDED(_SetStatusBarZone(phpi->psb, phpi->pfd->GetFtpSite())));
}
hr = FtpFindFirstFilePidlWrap(hint0, TRUE, &cmlc, pwe, NULL, &pidl,
(INTERNET_NO_CALLBACK | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RESYNCHRONIZE | INTERNET_FLAG_RELOAD), NULL, &hint);
if (hint)
{
WIRECHAR wCurrentDir[MAX_PATH]; // Used for _HandleSoftLinks().
wCurrentDir[0] = 0;
if (EVAL(m_pff))
{
// It would be better to CoCreateInstance the History object by using
// shell32!_SHCoCreateInstance() because it doesn't require COM.
// If any more bugs are found, see if it's exported in Win95 and use it.
if (FAILED(m_hrOleInited))
{
// Win95's background enum thread doesn't call CoInitialize() so this AddToUrlHistory will fail.
// We init it ourselves.
m_hrOleInited = SHCoInitialize();
}
m_pff->AddToUrlHistory(m_pfd->GetPidlReference());
}
//TraceMsg(TF_FTP_OTHER, "CFtpEidl::_PopulateItem() adding Name=%s", wCurrentDir);
if (pidl && SUCCEEDED(_HandleSoftLinks(hint0, pidl, wCurrentDir, ARRAYSIZE(wCurrentDir))))
hr = _AddFindDataToPidlList(pidl);
ILFree(pidl);
while (SUCCEEDED(hr))
{
hr = InternetFindNextFilePidlWrap(hint, TRUE, &cmlc, pwe, &pidl);
if (SUCCEEDED(hr))
{
//TraceMsg(TF_FTP_OTHER, "CFtpEidl::_PopulateItem() adding Name=%hs", FtpPidl_GetLastItemWireName(pidl));
// We may decide to not add it for some reasons.
if (SUCCEEDED(_HandleSoftLinks(hint0, pidl, wCurrentDir, ARRAYSIZE(wCurrentDir))))
hr = _AddFindDataToPidlList(pidl);
ILFree(pidl);
}
else
{
// We failed to get the next file.
if (HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES) != hr)
{
DisplayWininetError(phpi->hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_FOLDERENUM, IDS_FTPERR_WININET, MB_OK, NULL);
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Clean error to indicate we already displayed the error and don't need to do it later.
}
else
hr = S_OK; // That's fine if there aren't any more files to get
break; // We are done here.
}
}
EVAL(SUCCEEDED(pwe->ReSetCodePages(&cmlc, m_pflHfpl)));
InternetCloseHandle(hint);
}
else
{
// This will happen in two cases.
// 1. The folder is empty. (GetLastError() == ERROR_NO_MORE_FILES)
// 2. The user doesn't have enough access to view the folder. (GetLastError() == ERROR_INTERNET_EXTENDED_ERROR)
if (HRESULT_FROM_WIN32(ERROR_NO_MORE_FILES) != hr)
{
DisplayWininetError(phpi->hwnd, TRUE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_OPENFOLDER, IDS_FTPERR_WININET, MB_OK, NULL);
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); // Clean error to indicate we already displayed the error and don't need to do it later.
WININET_ASSERT(SUCCEEDED(hr));
}
else
hr = S_OK;
TraceMsg(TF_FTP_IDENUM, "CFtpEnum_New() - Can't opendir. hres=%#08lx.", hr);
}
if (phpi->psb)
phpi->psb->SetStatusMessage(IDS_EMPTY, NULL);
return hr;
}
/*****************************************************************************\
* CFtpEidl::_Init
\*****************************************************************************/
HRESULT CFtpEidl::_Init(void)
{
HRESULT hr = S_FALSE;
ASSERT(m_pfd);
IUnknown_Set(&m_pflHfpl, NULL);
m_pflHfpl = m_pfd->GetHfpl(); // Use cached copy if it exists.
if (m_pflHfpl)
{
// We will just use the previous copy because we already have the contents.
// TODO: Maybe we want to purge the results if a certain amount of time as ellapsed.
m_fInited = TRUE;
hr = S_OK;
}
else if (!m_pfd->GetFtpSite()->IsSiteBlockedByRatings(m_hwndOwner))
{
CFtpPidlList_Create(0, NULL, &m_pflHfpl);
if (m_pflHfpl)
{
CStatusBar * psb = GetCStatusBarFromDefViewSite(_punkSite);
ASSERT(!m_pfd->IsRoot());
//TraceMsg(TF_ALWAYS, "CFtpEidl::_Init() and enumerating");
hr = m_pfd->WithHint(psb, m_hwndOwner, CFtpEidl::_PopulateItemCB, this, _punkSite, m_pff);
if (SUCCEEDED(hr))
{
m_pfd->SetCache(m_pflHfpl);
m_fInited = TRUE;
hr = S_OK;
}
else
IUnknown_Set(&m_pflHfpl, NULL);
}
}
return hr;
}
/*****************************************************************************
* CFtpEidl::_NextOne
*****************************************************************************/
LPITEMIDLIST CFtpEidl::_NextOne(DWORD * pdwIndex)
{
LPITEMIDLIST pidl = NULL;
LPITEMIDLIST pidlResult = NULL;
if (m_pflHfpl)
{
while ((*pdwIndex < (DWORD) m_pflHfpl->GetCount()) && (pidl = m_pflHfpl->GetPidl(*pdwIndex)))
{
ASSERT(IsValidPIDL(pidl));
(*pdwIndex)++;
if (_fFilter(m_shcontf, FtpPidl_GetAttributes(pidl)))
{
pidlResult = ILClone(pidl);
break; // We don't need to search any more.
}
}
}
return pidlResult;
}
//===========================
// *** IEnumIDList Interface ***
//===========================
/*****************************************************************************
*
* IEnumIDList::Next
*
* Creates a brand new enumerator based on an existing one.
*
*
* OLE random documentation of the day: IEnumXXX::Next.
*
* rgelt - Receives an array of size celt (or larger).
*
* "Receives an array"? No, it doesn't receive an array.
* It *is* an array. The array receives *elements*.
*
* "Or larger"? Does this mean I can return more than the caller
* asked for? No, of course not, because the caller didn't allocate
* enough memory to hold that many return values.
*
* No semantics are assigned to the possibility of celt = 0.
* Since I am a mathematician, I treat it as vacuous success.
*
* pcelt is documented as an INOUT parameter, but no semantics
* are assigned to its input value.
*
* The dox don't say that you are allowed to return *pcelt < celt
* for reasons other than "no more elements", but the shell does
* it everywhere, so maybe it's legal...
*
*****************************************************************************/
HRESULT CFtpEidl::Next(ULONG celt, LPITEMIDLIST * rgelt, ULONG *pceltFetched)
{
HRESULT hr = S_OK;
LPITEMIDLIST pidl = NULL;
DWORD dwIndex;
// The shell on pre-NT5 enums us w/o ole initialized which causes problems
// when we call CoCreateInstance(). This happens in the thunking code
// of encode.cpp when thunking strings.
HRESULT hrOleInit = SHOleInitialize(0);
if (pceltFetched) // In case of failure.
{
*pceltFetched = 0;
}
if (m_fDead)
return E_FAIL;
if (!m_fInited)
{
hr = _Init();
if (FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr))
{
// Did we need to redirect because of a new password or username?
if (HRESULT_FROM_WIN32(ERROR_NETWORK_ACCESS_DENIED) == hr)
{
m_fDead = TRUE;
hr = E_FAIL;
}
else if (!m_fErrorDisplayed)
{
DisplayWininetError(m_hwndOwner, FALSE, HRESULT_CODE(hr), IDS_FTPERR_TITLE_ERROR, IDS_FTPERR_GETDIRLISTING, IDS_FTPERR_WININET, MB_OK, NULL);
m_fErrorDisplayed = TRUE;
}
}
}
if (S_OK == hr)
{
// Do they want more and do we have more to give?
for (dwIndex = 0; (dwIndex < celt) && (pidl = _NextOne(&m_nIndex)); dwIndex++)
rgelt[dwIndex] = pidl; // Yes, so give away...
if (pceltFetched)
*pceltFetched = dwIndex;
// Were we able to give any?
if (0 == dwIndex)
hr = S_FALSE;
}
SHOleUninitialize(hrOleInit);
return hr;
}
/*****************************************************************************
* IEnumIDList::Skip
*****************************************************************************/
HRESULT CFtpEidl::Skip(ULONG celt)
{
m_nIndex += celt;
return S_OK;
}
/*****************************************************************************
* IEnumIDList::Reset
*****************************************************************************/
HRESULT CFtpEidl::Reset(void)
{
m_fErrorDisplayed = FALSE;
if (!m_fInited)
_Init();
m_nIndex = 0;
return S_OK;
}
/*****************************************************************************\
* IEnumIDList::Clone
*
* Creates a brand new enumerator based on an existing one.
\*****************************************************************************/
HRESULT CFtpEidl::Clone(IEnumIDList **ppenum)
{
return CFtpEidl_Create(m_pfd, m_pff, m_hwndOwner, m_shcontf, m_nIndex, ppenum);
}
/*****************************************************************************\
* CFtpEidl_Create
*
* Creates a brand new enumerator based on an ftp site.
\*****************************************************************************/
HRESULT CFtpEidl_Create(CFtpDir * pfd, CFtpFolder * pff, HWND hwndOwner, DWORD shcontf, IEnumIDList ** ppenum)
{
CFtpEidl * pfe;
HRESULT hres = CFtpEidl_Create(pfd, pff, hwndOwner, shcontf, &pfe);
*ppenum = NULL;
if (pfe)
{
hres = pfe->QueryInterface(IID_IEnumIDList, (LPVOID *) ppenum);
pfe->Release();
}
return hres;
}
/*****************************************************************************
*
* CFtpEidl_Create
*
* Creates a brand new enumerator based on an ftp site.
*
*****************************************************************************/
HRESULT CFtpEidl_Create(CFtpDir * pfd, CFtpFolder * pff, HWND hwndOwner, DWORD shcontf, CFtpEidl ** ppfe)
{
CFtpEidl * pfe = new CFtpEidl();
HRESULT hr = E_OUTOFMEMORY;
ASSERT(pfd && pff && ppfe);
*ppfe = pfe;
if (pfe)
{
ATOMICRELEASE(pfe->m_pm);
pfe->m_pm = pff->GetIMalloc();
IUnknown_Set(&pfe->m_pff, pff);
IUnknown_Set(&pfe->m_pfd, pfd);
pfe->m_pflHfpl = pfd->GetHfpl();
pfe->m_shcontf = shcontf;
pfe->m_hwndOwner = hwndOwner;
}
return hr;
}
/*****************************************************************************\
* CFtpEidl_Create
*
* Creates a brand new enumerator based on an ftp site.
\*****************************************************************************/
HRESULT CFtpEidl_Create(CFtpDir * pfd, CFtpFolder * pff, HWND hwndOwner, DWORD shcontf, DWORD dwIndex, IEnumIDList ** ppenum)
{
CFtpEidl * pfe;
HRESULT hres = CFtpEidl_Create(pfd, pff, hwndOwner, shcontf, &pfe);
if (SUCCEEDED(hres))
{
pfe->m_nIndex = dwIndex;
hres = pfe->QueryInterface(IID_IEnumIDList, (LPVOID *) ppenum);
ASSERT(SUCCEEDED(hres));
pfe->Release();
}
return hres;
}
/****************************************************\
Constructor
\****************************************************/
CFtpEidl::CFtpEidl() : m_cRef(1)
{
DllAddRef();
// This needs to be allocated in Zero Inited Memory.
// Assert that all Member Variables are inited to Zero.
ASSERT(!m_fInited);
ASSERT(!m_nIndex);
ASSERT(!m_shcontf);
ASSERT(!m_pflHfpl);
ASSERT(!m_pfd);
ASSERT(!m_pm);
ASSERT(!m_hwndOwner);
ASSERT(!m_fInited);
ASSERT(!m_fDead);
m_hrOleInited = E_FAIL;
LEAK_ADDREF(LEAK_CFtpEidl);
}
/****************************************************\
Destructor
\****************************************************/
CFtpEidl::~CFtpEidl()
{
IUnknown_Set(&m_pflHfpl, NULL);
IUnknown_Set(&m_pm, NULL);
IUnknown_Set(&m_pfd, NULL);
IUnknown_Set(&m_pff, NULL);
DllRelease();
LEAK_DELREF(LEAK_CFtpEidl);
SHCoUninitialize(m_hrOleInited);
}
//===========================
// *** IUnknown Interface ***
//===========================
ULONG CFtpEidl::AddRef()
{
m_cRef++;
return m_cRef;
}
ULONG CFtpEidl::Release()
{
ASSERT(m_cRef > 0);
m_cRef--;
if (m_cRef > 0)
return m_cRef;
delete this;
return 0;
}
HRESULT CFtpEidl::QueryInterface(REFIID riid, void **ppvObj)
{
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IEnumIDList))
{
*ppvObj = SAFECAST(this, IEnumIDList*);
}
else if (IsEqualIID(riid, IID_IObjectWithSite))
{
*ppvObj = SAFECAST(this, IObjectWithSite*);
}
else
{
TraceMsg(TF_FTPQI, "CFtpEidl::QueryInterface() failed.");
*ppvObj = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}