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