1 /* 2 * 3 * Copyright 1997 Marcus Meissner 4 * Copyright 1998 Juergen Schmied 5 * Copyright 2005 Mike McCormack 6 * Copyright 2009 Andrew Hill 7 * Copyright 2013 Dominik Hornung 8 * Copyright 2017 Hermes Belusca-Maito 9 * Copyright 2018 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 10 * 11 * This library is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU Lesser General Public 13 * License as published by the Free Software Foundation; either 14 * version 2.1 of the License, or (at your option) any later version. 15 * 16 * This library is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * Lesser General Public License for more details. 20 * 21 * You should have received a copy of the GNU Lesser General Public 22 * License along with this library; if not, write to the Free Software 23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 24 * 25 * NOTES 26 * Nearly complete information about the binary formats 27 * of .lnk files available at http://www.wotsit.org 28 * 29 * You can use winedump to examine the contents of a link file: 30 * winedump lnk sc.lnk 31 * 32 * MSI advertised shortcuts are totally undocumented. They provide an 33 * icon for a program that is not yet installed, and invoke MSI to 34 * install the program when the shortcut is clicked on. They are 35 * created by passing a special string to SetPath, and the information 36 * in that string is parsed an stored. 37 */ 38 /* 39 * In the following is listed more documentation about the Shell Link file format, 40 * as well as its interface. 41 * 42 * General introduction about "Shell Links" (MSDN): 43 * https://msdn.microsoft.com/en-us/library/windows/desktop/bb776891(v=vs.85).aspx 44 * 45 * 46 * Details of the file format: 47 * 48 * - Official MSDN documentation "[MS-SHLLINK]: Shell Link (.LNK) Binary File Format": 49 * https://msdn.microsoft.com/en-us/library/dd871305.aspx 50 * 51 * - Forensics: 52 * http://forensicswiki.org/wiki/LNK 53 * http://computerforensics.parsonage.co.uk/downloads/TheMeaningofLIFE.pdf 54 * https://ithreats.files.wordpress.com/2009/05/lnk_the_windows_shortcut_file_format.pdf 55 * https://github.com/libyal/liblnk/blob/master/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc 56 * 57 * - List of possible shell link header flags (SHELL_LINK_DATA_FLAGS enumeration): 58 * https://msdn.microsoft.com/en-us/library/windows/desktop/bb762540(v=vs.85).aspx 59 * https://msdn.microsoft.com/en-us/library/dd891314.aspx 60 * 61 * 62 * In addition to storing its target by using a PIDL, a shell link file also 63 * stores metadata to make the shell able to track the link target, in situations 64 * where the link target is moved amongst local or network directories, or moved 65 * to different volumes. For this, two structures are used: 66 * 67 * - The first and oldest one (from NewShell/WinNT4) is the "LinkInfo" structure, 68 * stored in a serialized manner at the beginning of the shell link file: 69 * https://msdn.microsoft.com/en-us/library/dd871404.aspx 70 * The official API for manipulating this is located in LINKINFO.DLL . 71 * 72 * - The second, more recent one, is an extra binary block appended to the 73 * extra-data list of the shell link file: this is the "TrackerDataBlock": 74 * https://msdn.microsoft.com/en-us/library/dd891376.aspx 75 * Its purpose is for link tracking, and works in coordination with the 76 * "Distributed Link Tracking" service ('TrkWks' client, 'TrkSvr' server). 77 * See a detailed explanation at: 78 * http://www.serverwatch.com/tutorials/article.php/1476701/Searching-for-the-Missing-Link-Distributed-Link-Tracking.htm 79 * 80 * 81 * MSI installations most of the time create so-called "advertised shortcuts". 82 * They provide an icon for a program that may not be installed yet, and invoke 83 * MSI to install the program when the shortcut is opened (resolved). 84 * The philosophy of this approach is explained in detail inside the MSDN article 85 * "Application Resiliency: Unlock the Hidden Features of Windows Installer" 86 * (by Michael Sanford), here: 87 * https://msdn.microsoft.com/en-us/library/aa302344.aspx 88 * 89 * This functionality is implemented by adding a binary "Darwin" data block 90 * of type "EXP_DARWIN_LINK", signature EXP_DARWIN_ID_SIG == 0xA0000006, 91 * to the shell link file: 92 * https://msdn.microsoft.com/en-us/library/dd871369.aspx 93 * or, this could be done more simply by specifying a special link target path 94 * with the IShellLink::SetPath() function. Defining the following GUID: 95 * SHELL32_AdvtShortcutComponent = "::{9db1186e-40df-11d1-aa8c-00c04fb67863}:" 96 * setting a target of the form: 97 * "::{SHELL32_AdvtShortcutComponent}:<MSI_App_ID>" 98 * would automatically create the necessary binary block. 99 * 100 * With that, the target of the shortcut now becomes the MSI data. The latter 101 * is parsed from MSI and retrieved by the shell that then can run the program. 102 * 103 * This MSI functionality, dubbed "link blessing", actually originates from an 104 * older technology introduced in Internet Explorer 3 (and now obsolete since 105 * Internet Explorer 7), called "MS Internet Component Download (MSICD)", see 106 * this MSDN introductory article: 107 * https://msdn.microsoft.com/en-us/library/aa741198(v=vs.85).aspx 108 * and leveraged in Internet Explorer 4 with "Software Update Channels", see: 109 * https://msdn.microsoft.com/en-us/library/aa740931(v=vs.85).aspx 110 * Applications supporting this technology could present shell links having 111 * a special target, see subsection "Modifying the Shortcut" in the article: 112 * https://msdn.microsoft.com/en-us/library/aa741201(v=vs.85).aspx#pub_shor 113 * 114 * Similarly as for the MSI shortcuts, these MSICD shortcuts are created by 115 * specifying a special link target path with the IShellLink::SetPath() function, 116 * defining the following GUID: 117 * SHELL32_AdvtShortcutProduct = "::{9db1186f-40df-11d1-aa8c-00c04fb67863}:" 118 * and setting a target of the form: 119 * "::{SHELL32_AdvtShortcutProduct}:<AppName>::<Path>" . 120 * A tool, called "blesslnk.exe", was also provided for automatizing the process; 121 * its ReadMe can be found in the (now outdated) MS "Internet Client SDK" (INetSDK, 122 * for MS Windows 95 and NT), whose contents can be read at: 123 * http://www.msfn.org/board/topic/145352-new-windows-lnk-vulnerability/?page=4#comment-944223 124 * The MS INetSDK can be found at: 125 * https://web.archive.org/web/20100924000013/http://support.microsoft.com/kb/177877 126 * 127 * Internally the shell link target of these MSICD shortcuts is converted into 128 * a binary data block of a type similar to Darwin / "EXP_DARWIN_LINK", but with 129 * a different signature EXP_LOGO3_ID_SIG == 0xA0000007 . Such shell links are 130 * called "Logo3" shortcuts. They were evoked in this user comment in "The Old 131 * New Thing" blog: 132 * https://blogs.msdn.microsoft.com/oldnewthing/20121210-00/?p=5883#comment-1025083 133 * 134 * The shell exports the API 'SoftwareUpdateMessageBox' (in shdocvw.dll) that 135 * displays a message when an update for an application supporting this 136 * technology is available. 137 * 138 */ 139 140 #include "precomp.h" 141 142 #include <appmgmt.h> 143 144 WINE_DEFAULT_DEBUG_CHANNEL(shell); 145 146 /* 147 * Allows to define whether or not Windows-compatible behaviour 148 * should be adopted when setting and retrieving icon location paths. 149 * See CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT iIcon) 150 * for more details. 151 */ 152 #define ICON_LINK_WINDOWS_COMPAT 153 154 #define SHLINK_LOCAL 0 155 #define SHLINK_REMOTE 1 156 157 /* link file formats */ 158 159 #include "pshpack1.h" 160 161 struct LOCATION_INFO 162 { 163 DWORD dwTotalSize; 164 DWORD dwHeaderSize; 165 DWORD dwFlags; 166 DWORD dwVolTableOfs; 167 DWORD dwLocalPathOfs; 168 DWORD dwNetworkVolTableOfs; 169 DWORD dwFinalPathOfs; 170 }; 171 172 struct LOCAL_VOLUME_INFO 173 { 174 DWORD dwSize; 175 DWORD dwType; 176 DWORD dwVolSerial; 177 DWORD dwVolLabelOfs; 178 }; 179 180 struct volume_info 181 { 182 DWORD type; 183 DWORD serial; 184 WCHAR label[12]; /* assume 8.3 */ 185 }; 186 187 #include "poppack.h" 188 189 /* IShellLink Implementation */ 190 191 static HRESULT ShellLink_UpdatePath(LPCWSTR sPathRel, LPCWSTR path, LPCWSTR sWorkDir, LPWSTR* psPath); 192 193 /* strdup on the process heap */ 194 static LPWSTR __inline HEAP_strdupAtoW(HANDLE heap, DWORD flags, LPCSTR str) 195 { 196 INT len; 197 LPWSTR p; 198 199 assert(str); 200 201 len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); 202 p = (LPWSTR)HeapAlloc(heap, flags, len * sizeof(WCHAR)); 203 if (!p) 204 return p; 205 MultiByteToWideChar(CP_ACP, 0, str, -1, p, len); 206 return p; 207 } 208 209 static LPWSTR __inline strdupW(LPCWSTR src) 210 { 211 LPWSTR dest; 212 if (!src) return NULL; 213 dest = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(src) + 1) * sizeof(WCHAR)); 214 if (dest) 215 wcscpy(dest, src); 216 return dest; 217 } 218 219 // TODO: Use it for constructor & destructor too 220 VOID CShellLink::Reset() 221 { 222 ILFree(m_pPidl); 223 m_pPidl = NULL; 224 225 HeapFree(GetProcessHeap(), 0, m_sPath); 226 m_sPath = NULL; 227 ZeroMemory(&volume, sizeof(volume)); 228 229 HeapFree(GetProcessHeap(), 0, m_sDescription); 230 m_sDescription = NULL; 231 HeapFree(GetProcessHeap(), 0, m_sPathRel); 232 m_sPathRel = NULL; 233 HeapFree(GetProcessHeap(), 0, m_sWorkDir); 234 m_sWorkDir = NULL; 235 HeapFree(GetProcessHeap(), 0, m_sArgs); 236 m_sArgs = NULL; 237 HeapFree(GetProcessHeap(), 0, m_sIcoPath); 238 m_sIcoPath = NULL; 239 240 m_bRunAs = FALSE; 241 m_bDirty = FALSE; 242 243 if (m_pDBList) 244 SHFreeDataBlockList(m_pDBList); 245 m_pDBList = NULL; 246 247 /**/sProduct = sComponent = NULL;/**/ 248 } 249 250 CShellLink::CShellLink() 251 { 252 m_Header.dwSize = sizeof(m_Header); 253 m_Header.clsid = CLSID_ShellLink; 254 m_Header.dwFlags = 0; 255 256 m_Header.dwFileAttributes = 0; 257 ZeroMemory(&m_Header.ftCreationTime, sizeof(m_Header.ftCreationTime)); 258 ZeroMemory(&m_Header.ftLastAccessTime, sizeof(m_Header.ftLastAccessTime)); 259 ZeroMemory(&m_Header.ftLastWriteTime, sizeof(m_Header.ftLastWriteTime)); 260 m_Header.nFileSizeLow = 0; 261 262 m_Header.nIconIndex = 0; 263 m_Header.nShowCommand = SW_SHOWNORMAL; 264 m_Header.wHotKey = 0; 265 266 m_pPidl = NULL; 267 268 m_sPath = NULL; 269 ZeroMemory(&volume, sizeof(volume)); 270 271 m_sDescription = NULL; 272 m_sPathRel = NULL; 273 m_sWorkDir = NULL; 274 m_sArgs = NULL; 275 m_sIcoPath = NULL; 276 m_bRunAs = FALSE; 277 m_bDirty = FALSE; 278 m_pDBList = NULL; 279 m_bInInit = FALSE; 280 m_hIcon = NULL; 281 282 m_sLinkPath = NULL; 283 m_iIdOpen = -1; 284 285 /**/sProduct = sComponent = NULL;/**/ 286 } 287 288 CShellLink::~CShellLink() 289 { 290 TRACE("-- destroying IShellLink(%p)\n", this); 291 292 ILFree(m_pPidl); 293 294 HeapFree(GetProcessHeap(), 0, m_sPath); 295 296 HeapFree(GetProcessHeap(), 0, m_sDescription); 297 HeapFree(GetProcessHeap(), 0, m_sPathRel); 298 HeapFree(GetProcessHeap(), 0, m_sWorkDir); 299 HeapFree(GetProcessHeap(), 0, m_sArgs); 300 HeapFree(GetProcessHeap(), 0, m_sIcoPath); 301 HeapFree(GetProcessHeap(), 0, m_sLinkPath); 302 SHFreeDataBlockList(m_pDBList); 303 } 304 305 HRESULT STDMETHODCALLTYPE CShellLink::GetClassID(CLSID *pclsid) 306 { 307 TRACE("%p %p\n", this, pclsid); 308 309 if (pclsid == NULL) 310 return E_POINTER; 311 *pclsid = CLSID_ShellLink; 312 return S_OK; 313 } 314 315 /************************************************************************ 316 * IPersistStream_IsDirty (IPersistStream) 317 */ 318 HRESULT STDMETHODCALLTYPE CShellLink::IsDirty() 319 { 320 TRACE("(%p)\n", this); 321 return (m_bDirty ? S_OK : S_FALSE); 322 } 323 324 HRESULT STDMETHODCALLTYPE CShellLink::Load(LPCOLESTR pszFileName, DWORD dwMode) 325 { 326 TRACE("(%p, %s, %x)\n", this, debugstr_w(pszFileName), dwMode); 327 328 if (dwMode == 0) 329 dwMode = STGM_READ | STGM_SHARE_DENY_WRITE; 330 331 CComPtr<IStream> stm; 332 HRESULT hr = SHCreateStreamOnFileW(pszFileName, dwMode, &stm); 333 if (SUCCEEDED(hr)) 334 { 335 HeapFree(GetProcessHeap(), 0, m_sLinkPath); 336 m_sLinkPath = strdupW(pszFileName); 337 hr = Load(stm); 338 ShellLink_UpdatePath(m_sPathRel, pszFileName, m_sWorkDir, &m_sPath); 339 m_bDirty = FALSE; 340 } 341 TRACE("-- returning hr %08x\n", hr); 342 return hr; 343 } 344 345 HRESULT STDMETHODCALLTYPE CShellLink::Save(LPCOLESTR pszFileName, BOOL fRemember) 346 { 347 TRACE("(%p)->(%s)\n", this, debugstr_w(pszFileName)); 348 349 if (!pszFileName) 350 return E_FAIL; 351 352 CComPtr<IStream> stm; 353 HRESULT hr = SHCreateStreamOnFileW(pszFileName, STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, &stm); 354 if (SUCCEEDED(hr)) 355 { 356 hr = Save(stm, FALSE); 357 358 if (SUCCEEDED(hr)) 359 { 360 if (m_sLinkPath) 361 HeapFree(GetProcessHeap(), 0, m_sLinkPath); 362 363 m_sLinkPath = strdupW(pszFileName); 364 m_bDirty = FALSE; 365 } 366 else 367 { 368 DeleteFileW(pszFileName); 369 WARN("Failed to create shortcut %s\n", debugstr_w(pszFileName)); 370 } 371 } 372 373 return hr; 374 } 375 376 HRESULT STDMETHODCALLTYPE CShellLink::SaveCompleted(LPCOLESTR pszFileName) 377 { 378 FIXME("(%p)->(%s)\n", this, debugstr_w(pszFileName)); 379 return S_OK; 380 } 381 382 HRESULT STDMETHODCALLTYPE CShellLink::GetCurFile(LPOLESTR *ppszFileName) 383 { 384 *ppszFileName = NULL; 385 386 if (!m_sLinkPath) 387 { 388 /* IPersistFile::GetCurFile called before IPersistFile::Save */ 389 return S_FALSE; 390 } 391 392 *ppszFileName = (LPOLESTR)CoTaskMemAlloc((wcslen(m_sLinkPath) + 1) * sizeof(WCHAR)); 393 if (!*ppszFileName) 394 { 395 /* out of memory */ 396 return E_OUTOFMEMORY; 397 } 398 399 /* copy last saved filename */ 400 wcscpy(*ppszFileName, m_sLinkPath); 401 402 return S_OK; 403 } 404 405 static HRESULT Stream_LoadString(IStream* stm, BOOL unicode, LPWSTR *pstr) 406 { 407 TRACE("%p\n", stm); 408 409 USHORT len; 410 DWORD count = 0; 411 HRESULT hr = stm->Read(&len, sizeof(len), &count); 412 if (FAILED(hr) || count != sizeof(len)) 413 return E_FAIL; 414 415 if (unicode) 416 len *= sizeof(WCHAR); 417 418 TRACE("reading %d\n", len); 419 LPSTR temp = (LPSTR)HeapAlloc(GetProcessHeap(), 0, len + sizeof(WCHAR)); 420 if (!temp) 421 return E_OUTOFMEMORY; 422 count = 0; 423 hr = stm->Read(temp, len, &count); 424 if (FAILED(hr) || count != len) 425 { 426 HeapFree(GetProcessHeap(), 0, temp); 427 return E_FAIL; 428 } 429 430 TRACE("read %s\n", debugstr_an(temp, len)); 431 432 /* convert to unicode if necessary */ 433 LPWSTR str; 434 if (!unicode) 435 { 436 count = MultiByteToWideChar(CP_ACP, 0, temp, len, NULL, 0); 437 str = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (count + 1) * sizeof(WCHAR)); 438 if (!str) 439 { 440 HeapFree(GetProcessHeap(), 0, temp); 441 return E_OUTOFMEMORY; 442 } 443 MultiByteToWideChar(CP_ACP, 0, temp, len, str, count); 444 HeapFree(GetProcessHeap(), 0, temp); 445 } 446 else 447 { 448 count /= sizeof(WCHAR); 449 str = (LPWSTR)temp; 450 } 451 str[count] = 0; 452 453 *pstr = str; 454 455 return S_OK; 456 } 457 458 459 /* 460 * NOTE: The following 5 functions are part of LINKINFO.DLL 461 */ 462 static BOOL ShellLink_GetVolumeInfo(LPCWSTR path, CShellLink::volume_info *volume) 463 { 464 WCHAR drive[4] = { path[0], ':', '\\', 0 }; 465 466 volume->type = GetDriveTypeW(drive); 467 BOOL bRet = GetVolumeInformationW(drive, volume->label, _countof(volume->label), &volume->serial, NULL, NULL, NULL, 0); 468 TRACE("ret = %d type %d serial %08x name %s\n", bRet, 469 volume->type, volume->serial, debugstr_w(volume->label)); 470 return bRet; 471 } 472 473 static HRESULT Stream_ReadChunk(IStream* stm, LPVOID *data) 474 { 475 struct sized_chunk 476 { 477 DWORD size; 478 unsigned char data[1]; 479 } *chunk; 480 481 TRACE("%p\n", stm); 482 483 DWORD size; 484 ULONG count; 485 HRESULT hr = stm->Read(&size, sizeof(size), &count); 486 if (FAILED(hr) || count != sizeof(size)) 487 return E_FAIL; 488 489 chunk = static_cast<sized_chunk *>(HeapAlloc(GetProcessHeap(), 0, size)); 490 if (!chunk) 491 return E_OUTOFMEMORY; 492 493 chunk->size = size; 494 hr = stm->Read(chunk->data, size - sizeof(size), &count); 495 if (FAILED(hr) || count != (size - sizeof(size))) 496 { 497 HeapFree(GetProcessHeap(), 0, chunk); 498 return E_FAIL; 499 } 500 501 TRACE("Read %d bytes\n", chunk->size); 502 503 *data = chunk; 504 505 return S_OK; 506 } 507 508 static BOOL Stream_LoadVolume(LOCAL_VOLUME_INFO *vol, CShellLink::volume_info *volume) 509 { 510 volume->serial = vol->dwVolSerial; 511 volume->type = vol->dwType; 512 513 if (!vol->dwVolLabelOfs) 514 return FALSE; 515 if (vol->dwSize <= vol->dwVolLabelOfs) 516 return FALSE; 517 INT len = vol->dwSize - vol->dwVolLabelOfs; 518 519 LPSTR label = (LPSTR)vol; 520 label += vol->dwVolLabelOfs; 521 MultiByteToWideChar(CP_ACP, 0, label, len, volume->label, _countof(volume->label)); 522 523 return TRUE; 524 } 525 526 static LPWSTR Stream_LoadPath(LPCSTR p, DWORD maxlen) 527 { 528 UINT len = 0; 529 530 while (len < maxlen && p[len]) 531 len++; 532 533 UINT wlen = MultiByteToWideChar(CP_ACP, 0, p, len, NULL, 0); 534 LPWSTR path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wlen + 1) * sizeof(WCHAR)); 535 if (!path) 536 return NULL; 537 MultiByteToWideChar(CP_ACP, 0, p, len, path, wlen); 538 path[wlen] = 0; 539 540 return path; 541 } 542 543 static HRESULT Stream_LoadLocation(IStream *stm, 544 CShellLink::volume_info *volume, LPWSTR *path) 545 { 546 char *p = NULL; 547 HRESULT hr = Stream_ReadChunk(stm, (LPVOID*) &p); 548 if (FAILED(hr)) 549 return hr; 550 551 LOCATION_INFO *loc = reinterpret_cast<LOCATION_INFO *>(p); 552 if (loc->dwTotalSize < sizeof(LOCATION_INFO)) 553 { 554 HeapFree(GetProcessHeap(), 0, p); 555 return E_FAIL; 556 } 557 558 /* if there's valid local volume information, load it */ 559 if (loc->dwVolTableOfs && 560 ((loc->dwVolTableOfs + sizeof(LOCAL_VOLUME_INFO)) <= loc->dwTotalSize)) 561 { 562 LOCAL_VOLUME_INFO *volume_info; 563 564 volume_info = (LOCAL_VOLUME_INFO*) &p[loc->dwVolTableOfs]; 565 Stream_LoadVolume(volume_info, volume); 566 } 567 568 /* if there's a local path, load it */ 569 DWORD n = loc->dwLocalPathOfs; 570 if (n && n < loc->dwTotalSize) 571 *path = Stream_LoadPath(&p[n], loc->dwTotalSize - n); 572 573 TRACE("type %d serial %08x name %s path %s\n", volume->type, 574 volume->serial, debugstr_w(volume->label), debugstr_w(*path)); 575 576 HeapFree(GetProcessHeap(), 0, p); 577 return S_OK; 578 } 579 580 581 /* 582 * The format of the advertised shortcut info is: 583 * 584 * Offset Description 585 * ------ ----------- 586 * 0 Length of the block (4 bytes, usually 0x314) 587 * 4 tag (dword) 588 * 8 string data in ASCII 589 * 8+0x104 string data in UNICODE 590 * 591 * In the original Win32 implementation the buffers are not initialized 592 * to zero, so data trailing the string is random garbage. 593 */ 594 HRESULT CShellLink::GetAdvertiseInfo(LPWSTR *str, DWORD dwSig) 595 { 596 LPEXP_DARWIN_LINK pInfo; 597 598 *str = NULL; 599 600 pInfo = (LPEXP_DARWIN_LINK)SHFindDataBlock(m_pDBList, dwSig); 601 if (!pInfo) 602 return E_FAIL; 603 604 /* Make sure that the size of the structure is valid */ 605 if (pInfo->dbh.cbSize != sizeof(*pInfo)) 606 { 607 ERR("Ooops. This structure is not as expected...\n"); 608 return E_FAIL; 609 } 610 611 TRACE("dwSig %08x string = '%s'\n", pInfo->dbh.dwSignature, debugstr_w(pInfo->szwDarwinID)); 612 613 *str = pInfo->szwDarwinID; 614 return S_OK; 615 } 616 617 /************************************************************************ 618 * IPersistStream_Load (IPersistStream) 619 */ 620 HRESULT STDMETHODCALLTYPE CShellLink::Load(IStream *stm) 621 { 622 TRACE("%p %p\n", this, stm); 623 624 if (!stm) 625 return STG_E_INVALIDPOINTER; 626 627 /* Free all the old stuff */ 628 Reset(); 629 630 ULONG dwBytesRead = 0; 631 HRESULT hr = stm->Read(&m_Header, sizeof(m_Header), &dwBytesRead); 632 if (FAILED(hr)) 633 return hr; 634 635 if (dwBytesRead != sizeof(m_Header)) 636 return E_FAIL; 637 if (m_Header.dwSize != sizeof(m_Header)) 638 return E_FAIL; 639 if (!IsEqualIID(m_Header.clsid, CLSID_ShellLink)) 640 return E_FAIL; 641 642 /* Load the new data in order */ 643 644 if (TRACE_ON(shell)) 645 { 646 SYSTEMTIME stCreationTime; 647 SYSTEMTIME stLastAccessTime; 648 SYSTEMTIME stLastWriteTime; 649 WCHAR sTemp[MAX_PATH]; 650 651 FileTimeToSystemTime(&m_Header.ftCreationTime, &stCreationTime); 652 FileTimeToSystemTime(&m_Header.ftLastAccessTime, &stLastAccessTime); 653 FileTimeToSystemTime(&m_Header.ftLastWriteTime, &stLastWriteTime); 654 655 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stCreationTime, 656 NULL, sTemp, _countof(sTemp)); 657 TRACE("-- stCreationTime: %s\n", debugstr_w(sTemp)); 658 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stLastAccessTime, 659 NULL, sTemp, _countof(sTemp)); 660 TRACE("-- stLastAccessTime: %s\n", debugstr_w(sTemp)); 661 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stLastWriteTime, 662 NULL, sTemp, _countof(sTemp)); 663 TRACE("-- stLastWriteTime: %s\n", debugstr_w(sTemp)); 664 } 665 666 /* load all the new stuff */ 667 if (m_Header.dwFlags & SLDF_HAS_ID_LIST) 668 { 669 hr = ILLoadFromStream(stm, &m_pPidl); 670 if (FAILED(hr)) 671 return hr; 672 } 673 pdump(m_pPidl); 674 675 /* Load the location information... */ 676 if (m_Header.dwFlags & SLDF_HAS_LINK_INFO) 677 { 678 hr = Stream_LoadLocation(stm, &volume, &m_sPath); 679 if (FAILED(hr)) 680 return hr; 681 } 682 /* ... but if it is required not to use it, clear it */ 683 if (m_Header.dwFlags & SLDF_FORCE_NO_LINKINFO) 684 { 685 HeapFree(GetProcessHeap(), 0, m_sPath); 686 m_sPath = NULL; 687 ZeroMemory(&volume, sizeof(volume)); 688 } 689 690 BOOL unicode = !!(m_Header.dwFlags & SLDF_UNICODE); 691 692 if (m_Header.dwFlags & SLDF_HAS_NAME) 693 { 694 hr = Stream_LoadString(stm, unicode, &m_sDescription); 695 if (FAILED(hr)) 696 return hr; 697 TRACE("Description -> %s\n", debugstr_w(m_sDescription)); 698 } 699 700 if (m_Header.dwFlags & SLDF_HAS_RELPATH) 701 { 702 hr = Stream_LoadString(stm, unicode, &m_sPathRel); 703 if (FAILED(hr)) 704 return hr; 705 TRACE("Relative Path-> %s\n", debugstr_w(m_sPathRel)); 706 } 707 708 if (m_Header.dwFlags & SLDF_HAS_WORKINGDIR) 709 { 710 hr = Stream_LoadString(stm, unicode, &m_sWorkDir); 711 if (FAILED(hr)) 712 return hr; 713 PathRemoveBackslash(m_sWorkDir); 714 TRACE("Working Dir -> %s\n", debugstr_w(m_sWorkDir)); 715 } 716 717 if (m_Header.dwFlags & SLDF_HAS_ARGS) 718 { 719 hr = Stream_LoadString(stm, unicode, &m_sArgs); 720 if (FAILED(hr)) 721 return hr; 722 TRACE("Arguments -> %s\n", debugstr_w(m_sArgs)); 723 } 724 725 if (m_Header.dwFlags & SLDF_HAS_ICONLOCATION) 726 { 727 hr = Stream_LoadString(stm, unicode, &m_sIcoPath); 728 if (FAILED(hr)) 729 return hr; 730 TRACE("Icon file -> %s\n", debugstr_w(m_sIcoPath)); 731 } 732 733 /* Now load the optional data block list */ 734 hr = SHReadDataBlockList(stm, &m_pDBList); 735 if (FAILED(hr)) // FIXME: Should we fail? 736 return hr; 737 738 if (TRACE_ON(shell)) 739 { 740 #if (NTDDI_VERSION < NTDDI_LONGHORN) 741 if (m_Header.dwFlags & SLDF_HAS_LOGO3ID) 742 { 743 hr = GetAdvertiseInfo(&sProduct, EXP_LOGO3_ID_SIG); 744 if (SUCCEEDED(hr)) 745 TRACE("Product -> %s\n", debugstr_w(sProduct)); 746 } 747 #endif 748 if (m_Header.dwFlags & SLDF_HAS_DARWINID) 749 { 750 hr = GetAdvertiseInfo(&sComponent, EXP_DARWIN_ID_SIG); 751 if (SUCCEEDED(hr)) 752 TRACE("Component -> %s\n", debugstr_w(sComponent)); 753 } 754 } 755 756 if (m_Header.dwFlags & SLDF_RUNAS_USER) 757 m_bRunAs = TRUE; 758 else 759 m_bRunAs = FALSE; 760 761 TRACE("OK\n"); 762 763 pdump(m_pPidl); 764 765 return S_OK; 766 } 767 768 /************************************************************************ 769 * Stream_WriteString 770 * 771 * Helper function for IPersistStream_Save. Writes a unicode string 772 * with terminating nul byte to a stream, preceded by the its length. 773 */ 774 static HRESULT Stream_WriteString(IStream* stm, LPCWSTR str) 775 { 776 USHORT len = wcslen(str) + 1; // FIXME: Possible overflows? 777 DWORD count; 778 779 HRESULT hr = stm->Write(&len, sizeof(len), &count); 780 if (FAILED(hr)) 781 return hr; 782 783 len *= sizeof(WCHAR); 784 785 hr = stm->Write(str, len, &count); 786 if (FAILED(hr)) 787 return hr; 788 789 return S_OK; 790 } 791 792 /************************************************************************ 793 * Stream_WriteLocationInfo 794 * 795 * Writes the location info to a stream 796 * 797 * FIXME: One day we might want to write the network volume information 798 * and the final path. 799 * Figure out how Windows deals with unicode paths here. 800 */ 801 static HRESULT Stream_WriteLocationInfo(IStream* stm, LPCWSTR path, 802 CShellLink::volume_info *volume) 803 { 804 LOCAL_VOLUME_INFO *vol; 805 LOCATION_INFO *loc; 806 807 TRACE("%p %s %p\n", stm, debugstr_w(path), volume); 808 809 /* figure out the size of everything */ 810 DWORD label_size = WideCharToMultiByte(CP_ACP, 0, volume->label, -1, 811 NULL, 0, NULL, NULL); 812 DWORD path_size = WideCharToMultiByte(CP_ACP, 0, path, -1, 813 NULL, 0, NULL, NULL); 814 DWORD volume_info_size = sizeof(*vol) + label_size; 815 DWORD final_path_size = 1; 816 DWORD total_size = sizeof(*loc) + volume_info_size + path_size + final_path_size; 817 818 /* create pointers to everything */ 819 loc = static_cast<LOCATION_INFO *>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, total_size)); 820 vol = (LOCAL_VOLUME_INFO*) &loc[1]; 821 LPSTR szLabel = (LPSTR) &vol[1]; 822 LPSTR szPath = &szLabel[label_size]; 823 LPSTR szFinalPath = &szPath[path_size]; 824 825 /* fill in the location information header */ 826 loc->dwTotalSize = total_size; 827 loc->dwHeaderSize = sizeof(*loc); 828 loc->dwFlags = 1; 829 loc->dwVolTableOfs = sizeof(*loc); 830 loc->dwLocalPathOfs = sizeof(*loc) + volume_info_size; 831 loc->dwNetworkVolTableOfs = 0; 832 loc->dwFinalPathOfs = sizeof(*loc) + volume_info_size + path_size; 833 834 /* fill in the volume information */ 835 vol->dwSize = volume_info_size; 836 vol->dwType = volume->type; 837 vol->dwVolSerial = volume->serial; 838 vol->dwVolLabelOfs = sizeof(*vol); 839 840 /* copy in the strings */ 841 WideCharToMultiByte(CP_ACP, 0, volume->label, -1, 842 szLabel, label_size, NULL, NULL); 843 WideCharToMultiByte(CP_ACP, 0, path, -1, 844 szPath, path_size, NULL, NULL); 845 *szFinalPath = 0; 846 847 ULONG count = 0; 848 HRESULT hr = stm->Write(loc, total_size, &count); 849 HeapFree(GetProcessHeap(), 0, loc); 850 851 return hr; 852 } 853 854 /************************************************************************ 855 * IPersistStream_Save (IPersistStream) 856 * 857 * FIXME: makes assumptions about byte order 858 */ 859 HRESULT STDMETHODCALLTYPE CShellLink::Save(IStream *stm, BOOL fClearDirty) 860 { 861 TRACE("%p %p %x\n", this, stm, fClearDirty); 862 863 m_Header.dwSize = sizeof(m_Header); 864 m_Header.clsid = CLSID_ShellLink; 865 866 /* 867 * Reset the flags: keep only the flags related to data blocks as they were 868 * already set in accordance by the different mutator member functions. 869 * The other flags will be determined now by the presence or absence of data. 870 */ 871 m_Header.dwFlags &= (SLDF_RUN_WITH_SHIMLAYER | SLDF_RUNAS_USER | 872 SLDF_RUN_IN_SEPARATE | SLDF_HAS_DARWINID | 873 #if (NTDDI_VERSION < NTDDI_LONGHORN) 874 SLDF_HAS_LOGO3ID | 875 #endif 876 SLDF_HAS_EXP_ICON_SZ | SLDF_HAS_EXP_SZ); 877 // TODO: When we will support Vista+ functionality, add other flags to this list. 878 879 /* The stored strings are in UNICODE */ 880 m_Header.dwFlags |= SLDF_UNICODE; 881 882 if (m_pPidl) 883 m_Header.dwFlags |= SLDF_HAS_ID_LIST; 884 if (m_sPath) 885 m_Header.dwFlags |= SLDF_HAS_LINK_INFO; 886 if (m_sDescription && *m_sDescription) 887 m_Header.dwFlags |= SLDF_HAS_NAME; 888 if (m_sPathRel && *m_sPathRel) 889 m_Header.dwFlags |= SLDF_HAS_RELPATH; 890 if (m_sWorkDir && *m_sWorkDir) 891 m_Header.dwFlags |= SLDF_HAS_WORKINGDIR; 892 if (m_sArgs && *m_sArgs) 893 m_Header.dwFlags |= SLDF_HAS_ARGS; 894 if (m_sIcoPath && *m_sIcoPath) 895 m_Header.dwFlags |= SLDF_HAS_ICONLOCATION; 896 if (m_bRunAs) 897 m_Header.dwFlags |= SLDF_RUNAS_USER; 898 899 /* Write the shortcut header */ 900 ULONG count; 901 HRESULT hr = stm->Write(&m_Header, sizeof(m_Header), &count); 902 if (FAILED(hr)) 903 { 904 ERR("Write failed\n"); 905 return hr; 906 } 907 908 /* Save the data in order */ 909 910 if (m_pPidl) 911 { 912 hr = ILSaveToStream(stm, m_pPidl); 913 if (FAILED(hr)) 914 { 915 ERR("Failed to write PIDL\n"); 916 return hr; 917 } 918 } 919 920 if (m_sPath) 921 { 922 hr = Stream_WriteLocationInfo(stm, m_sPath, &volume); 923 if (FAILED(hr)) 924 return hr; 925 } 926 927 if (m_Header.dwFlags & SLDF_HAS_NAME) 928 { 929 hr = Stream_WriteString(stm, m_sDescription); 930 if (FAILED(hr)) 931 return hr; 932 } 933 934 if (m_Header.dwFlags & SLDF_HAS_RELPATH) 935 { 936 hr = Stream_WriteString(stm, m_sPathRel); 937 if (FAILED(hr)) 938 return hr; 939 } 940 941 if (m_Header.dwFlags & SLDF_HAS_WORKINGDIR) 942 { 943 hr = Stream_WriteString(stm, m_sWorkDir); 944 if (FAILED(hr)) 945 return hr; 946 } 947 948 if (m_Header.dwFlags & SLDF_HAS_ARGS) 949 { 950 hr = Stream_WriteString(stm, m_sArgs); 951 if (FAILED(hr)) 952 return hr; 953 } 954 955 if (m_Header.dwFlags & SLDF_HAS_ICONLOCATION) 956 { 957 hr = Stream_WriteString(stm, m_sIcoPath); 958 if (FAILED(hr)) 959 return hr; 960 } 961 962 /* 963 * Now save the data block list. 964 * 965 * NOTE that both advertised Product and Component are already saved 966 * inside Logo3 and Darwin data blocks in the m_pDBList list, and the 967 * m_Header.dwFlags is suitably initialized. 968 */ 969 hr = SHWriteDataBlockList(stm, m_pDBList); 970 if (FAILED(hr)) 971 return hr; 972 973 /* Clear the dirty bit if requested */ 974 if (fClearDirty) 975 m_bDirty = FALSE; 976 977 return hr; 978 } 979 980 /************************************************************************ 981 * IPersistStream_GetSizeMax (IPersistStream) 982 */ 983 HRESULT STDMETHODCALLTYPE CShellLink::GetSizeMax(ULARGE_INTEGER *pcbSize) 984 { 985 TRACE("(%p)\n", this); 986 return E_NOTIMPL; 987 } 988 989 static BOOL SHELL_ExistsFileW(LPCWSTR path) 990 { 991 if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(path)) 992 return FALSE; 993 994 return TRUE; 995 } 996 997 /************************************************************************** 998 * ShellLink_UpdatePath 999 * update absolute path in sPath using relative path in sPathRel 1000 */ 1001 static HRESULT ShellLink_UpdatePath(LPCWSTR sPathRel, LPCWSTR path, LPCWSTR sWorkDir, LPWSTR* psPath) 1002 { 1003 if (!path || !psPath) 1004 return E_INVALIDARG; 1005 1006 if (!*psPath && sPathRel) 1007 { 1008 WCHAR buffer[2*MAX_PATH], abs_path[2*MAX_PATH]; 1009 LPWSTR final = NULL; 1010 1011 /* first try if [directory of link file] + [relative path] finds an existing file */ 1012 1013 GetFullPathNameW(path, MAX_PATH * 2, buffer, &final); 1014 if (!final) 1015 final = buffer; 1016 wcscpy(final, sPathRel); 1017 1018 *abs_path = '\0'; 1019 1020 if (SHELL_ExistsFileW(buffer)) 1021 { 1022 if (!GetFullPathNameW(buffer, MAX_PATH, abs_path, &final)) 1023 wcscpy(abs_path, buffer); 1024 } 1025 else 1026 { 1027 /* try if [working directory] + [relative path] finds an existing file */ 1028 if (sWorkDir) 1029 { 1030 wcscpy(buffer, sWorkDir); 1031 wcscpy(PathAddBackslashW(buffer), sPathRel); 1032 1033 if (SHELL_ExistsFileW(buffer)) 1034 if (!GetFullPathNameW(buffer, MAX_PATH, abs_path, &final)) 1035 wcscpy(abs_path, buffer); 1036 } 1037 } 1038 1039 /* FIXME: This is even not enough - not all shell links can be resolved using this algorithm. */ 1040 if (!*abs_path) 1041 wcscpy(abs_path, sPathRel); 1042 1043 *psPath = strdupW(abs_path); 1044 if (!*psPath) 1045 return E_OUTOFMEMORY; 1046 } 1047 1048 return S_OK; 1049 } 1050 1051 HRESULT STDMETHODCALLTYPE CShellLink::GetPath(LPSTR pszFile, INT cchMaxPath, WIN32_FIND_DATAA *pfd, DWORD fFlags) 1052 { 1053 HRESULT hr; 1054 LPWSTR pszFileW; 1055 WIN32_FIND_DATAW wfd; 1056 1057 TRACE("(%p)->(pfile=%p len=%u find_data=%p flags=%u)(%s)\n", 1058 this, pszFile, cchMaxPath, pfd, fFlags, debugstr_w(m_sPath)); 1059 1060 /* Allocate a temporary UNICODE buffer */ 1061 pszFileW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, cchMaxPath * sizeof(WCHAR)); 1062 if (!pszFileW) 1063 return E_OUTOFMEMORY; 1064 1065 /* Call the UNICODE function */ 1066 hr = GetPath(pszFileW, cchMaxPath, &wfd, fFlags); 1067 1068 /* Convert the file path back to ANSI */ 1069 WideCharToMultiByte(CP_ACP, 0, pszFileW, -1, 1070 pszFile, cchMaxPath, NULL, NULL); 1071 1072 /* Free the temporary buffer */ 1073 HeapFree(GetProcessHeap(), 0, pszFileW); 1074 1075 if (pfd) 1076 { 1077 ZeroMemory(pfd, sizeof(*pfd)); 1078 1079 /* Copy the file data if a file path was returned */ 1080 if (*pszFile) 1081 { 1082 DWORD len; 1083 1084 /* Copy the fixed part */ 1085 CopyMemory(pfd, &wfd, FIELD_OFFSET(WIN32_FIND_DATAA, cFileName)); 1086 1087 /* Convert the file names to ANSI */ 1088 len = lstrlenW(wfd.cFileName); 1089 WideCharToMultiByte(CP_ACP, 0, wfd.cFileName, len + 1, 1090 pfd->cFileName, sizeof(pfd->cFileName), NULL, NULL); 1091 len = lstrlenW(wfd.cAlternateFileName); 1092 WideCharToMultiByte(CP_ACP, 0, wfd.cAlternateFileName, len + 1, 1093 pfd->cAlternateFileName, sizeof(pfd->cAlternateFileName), NULL, NULL); 1094 } 1095 } 1096 1097 return hr; 1098 } 1099 1100 HRESULT STDMETHODCALLTYPE CShellLink::GetIDList(LPITEMIDLIST *ppidl) 1101 { 1102 TRACE("(%p)->(ppidl=%p)\n", this, ppidl); 1103 1104 if (!m_pPidl) 1105 { 1106 *ppidl = NULL; 1107 return S_FALSE; 1108 } 1109 1110 *ppidl = ILClone(m_pPidl); 1111 return S_OK; 1112 } 1113 1114 HRESULT STDMETHODCALLTYPE CShellLink::SetIDList(LPCITEMIDLIST pidl) 1115 { 1116 TRACE("(%p)->(pidl=%p)\n", this, pidl); 1117 return SetTargetFromPIDLOrPath(pidl, NULL); 1118 } 1119 1120 HRESULT STDMETHODCALLTYPE CShellLink::GetDescription(LPSTR pszName, INT cchMaxName) 1121 { 1122 TRACE("(%p)->(%p len=%u)\n", this, pszName, cchMaxName); 1123 1124 if (cchMaxName) 1125 *pszName = 0; 1126 1127 if (m_sDescription) 1128 WideCharToMultiByte(CP_ACP, 0, m_sDescription, -1, 1129 pszName, cchMaxName, NULL, NULL); 1130 1131 return S_OK; 1132 } 1133 1134 HRESULT STDMETHODCALLTYPE CShellLink::SetDescription(LPCSTR pszName) 1135 { 1136 TRACE("(%p)->(pName=%s)\n", this, pszName); 1137 1138 HeapFree(GetProcessHeap(), 0, m_sDescription); 1139 m_sDescription = NULL; 1140 1141 if (pszName) 1142 { 1143 m_sDescription = HEAP_strdupAtoW(GetProcessHeap(), 0, pszName); 1144 if (!m_sDescription) 1145 return E_OUTOFMEMORY; 1146 } 1147 m_bDirty = TRUE; 1148 1149 return S_OK; 1150 } 1151 1152 HRESULT STDMETHODCALLTYPE CShellLink::GetWorkingDirectory(LPSTR pszDir, INT cchMaxPath) 1153 { 1154 TRACE("(%p)->(%p len=%u)\n", this, pszDir, cchMaxPath); 1155 1156 if (cchMaxPath) 1157 *pszDir = 0; 1158 1159 if (m_sWorkDir) 1160 WideCharToMultiByte(CP_ACP, 0, m_sWorkDir, -1, 1161 pszDir, cchMaxPath, NULL, NULL); 1162 1163 return S_OK; 1164 } 1165 1166 HRESULT STDMETHODCALLTYPE CShellLink::SetWorkingDirectory(LPCSTR pszDir) 1167 { 1168 TRACE("(%p)->(dir=%s)\n", this, pszDir); 1169 1170 HeapFree(GetProcessHeap(), 0, m_sWorkDir); 1171 m_sWorkDir = NULL; 1172 1173 if (pszDir) 1174 { 1175 m_sWorkDir = HEAP_strdupAtoW(GetProcessHeap(), 0, pszDir); 1176 if (!m_sWorkDir) 1177 return E_OUTOFMEMORY; 1178 } 1179 m_bDirty = TRUE; 1180 1181 return S_OK; 1182 } 1183 1184 HRESULT STDMETHODCALLTYPE CShellLink::GetArguments(LPSTR pszArgs, INT cchMaxPath) 1185 { 1186 TRACE("(%p)->(%p len=%u)\n", this, pszArgs, cchMaxPath); 1187 1188 if (cchMaxPath) 1189 *pszArgs = 0; 1190 1191 if (m_sArgs) 1192 WideCharToMultiByte(CP_ACP, 0, m_sArgs, -1, 1193 pszArgs, cchMaxPath, NULL, NULL); 1194 1195 return S_OK; 1196 } 1197 1198 HRESULT STDMETHODCALLTYPE CShellLink::SetArguments(LPCSTR pszArgs) 1199 { 1200 TRACE("(%p)->(args=%s)\n", this, pszArgs); 1201 1202 HeapFree(GetProcessHeap(), 0, m_sArgs); 1203 m_sArgs = NULL; 1204 1205 if (pszArgs) 1206 { 1207 m_sArgs = HEAP_strdupAtoW(GetProcessHeap(), 0, pszArgs); 1208 if (!m_sArgs) 1209 return E_OUTOFMEMORY; 1210 } 1211 m_bDirty = TRUE; 1212 1213 return S_OK; 1214 } 1215 1216 HRESULT STDMETHODCALLTYPE CShellLink::GetHotkey(WORD *pwHotkey) 1217 { 1218 TRACE("(%p)->(%p)(0x%08x)\n", this, pwHotkey, m_Header.wHotKey); 1219 *pwHotkey = m_Header.wHotKey; 1220 return S_OK; 1221 } 1222 1223 HRESULT STDMETHODCALLTYPE CShellLink::SetHotkey(WORD wHotkey) 1224 { 1225 TRACE("(%p)->(hotkey=%x)\n", this, wHotkey); 1226 1227 m_Header.wHotKey = wHotkey; 1228 m_bDirty = TRUE; 1229 1230 return S_OK; 1231 } 1232 1233 HRESULT STDMETHODCALLTYPE CShellLink::GetShowCmd(INT *piShowCmd) 1234 { 1235 TRACE("(%p)->(%p) %d\n", this, piShowCmd, m_Header.nShowCommand); 1236 *piShowCmd = m_Header.nShowCommand; 1237 return S_OK; 1238 } 1239 1240 HRESULT STDMETHODCALLTYPE CShellLink::SetShowCmd(INT iShowCmd) 1241 { 1242 TRACE("(%p) %d\n", this, iShowCmd); 1243 1244 m_Header.nShowCommand = iShowCmd; 1245 m_bDirty = TRUE; 1246 1247 return S_OK; 1248 } 1249 1250 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(LPSTR pszIconPath, INT cchIconPath, INT *piIcon) 1251 { 1252 HRESULT hr; 1253 LPWSTR pszIconPathW; 1254 1255 TRACE("(%p)->(%p len=%u iicon=%p)\n", this, pszIconPath, cchIconPath, piIcon); 1256 1257 /* Allocate a temporary UNICODE buffer */ 1258 pszIconPathW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, cchIconPath * sizeof(WCHAR)); 1259 if (!pszIconPathW) 1260 return E_OUTOFMEMORY; 1261 1262 /* Call the UNICODE function */ 1263 hr = GetIconLocation(pszIconPathW, cchIconPath, piIcon); 1264 1265 /* Convert the file path back to ANSI */ 1266 WideCharToMultiByte(CP_ACP, 0, pszIconPathW, -1, 1267 pszIconPath, cchIconPath, NULL, NULL); 1268 1269 /* Free the temporary buffer */ 1270 HeapFree(GetProcessHeap(), 0, pszIconPathW); 1271 1272 return hr; 1273 } 1274 1275 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(UINT uFlags, PSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags) 1276 { 1277 HRESULT hr; 1278 LPWSTR pszIconFileW; 1279 1280 TRACE("(%p)->(%u %p len=%u piIndex=%p pwFlags=%p)\n", this, uFlags, pszIconFile, cchMax, piIndex, pwFlags); 1281 1282 /* Allocate a temporary UNICODE buffer */ 1283 pszIconFileW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, cchMax * sizeof(WCHAR)); 1284 if (!pszIconFileW) 1285 return E_OUTOFMEMORY; 1286 1287 /* Call the UNICODE function */ 1288 hr = GetIconLocation(uFlags, pszIconFileW, cchMax, piIndex, pwFlags); 1289 1290 /* Convert the file path back to ANSI */ 1291 WideCharToMultiByte(CP_ACP, 0, pszIconFileW, -1, 1292 pszIconFile, cchMax, NULL, NULL); 1293 1294 /* Free the temporary buffer */ 1295 HeapFree(GetProcessHeap(), 0, pszIconFileW); 1296 1297 return hr; 1298 } 1299 1300 HRESULT STDMETHODCALLTYPE CShellLink::Extract(PCSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize) 1301 { 1302 TRACE("(%p)->(path=%s iicon=%u)\n", this, pszFile, nIconIndex); 1303 1304 LPWSTR str = NULL; 1305 if (pszFile) 1306 { 1307 str = HEAP_strdupAtoW(GetProcessHeap(), 0, pszFile); 1308 if (!str) 1309 return E_OUTOFMEMORY; 1310 } 1311 1312 HRESULT hr = Extract(str, nIconIndex, phiconLarge, phiconSmall, nIconSize); 1313 1314 if (str) 1315 HeapFree(GetProcessHeap(), 0, str); 1316 1317 return hr; 1318 } 1319 1320 HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCSTR pszIconPath, INT iIcon) 1321 { 1322 TRACE("(%p)->(path=%s iicon=%u)\n", this, pszIconPath, iIcon); 1323 1324 LPWSTR str = NULL; 1325 if (pszIconPath) 1326 { 1327 str = HEAP_strdupAtoW(GetProcessHeap(), 0, pszIconPath); 1328 if (!str) 1329 return E_OUTOFMEMORY; 1330 } 1331 1332 HRESULT hr = SetIconLocation(str, iIcon); 1333 1334 if (str) 1335 HeapFree(GetProcessHeap(), 0, str); 1336 1337 return hr; 1338 } 1339 1340 HRESULT STDMETHODCALLTYPE CShellLink::SetRelativePath(LPCSTR pszPathRel, DWORD dwReserved) 1341 { 1342 TRACE("(%p)->(path=%s %x)\n", this, pszPathRel, dwReserved); 1343 1344 HeapFree(GetProcessHeap(), 0, m_sPathRel); 1345 m_sPathRel = NULL; 1346 1347 if (pszPathRel) 1348 { 1349 m_sPathRel = HEAP_strdupAtoW(GetProcessHeap(), 0, pszPathRel); 1350 m_bDirty = TRUE; 1351 } 1352 1353 return ShellLink_UpdatePath(m_sPathRel, m_sPath, m_sWorkDir, &m_sPath); 1354 } 1355 1356 static LPWSTR 1357 shelllink_get_msi_component_path(LPWSTR component) 1358 { 1359 DWORD Result, sz = 0; 1360 1361 Result = CommandLineFromMsiDescriptor(component, NULL, &sz); 1362 if (Result != ERROR_SUCCESS) 1363 return NULL; 1364 1365 sz++; 1366 LPWSTR path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, sz * sizeof(WCHAR)); 1367 Result = CommandLineFromMsiDescriptor(component, path, &sz); 1368 if (Result != ERROR_SUCCESS) 1369 { 1370 HeapFree(GetProcessHeap(), 0, path); 1371 path = NULL; 1372 } 1373 1374 TRACE("returning %s\n", debugstr_w(path)); 1375 1376 return path; 1377 } 1378 1379 HRESULT STDMETHODCALLTYPE CShellLink::Resolve(HWND hwnd, DWORD fFlags) 1380 { 1381 HRESULT hr = S_OK; 1382 BOOL bSuccess; 1383 1384 TRACE("(%p)->(hwnd=%p flags=%x)\n", this, hwnd, fFlags); 1385 1386 /* FIXME: use IResolveShellLink interface? */ 1387 1388 // FIXME: See InvokeCommand(). 1389 1390 #if (NTDDI_VERSION < NTDDI_LONGHORN) 1391 // NOTE: For Logo3 (EXP_LOGO3_ID_SIG), check also for SHRestricted(REST_NOLOGO3CHANNELNOTIFY) 1392 if (m_Header.dwFlags & SLDF_HAS_LOGO3ID) 1393 { 1394 FIXME("Logo3 links are not supported yet!\n"); 1395 return E_FAIL; 1396 } 1397 #endif 1398 1399 /* Resolve Darwin (MSI) target */ 1400 if (m_Header.dwFlags & SLDF_HAS_DARWINID) 1401 { 1402 LPWSTR component = NULL; 1403 hr = GetAdvertiseInfo(&component, EXP_DARWIN_ID_SIG); 1404 if (FAILED(hr)) 1405 return E_FAIL; 1406 1407 /* Clear the cached path */ 1408 HeapFree(GetProcessHeap(), 0, m_sPath); 1409 m_sPath = shelllink_get_msi_component_path(component); 1410 if (!m_sPath) 1411 return E_FAIL; 1412 } 1413 1414 if (!m_sPath && m_pPidl) 1415 { 1416 WCHAR buffer[MAX_PATH]; 1417 1418 bSuccess = SHGetPathFromIDListW(m_pPidl, buffer); 1419 if (bSuccess && *buffer) 1420 { 1421 m_sPath = strdupW(buffer); 1422 if (!m_sPath) 1423 return E_OUTOFMEMORY; 1424 1425 m_bDirty = TRUE; 1426 } 1427 else 1428 { 1429 hr = S_OK; /* don't report an error occurred while just caching information */ 1430 } 1431 } 1432 1433 // FIXME: Strange to do that here... 1434 if (!m_sIcoPath && m_sPath) 1435 { 1436 m_sIcoPath = strdupW(m_sPath); 1437 if (!m_sIcoPath) 1438 return E_OUTOFMEMORY; 1439 1440 m_Header.nIconIndex = 0; 1441 1442 m_bDirty = TRUE; 1443 } 1444 1445 return hr; 1446 } 1447 1448 HRESULT STDMETHODCALLTYPE CShellLink::SetPath(LPCSTR pszFile) 1449 { 1450 TRACE("(%p)->(path=%s)\n", this, pszFile); 1451 1452 if (!pszFile) 1453 return E_INVALIDARG; 1454 1455 LPWSTR str = HEAP_strdupAtoW(GetProcessHeap(), 0, pszFile); 1456 if (!str) 1457 return E_OUTOFMEMORY; 1458 1459 HRESULT hr = SetPath(str); 1460 HeapFree(GetProcessHeap(), 0, str); 1461 1462 return hr; 1463 } 1464 1465 HRESULT STDMETHODCALLTYPE CShellLink::GetPath(LPWSTR pszFile, INT cchMaxPath, WIN32_FIND_DATAW *pfd, DWORD fFlags) 1466 { 1467 WCHAR buffer[MAX_PATH]; 1468 1469 TRACE("(%p)->(pfile=%p len=%u find_data=%p flags=%u)(%s)\n", 1470 this, pszFile, cchMaxPath, pfd, fFlags, debugstr_w(m_sPath)); 1471 1472 if (cchMaxPath) 1473 *pszFile = 0; 1474 // FIXME: What if cchMaxPath == 0 , or pszFile == NULL ?? 1475 1476 // FIXME: What about Darwin?? 1477 1478 /* 1479 * Retrieve the path to the target from the PIDL (if we have one). 1480 * NOTE: Do NOT use the cached path (m_sPath from link info). 1481 */ 1482 if (m_pPidl && SHGetPathFromIDListW(m_pPidl, buffer)) 1483 { 1484 if (fFlags & SLGP_SHORTPATH) 1485 GetShortPathNameW(buffer, buffer, _countof(buffer)); 1486 // FIXME: Add support for SLGP_UNCPRIORITY 1487 } 1488 else 1489 { 1490 *buffer = 0; 1491 } 1492 1493 /* If we have a FindData structure, initialize it */ 1494 if (pfd) 1495 { 1496 ZeroMemory(pfd, sizeof(*pfd)); 1497 1498 /* Copy the file data if the target is a file path */ 1499 if (*buffer) 1500 { 1501 pfd->dwFileAttributes = m_Header.dwFileAttributes; 1502 pfd->ftCreationTime = m_Header.ftCreationTime; 1503 pfd->ftLastAccessTime = m_Header.ftLastAccessTime; 1504 pfd->ftLastWriteTime = m_Header.ftLastWriteTime; 1505 pfd->nFileSizeHigh = 0; 1506 pfd->nFileSizeLow = m_Header.nFileSizeLow; 1507 1508 /* 1509 * Build temporarily a short path in pfd->cFileName (of size MAX_PATH), 1510 * then extract and store the short file name in pfd->cAlternateFileName. 1511 */ 1512 GetShortPathNameW(buffer, pfd->cFileName, _countof(pfd->cFileName)); 1513 lstrcpynW(pfd->cAlternateFileName, 1514 PathFindFileNameW(pfd->cFileName), 1515 _countof(pfd->cAlternateFileName)); 1516 1517 /* Now extract and store the long file name in pfd->cFileName */ 1518 lstrcpynW(pfd->cFileName, 1519 PathFindFileNameW(buffer), 1520 _countof(pfd->cFileName)); 1521 } 1522 } 1523 1524 /* Finally check if we have a raw path the user actually wants to retrieve */ 1525 if ((fFlags & SLGP_RAWPATH) && (m_Header.dwFlags & SLDF_HAS_EXP_SZ)) 1526 { 1527 /* Search for a target environment block */ 1528 LPEXP_SZ_LINK pInfo; 1529 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_LINK_SIG); 1530 if (pInfo && (pInfo->cbSize == sizeof(*pInfo))) 1531 lstrcpynW(buffer, pInfo->szwTarget, cchMaxPath); 1532 } 1533 1534 /* For diagnostics purposes only... */ 1535 // NOTE: SLGP_UNCPRIORITY is unsupported 1536 fFlags &= ~(SLGP_RAWPATH | SLGP_SHORTPATH); 1537 if (fFlags) FIXME("(%p): Unsupported flags %lu\n", this, fFlags); 1538 1539 /* Copy the data back to the user */ 1540 if (*buffer) 1541 lstrcpynW(pszFile, buffer, cchMaxPath); 1542 1543 return (*buffer ? S_OK : S_FALSE); 1544 } 1545 1546 HRESULT STDMETHODCALLTYPE CShellLink::GetDescription(LPWSTR pszName, INT cchMaxName) 1547 { 1548 TRACE("(%p)->(%p len=%u)\n", this, pszName, cchMaxName); 1549 1550 *pszName = 0; 1551 if (m_sDescription) 1552 lstrcpynW(pszName, m_sDescription, cchMaxName); 1553 1554 return S_OK; 1555 } 1556 1557 HRESULT STDMETHODCALLTYPE CShellLink::SetDescription(LPCWSTR pszName) 1558 { 1559 TRACE("(%p)->(desc=%s)\n", this, debugstr_w(pszName)); 1560 1561 HeapFree(GetProcessHeap(), 0, m_sDescription); 1562 m_sDescription = NULL; 1563 1564 if (pszName) 1565 { 1566 m_sDescription = strdupW(pszName); 1567 if (!m_sDescription) 1568 return E_OUTOFMEMORY; 1569 } 1570 m_bDirty = TRUE; 1571 1572 return S_OK; 1573 } 1574 1575 HRESULT STDMETHODCALLTYPE CShellLink::GetWorkingDirectory(LPWSTR pszDir, INT cchMaxPath) 1576 { 1577 TRACE("(%p)->(%p len %u)\n", this, pszDir, cchMaxPath); 1578 1579 if (cchMaxPath) 1580 *pszDir = 0; 1581 1582 if (m_sWorkDir) 1583 lstrcpynW(pszDir, m_sWorkDir, cchMaxPath); 1584 1585 return S_OK; 1586 } 1587 1588 HRESULT STDMETHODCALLTYPE CShellLink::SetWorkingDirectory(LPCWSTR pszDir) 1589 { 1590 TRACE("(%p)->(dir=%s)\n", this, debugstr_w(pszDir)); 1591 1592 HeapFree(GetProcessHeap(), 0, m_sWorkDir); 1593 m_sWorkDir = NULL; 1594 1595 if (pszDir) 1596 { 1597 m_sWorkDir = strdupW(pszDir); 1598 if (!m_sWorkDir) 1599 return E_OUTOFMEMORY; 1600 } 1601 m_bDirty = TRUE; 1602 1603 return S_OK; 1604 } 1605 1606 HRESULT STDMETHODCALLTYPE CShellLink::GetArguments(LPWSTR pszArgs, INT cchMaxPath) 1607 { 1608 TRACE("(%p)->(%p len=%u)\n", this, pszArgs, cchMaxPath); 1609 1610 if (cchMaxPath) 1611 *pszArgs = 0; 1612 1613 if (m_sArgs) 1614 lstrcpynW(pszArgs, m_sArgs, cchMaxPath); 1615 1616 return S_OK; 1617 } 1618 1619 HRESULT STDMETHODCALLTYPE CShellLink::SetArguments(LPCWSTR pszArgs) 1620 { 1621 TRACE("(%p)->(args=%s)\n", this, debugstr_w(pszArgs)); 1622 1623 HeapFree(GetProcessHeap(), 0, m_sArgs); 1624 m_sArgs = NULL; 1625 1626 if (pszArgs) 1627 { 1628 m_sArgs = strdupW(pszArgs); 1629 if (!m_sArgs) 1630 return E_OUTOFMEMORY; 1631 } 1632 m_bDirty = TRUE; 1633 1634 return S_OK; 1635 } 1636 1637 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(LPWSTR pszIconPath, INT cchIconPath, INT *piIcon) 1638 { 1639 TRACE("(%p)->(%p len=%u iicon=%p)\n", this, pszIconPath, cchIconPath, piIcon); 1640 1641 if (cchIconPath) 1642 *pszIconPath = 0; 1643 1644 *piIcon = 0; 1645 1646 /* Update the original icon path location */ 1647 if (m_Header.dwFlags & SLDF_HAS_EXP_ICON_SZ) 1648 { 1649 WCHAR szPath[MAX_PATH]; 1650 1651 /* Search for an icon environment block */ 1652 LPEXP_SZ_LINK pInfo; 1653 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_ICON_SIG); 1654 if (pInfo && (pInfo->cbSize == sizeof(*pInfo))) 1655 { 1656 SHExpandEnvironmentStringsW(pInfo->szwTarget, szPath, _countof(szPath)); 1657 1658 m_Header.dwFlags &= ~SLDF_HAS_ICONLOCATION; 1659 HeapFree(GetProcessHeap(), 0, m_sIcoPath); 1660 1661 m_sIcoPath = strdupW(szPath); 1662 if (!m_sIcoPath) 1663 return E_OUTOFMEMORY; 1664 1665 m_Header.dwFlags |= SLDF_HAS_ICONLOCATION; 1666 1667 m_bDirty = TRUE; 1668 } 1669 } 1670 1671 *piIcon = m_Header.nIconIndex; 1672 1673 if (m_sIcoPath) 1674 lstrcpynW(pszIconPath, m_sIcoPath, cchIconPath); 1675 1676 return S_OK; 1677 } 1678 1679 static HRESULT SHELL_PidlGetIconLocationW(PCIDLIST_ABSOLUTE pidl, 1680 UINT uFlags, PWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags) 1681 { 1682 LPCITEMIDLIST pidlLast; 1683 CComPtr<IShellFolder> psf; 1684 1685 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast); 1686 if (FAILED_UNEXPECTEDLY(hr)) 1687 return hr; 1688 1689 CComPtr<IExtractIconW> pei; 1690 hr = psf->GetUIObjectOf(0, 1, &pidlLast, IID_NULL_PPV_ARG(IExtractIconW, &pei)); 1691 if (FAILED_UNEXPECTEDLY(hr)) 1692 return hr; 1693 1694 hr = pei->GetIconLocation(uFlags, pszIconFile, cchMax, piIndex, pwFlags); 1695 if (FAILED_UNEXPECTEDLY(hr)) 1696 return hr; 1697 1698 return S_OK; 1699 } 1700 1701 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(UINT uFlags, PWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags) 1702 { 1703 HRESULT hr; 1704 1705 pszIconFile[0] = UNICODE_NULL; 1706 1707 /* 1708 * It is possible for a shell link to point to another shell link, 1709 * and in particular there is the possibility to point to itself. 1710 * Now, suppose we ask such a link to retrieve its associated icon. 1711 * This function would be called, and due to COM would be called again 1712 * recursively. To solve this issue, we forbid calling GetIconLocation() 1713 * with GIL_FORSHORTCUT set in uFlags, as done by Windows (shown by tests). 1714 */ 1715 if (uFlags & GIL_FORSHORTCUT) 1716 return E_INVALIDARG; 1717 1718 /* 1719 * Now, we set GIL_FORSHORTCUT so that: i) we allow the icon extractor 1720 * of the target to give us a suited icon, and ii) we protect ourselves 1721 * against recursive call. 1722 */ 1723 uFlags |= GIL_FORSHORTCUT; 1724 1725 if (uFlags & GIL_DEFAULTICON) 1726 return S_FALSE; 1727 1728 hr = GetIconLocation(pszIconFile, cchMax, piIndex); 1729 if (FAILED(hr) || pszIconFile[0] == UNICODE_NULL) 1730 { 1731 hr = SHELL_PidlGetIconLocationW(m_pPidl, uFlags, pszIconFile, cchMax, piIndex, pwFlags); 1732 } 1733 else 1734 { 1735 *pwFlags = GIL_NOTFILENAME | GIL_PERCLASS; 1736 } 1737 1738 return hr; 1739 } 1740 1741 HRESULT STDMETHODCALLTYPE 1742 CShellLink::Extract(PCWSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize) 1743 { 1744 HRESULT hr = NOERROR; 1745 UINT cxyLarge = LOWORD(nIconSize), cxySmall = HIWORD(nIconSize); 1746 1747 if (phiconLarge) 1748 { 1749 *phiconLarge = NULL; 1750 PrivateExtractIconsW(pszFile, nIconIndex, cxyLarge, cxyLarge, phiconLarge, NULL, 1, 0); 1751 1752 if (*phiconLarge == NULL) 1753 hr = S_FALSE; 1754 } 1755 1756 if (phiconSmall) 1757 { 1758 *phiconSmall = NULL; 1759 PrivateExtractIconsW(pszFile, nIconIndex, cxySmall, cxySmall, phiconSmall, NULL, 1, 0); 1760 1761 if (*phiconSmall == NULL) 1762 hr = S_FALSE; 1763 } 1764 1765 if (hr == S_FALSE) 1766 { 1767 if (phiconLarge && *phiconLarge) 1768 { 1769 DestroyIcon(*phiconLarge); 1770 *phiconLarge = NULL; 1771 } 1772 if (phiconSmall && *phiconSmall) 1773 { 1774 DestroyIcon(*phiconSmall); 1775 *phiconSmall = NULL; 1776 } 1777 } 1778 1779 return hr; 1780 } 1781 1782 #if 0 1783 /* Extends the functionality of PathUnExpandEnvStringsW */ 1784 BOOL PathFullyUnExpandEnvStringsW( 1785 _In_ LPCWSTR pszPath, 1786 _Out_ LPWSTR pszBuf, 1787 _In_ UINT cchBuf) 1788 { 1789 BOOL Ret = FALSE; // Set to TRUE as soon as PathUnExpandEnvStrings starts unexpanding. 1790 BOOL res; 1791 LPCWSTR p; 1792 1793 // *pszBuf = L'\0'; 1794 while (*pszPath && cchBuf > 0) 1795 { 1796 /* Attempt unexpanding the path */ 1797 res = PathUnExpandEnvStringsW(pszPath, pszBuf, cchBuf); 1798 if (!res) 1799 { 1800 /* The unexpansion failed. Try to find a path delimiter. */ 1801 p = wcspbrk(pszPath, L" /\\:*?\"<>|%"); 1802 if (!p) /* None found, we will copy the remaining path */ 1803 p = pszPath + wcslen(pszPath); 1804 else /* Found one, we will copy the delimiter and skip it */ 1805 ++p; 1806 /* If we overflow, we cannot unexpand more, so return FALSE */ 1807 if (p - pszPath >= cchBuf) 1808 return FALSE; // *pszBuf = L'\0'; 1809 1810 /* Copy the untouched portion of path up to the delimiter, included */ 1811 wcsncpy(pszBuf, pszPath, p - pszPath); 1812 pszBuf[p - pszPath] = L'\0'; // NULL-terminate 1813 1814 /* Advance the pointers and decrease the remaining buffer size */ 1815 cchBuf -= (p - pszPath); 1816 pszBuf += (p - pszPath); 1817 pszPath += (p - pszPath); 1818 } 1819 else 1820 { 1821 /* 1822 * The unexpansion succeeded. Skip the unexpanded part by trying 1823 * to find where the original path and the unexpanded string 1824 * become different. 1825 * NOTE: An alternative(?) would be to stop also at the last 1826 * path delimiter encountered in the loop (i.e. would be the 1827 * first path delimiter in the strings). 1828 */ 1829 LPWSTR q; 1830 1831 /* 1832 * The algorithm starts at the end of the strings and loops back 1833 * while the characters are equal, until it finds a discrepancy. 1834 */ 1835 p = pszPath + wcslen(pszPath); 1836 q = pszBuf + wcslen(pszBuf); // This wcslen should be < cchBuf 1837 while ((*p == *q) && (p > pszPath) && (q > pszBuf)) 1838 { 1839 --p; --q; 1840 } 1841 /* Skip discrepancy */ 1842 ++p; ++q; 1843 1844 /* Advance the pointers and decrease the remaining buffer size */ 1845 cchBuf -= (q - pszBuf); 1846 pszBuf = q; 1847 pszPath = p; 1848 1849 Ret = TRUE; 1850 } 1851 } 1852 1853 return Ret; 1854 } 1855 #endif 1856 1857 HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT iIcon) 1858 { 1859 HRESULT hr = E_FAIL; 1860 WCHAR szIconPath[MAX_PATH]; 1861 1862 TRACE("(%p)->(path=%s iicon=%u)\n", this, debugstr_w(pszIconPath), iIcon); 1863 1864 if (pszIconPath) 1865 { 1866 /* 1867 * Check whether the user-given file path contains unexpanded 1868 * environment variables. If so, create a target environment block. 1869 * Note that in this block we will store the user-given path. 1870 * It will contain the unexpanded environment variables, but 1871 * it can also contain already expanded path that the user does 1872 * not want to see them unexpanded (e.g. so that they always 1873 * refer to the same place even if the would-be corresponding 1874 * environment variable could change). 1875 */ 1876 #ifdef ICON_LINK_WINDOWS_COMPAT 1877 /* Try to fully unexpand the icon path */ 1878 // if (PathFullyUnExpandEnvStringsW(pszIconPath, szIconPath, _countof(szIconPath))) 1879 BOOL bSuccess = PathUnExpandEnvStringsW(pszIconPath, szIconPath, _countof(szIconPath)); 1880 if (bSuccess && wcscmp(pszIconPath, szIconPath) != 0) 1881 #else 1882 /* 1883 * In some situations, described in http://stackoverflow.com/questions/2976489/ishelllinkseticonlocation-translates-my-icon-path-into-program-files-which-i 1884 * the result of PathUnExpandEnvStringsW() could be wrong, and instead 1885 * one would have to store the actual provided icon location path, while 1886 * creating an icon environment block ONLY if that path already contains 1887 * environment variables. This is what the present case is trying to implement. 1888 */ 1889 SHExpandEnvironmentStringsW(pszIconPath, szIconPath, _countof(szIconPath)); 1890 if (wcscmp(pszIconPath, szIconPath) != 0) 1891 #endif 1892 { 1893 /* 1894 * The user-given file path contains unexpanded environment 1895 * variables, so we need an icon environment block. 1896 */ 1897 EXP_SZ_LINK buffer; 1898 LPEXP_SZ_LINK pInfo; 1899 1900 #ifdef ICON_LINK_WINDOWS_COMPAT 1901 /* Make pszIconPath point to the unexpanded path */ 1902 LPCWSTR pszOrgIconPath = pszIconPath; 1903 pszIconPath = szIconPath; 1904 #endif 1905 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_ICON_SIG); 1906 if (pInfo) 1907 { 1908 /* Make sure that the size of the structure is valid */ 1909 if (pInfo->cbSize != sizeof(*pInfo)) 1910 { 1911 ERR("Ooops. This structure is not as expected...\n"); 1912 1913 /* Invalid structure, remove it altogether */ 1914 m_Header.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ; 1915 RemoveDataBlock(EXP_SZ_ICON_SIG); 1916 1917 /* Reset the pointer and go use the static buffer */ 1918 pInfo = NULL; 1919 } 1920 } 1921 if (!pInfo) 1922 { 1923 /* Use the static buffer */ 1924 pInfo = &buffer; 1925 buffer.cbSize = sizeof(buffer); 1926 buffer.dwSignature = EXP_SZ_ICON_SIG; 1927 } 1928 1929 lstrcpynW(pInfo->szwTarget, pszIconPath, _countof(pInfo->szwTarget)); 1930 WideCharToMultiByte(CP_ACP, 0, pszIconPath, -1, 1931 pInfo->szTarget, _countof(pInfo->szTarget), NULL, NULL); 1932 1933 hr = S_OK; 1934 if (pInfo == &buffer) 1935 hr = AddDataBlock(pInfo); 1936 if (hr == S_OK) 1937 m_Header.dwFlags |= SLDF_HAS_EXP_ICON_SZ; 1938 1939 #ifdef ICON_LINK_WINDOWS_COMPAT 1940 /* Set pszIconPath back to the original one */ 1941 pszIconPath = pszOrgIconPath; 1942 #else 1943 /* Now, make pszIconPath point to the expanded path */ 1944 pszIconPath = szIconPath; 1945 #endif 1946 } 1947 else 1948 { 1949 /* 1950 * The user-given file path does not contain unexpanded environment 1951 * variables, so we need to remove any icon environment block. 1952 */ 1953 m_Header.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ; 1954 RemoveDataBlock(EXP_SZ_ICON_SIG); 1955 1956 /* pszIconPath points to the user path */ 1957 } 1958 } 1959 1960 #ifdef ICON_LINK_WINDOWS_COMPAT 1961 /* Store the original icon path location (may contain unexpanded environment strings) */ 1962 #endif 1963 if (pszIconPath) 1964 { 1965 m_Header.dwFlags &= ~SLDF_HAS_ICONLOCATION; 1966 HeapFree(GetProcessHeap(), 0, m_sIcoPath); 1967 1968 m_sIcoPath = strdupW(pszIconPath); 1969 if (!m_sIcoPath) 1970 return E_OUTOFMEMORY; 1971 1972 m_Header.dwFlags |= SLDF_HAS_ICONLOCATION; 1973 } 1974 1975 hr = S_OK; 1976 1977 m_Header.nIconIndex = iIcon; 1978 m_bDirty = TRUE; 1979 1980 return hr; 1981 } 1982 1983 HRESULT STDMETHODCALLTYPE CShellLink::SetRelativePath(LPCWSTR pszPathRel, DWORD dwReserved) 1984 { 1985 TRACE("(%p)->(path=%s %x)\n", this, debugstr_w(pszPathRel), dwReserved); 1986 1987 HeapFree(GetProcessHeap(), 0, m_sPathRel); 1988 m_sPathRel = NULL; 1989 1990 if (pszPathRel) 1991 { 1992 m_sPathRel = strdupW(pszPathRel); 1993 if (!m_sPathRel) 1994 return E_OUTOFMEMORY; 1995 } 1996 m_bDirty = TRUE; 1997 1998 return ShellLink_UpdatePath(m_sPathRel, m_sPath, m_sWorkDir, &m_sPath); 1999 } 2000 2001 static LPWSTR GetAdvertisedArg(LPCWSTR str) 2002 { 2003 if (!str) 2004 return NULL; 2005 2006 LPCWSTR p = wcschr(str, L':'); 2007 if (!p) 2008 return NULL; 2009 2010 DWORD len = p - str; 2011 LPWSTR ret = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * (len + 1)); 2012 if (!ret) 2013 return ret; 2014 2015 memcpy(ret, str, sizeof(WCHAR)*len); 2016 ret[len] = 0; 2017 return ret; 2018 } 2019 2020 HRESULT CShellLink::WriteAdvertiseInfo(LPCWSTR string, DWORD dwSig) 2021 { 2022 EXP_DARWIN_LINK buffer; 2023 LPEXP_DARWIN_LINK pInfo; 2024 2025 if ( (dwSig != EXP_DARWIN_ID_SIG) 2026 #if (NTDDI_VERSION < NTDDI_LONGHORN) 2027 && (dwSig != EXP_LOGO3_ID_SIG) 2028 #endif 2029 ) 2030 { 2031 return E_INVALIDARG; 2032 } 2033 2034 if (!string) 2035 return S_FALSE; 2036 2037 pInfo = (LPEXP_DARWIN_LINK)SHFindDataBlock(m_pDBList, dwSig); 2038 if (pInfo) 2039 { 2040 /* Make sure that the size of the structure is valid */ 2041 if (pInfo->dbh.cbSize != sizeof(*pInfo)) 2042 { 2043 ERR("Ooops. This structure is not as expected...\n"); 2044 2045 /* Invalid structure, remove it altogether */ 2046 if (dwSig == EXP_DARWIN_ID_SIG) 2047 m_Header.dwFlags &= ~SLDF_HAS_DARWINID; 2048 #if (NTDDI_VERSION < NTDDI_LONGHORN) 2049 else if (dwSig == EXP_LOGO3_ID_SIG) 2050 m_Header.dwFlags &= ~SLDF_HAS_LOGO3ID; 2051 #endif 2052 RemoveDataBlock(dwSig); 2053 2054 /* Reset the pointer and go use the static buffer */ 2055 pInfo = NULL; 2056 } 2057 } 2058 if (!pInfo) 2059 { 2060 /* Use the static buffer */ 2061 pInfo = &buffer; 2062 buffer.dbh.cbSize = sizeof(buffer); 2063 buffer.dbh.dwSignature = dwSig; 2064 } 2065 2066 lstrcpynW(pInfo->szwDarwinID, string, _countof(pInfo->szwDarwinID)); 2067 WideCharToMultiByte(CP_ACP, 0, string, -1, 2068 pInfo->szDarwinID, _countof(pInfo->szDarwinID), NULL, NULL); 2069 2070 HRESULT hr = S_OK; 2071 if (pInfo == &buffer) 2072 hr = AddDataBlock(pInfo); 2073 if (hr == S_OK) 2074 { 2075 if (dwSig == EXP_DARWIN_ID_SIG) 2076 m_Header.dwFlags |= SLDF_HAS_DARWINID; 2077 #if (NTDDI_VERSION < NTDDI_LONGHORN) 2078 else if (dwSig == EXP_LOGO3_ID_SIG) 2079 m_Header.dwFlags |= SLDF_HAS_LOGO3ID; 2080 #endif 2081 } 2082 2083 return hr; 2084 } 2085 2086 HRESULT CShellLink::SetAdvertiseInfo(LPCWSTR str) 2087 { 2088 HRESULT hr; 2089 LPCWSTR szComponent = NULL, szProduct = NULL, p; 2090 INT len; 2091 GUID guid; 2092 WCHAR szGuid[38+1]; 2093 2094 /**/sProduct = sComponent = NULL;/**/ 2095 2096 while (str[0]) 2097 { 2098 /* each segment must start with two colons */ 2099 if (str[0] != ':' || str[1] != ':') 2100 return E_FAIL; 2101 2102 /* the last segment is just two colons */ 2103 if (!str[2]) 2104 break; 2105 str += 2; 2106 2107 /* there must be a colon straight after a guid */ 2108 p = wcschr(str, L':'); 2109 if (!p) 2110 return E_FAIL; 2111 len = p - str; 2112 if (len != 38) 2113 return E_FAIL; 2114 2115 /* get the guid, and check if it's validly formatted */ 2116 memcpy(szGuid, str, sizeof(WCHAR)*len); 2117 szGuid[len] = 0; 2118 2119 hr = CLSIDFromString(szGuid, &guid); 2120 if (hr != S_OK) 2121 return hr; 2122 str = p + 1; 2123 2124 /* match it up to a guid that we care about */ 2125 if (IsEqualGUID(guid, SHELL32_AdvtShortcutComponent) && !szComponent) 2126 szComponent = str; /* Darwin */ 2127 else if (IsEqualGUID(guid, SHELL32_AdvtShortcutProduct) && !szProduct) 2128 szProduct = str; /* Logo3 */ 2129 else 2130 return E_FAIL; 2131 2132 /* skip to the next field */ 2133 str = wcschr(str, L':'); 2134 if (!str) 2135 return E_FAIL; 2136 } 2137 2138 /* we have to have a component for an advertised shortcut */ 2139 if (!szComponent) 2140 return E_FAIL; 2141 2142 szComponent = GetAdvertisedArg(szComponent); 2143 szProduct = GetAdvertisedArg(szProduct); 2144 2145 hr = WriteAdvertiseInfo(szComponent, EXP_DARWIN_ID_SIG); 2146 // if (FAILED(hr)) 2147 // return hr; 2148 #if (NTDDI_VERSION < NTDDI_LONGHORN) 2149 hr = WriteAdvertiseInfo(szProduct, EXP_LOGO3_ID_SIG); 2150 // if (FAILED(hr)) 2151 // return hr; 2152 #endif 2153 2154 HeapFree(GetProcessHeap(), 0, (PVOID)szComponent); 2155 HeapFree(GetProcessHeap(), 0, (PVOID)szProduct); 2156 2157 if (TRACE_ON(shell)) 2158 { 2159 GetAdvertiseInfo(&sComponent, EXP_DARWIN_ID_SIG); 2160 TRACE("Component = %s\n", debugstr_w(sComponent)); 2161 #if (NTDDI_VERSION < NTDDI_LONGHORN) 2162 GetAdvertiseInfo(&sProduct, EXP_LOGO3_ID_SIG); 2163 TRACE("Product = %s\n", debugstr_w(sProduct)); 2164 #endif 2165 } 2166 2167 return S_OK; 2168 } 2169 2170 /* 2171 * Since the real PathResolve (from Wine) is unimplemented at the moment, 2172 * we use this local implementation, until a better one is written (using 2173 * code parts of the SHELL_xxx helpers in Wine's shellpath.c). 2174 */ 2175 static BOOL HACKISH_PathResolve( 2176 IN OUT PWSTR pszPath, 2177 IN PZPCWSTR dirs OPTIONAL, 2178 IN UINT fFlags) 2179 { 2180 // FIXME: This is unimplemented!!! 2181 #if 0 2182 return PathResolve(pszPath, dirs, fFlags); 2183 #else 2184 BOOL Success = FALSE; 2185 USHORT i; 2186 LPWSTR fname = NULL; 2187 WCHAR szPath[MAX_PATH]; 2188 2189 /* First, search for a valid existing path */ 2190 2191 // NOTE: See also: SHELL_FindExecutable() 2192 2193 /* 2194 * List of extensions searched for, by PathResolve with the flag 2195 * PRF_TRYPROGRAMEXTENSIONS == PRF_EXECUTABLE | PRF_VERIFYEXISTS set, 2196 * according to MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/bb776478(v=vs.85).aspx 2197 */ 2198 static PCWSTR Extensions[] = {L".pif", L".com", L".bat", L".cmd", L".lnk", L".exe", NULL}; 2199 #define LNK_EXT_INDEX 4 // ".lnk" has index 4 in the array above 2200 2201 /* 2202 * Start at the beginning of the list if PRF_EXECUTABLE is set, otherwise 2203 * just use the last element 'NULL' (no extension checking). 2204 */ 2205 i = ((fFlags & PRF_EXECUTABLE) ? 0 : _countof(Extensions) - 1); 2206 for (; i < _countof(Extensions); ++i) 2207 { 2208 /* Ignore shell links ".lnk" if needed */ 2209 if ((fFlags & PRF_DONTFINDLNK) && (i == LNK_EXT_INDEX)) 2210 continue; 2211 2212 Success = (SearchPathW(NULL, pszPath, Extensions[i], 2213 _countof(szPath), szPath, NULL) != 0); 2214 if (!Success) 2215 { 2216 ERR("SearchPathW(pszPath = '%S') failed. Error code: %lu\n", pszPath, GetLastError()); 2217 } 2218 else 2219 { 2220 ERR("SearchPathW(pszPath = '%S', szPath = '%S') succeeded\n", pszPath, szPath); 2221 break; 2222 } 2223 } 2224 2225 if (!Success) 2226 { 2227 ERR("SearchPathW(pszPath = '%S') failed. Error code: %lu\n", pszPath, GetLastError()); 2228 2229 /* We failed, try with PathFindOnPath, as explained by MSDN */ 2230 // Success = PathFindOnPathW(pszPath, dirs); 2231 StringCchCopyW(szPath, _countof(szPath), pszPath); 2232 Success = PathFindOnPathW(szPath, dirs); 2233 if (!Success) 2234 { 2235 ERR("PathFindOnPathW(pszPath = '%S') failed\n", pszPath); 2236 2237 /* We failed again, fall back to building a possible non-existing path */ 2238 if (!GetFullPathNameW(pszPath, _countof(szPath), szPath, &fname)) 2239 { 2240 ERR("GetFullPathNameW(pszPath = '%S') failed. Error code: %lu\n", pszPath, GetLastError()); 2241 return FALSE; 2242 } 2243 2244 Success = PathFileExistsW(szPath); 2245 if (!Success) 2246 ERR("PathFileExistsW(szPath = '%S') failed. Error code: %lu\n", szPath, GetLastError()); 2247 2248 /******************************************************/ 2249 /* Question: Why this line is needed only for files?? */ 2250 if (fname && (_wcsicmp(pszPath, fname) == 0)) 2251 *szPath = L'\0'; 2252 /******************************************************/ 2253 } 2254 else 2255 { 2256 ERR("PathFindOnPathW(pszPath = '%S' ==> '%S') succeeded\n", pszPath, szPath); 2257 } 2258 } 2259 2260 /* Copy back the results to the caller */ 2261 StringCchCopyW(pszPath, MAX_PATH, szPath); 2262 2263 /* 2264 * Since the called functions always checked whether the file path existed, 2265 * we do not need to redo a final check: we can use instead the cached 2266 * result in 'Success'. 2267 */ 2268 return ((fFlags & PRF_VERIFYEXISTS) ? Success : TRUE); 2269 #endif 2270 } 2271 2272 HRESULT CShellLink::SetTargetFromPIDLOrPath(LPCITEMIDLIST pidl, LPCWSTR pszFile) 2273 { 2274 HRESULT hr = S_OK; 2275 LPITEMIDLIST pidlNew = NULL; 2276 WCHAR szPath[MAX_PATH]; 2277 2278 /* 2279 * Not both 'pidl' and 'pszFile' should be set. 2280 * But either one or both can be NULL. 2281 */ 2282 if (pidl && pszFile) 2283 return E_FAIL; 2284 2285 if (pidl) 2286 { 2287 /* Clone the PIDL */ 2288 pidlNew = ILClone(pidl); 2289 if (!pidlNew) 2290 return E_FAIL; 2291 } 2292 else if (pszFile) 2293 { 2294 /* Build a PIDL for this path target */ 2295 hr = SHILCreateFromPathW(pszFile, &pidlNew, NULL); 2296 if (FAILED(hr)) 2297 { 2298 /* This failed, try to resolve the path, then create a simple PIDL */ 2299 2300 StringCchCopyW(szPath, _countof(szPath), pszFile); 2301 // FIXME: Because PathResolve is unimplemented, we use our hackish implementation! 2302 HACKISH_PathResolve(szPath, NULL, PRF_TRYPROGRAMEXTENSIONS); 2303 2304 pidlNew = SHSimpleIDListFromPathW(szPath); 2305 /******************************************************/ 2306 /* Question: Why this line is needed only for files?? */ 2307 hr = (*szPath ? S_OK : E_INVALIDARG); // S_FALSE 2308 /******************************************************/ 2309 } 2310 } 2311 // else if (!pidl && !pszFile) { pidlNew = NULL; hr = S_OK; } 2312 2313 ILFree(m_pPidl); 2314 m_pPidl = pidlNew; 2315 2316 if (!pszFile) 2317 { 2318 if (SHGetPathFromIDListW(pidlNew, szPath)) 2319 pszFile = szPath; 2320 } 2321 2322 // TODO: Fully update link info, tracker, file attribs... 2323 2324 // if (pszFile) 2325 if (!pszFile) 2326 { 2327 *szPath = L'\0'; 2328 pszFile = szPath; 2329 } 2330 2331 /* Update the cached path (for link info) */ 2332 ShellLink_GetVolumeInfo(pszFile, &volume); 2333 2334 if (m_sPath) 2335 HeapFree(GetProcessHeap(), 0, m_sPath); 2336 2337 m_sPath = strdupW(pszFile); 2338 if (!m_sPath) 2339 return E_OUTOFMEMORY; 2340 2341 m_bDirty = TRUE; 2342 return hr; 2343 } 2344 2345 HRESULT STDMETHODCALLTYPE CShellLink::SetPath(LPCWSTR pszFile) 2346 { 2347 LPWSTR unquoted = NULL; 2348 HRESULT hr = S_OK; 2349 2350 TRACE("(%p)->(path=%s)\n", this, debugstr_w(pszFile)); 2351 2352 if (!pszFile) 2353 return E_INVALIDARG; 2354 2355 /* 2356 * Allow upgrading Logo3 shortcuts (m_Header.dwFlags & SLDF_HAS_LOGO3ID), 2357 * but forbid upgrading Darwin ones. 2358 */ 2359 if (m_Header.dwFlags & SLDF_HAS_DARWINID) 2360 return S_FALSE; 2361 2362 /* quotes at the ends of the string are stripped */ 2363 SIZE_T len = wcslen(pszFile); 2364 if (pszFile[0] == L'"' && pszFile[len-1] == L'"') 2365 { 2366 unquoted = strdupW(pszFile); 2367 PathUnquoteSpacesW(unquoted); 2368 pszFile = unquoted; 2369 } 2370 2371 /* any other quote marks are invalid */ 2372 if (wcschr(pszFile, L'"')) 2373 { 2374 hr = S_FALSE; 2375 goto end; 2376 } 2377 2378 /* Clear the cached path */ 2379 HeapFree(GetProcessHeap(), 0, m_sPath); 2380 m_sPath = NULL; 2381 2382 /* Check for an advertised target (Logo3 or Darwin) */ 2383 if (SetAdvertiseInfo(pszFile) != S_OK) 2384 { 2385 /* This is not an advertised target, but a regular path */ 2386 WCHAR szPath[MAX_PATH]; 2387 2388 /* 2389 * Check whether the user-given file path contains unexpanded 2390 * environment variables. If so, create a target environment block. 2391 * Note that in this block we will store the user-given path. 2392 * It will contain the unexpanded environment variables, but 2393 * it can also contain already expanded path that the user does 2394 * not want to see them unexpanded (e.g. so that they always 2395 * refer to the same place even if the would-be corresponding 2396 * environment variable could change). 2397 */ 2398 if (*pszFile) 2399 SHExpandEnvironmentStringsW(pszFile, szPath, _countof(szPath)); 2400 else 2401 *szPath = L'\0'; 2402 2403 if (*pszFile && (wcscmp(pszFile, szPath) != 0)) 2404 { 2405 /* 2406 * The user-given file path contains unexpanded environment 2407 * variables, so we need a target environment block. 2408 */ 2409 EXP_SZ_LINK buffer; 2410 LPEXP_SZ_LINK pInfo; 2411 2412 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_LINK_SIG); 2413 if (pInfo) 2414 { 2415 /* Make sure that the size of the structure is valid */ 2416 if (pInfo->cbSize != sizeof(*pInfo)) 2417 { 2418 ERR("Ooops. This structure is not as expected...\n"); 2419 2420 /* Invalid structure, remove it altogether */ 2421 m_Header.dwFlags &= ~SLDF_HAS_EXP_SZ; 2422 RemoveDataBlock(EXP_SZ_LINK_SIG); 2423 2424 /* Reset the pointer and go use the static buffer */ 2425 pInfo = NULL; 2426 } 2427 } 2428 if (!pInfo) 2429 { 2430 /* Use the static buffer */ 2431 pInfo = &buffer; 2432 buffer.cbSize = sizeof(buffer); 2433 buffer.dwSignature = EXP_SZ_LINK_SIG; 2434 } 2435 2436 lstrcpynW(pInfo->szwTarget, pszFile, _countof(pInfo->szwTarget)); 2437 WideCharToMultiByte(CP_ACP, 0, pszFile, -1, 2438 pInfo->szTarget, _countof(pInfo->szTarget), NULL, NULL); 2439 2440 hr = S_OK; 2441 if (pInfo == &buffer) 2442 hr = AddDataBlock(pInfo); 2443 if (hr == S_OK) 2444 m_Header.dwFlags |= SLDF_HAS_EXP_SZ; 2445 2446 /* Now, make pszFile point to the expanded path */ 2447 pszFile = szPath; 2448 } 2449 else 2450 { 2451 /* 2452 * The user-given file path does not contain unexpanded environment 2453 * variables, so we need to remove any target environment block. 2454 */ 2455 m_Header.dwFlags &= ~SLDF_HAS_EXP_SZ; 2456 RemoveDataBlock(EXP_SZ_LINK_SIG); 2457 2458 /* pszFile points to the user path */ 2459 } 2460 2461 /* Set the target */ 2462 hr = SetTargetFromPIDLOrPath(NULL, pszFile); 2463 } 2464 2465 m_bDirty = TRUE; 2466 2467 end: 2468 HeapFree(GetProcessHeap(), 0, unquoted); 2469 return hr; 2470 } 2471 2472 HRESULT STDMETHODCALLTYPE CShellLink::AddDataBlock(void* pDataBlock) 2473 { 2474 if (SHAddDataBlock(&m_pDBList, (DATABLOCK_HEADER*)pDataBlock)) 2475 { 2476 m_bDirty = TRUE; 2477 return S_OK; 2478 } 2479 return S_FALSE; 2480 } 2481 2482 HRESULT STDMETHODCALLTYPE CShellLink::CopyDataBlock(DWORD dwSig, void** ppDataBlock) 2483 { 2484 DATABLOCK_HEADER* pBlock; 2485 PVOID pDataBlock; 2486 2487 TRACE("%p %08x %p\n", this, dwSig, ppDataBlock); 2488 2489 *ppDataBlock = NULL; 2490 2491 pBlock = SHFindDataBlock(m_pDBList, dwSig); 2492 if (!pBlock) 2493 { 2494 ERR("unknown datablock %08x (not found)\n", dwSig); 2495 return E_FAIL; 2496 } 2497 2498 pDataBlock = LocalAlloc(LMEM_ZEROINIT, pBlock->cbSize); 2499 if (!pDataBlock) 2500 return E_OUTOFMEMORY; 2501 2502 CopyMemory(pDataBlock, pBlock, pBlock->cbSize); 2503 2504 *ppDataBlock = pDataBlock; 2505 return S_OK; 2506 } 2507 2508 HRESULT STDMETHODCALLTYPE CShellLink::RemoveDataBlock(DWORD dwSig) 2509 { 2510 if (SHRemoveDataBlock(&m_pDBList, dwSig)) 2511 { 2512 m_bDirty = TRUE; 2513 return S_OK; 2514 } 2515 return S_FALSE; 2516 } 2517 2518 HRESULT STDMETHODCALLTYPE CShellLink::GetFlags(DWORD *pdwFlags) 2519 { 2520 TRACE("%p %p\n", this, pdwFlags); 2521 *pdwFlags = m_Header.dwFlags; 2522 return S_OK; 2523 } 2524 2525 HRESULT STDMETHODCALLTYPE CShellLink::SetFlags(DWORD dwFlags) 2526 { 2527 #if 0 // FIXME! 2528 m_Header.dwFlags = dwFlags; 2529 m_bDirty = TRUE; 2530 return S_OK; 2531 #else 2532 FIXME("\n"); 2533 return E_NOTIMPL; 2534 #endif 2535 } 2536 2537 /************************************************************************** 2538 * CShellLink implementation of IShellExtInit::Initialize() 2539 * 2540 * Loads the shelllink from the dataobject the shell is pointing to. 2541 */ 2542 HRESULT STDMETHODCALLTYPE CShellLink::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID) 2543 { 2544 TRACE("%p %p %p %p\n", this, pidlFolder, pdtobj, hkeyProgID); 2545 2546 if (!pdtobj) 2547 return E_FAIL; 2548 2549 FORMATETC format; 2550 format.cfFormat = CF_HDROP; 2551 format.ptd = NULL; 2552 format.dwAspect = DVASPECT_CONTENT; 2553 format.lindex = -1; 2554 format.tymed = TYMED_HGLOBAL; 2555 2556 STGMEDIUM stgm; 2557 HRESULT hr = pdtobj->GetData(&format, &stgm); 2558 if (FAILED(hr)) 2559 return hr; 2560 2561 UINT count = DragQueryFileW((HDROP)stgm.hGlobal, -1, NULL, 0); 2562 if (count == 1) 2563 { 2564 count = DragQueryFileW((HDROP)stgm.hGlobal, 0, NULL, 0); 2565 count++; 2566 LPWSTR path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, count * sizeof(WCHAR)); 2567 if (path) 2568 { 2569 count = DragQueryFileW((HDROP)stgm.hGlobal, 0, path, count); 2570 hr = Load(path, 0); 2571 HeapFree(GetProcessHeap(), 0, path); 2572 } 2573 } 2574 ReleaseStgMedium(&stgm); 2575 2576 return S_OK; 2577 } 2578 2579 HRESULT STDMETHODCALLTYPE CShellLink::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) 2580 { 2581 int id = 1; 2582 2583 TRACE("%p %p %u %u %u %u\n", this, 2584 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags); 2585 2586 if (!hMenu) 2587 return E_INVALIDARG; 2588 2589 WCHAR wszOpen[20]; 2590 if (!LoadStringW(shell32_hInstance, IDS_OPEN_VERB, wszOpen, _countof(wszOpen))) 2591 *wszOpen = L'\0'; 2592 2593 MENUITEMINFOW mii; 2594 ZeroMemory(&mii, sizeof(mii)); 2595 mii.cbSize = sizeof(mii); 2596 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE; 2597 mii.dwTypeData = wszOpen; 2598 mii.cch = wcslen(mii.dwTypeData); 2599 mii.wID = idCmdFirst + id++; 2600 mii.fState = MFS_DEFAULT | MFS_ENABLED; 2601 mii.fType = MFT_STRING; 2602 if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii)) 2603 return E_FAIL; 2604 m_iIdOpen = 1; 2605 2606 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, id); 2607 } 2608 2609 HRESULT STDMETHODCALLTYPE CShellLink::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) 2610 { 2611 LPWSTR args = NULL; 2612 LPWSTR path = NULL; 2613 2614 TRACE("%p %p\n", this, lpici); 2615 2616 if (lpici->cbSize < sizeof(CMINVOKECOMMANDINFO)) 2617 return E_INVALIDARG; 2618 2619 // NOTE: We could use lpici->hwnd (certainly in case lpici->fMask doesn't contain CMIC_MASK_FLAG_NO_UI) 2620 // as the parent window handle... ? 2621 /* FIXME: get using interface set from IObjectWithSite?? */ 2622 // NOTE: We might need an extended version of Resolve that provides us with paths... 2623 HRESULT hr = Resolve(lpici->hwnd, 0); 2624 if (FAILED(hr)) 2625 { 2626 TRACE("failed to resolve component with error 0x%08x", hr); 2627 return hr; 2628 } 2629 2630 path = strdupW(m_sPath); 2631 2632 if ( lpici->cbSize == sizeof(CMINVOKECOMMANDINFOEX) && 2633 (lpici->fMask & CMIC_MASK_UNICODE) ) 2634 { 2635 LPCMINVOKECOMMANDINFOEX iciex = (LPCMINVOKECOMMANDINFOEX)lpici; 2636 SIZE_T len = 2; 2637 2638 if (m_sArgs) 2639 len += wcslen(m_sArgs); 2640 if (iciex->lpParametersW) 2641 len += wcslen(iciex->lpParametersW); 2642 2643 args = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); 2644 *args = 0; 2645 if (m_sArgs) 2646 wcscat(args, m_sArgs); 2647 if (iciex->lpParametersW) 2648 { 2649 wcscat(args, L" "); 2650 wcscat(args, iciex->lpParametersW); 2651 } 2652 } 2653 else if (m_sArgs != NULL) 2654 { 2655 args = strdupW(m_sArgs); 2656 } 2657 2658 SHELLEXECUTEINFOW sei; 2659 ZeroMemory(&sei, sizeof(sei)); 2660 sei.cbSize = sizeof(sei); 2661 sei.fMask = SEE_MASK_HASLINKNAME | SEE_MASK_UNICODE | 2662 (lpici->fMask & (SEE_MASK_NOASYNC | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI)); 2663 sei.lpFile = path; 2664 sei.lpClass = m_sLinkPath; 2665 sei.nShow = m_Header.nShowCommand; 2666 sei.lpDirectory = m_sWorkDir; 2667 sei.lpParameters = args; 2668 sei.lpVerb = L"open"; 2669 2670 // HACK for ShellExecuteExW 2671 if (m_sPath && wcsstr(m_sPath, L".cpl")) 2672 sei.lpVerb = L"cplopen"; 2673 2674 if (ShellExecuteExW(&sei)) 2675 hr = S_OK; 2676 else 2677 hr = E_FAIL; 2678 2679 HeapFree(GetProcessHeap(), 0, args); 2680 HeapFree(GetProcessHeap(), 0, path); 2681 2682 return hr; 2683 } 2684 2685 HRESULT STDMETHODCALLTYPE CShellLink::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pwReserved, LPSTR pszName, UINT cchMax) 2686 { 2687 FIXME("%p %lu %u %p %p %u\n", this, idCmd, uType, pwReserved, pszName, cchMax); 2688 return E_NOTIMPL; 2689 } 2690 2691 INT_PTR CALLBACK ExtendedShortcutProc(HWND hwndDlg, UINT uMsg, 2692 WPARAM wParam, LPARAM lParam) 2693 { 2694 switch(uMsg) 2695 { 2696 case WM_INITDIALOG: 2697 if (lParam) 2698 { 2699 HWND hDlgCtrl = GetDlgItem(hwndDlg, IDC_SHORTEX_RUN_DIFFERENT); 2700 SendMessage(hDlgCtrl, BM_SETCHECK, BST_CHECKED, 0); 2701 } 2702 return TRUE; 2703 case WM_COMMAND: 2704 { 2705 HWND hDlgCtrl = GetDlgItem(hwndDlg, IDC_SHORTEX_RUN_DIFFERENT); 2706 if (LOWORD(wParam) == IDOK) 2707 { 2708 if (SendMessage(hDlgCtrl, BM_GETCHECK, 0, 0) == BST_CHECKED) 2709 EndDialog(hwndDlg, 1); 2710 else 2711 EndDialog(hwndDlg, 0); 2712 } 2713 else if (LOWORD(wParam) == IDCANCEL) 2714 { 2715 EndDialog(hwndDlg, -1); 2716 } 2717 else if (LOWORD(wParam) == IDC_SHORTEX_RUN_DIFFERENT) 2718 { 2719 if (SendMessage(hDlgCtrl, BM_GETCHECK, 0, 0) == BST_CHECKED) 2720 SendMessage(hDlgCtrl, BM_SETCHECK, BST_UNCHECKED, 0); 2721 else 2722 SendMessage(hDlgCtrl, BM_SETCHECK, BST_CHECKED, 0); 2723 } 2724 } 2725 } 2726 return FALSE; 2727 } 2728 2729 EXTERN_C HRESULT 2730 WINAPI 2731 SHOpenFolderAndSelectItems(LPITEMIDLIST pidlFolder, 2732 UINT cidl, 2733 PCUITEMID_CHILD_ARRAY apidl, 2734 DWORD dwFlags); 2735 2736 /************************************************************************** 2737 * SH_GetTargetTypeByPath 2738 * 2739 * Function to get target type by passing full path to it 2740 */ 2741 LPWSTR SH_GetTargetTypeByPath(LPCWSTR lpcwFullPath) 2742 { 2743 LPCWSTR pwszExt; 2744 static WCHAR wszBuf[MAX_PATH]; 2745 2746 /* Get file information */ 2747 SHFILEINFOW fi; 2748 if (!SHGetFileInfoW(lpcwFullPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES)) 2749 { 2750 ERR("SHGetFileInfoW failed for %ls (%lu)\n", lpcwFullPath, GetLastError()); 2751 fi.szTypeName[0] = L'\0'; 2752 fi.hIcon = NULL; 2753 } 2754 2755 pwszExt = PathFindExtensionW(lpcwFullPath); 2756 if (pwszExt[0]) 2757 { 2758 if (!fi.szTypeName[0]) 2759 { 2760 /* The file type is unknown, so default to string "FileExtension File" */ 2761 size_t cchRemaining = 0; 2762 LPWSTR pwszEnd = NULL; 2763 2764 StringCchPrintfExW(wszBuf, _countof(wszBuf), &pwszEnd, &cchRemaining, 0, L"%s ", pwszExt + 1); 2765 } 2766 else 2767 { 2768 /* Update file type */ 2769 StringCbPrintfW(wszBuf, sizeof(wszBuf), L"%s (%s)", fi.szTypeName, pwszExt); 2770 } 2771 } 2772 2773 return wszBuf; 2774 } 2775 2776 BOOL CShellLink::OnInitDialog(HWND hwndDlg, HWND hwndFocus, LPARAM lParam) 2777 { 2778 TRACE("CShellLink::OnInitDialog(hwnd %p hwndFocus %p lParam %p)\n", hwndDlg, hwndFocus, lParam); 2779 2780 TRACE("m_sArgs: %S sComponent: %S m_sDescription: %S m_sIcoPath: %S m_sPath: %S m_sPathRel: %S sProduct: %S m_sWorkDir: %S\n", m_sArgs, sComponent, m_sDescription, 2781 m_sIcoPath, m_sPath, m_sPathRel, sProduct, m_sWorkDir); 2782 2783 m_bInInit = TRUE; 2784 2785 /* Get file information */ 2786 // FIXME! FIXME! Shouldn't we use m_sIcoPath, m_Header.nIconIndex instead??? 2787 SHFILEINFOW fi; 2788 if (!SHGetFileInfoW(m_sLinkPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_ICON)) 2789 { 2790 ERR("SHGetFileInfoW failed for %ls (%lu)\n", m_sLinkPath, GetLastError()); 2791 fi.szTypeName[0] = L'\0'; 2792 fi.hIcon = NULL; 2793 } 2794 2795 if (fi.hIcon) 2796 { 2797 if (m_hIcon) 2798 DestroyIcon(m_hIcon); 2799 m_hIcon = fi.hIcon; 2800 SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)m_hIcon, 0); 2801 } 2802 else 2803 ERR("ExtractIconW failed %ls %u\n", m_sIcoPath, m_Header.nIconIndex); 2804 2805 /* Target type */ 2806 if (m_sPath) 2807 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TYPE_EDIT, SH_GetTargetTypeByPath(m_sPath)); 2808 2809 /* Target location */ 2810 if (m_sPath) 2811 { 2812 WCHAR target[MAX_PATH]; 2813 StringCchCopyW(target, _countof(target), m_sPath); 2814 PathRemoveFileSpecW(target); 2815 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION_EDIT, PathFindFileNameW(target)); 2816 } 2817 2818 /* Target path */ 2819 if (m_sPath) 2820 { 2821 WCHAR newpath[2*MAX_PATH] = L"\0"; 2822 if (wcschr(m_sPath, ' ')) 2823 StringCchPrintfExW(newpath, _countof(newpath), NULL, NULL, 0, L"\"%ls\"", m_sPath); 2824 else 2825 StringCchCopyExW(newpath, _countof(newpath), m_sPath, NULL, NULL, 0); 2826 2827 if (m_sArgs && m_sArgs[0]) 2828 { 2829 StringCchCatW(newpath, _countof(newpath), L" "); 2830 StringCchCatW(newpath, _countof(newpath), m_sArgs); 2831 } 2832 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, newpath); 2833 } 2834 2835 /* Working dir */ 2836 if (m_sWorkDir) 2837 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, m_sWorkDir); 2838 2839 /* Description */ 2840 if (m_sDescription) 2841 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_COMMENT_EDIT, m_sDescription); 2842 2843 m_bInInit = FALSE; 2844 2845 return TRUE; 2846 } 2847 2848 void CShellLink::OnCommand(HWND hwndDlg, int id, HWND hwndCtl, UINT codeNotify) 2849 { 2850 switch (id) 2851 { 2852 case IDC_SHORTCUT_FIND: 2853 SHOpenFolderAndSelectItems(m_pPidl, 0, NULL, 0); 2854 /// 2855 /// FIXME 2856 /// open target directory 2857 /// 2858 return; 2859 2860 case IDC_SHORTCUT_CHANGE_ICON: 2861 { 2862 WCHAR wszPath[MAX_PATH] = L""; 2863 2864 if (m_sIcoPath) 2865 wcscpy(wszPath, m_sIcoPath); 2866 else 2867 FindExecutableW(m_sPath, NULL, wszPath); 2868 2869 INT IconIndex = m_Header.nIconIndex; 2870 if (PickIconDlg(hwndDlg, wszPath, _countof(wszPath), &IconIndex)) 2871 { 2872 SetIconLocation(wszPath, IconIndex); 2873 PropSheet_Changed(GetParent(hwndDlg), hwndDlg); 2874 2875 HICON hIconLarge = CreateShortcutIcon(wszPath, IconIndex); 2876 if (hIconLarge) 2877 { 2878 if (m_hIcon) 2879 DestroyIcon(m_hIcon); 2880 m_hIcon = hIconLarge; 2881 SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)m_hIcon, 0); 2882 } 2883 } 2884 return; 2885 } 2886 2887 case IDC_SHORTCUT_ADVANCED: 2888 { 2889 INT_PTR result = DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_SHORTCUT_EXTENDED_PROPERTIES), hwndDlg, ExtendedShortcutProc, (LPARAM)m_bRunAs); 2890 if (result == 1 || result == 0) 2891 { 2892 if (m_bRunAs != result) 2893 { 2894 PropSheet_Changed(GetParent(hwndDlg), hwndDlg); 2895 } 2896 2897 m_bRunAs = result; 2898 } 2899 return; 2900 } 2901 } 2902 if (codeNotify == EN_CHANGE) 2903 { 2904 if (!m_bInInit) 2905 PropSheet_Changed(GetParent(hwndDlg), hwndDlg); 2906 } 2907 } 2908 2909 LRESULT CShellLink::OnNotify(HWND hwndDlg, int idFrom, LPNMHDR pnmhdr) 2910 { 2911 WCHAR wszBuf[MAX_PATH]; 2912 LPPSHNOTIFY lppsn = (LPPSHNOTIFY)pnmhdr; 2913 2914 if (lppsn->hdr.code == PSN_APPLY) 2915 { 2916 /* set working directory */ 2917 GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, wszBuf, _countof(wszBuf)); 2918 SetWorkingDirectory(wszBuf); 2919 2920 /* set link destination */ 2921 GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, wszBuf, _countof(wszBuf)); 2922 LPWSTR lpszArgs = NULL; 2923 LPWSTR unquoted = strdupW(wszBuf); 2924 StrTrimW(unquoted, L" "); 2925 2926 if (!PathFileExistsW(unquoted)) 2927 { 2928 lpszArgs = PathGetArgsW(unquoted); 2929 PathRemoveArgsW(unquoted); 2930 StrTrimW(lpszArgs, L" "); 2931 } 2932 if (unquoted[0] == '"' && unquoted[wcslen(unquoted) - 1] == '"') 2933 PathUnquoteSpacesW(unquoted); 2934 2935 WCHAR *pwszExt = PathFindExtensionW(unquoted); 2936 if (!wcsicmp(pwszExt, L".lnk")) 2937 { 2938 // FIXME load localized error msg 2939 MessageBoxW(hwndDlg, L"You cannot create a link to a shortcut", L"Error", MB_ICONERROR); 2940 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE); 2941 return TRUE; 2942 } 2943 2944 if (!PathFileExistsW(unquoted)) 2945 { 2946 // FIXME load localized error msg 2947 MessageBoxW(hwndDlg, L"The specified file name in the target box is invalid", L"Error", MB_ICONERROR); 2948 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE); 2949 return TRUE; 2950 } 2951 2952 SetPath(unquoted); 2953 if (lpszArgs) 2954 SetArguments(lpszArgs); 2955 else 2956 SetArguments(L"\0"); 2957 2958 HeapFree(GetProcessHeap(), 0, unquoted); 2959 2960 TRACE("This %p m_sLinkPath %S\n", this, m_sLinkPath); 2961 Save(m_sLinkPath, TRUE); 2962 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_sLinkPath, NULL); 2963 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); 2964 return TRUE; 2965 } 2966 return FALSE; 2967 } 2968 2969 void CShellLink::OnDestroy(HWND hwndDlg) 2970 { 2971 if (m_hIcon) 2972 { 2973 DestroyIcon(m_hIcon); 2974 m_hIcon = NULL; 2975 } 2976 } 2977 2978 /************************************************************************** 2979 * SH_ShellLinkDlgProc 2980 * 2981 * dialog proc of the shortcut property dialog 2982 */ 2983 2984 INT_PTR CALLBACK 2985 CShellLink::SH_ShellLinkDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) 2986 { 2987 LPPROPSHEETPAGEW ppsp; 2988 CShellLink *pThis = reinterpret_cast<CShellLink *>(GetWindowLongPtr(hwndDlg, DWLP_USER)); 2989 2990 switch (uMsg) 2991 { 2992 case WM_INITDIALOG: 2993 ppsp = (LPPROPSHEETPAGEW)lParam; 2994 if (ppsp == NULL) 2995 break; 2996 2997 pThis = reinterpret_cast<CShellLink *>(ppsp->lParam); 2998 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pThis); 2999 return pThis->OnInitDialog(hwndDlg, (HWND)(wParam), lParam); 3000 3001 case WM_NOTIFY: 3002 return pThis->OnNotify(hwndDlg, (int)wParam, (NMHDR *)lParam); 3003 3004 case WM_COMMAND: 3005 pThis->OnCommand(hwndDlg, LOWORD(wParam), (HWND)lParam, HIWORD(wParam)); 3006 break; 3007 3008 case WM_DESTROY: 3009 pThis->OnDestroy(hwndDlg); 3010 break; 3011 3012 default: 3013 break; 3014 } 3015 3016 return FALSE; 3017 } 3018 3019 /************************************************************************** 3020 * ShellLink_IShellPropSheetExt interface 3021 */ 3022 3023 HRESULT STDMETHODCALLTYPE CShellLink::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) 3024 { 3025 HPROPSHEETPAGE hPage = SH_CreatePropertySheetPage(IDD_SHORTCUT_PROPERTIES, SH_ShellLinkDlgProc, (LPARAM)this, NULL); 3026 if (hPage == NULL) 3027 { 3028 ERR("failed to create property sheet page\n"); 3029 return E_FAIL; 3030 } 3031 3032 if (!pfnAddPage(hPage, lParam)) 3033 return E_FAIL; 3034 3035 return S_OK; 3036 } 3037 3038 HRESULT STDMETHODCALLTYPE CShellLink::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam) 3039 { 3040 TRACE("(%p) (uPageID %u, pfnReplacePage %p lParam %p\n", this, uPageID, pfnReplacePage, lParam); 3041 return E_NOTIMPL; 3042 } 3043 3044 HRESULT STDMETHODCALLTYPE CShellLink::SetSite(IUnknown *punk) 3045 { 3046 TRACE("%p %p\n", this, punk); 3047 3048 m_site = punk; 3049 3050 return S_OK; 3051 } 3052 3053 HRESULT STDMETHODCALLTYPE CShellLink::GetSite(REFIID iid, void ** ppvSite) 3054 { 3055 TRACE("%p %s %p\n", this, debugstr_guid(&iid), ppvSite); 3056 3057 if (m_site == NULL) 3058 return E_FAIL; 3059 3060 return m_site->QueryInterface(iid, ppvSite); 3061 } 3062 3063 HRESULT STDMETHODCALLTYPE CShellLink::DragEnter(IDataObject *pDataObject, 3064 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect) 3065 { 3066 TRACE("(%p)->(DataObject=%p)\n", this, pDataObject); 3067 LPCITEMIDLIST pidlLast; 3068 CComPtr<IShellFolder> psf; 3069 3070 HRESULT hr = SHBindToParent(m_pPidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast); 3071 3072 if (SUCCEEDED(hr)) 3073 { 3074 hr = psf->GetUIObjectOf(0, 1, &pidlLast, IID_NULL_PPV_ARG(IDropTarget, &m_DropTarget)); 3075 3076 if (SUCCEEDED(hr)) 3077 hr = m_DropTarget->DragEnter(pDataObject, dwKeyState, pt, pdwEffect); 3078 else 3079 *pdwEffect = DROPEFFECT_NONE; 3080 } 3081 else 3082 *pdwEffect = DROPEFFECT_NONE; 3083 3084 return S_OK; 3085 } 3086 3087 HRESULT STDMETHODCALLTYPE CShellLink::DragOver(DWORD dwKeyState, POINTL pt, 3088 DWORD *pdwEffect) 3089 { 3090 TRACE("(%p)\n", this); 3091 HRESULT hr = S_OK; 3092 if (m_DropTarget) 3093 hr = m_DropTarget->DragOver(dwKeyState, pt, pdwEffect); 3094 return hr; 3095 } 3096 3097 HRESULT STDMETHODCALLTYPE CShellLink::DragLeave() 3098 { 3099 TRACE("(%p)\n", this); 3100 HRESULT hr = S_OK; 3101 if (m_DropTarget) 3102 { 3103 hr = m_DropTarget->DragLeave(); 3104 m_DropTarget.Release(); 3105 } 3106 3107 return hr; 3108 } 3109 3110 HRESULT STDMETHODCALLTYPE CShellLink::Drop(IDataObject *pDataObject, 3111 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect) 3112 { 3113 TRACE("(%p)\n", this); 3114 HRESULT hr = S_OK; 3115 if (m_DropTarget) 3116 hr = m_DropTarget->Drop(pDataObject, dwKeyState, pt, pdwEffect); 3117 3118 return hr; 3119 } 3120 3121 /************************************************************************** 3122 * IShellLink_ConstructFromFile 3123 */ 3124 HRESULT WINAPI IShellLink_ConstructFromPath(WCHAR *path, REFIID riid, LPVOID *ppv) 3125 { 3126 CComPtr<IPersistFile> ppf; 3127 HRESULT hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IPersistFile, &ppf)); 3128 if (FAILED(hr)) 3129 return hr; 3130 3131 hr = ppf->Load(path, 0); 3132 if (FAILED(hr)) 3133 return hr; 3134 3135 return ppf->QueryInterface(riid, ppv); 3136 } 3137 3138 HRESULT WINAPI IShellLink_ConstructFromFile(IShellFolder * psf, LPCITEMIDLIST pidl, REFIID riid, LPVOID *ppv) 3139 { 3140 WCHAR path[MAX_PATH]; 3141 if (!ILGetDisplayNameExW(psf, pidl, path, 0)) 3142 return E_FAIL; 3143 3144 return IShellLink_ConstructFromPath(path, riid, ppv); 3145 } 3146 3147 HICON CShellLink::CreateShortcutIcon(LPCWSTR wszIconPath, INT IconIndex) 3148 { 3149 const INT cx = GetSystemMetrics(SM_CXICON), cy = GetSystemMetrics(SM_CYICON); 3150 const COLORREF crMask = GetSysColor(COLOR_3DFACE); 3151 HDC hDC; 3152 HIMAGELIST himl = ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, 1, 1); 3153 HICON hIcon = NULL, hNewIcon = NULL; 3154 HICON hShortcut = (HICON)LoadImageW(shell32_hInstance, MAKEINTRESOURCE(IDI_SHELL_SHORTCUT), 3155 IMAGE_ICON, cx, cy, 0); 3156 3157 ::ExtractIconExW(wszIconPath, IconIndex, &hIcon, NULL, 1); 3158 if (!hIcon || !hShortcut || !himl) 3159 goto cleanup; 3160 3161 hDC = CreateCompatibleDC(NULL); 3162 if (hDC) 3163 { 3164 // create 32bpp bitmap 3165 BITMAPINFO bi; 3166 ZeroMemory(&bi, sizeof(bi)); 3167 bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 3168 bi.bmiHeader.biWidth = cx; 3169 bi.bmiHeader.biHeight = cy; 3170 bi.bmiHeader.biPlanes = 1; 3171 bi.bmiHeader.biBitCount = 32; 3172 LPVOID pvBits; 3173 HBITMAP hbm = CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, &pvBits, NULL, 0); 3174 if (hbm) 3175 { 3176 // draw the icon image 3177 HGDIOBJ hbmOld = SelectObject(hDC, hbm); 3178 { 3179 HBRUSH hbr = CreateSolidBrush(crMask); 3180 RECT rc = { 0, 0, cx, cy }; 3181 FillRect(hDC, &rc, hbr); 3182 DeleteObject(hbr); 3183 3184 DrawIconEx(hDC, 0, 0, hIcon, cx, cy, 0, NULL, DI_NORMAL); 3185 DrawIconEx(hDC, 0, 0, hShortcut, cx, cy, 0, NULL, DI_NORMAL); 3186 } 3187 SelectObject(hDC, hbmOld); 3188 3189 INT iAdded = ImageList_AddMasked(himl, hbm, crMask); 3190 hNewIcon = ImageList_GetIcon(himl, iAdded, ILD_NORMAL | ILD_TRANSPARENT); 3191 3192 DeleteObject(hbm); 3193 } 3194 DeleteDC(hDC); 3195 } 3196 3197 cleanup: 3198 if (hIcon) 3199 DestroyIcon(hIcon); 3200 if (hShortcut) 3201 DestroyIcon(hShortcut); 3202 if (himl) 3203 ImageList_Destroy(himl); 3204 3205 return hNewIcon; 3206 } 3207