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