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