Click here to Skip to main content
15,894,740 members
Articles / Web Development / HTML

Cabinet File (*.CAB) Compression and Extraction

Rate me:
Please Sign up or sign in to vote.
4.93/5 (217 votes)
23 Mar 2012CPOL38 min read 3.2M   26.5K   573  
How to implement creation and extraction of Microsoft CAB files
// -------------------------------------------------------
// CabLib by Elm�Soft
// www.netcult.ch/elmue
// -------------------------------------------------------


#include "stdafx.h"

#include "stdio.h"
#include <windows.h>
#include "LibExtract.h" // LAST IN ORDER !!

#pragma warning(disable: 4996)

// Workaround for Compiler Bug: Error C2039 when using Directory::CreateDirectory()
#undef CreateDirectory

using namespace System;
using namespace System::IO;
using namespace System::Text;
using namespace System::Reflection;
using namespace System::Security::Cryptography;
using namespace System::Runtime::InteropServices;  // Marshal

namespace CabLib
{

// Constructor
CabLib::Extract::Extract()
{
    mi_Extract = 0;
    CleanUp();
}

// Destructor
CabLib::Extract::~Extract()
{
    CleanUp();
}

// Free memory, close handles
// Additionally reset all user settings
void CabLib::Extract::CleanUp()
{
    Reset();

    mu8_Key       = gcnew array<Byte>(0);
    mb_Passive    = true;
    mb_ExtractMem = false;
    ms_Proxy      = "";
    ms_Headers    = "";
    ms_SingleFile = "";
    ms32_ProgressInterval = 200; // call the callback every 200 ms
    mu32_Codepage = 1252; // Default = ANSI (this can be overridden in SetCodepage())
}

// Free memory, close handles
// Do NOT reset user settings
void CabLib::Extract::Reset()
{
    if (mi_Extract) 
    {
        mi_Extract->CleanUp(); // Close handles, free memory
        delete mi_Extract;
        mi_Extract = 0;
    }
    mb_Internet = false;
}

// This must be called from another thread!
void CabLib::Extract::AbortOperation()
{
    if (mi_Extract) mi_Extract->AbortOperation();
}

// ATTENTION: Normally you will NOT need this function!!
// If you don't know what is the purpose of this function, don't use it! Read the documentation!
// CAB files created with this library are UTF-8 encoded and do NOT need any Codepage setting!
// !ONLY! if you extract CAB files that have been created with any strange codepage other than ANSI or UTF-8, 
// define the codepage here, otherwise you will get corrupted filenames!!
void CabLib::Extract::SetCodepage(UInt32 u32_Codepage)
{
    mu32_Codepage = u32_Codepage;
}

// Creates a SHA512 hash from the given Unicode string 
// and uses this 64 Byte hash as key for the Blowfish algorithm which encrypts CAB data.
// This makes encryption more secure: See http://en.wikipedia.org/wiki/Key_derivation_function
void CabLib::Extract::SetDecryptionKey(String^ s_Password)
{
    if (s_Password->Length == 0)
    {
        mu8_Key = gcnew array<Byte>(0);
    }
    else
    {
        array<Byte>^ u8_Pass = gcnew array<Byte>(s_Password->Length * 2);

        // Copy the unicode string into the Byte array
        for (int i=0; i<s_Password->Length; i++)
        {
            u8_Pass[i*2]   = s_Password[i] % 256;
            u8_Pass[i*2+1] = s_Password[i] / 256;
        }

        SHA512^ i_Hash = gcnew SHA512Managed(); 
        mu8_Key = i_Hash->ComputeHash(u8_Pass);
    }
}

// The key passed to this function will be passed directly to the Blowfish decryption
// If the key is longer  than 72 Byte, the remaining bytes will be ignored.
// If the key is shorter than 72 Byte, its bytes will be reused multiple times.
// You should use this function only for setting binary keys.
// For plain text passwords use SetDecryptionKey(String^)
void CabLib::Extract::SetDecryptionKey(array<Byte>^ u8_Key)
{ 
    mu8_Key = u8_Key;
}

// Extract only one single file from the archive.
// This file MUST be located in the root folder of the CAB archive !!!
// For splitted CAB files the file MUST be in the FIRST archive!
// After setting a single file the event evBeforeCopyFile will NOT be fired !!!
// You must set the single file before calling
// ExtractFile() or ExtractResource() or ExtractStream()
// The single file will only be valid for ONE extraction process !!!
void CabLib::Extract::SetSingleFile(String^ s_File)
{
    ms_SingleFile = s_File;
}

// Set the interval in ms after which the Progress Callback is called
// If the file extracts faster than this interval only 0.0% and 100.0% are reported
// If the interval is set to zero EVERY write process calls the callback
void CabLib::Extract::SetProgressInterval(int s32_CallbackInterval)
{
    ms32_ProgressInterval = s32_CallbackInterval;
}

// ##################################### FILE ####################################################

// This static function checks if the given file ends with ".URL" or ".LNK"
// If so it resolves the shortcut and returns the target in the corresponding OUT string.
// Otherwise it returns false.
bool CabLib::Extract::ResolveShortcut(String^ s_File, [Out]String^% s_Url, [Out]String^% s_Lnk)
{
    s_Url = nullptr;
    s_Lnk = nullptr;
    WCHAR  u16_Target[8000];
    WCHAR* u16_File = (WCHAR*)Marshal::StringToHGlobalUni(s_File).ToPointer();
    bool b_Ret = false;

    if (s_File->ToLower()->EndsWith(".url"))
    {
        if (0 != Cabinet::CFile::ResolveUrlShortcutW(u16_File, u16_Target, sizeof(u16_Target)/2))
            throw gcnew Exception(String::Concat("Could not resolve shortcut: ", s_File));

        s_Url = gcnew String(u16_Target);
        b_Ret = true;
    }

    if (s_File->ToLower()->EndsWith(".lnk"))
    {
        if (0 != Cabinet::CFile::ResolveLnkShortcutW(u16_File, u16_Target, sizeof(u16_Target)/2))
            throw gcnew Exception(String::Concat("Could not resolve shortcut: ", s_File));

        s_Lnk = gcnew String(u16_Target);
        b_Ret = true;
    }

    Marshal::FreeHGlobal((IntPtr)u16_File);
    return b_Ret;
}

// Extracts the content of a CAB file s_CabFile into the given folder s_Folder
// The subfolder structure in the CAB file will be maintained on disk
// Also file attributes and dates of the files are maintained
// Throws an exception on error
void CabLib::Extract::ExtractFile(String^ s_CabFile, String^ s_Folder)
{
    if (mi_Extract) throw gcnew Exception(gcnew String("You cannot call the extraction functions from two different threads. Please create a new instance for each thread! After URL extraction you MUST call CleanUp()"));

    String^ s_Url = nullptr;
    String^ s_Lnk = nullptr;
    if (ResolveShortcut(s_CabFile, s_Url, s_Lnk))
    {
        if (s_Url != nullptr) // Redirect URL shortcuts to ExtractUrl()
        {
            // extract the URL after downloading it to a temp file
            ExtractUrl(s_Url, 0, L"", s_Folder);
            return;
        }
        if (s_Lnk != nullptr) // use LNK target
        {
            s_CabFile = s_Lnk;
        }
    }

    Reset();
    Exception^ i_Ex = nullptr;

    WCHAR* u16_CabFile = (WCHAR*)Marshal::StringToHGlobalUni(s_CabFile).ToPointer();
    WCHAR* u16_Folder  = (WCHAR*)Marshal::StringToHGlobalUni(s_Folder) .ToPointer();

    Cabinet::CExtract* pi_Extract = new Cabinet::CExtract();
    mi_Extract = pi_Extract;

    cExtractBridge i_Bridge(this, ms_SingleFile, 0, ms32_ProgressInterval);

    pi_Extract->SetCallbacks(i_Bridge.GetCallbacks());

    BYTE  u8_Key[72];
    UINT u32_Len = min(72, mu8_Key->Length);
    for (UINT i=0; i<u32_Len; i++)
    {
        u8_Key[i] = mu8_Key[i]; // copy managed --> unmanged
    }
    pi_Extract->SetDecryptionKey(u8_Key, u32_Len);
    pi_Extract->SetCodepage(mu32_Codepage);

    if (!pi_Extract->CreateFDIContext()) 
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR: Could not create FDI context: {0}", 
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    // The i_Bridge instance must be passed to the static callbacks to access its members
    if (!pi_Extract->ExtractFileW(u16_CabFile, u16_Folder, &i_Bridge))
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR while extracting files from cabinet archive: {0}", 
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    _Exit:
    Marshal::FreeHGlobal((IntPtr)u16_CabFile);
    Marshal::FreeHGlobal((IntPtr)u16_Folder);

    CleanUp();
    if (i_Ex) throw i_Ex;
}

// returns true if the file is a valid CABINET file
// returns additional information which is read from the header of the CAB file
bool CabLib::Extract::IsFileCabinet(String^ s_CabFile, [Out]kHeaderInfo^% pk_HeaderInfo)
{
    pk_HeaderInfo = gcnew kHeaderInfo();

    Exception^ i_Ex = nullptr;

    WCHAR* u16_CabFile = (WCHAR*)Marshal::StringToHGlobalUni(s_CabFile).ToPointer();

    BOOL              b_Ret = FALSE;
    FDICABINETINFO    k_Info;
    Cabinet::CExtract i_Extract;

    BYTE  u8_Key[72];
    UINT u32_Len = min(72, mu8_Key->Length);
    for (UINT i=0; i<u32_Len; i++)
    {
        u8_Key[i] = mu8_Key[i]; // copy managed --> unmanged
    }
    i_Extract.SetDecryptionKey(u8_Key, u32_Len);
    i_Extract.SetCodepage(mu32_Codepage);

    if (!i_Extract.CreateFDIContext()) 
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR: Could not create FDI context: {0}", 
                               gcnew String(i_Extract.LastErrorW())));
    }
    else
    {
        b_Ret = i_Extract.IsCabinetW(u16_CabFile, &k_Info);
    }
    
    if (!b_Ret) memset(&k_Info, 0, sizeof(FDICABINETINFO));
    
    (pk_HeaderInfo)->u32_FileSize  = k_Info.cbCabinet;
    (pk_HeaderInfo)->u16_Folders   = k_Info.cFolders;
    (pk_HeaderInfo)->u16_Files     = k_Info.cFiles;
    (pk_HeaderInfo)->u16_SetID     = k_Info.setID;
    (pk_HeaderInfo)->u16_CabIndex  = k_Info.iCabinet;
    (pk_HeaderInfo)->b_HasReserve  =(k_Info.fReserve != FALSE);
    (pk_HeaderInfo)->b_HasPrevious =(k_Info.hasprev  != FALSE);
    (pk_HeaderInfo)->b_HasNext     =(k_Info.hasnext  != FALSE); // ATTENTION: returns 2 or 0 !!
    
    i_Extract.CleanUp();
    Marshal::FreeHGlobal((IntPtr)u16_CabFile);

    if (i_Ex) throw i_Ex;

    return (b_Ret != FALSE);
}

