Click here to Skip to main content
15,893,486 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
// LibExtract.cpp

#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::Runtime::InteropServices;  // Marshal

namespace CabLib
{

// Constructor
CabLib::Extract::Extract()
{
	mp_This       = 0;
	ms_SingleFile = 0;
	ms_Decrypt    = "";
}

void CabLib::Extract::SetDecryptionKey(String* s_Key)
{ 
	ms_Decrypt = s_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;
}

// 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)
{
	mi_Mutex->WaitOne(); // assure thread-safety
	Exception *i_Ex = 0;
	ms_Folder = s_Folder;
	mp_This   = this;

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

	CExtract i_Extract;
	if (!i_Extract.SetDecryptionKeyW(u16_Decrypt))
	{
		i_Ex = new Exception("CabLib Extract ERROR: Decryption key too long.");
		goto _Exit;
	}

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

	if (!i_Extract.ExtractFileW(u16_CabFile, u16_Folder))
	{
		i_Ex = new Exception(String::Format("CabLib Extract ERROR while extracting files from cabinet archive: {0}", 
		                     new String(i_Extract.LastErrorW())));
		goto _Exit;
	}

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

	mp_This       = 0;
	ms_SingleFile = 0;

	mi_Mutex->ReleaseMutex(); // The mutex must ALWAYS be released!!
	if (i_Ex) throw i_Ex;
}


// 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
{
	mi_Mutex->WaitOne(); // assure thread-safety
	Exception *i_Ex = 0;
	ms_Folder = s_Folder;
	mp_This   = this;

	WCHAR* u16_Module  = 0;
	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_Decrypt = (WCHAR*)Marshal::StringToHGlobalUni(ms_Decrypt).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() == __typeof(Int32))  swprintf(u16_RscName, L"#%s", u16_ObjName);
	else if (o_RscName->GetType() == __typeof(String)) wcscpy  (u16_RscName, u16_ObjName);

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

	CExtractResource i_Extract;
	if (!i_Extract.SetDecryptionKeyW(u16_Decrypt))
	{
		i_Ex = new Exception("CabLib Extract ERROR: Decryption key too long.");
		goto _Exit;
	}

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

	if (!i_Extract.ExtractResourceW(u16_Module, u16_RscName, u16_RscType, u16_Folder))
	{
		i_Ex = new Exception(String::Format("CabLib Extract ERROR while extracting files from cabinet resource: {0}",
	                         new String(i_Extract.LastErrorW())));
		goto _Exit;
	}

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

	mp_This       = 0;
	ms_SingleFile = 0;

	mi_Mutex->ReleaseMutex(); // The mutex must ALWAYS be released!!
	if (i_Ex) throw i_Ex;
}

// 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 (!i_Stream->CanSeek || !i_Stream->CanRead)
		throw new Exception("The stream specified for CAB extraction is invalid! The stream must be capable of reading and seeking.");

	mi_Mutex->WaitOne(); // assure thread-safety
	Exception *i_Ex = 0;
	mi_Stream = i_Stream;
	ms_Folder = s_Folder;
	mp_This   = this;

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

	CExtractStream i_Extract;
	if (!i_Extract.SetDecryptionKeyW(u16_Decrypt))
	{
		i_Ex = new Exception("CabLib Extract ERROR: Decryption key too long.");
		goto _Exit;
	}

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

	if (!i_Extract.ExtractStreamW(u16_Folder))
	{
		i_Ex = new Exception(String::Format("CabLib Extract ERROR while extracting files from cabinet stream: {0}",
	                         new String(i_Extract.LastErrorW())));
		goto _Exit;
	}

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

	mi_Stream->Close();
	mi_Stream     = 0;
	mp_This       = 0;
	ms_SingleFile = 0;

	mi_Mutex->ReleaseMutex(); // The mutex must ALWAYS be released!!
	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::Extract::OnBeforeCopyFile(kCabinetFileInfo *pk_Info, void* pParam)
{ 
	// Extract single file specified by user from the root folder in the CAB file
	if (mp_This && mp_This->ms_SingleFile)
		return (String::Compare(mp_This->ms_SingleFile, pk_Info->s_File, true) == 0);

	if (mp_This && mp_This->evBeforeCopyFile) // fire event
		return (mp_This->evBeforeCopyFile(pk_Info) == true); // bool --> BOOL

	return TRUE; 
}

// This function will be called when a file has been succesfully extracted.
void CabLib::Extract::OnAfterCopyFile(String* s_File, void* pParam)
{ 
	if (mp_This && mp_This->evAfterCopyFile)
		mp_This->evAfterCopyFile(s_File);
}

// 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::Extract::OnCabinetInfo(kCabinetInfo *pk_Info, void* pParam)
{
	if (mp_This && mp_This->evCabinetInfo) 
		mp_This->evCabinetInfo(pk_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::Extract::OnNextCabinet(kCabinetInfo *pk_Info, int s32_FdiError, void* pParam)
{ 
	if (mp_This && mp_This->evNextCabinet)
		mp_This->evNextCabinet(pk_Info, s32_FdiError);
}

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


int CabLib::Extract::OnStreamRead(void* p_Buffer, long Pos, UINT u32_Count)
{
	if (!mi_Stream) return 0;
	mi_Stream->Position = Pos;

	System::Byte u8_ReadBuf[] = new System::Byte[u32_Count];
	int s32_Read = mi_Stream->Read(u8_ReadBuf, 0, u32_Count);

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

// returns the total size of bytes to read
int CabLib::Extract::OnStreamGetLength()
{
	if (!mi_Stream) return 0;
	return (int) mi_Stream->Length;
}

} // Namespace Cabinet

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