#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; }