// ################################### RESOURCE ##################################################

// Extracts the content of a CAB file which is stored in the file s_Module (DLL or EXE)
// into the given folder s_Folder.
// If s_Module == null --> The Cab resource is expected in the file which created the process
// Otherwise s_Module must be the filename (without path) of the DLL which contains the CAB resource
// The subfolder structure in the CAB file will be maintained on disk
// Also file attributes and dates of the files are maintained
// Throws an exception on error
// To test this function with the file Test.cab included in this project
// set s_Module = "CabLib.dll" and o_RscName = ID_CAB_TEST and o_RscType = "CABFILE"
// because the file CabLib.rc contains:
// ID_CAB_TEST        CABFILE         "Res\\Test.cab"
void CabLib::Extract::ExtractResource(String^ s_Module,  // filename of DLL or null 
                                      Object^ o_RscName, // Name of resource (String or integer)
                                      Object^ o_RscType, // Type of resource (String or integer)
                                      String^ s_Folder)  // Where to extract the CAB to
{
    if (mi_Extract) throw gcnew Exception(gcnew String("You cannot call the extraction functions from two different threads. Please create a new instance for each thread! After URL extraction you MUST call CleanUp()"));

    Reset();
    Exception^ i_Ex = nullptr;

    WCHAR* u16_Module = NULL;
    WCHAR  u16_RscName[1000] = L"";
    WCHAR  u16_RscType[1000] = L"";

    if (s_Module) u16_Module  = (WCHAR*)Marshal::StringToHGlobalUni(s_Module).ToPointer();
    WCHAR*        u16_Folder  = (WCHAR*)Marshal::StringToHGlobalUni(s_Folder).ToPointer();
    WCHAR*        u16_ObjName = (WCHAR*)Marshal::StringToHGlobalUni(o_RscName->ToString()).ToPointer();
    WCHAR*        u16_ObjType = (WCHAR*)Marshal::StringToHGlobalUni(o_RscType->ToString()).ToPointer();

         if (o_RscName->GetType() == Int32::typeid)  swprintf(u16_RscName, L"#%s", u16_ObjName);
    else if (o_RscName->GetType() == String::typeid) wcscpy  (u16_RscName, u16_ObjName);

         if (o_RscType->GetType() == Int32::typeid)  swprintf(u16_RscType, L"#%s", u16_ObjType);
    else if (o_RscType->GetType() == String::typeid) wcscpy  (u16_RscType, u16_ObjType);

    Cabinet::CExtractResource* pi_Extract = new Cabinet::CExtractResource();
    mi_Extract = pi_Extract;

    cExtractBridge i_Bridge(this, ms_SingleFile, 0, ms32_ProgressInterval);

    pi_Extract->SetCallbacks(i_Bridge.GetCallbacks());

    BYTE  u8_Key[72];
    UINT u32_Len = min(72, mu8_Key->Length);
    for (UINT i=0; i<u32_Len; i++)
    {
        u8_Key[i] = mu8_Key[i]; // copy managed --> unmanged
    }
    pi_Extract->SetDecryptionKey(u8_Key, u32_Len);
    pi_Extract->SetCodepage(mu32_Codepage);

    if (!pi_Extract->CreateFDIContext()) 
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR: Could not create FDI context: {0}", 
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    // The i_Bridge instance must be passed to the static callbacks to access its members
    if (!pi_Extract->ExtractResourceW(u16_Module, u16_RscName, u16_RscType, u16_Folder, &i_Bridge))
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR while extracting files from cabinet resource: {0}",
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    _Exit:
    if (s_Module) Marshal::FreeHGlobal((IntPtr)u16_Module);
    Marshal::FreeHGlobal((IntPtr)u16_Folder);
    Marshal::FreeHGlobal((IntPtr)u16_ObjType);
    Marshal::FreeHGlobal((IntPtr)u16_ObjName);

    CleanUp();
    if (i_Ex) throw i_Ex;
}

