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