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

582 lines
19 KiB
C++

#include "stdafx.h"
#include "netplace.h"
#include "msdasc.h"
#pragma hdrstop
CNetworkPlace::CNetworkPlace() :
_pidl(NULL), _fSupportWebFolders(FALSE), _fIsWebFolder(FALSE), _fDeleteWebFolder(FALSE)
{
_szTarget[0] = TEXT('\0');
_szName[0] = TEXT('\0');
_szDescription[0] = TEXT('\0');
}
// destructor - clean up our state
CNetworkPlace::~CNetworkPlace()
{
_InvalidateCache();
}
void CNetworkPlace::_InvalidateCache()
{
// web folders will create a shortcut to objects if we go through its binding
// process, therefore when we attempt to invalidate our cache we should
// clean up our mess.
//
// if the user has commited the change then we can/will keep the shortcut
// around, otherwise we call the delete verb on it.
if (_fIsWebFolder && _fDeleteWebFolder && _pidl)
{
IShellFolder *psf;
LPCITEMIDLIST pidlLast;
HRESULT hr = SHBindToIDListParent(_pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast);
if (SUCCEEDED(hr))
{
IContextMenu *pcm;
hr = psf->GetUIObjectOf(NULL, 1, &pidlLast, IID_X_PPV_ARG(IContextMenu, NULL, &pcm));
if (SUCCEEDED(hr))
{
CMINVOKECOMMANDINFO ici = {0};
ici.cbSize = sizeof (ici);
ici.fMask = CMIC_MASK_FLAG_NO_UI;
ici.lpVerb = "Delete";
ici.nShow = SW_SHOWNORMAL;
hr = pcm->InvokeCommand(&ici);
pcm->Release();
}
psf->Release();
}
}
// now clean up the rest of our state.
ILFree(_pidl);
_pidl = NULL;
_szTarget[0] = TEXT('\0');
_szName[0] = TEXT('\0');
_szDescription[0] = TEXT('\0');
_fIsWebFolder = FALSE;
_fDeleteWebFolder = FALSE;
}
HRESULT CNetworkPlace::SetTarget(HWND hwnd, LPCWSTR pszTarget, DWORD dwFlags)
{
_InvalidateCache();
HRESULT hr = S_OK;
if (pszTarget)
{
// set our state accordingly
_fSupportWebFolders = (dwFlags & NPTF_ALLOWWEBFOLDERS) != 0;
// copy the URL and prepare for parsing
StrCpyN(_szTarget, pszTarget, ARRAYSIZE(_szTarget));
INT cchTarget = lstrlen(_szTarget)-1;
if ((_szTarget[cchTarget] == L'\\') || (_szTarget[cchTarget] == '/'))
{
_szTarget[cchTarget] = TEXT('\0');
}
if (dwFlags & NPTF_VALIDATE)
{
// connecting to a server root or local path is not supported
if (PathIsUNCServer(_szTarget) || PathGetDriveNumber(_szTarget) != -1)
{
hr = E_INVALIDARG;
}
else
{
// check the policy to see if we are setting this.
if (PathIsUNC(_szTarget) && SHRestricted(REST_NONETCONNECTDISCONNECT))
{
hr = E_INVALIDARG;
}
else
{
hr = _IDListFromTarget(hwnd);
}
}
if (FAILED(hr))
{
if (hwnd && !(dwFlags & NPTF_SILENT))
{
::DisplayFormatMessage(hwnd,
IDS_ANP_CAPTION,
PathIsUNCServer(_szTarget) ? IDS_PUB_ONLYSERVER:IDS_CANTFINDFOLDER,
MB_OK|MB_ICONERROR);
}
_InvalidateCache();
}
}
}
return hr;
}
HRESULT CNetworkPlace::SetName(HWND hwnd, LPCWSTR pszName)
{
HRESULT hr = S_OK;
if (!_fIsWebFolder)
{
// check to see if we are going to overwrite an existing place, if we
// are then display a prompt and let the user choose. if they answer
// yes, then have at it!
TCHAR szPath[MAX_PATH];
if (hwnd && _IsPlaceTaken(pszName, szPath))
{
if (IDNO == ::DisplayFormatMessage(hwnd,
IDS_ANP_CAPTION , IDS_FRIENDLYNAMEINUSE,
MB_YESNO|MB_ICONQUESTION,
pszName))
{
hr = E_FAIL;
}
}
}
// if we succeed the above then lets use the new name.
if (SUCCEEDED(hr))
StrCpyN(_szName, pszName, ARRAYSIZE(_szName));
return hr;
}
HRESULT CNetworkPlace::SetDescription(LPCWSTR pszDescription)
{
StrCpyN(_szDescription, pszDescription, ARRAYSIZE(_szDescription));
return S_OK;
}
// recompute the URL based on the new user/password information that
// we were just given.
HRESULT CNetworkPlace::SetLoginInfo(LPCWSTR pszUser, LPCWSTR pszPassword)
{
TCHAR szServer[INTERNET_MAX_HOST_NAME_LENGTH + 1];
TCHAR szUrlPath[INTERNET_MAX_PATH_LENGTH + 1];
TCHAR szExtraInfo[MAX_PATH + 1]; // Includes Port Number and download type (ASCII, Binary, Detect)
URL_COMPONENTS urlComps = {0};
urlComps.dwStructSize = sizeof(urlComps);
urlComps.lpszHostName = szServer;
urlComps.dwHostNameLength = ARRAYSIZE(szServer);
urlComps.lpszUrlPath = szUrlPath;
urlComps.dwUrlPathLength = ARRAYSIZE(szUrlPath);
urlComps.lpszExtraInfo = szExtraInfo;
urlComps.dwExtraInfoLength = ARRAYSIZE(szExtraInfo);
// WARNING - the ICU_DECODE/ICU_ESCAPE is a lossy roundtrip - ZekeL - 26-MAR-2001
// many escaped characters are not correctly identified and re-escaped.
// any characters that are reserved for URL parsing purposes
// will be interpreted as their parsing char (ie '/').
BOOL fResult = InternetCrackUrl(_szTarget, 0, 0, &urlComps);
if (fResult)
{
urlComps.lpszUserName = (LPTSTR) pszUser;
urlComps.dwUserNameLength = (pszUser ? lstrlen(pszUser) : 0);
urlComps.lpszPassword = (LPTSTR) pszPassword;
urlComps.dwPasswordLength = (pszPassword ? lstrlen(pszPassword) : 0);
DWORD cchSize = ARRAYSIZE(_szTarget);
fResult = InternetCreateUrl(&urlComps, (ICU_ESCAPE | ICU_USERNAME), _szTarget, &cchSize);
// if we have a cached IDList then lets ensure that we clear it up
// so that we rebind and the FTP namespace gets a crack at it.
if (fResult && _pidl)
{
ILFree(_pidl);
_pidl = NULL;
}
}
return fResult ? S_OK : HRESULT_FROM_WIN32(GetLastError());
}
HRESULT CNetworkPlace::GetIDList(HWND hwnd, LPITEMIDLIST *ppidl)
{
HRESULT hr = _IDListFromTarget(hwnd);
if (SUCCEEDED(hr))
{
hr = SHILClone(_pidl, ppidl);
}
return hr;
}
HRESULT CNetworkPlace::GetObject(HWND hwnd, REFIID riid, void **ppv)
{
HRESULT hr = _IDListFromTarget(hwnd);
if (SUCCEEDED(hr))
{
hr = SHBindToObject(NULL, riid, _pidl, ppv);
}
return hr;
}
HRESULT CNetworkPlace::GetName(LPWSTR pszBuffer, int cchBuffer)
{
HRESULT hr = _IDListFromTarget(NULL);
if (SUCCEEDED(hr))
{
StrCpyN(pszBuffer, _szName, cchBuffer);
hr = S_OK;
}
return hr;
}
// check to see if we are going to overwrite a network place
BOOL CNetworkPlace::_IsPlaceTaken(LPCTSTR pszName, LPTSTR pszPath)
{
BOOL fOverwriting = FALSE;
SHGetSpecialFolderPath(NULL, pszPath, CSIDL_NETHOOD, TRUE);
PathCombine(pszPath, pszPath, pszName);
IShellFolder *psf;
HRESULT hr = SHGetDesktopFolder(&psf);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl;
if (SUCCEEDED(psf->ParseDisplayName(NULL, NULL, pszPath, NULL, &pidl, NULL)))
{
// we think we are going to overwrite an existing net place, so lets
// check first to see if the place which is there is not actually
// pointing at our new target. if its is then we can just
// ignore all of this.
TCHAR szTarget[INTERNET_MAX_URL_LENGTH];
hr = _GetTargetPath(pidl, szTarget, ARRAYSIZE(szTarget));
if (FAILED(hr) || (0 != StrCmpI(szTarget, _szTarget)))
{
fOverwriting = TRUE;
}
ILFree(pidl);
}
psf->Release();
}
return fOverwriting;
}
// handle creating the web folders IDLIST for this item. we check with the
// rosebud binder to find out if this scheme is supported, if so then
// we attempt to have the Web Folders code crack the URL
static const BYTE c_pidlWebFolders[] =
{
0x14,0x00,0x1F,0x0F,0xE0,0x4F,0xD0,0x20,
0xEA,0x3A,0x69,0x10,0xA2,0xD8,0x08,0x00,
0x2B,0x30,0x30,0x9D,0x14,0x00,0x2E,0x00,
0x00,0xDF,0xEA,0xBD,0x65,0xC2,0xD0,0x11,
0xBC,0xED,0x00,0xA0,0xC9,0x0A,0xB5,0x0F,
0x00,0x00
};
HRESULT CNetworkPlace::_TryWebFolders(HWND hwnd)
{
// lets see if Rosebud can handle this scheme item by checking the
// scheme and seeing if the rosebud binder can handle it.
TCHAR szScheme[INTERNET_MAX_SCHEME_LENGTH + 1];
DWORD cchScheme = ARRAYSIZE(szScheme);
HRESULT hr = UrlGetPart(_szTarget, szScheme, &cchScheme, URL_PART_SCHEME, 0);
if (SUCCEEDED(hr))
{
IRegisterProvider *prp;
hr = CoCreateInstance(CLSID_RootBinder, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IRegisterProvider, &prp));
if (SUCCEEDED(hr))
{
// let the web folders code have a go at creating a link to this storage,
// the IDLIST we generate points to the folder inside My Computer (hidden)
CLSID clsidOut;
hr = prp->GetURLMapping(szScheme, 0, &clsidOut);
if (hr == S_OK)
{
IShellFolder *psf;
hr = SHBindToObject(NULL, IID_IShellFolder, (LPCITEMIDLIST)c_pidlWebFolders, (void**)&psf);
if (SUCCEEDED(hr))
{
IBindCtx *pbc;
hr = CreateBindCtx(NULL, &pbc);
if (SUCCEEDED(hr))
{
BIND_OPTS bo = {sizeof(bo), 0, STGM_CREATE};
hr = pbc->SetBindOptions(&bo);
if (SUCCEEDED(hr))
{
// we need to pase NULL hWnd to this so that Web Folders doesn't display any
// UI, in particular its ever so useful NULL error message box... mumble mumble
LPITEMIDLIST pidl;
hr = psf->ParseDisplayName(NULL, pbc, _szTarget, NULL, &pidl, NULL);
if (SUCCEEDED(hr))
{
ASSERT(!_pidl);
hr = SHILCombine((LPCITEMIDLIST)c_pidlWebFolders, pidl, &_pidl);
ILFree(pidl);
_fDeleteWebFolder = TRUE; // we now have the magic web folders link (clean it up)
}
}
pbc->Release();
}
psf->Release();
}
}
else
{
hr = E_FAIL;
}
prp->Release();
}
}
return hr;
}
// dereference a link and get the target path
HRESULT CNetworkPlace::_GetTargetPath(LPCITEMIDLIST pidl, LPTSTR pszPath, int cchPath)
{
LPITEMIDLIST pidlTarget;
HRESULT hr = SHGetTargetFolderIDList(pidl, &pidlTarget);
if (SUCCEEDED(hr))
{
SHGetNameAndFlags(pidlTarget, SHGDN_FORPARSING, pszPath, cchPath, NULL);
ILFree(pidlTarget);
}
return hr;
}
// create an IDLIST for the target that we have, this code attempts to parse the name and
// then set our state for the item. if we fail to parse then we attempt to have Web Folders
// look at it - this most common scenario for this will be the DAV RDR failing because
// the server isn't a DAV store, so instead we try Web Folders to handle WEC etc.
HRESULT CNetworkPlace::_IDListFromTarget(HWND hwnd)
{
HRESULT hr = S_OK;
if (!_pidl)
{
if (_szTarget[0])
{
_fIsWebFolder = FALSE; // not a web folder
BINDCTX_PARAM rgParams[] =
{
{ STR_PARSE_PREFER_FOLDER_BROWSING, NULL},
{ L"BUT NOT WEBFOLDERS", NULL},
};
IBindCtx *pbc;
hr = BindCtx_RegisterObjectParams(NULL, rgParams, ARRAYSIZE(rgParams), &pbc);
if (SUCCEEDED(hr))
{
IBindCtx *pbcWindow;
hr = BindCtx_RegisterUIWindow(pbc, hwnd, &pbcWindow);
if (SUCCEEDED(hr))
{
SFGAOF sfgao;
hr = SHParseDisplayName(_szTarget, pbcWindow, &_pidl, SFGAO_FOLDER, &sfgao);
// if we parsed something that turns out to not
// be a folder, we want to throw it away
if (SUCCEEDED(hr) && !(sfgao & SFGAO_FOLDER))
{
ILFree(_pidl);
_pidl = 0;
hr = E_FAIL;
}
// if that failed, its is a HTTP/HTTPS and we have web folders support then lets try
// and fall back to the old behaviour.
if (FAILED(hr) && _fSupportWebFolders)
{
DWORD scheme = GetUrlScheme(_szTarget);
if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS)
{
switch (hr)
{
#if 0
case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
case HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND):
case HRESULT_FROM_WIN32(ERROR_BAD_NET_NAME):
case HRESULT_FROM_WIN32(ERROR_BAD_NETPATH):
#endif
case HRESULT_FROM_WIN32(ERROR_CANCELLED):
break;
default:
{
hr = _TryWebFolders(hwnd);
if (SUCCEEDED(hr))
{
_fIsWebFolder = TRUE;
}
}
}
}
}
if (SUCCEEDED(hr))
{
// given that we may have translated the name above for the parse
// to work, lets read back the name we used into our _szTarget.
SHGetNameAndFlags(_pidl, SHGDN_FORPARSING, _szTarget, ARRAYSIZE(_szTarget), NULL);
}
pbcWindow->Release();
}
// compute the place name for the location we have hit, this includes reusing
// any places we have already created.
if (SUCCEEDED(hr) && !_szName[0])
{
SHGetNameAndFlags(_pidl, SHGDN_NORMAL, _szName, ARRAYSIZE(_szName), NULL);
TCHAR szPath[MAX_PATH];
if (!_fIsWebFolder && _IsPlaceTaken(_szName, szPath))
{
PathYetAnotherMakeUniqueName(szPath, szPath, NULL, NULL);
StrCpyN(_szName, PathFindFileName(szPath), ARRAYSIZE(_szName)); // update our state
}
}
pbc->Release();
}
}
else
{
hr = E_FAIL;
}
}
return hr;
}
// handle creating the network place shortcut
HRESULT CNetworkPlace::CreatePlace(HWND hwnd, BOOL fOpen)
{
HRESULT hr = _IDListFromTarget(hwnd);
if (SUCCEEDED(hr))
{
// web folders already have their links created, therefore we can ignore this
// whole process for them, and instead fall back to just executing their link.
//
// for regular folders though we must attempt to find a unique name and create
// the link, or if the link already exists that we can use then just open it.
if (!_fIsWebFolder)
{
IShellLink *psl;
hr = CoCreateInstance(CLSID_FolderShortcut, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellLink, &psl));
if (SUCCEEDED(hr))
{
hr = psl->SetIDList(_pidl);
if (SUCCEEDED(hr))
hr = psl->SetDescription(_szDescription[0] ? _szDescription:_szTarget);
if (SUCCEEDED(hr))
{
IPersistFile *ppf;
hr = psl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
if (SUCCEEDED(hr))
{
// get the name to the shortcut, we assume that this is unique
TCHAR szPath[MAX_PATH];
SHGetSpecialFolderPath(NULL, szPath, CSIDL_NETHOOD, TRUE);
PathCombine(szPath, szPath, _szName);
hr = ppf->Save(szPath, TRUE);
ppf->Release();
}
}
psl->Release();
}
}
else
{
// this is the web folder case, so we now need to set the display
// name for this guy. note that we don't have any control over
// the description text we are going to be seeing.
IShellFolder *psf;
LPCITEMIDLIST pidlLast;
hr = SHBindToIDListParent(_pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlNew;
hr = psf->SetNameOf(hwnd, pidlLast, _szName, SHGDN_INFOLDER, &pidlNew);
if (SUCCEEDED(hr))
{
_fDeleteWebFolder = FALSE;
//Web folders will return S_FALSE with bogus pidlNew if _szName is the same as the current name
if (S_OK == hr)
{
ILFree(_pidl);
hr = SHILCombine((LPCITEMIDLIST)c_pidlWebFolders, pidlNew, &_pidl);
}
}
psf->Release();
}
}
// now open the target if thats what they asked for
if (SUCCEEDED(hr) && fOpen)
{
LPITEMIDLIST pidlNetPlaces;
hr = SHGetSpecialFolderLocation(hwnd, CSIDL_NETWORK, &pidlNetPlaces);
if (SUCCEEDED(hr))
{
IShellFolder *psf;
hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidlNetPlaces, &psf));
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl;
hr = psf->ParseDisplayName(hwnd, NULL, _szName, NULL, &pidl, NULL);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlToOpen;
hr = SHILCombine(pidlNetPlaces, pidl, &pidlToOpen);
if (SUCCEEDED(hr))
{
BrowseToPidl(pidlToOpen);
ILFree(pidlToOpen);
}
ILFree(pidl);
}
psf->Release();
}
ILFree(pidlNetPlaces);
}
}
}
return hr;
}