// ##################################### STREAM ##################################################

// Extracts the content of a stream, which contains a CAB file, into the given folder s_Folder
// The subfolder structure in the CAB file will be maintained on disk
// Also file attributes and dates of the files are maintained
// Throws an exception on error
void CabLib::Extract::ExtractStream(Stream^ i_Stream, String^ s_Folder)
{
    if (mi_Extract) throw gcnew Exception(gcnew String(L"You cannot call the extraction functions from two different threads. Please create a new instance for each thread! After URL extraction you MUST call CleanUp()"));

    if (!i_Stream->CanSeek || !i_Stream->CanRead)
        throw gcnew Exception(L"The stream specified for CAB extraction is invalid! The stream must be capable of reading and seeking.");

    Reset();
    Exception^ i_Ex = nullptr;

    WCHAR* u16_Folder = (WCHAR*)Marshal::StringToHGlobalUni( s_Folder). ToPointer();

    Cabinet::CExtractStream* pi_Extract = new Cabinet::CExtractStream();
    mi_Extract = pi_Extract;

    cExtractBridge i_Bridge(this, ms_SingleFile, i_Stream, ms32_ProgressInterval);

    pi_Extract->SetCallbacks(i_Bridge.GetCallbacks());

    BYTE  u8_Key[72];
    UINT u32_Len = min(72, mu8_Key->Length);
    for (UINT i=0; i<u32_Len; i++)
    {
        u8_Key[i] = mu8_Key[i]; // copy managed --> unmanged
    }
    pi_Extract->SetDecryptionKey(u8_Key, u32_Len);
    pi_Extract->SetCodepage(mu32_Codepage);

    if (!pi_Extract->CreateFDIContext()) 
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR: Could not create FDI context: {0}", 
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    // The i_Bridge instance must be passed to the static callbacks to access its members
    if (!pi_Extract->ExtractStreamW(u16_Folder, &i_Bridge))
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR while extracting files from cabinet stream: {0}",
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    _Exit:
    Marshal::FreeHGlobal((IntPtr)u16_Folder);

    i_Stream->Close();
    CleanUp();

    if (i_Ex) throw i_Ex;
}

