From 7fdec9600932e308ad66037b3fe5823f25cde174 Mon Sep 17 00:00:00 2001 From: Katayama Hirofumi MZ Date: Sat, 13 Apr 2024 21:07:12 +0900 Subject: [PATCH] [SHELL32] Fix ParseDisplayName Part 2 (#6740) Follow-up to #6721. Reduce SHParseDisplayName failures. JIRA issue: CORE-19495 - Re-implement CFSFolder::ParseDisplayName method to validate the names. - Add CFSFolder::_ParseSimple, CFSFolder::_GetFindDataFromName, and CFSFolder::_CreateIDListFromName helper methods. - Add PathIsDotOrDotDotW, PathIsValidElement, PathIsDosDevice, and SHILAppend helper functions. - Delete GetNextElementW and add Shell_NextElement function. --- dll/win32/shell32/folders/CFSFolder.cpp | 239 +++++++++++++++--------- dll/win32/shell32/folders/CFSFolder.h | 8 + dll/win32/shell32/precomp.h | 8 +- dll/win32/shell32/shfldr.h | 10 +- dll/win32/shell32/shlfolder.cpp | 71 ++++--- dll/win32/shell32/utils.cpp | 90 +++++++-- 6 files changed, 288 insertions(+), 138 deletions(-) diff --git a/dll/win32/shell32/folders/CFSFolder.cpp b/dll/win32/shell32/folders/CFSFolder.cpp index 978b0d440fd..f6546dc23bf 100644 --- a/dll/win32/shell32/folders/CFSFolder.cpp +++ b/dll/win32/shell32/folders/CFSFolder.cpp @@ -4,7 +4,7 @@ * PURPOSE: file system folder * COPYRIGHT: Copyright 1997 Marcus Meissner * Copyright 1998, 1999, 2002 Juergen Schmied - * Copyright 2019 Katayama Hirofumi MZ + * Copyright 2019-2024 Katayama Hirofumi MZ * Copyright 2020 Mark Jansen (mark.jansen@reactos.org) */ @@ -539,46 +539,6 @@ static const shvheader GenericSFHeader[] = { #define GENERICSHELLVIEWCOLUMNS 6 -/************************************************************************** - * SHELL32_CreatePidlFromBindCtx [internal] - * - * If the caller bound File System Bind Data, assume it is the - * find data for the path. - * This allows binding of paths that don't exist. - */ -LPITEMIDLIST SHELL32_CreatePidlFromBindCtx(IBindCtx *pbc, LPCWSTR path) -{ - IFileSystemBindData *fsbd = NULL; - LPITEMIDLIST pidl = NULL; - IUnknown *param = NULL; - WIN32_FIND_DATAW wfd; - HRESULT r; - - TRACE("%p %s\n", pbc, debugstr_w(path)); - - if (!pbc) - return NULL; - - /* see if the caller bound File System Bind Data */ - r = pbc->GetObjectParam((LPOLESTR)STR_FILE_SYS_BIND_DATA, ¶m); - if (FAILED(r)) - return NULL; - - r = param->QueryInterface(IID_PPV_ARG(IFileSystemBindData,&fsbd)); - if (SUCCEEDED(r)) - { - r = fsbd->GetFindData(&wfd); - if (SUCCEEDED(r)) - { - lstrcpynW(&wfd.cFileName[0], path, MAX_PATH); - pidl = _ILCreateFromFindDataW(&wfd); - } - fsbd->Release(); - } - - return pidl; -} - static HRESULT SHELL32_GetCLSIDForDirectory(LPCWSTR pwszDir, LPCWSTR KeyName, CLSID* pclsidFolder) { WCHAR wszCLSIDValue[CHARS_IN_GUID]; @@ -705,6 +665,91 @@ HRESULT SHELL32_GetFSItemAttributes(IShellFolder * psf, LPCITEMIDLIST pidl, LPDW return S_OK; } +HRESULT CFSFolder::_ParseSimple( + _In_ LPOLESTR lpszDisplayName, + _Out_ WIN32_FIND_DATAW *pFind, + _Out_ LPITEMIDLIST *ppidl) +{ + HRESULT hr; + LPWSTR pchNext = lpszDisplayName; + + *ppidl = NULL; + + LPITEMIDLIST pidl; + for (hr = S_OK; SUCCEEDED(hr); hr = SHILAppend(pidl, ppidl)) + { + hr = Shell_NextElement(&pchNext, pFind->cFileName, _countof(pFind->cFileName), FALSE); + if (hr != S_OK) + break; + + if (pchNext) + pFind->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; + + pidl = _ILCreateFromFindDataW(pFind); + if (!pidl) + { + hr = E_OUTOFMEMORY; + break; + } + } + + if (SUCCEEDED(hr)) + return S_OK; + + if (*ppidl) + { + ILFree(*ppidl); + *ppidl = NULL; + } + + return hr; +} + +BOOL CFSFolder::_GetFindDataFromName(_In_ LPCWSTR pszName, _Out_ WIN32_FIND_DATAW *pFind) +{ + WCHAR szPath[MAX_PATH]; + lstrcpynW(szPath, m_sPathTarget, _countof(szPath)); + PathAppendW(szPath, pszName); + + HANDLE hFind = ::FindFirstFileW(szPath, pFind); + if (hFind == INVALID_HANDLE_VALUE) + return FALSE; + + ::FindClose(hFind); + return TRUE; +} + +HRESULT CFSFolder::_CreateIDListFromName(LPCWSTR pszName, DWORD attrs, IBindCtx *pbc, LPITEMIDLIST *ppidl) +{ + *ppidl = NULL; + + if (PathIsDosDevice(pszName)) + return HRESULT_FROM_WIN32(ERROR_BAD_DEVICE); + + WIN32_FIND_DATAW FindData = { 0 }; + + HRESULT hr = S_OK; + if (attrs == ULONG_MAX) // Invalid attributes + { + if (!_GetFindDataFromName(pszName, &FindData)) + hr = HRESULT_FROM_WIN32(::GetLastError()); + } + else // Pretend as an item of attrs + { + StringCchCopyW(FindData.cFileName, _countof(FindData.cFileName), pszName); + FindData.dwFileAttributes = attrs; + } + + if (FAILED(hr)) + return hr; + + *ppidl = _ILCreateFromFindDataW(&FindData); + if (!*ppidl) + return E_OUTOFMEMORY; + + return S_OK; +} + /************************************************************************** * CFSFolder::ParseDisplayName {SHELL32} * @@ -736,13 +781,6 @@ HRESULT WINAPI CFSFolder::ParseDisplayName(HWND hwndOwner, DWORD *pchEaten, PIDLIST_RELATIVE *ppidl, DWORD *pdwAttributes) { - HRESULT hr = E_INVALIDARG; - LPCWSTR szNext = NULL; - WCHAR szElement[MAX_PATH]; - WCHAR szPath[MAX_PATH]; - LPITEMIDLIST pidlTemp = NULL; - DWORD len; - TRACE ("(%p)->(HWND=%p,%p,%p=%s,%p,pidl=%p,%p)\n", this, hwndOwner, pbc, lpszDisplayName, debugstr_w (lpszDisplayName), pchEaten, ppidl, pdwAttributes); @@ -750,68 +788,85 @@ HRESULT WINAPI CFSFolder::ParseDisplayName(HWND hwndOwner, if (!ppidl) return E_INVALIDARG; - if (!lpszDisplayName) - { - *ppidl = NULL; - return E_INVALIDARG; - } - *ppidl = NULL; - if (pchEaten) - *pchEaten = 0; /* strange but like the original */ + if (!lpszDisplayName) + return E_INVALIDARG; - if (*lpszDisplayName) + HRESULT hr; + WIN32_FIND_DATAW FindData; + if (SHIsFileSysBindCtx(pbc, &FindData) == S_OK) { - /* get the next element */ - szNext = GetNextElementW (lpszDisplayName, szElement, MAX_PATH); - - pidlTemp = SHELL32_CreatePidlFromBindCtx(pbc, szElement); - if (pidlTemp != NULL) + CComHeapPtr pidlTemp; + hr = _ParseSimple(lpszDisplayName, &FindData, &pidlTemp); + if (SUCCEEDED(hr) && pdwAttributes && *pdwAttributes) { - /* We are creating an id list without ensuring that the items exist. - If we have a remaining path, this must be a folder. - We have to do it now because it is set as a file by default */ - if (szNext) - { - pidlTemp->mkid.abID[0] = PT_FOLDER; - } - hr = S_OK; + LPCITEMIDLIST pidlLast = ILFindLastID(pidlTemp); + GetAttributesOf(1, &pidlLast, pdwAttributes); } - else - { - /* build the full pathname to the element */ - lstrcpynW(szPath, m_sPathTarget, MAX_PATH - 1); - PathAddBackslashW(szPath); - len = wcslen(szPath); - lstrcpynW(szPath + len, szElement, MAX_PATH - len); - /* get the pidl */ - hr = _ILCreateFromPathW(szPath, &pidlTemp); + if (SUCCEEDED(hr)) + *ppidl = pidlTemp.Detach(); + } + else + { + INT cchElement = lstrlenW(lpszDisplayName) + 1; + LPWSTR pszElement = (LPWSTR)alloca(cchElement * sizeof(WCHAR)); + LPWSTR pchNext = lpszDisplayName; + hr = Shell_NextElement(&pchNext, pszElement, cchElement, TRUE); + if (FAILED(hr)) + return hr; + + hr = _CreateIDListFromName(pszElement, ULONG_MAX, pbc, ppidl); + if (FAILED(hr)) + { + if (pchNext) // Is there the next element? + { + // pszElement seems like a directory + if (_GetFindDataFromName(pszElement, &FindData) && + (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + hr = _CreateIDListFromName(pszElement, FILE_ATTRIBUTE_DIRECTORY, pbc, ppidl); + } + } + else + { + // pszElement seems like a non-directory + if ((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || + hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) && + (BindCtx_GetMode(pbc, 0) & STGM_CREATE)) + { + // Pretend like a normal file + hr = _CreateIDListFromName(pszElement, FILE_ATTRIBUTE_NORMAL, pbc, ppidl); + } + } } if (SUCCEEDED(hr)) { - if (szNext && *szNext) + if (pchNext) // Is there next? { - /* try to analyse the next element */ - hr = SHELL32_ParseNextElement(this, hwndOwner, pbc, - &pidlTemp, (LPOLESTR) szNext, pchEaten, pdwAttributes); + CComPtr psfChild; + hr = BindToObject(*ppidl, pbc, IID_PPV_ARG(IShellFolder, &psfChild)); + if (FAILED(hr)) + return hr; + + DWORD chEaten; + CComHeapPtr pidlChild; + hr = psfChild->ParseDisplayName(hwndOwner, pbc, pchNext, &chEaten, &pidlChild, + pdwAttributes); + + // Append pidlChild to ppidl + if (SUCCEEDED(hr)) + hr = SHILAppend(pidlChild.Detach(), ppidl); } - else + else if (pdwAttributes && *pdwAttributes) { - /* it's the last element */ - if (pdwAttributes && *pdwAttributes) - hr = SHELL32_GetFSItemAttributes(this, pidlTemp, pdwAttributes); + GetAttributesOf(1, (LPCITEMIDLIST*)ppidl, pdwAttributes); } } } - if (SUCCEEDED(hr)) - *ppidl = pidlTemp; - else - *ppidl = NULL; - TRACE("(%p)->(-- pidl=%p ret=0x%08x)\n", this, ppidl ? *ppidl : 0, hr); return hr; diff --git a/dll/win32/shell32/folders/CFSFolder.h b/dll/win32/shell32/folders/CFSFolder.h index 924dd9bd893..8d29c4d3479 100644 --- a/dll/win32/shell32/folders/CFSFolder.h +++ b/dll/win32/shell32/folders/CFSFolder.h @@ -33,6 +33,14 @@ class CFSFolder : HRESULT _CreateExtensionUIObject(LPCITEMIDLIST pidl, REFIID riid, LPVOID *ppvOut); HRESULT _GetDropTarget(LPCITEMIDLIST pidl, LPVOID *ppvOut); HRESULT _GetIconHandler(LPCITEMIDLIST pidl, REFIID riid, LPVOID *ppvOut); + + HRESULT _ParseSimple( + _In_ LPOLESTR lpszDisplayName, + _Out_ WIN32_FIND_DATAW *pFind, + _Out_ LPITEMIDLIST *ppidl); + BOOL _GetFindDataFromName(_In_ LPCWSTR pszName, _Out_ WIN32_FIND_DATAW *pFind); + HRESULT _CreateIDListFromName(LPCWSTR pszName, DWORD attrs, IBindCtx *pbc, LPITEMIDLIST *ppidl); + public: CFSFolder(); ~CFSFolder(); diff --git a/dll/win32/shell32/precomp.h b/dll/win32/shell32/precomp.h index 74765c7c2e9..19c4255fc0f 100644 --- a/dll/win32/shell32/precomp.h +++ b/dll/win32/shell32/precomp.h @@ -161,8 +161,9 @@ Shell_TranslateIDListAlias( _In_ DWORD dwFlags); BOOL BindCtx_ContainsObject(_In_ IBindCtx *pBindCtx, _In_ LPCWSTR pszName); +DWORD BindCtx_GetMode(_In_ IBindCtx *pbc, _In_ DWORD dwDefault); BOOL SHSkipJunctionBinding(_In_ IBindCtx *pbc, _In_ CLSID *pclsid); -HRESULT SHIsFileSysBindCtx(_In_ IBindCtx *pBindCtx, _Out_opt_ WIN32_FIND_DATAW **ppFindData); +HRESULT SHIsFileSysBindCtx(_In_ IBindCtx *pBindCtx, _Out_opt_ WIN32_FIND_DATAW *pFindData); BOOL Shell_FailForceReturn(_In_ HRESULT hr); EXTERN_C INT @@ -211,4 +212,9 @@ BindCtx_RegisterObjectParam( _In_opt_ IUnknown *punk, _Out_ LPBC *ppbc); +BOOL PathIsDotOrDotDotW(_In_ LPCWSTR pszPath); +BOOL PathIsValidElement(_In_ LPCWSTR pszPath); +BOOL PathIsDosDevice(_In_ LPCWSTR pszName); +HRESULT SHILAppend(_Inout_ LPITEMIDLIST pidl, _Inout_ LPITEMIDLIST *ppidl); + #endif /* _PRECOMP_H__ */ diff --git a/dll/win32/shell32/shfldr.h b/dll/win32/shell32/shfldr.h index 46878c28a0c..64e236a5d73 100644 --- a/dll/win32/shell32/shfldr.h +++ b/dll/win32/shell32/shfldr.h @@ -36,14 +36,18 @@ typedef struct { #define GET_SHGDN_FOR(dwFlags) ((DWORD)dwFlags & (DWORD)0x0000FF00) #define GET_SHGDN_RELATION(dwFlags) ((DWORD)dwFlags & (DWORD)0x000000FF) -LPCWSTR GetNextElementW (LPCWSTR pszNext, LPWSTR pszOut, DWORD dwOut); +HRESULT +Shell_NextElement( + _Inout_ LPWSTR *ppch, + _Out_ LPWSTR pszOut, + _In_ INT cchOut, + _In_ BOOL bValidate); + HRESULT SHELL32_ParseNextElement (IShellFolder2 * psf, HWND hwndOwner, LPBC pbc, LPITEMIDLIST * pidlInOut, LPOLESTR szNext, DWORD * pEaten, DWORD * pdwAttributes); HRESULT SHELL32_GetDisplayNameOfChild (IShellFolder2 * psf, LPCITEMIDLIST pidl, DWORD dwFlags, LPSTRRET strRet); -LPITEMIDLIST SHELL32_CreatePidlFromBindCtx(IBindCtx *pbc, LPCWSTR path); - HRESULT SHELL32_GetFSItemAttributes(IShellFolder * psf, LPCITEMIDLIST pidl, LPDWORD pdwAttributes); HRESULT SHELL32_CompareDetails(IShellFolder2* isf, LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2); diff --git a/dll/win32/shell32/shlfolder.cpp b/dll/win32/shell32/shlfolder.cpp index 283cf76ce8b..3a396e5fc60 100644 --- a/dll/win32/shell32/shlfolder.cpp +++ b/dll/win32/shell32/shlfolder.cpp @@ -26,45 +26,54 @@ WINE_DEFAULT_DEBUG_CHANNEL(shell); -/*************************************************************************** - * GetNextElement (internal function) - * - * Gets a part of a string till the first backslash. - * - * PARAMETERS - * pszNext [IN] string to get the element from - * pszOut [IN] pointer to buffer which receives string - * dwOut [IN] length of pszOut - * - * RETURNS - * LPSTR pointer to first, not yet parsed char - */ - -LPCWSTR GetNextElementW (LPCWSTR pszNext, LPWSTR pszOut, DWORD dwOut) +HRESULT +Shell_NextElement( + _Inout_ LPWSTR *ppch, + _Out_ LPWSTR pszOut, + _In_ INT cchOut, + _In_ BOOL bValidate) { - LPCWSTR pszTail = pszNext; - DWORD dwCopy; + *pszOut = UNICODE_NULL; - TRACE ("(%s %p 0x%08x)\n", debugstr_w (pszNext), pszOut, dwOut); + if (!*ppch) + return S_FALSE; - *pszOut = 0x0000; + HRESULT hr; + LPWSTR pchNext = wcschr(*ppch, L'\\'); + if (pchNext) + { + if (*ppch < pchNext) + { + /* Get an element */ + StringCchCopyNW(pszOut, cchOut, *ppch, pchNext - *ppch); + ++pchNext; - if (!pszNext || !*pszNext) - return NULL; + if (!*pchNext) + pchNext = NULL; /* No next */ - while (*pszTail && (*pszTail != (WCHAR) '\\')) - pszTail++; + hr = S_OK; + } + else /* Double backslashes found? */ + { + pchNext = NULL; + hr = E_INVALIDARG; + } + } + else /* No more next */ + { + StringCchCopyW(pszOut, cchOut, *ppch); + hr = S_OK; + } - dwCopy = pszTail - pszNext + 1; - lstrcpynW (pszOut, pszNext, (dwOut < dwCopy) ? dwOut : dwCopy); + *ppch = pchNext; /* Go next */ - if (*pszTail) - pszTail++; - else - pszTail = NULL; + if (hr == S_OK && bValidate && !PathIsValidElement(pszOut)) + { + *pszOut = UNICODE_NULL; + hr = E_INVALIDARG; + } - TRACE ("--(%s %s 0x%08x %p)\n", debugstr_w (pszNext), debugstr_w (pszOut), dwOut, pszTail); - return pszTail; + return hr; } HRESULT SHELL32_ParseNextElement (IShellFolder2 * psf, HWND hwndOwner, LPBC pbc, diff --git a/dll/win32/shell32/utils.cpp b/dll/win32/shell32/utils.cpp index 0909b235573..ae028bf6272 100644 --- a/dll/win32/shell32/utils.cpp +++ b/dll/win32/shell32/utils.cpp @@ -9,6 +9,68 @@ WINE_DEFAULT_DEBUG_CHANNEL(shell); +BOOL PathIsDotOrDotDotW(_In_ LPCWSTR pszPath) +{ + if (pszPath[0] != L'.') + return FALSE; + return !pszPath[1] || (pszPath[1] == L'.' && !pszPath[2]); +} + +#define PATH_VALID_ELEMENT ( \ + PATH_CHAR_CLASS_DOT | PATH_CHAR_CLASS_SEMICOLON | PATH_CHAR_CLASS_COMMA | \ + PATH_CHAR_CLASS_SPACE | PATH_CHAR_CLASS_OTHER_VALID \ +) + +BOOL PathIsValidElement(_In_ LPCWSTR pszPath) +{ + if (!*pszPath || PathIsDotOrDotDotW(pszPath)) + return FALSE; + + for (LPCWSTR pch = pszPath; *pch; ++pch) + { + if (!PathIsValidCharW(*pch, PATH_VALID_ELEMENT)) + return FALSE; + } + + return TRUE; +} + +BOOL PathIsDosDevice(_In_ LPCWSTR pszName) +{ + WCHAR szPath[MAX_PATH]; + StringCchCopyW(szPath, _countof(szPath), pszName); + PathRemoveExtensionW(szPath); + + if (lstrcmpiW(szPath, L"NUL") == 0 || lstrcmpiW(szPath, L"PRN") == 0 || + lstrcmpiW(szPath, L"CON") == 0 || lstrcmpiW(szPath, L"AUX") == 0) + { + return TRUE; + } + + if (_wcsnicmp(szPath, L"LPT", 3) == 0 || _wcsnicmp(szPath, L"COM", 3) == 0) + { + if ((L'0' <= szPath[3] && szPath[3] <= L'9') && szPath[4] == UNICODE_NULL) + return TRUE; + } + + return FALSE; +} + +HRESULT SHILAppend(_Inout_ LPITEMIDLIST pidl, _Inout_ LPITEMIDLIST *ppidl) +{ + LPITEMIDLIST pidlOld = *ppidl; + if (!pidlOld) + { + *ppidl = pidl; + return S_OK; + } + + HRESULT hr = SHILCombine(*ppidl, pidl, ppidl); + ILFree(pidlOld); + ILFree(pidl); + return hr; +} + static BOOL OpenEffectiveToken( _In_ DWORD DesiredAccess, @@ -49,6 +111,19 @@ BOOL BindCtx_ContainsObject(_In_ IBindCtx *pBindCtx, _In_ LPCWSTR pszName) return TRUE; } +DWORD BindCtx_GetMode(_In_ IBindCtx *pbc, _In_ DWORD dwDefault) +{ + if (!pbc) + return dwDefault; + + BIND_OPTS BindOpts = { sizeof(BindOpts) }; + HRESULT hr = pbc->GetBindOptions(&BindOpts); + if (FAILED(hr)) + return dwDefault; + + return BindOpts.grfMode; +} + BOOL SHSkipJunctionBinding(_In_ IBindCtx *pbc, _In_ CLSID *pclsid) { if (!pbc) @@ -61,7 +136,7 @@ BOOL SHSkipJunctionBinding(_In_ IBindCtx *pbc, _In_ CLSID *pclsid) return pclsid && SHSkipJunction(pbc, pclsid); } -HRESULT SHIsFileSysBindCtx(_In_ IBindCtx *pBindCtx, _Out_opt_ WIN32_FIND_DATAW **ppFindData) +HRESULT SHIsFileSysBindCtx(_In_ IBindCtx *pBindCtx, _Out_opt_ WIN32_FIND_DATAW *pFindData) { CComPtr punk; CComPtr pBindData; @@ -72,17 +147,10 @@ HRESULT SHIsFileSysBindCtx(_In_ IBindCtx *pBindCtx, _Out_opt_ WIN32_FIND_DATAW * if (FAILED(punk->QueryInterface(IID_PPV_ARG(IFileSystemBindData, &pBindData)))) return S_FALSE; - HRESULT hr = S_OK; - if (ppFindData) - { - *ppFindData = (WIN32_FIND_DATAW*)LocalAlloc(LPTR, sizeof(WIN32_FIND_DATAW)); - if (*ppFindData) - pBindData->GetFindData(*ppFindData); - else - hr = E_OUTOFMEMORY; - } + if (pFindData) + pBindData->GetFindData(pFindData); - return hr; + return S_OK; } BOOL Shell_FailForceReturn(_In_ HRESULT hr)