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