1 /*
2  * PROJECT:     ReactOS Search Shell Extension
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Search results folder
5  * COPYRIGHT:   Copyright 2019 Brock Mammen
6  */
7 
8 #include "CFindFolder.h"
9 #include <exdispid.h>
10 
11 WINE_DEFAULT_DEBUG_CHANNEL(shellfind);
12 
SHELL32_CoCreateInitSF(LPCITEMIDLIST pidlRoot,PERSIST_FOLDER_TARGET_INFO * ppfti,LPCITEMIDLIST pidlChild,const GUID * clsid,REFIID riid,LPVOID * ppvOut)13 static HRESULT SHELL32_CoCreateInitSF(LPCITEMIDLIST pidlRoot, PERSIST_FOLDER_TARGET_INFO* ppfti,
14                                 LPCITEMIDLIST pidlChild, const GUID* clsid, REFIID riid, LPVOID *ppvOut)
15 {
16     HRESULT hr;
17     CComPtr<IShellFolder> pShellFolder;
18 
19     hr = SHCoCreateInstance(NULL, clsid, NULL, IID_PPV_ARG(IShellFolder, &pShellFolder));
20     if (FAILED(hr))
21         return hr;
22 
23     LPITEMIDLIST pidlAbsolute = ILCombine (pidlRoot, pidlChild);
24     CComPtr<IPersistFolder> ppf;
25     CComPtr<IPersistFolder3> ppf3;
26 
27     if (ppfti && SUCCEEDED(pShellFolder->QueryInterface(IID_PPV_ARG(IPersistFolder3, &ppf3))))
28     {
29         ppf3->InitializeEx(NULL, pidlAbsolute, ppfti);
30     }
31     else if (SUCCEEDED(pShellFolder->QueryInterface(IID_PPV_ARG(IPersistFolder, &ppf))))
32     {
33         ppf->Initialize(pidlAbsolute);
34     }
35     ILFree (pidlAbsolute);
36 
37     return pShellFolder->QueryInterface(riid, ppvOut);
38 }
39 
_InsertMenuItemW(HMENU hMenu,UINT indexMenu,BOOL fByPosition,UINT wID,UINT fType,LPCWSTR dwTypeData,UINT fState)40 static void WINAPI _InsertMenuItemW(
41         HMENU hMenu,
42         UINT indexMenu,
43         BOOL fByPosition,
44         UINT wID,
45         UINT fType,
46         LPCWSTR dwTypeData,
47         UINT fState)
48 {
49     MENUITEMINFOW mii;
50     WCHAR wszText[100];
51 
52     ZeroMemory(&mii, sizeof(mii));
53     mii.cbSize = sizeof(mii);
54     if (fType == MFT_SEPARATOR)
55         mii.fMask = MIIM_ID | MIIM_TYPE;
56     else if (fType == MFT_STRING)
57     {
58         mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE;
59         if (IS_INTRESOURCE(dwTypeData))
60         {
61             if (LoadStringW(_AtlBaseModule.GetResourceInstance(), LOWORD((ULONG_PTR)dwTypeData), wszText, _countof(wszText)))
62                 mii.dwTypeData = wszText;
63             else
64             {
65                 ERR("failed to load string %p\n", dwTypeData);
66                 return;
67             }
68         }
69         else
70             mii.dwTypeData = (LPWSTR)dwTypeData;
71         mii.fState = fState;
72     }
73 
74     mii.wID = wID;
75     mii.fType = fType;
76     InsertMenuItemW(hMenu, indexMenu, fByPosition, &mii);
77 }
78 
79 struct FolderViewColumns
80 {
81     int iResource;
82     DWORD dwDefaultState;
83     int fmt;
84     int cxChar;
85 };
86 
87 static FolderViewColumns g_ColumnDefs[] =
88 {
89     {IDS_COL_NAME,      SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30},
90     {IDS_COL_LOCATION,  SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 30},
91     {IDS_COL_RELEVANCE, SHCOLSTATE_TYPE_STR,                          LVCFMT_LEFT, 0}
92 };
93 
CFindFolder()94 CFindFolder::CFindFolder() :
95     m_hStopEvent(NULL)
96 {
97 }
98 
_ILCreate(LPCWSTR lpszPath)99 static LPITEMIDLIST _ILCreate(LPCWSTR lpszPath)
100 {
101     CComHeapPtr<ITEMIDLIST> lpFSPidl(ILCreateFromPathW(lpszPath));
102     if (!lpFSPidl)
103     {
104         ERR("Failed to create pidl from path\n");
105         return NULL;
106     }
107     LPITEMIDLIST lpLastFSPidl = ILFindLastID(lpFSPidl);
108 
109     int pathLen = (PathFindFileNameW(lpszPath) - lpszPath) * sizeof(WCHAR);
110     int cbData = sizeof(WORD) + pathLen + lpLastFSPidl->mkid.cb;
111     LPITEMIDLIST pidl = (LPITEMIDLIST) SHAlloc(cbData + sizeof(WORD));
112     if (!pidl)
113         return NULL;
114 
115     LPBYTE p = (LPBYTE) pidl;
116     *((WORD *) p) = cbData;
117     p += sizeof(WORD);
118 
119     memcpy(p, lpszPath, pathLen);
120     p += pathLen - sizeof(WCHAR);
121     *((WCHAR *) p) = '\0';
122     p += sizeof(WCHAR);
123 
124     memcpy(p, lpLastFSPidl, lpLastFSPidl->mkid.cb);
125     p += lpLastFSPidl->mkid.cb;
126 
127     *((WORD *) p) = 0;
128 
129     return pidl;
130 }
131 
_ILGetPath(LPCITEMIDLIST pidl)132 static LPCWSTR _ILGetPath(LPCITEMIDLIST pidl)
133 {
134     if (!pidl || !pidl->mkid.cb)
135         return NULL;
136     return (LPCWSTR) pidl->mkid.abID;
137 }
138 
_ILGetFSPidl(LPCITEMIDLIST pidl)139 static LPCITEMIDLIST _ILGetFSPidl(LPCITEMIDLIST pidl)
140 {
141     if (!pidl || !pidl->mkid.cb)
142         return pidl;
143     return (LPCITEMIDLIST) ((LPBYTE) pidl->mkid.abID
144                             + ((wcslen((LPCWSTR) pidl->mkid.abID) + 1) * sizeof(WCHAR)));
145 }
146 
147 struct _SearchData
148 {
149     HWND hwnd;
150     HANDLE hStopEvent;
151     CStringW szPath;
152     CStringW szFileName;
153     CStringA szQueryA;
154     CStringW szQueryW;
155     CStringW szQueryU16BE;
156     CStringA szQueryU8;
157     BOOL SearchHidden;
158     CComPtr<CFindFolder> pFindFolder;
159 };
160 
161 template<typename TChar, typename TString, int (&StrNCmp)(const TChar *, const TChar *, size_t)>
StrStrN(const TChar * lpFirst,const TString & lpSrch,UINT cchMax)162 static const TChar* StrStrN(const TChar *lpFirst, const TString &lpSrch, UINT cchMax)
163 {
164     if (!lpFirst || lpSrch.IsEmpty() || !cchMax)
165         return NULL;
166 
167     for (UINT i = cchMax; i > 0 && *lpFirst; i--, lpFirst++)
168     {
169         if (!StrNCmp(lpFirst, lpSrch, lpSrch.GetLength()))
170             return (const TChar*)lpFirst;
171     }
172 
173     return NULL;
174 }
175 
176 static inline BOOL
StrFindNIA(const CHAR * lpFirst,const CStringA & lpSrch,UINT cchMax)177 StrFindNIA(const CHAR *lpFirst, const CStringA &lpSrch, UINT cchMax)
178 {
179     return StrStrN<CHAR, CStringA, _strnicmp>(lpFirst, lpSrch, cchMax) != NULL;
180 }
181 
182 static inline BOOL
StrFindNIW(const WCHAR * lpFirst,const CStringW & lpSrch,UINT cchMax)183 StrFindNIW(const WCHAR *lpFirst, const CStringW &lpSrch, UINT cchMax)
184 {
185     return StrStrN<WCHAR, CStringW, _wcsnicmp>(lpFirst, lpSrch, cchMax) != NULL;
186 }
187 
188 /*
189  * The following code is borrowed from base/applications/cmdutils/more/more.c .
190  */
191 typedef enum
192 {
193     ENCODING_ANSI    =  0,
194     ENCODING_UTF16LE =  1,
195     ENCODING_UTF16BE =  2,
196     ENCODING_UTF8    =  3
197 } ENCODING;
198 
199 static BOOL
IsDataUnicode(IN PVOID Buffer,IN DWORD BufferSize,OUT ENCODING * Encoding OPTIONAL,OUT PDWORD SkipBytes OPTIONAL)200 IsDataUnicode(
201     IN PVOID Buffer,
202     IN DWORD BufferSize,
203     OUT ENCODING* Encoding OPTIONAL,
204     OUT PDWORD SkipBytes OPTIONAL)
205 {
206     PBYTE pBytes = (PBYTE)Buffer;
207     ENCODING encFile = ENCODING_ANSI;
208     DWORD dwPos = 0;
209 
210     /*
211      * See http://archives.miloush.net/michkap/archive/2007/04/22/2239345.html
212      * for more details about the algorithm and the pitfalls behind it.
213      * Of course it would be actually great to make a nice function that
214      * would work, once and for all, and put it into a library.
215      */
216 
217     /* Look for Byte Order Marks */
218     if ((BufferSize >= 2) && (pBytes[0] == 0xFF) && (pBytes[1] == 0xFE))
219     {
220         encFile = ENCODING_UTF16LE;
221         dwPos = 2;
222     }
223     else if ((BufferSize >= 2) && (pBytes[0] == 0xFE) && (pBytes[1] == 0xFF))
224     {
225         encFile = ENCODING_UTF16BE;
226         dwPos = 2;
227     }
228     else if ((BufferSize >= 3) && (pBytes[0] == 0xEF) && (pBytes[1] == 0xBB) && (pBytes[2] == 0xBF))
229     {
230         encFile = ENCODING_UTF8;
231         dwPos = 3;
232     }
233     else
234     {
235         /*
236          * Try using statistical analysis. Do not rely on the return value of
237          * IsTextUnicode as we can get FALSE even if the text is in UTF-16 BE
238          * (i.e. we have some of the IS_TEXT_UNICODE_REVERSE_MASK bits set).
239          * Instead, set all the tests we want to perform, then just check
240          * the passed tests and try to deduce the string properties.
241          */
242 
243 /*
244  * This mask contains the 3 highest bits from IS_TEXT_UNICODE_NOT_ASCII_MASK
245  * and the 1st highest bit from IS_TEXT_UNICODE_NOT_UNICODE_MASK.
246  */
247 #define IS_TEXT_UNKNOWN_FLAGS_MASK  ((7 << 13) | (1 << 11))
248 
249         /* Flag out the unknown flags here, the passed tests will not have them either */
250         INT Tests = (IS_TEXT_UNICODE_NOT_ASCII_MASK   |
251                      IS_TEXT_UNICODE_NOT_UNICODE_MASK |
252                      IS_TEXT_UNICODE_REVERSE_MASK | IS_TEXT_UNICODE_UNICODE_MASK)
253                         & ~IS_TEXT_UNKNOWN_FLAGS_MASK;
254         INT Results;
255 
256         IsTextUnicode(Buffer, BufferSize, &Tests);
257         Results = Tests;
258 
259         /*
260          * As the IS_TEXT_UNICODE_NULL_BYTES or IS_TEXT_UNICODE_ILLEGAL_CHARS
261          * flags are expected to be potentially present in the result without
262          * modifying our expectations, filter them out now.
263          */
264         Results &= ~(IS_TEXT_UNICODE_NULL_BYTES | IS_TEXT_UNICODE_ILLEGAL_CHARS);
265 
266         /*
267          * NOTE: The flags IS_TEXT_UNICODE_ASCII16 and
268          * IS_TEXT_UNICODE_REVERSE_ASCII16 are not reliable.
269          *
270          * NOTE2: Check for potential "bush hid the facts" effect by also
271          * checking the original results (in 'Tests') for the absence of
272          * the IS_TEXT_UNICODE_NULL_BYTES flag, as we may presumably expect
273          * that in UTF-16 text there will be at some point some NULL bytes.
274          * If not, fall back to ANSI. This shows the limitations of using the
275          * IsTextUnicode API to perform such tests, and the usage of a more
276          * improved encoding detection algorithm would be really welcome.
277          */
278         if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) &&
279             !(Results & IS_TEXT_UNICODE_REVERSE_MASK)     &&
280              (Results & IS_TEXT_UNICODE_UNICODE_MASK)     &&
281              (Tests   & IS_TEXT_UNICODE_NULL_BYTES))
282         {
283             encFile = ENCODING_UTF16LE;
284             dwPos = (Results & IS_TEXT_UNICODE_SIGNATURE) ? 2 : 0;
285         }
286         else
287         if (!(Results & IS_TEXT_UNICODE_NOT_UNICODE_MASK) &&
288             !(Results & IS_TEXT_UNICODE_UNICODE_MASK)     &&
289              (Results & IS_TEXT_UNICODE_REVERSE_MASK)     &&
290              (Tests   & IS_TEXT_UNICODE_NULL_BYTES))
291         {
292             encFile = ENCODING_UTF16BE;
293             dwPos = (Results & IS_TEXT_UNICODE_REVERSE_SIGNATURE) ? 2 : 0;
294         }
295         else
296         {
297             /*
298              * Either 'Results' has neither of those masks set, as it can be
299              * the case for UTF-8 text (or ANSI), or it has both as can be the
300              * case when analysing pure binary data chunk. This is therefore
301              * invalid and we fall back to ANSI encoding.
302              * FIXME: In case of failure, assume ANSI (as long as we do not have
303              * correct tests for UTF8, otherwise we should do them, and at the
304              * very end, assume ANSI).
305              */
306             encFile = ENCODING_ANSI; // ENCODING_UTF8;
307             dwPos = 0;
308         }
309     }
310 
311     if (Encoding)
312         *Encoding = encFile;
313     if (SkipBytes)
314         *SkipBytes = dwPos;
315 
316     return (encFile != ENCODING_ANSI);
317 }
318 
SearchFile(LPCWSTR lpFilePath,_SearchData * pSearchData)319 static BOOL SearchFile(LPCWSTR lpFilePath, _SearchData *pSearchData)
320 {
321     HANDLE hFile = CreateFileW(lpFilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
322     if (hFile == INVALID_HANDLE_VALUE)
323         return FALSE;
324 
325     // FIXME: support large file
326     DWORD size = GetFileSize(hFile, NULL);
327     if (size == 0 || size == INVALID_FILE_SIZE)
328     {
329         CloseHandle(hFile);
330         return FALSE;
331     }
332 
333     HANDLE hFileMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, size, NULL);
334     CloseHandle(hFile);
335     if (hFileMap == INVALID_HANDLE_VALUE)
336         return FALSE;
337 
338     LPBYTE pbContents = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, size);
339     CloseHandle(hFileMap);
340     if (!pbContents)
341         return FALSE;
342 
343     ENCODING encoding;
344     IsDataUnicode(pbContents, size, &encoding, NULL);
345 
346     BOOL bFound;
347     switch (encoding)
348     {
349         case ENCODING_UTF16LE:
350             // UTF-16
351             bFound = StrFindNIW((LPCWSTR)pbContents, pSearchData->szQueryW, size / sizeof(WCHAR));
352             break;
353         case ENCODING_UTF16BE:
354             // UTF-16 BE
355             bFound = StrFindNIW((LPCWSTR)pbContents, pSearchData->szQueryU16BE, size / sizeof(WCHAR));
356             break;
357         case ENCODING_UTF8:
358             // UTF-8
359             bFound = StrFindNIA((LPCSTR)pbContents, pSearchData->szQueryU8, size / sizeof(CHAR));
360             break;
361         case ENCODING_ANSI:
362         default:
363             // ANSI or UTF-8 without BOM
364             bFound = StrFindNIA((LPCSTR)pbContents, pSearchData->szQueryA, size / sizeof(CHAR));
365             if (!bFound && pSearchData->szQueryA != pSearchData->szQueryU8)
366                 bFound = StrFindNIA((LPCSTR)pbContents, pSearchData->szQueryU8, size / sizeof(CHAR));
367             break;
368     }
369 
370     UnmapViewOfFile(pbContents);
371     return bFound;
372 }
373 
FileNameMatch(LPCWSTR FindDataFileName,_SearchData * pSearchData)374 static BOOL FileNameMatch(LPCWSTR FindDataFileName, _SearchData *pSearchData)
375 {
376     if (pSearchData->szFileName.IsEmpty() || PathMatchSpecW(FindDataFileName, pSearchData->szFileName))
377     {
378         return TRUE;
379     }
380     return FALSE;
381 }
382 
ContentsMatch(LPCWSTR szPath,_SearchData * pSearchData)383 static BOOL ContentsMatch(LPCWSTR szPath, _SearchData *pSearchData)
384 {
385     if (pSearchData->szQueryA.IsEmpty() || SearchFile(szPath, pSearchData))
386     {
387         return TRUE;
388     }
389     return FALSE;
390 }
391 
AttribHiddenMatch(DWORD FileAttributes,_SearchData * pSearchData)392 static BOOL AttribHiddenMatch(DWORD FileAttributes, _SearchData *pSearchData)
393 {
394     if (!(FileAttributes & FILE_ATTRIBUTE_HIDDEN) || (pSearchData->SearchHidden))
395     {
396         return TRUE;
397     }
398     return FALSE;
399 }
400 
RecursiveFind(LPCWSTR lpPath,_SearchData * pSearchData)401 static UINT RecursiveFind(LPCWSTR lpPath, _SearchData *pSearchData)
402 {
403     if (WaitForSingleObject(pSearchData->hStopEvent, 0) != WAIT_TIMEOUT)
404         return 0;
405 
406     WCHAR szPath[MAX_PATH];
407     WIN32_FIND_DATAW FindData;
408     HANDLE hFindFile;
409     BOOL bMoreFiles = TRUE;
410     UINT uTotalFound = 0;
411 
412     PathCombineW(szPath, lpPath, L"*");
413 
414     for (hFindFile = FindFirstFileW(szPath, &FindData);
415         bMoreFiles && hFindFile != INVALID_HANDLE_VALUE;
416         bMoreFiles = FindNextFileW(hFindFile, &FindData))
417     {
418 #define IS_DOTS(psz) ((psz)[0] == L'.' && ((psz)[1] == 0 || ((psz)[1] == L'.' && (psz)[2] == 0)))
419         if (IS_DOTS(FindData.cFileName))
420             continue;
421 
422         PathCombineW(szPath, lpPath, FindData.cFileName);
423 
424         if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
425         {
426             CStringW status;
427             if (pSearchData->szQueryW.IsEmpty() &&
428                 FileNameMatch(FindData.cFileName, pSearchData) &&
429                 AttribHiddenMatch(FindData.dwFileAttributes, pSearchData))
430             {
431                 LPWSTR pszPathDup;
432                 SHStrDupW(szPath, &pszPathDup);
433                 PostMessageW(pSearchData->hwnd, WM_SEARCH_ADD_RESULT, 0, (LPARAM)pszPathDup);
434                 uTotalFound++;
435             }
436             status.Format(IDS_SEARCH_FOLDER, FindData.cFileName);
437             LPWSTR pszStatusDup;
438             SHStrDupW(status.GetBuffer(), &pszStatusDup);
439             PostMessageW(pSearchData->hwnd, WM_SEARCH_UPDATE_STATUS, 0, (LPARAM)pszStatusDup);
440 
441             uTotalFound += RecursiveFind(szPath, pSearchData);
442         }
443         else if (FileNameMatch(FindData.cFileName, pSearchData)
444                 && AttribHiddenMatch(FindData.dwFileAttributes, pSearchData)
445                 && ContentsMatch(szPath, pSearchData))
446         {
447             uTotalFound++;
448             LPWSTR pszPathDup;
449             SHStrDupW(szPath, &pszPathDup);
450             PostMessageW(pSearchData->hwnd, WM_SEARCH_ADD_RESULT, 0, (LPARAM)pszPathDup);
451         }
452     }
453 
454     if (hFindFile != INVALID_HANDLE_VALUE)
455         FindClose(hFindFile);
456 
457     return uTotalFound;
458 }
459 
SearchThreadProc(LPVOID lpParameter)460 DWORD WINAPI CFindFolder::SearchThreadProc(LPVOID lpParameter)
461 {
462     _SearchData *data = static_cast<_SearchData*>(lpParameter);
463 
464     HRESULT hrCoInit = CoInitializeEx(NULL, COINIT_MULTITHREADED);
465 
466     data->pFindFolder->NotifyConnections(DISPID_SEARCHSTART);
467 
468     UINT uTotalFound = RecursiveFind(data->szPath, data);
469 
470     data->pFindFolder->NotifyConnections(DISPID_SEARCHCOMPLETE);
471 
472     CStringW status;
473     status.Format(IDS_SEARCH_FILES_FOUND, uTotalFound);
474     LPWSTR pszStatusDup;
475     SHStrDupW(status.GetBuffer(), &pszStatusDup);
476     ::PostMessageW(data->hwnd, WM_SEARCH_UPDATE_STATUS, 0, (LPARAM)pszStatusDup);
477     ::SendMessageW(data->hwnd, WM_SEARCH_STOP, 0, 0);
478 
479     CloseHandle(data->hStopEvent);
480     delete data;
481 
482     if (SUCCEEDED(hrCoInit))
483         CoUninitialize();
484 
485     return 0;
486 }
487 
NotifyConnections(DISPID id)488 void CFindFolder::NotifyConnections(DISPID id)
489 {
490     DISPPARAMS dispatchParams = {0};
491     CComDynamicUnkArray &subscribers =
492         IConnectionPointImpl<CFindFolder, &DIID_DSearchCommandEvents>::m_vec;
493     for (IUnknown** pSubscriber = subscribers.begin(); pSubscriber < subscribers.end(); pSubscriber++)
494     {
495         if (!*pSubscriber)
496             continue;
497 
498         CComPtr<IDispatch> pDispatch;
499         HRESULT hResult = (*pSubscriber)->QueryInterface(IID_PPV_ARG(IDispatch, &pDispatch));
500         if (!FAILED_UNEXPECTEDLY(hResult))
501             pDispatch->Invoke(id, GUID_NULL, 0, DISPATCH_METHOD, &dispatchParams, NULL, NULL, NULL);
502     }
503 }
504 
StartSearch(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)505 LRESULT CFindFolder::StartSearch(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
506 {
507     HKEY hkey;
508     DWORD size = sizeof(DWORD);
509     DWORD result;
510     DWORD SearchHiddenValue = 0;
511 
512     if (!lParam)
513         return 0;
514 
515     // Clear all previous search results
516     UINT uItemIndex;
517     m_shellFolderView->RemoveObject(NULL, &uItemIndex);
518 
519     _SearchData* pSearchData = new _SearchData();
520     pSearchData->pFindFolder = this;
521     pSearchData->hwnd = m_hWnd;
522 
523     SearchStart *pSearchParams = (SearchStart *) lParam;
524     pSearchData->szPath = pSearchParams->szPath;
525     pSearchData->szFileName = pSearchParams->szFileName;
526     pSearchData->szQueryA = pSearchParams->szQuery;
527     pSearchData->szQueryW = pSearchParams->szQuery;
528 
529     // UTF-16 BE
530     {
531         CStringW utf16 = pSearchData->szQueryW;
532         LPWSTR psz = utf16.GetBuffer();
533         for (SIZE_T i = 0; psz[i]; ++i)
534         {
535             psz[i] = MAKEWORD(HIBYTE(psz[i]), LOBYTE(psz[i]));
536         }
537         utf16.ReleaseBuffer();
538         pSearchData->szQueryU16BE = utf16;
539     }
540 
541     // UTF-8
542     {
543         CStringA utf8;
544         INT cch = WideCharToMultiByte(CP_UTF8, 0, pSearchData->szQueryW, -1, NULL, 0, NULL, NULL);
545         if (cch > 0)
546         {
547             LPSTR psz = utf8.GetBuffer(cch);
548             WideCharToMultiByte(CP_UTF8, 0, pSearchData->szQueryW, -1, psz, cch, NULL, NULL);
549             utf8.ReleaseBuffer();
550             pSearchData->szQueryU8 = utf8;
551         }
552         else
553         {
554             pSearchData->szQueryU8 = pSearchData->szQueryA;
555         }
556     }
557 
558     pSearchData->SearchHidden = pSearchParams->SearchHidden;
559     SHFree(pSearchParams);
560 
561     TRACE("pSearchParams->SearchHidden is '%d'.\n", pSearchData->SearchHidden);
562 
563     if (pSearchData->SearchHidden)
564         SearchHiddenValue = 1;
565     else
566         SearchHiddenValue = 0;
567 
568     /* Placing the code to save the changed settings to the registry here has the effect of not saving any changes */
569     /* to the registry unless the user clicks on the "Search" button. This is the same as what we see in Windows.  */
570     result = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer", 0, KEY_SET_VALUE, &hkey);
571     if (result == ERROR_SUCCESS)
572     {
573         if (RegSetValueExW(hkey, L"SearchHidden", NULL, REG_DWORD, (const BYTE*)&SearchHiddenValue, size) == ERROR_SUCCESS)
574         {
575             TRACE("SearchHidden is '%d'.\n", SearchHiddenValue);
576         }
577         else
578         {
579             ERR("RegSetValueEx for \"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SearchHidden\" Failed.\n");
580         }
581         RegCloseKey(hkey);
582     }
583     else
584     {
585         ERR("RegOpenKey for \"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\" Failed.\n");
586     }
587 
588     if (m_hStopEvent)
589         SetEvent(m_hStopEvent);
590     pSearchData->hStopEvent = m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
591 
592     if (!SHCreateThread(SearchThreadProc, pSearchData, NULL, NULL))
593     {
594         SHFree(pSearchData);
595         return 0;
596     }
597 
598     return 0;
599 }
600 
StopSearch(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)601 LRESULT CFindFolder::StopSearch(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
602 {
603     if (m_hStopEvent)
604     {
605         SetEvent(m_hStopEvent);
606         m_hStopEvent = NULL;
607     }
608     return 0;
609 }
610 
AddResult(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)611 LRESULT CFindFolder::AddResult(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
612 {
613     if (!lParam)
614         return 0;
615 
616     CComHeapPtr<WCHAR> lpPath((LPWSTR) lParam);
617 
618     CComHeapPtr<ITEMIDLIST> lpSearchPidl(_ILCreate(lpPath));
619     if (lpSearchPidl)
620     {
621         UINT uItemIndex;
622         m_shellFolderView->AddObject(lpSearchPidl, &uItemIndex);
623     }
624 
625     return 0;
626 }
627 
UpdateStatus(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)628 LRESULT CFindFolder::UpdateStatus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
629 {
630     CComHeapPtr<WCHAR> status((LPWSTR) lParam);
631     if (m_shellBrowser)
632     {
633         m_shellBrowser->SetStatusTextSB(status);
634     }
635 
636     return 0;
637 }
638 
639 // *** IShellFolder2 methods ***
GetDefaultSearchGUID(GUID * pguid)640 STDMETHODIMP CFindFolder::GetDefaultSearchGUID(GUID *pguid)
641 {
642     UNIMPLEMENTED;
643     return E_NOTIMPL;
644 }
645 
EnumSearches(IEnumExtraSearch ** ppenum)646 STDMETHODIMP CFindFolder::EnumSearches(IEnumExtraSearch **ppenum)
647 {
648     UNIMPLEMENTED;
649     return E_NOTIMPL;
650 }
651 
GetDefaultColumn(DWORD,ULONG * pSort,ULONG * pDisplay)652 STDMETHODIMP CFindFolder::GetDefaultColumn(DWORD, ULONG *pSort, ULONG *pDisplay)
653 {
654     if (pSort)
655         *pSort = 0;
656     if (pDisplay)
657         *pDisplay = 0;
658     return S_OK;
659 }
660 
GetDefaultColumnState(UINT iColumn,DWORD * pcsFlags)661 STDMETHODIMP CFindFolder::GetDefaultColumnState(UINT iColumn, DWORD *pcsFlags)
662 {
663     if (!pcsFlags)
664         return E_INVALIDARG;
665     if (iColumn >= _countof(g_ColumnDefs))
666         return m_pisfInner->GetDefaultColumnState(iColumn - _countof(g_ColumnDefs) + 1, pcsFlags);
667     *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState;
668     return S_OK;
669 }
670 
GetDetailsEx(PCUITEMID_CHILD pidl,const SHCOLUMNID * pscid,VARIANT * pv)671 STDMETHODIMP CFindFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
672 {
673     return m_pisfInner->GetDetailsEx(pidl, pscid, pv);
674 }
675 
GetDetailsOf(PCUITEMID_CHILD pidl,UINT iColumn,SHELLDETAILS * pDetails)676 STDMETHODIMP CFindFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *pDetails)
677 {
678     if (iColumn >= _countof(g_ColumnDefs))
679         return m_pisfInner->GetDetailsOf(_ILGetFSPidl(pidl), iColumn - _countof(g_ColumnDefs) + 1, pDetails);
680 
681     pDetails->cxChar = g_ColumnDefs[iColumn].cxChar;
682     pDetails->fmt = g_ColumnDefs[iColumn].fmt;
683 
684     if (!pidl)
685         return SHSetStrRet(&pDetails->str, _AtlBaseModule.GetResourceInstance(), g_ColumnDefs[iColumn].iResource);
686 
687     if (iColumn == COL_LOCATION_INDEX)
688     {
689         return SHSetStrRet(&pDetails->str, _ILGetPath(pidl));
690     }
691 
692     if (iColumn == COL_RELEVANCE_INDEX)
693     {
694         // TODO: Fill once the relevance is calculated
695         return SHSetStrRet(&pDetails->str, "");
696     }
697 
698     return GetDisplayNameOf(pidl, SHGDN_NORMAL, &pDetails->str);
699 }
700 
MapColumnToSCID(UINT iColumn,SHCOLUMNID * pscid)701 STDMETHODIMP CFindFolder::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
702 {
703     UNIMPLEMENTED;
704     return E_NOTIMPL;
705 }
706 
707 // *** IShellFolder methods ***
ParseDisplayName(HWND hwndOwner,LPBC pbc,LPOLESTR lpszDisplayName,ULONG * pchEaten,PIDLIST_RELATIVE * ppidl,ULONG * pdwAttributes)708 STDMETHODIMP CFindFolder::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, ULONG *pchEaten,
709                                            PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes)
710 {
711     UNIMPLEMENTED;
712     return E_NOTIMPL;
713 }
714 
EnumObjects(HWND hwndOwner,DWORD dwFlags,LPENUMIDLIST * ppEnumIDList)715 STDMETHODIMP CFindFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
716 {
717     *ppEnumIDList = NULL;
718     return S_FALSE;
719 }
720 
BindToObject(PCUIDLIST_RELATIVE pidl,LPBC pbcReserved,REFIID riid,LPVOID * ppvOut)721 STDMETHODIMP CFindFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
722 {
723     UNIMPLEMENTED;
724     return E_NOTIMPL;
725 }
726 
BindToStorage(PCUIDLIST_RELATIVE pidl,LPBC pbcReserved,REFIID riid,LPVOID * ppvOut)727 STDMETHODIMP CFindFolder::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
728 {
729     UNIMPLEMENTED;
730     return E_NOTIMPL;
731 }
732 
CompareIDs(LPARAM lParam,PCUIDLIST_RELATIVE pidl1,PCUIDLIST_RELATIVE pidl2)733 STDMETHODIMP CFindFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
734 {
735     WORD wColumn = LOWORD(lParam);
736     switch (wColumn)
737     {
738     case COL_NAME_INDEX: // Name
739         break;
740     case COL_LOCATION_INDEX: // Path
741         return MAKE_COMPARE_HRESULT(StrCmpW(_ILGetPath(pidl1), _ILGetPath(pidl2)));
742     case COL_RELEVANCE_INDEX: // Relevance
743         return E_NOTIMPL;
744     default: // Default columns
745         wColumn -= _countof(g_ColumnDefs) - 1;
746         break;
747     }
748     return m_pisfInner->CompareIDs(HIWORD(lParam) | wColumn, _ILGetFSPidl(pidl1), _ILGetFSPidl(pidl2));
749 }
750 
CreateViewObject(HWND hwndOwner,REFIID riid,LPVOID * ppvOut)751 STDMETHODIMP CFindFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut)
752 {
753     if (riid == IID_IShellView)
754     {
755         SFV_CREATE sfvparams = {};
756         sfvparams.cbSize = sizeof(SFV_CREATE);
757         sfvparams.pshf = this;
758         sfvparams.psfvcb = this;
759         HRESULT hr = SHCreateShellFolderView(&sfvparams, (IShellView **) ppvOut);
760         if (FAILED_UNEXPECTEDLY(hr))
761         {
762             return hr;
763         }
764 
765         return ((IShellView * ) * ppvOut)->QueryInterface(IID_PPV_ARG(IShellFolderView, &m_shellFolderView));
766     }
767     return E_NOINTERFACE;
768 }
769 
GetAttributesOf(UINT cidl,PCUITEMID_CHILD_ARRAY apidl,DWORD * rgfInOut)770 STDMETHODIMP CFindFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut)
771 {
772     CComHeapPtr<PCITEMID_CHILD> aFSPidl;
773     aFSPidl.Allocate(cidl);
774     for (UINT i = 0; i < cidl; i++)
775     {
776         aFSPidl[i] = _ILGetFSPidl(apidl[i]);
777     }
778 
779     return m_pisfInner->GetAttributesOf(cidl, aFSPidl, rgfInOut);
780 }
781 
782 class CFindFolderContextMenu :
783         public IContextMenu,
784         public CComObjectRootEx<CComMultiThreadModelNoCS>
785 {
786     CComPtr<IContextMenu> m_pInner;
787     CComPtr<IShellFolderView> m_shellFolderView;
788     UINT m_firstCmdId;
789     static const UINT ADDITIONAL_MENU_ITEMS = 2;
790 
791     //// *** IContextMenu methods ***
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)792     STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
793     {
794         m_firstCmdId = indexMenu;
795         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst++, MFT_STRING, MAKEINTRESOURCEW(IDS_SEARCH_OPEN_FOLDER), MFS_ENABLED);
796         _InsertMenuItemW(hMenu, indexMenu++, TRUE, idCmdFirst++, MFT_SEPARATOR, NULL, 0);
797         return m_pInner->QueryContextMenu(hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
798     }
799 
InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)800     STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
801     {
802         if (!IS_INTRESOURCE(lpcmi->lpVerb))
803         {
804             return m_pInner->InvokeCommand(lpcmi);
805         }
806 
807         if (LOWORD(lpcmi->lpVerb) < m_firstCmdId + ADDITIONAL_MENU_ITEMS)
808         {
809             PCUITEMID_CHILD *apidl;
810             UINT cidl;
811             HRESULT hResult = m_shellFolderView->GetSelectedObjects(&apidl, &cidl);
812             if (FAILED_UNEXPECTEDLY(hResult))
813                 return hResult;
814 
815             for (UINT i = 0; i < cidl; i++)
816             {
817                 CComHeapPtr<ITEMIDLIST> folderPidl(ILCreateFromPathW(_ILGetPath(apidl[i])));
818                 if (!folderPidl)
819                     return E_OUTOFMEMORY;
820                 LPCITEMIDLIST child = _ILGetFSPidl(apidl[i]);
821                 SHOpenFolderAndSelectItems(folderPidl, 1, &child, 0);
822             }
823             LocalFree(apidl); // Yes, LocalFree
824             return S_OK;
825         }
826 
827         CMINVOKECOMMANDINFOEX actualCmdInfo;
828         memcpy(&actualCmdInfo, lpcmi, lpcmi->cbSize);
829         actualCmdInfo.lpVerb -= ADDITIONAL_MENU_ITEMS;
830         return m_pInner->InvokeCommand((CMINVOKECOMMANDINFO *)&actualCmdInfo);
831     }
832 
GetCommandString(UINT_PTR idCommand,UINT uFlags,UINT * lpReserved,LPSTR lpszName,UINT uMaxNameLen)833     STDMETHODIMP GetCommandString(UINT_PTR idCommand, UINT uFlags, UINT *lpReserved, LPSTR lpszName, UINT uMaxNameLen)
834     {
835         return m_pInner->GetCommandString(idCommand, uFlags, lpReserved, lpszName, uMaxNameLen);
836     }
837 
838 public:
Create(IShellFolderView * pShellFolderView,IContextMenu * pInnerContextMenu,IContextMenu ** pContextMenu)839     static HRESULT Create(IShellFolderView *pShellFolderView, IContextMenu *pInnerContextMenu, IContextMenu **pContextMenu)
840     {
841         CComObject<CFindFolderContextMenu> *pObj;
842         HRESULT hResult = CComObject<CFindFolderContextMenu>::CreateInstance(&pObj);
843         if (FAILED_UNEXPECTEDLY(hResult))
844             return hResult;
845         pObj->m_shellFolderView = pShellFolderView;
846         pObj->m_pInner = pInnerContextMenu;
847         return pObj->QueryInterface(IID_PPV_ARG(IContextMenu, pContextMenu));
848     }
849 
850     BEGIN_COM_MAP(CFindFolderContextMenu)
851         COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
852     END_COM_MAP()
853 };
854 
GetUIObjectOf(HWND hwndOwner,UINT cidl,PCUITEMID_CHILD_ARRAY apidl,REFIID riid,UINT * prgfInOut,LPVOID * ppvOut)855 STDMETHODIMP CFindFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid,
856                                         UINT *prgfInOut, LPVOID *ppvOut)
857 {
858     if (cidl <= 0)
859     {
860         return m_pisfInner->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, ppvOut);
861     }
862 
863     CComHeapPtr<PCITEMID_CHILD> aFSPidl;
864     aFSPidl.Allocate(cidl);
865     for (UINT i = 0; i < cidl; i++)
866     {
867         aFSPidl[i] = _ILGetFSPidl(apidl[i]);
868     }
869 
870     if (riid == IID_IContextMenu)
871     {
872         CComHeapPtr<ITEMIDLIST> folderPidl(ILCreateFromPathW(_ILGetPath(apidl[0])));
873         if (!folderPidl)
874             return E_OUTOFMEMORY;
875         CComPtr<IShellFolder> pDesktopFolder;
876         HRESULT hResult = SHGetDesktopFolder(&pDesktopFolder);
877         if (FAILED_UNEXPECTEDLY(hResult))
878             return hResult;
879         CComPtr<IShellFolder> pShellFolder;
880         hResult = pDesktopFolder->BindToObject(folderPidl, NULL, IID_PPV_ARG(IShellFolder, &pShellFolder));
881         if (FAILED_UNEXPECTEDLY(hResult))
882             return hResult;
883         CComPtr<IContextMenu> pContextMenu;
884         hResult = pShellFolder->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, (LPVOID *)&pContextMenu);
885         if (FAILED_UNEXPECTEDLY(hResult))
886             return hResult;
887         return CFindFolderContextMenu::Create(m_shellFolderView, pContextMenu, (IContextMenu **)ppvOut);
888     }
889 
890     return m_pisfInner->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, ppvOut);
891 }
892 
GetDisplayNameOf(PCUITEMID_CHILD pidl,DWORD dwFlags,LPSTRRET pName)893 STDMETHODIMP CFindFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName)
894 {
895     return m_pisfInner->GetDisplayNameOf(_ILGetFSPidl(pidl), dwFlags, pName);
896 }
897 
SetNameOf(HWND hwndOwner,PCUITEMID_CHILD pidl,LPCOLESTR lpName,DWORD dwFlags,PITEMID_CHILD * pPidlOut)898 STDMETHODIMP CFindFolder::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags,
899                                     PITEMID_CHILD *pPidlOut)
900 {
901     UNIMPLEMENTED;
902     return E_NOTIMPL;
903 }
904 
905 //// *** IShellFolderViewCB method ***
MessageSFVCB(UINT uMsg,WPARAM wParam,LPARAM lParam)906 STDMETHODIMP CFindFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam)
907 {
908     switch (uMsg)
909     {
910         case SFVM_DEFVIEWMODE:
911         {
912             FOLDERVIEWMODE *pViewMode = (FOLDERVIEWMODE *) lParam;
913             *pViewMode = FVM_DETAILS;
914             return S_OK;
915         }
916         case SFVM_WINDOWCREATED:
917         {
918             // Subclass window to receive window messages
919             SubclassWindow((HWND) wParam);
920 
921             // Get shell browser for updating status bar text
922             CComPtr<IServiceProvider> pServiceProvider;
923             HRESULT hr = m_shellFolderView->QueryInterface(IID_PPV_ARG(IServiceProvider, &pServiceProvider));
924             if (FAILED_UNEXPECTEDLY(hr))
925                 return hr;
926             hr = pServiceProvider->QueryService(SID_SShellBrowser, IID_PPV_ARG(IShellBrowser, &m_shellBrowser));
927             if (FAILED_UNEXPECTEDLY(hr))
928                 return hr;
929 
930             // Open search bar
931             CComPtr<IWebBrowser2> pWebBrowser2;
932             hr = m_shellBrowser->QueryInterface(IID_PPV_ARG(IWebBrowser2, &pWebBrowser2));
933             if (FAILED_UNEXPECTEDLY(hr))
934                 return hr;
935             WCHAR pwszGuid[MAX_PATH];
936             StringFromGUID2(CLSID_FileSearchBand, pwszGuid, _countof(pwszGuid));
937             CComVariant searchBar(pwszGuid);
938             return pWebBrowser2->ShowBrowserBar(&searchBar, NULL, NULL);
939         }
940         case SFVM_GETCOMMANDDIR:
941         {
942             HRESULT hr = E_FAIL;
943             if (m_shellFolderView)
944             {
945                 PCUITEMID_CHILD *apidl;
946                 UINT cidl = 0;
947                 if (SUCCEEDED(hr = m_shellFolderView->GetSelectedObjects(&apidl, &cidl)))
948                 {
949                     if (cidl)
950                         hr = StringCchCopyW((PWSTR)lParam, wParam, _ILGetPath(apidl[0]));
951                     LocalFree(apidl);
952                 }
953             }
954             return hr;
955         }
956     }
957     return E_NOTIMPL;
958 }
959 
960 //// *** IPersistFolder2 methods ***
GetCurFolder(PIDLIST_ABSOLUTE * pidl)961 STDMETHODIMP CFindFolder::GetCurFolder(PIDLIST_ABSOLUTE *pidl)
962 {
963     *pidl = ILClone(m_pidl);
964     return S_OK;
965 }
966 
967 // *** IPersistFolder methods ***
Initialize(PCIDLIST_ABSOLUTE pidl)968 STDMETHODIMP CFindFolder::Initialize(PCIDLIST_ABSOLUTE pidl)
969 {
970     m_pidl = ILClone(pidl);
971     if (!m_pidl)
972         return E_OUTOFMEMORY;
973 
974     return SHELL32_CoCreateInitSF(m_pidl,
975                                   NULL,
976                                   NULL,
977                                   &CLSID_ShellFSFolder,
978                                   IID_PPV_ARG(IShellFolder2, &m_pisfInner));
979 }
980 
981 // *** IPersist methods ***
GetClassID(CLSID * pClassId)982 STDMETHODIMP CFindFolder::GetClassID(CLSID *pClassId)
983 {
984     if (pClassId == NULL)
985         return E_INVALIDARG;
986     *pClassId = CLSID_FindFolder;
987     return S_OK;
988 }
989