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