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