// ##################################### INTERNET ################################################

// Sets FTP transfer to passive / active mode
void CabLib::Extract::SetPassiveFtpMode(bool b_Passive)
{
    mb_Passive = b_Passive;
}

// Proxy Types: CERN, TIS or SOCKS
// String Format= "http=http://Proxy1.com:8000 https=https://Proxy2.com:443"
// Possible Proxies for HTTP, HTTPS, FTP
// s_ProxyString = "" --> Use Internet Explorer default settings
void CabLib::Extract::SetProxy(String^ s_ProxyString)
{
    if (s_ProxyString->Length > 0 && s_ProxyString->IndexOf("=") < 0)
        throw gcnew Exception("Read the documentation before using this library!");

    ms_Proxy = s_ProxyString;
}

// Modifies the HTTP headers which are sent to the server. (separated by pipe)
// e.g. "Referer: http://www.test.com|Accept-Language:en"  (no space before or after pipe!!)
// If the value of a header is empty, the header is removed.
void CabLib::Extract::SetHttpHeaders(String^ s_Headers)
{
    if (s_Headers->IndexOf("| ") >= 0 || s_Headers->IndexOf(" |") >= 0 ||
        s_Headers->IndexOf("\r") >= 0 || s_Headers->IndexOf("\n") >= 0)
        throw gcnew Exception("Read the documentation before using this library!");

    ms_Headers = s_Headers;
}

