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,CStringA & szDest,UINT Codepage)26 WideToMultiByte(const CStringW &szSource, CStringA &szDest, UINT Codepage)
27 {
28 // determine the needed size
29 INT sz = WideCharToMultiByte(Codepage, 0, szSource, -1, NULL, NULL, NULL, NULL);
30 if (!sz)
31 return FALSE;
32
33 // do the actual conversion
34 sz = WideCharToMultiByte(Codepage, 0, szSource, -1, szDest.GetBuffer(sz), sz, NULL, NULL);
35
36 szDest.ReleaseBuffer();
37 return sz != 0;
38 }
39
40 // converts CStringA to CStringW using a given codepage
41 inline BOOL
MultiByteToWide(const CStringA & szSource,CStringW & szDest,UINT Codepage)42 MultiByteToWide(const CStringA &szSource, CStringW &szDest, UINT Codepage)
43 {
44 // determine the needed size
45 INT sz = MultiByteToWideChar(Codepage, 0, szSource, -1, NULL, NULL);
46 if (!sz)
47 return FALSE;
48
49 // do the actual conversion
50 sz = MultiByteToWideChar(Codepage, 0, szSource, -1, szDest.GetBuffer(sz), sz);
51
52 szDest.ReleaseBuffer();
53 return sz != 0;
54 }
55
56 struct NotifyData
57 {
58 EXTRACTCALLBACK Callback;
59 void *CallerCookie;
60 LPCSTR OutputDir;
61 };
62
63 /* FDICreate callbacks */
64
FNALLOC(fnMemAlloc)65 FNALLOC(fnMemAlloc)
66 {
67 return HeapAlloc(GetProcessHeap(), NULL, cb);
68 }
69
FNFREE(fnMemFree)70 FNFREE(fnMemFree)
71 {
72 HeapFree(GetProcessHeap(), NULL, pv);
73 }
74
FNOPEN(fnFileOpen)75 FNOPEN(fnFileOpen)
76 {
77 HANDLE hFile = NULL;
78 DWORD dwDesiredAccess = 0;
79 DWORD dwCreationDisposition = 0;
80 CStringW szFileName;
81
82 UNREFERENCED_PARAMETER(pmode);
83
84 if (oflag & _O_RDWR)
85 {
86 dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
87 }
88 else if (oflag & _O_WRONLY)
89 {
90 dwDesiredAccess = GENERIC_WRITE;
91 }
92 else
93 {
94 dwDesiredAccess = GENERIC_READ;
95 }
96
97 if (oflag & _O_CREAT)
98 {
99 dwCreationDisposition = CREATE_ALWAYS;
100 }
101 else
102 {
103 dwCreationDisposition = OPEN_EXISTING;
104 }
105
106 MultiByteToWide(pszFile, szFileName, CP_UTF8);
107
108 hFile = CreateFileW(
109 szFileName, dwDesiredAccess, FILE_SHARE_READ, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
110
111 return (INT_PTR)hFile;
112 }
113
FNREAD(fnFileRead)114 FNREAD(fnFileRead)
115 {
116 DWORD dwBytesRead = 0;
117
118 if (ReadFile((HANDLE)hf, pv, cb, &dwBytesRead, NULL) == FALSE)
119 {
120 dwBytesRead = (DWORD)-1L;
121 }
122
123 return dwBytesRead;
124 }
125
FNWRITE(fnFileWrite)126 FNWRITE(fnFileWrite)
127 {
128 DWORD dwBytesWritten = 0;
129
130 if (WriteFile((HANDLE)hf, pv, cb, &dwBytesWritten, NULL) == FALSE)
131 {
132 dwBytesWritten = (DWORD)-1;
133 }
134
135 return dwBytesWritten;
136 }
137
FNCLOSE(fnFileClose)138 FNCLOSE(fnFileClose)
139 {
140 return (CloseHandle((HANDLE)hf) != FALSE) ? 0 : -1;
141 }
142
FNSEEK(fnFileSeek)143 FNSEEK(fnFileSeek)
144 {
145 return SetFilePointer((HANDLE)hf, dist, NULL, seektype);
146 }
147
148 /* FDICopy callbacks */
149
FNFDINOTIFY(fnNotify)150 FNFDINOTIFY(fnNotify)
151 {
152 INT_PTR iResult = 0;
153 NotifyData *pND = (NotifyData *)pfdin->pv;
154
155 switch (fdint)
156 {
157 case fdintCOPY_FILE:
158 {
159 CStringW szExtractDir, szCabFileName;
160
161 // Append the destination directory to the file name.
162 MultiByteToWide(pND->OutputDir, szExtractDir, CP_UTF8);
163 UINT codepage = (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP;
164 MultiByteToWide(pfdin->psz1, szCabFileName, codepage);
165
166 if (!NotifyFileExtractCallback(szCabFileName, pfdin->cb, pfdin->attribs,
167 pND->Callback, pND->CallerCookie))
168 {
169 break; // Skip file
170 }
171
172 if (szCabFileName.Find('\\') >= 0)
173 {
174 CStringW szNewDirName = szExtractDir;
175 int nTokenPos = 0;
176 // We do not want to interpret the filename as directory,
177 // so bail out before the last token!
178 while (szCabFileName.Find('\\', nTokenPos) >= 0)
179 {
180 CStringW token = szCabFileName.Tokenize(L"\\", nTokenPos);
181 if (token.IsEmpty())
182 break;
183
184 szNewDirName += L"\\" + token;
185 if (!CreateDirectoryW(szNewDirName, NULL))
186 {
187 DWORD dwErr = GetLastError();
188 if (dwErr != ERROR_ALREADY_EXISTS)
189 {
190 DPRINT1(
191 "ERROR: Unable to create directory %S (err %lu)\n", szNewDirName.GetString(), dwErr);
192 }
193 }
194 }
195 }
196
197 CStringW szNewFileName = szExtractDir + L"\\" + szCabFileName;
198
199 CStringA szFilePathUTF8;
200 WideToMultiByte(szNewFileName, szFilePathUTF8, CP_UTF8);
201
202 // Open the file
203 iResult = fnFileOpen((LPSTR)szFilePathUTF8.GetString(), _O_WRONLY | _O_CREAT, 0);
204 }
205 break;
206
207 case fdintCLOSE_FILE_INFO:
208 iResult = !fnFileClose(pfdin->hf);
209 break;
210
211 case fdintNEXT_CABINET:
212 if (pfdin->fdie != FDIERROR_NONE)
213 {
214 iResult = -1;
215 }
216 break;
217
218 case fdintPARTIAL_FILE:
219 iResult = 0;
220 break;
221
222 case fdintCABINET_INFO:
223 iResult = 0;
224 break;
225
226 case fdintENUMERATE:
227 iResult = 0;
228 break;
229
230 default:
231 iResult = -1;
232 break;
233 }
234
235 return iResult;
236 }
237
238 /* cabinet.dll FDI function pointers */
239
240 typedef HFDI (*fnFDICreate)(PFNALLOC, PFNFREE, PFNOPEN, PFNREAD, PFNWRITE, PFNCLOSE, PFNSEEK, int, PERF);
241
242 typedef BOOL (*fnFDICopy)(HFDI, LPSTR, LPSTR, INT, PFNFDINOTIFY, PFNFDIDECRYPT, void FAR *pvUser);
243
244 typedef BOOL (*fnFDIDestroy)(HFDI);
245
246 /*
247 * Extraction function
248 */
249 BOOL
ExtractFilesFromCab(const CStringW & szCabName,const CStringW & szCabDir,const CStringW & szOutputDir,EXTRACTCALLBACK Callback,void * Cookie)250 ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const CStringW &szOutputDir,
251 EXTRACTCALLBACK Callback, void *Cookie)
252 {
253 HINSTANCE hCabinetDll;
254 HFDI ExtractHandler;
255 ERF ExtractErrors;
256 ATL::CStringA szCabNameUTF8, szCabDirUTF8, szOutputDirUTF8;
257 fnFDICreate pfnFDICreate;
258 fnFDICopy pfnFDICopy;
259 fnFDIDestroy pfnFDIDestroy;
260 BOOL bResult;
261 NotifyData nd = { Callback, Cookie };
262
263 // Load cabinet.dll and extract needed functions
264 hCabinetDll = LoadLibraryW(L"cabinet.dll");
265
266 if (!hCabinetDll)
267 {
268 return FALSE;
269 }
270
271 pfnFDICreate = (fnFDICreate)GetProcAddress(hCabinetDll, "FDICreate");
272 pfnFDICopy = (fnFDICopy)GetProcAddress(hCabinetDll, "FDICopy");
273 pfnFDIDestroy = (fnFDIDestroy)GetProcAddress(hCabinetDll, "FDIDestroy");
274
275 if (!pfnFDICreate || !pfnFDICopy || !pfnFDIDestroy)
276 {
277 FreeLibrary(hCabinetDll);
278 return FALSE;
279 }
280
281 // Create FDI context
282 ExtractHandler = pfnFDICreate(
283 fnMemAlloc, fnMemFree, fnFileOpen, fnFileRead, fnFileWrite, fnFileClose, fnFileSeek, cpuUNKNOWN,
284 &ExtractErrors);
285
286 if (!ExtractHandler)
287 {
288 FreeLibrary(hCabinetDll);
289 return FALSE;
290 }
291
292 // Create output dir
293 bResult = CreateDirectoryW(szOutputDir, NULL);
294
295 if (bResult || GetLastError() == ERROR_ALREADY_EXISTS)
296 {
297 // Convert wide strings to UTF-8
298 bResult = WideToMultiByte(szCabName, szCabNameUTF8, CP_UTF8);
299 bResult &= WideToMultiByte(szCabDir, szCabDirUTF8, CP_UTF8);
300 bResult &= WideToMultiByte(szOutputDir, szOutputDirUTF8, CP_UTF8);
301 }
302
303 // Perform extraction
304 if (bResult)
305 {
306 // Add a slash to cab name as required by the api
307 szCabNameUTF8 = "\\" + szCabNameUTF8;
308
309 nd.OutputDir = szOutputDirUTF8.GetString();
310 bResult = pfnFDICopy(
311 ExtractHandler, (LPSTR)szCabNameUTF8.GetString(), (LPSTR)szCabDirUTF8.GetString(), 0, fnNotify, NULL,
312 (void FAR *)&nd);
313 }
314
315 pfnFDIDestroy(ExtractHandler);
316 FreeLibrary(hCabinetDll);
317 return bResult;
318 }
319
320 BOOL
ExtractFilesFromCab(LPCWSTR FullCabPath,const CStringW & szOutputDir,EXTRACTCALLBACK Callback,void * Cookie)321 ExtractFilesFromCab(LPCWSTR FullCabPath, const CStringW &szOutputDir,
322 EXTRACTCALLBACK Callback, void *Cookie)
323 {
324 CStringW dir, file = SplitFileAndDirectory(FullCabPath, &dir);
325 return ExtractFilesFromCab(file, dir, szOutputDir, Callback, Cookie);
326 }
327