xref: /reactos/base/applications/rapps/cabinet.cpp (revision 84344399)
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(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 
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             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
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
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