// This must be called from another thread while a download is in progress.
// If this is called during a block download to memory,
// it will return only the progress of the current block
// Really usefull only for downloads of entire files
// Not all HTTP Servers return "CONTENT-LENGTH" (e.g. AOL servers) --> Total = 0
void CabLib::Extract::InternetGetProgress([Out] Int32^% s32_Total, [Out] Int32^% s32_Read)
{
    *s32_Read  = 0;
    *s32_Total = 0;

    if (!mb_Internet || !mi_Extract)
        return;

    Cabinet::CExtractUrl* i_ExtUrl = (Cabinet::CExtractUrl*) mi_Extract;

    ULONGLONG s64_Total, s64_Read;
    i_ExtUrl->GetProgress(&s64_Total, &s64_Read);

    // CAB files can never be bigger than 2 Gigabyte, so we don't need 64 bit here
    *s32_Read  = (int)min(s64_Read, s64_Total);
    *s32_Total = (int)s64_Total;
}

// ---------------------------------------------------------------------------------------------
//           !! READ THE DOCUMENTATION FOR MORE DETAILS ABOUT THIS FUNCTION !!
// ---------------------------------------------------------------------------------------------

void CabLib::Extract::ExtractUrl(String^ s_Url, Int32 s32_BlockSize, String^ s_LocalPath, String^ s_Folder)
{
    if (mi_Extract) throw gcnew Exception(gcnew String("You cannot call the extraction functions from two different threads. Please create a new instance for each thread! After URL extraction you MUST call CleanUp()"));

    // Redirect "file://" URLs to ExtractFile()
    if (s_Url->ToLower()->StartsWith("file:"))
    {
        String^ s_File = s_Url->Substring(5);
        while  (s_File->StartsWith("/"))
        {
            // The URL may be "file://.." or "file:///.."
            s_File = s_File->Substring(1);
        }
        ExtractFile(s_File->Replace("/", "\\"), s_Folder);
        return;
    }

    Reset();
    mb_Internet = true;
    Exception^ i_Ex = nullptr;

    WCHAR* u16_Url     = (WCHAR*)Marshal::StringToHGlobalUni(s_Url).ToPointer();
    WCHAR* u16_Local   = (WCHAR*)Marshal::StringToHGlobalUni(s_LocalPath).ToPointer();
    WCHAR* u16_Folder  = (WCHAR*)Marshal::StringToHGlobalUni(s_Folder).ToPointer();
    WCHAR* u16_Proxy   = (WCHAR*)Marshal::StringToHGlobalUni(ms_Proxy).ToPointer();
    WCHAR* u16_Headers = (WCHAR*)Marshal::StringToHGlobalUni(ms_Headers).ToPointer();

    Cabinet::CExtractUrl* pi_Extract = new Cabinet::CExtractUrl();
    mi_Extract = pi_Extract;

    cExtractBridge i_Bridge(this, ms_SingleFile, 0, ms32_ProgressInterval);

    pi_Extract->SetCallbacks     (i_Bridge.GetCallbacks());
    pi_Extract->SetPassiveFtpMode(mb_Passive != false);
    pi_Extract->SetProxyW        (u16_Proxy);
    pi_Extract->SetHttpHeadersW  (u16_Headers);

    BYTE  u8_Key[72];
    UINT u32_Len = min(72, mu8_Key->Length);
    for (UINT i=0; i<u32_Len; i++)
    {
        u8_Key[i] = mu8_Key[i]; // copy managed --> unmanged
    }
    pi_Extract->SetDecryptionKey(u8_Key, u32_Len);
    pi_Extract->SetCodepage(mu32_Codepage);

    if (!pi_Extract->CreateFDIContext()) 
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR: Could not create FDI context: {0}", 
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    // The i_Bridge instance must be passed to the static callbacks to access its members
    if (!pi_Extract->ExtractUrlW(u16_Url, s32_BlockSize, u16_Local, u16_Folder, &i_Bridge))
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR while extracting files from URL: {0}",
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    _Exit:
    Marshal::FreeHGlobal((IntPtr)u16_Url);
    Marshal::FreeHGlobal((IntPtr)u16_Local);
    Marshal::FreeHGlobal((IntPtr)u16_Folder);
    Marshal::FreeHGlobal((IntPtr)u16_Proxy);
    Marshal::FreeHGlobal((IntPtr)u16_Headers);

    // No CleanUp() here! (Maybe there are more files to be extracted later from the same CAB file)
    ms_SingleFile = "";
    pi_Extract->DestroyFDIContext(); 
    if (i_Ex) throw i_Ex;
}

 void CabLib::Extract::ExtractMoreUrl(String^ s_Folder)
 {
    if (!mb_Internet || !mi_Extract) throw gcnew Exception("Read the documentation before using this library!");

    // No Reset() here !!
    Exception^ i_Ex = nullptr;
    WCHAR* u16_Folder = (WCHAR*)Marshal::StringToHGlobalUni(s_Folder).ToPointer();

    cExtractBridge i_Bridge(this, ms_SingleFile, 0, ms32_ProgressInterval);

    Cabinet::CExtractUrl* pi_Extract = (Cabinet::CExtractUrl*)mi_Extract;

    pi_Extract->SetCallbacks(i_Bridge.GetCallbacks());

    if (!pi_Extract->CreateFDIContext()) 
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR: Could not create FDI context: {0}", 
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    // The i_Bridge instance must be passed to the static callbacks to access the member variables
    if (!pi_Extract->ExtractMoreUrlW(u16_Folder, &i_Bridge))
    {
        i_Ex = gcnew Exception(String::Format("CabLib Extract ERROR while extracting more files from URL: {0}",
                               gcnew String(pi_Extract->LastErrorW())));
        goto _Exit;
    }

    _Exit:
    Marshal::FreeHGlobal((IntPtr)u16_Folder);

    // No CleanUp() here! (Maybe there are more files to be extracted later from the same CAB file)
    ms_SingleFile = "";
    pi_Extract->DestroyFDIContext();

    if (i_Ex) throw i_Ex;
 }

