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