xref: /reactos/base/applications/rapps/cabinet.cpp (revision 4225717d)
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
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
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(CP_UTF8, 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 
65 FNALLOC(fnMemAlloc)
66 {
67     return HeapAlloc(GetProcessHeap(), NULL, cb);
68 }
69 
70 FNFREE(fnMemFree)
71 {
72     HeapFree(GetProcessHeap(), NULL, pv);
73 }
74 
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 
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 
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 
138 FNCLOSE(fnFileClose)
139 {
140     return (CloseHandle((HANDLE)hf) != FALSE) ? 0 : -1;
141 }
142 
143 FNSEEK(fnFileSeek)
144 {
145     return SetFilePointer((HANDLE)hf, dist, NULL, seektype);
146 }
147 
148 /* FDICopy callbacks */
149 
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             MultiByteToWide(pfdin->psz1, szCabFileName, CP_ACP);
164 
165             if (!NotifyFileExtractCallback(szCabFileName, pfdin->cb, pfdin->attribs,
166                                            pND->Callback, pND->CallerCookie))
167             {
168                 break; // Skip file
169             }
170 
171             if (szCabFileName.Find('\\') >= 0)
172             {
173                 CStringW szNewDirName = szExtractDir;
174                 int nTokenPos = 0;
175                 // We do not want to interpret the filename as directory,
176                 // so bail out before the last token!
177                 while (szCabFileName.Find('\\', nTokenPos) >= 0)
178                 {
179                     CStringW token = szCabFileName.Tokenize(L"\\", nTokenPos);
180                     if (token.IsEmpty())
181                         break;
182 
183                     szNewDirName += L"\\" + token;
184                     if (!CreateDirectoryW(szNewDirName, NULL))
185                     {
186                         DWORD dwErr = GetLastError();
187                         if (dwErr != ERROR_ALREADY_EXISTS)
188                         {
189                             DPRINT1(
190                                 "ERROR: Unable to create directory %S (err %lu)\n", szNewDirName.GetString(), dwErr);
191                         }
192                     }
193                 }
194             }
195 
196             CStringW szNewFileName = szExtractDir + L"\\" + szCabFileName;
197 
198             CStringA szFilePathUTF8;
199             WideToMultiByte(szNewFileName, szFilePathUTF8, CP_UTF8);
200 
201             // Open the file
202             iResult = fnFileOpen((LPSTR)szFilePathUTF8.GetString(), _O_WRONLY | _O_CREAT, 0);
203         }
204         break;
205 
206         case fdintCLOSE_FILE_INFO:
207             iResult = !fnFileClose(pfdin->hf);
208             break;
209 
210         case fdintNEXT_CABINET:
211             if (pfdin->fdie != FDIERROR_NONE)
212             {
213                 iResult = -1;
214             }
215             break;
216 
217         case fdintPARTIAL_FILE:
218             iResult = 0;
219             break;
220 
221         case fdintCABINET_INFO:
222             iResult = 0;
223             break;
224 
225         case fdintENUMERATE:
226             iResult = 0;
227             break;
228 
229         default:
230             iResult = -1;
231             break;
232     }
233 
234     return iResult;
235 }
236 
237 /* cabinet.dll FDI function pointers */
238 
239 typedef HFDI (*fnFDICreate)(PFNALLOC, PFNFREE, PFNOPEN, PFNREAD, PFNWRITE, PFNCLOSE, PFNSEEK, int, PERF);
240 
241 typedef BOOL (*fnFDICopy)(HFDI, LPSTR, LPSTR, INT, PFNFDINOTIFY, PFNFDIDECRYPT, void FAR *pvUser);
242 
243 typedef BOOL (*fnFDIDestroy)(HFDI);
244 
245 /*
246  * Extraction function
247  */
248 BOOL
249 ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const CStringW &szOutputDir,
250                     EXTRACTCALLBACK Callback, void *Cookie)
251 {
252     HINSTANCE hCabinetDll;
253     HFDI ExtractHandler;
254     ERF ExtractErrors;
255     ATL::CStringA szCabNameUTF8, szCabDirUTF8, szOutputDirUTF8;
256     fnFDICreate pfnFDICreate;
257     fnFDICopy pfnFDICopy;
258     fnFDIDestroy pfnFDIDestroy;
259     BOOL bResult;
260     NotifyData nd = { Callback, Cookie };
261 
262     // Load cabinet.dll and extract needed functions
263     hCabinetDll = LoadLibraryW(L"cabinet.dll");
264 
265     if (!hCabinetDll)
266     {
267         return FALSE;
268     }
269 
270     pfnFDICreate = (fnFDICreate)GetProcAddress(hCabinetDll, "FDICreate");
271     pfnFDICopy = (fnFDICopy)GetProcAddress(hCabinetDll, "FDICopy");
272     pfnFDIDestroy = (fnFDIDestroy)GetProcAddress(hCabinetDll, "FDIDestroy");
273 
274     if (!pfnFDICreate || !pfnFDICopy || !pfnFDIDestroy)
275     {
276         FreeLibrary(hCabinetDll);
277         return FALSE;
278     }
279 
280     // Create FDI context
281     ExtractHandler = pfnFDICreate(
282         fnMemAlloc, fnMemFree, fnFileOpen, fnFileRead, fnFileWrite, fnFileClose, fnFileSeek, cpuUNKNOWN,
283         &ExtractErrors);
284 
285     if (!ExtractHandler)
286     {
287         FreeLibrary(hCabinetDll);
288         return FALSE;
289     }
290 
291     // Create output dir
292     bResult = CreateDirectoryW(szOutputDir, NULL);
293 
294     if (bResult || GetLastError() == ERROR_ALREADY_EXISTS)
295     {
296         // Convert wide strings to UTF-8
297         bResult = WideToMultiByte(szCabName, szCabNameUTF8, CP_UTF8);
298         bResult &= WideToMultiByte(szCabDir, szCabDirUTF8, CP_UTF8);
299         bResult &= WideToMultiByte(szOutputDir, szOutputDirUTF8, CP_UTF8);
300     }
301 
302     // Perform extraction
303     if (bResult)
304     {
305         // Add a slash to cab name as required by the api
306         szCabNameUTF8 = "\\" + szCabNameUTF8;
307 
308         nd.OutputDir = szOutputDirUTF8.GetString();
309         bResult = pfnFDICopy(
310             ExtractHandler, (LPSTR)szCabNameUTF8.GetString(), (LPSTR)szCabDirUTF8.GetString(), 0, fnNotify, NULL,
311             (void FAR *)&nd);
312     }
313 
314     pfnFDIDestroy(ExtractHandler);
315     FreeLibrary(hCabinetDll);
316     return bResult;
317 }
318 
319 BOOL
320 ExtractFilesFromCab(LPCWSTR FullCabPath, const CStringW &szOutputDir,
321                     EXTRACTCALLBACK Callback, void *Cookie)
322 {
323     CStringW dir, file = SplitFileAndDirectory(FullCabPath, &dir);
324     return ExtractFilesFromCab(file, dir, szOutputDir, Callback, Cookie);
325 }
326