// ###############################################################################################
// ###############################################################################################
//                                  EXTRACT EVENT HANDLER
// ###############################################################################################
// ###############################################################################################


// This function will be called for each file in a cabinet before it is extracted.
// Return TRUE to extract the file or FALSE to skip the file.
BOOL CabLib::cExtractBridge::OnBeforeCopyFile(Cabinet::CExtract::kCabinetFileInfo *pk_Info, void* p_Param)
{ 
    cExtractBridge* p_Bridge = (cExtractBridge*)p_Param;

    // Extract single file specified by user from the root folder in the CAB file
    if (p_Bridge->ms_SingleFile && p_Bridge->ms_SingleFile->Length)
        return String::Compare(p_Bridge->ms_SingleFile, gcnew String(pk_Info->u16_File), true) == 0;

    Extract::kCabinetFileInfo^ k_Info = gcnew Extract::kCabinetFileInfo();

    k_Info->s_File      = gcnew String(pk_Info->u16_File);
    k_Info->s_SubFolder = gcnew String(pk_Info->u16_SubFolder);
    k_Info->s_RelPath   = gcnew String(pk_Info->u16_RelPath);
    k_Info->s_Path      = gcnew String(pk_Info->u16_Path);
    k_Info->s_FullPath  = gcnew String(pk_Info->u16_FullPath);
    k_Info->s32_Size    = pk_Info->s32_Size;
    k_Info->u16_Attribs = pk_Info->u16_Attribs;

    // Because DateTime::FromFileTime() will add the timezone
    // we must subtract the timezone before to get the correct result
    // DateTime::FromFileTimeUtc() is not available on Visual Studio 7.0
    ::FILETIME k_Local;
    LocalFileTimeToFileTime(&pk_Info->k_Time, &k_Local);
    k_Info->k_Time = DateTime::FromFileTime(_FileTimeTo64(k_Local));

    return (p_Bridge->mi_Instance->FireBeforeCopyFile(k_Info) != false); // bool -> BOOL
}

