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