1 /*
2  * ReactOS Explorer
3  *
4  * Copyright 2016 Sylvain Deverre <deverre dot sylv at gmail dot com>
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 
21 /*
22  * Wraps the component categories manager enum
23  */
24 
25 #include "shellbars.h"
26 
27 #define REGPATH L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Discardable\\PostSetup\\Component Categories"
28 #define IMPLEMENTING L"Implementing"
29 #define REQUIRING L"Requiring"
30 
31 typedef struct categoryCacheHeader
32 {
33     DWORD               dwSize;         // size of header only
34     DWORD               version;        // currently 1
35     SYSTEMTIME          writeTime;      // time we were written to registry
36     DWORD               classCount;     // number of classes following
37 } CATCACHEHDR, *PCATCACHEHDR;
38 
39 /*
40  * This class manages a cached explorer component categories items, writing cache if it
41  * doesn't exist yet.
42  * It is used by CSHEnumClassesOfCategories internally.
43  */
44 class CComCatCachedCategory
45 {
46     public:
47         CComCatCachedCategory();
48         virtual ~CComCatCachedCategory();
49         HRESULT WriteCacheToDSA(HDSA pDest);
50         HRESULT STDMETHODCALLTYPE Initialize(CATID &catID, BOOL reloadCache);
51     private:
52         BOOL LoadFromRegistry();
53         HRESULT LoadFromComCatMgr();
54         HRESULT CacheDSA();
55         CATID fCategory;
56         HDSA fLocalDsa;
57 };
58 
59 CComCatCachedCategory::CComCatCachedCategory()
60 {
61     fLocalDsa = DSA_Create(sizeof(GUID), 5);
62 }
63 
64 HRESULT STDMETHODCALLTYPE CComCatCachedCategory::Initialize(CATID &catID, BOOL reloadCache)
65 {
66     HRESULT hr;
67 
68     fCategory = catID;
69     if (reloadCache || !LoadFromRegistry())
70     {
71         hr = LoadFromComCatMgr();
72         if (FAILED_UNEXPECTEDLY(hr))
73             return hr;
74 
75         hr = CacheDSA();
76         if (FAILED_UNEXPECTEDLY(hr))
77             return hr;
78     }
79     return S_OK;
80 }
81 
82 CComCatCachedCategory::~CComCatCachedCategory()
83 {
84     DSA_Destroy(fLocalDsa);
85 }
86 
87 BOOL CComCatCachedCategory::LoadFromRegistry()
88 {
89     WCHAR bufKey[MAX_PATH];
90     WCHAR guidStr[MAX_PATH];
91     DWORD dataSize, i;
92     CComHeapPtr<CATCACHEHDR> buffer;
93     GUID *guidArray;
94 
95     if (!fLocalDsa)
96         return FALSE;
97 
98     dataSize = 0;
99     if (!StringFromGUID2(fCategory, guidStr, MAX_PATH))
100         return FALSE;
101 
102     wsprintf(bufKey, L"%s\\%s\\%s", REGPATH , guidStr, L"Enum");
103 
104     // Try to read key and get proper value size
105     if (SHGetValue(HKEY_CURRENT_USER, bufKey, IMPLEMENTING, NULL, NULL, &dataSize))
106         return FALSE;
107 
108     buffer.Attach((PCATCACHEHDR)CoTaskMemAlloc(dataSize));
109 
110     SHGetValue(HKEY_CURRENT_USER, bufKey, IMPLEMENTING, NULL, buffer, &dataSize);
111     guidArray = (GUID*)(buffer + 1);
112     for (i = 0; i < buffer->classCount; i++)
113     {
114         // Add class to cache
115         DSA_InsertItem(fLocalDsa, DSA_APPEND, guidArray + i);
116     }
117 
118     return TRUE;
119 }
120 
121 HRESULT CComCatCachedCategory::CacheDSA()
122 {
123     WCHAR                               bufKey[MAX_PATH];
124     WCHAR                               guidStr[MAX_PATH];
125     UINT                                elemCount;
126     UINT                                i;
127     UINT                                bufferSize;
128     CComHeapPtr<CATCACHEHDR>            buffer;
129     GUID                                *guidArray;
130     GUID                                *tmp;
131 
132     elemCount = DSA_GetItemCount(fLocalDsa);
133     bufferSize = sizeof(CATCACHEHDR) + elemCount * sizeof(GUID);
134     if (!StringFromGUID2(fCategory, guidStr, MAX_PATH))
135         return E_FAIL;
136 
137     buffer.Attach((PCATCACHEHDR)CoTaskMemAlloc(bufferSize));
138     if (!buffer)
139         return E_OUTOFMEMORY;
140 
141     // Correctly fill cache header
142     buffer->dwSize = sizeof(CATCACHEHDR);
143     buffer->version = 1;
144     GetSystemTime(&buffer->writeTime);
145     buffer->classCount = (DWORD)elemCount;
146 
147     guidArray = (GUID*)(buffer + 1);
148     wsprintf(bufKey, L"%s\\%s\\%s", REGPATH , guidStr, L"Enum");
149 
150     // Write DSA contents inside the memory buffer allocated
151     for(i = 0; i < elemCount; i++)
152     {
153         tmp = (GUID*)DSA_GetItemPtr(fLocalDsa, i);
154         if (tmp)
155         {
156             guidArray[i] = *tmp;
157         }
158     }
159 
160     // Save items to registry
161     SHSetValue(HKEY_CURRENT_USER, bufKey, IMPLEMENTING, REG_BINARY, buffer, bufferSize);
162 
163     guidArray = NULL;
164     return S_OK;
165 }
166 
167 HRESULT CComCatCachedCategory::LoadFromComCatMgr()
168 {
169     HRESULT hr;
170     CComPtr<ICatInformation> pCatInformation;
171     CComPtr<IEnumGUID> pEnumGUID;
172     ULONG pFetched;
173     CLSID tmp;
174 
175     // Get component categories manager instance
176     hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC_SERVER,
177         IID_PPV_ARG(ICatInformation, &pCatInformation));
178     if (FAILED_UNEXPECTEDLY(hr))
179         return hr;
180 
181     // Get the proper enumerator
182     hr = pCatInformation->EnumClassesOfCategories(1, &fCategory, NULL, NULL, &pEnumGUID);
183     if (FAILED_UNEXPECTEDLY(hr))
184         return hr;
185 
186     // Enumerate elements
187     do
188     {
189         pFetched = 0;
190         pEnumGUID->Next(1, &tmp, &pFetched);
191         if (pFetched)
192         {
193             if (DSA_InsertItem(fLocalDsa, DSA_APPEND, &tmp) == E_OUTOFMEMORY)
194                 return E_OUTOFMEMORY;
195         }
196     }
197     while (pFetched > 0);
198     return S_OK;
199 }
200 
201 HRESULT CComCatCachedCategory::WriteCacheToDSA(HDSA pDest)
202 {
203     INT i;
204     for(i = 0; i < DSA_GetItemCount(fLocalDsa); i++)
205     {
206         if (DSA_InsertItem(pDest, DSA_APPEND, DSA_GetItemPtr(fLocalDsa, i)) == DSA_ERR)
207             return E_OUTOFMEMORY;
208     }
209     return S_OK;
210 }
211 
212 class CSHEnumClassesOfCategories :
213     public CComCoClass<CSHEnumClassesOfCategories>,
214     public CComObjectRootEx<CComMultiThreadModelNoCS>,
215     public IEnumGUID
216 {
217     private:
218         CComPtr<ICatInformation> fCatInformation;
219         HDSA fDsa;
220         ULONG fCursor;
221 
222     public:
223         CSHEnumClassesOfCategories();
224         virtual ~CSHEnumClassesOfCategories();
225         virtual HRESULT STDMETHODCALLTYPE Initialize(ULONG cImplemented, CATID *pImplemented, ULONG cRequired, CATID *pRequired);
226         // *** IEnumGUID methods ***
227         virtual HRESULT STDMETHODCALLTYPE Clone(IEnumCLSID **ppvOut);
228         virtual HRESULT STDMETHODCALLTYPE Next(ULONG cElt, CLSID *pElts, ULONG *pFetched);
229         virtual HRESULT STDMETHODCALLTYPE Reset();
230         virtual HRESULT STDMETHODCALLTYPE Skip(ULONG nbElts);
231 
232         BEGIN_COM_MAP(CSHEnumClassesOfCategories)
233             COM_INTERFACE_ENTRY_IID(IID_IEnumGUID, IEnumGUID)
234         END_COM_MAP()
235 };
236 
237 CSHEnumClassesOfCategories::CSHEnumClassesOfCategories()
238 {
239     fCursor = 0;
240     fDsa = DSA_Create(sizeof(GUID), 5);
241 }
242 
243 CSHEnumClassesOfCategories::~CSHEnumClassesOfCategories()
244 {
245     if (fDsa)
246         DSA_Destroy(fDsa);
247 }
248 
249 HRESULT CSHEnumClassesOfCategories::Initialize(ULONG cImplemented, CATID *pImplemented, ULONG cRequired, CATID *pRequired)
250 {
251     UINT i;
252     HRESULT hr;
253 
254     if (!fDsa)
255         return E_FAIL;
256 
257     // Parameter validation:
258     // - We must have at least one category to manage.
259     // - The array pointers must not be NULL if there is a non-zero
260     //   element count specified for them.
261     if (cImplemented == 0 && cRequired == 0)
262         return E_INVALIDARG;
263     if ((cImplemented && !pImplemented) || (cRequired && !pRequired))
264         return E_INVALIDARG;
265 
266     // For each implemented category, create a cache and add it to our local DSA.
267     for (i = 0; i < cImplemented; i++)
268     {
269         CComCatCachedCategory cachedCat;
270         hr = cachedCat.Initialize(pImplemented[i], FALSE);
271         if (FAILED_UNEXPECTEDLY(hr))
272             return hr;
273         cachedCat.WriteCacheToDSA(fDsa);
274     }
275 
276     // TODO: Implement caching of the required categories.
277     if (cRequired > 0)
278     {
279         FIXME("Implement required categories class enumeration\n");
280 
281         // Only fail in case we didn't look at the implemented categories.
282         if (cImplemented == 0)
283             return E_NOTIMPL;
284     }
285 
286     return S_OK;
287 }
288 
289 // *** IEnumGUID methods ***
290 
291 HRESULT STDMETHODCALLTYPE CSHEnumClassesOfCategories::Clone(IEnumCLSID **ppvOut)
292 {
293     return E_NOTIMPL;
294 }
295 
296 HRESULT STDMETHODCALLTYPE CSHEnumClassesOfCategories::Next(ULONG cElt, CLSID *pElts, ULONG *pFetched)
297 {
298     ULONG i;
299     ULONG read;
300     GUID *tmp;
301 
302     if (!pElts)
303         return E_INVALIDARG;
304     read = 0;
305     for (i = 0; i < cElt && (fCursor < (ULONG)DSA_GetItemCount(fDsa)); i++)
306     {
307         tmp = (GUID*)DSA_GetItemPtr(fDsa, fCursor + i);
308         if (!tmp)
309             break;
310         pElts[i] = *tmp;
311         read++;
312     }
313     fCursor += read;
314     if (pFetched)
315         *pFetched = read;
316     return S_OK;
317 }
318 
319 HRESULT STDMETHODCALLTYPE CSHEnumClassesOfCategories::Reset()
320 {
321     fCursor = 0;
322     return S_OK;
323 }
324 
325 HRESULT STDMETHODCALLTYPE CSHEnumClassesOfCategories::Skip(ULONG nbElts)
326 {
327     if (fCursor + nbElts >= (ULONG)DSA_GetItemCount(fDsa))
328         return E_INVALIDARG;
329     fCursor += nbElts;
330     return S_OK;
331 }
332 
333 /*************************************************************************
334  * SHEnumClassesOfCategories	[BROWSEUI.136]
335  */
336 extern "C" HRESULT WINAPI SHEnumClassesOfCategories(ULONG cImplemented, CATID *pImplemented, ULONG cRequired, CATID *pRequired, IEnumGUID **out)
337 {
338     HRESULT hr;
339 
340     if (!out)
341         return E_INVALIDARG;
342 
343     hr = ShellObjectCreatorInit<CSHEnumClassesOfCategories>(
344             cImplemented, pImplemented, cRequired, pRequired, IID_PPV_ARG(IEnumGUID, out));
345     if (FAILED_UNEXPECTEDLY(hr))
346         return hr;
347     return S_OK;
348 }
349