reactos/base/applications/rapps/cabinet.cpp
Whindmar Saksit a45d375f60
[RAPPS] Fix CAB codepage handling (#7165)
- MultiByteToWide parameter bug.
- Use CP_UTF8 to convert cab filenames if the UTF attribute is set.
2024-07-22 16:20:31 +02:00

326 lines
8.5 KiB
C++

/*
* PROJECT: ReactOS Applications Manager
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Cabinet extraction using FDI API
* COPYRIGHT: Copyright 2018 Alexander Shaposhnikov (sanchaez@reactos.org)
*/
#include "rapps.h"
#include <debug.h>
#include <fdi.h>
#include <fcntl.h>
/*
* HACK: treat any input strings as Unicode (UTF-8)
* cabinet.dll lacks any sort of a Unicode API, but FCI/FDI
* provide an ability to use user-defined callbacks for any file or memory
* operations. This flexibility and the magic power of C/C++ casting allows
* us to treat input as we please.
* This is by far the best way to extract .cab using Unicode paths.
*/
/* String conversion helper functions */
// converts CStringW to CStringA using a given codepage
inline BOOL
WideToMultiByte(const CStringW &szSource, CStringA &szDest, UINT Codepage)
{
// determine the needed size
INT sz = WideCharToMultiByte(Codepage, 0, szSource, -1, NULL, NULL, NULL, NULL);
if (!sz)
return FALSE;
// do the actual conversion
sz = WideCharToMultiByte(Codepage, 0, szSource, -1, szDest.GetBuffer(sz), sz, NULL, NULL);
szDest.ReleaseBuffer();
return sz != 0;
}
// converts CStringA to CStringW using a given codepage
inline BOOL
MultiByteToWide(const CStringA &szSource, CStringW &szDest, UINT Codepage)
{
// determine the needed size
INT sz = MultiByteToWideChar(Codepage, 0, szSource, -1, NULL, NULL);
if (!sz)
return FALSE;
// do the actual conversion
sz = MultiByteToWideChar(Codepage, 0, szSource, -1, szDest.GetBuffer(sz), sz);
szDest.ReleaseBuffer();
return sz != 0;
}
struct NotifyData
{
EXTRACTCALLBACK Callback;
void *CallerCookie;
LPCSTR OutputDir;
};
/* FDICreate callbacks */
FNALLOC(fnMemAlloc)
{
return HeapAlloc(GetProcessHeap(), NULL, cb);
}
FNFREE(fnMemFree)
{
HeapFree(GetProcessHeap(), NULL, pv);
}
FNOPEN(fnFileOpen)
{
HANDLE hFile = NULL;
DWORD dwDesiredAccess = 0;
DWORD dwCreationDisposition = 0;
CStringW szFileName;
UNREFERENCED_PARAMETER(pmode);
if (oflag & _O_RDWR)
{
dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
}
else if (oflag & _O_WRONLY)
{
dwDesiredAccess = GENERIC_WRITE;
}
else
{
dwDesiredAccess = GENERIC_READ;
}
if (oflag & _O_CREAT)
{
dwCreationDisposition = CREATE_ALWAYS;
}
else
{
dwCreationDisposition = OPEN_EXISTING;
}
MultiByteToWide(pszFile, szFileName, CP_UTF8);
hFile = CreateFileW(
szFileName, dwDesiredAccess, FILE_SHARE_READ, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
return (INT_PTR)hFile;
}
FNREAD(fnFileRead)
{
DWORD dwBytesRead = 0;
if (ReadFile((HANDLE)hf, pv, cb, &dwBytesRead, NULL) == FALSE)
{
dwBytesRead = (DWORD)-1L;
}
return dwBytesRead;
}
FNWRITE(fnFileWrite)
{
DWORD dwBytesWritten = 0;
if (WriteFile((HANDLE)hf, pv, cb, &dwBytesWritten, NULL) == FALSE)
{
dwBytesWritten = (DWORD)-1;
}
return dwBytesWritten;
}
FNCLOSE(fnFileClose)
{
return (CloseHandle((HANDLE)hf) != FALSE) ? 0 : -1;
}
FNSEEK(fnFileSeek)
{
return SetFilePointer((HANDLE)hf, dist, NULL, seektype);
}
/* FDICopy callbacks */
FNFDINOTIFY(fnNotify)
{
INT_PTR iResult = 0;
NotifyData *pND = (NotifyData *)pfdin->pv;
switch (fdint)
{
case fdintCOPY_FILE:
{
CStringW szExtractDir, szCabFileName;
// Append the destination directory to the file name.
MultiByteToWide(pND->OutputDir, szExtractDir, CP_UTF8);
UINT codepage = (pfdin->attribs & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP;
MultiByteToWide(pfdin->psz1, szCabFileName, codepage);
if (!NotifyFileExtractCallback(szCabFileName, pfdin->cb, pfdin->attribs,
pND->Callback, pND->CallerCookie))
{
break; // Skip file
}
if (szCabFileName.Find('\\') >= 0)
{
CStringW szNewDirName = szExtractDir;
int nTokenPos = 0;
// We do not want to interpret the filename as directory,
// so bail out before the last token!
while (szCabFileName.Find('\\', nTokenPos) >= 0)
{
CStringW token = szCabFileName.Tokenize(L"\\", nTokenPos);
if (token.IsEmpty())
break;
szNewDirName += L"\\" + token;
if (!CreateDirectoryW(szNewDirName, NULL))
{
DWORD dwErr = GetLastError();
if (dwErr != ERROR_ALREADY_EXISTS)
{
DPRINT1(
"ERROR: Unable to create directory %S (err %lu)\n", szNewDirName.GetString(), dwErr);
}
}
}
}
CStringW szNewFileName = szExtractDir + L"\\" + szCabFileName;
CStringA szFilePathUTF8;
WideToMultiByte(szNewFileName, szFilePathUTF8, CP_UTF8);
// Open the file
iResult = fnFileOpen((LPSTR)szFilePathUTF8.GetString(), _O_WRONLY | _O_CREAT, 0);
}
break;
case fdintCLOSE_FILE_INFO:
iResult = !fnFileClose(pfdin->hf);
break;
case fdintNEXT_CABINET:
if (pfdin->fdie != FDIERROR_NONE)
{
iResult = -1;
}
break;
case fdintPARTIAL_FILE:
iResult = 0;
break;
case fdintCABINET_INFO:
iResult = 0;
break;
case fdintENUMERATE:
iResult = 0;
break;
default:
iResult = -1;
break;
}
return iResult;
}
/* cabinet.dll FDI function pointers */
typedef HFDI (*fnFDICreate)(PFNALLOC, PFNFREE, PFNOPEN, PFNREAD, PFNWRITE, PFNCLOSE, PFNSEEK, int, PERF);
typedef BOOL (*fnFDICopy)(HFDI, LPSTR, LPSTR, INT, PFNFDINOTIFY, PFNFDIDECRYPT, void FAR *pvUser);
typedef BOOL (*fnFDIDestroy)(HFDI);
/*
* Extraction function
*/
BOOL
ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const CStringW &szOutputDir,
EXTRACTCALLBACK Callback, void *Cookie)
{
HINSTANCE hCabinetDll;
HFDI ExtractHandler;
ERF ExtractErrors;
ATL::CStringA szCabNameUTF8, szCabDirUTF8, szOutputDirUTF8;
fnFDICreate pfnFDICreate;
fnFDICopy pfnFDICopy;
fnFDIDestroy pfnFDIDestroy;
BOOL bResult;
NotifyData nd = { Callback, Cookie };
// Load cabinet.dll and extract needed functions
hCabinetDll = LoadLibraryW(L"cabinet.dll");
if (!hCabinetDll)
{
return FALSE;
}
pfnFDICreate = (fnFDICreate)GetProcAddress(hCabinetDll, "FDICreate");
pfnFDICopy = (fnFDICopy)GetProcAddress(hCabinetDll, "FDICopy");
pfnFDIDestroy = (fnFDIDestroy)GetProcAddress(hCabinetDll, "FDIDestroy");
if (!pfnFDICreate || !pfnFDICopy || !pfnFDIDestroy)
{
FreeLibrary(hCabinetDll);
return FALSE;
}
// Create FDI context
ExtractHandler = pfnFDICreate(
fnMemAlloc, fnMemFree, fnFileOpen, fnFileRead, fnFileWrite, fnFileClose, fnFileSeek, cpuUNKNOWN,
&ExtractErrors);
if (!ExtractHandler)
{
FreeLibrary(hCabinetDll);
return FALSE;
}
// Create output dir
bResult = CreateDirectoryW(szOutputDir, NULL);
if (bResult || GetLastError() == ERROR_ALREADY_EXISTS)
{
// Convert wide strings to UTF-8
bResult = WideToMultiByte(szCabName, szCabNameUTF8, CP_UTF8);
bResult &= WideToMultiByte(szCabDir, szCabDirUTF8, CP_UTF8);
bResult &= WideToMultiByte(szOutputDir, szOutputDirUTF8, CP_UTF8);
}
// Perform extraction
if (bResult)
{
// Add a slash to cab name as required by the api
szCabNameUTF8 = "\\" + szCabNameUTF8;
nd.OutputDir = szOutputDirUTF8.GetString();
bResult = pfnFDICopy(
ExtractHandler, (LPSTR)szCabNameUTF8.GetString(), (LPSTR)szCabDirUTF8.GetString(), 0, fnNotify, NULL,
(void FAR *)&nd);
}
pfnFDIDestroy(ExtractHandler);
FreeLibrary(hCabinetDll);
return bResult;
}
BOOL
ExtractFilesFromCab(LPCWSTR FullCabPath, const CStringW &szOutputDir,
EXTRACTCALLBACK Callback, void *Cookie)
{
CStringW dir, file = SplitFileAndDirectory(FullCabPath, &dir);
return ExtractFilesFromCab(file, dir, szOutputDir, Callback, Cookie);
}