/***************************************************************************** * * 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; }