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