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