// This function will be called when a file has been succesfully extracted.
// If ExtractToMemory is enabled, no file is written to disk,
// instead the entire content of the file is passed in u8_ExtractMem
// If ExtractToMemory is not enabled, u8_ExtractMem is null
void CabLib::cExtractBridge::OnAfterCopyFile(WCHAR* u16_File, Cabinet::CMemory* pi_ExtractMem, void* p_Param)
{ 
    cExtractBridge* p_Bridge = (cExtractBridge*)p_Param;

    array<Byte>^ u8_ExtractMem = nullptr;
    if (pi_ExtractMem)
    {
        int  s32_Len  = 0;
        IntPtr p_Data = (IntPtr)pi_ExtractMem->GetData(&s32_Len);

        u8_ExtractMem = gcnew array<Byte>(s32_Len);
        Marshal::Copy(p_Data, u8_ExtractMem, 0, s32_Len);
    }
        
    p_Bridge->mi_Instance->FireAfterCopyFile(gcnew String(u16_File), u8_ExtractMem);
}

void CabLib::cExtractBridge::OnProgressInfo(Cabinet::CExtract::kProgressInfo* pk_Info, void* p_Param)
{ 
    cExtractBridge* p_Bridge = (cExtractBridge*)p_Param;

    // copy to managed structure
    Extract::kProgressInfo^ k_Info = gcnew Extract::kProgressInfo();

    k_Info->s_RelPath   = gcnew String(pk_Info->u16_RelPath);
    k_Info->s_FullPath  = gcnew String(pk_Info->u16_FullPath);
    k_Info->u32_TotSize = pk_Info->u32_TotSize;
    k_Info->u32_Written = pk_Info->u32_Written;
    k_Info->fl_Percent  = pk_Info->fl_Percent;

    p_Bridge->mi_Instance->FireProgressInfo(k_Info);
}

