1 /* 2 * PROJECT: ReactOS Applications Manager 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * FILE: base/applications/rapps/cabinet.cpp 5 * PURPOSE: Cabinet extraction using FDI API 6 * COPYRIGHT: Copyright 2018 Alexander Shaposhnikov (sanchaez@reactos.org) 7 */ 8 #include "rapps.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 ATL::CStringW szNewFileName, szExtractDir, szCabFileName; 183 ATL::CStringA szFilePathUTF8; 184 185 // Append the destination directory to the file name. 186 MultiByteToWide((LPCSTR) pfdin->pv, szExtractDir, CP_UTF8); 187 MultiByteToWide(pfdin->psz1, szCabFileName, CP_ACP); 188 189 szNewFileName = szExtractDir + L"\\" + szCabFileName; 190 191 WideToMultiByte(szNewFileName, szFilePathUTF8, CP_UTF8); 192 193 // Copy file 194 iResult = fnFileOpen((LPSTR) szFilePathUTF8.GetString(), 195 _O_WRONLY | _O_CREAT, 196 0); 197 } 198 break; 199 200 case fdintCLOSE_FILE_INFO: 201 iResult = !fnFileClose(pfdin->hf); 202 break; 203 204 case fdintNEXT_CABINET: 205 if (pfdin->fdie != FDIERROR_NONE) 206 { 207 iResult = -1; 208 } 209 break; 210 211 case fdintPARTIAL_FILE: 212 iResult = 0; 213 break; 214 215 case fdintCABINET_INFO: 216 iResult = 0; 217 break; 218 219 case fdintENUMERATE: 220 iResult = 0; 221 break; 222 223 default: 224 iResult = -1; 225 break; 226 } 227 228 return iResult; 229 } 230 231 /* cabinet.dll FDI function pointers */ 232 233 typedef HFDI(*fnFDICreate)(PFNALLOC, 234 PFNFREE, 235 PFNOPEN, 236 PFNREAD, 237 PFNWRITE, 238 PFNCLOSE, 239 PFNSEEK, 240 int, 241 PERF); 242 243 typedef BOOL(*fnFDICopy)(HFDI, 244 LPSTR, 245 LPSTR, 246 INT, 247 PFNFDINOTIFY, 248 PFNFDIDECRYPT, 249 void FAR *pvUser); 250 251 typedef BOOL(*fnFDIDestroy)(HFDI); 252 253 /* 254 * Extraction function 255 * TODO: require only a full path to the cab as an argument 256 */ 257 BOOL ExtractFilesFromCab(const ATL::CStringW& szCabName, 258 const ATL::CStringW& szCabDir, 259 const ATL::CStringW& szOutputDir) 260 { 261 HINSTANCE hCabinetDll; 262 HFDI ExtractHandler; 263 ERF ExtractErrors; 264 ATL::CStringA szCabNameUTF8, szCabDirUTF8, szOutputDirUTF8; 265 fnFDICreate pfnFDICreate; 266 fnFDICopy pfnFDICopy; 267 fnFDIDestroy pfnFDIDestroy; 268 BOOL bResult; 269 270 // Load cabinet.dll and extract needed functions 271 hCabinetDll = LoadLibraryW(L"cabinet.dll"); 272 273 if (!hCabinetDll) 274 { 275 return FALSE; 276 } 277 278 pfnFDICreate = (fnFDICreate) GetProcAddress(hCabinetDll, "FDICreate"); 279 pfnFDICopy = (fnFDICopy) GetProcAddress(hCabinetDll, "FDICopy"); 280 pfnFDIDestroy = (fnFDIDestroy) GetProcAddress(hCabinetDll, "FDIDestroy"); 281 282 if (!pfnFDICreate || !pfnFDICopy || !pfnFDIDestroy) 283 { 284 FreeLibrary(hCabinetDll); 285 return FALSE; 286 } 287 288 // Create FDI context 289 ExtractHandler = pfnFDICreate(fnMemAlloc, 290 fnMemFree, 291 fnFileOpen, 292 fnFileRead, 293 fnFileWrite, 294 fnFileClose, 295 fnFileSeek, 296 cpuUNKNOWN, 297 &ExtractErrors); 298 299 if (!ExtractHandler) 300 { 301 FreeLibrary(hCabinetDll); 302 return FALSE; 303 } 304 305 // Create output dir 306 bResult = CreateDirectoryW(szOutputDir, NULL); 307 308 if (bResult || GetLastError() == ERROR_ALREADY_EXISTS) 309 { 310 // Convert wide strings to UTF-8 311 bResult = WideToMultiByte(szCabName, szCabNameUTF8, CP_UTF8); 312 bResult &= WideToMultiByte(szCabDir, szCabDirUTF8, CP_UTF8); 313 bResult &= WideToMultiByte(szOutputDir, szOutputDirUTF8, CP_UTF8); 314 } 315 316 // Perform extraction 317 if (bResult) 318 { 319 // Add a slash to cab name as required by the api 320 szCabNameUTF8 = "\\" + szCabNameUTF8; 321 322 bResult = pfnFDICopy(ExtractHandler, 323 (LPSTR) szCabNameUTF8.GetString(), 324 (LPSTR) szCabDirUTF8.GetString(), 325 0, 326 fnNotify, 327 NULL, 328 (void FAR *) szOutputDirUTF8.GetString()); 329 } 330 331 pfnFDIDestroy(ExtractHandler); 332 FreeLibrary(hCabinetDll); 333 return bResult; 334 } 335