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