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