1 /* 2 * PROJECT: ReactOS Shell 3 * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) 4 * PURPOSE: Implement shell property bags 5 * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 6 */ 7 8 #define _ATL_NO_EXCEPTIONS 9 #include "precomp.h" 10 #include <shlwapi.h> 11 #include <shlwapi_undoc.h> 12 #include <atlstr.h> // for CStringW 13 #include <atlsimpcoll.h> // for CSimpleMap 14 #include <atlcomcli.h> // for CComVariant 15 #include <atlconv.h> // for CA2W and CW2A 16 17 WINE_DEFAULT_DEBUG_CHANNEL(shell); 18 19 class CBasePropertyBag 20 : public IPropertyBag 21 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA) 22 , public IPropertyBag2 23 #endif 24 { 25 protected: 26 LONG m_cRefs; // reference count 27 DWORD m_dwMode; // STGM_* flags 28 29 public: 30 CBasePropertyBag(DWORD dwMode) 31 : m_cRefs(0) 32 , m_dwMode(dwMode) 33 { 34 } 35 36 virtual ~CBasePropertyBag() { } 37 38 // IUnknown interface 39 STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override 40 { 41 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA) 42 if (::IsEqualGUID(riid, IID_IPropertyBag2)) 43 { 44 AddRef(); 45 *ppvObject = static_cast<IPropertyBag2*>(this); 46 return S_OK; 47 } 48 #endif 49 if (::IsEqualGUID(riid, IID_IUnknown) || ::IsEqualGUID(riid, IID_IPropertyBag)) 50 { 51 AddRef(); 52 *ppvObject = static_cast<IPropertyBag*>(this); 53 return S_OK; 54 } 55 56 ERR("%p: %s: E_NOTIMPL\n", this, debugstr_guid(&riid)); 57 return E_NOTIMPL; 58 } 59 STDMETHODIMP_(ULONG) AddRef() override 60 { 61 return ::InterlockedIncrement(&m_cRefs); 62 } 63 STDMETHODIMP_(ULONG) Release() override 64 { 65 if (::InterlockedDecrement(&m_cRefs) == 0) 66 { 67 delete this; 68 return 0; 69 } 70 return m_cRefs; 71 } 72 73 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA) 74 // IPropertyBag2 interface (stubs) 75 STDMETHODIMP Read( 76 _In_ ULONG cProperties, 77 _In_ PROPBAG2 *pPropBag, 78 _In_opt_ IErrorLog *pErrorLog, 79 _Out_ VARIANT *pvarValue, 80 _Out_ HRESULT *phrError) override 81 { 82 return E_NOTIMPL; 83 } 84 STDMETHODIMP Write( 85 _In_ ULONG cProperties, 86 _In_ PROPBAG2 *pPropBag, 87 _In_ VARIANT *pvarValue) 88 { 89 return E_NOTIMPL; 90 } 91 STDMETHODIMP CountProperties(_Out_ ULONG *pcProperties) override 92 { 93 return E_NOTIMPL; 94 } 95 STDMETHODIMP GetPropertyInfo( 96 _In_ ULONG iProperty, 97 _In_ ULONG cProperties, 98 _Out_ PROPBAG2 *pPropBag, 99 _Out_ ULONG *pcProperties) override 100 { 101 return E_NOTIMPL; 102 } 103 STDMETHODIMP LoadObject( 104 _In_z_ LPCWSTR pstrName, 105 _In_ DWORD dwHint, 106 _In_ IUnknown *pUnkObject, 107 _In_opt_ IErrorLog *pErrorLog) override 108 { 109 return E_NOTIMPL; 110 } 111 #endif 112 }; 113 114 struct CPropMapEqual 115 { 116 static bool IsEqualKey(const ATL::CStringW& k1, const ATL::CStringW& k2) 117 { 118 return k1.CompareNoCase(k2) == 0; 119 } 120 121 static bool IsEqualValue(const ATL::CComVariant& v1, const ATL::CComVariant& v2) 122 { 123 return false; 124 } 125 }; 126 127 class CMemPropertyBag : public CBasePropertyBag 128 { 129 protected: 130 ATL::CSimpleMap<ATL::CStringW, ATL::CComVariant, CPropMapEqual> m_PropMap; 131 132 public: 133 CMemPropertyBag(DWORD dwFlags) : CBasePropertyBag(dwFlags) { } 134 135 STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari, 136 _Inout_opt_ IErrorLog *pErrorLog) override; 137 STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override; 138 }; 139 140 STDMETHODIMP 141 CMemPropertyBag::Read( 142 _In_z_ LPCWSTR pszPropName, 143 _Inout_ VARIANT *pvari, 144 _Inout_opt_ IErrorLog *pErrorLog) 145 { 146 UNREFERENCED_PARAMETER(pErrorLog); 147 148 TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog); 149 150 VARTYPE vt = V_VT(pvari); 151 152 ::VariantInit(pvari); 153 154 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA) 155 if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_WRITE) 156 { 157 ERR("%p: 0x%X\n", this, m_dwMode); 158 return E_ACCESSDENIED; 159 } 160 #endif 161 162 if (!pszPropName || !pvari) 163 { 164 ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog); 165 return E_INVALIDARG; 166 } 167 168 INT iItem = m_PropMap.FindKey(pszPropName); 169 if (iItem == -1) 170 { 171 ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog); 172 return E_FAIL; 173 } 174 175 HRESULT hr = ::VariantCopy(pvari, &m_PropMap.GetValueAt(iItem)); 176 if (FAILED(hr)) 177 { 178 ERR("%p: 0x%08X %p\n", this, hr, pvari); 179 return hr; 180 } 181 182 hr = ::VariantChangeTypeForRead(pvari, vt); 183 if (FAILED(hr)) 184 { 185 ERR("%p: 0x%08X %p\n", this, hr, pvari); 186 return hr; 187 } 188 189 return hr; 190 } 191 192 STDMETHODIMP 193 CMemPropertyBag::Write( 194 _In_z_ LPCWSTR pszPropName, 195 _In_ VARIANT *pvari) 196 { 197 TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari); 198 199 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA) 200 if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_READ) 201 { 202 ERR("%p: 0x%X\n", this, m_dwMode); 203 return E_ACCESSDENIED; 204 } 205 #endif 206 207 if (!pszPropName || !pvari) 208 { 209 ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari); 210 return E_INVALIDARG; 211 } 212 213 ATL::CComVariant vari; 214 HRESULT hr = vari.Copy(pvari); 215 if (FAILED(hr)) 216 { 217 ERR("%p: %s %p: 0x%08X\n", this, debugstr_w(pszPropName), pvari, hr); 218 return hr; 219 } 220 221 if (!m_PropMap.SetAt(pszPropName, vari)) 222 { 223 ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari); 224 return E_FAIL; 225 } 226 227 return hr; 228 } 229 230 /************************************************************************** 231 * SHCreatePropertyBagOnMemory (SHLWAPI.477) 232 * 233 * Creates a property bag object on memory. 234 * 235 * @param dwMode Specifies either STGM_READ, STGM_WRITE or STGM_READWRITE. Ignored on Vista+. 236 * @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2. 237 * Vista+ rejects IID_IPropertyBag2. 238 * @param ppvObj Receives an IPropertyBag pointer. 239 * @return An HRESULT value. S_OK on success, non-zero on failure. 240 * @see http://undoc.airesoft.co.uk/shlwapi.dll/SHCreatePropertyBagOnMemory.php 241 */ 242 EXTERN_C HRESULT WINAPI 243 SHCreatePropertyBagOnMemory(_In_ DWORD dwMode, _In_ REFIID riid, _Out_ void **ppvObj) 244 { 245 TRACE("0x%08X, %s, %p\n", dwMode, debugstr_guid(&riid), ppvObj); 246 247 *ppvObj = NULL; 248 249 CComPtr<CMemPropertyBag> pMemBag(new CMemPropertyBag(dwMode)); 250 251 return pMemBag->QueryInterface(riid, ppvObj); 252 } 253 254 class CRegPropertyBag : public CBasePropertyBag 255 { 256 protected: 257 HKEY m_hKey; 258 259 HRESULT _ReadDword(LPCWSTR pszPropName, VARIANT *pvari); 260 HRESULT _ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len); 261 HRESULT _ReadBinary(LPCWSTR pszPropName, VARIANT *pvari, VARTYPE vt, DWORD uBytes); 262 HRESULT _ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit); 263 HRESULT _CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb); 264 HRESULT _GetStreamSize(IStream *pStream, LPDWORD pcbSize); 265 HRESULT _WriteStream(LPCWSTR pszPropName, IStream *pStream); 266 267 public: 268 CRegPropertyBag(DWORD dwMode) 269 : CBasePropertyBag(dwMode) 270 , m_hKey(NULL) 271 { 272 } 273 274 ~CRegPropertyBag() override 275 { 276 if (m_hKey) 277 ::RegCloseKey(m_hKey); 278 } 279 280 HRESULT Init(HKEY hKey, LPCWSTR lpSubKey); 281 282 STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari, 283 _Inout_opt_ IErrorLog *pErrorLog) override; 284 STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override; 285 }; 286 287 HRESULT CRegPropertyBag::Init(HKEY hKey, LPCWSTR lpSubKey) 288 { 289 REGSAM nAccess = 0; 290 if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_WRITE) 291 nAccess |= KEY_READ; 292 if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_READ) 293 nAccess |= KEY_WRITE; 294 295 LONG error; 296 if (m_dwMode & STGM_CREATE) 297 error = ::RegCreateKeyExW(hKey, lpSubKey, 0, NULL, 0, nAccess, NULL, &m_hKey, NULL); 298 else 299 error = ::RegOpenKeyExW(hKey, lpSubKey, 0, nAccess, &m_hKey); 300 301 if (error != ERROR_SUCCESS) 302 { 303 ERR("%p %s 0x%08X\n", hKey, debugstr_w(lpSubKey), error); 304 return HRESULT_FROM_WIN32(error); 305 } 306 307 return S_OK; 308 } 309 310 HRESULT CRegPropertyBag::_ReadDword(LPCWSTR pszPropName, VARIANT *pvari) 311 { 312 DWORD cbData = sizeof(DWORD); 313 LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, &V_UI4(pvari), &cbData); 314 if (error) 315 return E_FAIL; 316 317 V_VT(pvari) = VT_UI4; 318 return S_OK; 319 } 320 321 HRESULT CRegPropertyBag::_ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len) 322 { 323 BSTR bstr = ::SysAllocStringByteLen(NULL, len); 324 V_BSTR(pvarg) = bstr; 325 if (!bstr) 326 return E_OUTOFMEMORY; 327 328 V_VT(pvarg) = VT_BSTR; 329 LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, bstr, (LPDWORD)&len); 330 if (error) 331 { 332 ::VariantClear(pvarg); 333 return E_FAIL; 334 } 335 336 return S_OK; 337 } 338 339 HRESULT CRegPropertyBag::_ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit) 340 { 341 IStream *pStream = SHCreateMemStream(pInit, cbInit); 342 V_UNKNOWN(pvari) = pStream; 343 if (!pStream) 344 return E_OUTOFMEMORY; 345 V_VT(pvari) = VT_UNKNOWN; 346 return S_OK; 347 } 348 349 HRESULT 350 CRegPropertyBag::_ReadBinary( 351 LPCWSTR pszPropName, 352 VARIANT *pvari, 353 VARTYPE vt, 354 DWORD uBytes) 355 { 356 HRESULT hr = E_FAIL; 357 if (vt != VT_UNKNOWN || uBytes < sizeof(GUID)) 358 return hr; 359 360 LPBYTE pbData = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, uBytes); 361 if (!pbData) 362 return hr; 363 364 if (!SHGetValueW(m_hKey, NULL, pszPropName, NULL, pbData, &uBytes) && 365 memcmp(&GUID_NULL, pbData, sizeof(GUID)) == 0) 366 { 367 hr = _ReadStream(pvari, pbData + sizeof(GUID), uBytes - sizeof(GUID)); 368 } 369 370 ::LocalFree(pbData); 371 372 return hr; 373 } 374 375 HRESULT CRegPropertyBag::_CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb) 376 { 377 LARGE_INTEGER li; 378 li.QuadPart = 0; 379 HRESULT hr = pStream->Seek(li, 0, NULL); 380 if (FAILED(hr)) 381 return hr; 382 return pStream->Read(pv, cb, NULL); 383 } 384 385 HRESULT CRegPropertyBag::_GetStreamSize(IStream *pStream, LPDWORD pcbSize) 386 { 387 *pcbSize = 0; 388 389 ULARGE_INTEGER ui; 390 HRESULT hr = IStream_Size(pStream, &ui); 391 if (FAILED(hr)) 392 return hr; 393 394 if (ui.DUMMYSTRUCTNAME.HighPart) 395 return E_FAIL; /* 64-bit value is not supported */ 396 397 *pcbSize = ui.DUMMYSTRUCTNAME.LowPart; 398 return hr; 399 } 400 401 STDMETHODIMP 402 CRegPropertyBag::Read( 403 _In_z_ LPCWSTR pszPropName, 404 _Inout_ VARIANT *pvari, 405 _Inout_opt_ IErrorLog *pErrorLog) 406 { 407 UNREFERENCED_PARAMETER(pErrorLog); 408 409 TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog); 410 411 if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_WRITE) 412 { 413 ERR("%p: 0x%X\n", this, m_dwMode); 414 ::VariantInit(pvari); 415 return E_ACCESSDENIED; 416 } 417 418 VARTYPE vt = V_VT(pvari); 419 VariantInit(pvari); 420 421 HRESULT hr; 422 DWORD dwType, cbValue; 423 LONG error = SHGetValueW(m_hKey, NULL, pszPropName, &dwType, NULL, &cbValue); 424 if (error != ERROR_SUCCESS) 425 hr = E_FAIL; 426 else if (dwType == REG_SZ) 427 hr = _ReadString(pszPropName, pvari, cbValue); 428 else if (dwType == REG_BINARY) 429 hr = _ReadBinary(pszPropName, pvari, vt, cbValue); 430 else if (dwType == REG_DWORD) 431 hr = _ReadDword(pszPropName, pvari); 432 else 433 hr = E_FAIL; 434 435 if (FAILED(hr)) 436 { 437 ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari); 438 ::VariantInit(pvari); 439 return hr; 440 } 441 442 hr = ::VariantChangeTypeForRead(pvari, vt); 443 if (FAILED(hr)) 444 { 445 ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari); 446 ::VariantInit(pvari); 447 } 448 449 return hr; 450 } 451 452 HRESULT 453 CRegPropertyBag::_WriteStream(LPCWSTR pszPropName, IStream *pStream) 454 { 455 DWORD cbData; 456 HRESULT hr = _GetStreamSize(pStream, &cbData); 457 if (FAILED(hr) || !cbData) 458 return hr; 459 460 DWORD cbBinary = cbData + sizeof(GUID); 461 LPBYTE pbBinary = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, cbBinary); 462 if (!pbBinary) 463 return E_OUTOFMEMORY; 464 465 hr = _CopyStreamIntoBuff(pStream, pbBinary + sizeof(GUID), cbData); 466 if (SUCCEEDED(hr)) 467 { 468 if (SHSetValueW(m_hKey, NULL, pszPropName, REG_BINARY, pbBinary, cbBinary)) 469 hr = E_FAIL; 470 } 471 472 ::LocalFree(pbBinary); 473 return hr; 474 } 475 476 STDMETHODIMP 477 CRegPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) 478 { 479 TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari); 480 481 if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_READ) 482 { 483 ERR("%p: 0x%X\n", this, m_dwMode); 484 return E_ACCESSDENIED; 485 } 486 487 HRESULT hr; 488 LONG error; 489 VARIANTARG vargTemp = { 0 }; 490 switch (V_VT(pvari)) 491 { 492 case VT_EMPTY: 493 SHDeleteValueW(m_hKey, NULL, pszPropName); 494 hr = S_OK; 495 break; 496 497 case VT_BOOL: 498 case VT_I1: 499 case VT_I2: 500 case VT_I4: 501 case VT_UI1: 502 case VT_UI2: 503 case VT_UI4: 504 case VT_INT: 505 case VT_UINT: 506 { 507 hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_UI4); 508 if (FAILED(hr)) 509 return hr; 510 511 error = SHSetValueW(m_hKey, NULL, pszPropName, REG_DWORD, &V_UI4(&vargTemp), sizeof(DWORD)); 512 if (error) 513 hr = E_FAIL; 514 515 ::VariantClear(&vargTemp); 516 break; 517 } 518 519 case VT_UNKNOWN: 520 { 521 CComPtr<IStream> pStream; 522 hr = V_UNKNOWN(pvari)->QueryInterface(IID_IStream, (void **)&pStream); 523 if (FAILED(hr)) 524 return hr; 525 526 hr = _WriteStream(pszPropName, pStream); 527 break; 528 } 529 530 default: 531 { 532 hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR); 533 if (FAILED(hr)) 534 return hr; 535 536 int cch = lstrlenW(V_BSTR(&vargTemp)); 537 DWORD cb = (cch + 1) * sizeof(WCHAR); 538 error = SHSetValueW(m_hKey, NULL, pszPropName, REG_SZ, V_BSTR(&vargTemp), cb); 539 if (error) 540 hr = E_FAIL; 541 542 ::VariantClear(&vargTemp); 543 break; 544 } 545 } 546 547 return hr; 548 } 549 550 /************************************************************************** 551 * SHCreatePropertyBagOnRegKey (SHLWAPI.471) 552 * 553 * Creates a property bag object on registry key. 554 * 555 * @param hKey The registry key. 556 * @param pszSubKey The path of the sub-key. 557 * @param dwMode The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE. 558 * @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2. 559 * @param ppvObj Receives an IPropertyBag pointer. 560 * @return An HRESULT value. S_OK on success, non-zero on failure. 561 * @see https://source.winehq.org/WineAPI/SHCreatePropertyBagOnRegKey.html 562 */ 563 EXTERN_C HRESULT WINAPI 564 SHCreatePropertyBagOnRegKey( 565 _In_ HKEY hKey, 566 _In_z_ LPCWSTR pszSubKey, 567 _In_ DWORD dwMode, 568 _In_ REFIID riid, 569 _Out_ void **ppvObj) 570 { 571 TRACE("%p, %s, 0x%08X, %s, %p\n", hKey, debugstr_w(pszSubKey), dwMode, 572 debugstr_guid(&riid), ppvObj); 573 574 *ppvObj = NULL; 575 576 CComPtr<CRegPropertyBag> pRegBag(new CRegPropertyBag(dwMode)); 577 578 HRESULT hr = pRegBag->Init(hKey, pszSubKey); 579 if (FAILED(hr)) 580 return hr; 581 582 return pRegBag->QueryInterface(riid, ppvObj); 583 } 584 585 /************************************************************************** 586 * SHGetIniStringW (SHLWAPI.294) 587 * 588 * @see https://source.winehq.org/WineAPI/SHGetIniStringW.html 589 */ 590 EXTERN_C DWORD WINAPI 591 SHGetIniStringW( 592 _In_z_ LPCWSTR appName, 593 _In_z_ LPCWSTR keyName, 594 _Out_writes_to_(outLen, return + 1) LPWSTR out, 595 _In_ DWORD outLen, 596 _In_z_ LPCWSTR filename) 597 { 598 TRACE("(%s,%s,%p,%08x,%s)\n", debugstr_w(appName), debugstr_w(keyName), 599 out, outLen, debugstr_w(filename)); 600 601 if (outLen == 0) 602 return 0; 603 604 // Try ".W"-appended section name. See also SHSetIniStringW 605 CStringW szSection(appName); 606 szSection += L".W"; 607 CStringW pszWideBuff; 608 const INT cchWideMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer. 609 GetPrivateProfileStringW(szSection, keyName, NULL, 610 pszWideBuff.GetBuffer(cchWideMax), cchWideMax, filename); 611 pszWideBuff.ReleaseBuffer(); 612 613 if (pszWideBuff.IsEmpty()) // It's empty or not found 614 { 615 // Try the normal section name 616 return GetPrivateProfileStringW(appName, keyName, NULL, out, outLen, filename); 617 } 618 619 // Okay, now ".W" version is valid. Its value is a UTF-7 string in UTF-16 620 CW2A wide2utf7(pszWideBuff); 621 MultiByteToWideChar(CP_UTF7, 0, wide2utf7, -1, out, outLen); 622 out[outLen - 1] = UNICODE_NULL; 623 624 return lstrlenW(out); 625 } 626 627 static BOOL Is7BitClean(LPCWSTR psz) 628 { 629 if (!psz) 630 return TRUE; 631 632 while (*psz) 633 { 634 if (*psz > 0x7F) 635 return FALSE; 636 ++psz; 637 } 638 return TRUE; 639 } 640 641 /************************************************************************** 642 * SHSetIniStringW (SHLWAPI.295) 643 * 644 * @see https://source.winehq.org/WineAPI/SHSetIniStringW.html 645 */ 646 EXTERN_C BOOL WINAPI 647 SHSetIniStringW( 648 _In_z_ LPCWSTR appName, 649 _In_z_ LPCWSTR keyName, 650 _In_opt_z_ LPCWSTR str, 651 _In_z_ LPCWSTR filename) 652 { 653 TRACE("(%s, %p, %s, %s)\n", debugstr_w(appName), keyName, debugstr_w(str), 654 debugstr_w(filename)); 655 656 // Write a normal profile string. If str was NULL, then key will be deleted 657 if (!WritePrivateProfileStringW(appName, keyName, str, filename)) 658 return FALSE; 659 660 if (Is7BitClean(str)) 661 { 662 // Delete ".A" version 663 CStringW szSection(appName); 664 szSection += L".A"; 665 WritePrivateProfileStringW(szSection, keyName, NULL, filename); 666 667 // Delete ".W" version 668 szSection = appName; 669 szSection += L".W"; 670 WritePrivateProfileStringW(szSection, keyName, NULL, filename); 671 672 return TRUE; 673 } 674 675 // Now str is not 7-bit clean. It needs UTF-7 encoding in UTF-16. 676 // We write ".A" and ".W"-appended sections 677 CW2A wide2utf7(str, CP_UTF7); 678 CA2W utf72wide(wide2utf7, CP_ACP); 679 680 BOOL ret = TRUE; 681 682 // Write ".A" version 683 CStringW szSection(appName); 684 szSection += L".A"; 685 if (!WritePrivateProfileStringW(szSection, keyName, str, filename)) 686 ret = FALSE; 687 688 // Write ".W" version 689 szSection = appName; 690 szSection += L".W"; 691 if (!WritePrivateProfileStringW(szSection, keyName, utf72wide, filename)) 692 ret = FALSE; 693 694 return ret; 695 } 696 697 /************************************************************************** 698 * SHGetIniStringUTF7W (SHLWAPI.473) 699 * 700 * Retrieves a string value from an INI file. 701 * 702 * @param lpAppName The section name. 703 * @param lpKeyName The key name. 704 * If this string begins from '@', the value will be interpreted as UTF-7. 705 * @param lpReturnedString Receives a wide string value. 706 * @param nSize The number of characters in lpReturnedString. 707 * @param lpFileName The INI file. 708 * @return The number of characters copied to the buffer if succeeded. 709 */ 710 EXTERN_C DWORD WINAPI 711 SHGetIniStringUTF7W( 712 _In_opt_z_ LPCWSTR lpAppName, 713 _In_z_ LPCWSTR lpKeyName, 714 _Out_writes_to_(nSize, return + 1) _Post_z_ LPWSTR lpReturnedString, 715 _In_ DWORD nSize, 716 _In_z_ LPCWSTR lpFileName) 717 { 718 if (*lpKeyName == L'@') // UTF-7 719 return SHGetIniStringW(lpAppName, lpKeyName + 1, lpReturnedString, nSize, lpFileName); 720 721 return GetPrivateProfileStringW(lpAppName, lpKeyName, L"", lpReturnedString, nSize, lpFileName); 722 } 723 724 /************************************************************************** 725 * SHSetIniStringUTF7W (SHLWAPI.474) 726 * 727 * Sets a string value on an INI file. 728 * 729 * @param lpAppName The section name. 730 * @param lpKeyName The key name. 731 * If this begins from '@', the value will be stored as UTF-7. 732 * @param lpString The wide string value to be set. 733 * @param lpFileName The INI file. 734 * @return TRUE if successful. FALSE if failed. 735 */ 736 EXTERN_C BOOL WINAPI 737 SHSetIniStringUTF7W( 738 _In_z_ LPCWSTR lpAppName, 739 _In_z_ LPCWSTR lpKeyName, 740 _In_opt_z_ LPCWSTR lpString, 741 _In_z_ LPCWSTR lpFileName) 742 { 743 if (*lpKeyName == L'@') // UTF-7 744 return SHSetIniStringW(lpAppName, lpKeyName + 1, lpString, lpFileName); 745 746 return WritePrivateProfileStringW(lpAppName, lpKeyName, lpString, lpFileName); 747 } 748 749 class CIniPropertyBag : public CBasePropertyBag 750 { 751 protected: 752 LPWSTR m_pszFileName; 753 LPWSTR m_pszSection; 754 BOOL m_bAlternateStream; // ADS (Alternate Data Stream) 755 756 static BOOL LooksLikeAnAlternateStream(LPCWSTR pszStart) 757 { 758 LPCWSTR pch = StrRChrW(pszStart, NULL, L'\\'); 759 if (!pch) 760 pch = pszStart; 761 return StrChrW(pch, L':') != NULL; 762 } 763 764 HRESULT 765 _GetSectionAndName( 766 LPCWSTR pszStart, 767 LPWSTR pszSection, 768 UINT cchSectionMax, 769 LPWSTR pszName, 770 UINT cchNameMax); 771 772 public: 773 CIniPropertyBag(DWORD dwMode) 774 : CBasePropertyBag(dwMode) 775 , m_pszFileName(NULL) 776 , m_pszSection(NULL) 777 , m_bAlternateStream(FALSE) 778 { 779 } 780 781 ~CIniPropertyBag() override 782 { 783 ::LocalFree(m_pszFileName); 784 ::LocalFree(m_pszSection); 785 } 786 787 HRESULT Init(LPCWSTR pszIniFile, LPCWSTR pszSection); 788 789 STDMETHODIMP Read( 790 _In_z_ LPCWSTR pszPropName, 791 _Inout_ VARIANT *pvari, 792 _Inout_opt_ IErrorLog *pErrorLog) override; 793 794 STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override; 795 }; 796 797 HRESULT CIniPropertyBag::Init(LPCWSTR pszIniFile, LPCWSTR pszSection) 798 { 799 m_pszFileName = StrDupW(pszIniFile); 800 if (!m_pszFileName) 801 return E_OUTOFMEMORY; 802 803 // Is it an ADS (Alternate Data Stream) pathname? 804 m_bAlternateStream = LooksLikeAnAlternateStream(m_pszFileName); 805 806 if (pszSection) 807 { 808 m_pszSection = StrDupW(pszSection); 809 if (!m_pszSection) 810 return E_OUTOFMEMORY; 811 } 812 813 return S_OK; 814 } 815 816 HRESULT 817 CIniPropertyBag::_GetSectionAndName( 818 LPCWSTR pszStart, 819 LPWSTR pszSection, 820 UINT cchSectionMax, 821 LPWSTR pszName, 822 UINT cchNameMax) 823 { 824 LPCWSTR pchSep = StrChrW(pszStart, L'\\'); 825 if (pchSep) 826 { 827 UINT cchSep = (UINT)(pchSep - pszStart + 1); 828 StrCpyNW(pszSection, pszStart, min(cchSep, cchSectionMax)); 829 StrCpyNW(pszName, pchSep + 1, cchNameMax); 830 return S_OK; 831 } 832 833 if (m_pszSection) 834 { 835 StrCpyNW(pszSection, m_pszSection, cchSectionMax); 836 StrCpyNW(pszName, pszStart, cchNameMax); 837 return S_OK; 838 } 839 840 ERR("%p: %s\n", this, debugstr_w(pszStart)); 841 return E_INVALIDARG; 842 } 843 844 STDMETHODIMP 845 CIniPropertyBag::Read( 846 _In_z_ LPCWSTR pszPropName, 847 _Inout_ VARIANT *pvari, 848 _Inout_opt_ IErrorLog *pErrorLog) 849 { 850 UNREFERENCED_PARAMETER(pErrorLog); 851 852 TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog); 853 854 VARTYPE vt = V_VT(pvari); 855 856 ::VariantInit(pvari); 857 858 if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_WRITE) 859 { 860 ERR("%p: 0x%X\n", this, m_dwMode); 861 return E_ACCESSDENIED; 862 } 863 864 WCHAR szSection[64], szName[64]; 865 HRESULT hr = 866 _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName)); 867 if (FAILED(hr)) 868 return hr; 869 870 const INT cchBuffMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer. 871 CComHeapPtr<WCHAR> pszBuff; 872 if (!pszBuff.Allocate(cchBuffMax * sizeof(WCHAR))) 873 return E_OUTOFMEMORY; 874 875 if (!SHGetIniStringUTF7W(szSection, szName, pszBuff, cchBuffMax, m_pszFileName)) 876 return E_FAIL; 877 878 BSTR bstr = ::SysAllocString(pszBuff); 879 V_BSTR(pvari) = bstr; 880 if (!bstr) 881 return E_OUTOFMEMORY; 882 883 V_VT(pvari) = VT_BSTR; 884 return ::VariantChangeTypeForRead(pvari, vt); 885 } 886 887 STDMETHODIMP 888 CIniPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) 889 { 890 TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari); 891 892 if ((m_dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_READ) 893 { 894 ERR("%p: 0x%X\n", this, m_dwMode); 895 return E_ACCESSDENIED; 896 } 897 898 HRESULT hr; 899 BSTR bstr; 900 VARIANTARG vargTemp = { 0 }; 901 switch (V_VT(pvari)) 902 { 903 case VT_EMPTY: 904 bstr = NULL; 905 break; 906 907 case VT_BSTR: 908 bstr = V_BSTR(pvari); 909 break; 910 911 default: 912 hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR); 913 if (FAILED(hr)) 914 goto Quit; 915 916 bstr = V_BSTR(&vargTemp); 917 break; 918 } 919 920 WCHAR szSection[64], szName[64]; 921 hr = _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName)); 922 if (SUCCEEDED(hr)) 923 { 924 if (SHSetIniStringUTF7W(szSection, szName, bstr, m_pszFileName)) 925 { 926 if (!m_bAlternateStream) 927 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_pszFileName, NULL); 928 } 929 else 930 { 931 hr = E_FAIL; 932 } 933 } 934 935 Quit: 936 ::VariantClear(&vargTemp); 937 return hr; 938 } 939 940 /************************************************************************** 941 * SHCreatePropertyBagOnProfileSection (SHLWAPI.472) 942 * 943 * Creates a property bag object on INI file. 944 * 945 * @param lpFileName The INI filename. 946 * @param pszSection The optional section name. 947 * @param dwMode The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE. 948 * @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2. 949 * @param ppvObj Receives an IPropertyBag pointer. 950 * @return An HRESULT value. S_OK on success, non-zero on failure. 951 * @see https://www.geoffchappell.com/studies/windows/shell/shlwapi/api/propbag/createonprofilesection.htm 952 */ 953 EXTERN_C HRESULT WINAPI 954 SHCreatePropertyBagOnProfileSection( 955 _In_z_ LPCWSTR lpFileName, 956 _In_opt_z_ LPCWSTR pszSection, 957 _In_ DWORD dwMode, 958 _In_ REFIID riid, 959 _Out_ void **ppvObj) 960 { 961 HANDLE hFile; 962 PWCHAR pchFileTitle; 963 WCHAR szBuff[MAX_PATH]; 964 965 if (dwMode & STGM_CREATE) 966 { 967 hFile = ::CreateFileW(lpFileName, 0, FILE_SHARE_DELETE, 0, CREATE_NEW, 968 FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, NULL); 969 if (hFile != INVALID_HANDLE_VALUE) 970 { 971 pchFileTitle = PathFindFileNameW(lpFileName); 972 if (lstrcmpiW(pchFileTitle, L"desktop.ini") == 0) 973 { 974 StrCpyNW(szBuff, lpFileName, _countof(szBuff)); 975 if (PathRemoveFileSpecW(szBuff)) 976 PathMakeSystemFolderW(szBuff); 977 } 978 ::CloseHandle(hFile); 979 } 980 } 981 982 *ppvObj = NULL; 983 984 if (!PathFileExistsW(lpFileName)) 985 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 986 987 CComPtr<CIniPropertyBag> pIniPB(new CIniPropertyBag(dwMode)); 988 989 HRESULT hr = pIniPB->Init(lpFileName, pszSection); 990 if (FAILED(hr)) 991 { 992 ERR("0x%08X\n", hr); 993 return hr; 994 } 995 996 return pIniPB->QueryInterface(riid, ppvObj); 997 } 998 999 class CDesktopUpgradePropertyBag : public CBasePropertyBag 1000 { 1001 protected: 1002 BOOL _AlreadyUpgraded(HKEY hKey); 1003 VOID _MarkAsUpgraded(HKEY hkey); 1004 HRESULT _ReadFlags(VARIANT *pvari); 1005 HRESULT _ReadItemPositions(VARIANT *pvari); 1006 IStream* _GetOldDesktopViewStream(); 1007 IStream* _NewStreamFromOld(IStream *pOldStream); 1008 1009 public: 1010 CDesktopUpgradePropertyBag() : CBasePropertyBag(0) { } 1011 1012 STDMETHODIMP Read( 1013 _In_z_ LPCWSTR pszPropName, 1014 _Inout_ VARIANT *pvari, 1015 _Inout_opt_ IErrorLog *pErrorLog) override; 1016 1017 STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override 1018 { 1019 return E_NOTIMPL; 1020 } 1021 }; 1022 1023 VOID CDesktopUpgradePropertyBag::_MarkAsUpgraded(HKEY hkey) 1024 { 1025 DWORD dwValue = TRUE; 1026 SHSetValueW(hkey, NULL, L"Upgrade", REG_DWORD, &dwValue, sizeof(dwValue)); 1027 } 1028 1029 BOOL CDesktopUpgradePropertyBag::_AlreadyUpgraded(HKEY hKey) 1030 { 1031 // Check the existence of the value written in _MarkAsUpgraded. 1032 DWORD dwValue, cbData = sizeof(dwValue); 1033 return SHGetValueW(hKey, NULL, L"Upgrade", NULL, &dwValue, &cbData) == ERROR_SUCCESS; 1034 } 1035 1036 typedef DWORDLONG DESKVIEW_FLAGS; // 64-bit data 1037 1038 HRESULT CDesktopUpgradePropertyBag::_ReadFlags(VARIANT *pvari) 1039 { 1040 DESKVIEW_FLAGS Flags; 1041 DWORD cbValue = sizeof(Flags); 1042 if (SHGetValueW(HKEY_CURRENT_USER, 1043 L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DeskView", 1044 L"Settings", 1045 NULL, 1046 &Flags, 1047 &cbValue) != ERROR_SUCCESS || cbValue < sizeof(Flags)) 1048 { 1049 return E_FAIL; 1050 } 1051 1052 V_UINT(pvari) = ((UINT)(Flags >> 32)) | 0x220; // FIXME: Magic number 1053 V_VT(pvari) = VT_UINT; 1054 return S_OK; 1055 } 1056 1057 typedef struct tagOLD_STREAM_HEADER 1058 { 1059 WORD wMagic; 1060 WORD awUnknown[6]; 1061 WORD wSize; 1062 } OLD_STREAM_HEADER, *POLD_STREAM_HEADER; 1063 1064 IStream* CDesktopUpgradePropertyBag::_NewStreamFromOld(IStream *pOldStream) 1065 { 1066 OLD_STREAM_HEADER Header; 1067 HRESULT hr = pOldStream->Read(&Header, sizeof(Header), NULL); 1068 if (FAILED(hr) || Header.wMagic != 28) 1069 return NULL; 1070 1071 // Move stream pointer 1072 LARGE_INTEGER li; 1073 li.QuadPart = Header.wSize - sizeof(Header); 1074 hr = pOldStream->Seek(li, STREAM_SEEK_CUR, NULL); 1075 if (FAILED(hr)) 1076 return NULL; 1077 1078 // Get the size 1079 ULARGE_INTEGER uli; 1080 hr = IStream_Size(pOldStream, &uli); 1081 if (FAILED(hr)) 1082 return NULL; 1083 1084 // Create new stream and attach 1085 CComPtr<IStream> pNewStream; 1086 pNewStream.Attach(SHCreateMemStream(NULL, 0)); 1087 if (!pNewStream) 1088 return NULL; 1089 1090 // Subtract Header.wSize from the size 1091 uli.QuadPart -= Header.wSize; 1092 1093 // Copy to pNewStream 1094 hr = pOldStream->CopyTo(pNewStream, uli, NULL, NULL); 1095 if (FAILED(hr)) 1096 return NULL; 1097 1098 li.QuadPart = 0; 1099 pNewStream->Seek(li, STREAM_SEEK_SET, NULL); 1100 1101 return pNewStream.Detach(); 1102 } 1103 1104 IStream* CDesktopUpgradePropertyBag::_GetOldDesktopViewStream() 1105 { 1106 HKEY hKey = SHGetShellKey(SHKEY_Root_HKCU, L"Streams\\Desktop", FALSE); 1107 if (!hKey) 1108 return NULL; 1109 1110 CComPtr<IStream> pOldStream; 1111 if (!_AlreadyUpgraded(hKey)) 1112 { 1113 pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView2", 0)); 1114 if (pOldStream) 1115 { 1116 ULARGE_INTEGER uli; 1117 HRESULT hr = IStream_Size(pOldStream, &uli); 1118 if (SUCCEEDED(hr) && !uli.QuadPart) 1119 pOldStream.Release(); 1120 } 1121 1122 if (!pOldStream) 1123 pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView", 0)); 1124 1125 _MarkAsUpgraded(hKey); 1126 } 1127 1128 ::RegCloseKey(hKey); 1129 return pOldStream.Detach(); 1130 } 1131 1132 HRESULT CDesktopUpgradePropertyBag::_ReadItemPositions(VARIANT *pvari) 1133 { 1134 CComPtr<IStream> pOldStream; 1135 pOldStream.Attach(_GetOldDesktopViewStream()); 1136 if (!pOldStream) 1137 return E_FAIL; 1138 1139 HRESULT hr = E_FAIL; 1140 IStream *pNewStream = _NewStreamFromOld(pOldStream); 1141 if (pNewStream) 1142 { 1143 V_UNKNOWN(pvari) = pNewStream; 1144 V_VT(pvari) = VT_UNKNOWN; 1145 hr = S_OK; 1146 } 1147 1148 return hr; 1149 } 1150 1151 STDMETHODIMP 1152 CDesktopUpgradePropertyBag::Read( 1153 _In_z_ LPCWSTR pszPropName, 1154 _Inout_ VARIANT *pvari, 1155 _Inout_opt_ IErrorLog *pErrorLog) 1156 { 1157 UNREFERENCED_PARAMETER(pErrorLog); 1158 1159 VARTYPE vt = V_VT(pvari); 1160 1161 HRESULT hr = E_FAIL; 1162 if (StrCmpW(L"FFlags", pszPropName) == 0) 1163 hr = _ReadFlags(pvari); 1164 else if (StrCmpNW(L"ItemPos", pszPropName, 7) == 0) 1165 hr = _ReadItemPositions(pvari); 1166 1167 if (FAILED(hr)) 1168 { 1169 ::VariantInit(pvari); 1170 return hr; 1171 } 1172 1173 return ::VariantChangeType(pvari, pvari, 0, vt); 1174 } 1175 1176 /************************************************************************** 1177 * SHGetDesktopUpgradePropertyBag (Not exported; used in CViewStatePropertyBag) 1178 * 1179 * Creates or gets a property bag object for desktop upgrade 1180 * 1181 * @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2. 1182 * @param ppvObj Receives an IPropertyBag pointer. 1183 * @return An HRESULT value. S_OK on success, non-zero on failure. 1184 */ 1185 HRESULT SHGetDesktopUpgradePropertyBag(REFIID riid, void **ppvObj) 1186 { 1187 *ppvObj = NULL; 1188 CComPtr<CDesktopUpgradePropertyBag> pPropBag(new CDesktopUpgradePropertyBag()); 1189 return pPropBag->QueryInterface(riid, ppvObj); 1190 } 1191