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