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