[FORMATTING] Remove trailing whitespace. Addendum to 34593d93.
[reactos.git] / base / applications / rapps / cabinet.cpp
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 #include <debug.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 CStringW szExtractDir, szCabFileName;
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 if (szCabFileName.Find('\\') >= 0)
189 {
190 CStringW szNewDirName = szExtractDir;
191 int nTokenPos = 0;
192 // We do not want to interpret the filename as directory,
193 // so bail out before the last token!
194 while (szCabFileName.Find('\\', nTokenPos) >= 0)
195 {
196 CStringW token = szCabFileName.Tokenize(L"\\", nTokenPos);
197 if (token.IsEmpty())
198 break;
199
200 szNewDirName += L"\\" + token;
201 if (!CreateDirectoryW(szNewDirName, NULL))
202 {
203 DWORD dwErr = GetLastError();
204 if (dwErr != ERROR_ALREADY_EXISTS)
205 {
206 DPRINT1("ERROR: Unable to create directory %S (err %lu)\n", szNewDirName.GetString(), dwErr);
207 }
208 }
209 }
210 }
211
212 CStringW szNewFileName = szExtractDir + L"\\" + szCabFileName;
213
214 CStringA szFilePathUTF8;
215 WideToMultiByte(szNewFileName, szFilePathUTF8, CP_UTF8);
216
217 // Open the file
218 iResult = fnFileOpen((LPSTR) szFilePathUTF8.GetString(),
219 _O_WRONLY | _O_CREAT,
220 0);
221 }
222 break;
223
224 case fdintCLOSE_FILE_INFO:
225 iResult = !fnFileClose(pfdin->hf);
226 break;
227
228 case fdintNEXT_CABINET:
229 if (pfdin->fdie != FDIERROR_NONE)
230 {
231 iResult = -1;
232 }
233 break;
234
235 case fdintPARTIAL_FILE:
236 iResult = 0;
237 break;
238
239 case fdintCABINET_INFO:
240 iResult = 0;
241 break;
242
243 case fdintENUMERATE:
244 iResult = 0;
245 break;
246
247 default:
248 iResult = -1;
249 break;
250 }
251
252 return iResult;
253 }
254
255 /* cabinet.dll FDI function pointers */
256
257 typedef HFDI(*fnFDICreate)(PFNALLOC,
258 PFNFREE,
259 PFNOPEN,
260 PFNREAD,
261 PFNWRITE,
262 PFNCLOSE,
263 PFNSEEK,
264 int,
265 PERF);
266
267 typedef BOOL(*fnFDICopy)(HFDI,
268 LPSTR,
269 LPSTR,
270 INT,
271 PFNFDINOTIFY,
272 PFNFDIDECRYPT,
273 void FAR *pvUser);
274
275 typedef BOOL(*fnFDIDestroy)(HFDI);
276
277 /*
278 * Extraction function
279 * TODO: require only a full path to the cab as an argument
280 */
281 BOOL ExtractFilesFromCab(const ATL::CStringW& szCabName,
282 const ATL::CStringW& szCabDir,
283 const ATL::CStringW& szOutputDir)
284 {
285 HINSTANCE hCabinetDll;
286 HFDI ExtractHandler;
287 ERF ExtractErrors;
288 ATL::CStringA szCabNameUTF8, szCabDirUTF8, szOutputDirUTF8;
289 fnFDICreate pfnFDICreate;
290 fnFDICopy pfnFDICopy;
291 fnFDIDestroy pfnFDIDestroy;
292 BOOL bResult;
293
294 // Load cabinet.dll and extract needed functions
295 hCabinetDll = LoadLibraryW(L"cabinet.dll");
296
297 if (!hCabinetDll)
298 {
299 return FALSE;
300 }
301
302 pfnFDICreate = (fnFDICreate) GetProcAddress(hCabinetDll, "FDICreate");
303 pfnFDICopy = (fnFDICopy) GetProcAddress(hCabinetDll, "FDICopy");
304 pfnFDIDestroy = (fnFDIDestroy) GetProcAddress(hCabinetDll, "FDIDestroy");
305
306 if (!pfnFDICreate || !pfnFDICopy || !pfnFDIDestroy)
307 {
308 FreeLibrary(hCabinetDll);
309 return FALSE;
310 }
311
312 // Create FDI context
313 ExtractHandler = pfnFDICreate(fnMemAlloc,
314 fnMemFree,
315 fnFileOpen,
316 fnFileRead,
317 fnFileWrite,
318 fnFileClose,
319 fnFileSeek,
320 cpuUNKNOWN,
321 &ExtractErrors);
322
323 if (!ExtractHandler)
324 {
325 FreeLibrary(hCabinetDll);
326 return FALSE;
327 }
328
329 // Create output dir
330 bResult = CreateDirectoryW(szOutputDir, NULL);
331
332 if (bResult || GetLastError() == ERROR_ALREADY_EXISTS)
333 {
334 // Convert wide strings to UTF-8
335 bResult = WideToMultiByte(szCabName, szCabNameUTF8, CP_UTF8);
336 bResult &= WideToMultiByte(szCabDir, szCabDirUTF8, CP_UTF8);
337 bResult &= WideToMultiByte(szOutputDir, szOutputDirUTF8, CP_UTF8);
338 }
339
340 // Perform extraction
341 if (bResult)
342 {
343 // Add a slash to cab name as required by the api
344 szCabNameUTF8 = "\\" + szCabNameUTF8;
345
346 bResult = pfnFDICopy(ExtractHandler,
347 (LPSTR) szCabNameUTF8.GetString(),
348 (LPSTR) szCabDirUTF8.GetString(),
349 0,
350 fnNotify,
351 NULL,
352 (void FAR *) szOutputDirUTF8.GetString());
353 }
354
355 pfnFDIDestroy(ExtractHandler);
356 FreeLibrary(hCabinetDll);
357 return bResult;
358 }