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