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