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