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