1 /*
2  * IQueryAssociations object and helper functions
3  *
4  * Copyright 2002 Jon Griffiths
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include "precomp.h"
22 
23 WINE_DEFAULT_DEBUG_CHANNEL(shell);
24 
25 /**************************************************************************
26  *  IQueryAssociations
27  *
28  * DESCRIPTION
29  *  This object provides a layer of abstraction over the system registry in
30  *  order to simplify the process of parsing associations between files.
31  *  Associations in this context means the registry entries that link (for
32  *  example) the extension of a file with its description, list of
33  *  applications to open the file with, and actions that can be performed on it
34  *  (the shell displays such information in the context menu of explorer
35  *  when you right-click on a file).
36  *
37  * HELPERS
38  * You can use this object transparently by calling the helper functions
39  * AssocQueryKeyA(), AssocQueryStringA() and AssocQueryStringByKeyA(). These
40  * create an IQueryAssociations object, perform the requested actions
41  * and then dispose of the object. Alternatively, you can create an instance
42  * of the object using AssocCreate() and call the following methods on it:
43  *
44  * METHODS
45  */
46 
47 CQueryAssociations::CQueryAssociations() : hkeySource(0), hkeyProgID(0)
48 {
49 }
50 
51 CQueryAssociations::~CQueryAssociations()
52 {
53 }
54 
55 /**************************************************************************
56  *  IQueryAssociations_Init
57  *
58  * Initialise an IQueryAssociations object.
59  *
60  * PARAMS
61  *  cfFlags    [I] ASSOCF_ flags from "shlwapi.h"
62  *  pszAssoc   [I] String for the root key name, or NULL if hkeyProgid is given
63  *  hkeyProgid [I] Handle for the root key, or NULL if pszAssoc is given
64  *  hWnd       [I] Reserved, must be NULL.
65  *
66  * RETURNS
67  *  Success: S_OK. iface is initialised with the parameters given.
68  *  Failure: An HRESULT error code indicating the error.
69  */
70 HRESULT STDMETHODCALLTYPE CQueryAssociations::Init(
71     ASSOCF cfFlags,
72     LPCWSTR pszAssoc,
73     HKEY hkeyProgid,
74     HWND hWnd)
75 {
76     static const WCHAR szProgID[] = L"ProgID";
77 
78     TRACE("(%p)->(%d,%s,%p,%p)\n", this,
79                                     cfFlags,
80                                     debugstr_w(pszAssoc),
81                                     hkeyProgid,
82                                     hWnd);
83 
84     if (hWnd != NULL)
85     {
86         FIXME("hwnd != NULL not supported\n");
87     }
88 
89     if (cfFlags != 0)
90     {
91         FIXME("unsupported flags: %x\n", cfFlags);
92     }
93 
94     RegCloseKey(this->hkeySource);
95     RegCloseKey(this->hkeyProgID);
96     this->hkeySource = this->hkeyProgID = NULL;
97     if (pszAssoc != NULL)
98     {
99         WCHAR *progId;
100         HRESULT hr;
101 
102         LONG ret = RegOpenKeyExW(HKEY_CLASSES_ROOT,
103                             pszAssoc,
104                             0,
105                             KEY_READ,
106                             &this->hkeySource);
107         if (ret)
108         {
109             return S_OK;
110         }
111 
112         /* if this is a progid */
113         if (*pszAssoc != '.' && *pszAssoc != '{')
114         {
115             this->hkeyProgID = this->hkeySource;
116             return S_OK;
117         }
118 
119         /* if it's not a progid, it's a file extension or clsid */
120         if (*pszAssoc == '.')
121         {
122             /* for a file extension, the progid is the default value */
123             hr = this->GetValue(this->hkeySource, NULL, (void**)&progId, NULL);
124             if (FAILED(hr))
125                 return S_OK;
126         }
127         else /* if (*pszAssoc == '{') */
128         {
129             HKEY progIdKey;
130             /* for a clsid, the progid is the default value of the ProgID subkey */
131             ret = RegOpenKeyExW(this->hkeySource,
132                                 szProgID,
133                                 0,
134                                 KEY_READ,
135                                 &progIdKey);
136             if (ret != ERROR_SUCCESS)
137                 return S_OK;
138             hr = this->GetValue(progIdKey, NULL, (void**)&progId, NULL);
139             if (FAILED(hr))
140                 return S_OK;
141             RegCloseKey(progIdKey);
142         }
143 
144         /* open the actual progid key, the one with the shell subkey */
145         ret = RegOpenKeyExW(HKEY_CLASSES_ROOT,
146                             progId,
147                             0,
148                             KEY_READ,
149                             &this->hkeyProgID);
150         HeapFree(GetProcessHeap(), 0, progId);
151 
152         return S_OK;
153     }
154     else if (hkeyProgid != NULL)
155     {
156         /* reopen the key so we don't end up closing a key owned by the caller */
157         RegOpenKeyExW(hkeyProgid, NULL, 0, KEY_READ, &this->hkeyProgID);
158         this->hkeySource = this->hkeyProgID;
159         return S_OK;
160     }
161     else
162         return E_INVALIDARG;
163 }
164 
165 /**************************************************************************
166  *  IQueryAssociations_GetString
167  *
168  * Get a file association string from the registry.
169  *
170  * PARAMS
171  *  cfFlags  [I]   ASSOCF_ flags from "shlwapi.h"
172  *  str      [I]   Type of string to get (ASSOCSTR enum from "shlwapi.h")
173  *  pszExtra [I]   Extra information about the string location
174  *  pszOut   [O]   Destination for the association string
175  *  pcchOut  [I/O] Length of pszOut
176  *
177  * RETURNS
178  *  Success: S_OK. pszOut contains the string, pcchOut contains its length.
179  *  Failure: An HRESULT error code indicating the error.
180  */
181 HRESULT STDMETHODCALLTYPE CQueryAssociations::GetString(
182     ASSOCF flags,
183     ASSOCSTR str,
184     LPCWSTR pszExtra,
185     LPWSTR pszOut,
186     DWORD *pcchOut)
187 {
188     const ASSOCF unimplemented_flags = ~ASSOCF_NOTRUNCATE;
189     DWORD len = 0;
190     HRESULT hr;
191     WCHAR path[MAX_PATH];
192 
193     TRACE("(%p)->(0x%08x, %u, %s, %p, %p)\n", this, flags, str, debugstr_w(pszExtra), pszOut, pcchOut);
194     if (flags & unimplemented_flags)
195     {
196         FIXME("%08x: unimplemented flags\n", flags & unimplemented_flags);
197     }
198 
199     if (!pcchOut)
200     {
201         return E_UNEXPECTED;
202     }
203 
204     if (!this->hkeySource && !this->hkeyProgID)
205     {
206         return HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);
207     }
208 
209     switch (str)
210     {
211         case ASSOCSTR_COMMAND:
212         {
213             WCHAR *command;
214             hr = this->GetCommand(pszExtra, &command);
215             if (SUCCEEDED(hr))
216             {
217                 hr = this->ReturnString(flags, pszOut, pcchOut, command, strlenW(command) + 1);
218                 HeapFree(GetProcessHeap(), 0, command);
219             }
220             return hr;
221         }
222         case ASSOCSTR_EXECUTABLE:
223         {
224             hr = this->GetExecutable(pszExtra, path, MAX_PATH, &len);
225             if (FAILED(hr))
226             {
227                 return hr;
228             }
229             len++;
230             return this->ReturnString(flags, pszOut, pcchOut, path, len);
231         }
232         case ASSOCSTR_FRIENDLYDOCNAME:
233         {
234             WCHAR *pszFileType;
235 
236             hr = this->GetValue(this->hkeySource, NULL, (void**)&pszFileType, NULL);
237             if (FAILED(hr))
238             {
239                 return hr;
240             }
241             DWORD size = 0;
242             DWORD ret = RegGetValueW(HKEY_CLASSES_ROOT, pszFileType, NULL, RRF_RT_REG_SZ, NULL, NULL, &size);
243             if (ret == ERROR_SUCCESS)
244             {
245                 WCHAR *docName = static_cast<WCHAR *>(HeapAlloc(GetProcessHeap(), 0, size));
246                 if (docName)
247                 {
248                     ret = RegGetValueW(HKEY_CLASSES_ROOT, pszFileType, NULL, RRF_RT_REG_SZ, NULL, docName, &size);
249                     if (ret == ERROR_SUCCESS)
250                     {
251                         hr = this->ReturnString(flags, pszOut, pcchOut, docName, strlenW(docName) + 1);
252                     }
253                     else
254                     {
255                         hr = HRESULT_FROM_WIN32(ret);
256                     }
257                     HeapFree(GetProcessHeap(), 0, docName);
258                 }
259                 else
260                 {
261                     hr = E_OUTOFMEMORY;
262                 }
263             }
264             else
265             {
266                 hr = HRESULT_FROM_WIN32(ret);
267             }
268             HeapFree(GetProcessHeap(), 0, pszFileType);
269             return hr;
270         }
271         case ASSOCSTR_FRIENDLYAPPNAME:
272         {
273             PVOID verinfoW = NULL;
274             DWORD size, retval = 0;
275             UINT flen;
276             WCHAR *bufW;
277             static const WCHAR translationW[] = L"\\VarFileInfo\\Translation";
278             static const WCHAR fileDescFmtW[] = L"\\StringFileInfo\\%04x%04x\\FileDescription";
279             WCHAR fileDescW[41];
280 
281             hr = this->GetExecutable(pszExtra, path, MAX_PATH, &len);
282             if (FAILED(hr))
283             {
284                 return hr;
285             }
286             retval = GetFileVersionInfoSizeW(path, &size);
287             if (!retval)
288             {
289                 goto get_friendly_name_fail;
290             }
291             verinfoW = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, retval);
292             if (!verinfoW)
293             {
294                 return E_OUTOFMEMORY;
295             }
296             if (!GetFileVersionInfoW(path, 0, retval, verinfoW))
297             {
298                 goto get_friendly_name_fail;
299             }
300             if (VerQueryValueW(verinfoW, translationW, (LPVOID *)&bufW, &flen))
301             {
302                 UINT i;
303                 DWORD *langCodeDesc = (DWORD *)bufW;
304                 for (i = 0; i < flen / sizeof(DWORD); i++)
305                 {
306                     sprintfW(fileDescW, fileDescFmtW, LOWORD(langCodeDesc[i]), HIWORD(langCodeDesc[i]));
307                     if (VerQueryValueW(verinfoW, fileDescW, (LPVOID *)&bufW, &flen))
308                     {
309                         /* Does strlenW(bufW) == 0 mean we use the filename? */
310                         len = strlenW(bufW) + 1;
311                         TRACE("found FileDescription: %s\n", debugstr_w(bufW));
312                         hr = this->ReturnString(flags, pszOut, pcchOut, bufW, len);
313                         HeapFree(GetProcessHeap(), 0, verinfoW);
314                         return hr;
315                     }
316                 }
317             }
318         get_friendly_name_fail:
319             PathRemoveExtensionW(path);
320             PathStripPathW(path);
321             TRACE("using filename: %s\n", debugstr_w(path));
322             hr = this->ReturnString(flags, pszOut, pcchOut, path, strlenW(path) + 1);
323             HeapFree(GetProcessHeap(), 0, verinfoW);
324             return hr;
325         }
326         case ASSOCSTR_CONTENTTYPE:
327         {
328             static const WCHAR Content_TypeW[] = L"Content Type";
329 
330             DWORD size = 0;
331             DWORD ret = RegGetValueW(this->hkeySource, NULL, Content_TypeW, RRF_RT_REG_SZ, NULL, NULL, &size);
332             if (ret != ERROR_SUCCESS)
333             {
334                 return HRESULT_FROM_WIN32(ret);
335             }
336             WCHAR *contentType = static_cast<WCHAR *>(HeapAlloc(GetProcessHeap(), 0, size));
337             if (contentType != NULL)
338             {
339                 ret = RegGetValueW(this->hkeySource, NULL, Content_TypeW, RRF_RT_REG_SZ, NULL, contentType, &size);
340                 if (ret == ERROR_SUCCESS)
341                 {
342                     hr = this->ReturnString(flags, pszOut, pcchOut, contentType, strlenW(contentType) + 1);
343                 }
344                 else
345                 {
346                     hr = HRESULT_FROM_WIN32(ret);
347                 }
348                 HeapFree(GetProcessHeap(), 0, contentType);
349             }
350             else
351             {
352                 hr = E_OUTOFMEMORY;
353             }
354             return hr;
355         }
356         case ASSOCSTR_DEFAULTICON:
357         {
358             static const WCHAR DefaultIconW[] = L"DefaultIcon";
359             DWORD ret;
360             DWORD size = 0;
361             ret = RegGetValueW(this->hkeyProgID, DefaultIconW, NULL, RRF_RT_REG_SZ, NULL, NULL, &size);
362             if (ret == ERROR_SUCCESS)
363             {
364                 WCHAR *icon = static_cast<WCHAR *>(HeapAlloc(GetProcessHeap(), 0, size));
365                 if (icon)
366                 {
367                     ret = RegGetValueW(this->hkeyProgID, DefaultIconW, NULL, RRF_RT_REG_SZ, NULL, icon, &size);
368                     if (ret == ERROR_SUCCESS)
369                     {
370                         hr = this->ReturnString(flags, pszOut, pcchOut, icon, strlenW(icon) + 1);
371                     }
372                     else
373                     {
374                         hr = HRESULT_FROM_WIN32(ret);
375                     }
376                     HeapFree(GetProcessHeap(), 0, icon);
377                 }
378                 else
379                 {
380                     hr = HRESULT_FROM_WIN32(ret);
381                 }
382             }
383             else
384             {
385                 hr = HRESULT_FROM_WIN32(ret);
386             }
387             return hr;
388         }
389         case ASSOCSTR_SHELLEXTENSION:
390         {
391             static const WCHAR shellexW[] = L"ShellEx\\";
392             WCHAR keypath[sizeof(shellexW) / sizeof(shellexW[0]) + 39], guid[39];
393             CLSID clsid;
394             HKEY hkey;
395 
396             hr = CLSIDFromString(pszExtra, &clsid);
397             if (FAILED(hr))
398             {
399                 return hr;
400             }
401             strcpyW(keypath, shellexW);
402             strcatW(keypath, pszExtra);
403             LONG ret = RegOpenKeyExW(this->hkeySource, keypath, 0, KEY_READ, &hkey);
404             if (ret)
405             {
406                 return HRESULT_FROM_WIN32(ret);
407             }
408             DWORD size = sizeof(guid);
409             ret = RegGetValueW(hkey, NULL, NULL, RRF_RT_REG_SZ, NULL, guid, &size);
410             RegCloseKey(hkey);
411             if (ret)
412             {
413                 return HRESULT_FROM_WIN32(ret);
414             }
415             return this->ReturnString(flags, pszOut, pcchOut, guid, size / sizeof(WCHAR));
416         }
417 
418         default:
419         {
420             FIXME("assocstr %d unimplemented!\n", str);
421             return E_NOTIMPL;
422         }
423     }
424 }
425 
426 /**************************************************************************
427  *  IQueryAssociations_GetKey
428  *
429  * Get a file association key from the registry.
430  *
431  * PARAMS
432  *  cfFlags  [I] ASSOCF_ flags from "shlwapi.h"
433  *  assockey [I] Type of key to get (ASSOCKEY enum from "shlwapi.h")
434  *  pszExtra [I] Extra information about the key location
435  *  phkeyOut [O] Destination for the association key
436  *
437  * RETURNS
438  *  Success: S_OK. phkeyOut contains a handle to the key.
439  *  Failure: An HRESULT error code indicating the error.
440  */
441 HRESULT STDMETHODCALLTYPE CQueryAssociations::GetKey(
442     ASSOCF cfFlags,
443     ASSOCKEY assockey,
444     LPCWSTR pszExtra,
445     HKEY *phkeyOut)
446 {
447     FIXME("(%p,0x%8x,0x%8x,%s,%p)-stub!\n", this, cfFlags, assockey,
448             debugstr_w(pszExtra), phkeyOut);
449     return E_NOTIMPL;
450 }
451 
452 /**************************************************************************
453  *  IQueryAssociations_GetData
454  *
455  * Get the data for a file association key from the registry.
456  *
457  * PARAMS
458  *  cfFlags   [I]   ASSOCF_ flags from "shlwapi.h"
459  *  assocdata [I]   Type of data to get (ASSOCDATA enum from "shlwapi.h")
460  *  pszExtra  [I]   Extra information about the data location
461  *  pvOut     [O]   Destination for the association key
462  *  pcbOut    [I/O] Size of pvOut
463  *
464  * RETURNS
465  *  Success: S_OK. pszOut contains the data, pcbOut contains its length.
466  *  Failure: An HRESULT error code indicating the error.
467  */
468 HRESULT STDMETHODCALLTYPE CQueryAssociations::GetData(ASSOCF cfFlags, ASSOCDATA assocdata, LPCWSTR pszExtra, LPVOID pvOut, DWORD *pcbOut)
469 {
470     static const WCHAR edit_flags[] = L"EditFlags";
471 
472     TRACE("(%p,0x%8x,0x%8x,%s,%p,%p)\n", this, cfFlags, assocdata,
473             debugstr_w(pszExtra), pvOut, pcbOut);
474 
475     if(cfFlags)
476     {
477         FIXME("Unsupported flags: %x\n", cfFlags);
478     }
479 
480     switch(assocdata)
481     {
482         case ASSOCDATA_EDITFLAGS:
483         {
484             if(!this->hkeyProgID)
485             {
486                 return HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);
487             }
488 
489             void *data;
490             DWORD size;
491             HRESULT hres = this->GetValue(this->hkeyProgID, edit_flags, &data, &size);
492             if(FAILED(hres))
493             {
494                 return hres;
495             }
496 
497             if (!pcbOut)
498             {
499                 HeapFree(GetProcessHeap(), 0, data);
500                 return hres;
501             }
502 
503             hres = this->ReturnData(pvOut, pcbOut, data, size);
504             HeapFree(GetProcessHeap(), 0, data);
505             return hres;
506         }
507         default:
508         {
509             FIXME("Unsupported ASSOCDATA value: %d\n", assocdata);
510             return E_NOTIMPL;
511         }
512     }
513 }
514 
515 /**************************************************************************
516  *  IQueryAssociations_GetEnum
517  *
518  * Not yet implemented in native Win32.
519  *
520  * PARAMS
521  *  cfFlags   [I] ASSOCF_ flags from "shlwapi.h"
522  *  assocenum [I] Type of enum to get (ASSOCENUM enum from "shlwapi.h")
523  *  pszExtra  [I] Extra information about the enum location
524  *  riid      [I] REFIID to look for
525  *  ppvOut    [O] Destination for the interface.
526  *
527  * RETURNS
528  *  Success: S_OK.
529  *  Failure: An HRESULT error code indicating the error.
530  *
531  * NOTES
532  *  Presumably this function returns an enumerator object.
533  */
534 HRESULT STDMETHODCALLTYPE CQueryAssociations::GetEnum(
535     ASSOCF cfFlags,
536     ASSOCENUM assocenum,
537     LPCWSTR pszExtra,
538     REFIID riid,
539     LPVOID *ppvOut)
540 {
541     return E_NOTIMPL;
542 }
543 
544 HRESULT CQueryAssociations::GetValue(HKEY hkey, const WCHAR *name, void **data, DWORD *data_size)
545 {
546     DWORD size;
547     LONG ret;
548 
549     ret = RegQueryValueExW(hkey, name, 0, NULL, NULL, &size);
550     if (ret != ERROR_SUCCESS)
551     {
552         return HRESULT_FROM_WIN32(ret);
553     }
554     if (!size)
555     {
556         return E_FAIL;
557     }
558     *data = HeapAlloc(GetProcessHeap(), 0, size);
559     if (!*data)
560     {
561         return E_OUTOFMEMORY;
562     }
563     ret = RegQueryValueExW(hkey, name, 0, NULL, (LPBYTE)*data, &size);
564     if (ret != ERROR_SUCCESS)
565     {
566         HeapFree(GetProcessHeap(), 0, *data);
567         return HRESULT_FROM_WIN32(ret);
568     }
569     if(data_size)
570     {
571         *data_size = size;
572     }
573     return S_OK;
574 }
575 
576 HRESULT CQueryAssociations::GetCommand(const WCHAR *extra, WCHAR **command)
577 {
578     HKEY hkeyCommand;
579     HKEY hkeyShell;
580     HKEY hkeyVerb;
581     HRESULT hr;
582     LONG ret;
583     WCHAR *extra_from_reg = NULL;
584     WCHAR *filetype;
585     static const WCHAR commandW[] = L"command";
586     static const WCHAR shellW[] = L"shell";
587 
588     /* When looking for file extension it's possible to have a default value
589      that points to another key that contains 'shell/<verb>/command' subtree. */
590     hr = this->GetValue(this->hkeySource, NULL, (void**)&filetype, NULL);
591     if (hr == S_OK)
592     {
593         HKEY hkeyFile;
594 
595         ret = RegOpenKeyExW(HKEY_CLASSES_ROOT, filetype, 0, KEY_READ, &hkeyFile);
596         HeapFree(GetProcessHeap(), 0, filetype);
597 
598         if (ret == ERROR_SUCCESS)
599         {
600             ret = RegOpenKeyExW(hkeyFile, shellW, 0, KEY_READ, &hkeyShell);
601             RegCloseKey(hkeyFile);
602         }
603         else
604         {
605             ret = RegOpenKeyExW(this->hkeySource, shellW, 0, KEY_READ, &hkeyShell);
606         }
607     }
608     else
609     {
610         ret = RegOpenKeyExW(this->hkeySource, shellW, 0, KEY_READ, &hkeyShell);
611     }
612 
613     if (ret)
614     {
615         return HRESULT_FROM_WIN32(ret);
616     }
617 
618     if (!extra)
619     {
620         /* check for default verb */
621         hr = this->GetValue(hkeyShell, NULL, (void**)&extra_from_reg, NULL);
622         if (FAILED(hr))
623         {
624             /* no default verb, try first subkey */
625             DWORD max_subkey_len;
626 
627             ret = RegQueryInfoKeyW(hkeyShell, NULL, NULL, NULL, NULL, &max_subkey_len, NULL, NULL, NULL, NULL, NULL, NULL);
628             if (ret)
629             {
630                 RegCloseKey(hkeyShell);
631                 return HRESULT_FROM_WIN32(ret);
632             }
633 
634             max_subkey_len++;
635             extra_from_reg = static_cast<WCHAR*>(HeapAlloc(GetProcessHeap(), 0, max_subkey_len * sizeof(WCHAR)));
636             if (!extra_from_reg)
637             {
638                 RegCloseKey(hkeyShell);
639                 return E_OUTOFMEMORY;
640             }
641 
642             ret = RegEnumKeyExW(hkeyShell, 0, extra_from_reg, &max_subkey_len, NULL, NULL, NULL, NULL);
643             if (ret)
644             {
645                 HeapFree(GetProcessHeap(), 0, extra_from_reg);
646                 RegCloseKey(hkeyShell);
647                 return HRESULT_FROM_WIN32(ret);
648             }
649         }
650         extra = extra_from_reg;
651     }
652 
653     /* open verb subkey */
654     ret = RegOpenKeyExW(hkeyShell, extra, 0, KEY_READ, &hkeyVerb);
655     HeapFree(GetProcessHeap(), 0, extra_from_reg);
656     RegCloseKey(hkeyShell);
657     if (ret)
658     {
659         return HRESULT_FROM_WIN32(ret);
660     }
661     /* open command subkey */
662     ret = RegOpenKeyExW(hkeyVerb, commandW, 0, KEY_READ, &hkeyCommand);
663     RegCloseKey(hkeyVerb);
664     if (ret)
665     {
666         return HRESULT_FROM_WIN32(ret);
667     }
668     hr = this->GetValue(hkeyCommand, NULL, (void**)command, NULL);
669     RegCloseKey(hkeyCommand);
670     return hr;
671 }
672 
673 HRESULT CQueryAssociations::GetExecutable(LPCWSTR pszExtra, LPWSTR path, DWORD pathlen, DWORD *len)
674 {
675     WCHAR *pszCommand;
676     WCHAR *pszStart;
677     WCHAR *pszEnd;
678 
679     HRESULT hr = this->GetCommand(pszExtra, &pszCommand);
680     if (FAILED(hr))
681     {
682         return hr;
683     }
684 
685     DWORD expLen = ExpandEnvironmentStringsW(pszCommand, NULL, 0);
686     if (expLen > 0)
687     {
688         expLen++;
689         WCHAR *buf = static_cast<WCHAR *>(HeapAlloc(GetProcessHeap(), 0, expLen * sizeof(WCHAR)));
690         ExpandEnvironmentStringsW(pszCommand, buf, expLen);
691         HeapFree(GetProcessHeap(), 0, pszCommand);
692         pszCommand = buf;
693     }
694 
695     /* cleanup pszCommand */
696     if (pszCommand[0] == '"')
697     {
698         pszStart = pszCommand + 1;
699         pszEnd = strchrW(pszStart, '"');
700         if (pszEnd)
701         {
702             *pszEnd = 0;
703         }
704         *len = SearchPathW(NULL, pszStart, NULL, pathlen, path, NULL);
705     }
706     else
707     {
708         pszStart = pszCommand;
709         for (pszEnd = pszStart; (pszEnd = strchrW(pszEnd, ' ')); pszEnd++)
710         {
711             WCHAR c = *pszEnd;
712             *pszEnd = 0;
713             if ((*len = SearchPathW(NULL, pszStart, NULL, pathlen, path, NULL)))
714             {
715                 break;
716             }
717             *pszEnd = c;
718         }
719         if (!pszEnd)
720         {
721             *len = SearchPathW(NULL, pszStart, NULL, pathlen, path, NULL);
722         }
723     }
724 
725     HeapFree(GetProcessHeap(), 0, pszCommand);
726     if (!*len)
727     {
728         return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
729     }
730     return S_OK;
731 }
732 
733 HRESULT CQueryAssociations::ReturnData(void *out, DWORD *outlen, const void *data, DWORD datalen)
734 {
735     if (out)
736     {
737         if (*outlen < datalen)
738         {
739             *outlen = datalen;
740             return E_POINTER;
741         }
742         *outlen = datalen;
743         memcpy(out, data, datalen);
744         return S_OK;
745     }
746     else
747     {
748         *outlen = datalen;
749         return S_FALSE;
750     }
751 }
752 
753 HRESULT CQueryAssociations::ReturnString(ASSOCF flags, LPWSTR out, DWORD *outlen, LPCWSTR data, DWORD datalen)
754 {
755     HRESULT hr = S_OK;
756     DWORD len;
757 
758     TRACE("flags=0x%08x, data=%s\n", flags, debugstr_w(data));
759 
760     if (!out)
761     {
762         *outlen = datalen;
763         return S_FALSE;
764     }
765 
766     if (*outlen < datalen)
767     {
768         if (flags & ASSOCF_NOTRUNCATE)
769         {
770             len = 0;
771             if (*outlen > 0) out[0] = 0;
772             hr = E_POINTER;
773         }
774         else
775         {
776             len = min(*outlen, datalen);
777             hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
778         }
779         *outlen = datalen;
780     }
781     else
782     {
783         len = datalen;
784     }
785 
786     if (len)
787     {
788         memcpy(out, data, len*sizeof(WCHAR));
789     }
790 
791     return hr;
792 }