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 LPCITEMIDLIST pidlLast;
1733 CComPtr<IShellFolder> psf;
1734
1735 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast);
1736 if (FAILED_UNEXPECTEDLY(hr))
1737 return hr;
1738
1739 CComPtr<IExtractIconW> pei;
1740 hr = psf->GetUIObjectOf(0, 1, &pidlLast, IID_NULL_PPV_ARG(IExtractIconW, &pei));
1741 if (FAILED_UNEXPECTEDLY(hr))
1742 return hr;
1743
1744 hr = pei->GetIconLocation(uFlags, pszIconFile, cchMax, piIndex, pwFlags);
1745 if (FAILED_UNEXPECTEDLY(hr))
1746 return hr;
1747
1748 return S_OK;
1749 }
1750
GetIconLocation(UINT uFlags,PWSTR pszIconFile,UINT cchMax,int * piIndex,UINT * pwFlags)1751 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(UINT uFlags, PWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags)
1752 {
1753 HRESULT hr;
1754
1755 pszIconFile[0] = UNICODE_NULL;
1756
1757 /*
1758 * It is possible for a shell link to point to another shell link,
1759 * and in particular there is the possibility to point to itself.
1760 * Now, suppose we ask such a link to retrieve its associated icon.
1761 * This function would be called, and due to COM would be called again
1762 * recursively. To solve this issue, we forbid calling GetIconLocation()
1763 * with GIL_FORSHORTCUT set in uFlags, as done by Windows (shown by tests).
1764 */
1765 if (uFlags & GIL_FORSHORTCUT)
1766 return E_INVALIDARG;
1767
1768 /*
1769 * Now, we set GIL_FORSHORTCUT so that: i) we allow the icon extractor
1770 * of the target to give us a suited icon, and ii) we protect ourselves
1771 * against recursive call.
1772 */
1773 uFlags |= GIL_FORSHORTCUT;
1774
1775 if (uFlags & GIL_DEFAULTICON)
1776 return S_FALSE;
1777
1778 hr = GetIconLocation(pszIconFile, cchMax, piIndex);
1779 if (FAILED(hr) || pszIconFile[0] == UNICODE_NULL)
1780 {
1781 hr = SHELL_PidlGetIconLocationW(m_pPidl, uFlags, pszIconFile, cchMax, piIndex, pwFlags);
1782 }
1783 else
1784 {
1785 *pwFlags = GIL_NOTFILENAME | GIL_PERCLASS;
1786 }
1787
1788 return hr;
1789 }
1790
1791 HRESULT STDMETHODCALLTYPE
Extract(PCWSTR pszFile,UINT nIconIndex,HICON * phiconLarge,HICON * phiconSmall,UINT nIconSize)1792 CShellLink::Extract(PCWSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize)
1793 {
1794 HRESULT hr = NOERROR;
1795 UINT cxyLarge = LOWORD(nIconSize), cxySmall = HIWORD(nIconSize);
1796
1797 if (phiconLarge)
1798 {
1799 *phiconLarge = NULL;
1800 PrivateExtractIconsW(pszFile, nIconIndex, cxyLarge, cxyLarge, phiconLarge, NULL, 1, 0);
1801
1802 if (*phiconLarge == NULL)
1803 hr = S_FALSE;
1804 }
1805
1806 if (phiconSmall)
1807 {
1808 *phiconSmall = NULL;
1809 PrivateExtractIconsW(pszFile, nIconIndex, cxySmall, cxySmall, phiconSmall, NULL, 1, 0);
1810
1811 if (*phiconSmall == NULL)
1812 hr = S_FALSE;
1813 }
1814
1815 if (hr == S_FALSE)
1816 {
1817 if (phiconLarge && *phiconLarge)
1818 {
1819 DestroyIcon(*phiconLarge);
1820 *phiconLarge = NULL;
1821 }
1822 if (phiconSmall && *phiconSmall)
1823 {
1824 DestroyIcon(*phiconSmall);
1825 *phiconSmall = NULL;
1826 }
1827 }
1828
1829 return hr;
1830 }
1831
1832 #if 0
1833 /* Extends the functionality of PathUnExpandEnvStringsW */
1834 BOOL PathFullyUnExpandEnvStringsW(
1835 _In_ LPCWSTR pszPath,
1836 _Out_ LPWSTR pszBuf,
1837 _In_ UINT cchBuf)
1838 {
1839 BOOL Ret = FALSE; // Set to TRUE as soon as PathUnExpandEnvStrings starts unexpanding.
1840 BOOL res;
1841 LPCWSTR p;
1842
1843 // *pszBuf = L'\0';
1844 while (*pszPath && cchBuf > 0)
1845 {
1846 /* Attempt unexpanding the path */
1847 res = PathUnExpandEnvStringsW(pszPath, pszBuf, cchBuf);
1848 if (!res)
1849 {
1850 /* The unexpansion failed. Try to find a path delimiter. */
1851 p = wcspbrk(pszPath, L" /\\:*?\"<>|%");
1852 if (!p) /* None found, we will copy the remaining path */
1853 p = pszPath + wcslen(pszPath);
1854 else /* Found one, we will copy the delimiter and skip it */
1855 ++p;
1856 /* If we overflow, we cannot unexpand more, so return FALSE */
1857 if (p - pszPath >= cchBuf)
1858 return FALSE; // *pszBuf = L'\0';
1859
1860 /* Copy the untouched portion of path up to the delimiter, included */
1861 wcsncpy(pszBuf, pszPath, p - pszPath);
1862 pszBuf[p - pszPath] = L'\0'; // NULL-terminate
1863
1864 /* Advance the pointers and decrease the remaining buffer size */
1865 cchBuf -= (p - pszPath);
1866 pszBuf += (p - pszPath);
1867 pszPath += (p - pszPath);
1868 }
1869 else
1870 {
1871 /*
1872 * The unexpansion succeeded. Skip the unexpanded part by trying
1873 * to find where the original path and the unexpanded string
1874 * become different.
1875 * NOTE: An alternative(?) would be to stop also at the last
1876 * path delimiter encountered in the loop (i.e. would be the
1877 * first path delimiter in the strings).
1878 */
1879 LPWSTR q;
1880
1881 /*
1882 * The algorithm starts at the end of the strings and loops back
1883 * while the characters are equal, until it finds a discrepancy.
1884 */
1885 p = pszPath + wcslen(pszPath);
1886 q = pszBuf + wcslen(pszBuf); // This wcslen should be < cchBuf
1887 while ((*p == *q) && (p > pszPath) && (q > pszBuf))
1888 {
1889 --p; --q;
1890 }
1891 /* Skip discrepancy */
1892 ++p; ++q;
1893
1894 /* Advance the pointers and decrease the remaining buffer size */
1895 cchBuf -= (q - pszBuf);
1896 pszBuf = q;
1897 pszPath = p;
1898
1899 Ret = TRUE;
1900 }
1901 }
1902
1903 return Ret;
1904 }
1905 #endif
1906
SetIconLocation(LPCWSTR pszIconPath,INT iIcon)1907 HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT iIcon)
1908 {
1909 HRESULT hr = E_FAIL;
1910 WCHAR szIconPath[MAX_PATH];
1911
1912 TRACE("(%p)->(path=%s iicon=%u)\n", this, debugstr_w(pszIconPath), iIcon);
1913
1914 if (pszIconPath)
1915 {
1916 /*
1917 * Check whether the user-given file path contains unexpanded
1918 * environment variables. If so, create a target environment block.
1919 * Note that in this block we will store the user-given path.
1920 * It will contain the unexpanded environment variables, but
1921 * it can also contain already expanded path that the user does
1922 * not want to see them unexpanded (e.g. so that they always
1923 * refer to the same place even if the would-be corresponding
1924 * environment variable could change).
1925 */
1926 #ifdef ICON_LINK_WINDOWS_COMPAT
1927 /* Try to fully unexpand the icon path */
1928 // if (PathFullyUnExpandEnvStringsW(pszIconPath, szIconPath, _countof(szIconPath)))
1929 BOOL bSuccess = PathUnExpandEnvStringsW(pszIconPath, szIconPath, _countof(szIconPath));
1930 if (bSuccess && wcscmp(pszIconPath, szIconPath) != 0)
1931 #else
1932 /*
1933 * In some situations, described in http://stackoverflow.com/questions/2976489/ishelllinkseticonlocation-translates-my-icon-path-into-program-files-which-i
1934 * the result of PathUnExpandEnvStringsW() could be wrong, and instead
1935 * one would have to store the actual provided icon location path, while
1936 * creating an icon environment block ONLY if that path already contains
1937 * environment variables. This is what the present case is trying to implement.
1938 */
1939 SHExpandEnvironmentStringsW(pszIconPath, szIconPath, _countof(szIconPath));
1940 if (wcscmp(pszIconPath, szIconPath) != 0)
1941 #endif
1942 {
1943 /*
1944 * The user-given file path contains unexpanded environment
1945 * variables, so we need an icon environment block.
1946 */
1947 EXP_SZ_LINK buffer;
1948 LPEXP_SZ_LINK pInfo;
1949
1950 #ifdef ICON_LINK_WINDOWS_COMPAT
1951 /* Make pszIconPath point to the unexpanded path */
1952 LPCWSTR pszOrgIconPath = pszIconPath;
1953 pszIconPath = szIconPath;
1954 #endif
1955 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_ICON_SIG);
1956 if (pInfo)
1957 {
1958 /* Make sure that the size of the structure is valid */
1959 if (pInfo->cbSize != sizeof(*pInfo))
1960 {
1961 ERR("Ooops. This structure is not as expected...\n");
1962
1963 /* Invalid structure, remove it altogether */
1964 m_Header.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ;
1965 RemoveDataBlock(EXP_SZ_ICON_SIG);
1966
1967 /* Reset the pointer and go use the static buffer */
1968 pInfo = NULL;
1969 }
1970 }
1971 if (!pInfo)
1972 {
1973 /* Use the static buffer */
1974 pInfo = &buffer;
1975 buffer.cbSize = sizeof(buffer);
1976 buffer.dwSignature = EXP_SZ_ICON_SIG;
1977 }
1978
1979 lstrcpynW(pInfo->szwTarget, pszIconPath, _countof(pInfo->szwTarget));
1980 WideCharToMultiByte(CP_ACP, 0, pszIconPath, -1,
1981 pInfo->szTarget, _countof(pInfo->szTarget), NULL, NULL);
1982
1983 hr = S_OK;
1984 if (pInfo == &buffer)
1985 hr = AddDataBlock(pInfo);
1986 if (hr == S_OK)
1987 m_Header.dwFlags |= SLDF_HAS_EXP_ICON_SZ;
1988
1989 #ifdef ICON_LINK_WINDOWS_COMPAT
1990 /* Set pszIconPath back to the original one */
1991 pszIconPath = pszOrgIconPath;
1992 #else
1993 /* Now, make pszIconPath point to the expanded path */
1994 pszIconPath = szIconPath;
1995 #endif
1996 }
1997 else
1998 {
1999 /*
2000 * The user-given file path does not contain unexpanded environment
2001 * variables, so we need to remove any icon environment block.
2002 */
2003 m_Header.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ;
2004 RemoveDataBlock(EXP_SZ_ICON_SIG);
2005
2006 /* pszIconPath points to the user path */
2007 }
2008 }
2009
2010 #ifdef ICON_LINK_WINDOWS_COMPAT
2011 /* Store the original icon path location (may contain unexpanded environment strings) */
2012 #endif
2013 if (pszIconPath)
2014 {
2015 m_Header.dwFlags &= ~SLDF_HAS_ICONLOCATION;
2016 HeapFree(GetProcessHeap(), 0, m_sIcoPath);
2017
2018 m_sIcoPath = strdupW(pszIconPath);
2019 if (!m_sIcoPath)
2020 return E_OUTOFMEMORY;
2021
2022 m_Header.dwFlags |= SLDF_HAS_ICONLOCATION;
2023 }
2024
2025 hr = S_OK;
2026
2027 m_Header.nIconIndex = iIcon;
2028 m_bDirty = TRUE;
2029
2030 return hr;
2031 }
2032
SetRelativePath(LPCWSTR pszPathRel,DWORD dwReserved)2033 HRESULT STDMETHODCALLTYPE CShellLink::SetRelativePath(LPCWSTR pszPathRel, DWORD dwReserved)
2034 {
2035 TRACE("(%p)->(path=%s %x)\n", this, debugstr_w(pszPathRel), dwReserved);
2036
2037 HeapFree(GetProcessHeap(), 0, m_sPathRel);
2038 m_sPathRel = NULL;
2039
2040 if (pszPathRel)
2041 {
2042 m_sPathRel = strdupW(pszPathRel);
2043 if (!m_sPathRel)
2044 return E_OUTOFMEMORY;
2045 }
2046 m_bDirty = TRUE;
2047
2048 return ShellLink_UpdatePath(m_sPathRel, m_sPath, m_sWorkDir, &m_sPath);
2049 }
2050
GetAdvertisedArg(LPCWSTR str)2051 static LPWSTR GetAdvertisedArg(LPCWSTR str)
2052 {
2053 if (!str)
2054 return NULL;
2055
2056 LPCWSTR p = wcschr(str, L':');
2057 if (!p)
2058 return NULL;
2059
2060 DWORD len = p - str;
2061 LPWSTR ret = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * (len + 1));
2062 if (!ret)
2063 return ret;
2064
2065 memcpy(ret, str, sizeof(WCHAR)*len);
2066 ret[len] = 0;
2067 return ret;
2068 }
2069
WriteAdvertiseInfo(LPCWSTR string,DWORD dwSig)2070 HRESULT CShellLink::WriteAdvertiseInfo(LPCWSTR string, DWORD dwSig)
2071 {
2072 EXP_DARWIN_LINK buffer;
2073 LPEXP_DARWIN_LINK pInfo;
2074
2075 if ( (dwSig != EXP_DARWIN_ID_SIG)
2076 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2077 && (dwSig != EXP_LOGO3_ID_SIG)
2078 #endif
2079 )
2080 {
2081 return E_INVALIDARG;
2082 }
2083
2084 if (!string)
2085 return S_FALSE;
2086
2087 pInfo = (LPEXP_DARWIN_LINK)SHFindDataBlock(m_pDBList, dwSig);
2088 if (pInfo)
2089 {
2090 /* Make sure that the size of the structure is valid */
2091 if (pInfo->dbh.cbSize != sizeof(*pInfo))
2092 {
2093 ERR("Ooops. This structure is not as expected...\n");
2094
2095 /* Invalid structure, remove it altogether */
2096 if (dwSig == EXP_DARWIN_ID_SIG)
2097 m_Header.dwFlags &= ~SLDF_HAS_DARWINID;
2098 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2099 else if (dwSig == EXP_LOGO3_ID_SIG)
2100 m_Header.dwFlags &= ~SLDF_HAS_LOGO3ID;
2101 #endif
2102 RemoveDataBlock(dwSig);
2103
2104 /* Reset the pointer and go use the static buffer */
2105 pInfo = NULL;
2106 }
2107 }
2108 if (!pInfo)
2109 {
2110 /* Use the static buffer */
2111 pInfo = &buffer;
2112 buffer.dbh.cbSize = sizeof(buffer);
2113 buffer.dbh.dwSignature = dwSig;
2114 }
2115
2116 lstrcpynW(pInfo->szwDarwinID, string, _countof(pInfo->szwDarwinID));
2117 WideCharToMultiByte(CP_ACP, 0, string, -1,
2118 pInfo->szDarwinID, _countof(pInfo->szDarwinID), NULL, NULL);
2119
2120 HRESULT hr = S_OK;
2121 if (pInfo == &buffer)
2122 hr = AddDataBlock(pInfo);
2123 if (hr == S_OK)
2124 {
2125 if (dwSig == EXP_DARWIN_ID_SIG)
2126 m_Header.dwFlags |= SLDF_HAS_DARWINID;
2127 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2128 else if (dwSig == EXP_LOGO3_ID_SIG)
2129 m_Header.dwFlags |= SLDF_HAS_LOGO3ID;
2130 #endif
2131 }
2132
2133 return hr;
2134 }
2135
SetAdvertiseInfo(LPCWSTR str)2136 HRESULT CShellLink::SetAdvertiseInfo(LPCWSTR str)
2137 {
2138 HRESULT hr;
2139 LPCWSTR szComponent = NULL, szProduct = NULL, p;
2140 INT len;
2141 GUID guid;
2142 WCHAR szGuid[38+1];
2143
2144 /**/sProduct = sComponent = NULL;/**/
2145
2146 while (str[0])
2147 {
2148 /* each segment must start with two colons */
2149 if (str[0] != ':' || str[1] != ':')
2150 return E_FAIL;
2151
2152 /* the last segment is just two colons */
2153 if (!str[2])
2154 break;
2155 str += 2;
2156
2157 /* there must be a colon straight after a guid */
2158 p = wcschr(str, L':');
2159 if (!p)
2160 return E_FAIL;
2161 len = p - str;
2162 if (len != 38)
2163 return E_FAIL;
2164
2165 /* get the guid, and check if it's validly formatted */
2166 memcpy(szGuid, str, sizeof(WCHAR)*len);
2167 szGuid[len] = 0;
2168
2169 hr = CLSIDFromString(szGuid, &guid);
2170 if (hr != S_OK)
2171 return hr;
2172 str = p + 1;
2173
2174 /* match it up to a guid that we care about */
2175 if (IsEqualGUID(guid, SHELL32_AdvtShortcutComponent) && !szComponent)
2176 szComponent = str; /* Darwin */
2177 else if (IsEqualGUID(guid, SHELL32_AdvtShortcutProduct) && !szProduct)
2178 szProduct = str; /* Logo3 */
2179 else
2180 return E_FAIL;
2181
2182 /* skip to the next field */
2183 str = wcschr(str, L':');
2184 if (!str)
2185 return E_FAIL;
2186 }
2187
2188 /* we have to have a component for an advertised shortcut */
2189 if (!szComponent)
2190 return E_FAIL;
2191
2192 szComponent = GetAdvertisedArg(szComponent);
2193 szProduct = GetAdvertisedArg(szProduct);
2194
2195 hr = WriteAdvertiseInfo(szComponent, EXP_DARWIN_ID_SIG);
2196 // if (FAILED(hr))
2197 // return hr;
2198 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2199 hr = WriteAdvertiseInfo(szProduct, EXP_LOGO3_ID_SIG);
2200 // if (FAILED(hr))
2201 // return hr;
2202 #endif
2203
2204 HeapFree(GetProcessHeap(), 0, (PVOID)szComponent);
2205 HeapFree(GetProcessHeap(), 0, (PVOID)szProduct);
2206
2207 if (TRACE_ON(shell))
2208 {
2209 GetAdvertiseInfo(&sComponent, EXP_DARWIN_ID_SIG);
2210 TRACE("Component = %s\n", debugstr_w(sComponent));
2211 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2212 GetAdvertiseInfo(&sProduct, EXP_LOGO3_ID_SIG);
2213 TRACE("Product = %s\n", debugstr_w(sProduct));
2214 #endif
2215 }
2216
2217 return S_OK;
2218 }
2219
SetTargetFromPIDLOrPath(LPCITEMIDLIST pidl,LPCWSTR pszFile)2220 HRESULT CShellLink::SetTargetFromPIDLOrPath(LPCITEMIDLIST pidl, LPCWSTR pszFile)
2221 {
2222 HRESULT hr = S_OK;
2223 LPITEMIDLIST pidlNew = NULL;
2224 WCHAR szPath[MAX_PATH];
2225
2226 /*
2227 * Not both 'pidl' and 'pszFile' should be set.
2228 * But either one or both can be NULL.
2229 */
2230 if (pidl && pszFile)
2231 return E_FAIL;
2232
2233 if (pidl)
2234 {
2235 /* Clone the PIDL */
2236 pidlNew = ILClone(pidl);
2237 if (!pidlNew)
2238 return E_FAIL;
2239 }
2240 else if (pszFile)
2241 {
2242 /* Build a PIDL for this path target */
2243 hr = SHILCreateFromPathW(pszFile, &pidlNew, NULL);
2244 if (FAILED(hr))
2245 {
2246 /* This failed, try to resolve the path, then create a simple PIDL */
2247
2248 StringCchCopyW(szPath, _countof(szPath), pszFile);
2249 PathResolveW(szPath, NULL, PRF_TRYPROGRAMEXTENSIONS);
2250
2251 if (PathIsFileSpecW(szPath))
2252 {
2253 hr = E_INVALIDARG;
2254 szPath[0] = 0;
2255 }
2256 else
2257 {
2258 hr = S_OK;
2259 pidlNew = SHSimpleIDListFromPathW(szPath);
2260 // NOTE: Don't make it failed here even if pidlNew was NULL.
2261 // We don't fail on purpose even if SHSimpleIDListFromPathW returns NULL.
2262 // This behaviour has been verified with tests.
2263 }
2264 }
2265 }
2266 // else if (!pidl && !pszFile) { pidlNew = NULL; hr = S_OK; }
2267
2268 ILFree(m_pPidl);
2269 m_pPidl = pidlNew;
2270
2271 if (!pszFile)
2272 {
2273 if (SHGetPathFromIDListW(pidlNew, szPath))
2274 pszFile = szPath;
2275 }
2276
2277 // TODO: Fully update link info, tracker, file attribs...
2278
2279 // if (pszFile)
2280 if (!pszFile)
2281 {
2282 *szPath = L'\0';
2283 pszFile = szPath;
2284 }
2285
2286 /* Update the cached path (for link info) */
2287 ShellLink_GetVolumeInfo(pszFile, &volume);
2288
2289 if (m_sPath)
2290 HeapFree(GetProcessHeap(), 0, m_sPath);
2291
2292 m_sPath = strdupW(pszFile);
2293 if (!m_sPath)
2294 return E_OUTOFMEMORY;
2295
2296 m_bDirty = TRUE;
2297 return hr;
2298 }
2299
SetPath(LPCWSTR pszFile)2300 HRESULT STDMETHODCALLTYPE CShellLink::SetPath(LPCWSTR pszFile)
2301 {
2302 LPWSTR unquoted = NULL;
2303 HRESULT hr = S_OK;
2304
2305 TRACE("(%p)->(path=%s)\n", this, debugstr_w(pszFile));
2306
2307 if (!pszFile)
2308 return E_INVALIDARG;
2309
2310 /*
2311 * Allow upgrading Logo3 shortcuts (m_Header.dwFlags & SLDF_HAS_LOGO3ID),
2312 * but forbid upgrading Darwin ones.
2313 */
2314 if (m_Header.dwFlags & SLDF_HAS_DARWINID)
2315 return S_FALSE;
2316
2317 /* quotes at the ends of the string are stripped */
2318 SIZE_T len = wcslen(pszFile);
2319 if (pszFile[0] == L'"' && pszFile[len-1] == L'"')
2320 {
2321 unquoted = strdupW(pszFile);
2322 PathUnquoteSpacesW(unquoted);
2323 pszFile = unquoted;
2324 }
2325
2326 /* any other quote marks are invalid */
2327 if (wcschr(pszFile, L'"'))
2328 {
2329 hr = S_FALSE;
2330 goto end;
2331 }
2332
2333 /* Clear the cached path */
2334 HeapFree(GetProcessHeap(), 0, m_sPath);
2335 m_sPath = NULL;
2336
2337 /* Check for an advertised target (Logo3 or Darwin) */
2338 if (SetAdvertiseInfo(pszFile) != S_OK)
2339 {
2340 /* This is not an advertised target, but a regular path */
2341 WCHAR szPath[MAX_PATH];
2342
2343 /*
2344 * Check whether the user-given file path contains unexpanded
2345 * environment variables. If so, create a target environment block.
2346 * Note that in this block we will store the user-given path.
2347 * It will contain the unexpanded environment variables, but
2348 * it can also contain already expanded path that the user does
2349 * not want to see them unexpanded (e.g. so that they always
2350 * refer to the same place even if the would-be corresponding
2351 * environment variable could change).
2352 */
2353 if (*pszFile)
2354 SHExpandEnvironmentStringsW(pszFile, szPath, _countof(szPath));
2355 else
2356 *szPath = L'\0';
2357
2358 if (*pszFile && (wcscmp(pszFile, szPath) != 0))
2359 {
2360 /*
2361 * The user-given file path contains unexpanded environment
2362 * variables, so we need a target environment block.
2363 */
2364 EXP_SZ_LINK buffer;
2365 LPEXP_SZ_LINK pInfo;
2366
2367 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_LINK_SIG);
2368 if (pInfo)
2369 {
2370 /* Make sure that the size of the structure is valid */
2371 if (pInfo->cbSize != sizeof(*pInfo))
2372 {
2373 ERR("Ooops. This structure is not as expected...\n");
2374
2375 /* Invalid structure, remove it altogether */
2376 m_Header.dwFlags &= ~SLDF_HAS_EXP_SZ;
2377 RemoveDataBlock(EXP_SZ_LINK_SIG);
2378
2379 /* Reset the pointer and go use the static buffer */
2380 pInfo = NULL;
2381 }
2382 }
2383 if (!pInfo)
2384 {
2385 /* Use the static buffer */
2386 pInfo = &buffer;
2387 buffer.cbSize = sizeof(buffer);
2388 buffer.dwSignature = EXP_SZ_LINK_SIG;
2389 }
2390
2391 lstrcpynW(pInfo->szwTarget, pszFile, _countof(pInfo->szwTarget));
2392 WideCharToMultiByte(CP_ACP, 0, pszFile, -1,
2393 pInfo->szTarget, _countof(pInfo->szTarget), NULL, NULL);
2394
2395 hr = S_OK;
2396 if (pInfo == &buffer)
2397 hr = AddDataBlock(pInfo);
2398 if (hr == S_OK)
2399 m_Header.dwFlags |= SLDF_HAS_EXP_SZ;
2400
2401 /* Now, make pszFile point to the expanded path */
2402 pszFile = szPath;
2403 }
2404 else
2405 {
2406 /*
2407 * The user-given file path does not contain unexpanded environment
2408 * variables, so we need to remove any target environment block.
2409 */
2410 m_Header.dwFlags &= ~SLDF_HAS_EXP_SZ;
2411 RemoveDataBlock(EXP_SZ_LINK_SIG);
2412
2413 /* pszFile points to the user path */
2414 }
2415
2416 /* Set the target */
2417 hr = SetTargetFromPIDLOrPath(NULL, pszFile);
2418 }
2419
2420 m_bDirty = TRUE;
2421
2422 end:
2423 HeapFree(GetProcessHeap(), 0, unquoted);
2424 return hr;
2425 }
2426
AddDataBlock(void * pDataBlock)2427 HRESULT STDMETHODCALLTYPE CShellLink::AddDataBlock(void* pDataBlock)
2428 {
2429 if (SHAddDataBlock(&m_pDBList, (DATABLOCK_HEADER*)pDataBlock))
2430 {
2431 m_bDirty = TRUE;
2432 return S_OK;
2433 }
2434 return S_FALSE;
2435 }
2436
CopyDataBlock(DWORD dwSig,void ** ppDataBlock)2437 HRESULT STDMETHODCALLTYPE CShellLink::CopyDataBlock(DWORD dwSig, void** ppDataBlock)
2438 {
2439 DATABLOCK_HEADER* pBlock;
2440 PVOID pDataBlock;
2441
2442 TRACE("%p %08x %p\n", this, dwSig, ppDataBlock);
2443
2444 *ppDataBlock = NULL;
2445
2446 pBlock = SHFindDataBlock(m_pDBList, dwSig);
2447 if (!pBlock)
2448 {
2449 ERR("unknown datablock %08x (not found)\n", dwSig);
2450 return E_FAIL;
2451 }
2452
2453 pDataBlock = LocalAlloc(LMEM_ZEROINIT, pBlock->cbSize);
2454 if (!pDataBlock)
2455 return E_OUTOFMEMORY;
2456
2457 CopyMemory(pDataBlock, pBlock, pBlock->cbSize);
2458
2459 *ppDataBlock = pDataBlock;
2460 return S_OK;
2461 }
2462
RemoveDataBlock(DWORD dwSig)2463 HRESULT STDMETHODCALLTYPE CShellLink::RemoveDataBlock(DWORD dwSig)
2464 {
2465 if (SHRemoveDataBlock(&m_pDBList, dwSig))
2466 {
2467 m_bDirty = TRUE;
2468 return S_OK;
2469 }
2470 return S_FALSE;
2471 }
2472
GetFlags(DWORD * pdwFlags)2473 HRESULT STDMETHODCALLTYPE CShellLink::GetFlags(DWORD *pdwFlags)
2474 {
2475 TRACE("%p %p\n", this, pdwFlags);
2476 *pdwFlags = m_Header.dwFlags;
2477 return S_OK;
2478 }
2479
SetFlags(DWORD dwFlags)2480 HRESULT STDMETHODCALLTYPE CShellLink::SetFlags(DWORD dwFlags)
2481 {
2482 #if 0 // FIXME!
2483 m_Header.dwFlags = dwFlags;
2484 m_bDirty = TRUE;
2485 return S_OK;
2486 #else
2487 FIXME("\n");
2488 return E_NOTIMPL;
2489 #endif
2490 }
2491
2492 /**************************************************************************
2493 * CShellLink implementation of IShellExtInit::Initialize()
2494 *
2495 * Loads the shelllink from the dataobject the shell is pointing to.
2496 */
Initialize(PCIDLIST_ABSOLUTE pidlFolder,IDataObject * pdtobj,HKEY hkeyProgID)2497 HRESULT STDMETHODCALLTYPE CShellLink::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
2498 {
2499 TRACE("%p %p %p %p\n", this, pidlFolder, pdtobj, hkeyProgID);
2500
2501 if (!pdtobj)
2502 return E_FAIL;
2503
2504 FORMATETC format;
2505 format.cfFormat = CF_HDROP;
2506 format.ptd = NULL;
2507 format.dwAspect = DVASPECT_CONTENT;
2508 format.lindex = -1;
2509 format.tymed = TYMED_HGLOBAL;
2510
2511 STGMEDIUM stgm;
2512 HRESULT hr = pdtobj->GetData(&format, &stgm);
2513 if (FAILED(hr))
2514 return hr;
2515
2516 UINT count = DragQueryFileW((HDROP)stgm.hGlobal, -1, NULL, 0);
2517 if (count == 1)
2518 {
2519 count = DragQueryFileW((HDROP)stgm.hGlobal, 0, NULL, 0);
2520 count++;
2521 LPWSTR path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, count * sizeof(WCHAR));
2522 if (path)
2523 {
2524 count = DragQueryFileW((HDROP)stgm.hGlobal, 0, path, count);
2525 hr = Load(path, 0);
2526 HeapFree(GetProcessHeap(), 0, path);
2527 }
2528 }
2529 ReleaseStgMedium(&stgm);
2530
2531 return S_OK;
2532 }
2533
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)2534 HRESULT STDMETHODCALLTYPE CShellLink::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
2535 {
2536 INT id = 0;
2537
2538 m_idCmdFirst = idCmdFirst;
2539
2540 TRACE("%p %p %u %u %u %u\n", this,
2541 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
2542
2543 if (!hMenu)
2544 return E_INVALIDARG;
2545
2546 CStringW strOpen(MAKEINTRESOURCEW(IDS_OPEN_VERB));
2547 CStringW strOpenFileLoc(MAKEINTRESOURCEW(IDS_OPENFILELOCATION));
2548
2549 MENUITEMINFOW mii;
2550 ZeroMemory(&mii, sizeof(mii));
2551 mii.cbSize = sizeof(mii);
2552 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE;
2553 mii.dwTypeData = strOpen.GetBuffer();
2554 mii.cch = wcslen(mii.dwTypeData);
2555 mii.wID = idCmdFirst + id++;
2556 mii.fState = MFS_DEFAULT | MFS_ENABLED;
2557 mii.fType = MFT_STRING;
2558 if (!InsertMenuItemW(hMenu, indexMenu++, TRUE, &mii))
2559 return E_FAIL;
2560
2561 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE;
2562 mii.dwTypeData = strOpenFileLoc.GetBuffer();
2563 mii.cch = wcslen(mii.dwTypeData);
2564 mii.wID = idCmdFirst + id++;
2565 mii.fState = MFS_ENABLED;
2566 mii.fType = MFT_STRING;
2567 if (!InsertMenuItemW(hMenu, indexMenu++, TRUE, &mii))
2568 return E_FAIL;
2569
2570 UNREFERENCED_PARAMETER(indexMenu);
2571
2572 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, id);
2573 }
2574
DoOpenFileLocation()2575 HRESULT CShellLink::DoOpenFileLocation()
2576 {
2577 WCHAR szParams[MAX_PATH + 64];
2578 StringCbPrintfW(szParams, sizeof(szParams), L"/select,%s", m_sPath);
2579
2580 INT_PTR ret;
2581 ret = reinterpret_cast<INT_PTR>(ShellExecuteW(NULL, NULL, L"explorer.exe", szParams,
2582 NULL, m_Header.nShowCommand));
2583 if (ret <= 32)
2584 {
2585 ERR("ret: %08lX\n", ret);
2586 return E_FAIL;
2587 }
2588
2589 return S_OK;
2590 }
2591
InvokeCommand(LPCMINVOKECOMMANDINFO lpici)2592 HRESULT STDMETHODCALLTYPE CShellLink::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
2593 {
2594 TRACE("%p %p\n", this, lpici);
2595
2596 if (lpici->cbSize < sizeof(CMINVOKECOMMANDINFO))
2597 return E_INVALIDARG;
2598
2599 // NOTE: We could use lpici->hwnd (certainly in case lpici->fMask doesn't contain CMIC_MASK_FLAG_NO_UI)
2600 // as the parent window handle... ?
2601 /* FIXME: get using interface set from IObjectWithSite?? */
2602 // NOTE: We might need an extended version of Resolve that provides us with paths...
2603 HRESULT hr = Resolve(lpici->hwnd, (lpici->fMask & CMIC_MASK_FLAG_NO_UI) ? SLR_NO_UI : 0);
2604 if (FAILED(hr))
2605 {
2606 TRACE("failed to resolve component error 0x%08x\n", hr);
2607 return hr;
2608 }
2609
2610 UINT idCmd = LOWORD(lpici->lpVerb);
2611 TRACE("idCmd: %d\n", idCmd);
2612
2613 switch (idCmd)
2614 {
2615 case IDCMD_OPEN:
2616 return DoOpen(lpici);
2617 case IDCMD_OPENFILELOCATION:
2618 return DoOpenFileLocation();
2619 default:
2620 return E_NOTIMPL;
2621 }
2622 }
2623
DoOpen(LPCMINVOKECOMMANDINFO lpici)2624 HRESULT CShellLink::DoOpen(LPCMINVOKECOMMANDINFO lpici)
2625 {
2626 LPCMINVOKECOMMANDINFOEX iciex = (LPCMINVOKECOMMANDINFOEX)lpici;
2627 const BOOL unicode = IsUnicode(*lpici);
2628
2629 CStringW args;
2630 if (m_sArgs)
2631 args = m_sArgs;
2632
2633 if (unicode)
2634 {
2635 if (!StrIsNullOrEmpty(iciex->lpParametersW))
2636 {
2637 args += L' ';
2638 args += iciex->lpParametersW;
2639 }
2640 }
2641 else
2642 {
2643 CComHeapPtr<WCHAR> pszParams;
2644 if (!StrIsNullOrEmpty(lpici->lpParameters) && __SHCloneStrAtoW(&pszParams, lpici->lpParameters))
2645 {
2646 args += L' ';
2647 args += pszParams;
2648 }
2649 }
2650
2651 WCHAR dir[MAX_PATH];
2652 SHELLEXECUTEINFOW sei = { sizeof(sei) };
2653 sei.fMask = SEE_MASK_HASLINKNAME | SEE_MASK_UNICODE | SEE_MASK_DOENVSUBST |
2654 (lpici->fMask & (SEE_MASK_NOASYNC | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI));
2655 sei.lpDirectory = m_sWorkDir;
2656 if (m_pPidl)
2657 {
2658 sei.lpIDList = m_pPidl;
2659 sei.fMask |= SEE_MASK_INVOKEIDLIST;
2660 }
2661 else
2662 {
2663 sei.lpFile = m_sPath;
2664 if (!(m_Header.dwFlags & SLDF_HAS_EXP_SZ))
2665 {
2666 sei.fMask &= ~SEE_MASK_DOENVSUBST; // The link does not want to expand lpFile
2667 if (m_sWorkDir && ExpandEnvironmentStringsW(m_sWorkDir, dir, _countof(dir)) <= _countof(dir))
2668 sei.lpDirectory = dir;
2669 }
2670 }
2671 sei.lpParameters = args;
2672 sei.lpClass = m_sLinkPath;
2673 sei.nShow = m_Header.nShowCommand;
2674 if (lpici->nShow != SW_SHOWNORMAL && lpici->nShow != SW_SHOW)
2675 sei.nShow = lpici->nShow; // Allow invoker to override .lnk show mode
2676
2677 // Use the invoker specified working directory if the link did not specify one
2678 if (StrIsNullOrEmpty(sei.lpDirectory) || !PathIsDirectoryW(sei.lpDirectory))
2679 {
2680 LPCSTR pszDirA = lpici->lpDirectory;
2681 if (unicode && !StrIsNullOrEmpty(iciex->lpDirectoryW))
2682 sei.lpDirectory = iciex->lpDirectoryW;
2683 else if (pszDirA && SHAnsiToUnicode(pszDirA, dir, _countof(dir)))
2684 sei.lpDirectory = dir;
2685 }
2686 return (ShellExecuteExW(&sei) ? S_OK : E_FAIL);
2687 }
2688
GetCommandString(UINT_PTR idCmd,UINT uType,UINT * pwReserved,LPSTR pszName,UINT cchMax)2689 HRESULT STDMETHODCALLTYPE CShellLink::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pwReserved, LPSTR pszName, UINT cchMax)
2690 {
2691 FIXME("%p %lu %u %p %p %u\n", this, idCmd, uType, pwReserved, pszName, cchMax);
2692 return E_NOTIMPL;
2693 }
2694
ExtendedShortcutProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)2695 INT_PTR CALLBACK ExtendedShortcutProc(HWND hwndDlg, UINT uMsg,
2696 WPARAM wParam, LPARAM lParam)
2697 {
2698 switch(uMsg)
2699 {
2700 case WM_INITDIALOG:
2701 if (lParam)
2702 {
2703 HWND hDlgCtrl = GetDlgItem(hwndDlg, IDC_SHORTEX_RUN_DIFFERENT);
2704 SendMessage(hDlgCtrl, BM_SETCHECK, BST_CHECKED, 0);
2705 }
2706 return TRUE;
2707 case WM_COMMAND:
2708 {
2709 HWND hDlgCtrl = GetDlgItem(hwndDlg, IDC_SHORTEX_RUN_DIFFERENT);
2710 if (LOWORD(wParam) == IDOK)
2711 {
2712 if (SendMessage(hDlgCtrl, BM_GETCHECK, 0, 0) == BST_CHECKED)
2713 EndDialog(hwndDlg, 1);
2714 else
2715 EndDialog(hwndDlg, 0);
2716 }
2717 else if (LOWORD(wParam) == IDCANCEL)
2718 {
2719 EndDialog(hwndDlg, -1);
2720 }
2721 else if (LOWORD(wParam) == IDC_SHORTEX_RUN_DIFFERENT)
2722 {
2723 if (SendMessage(hDlgCtrl, BM_GETCHECK, 0, 0) == BST_CHECKED)
2724 SendMessage(hDlgCtrl, BM_SETCHECK, BST_UNCHECKED, 0);
2725 else
2726 SendMessage(hDlgCtrl, BM_SETCHECK, BST_CHECKED, 0);
2727 }
2728 }
2729 }
2730 return FALSE;
2731 }
2732
GetTypeDescriptionByPath(PCWSTR pszFullPath,DWORD fAttributes,PWSTR szBuf,UINT cchBuf)2733 static void GetTypeDescriptionByPath(PCWSTR pszFullPath, DWORD fAttributes, PWSTR szBuf, UINT cchBuf)
2734 {
2735 if (fAttributes == INVALID_FILE_ATTRIBUTES && !PathFileExistsAndAttributesW(pszFullPath, &fAttributes))
2736 fAttributes = 0;
2737
2738 SHFILEINFOW fi;
2739 if (!SHGetFileInfoW(pszFullPath, fAttributes, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES))
2740 {
2741 ERR("SHGetFileInfoW failed for %ls (%lu)\n", pszFullPath, GetLastError());
2742 fi.szTypeName[0] = UNICODE_NULL;
2743 }
2744
2745 BOOL fFolder = (fAttributes & FILE_ATTRIBUTE_DIRECTORY);
2746 LPCWSTR pwszExt = fFolder ? L"" : PathFindExtensionW(pszFullPath);
2747 if (pwszExt[0])
2748 {
2749 if (!fi.szTypeName[0])
2750 StringCchPrintfW(szBuf, cchBuf, L"%s", pwszExt + 1);
2751 else
2752 StringCchPrintfW(szBuf, cchBuf, L"%s (%s)", fi.szTypeName, pwszExt);
2753 }
2754 else
2755 {
2756 StringCchPrintfW(szBuf, cchBuf, L"%s", fi.szTypeName);
2757 }
2758 }
2759
OnInitDialog(HWND hwndDlg,HWND hwndFocus,LPARAM lParam)2760 BOOL CShellLink::OnInitDialog(HWND hwndDlg, HWND hwndFocus, LPARAM lParam)
2761 {
2762 TRACE("CShellLink::OnInitDialog(hwnd %p hwndFocus %p lParam %p)\n", hwndDlg, hwndFocus, lParam);
2763
2764 Resolve(0, SLR_NO_UI | SLR_NOUPDATE | SLR_NOSEARCH | SLR_NOTRACK);
2765
2766 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,
2767 m_sIcoPath, m_sPath, m_sPathRel, sProduct, m_sWorkDir);
2768
2769 m_bInInit = TRUE;
2770 UINT darwin = m_Header.dwFlags & (SLDF_HAS_DARWINID);
2771
2772 /* Get file information */
2773 // FIXME! FIXME! Shouldn't we use m_sIcoPath, m_Header.nIconIndex instead???
2774 SHFILEINFOW fi;
2775 if (!SHGetFileInfoW(m_sLinkPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_ICON))
2776 {
2777 ERR("SHGetFileInfoW failed for %ls (%lu)\n", m_sLinkPath, GetLastError());
2778 fi.szTypeName[0] = L'\0';
2779 fi.hIcon = NULL;
2780 }
2781
2782 if (fi.hIcon)
2783 {
2784 if (m_hIcon)
2785 DestroyIcon(m_hIcon);
2786 m_hIcon = fi.hIcon;
2787 SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)m_hIcon, 0);
2788 }
2789 else
2790 ERR("ExtractIconW failed %ls %u\n", m_sIcoPath, m_Header.nIconIndex);
2791
2792 if (!SHGetFileInfoW(m_sLinkPath, 0, &fi, sizeof(fi), SHGFI_DISPLAYNAME))
2793 fi.szDisplayName[0] = UNICODE_NULL;
2794 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TEXT, fi.szDisplayName);
2795
2796 /* Target type */
2797 if (m_sPath)
2798 {
2799 WCHAR buf[MAX_PATH];
2800 GetTypeDescriptionByPath(m_sPath, m_Header.dwFileAttributes, buf, _countof(buf));
2801 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TYPE_EDIT, buf);
2802 }
2803
2804 /* Target location */
2805 if (m_sPath)
2806 {
2807 WCHAR target[MAX_PATH];
2808 StringCchCopyW(target, _countof(target), m_sPath);
2809 PathRemoveFileSpecW(target);
2810 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION_EDIT, PathFindFileNameW(target));
2811 }
2812
2813 /* Target path */
2814 if (m_sPath)
2815 {
2816 WCHAR newpath[MAX_PATH * 2];
2817 newpath[0] = UNICODE_NULL;
2818 if (wcschr(m_sPath, ' '))
2819 StringCchPrintfExW(newpath, _countof(newpath), NULL, NULL, 0, L"\"%ls\"", m_sPath);
2820 else
2821 StringCchCopyExW(newpath, _countof(newpath), m_sPath, NULL, NULL, 0);
2822
2823 if (m_sArgs && m_sArgs[0])
2824 {
2825 StringCchCatW(newpath, _countof(newpath), L" ");
2826 StringCchCatW(newpath, _countof(newpath), m_sArgs);
2827 }
2828 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, newpath);
2829 }
2830
2831 /* Working dir */
2832 if (m_sWorkDir)
2833 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, m_sWorkDir);
2834
2835 /* Description */
2836 if (m_sDescription)
2837 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_COMMENT_EDIT, m_sDescription);
2838
2839 /* Hot key */
2840 SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_KEY_HOTKEY, HKM_SETHOTKEY, m_Header.wHotKey, 0);
2841
2842 /* Run */
2843 const WORD runstrings[] = { IDS_SHORTCUT_RUN_NORMAL, IDS_SHORTCUT_RUN_MIN, IDS_SHORTCUT_RUN_MAX };
2844 const DWORD runshowcmd[] = { SW_SHOWNORMAL, SW_SHOWMINNOACTIVE, SW_SHOWMAXIMIZED };
2845 HWND hRunCombo = GetDlgItem(hwndDlg, IDC_SHORTCUT_RUN_COMBO);
2846 for (UINT i = 0; i < _countof(runstrings); ++i)
2847 {
2848 WCHAR buf[MAX_PATH];
2849 if (!LoadStringW(shell32_hInstance, runstrings[i], buf, _countof(buf)))
2850 break;
2851
2852 int index = SendMessageW(hRunCombo, CB_ADDSTRING, 0, (LPARAM)buf);
2853 if (index < 0)
2854 continue;
2855 SendMessageW(hRunCombo, CB_SETITEMDATA, index, runshowcmd[i]);
2856 if (!i || m_Header.nShowCommand == runshowcmd[i])
2857 SendMessageW(hRunCombo, CB_SETCURSEL, index, 0);
2858 }
2859
2860 BOOL disablecontrols = FALSE;
2861 if (darwin)
2862 {
2863 disablecontrols = TRUE;
2864 EnableWindow(GetDlgItem(hwndDlg, IDC_SHORTCUT_FIND), FALSE);
2865 EnableWindow(GetDlgItem(hwndDlg, IDC_SHORTCUT_CHANGE_ICON), FALSE);
2866 }
2867 else
2868 {
2869 WCHAR path[MAX_PATH * 2];
2870 path[0] = UNICODE_NULL;
2871 HRESULT hr = GetPath(path, _countof(path), NULL, SLGP_RAWPATH);
2872 if (FAILED(hr))
2873 hr = GetPath(path, _countof(path), NULL, 0);
2874 #if DBG
2875 if (GetKeyState(VK_CONTROL) < 0) // Allow inspection of PIDL parsing path
2876 {
2877 hr = S_OK;
2878 path[0] = UNICODE_NULL;
2879 }
2880 #endif
2881 if (hr != S_OK)
2882 {
2883 disablecontrols = TRUE;
2884 LPITEMIDLIST pidl;
2885 if (GetIDList(&pidl) == S_OK)
2886 {
2887 path[0] = UNICODE_NULL;
2888 SHGetNameAndFlagsW(pidl, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, path, _countof(path), NULL);
2889 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TYPE_EDIT, path);
2890 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, path);
2891 ILRemoveLastID(pidl);
2892 path[0] = UNICODE_NULL;
2893 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, path);
2894 SHGetNameAndFlagsW(pidl, SHGDN_NORMAL, path, _countof(path), NULL);
2895 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION_EDIT, path);
2896 ILFree(pidl);
2897 }
2898 EnableWindow(GetDlgItem(hwndDlg, IDC_SHORTCUT_ADVANCED), FALSE);
2899 EnableWindow(GetDlgItem(hwndDlg, IDC_SHORTCUT_START_IN_EDIT), FALSE);
2900 }
2901 else
2902 {
2903 ASSERT(FAILED(hr) || !(path[0] == ':' && path[1] == ':' && path[2] == '{'));
2904 }
2905 }
2906 EnableWindow(GetDlgItem(hwndDlg, IDC_SHORTCUT_TARGET_TEXT), !disablecontrols);
2907
2908 /* auto-completion */
2909 SHAutoComplete(GetDlgItem(hwndDlg, IDC_SHORTCUT_TARGET_TEXT), SHACF_DEFAULT);
2910 SHAutoComplete(GetDlgItem(hwndDlg, IDC_SHORTCUT_START_IN_EDIT), SHACF_DEFAULT);
2911
2912 m_bInInit = FALSE;
2913
2914 return TRUE;
2915 }
2916
OnCommand(HWND hwndDlg,int id,HWND hwndCtl,UINT codeNotify)2917 void CShellLink::OnCommand(HWND hwndDlg, int id, HWND hwndCtl, UINT codeNotify)
2918 {
2919 switch (id)
2920 {
2921 case IDC_SHORTCUT_FIND:
2922 SHOpenFolderAndSelectItems(m_pPidl, 0, NULL, 0);
2923 return;
2924
2925 case IDC_SHORTCUT_CHANGE_ICON:
2926 {
2927 WCHAR wszPath[MAX_PATH] = L"";
2928
2929 if (m_sIcoPath)
2930 wcscpy(wszPath, m_sIcoPath);
2931 else
2932 FindExecutableW(m_sPath, NULL, wszPath);
2933
2934 INT IconIndex = m_Header.nIconIndex;
2935 if (PickIconDlg(hwndDlg, wszPath, _countof(wszPath), &IconIndex))
2936 {
2937 SetIconLocation(wszPath, IconIndex);
2938 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
2939
2940 HICON hIconLarge = CreateShortcutIcon(wszPath, IconIndex);
2941 if (hIconLarge)
2942 {
2943 if (m_hIcon)
2944 DestroyIcon(m_hIcon);
2945 m_hIcon = hIconLarge;
2946 SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)m_hIcon, 0);
2947 }
2948 }
2949 return;
2950 }
2951
2952 case IDC_SHORTCUT_ADVANCED:
2953 {
2954 INT_PTR result = DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_SHORTCUT_EXTENDED_PROPERTIES), hwndDlg, ExtendedShortcutProc, (LPARAM)m_bRunAs);
2955 if (result == 1 || result == 0)
2956 {
2957 if (m_bRunAs != result)
2958 {
2959 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
2960 }
2961
2962 m_bRunAs = result;
2963 }
2964 return;
2965 }
2966 }
2967 if (codeNotify == EN_CHANGE || codeNotify == CBN_SELCHANGE)
2968 {
2969 if (!m_bInInit)
2970 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
2971 }
2972 }
2973
OnNotify(HWND hwndDlg,int idFrom,LPNMHDR pnmhdr)2974 LRESULT CShellLink::OnNotify(HWND hwndDlg, int idFrom, LPNMHDR pnmhdr)
2975 {
2976 WCHAR wszBuf[MAX_PATH];
2977 LPPSHNOTIFY lppsn = (LPPSHNOTIFY)pnmhdr;
2978
2979 if (lppsn->hdr.code == PSN_APPLY)
2980 {
2981 /* set working directory */
2982 GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, wszBuf, _countof(wszBuf));
2983 SetWorkingDirectory(wszBuf);
2984
2985 /* set link destination */
2986 GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, wszBuf, _countof(wszBuf));
2987 LPWSTR lpszArgs = NULL;
2988 LPWSTR unquoted = strdupW(wszBuf);
2989 StrTrimW(unquoted, L" ");
2990
2991 if (!PathFileExistsW(unquoted))
2992 {
2993 lpszArgs = PathGetArgsW(unquoted);
2994 PathRemoveArgsW(unquoted);
2995 StrTrimW(lpszArgs, L" ");
2996 }
2997 if (unquoted[0] == '"' && unquoted[wcslen(unquoted) - 1] == '"')
2998 PathUnquoteSpacesW(unquoted);
2999
3000 WCHAR *pwszExt = PathFindExtensionW(unquoted);
3001 if (!_wcsicmp(pwszExt, L".lnk"))
3002 {
3003 // FIXME load localized error msg
3004 MessageBoxW(hwndDlg, L"You cannot create a link to a shortcut", L"Error", MB_ICONERROR);
3005 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
3006 return TRUE;
3007 }
3008
3009 if (!PathFileExistsW(unquoted))
3010 {
3011 // FIXME load localized error msg
3012 MessageBoxW(hwndDlg, L"The specified file name in the target box is invalid", L"Error", MB_ICONERROR);
3013 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
3014 return TRUE;
3015 }
3016
3017 SetPath(unquoted);
3018 if (lpszArgs)
3019 SetArguments(lpszArgs);
3020 else
3021 SetArguments(L"\0");
3022
3023 HeapFree(GetProcessHeap(), 0, unquoted);
3024
3025 m_Header.wHotKey = (WORD)SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_KEY_HOTKEY, HKM_GETHOTKEY, 0, 0);
3026
3027 int index = (int)SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_RUN_COMBO, CB_GETCURSEL, 0, 0);
3028 if (index != CB_ERR)
3029 {
3030 m_Header.nShowCommand = (UINT)SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_RUN_COMBO, CB_GETITEMDATA, index, 0);
3031 }
3032
3033 TRACE("This %p m_sLinkPath %S\n", this, m_sLinkPath);
3034 Save(m_sLinkPath, TRUE);
3035 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_sLinkPath, NULL);
3036 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
3037 return TRUE;
3038 }
3039 return FALSE;
3040 }
3041
OnDestroy(HWND hwndDlg)3042 void CShellLink::OnDestroy(HWND hwndDlg)
3043 {
3044 if (m_hIcon)
3045 {
3046 DestroyIcon(m_hIcon);
3047 m_hIcon = NULL;
3048 }
3049 }
3050
3051 /**************************************************************************
3052 * SH_ShellLinkDlgProc
3053 *
3054 * dialog proc of the shortcut property dialog
3055 */
3056
3057 INT_PTR CALLBACK
SH_ShellLinkDlgProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)3058 CShellLink::SH_ShellLinkDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
3059 {
3060 LPPROPSHEETPAGEW ppsp;
3061 CShellLink *pThis = reinterpret_cast<CShellLink *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
3062
3063 switch (uMsg)
3064 {
3065 case WM_INITDIALOG:
3066 ppsp = (LPPROPSHEETPAGEW)lParam;
3067 if (ppsp == NULL)
3068 break;
3069
3070 pThis = reinterpret_cast<CShellLink *>(ppsp->lParam);
3071 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pThis);
3072 return pThis->OnInitDialog(hwndDlg, (HWND)(wParam), lParam);
3073
3074 case WM_NOTIFY:
3075 return pThis->OnNotify(hwndDlg, (int)wParam, (NMHDR *)lParam);
3076
3077 case WM_COMMAND:
3078 pThis->OnCommand(hwndDlg, LOWORD(wParam), (HWND)lParam, HIWORD(wParam));
3079 break;
3080
3081 case WM_DESTROY:
3082 pThis->OnDestroy(hwndDlg);
3083 break;
3084
3085 default:
3086 break;
3087 }
3088
3089 return FALSE;
3090 }
3091
3092 /**************************************************************************
3093 * ShellLink_IShellPropSheetExt interface
3094 */
3095
AddPages(LPFNADDPROPSHEETPAGE pfnAddPage,LPARAM lParam)3096 HRESULT STDMETHODCALLTYPE CShellLink::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
3097 {
3098 HPROPSHEETPAGE hPage = SH_CreatePropertySheetPage(IDD_SHORTCUT_PROPERTIES, SH_ShellLinkDlgProc, (LPARAM)this, NULL);
3099 if (hPage == NULL)
3100 {
3101 ERR("failed to create property sheet page\n");
3102 return E_FAIL;
3103 }
3104
3105 if (!pfnAddPage(hPage, lParam))
3106 return E_FAIL;
3107
3108 return S_OK;
3109 }
3110
ReplacePage(UINT uPageID,LPFNADDPROPSHEETPAGE pfnReplacePage,LPARAM lParam)3111 HRESULT STDMETHODCALLTYPE CShellLink::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam)
3112 {
3113 TRACE("(%p) (uPageID %u, pfnReplacePage %p lParam %p\n", this, uPageID, pfnReplacePage, lParam);
3114 return E_NOTIMPL;
3115 }
3116
SetSite(IUnknown * punk)3117 HRESULT STDMETHODCALLTYPE CShellLink::SetSite(IUnknown *punk)
3118 {
3119 TRACE("%p %p\n", this, punk);
3120
3121 m_site = punk;
3122
3123 return S_OK;
3124 }
3125
GetSite(REFIID iid,void ** ppvSite)3126 HRESULT STDMETHODCALLTYPE CShellLink::GetSite(REFIID iid, void ** ppvSite)
3127 {
3128 TRACE("%p %s %p\n", this, debugstr_guid(&iid), ppvSite);
3129
3130 if (m_site == NULL)
3131 return E_FAIL;
3132
3133 return m_site->QueryInterface(iid, ppvSite);
3134 }
3135
DragEnter(IDataObject * pDataObject,DWORD dwKeyState,POINTL pt,DWORD * pdwEffect)3136 HRESULT STDMETHODCALLTYPE CShellLink::DragEnter(IDataObject *pDataObject,
3137 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
3138 {
3139 TRACE("(%p)->(DataObject=%p)\n", this, pDataObject);
3140
3141 if (*pdwEffect == DROPEFFECT_NONE)
3142 return S_OK;
3143
3144 LPCITEMIDLIST pidlLast;
3145 CComPtr<IShellFolder> psf;
3146
3147 HRESULT hr = SHBindToParent(m_pPidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast);
3148
3149 if (SUCCEEDED(hr))
3150 {
3151 hr = psf->GetUIObjectOf(0, 1, &pidlLast, IID_NULL_PPV_ARG(IDropTarget, &m_DropTarget));
3152
3153 if (SUCCEEDED(hr))
3154 hr = m_DropTarget->DragEnter(pDataObject, dwKeyState, pt, pdwEffect);
3155 else
3156 *pdwEffect = DROPEFFECT_NONE;
3157 }
3158 else
3159 *pdwEffect = DROPEFFECT_NONE;
3160
3161 return S_OK;
3162 }
3163
DragOver(DWORD dwKeyState,POINTL pt,DWORD * pdwEffect)3164 HRESULT STDMETHODCALLTYPE CShellLink::DragOver(DWORD dwKeyState, POINTL pt,
3165 DWORD *pdwEffect)
3166 {
3167 TRACE("(%p)\n", this);
3168 HRESULT hr = S_OK;
3169 if (m_DropTarget)
3170 hr = m_DropTarget->DragOver(dwKeyState, pt, pdwEffect);
3171 return hr;
3172 }
3173
DragLeave()3174 HRESULT STDMETHODCALLTYPE CShellLink::DragLeave()
3175 {
3176 TRACE("(%p)\n", this);
3177 HRESULT hr = S_OK;
3178 if (m_DropTarget)
3179 {
3180 hr = m_DropTarget->DragLeave();
3181 m_DropTarget.Release();
3182 }
3183
3184 return hr;
3185 }
3186
Drop(IDataObject * pDataObject,DWORD dwKeyState,POINTL pt,DWORD * pdwEffect)3187 HRESULT STDMETHODCALLTYPE CShellLink::Drop(IDataObject *pDataObject,
3188 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
3189 {
3190 TRACE("(%p)\n", this);
3191 HRESULT hr = S_OK;
3192 if (m_DropTarget)
3193 hr = m_DropTarget->Drop(pDataObject, dwKeyState, pt, pdwEffect);
3194
3195 return hr;
3196 }
3197
3198 /**************************************************************************
3199 * IShellLink_ConstructFromFile
3200 */
IShellLink_ConstructFromPath(WCHAR * path,REFIID riid,LPVOID * ppv)3201 HRESULT WINAPI IShellLink_ConstructFromPath(WCHAR *path, REFIID riid, LPVOID *ppv)
3202 {
3203 CComPtr<IPersistFile> ppf;
3204 HRESULT hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IPersistFile, &ppf));
3205 if (FAILED(hr))
3206 return hr;
3207
3208 hr = ppf->Load(path, 0);
3209 if (FAILED(hr))
3210 return hr;
3211
3212 return ppf->QueryInterface(riid, ppv);
3213 }
3214
IShellLink_ConstructFromFile(IShellFolder * psf,LPCITEMIDLIST pidl,REFIID riid,LPVOID * ppv)3215 HRESULT WINAPI IShellLink_ConstructFromFile(IShellFolder * psf, LPCITEMIDLIST pidl, REFIID riid, LPVOID *ppv)
3216 {
3217 WCHAR path[MAX_PATH];
3218 if (!ILGetDisplayNameExW(psf, pidl, path, 0))
3219 return E_FAIL;
3220
3221 return IShellLink_ConstructFromPath(path, riid, ppv);
3222 }
3223
CreateShortcutIcon(LPCWSTR wszIconPath,INT IconIndex)3224 HICON CShellLink::CreateShortcutIcon(LPCWSTR wszIconPath, INT IconIndex)
3225 {
3226 const INT cx = GetSystemMetrics(SM_CXICON), cy = GetSystemMetrics(SM_CYICON);
3227 const COLORREF crMask = GetSysColor(COLOR_3DFACE);
3228 WCHAR wszLnkIcon[MAX_PATH];
3229 int lnk_idx;
3230 HDC hDC;
3231 HIMAGELIST himl = ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, 1, 1);
3232 HICON hIcon = NULL, hNewIcon = NULL, hShortcut;
3233
3234 if (HLM_GetIconW(IDI_SHELL_SHORTCUT - 1, wszLnkIcon, _countof(wszLnkIcon), &lnk_idx))
3235 {
3236 ::ExtractIconExW(wszLnkIcon, lnk_idx, &hShortcut, NULL, 1);
3237 }
3238 else
3239 {
3240 hShortcut = (HICON)LoadImageW(shell32_hInstance, MAKEINTRESOURCE(IDI_SHELL_SHORTCUT),
3241 IMAGE_ICON, cx, cy, 0);
3242 }
3243
3244 ::ExtractIconExW(wszIconPath, IconIndex, &hIcon, NULL, 1);
3245 if (!hIcon || !hShortcut || !himl)
3246 goto cleanup;
3247
3248 hDC = CreateCompatibleDC(NULL);
3249 if (hDC)
3250 {
3251 // create 32bpp bitmap
3252 BITMAPINFO bi;
3253 ZeroMemory(&bi, sizeof(bi));
3254 bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
3255 bi.bmiHeader.biWidth = cx;
3256 bi.bmiHeader.biHeight = cy;
3257 bi.bmiHeader.biPlanes = 1;
3258 bi.bmiHeader.biBitCount = 32;
3259 LPVOID pvBits;
3260 HBITMAP hbm = CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, &pvBits, NULL, 0);
3261 if (hbm)
3262 {
3263 // draw the icon image
3264 HGDIOBJ hbmOld = SelectObject(hDC, hbm);
3265 {
3266 HBRUSH hbr = CreateSolidBrush(crMask);
3267 RECT rc = { 0, 0, cx, cy };
3268 FillRect(hDC, &rc, hbr);
3269 DeleteObject(hbr);
3270
3271 DrawIconEx(hDC, 0, 0, hIcon, cx, cy, 0, NULL, DI_NORMAL);
3272 DrawIconEx(hDC, 0, 0, hShortcut, cx, cy, 0, NULL, DI_NORMAL);
3273 }
3274 SelectObject(hDC, hbmOld);
3275
3276 INT iAdded = ImageList_AddMasked(himl, hbm, crMask);
3277 hNewIcon = ImageList_GetIcon(himl, iAdded, ILD_NORMAL | ILD_TRANSPARENT);
3278
3279 DeleteObject(hbm);
3280 }
3281 DeleteDC(hDC);
3282 }
3283
3284 cleanup:
3285 if (hIcon)
3286 DestroyIcon(hIcon);
3287 if (hShortcut)
3288 DestroyIcon(hShortcut);
3289 if (himl)
3290 ImageList_Destroy(himl);
3291
3292 return hNewIcon;
3293 }
3294