xref: /reactos/base/applications/rapps/cabinet.cpp (revision 8540ab04)
1 /*
2 * PROJECT:     ReactOS Applications Manager
3 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * FILE:        base/applications/rapps/cabinet.cpp
5 * PURPOSE:     Cabinet extraction using FDI API
6 * COPYRIGHT:   Copyright 2018 Alexander Shaposhnikov     (sanchaez@reactos.org)
7 */
8 #include "rapps.h"
9 
10 #include <fdi.h>
11 #include <fcntl.h>
12 
13 /*
14  * HACK: treat any input strings as Unicode (UTF-8)
15  * cabinet.dll lacks any sort of a Unicode API, but FCI/FDI
16  * provide an ability to use user-defined callbacks for any file or memory
17  * operations. This flexibility and the magic power of C/C++ casting allows
18  * us to treat input as we please.
19  * This is by far the best way to extract .cab using Unicode paths.
20  */
21 
22 /* String conversion helper functions */
23 
24 // converts CStringW to CStringA using a given codepage
25 inline BOOL WideToMultiByte(const CStringW& szSource,
26                             CStringA& szDest,
27                             UINT Codepage)
28 {
29     // determine the needed size
30     INT sz = WideCharToMultiByte(Codepage,
31                                     0,
32                                     szSource,
33                                     -1,
34                                     NULL,
35                                     NULL,
36                                     NULL,
37                                     NULL);
38     if (!sz)
39         return FALSE;
40 
41     // do the actual conversion
42     sz = WideCharToMultiByte(Codepage,
43                                 0,
44                                 szSource,
45                                 -1,
46                                 szDest.GetBuffer(sz),
47                                 sz,
48                                 NULL,
49                                 NULL);
50 
51     szDest.ReleaseBuffer();
52     return sz != 0;
53 }
54 
55 // converts CStringA to CStringW using a given codepage
56 inline BOOL MultiByteToWide(const CStringA& szSource,
57                             CStringW& szDest,
58                             UINT Codepage)
59 {
60     // determine the needed size
61     INT sz = MultiByteToWideChar(Codepage,
62                                     0,
63                                     szSource,
64                                     -1,
65                                     NULL,
66                                     NULL);
67     if (!sz)
68         return FALSE;
69 
70     // do the actual conversion
71     sz = MultiByteToWideChar(CP_UTF8,
72                                 0,
73                                 szSource,
74                                 -1,
75                                 szDest.GetBuffer(sz),
76                                 sz);
77 
78     szDest.ReleaseBuffer();
79     return sz != 0;
80 }
81 
82 /* FDICreate callbacks */
83 
84 FNALLOC(fnMemAlloc)
85 {
86     return HeapAlloc(GetProcessHeap(), NULL, cb);
87 }
88 
89 FNFREE(fnMemFree)
90 {
91     HeapFree(GetProcessHeap(), NULL, pv);
92 }
93 
94 FNOPEN(fnFileOpen)
95 {
96     HANDLE hFile = NULL;
97     DWORD dwDesiredAccess = 0;
98     DWORD dwCreationDisposition = 0;
99     ATL::CStringW szFileName;
100 
101     UNREFERENCED_PARAMETER(pmode);
102 
103     if (oflag & _O_RDWR)
104     {
105         dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
106     }
107     else if (oflag & _O_WRONLY)
108     {
109         dwDesiredAccess = GENERIC_WRITE;
110     }
111     else
112     {
113         dwDesiredAccess = GENERIC_READ;
114     }
115 
116     if (oflag & _O_CREAT)
117     {
118         dwCreationDisposition = CREATE_ALWAYS;
119     }
120     else
121     {
122         dwCreationDisposition = OPEN_EXISTING;
123     }
124 
125     MultiByteToWide(pszFile, szFileName, CP_UTF8);
126 
127     hFile = CreateFileW(szFileName,
128                         dwDesiredAccess,
129                         FILE_SHARE_READ,
130                         NULL,
131                         dwCreationDisposition,
132                         FILE_ATTRIBUTE_NORMAL,
133                         NULL);
134 
135     return (INT_PTR) hFile;
136 }
137 
138 FNREAD(fnFileRead)
139 {
140     DWORD dwBytesRead = 0;
141 
142     if (ReadFile((HANDLE) hf, pv, cb, &dwBytesRead, NULL) == FALSE)
143     {
144         dwBytesRead = (DWORD) -1L;
145     }
146 
147     return dwBytesRead;
148 }
149 
150 FNWRITE(fnFileWrite)
151 {
152     DWORD dwBytesWritten = 0;
153 
154     if (WriteFile((HANDLE) hf, pv, cb, &dwBytesWritten, NULL) == FALSE)
155     {
156         dwBytesWritten = (DWORD) -1;
157     }
158 
159     return dwBytesWritten;
160 }
161 
162 FNCLOSE(fnFileClose)
163 {
164     return (CloseHandle((HANDLE) hf) != FALSE) ? 0 : -1;
165 }
166 
167 FNSEEK(fnFileSeek)
168 {
169     return SetFilePointer((HANDLE) hf, dist, NULL, seektype);
170 }
171 
172 /* FDICopy callbacks */
173 
174 FNFDINOTIFY(fnNotify)
175 {
176     INT_PTR iResult = 0;
177 
178     switch (fdint)
179     {
180     case fdintCOPY_FILE:
181     {
182         ATL::CStringW szNewFileName, szExtractDir, szCabFileName;
183         ATL::CStringA szFilePathUTF8;
184 
185         // Append the destination directory to the file name.
186         MultiByteToWide((LPCSTR) pfdin->pv, szExtractDir, CP_UTF8);
187         MultiByteToWide(pfdin->psz1, szCabFileName, CP_ACP);
188 
189         szNewFileName = szExtractDir + L"\\" + szCabFileName;
190 
191         WideToMultiByte(szNewFileName, szFilePathUTF8, CP_UTF8);
192 
193         // Copy file
194         iResult = fnFileOpen((LPSTR) szFilePathUTF8.GetString(),
195                              _O_WRONLY | _O_CREAT,
196                              0);
197     }
198     break;
199 
200     case fdintCLOSE_FILE_INFO:
201         iResult = !fnFileClose(pfdin->hf);
202         break;
203 
204     case fdintNEXT_CABINET:
205         if (pfdin->fdie != FDIERROR_NONE)
206         {
207             iResult = -1;
208         }
209         break;
210 
211     case fdintPARTIAL_FILE:
212         iResult = 0;
213         break;
214 
215     case fdintCABINET_INFO:
216         iResult = 0;
217         break;
218 
219     case fdintENUMERATE:
220         iResult = 0;
221         break;
222 
223     default:
224         iResult = -1;
225         break;
226     }
227 
228     return iResult;
229 }
230 
231 /* cabinet.dll FDI function pointers */
232 
233 typedef HFDI(*fnFDICreate)(PFNALLOC,
234                            PFNFREE,
235                            PFNOPEN,
236                            PFNREAD,
237                            PFNWRITE,
238                            PFNCLOSE,
239                            PFNSEEK,
240                            int,
241                            PERF);
242 
243 typedef BOOL(*fnFDICopy)(HFDI,
244                          LPSTR,
245                          LPSTR,
246                          INT,
247                          PFNFDINOTIFY,
248                          PFNFDIDECRYPT,
249                          void FAR *pvUser);
250 
251 typedef BOOL(*fnFDIDestroy)(HFDI);
252 
253 /*
254  * Extraction function
255  * TODO: require only a full path to the cab as an argument
256  */
257 BOOL ExtractFilesFromCab(const ATL::CStringW& szCabName,
258                          const ATL::CStringW& szCabDir,
259                          const ATL::CStringW& szOutputDir)
260 {
261     HINSTANCE hCabinetDll;
262     HFDI ExtractHandler;
263     ERF ExtractErrors;
264     ATL::CStringA szCabNameUTF8, szCabDirUTF8, szOutputDirUTF8;
265     fnFDICreate pfnFDICreate;
266     fnFDICopy pfnFDICopy;
267     fnFDIDestroy pfnFDIDestroy;
268     BOOL bResult;
269 
270     // Load cabinet.dll and extract needed functions
271     hCabinetDll = LoadLibraryW(L"cabinet.dll");
272 
273     if (!hCabinetDll)
274     {
275         return FALSE;
276     }
277 
278     pfnFDICreate = (fnFDICreate) GetProcAddress(hCabinetDll, "FDICreate");
279     pfnFDICopy = (fnFDICopy) GetProcAddress(hCabinetDll, "FDICopy");
280     pfnFDIDestroy = (fnFDIDestroy) GetProcAddress(hCabinetDll, "FDIDestroy");
281 
282     if (!pfnFDICreate || !pfnFDICopy || !pfnFDIDestroy)
283     {
284         FreeLibrary(hCabinetDll);
285         return FALSE;
286     }
287 
288     // Create FDI context
289     ExtractHandler = pfnFDICreate(fnMemAlloc,
290                                   fnMemFree,
291                                   fnFileOpen,
292                                   fnFileRead,
293                                   fnFileWrite,
294                                   fnFileClose,
295                                   fnFileSeek,
296                                   cpuUNKNOWN,
297                                   &ExtractErrors);
298 
299     if (!ExtractHandler)
300     {
301         FreeLibrary(hCabinetDll);
302         return FALSE;
303     }
304 
305     // Create output dir
306     bResult = CreateDirectoryW(szOutputDir, NULL);
307 
308     if (bResult || GetLastError() == ERROR_ALREADY_EXISTS)
309     {
310         // Convert wide strings to UTF-8
311         bResult = WideToMultiByte(szCabName, szCabNameUTF8, CP_UTF8);
312         bResult &= WideToMultiByte(szCabDir, szCabDirUTF8, CP_UTF8);
313         bResult &= WideToMultiByte(szOutputDir, szOutputDirUTF8, CP_UTF8);
314     }
315 
316     // Perform extraction
317     if (bResult)
318     {
319         // Add a slash to cab name as required by the api
320         szCabNameUTF8 = "\\" + szCabNameUTF8;
321 
322         bResult = pfnFDICopy(ExtractHandler,
323                              (LPSTR) szCabNameUTF8.GetString(),
324                              (LPSTR) szCabDirUTF8.GetString(),
325                              0,
326                              fnNotify,
327                              NULL,
328                              (void FAR *) szOutputDirUTF8.GetString());
329     }
330 
331     pfnFDIDestroy(ExtractHandler);
332     FreeLibrary(hCabinetDll);
333     return bResult;
334 }
335