xref: /reactos/dll/win32/shell32/CShellLink.cpp (revision f06c4dcc)
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 */
194 static LPWSTR __inline HEAP_strdupAtoW(HANDLE heap, DWORD flags, LPCSTR str)
195 {
196     INT len;
197     LPWSTR p;
198 
199     assert(str);
200 
201     len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
202     p = (LPWSTR)HeapAlloc(heap, flags, len * sizeof(WCHAR));
203     if (!p)
204         return p;
205     MultiByteToWideChar(CP_ACP, 0, str, -1, p, len);
206     return p;
207 }
208 
209 static LPWSTR __inline strdupW(LPCWSTR src)
210 {
211     LPWSTR dest;
212     if (!src) return NULL;
213     dest = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(src) + 1) * sizeof(WCHAR));
214     if (dest)
215         wcscpy(dest, src);
216     return dest;
217 }
218 
219 // TODO: Use it for constructor & destructor too
220 VOID CShellLink::Reset()
221 {
222     ILFree(m_pPidl);
223     m_pPidl = NULL;
224 
225     HeapFree(GetProcessHeap(), 0, m_sPath);
226     m_sPath = NULL;
227     ZeroMemory(&volume, sizeof(volume));
228 
229     HeapFree(GetProcessHeap(), 0, m_sDescription);
230     m_sDescription = NULL;
231     HeapFree(GetProcessHeap(), 0, m_sPathRel);
232     m_sPathRel = NULL;
233     HeapFree(GetProcessHeap(), 0, m_sWorkDir);
234     m_sWorkDir = NULL;
235     HeapFree(GetProcessHeap(), 0, m_sArgs);
236     m_sArgs = NULL;
237     HeapFree(GetProcessHeap(), 0, m_sIcoPath);
238     m_sIcoPath = NULL;
239 
240     m_bRunAs = FALSE;
241     m_bDirty = FALSE;
242 
243     if (m_pDBList)
244         SHFreeDataBlockList(m_pDBList);
245     m_pDBList = NULL;
246 
247     /**/sProduct = sComponent = NULL;/**/
248 }
249 
250 CShellLink::CShellLink()
251 {
252     m_Header.dwSize = sizeof(m_Header);
253     m_Header.clsid = CLSID_ShellLink;
254     m_Header.dwFlags = 0;
255 
256     m_Header.dwFileAttributes = 0;
257     ZeroMemory(&m_Header.ftCreationTime, sizeof(m_Header.ftCreationTime));
258     ZeroMemory(&m_Header.ftLastAccessTime, sizeof(m_Header.ftLastAccessTime));
259     ZeroMemory(&m_Header.ftLastWriteTime, sizeof(m_Header.ftLastWriteTime));
260     m_Header.nFileSizeLow = 0;
261 
262     m_Header.nIconIndex = 0;
263     m_Header.nShowCommand = SW_SHOWNORMAL;
264     m_Header.wHotKey = 0;
265 
266     m_pPidl = NULL;
267 
268     m_sPath = NULL;
269     ZeroMemory(&volume, sizeof(volume));
270 
271     m_sDescription = NULL;
272     m_sPathRel = NULL;
273     m_sWorkDir = NULL;
274     m_sArgs = NULL;
275     m_sIcoPath = NULL;
276     m_bRunAs = FALSE;
277     m_bDirty = FALSE;
278     m_pDBList = NULL;
279     m_bInInit = FALSE;
280     m_hIcon = NULL;
281     m_idCmdFirst = 0;
282 
283     m_sLinkPath = NULL;
284 
285     /**/sProduct = sComponent = NULL;/**/
286 }
287 
288 CShellLink::~CShellLink()
289 {
290     TRACE("-- destroying IShellLink(%p)\n", this);
291 
292     ILFree(m_pPidl);
293 
294     HeapFree(GetProcessHeap(), 0, m_sPath);
295 
296     HeapFree(GetProcessHeap(), 0, m_sDescription);
297     HeapFree(GetProcessHeap(), 0, m_sPathRel);
298     HeapFree(GetProcessHeap(), 0, m_sWorkDir);
299     HeapFree(GetProcessHeap(), 0, m_sArgs);
300     HeapFree(GetProcessHeap(), 0, m_sIcoPath);
301     HeapFree(GetProcessHeap(), 0, m_sLinkPath);
302     SHFreeDataBlockList(m_pDBList);
303 }
304 
305 HRESULT STDMETHODCALLTYPE CShellLink::GetClassID(CLSID *pclsid)
306 {
307     TRACE("%p %p\n", this, pclsid);
308 
309     if (pclsid == NULL)
310         return E_POINTER;
311     *pclsid = CLSID_ShellLink;
312     return S_OK;
313 }
314 
315 /************************************************************************
316  * IPersistStream_IsDirty (IPersistStream)
317  */
318 HRESULT STDMETHODCALLTYPE CShellLink::IsDirty()
319 {
320     TRACE("(%p)\n", this);
321     return (m_bDirty ? S_OK : S_FALSE);
322 }
323 
324 HRESULT STDMETHODCALLTYPE CShellLink::Load(LPCOLESTR pszFileName, DWORD dwMode)
325 {
326     TRACE("(%p, %s, %x)\n", this, debugstr_w(pszFileName), dwMode);
327 
328     if (dwMode == 0)
329         dwMode = STGM_READ | STGM_SHARE_DENY_WRITE;
330 
331     CComPtr<IStream> stm;
332     HRESULT hr = SHCreateStreamOnFileW(pszFileName, dwMode, &stm);
333     if (SUCCEEDED(hr))
334     {
335         HeapFree(GetProcessHeap(), 0, m_sLinkPath);
336         m_sLinkPath = strdupW(pszFileName);
337         hr = Load(stm);
338         ShellLink_UpdatePath(m_sPathRel, pszFileName, m_sWorkDir, &m_sPath);
339         m_bDirty = FALSE;
340     }
341     TRACE("-- returning hr %08x\n", hr);
342     return hr;
343 }
344 
345 HRESULT STDMETHODCALLTYPE CShellLink::Save(LPCOLESTR pszFileName, BOOL fRemember)
346 {
347     BOOL bAlreadyExists;
348     WCHAR szFullPath[MAX_PATH];
349 
350     TRACE("(%p)->(%s)\n", this, debugstr_w(pszFileName));
351 
352     if (!pszFileName)
353         return E_FAIL;
354 
355     bAlreadyExists = PathFileExistsW(pszFileName);
356 
357     CComPtr<IStream> stm;
358     HRESULT hr = SHCreateStreamOnFileW(pszFileName, STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, &stm);
359     if (SUCCEEDED(hr))
360     {
361         hr = Save(stm, FALSE);
362 
363         if (SUCCEEDED(hr))
364         {
365             GetFullPathNameW(pszFileName, _countof(szFullPath), szFullPath, NULL);
366             if (bAlreadyExists)
367                 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, szFullPath, NULL);
368             else
369                 SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, szFullPath, NULL);
370 
371             if (m_sLinkPath)
372                 HeapFree(GetProcessHeap(), 0, m_sLinkPath);
373 
374             m_sLinkPath = strdupW(pszFileName);
375             m_bDirty = FALSE;
376         }
377         else
378         {
379             DeleteFileW(pszFileName);
380             WARN("Failed to create shortcut %s\n", debugstr_w(pszFileName));
381         }
382     }
383 
384     return hr;
385 }
386 
387 HRESULT STDMETHODCALLTYPE CShellLink::SaveCompleted(LPCOLESTR pszFileName)
388 {
389     FIXME("(%p)->(%s)\n", this, debugstr_w(pszFileName));
390     return S_OK;
391 }
392 
393 HRESULT STDMETHODCALLTYPE CShellLink::GetCurFile(LPOLESTR *ppszFileName)
394 {
395     *ppszFileName = NULL;
396 
397     if (!m_sLinkPath)
398     {
399         /* IPersistFile::GetCurFile called before IPersistFile::Save */
400         return S_FALSE;
401     }
402 
403     *ppszFileName = (LPOLESTR)CoTaskMemAlloc((wcslen(m_sLinkPath) + 1) * sizeof(WCHAR));
404     if (!*ppszFileName)
405     {
406         /* out of memory */
407         return E_OUTOFMEMORY;
408     }
409 
410     /* copy last saved filename */
411     wcscpy(*ppszFileName, m_sLinkPath);
412 
413     return S_OK;
414 }
415 
416 static HRESULT Stream_LoadString(IStream* stm, BOOL unicode, LPWSTR *pstr)
417 {
418     TRACE("%p\n", stm);
419 
420     USHORT len;
421     DWORD count = 0;
422     HRESULT hr = stm->Read(&len, sizeof(len), &count);
423     if (FAILED(hr) || count != sizeof(len))
424         return E_FAIL;
425 
426     if (unicode)
427         len *= sizeof(WCHAR);
428 
429     TRACE("reading %d\n", len);
430     LPSTR temp = (LPSTR)HeapAlloc(GetProcessHeap(), 0, len + sizeof(WCHAR));
431     if (!temp)
432         return E_OUTOFMEMORY;
433     count = 0;
434     hr = stm->Read(temp, len, &count);
435     if (FAILED(hr) || count != len)
436     {
437         HeapFree(GetProcessHeap(), 0, temp);
438         return E_FAIL;
439     }
440 
441     TRACE("read %s\n", debugstr_an(temp, len));
442 
443     /* convert to unicode if necessary */
444     LPWSTR str;
445     if (!unicode)
446     {
447         count = MultiByteToWideChar(CP_ACP, 0, temp, len, NULL, 0);
448         str = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (count + 1) * sizeof(WCHAR));
449         if (!str)
450         {
451             HeapFree(GetProcessHeap(), 0, temp);
452             return E_OUTOFMEMORY;
453         }
454         MultiByteToWideChar(CP_ACP, 0, temp, len, str, count);
455         HeapFree(GetProcessHeap(), 0, temp);
456     }
457     else
458     {
459         count /= sizeof(WCHAR);
460         str = (LPWSTR)temp;
461     }
462     str[count] = 0;
463 
464     *pstr = str;
465 
466     return S_OK;
467 }
468 
469 
470 /*
471  * NOTE: The following 5 functions are part of LINKINFO.DLL
472  */
473 static BOOL ShellLink_GetVolumeInfo(LPCWSTR path, CShellLink::volume_info *volume)
474 {
475     WCHAR drive[4] = { path[0], ':', '\\', 0 };
476 
477     volume->type = GetDriveTypeW(drive);
478     BOOL bRet = GetVolumeInformationW(drive, volume->label, _countof(volume->label), &volume->serial, NULL, NULL, NULL, 0);
479     TRACE("ret = %d type %d serial %08x name %s\n", bRet,
480           volume->type, volume->serial, debugstr_w(volume->label));
481     return bRet;
482 }
483 
484 static HRESULT Stream_ReadChunk(IStream* stm, LPVOID *data)
485 {
486     struct sized_chunk
487     {
488         DWORD size;
489         unsigned char data[1];
490     } *chunk;
491 
492     TRACE("%p\n", stm);
493 
494     DWORD size;
495     ULONG count;
496     HRESULT hr = stm->Read(&size, sizeof(size), &count);
497     if (FAILED(hr) || count != sizeof(size))
498         return E_FAIL;
499 
500     chunk = static_cast<sized_chunk *>(HeapAlloc(GetProcessHeap(), 0, size));
501     if (!chunk)
502         return E_OUTOFMEMORY;
503 
504     chunk->size = size;
505     hr = stm->Read(chunk->data, size - sizeof(size), &count);
506     if (FAILED(hr) || count != (size - sizeof(size)))
507     {
508         HeapFree(GetProcessHeap(), 0, chunk);
509         return E_FAIL;
510     }
511 
512     TRACE("Read %d bytes\n", chunk->size);
513 
514     *data = chunk;
515 
516     return S_OK;
517 }
518 
519 static BOOL Stream_LoadVolume(LOCAL_VOLUME_INFO *vol, CShellLink::volume_info *volume)
520 {
521     volume->serial = vol->dwVolSerial;
522     volume->type = vol->dwType;
523 
524     if (!vol->dwVolLabelOfs)
525         return FALSE;
526     if (vol->dwSize <= vol->dwVolLabelOfs)
527         return FALSE;
528     INT len = vol->dwSize - vol->dwVolLabelOfs;
529 
530     LPSTR label = (LPSTR)vol;
531     label += vol->dwVolLabelOfs;
532     MultiByteToWideChar(CP_ACP, 0, label, len, volume->label, _countof(volume->label));
533 
534     return TRUE;
535 }
536 
537 static LPWSTR Stream_LoadPath(LPCSTR p, DWORD maxlen)
538 {
539     UINT len = 0;
540 
541     while (len < maxlen && p[len])
542         len++;
543 
544     UINT wlen = MultiByteToWideChar(CP_ACP, 0, p, len, NULL, 0);
545     LPWSTR path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wlen + 1) * sizeof(WCHAR));
546     if (!path)
547         return NULL;
548     MultiByteToWideChar(CP_ACP, 0, p, len, path, wlen);
549     path[wlen] = 0;
550 
551     return path;
552 }
553 
554 static HRESULT Stream_LoadLocation(IStream *stm,
555                                    CShellLink::volume_info *volume, LPWSTR *path)
556 {
557     char *p = NULL;
558     HRESULT hr = Stream_ReadChunk(stm, (LPVOID*) &p);
559     if (FAILED(hr))
560         return hr;
561 
562     LOCATION_INFO *loc = reinterpret_cast<LOCATION_INFO *>(p);
563     if (loc->dwTotalSize < sizeof(LOCATION_INFO))
564     {
565         HeapFree(GetProcessHeap(), 0, p);
566         return E_FAIL;
567     }
568 
569     /* if there's valid local volume information, load it */
570     if (loc->dwVolTableOfs &&
571         ((loc->dwVolTableOfs + sizeof(LOCAL_VOLUME_INFO)) <= loc->dwTotalSize))
572     {
573         LOCAL_VOLUME_INFO *volume_info;
574 
575         volume_info = (LOCAL_VOLUME_INFO*) &p[loc->dwVolTableOfs];
576         Stream_LoadVolume(volume_info, volume);
577     }
578 
579     /* if there's a local path, load it */
580     DWORD n = loc->dwLocalPathOfs;
581     if (n && n < loc->dwTotalSize)
582         *path = Stream_LoadPath(&p[n], loc->dwTotalSize - n);
583 
584     TRACE("type %d serial %08x name %s path %s\n", volume->type,
585           volume->serial, debugstr_w(volume->label), debugstr_w(*path));
586 
587     HeapFree(GetProcessHeap(), 0, p);
588     return S_OK;
589 }
590 
591 
592 /*
593  * The format of the advertised shortcut info is:
594  *
595  *  Offset     Description
596  *  ------     -----------
597  *    0          Length of the block (4 bytes, usually 0x314)
598  *    4          tag (dword)
599  *    8          string data in ASCII
600  *    8+0x104    string data in UNICODE
601  *
602  * In the original Win32 implementation the buffers are not initialized
603  * to zero, so data trailing the string is random garbage.
604  */
605 HRESULT CShellLink::GetAdvertiseInfo(LPWSTR *str, DWORD dwSig)
606 {
607     LPEXP_DARWIN_LINK pInfo;
608 
609     *str = NULL;
610 
611     pInfo = (LPEXP_DARWIN_LINK)SHFindDataBlock(m_pDBList, dwSig);
612     if (!pInfo)
613         return E_FAIL;
614 
615     /* Make sure that the size of the structure is valid */
616     if (pInfo->dbh.cbSize != sizeof(*pInfo))
617     {
618         ERR("Ooops. This structure is not as expected...\n");
619         return E_FAIL;
620     }
621 
622     TRACE("dwSig %08x  string = '%s'\n", pInfo->dbh.dwSignature, debugstr_w(pInfo->szwDarwinID));
623 
624     *str = pInfo->szwDarwinID;
625     return S_OK;
626 }
627 
628 /************************************************************************
629  * IPersistStream_Load (IPersistStream)
630  */
631 HRESULT STDMETHODCALLTYPE CShellLink::Load(IStream *stm)
632 {
633     TRACE("%p %p\n", this, stm);
634 
635     if (!stm)
636         return STG_E_INVALIDPOINTER;
637 
638     /* Free all the old stuff */
639     Reset();
640 
641     ULONG dwBytesRead = 0;
642     HRESULT hr = stm->Read(&m_Header, sizeof(m_Header), &dwBytesRead);
643     if (FAILED(hr))
644         return hr;
645 
646     if (dwBytesRead != sizeof(m_Header))
647         return E_FAIL;
648     if (m_Header.dwSize != sizeof(m_Header))
649         return E_FAIL;
650     if (!IsEqualIID(m_Header.clsid, CLSID_ShellLink))
651         return E_FAIL;
652 
653     /* Load the new data in order */
654 
655     if (TRACE_ON(shell))
656     {
657         SYSTEMTIME stCreationTime;
658         SYSTEMTIME stLastAccessTime;
659         SYSTEMTIME stLastWriteTime;
660         WCHAR sTemp[MAX_PATH];
661 
662         FileTimeToSystemTime(&m_Header.ftCreationTime, &stCreationTime);
663         FileTimeToSystemTime(&m_Header.ftLastAccessTime, &stLastAccessTime);
664         FileTimeToSystemTime(&m_Header.ftLastWriteTime, &stLastWriteTime);
665 
666         GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stCreationTime,
667                        NULL, sTemp, _countof(sTemp));
668         TRACE("-- stCreationTime: %s\n", debugstr_w(sTemp));
669         GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stLastAccessTime,
670                        NULL, sTemp, _countof(sTemp));
671         TRACE("-- stLastAccessTime: %s\n", debugstr_w(sTemp));
672         GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stLastWriteTime,
673                        NULL, sTemp, _countof(sTemp));
674         TRACE("-- stLastWriteTime: %s\n", debugstr_w(sTemp));
675     }
676 
677     /* load all the new stuff */
678     if (m_Header.dwFlags & SLDF_HAS_ID_LIST)
679     {
680         hr = ILLoadFromStream(stm, &m_pPidl);
681         if (FAILED(hr))
682             return hr;
683     }
684     pdump(m_pPidl);
685 
686     /* Load the location information... */
687     if (m_Header.dwFlags & SLDF_HAS_LINK_INFO)
688     {
689         hr = Stream_LoadLocation(stm, &volume, &m_sPath);
690         if (FAILED(hr))
691             return hr;
692     }
693     /* ... but if it is required not to use it, clear it */
694     if (m_Header.dwFlags & SLDF_FORCE_NO_LINKINFO)
695     {
696         HeapFree(GetProcessHeap(), 0, m_sPath);
697         m_sPath = NULL;
698         ZeroMemory(&volume, sizeof(volume));
699     }
700 
701     BOOL unicode = !!(m_Header.dwFlags & SLDF_UNICODE);
702 
703     if (m_Header.dwFlags & SLDF_HAS_NAME)
704     {
705         hr = Stream_LoadString(stm, unicode, &m_sDescription);
706         if (FAILED(hr))
707             return hr;
708         TRACE("Description  -> %s\n", debugstr_w(m_sDescription));
709     }
710 
711     if (m_Header.dwFlags & SLDF_HAS_RELPATH)
712     {
713         hr = Stream_LoadString(stm, unicode, &m_sPathRel);
714         if (FAILED(hr))
715             return hr;
716         TRACE("Relative Path-> %s\n", debugstr_w(m_sPathRel));
717     }
718 
719     if (m_Header.dwFlags & SLDF_HAS_WORKINGDIR)
720     {
721         hr = Stream_LoadString(stm, unicode, &m_sWorkDir);
722         if (FAILED(hr))
723             return hr;
724         PathRemoveBackslash(m_sWorkDir);
725         TRACE("Working Dir  -> %s\n", debugstr_w(m_sWorkDir));
726     }
727 
728     if (m_Header.dwFlags & SLDF_HAS_ARGS)
729     {
730         hr = Stream_LoadString(stm, unicode, &m_sArgs);
731         if (FAILED(hr))
732             return hr;
733         TRACE("Arguments    -> %s\n", debugstr_w(m_sArgs));
734     }
735 
736     if (m_Header.dwFlags & SLDF_HAS_ICONLOCATION)
737     {
738         hr = Stream_LoadString(stm, unicode, &m_sIcoPath);
739         if (FAILED(hr))
740             return hr;
741         TRACE("Icon file    -> %s\n", debugstr_w(m_sIcoPath));
742     }
743 
744     /* Now load the optional data block list */
745     hr = SHReadDataBlockList(stm, &m_pDBList);
746     if (FAILED(hr)) // FIXME: Should we fail?
747         return hr;
748 
749     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  */
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  */
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  */
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  */
1033 HRESULT STDMETHODCALLTYPE CShellLink::GetSizeMax(ULARGE_INTEGER *pcbSize)
1034 {
1035     TRACE("(%p)\n", this);
1036     return E_NOTIMPL;
1037 }
1038 
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  */
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 
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 
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 
1164 HRESULT STDMETHODCALLTYPE CShellLink::SetIDList(PCIDLIST_ABSOLUTE pidl)
1165 {
1166     TRACE("(%p)->(pidl=%p)\n", this, pidl);
1167     return SetTargetFromPIDLOrPath(pidl, NULL);
1168 }
1169 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
1729 static HRESULT SHELL_PidlGetIconLocationW(PCIDLIST_ABSOLUTE pidl,
1730         UINT uFlags, PWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags)
1731 {
1732     CComPtr<IExtractIconW> pei;
1733     HRESULT hr = SHELL_GetUIObjectOfAbsoluteItem(NULL, pidl, IID_PPV_ARG(IExtractIconW, &pei));
1734     if (FAILED_UNEXPECTEDLY(hr))
1735         return hr;
1736     hr = pei->GetIconLocation(uFlags, pszIconFile, cchMax, piIndex, pwFlags);
1737     if (FAILED_UNEXPECTEDLY(hr))
1738         return hr;
1739 
1740     return S_OK;
1741 }
1742 
1743 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(UINT uFlags, PWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags)
1744 {
1745     HRESULT hr;
1746 
1747     pszIconFile[0] = UNICODE_NULL;
1748 
1749     /*
1750      * It is possible for a shell link to point to another shell link,
1751      * and in particular there is the possibility to point to itself.
1752      * Now, suppose we ask such a link to retrieve its associated icon.
1753      * This function would be called, and due to COM would be called again
1754      * recursively. To solve this issue, we forbid calling GetIconLocation()
1755      * with GIL_FORSHORTCUT set in uFlags, as done by Windows (shown by tests).
1756      */
1757     if (uFlags & GIL_FORSHORTCUT)
1758         return E_INVALIDARG;
1759 
1760     /*
1761      * Now, we set GIL_FORSHORTCUT so that: i) we allow the icon extractor
1762      * of the target to give us a suited icon, and ii) we protect ourselves
1763      * against recursive call.
1764      */
1765     uFlags |= GIL_FORSHORTCUT;
1766 
1767     if (uFlags & GIL_DEFAULTICON)
1768         return S_FALSE;
1769 
1770     hr = GetIconLocation(pszIconFile, cchMax, piIndex);
1771     if (FAILED(hr) || pszIconFile[0] == UNICODE_NULL)
1772     {
1773         hr = SHELL_PidlGetIconLocationW(m_pPidl, uFlags, pszIconFile, cchMax, piIndex, pwFlags);
1774     }
1775     else
1776     {
1777         *pwFlags = GIL_NOTFILENAME | GIL_PERCLASS;
1778     }
1779 
1780     return hr;
1781 }
1782 
1783 HRESULT STDMETHODCALLTYPE
1784 CShellLink::Extract(PCWSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize)
1785 {
1786     HRESULT hr = NOERROR;
1787     UINT cxyLarge = LOWORD(nIconSize), cxySmall = HIWORD(nIconSize);
1788 
1789     if (phiconLarge)
1790     {
1791         *phiconLarge = NULL;
1792         PrivateExtractIconsW(pszFile, nIconIndex, cxyLarge, cxyLarge, phiconLarge, NULL, 1, 0);
1793 
1794         if (*phiconLarge == NULL)
1795             hr = S_FALSE;
1796     }
1797 
1798     if (phiconSmall)
1799     {
1800         *phiconSmall = NULL;
1801         PrivateExtractIconsW(pszFile, nIconIndex, cxySmall, cxySmall, phiconSmall, NULL, 1, 0);
1802 
1803         if (*phiconSmall == NULL)
1804             hr = S_FALSE;
1805     }
1806 
1807     if (hr == S_FALSE)
1808     {
1809         if (phiconLarge && *phiconLarge)
1810         {
1811             DestroyIcon(*phiconLarge);
1812             *phiconLarge = NULL;
1813         }
1814         if (phiconSmall && *phiconSmall)
1815         {
1816             DestroyIcon(*phiconSmall);
1817             *phiconSmall = NULL;
1818         }
1819     }
1820 
1821     return hr;
1822 }
1823 
1824 #if 0
1825 /* Extends the functionality of PathUnExpandEnvStringsW */
1826 BOOL PathFullyUnExpandEnvStringsW(
1827     _In_  LPCWSTR pszPath,
1828     _Out_ LPWSTR  pszBuf,
1829     _In_  UINT    cchBuf)
1830 {
1831     BOOL Ret = FALSE; // Set to TRUE as soon as PathUnExpandEnvStrings starts unexpanding.
1832     BOOL res;
1833     LPCWSTR p;
1834 
1835     // *pszBuf = L'\0';
1836     while (*pszPath && cchBuf > 0)
1837     {
1838         /* Attempt unexpanding the path */
1839         res = PathUnExpandEnvStringsW(pszPath, pszBuf, cchBuf);
1840         if (!res)
1841         {
1842             /* The unexpansion failed. Try to find a path delimiter. */
1843             p = wcspbrk(pszPath, L" /\\:*?\"<>|%");
1844             if (!p) /* None found, we will copy the remaining path */
1845                 p = pszPath + wcslen(pszPath);
1846             else    /* Found one, we will copy the delimiter and skip it */
1847                 ++p;
1848             /* If we overflow, we cannot unexpand more, so return FALSE */
1849             if (p - pszPath >= cchBuf)
1850                 return FALSE; // *pszBuf = L'\0';
1851 
1852             /* Copy the untouched portion of path up to the delimiter, included */
1853             wcsncpy(pszBuf, pszPath, p - pszPath);
1854             pszBuf[p - pszPath] = L'\0'; // NULL-terminate
1855 
1856             /* Advance the pointers and decrease the remaining buffer size */
1857             cchBuf -= (p - pszPath);
1858             pszBuf += (p - pszPath);
1859             pszPath += (p - pszPath);
1860         }
1861         else
1862         {
1863             /*
1864              * The unexpansion succeeded. Skip the unexpanded part by trying
1865              * to find where the original path and the unexpanded string
1866              * become different.
1867              * NOTE: An alternative(?) would be to stop also at the last
1868              * path delimiter encountered in the loop (i.e. would be the
1869              * first path delimiter in the strings).
1870              */
1871             LPWSTR q;
1872 
1873             /*
1874              * The algorithm starts at the end of the strings and loops back
1875              * while the characters are equal, until it finds a discrepancy.
1876              */
1877             p = pszPath + wcslen(pszPath);
1878             q = pszBuf + wcslen(pszBuf); // This wcslen should be < cchBuf
1879             while ((*p == *q) && (p > pszPath) && (q > pszBuf))
1880             {
1881                 --p; --q;
1882             }
1883             /* Skip discrepancy */
1884             ++p; ++q;
1885 
1886             /* Advance the pointers and decrease the remaining buffer size */
1887             cchBuf -= (q - pszBuf);
1888             pszBuf = q;
1889             pszPath = p;
1890 
1891             Ret = TRUE;
1892         }
1893     }
1894 
1895     return Ret;
1896 }
1897 #endif
1898 
1899 HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT iIcon)
1900 {
1901     HRESULT hr = E_FAIL;
1902     WCHAR szIconPath[MAX_PATH];
1903 
1904     TRACE("(%p)->(path=%s iicon=%u)\n", this, debugstr_w(pszIconPath), iIcon);
1905 
1906     if (pszIconPath)
1907     {
1908         /*
1909          * Check whether the user-given file path contains unexpanded
1910          * environment variables. If so, create a target environment block.
1911          * Note that in this block we will store the user-given path.
1912          * It will contain the unexpanded environment variables, but
1913          * it can also contain already expanded path that the user does
1914          * not want to see them unexpanded (e.g. so that they always
1915          * refer to the same place even if the would-be corresponding
1916          * environment variable could change).
1917          */
1918 #ifdef ICON_LINK_WINDOWS_COMPAT
1919         /* Try to fully unexpand the icon path */
1920         // if (PathFullyUnExpandEnvStringsW(pszIconPath, szIconPath, _countof(szIconPath)))
1921         BOOL bSuccess = PathUnExpandEnvStringsW(pszIconPath, szIconPath, _countof(szIconPath));
1922         if (bSuccess && wcscmp(pszIconPath, szIconPath) != 0)
1923 #else
1924         /*
1925          * In some situations, described in http://stackoverflow.com/questions/2976489/ishelllinkseticonlocation-translates-my-icon-path-into-program-files-which-i
1926          * the result of PathUnExpandEnvStringsW() could be wrong, and instead
1927          * one would have to store the actual provided icon location path, while
1928          * creating an icon environment block ONLY if that path already contains
1929          * environment variables. This is what the present case is trying to implement.
1930          */
1931         SHExpandEnvironmentStringsW(pszIconPath, szIconPath, _countof(szIconPath));
1932         if (wcscmp(pszIconPath, szIconPath) != 0)
1933 #endif
1934         {
1935             /*
1936              * The user-given file path contains unexpanded environment
1937              * variables, so we need an icon environment block.
1938              */
1939             EXP_SZ_LINK buffer;
1940             LPEXP_SZ_LINK pInfo;
1941 
1942 #ifdef ICON_LINK_WINDOWS_COMPAT
1943             /* Make pszIconPath point to the unexpanded path */
1944             LPCWSTR pszOrgIconPath = pszIconPath;
1945             pszIconPath = szIconPath;
1946 #endif
1947             pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_ICON_SIG);
1948             if (pInfo)
1949             {
1950                 /* Make sure that the size of the structure is valid */
1951                 if (pInfo->cbSize != sizeof(*pInfo))
1952                 {
1953                     ERR("Ooops. This structure is not as expected...\n");
1954 
1955                     /* Invalid structure, remove it altogether */
1956                     m_Header.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ;
1957                     RemoveDataBlock(EXP_SZ_ICON_SIG);
1958 
1959                     /* Reset the pointer and go use the static buffer */
1960                     pInfo = NULL;
1961                 }
1962             }
1963             if (!pInfo)
1964             {
1965                 /* Use the static buffer */
1966                 pInfo = &buffer;
1967                 buffer.cbSize = sizeof(buffer);
1968                 buffer.dwSignature = EXP_SZ_ICON_SIG;
1969             }
1970 
1971             lstrcpynW(pInfo->szwTarget, pszIconPath, _countof(pInfo->szwTarget));
1972             WideCharToMultiByte(CP_ACP, 0, pszIconPath, -1,
1973                                 pInfo->szTarget, _countof(pInfo->szTarget), NULL, NULL);
1974 
1975             hr = S_OK;
1976             if (pInfo == &buffer)
1977                 hr = AddDataBlock(pInfo);
1978             if (hr == S_OK)
1979                 m_Header.dwFlags |= SLDF_HAS_EXP_ICON_SZ;
1980 
1981 #ifdef ICON_LINK_WINDOWS_COMPAT
1982             /* Set pszIconPath back to the original one */
1983             pszIconPath = pszOrgIconPath;
1984 #else
1985             /* Now, make pszIconPath point to the expanded path */
1986             pszIconPath = szIconPath;
1987 #endif
1988         }
1989         else
1990         {
1991             /*
1992              * The user-given file path does not contain unexpanded environment
1993              * variables, so we need to remove any icon environment block.
1994              */
1995             m_Header.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ;
1996             RemoveDataBlock(EXP_SZ_ICON_SIG);
1997 
1998             /* pszIconPath points to the user path */
1999         }
2000     }
2001 
2002 #ifdef ICON_LINK_WINDOWS_COMPAT
2003     /* Store the original icon path location (may contain unexpanded environment strings) */
2004 #endif
2005     if (pszIconPath)
2006     {
2007         m_Header.dwFlags &= ~SLDF_HAS_ICONLOCATION;
2008         HeapFree(GetProcessHeap(), 0, m_sIcoPath);
2009 
2010         m_sIcoPath = strdupW(pszIconPath);
2011         if (!m_sIcoPath)
2012             return E_OUTOFMEMORY;
2013 
2014         m_Header.dwFlags |= SLDF_HAS_ICONLOCATION;
2015     }
2016 
2017     hr = S_OK;
2018 
2019     m_Header.nIconIndex = iIcon;
2020     m_bDirty = TRUE;
2021 
2022     return hr;
2023 }
2024 
2025 HRESULT STDMETHODCALLTYPE CShellLink::SetRelativePath(LPCWSTR pszPathRel, DWORD dwReserved)
2026 {
2027     TRACE("(%p)->(path=%s %x)\n", this, debugstr_w(pszPathRel), dwReserved);
2028 
2029     HeapFree(GetProcessHeap(), 0, m_sPathRel);
2030     m_sPathRel = NULL;
2031 
2032     if (pszPathRel)
2033     {
2034         m_sPathRel = strdupW(pszPathRel);
2035         if (!m_sPathRel)
2036             return E_OUTOFMEMORY;
2037     }
2038     m_bDirty = TRUE;
2039 
2040     return ShellLink_UpdatePath(m_sPathRel, m_sPath, m_sWorkDir, &m_sPath);
2041 }
2042 
2043 static LPWSTR GetAdvertisedArg(LPCWSTR str)
2044 {
2045     if (!str)
2046         return NULL;
2047 
2048     LPCWSTR p = wcschr(str, L':');
2049     if (!p)
2050         return NULL;
2051 
2052     DWORD len = p - str;
2053     LPWSTR ret = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * (len + 1));
2054     if (!ret)
2055         return ret;
2056 
2057     memcpy(ret, str, sizeof(WCHAR)*len);
2058     ret[len] = 0;
2059     return ret;
2060 }
2061 
2062 HRESULT CShellLink::WriteAdvertiseInfo(LPCWSTR string, DWORD dwSig)
2063 {
2064     EXP_DARWIN_LINK buffer;
2065     LPEXP_DARWIN_LINK pInfo;
2066 
2067     if (   (dwSig != EXP_DARWIN_ID_SIG)
2068 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2069         && (dwSig != EXP_LOGO3_ID_SIG)
2070 #endif
2071         )
2072     {
2073         return E_INVALIDARG;
2074     }
2075 
2076     if (!string)
2077         return S_FALSE;
2078 
2079     pInfo = (LPEXP_DARWIN_LINK)SHFindDataBlock(m_pDBList, dwSig);
2080     if (pInfo)
2081     {
2082         /* Make sure that the size of the structure is valid */
2083         if (pInfo->dbh.cbSize != sizeof(*pInfo))
2084         {
2085             ERR("Ooops. This structure is not as expected...\n");
2086 
2087             /* Invalid structure, remove it altogether */
2088             if (dwSig == EXP_DARWIN_ID_SIG)
2089                 m_Header.dwFlags &= ~SLDF_HAS_DARWINID;
2090 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2091             else if (dwSig == EXP_LOGO3_ID_SIG)
2092                 m_Header.dwFlags &= ~SLDF_HAS_LOGO3ID;
2093 #endif
2094             RemoveDataBlock(dwSig);
2095 
2096             /* Reset the pointer and go use the static buffer */
2097             pInfo = NULL;
2098         }
2099     }
2100     if (!pInfo)
2101     {
2102         /* Use the static buffer */
2103         pInfo = &buffer;
2104         buffer.dbh.cbSize = sizeof(buffer);
2105         buffer.dbh.dwSignature = dwSig;
2106     }
2107 
2108     lstrcpynW(pInfo->szwDarwinID, string, _countof(pInfo->szwDarwinID));
2109     WideCharToMultiByte(CP_ACP, 0, string, -1,
2110                         pInfo->szDarwinID, _countof(pInfo->szDarwinID), NULL, NULL);
2111 
2112     HRESULT hr = S_OK;
2113     if (pInfo == &buffer)
2114         hr = AddDataBlock(pInfo);
2115     if (hr == S_OK)
2116     {
2117         if (dwSig == EXP_DARWIN_ID_SIG)
2118             m_Header.dwFlags |= SLDF_HAS_DARWINID;
2119 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2120         else if (dwSig == EXP_LOGO3_ID_SIG)
2121             m_Header.dwFlags |= SLDF_HAS_LOGO3ID;
2122 #endif
2123     }
2124 
2125     return hr;
2126 }
2127 
2128 HRESULT CShellLink::SetAdvertiseInfo(LPCWSTR str)
2129 {
2130     HRESULT hr;
2131     LPCWSTR szComponent = NULL, szProduct = NULL, p;
2132     INT len;
2133     GUID guid;
2134     WCHAR szGuid[38+1];
2135 
2136     /**/sProduct = sComponent = NULL;/**/
2137 
2138     while (str[0])
2139     {
2140         /* each segment must start with two colons */
2141         if (str[0] != ':' || str[1] != ':')
2142             return E_FAIL;
2143 
2144         /* the last segment is just two colons */
2145         if (!str[2])
2146             break;
2147         str += 2;
2148 
2149         /* there must be a colon straight after a guid */
2150         p = wcschr(str, L':');
2151         if (!p)
2152             return E_FAIL;
2153         len = p - str;
2154         if (len != 38)
2155             return E_FAIL;
2156 
2157         /* get the guid, and check if it's validly formatted */
2158         memcpy(szGuid, str, sizeof(WCHAR)*len);
2159         szGuid[len] = 0;
2160 
2161         hr = CLSIDFromString(szGuid, &guid);
2162         if (hr != S_OK)
2163             return hr;
2164         str = p + 1;
2165 
2166         /* match it up to a guid that we care about */
2167         if (IsEqualGUID(guid, SHELL32_AdvtShortcutComponent) && !szComponent)
2168             szComponent = str; /* Darwin */
2169         else if (IsEqualGUID(guid, SHELL32_AdvtShortcutProduct) && !szProduct)
2170             szProduct = str;   /* Logo3  */
2171         else
2172             return E_FAIL;
2173 
2174         /* skip to the next field */
2175         str = wcschr(str, L':');
2176         if (!str)
2177             return E_FAIL;
2178     }
2179 
2180     /* we have to have a component for an advertised shortcut */
2181     if (!szComponent)
2182         return E_FAIL;
2183 
2184     szComponent = GetAdvertisedArg(szComponent);
2185     szProduct = GetAdvertisedArg(szProduct);
2186 
2187     hr = WriteAdvertiseInfo(szComponent, EXP_DARWIN_ID_SIG);
2188     // if (FAILED(hr))
2189         // return hr;
2190 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2191     hr = WriteAdvertiseInfo(szProduct, EXP_LOGO3_ID_SIG);
2192     // if (FAILED(hr))
2193         // return hr;
2194 #endif
2195 
2196     HeapFree(GetProcessHeap(), 0, (PVOID)szComponent);
2197     HeapFree(GetProcessHeap(), 0, (PVOID)szProduct);
2198 
2199     if (TRACE_ON(shell))
2200     {
2201         GetAdvertiseInfo(&sComponent, EXP_DARWIN_ID_SIG);
2202         TRACE("Component = %s\n", debugstr_w(sComponent));
2203 #if (NTDDI_VERSION < NTDDI_LONGHORN)
2204         GetAdvertiseInfo(&sProduct, EXP_LOGO3_ID_SIG);
2205         TRACE("Product = %s\n", debugstr_w(sProduct));
2206 #endif
2207     }
2208 
2209     return S_OK;
2210 }
2211 
2212 HRESULT CShellLink::SetTargetFromPIDLOrPath(LPCITEMIDLIST pidl, LPCWSTR pszFile)
2213 {
2214     HRESULT hr = S_OK;
2215     LPITEMIDLIST pidlNew = NULL;
2216     WCHAR szPath[MAX_PATH];
2217 
2218     /*
2219      * Not both 'pidl' and 'pszFile' should be set.
2220      * But either one or both can be NULL.
2221      */
2222     if (pidl && pszFile)
2223         return E_FAIL;
2224 
2225     if (pidl)
2226     {
2227         /* Clone the PIDL */
2228         pidlNew = ILClone(pidl);
2229         if (!pidlNew)
2230             return E_FAIL;
2231     }
2232     else if (pszFile)
2233     {
2234         /* Build a PIDL for this path target */
2235         hr = SHILCreateFromPathW(pszFile, &pidlNew, NULL);
2236         if (FAILED(hr))
2237         {
2238             /* This failed, try to resolve the path, then create a simple PIDL */
2239 
2240             StringCchCopyW(szPath, _countof(szPath), pszFile);
2241             PathResolveW(szPath, NULL, PRF_TRYPROGRAMEXTENSIONS);
2242 
2243             if (PathIsFileSpecW(szPath))
2244             {
2245                 hr = E_INVALIDARG;
2246                 szPath[0] = 0;
2247             }
2248             else
2249             {
2250                 hr = S_OK;
2251                 pidlNew = SHSimpleIDListFromPathW(szPath);
2252                 // NOTE: Don't make it failed here even if pidlNew was NULL.
2253                 // We don't fail on purpose even if SHSimpleIDListFromPathW returns NULL.
2254                 // This behaviour has been verified with tests.
2255             }
2256         }
2257     }
2258     // else if (!pidl && !pszFile) { pidlNew = NULL; hr = S_OK; }
2259 
2260     ILFree(m_pPidl);
2261     m_pPidl = pidlNew;
2262 
2263     if (!pszFile)
2264     {
2265         if (SHGetPathFromIDListW(pidlNew, szPath))
2266             pszFile = szPath;
2267     }
2268 
2269     // TODO: Fully update link info, tracker, file attribs...
2270 
2271     // if (pszFile)
2272     if (!pszFile)
2273     {
2274         *szPath = L'\0';
2275         pszFile = szPath;
2276     }
2277 
2278     /* Update the cached path (for link info) */
2279     ShellLink_GetVolumeInfo(pszFile, &volume);
2280 
2281     if (m_sPath)
2282         HeapFree(GetProcessHeap(), 0, m_sPath);
2283 
2284     m_sPath = strdupW(pszFile);
2285     if (!m_sPath)
2286         return E_OUTOFMEMORY;
2287 
2288     m_bDirty = TRUE;
2289     return hr;
2290 }
2291 
2292 HRESULT STDMETHODCALLTYPE CShellLink::SetPath(LPCWSTR pszFile)
2293 {
2294     LPWSTR unquoted = NULL;
2295     HRESULT hr = S_OK;
2296 
2297     TRACE("(%p)->(path=%s)\n", this, debugstr_w(pszFile));
2298 
2299     if (!pszFile)
2300         return E_INVALIDARG;
2301 
2302     /*
2303      * Allow upgrading Logo3 shortcuts (m_Header.dwFlags & SLDF_HAS_LOGO3ID),
2304      * but forbid upgrading Darwin ones.
2305      */
2306     if (m_Header.dwFlags & SLDF_HAS_DARWINID)
2307         return S_FALSE;
2308 
2309     /* quotes at the ends of the string are stripped */
2310     SIZE_T len = wcslen(pszFile);
2311     if (pszFile[0] == L'"' && pszFile[len-1] == L'"')
2312     {
2313         unquoted = strdupW(pszFile);
2314         PathUnquoteSpacesW(unquoted);
2315         pszFile = unquoted;
2316     }
2317 
2318     /* any other quote marks are invalid */
2319     if (wcschr(pszFile, L'"'))
2320     {
2321         hr = S_FALSE;
2322         goto end;
2323     }
2324 
2325     /* Clear the cached path */
2326     HeapFree(GetProcessHeap(), 0, m_sPath);
2327     m_sPath = NULL;
2328 
2329     /* Check for an advertised target (Logo3 or Darwin) */
2330     if (SetAdvertiseInfo(pszFile) != S_OK)
2331     {
2332         /* This is not an advertised target, but a regular path */
2333         WCHAR szPath[MAX_PATH];
2334 
2335         /*
2336          * Check whether the user-given file path contains unexpanded
2337          * environment variables. If so, create a target environment block.
2338          * Note that in this block we will store the user-given path.
2339          * It will contain the unexpanded environment variables, but
2340          * it can also contain already expanded path that the user does
2341          * not want to see them unexpanded (e.g. so that they always
2342          * refer to the same place even if the would-be corresponding
2343          * environment variable could change).
2344          */
2345         if (*pszFile)
2346             SHExpandEnvironmentStringsW(pszFile, szPath, _countof(szPath));
2347         else
2348             *szPath = L'\0';
2349 
2350         if (*pszFile && (wcscmp(pszFile, szPath) != 0))
2351         {
2352             /*
2353              * The user-given file path contains unexpanded environment
2354              * variables, so we need a target environment block.
2355              */
2356             EXP_SZ_LINK buffer;
2357             LPEXP_SZ_LINK pInfo;
2358 
2359             pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_LINK_SIG);
2360             if (pInfo)
2361             {
2362                 /* Make sure that the size of the structure is valid */
2363                 if (pInfo->cbSize != sizeof(*pInfo))
2364                 {
2365                     ERR("Ooops. This structure is not as expected...\n");
2366 
2367                     /* Invalid structure, remove it altogether */
2368                     m_Header.dwFlags &= ~SLDF_HAS_EXP_SZ;
2369                     RemoveDataBlock(EXP_SZ_LINK_SIG);
2370 
2371                     /* Reset the pointer and go use the static buffer */
2372                     pInfo = NULL;
2373                 }
2374             }
2375             if (!pInfo)
2376             {
2377                 /* Use the static buffer */
2378                 pInfo = &buffer;
2379                 buffer.cbSize = sizeof(buffer);
2380                 buffer.dwSignature = EXP_SZ_LINK_SIG;
2381             }
2382 
2383             lstrcpynW(pInfo->szwTarget, pszFile, _countof(pInfo->szwTarget));
2384             WideCharToMultiByte(CP_ACP, 0, pszFile, -1,
2385                                 pInfo->szTarget, _countof(pInfo->szTarget), NULL, NULL);
2386 
2387             hr = S_OK;
2388             if (pInfo == &buffer)
2389                 hr = AddDataBlock(pInfo);
2390             if (hr == S_OK)
2391                 m_Header.dwFlags |= SLDF_HAS_EXP_SZ;
2392 
2393             /* Now, make pszFile point to the expanded path */
2394             pszFile = szPath;
2395         }
2396         else
2397         {
2398             /*
2399              * The user-given file path does not contain unexpanded environment
2400              * variables, so we need to remove any target environment block.
2401              */
2402             m_Header.dwFlags &= ~SLDF_HAS_EXP_SZ;
2403             RemoveDataBlock(EXP_SZ_LINK_SIG);
2404 
2405             /* pszFile points to the user path */
2406         }
2407 
2408         /* Set the target */
2409         hr = SetTargetFromPIDLOrPath(NULL, pszFile);
2410     }
2411 
2412     m_bDirty = TRUE;
2413 
2414 end:
2415     HeapFree(GetProcessHeap(), 0, unquoted);
2416     return hr;
2417 }
2418 
2419 HRESULT STDMETHODCALLTYPE CShellLink::AddDataBlock(void* pDataBlock)
2420 {
2421     if (SHAddDataBlock(&m_pDBList, (DATABLOCK_HEADER*)pDataBlock))
2422     {
2423         m_bDirty = TRUE;
2424         return S_OK;
2425     }
2426     return S_FALSE;
2427 }
2428 
2429 HRESULT STDMETHODCALLTYPE CShellLink::CopyDataBlock(DWORD dwSig, void** ppDataBlock)
2430 {
2431     DATABLOCK_HEADER* pBlock;
2432     PVOID pDataBlock;
2433 
2434     TRACE("%p %08x %p\n", this, dwSig, ppDataBlock);
2435 
2436     *ppDataBlock = NULL;
2437 
2438     pBlock = SHFindDataBlock(m_pDBList, dwSig);
2439     if (!pBlock)
2440     {
2441         ERR("unknown datablock %08x (not found)\n", dwSig);
2442         return E_FAIL;
2443     }
2444 
2445     pDataBlock = LocalAlloc(LMEM_ZEROINIT, pBlock->cbSize);
2446     if (!pDataBlock)
2447         return E_OUTOFMEMORY;
2448 
2449     CopyMemory(pDataBlock, pBlock, pBlock->cbSize);
2450 
2451     *ppDataBlock = pDataBlock;
2452     return S_OK;
2453 }
2454 
2455 HRESULT STDMETHODCALLTYPE CShellLink::RemoveDataBlock(DWORD dwSig)
2456 {
2457     if (SHRemoveDataBlock(&m_pDBList, dwSig))
2458     {
2459         m_bDirty = TRUE;
2460         return S_OK;
2461     }
2462     return S_FALSE;
2463 }
2464 
2465 HRESULT STDMETHODCALLTYPE CShellLink::GetFlags(DWORD *pdwFlags)
2466 {
2467     TRACE("%p %p\n", this, pdwFlags);
2468     *pdwFlags = m_Header.dwFlags;
2469     return S_OK;
2470 }
2471 
2472 HRESULT STDMETHODCALLTYPE CShellLink::SetFlags(DWORD dwFlags)
2473 {
2474 #if 0 // FIXME!
2475     m_Header.dwFlags = dwFlags;
2476     m_bDirty = TRUE;
2477     return S_OK;
2478 #else
2479     FIXME("\n");
2480     return E_NOTIMPL;
2481 #endif
2482 }
2483 
2484 /**************************************************************************
2485  * CShellLink implementation of IShellExtInit::Initialize()
2486  *
2487  * Loads the shelllink from the dataobject the shell is pointing to.
2488  */
2489 HRESULT STDMETHODCALLTYPE CShellLink::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
2490 {
2491     TRACE("%p %p %p %p\n", this, pidlFolder, pdtobj, hkeyProgID);
2492 
2493     if (!pdtobj)
2494         return E_FAIL;
2495 
2496     FORMATETC format;
2497     format.cfFormat = CF_HDROP;
2498     format.ptd = NULL;
2499     format.dwAspect = DVASPECT_CONTENT;
2500     format.lindex = -1;
2501     format.tymed = TYMED_HGLOBAL;
2502 
2503     STGMEDIUM stgm;
2504     HRESULT hr = pdtobj->GetData(&format, &stgm);
2505     if (FAILED(hr))
2506         return hr;
2507 
2508     UINT count = DragQueryFileW((HDROP)stgm.hGlobal, -1, NULL, 0);
2509     if (count == 1)
2510     {
2511         count = DragQueryFileW((HDROP)stgm.hGlobal, 0, NULL, 0);
2512         count++;
2513         LPWSTR path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, count * sizeof(WCHAR));
2514         if (path)
2515         {
2516             count = DragQueryFileW((HDROP)stgm.hGlobal, 0, path, count);
2517             hr = Load(path, 0);
2518             HeapFree(GetProcessHeap(), 0, path);
2519         }
2520     }
2521     ReleaseStgMedium(&stgm);
2522 
2523     return S_OK;
2524 }
2525 
2526 HRESULT STDMETHODCALLTYPE CShellLink::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
2527 {
2528     INT id = 0;
2529 
2530     m_idCmdFirst = idCmdFirst;
2531 
2532     TRACE("%p %p %u %u %u %u\n", this,
2533           hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
2534 
2535     if (!hMenu)
2536         return E_INVALIDARG;
2537 
2538     CStringW strOpen(MAKEINTRESOURCEW(IDS_OPEN_VERB));
2539     CStringW strOpenFileLoc(MAKEINTRESOURCEW(IDS_OPENFILELOCATION));
2540 
2541     MENUITEMINFOW mii;
2542     ZeroMemory(&mii, sizeof(mii));
2543     mii.cbSize = sizeof(mii);
2544     mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE;
2545     mii.dwTypeData = strOpen.GetBuffer();
2546     mii.cch = wcslen(mii.dwTypeData);
2547     mii.wID = idCmdFirst + id++;
2548     mii.fState = MFS_DEFAULT | MFS_ENABLED;
2549     mii.fType = MFT_STRING;
2550     if (!InsertMenuItemW(hMenu, indexMenu++, TRUE, &mii))
2551         return E_FAIL;
2552 
2553     mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE;
2554     mii.dwTypeData = strOpenFileLoc.GetBuffer();
2555     mii.cch = wcslen(mii.dwTypeData);
2556     mii.wID = idCmdFirst + id++;
2557     mii.fState = MFS_ENABLED;
2558     mii.fType = MFT_STRING;
2559     if (!InsertMenuItemW(hMenu, indexMenu++, TRUE, &mii))
2560         return E_FAIL;
2561 
2562     UNREFERENCED_PARAMETER(indexMenu);
2563 
2564     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, id);
2565 }
2566 
2567 HRESULT CShellLink::DoOpenFileLocation()
2568 {
2569     WCHAR szParams[MAX_PATH + 64];
2570     StringCbPrintfW(szParams, sizeof(szParams), L"/select,%s", m_sPath);
2571 
2572     INT_PTR ret;
2573     ret = reinterpret_cast<INT_PTR>(ShellExecuteW(NULL, NULL, L"explorer.exe", szParams,
2574                                                   NULL, m_Header.nShowCommand));
2575     if (ret <= 32)
2576     {
2577         ERR("ret: %08lX\n", ret);
2578         return E_FAIL;
2579     }
2580 
2581     return S_OK;
2582 }
2583 
2584 HRESULT STDMETHODCALLTYPE CShellLink::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
2585 {
2586     TRACE("%p %p\n", this, lpici);
2587 
2588     if (lpici->cbSize < sizeof(CMINVOKECOMMANDINFO))
2589         return E_INVALIDARG;
2590 
2591     // NOTE: We could use lpici->hwnd (certainly in case lpici->fMask doesn't contain CMIC_MASK_FLAG_NO_UI)
2592     // as the parent window handle... ?
2593     /* FIXME: get using interface set from IObjectWithSite?? */
2594     // NOTE: We might need an extended version of Resolve that provides us with paths...
2595     HRESULT hr = Resolve(lpici->hwnd, (lpici->fMask & CMIC_MASK_FLAG_NO_UI) ? SLR_NO_UI : 0);
2596     if (FAILED(hr))
2597     {
2598         TRACE("failed to resolve component error 0x%08x\n", hr);
2599         return hr;
2600     }
2601 
2602     UINT idCmd = LOWORD(lpici->lpVerb);
2603     TRACE("idCmd: %d\n", idCmd);
2604 
2605     switch (idCmd)
2606     {
2607     case IDCMD_OPEN:
2608         return DoOpen(lpici);
2609     case IDCMD_OPENFILELOCATION:
2610         return DoOpenFileLocation();
2611     default:
2612         return E_NOTIMPL;
2613     }
2614 }
2615 
2616 HRESULT CShellLink::DoOpen(LPCMINVOKECOMMANDINFO lpici)
2617 {
2618     LPCMINVOKECOMMANDINFOEX iciex = (LPCMINVOKECOMMANDINFOEX)lpici;
2619     const BOOL unicode = IsUnicode(*lpici);
2620 
2621     CStringW args;
2622     if (m_sArgs)
2623         args = m_sArgs;
2624 
2625     if (unicode)
2626     {
2627         if (!StrIsNullOrEmpty(iciex->lpParametersW))
2628         {
2629             args += L' ';
2630             args += iciex->lpParametersW;
2631         }
2632     }
2633     else
2634     {
2635         CComHeapPtr<WCHAR> pszParams;
2636         if (!StrIsNullOrEmpty(lpici->lpParameters) && __SHCloneStrAtoW(&pszParams, lpici->lpParameters))
2637         {
2638             args += L' ';
2639             args += pszParams;
2640         }
2641     }
2642 
2643     WCHAR dir[MAX_PATH];
2644     SHELLEXECUTEINFOW sei = { sizeof(sei) };
2645     sei.fMask = SEE_MASK_HASLINKNAME | SEE_MASK_UNICODE | SEE_MASK_DOENVSUBST |
2646                (lpici->fMask & (SEE_MASK_NOASYNC | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI));
2647     sei.lpDirectory = m_sWorkDir;
2648     if (m_pPidl)
2649     {
2650         sei.lpIDList = m_pPidl;
2651         sei.fMask |= SEE_MASK_INVOKEIDLIST;
2652     }
2653     else
2654     {
2655         sei.lpFile = m_sPath;
2656         if (!(m_Header.dwFlags & SLDF_HAS_EXP_SZ))
2657         {
2658             sei.fMask &= ~SEE_MASK_DOENVSUBST; // The link does not want to expand lpFile
2659             if (m_sWorkDir && ExpandEnvironmentStringsW(m_sWorkDir, dir, _countof(dir)) <= _countof(dir))
2660                 sei.lpDirectory = dir;
2661         }
2662     }
2663     sei.lpParameters = args;
2664     sei.lpClass = m_sLinkPath;
2665     sei.nShow = m_Header.nShowCommand;
2666     if (lpici->nShow != SW_SHOWNORMAL && lpici->nShow != SW_SHOW)
2667         sei.nShow = lpici->nShow; // Allow invoker to override .lnk show mode
2668 
2669     // Use the invoker specified working directory if the link did not specify one
2670     if (StrIsNullOrEmpty(sei.lpDirectory) || !PathIsDirectoryW(sei.lpDirectory))
2671     {
2672         LPCSTR pszDirA = lpici->lpDirectory;
2673         if (unicode && !StrIsNullOrEmpty(iciex->lpDirectoryW))
2674             sei.lpDirectory = iciex->lpDirectoryW;
2675         else if (pszDirA && SHAnsiToUnicode(pszDirA, dir, _countof(dir)))
2676             sei.lpDirectory = dir;
2677     }
2678     return (ShellExecuteExW(&sei) ? S_OK : E_FAIL);
2679 }
2680 
2681 HRESULT STDMETHODCALLTYPE CShellLink::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pwReserved, LPSTR pszName, UINT cchMax)
2682 {
2683     FIXME("%p %lu %u %p %p %u\n", this, idCmd, uType, pwReserved, pszName, cchMax);
2684     return E_NOTIMPL;
2685 }
2686 
2687 INT_PTR CALLBACK ExtendedShortcutProc(HWND hwndDlg, UINT uMsg,
2688                                       WPARAM wParam, LPARAM lParam)
2689 {
2690     switch(uMsg)
2691     {
2692         case WM_INITDIALOG:
2693             if (lParam)
2694             {
2695                 HWND hDlgCtrl = GetDlgItem(hwndDlg, IDC_SHORTEX_RUN_DIFFERENT);
2696                 SendMessage(hDlgCtrl, BM_SETCHECK, BST_CHECKED, 0);
2697             }
2698             return TRUE;
2699         case WM_COMMAND:
2700         {
2701             HWND hDlgCtrl = GetDlgItem(hwndDlg, IDC_SHORTEX_RUN_DIFFERENT);
2702             if (LOWORD(wParam) == IDOK)
2703             {
2704                 if (SendMessage(hDlgCtrl, BM_GETCHECK, 0, 0) == BST_CHECKED)
2705                     EndDialog(hwndDlg, 1);
2706                 else
2707                     EndDialog(hwndDlg, 0);
2708             }
2709             else if (LOWORD(wParam) == IDCANCEL)
2710             {
2711                 EndDialog(hwndDlg, -1);
2712             }
2713             else if (LOWORD(wParam) == IDC_SHORTEX_RUN_DIFFERENT)
2714             {
2715                 if (SendMessage(hDlgCtrl, BM_GETCHECK, 0, 0) == BST_CHECKED)
2716                     SendMessage(hDlgCtrl, BM_SETCHECK, BST_UNCHECKED, 0);
2717                 else
2718                     SendMessage(hDlgCtrl, BM_SETCHECK, BST_CHECKED, 0);
2719             }
2720         }
2721     }
2722     return FALSE;
2723 }
2724 
2725 static void GetTypeDescriptionByPath(PCWSTR pszFullPath, DWORD fAttributes, PWSTR szBuf, UINT cchBuf)
2726 {
2727     if (fAttributes == INVALID_FILE_ATTRIBUTES && !PathFileExistsAndAttributesW(pszFullPath, &fAttributes))
2728         fAttributes = 0;
2729 
2730     SHFILEINFOW fi;
2731     if (!SHGetFileInfoW(pszFullPath, fAttributes, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES))
2732     {
2733         ERR("SHGetFileInfoW failed for %ls (%lu)\n", pszFullPath, GetLastError());
2734         fi.szTypeName[0] = UNICODE_NULL;
2735     }
2736 
2737     BOOL fFolder = (fAttributes & FILE_ATTRIBUTE_DIRECTORY);
2738     LPCWSTR pwszExt = fFolder ? L"" : PathFindExtensionW(pszFullPath);
2739     if (pwszExt[0])
2740     {
2741         if (!fi.szTypeName[0])
2742             StringCchPrintfW(szBuf, cchBuf, L"%s", pwszExt + 1);
2743         else
2744             StringCchPrintfW(szBuf, cchBuf, L"%s (%s)", fi.szTypeName, pwszExt);
2745     }
2746     else
2747     {
2748         StringCchPrintfW(szBuf, cchBuf, L"%s", fi.szTypeName);
2749     }
2750 }
2751 
2752 BOOL CShellLink::OnInitDialog(HWND hwndDlg, HWND hwndFocus, LPARAM lParam)
2753 {
2754     TRACE("CShellLink::OnInitDialog(hwnd %p hwndFocus %p lParam %p)\n", hwndDlg, hwndFocus, lParam);
2755 
2756     Resolve(0, SLR_NO_UI | SLR_NOUPDATE | SLR_NOSEARCH | SLR_NOTRACK);
2757 
2758     TRACE("m_sArgs: %S sComponent: %S m_sDescription: %S m_sIcoPath: %S m_sPath: %S m_sPathRel: %S sProduct: %S m_sWorkDir: %S\n", m_sArgs, sComponent, m_sDescription,
2759           m_sIcoPath, m_sPath, m_sPathRel, sProduct, m_sWorkDir);
2760 
2761     m_bInInit = TRUE;
2762     UINT darwin = m_Header.dwFlags & (SLDF_HAS_DARWINID);
2763 
2764     /* Get file information */
2765     // FIXME! FIXME! Shouldn't we use m_sIcoPath, m_Header.nIconIndex instead???
2766     SHFILEINFOW fi;
2767     if (!SHGetFileInfoW(m_sLinkPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_ICON))
2768     {
2769         ERR("SHGetFileInfoW failed for %ls (%lu)\n", m_sLinkPath, GetLastError());
2770         fi.szTypeName[0] = L'\0';
2771         fi.hIcon = NULL;
2772     }
2773 
2774     if (fi.hIcon)
2775     {
2776         if (m_hIcon)
2777             DestroyIcon(m_hIcon);
2778         m_hIcon = fi.hIcon;
2779         SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)m_hIcon, 0);
2780     }
2781     else
2782         ERR("ExtractIconW failed %ls %u\n", m_sIcoPath, m_Header.nIconIndex);
2783 
2784     if (!SHGetFileInfoW(m_sLinkPath, 0, &fi, sizeof(fi), SHGFI_DISPLAYNAME))
2785         fi.szDisplayName[0] = UNICODE_NULL;
2786     SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TEXT, fi.szDisplayName);
2787 
2788     /* Target type */
2789     if (m_sPath)
2790     {
2791         WCHAR buf[MAX_PATH];
2792         GetTypeDescriptionByPath(m_sPath, m_Header.dwFileAttributes, buf, _countof(buf));
2793         SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TYPE_EDIT, buf);
2794     }
2795 
2796     /* Target location */
2797     if (m_sPath)
2798     {
2799         WCHAR target[MAX_PATH];
2800         StringCchCopyW(target, _countof(target), m_sPath);
2801         PathRemoveFileSpecW(target);
2802         SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION_EDIT, PathFindFileNameW(target));
2803     }
2804 
2805     /* Target path */
2806     if (m_sPath)
2807     {
2808         WCHAR newpath[MAX_PATH * 2];
2809         newpath[0] = UNICODE_NULL;
2810         if (wcschr(m_sPath, ' '))
2811             StringCchPrintfExW(newpath, _countof(newpath), NULL, NULL, 0, L"\"%ls\"", m_sPath);
2812         else
2813             StringCchCopyExW(newpath, _countof(newpath), m_sPath, NULL, NULL, 0);
2814 
2815         if (m_sArgs && m_sArgs[0])
2816         {
2817             StringCchCatW(newpath, _countof(newpath), L" ");
2818             StringCchCatW(newpath, _countof(newpath), m_sArgs);
2819         }
2820         SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, newpath);
2821     }
2822 
2823     /* Working dir */
2824     if (m_sWorkDir)
2825         SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, m_sWorkDir);
2826 
2827     /* Description */
2828     if (m_sDescription)
2829         SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_COMMENT_EDIT, m_sDescription);
2830 
2831     /* Hot key */
2832     SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_KEY_HOTKEY, HKM_SETHOTKEY, m_Header.wHotKey, 0);
2833 
2834     /* Run */
2835     const WORD runstrings[] = { IDS_SHORTCUT_RUN_NORMAL, IDS_SHORTCUT_RUN_MIN, IDS_SHORTCUT_RUN_MAX };
2836     const DWORD runshowcmd[] = { SW_SHOWNORMAL, SW_SHOWMINNOACTIVE, SW_SHOWMAXIMIZED };
2837     HWND hRunCombo = GetDlgItem(hwndDlg, IDC_SHORTCUT_RUN_COMBO);
2838     for (UINT i = 0; i < _countof(runstrings); ++i)
2839     {
2840         WCHAR buf[MAX_PATH];
2841         if (!LoadStringW(shell32_hInstance, runstrings[i], buf, _countof(buf)))
2842             break;
2843 
2844         int index = SendMessageW(hRunCombo, CB_ADDSTRING, 0, (LPARAM)buf);
2845         if (index < 0)
2846             continue;
2847         SendMessageW(hRunCombo, CB_SETITEMDATA, index, runshowcmd[i]);
2848         if (!i || m_Header.nShowCommand == runshowcmd[i])
2849             SendMessageW(hRunCombo, CB_SETCURSEL, index, 0);
2850     }
2851 
2852     BOOL disablecontrols = FALSE;
2853     if (darwin)
2854     {
2855         disablecontrols = TRUE;
2856         EnableWindow(GetDlgItem(hwndDlg, IDC_SHORTCUT_FIND), FALSE);
2857         EnableWindow(GetDlgItem(hwndDlg, IDC_SHORTCUT_CHANGE_ICON), FALSE);
2858     }
2859     else
2860     {
2861         WCHAR path[MAX_PATH * 2];
2862         path[0] = UNICODE_NULL;
2863         HRESULT hr = GetPath(path, _countof(path), NULL, SLGP_RAWPATH);
2864         if (FAILED(hr))
2865             hr = GetPath(path, _countof(path), NULL, 0);
2866 #if DBG
2867         if (GetKeyState(VK_CONTROL) < 0) // Allow inspection of PIDL parsing path
2868         {
2869             hr = S_OK;
2870             path[0] = UNICODE_NULL;
2871         }
2872 #endif
2873         if (hr != S_OK)
2874         {
2875             disablecontrols = TRUE;
2876             LPITEMIDLIST pidl;
2877             if (GetIDList(&pidl) == S_OK)
2878             {
2879                 path[0] = UNICODE_NULL;
2880                 SHGetNameAndFlagsW(pidl, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, path, _countof(path), NULL);
2881                 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TYPE_EDIT, path);
2882                 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, path);
2883                 ILRemoveLastID(pidl);
2884                 path[0] = UNICODE_NULL;
2885                 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, path);
2886                 SHGetNameAndFlagsW(pidl, SHGDN_NORMAL, path, _countof(path), NULL);
2887                 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION_EDIT, path);
2888                 ILFree(pidl);
2889             }
2890             EnableWindow(GetDlgItem(hwndDlg, IDC_SHORTCUT_ADVANCED), FALSE);
2891             EnableWindow(GetDlgItem(hwndDlg, IDC_SHORTCUT_START_IN_EDIT), FALSE);
2892         }
2893         else
2894         {
2895             ASSERT(FAILED(hr) || !(path[0] == ':' && path[1] == ':' && path[2] == '{'));
2896         }
2897     }
2898 
2899     HWND hWndTarget = GetDlgItem(hwndDlg, IDC_SHORTCUT_TARGET_TEXT);
2900     EnableWindow(hWndTarget, !disablecontrols);
2901     PostMessage(hWndTarget, EM_SETSEL, 0, -1); // Fix caret bug when first opening the tab
2902 
2903     /* auto-completion */
2904     SHAutoComplete(hWndTarget, SHACF_DEFAULT);
2905     SHAutoComplete(GetDlgItem(hwndDlg, IDC_SHORTCUT_START_IN_EDIT), SHACF_DEFAULT);
2906 
2907     m_bInInit = FALSE;
2908 
2909     return TRUE;
2910 }
2911 
2912 void CShellLink::OnCommand(HWND hwndDlg, int id, HWND hwndCtl, UINT codeNotify)
2913 {
2914     switch (id)
2915     {
2916         case IDC_SHORTCUT_FIND:
2917             SHOpenFolderAndSelectItems(m_pPidl, 0, NULL, 0);
2918             return;
2919 
2920         case IDC_SHORTCUT_CHANGE_ICON:
2921         {
2922             WCHAR wszPath[MAX_PATH] = L"";
2923 
2924             if (m_sIcoPath)
2925                 wcscpy(wszPath, m_sIcoPath);
2926             else
2927                 FindExecutableW(m_sPath, NULL, wszPath);
2928 
2929             INT IconIndex = m_Header.nIconIndex;
2930             if (PickIconDlg(hwndDlg, wszPath, _countof(wszPath), &IconIndex))
2931             {
2932                 SetIconLocation(wszPath, IconIndex);
2933                 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
2934 
2935                 HICON hIconLarge = CreateShortcutIcon(wszPath, IconIndex);
2936                 if (hIconLarge)
2937                 {
2938                     if (m_hIcon)
2939                         DestroyIcon(m_hIcon);
2940                     m_hIcon = hIconLarge;
2941                     SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)m_hIcon, 0);
2942                 }
2943             }
2944             return;
2945         }
2946 
2947         case IDC_SHORTCUT_ADVANCED:
2948         {
2949             INT_PTR result = DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_SHORTCUT_EXTENDED_PROPERTIES), hwndDlg, ExtendedShortcutProc, (LPARAM)m_bRunAs);
2950             if (result == 1 || result == 0)
2951             {
2952                 if (m_bRunAs != result)
2953                 {
2954                     PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
2955                 }
2956 
2957                 m_bRunAs = result;
2958             }
2959             return;
2960         }
2961     }
2962     if (codeNotify == EN_CHANGE || codeNotify == CBN_SELCHANGE)
2963     {
2964         if (!m_bInInit)
2965             PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
2966     }
2967 }
2968 
2969 LRESULT CShellLink::OnNotify(HWND hwndDlg, int idFrom, LPNMHDR pnmhdr)
2970 {
2971     WCHAR wszBuf[MAX_PATH];
2972     LPPSHNOTIFY lppsn = (LPPSHNOTIFY)pnmhdr;
2973 
2974     if (lppsn->hdr.code == PSN_APPLY)
2975     {
2976         /* set working directory */
2977         GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, wszBuf, _countof(wszBuf));
2978         SetWorkingDirectory(wszBuf);
2979 
2980         /* set link destination */
2981         GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, wszBuf, _countof(wszBuf));
2982         LPWSTR lpszArgs = NULL;
2983         LPWSTR unquoted = strdupW(wszBuf);
2984         StrTrimW(unquoted, L" ");
2985 
2986         if (!PathFileExistsW(unquoted))
2987         {
2988             lpszArgs = PathGetArgsW(unquoted);
2989             PathRemoveArgsW(unquoted);
2990             StrTrimW(lpszArgs, L" ");
2991         }
2992         if (unquoted[0] == '"' && unquoted[wcslen(unquoted) - 1] == '"')
2993             PathUnquoteSpacesW(unquoted);
2994 
2995         WCHAR *pwszExt = PathFindExtensionW(unquoted);
2996         if (!_wcsicmp(pwszExt, L".lnk"))
2997         {
2998             // FIXME load localized error msg
2999             MessageBoxW(hwndDlg, L"You cannot create a link to a shortcut", L"Error", MB_ICONERROR);
3000             SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
3001             return TRUE;
3002         }
3003 
3004         if (!PathFileExistsW(unquoted))
3005         {
3006             // FIXME load localized error msg
3007             MessageBoxW(hwndDlg, L"The specified file name in the target box is invalid", L"Error", MB_ICONERROR);
3008             SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
3009             return TRUE;
3010         }
3011 
3012         SetPath(unquoted);
3013         if (lpszArgs)
3014             SetArguments(lpszArgs);
3015         else
3016             SetArguments(L"\0");
3017 
3018         HeapFree(GetProcessHeap(), 0, unquoted);
3019 
3020         m_Header.wHotKey = (WORD)SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_KEY_HOTKEY, HKM_GETHOTKEY, 0, 0);
3021 
3022         int index = (int)SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_RUN_COMBO, CB_GETCURSEL, 0, 0);
3023         if (index != CB_ERR)
3024         {
3025             m_Header.nShowCommand = (UINT)SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_RUN_COMBO, CB_GETITEMDATA, index, 0);
3026         }
3027 
3028         TRACE("This %p m_sLinkPath %S\n", this, m_sLinkPath);
3029         Save(m_sLinkPath, TRUE);
3030         SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_sLinkPath, NULL);
3031         SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
3032         return TRUE;
3033     }
3034     return FALSE;
3035 }
3036 
3037 void CShellLink::OnDestroy(HWND hwndDlg)
3038 {
3039     if (m_hIcon)
3040     {
3041         DestroyIcon(m_hIcon);
3042         m_hIcon = NULL;
3043     }
3044 }
3045 
3046 /**************************************************************************
3047  * SH_ShellLinkDlgProc
3048  *
3049  * dialog proc of the shortcut property dialog
3050  */
3051 
3052 INT_PTR CALLBACK
3053 CShellLink::SH_ShellLinkDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
3054 {
3055     LPPROPSHEETPAGEW ppsp;
3056     CShellLink *pThis = reinterpret_cast<CShellLink *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
3057 
3058     switch (uMsg)
3059     {
3060         case WM_INITDIALOG:
3061             ppsp = (LPPROPSHEETPAGEW)lParam;
3062             if (ppsp == NULL)
3063                 break;
3064 
3065             pThis = reinterpret_cast<CShellLink *>(ppsp->lParam);
3066             SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pThis);
3067             return pThis->OnInitDialog(hwndDlg, (HWND)(wParam), lParam);
3068 
3069         case WM_NOTIFY:
3070             return pThis->OnNotify(hwndDlg, (int)wParam, (NMHDR *)lParam);
3071 
3072         case WM_COMMAND:
3073             pThis->OnCommand(hwndDlg, LOWORD(wParam), (HWND)lParam, HIWORD(wParam));
3074             break;
3075 
3076         case WM_DESTROY:
3077             pThis->OnDestroy(hwndDlg);
3078             break;
3079 
3080         default:
3081             break;
3082     }
3083 
3084     return FALSE;
3085 }
3086 
3087 /**************************************************************************
3088  * ShellLink_IShellPropSheetExt interface
3089  */
3090 
3091 HRESULT STDMETHODCALLTYPE CShellLink::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
3092 {
3093     HPROPSHEETPAGE hPage = SH_CreatePropertySheetPageEx(IDD_SHORTCUT_PROPERTIES, SH_ShellLinkDlgProc,
3094                                                         (LPARAM)this, NULL, &PropSheetPageLifetimeCallback<CShellLink>);
3095     HRESULT hr = AddPropSheetPage(hPage, pfnAddPage, lParam);
3096     if (FAILED_UNEXPECTEDLY(hr))
3097         return hr;
3098     else
3099         AddRef(); // For PropSheetPageLifetimeCallback
3100     enum { CShellLink_PageIndex_Shortcut = 0 };
3101     return 1 + CShellLink_PageIndex_Shortcut; // Make this page the default (one-based)
3102 }
3103 
3104 HRESULT STDMETHODCALLTYPE CShellLink::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam)
3105 {
3106     TRACE("(%p) (uPageID %u, pfnReplacePage %p lParam %p\n", this, uPageID, pfnReplacePage, lParam);
3107     return E_NOTIMPL;
3108 }
3109 
3110 HRESULT STDMETHODCALLTYPE CShellLink::SetSite(IUnknown *punk)
3111 {
3112     TRACE("%p %p\n", this, punk);
3113 
3114     m_site = punk;
3115 
3116     return S_OK;
3117 }
3118 
3119 HRESULT STDMETHODCALLTYPE CShellLink::GetSite(REFIID iid, void ** ppvSite)
3120 {
3121     TRACE("%p %s %p\n", this, debugstr_guid(&iid), ppvSite);
3122 
3123     if (m_site == NULL)
3124         return E_FAIL;
3125 
3126     return m_site->QueryInterface(iid, ppvSite);
3127 }
3128 
3129 HRESULT STDMETHODCALLTYPE CShellLink::DragEnter(IDataObject *pDataObject,
3130     DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
3131 {
3132     TRACE("(%p)->(DataObject=%p)\n", this, pDataObject);
3133 
3134     if (*pdwEffect == DROPEFFECT_NONE)
3135         return S_OK;
3136 
3137     HRESULT hr = SHELL_GetUIObjectOfAbsoluteItem(NULL, m_pPidl, IID_PPV_ARG(IDropTarget, &m_DropTarget));
3138     if (SUCCEEDED(hr))
3139         hr = m_DropTarget->DragEnter(pDataObject, dwKeyState, pt, pdwEffect);
3140     else
3141         *pdwEffect = DROPEFFECT_NONE;
3142 
3143     return S_OK;
3144 }
3145 
3146 HRESULT STDMETHODCALLTYPE CShellLink::DragOver(DWORD dwKeyState, POINTL pt,
3147     DWORD *pdwEffect)
3148 {
3149     TRACE("(%p)\n", this);
3150     HRESULT hr = S_OK;
3151     if (m_DropTarget)
3152         hr = m_DropTarget->DragOver(dwKeyState, pt, pdwEffect);
3153     return hr;
3154 }
3155 
3156 HRESULT STDMETHODCALLTYPE CShellLink::DragLeave()
3157 {
3158     TRACE("(%p)\n", this);
3159     HRESULT hr = S_OK;
3160     if (m_DropTarget)
3161     {
3162         hr = m_DropTarget->DragLeave();
3163         m_DropTarget.Release();
3164     }
3165 
3166     return hr;
3167 }
3168 
3169 HRESULT STDMETHODCALLTYPE CShellLink::Drop(IDataObject *pDataObject,
3170     DWORD dwKeyState, POINTL pt, DWORD *pdwEffect)
3171 {
3172     TRACE("(%p)\n", this);
3173     HRESULT hr = S_OK;
3174     if (m_DropTarget)
3175         hr = m_DropTarget->Drop(pDataObject, dwKeyState, pt, pdwEffect);
3176 
3177     return hr;
3178 }
3179 
3180 /**************************************************************************
3181  *      IShellLink_ConstructFromFile
3182  */
3183 HRESULT WINAPI IShellLink_ConstructFromPath(WCHAR *path, REFIID riid, LPVOID *ppv)
3184 {
3185     CComPtr<IPersistFile> ppf;
3186     HRESULT hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IPersistFile, &ppf));
3187     if (FAILED(hr))
3188         return hr;
3189 
3190     hr = ppf->Load(path, 0);
3191     if (FAILED(hr))
3192         return hr;
3193 
3194     return ppf->QueryInterface(riid, ppv);
3195 }
3196 
3197 HRESULT WINAPI IShellLink_ConstructFromFile(IShellFolder * psf, LPCITEMIDLIST pidl, REFIID riid, LPVOID *ppv)
3198 {
3199     WCHAR path[MAX_PATH];
3200     if (!ILGetDisplayNameExW(psf, pidl, path, 0))
3201         return E_FAIL;
3202 
3203     return IShellLink_ConstructFromPath(path, riid, ppv);
3204 }
3205 
3206 HICON CShellLink::CreateShortcutIcon(LPCWSTR wszIconPath, INT IconIndex)
3207 {
3208     const INT cx = GetSystemMetrics(SM_CXICON), cy = GetSystemMetrics(SM_CYICON);
3209     const COLORREF crMask = GetSysColor(COLOR_3DFACE);
3210     WCHAR wszLnkIcon[MAX_PATH];
3211     int lnk_idx;
3212     HDC hDC;
3213     HIMAGELIST himl = ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, 1, 1);
3214     HICON hIcon = NULL, hNewIcon = NULL, hShortcut;
3215 
3216     if (HLM_GetIconW(IDI_SHELL_SHORTCUT - 1, wszLnkIcon, _countof(wszLnkIcon), &lnk_idx))
3217     {
3218         ::ExtractIconExW(wszLnkIcon, lnk_idx, &hShortcut, NULL, 1);
3219     }
3220     else
3221     {
3222         hShortcut = (HICON)LoadImageW(shell32_hInstance, MAKEINTRESOURCE(IDI_SHELL_SHORTCUT),
3223                                       IMAGE_ICON, cx, cy, 0);
3224     }
3225 
3226     ::ExtractIconExW(wszIconPath, IconIndex, &hIcon, NULL, 1);
3227     if (!hIcon || !hShortcut || !himl)
3228         goto cleanup;
3229 
3230     hDC = CreateCompatibleDC(NULL);
3231     if (hDC)
3232     {
3233         // create 32bpp bitmap
3234         BITMAPINFO bi;
3235         ZeroMemory(&bi, sizeof(bi));
3236         bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
3237         bi.bmiHeader.biWidth = cx;
3238         bi.bmiHeader.biHeight = cy;
3239         bi.bmiHeader.biPlanes = 1;
3240         bi.bmiHeader.biBitCount = 32;
3241         LPVOID pvBits;
3242         HBITMAP hbm = CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, &pvBits, NULL, 0);
3243         if (hbm)
3244         {
3245             // draw the icon image
3246             HGDIOBJ hbmOld = SelectObject(hDC, hbm);
3247             {
3248                 HBRUSH hbr = CreateSolidBrush(crMask);
3249                 RECT rc = { 0, 0, cx, cy };
3250                 FillRect(hDC, &rc, hbr);
3251                 DeleteObject(hbr);
3252 
3253                 DrawIconEx(hDC, 0, 0, hIcon, cx, cy, 0, NULL, DI_NORMAL);
3254                 DrawIconEx(hDC, 0, 0, hShortcut, cx, cy, 0, NULL, DI_NORMAL);
3255             }
3256             SelectObject(hDC, hbmOld);
3257 
3258             INT iAdded = ImageList_AddMasked(himl, hbm, crMask);
3259             hNewIcon = ImageList_GetIcon(himl, iAdded, ILD_NORMAL | ILD_TRANSPARENT);
3260 
3261             DeleteObject(hbm);
3262         }
3263         DeleteDC(hDC);
3264     }
3265 
3266 cleanup:
3267     if (hIcon)
3268         DestroyIcon(hIcon);
3269     if (hShortcut)
3270         DestroyIcon(hShortcut);
3271     if (himl)
3272         ImageList_Destroy(himl);
3273 
3274     return hNewIcon;
3275 }
3276