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