/*****************************************************************************\ FILE: view.cpp DESCRIPTION: This is our ShellView which implements FTP specific behavior. We get the default DefView implementation and then use IShellFolderViewCB to override behavior specific to us. \*****************************************************************************/ #include "priv.h" #include "view.h" #include "ftpobj.h" #include "statusbr.h" #include "dialogs.h" #include #include #include "newmenu.h" extern ULONG g_cRef_CFtpView; // {FBDB45F0-DBF8-11d2-BB9B-006097DF5BD4} Private to msieftp.dll, NEVER EVER use outside of this DLL const GUID IID_CFtpViewPrivThis = { 0xfbdb45f0, 0xdbf8, 0x11d2, { 0xbb, 0x9b, 0x0, 0x60, 0x97, 0xdf, 0x5b, 0xd4 } }; /***************************************************************************** * * COLINFO, c_rgci * * Column information for DVM_GETDETAILSOF. * *****************************************************************************/ const struct COLINFO { UINT cchCol; UINT uiFmt; } c_rgci[] = { { 30, LVCFMT_LEFT }, { 10, LVCFMT_RIGHT }, { 20, LVCFMT_LEFT }, { 20, LVCFMT_LEFT }, }; BOOL CFtpView::IsForegroundThread(void) { return (GetCurrentThreadId() == m_nThreadID); } /*****************************************************************************\ FUNCTION: _MOTDDialogProc DESCRIPTION: \*****************************************************************************/ INT_PTR CALLBACK CFtpView::_MOTDDialogProc(HWND hDlg, UINT wm, WPARAM wParam, LPARAM lParam) { LRESULT lResult = FALSE; switch (wm) { case WM_INITDIALOG: { CFtpView * pThis = (CFtpView *) lParam; CFtpGlob * pfg = pThis->m_pff->GetSiteMotd(); if (EVAL(pfg)) { // TODO: NT #250018. Format the message and make it look pretty. // so it doesn't have the FTP status numbers. We may also // want to filter only the message that comes thru with // status numbers 230 EVAL(SetWindowText(GetDlgItem(hDlg, IDC_MOTDDLG_MESSAGE), pfg->GetHGlobAsTCHAR())); pfg->Release(); } } break; case WM_COMMAND: if ((IDOK == GET_WM_COMMAND_ID(wParam, lParam)) || (IDCANCEL == GET_WM_COMMAND_ID(wParam, lParam))) EndDialog(hDlg, TRUE); break; } return lResult; } /***************************************************************************** * * _ShowMotdPsf * * Show the motd for a particular psf. * *****************************************************************************/ void CFtpView::_ShowMotdPsf(HWND hwndOwner) { DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(IDD_MOTDDLG), hwndOwner, _MOTDDialogProc, (LPARAM)this); } /***************************************************************************** * * _ShowMotd * * When Explorer finally goes idle, this procedure will be called, * and we will show the FTP site's (new) motd. * *****************************************************************************/ void CFtpView::_ShowMotd(void) { m_hgtiWelcome = 0; if (EVAL(m_pff)) _ShowMotdPsf(m_hwndOwner); else { // We got cancelled prematurely } } /***************************************************************************** * * _OnGetDetailsOf * * ici - column for which information is requested * pdi -> DETAILSINFO * * If pdi->pidl is 0, then we are asking for information about * what columns to display. If pdi->pidl is nonzero, then we * are asking for particular information about the specified pidl. * * _UNDOCUMENTED_: This callback and the DETAILSINFO structure * are not documented. Nor is the quirk about pdi->pidl as * noted above. * *****************************************************************************/ #define MAX_SIZE_STR 30 HRESULT CFtpView::_OnGetDetailsOf(UINT ici, PDETAILSINFO pdi) { HRESULT hr = E_FAIL; if (ici < COL_MAX) { pdi->str.uType = STRRET_CSTR; pdi->str.cStr[0] = '\0'; if (pdi->pidl) { switch (ici) { case COL_NAME: { WCHAR wzDisplayName[MAX_PATH]; hr = FtpItemID_GetDisplayName(pdi->pidl, wzDisplayName, ARRAYSIZE(wzDisplayName)); if (EVAL(SUCCEEDED(hr))) StringToStrRetW(wzDisplayName, &pdi->str); } break; case COL_SIZE: // (Directories don't get a size. Shell rules.) if (!FtpPidl_IsDirectory(pdi->pidl, TRUE)) { LONGLONG llSize = (LONGLONG) FtpItemID_GetFileSize(pdi->pidl); WCHAR wzSizeStr[MAX_SIZE_STR]; if (StrFormatByteSizeW(llSize, wzSizeStr, ARRAYSIZE(wzSizeStr))) SHUnicodeToAnsi(wzSizeStr, pdi->str.cStr, ARRAYSIZE(pdi->str.cStr)); else StrFormatByteSizeA(FtpItemID_GetFileSizeLo(pdi->pidl), pdi->str.cStr, ARRAYSIZE(pdi->str.cStr)); } hr = S_OK; break; case COL_TYPE: hr = FtpPidl_GetFileTypeStrRet(pdi->pidl, &pdi->str); break; case COL_MODIFIED: { TCHAR szDateTime[MAX_PATH]; // We need the time in UTC because that's what Misc_StringFromFileTime() // wants. FILETIME ftLastModifiedUTC = FtpPidl_GetFileTime(pdi->pidl); DWORD dwFlags = FDTF_SHORTDATE | FDTF_SHORTTIME; switch (pdi->fmt) { case LVCFMT_LEFT_TO_RIGHT : dwFlags |= FDTF_LTRDATE; break; case LVCFMT_RIGHT_TO_LEFT : dwFlags |= FDTF_RTLDATE; break; } // Misc_StringFromFileTime() wants UTC Misc_StringFromFileTime(szDateTime, ARRAYSIZE(szDateTime), &ftLastModifiedUTC, dwFlags); hr = StringToStrRetW(szDateTime, &pdi->str); } break; } } else { WCHAR wzColumnLable[MAX_PATH]; pdi->fmt = c_rgci[ici].uiFmt; pdi->cxChar = c_rgci[ici].cchCol; EVAL(LoadStringW(HINST_THISDLL, IDS_HEADER_NAME(ici), wzColumnLable, ARRAYSIZE(wzColumnLable))); hr = StringToStrRetW(wzColumnLable, &pdi->str); } } else hr = E_NOTIMPL; return hr; } /*****************************************************************************\ FUNCTION: _OnColumnClick DESCRIPTION: _UNDOCUMENTED_: This callback and its parameters are not documented. _UNDOCUMENTED_: ShellFolderView_ReArrange is not documented. PARAMETERS: hwnd - view window ici - column that was clicked \*****************************************************************************/ HRESULT CFtpView::_OnColumnClick(UINT ici) { ShellFolderView_ReArrange(m_hwndOwner, ici); return S_OK; } HRESULT CFtpView::_OnAddPropertyPages(SFVM_PROPPAGE_DATA * pData) { return AddFTPPropertyPages(pData->pfn, pData->lParam, &m_hinstInetCpl, m_psfv); } /*****************************************************************************\ FUNCTION: _OnInitMenuPopup DESCRIPTION: We use IContextMenu::QueryContectMenu() to merge background items into the File menu. This doesn't work on browser only because it's not supported so we would like to see if this works. PARAMETERS: \*****************************************************************************/ HRESULT CFtpView::_OnInitMenuPopup(HMENU hmenu, UINT idCmdFirst, UINT nIndex) { return S_OK; } /*****************************************************************************\ FUNCTION: _OnMergeMenu DESCRIPTION: _UNDOCUMENTED_: This callback and its parameters are not documented. _UNDOCUMENTED_: Nothing about menu merging is documented. PARAMETERS: pqcm - QueryContextMenu info \*****************************************************************************/ HRESULT CFtpView::_OnMergeMenu(LPQCMINFO pqcm) { HRESULT hr; HMENU hmenu = LoadMenu(HINST_THISDLL, MAKEINTRESOURCE(IDM_FTPMERGE)); if (SHELL_VERSION_W95NT4 != GetShellVersion()) { // We prefer to add "New" and "Login As" via // IContextMenu::QueryContextMenu() but it wasn't implemented // in browser only. The IDM_FTPMERGE menu contains a second // copy for the browser only case so we need to remove them // if it's not browser only. EVAL(DeleteMenu(hmenu, FCIDM_MENU_FILE, MF_BYCOMMAND)); } if (SHELL_VERSION_IE4 < GetShellVersion()) { // Remove "Help.FTP Help" because we will have that work done // in "Help.Help Topics" on NT5 and after. We don't do this for // earlier versions of shell32 because shell32 in NT5 is the // first version to support "HtmlHelp" over WinHelp. This is // needed because FTP's help is stored in IE's HTML Help files. EVAL(DeleteMenu(hmenu, IDC_ITEM_FTPHELP, MF_BYCOMMAND)); } if (hmenu) { MergeMenuHierarchy(pqcm->hmenu, hmenu, pqcm->idCmdFirst, pqcm->idCmdLast); m_idMergedMenus = pqcm->idCmdFirst; m_nMenuItemsAdded = GetMenuItemCount(hmenu); DestroyMenu(hmenu); // Remove duplicate items. (Browser Only) _SHPrettyMenu(pqcm->hmenu); int nItems = GetMenuItemCount(pqcm->hmenu); if (nItems) { // Pretty the submenus because we added separators. NT #358197 for (int nIndex = 0; nIndex < nItems; nIndex++) { HMENU hSubMenu = GetSubMenu(pqcm->hmenu, nIndex); _SHPrettyMenu(hSubMenu); } } hr = S_OK; } else { hr = E_OUTOFMEMORY; } // NT #267081, some other people (IE) will reformat the StatusBar during the // asynch navigation. I take this event (MergeMenus) and reformat the // status bar if necessary. _InitStatusBar(); return hr; } /*****************************************************************************\ FUNCTION: UnMergeMenu DESCRIPTION: PARAMETERS: \*****************************************************************************/ HRESULT UnMergeMenu(HMENU hMenu, UINT idOffset, HMENU hMenuTemplate) { HRESULT hr = S_OK; UINT nIndex; UINT nEnd = GetMenuItemCount(hMenuTemplate); for (nIndex = 0; nIndex < nEnd; nIndex++) { UINT idToDelete = GetMenuItemID(hMenuTemplate, nIndex); if (-1 != idToDelete) DeleteMenu(hMenu, (idToDelete + idOffset), MF_BYPOSITION); else { // It may be a submenu, so we may need to recurse. MENUITEMINFO mii; mii.cbSize = sizeof(mii); mii.fMask = MIIM_SUBMENU; mii.cch = 0; // just in case if (GetMenuItemInfo(hMenuTemplate, nIndex, TRUE, &mii) && mii.hSubMenu) { // It is a sub menu, so delete those items also. hr = UnMergeMenu(hMenu, idOffset, mii.hSubMenu); } } } return hr; } HRESULT CFtpView::_OnUnMergeMenu(HMENU hMenu) { HRESULT hr = S_OK; // Did I merge anything? if (m_idMergedMenus && m_nMenuItemsAdded) { HMENU hMenuFTP = LoadMenu(HINST_THISDLL, MAKEINTRESOURCE(IDM_FTPMERGE)); if (hMenuFTP) { hr = UnMergeMenu(hMenu, m_idMergedMenus, hMenuFTP); DestroyMenu(hMenuFTP); } m_idMergedMenus = 0; } return hr; } /*****************************************************************************\ FUNCTION: _OnInvokeLoginAs DESCRIPTION: PARAMETERS: \*****************************************************************************/ HRESULT CFtpView::_OnInvokeLoginAs(HWND hwndOwner) { ASSERT(m_pff); return LoginAsViaFolder(hwndOwner, m_pff, m_psfv); } /*****************************************************************************\ FUNCTION: _OnInvokeNewFolder DESCRIPTION: PARAMETERS: \*****************************************************************************/ HRESULT CFtpView::_OnInvokeNewFolder(HWND hwndOwner) { POINT pt = {0,0}; return CreateNewFolder(hwndOwner, m_pff, NULL, m_psfv, FALSE, pt); } /*****************************************************************************\ FUNCTION: _OnInvokeCommand DESCRIPTION: _UNDOCUMENTED_: This callback and its parameters are not documented. _UNDOCUMENTED_: ShellFolderView_ReArrange is not documented. PARAMETERS: idc - Command being invoked \*****************************************************************************/ HRESULT CFtpView::_OnInvokeCommand(UINT idc) { HRESULT hr = S_OK; switch (idc) { case IDM_SORTBYNAME: case IDM_SORTBYSIZE: case IDM_SORTBYTYPE: case IDM_SORTBYDATE: ShellFolderView_ReArrange(m_hwndOwner, CONVERT_IDMID_TO_COLNAME(idc)); break; case IDC_ITEM_ABOUTSITE: _ShowMotdPsf(m_hwndOwner); break; case IDC_ITEM_FTPHELP: _OnInvokeFtpHelp(m_hwndOwner); break; case IDC_LOGIN_AS: _OnInvokeLoginAs(m_hwndOwner); break; case IDC_ITEM_NEWFOLDER: _OnInvokeNewFolder(m_hwndOwner); break; #ifdef ADD_ABOUTBOX case IDC_ITEM_ABOUTFTP: hr = DisplayAboutBox(m_hwndOwner); break; #endif // ADD_ABOUTBOX default: ASSERT(0); hr = E_NOTIMPL; break; } return hr; } /*****************************************************************************\ FUNCTION: _OnGetHelpText DESCRIPTION: The shell want's the Help Text but they want it in their format (Ansi vs. Unicode). \*****************************************************************************/ HRESULT CFtpView::_OnGetHelpText(LPARAM lParam, WPARAM wParam) { HRESULT hres = E_FAIL; UINT uiID = IDS_ITEM_HELP(LOWORD(wParam)); TCHAR szHelpText[MAX_PATH]; LPWSTR pwzHelpTextOut = (LPWSTR) lParam; // Only one of these is correct and fUnicodeShell indicates which one. LPSTR pszHelpTextOut = (LPSTR) lParam; pwzHelpTextOut[0] = L'\0'; // Terminate string. (Ok if it's ANSI) szHelpText[0] = TEXT('\0'); // This will fail for some items that the shell will provide for us. // These include View.ArrangeIcon.AutoArrange. // NOTE: This currently doesn't work for everything in the View.ArrangeIcon // menu except AutoArrange because uiID is 30-33, // not 40-43 (IDS_HEADER_HELP(COL_NAME) - IDS_HEADER_HELP(COL_MODIFIED)). // This will require changing the resource IDs but that will mess up // the localizers and require changing IDS_HEADER_NAME(). if (LoadString(HINST_THISDLL, uiID, szHelpText, ARRAYSIZE(szHelpText))) { HMODULE hMod = GetModuleHandle(TEXT("shell32.dll")); if (hMod) { BOOL fUnicodeShell = (NULL != GetProcAddress(hMod, "WOWShellExecute")); // NOTE: DVM_GETHELPTEXT will want a UNICODE string if we are running // on NT and an Ansi string if we are running on Win95. Let's thunk it to what // they want. if (fUnicodeShell) SHTCharToUnicode(szHelpText, pwzHelpTextOut, HIWORD(wParam)); else SHTCharToAnsi(szHelpText, pszHelpTextOut, HIWORD(wParam)); hres = S_OK; } } return hres; } #define SZ_HELPTOPIC_FILEA "iexplore.chm > iedefault" #define SZ_HELPTOPIC_FTPSECTIONA "ftp_over.htm" #define SZ_HELPTOPIC_FILEW L"iexplore.chm" #define SZ_HELPTOPIC_FTPSECTIONW L"ftp_over.htm" /*****************************************************************************\ FUNCTION: _OnInvokeFtpHelp DESCRIPTION: The wants Help specific to FTP. \*****************************************************************************/ HRESULT CFtpView::_OnInvokeFtpHelp(HWND hwnd) { HRESULT hr = E_INVALIDARG; uCLSSPEC ucs; QUERYCONTEXT qc = { 0 }; ucs.tyspec = TYSPEC_CLSID; ucs.tagged_union.clsid = CLSID_IEHelp; // ASSERT(m_hwndOwner && m_psfv); // Not available on browser only IUnknown_EnableModless((IUnknown *)m_psfv, FALSE); hr = FaultInIEFeature(m_hwndOwner, &ucs, &qc, FIEF_FLAG_FORCE_JITUI); IUnknown_EnableModless((IUnknown *)m_psfv, TRUE); HtmlHelpA(NULL, SZ_HELPTOPIC_FILEA, HH_HELP_FINDER, (DWORD_PTR) SZ_HELPTOPIC_FTPSECTIONA); return hr; } /*****************************************************************************\ FUNCTION: _OnGetHelpTopic DESCRIPTION: Remove "Help.FTP Help" because we will have that work done in "Help.Help Topics" on NT5 and after. We don't do this for earlier versions of shell32 because shell32 in NT5 is the first version to support "HtmlHelp" over WinHelp. This is needed because FTP's help is stored in IE's HTML Help files. \*****************************************************************************/ HRESULT CFtpView::_OnGetHelpTopic(SFVM_HELPTOPIC_DATA * phtd) { HRESULT hr = E_NOTIMPL; // Remove "Help.FTP Help" because we will have that work done // in "Help.Help Topics" on NT5 and after. We don't do this for // earlier versions of shell32 because shell32 in NT5 is the // first version to support "HtmlHelp" over WinHelp. This is // needed because FTP's help is stored in IE's HTML Help files. if (SHELL_VERSION_IE4 < GetShellVersion()) { StrCpyNW(phtd->wszHelpFile, SZ_HELPTOPIC_FILEW, ARRAYSIZE(phtd->wszHelpFile)); StrCpyNW(phtd->wszHelpTopic, SZ_HELPTOPIC_FTPSECTIONW, ARRAYSIZE(phtd->wszHelpTopic)); hr = S_OK; } return hr; } /*****************************************************************************\ FUNCTION: _OnGetZone DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::_OnGetZone(DWORD * pdwZone, WPARAM wParam) { HRESULT hr = E_INVALIDARG; DWORD dwZone = URLZONE_INTERNET; // Default LPCITEMIDLIST pidl = m_pff->GetPrivatePidlReference(); if (pidl) { WCHAR wzUrl[MAX_URL_STRING]; // NT #277100: This may fail if TweakUI is installed because // they abuse us. hr = UrlCreateFromPidlW(pidl, SHGDN_FORPARSING, wzUrl, ARRAYSIZE(wzUrl), ICU_ESCAPE | ICU_USERNAME, FALSE); if (SUCCEEDED(hr)) { IInternetSecurityManager * pism; if (SUCCEEDED(CoCreateInstance(CLSID_InternetSecurityManager, NULL, CLSCTX_INPROC_SERVER, IID_IInternetSecurityManager, (void **) &pism))) { pism->MapUrlToZone(wzUrl, &dwZone, 0); pism->Release(); } } } if (pdwZone) { *pdwZone = dwZone; hr = S_OK; } return hr; } /*****************************************************************************\ FUNCTION: _OnGetPane DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::_OnGetPane(DWORD dwPaneID, DWORD * pdwPane) { HRESULT hr = E_INVALIDARG; DWORD dwPane = PANE_NONE; // Default unknown switch (dwPaneID) { case PANE_NAVIGATION: dwPane = STATUS_PANE_STATUS; break; case PANE_ZONE: dwPane = STATUS_PANE_ZONE; break; default: break; } if (pdwPane) { *pdwPane = dwPane; hr = S_OK; } return hr; } /*****************************************************************************\ FUNCTION: _OnRefresh DESCRIPTION: We need to purge the cache and force our selves to hit the server again. \*****************************************************************************/ HRESULT CFtpView::_OnRefresh(BOOL fReload) { if (EVAL(m_pff) && fReload) m_pff->InvalidateCache(); return S_OK; } /*****************************************************************************\ FUNCTION: _OnBackGroundEnumDone DESCRIPTION: Our enum happens on the background. Sometimes we decide that we want to do a redirect during the enumeration because the UserName/Password didn't allow access to the server but the user provided a pair that does. Since we can't access the ComDlgBrowser's IShellBrowser::BrowseObject() on the background, we need to call it on the forground. In order to do that, we need an event that happens on the forground. Well this is that even baby. \*****************************************************************************/ HRESULT CFtpView::_OnBackGroundEnumDone(void) { HRESULT hr = S_OK; if (m_pidlRedirect) { LPITEMIDLIST pidlRedirect = NULL; ENTERCRITICAL; if (m_pidlRedirect) { pidlRedirect = m_pidlRedirect; m_pidlRedirect = NULL; } LEAVECRITICAL; if (pidlRedirect) { IShellBrowser * psb; hr = IUnknown_QueryService(_punkSite, SID_SCommDlgBrowser, IID_IShellBrowser, (LPVOID *) &psb); if (SUCCEEDED(hr)) { hr = psb->BrowseObject(pidlRedirect, 0); AssertMsg(SUCCEEDED(hr), TEXT("CFtpView::_OnBackGroundEnumDone() defview needs to support QS(SID_ShellFolderViewCB) on all platforms that hit this point")); psb->Release(); } ILFree(pidlRedirect); } } return hr; } /*****************************************************************************\ FUNCTION: _OnGetNotify DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::_OnGetNotify(LPITEMIDLIST * ppidl, LONG * lEvents) { if (EVAL(lEvents)) *lEvents = FTP_SHCNE_EVENTS; if (EVAL(ppidl)) { // Normally I would use pidlRoot to get ChangeNotify messages but since // that doesn't work, it's necessary to broadcast ChangeNotify messages // using pidlTarget and receive them using pidlTarget. This is the later // case. if (EVAL(m_pff)) *ppidl = (LPITEMIDLIST) m_pff->GetPublicTargetPidlReference(); else *ppidl = NULL; } return S_OK; } /*****************************************************************************\ FUNCTION: _OnSize DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::_OnSize(LONG x, LONG y) { RECT rcCurrent; HRESULT hr = S_OK; ASSERT(m_hwndOwner); GetWindowRect(m_hwndOwner, &rcCurrent); // Has the size really changed? if ((m_rcPrev.bottom != rcCurrent.bottom) || (m_rcPrev.top != rcCurrent.top) || (m_rcPrev.left != rcCurrent.left) || (m_rcPrev.right != rcCurrent.right)) { // yes, so update the StatusBar. if (m_psb) hr = m_psb->Resize(x, y); m_rcPrev = rcCurrent; } else { // No, so ignore it because we may stomp on some other // active view. (Because we get this message even after // another view took over the brower). // I don't care about resizing to zero. // I don't think the user will ever need it and it casues // bug #198695 where the addressband goes blank. This is because // defview will call us thru each of the two places: // 1) CFtpFolder::CreateViewObject() (Old URL) // 2) CDefView::CreateViewWindow2()->CFtpView::_OnSize() (Old URL) // 3) DV_UpdateStatusBar()->CFtpView::_OnUpdateStatusBar() (New URL) // 4) ReleaseWindowLV()->WndSize()->CFtpView::_OnSize() (Old URL) // #4 makes us update the URL and replace #3 which is valid. } return hr; } /*****************************************************************************\ FUNCTION: _OnThisIDList DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::_OnThisIDList(LPITEMIDLIST * ppidl) { HRESULT hr = S_FALSE; if (EVAL(ppidl)) { *ppidl = ILClone(m_pff->GetPublicRootPidlReference()); hr = S_OK; } return hr; } /*****************************************************************************\ FUNCTION: _OnUpdateStatusBar DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::_OnUpdateStatusBar(void) { HRESULT hr = S_FALSE; LPCITEMIDLIST pidl = m_pff->GetPrivatePidlReference(); if (EVAL(pidl)) { TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; BOOL fAnnonymousLogin = TRUE; hr = FtpPidl_GetUserName(pidl, szUserName, ARRAYSIZE(szUserName)); if (SUCCEEDED(hr) && szUserName[0]) fAnnonymousLogin = FALSE; if (m_psb) { // Even if the above call fails, we set the user name to clear out // any old invalid values. m_psb->SetUserName(szUserName, fAnnonymousLogin); } EVAL(SUCCEEDED(_SetStatusBarZone(m_psb, m_pff->m_pfs))); } return hr; } /*****************************************************************************\ FUNCTION: SetRedirectPidl DESCRIPTION: See the comments in _OnBackGroundEnumDone(). \*****************************************************************************/ HRESULT CFtpView::SetRedirectPidl(LPCITEMIDLIST pidlRedirect) { ENTERCRITICAL; Pidl_Set(&m_pidlRedirect, pidlRedirect); LEAVECRITICAL; return S_OK; } /*****************************************************************************\ FUNCTION: DummyHintCallback DESCRIPTION: Doesn't do anything; simply forces the connection to be established and the motd to be obtained. \*****************************************************************************/ HRESULT CFtpView::DummyHintCallback(HWND hwnd, CFtpFolder * pff, HINTERNET hint, LPVOID pv1, LPVOID pv2) { return S_OK; } /*****************************************************************************\ FUNCTION: _InitStatusBar DESCRIPTION: Obtains and initializes the status bar window. It is not an error if the viewer does not provide a status bar. \*****************************************************************************/ void CFtpView::_InitStatusBar(void) { if (m_psb) m_psb->SetStatusMessage(IDS_EMPTY, 0); } /*****************************************************************************\ FUNCTION: _OnWindowCreated (from shell32.IShellView) DESCRIPTION: When the window is created, we get the motd. Very soon thereafter, DefView is going to ask for the IEnumIDList, which will now be in the cache. (GROSS! Screws up background enumeration!) Do this only if we don't already have a motd. \*****************************************************************************/ HRESULT CFtpView::_OnWindowCreated(void) { HRESULT hr = S_FALSE; // Previously, we cached the MOTD here, but we now do it else where. We // could do it here also if we wanted to have a MOTD per site and per folder // but almost no servers support this and user's pretty much never need it. // Besides, there are ambiguious cases that we couldn't get right on other // servers. return hr; } /*****************************************************************************\ FUNCTION: _OnDefItemCount (from shell32.IShellView) DESCRIPTION: _UNDOCUMENTED_: This callback and its parameters are not documented. Called to advise the browser of how many items we might have. This allows preliminary UI to appear while we are busy enumerating the contents. \*****************************************************************************/ HRESULT CFtpView::_OnDefItemCount(LPINT pi) { *pi = 20; return S_OK; } /*****************************************************************************\ FUNCTION: _OnDidDragDrop DESCRIPTION: Called to advise the browser that somebody did a drag/drop operation on objects in the folder. If the effect was DROPEFFECT_MOVE, then we delete the source, if we aren't still doing the copy asynch on a background thread. RETURN VALUES: S_OK: We take responsibility of deleting the files which we can do here in the synch case, or in IAsynchOperation::EndOperation() in the asynch case. S_FALSE: We didn't do the delete but it's OK for the caller to do it. so the caller needs to display UI and then delete via IContextMenu->InvokeCommand(-delete-). \*****************************************************************************/ HRESULT CFtpView::_OnDidDragDrop(DROPEFFECT de, IDataObject * pdo) { HRESULT hr = S_OK; if (DROPEFFECT_MOVE == de) { IAsyncOperation * pao; hr = pdo->QueryInterface(IID_IAsyncOperation, (void **) &pao); if (SUCCEEDED(hr)) { BOOL fInAsyncOp = TRUE; hr = pao->InOperation(&fInAsyncOp); hr = S_OK; // Don't have caller do the delete. if (FALSE == fInAsyncOp) { #ifdef FEATURE_CUT_MOVE CLSID clsid; BOOL fDoDelete = TRUE; CFtpObj * pfo = (CFtpObj *) pdo; // Is the destination the recycle bin? if (SUCCEEDED(DataObj_GetDropTarget(pdo, &clsid)) && IsEqualCLSID(clsid, CLSID_RecycleBin)) { // Yes, so we need to first inform the user that drops to the // Recycle bin are perminate deletes and the user can't undo // the operation. if (IDYES != SHMessageBox(m_hwndOwner, NULL, IDS_RECYCLE_IS_PERM_WARNING, IDS_FTPERR_TITLE, (MB_ICONQUESTION | MB_YESNO))) fDoDelete = FALSE; } // We didn't do the operation aynch so we need to DELETE the // files now to complete the MOVE operation (MOVE=Copy + Delete). if (fDoDelete) { Misc_DeleteHfpl(m_pff, m_hwndOwner, pfo->GetHfpl()); // Will fail on permission denied. } #else // FEATURE_CUT_MOVE hr = S_FALSE; // Have parent do the delete. #endif //FEATURE_CUT_MOVE } pao->Release(); } else hr = S_OK; // Don't have caller delete. IAsyncOperation::EndOperation() will. } return hr; } //=========================== // *** IFtpWebView Interface *** //=========================== /*****************************************************************************\ FUNCTION: IFtpWebView::get_MessageOfTheDay DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::get_MessageOfTheDay(BSTR * pbstr) { HRESULT hr = S_FALSE; if (EVAL(pbstr)) { *pbstr = NULL; if (EVAL(m_pff)) { TCHAR szDefault[MAX_PATH]; LPCTSTR pszMOTD = szDefault; CFtpGlob * pfg = m_pff->GetSiteMotd(); szDefault[0] = 0; if (pfg) pszMOTD = pfg->GetHGlobAsTCHAR(); // if we were not able to get the message of the day // from CFtpFolder or it was empty, display "None" if ((pszMOTD == szDefault) || (!pszMOTD[0])) { pszMOTD = szDefault; LoadString(HINST_THISDLL, IDS_NO_MESSAGEOFTHEDAY, szDefault, ARRAYSIZE(szDefault)); } *pbstr = TCharSysAllocString(pszMOTD); if (pfg) pfg->Release(); hr = S_OK; } } else hr = E_INVALIDARG; ASSERT_POINTER_MATCHES_HRESULT(*pbstr, hr); return hr; } /*****************************************************************************\ FUNCTION: IFtpWebView::get_Server DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::get_Server(BSTR * pbstr) { HRESULT hr = S_FALSE; if (EVAL(pbstr)) { *pbstr = NULL; if (EVAL(m_pff)) { TCHAR szServer[INTERNET_MAX_HOST_NAME_LENGTH]; if (SUCCEEDED(FtpPidl_GetServer(m_pff->GetPrivatePidlReference(), szServer, ARRAYSIZE(szServer)))) { *pbstr = TCharSysAllocString(szServer); if (*pbstr) hr = S_OK; } } } else hr = E_INVALIDARG; // ASSERT_POINTER_MATCHES_HRESULT(*pbstr, hr); return hr; } /*****************************************************************************\ FUNCTION: IFtpWebView::get_Directory DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::get_Directory(BSTR * pbstr) { HRESULT hr = S_FALSE; if (EVAL(pbstr)) { *pbstr = NULL; if (EVAL(m_pff)) { TCHAR szUrlPath[INTERNET_MAX_PATH_LENGTH]; if (EVAL(SUCCEEDED(GetDisplayPathFromPidl(m_pff->GetPrivatePidlReference(), szUrlPath, ARRAYSIZE(szUrlPath), FALSE)))) { *pbstr = TCharSysAllocString(szUrlPath); if (*pbstr) hr = S_OK; } } } else hr = E_INVALIDARG; ASSERT_POINTER_MATCHES_HRESULT(*pbstr, hr); return hr; } /*****************************************************************************\ FUNCTION: IFtpWebView::get_UserName DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::get_UserName(BSTR * pbstr) { HRESULT hr = S_FALSE; if (EVAL(pbstr)) { *pbstr = NULL; if (EVAL(m_pff)) { TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; if (EVAL(SUCCEEDED(FtpPidl_GetUserName(m_pff->GetPrivatePidlReference(), szUserName, ARRAYSIZE(szUserName))))) { *pbstr = TCharSysAllocString((0 != szUserName[0]) ? szUserName : SZ_ANONYMOUS); if (*pbstr) hr = S_OK; } } } else hr = E_INVALIDARG; ASSERT_POINTER_MATCHES_HRESULT(*pbstr, hr); return hr; } /*****************************************************************************\ FUNCTION: IFtpWebView::get_PasswordLength DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::get_PasswordLength(long * plLength) { HRESULT hr = S_FALSE; if (EVAL(plLength)) { TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH]; *plLength = 0; if (SUCCEEDED(FtpPidl_GetPassword(m_pff->GetPrivatePidlReference(), szPassword, ARRAYSIZE(szPassword), FALSE))) { *plLength = lstrlen(szPassword); hr = S_OK; } } else hr = E_INVALIDARG; ASSERT_POINTER_MATCHES_HRESULT(*plLength, hr); return hr; } /*****************************************************************************\ FUNCTION: IFtpWebView::get_EmailAddress DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::get_EmailAddress(BSTR * pbstr) { HRESULT hr = S_OK; if (EVAL(pbstr)) { TCHAR szEmailName[MAX_PATH]; DWORD dwType = REG_SZ; DWORD cbSize = sizeof(szEmailName); if (ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER, SZ_REGKEY_INTERNET_SETTINGS, SZ_REGKEY_EMAIL_NAME, &dwType, szEmailName, &cbSize)) *pbstr = TCharSysAllocString(szEmailName); else { hr = S_FALSE; *pbstr = NULL; } } else hr = E_INVALIDARG; return hr; } /*****************************************************************************\ FUNCTION: IFtpWebView::put_EmailAddress DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::put_EmailAddress(BSTR bstr) { HRESULT hr = S_OK; if (EVAL(bstr)) { TCHAR szEmailName[MAX_PATH]; SHUnicodeToTChar(bstr, szEmailName, ARRAYSIZE(szEmailName)); if (ERROR_SUCCESS != SHSetValue(HKEY_CURRENT_USER, SZ_REGKEY_INTERNET_SETTINGS, SZ_REGKEY_EMAIL_NAME, REG_SZ, szEmailName, sizeof(szEmailName))) hr = S_FALSE; } else hr = E_INVALIDARG; return hr; } /*****************************************************************************\ FUNCTION: IFtpWebView::get_CurrentLoginAnonymous DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::get_CurrentLoginAnonymous(VARIANT_BOOL * pfAnonymousLogin) { HRESULT hr = S_OK; if (EVAL(pfAnonymousLogin)) { TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; if (EVAL(m_pff) && SUCCEEDED(FtpPidl_GetUserName(m_pff->GetPrivatePidlReference(), szUserName, ARRAYSIZE(szUserName))) && szUserName[0] && (0 != StrCmpI(szUserName, TEXT("anonymous")))) { *pfAnonymousLogin = VARIANT_FALSE; } else *pfAnonymousLogin = VARIANT_TRUE; } else hr = E_INVALIDARG; return hr; } /*****************************************************************************\ FUNCTION: IFtpWebView::LoginAnonymously DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::LoginAnonymously(void) { return _LoginWithPassword(NULL, NULL); } /*****************************************************************************\ FUNCTION: IFtpWebView::LoginWithPassword DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::LoginWithPassword(BSTR bUserName, BSTR bPassword) { HRESULT hr = S_OK; TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH]; SHUnicodeToTChar(bUserName, szUserName, ARRAYSIZE(szUserName)); SHUnicodeToTChar(bPassword, szPassword, ARRAYSIZE(szPassword)); return _LoginWithPassword(szUserName, szPassword); } /*****************************************************************************\ FUNCTION: IFtpWebView::LoginWithoutPassword DESCRIPTION: \*****************************************************************************/ HRESULT CFtpView::LoginWithoutPassword(BSTR bUserName) { HRESULT hr = S_FALSE; TCHAR szUserName[INTERNET_MAX_USER_NAME_LENGTH]; TCHAR szPassword[INTERNET_MAX_PASSWORD_LENGTH]; SHUnicodeToTChar(bUserName, szUserName, ARRAYSIZE(szUserName)); if (SUCCEEDED(FtpPidl_GetPassword(m_pff->GetPrivatePidlReference(), szPassword, ARRAYSIZE(szPassword), TRUE))) hr = _LoginWithPassword(szUserName, szPassword); return hr; } HRESULT CFtpView::_LoginWithPassword(LPCTSTR pszUserName, LPCTSTR pszPassword) { HRESULT hr = S_OK; LPITEMIDLIST pidlUser; hr = PidlReplaceUserPassword(m_pff->GetPrivatePidlReference(), &pidlUser, m_pff->GetItemAllocatorDirect(), pszUserName, pszPassword); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlFull = m_pff->CreateFullPublicPidl(pidlUser); if (pidlFull) { hr = IUnknown_PidlNavigate(m_psfv, pidlFull, TRUE); ASSERT(SUCCEEDED(hr)); ILFree(pidlFull); } ILFree(pidlUser); } if (FAILED(hr)) hr = S_FALSE; // Automation interfaces don't like failure returns. return hr; } //=========================== // *** IDispatch Interface *** //=========================== STDMETHODIMP CFtpView::GetTypeInfoCount(UINT * pctinfo) { return CImpIDispatch::GetTypeInfoCount(pctinfo); } STDMETHODIMP CFtpView::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo * * pptinfo) { return CImpIDispatch::GetTypeInfo(itinfo, lcid, pptinfo); } STDMETHODIMP CFtpView::GetIDsOfNames(REFIID riid, OLECHAR * * rgszNames, UINT cNames, LCID lcid, DISPID * rgdispid) { return CImpIDispatch::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid); } STDMETHODIMP CFtpView::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pdispparams, VARIANT * pvarResult, EXCEPINFO * pexcepinfo, UINT * puArgErr) { return CImpIDispatch::Invoke(dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); } /***************************************************************************** * * CFtpView_Create * * Creates a brand new enumerator based on an ftp site. * *****************************************************************************/ HRESULT CFtpView_Create(CFtpFolder * pff, HWND hwndOwner, REFIID riid, LPVOID * ppv) { HRESULT hr = E_OUTOFMEMORY; CFtpView * pfv = new CFtpView(pff, hwndOwner); if (pfv) { hr = pfv->QueryInterface(riid, ppv); pfv->Release(); } ASSERT_POINTER_MATCHES_HRESULT(*ppv, hr); return hr; } /****************************************************\ Constructor \****************************************************/ CFtpView::CFtpView(CFtpFolder * pff, HWND hwndOwner) : CImpIDispatch(&LIBID_MSIEFTPLib) { DllAddRef(); // This needs to be allocated in Zero Inited Memory. // Assert that all Member Variables are inited to Zero. ASSERT(!m_hwndOwner); ASSERT(!m_hwndStatusBar); ASSERT(!m_pff); ASSERT(!m_hgtiWelcome); m_nThreadID = GetCurrentThreadId(); if (hwndOwner) { m_hwndOwner = hwndOwner; m_hwndStatusBar = Misc_FindStatusBar(hwndOwner); m_psb = CStatusBar_Create(m_hwndStatusBar); _InitStatusBar(); } m_rcPrev.top = m_rcPrev.bottom = m_rcPrev.right = m_rcPrev.left = -1; IUnknown_Set(&m_pff, pff); LEAK_ADDREF(LEAK_CFtpView); g_cRef_CFtpView++; // Needed to determine when to purge cache. } /****************************************************\ Destructor \****************************************************/ /***************************************************************************** * We release the psf before triggering the timeout, which is a * signal to the trigger not to do anything. * * _UNDOCUMENTED_: This callback and its parameters are not documented. * *****************************************************************************/ CFtpView::~CFtpView() { IUnknown_Set(&m_pff, NULL); TriggerDelayedAction(&m_hgtiWelcome); // Kick out the old one SetRedirectPidl(NULL); if (m_psb) delete m_psb; if (m_hinstInetCpl) FreeLibrary(m_hinstInetCpl); DllRelease(); LEAK_DELREF(LEAK_CFtpView); g_cRef_CFtpView--; // Needed to determine when to purge cache. } //=========================== // *** IUnknown Interface *** //=========================== HRESULT CFtpView::QueryInterface(REFIID riid, void **ppvObj) { if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IDispatch)) { *ppvObj = SAFECAST(this, IDispatch *); } else if (IsEqualIID(riid, IID_IFtpWebView)) { *ppvObj = SAFECAST(this, IFtpWebView *); } else if (IsEqualIID(riid, IID_CFtpViewPrivThis)) { *ppvObj = (void *)this; } else return CBaseFolderViewCB::QueryInterface(riid, ppvObj); AddRef(); return S_OK; } CFtpView * GetCFtpViewFromDefViewSite(IUnknown * punkSite) { CFtpView * pfv = NULL; IShellFolderViewCB * psfvcb = NULL; // This fails on Browser Only IUnknown_QueryService(punkSite, SID_ShellFolderViewCB, IID_IShellFolderViewCB, (LPVOID *) &psfvcb); if (psfvcb) { psfvcb->QueryInterface(IID_CFtpViewPrivThis, (void **) &pfv); psfvcb->Release(); } return pfv; } CStatusBar * GetCStatusBarFromDefViewSite(IUnknown * punkSite) { CStatusBar * psb = NULL; CFtpView * pfv = GetCFtpViewFromDefViewSite(punkSite); if (pfv) { psb = pfv->GetStatusBar(); pfv->Release(); } return psb; }