xref: /reactos/dll/win32/shell32/CShellLink.cpp (revision a17b6e93)
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