// 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;
}