// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. #include "precomp.h" // Exit macros #define CabExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__) #define CabExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__) #define CabExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__) #define CabExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__) #define CabExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__) #define CabExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_CABUTIL, x, s, __VA_ARGS__) #define CabExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_CABUTIL, p, x, e, s, __VA_ARGS__) #define CabExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_CABUTIL, p, x, s, __VA_ARGS__) #define CabExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_CABUTIL, p, x, e, s, __VA_ARGS__) #define CabExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_CABUTIL, p, x, s, __VA_ARGS__) #define CabExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_CABUTIL, e, x, s, __VA_ARGS__) #define CabExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_CABUTIL, g, x, s, __VA_ARGS__) // external prototypes typedef BOOL (FAR DIAMONDAPI *PFNFDIDESTROY)(VOID*); typedef HFDI (FAR DIAMONDAPI *PFNFDICREATE)(PFNALLOC, PFNFREE, PFNOPEN, PFNREAD, PFNWRITE, PFNCLOSE, PFNSEEK, int, PERF); typedef BOOL (FAR DIAMONDAPI *PFNFDIISCABINET)(HFDI, INT_PTR, PFDICABINETINFO); typedef BOOL (FAR DIAMONDAPI *PFNFDICOPY)(HFDI, char *, char *, int, PFNFDINOTIFY, PFNFDIDECRYPT, void *); // // static globals // static HMODULE vhCabinetDll = NULL; static HFDI vhfdi = NULL; static PFNFDICREATE vpfnFDICreate = NULL; static PFNFDICOPY vpfnFDICopy = NULL; static PFNFDIISCABINET vpfnFDIIsCabinet = NULL; static PFNFDIDESTROY vpfnFDIDestroy = NULL; static ERF verf; static DWORD64 vdw64EmbeddedOffset = 0; // // structs // struct CAB_CALLBACK_STRUCT { BOOL fStopExtracting; // flag set when no more files are needed LPCWSTR pwzExtract; // file to extract ("*" means extract all) LPCWSTR pwzExtractDir; // directory to extract files to // possible user data CAB_CALLBACK_PROGRESS pfnProgress; LPVOID pvContext; }; // // prototypes // static __callback LPVOID DIAMONDAPI CabExtractAlloc(__in DWORD dwSize); static __callback void DIAMONDAPI CabExtractFree(__in LPVOID pvData); static __callback INT_PTR FAR DIAMONDAPI CabExtractOpen(__in_z PSTR pszFile, __in int oflag, __in int pmode); static __callback UINT FAR DIAMONDAPI CabExtractRead(__in INT_PTR hf, __out void FAR *pv, __in UINT cb); static __callback UINT FAR DIAMONDAPI CabExtractWrite(__in INT_PTR hf, __in void FAR *pv, __in UINT cb); static __callback int FAR DIAMONDAPI CabExtractClose(__in INT_PTR hf); static __callback long FAR DIAMONDAPI CabExtractSeek(__in INT_PTR hf, __in long dist, __in int seektype); static __callback INT_PTR DIAMONDAPI CabExtractCallback(__in FDINOTIFICATIONTYPE iNotification, __inout FDINOTIFICATION *pFDINotify); static HRESULT DAPI CabOperation(__in LPCWSTR wzCabinet, __in LPCWSTR wzExtractFile, __in_opt LPCWSTR wzExtractDir, __in_opt CAB_CALLBACK_PROGRESS pfnProgress, __in_opt LPVOID pvContext, __in_opt STDCALL_PFNFDINOTIFY pfnNotify, __in DWORD64 dw64EmbeddedOffset); static STDCALL_PFNFDINOTIFY v_pfnNetFx11Notify = NULL; inline HRESULT LoadCabinetDll() { HRESULT hr = S_OK; if (!vhCabinetDll) { hr = LoadSystemLibrary(L"cabinet.dll", &vhCabinetDll); CabExitOnFailure(hr, "failed to load cabinet.dll"); // retrieve all address functions vpfnFDICreate = reinterpret_cast<PFNFDICREATE>(::GetProcAddress(vhCabinetDll, "FDICreate")); CabExitOnNullWithLastError(vpfnFDICreate, hr, "failed to import FDICreate from CABINET.DLL"); vpfnFDICopy = reinterpret_cast<PFNFDICOPY>(::GetProcAddress(vhCabinetDll, "FDICopy")); CabExitOnNullWithLastError(vpfnFDICopy, hr, "failed to import FDICopy from CABINET.DLL"); vpfnFDIIsCabinet = reinterpret_cast<PFNFDIISCABINET>(::GetProcAddress(vhCabinetDll, "FDIIsCabinet")); CabExitOnNullWithLastError(vpfnFDIIsCabinet, hr, "failed to import FDIIsCabinetfrom CABINET.DLL"); vpfnFDIDestroy = reinterpret_cast<PFNFDIDESTROY>(::GetProcAddress(vhCabinetDll, "FDIDestroy")); CabExitOnNullWithLastError(vpfnFDIDestroy, hr, "failed to import FDIDestroyfrom CABINET.DLL"); vhfdi = vpfnFDICreate(CabExtractAlloc, CabExtractFree, CabExtractOpen, CabExtractRead, CabExtractWrite, CabExtractClose, CabExtractSeek, cpuUNKNOWN, &verf); CabExitOnNull(vhfdi, hr, E_FAIL, "failed to initialize cabinet.dll"); } LExit: if (FAILED(hr) && vhCabinetDll) { ::FreeLibrary(vhCabinetDll); vhCabinetDll = NULL; } return hr; } static HANDLE OpenFileWithRetry( __in LPCWSTR wzPath, __in DWORD dwDesiredAccess, __in DWORD dwCreationDisposition ) { HANDLE hFile = INVALID_HANDLE_VALUE; for (DWORD i = 0; i < 30; ++i) { hFile = ::CreateFileW(wzPath, dwDesiredAccess, FILE_SHARE_READ, NULL, dwCreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE != hFile) { break; } ::Sleep(100); } return hFile; } /******************************************************************** CabInitialize - initializes internal static variables ********************************************************************/ extern "C" HRESULT DAPI CabInitialize( __in BOOL fDelayLoad ) { HRESULT hr = S_OK; if (!fDelayLoad) { hr = LoadCabinetDll(); CabExitOnFailure(hr, "failed to load CABINET.DLL"); } LExit: return hr; } /******************************************************************** CabUninitialize - initializes internal static variables ********************************************************************/ extern "C" void DAPI CabUninitialize( ) { if (vhfdi) { if (vpfnFDIDestroy) { vpfnFDIDestroy(vhfdi); } vhfdi = NULL; } vpfnFDICreate = NULL; vpfnFDICopy =NULL; vpfnFDIIsCabinet = NULL; vpfnFDIDestroy = NULL; if (vhCabinetDll) { ::FreeLibrary(vhCabinetDll); vhCabinetDll = NULL; } } /******************************************************************** CabEnumerate - list files inside cabinet NOTE: wzCabinet must be full path to cabinet file pfnNotify is callback function to get notified for each file in the cabinet ********************************************************************/ extern "C" HRESULT DAPI CabEnumerate( __in_z LPCWSTR wzCabinet, __in_z LPCWSTR wzEnumerateFile, __in STDCALL_PFNFDINOTIFY pfnNotify, __in DWORD64 dw64EmbeddedOffset ) { return CabOperation(wzCabinet, wzEnumerateFile, NULL, NULL, NULL, pfnNotify, dw64EmbeddedOffset); } /******************************************************************** CabExtract - extracts one or all files from a cabinet NOTE: wzCabinet must be full path to cabinet file wzExtractFile can be a single file id or "*" to extract all files wzExttractDir must be normalized (end in a "\") if pfnBeginFile is NULL pfnEndFile must be NULL and vice versa ********************************************************************/ extern "C" HRESULT DAPI CabExtract( __in_z LPCWSTR wzCabinet, __in_z LPCWSTR wzExtractFile, __in_z LPCWSTR wzExtractDir, __in_opt CAB_CALLBACK_PROGRESS pfnProgress, __in_opt LPVOID pvContext, __in DWORD64 dw64EmbeddedOffset ) { return CabOperation(wzCabinet, wzExtractFile, wzExtractDir, pfnProgress, pvContext, NULL, dw64EmbeddedOffset); } // // private // /******************************************************************** FDINotify -- wrapper that converts call convention from __cdecl to __stdcall. NOTE: Since netfx 1.1 supports only function pointers (delegates) with __stdcall calling convention and cabinet api uses __cdecl calling convention, we need this wrapper function. netfx 2.0 will work with [UnmanagedFunctionPointer(CallingConvention.Cdecl)] attribute on the delegate. TODO: remove this when upgrading to netfx 2.0. ********************************************************************/ static __callback INT_PTR DIAMONDAPI FDINotify( __in FDINOTIFICATIONTYPE iNotification, __inout FDINOTIFICATION *pFDINotify ) { if (NULL != v_pfnNetFx11Notify) { return v_pfnNetFx11Notify(iNotification, pFDINotify); } else { return (INT_PTR)0; } } /******************************************************************** CabOperation - helper function that enumerates or extracts files from cabinet NOTE: wzCabinet must be full path to cabinet file wzExtractFile can be a single file id or "*" to extract all files wzExttractDir must be normalized (end in a "\") if pfnBeginFile is NULL pfnEndFile must be NULL and vice versa pfnNotify is callback function to get notified for each file in the cabinet. If it's NULL, files will be extracted. ********************************************************************/ static HRESULT DAPI CabOperation( __in LPCWSTR wzCabinet, __in LPCWSTR wzExtractFile, __in_opt LPCWSTR wzExtractDir, __in_opt CAB_CALLBACK_PROGRESS pfnProgress, __in_opt LPVOID pvContext, __in_opt STDCALL_PFNFDINOTIFY pfnNotify, __in DWORD64 dw64EmbeddedOffset ) { HRESULT hr = S_OK; BOOL fResult = FALSE; LPWSTR sczCabinet = NULL; LPWSTR pwz = NULL; LPSTR pszCabDirectory = NULL; CHAR szCabFile[MAX_PATH * 4] = { }; // Make sure this is big enough for UTF-8 strings CAB_CALLBACK_STRUCT ccs = { }; PFNFDINOTIFY pfnFdiNotify = NULL; // // ensure the cabinet.dll is loaded // if (!vhfdi) { hr = LoadCabinetDll(); CabExitOnFailure(hr, "failed to load CABINET.DLL"); } hr = StrAllocString(&sczCabinet, wzCabinet, 0); CabExitOnFailure(hr, "Failed to make copy of cabinet name:%ls", wzCabinet); // // split the cabinet full path into directory and filename and convert to multi-byte (ick!) // pwz = PathFile(sczCabinet); CabExitOnNull(pwz, hr, E_INVALIDARG, "failed to process cabinet path: %ls", wzCabinet); if (!::WideCharToMultiByte(CP_UTF8, 0, pwz, -1, szCabFile, countof(szCabFile), NULL, NULL)) { CabExitWithLastError(hr, "failed to convert cabinet filename to ASCII: %ls", pwz); } *pwz = '\0'; // If a full path was not provided, use the relative current directory. if (wzCabinet == pwz) { hr = StrAnsiAllocStringAnsi(&pszCabDirectory, ".\\", 0); CabExitOnFailure(hr, "Failed to copy relative current directory as cabinet directory."); } else { hr = StrAnsiAllocString(&pszCabDirectory, sczCabinet, 0, CP_UTF8); CabExitOnFailure(hr, "failed to convert cabinet directory to ASCII: %ls", sczCabinet); } // // iterate through files in cabinet extracting them to the callback function // ccs.fStopExtracting = FALSE; ccs.pwzExtract = wzExtractFile; ccs.pwzExtractDir = wzExtractDir; ccs.pfnProgress = pfnProgress; ccs.pvContext = pvContext; vdw64EmbeddedOffset = dw64EmbeddedOffset; // if pfnNotify is given, use it, otherwise use default callback if (NULL == pfnNotify) { pfnFdiNotify = CabExtractCallback; } else { v_pfnNetFx11Notify = pfnNotify; pfnFdiNotify = FDINotify; } fResult = vpfnFDICopy(vhfdi, szCabFile, pszCabDirectory, 0, pfnFdiNotify, NULL, static_cast<void*>(&ccs)); if (!fResult && !ccs.fStopExtracting) // if something went wrong and it wasn't us just stopping the extraction, then return a failure { CabExitWithLastError(hr, "failed to extract cabinet file: %ls", sczCabinet); } LExit: ReleaseStr(sczCabinet); ReleaseStr(pszCabDirectory); v_pfnNetFx11Notify = NULL; return hr; } /**************************************************************************** default extract routines ****************************************************************************/ static __callback LPVOID DIAMONDAPI CabExtractAlloc(__in DWORD dwSize) { return MemAlloc(dwSize, FALSE); } static __callback void DIAMONDAPI CabExtractFree(__in LPVOID pvData) { MemFree(pvData); } static __callback INT_PTR FAR DIAMONDAPI CabExtractOpen(__in_z PSTR pszFile, __in int oflag, __in int pmode) { HRESULT hr = S_OK; HANDLE hFile = INVALID_HANDLE_VALUE; INT_PTR pFile = -1; LPWSTR sczCabFile = NULL; // if FDI asks for some unusual mode (in low memory situation it could ask for a scratch file) fail if ((oflag != (/*_O_BINARY*/ 0x8000 | /*_O_RDONLY*/ 0x0000)) || (pmode != (_S_IREAD | _S_IWRITE))) { hr = E_OUTOFMEMORY; CabExitOnFailure(hr, "FDI asked for a scratch file to be created, which is unsupported"); } hr = StrAllocStringAnsi(&sczCabFile, pszFile, 0, CP_UTF8); CabExitOnFailure(hr, "Failed to convert UTF8 cab file name to wide character string"); hFile = OpenFileWithRetry(sczCabFile, GENERIC_READ, OPEN_EXISTING); if (INVALID_HANDLE_VALUE == hFile) { CabExitWithLastError(hr, "failed to open file: %ls", sczCabFile); } pFile = reinterpret_cast<INT_PTR>(hFile); if (vdw64EmbeddedOffset) { hr = CabExtractSeek(pFile, 0, 0); CabExitOnFailure(hr, "Failed to seek to embedded offset %I64d", vdw64EmbeddedOffset); } hFile = INVALID_HANDLE_VALUE; LExit: ReleaseFileHandle(hFile); ReleaseStr(sczCabFile); return FAILED(hr) ? -1 : pFile; } static __callback UINT FAR DIAMONDAPI CabExtractRead(__in INT_PTR hf, __out void FAR *pv, __in UINT cb) { HRESULT hr = S_OK; DWORD cbRead = 0; CabExitOnNull(hf, hr, E_INVALIDARG, "Failed to read file during cabinet extraction - no file given to read"); if (!::ReadFile(reinterpret_cast<HANDLE>(hf), pv, cb, &cbRead, NULL)) { CabExitWithLastError(hr, "failed to read during cabinet extraction"); } LExit: return FAILED(hr) ? -1 : cbRead; } static __callback UINT FAR DIAMONDAPI CabExtractWrite(__in INT_PTR hf, __in void FAR *pv, __in UINT cb) { HRESULT hr = S_OK; DWORD cbWrite = 0; CabExitOnNull(hf, hr, E_INVALIDARG, "Failed to write file during cabinet extraction - no file given to write"); if (!::WriteFile(reinterpret_cast<HANDLE>(hf), pv, cb, &cbWrite, NULL)) { CabExitWithLastError(hr, "failed to write during cabinet extraction"); } LExit: return FAILED(hr) ? -1 : cbWrite; } static __callback long FAR DIAMONDAPI CabExtractSeek(__in INT_PTR hf, __in long dist, __in int seektype) { HRESULT hr = S_OK; DWORD dwMoveMethod; LONG lMove = 0; switch (seektype) { case 0: // SEEK_SET dwMoveMethod = FILE_BEGIN; dist += static_cast<long>(vdw64EmbeddedOffset); break; case 1: /// SEEK_CUR dwMoveMethod = FILE_CURRENT; break; case 2: // SEEK_END dwMoveMethod = FILE_END; break; default : dwMoveMethod = 0; hr = E_UNEXPECTED; CabExitOnFailure(hr, "unexpected seektype in FDISeek(): %d", seektype); } // SetFilePointer returns -1 if it fails (this will cause FDI to quit with an FDIERROR_USER_ABORT error. // (Unless this happens while working on a cabinet, in which case FDI returns FDIERROR_CORRUPT_CABINET) lMove = ::SetFilePointer(reinterpret_cast<HANDLE>(hf), dist, NULL, dwMoveMethod); if (0xFFFFFFFF == lMove) { CabExitWithLastError(hr, "failed to move file pointer %d bytes", dist); } LExit: return FAILED(hr) ? -1 : lMove - static_cast<long>(vdw64EmbeddedOffset); } static __callback int FAR DIAMONDAPI CabExtractClose(__in INT_PTR hf) { HRESULT hr = S_OK; if (!::CloseHandle(reinterpret_cast<HANDLE>(hf))) { CabExitWithLastError(hr, "failed to close file during cabinet extraction"); } LExit: return FAILED(hr) ? -1 : 0; } static __callback INT_PTR DIAMONDAPI CabExtractCallback(__in FDINOTIFICATIONTYPE iNotification, __inout FDINOTIFICATION *pFDINotify) { Assert(pFDINotify->pv); HRESULT hr = S_OK; HANDLE hFile = INVALID_HANDLE_VALUE; INT_PTR ipResult = 0; // result to return on success CAB_CALLBACK_STRUCT* pccs = static_cast<CAB_CALLBACK_STRUCT*>(pFDINotify->pv); LPCSTR sz = NULL; LPWSTR pwz = NULL; LPWSTR pwzPath = NULL; FILETIME ft = { }; switch (iNotification) { case fdintCOPY_FILE: // begin extracting a resource from cabinet Assert(pccs && pFDINotify->psz1); CabExitOnNull(pccs, hr, E_INVALIDARG, "Failed to call cabextract callback, because no callback struct was provided"); if (pccs->fStopExtracting) { ExitFunction1(hr = S_FALSE); // no more extracting } // convert params to useful variables sz = static_cast<LPCSTR>(pFDINotify->psz1); CabExitOnNull(sz, hr, E_INVALIDARG, "No cabinet file ID given to convert"); hr = StrAllocStringAnsi(&pwz, sz, 0, CP_ACP); CabExitOnFailure(hr, "failed to convert cabinet file id to unicode: %hs", sz); if (pccs->pfnProgress) { hr = pccs->pfnProgress(TRUE, pwz, pccs->pvContext); if (S_OK != hr) { ExitFunction(); } } if (L'*' == *pccs->pwzExtract || 0 == lstrcmpW(pccs->pwzExtract, pwz)) { // get the created date for the resource in the cabinet FILETIME ftLocal; if (!::DosDateTimeToFileTime(pFDINotify->date, pFDINotify->time, &ftLocal)) { CabExitWithLastError(hr, "failed to get time for resource: %ls", pwz); } ::LocalFileTimeToFileTime(&ftLocal, &ft); hr = PathConcat(pccs->pwzExtractDir, pwz, &pwzPath); CabExitOnFailure(hr, "failed to concat onto path: %ls file: %ls", pccs->pwzExtractDir, pwz); hFile = OpenFileWithRetry(pwzPath, GENERIC_WRITE, CREATE_ALWAYS); if (INVALID_HANDLE_VALUE == hFile) { CabExitWithLastError(hr, "failed to create file: %ls", pwzPath); } ::SetFileTime(hFile, &ft, &ft, &ft); // try to set the file time (who cares if it fails) if (::SetFilePointer(hFile, pFDINotify->cb, NULL, FILE_BEGIN)) // try to set the end of the file (don't worry if this fails) { if (::SetEndOfFile(hFile)) { ::SetFilePointer(hFile, 0, NULL, FILE_BEGIN); // reset the file pointer } } ipResult = reinterpret_cast<INT_PTR>(hFile); hFile = INVALID_HANDLE_VALUE; } else // resource wasn't requested, skip it { hr = S_OK; ipResult = 0; } break; case fdintCLOSE_FILE_INFO: // resource extraction complete Assert(pFDINotify->hf && pccs && pFDINotify->psz1); CabExitOnNull(pccs, hr, E_INVALIDARG, "Failed to call cabextract callback, because no callback struct was provided"); // convert params to useful variables sz = static_cast<LPCSTR>(pFDINotify->psz1); CabExitOnNull(sz, hr, E_INVALIDARG, "Failed to convert cabinet file id, because no cabinet file id was provided"); hr = StrAllocStringAnsi(&pwz, sz, 0, CP_ACP); CabExitOnFailure(hr, "failed to convert cabinet file id to unicode: %hs", sz); if (NULL != pFDINotify->hf) // just close the file { ::CloseHandle(reinterpret_cast<HANDLE>(pFDINotify->hf)); } if (pccs->pfnProgress) { hr = pccs->pfnProgress(FALSE, pwz, pccs->pvContext); } if (S_OK == hr && L'*' == *pccs->pwzExtract) // if everything is okay and we're extracting all files, keep going { ipResult = TRUE; } else // something went wrong or we only needed to extract one file { hr = S_OK; ipResult = FALSE; pccs->fStopExtracting = TRUE; } break; case fdintPARTIAL_FILE: __fallthrough; // no action needed for these messages, fall through case fdintNEXT_CABINET: __fallthrough; case fdintENUMERATE: __fallthrough; case fdintCABINET_INFO: break; default: AssertSz(FALSE, "CabExtractCallback() - unknown FDI notification command"); }; LExit: ReleaseFileHandle(hFile); ReleaseStr(pwz); ReleaseStr(pwzPath); return (S_OK == hr) ? ipResult : -1; }