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