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 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 MultiByteToWide(pfdin->psz1, szCabFileName, CP_ACP); 164 165 if (!NotifyFileExtractCallback(szCabFileName, pfdin->cb, pfdin->attribs, 166 pND->Callback, pND->CallerCookie)) 167 { 168 break; // Skip file 169 } 170 171 if (szCabFileName.Find('\\') >= 0) 172 { 173 CStringW szNewDirName = szExtractDir; 174 int nTokenPos = 0; 175 // We do not want to interpret the filename as directory, 176 // so bail out before the last token! 177 while (szCabFileName.Find('\\', nTokenPos) >= 0) 178 { 179 CStringW token = szCabFileName.Tokenize(L"\\", nTokenPos); 180 if (token.IsEmpty()) 181 break; 182 183 szNewDirName += L"\\" + token; 184 if (!CreateDirectoryW(szNewDirName, NULL)) 185 { 186 DWORD dwErr = GetLastError(); 187 if (dwErr != ERROR_ALREADY_EXISTS) 188 { 189 DPRINT1( 190 "ERROR: Unable to create directory %S (err %lu)\n", szNewDirName.GetString(), dwErr); 191 } 192 } 193 } 194 } 195 196 CStringW szNewFileName = szExtractDir + L"\\" + szCabFileName; 197 198 CStringA szFilePathUTF8; 199 WideToMultiByte(szNewFileName, szFilePathUTF8, CP_UTF8); 200 201 // Open the file 202 iResult = fnFileOpen((LPSTR)szFilePathUTF8.GetString(), _O_WRONLY | _O_CREAT, 0); 203 } 204 break; 205 206 case fdintCLOSE_FILE_INFO: 207 iResult = !fnFileClose(pfdin->hf); 208 break; 209 210 case fdintNEXT_CABINET: 211 if (pfdin->fdie != FDIERROR_NONE) 212 { 213 iResult = -1; 214 } 215 break; 216 217 case fdintPARTIAL_FILE: 218 iResult = 0; 219 break; 220 221 case fdintCABINET_INFO: 222 iResult = 0; 223 break; 224 225 case fdintENUMERATE: 226 iResult = 0; 227 break; 228 229 default: 230 iResult = -1; 231 break; 232 } 233 234 return iResult; 235 } 236 237 /* cabinet.dll FDI function pointers */ 238 239 typedef HFDI (*fnFDICreate)(PFNALLOC, PFNFREE, PFNOPEN, PFNREAD, PFNWRITE, PFNCLOSE, PFNSEEK, int, PERF); 240 241 typedef BOOL (*fnFDICopy)(HFDI, LPSTR, LPSTR, INT, PFNFDINOTIFY, PFNFDIDECRYPT, void FAR *pvUser); 242 243 typedef BOOL (*fnFDIDestroy)(HFDI); 244 245 /* 246 * Extraction function 247 */ 248 BOOL 249 ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const CStringW &szOutputDir, 250 EXTRACTCALLBACK Callback, void *Cookie) 251 { 252 HINSTANCE hCabinetDll; 253 HFDI ExtractHandler; 254 ERF ExtractErrors; 255 ATL::CStringA szCabNameUTF8, szCabDirUTF8, szOutputDirUTF8; 256 fnFDICreate pfnFDICreate; 257 fnFDICopy pfnFDICopy; 258 fnFDIDestroy pfnFDIDestroy; 259 BOOL bResult; 260 NotifyData nd = { Callback, Cookie }; 261 262 // Load cabinet.dll and extract needed functions 263 hCabinetDll = LoadLibraryW(L"cabinet.dll"); 264 265 if (!hCabinetDll) 266 { 267 return FALSE; 268 } 269 270 pfnFDICreate = (fnFDICreate)GetProcAddress(hCabinetDll, "FDICreate"); 271 pfnFDICopy = (fnFDICopy)GetProcAddress(hCabinetDll, "FDICopy"); 272 pfnFDIDestroy = (fnFDIDestroy)GetProcAddress(hCabinetDll, "FDIDestroy"); 273 274 if (!pfnFDICreate || !pfnFDICopy || !pfnFDIDestroy) 275 { 276 FreeLibrary(hCabinetDll); 277 return FALSE; 278 } 279 280 // Create FDI context 281 ExtractHandler = pfnFDICreate( 282 fnMemAlloc, fnMemFree, fnFileOpen, fnFileRead, fnFileWrite, fnFileClose, fnFileSeek, cpuUNKNOWN, 283 &ExtractErrors); 284 285 if (!ExtractHandler) 286 { 287 FreeLibrary(hCabinetDll); 288 return FALSE; 289 } 290 291 // Create output dir 292 bResult = CreateDirectoryW(szOutputDir, NULL); 293 294 if (bResult || GetLastError() == ERROR_ALREADY_EXISTS) 295 { 296 // Convert wide strings to UTF-8 297 bResult = WideToMultiByte(szCabName, szCabNameUTF8, CP_UTF8); 298 bResult &= WideToMultiByte(szCabDir, szCabDirUTF8, CP_UTF8); 299 bResult &= WideToMultiByte(szOutputDir, szOutputDirUTF8, CP_UTF8); 300 } 301 302 // Perform extraction 303 if (bResult) 304 { 305 // Add a slash to cab name as required by the api 306 szCabNameUTF8 = "\\" + szCabNameUTF8; 307 308 nd.OutputDir = szOutputDirUTF8.GetString(); 309 bResult = pfnFDICopy( 310 ExtractHandler, (LPSTR)szCabNameUTF8.GetString(), (LPSTR)szCabDirUTF8.GetString(), 0, fnNotify, NULL, 311 (void FAR *)&nd); 312 } 313 314 pfnFDIDestroy(ExtractHandler); 315 FreeLibrary(hCabinetDll); 316 return bResult; 317 } 318 319 BOOL 320 ExtractFilesFromCab(LPCWSTR FullCabPath, const CStringW &szOutputDir, 321 EXTRACTCALLBACK Callback, void *Cookie) 322 { 323 CStringW dir, file = SplitFileAndDirectory(FullCabPath, &dir); 324 return ExtractFilesFromCab(file, dir, szOutputDir, Callback, Cookie); 325 } 326