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