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