// This function will be called exactly once for each cabinet opened by Copy, 
// including continuation cabinets opened due to files which are spanning over cabinet boundaries. 
void CabLib::cExtractBridge::OnCabinetInfo(Cabinet::CExtract::kCabinetInfo* pk_Info, void* p_Param)
{
    cExtractBridge* p_Bridge = (cExtractBridge*)p_Param;

    // copy to managed structure
    Extract::kCabinetInfo^ k_Info = gcnew Extract::kCabinetInfo();

    k_Info->s_NextCabinet = gcnew String(pk_Info->s8_NextCabinet);
    k_Info->s_NextDisk    = gcnew String(pk_Info->s8_NextDisk);
    k_Info->s_Path        = gcnew String(pk_Info->u16_Path);
    k_Info->u16_SetID     = pk_Info->u16_SetID;
    k_Info->u16_Cabinet   = pk_Info->u16_Cabinet;

    p_Bridge->mi_Instance->FireCabinetInfo(k_Info);
}

// This function will be called when the next cabinet file in the sequence needs to be opened. 
// The path has to be verified and can be changed if necessary. 
// If the cabinet file cannot be opened this function will be called again 
// with the second parameter set to an error that describes the problem.
void CabLib::cExtractBridge::OnNextCabinet(Cabinet::CExtract::kCabinetInfo *pk_Info, int s32_FdiError, void* p_Param)
{ 
    cExtractBridge* p_Bridge = (cExtractBridge*)p_Param;

    // copy to managed structure
    Extract::kCabinetInfo^ k_Info = gcnew Extract::kCabinetInfo();

    k_Info->s_NextCabinet = gcnew String(pk_Info->s8_NextCabinet);
    k_Info->s_NextDisk    = gcnew String(pk_Info->s8_NextDisk);
    k_Info->s_Path        = gcnew String(pk_Info->u16_Path);
    k_Info->u16_SetID     = pk_Info->u16_SetID;
    k_Info->u16_Cabinet   = pk_Info->u16_Cabinet;

    p_Bridge->mi_Instance->FireNextCabinet(k_Info, s32_FdiError);
}

// ###############################################################################################
// ###############################################################################################
//                                  STREAM ACCESS FUNCTIONS
// ###############################################################################################
// ###############################################################################################


int CabLib::cExtractBridge::OnStreamGetLen(void* p_Param)
{
    cExtractBridge* p_Bridge = (cExtractBridge*)p_Param;
    return (int)p_Bridge->mi_Stream->Length; 
}

int CabLib::cExtractBridge::OnStreamRead(void* p_Buffer, int Pos, UINT u32_Count, void* p_Param)
{
    cExtractBridge* p_Bridge = (cExtractBridge*)p_Param;
    p_Bridge->mi_Stream->Position = Pos;

    array<Byte>^ u8_ReadBuf = gcnew array<Byte>(u32_Count);
    int s32_Read = p_Bridge->mi_Stream->Read(u8_ReadBuf, 0, u32_Count);

    Marshal::Copy(u8_ReadBuf, 0, (IntPtr)p_Buffer, s32_Read);
    return s32_Read;
}


} // Namespace CabLib

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) ElmüSoft
Chile Chile
Software Engineer since 40 years.

Comments and Discussions