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