1 /*
2  * PROJECT:     ReactOS shell extensions
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        dll/shellext/ntobjshex/regfolder.cpp
5  * PURPOSE:     NT Object Namespace shell extension
6  * PROGRAMMERS: David Quintana <gigaherz@gmail.com>
7  */
8 
9 #include "precomp.h"
10 
11 // {1C6D6E08-2332-4A7B-A94D-6432DB2B5AE6}
12 const GUID CLSID_RegistryFolder = { 0x1c6d6e08, 0x2332, 0x4a7b, { 0xa9, 0x4d, 0x64, 0x32, 0xdb, 0x2b, 0x5a, 0xe6 } };
13 
14 // {18A4B504-F6D8-4D8A-8661-6296514C2CF0}
15 //static const GUID GUID_RegistryColumns = { 0x18a4b504, 0xf6d8, 0x4d8a, { 0x86, 0x61, 0x62, 0x96, 0x51, 0x4c, 0x2c, 0xf0 } };
16 
17 enum RegistryColumns
18 {
19     REGISTRY_COLUMN_NAME = 0,
20     REGISTRY_COLUMN_TYPE,
21     REGISTRY_COLUMN_VALUE,
22     REGISTRY_COLUMN_END
23 };
24 
25 // -------------------------------
26 // CRegistryFolderExtractIcon
27 CRegistryFolderExtractIcon::CRegistryFolderExtractIcon() :
28     m_pcidlFolder(NULL),
29     m_pcidlChild(NULL)
30 {
31 
32 }
33 
34 CRegistryFolderExtractIcon::~CRegistryFolderExtractIcon()
35 {
36     if (m_pcidlFolder)
37         ILFree((LPITEMIDLIST)m_pcidlFolder);
38     if (m_pcidlChild)
39         ILFree((LPITEMIDLIST)m_pcidlChild);
40 }
41 
42 HRESULT CRegistryFolderExtractIcon::Initialize(LPCWSTR ntPath, PCIDLIST_ABSOLUTE parent, UINT cidl, PCUITEMID_CHILD_ARRAY apidl)
43 {
44     m_pcidlFolder = ILClone(parent);
45     if (cidl != 1)
46         return E_INVALIDARG;
47     m_pcidlChild = ILClone(apidl[0]);
48     return S_OK;
49 }
50 
51 HRESULT STDMETHODCALLTYPE CRegistryFolderExtractIcon::GetIconLocation(
52     UINT uFlags,
53     LPWSTR szIconFile,
54     UINT cchMax,
55     INT *piIndex,
56     UINT *pwFlags)
57 {
58     const RegPidlEntry * entry = (RegPidlEntry *)m_pcidlChild;
59 
60     if ((entry->cb < sizeof(RegPidlEntry)) || (entry->magic != REGISTRY_PIDL_MAGIC))
61         return E_INVALIDARG;
62 
63     UINT flags = 0;
64 
65     switch (entry->entryType)
66     {
67     case REG_ENTRY_KEY:
68     case REG_ENTRY_ROOT:
69         GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
70         *piIndex = -IDI_REGISTRYKEY;
71         *pwFlags = flags;
72         return S_OK;
73     case REG_ENTRY_VALUE:
74         GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
75         *piIndex = -IDI_REGISTRYVALUE;
76         *pwFlags = flags;
77         return S_OK;
78     default:
79         GetModuleFileNameW(g_hInstance, szIconFile, cchMax);
80         *piIndex = -IDI_NTOBJECTITEM;
81         *pwFlags = flags;
82         return S_OK;
83     }
84 }
85 
86 HRESULT STDMETHODCALLTYPE CRegistryFolderExtractIcon::Extract(
87     LPCWSTR pszFile,
88     UINT nIconIndex,
89     HICON *phiconLarge,
90     HICON *phiconSmall,
91     UINT nIconSize)
92 {
93     return SHDefExtractIconW(pszFile, nIconIndex, 0, phiconLarge, phiconSmall, nIconSize);
94 }
95 
96 // CRegistryFolder
97 
98 CRegistryFolder::CRegistryFolder()
99 {
100 }
101 
102 CRegistryFolder::~CRegistryFolder()
103 {
104 }
105 
106 // IShellFolder
107 HRESULT STDMETHODCALLTYPE CRegistryFolder::EnumObjects(
108     HWND hwndOwner,
109     SHCONTF grfFlags,
110     IEnumIDList **ppenumIDList)
111 {
112     if (m_NtPath[0] == 0 && m_hRoot == NULL)
113     {
114         return GetEnumRegistryRoot(ppenumIDList);
115     }
116     else
117     {
118         return GetEnumRegistryKey(m_NtPath, m_hRoot, ppenumIDList);
119     }
120 }
121 
122 HRESULT STDMETHODCALLTYPE CRegistryFolder::InternalBindToObject(
123     PWSTR path,
124     const RegPidlEntry * info,
125     LPITEMIDLIST first,
126     LPCITEMIDLIST rest,
127     LPITEMIDLIST fullPidl,
128     LPBC pbcReserved,
129     IShellFolder** ppsfChild)
130 {
131     if (wcslen(m_NtPath) == 0 && m_hRoot == NULL)
132     {
133         return ShellObjectCreatorInit<CRegistryFolder>(fullPidl, L"", info->rootKey, IID_PPV_ARG(IShellFolder, ppsfChild));
134     }
135 
136     return ShellObjectCreatorInit<CRegistryFolder>(fullPidl, path, m_hRoot, IID_PPV_ARG(IShellFolder, ppsfChild));
137 }
138 
139 HRESULT STDMETHODCALLTYPE CRegistryFolder::Initialize(LPCITEMIDLIST pidl)
140 {
141     m_shellPidl = ILClone(pidl);
142     m_hRoot = NULL;
143 
144     StringCbCopy(m_NtPath, _countof(m_NtPath), L"");
145     return S_OK;
146 }
147 
148 HRESULT STDMETHODCALLTYPE CRegistryFolder::Initialize(LPCITEMIDLIST pidl, PCWSTR ntPath, HKEY hRoot)
149 {
150     m_shellPidl = ILClone(pidl);
151     m_hRoot = hRoot;
152 
153     StringCbCopy(m_NtPath, _countof(m_NtPath), ntPath);
154     return S_OK;
155 }
156 
157 HRESULT STDMETHODCALLTYPE CRegistryFolder::GetDefaultColumnState(
158     UINT iColumn,
159     SHCOLSTATEF *pcsFlags)
160 {
161     switch (iColumn)
162     {
163     case REGISTRY_COLUMN_NAME:
164         *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT;
165         return S_OK;
166     case REGISTRY_COLUMN_TYPE:
167         *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT;
168         return S_OK;
169     case REGISTRY_COLUMN_VALUE:
170         *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_SLOW;
171         return S_OK;
172     }
173 
174     return E_INVALIDARG;
175 }
176 
177 HRESULT STDMETHODCALLTYPE CRegistryFolder::GetDetailsEx(
178     LPCITEMIDLIST pidl,
179     const SHCOLUMNID *pscid,
180     VARIANT *pv)
181 {
182     const RegPidlEntry * info;
183 
184     TRACE("GetDetailsEx\n");
185 
186     if (pidl)
187     {
188         HRESULT hr = GetInfoFromPidl(pidl, &info);
189         if (FAILED_UNEXPECTEDLY(hr))
190             return hr;
191 
192         static const GUID storage = PSGUID_STORAGE;
193         if (IsEqualGUID(pscid->fmtid, storage))
194         {
195             if (pscid->pid == PID_STG_NAME)
196             {
197                 if (info->entryNameLength > 0)
198                 {
199                     return MakeVariantString(pv, info->entryName);
200                 }
201                 return  MakeVariantString(pv, L"(Default)");
202             }
203             else if (pscid->pid == PID_STG_STORAGETYPE)
204             {
205                 if (info->entryType == REG_ENTRY_ROOT)
206                 {
207                     return MakeVariantString(pv, L"Key");
208                 }
209 
210                 if (info->entryType == REG_ENTRY_KEY)
211                 {
212                     if (info->contentsLength > 0)
213                     {
214                         PWSTR td = (PWSTR)(((PBYTE)info) + FIELD_OFFSET(RegPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR));
215 
216                         return MakeVariantString(pv, td);
217                     }
218                     return MakeVariantString(pv, L"Key");
219                 }
220 
221                 return MakeVariantString(pv, RegistryTypeNames[info->contentType]);
222             }
223             else if (pscid->pid == PID_STG_CONTENTS)
224             {
225                 PCWSTR strValueContents;
226 
227                 hr = FormatContentsForDisplay(info, m_hRoot, m_NtPath, &strValueContents);
228                 if (FAILED_UNEXPECTEDLY(hr))
229                     return hr;
230 
231                 if (hr == S_FALSE)
232                 {
233                     V_VT(pv) = VT_EMPTY;
234                     return S_OK;
235                 }
236 
237                 hr = MakeVariantString(pv, strValueContents);
238 
239                 CoTaskMemFree((PVOID)strValueContents);
240 
241                 return hr;
242 
243             }
244         }
245     }
246 
247     return E_INVALIDARG;
248 }
249 
250 HRESULT STDMETHODCALLTYPE CRegistryFolder::GetDetailsOf(
251     LPCITEMIDLIST pidl,
252     UINT iColumn,
253     SHELLDETAILS *psd)
254 {
255     const RegPidlEntry * info;
256 
257     TRACE("GetDetailsOf\n");
258 
259     if (pidl)
260     {
261         HRESULT hr = GetInfoFromPidl(pidl, &info);
262         if (FAILED_UNEXPECTEDLY(hr))
263             return hr;
264 
265         switch (iColumn)
266         {
267         case REGISTRY_COLUMN_NAME:
268             psd->fmt = LVCFMT_LEFT;
269 
270             if (info->entryNameLength > 0)
271             {
272                 return MakeStrRetFromString(info->entryName, info->entryNameLength, &(psd->str));
273             }
274             return MakeStrRetFromString(L"(Default)", &(psd->str));
275 
276         case REGISTRY_COLUMN_TYPE:
277             psd->fmt = LVCFMT_LEFT;
278 
279             if (info->entryType == REG_ENTRY_ROOT)
280             {
281                 return MakeStrRetFromString(L"Key", &(psd->str));
282             }
283 
284             if (info->entryType == REG_ENTRY_KEY)
285             {
286                 if (info->contentsLength > 0)
287                 {
288                     PWSTR td = (PWSTR)(((PBYTE)info) + FIELD_OFFSET(RegPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR));
289 
290                     return MakeStrRetFromString(td, info->contentsLength, &(psd->str));
291                 }
292 
293                 return MakeStrRetFromString(L"Key", &(psd->str));
294             }
295 
296             return MakeStrRetFromString(RegistryTypeNames[info->entryType], &(psd->str));
297 
298         case REGISTRY_COLUMN_VALUE:
299             psd->fmt = LVCFMT_LEFT;
300 
301             PCWSTR strValueContents;
302 
303             hr = FormatContentsForDisplay(info, m_hRoot, m_NtPath, &strValueContents);
304             if (FAILED_UNEXPECTEDLY(hr))
305                 return hr;
306 
307             if (hr == S_FALSE)
308             {
309                 return MakeStrRetFromString(L"(Empty)", &(psd->str));
310             }
311 
312             hr = MakeStrRetFromString(strValueContents, &(psd->str));
313 
314             CoTaskMemFree((PVOID)strValueContents);
315 
316             return hr;
317         }
318     }
319     else
320     {
321         switch (iColumn)
322         {
323         case REGISTRY_COLUMN_NAME:
324             psd->fmt = LVCFMT_LEFT;
325             psd->cxChar = 30;
326 
327             // TODO: Make localizable
328             MakeStrRetFromString(L"Object Name", &(psd->str));
329             return S_OK;
330         case REGISTRY_COLUMN_TYPE:
331             psd->fmt = LVCFMT_LEFT;
332             psd->cxChar = 20;
333 
334             // TODO: Make localizable
335             MakeStrRetFromString(L"Content Type", &(psd->str));
336             return S_OK;
337         case REGISTRY_COLUMN_VALUE:
338             psd->fmt = LVCFMT_LEFT;
339             psd->cxChar = 20;
340 
341             // TODO: Make localizable
342             MakeStrRetFromString(L"Value", &(psd->str));
343             return S_OK;
344         }
345     }
346 
347     return E_INVALIDARG;
348 }
349 
350 HRESULT STDMETHODCALLTYPE CRegistryFolder::MapColumnToSCID(
351     UINT iColumn,
352     SHCOLUMNID *pscid)
353 {
354     static const GUID storage = PSGUID_STORAGE;
355     switch (iColumn)
356     {
357     case REGISTRY_COLUMN_NAME:
358         pscid->fmtid = storage;
359         pscid->pid = PID_STG_NAME;
360         return S_OK;
361     case REGISTRY_COLUMN_TYPE:
362         pscid->fmtid = storage;
363         pscid->pid = PID_STG_STORAGETYPE;
364         return S_OK;
365     case REGISTRY_COLUMN_VALUE:
366         pscid->fmtid = storage;
367         pscid->pid = PID_STG_CONTENTS;
368         return S_OK;
369     }
370     return E_INVALIDARG;
371 }
372 
373 HRESULT CRegistryFolder::CompareIDs(LPARAM lParam, const RegPidlEntry * first, const RegPidlEntry * second)
374 {
375     HRESULT hr;
376 
377     DWORD sortMode = lParam & 0xFFFF0000;
378     DWORD column = lParam & 0x0000FFFF;
379 
380     if (sortMode == SHCIDS_ALLFIELDS)
381     {
382         if (column != 0)
383             return E_INVALIDARG;
384 
385         int minsize = min(first->cb, second->cb);
386         hr = MAKE_COMPARE_HRESULT(memcmp(second, first, minsize));
387         if (hr != S_EQUAL)
388             return hr;
389 
390         return MAKE_COMPARE_HRESULT(second->cb - first->cb);
391     }
392 
393     switch (column)
394     {
395     case REGISTRY_COLUMN_NAME:
396         return CompareName(lParam, first, second);
397 
398     case REGISTRY_COLUMN_TYPE:
399         return MAKE_COMPARE_HRESULT(second->contentType - first->contentType);
400 
401     case REGISTRY_COLUMN_VALUE:
402         // Can't sort by link target yet
403         return E_INVALIDARG;
404     }
405 
406     DbgPrint("Unsupported sorting mode.\n");
407     return E_INVALIDARG;
408 }
409 
410 ULONG CRegistryFolder::ConvertAttributes(const RegPidlEntry * entry, PULONG inMask)
411 {
412     ULONG mask = inMask ? *inMask : 0xFFFFFFFF;
413     ULONG flags = 0;
414 
415     if ((entry->entryType == REG_ENTRY_KEY) ||
416         (entry->entryType == REG_ENTRY_ROOT))
417         flags |= SFGAO_FOLDER | SFGAO_HASSUBFOLDER | SFGAO_BROWSABLE;
418 
419     return flags & mask;
420 }
421 
422 BOOL CRegistryFolder::IsFolder(const RegPidlEntry * info)
423 {
424     return (info->entryType == REG_ENTRY_KEY) ||(info->entryType == REG_ENTRY_ROOT);
425 }
426 
427 HRESULT CRegistryFolder::GetInfoFromPidl(LPCITEMIDLIST pcidl, const RegPidlEntry ** pentry)
428 {
429     RegPidlEntry * entry = (RegPidlEntry*) &(pcidl->mkid);
430 
431     if (entry->cb < sizeof(RegPidlEntry))
432     {
433         DbgPrint("PCIDL too small %l (required %l)\n", entry->cb, sizeof(RegPidlEntry));
434         return E_INVALIDARG;
435     }
436 
437     if (entry->magic != REGISTRY_PIDL_MAGIC)
438     {
439         DbgPrint("PCIDL magic mismatch %04x (expected %04x)\n", entry->magic, REGISTRY_PIDL_MAGIC);
440         return E_INVALIDARG;
441     }
442 
443     *pentry = entry;
444     return S_OK;
445 }
446 
447 HRESULT CRegistryFolder::FormatValueData(DWORD contentType, PVOID td, DWORD contentsLength, PCWSTR * strContents)
448 {
449     switch (contentType)
450     {
451     case 0:
452     {
453         PCWSTR strTodo = L"";
454         DWORD bufferLength = (wcslen(strTodo) + 1) * sizeof(WCHAR);
455         PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
456         StringCbCopyW(strValue, bufferLength, strTodo);
457         *strContents = strValue;
458         return S_OK;
459     }
460     case REG_SZ:
461     case REG_EXPAND_SZ:
462     {
463         PWSTR strValue = (PWSTR)CoTaskMemAlloc(contentsLength + sizeof(WCHAR));
464         StringCbCopyNW(strValue, contentsLength + sizeof(WCHAR), (LPCWSTR)td, contentsLength);
465         *strContents = strValue;
466         return S_OK;
467     }
468     case REG_MULTI_SZ:
469     {
470         PCWSTR separator = L" "; // To match regedit
471         size_t sepChars = wcslen(separator);
472         int strings = 0;
473         int stringChars = 0;
474 
475         PCWSTR strData = (PCWSTR)td;
476         while (*strData)
477         {
478             size_t len = wcslen(strData);
479             stringChars += len;
480             strData += len + 1; // Skips null-terminator
481             strings++;
482         }
483 
484         int cch = stringChars + (strings - 1) * sepChars + 1;
485 
486         PWSTR strValue = (PWSTR)CoTaskMemAlloc(cch * sizeof(WCHAR));
487 
488         strValue[0] = 0;
489 
490         strData = (PCWSTR)td;
491         while (*strData)
492         {
493             StrCatW(strValue, strData);
494             strData += wcslen(strData) + 1;
495             if (*strData)
496                 StrCatW(strValue, separator);
497         }
498 
499         *strContents = strValue;
500         return S_OK;
501     }
502     case REG_DWORD:
503     {
504         DWORD bufferLength = 64 * sizeof(WCHAR);
505         PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
506         StringCbPrintfW(strValue, bufferLength, L"0x%08x (%d)",
507             *(DWORD*)td, *(DWORD*)td);
508         *strContents = strValue;
509         return S_OK;
510     }
511     case REG_QWORD:
512     {
513         DWORD bufferLength = 64 * sizeof(WCHAR);
514         PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
515         StringCbPrintfW(strValue, bufferLength, L"0x%016llx (%lld)",
516             *(LARGE_INTEGER*)td, ((LARGE_INTEGER*)td)->QuadPart);
517         *strContents = strValue;
518         return S_OK;
519     }
520     case REG_BINARY:
521     {
522         DWORD bufferLength = (contentsLength * 3 + 1) * sizeof(WCHAR);
523         PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
524         PWSTR strTemp = strValue;
525         PBYTE data = (PBYTE)td;
526         for (DWORD i = 0; i < contentsLength; i++)
527         {
528             StringCbPrintfW(strTemp, bufferLength, L"%02x ", data[i]);
529             strTemp += 3;
530             bufferLength -= 3;
531         }
532         *strContents = strValue;
533         return S_OK;
534     }
535     default:
536     {
537         PCWSTR strFormat = L"<Unimplemented value type %d>";
538         DWORD bufferLength = (wcslen(strFormat) + 15) * sizeof(WCHAR);
539         PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
540         StringCbPrintfW(strValue, bufferLength, strFormat, contentType);
541         *strContents = strValue;
542         return S_OK;
543     }
544     }
545 }
546 
547 HRESULT CRegistryFolder::FormatContentsForDisplay(const RegPidlEntry * info, HKEY rootKey, LPCWSTR ntPath, PCWSTR * strContents)
548 {
549     PVOID td = (((PBYTE)info) + FIELD_OFFSET(RegPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR));
550 
551     if (info->entryType == REG_ENTRY_VALUE_WITH_CONTENT)
552     {
553         if (info->contentsLength > 0)
554         {
555             return FormatValueData(info->contentType, td, info->contentsLength, strContents);
556         }
557     }
558     else if (info->entryType == REG_ENTRY_VALUE)
559     {
560         PVOID valueData;
561         DWORD valueLength;
562         HRESULT hr = ReadRegistryValue(rootKey, ntPath, info->entryName, &valueData, &valueLength);
563         if (FAILED_UNEXPECTEDLY(hr))
564         {
565             PCWSTR strEmpty = L"(Error reading value)";
566             DWORD bufferLength = (wcslen(strEmpty) + 1) * sizeof(WCHAR);
567             PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
568             StringCbCopyW(strValue, bufferLength, strEmpty);
569             *strContents = strValue;
570             return S_OK;
571         }
572 
573         if (valueLength > 0)
574         {
575             hr = FormatValueData(info->contentType, valueData, valueLength, strContents);
576 
577             CoTaskMemFree(valueData);
578 
579             return hr;
580         }
581     }
582     else
583     {
584         PCWSTR strEmpty = L"";
585         DWORD bufferLength = (wcslen(strEmpty) + 1) * sizeof(WCHAR);
586         PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
587         StringCbCopyW(strValue, bufferLength, strEmpty);
588         *strContents = strValue;
589         return S_OK;
590     }
591 
592     PCWSTR strEmpty = L"(Empty)";
593     DWORD bufferLength = (wcslen(strEmpty) + 1) * sizeof(WCHAR);
594     PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength);
595     StringCbCopyW(strValue, bufferLength, strEmpty);
596     *strContents = strValue;
597     return S_OK;
598 }
599