1 /*
2  * PROJECT:     NT Object Namespace shell extension
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     NT Object Namespace folder class implementation
5  * COPYRIGHT:   Copyright 2015-2017 David Quintana <gigaherz@gmail.com>
6  */
7 
8 #include "precomp.h"
9 
10 #include <wine/unicode.h>
11 
12 // {845B0FB2-66E0-416B-8F91-314E23F7C12D}
13 const GUID CLSID_NtObjectFolder = { 0x845b0fb2, 0x66e0, 0x416b, { 0x8f, 0x91, 0x31, 0x4e, 0x23, 0xf7, 0xc1, 0x2d } };
14 
15 // {F4C430C3-3A8D-4B56-A018-E598DA60C2E0}
16 static const GUID GUID_NtObjectColumns = { 0xf4c430c3, 0x3a8d, 0x4b56, { 0xa0, 0x18, 0xe5, 0x98, 0xda, 0x60, 0xc2, 0xe0 } };
17 
18 enum NtObjectColumns
19 {
20     NTOBJECT_COLUMN_NAME = 0,
21     NTOBJECT_COLUMN_TYPE,
22     NTOBJECT_COLUMN_LINKTARGET,
23     NTOBJECT_COLUMN_END
24 };
25 
26 CNtObjectFolderExtractIcon::CNtObjectFolderExtractIcon() :
27     m_NtPath(NULL),
28     m_pcidlChild(NULL)
29 {
30 
31 }
32 
33 CNtObjectFolderExtractIcon::~CNtObjectFolderExtractIcon()
34 {
35     if (m_pcidlChild)
36         ILFree((LPITEMIDLIST) m_pcidlChild);
37 }
38 
39 HRESULT CNtObjectFolderExtractIcon::Initialize(LPCWSTR ntPath, PCIDLIST_ABSOLUTE parent, UINT cidl, PCUITEMID_CHILD_ARRAY apidl)
40 {
41     m_NtPath = ntPath;
42     if (cidl != 1)
43         return E_INVALIDARG;
44     m_pcidlChild = ILClone(apidl[0]);
45     return S_OK;
46 }
47 
48 HRESULT STDMETHODCALLTYPE CNtObjectFolderExtractIcon::GetIconLocation(
49     UINT uFlags,
50     LPWSTR szIconFile,
51     UINT cchMax,
52     INT *piIndex,
53     UINT *pwFlags)
54 {
55     const NtPidlEntry * entry = (NtPidlEntry *) m_pcidlChild;
56 
57     if ((entry->cb < sizeof(NtPidlEntry)) || (entry->magic != NT_OBJECT_PIDL_MAGIC))
58         return E_INVALIDARG;
59 
60     UINT flags = 0;
61 
62     switch (entry->objectType)
63     {
64         case DIRECTORY_OBJECT:
65         case SYMBOLICLINK_OBJECT:
66             GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
67             *piIndex = -((uFlags & GIL_OPENICON) ? IDI_NTOBJECTDIROPEN : IDI_NTOBJECTDIR);
68             *pwFlags = flags;
69             return S_OK;
70 
71         case DEVICE_OBJECT:
72             GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
73             *piIndex = -IDI_NTOBJECTDEVICE;
74             *pwFlags = flags;
75             return S_OK;
76 
77         case PORT_OBJECT:
78             GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
79             *piIndex = -IDI_NTOBJECTPORT;
80             *pwFlags = flags;
81             return S_OK;
82 
83         case KEY_OBJECT:
84             GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
85             *piIndex = -IDI_REGISTRYKEY;
86             *pwFlags = flags;
87             return S_OK;
88 
89         default:
90             GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
91             *piIndex = -IDI_NTOBJECTITEM;
92             *pwFlags = flags;
93             return S_OK;
94     }
95 }
96 
97 HRESULT STDMETHODCALLTYPE CNtObjectFolderExtractIcon::Extract(
98     LPCWSTR pszFile,
99     UINT nIconIndex,
100     HICON *phiconLarge,
101     HICON *phiconSmall,
102     UINT nIconSize)
103 {
104     return SHDefExtractIconW(pszFile, nIconIndex, 0, phiconLarge, phiconSmall, nIconSize);
105 }
106 
107 //-----------------------------------------------------------------------------
108 // CNtObjectFolder
109 
110 CNtObjectFolder::CNtObjectFolder()
111 {
112 }
113 
114 CNtObjectFolder::~CNtObjectFolder()
115 {
116 }
117 
118 // IShellFolder
119 
120 HRESULT STDMETHODCALLTYPE CNtObjectFolder::EnumObjects(
121     HWND hwndOwner,
122     SHCONTF grfFlags,
123     IEnumIDList **ppenumIDList)
124 {
125     return GetEnumNTDirectory(m_NtPath, ppenumIDList);
126 }
127 
128 BOOL STDMETHODCALLTYPE CNtObjectFolder::IsSymLink(const NtPidlEntry * info)
129 {
130     return info->objectType == SYMBOLICLINK_OBJECT;
131 }
132 
133 HRESULT STDMETHODCALLTYPE CNtObjectFolder::ResolveSymLink(
134     const NtPidlEntry * info,
135     LPITEMIDLIST * fullPidl)
136 {
137     WCHAR wbLink[MAX_PATH] = { 0 };
138     UNICODE_STRING link;
139     RtlInitEmptyUnicodeString(&link, wbLink, sizeof(wbLink));
140 
141     *fullPidl = NULL;
142 
143     HRESULT hr = GetNTObjectSymbolicLinkTarget(m_NtPath, info->entryName, &link);
144     if (FAILED_UNEXPECTEDLY(hr))
145         return hr;
146 
147     WCHAR path[MAX_PATH];
148 
149     if (link.Length == 0)
150         return E_UNEXPECTED;
151 
152     if (link.Buffer[1] == L':' && isalphaW(link.Buffer[0]))
153     {
154         StringCbCopyNW(path, sizeof(path), link.Buffer, link.Length);
155 
156         CComPtr<IShellFolder> psfDesktop;
157         hr = SHGetDesktopFolder(&psfDesktop);
158         if (FAILED_UNEXPECTEDLY(hr))
159             return hr;
160 
161         return psfDesktop->ParseDisplayName(NULL, NULL, path, NULL, fullPidl, NULL);
162     }
163 
164     StringCbCopyW(path, sizeof(path), L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{845B0FB2-66E0-416B-8F91-314E23F7C12D}");
165     PathAppend(path, link.Buffer);
166 
167     CComPtr<IShellFolder> psfDesktop;
168     hr = SHGetDesktopFolder(&psfDesktop);
169     if (FAILED_UNEXPECTEDLY(hr))
170         return hr;
171 
172     LPITEMIDLIST pidl;
173 
174     hr = psfDesktop->ParseDisplayName(NULL, NULL, path, NULL, &pidl, NULL);
175     if (FAILED(hr))
176         return hr;
177 
178     *fullPidl = pidl;
179 
180     DumpIdList(pidl);
181 
182     return S_OK;
183 }
184 
185 HRESULT STDMETHODCALLTYPE CNtObjectFolder::InternalBindToObject(
186     PWSTR path,
187     const NtPidlEntry * info,
188     LPITEMIDLIST first,
189     LPCITEMIDLIST rest,
190     LPITEMIDLIST fullPidl,
191     LPBC pbcReserved,
192     IShellFolder** ppsfChild)
193 {
194 
195     if (info->objectType == KEY_OBJECT)
196     {
197         return ShellObjectCreatorInit<CRegistryFolder>(fullPidl, path, (HKEY) NULL, IID_PPV_ARG(IShellFolder, ppsfChild));
198     }
199 
200     return ShellObjectCreatorInit<CNtObjectFolder>(fullPidl, path, IID_PPV_ARG(IShellFolder, ppsfChild));
201 }
202 
203 // IPersistFolder
204 HRESULT STDMETHODCALLTYPE CNtObjectFolder::Initialize(PCIDLIST_ABSOLUTE pidl)
205 {
206     m_shellPidl = ILClone(pidl);
207 
208     StringCbCopyW(m_NtPath, sizeof(m_NtPath), L"\\");
209 
210     return S_OK;
211 }
212 
213 // Internal
214 HRESULT STDMETHODCALLTYPE CNtObjectFolder::Initialize(PCIDLIST_ABSOLUTE pidl, PCWSTR ntPath)
215 {
216     m_shellPidl = ILClone(pidl);
217 
218     StringCbCopyW(m_NtPath, sizeof(m_NtPath), ntPath);
219 
220     return S_OK;
221 }
222 
223 HRESULT STDMETHODCALLTYPE CNtObjectFolder::GetDefaultColumnState(
224     UINT iColumn,
225     SHCOLSTATEF *pcsFlags)
226 {
227     switch (iColumn)
228     {
229         case NTOBJECT_COLUMN_NAME:
230             *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT;
231             return S_OK;
232 
233         case NTOBJECT_COLUMN_TYPE:
234             *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT;
235             return S_OK;
236 
237         case NTOBJECT_COLUMN_LINKTARGET:
238             *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_SLOW;
239             return S_OK;
240     }
241 
242     return E_INVALIDARG;
243 }
244 
245 HRESULT STDMETHODCALLTYPE CNtObjectFolder::GetDetailsEx(
246     LPCITEMIDLIST pidl,
247     const SHCOLUMNID *pscid,
248     VARIANT *pv)
249 {
250     const NtPidlEntry * info;
251 
252     TRACE("GetDetailsEx\n");
253 
254     if (pidl)
255     {
256         HRESULT hr = GetInfoFromPidl(pidl, &info);
257         if (FAILED_UNEXPECTEDLY(hr))
258             return hr;
259 
260         static const GUID storage = PSGUID_STORAGE;
261         if (IsEqualGUID(pscid->fmtid, storage))
262         {
263             if (pscid->pid == PID_STG_NAME)
264             {
265                 return MakeVariantString(pv, info->entryName);
266             }
267             else if (pscid->pid == PID_STG_STORAGETYPE)
268             {
269                 if (info->objectType < 0)
270                 {
271                     NtPidlTypeData * td = (NtPidlTypeData*) (((PBYTE) info) + FIELD_OFFSET(NtPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR));
272 
273                     if (td->typeNameLength > 0)
274                     {
275                         return MakeVariantString(pv, td->typeName);
276                     }
277                     else
278                     {
279                         return MakeVariantString(pv, L"Unknown");
280                     }
281                 }
282                 else
283                 {
284                     return MakeVariantString(pv, ObjectTypeNames[info->objectType]);
285                 }
286             }
287         }
288         else if (IsEqualGUID(pscid->fmtid, GUID_NtObjectColumns))
289         {
290             if (pscid->pid == NTOBJECT_COLUMN_LINKTARGET && info->objectType == SYMBOLICLINK_OBJECT)
291             {
292                 WCHAR wbLink[MAX_PATH] = { 0 };
293                 UNICODE_STRING link;
294                 RtlInitEmptyUnicodeString(&link, wbLink, sizeof(wbLink));
295 
296                 HRESULT hr = GetNTObjectSymbolicLinkTarget(m_NtPath, info->entryName, &link);
297 
298                 if (!FAILED_UNEXPECTEDLY(hr) && link.Length > 0)
299                 {
300                     return MakeVariantString(pv, link.Buffer);
301                 }
302             }
303 
304             V_VT(pv) = VT_EMPTY;
305             return S_OK;
306         }
307     }
308 
309     return E_INVALIDARG;
310 }
311 
312 HRESULT STDMETHODCALLTYPE CNtObjectFolder::GetDetailsOf(
313     LPCITEMIDLIST pidl,
314     UINT iColumn,
315     SHELLDETAILS *psd)
316 {
317     const NtPidlEntry * info;
318 
319     TRACE("GetDetailsOf\n");
320 
321     if (pidl)
322     {
323         HRESULT hr = GetInfoFromPidl(pidl, &info);
324         if (FAILED_UNEXPECTEDLY(hr))
325             return hr;
326 
327         switch (iColumn)
328         {
329             case NTOBJECT_COLUMN_NAME:
330             {
331                 psd->fmt = LVCFMT_LEFT;
332 
333                 MakeStrRetFromString(info->entryName, info->entryNameLength, &(psd->str));
334                 return S_OK;
335             }
336 
337             case NTOBJECT_COLUMN_TYPE:
338             {
339                 psd->fmt = LVCFMT_LEFT;
340 
341                 if (info->objectType < 0)
342                 {
343                     NtPidlTypeData * td = (NtPidlTypeData*) (((PBYTE) info) + FIELD_OFFSET(NtPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR));
344 
345                     if (td->typeNameLength > 0)
346                         MakeStrRetFromString(td->typeName, td->typeNameLength, &(psd->str));
347                     else
348                         MakeStrRetFromString(L"Unknown", &(psd->str));
349                 }
350                 else
351                     MakeStrRetFromString(ObjectTypeNames[info->objectType], &(psd->str));
352                 return S_OK;
353             }
354 
355             case NTOBJECT_COLUMN_LINKTARGET:
356             {
357                 psd->fmt = LVCFMT_LEFT;
358 
359                 if (info->objectType == SYMBOLICLINK_OBJECT)
360                 {
361                     WCHAR wbLink[MAX_PATH] = { 0 };
362                     UNICODE_STRING link;
363                     RtlInitEmptyUnicodeString(&link, wbLink, sizeof(wbLink));
364 
365                     HRESULT hr = GetNTObjectSymbolicLinkTarget(m_NtPath, info->entryName, &link);
366 
367                     if (!FAILED_UNEXPECTEDLY(hr) && link.Length > 0)
368                     {
369                         MakeStrRetFromString(link.Buffer, link.Length, &(psd->str));
370                         return S_OK;
371                     }
372                 }
373 
374                 MakeStrRetFromString(L"", &(psd->str));
375                 return S_OK;
376             }
377         }
378     }
379     else
380     {
381         switch (iColumn)
382         {
383             case NTOBJECT_COLUMN_NAME:
384                 psd->fmt = LVCFMT_LEFT;
385                 psd->cxChar = 30;
386 
387                 // TODO: Make localizable
388                 MakeStrRetFromString(L"Object Name", &(psd->str));
389                 return S_OK;
390 
391             case NTOBJECT_COLUMN_TYPE:
392                 psd->fmt = LVCFMT_LEFT;
393                 psd->cxChar = 20;
394 
395                 // TODO: Make localizable
396                 MakeStrRetFromString(L"Object Type", &(psd->str));
397                 return S_OK;
398 
399             case NTOBJECT_COLUMN_LINKTARGET:
400                 psd->fmt = LVCFMT_LEFT;
401                 psd->cxChar = 30;
402 
403                 // TODO: Make localizable
404                 MakeStrRetFromString(L"Symlink Target", &(psd->str));
405                 return S_OK;
406         }
407     }
408 
409     return E_INVALIDARG;
410 }
411 
412 HRESULT STDMETHODCALLTYPE CNtObjectFolder::MapColumnToSCID(
413     UINT iColumn,
414     SHCOLUMNID *pscid)
415 {
416     static const GUID storage = PSGUID_STORAGE;
417     switch (iColumn)
418     {
419         case NTOBJECT_COLUMN_NAME:
420             pscid->fmtid = storage;
421             pscid->pid = PID_STG_NAME;
422             return S_OK;
423 
424         case NTOBJECT_COLUMN_TYPE:
425             pscid->fmtid = storage;
426             pscid->pid = PID_STG_STORAGETYPE;
427             return S_OK;
428 
429         case NTOBJECT_COLUMN_LINKTARGET:
430             pscid->fmtid = GUID_NtObjectColumns;
431             pscid->pid = NTOBJECT_COLUMN_LINKTARGET;
432             return S_OK;
433     }
434     return E_INVALIDARG;
435 }
436 
437 HRESULT CNtObjectFolder::CompareIDs(LPARAM lParam, const NtPidlEntry * first, const NtPidlEntry * second)
438 {
439     HRESULT hr;
440 
441     DWORD sortMode = lParam & 0xFFFF0000;
442     DWORD column = lParam & 0x0000FFFF;
443 
444     if (sortMode == SHCIDS_ALLFIELDS)
445     {
446         if (column != 0)
447             return E_INVALIDARG;
448 
449         int minsize = min(first->cb, second->cb);
450         hr = MAKE_COMPARE_HRESULT(memcmp(second, first, minsize));
451         if (hr != S_EQUAL)
452             return hr;
453 
454         return MAKE_COMPARE_HRESULT(second->cb - first->cb);
455     }
456 
457     switch (column)
458     {
459         case NTOBJECT_COLUMN_NAME:
460             return CompareName(lParam, first, second);
461 
462         case NTOBJECT_COLUMN_TYPE:
463             return MAKE_COMPARE_HRESULT(second->objectType - first->objectType);
464 
465         case NTOBJECT_COLUMN_LINKTARGET:
466         {
467             if (first->objectType != SYMBOLICLINK_OBJECT && second->objectType != SYMBOLICLINK_OBJECT)
468                 return CompareName(lParam, first, second);
469 
470             if (first->objectType != SYMBOLICLINK_OBJECT || second->objectType != SYMBOLICLINK_OBJECT)
471                 return first->objectType != SYMBOLICLINK_OBJECT ? S_GREATERTHAN : S_LESSTHAN;
472 
473             WCHAR wbLink1[MAX_PATH] = { 0 }, wbLink2[MAX_PATH] = { 0 };
474             UNICODE_STRING firstLink, secondLink;
475             RtlInitEmptyUnicodeString(&firstLink, wbLink1, sizeof(wbLink1));
476 
477             if (FAILED_UNEXPECTEDLY(GetNTObjectSymbolicLinkTarget(m_NtPath, first->entryName, &firstLink)))
478                 return E_INVALIDARG;
479 
480             RtlInitEmptyUnicodeString(&secondLink, wbLink2, sizeof(wbLink2));
481 
482             if (FAILED_UNEXPECTEDLY(GetNTObjectSymbolicLinkTarget(m_NtPath, second->entryName, &secondLink)))
483                 return E_INVALIDARG;
484 
485             return MAKE_COMPARE_HRESULT(RtlCompareUnicodeString(&firstLink, &secondLink, TRUE));
486         }
487     }
488 
489     DbgPrint("Unsupported sorting mode.\n");
490     return E_INVALIDARG;
491 }
492 
493 ULONG CNtObjectFolder::ConvertAttributes(const NtPidlEntry * entry, PULONG inMask)
494 {
495     ULONG mask = inMask ? *inMask : 0xFFFFFFFF;
496     ULONG flags = SFGAO_HASPROPSHEET | SFGAO_CANLINK;
497 
498     if (entry->objectType == DIRECTORY_OBJECT)
499         flags |= SFGAO_FOLDER | SFGAO_HASSUBFOLDER | SFGAO_BROWSABLE;
500 
501     if (entry->objectType == SYMBOLICLINK_OBJECT)
502         flags |= SFGAO_LINK | SFGAO_FOLDER | SFGAO_HASSUBFOLDER | SFGAO_BROWSABLE;
503 
504     if (entry->objectType == KEY_OBJECT)
505         flags |= SFGAO_FOLDER | SFGAO_HASSUBFOLDER | SFGAO_BROWSABLE;
506 
507     return flags & mask;
508 }
509 
510 BOOL CNtObjectFolder::IsFolder(const NtPidlEntry * info)
511 {
512     return (info->objectType == DIRECTORY_OBJECT) ||
513         (info->objectType == SYMBOLICLINK_OBJECT) ||
514         (info->objectType == KEY_OBJECT);
515 }
516 
517 HRESULT CNtObjectFolder::GetInfoFromPidl(LPCITEMIDLIST pcidl, const NtPidlEntry ** pentry)
518 {
519     if (!pcidl)
520     {
521         DbgPrint("PCIDL is NULL\n");
522         return E_INVALIDARG;
523     }
524 
525     NtPidlEntry * entry = (NtPidlEntry*) &(pcidl->mkid);
526     if (entry->cb < sizeof(NtPidlEntry))
527     {
528         DbgPrint("PCIDL too small %l (required %l)\n", entry->cb, sizeof(NtPidlEntry));
529         return E_INVALIDARG;
530     }
531 
532     if (entry->magic != NT_OBJECT_PIDL_MAGIC)
533     {
534         DbgPrint("PCIDL magic mismatch %04x (expected %04x)\n", entry->magic, NT_OBJECT_PIDL_MAGIC);
535         return E_INVALIDARG;
536     }
537 
538     *pentry = entry;
539     return S_OK;
540 }
541