// 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"
//---------------------------------------------------------------------
// CABINET EXTRACTION
//---------------------------------------------------------------------
// Globals make this code unsuited for multhreaded use,
// but FDI doesn't provide any other way to pass context.
// Handle to the FDI (cab extraction) engine. Need access to this in a callback.
static HFDI g_hfdi;
// FDI is not unicode-aware, so avoid passing these paths through the callbacks.
static const wchar_t* g_szExtractDir;
static const wchar_t* g_szCabFile;
// Offset into the source file where the cabinet really starts.
// Used to trick FDI into extracting from a concatenated cabinet.
static int g_lCabOffset;
// Use the secure CRT version of _wsopen if available.
#ifdef __GOT_SECURE_LIB__
#define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode)
#else
#define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode)
#endif
///
/// FDI callback to open a cabinet file.
///
/// Name of the file to be opened. This parameter
/// is ignored since with our limited use this method is only ever called
/// to open the main cabinet file.
/// Type of operations allowed.
/// Permission setting.
/// Integer file handle, or -1 if the file could not be opened.
///
/// To support reading from a cabinet that is concatenated onto
/// another file, this function first searches for the offset of the cabinet,
/// then saves that offset for use in recalculating later seeks.
///
static FNOPEN(CabOpen)
{
UNREFERENCED_PARAMETER(pszFile);
int hf;
_wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode);
if (hf != -1)
{
FDICABINETINFO cabInfo;
int length = _lseek(hf, 0, SEEK_END);
for(int offset = 0; offset < length; offset += 256)
{
if (_lseek(hf, offset, SEEK_SET) != offset) break;
if (FDIIsCabinet(g_hfdi, hf, &cabInfo))
{
g_lCabOffset = offset;
_lseek(hf, offset, SEEK_SET);
return hf;
}
}
_close(hf);
}
return -1;
}
///
/// FDI callback to seek within a file.
///
/// File handle.
/// Seek distance
/// Whether to seek relative to the
/// beginning, current position, or end of the file.
/// Resultant position within the cabinet.
///
/// To support reading from a cabinet that is concatenated onto
/// another file, this function recalculates seeks based on the
/// offset that was determined when the cabinet was opened.
///
static FNSEEK(CabSeek)
{
if (seektype == SEEK_SET) dist += g_lCabOffset;
int pos = _lseek((int) hf, dist, seektype);
pos -= g_lCabOffset;
return pos;
}
///
/// Ensures a directory and its parent directory path exists.
///
/// Directory path, not including file name.
/// 0 if the directory exists or was successfully created, else nonzero.
///
/// This function modifies characters in szDirPath, but always restores them
/// regardless of error condition.
///
static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath)
{
int ret = 0;
if (!::CreateDirectoryW(szDirPath, NULL))
{
UINT err = ::GetLastError();
if (err != ERROR_ALREADY_EXISTS)
{
// Directory creation failed for some reason other than already existing.
// Try to create the parent directory first.
wchar_t* szLastSlash = NULL;
for (wchar_t* sz = szDirPath; *sz; sz++)
{
if (*sz == L'\\')
{
szLastSlash = sz;
}
}
if (szLastSlash)
{
// Temporarily take one directory off the path and recurse.
*szLastSlash = L'\0';
ret = EnsureDirectoryExists(szDirPath);
*szLastSlash = L'\\';
// Try to create the directory if all parents are created.
if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL))
{
err = ::GetLastError();
if (err != ERROR_ALREADY_EXISTS)
{
ret = -1;
}
}
}
else
{
ret = -1;
}
}
}
return ret;
}
///
/// Ensures a file's directory and its parent directory path exists.
///
/// Path including file name.
/// 0 if the file's directory exists or was successfully created, else nonzero.
///
/// This function modifies characters in szFilePath, but always restores them
/// regardless of error condition.
///
static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath)
{
int ret = 0;
wchar_t* szLastSlash = NULL;
for (wchar_t* sz = szFilePath; *sz; sz++)
{
if (*sz == L'\\')
{
szLastSlash = sz;
}
}
if (szLastSlash)
{
*szLastSlash = L'\0';
ret = EnsureDirectoryExists(szFilePath);
*szLastSlash = L'\\';
}
return ret;
}
///
/// FDI callback for handling files in the cabinet.
///
/// Type of notification.
/// Structure containing data about the notification.
///
/// Refer to fdi.h for more comments on this notification callback.
///
static FNFDINOTIFY(CabNotification)
{
// fdintCOPY_FILE:
// Called for each file that *starts* in the current cabinet, giving
// the client the opportunity to request that the file be copied or
// skipped.
// Entry:
// pfdin->psz1 = file name in cabinet
// pfdin->cb = uncompressed size of file
// pfdin->date = file date
// pfdin->time = file time
// pfdin->attribs = file attributes
// pfdin->iFolder = file's folder index
// Exit-Success:
// Return non-zero file handle for destination file; FDI writes
// data to this file use the PFNWRITE function supplied to FDICreate,
// and then calls fdintCLOSE_FILE_INFO to close the file and set
// the date, time, and attributes.
// Exit-Failure:
// Returns 0 => Skip file, do not copy
// Returns -1 => Abort FDICopy() call
if (fdint == fdintCOPY_FILE)
{
size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0);
size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile;
wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t));
if (szFilePath == NULL) return -1;
StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir);
StringCchCatW(szFilePath, cchFilePath + 1, L"\\");
MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1,
szFilePath + cchFilePath - cchFile, (int) cchFile + 1);
int hf = -1;
if (EnsureFileDirectoryExists(szFilePath) == 0)
{
_wsopen__s(hf, szFilePath,
_O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL,
_SH_DENYWR, _S_IREAD | _S_IWRITE);
}
return hf;
}
// fdintCLOSE_FILE_INFO:
// Called after all of the data has been written to a target file.
// This function must close the file and set the file date, time,
// and attributes.
// Entry:
// pfdin->psz1 = file name in cabinet
// pfdin->hf = file handle
// pfdin->date = file date
// pfdin->time = file time
// pfdin->attribs = file attributes
// pfdin->iFolder = file's folder index
// pfdin->cb = Run After Extract (0 - don't run, 1 Run)
// Exit-Success:
// Returns TRUE
// Exit-Failure:
// Returns FALSE, or -1 to abort
else if (fdint == fdintCLOSE_FILE_INFO)
{
_close((int) pfdin->hf);
return TRUE;
}
return 0;
}
///
/// Extracts all contents of a cabinet file to a directory.
///
/// Path to the cabinet file to be extracted.
/// The cabinet may actually start at some offset within the file,
/// as long as that offset is a multiple of 256.
/// Directory where files are to be extracted.
/// This directory must already exist, but should be empty.
/// 0 if the cabinet was extracted successfully,
/// or an error code if any error occurred.
///
/// The extraction will not overwrite any files in the destination
/// directory; extraction will be interrupted and fail if any files
/// with the same name already exist.
///
int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir)
{
ERF erf;
// Most of the FDI callbacks can be handled by existing CRT I/O functions.
// For our functionality we only need to handle the open and seek callbacks.
HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen,
(PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close,
CabSeek, cpu80386, &erf);
if (hfdi != NULL)
{
g_hfdi = hfdi;
g_szCabFile = szCabFile;
g_szExtractDir = szExtractDir;
char szEmpty[1] = {0};
if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL))
{
FDIDestroy(hfdi);
return 0;
}
FDIDestroy(hfdi);
}
return erf.erfOper;
}