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