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