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

628 lines
20 KiB
C++

/*****************************************************************************
*
* ftppl.cpp - FTP LPITEMIDLIST List object
*
*****************************************************************************/
#include "priv.h"
#include "ftppl.h"
#include "ftpurl.h"
typedef struct tagINETENUM
{
HINTERNET hint;
BOOL * pfValidhinst;
LPVOID pvData;
LPFNPROCESSITEMCB pfnProcessItemCB;
LPCITEMIDLIST pidlRoot;
HRESULT hr;
} INETENUM;
/*****************************************************************************\
FUNCTION: RecursiveEnum
DESCRIPTION:
This function will pack the parameters needed during the enum.
\*****************************************************************************/
HRESULT CFtpPidlList::RecursiveEnum(LPCITEMIDLIST pidlRoot, LPFNPROCESSITEMCB pfnProcessItemCB, HINTERNET hint, LPVOID pvData)
{
INETENUM inetEnum = {hint, NULL, pvData, pfnProcessItemCB, pidlRoot, S_OK};
Enum(RecursiveProcessPidl, (LPVOID) &inetEnum);
return inetEnum.hr;
}
// lParam can be: 0 == do a case sensitive search. 1 == do a case insensitive search.
int CFtpPidlList::ComparePidlName(LPVOID pvPidl1, LPVOID pvPidl2, LPARAM lParam)
{
DWORD dwFlags = FCMP_NORMAL;
if (lParam)
dwFlags |= FCMP_CASEINSENSE;
// return < 0 for pvPidl1 before pvPidl2.
// return == 0 for pvPidl1 equals pvPidl2.
// return > 0 for pvPidl1 after pvPidl2.
return FtpItemID_CompareIDsInt(COL_NAME, (LPCITEMIDLIST)pvPidl1, (LPCITEMIDLIST)pvPidl2, dwFlags);
}
HRESULT CFtpPidlList::InsertSorted(LPCITEMIDLIST pidl)
{
m_pfl->InsertSorted(ILClone(pidl), CFtpPidlList::ComparePidlName, FALSE /*Case Insensitive*/);
return S_OK;
};
int CFtpPidlList::FindPidlIndex(LPCITEMIDLIST pidlToFind, BOOL fCaseInsensitive)
{
return m_pfl->SortedSearch((LPVOID) pidlToFind, CFtpPidlList::ComparePidlName, (LPARAM)fCaseInsensitive, DPAS_SORTED);
}
LPITEMIDLIST CFtpPidlList::FindPidl(LPCITEMIDLIST pidlToFind, BOOL fCaseInsensitive)
{
LPITEMIDLIST pidlFound = NULL;
int nIndex = FindPidlIndex(pidlToFind, fCaseInsensitive);
if (-1 != nIndex)
{
pidlFound = ILClone(GetPidl(nIndex));
}
return pidlFound;
}
HRESULT CFtpPidlList::CompareAndDeletePidl(LPCITEMIDLIST pidlToDelete)
{
HRESULT hr = S_FALSE;
int nIndex = FindPidlIndex(pidlToDelete, FALSE /*Case Insensitive*/);
if (-1 != nIndex)
{
LPITEMIDLIST pidlCurrent = GetPidl((UINT)nIndex);
if (EVAL(pidlCurrent))
{
ASSERT(0 == FtpItemID_CompareIDsInt(COL_NAME, pidlCurrent, pidlToDelete, FCMP_NORMAL));
m_pfl->DeletePtrByIndex(nIndex);
ILFree(pidlCurrent); // Deallocate the memory
hr = S_OK; // Found and deleted.
}
}
return hr;
}
void CFtpPidlList::Delete(int nIndex)
{
LPITEMIDLIST pidlToDelete = GetPidl(nIndex);
ILFree(pidlToDelete); // Free the memory.
m_pfl->DeletePtrByIndex(nIndex);
}
HRESULT CFtpPidlList::ReplacePidl(LPCITEMIDLIST pidlSrc, LPCITEMIDLIST pidlDest)
{
HRESULT hr = S_FALSE;
int nIndex = FindPidlIndex(pidlSrc, FALSE);
if (-1 != nIndex)
{
LPITEMIDLIST pidlCurrent = GetPidl((UINT)nIndex);
if (EVAL(pidlCurrent))
{
ASSERT(0 == FtpItemID_CompareIDsInt(COL_NAME, pidlCurrent, pidlSrc, FCMP_NORMAL));
ILFree(pidlCurrent); // Deallocate the memory
m_pfl->DeletePtrByIndex(nIndex);
InsertSorted(pidlDest); // This function does the ILClone()
hr = S_OK; // Found and deleted.
}
}
return hr;
}
void CFtpPidlList::AssertSorted(void)
{
#ifdef DEBUG
// For perf reasons, we need to keep this list in order.
// This is mainly because parse display name looks thru
// the list, so we want that to be fast.
for (int nIndex = (GetCount() - 2); (nIndex >= 0); nIndex--)
{
LPITEMIDLIST pidl1 = GetPidl((UINT)nIndex);
LPITEMIDLIST pidl2 = GetPidl((UINT)nIndex + 1);
// Assert that pidl1 comes before pidl2.
if (!EVAL(0 >= FtpItemID_CompareIDsInt(COL_NAME, pidl1, pidl2, FCMP_NORMAL)))
{
TCHAR szPidl1[MAX_PATH];
TCHAR szPidl2[MAX_PATH];
if (FtpID_IsServerItemID(pidl1))
FtpPidl_GetServer(pidl1, szPidl1, ARRAYSIZE(szPidl1));
else
FtpPidl_GetDisplayName(pidl1, szPidl1, ARRAYSIZE(szPidl1));
if (FtpID_IsServerItemID(pidl2))
FtpPidl_GetServer(pidl2, szPidl2, ARRAYSIZE(szPidl2));
else
FtpPidl_GetDisplayName(pidl2, szPidl2, ARRAYSIZE(szPidl2));
TraceMsg(TF_ERROR, "CFtpPidlList::AssertSorted() '%s' & '%s' where found out of order", szPidl1, szPidl2);
}
// We do NOT need to free pidl1 or pidl2 because we get a pointer to someone else's copy.
}
#endif // DEBUG
}
void CFtpPidlList::TraceDump(LPCITEMIDLIST pidl, LPCTSTR pszCaller)
{
#ifdef DEBUG
/*
TCHAR szUrl[MAX_URL_STRING];
UrlCreateFromPidl(pidl, SHGDN_FORPARSING, szUrl, ARRAYSIZE(szUrl), ICU_USERNAME, FALSE);
TraceMsg(TF_PIDLLIST_DUMP, "CFtpPidlList::TraceDump() root is '%s', called from '%s'", szUrl, pszCaller);
// Let's look at the contents.
for (int nIndex = (GetCount() - 1); (nIndex >= 0); nIndex--)
{
LPITEMIDLIST pidlFull = ILCombine(pidl, GetPidl((UINT)nIndex));
if (pidlFull)
{
UrlCreateFromPidl(pidlFull, SHGDN_FORPARSING, szUrl, ARRAYSIZE(szUrl), ICU_USERNAME, FALSE);
TraceMsg(TF_PIDLLIST_DUMP, "CFtpPidlList::TraceDump() Index=%d, url=%s", nIndex, szUrl);
ILFree(pidlFull);
}
}
*/
#endif // DEBUG
}
void CFtpPidlList::UseCachedDirListings(BOOL fUseCachedDirListings)
{
// Normally we do two passes in the tree walker code. The first
// pass is to count up the time required to do the download. We
// normally force WININET to not use cached results because someone
// else could have changed the contents on the server.
// On the second pass, we normally do the work (upload, download, delete)
// and we want to use the cached results to get the perf advantage
// and the results shouldn't be more than a minute out of date.
if (fUseCachedDirListings)
m_dwInetFlags = INTERNET_NO_CALLBACK;
else
m_dwInetFlags = (INTERNET_NO_CALLBACK | INTERNET_FLAG_RESYNCHRONIZE | INTERNET_FLAG_RELOAD);
}
BOOL CFtpPidlList::AreAllFolders(void)
{
BOOL fAllFolder = TRUE;
for (int nIndex = (GetCount() - 1); fAllFolder && (nIndex >= 0); nIndex--)
{
LPITEMIDLIST pidl = GetPidl((UINT)nIndex);
if (EVAL(pidl))
fAllFolder = FtpPidl_IsDirectory(pidl, TRUE);
// We do NOT need to free pidl because we get a pointer to someone else's copy.
}
return fAllFolder;
}
BOOL CFtpPidlList::AreAllFiles(void)
{
BOOL fAllFiles = TRUE;
for (int nIndex = (GetCount() - 1); fAllFiles && (nIndex >= 0); nIndex--)
{
LPITEMIDLIST pidl = GetPidl((UINT)nIndex);
if (EVAL(pidl))
fAllFiles = !FtpPidl_IsDirectory(pidl, TRUE);
// We do NOT need to free pidl because we get a pointer to someone else's copy.
}
return fAllFiles;
}
/*****************************************************************************
*
* CFtpPidlList::_Fill
*
* Fill a list with an array.
*
* The elements in the array are copied rather than stolen.
*
*****************************************************************************/
HRESULT CFtpPidlList::_Fill(int cpidl, LPCITEMIDLIST rgpidl[])
{
HRESULT hres = S_OK;
for (int ipidl = 0; (ipidl < cpidl) && SUCCEEDED(hres); ipidl++)
{
ASSERT(IsValidPIDL(rgpidl[ipidl]));
hres = InsertSorted(rgpidl[ipidl]);
}
return hres;
}
/*****************************************************************************
*
* CFtpPidlList::GetPidlList
*
*****************************************************************************/
LPCITEMIDLIST * CFtpPidlList::GetPidlList(void)
{
LPITEMIDLIST * ppidl;
ppidl = (LPITEMIDLIST *) LocalAlloc(LPTR, sizeof(LPITEMIDLIST) * GetCount());
if (ppidl)
{
int nIndex;
for (nIndex = 0; nIndex < GetCount(); nIndex++)
{
// Later we can make this user ILClone() if we want to be able to wack on the
// pidl list while this list is being used.
ppidl[nIndex] = GetPidl(nIndex);
}
}
return (LPCITEMIDLIST *) ppidl;
}
/*****************************************************************************
*
* CFtpPidlList::FreePidlList
*
*****************************************************************************/
void CFtpPidlList::FreePidlList(LPCITEMIDLIST * ppidl)
{
LocalFree(ppidl);
}
/*****************************************************************************
*
* CFtpPidlList_Create
*
* Start up a new pv list, with a recommended initial size and other
* callback info.
*
*****************************************************************************/
HRESULT CFtpPidlList_Create(int cpidl, LPCITEMIDLIST rgpidl[], CFtpPidlList ** ppflpidl)
{
HRESULT hres = E_OUTOFMEMORY;
CFtpPidlList * pflpidl;
*ppflpidl = pflpidl = new CFtpPidlList();
if (pflpidl)
{
hres = pflpidl->_Fill(cpidl, rgpidl);
if (!EVAL(SUCCEEDED(hres)))
{
ASSERT(pflpidl->GetCount() == 0);
IUnknown_Set(ppflpidl, NULL);
}
}
return hres;
}
int CALLBACK PidlListDestroyCallback(LPVOID p, LPVOID pData)
{
ILFree((LPITEMIDLIST) p);
return 1;
}
/****************************************************\
Constructor
\****************************************************/
CFtpPidlList::CFtpPidlList() : m_cRef(1)
{
DllAddRef();
// This needs to be allocated in Zero Inited Memory.
// Assert that all Member Variables are inited to Zero.
ASSERT(!m_pfl);
CFtpList_Create(100, PidlListDestroyCallback, 100, &m_pfl);
ASSERT(m_pfl); // BUGBUG can fail in low memory
UseCachedDirListings(FALSE);
LEAK_ADDREF(LEAK_CFtpPidlList);
}
/****************************************************\
Destructor
\****************************************************/
CFtpPidlList::~CFtpPidlList()
{
AssertSorted();
if (m_pfl)
m_pfl->Release();
DllRelease();
LEAK_DELREF(LEAK_CFtpPidlList);
}
//===========================
// *** IUnknown Interface ***
//===========================
ULONG CFtpPidlList::AddRef()
{
m_cRef++;
return m_cRef;
}
ULONG CFtpPidlList::Release()
{
ASSERT(m_cRef > 0);
m_cRef--;
if (m_cRef > 0)
return m_cRef;
delete this;
return 0;
}
HRESULT CFtpPidlList::QueryInterface(REFIID riid, void **ppvObj)
{
if (IsEqualIID(riid, IID_IUnknown))
{
*ppvObj = SAFECAST(this, IUnknown *);
}
else
{
TraceMsg(TF_FTPQI, "CFtpPidlList::QueryInterface() failed.");
*ppvObj = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
////////////////////////////////////////////////////////////////////
// Pild List Enum Helpers
////////////////////////////////////////////////////////////////////
/*****************************************************************************\
FUNCTION: RecursiveProcessPidl
DESCRIPTION:
This function will will be called for each item in the initial Pidl List
(before the recursion occurs). This is a wrapper because the first list is
a list of pidls. The subsequent lists are of WIN32_FIND_DATA types.
\*****************************************************************************/
int RecursiveProcessPidl(LPVOID pvPidl, LPVOID pvInetEnum)
{
LPCITEMIDLIST pidl = (LPCITEMIDLIST) pvPidl;
INETENUM * pInetEnum = (INETENUM *) pvInetEnum;
LPITEMIDLIST pidlFull = ILCombine(pInetEnum->pidlRoot, pidl);
if (pidlFull)
{
pInetEnum->hr = pInetEnum->pfnProcessItemCB((LPVOID) pInetEnum->pfnProcessItemCB, pInetEnum->hint, pidlFull, pInetEnum->pfValidhinst, pInetEnum->pvData);
ILFree(pidlFull);
}
return (SUCCEEDED(pInetEnum->hr) ? TRUE : FALSE);
}
/*****************************************************************************\
FUNCTION: _EnumFolderPrep
DESCRIPTION:
This function will step into the pszDir directory and enum all of it's
contents. For each item, it will call the callback function provided (pfnProcessItemCB).
That callback function can then call EnumFolder() again (recursively) if
there is a subfolder.
NOTE:
This function needs to first find all the items and then in a second
loop call the callback function. This is because the WININET FTP APIs
only allow one enum to occur at a time, which may not happen if half way through
enuming one dir, a recursive call starts enuming a sub dir.
\*****************************************************************************/
HRESULT _EnumFolderPrep(HINTERNET hint, LPCITEMIDLIST pidlFull, CFtpPidlList * pPidlList, CWireEncoding * pwe, LPITEMIDLIST * ppidlCurrFtpPath)
{
HRESULT hr = S_OK;
// 1. Get Current Directory (To restore later).
hr = FtpGetCurrentDirectoryPidlWrap(hint, TRUE, pwe, ppidlCurrFtpPath);
if (SUCCEEDED(hr))
{
CMultiLanguageCache cmlc;
CWireEncoding we;
if (!pwe)
pwe = &we;
// It's important that this is a relative CD.
// 2. Change Directory Into the subdirectory.
hr = FtpSetCurrentDirectoryWrap(hint, TRUE, FtpPidl_GetLastItemWireName(pidlFull));
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlItem;
HINTERNET hInetFind = NULL;
hr = FtpFindFirstFilePidlWrap(hint, TRUE, &cmlc, pwe, NULL, &pidlItem, pPidlList->m_dwInetFlags, NULL, &hInetFind);
if (hInetFind)
{
do
{
LPCWIRESTR pwireStr = FtpPidl_GetLastItemWireName(pidlFull);
if (IS_VALID_FILE(pwireStr))
{
// Store entire pidl (containing WIN32_FIND_DATA) so we can get
// the attributes and other info later. Seeing if it's a dir
// is one need...
pPidlList->InsertSorted(pidlItem);
}
ILFree(pidlItem);
hr = InternetFindNextFilePidlWrap(hInetFind, TRUE, &cmlc, pwe, &pidlItem);
}
while (SUCCEEDED(hr));
ILFree(pidlItem);
InternetCloseHandle(hInetFind);
}
if (ERROR_NO_MORE_FILES == HRESULT_CODE(hr))
hr = S_OK;
}
EVAL(SUCCEEDED(pwe->ReSetCodePages(&cmlc, pPidlList)));
}
return hr;
}
/*****************************************************************************\
FUNCTION: _GetPathDifference
DESCRIPTION:
This function will step into the pszDir directory and enum all of it's
contents. For each item, it will call the callback function provided (pfnProcessItemCB).
That callback function can then call EnumFolder() again (recursively) if
there is a subfolder.
NOTE:
This function needs to first find all the items and then in a second
loop call the callback function. This is because the WININET FTP APIs
only allow one enum to occur at a time, which may not happen if half way through
enuming one dir, a recursive call starts enuming a sub dir.
PARAMETERS:
pszBaseUrl - This needs to be escaped.
pszDir - This needs to be escaped.
*ppszUrlPathDiff - This will be UnEscaped.
\*****************************************************************************/
void _GetPathDifference(LPCTSTR pszBaseUrl, LPCTSTR pszDir, LPTSTR * ppszUrlPathDiff)
{
TCHAR szUrlPathDiff[MAX_URL_STRING];
TCHAR szFullUrl[MAX_URL_STRING];
DWORD cchSize = ARRAYSIZE(szFullUrl);
// This is needed for this case:
// pszBaseUrl="ftp://server/subdir1/", pszDir="/subdir1/subdir2/file.txt"
// So, szUrlPathDiff="subdir2/file.txt" instead of pszDir
//
// ICU_NO_ENCODE is needed because Download Dlg may have paths with
// spaces that can't be escaped.
InternetCombineUrl(pszBaseUrl, pszDir, szFullUrl, &cchSize, ICU_NO_ENCODE);
UrlGetDifference(pszBaseUrl, szFullUrl, szUrlPathDiff, ARRAYSIZE(szUrlPathDiff));
// We will now use szFullUrl to store the UnEscaped version since these buffers
// are so large.
UnEscapeString(szUrlPathDiff, szFullUrl, ARRAYSIZE(szFullUrl));
Str_SetPtr(ppszUrlPathDiff, szFullUrl);
}
/*****************************************************************************\
FUNCTION: EnumFolder
DESCRIPTION:
This function will step into the pszDir directory and enum all of it's
contents. For each item, it will call the callback function provided (pfnProcessItemCB).
That callback function can then call EnumFolder() again (recursively) if
there is a subfolder.
PARAMETERS:
(pszBaseUrl=ftp://server/dir1/, pszDir=dir2, DirToEnum=ftp://server/dir1/dir2/)
pszDir - This is the directory we are enumerating. (dir2) It is relative to pszBaseUrl.
hint - The current working directory will be set to pszBaseUrl. _EnumFolderPrep will make it go into pszDir.
NOTE:
This function needs to first find all the items and then in a second
loop call the callback function. This is because the WININET FTP APIs
only allow one enum to occur at a time, which may not happen if half way through
enuming one dir, a recursive call starts enuming a sub dir.
\*****************************************************************************/
HRESULT EnumFolder(LPFNPROCESSITEMCB pfnProcessItemCB, HINTERNET hint, LPCITEMIDLIST pidlFull, CWireEncoding * pwe, BOOL * pfValidhinst, LPVOID pvData)
{
CFtpPidlList * pPidlList;
BOOL fValidhinst = TRUE;
HRESULT hr = CFtpPidlList_Create(0, &pidlFull, &pPidlList);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlCurrFtpPath = NULL;
hr = _EnumFolderPrep(hint, pidlFull, pPidlList, pwe, &pidlCurrFtpPath);
if (SUCCEEDED(hr))
{
hr = S_OK;
// 4. Process each file name, which may be recursive.
// This loop and the while loop above need to be
// separated because it's not possible to create
// more than one FTP Find File handle based on the
// same session.
for (int nIndex = 0; SUCCEEDED(hr) && (nIndex < pPidlList->GetCount()); nIndex++)
{
LPITEMIDLIST pidlNewFull = ILCombine(pidlFull, pPidlList->GetPidl(nIndex));
hr = pfnProcessItemCB(pfnProcessItemCB, hint, pidlNewFull, &fValidhinst, pvData);
ILFree(pidlNewFull);
}
// 5. Go back to original directory (from Step 2)
// The only time we don't want to return to the original directory is if
// the hinst was freed in an wininet callback function. We may cache the hinst
// so we need the directory to be valid later.
if (fValidhinst)
{
if (SUCCEEDED(hr))
{
// We still want to reset the directory but we don't want to over write
// the original error message.
hr = FtpSetCurrentDirectoryPidlWrap(hint, TRUE, pidlCurrFtpPath, TRUE, TRUE);
}
}
Pidl_Set(&pidlCurrFtpPath, NULL);
}
pPidlList->Release();
}
if (pfValidhinst)
*pfValidhinst = fValidhinst;
return hr;
}