denyhosts/clamav/libclamav/egg.c

2595 lines
95 KiB
C
Raw Normal View History

2022-10-22 18:41:00 +08:00
/*
* Copyright (C) 2019-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* EGG is an archive format created by ESTsoft used by their ALZip
* archiving software.
*
* This software is written from scratch based solely from ESTsoft's
* file format documentation and from testing with EGG format archives.
* ESTsoft's "unEGG" module was not used in the creation of this capability
* in order to avoid to licensing restrictions on the ESTsoft "unEGG" module.
*
* EGG structure:
*
* |-----------------------------------------------------|------|
* | EGG Header  | 1 |
* |-----------------------------------------------------|------|
* | Extra Field 1:   |   |
* | Split Compression  |   |
* | Solid Compression  | 0~N |
* | Global Encryption Header  | |
* |---------------------------------------|------|------|------|
* | File Header  | 1 |   |   |
* |---------------------------------------|------| | |
* | Extra Field 2:   |   | | |
* | Filename Header   |   | 1~N | |
* | Comment Header  | 0~N | | |
* | Windows File Information  | | | |
* | Posix File Information  | | | 0~N |
* | Encrypt Header    | | | |
* |---------------------------------------|------|------| |
* | Block Header | 1 | | |
* |---------------------------------------|------| | |
* | Extra Field 3: | 0~N | 0~N  | |
* |---------------------------------------|------| | |
* | Compressed Data | 1   | | |
* |---------------------------------------|------|------|------|
* | Extra Field 4:     | |
* | Archive Comment Header  | 0~N |
* |-----------------------------------------------------|------|
*
* Authors: Micah Snyder
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#include <stdint.h>
#include <inttypes.h>
#include <wchar.h>
#include <locale.h>
#include <zlib.h>
#if HAVE_BZLIB_H
#include <bzlib.h>
#endif
#include "lzma_iface.h"
#include "egg.h"
#include "entconv.h"
#include "str.h"
#ifndef WCHAR
typedef uint16_t WCHAR;
#endif
/*
* All EGG struct variables are little-endian.
*/
#ifndef HAVE_ATTRIB_PACKED
#define __attribute__(x)
#endif
#ifdef HAVE_PRAGMA_PACK
#pragma pack(1)
#endif
#ifdef HAVE_PRAGMA_PACK_HPPA
#pragma pack 1
#endif
/*
* general defines
*/
#define EOFARC 0x08E28222 /* Signals end of each header, or end of archive. */
//#define EOFAR_ 0x2282E208
/*
* egg_header
*/
#define EGG_HEADER_MAGIC 0x41474745
#define EGG_HEADER_VERSION 0x0100
typedef uint32_t magic32_t;
typedef struct __attribute__((packed)) {
magic32_t magic; /* 0x41474745 */
uint16_t version; /* 0x0100 */
uint32_t header_id; /* Random number of the program (Cannot be 0) */
uint32_t reserved; /* 0x00000000 */
} egg_header;
/*
* file_header
*/
#define FILE_HEADER_MAGIC 0x0A8590E3
typedef struct __attribute__((packed)) {
magic32_t magic; /* 0x0A8590E3 */
uint32_t file_id; /* Unique value for each header (Includes 0) */
uint64_t file_length; /* Total size of the file */
} file_header;
/*
* block_header
* Note: split block of files exceeding 4G
*/
#define BLOCK_HEADER_MAGIC 0x02B50C13
#define BLOCK_HEADER_COMPRESS_ALGORITHM_STORE 0
#define BLOCK_HEADER_COMPRESS_ALGORITHM_DEFLATE 1
#define BLOCK_HEADER_COMPRESS_ALGORITHM_BZIP2 2
#define BLOCK_HEADER_COMPRESS_ALGORITHM_AZO 3
#define BLOCK_HEADER_COMPRESS_ALGORITHM_LZMA 4
typedef struct __attribute__((packed)) {
magic32_t magic; /* 0x02B50C13 */
uint8_t compress_algorithm; /* compress method algorithm number */
uint8_t compress_hint; /* compress method hint */
uint32_t uncompress_size; /* size of the block before compressed */
uint32_t compress_size; /* size of the block after compressed */
uint32_t crc32; /* CRC value of the block */
} block_header;
/*
* extra_field
*
* The extra_field is followed by a uint16_t or uint32_t depending on the bit_flag.
* This describes the size of the following data.
* In this way, an unexpected header can still be parsed.
* Headers that make use of the extra_field:
* - windows_file_information header
* - posix_file_information header
* - encrypt header
* - filename header
* - comment header
* - split_compression header
* - solid_compression header
*/
#define EXTRA_FIELD_FLAGS_SIZE_IS_2BYTES 0x00
#define EXTRA_FIELD_FLAGS_SIZE_IS_4BYTES 0x01
typedef struct __attribute__((packed)) {
magic32_t magic;
uint8_t bit_flag; /* the size field following bit_flag depends if bit_flag bit 1: */
} extra_field; /* 0 (uint16_t) */
/* 1 (uint32_t) */
/*
* Extra field: encrypt
*
* The encrypt_header is followed by:
* 1) dummy data (size bytes)
*
* Note: Inserted in Extra Field 2 (optional, depending on KeyBase, AES, or LEA)
*/
#define ENCRYPT_HEADER_MAGIC 0x08D1470F
#define ENCRYPT_HEADER_ENCRYPT_METHOD_XOR 0x00
#define ENCRYPT_HEADER_ENCRYPT_METHOD_AES128 0x01
#define ENCRYPT_HEADER_ENCRYPT_METHOD_AES256 0x02
#define ENCRYPT_HEADER_ENCRYPT_METHOD_LEA128 0x10
#define ENCRYPT_HEADER_ENCRYPT_METHOD_LEA256 0x20
typedef struct __attribute__((packed)) {
uint8_t aes_header[10]; /* AES/LEA Header */
uint8_t aes_footer[10]; /* AES/LEA Footer */
} aes_lea_128;
typedef struct __attribute__((packed)) {
uint8_t aes_header[18]; /* AES/LEA header */
uint8_t aes_footer[10]; /* AES/LEA footer */
} aes_lea_256;
typedef struct __attribute__((packed)) {
uint8_t verify_data[12]; /* KeyBase encryption verification data */
uint32_t crc32; /* KeyBase partial block CRC */
} zip2_xor_keybase;
typedef struct __attribute__((packed)) {
uint8_t encrypt_method; /* See above encrypt method #defines */
} encrypt_header;
/*
* Extra field: windows_file_information
*/
#define WINDOWS_INFO_MAGIC 0x2C86950B
#define WINDOWS_INFO_ATTRIBUTE_READONLY 0x01
#define WINDOWS_INFO_ATTRIBUTE_HIDDEN 0x02
#define WINDOWS_INFO_ATTRIBUTE_SYSTEM_FILE 0x04
#define WINDOWS_INFO_ATTRIBUTE_LINK_FILE 0x10 /* junction file */
#define WINDOWS_INFO_ATTRIBUTE_DIRECTORY 0x40
typedef struct __attribute__((packed)) {
uint64_t last_modified_time; /* "100-Nanosecond Time" since the Windows Epoch (00:00:00 UTC, January 1, 1601) */
uint8_t attribute; /* See above attribute #defines */
} windows_file_information;
/*
* Extra field: posix_file_information
*/
#define POSIX_INFO_MAGIC 0x1EE922E5
#define POSIX_INFO_MODE_FILETYPE_BITMASK 0x0170000 /* bitmask for the file type bitfields */
#define POSIX_INFO_MODE_SOCKET 0x0140000 /* socket */
#define POSIX_INFO_MODE_SYM_LINK 0x0120000 /* symbolic link */
#define POSIX_INFO_MODE_REG_FILE 0x0100000 /* regular file */
#define POSIX_INFO_MODE_BLOCK_DEVICE 0x0060000 /* block device */
#define POSIX_INFO_MODE_DIRECTORY 0x0040000 /* directory */
#define POSIX_INFO_MODE_CHAR_DEVICE 0x0020000 /* character device */
#define POSIX_INFO_MODE_FIFO 0x0010000 /* FIFO */
#define POSIX_INFO_MODE_SET_UID_BIT 0x0004000 /* set UID bit */
#define POSIX_INFO_MODE_SET_GROUPID_BIT 0x0002000 /* set-group-ID bit (see below) */
#define POSIX_INFO_MODE_STICKY_BIT 0x0001000 /* sticky bit (see below) */
#define POSIX_INFO_MODE_PERM_OWNER_MASK 0x00700 /* mask for file owner permissions */
#define POSIX_INFO_MODE_PERM_OWNER_READ 0x00400 /* owner has read permission */
#define POSIX_INFO_MODE_PERM_OWNER_WRITE 0x00200 /* owner has write permission */
#define POSIX_INFO_MODE_PERM_OWNER_EXECUTE 0x00100 /* owner has execute permission */
#define POSIX_INFO_MODE_PERM_GROUP_MASK 0x00070 /* mask for group permissions */
#define POSIX_INFO_MODE_PERM_GROUP_READ 0x00040 /* group has read permission */
#define POSIX_INFO_MODE_PERM_GROUP_WRITE 0x00020 /* group has write permission */
#define POSIX_INFO_MODE_PERM_GROUP_EXECUTE 0x00010 /* group has execute permission */
#define POSIX_INFO_MODE_PERM_OTHERS_MASK 0x00007 /* mask for permissions for others (not in group) */
#define POSIX_INFO_MODE_PERM_OTHERS_READ 0x00004 /* others have read permission */
#define POSIX_INFO_MODE_PERM_OTHERS_WRITE 0x00002 /* others have write permission */
#define POSIX_INFO_MODE_PERM_OTHERS_EXECUTE 0x00001 /* others have execute permission*/
typedef struct __attribute__((packed)) {
uint32_t mode; /* see above mode #defines */
uint32_t uid; /* */
uint32_t gid; /* */
uint64_t last_modified_time; /* "Second Time" since the Unix Epoch (00:00:00 UTC, January 1, 1970) */
} posix_file_information;
/*
* Extra field: dummy_header
*
* The dummy header extra_info is followed by:
* 1) dummy data (size bytes)
*
* Note: No need to consider if the size is too small to fit the dummy header because it can be distinguished by size calculation.
*/
#define DUMMY_HEADER_MAGIC 0x07463307
/*
* Extra field: filename
*
* The filename extra_field is followed by:
* 1) uint16_t locale IFF bit_flag is NOT unicode (UCS-2 LE)
* 1) uint32_t parent_path_id IFF bit_flag is relative.
* parent_path_id will be the ID of a file possessing the parent path.
* 2) name buffer (size bytes minus above optional fields)
*/
#define FILENAME_HEADER_MAGIC 0x0A8591AC
#define FILENAME_HEADER_FLAGS_ENCRYPT 0x04
#define FILENAME_HEADER_FLAGS_MULTIBYTE_CODEPAGE_INSTEAD_OF_UTF8 0x08
#define FILENAME_HEADER_FLAGS_RELATIVE_PATH_INSTEAD_OF_ABSOLUTE 0x10
#define FILENAME_HEADER_LOCALE_USE_SYSTEM 0
#define FILENAME_HEADER_LOCALE_JAPANESE 932 /* Shift-JIS */
#define FILENAME_HEADER_LOCALE_KOREAN 949
// typedef struct __attribute__((packed)) {
// (optional) uint16_t locale
// (optional) uint32_t parent_path_id
// uint8_t name_data [extra_field->size - sizeof(locale) - sizeof(parent_path_id)]
// } filename_header;
/*
* Extra field: comment
*
* The comment extra_field is followed by:
* 1) comment of size "N", exclude NULL character.
*/
#define COMMENT_HEADER_MAGIC 0x04C63672
#define COMMENT_HEADER_FLAGS_ENCRYPT 0x04
#define COMMENT_HEADER_FLAGS_MULTIBYTE_CODEPAGE_INSTEAD_OF_UTF8 0x08
/*
* Extra field: split compression
*/
#define SPLIT_COMPRESSION_MAGIC 0x24F5A262
typedef struct __attribute__((packed)) {
uint32_t prev_file_id; /* ID of previous file, 0 if first */
uint32_t next_file_id; /* ID of next file, 0 if last */
} split_compression;
/*
* Extra field: solid compression
*/
#define SOLID_COMPRESSION_MAGIC 0x24E5A060
#ifdef HAVE_PRAGMA_PACK
#pragma pack()
#endif
#ifdef HAVE_PRAGMA_PACK_HPPA
#pragma pack
#endif
typedef struct {
char* name_utf8;
uint32_t parent_path_id;
} egg_filename;
typedef struct {
encrypt_header* header; /* Global Encryption Header */
union {
aes_lea_128* al128;
aes_lea_256* al256;
zip2_xor_keybase* xor ;
} encrypt_al;
} egg_encrypt;
typedef struct {
block_header* blockHeader;
char* compressedData;
} egg_block;
typedef struct {
file_header* file;
egg_filename filename;
windows_file_information* windowsFileInformation;
posix_file_information* posixFileInformation;
egg_encrypt* encrypt;
uint64_t nBlocks;
egg_block** blocks;
uint64_t nComments;
char** comments;
} egg_file;
typedef struct {
fmap_t* map;
size_t offset;
uint64_t fileExtractionIndex;
int bSolid; /* Solid == all files compressed together. */
int bSplit; /* Split == multiple files make up single archive. */
split_compression* splitInfo;
egg_encrypt* encrypt;
uint64_t nFiles;
egg_file** files;
uint64_t nBlocks;
egg_block** blocks;
uint64_t nComments;
char** comments;
} egg_handle;
#define EGG_VALIDATE_HANDLE(h) \
((!handle || !handle->map || (handle->offset > handle->map->len)) ? CL_EARG : CL_SUCCESS)
const char* getEncryptName(uint8_t method)
{
const char* encryptName = NULL;
switch (method) {
case ENCRYPT_HEADER_ENCRYPT_METHOD_XOR:
encryptName = "XOR";
break;
case ENCRYPT_HEADER_ENCRYPT_METHOD_AES128:
encryptName = "AES 128";
break;
case ENCRYPT_HEADER_ENCRYPT_METHOD_LEA128:
encryptName = "LEA 128";
break;
case ENCRYPT_HEADER_ENCRYPT_METHOD_AES256:
encryptName = "AES 256";
break;
case ENCRYPT_HEADER_ENCRYPT_METHOD_LEA256:
encryptName = "LEA 256";
break;
default:
encryptName = "<unknown method>";
}
return encryptName;
}
const char* getMagicHeaderName(uint32_t magic)
{
const char* magicName = NULL;
switch (magic) {
case EGG_HEADER_MAGIC:
magicName = "EGG_HEADER_MAGIC";
break;
case FILE_HEADER_MAGIC:
magicName = "FILE_HEADER_MAGIC";
break;
case BLOCK_HEADER_MAGIC:
magicName = "BLOCK_HEADER_MAGIC";
break;
case ENCRYPT_HEADER_MAGIC:
magicName = "ENCRYPT_HEADER_MAGIC";
break;
case WINDOWS_INFO_MAGIC:
magicName = "WINDOWS_INFO_MAGIC";
break;
case POSIX_INFO_MAGIC:
magicName = "POSIX_INFO_MAGIC";
break;
case DUMMY_HEADER_MAGIC:
magicName = "DUMMY_HEADER_MAGIC";
break;
case FILENAME_HEADER_MAGIC:
magicName = "FILENAME_HEADER_MAGIC";
break;
case COMMENT_HEADER_MAGIC:
magicName = "COMMENT_HEADER_MAGIC";
break;
case SPLIT_COMPRESSION_MAGIC:
magicName = "SPLIT_COMPRESSION_MAGIC";
break;
case SOLID_COMPRESSION_MAGIC:
magicName = "SOLID_COMPRESSION_MAGIC";
break;
default:
magicName = "<unknown header magic>";
}
return magicName;
}
static void egg_free_encrypt(egg_encrypt* encryptInfo)
{
free(encryptInfo);
}
static cl_error_t egg_parse_encrypt_header(const uint8_t* index, size_t size, egg_encrypt** encryptInfo)
{
/*
* The EGG specification (last updated 2016) for the encrypt header is not accurate.
* The following describes my findings of the actual format for the encrypt header.
*
* The significant discrepancy is that the Size includes the size of the header iself, not just the data following it.
* No other extra_field header's size field includes the size of itself.
* This must be accounted for by the caller of this function (see the "Fudge factor" comments where this function is used).
*
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
* | Magic(ENCRYP) | 4 | 0x08D1470F |
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
* | Bit flag | 1 | 0 |
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
* | Size | 2 | sizeof( Magic ) + sizeof( Bit flag ) + sizeof( Size ) + sizeof( Encrypt Method ) + sizeof( Method Header ) |
* |---------------|---------|---|--------------------------------------------------------------------------------------------------------|
* | Encrypt | 1 | 0 | KeyBase (XOR) |
* | Method | |---|--------------------------------------------------------------------------------------------------------|
* | | | 1 | AES128 |
* | | |---|--------------------------------------------------------------------------------------------------------|
* | | | 2 | AES256 |
* | | |---|--------------------------------------------------------------------------------------------------------|
* | | | 5 | LEA128 |
* | | |---|--------------------------------------------------------------------------------------------------------|
* | | | 6 | LEA256 |
* |---------------|---------|---|--------------------------------------------------------------------------------------------------------|
*
* Depending on the Method (XOR / AES/LEA128 / AES/LEA256) The above will be be followed one of the following Method Headers:
*
* XOR (KeyBase):
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
* | verify Data | 12 | Encryption Verification Data |
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
* | CRC32 | 4 | Partial Block CRC |
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
*
* AES / LEA 128
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
* | Magic(ENCRYP) | 10 | AES/LEA Header |
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
* | Magic(ENCRYP) | 10 | AES/LEA Footer |
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
*
* AES / LEA 256
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
* | Magic(ENCRYP) | 18 | AES/LEA Header |
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
* | Magic(ENCRYP) | 10 | AES/LEA Footer |
* |---------------|---------|------------------------------------------------------------------------------------------------------------|
*/
cl_error_t status = CL_EPARSE;
egg_encrypt* encrypt = NULL;
if (!index || 0 == size || !encryptInfo) {
cli_errmsg("egg_parse_encrypt_header: Invalid args.\n");
status = CL_EARG;
goto done;
}
*encryptInfo = NULL;
cli_dbgmsg("egg_parse_encrypt_header: Encrypted archive.\n");
cli_dbgmsg("egg_parse_encrypt_header: size of encrypt extra_field data: %zu\n", size);
if (size < sizeof(encrypt_header)) {
cli_warnmsg("egg_parse_encrypt_header: Encrypt header size too small (%zu < %zu)\n", size, sizeof(encrypt_header));
goto done;
}
encrypt = (egg_encrypt*)cli_calloc(1, sizeof(egg_encrypt));
if (NULL == encrypt) {
cli_errmsg("egg_parse_encrypt_header: Failed to allocate memory for egg_encrypt.\n");
status = CL_EMEM;
goto done;
}
encrypt->header = (encrypt_header*)index;
cli_dbgmsg("egg_parse_encrypt_header: encrypt_header->encrypt_method: %02x (%s)\n", encrypt->header->encrypt_method, getEncryptName(encrypt->header->encrypt_method));
index += sizeof(encrypt_header);
size -= sizeof(encrypt_header);
if (ENCRYPT_HEADER_ENCRYPT_METHOD_XOR == encrypt->header->encrypt_method) {
if (size != sizeof(zip2_xor_keybase)) {
cli_warnmsg("egg_parse_encrypt_header: Encrypt header size for XOR is different than expected (%zu != %zu)\n", size, sizeof(zip2_xor_keybase));
goto done;
}
encrypt->encrypt_al.xor = (zip2_xor_keybase*)index;
cli_dbgmsg("egg_parse_encrypt_header: encrypt_header->crc32: %08x\n", le32_to_host(encrypt->encrypt_al.xor->crc32));
} else {
/*
* For AES/LEA, the additional information is found inside of embedded extra field.
*/
switch (encrypt->header->encrypt_method) {
case ENCRYPT_HEADER_ENCRYPT_METHOD_AES128:
case ENCRYPT_HEADER_ENCRYPT_METHOD_LEA128: {
if (size < sizeof(aes_lea_128)) {
cli_warnmsg("egg_parse_encrypt_header: Encrypt header size for AES/LEA128 is different than expected (%zu != %zu)\n", size, sizeof(aes_lea_128));
goto done;
}
encrypt->encrypt_al.al128 = (aes_lea_128*)index;
index += sizeof(aes_lea_128);
size -= sizeof(aes_lea_128);
break;
}
case ENCRYPT_HEADER_ENCRYPT_METHOD_AES256:
case ENCRYPT_HEADER_ENCRYPT_METHOD_LEA256: {
if (size < sizeof(aes_lea_256)) {
cli_warnmsg("egg_parse_encrypt_header: Encrypt header size for AES/LEA256 is different than expected (%zu != %zu)\n", size, sizeof(aes_lea_256));
goto done;
}
encrypt->encrypt_al.al256 = (aes_lea_256*)index;
index += sizeof(aes_lea_256);
size -= sizeof(aes_lea_256);
break;
}
default: {
cli_warnmsg("egg_parse_encrypt_header: Unknown encrypt method: %d\n", encrypt->header->encrypt_method);
goto done;
}
}
}
*encryptInfo = encrypt;
status = CL_SUCCESS;
done:
if (CL_SUCCESS != status) {
egg_free_encrypt(encrypt);
}
return status;
}
static cl_error_t egg_parse_comment_header(const uint8_t* index, size_t size, extra_field* extraField, char** commentInfo)
{
cl_error_t status = CL_EPARSE;
char* comment_utf8 = NULL;
size_t comment_utf8_size = 0;
if (!index || 0 == size || !extraField || !commentInfo) {
cli_errmsg("egg_parse_comment_headers: Invalid args!\n");
return CL_EARG;
}
*commentInfo = NULL;
if (extraField->bit_flag & COMMENT_HEADER_FLAGS_ENCRYPT) {
/*
* comment is encrypted, nothing to be done.
*/
*commentInfo = cli_strdup("<encrypted>");
status = CL_EUNPACK;
goto done;
}
/*
* Store comment as UTF-8 string.
*/
if (extraField->bit_flag & COMMENT_HEADER_FLAGS_MULTIBYTE_CODEPAGE_INSTEAD_OF_UTF8) {
/*
* Unlike with filenames, the multibyte string codepage (or "locale") is not present in comment headers.
* Try conversion with CODEPAGE_UTF8.
*/
if (CL_SUCCESS != cli_codepage_to_utf8((char*)index, size, CODEPAGE_UTF8, &comment_utf8, &comment_utf8_size)) {
cli_dbgmsg("egg_parse_comment_header: failed to convert codepage \"0\" to UTF-8\n");
comment_utf8 = cli_genfname(NULL);
}
} else {
/* Should already be UTF-8. Use as-is.. */
comment_utf8 = CLI_STRNDUP((char*)index, size);
if (NULL == comment_utf8) {
cli_dbgmsg("egg_parse_comment_header: failed to allocate comment buffer.\n");
status = CL_EMEM;
goto done;
}
}
cli_dbgmsg("egg_parse_comment_header: comment: %s\n", comment_utf8);
*commentInfo = comment_utf8;
status = CL_SUCCESS;
done:
return status;
}
static void egg_free_egg_block(egg_block* block)
{
free(block);
}
static cl_error_t egg_parse_block_headers(egg_handle* handle, egg_block** block)
{
cl_error_t status = CL_EPARSE;
egg_block* eggBlock = NULL;
block_header* blockHeader = NULL;
uint32_t magic = 0;
const uint8_t* index = 0;
if (!handle || !block) {
cli_errmsg("egg_parse_block_headers: Invalid args!\n");
return CL_EARG;
}
*block = NULL;
if (CL_SUCCESS != EGG_VALIDATE_HANDLE(handle)) {
cli_errmsg("egg_parse_block_headers: Invalid handle values!\n");
status = CL_EARG;
goto done;
}
/*
* 1st:
* Block headers must start with the block_header.
*/
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(block_header));
if (!index) {
cli_dbgmsg("egg_parse_block_headers: File buffer too small to contain block header.\n");
goto done;
}
eggBlock = (egg_block*)cli_calloc(1, sizeof(egg_block));
if (NULL == eggBlock) {
cli_errmsg("egg_parse_block_headers: Failed to allocate memory for egg_block.\n");
status = CL_EMEM;
goto done;
}
blockHeader = (block_header*)index;
eggBlock->blockHeader = blockHeader;
if (BLOCK_HEADER_MAGIC != le32_to_host(blockHeader->magic)) {
cli_dbgmsg("egg_parse_block_headers: Invalid block header magic: %08x.\n", le32_to_host(blockHeader->magic));
goto done;
}
cli_dbgmsg("egg_parse_block_headers: block_header->magic: %08x (%s)\n", le32_to_host(blockHeader->magic), getMagicHeaderName(le32_to_host(blockHeader->magic)));
cli_dbgmsg("egg_parse_block_headers: block_header->compress_algorithm: %08x\n", blockHeader->compress_algorithm);
cli_dbgmsg("egg_parse_block_headers: block_header->compress_hint: %08x\n", blockHeader->compress_hint);
cli_dbgmsg("egg_parse_block_headers: block_header->uncompress_size: %08x\n", le32_to_host(blockHeader->uncompress_size));
cli_dbgmsg("egg_parse_block_headers: block_header->compress_size: %08x\n", le32_to_host(blockHeader->compress_size));
cli_dbgmsg("egg_parse_block_headers: block_header->crc32: %08x\n", le32_to_host(blockHeader->crc32));
if (0 == le16_to_host(blockHeader->compress_size)) {
cli_warnmsg("egg_parse_block_headers: Empty block!\n");
}
handle->offset += sizeof(block_header);
/*
* 2nd:
* After the block_header, the following extra field headers may be present:
* a) EOFARC
*/
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(magic32_t));
if (!index) {
cli_dbgmsg("egg_parse_block_headers: File buffer too small to contain end of archive magic bytes.\n");
goto done;
}
magic = le32_to_host(*((uint32_t*)index));
if (EOFARC != magic) {
cli_dbgmsg("egg_parse_block_headers: EOFARC missing after block header. Found these bytes instead: %08x. (%s)\n", magic, getMagicHeaderName(magic));
goto done;
}
cli_dbgmsg("egg_parse_block_headers: End of block header.\n");
handle->offset += sizeof(magic32_t);
/*
* Compressed data should follow the Block Header.
*/
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, blockHeader->compress_size);
if (!index) {
cli_dbgmsg("egg_parse_block_headers: File buffer too small to contain block compressed data.\n");
goto done;
}
eggBlock->compressedData = (char*)index;
handle->offset += blockHeader->compress_size;
*block = eggBlock;
status = CL_SUCCESS;
done:
if (CL_SUCCESS != status) {
if (eggBlock) {
egg_free_egg_block(eggBlock);
}
}
return status;
}
static void egg_free_egg_file(egg_file* file)
{
uint32_t i = 0;
if (NULL != file->filename.name_utf8) {
free(file->filename.name_utf8);
file->filename.name_utf8 = NULL;
}
2023-01-14 18:28:39 +08:00
if (NULL != file->encrypt) {
free(file->encrypt);
file->encrypt = NULL;
}
2022-10-22 18:41:00 +08:00
if (NULL != file->blocks) {
for (i = 0; i < file->nBlocks; i++) {
egg_free_egg_block(file->blocks[i]);
file->blocks[i] = NULL;
}
free(file->blocks);
file->blocks = NULL;
}
if (NULL != file->comments) {
for (i = 0; i < file->nComments; i++) {
free(file->comments[i]);
file->comments[i] = NULL;
}
free(file->comments);
file->comments = NULL;
}
free(file);
}
static cl_error_t egg_parse_archive_extra_field(egg_handle* handle)
{
cl_error_t status = CL_EPARSE;
const uint8_t* index = NULL;
extra_field* extraField = NULL;
uint32_t magic = 0;
uint32_t size = 0;
if (!handle) {
cli_errmsg("egg_parse_archive_extra_field: Invalid args!\n");
return CL_EARG;
}
if (CL_SUCCESS != EGG_VALIDATE_HANDLE(handle)) {
cli_errmsg("egg_parse_comment_headers: Invalid handle values!\n");
status = CL_EARG;
goto done;
}
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(extra_field));
if (!index) {
cli_dbgmsg("egg_parse_archive_extra_field: File buffer too small to contain extra_field header.\n");
goto done;
}
extraField = (extra_field*)index;
cli_dbgmsg("egg_parse_archive_extra_field: extra_field->magic: %08x (%s)\n", le32_to_host(extraField->magic), getMagicHeaderName(le32_to_host(extraField->magic)));
cli_dbgmsg("egg_parse_archive_extra_field: extra_field->bit_flag: %02x\n", extraField->bit_flag);
handle->offset += sizeof(extra_field);
if (extraField->bit_flag & EXTRA_FIELD_FLAGS_SIZE_IS_4BYTES) {
/* size is uint32_t */
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(uint32_t));
if (!index) {
cli_dbgmsg("egg_parse_archive_extra_field: File buffer too small to contain extra_field header.\n");
goto done;
}
size = le32_to_host(*(uint32_t*)index);
handle->offset += sizeof(uint32_t);
} else {
/* size is uint16_t */
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(uint16_t));
if (!index) {
cli_dbgmsg("egg_parse_archive_extra_field: File buffer too small to contain extra_field header.\n");
goto done;
}
size = le16_to_host(*(uint16_t*)index);
handle->offset += sizeof(uint16_t);
}
cli_dbgmsg("egg_parse_archive_extra_field: extra_field->size: %u\n", size);
magic = le32_to_host(extraField->magic);
switch (magic) {
case SOLID_COMPRESSION_MAGIC: {
/*
* Solid archive is an archive packed with a special compression method,
* which treats several or all files within the archive as one continuous data stream.
*/
cli_dbgmsg("egg_parse_archive_extra_field: Solid archive. Several or all files within the archive treated as one continuous data stream.\n");
if (0 != handle->bSolid) {
cli_warnmsg("egg_parse_archive_extra_field: Encountered more than 1 Solid extra_field!\n");
goto done;
}
handle->bSolid = 1;
break;
}
case SPLIT_COMPRESSION_MAGIC: {
/*
* Split archives are single archives split into multiple .egg volumes.
*
* It is the first file if previous files ID is 0, and is the last file
* if next files ID is 0.
*
* Header and Extra Field shouldnt be cut when split compressing.
* Compressed Block Data can be saved cut.
* If header is excluded from the split size, insert Dummy Extra Field.
*
* If file compression ratio not applied when split compressing, modify
* Magic of the header into Dummy Header or Skip Header (0xFFFF0000)
* so it can be skipped.
*/
split_compression* split = NULL;
if (0 != handle->bSplit) {
cli_warnmsg("egg_parse_archive_extra_field: Encountered more than 1 Split extra_field!\n");
goto done;
}
handle->bSplit = 1;
cli_warnmsg("egg_parse_archive_extra_field: Split archive. Split archives are single archives split into multiple .egg volumes.\n");
if (sizeof(split_compression) != size) {
cli_dbgmsg("egg_parse_archive_extra_field: size in extra_field is different than size of split_compression (%zu != %u).\n", sizeof(split_compression), size);
} else {
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(split_compression));
if (!index) {
cli_dbgmsg("egg_parse_archive_extra_field: File buffer too small to contain split compression header.\n");
goto done;
}
split = (split_compression*)index;
handle->splitInfo = split;
cli_dbgmsg("egg_parse_archive_extra_field: split_compression->prev_file_id: %08x\n", le32_to_host(split->prev_file_id));
cli_dbgmsg("egg_parse_archive_extra_field: split_compression->next_file_id: %08x\n", le32_to_host(split->next_file_id));
}
break;
}
case ENCRYPT_HEADER_MAGIC: {
/*
* EGG files may have a global encryption header.
* It is unclear if this means each file is encrypted, or that additional
* data beyond the file contents is encrypted.
*/
if (NULL != handle->encrypt) {
cli_warnmsg("egg_parse_archive_extra_field: Encountered more than 1 encrypt_header!\n");
goto done;
}
/*
* Fudge factor.
* The documentation is hazy about how the encrypt header works.
* From testing, it seems that for encrypted files, the size in the extra_field includes the size OF the extra field.
*/
size -= sizeof(extra_field) + sizeof(uint16_t);
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, size);
if (!index) {
cli_errmsg("egg_parse_archive_extra_field: File buffer too small to contain encryption headers.\n");
goto done;
}
if (CL_SUCCESS != egg_parse_encrypt_header(index, size, &handle->encrypt)) {
cli_errmsg("egg_parse_archive_extra_field: Failed to parse encryption headers.\n");
goto done;
}
break;
}
default: {
cli_dbgmsg("egg_parse_archive_extra_field: unexpected header magic: %08x (%s)\n", magic, getMagicHeaderName(magic));
}
}
handle->offset += size;
status = CL_SUCCESS;
done:
return status;
}
static void print_posix_info_mode(uint32_t mode)
{
/* File type flags */
if (mode & POSIX_INFO_MODE_REG_FILE) {
printf("-");
} else if (mode & POSIX_INFO_MODE_DIRECTORY) {
printf("d");
} else if (mode & POSIX_INFO_MODE_CHAR_DEVICE) {
printf("c");
} else if (mode & POSIX_INFO_MODE_BLOCK_DEVICE) {
printf("s");
} else if (mode & POSIX_INFO_MODE_SOCKET) {
printf("s");
} else if (mode & POSIX_INFO_MODE_FIFO) {
printf("p");
} else if (mode & POSIX_INFO_MODE_SYM_LINK) {
printf("l");
} else if (mode & POSIX_INFO_MODE_SOCKET) {
printf("s");
}
/* Owner/Group/Other permissions */
if (mode & POSIX_INFO_MODE_PERM_OWNER_READ) {
printf("r");
} else {
printf("-");
}
if (mode & POSIX_INFO_MODE_PERM_OWNER_WRITE) {
printf("w");
} else {
printf("-");
}
if (mode & POSIX_INFO_MODE_SET_UID_BIT) {
printf("s");
} else if (mode & POSIX_INFO_MODE_PERM_OWNER_EXECUTE) {
printf("x");
} else {
printf("-");
}
if (mode & POSIX_INFO_MODE_PERM_GROUP_READ) {
printf("r");
} else {
printf("-");
}
if (mode & POSIX_INFO_MODE_PERM_GROUP_WRITE) {
printf("w");
} else {
printf("-");
}
if (mode & POSIX_INFO_MODE_SET_UID_BIT) {
printf("s");
}
if (mode & POSIX_INFO_MODE_SET_GROUPID_BIT) {
printf("s");
}
if (mode & POSIX_INFO_MODE_PERM_GROUP_EXECUTE) {
printf("x");
} else {
printf("-");
}
if (mode & POSIX_INFO_MODE_PERM_OTHERS_READ) {
printf("r");
} else {
printf("-");
}
if (mode & POSIX_INFO_MODE_PERM_OTHERS_WRITE) {
printf("w");
} else {
printf("-");
}
if (mode & POSIX_INFO_MODE_PERM_OTHERS_EXECUTE) {
printf("x");
} else {
printf("-");
}
/* Sticky Bit */
if (mode & POSIX_INFO_MODE_STICKY_BIT)
printf("t");
printf("\n");
}
static cl_error_t egg_parse_file_extra_field(egg_handle* handle, egg_file* eggFile)
{
cl_error_t status = CL_EPARSE;
const uint8_t* index = NULL;
extra_field* extraField = NULL;
uint32_t magic = 0;
uint32_t size = 0;
if (!handle || !eggFile) {
cli_errmsg("egg_parse_file_extra_field: Invalid args!\n");
return CL_EARG;
}
if (CL_SUCCESS != EGG_VALIDATE_HANDLE(handle)) {
cli_errmsg("egg_parse_file_extra_field: Invalid handle values!\n");
status = CL_EARG;
goto done;
}
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(extra_field));
if (!index) {
cli_dbgmsg("egg_parse_file_extra_field: File buffer too small to contain extra_field header.\n");
goto done;
}
extraField = (extra_field*)index;
cli_dbgmsg("egg_parse_file_extra_field: extra_field->magic: %08x (%s)\n", le32_to_host(extraField->magic), getMagicHeaderName(le32_to_host(extraField->magic)));
cli_dbgmsg("egg_parse_file_extra_field: extra_field->bit_flag: %02x\n", extraField->bit_flag);
handle->offset += sizeof(extra_field);
if (extraField->bit_flag & EXTRA_FIELD_FLAGS_SIZE_IS_4BYTES) {
/* size is uint32_t */
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(uint32_t));
if (!index) {
cli_dbgmsg("egg_parse_file_extra_field: File buffer too small to contain extra_field header.\n");
goto done;
}
size = le32_to_host(*(uint32_t*)index);
handle->offset += sizeof(uint32_t);
} else {
/* size is uint16_t */
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(uint16_t));
if (!index) {
cli_dbgmsg("egg_parse_file_extra_field: File buffer too small to contain extra_field header.\n");
goto done;
}
size = le16_to_host(*(uint16_t*)index);
handle->offset += sizeof(uint16_t);
}
cli_dbgmsg("egg_parse_file_extra_field: extra_field->size: %u\n", size);
magic = le32_to_host(extraField->magic);
switch (magic) {
case FILENAME_HEADER_MAGIC: {
/*
* File Filename Header
*/
uint16_t codepage = 0; /* Windows code page https://docs.microsoft.com/en-us/windows/desktop/Intl/code-page-identifiers) */
uint32_t name_size = 0;
uint32_t remaining_size = size;
char* name_utf8 = NULL;
size_t name_utf8_size = 0;
if (NULL != eggFile->filename.name_utf8) {
cli_warnmsg("egg_parse_file_extra_field: Encountered more than 1 filename_header!\n");
goto done;
}
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, size);
if (!index) {
cli_dbgmsg("egg_parse_file_extra_field: File buffer too small to contain name fields.\n");
goto done;
}
if (extraField->bit_flag & FILENAME_HEADER_FLAGS_ENCRYPT)
cli_dbgmsg("egg_parse_file_extra_field: filename_header->bit_flag: encrypted\n");
else
cli_dbgmsg("egg_parse_file_extra_field: filename_header->bit_flag: not encrypted\n");
if (extraField->bit_flag & FILENAME_HEADER_FLAGS_RELATIVE_PATH_INSTEAD_OF_ABSOLUTE)
cli_dbgmsg("egg_parse_file_extra_field: filename_header->bit_flag: relative-path\n");
else
cli_dbgmsg("egg_parse_file_extra_field: filename_header->bit_flag: absolute-path\n");
if (extraField->bit_flag & FILENAME_HEADER_FLAGS_MULTIBYTE_CODEPAGE_INSTEAD_OF_UTF8)
cli_dbgmsg("egg_parse_file_extra_field: filename_header->bit_flag: Windows Multibyte + codepage\n");
else
cli_dbgmsg("egg_parse_file_extra_field: filename_header->bit_flag: UTF-8\n");
if (extraField->bit_flag & FILENAME_HEADER_FLAGS_MULTIBYTE_CODEPAGE_INSTEAD_OF_UTF8) {
/* Utf-8 - header will include locale */
/* Check that the size is big enough */
if (remaining_size < sizeof(uint16_t)) {
cli_dbgmsg("egg_parse_file_extra_field: size too small for locale information.\n");
goto done;
}
codepage = *(uint16_t*)index;
cli_dbgmsg("egg_parse_file_extra_field: filename_header->codepage: %u\n", codepage);
index += sizeof(uint16_t);
handle->offset += sizeof(uint16_t);
remaining_size -= sizeof(uint16_t);
}
if (extraField->bit_flag & FILENAME_HEADER_FLAGS_RELATIVE_PATH_INSTEAD_OF_ABSOLUTE) {
/* header will include parent_path_id */
/* Check that the size is big enough */
if (remaining_size < sizeof(uint32_t)) {
cli_dbgmsg("egg_parse_file_extra_field: size too small for parent_path_id.\n");
goto done;
}
eggFile->filename.parent_path_id = *(uint16_t*)index;
cli_dbgmsg("egg_parse_file_extra_field: filename_header->parent_path_id: %u\n", eggFile->filename.parent_path_id);
index += sizeof(uint32_t);
handle->offset += sizeof(uint32_t);
remaining_size -= sizeof(uint32_t);
}
if (remaining_size == 0) {
cli_dbgmsg("egg_parse_file_extra_field: size too small for name string.\n");
goto done;
}
name_size = remaining_size;
/*
* Store name as UTF-8 string.
*/
if (extraField->bit_flag & FILENAME_HEADER_FLAGS_MULTIBYTE_CODEPAGE_INSTEAD_OF_UTF8) {
/* Convert ANSI codepage to UTF-8. EGG format explicitly supports:
* - 949 (Korean Unified Code)
* - 932 (Japanese Shift-JIS) */
if (0 == codepage) {
if (CL_SUCCESS != cli_codepage_to_utf8((char*)index, name_size, CODEPAGE_UTF8, &name_utf8, &name_utf8_size)) {
cli_dbgmsg("egg_parse_file_extra_field: failed to convert codepage \"0\" to UTF-8\n");
name_utf8 = cli_genfname(NULL);
}
} else {
if (CL_SUCCESS != cli_codepage_to_utf8((char*)index, name_size, codepage, &name_utf8, &name_utf8_size)) {
cli_dbgmsg("egg_parse_file_extra_field: failed to convert codepage %u to UTF-8\n", codepage);
name_utf8 = cli_genfname(NULL);
}
}
} else {
/* Should already be UTF-8. Use as-is.. */
name_utf8 = CLI_STRNDUP((char*)index, name_size);
if (NULL == name_utf8) {
cli_dbgmsg("egg_parse_file_extra_field: failed to allocate name buffer.\n");
status = CL_EMEM;
goto done;
}
}
eggFile->filename.name_utf8 = name_utf8;
cli_dbgmsg("egg_parse_file_extra_field: filename_header->name: %s\n", eggFile->filename.name_utf8);
break;
}
case COMMENT_HEADER_MAGIC: {
/*
* File Comment Header
*/
cl_error_t retval = CL_EPARSE;
char* comment = NULL;
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, size);
if (!index) {
cli_dbgmsg("egg_parse_file_extra_field: File buffer too small to contain comment fields.\n");
goto done;
}
if (CL_SUCCESS != (retval = egg_parse_comment_header(index, size, extraField, &comment))) {
cli_dbgmsg("egg_parse_file_extra_field: Issue parsing comment header. Error code: %u\n", retval);
break;
} else {
/*
* Success?
*/
if (comment == NULL) {
/* Uh... no. */
cli_errmsg("egg_parse_file_extra_field: Logic error! Succesfully parsed comment header,"
" but did not return egg_comment information!\n");
goto done;
} else {
/*
* Comment found. Add comment to our list.
*/
char** comments_tmp;
comments_tmp = (char**)cli_realloc(
(void*)eggFile->comments,
sizeof(char*) * (eggFile->nComments + 1));
if (NULL == comments_tmp) {
free(comment);
status = CL_EMEM;
goto done;
}
eggFile->comments = comments_tmp;
eggFile->comments[eggFile->nComments] = comment;
eggFile->nComments++;
}
}
break;
}
case ENCRYPT_HEADER_MAGIC: {
/*
* File Encryption Header.
*/
if (NULL != eggFile->encrypt) {
cli_warnmsg("egg_parse_file_extra_field: Encountered more than 1 encrypt_header!\n");
goto done;
}
/*
* Fudge factor.
* The documentation is hazy about how the encrypt header works.
* From testing, it seems that for encrypted files, the size in the extra_field includes the size OF the extra field.
*/
size -= sizeof(extra_field) + sizeof(uint16_t);
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, size);
if (!index) {
cli_errmsg("egg_parse_file_extra_field: File buffer too small to contain encryption fields.\n");
goto done;
}
if (CL_SUCCESS != egg_parse_encrypt_header(index, size, &eggFile->encrypt)) {
cli_errmsg("egg_parse_file_extra_field: Failed to parse encrypt_header.\n");
goto done;
}
break;
}
case WINDOWS_INFO_MAGIC: {
windows_file_information* windowsFileInformation = NULL;
if (NULL != eggFile->windowsFileInformation) {
cli_warnmsg("egg_parse_file_extra_field: Encountered more than 1 windows_file_information!\n");
goto done;
}
if (sizeof(windows_file_information) != size) {
cli_warnmsg("egg_parse_file_extra_field: Invalid size of windows_file_information!\n");
}
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(windows_file_information));
if (!index) {
cli_dbgmsg("egg_parse_file_extra_field: File buffer too small to contain windows info.\n");
goto done;
}
windowsFileInformation = (windows_file_information*)index;
eggFile->windowsFileInformation = windowsFileInformation;
cli_dbgmsg("egg_parse_file_extra_field: windows_file_information->last_modified_time: %016" PRIx64 "\n", le64_to_host(windowsFileInformation->last_modified_time));
cli_dbgmsg("egg_parse_file_extra_field: windows_file_information->attribute: %08x\n", windowsFileInformation->attribute);
break;
}
case POSIX_INFO_MAGIC: {
posix_file_information* posixFileInformation = NULL;
if (NULL != eggFile->posixFileInformation) {
cli_warnmsg("egg_parse_file_extra_field: Encountered more than 1 posix_file_information!\n");
goto done;
}
if (sizeof(posix_file_information) != size) {
cli_warnmsg("egg_parse_file_extra_field: Invalid size of posix_file_information!\n");
}
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(posix_file_information));
if (!index) {
cli_dbgmsg("egg_parse_file_extra_field: File buffer too small to contain posix info.\n");
goto done;
}
posixFileInformation = (posix_file_information*)index;
eggFile->posixFileInformation = posixFileInformation;
cli_dbgmsg("egg_parse_file_extra_field: posix_file_information->mode: %08x ", le32_to_host(posixFileInformation->mode));
if (UNLIKELY(cli_debug_flag)) {
print_posix_info_mode(posixFileInformation->mode);
}
cli_dbgmsg("egg_parse_file_extra_field: posix_file_information->uid: %08x\n", le32_to_host(posixFileInformation->uid));
cli_dbgmsg("egg_parse_file_extra_field: posix_file_information->gid: %08x\n", le32_to_host(posixFileInformation->gid));
cli_dbgmsg("egg_parse_file_extra_field: posix_file_information->last_modified_time: %016" PRIx64 "\n", le64_to_host(posixFileInformation->last_modified_time));
break;
}
case FILE_HEADER_MAGIC: {
if (handle->bSolid) {
cli_dbgmsg("egg_parse_file_extra_field: Solid archive - on to next file header.\n");
} else {
cli_warnmsg("egg_parse_file_extra_field: Missing EOFARC in non-solid/standard archive.\n");
}
break;
}
default: {
cli_dbgmsg("egg_parse_file_extra_field: unexpected header magic: %08x (%s)\n", magic, getMagicHeaderName(magic));
}
}
handle->offset += size;
status = CL_SUCCESS;
done:
return status;
}
static cl_error_t egg_parse_file_headers(egg_handle* handle, egg_file** file)
{
cl_error_t status = CL_EPARSE;
cl_error_t retval;
egg_file* eggFile = NULL;
file_header* fileHeader = NULL;
uint32_t magic = 0;
const uint8_t* index = 0;
if (!handle || !file) {
cli_errmsg("egg_parse_file_headers: Invalid args!\n");
return CL_EARG;
}
*file = NULL;
if (CL_SUCCESS != EGG_VALIDATE_HANDLE(handle)) {
cli_errmsg("egg_parse_file_headers: Invalid handle values!\n");
status = CL_EARG;
goto done;
}
/*
* 1st:
* File headers must start with the file_header.
*/
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(file_header));
if (!index) {
cli_dbgmsg("egg_parse_file_headers: File buffer too small to contain file header.\n");
goto done;
}
eggFile = (egg_file*)cli_calloc(1, sizeof(egg_file));
if (NULL == eggFile) {
cli_errmsg("egg_parse_file_headers: Failed to allocate memory for egg_file.\n");
status = CL_EMEM;
goto done;
}
fileHeader = (file_header*)index;
eggFile->file = fileHeader;
if (FILE_HEADER_MAGIC != le32_to_host(fileHeader->magic)) {
cli_dbgmsg("egg_parse_file_headers: Invalid file header magic: %08x (%s).\n", le32_to_host(fileHeader->magic), getMagicHeaderName(le32_to_host(fileHeader->magic)));
goto done;
}
cli_dbgmsg("egg_parse_file_headers: file_header->magic: %08x (%s)\n", le32_to_host(fileHeader->magic), getMagicHeaderName(le32_to_host(fileHeader->magic)));
cli_dbgmsg("egg_parse_file_headers: file_header->file_id: %08x\n", le32_to_host(fileHeader->file_id));
cli_dbgmsg("egg_parse_file_headers: file_header->file_length: %016" PRIx64 " (%" PRIu64 ")\n",
le64_to_host(fileHeader->file_length),
le64_to_host(fileHeader->file_length));
if (0 == le16_to_host(fileHeader->file_length)) {
cli_dbgmsg("egg_parse_file_headers: Empty file!\n");
}
handle->offset += sizeof(file_header);
/*
* 2nd:
* After the file_header, the following extra field headers may be present:
* a) filename_header
* b) comment_header
* c) windows_file_information
* d) posix_file_information
* e) encrypt_header
* f) EOFARC
*/
while (handle->map->len > handle->offset) {
/* Get the next magic32_t */
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(magic32_t));
if (!index) {
cli_dbgmsg("egg_parse_file_headers: File buffer too small to contain end of archive magic bytes.\n");
goto done;
}
magic = le32_to_host(*((uint32_t*)index));
if (EOFARC == magic) {
/*
* File headers should conclude with EOFARC magic bytes.
*/
handle->offset += sizeof(magic32_t);
cli_dbgmsg("egg_parse_file_headers: End of archive headers.\n");
break; /* Break out of the loop */
} else {
/*
* Parse extra fields.
*/
retval = egg_parse_file_extra_field(handle, eggFile);
if (CL_SUCCESS != retval) {
cli_dbgmsg("egg_parse_file_headers: Failed to parse archive header, magic: %08x (%s)\n", magic, getMagicHeaderName(magic));
break; /* Break out of the loop */
}
}
}
*file = eggFile;
status = CL_SUCCESS;
done:
if (CL_SUCCESS != status) {
if (eggFile) {
egg_free_egg_file(eggFile);
}
}
return status;
}
static void egg_free_egg_handle(egg_handle* handle)
{
uint32_t i = 0;
if (NULL == handle) {
return;
}
if (NULL != handle->encrypt) {
egg_free_encrypt(handle->encrypt);
handle->encrypt = NULL;
}
if (NULL != handle->files) {
for (i = 0; i < handle->nFiles; i++) {
egg_free_egg_file(handle->files[i]);
handle->files[i] = NULL;
}
free(handle->files);
handle->files = NULL;
}
if (NULL != handle->blocks) {
for (i = 0; i < handle->nBlocks; i++) {
egg_free_egg_block(handle->blocks[i]);
handle->blocks[i] = NULL;
}
free(handle->blocks);
handle->blocks = NULL;
}
if (NULL != handle->comments) {
for (i = 0; i < handle->nComments; i++) {
free(handle->comments[i]);
handle->comments[i] = NULL;
}
free(handle->comments);
handle->comments = NULL;
}
free(handle);
}
static cl_error_t egg_parse_archive_headers(egg_handle* handle)
{
cl_error_t status = CL_EPARSE;
cl_error_t retval;
egg_header* eggHeader = NULL;
uint32_t magic = 0;
const uint8_t* index = 0;
if (!handle) {
cli_errmsg("egg_parse_archive_headers: Invalid args!\n");
return CL_EARG;
}
if (CL_SUCCESS != EGG_VALIDATE_HANDLE(handle)) {
cli_errmsg("egg_parse_archive_headers: Invalid handle values!\n");
status = CL_EARG;
goto done;
}
/*
* 1st:
* Archive headers begins with the egg_header.
*/
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(egg_header));
if (!index) {
cli_dbgmsg("egg_parse_archive_headers: File buffer too small to contain egg_header.\n");
goto done;
}
eggHeader = (egg_header*)index;
if (EGG_HEADER_MAGIC != le32_to_host(eggHeader->magic)) {
cli_dbgmsg("egg_parse_archive_headers: Invalid egg header magic: %08x.\n", le32_to_host(eggHeader->magic));
goto done;
}
cli_dbgmsg("egg_parse_archive_headers: egg_header->magic: %08x (%s)\n", le32_to_host(eggHeader->magic), getMagicHeaderName(le32_to_host(eggHeader->magic)));
cli_dbgmsg("egg_parse_archive_headers: egg_header->version: %04x\n", le16_to_host(eggHeader->version));
cli_dbgmsg("egg_parse_archive_headers: egg_header->header_id: %08x\n", le32_to_host(eggHeader->header_id));
cli_dbgmsg("egg_parse_archive_headers: egg_header->reserved: %08x\n", le32_to_host(eggHeader->reserved));
if (EGG_HEADER_VERSION != le16_to_host(eggHeader->version)) {
cli_dbgmsg("egg_parse_archive_headers: Unexpected EGG archive version #: %04x.\n",
le16_to_host(eggHeader->version));
}
handle->offset += sizeof(egg_header);
/*
* 2nd:
* Egg Header may be followed by:
* a) split_compression header and/or
* b) solid_compression
* c) global encryption header
* d) EOFARC
*/
while (handle->map->len > handle->offset) {
/* Get the next magic32_t */
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(magic32_t));
if (!index) {
cli_dbgmsg("egg_parse_archive_headers: File buffer too small to contain end of archive magic bytes.\n");
goto done;
}
magic = le32_to_host(*((uint32_t*)index));
if (EOFARC == magic) {
/*
* Archive headers should conclude with EOFARC magic bytes.
*/
handle->offset += sizeof(magic32_t);
cli_dbgmsg("egg_parse_archive_headers: End of archive headers.\n");
break; /* Break out of the loop */
} else {
/*
* Parse extra fields.
*/
retval = egg_parse_archive_extra_field(handle);
if (CL_SUCCESS != retval) {
cli_dbgmsg("egg_parse_archive_headers: Failed to parse archive header, magic: %08x (%s)\n", magic, getMagicHeaderName(magic));
break; /* Break out of the loop */
}
}
}
status = CL_SUCCESS;
done:
return status;
}
cl_error_t cli_egg_open(fmap_t* map, void** hArchive, char*** comments, uint32_t* nComments)
{
cl_error_t status = CL_EPARSE;
cl_error_t retval;
egg_handle* handle = NULL;
uint32_t magic = 0;
const uint8_t* index = 0;
if (!map || !hArchive) {
cli_errmsg("cli_egg_open: Invalid args!\n");
return CL_EARG;
}
handle = (egg_handle*)cli_calloc(1, sizeof(egg_handle));
if (NULL == handle) {
cli_errmsg("cli_egg_open: Failed to allocate memory for egg_handle.\n");
status = CL_EMEM;
goto done;
}
handle->map = map;
handle->offset = 0;
/*
* 1st:
* Parse the archive headers.
*/
if (CL_SUCCESS != (retval = egg_parse_archive_headers(handle))) {
cli_warnmsg("cli_egg_open: Failed to parse archive headers!\n");
goto done;
}
/*
* 2nd:
* Archive headers may be followed by:
* a) 0+ file headers
* b) 0+ block headers
* c) 0+ archive comment headers
*/
while (CL_SUCCESS == retval) {
/* Get the next magic32_t */
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(magic32_t));
if (!index) {
cli_dbgmsg("cli_egg_open: No more data in archive.\n");
break;
}
magic = le32_to_host(*((uint32_t*)index));
if (EOFARC == magic) {
/*
* Archive headers should conclude with EOFARC magic bytes.
*/
handle->offset += sizeof(magic32_t);
if (handle->map->len > handle->offset) {
cli_warnmsg("Apparent end to EGG archive, but an addition %zu bytes of data exists in the file!\n",
handle->map->len - handle->offset);
} else {
cli_dbgmsg("cli_egg_open: Successfully indexed EGG archive!\n");
}
break; /* Break out of the loop */
} else if (FILE_HEADER_MAGIC == magic) {
/*
* Archive File Header
*/
egg_file* found_file = NULL;
if (CL_SUCCESS != (retval = egg_parse_file_headers(handle, &found_file))) {
cli_dbgmsg("cli_egg_open: Issue parsing file header. Error code: %u\n", retval);
goto done;
} else if (found_file == NULL) {
cli_errmsg("cli_egg_open: Logic error! Succesfully parsed file headers,"
" but did not return egg_file information!\n");
goto done;
} else {
/* Add file to list. */
egg_file** files_tmp;
files_tmp = (egg_file**)cli_realloc(
(void*)handle->files,
sizeof(egg_file*) * (handle->nFiles + 1));
if (NULL == files_tmp) {
egg_free_egg_file(found_file);
status = CL_EMEM;
goto done;
}
handle->files = files_tmp;
handle->files[handle->nFiles] = found_file;
handle->nFiles++;
}
} else if (BLOCK_HEADER_MAGIC == magic) {
/*
* Archive Block Header
*/
egg_block* found_block = NULL;
if (CL_SUCCESS != (retval = egg_parse_block_headers(handle, &found_block))) {
cli_dbgmsg("cli_egg_open: Issue parsing block header. Error code: %u\n", retval);
goto done;
} else if (found_block == NULL) {
cli_errmsg("cli_egg_open: Logic error! Succesfully parsed block headers,"
" but did not return egg_block information!\n");
goto done;
} else {
/* Add block to list. */
if (handle->bSolid) {
egg_block** blocks_tmp;
blocks_tmp = (egg_block**)cli_realloc(
(void*)handle->blocks,
sizeof(egg_block*) * (handle->nBlocks + 1));
if (NULL == blocks_tmp) {
egg_free_egg_block(found_block);
status = CL_EMEM;
goto done;
}
handle->blocks = blocks_tmp;
handle->blocks[handle->nBlocks] = found_block;
handle->nBlocks++;
} else {
egg_file* eggFile = NULL;
/*
* Associate block with most recently added file.
*/
if (handle->nFiles == 0) {
cli_dbgmsg("cli_egg_open: No file found for block in non-solid archive.\n");
// TODO: create an unamed block.
} else {
egg_block** blocks_tmp;
eggFile = handle->files[handle->nFiles - 1];
/* Add block to list. */
blocks_tmp = (egg_block**)cli_realloc(
(void*)eggFile->blocks,
sizeof(egg_block*) * (eggFile->nBlocks + 1));
if (NULL == blocks_tmp) {
egg_free_egg_block(found_block);
status = CL_EMEM;
goto done;
}
eggFile->blocks = blocks_tmp;
eggFile->blocks[eggFile->nBlocks] = found_block;
eggFile->nBlocks++;
}
}
}
} else if (COMMENT_HEADER_MAGIC == magic) {
/*
* Parse extra field for archive comment header.
*/
char** comments_tmp;
extra_field* extraField = NULL;
char* comment = NULL;
uint32_t size = 0;
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(extra_field));
if (!index) {
cli_dbgmsg("cli_egg_open: File buffer too small to contain extra_field header.\n");
goto done;
}
extraField = (extra_field*)index;
cli_dbgmsg("cli_egg_open: archive comment extra_field->magic: %08x (%s)\n", le32_to_host(extraField->magic), getMagicHeaderName(le32_to_host(extraField->magic)));
cli_dbgmsg("cli_egg_open: archive comment extra_field->bit_flag: %02x\n", extraField->bit_flag);
handle->offset += sizeof(extra_field);
if (extraField->bit_flag & EXTRA_FIELD_FLAGS_SIZE_IS_4BYTES) {
/* size is uint32_t */
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(uint32_t));
if (!index) {
cli_dbgmsg("cli_egg_open: File buffer too small to contain archive comment extra_field header.\n");
goto done;
}
size = le32_to_host(*(uint32_t*)index);
handle->offset += sizeof(uint32_t);
} else {
/* size is uint16_t */
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, sizeof(uint16_t));
if (!index) {
cli_dbgmsg("cli_egg_open: File buffer too small to contain archive comment extra_field header.\n");
goto done;
}
size = le16_to_host(*(uint16_t*)index);
handle->offset += sizeof(uint16_t);
}
cli_dbgmsg("cli_egg_open: archive comment extra_field->size: %u\n", size);
index = (const uint8_t*)fmap_need_off_once(handle->map, handle->offset, size);
if (!index) {
cli_dbgmsg("cli_egg_open: File buffer too small to contain extra_field header.\n");
goto done;
}
retval = egg_parse_comment_header(index, size, extraField, &comment);
if (CL_SUCCESS != retval) {
cli_dbgmsg("cli_egg_open: Failed to parse archive comment extra_field data.\n");
goto done;
}
comments_tmp = (char**)cli_realloc(
(void*)handle->comments,
sizeof(char*) * (handle->nComments + 1));
if (NULL == comments_tmp) {
free(comment);
status = CL_EMEM;
goto done;
}
handle->comments = comments_tmp;
handle->comments[handle->nComments] = comment;
handle->nComments++;
handle->offset += size;
} else {
cli_dbgmsg("cli_egg_open: unexpected header magic: %08x (%s)\n", magic, getMagicHeaderName(magic));
status = CL_EPARSE;
goto done;
}
}
if (CL_SUCCESS != retval) {
if (CL_BREAK == retval) {
/* End of archive. */
if ((handle->bSplit) && (handle->splitInfo->next_file_id != 0))
cli_warnmsg("cli_egg_open: Abrupt end to EGG volume!\n");
else
cli_dbgmsg("cli_egg_open: End of EGG volume in split archive.\n");
} else {
/* Something went wrong. */
cli_warnmsg("cli_egg_open: Failed to parse file headers!\n");
}
}
*hArchive = handle;
*comments = handle->comments;
*nComments = handle->nComments;
status = CL_SUCCESS;
done:
if (CL_SUCCESS != status) {
if (handle) {
egg_free_egg_handle(handle);
}
*hArchive = NULL;
}
return status;
}
cl_error_t cli_egg_peek_file_header(void* hArchive, cl_egg_metadata* file_metadata)
{
cl_error_t status = CL_EPARSE;
egg_handle* handle = NULL;
egg_file* currFile = NULL;
if (!hArchive || !file_metadata) {
cli_errmsg("cli_egg_peek_file_header: Invalid args!\n");
return CL_EARG;
}
handle = (egg_handle*)hArchive;
if (CL_SUCCESS != EGG_VALIDATE_HANDLE(handle)) {
cli_errmsg("cli_egg_peek_file_header: Invalid handle values!\n");
status = CL_EARG;
goto done;
}
memset(file_metadata, 0, sizeof(cl_egg_metadata));
if (handle->fileExtractionIndex >= handle->nFiles) {
status = CL_BREAK;
goto done;
}
currFile = handle->files[handle->fileExtractionIndex];
if (NULL == currFile) {
cli_errmsg("cli_egg_peek_file_header: invalid egg_file pointer!\n");
goto done;
}
if (NULL == currFile->file) {
cli_errmsg("cli_egg_peek_file_header: egg_file is missing file header!\n");
goto done;
}
if (NULL == currFile->filename.name_utf8) {
cli_errmsg("cli_egg_extract_file: egg_file is missing filename!\n");
goto done;
}
if (handle->bSolid) {
/*
* TODO: Add support for extracting files from solid archives.
*
* See the comments in cli_egg_extract_file() for more details.
*/
file_metadata->pack_size = 0;
file_metadata->unpack_size = currFile->file->file_length;
} else {
uint64_t i = 0;
if (!currFile->blocks) {
cli_dbgmsg("cli_egg_peek_file_header: Empty file!\n");
}
for (i = 0; i < currFile->nBlocks; i++) {
egg_block* currBlock = currFile->blocks[i];
if (!currBlock->blockHeader) {
cli_errmsg("cli_egg_peek_file_header: egg_block missing block_header!\n");
goto done;
}
file_metadata->pack_size += currBlock->blockHeader->compress_size;
file_metadata->unpack_size += currBlock->blockHeader->uncompress_size;
}
if (file_metadata->unpack_size != currFile->file->file_length) {
cli_warnmsg("cli_egg_peek_file_header: sum of block uncompress_size's does not match listed file_length!\n");
}
}
file_metadata->filename = strdup(currFile->filename.name_utf8);
if (NULL != currFile->encrypt)
file_metadata->encrypted = 1;
if (currFile->posixFileInformation && currFile->posixFileInformation->mode & POSIX_INFO_MODE_DIRECTORY)
file_metadata->is_dir = 1;
else if (currFile->windowsFileInformation && currFile->windowsFileInformation->attribute & WINDOWS_INFO_ATTRIBUTE_DIRECTORY)
file_metadata->is_dir = 1;
status = CL_SUCCESS;
done:
return status;
}
cl_error_t cli_egg_deflate_decompress(char* compressed, size_t compressed_size, char** decompressed, size_t* decompressed_size)
{
cl_error_t status = CL_EPARSE;
uint8_t* decoded_tmp;
uint8_t* decoded = NULL;
uint32_t declen = 0, capacity = 0;
z_stream stream;
int stream_initialized = 0;
int zstat;
if (NULL == compressed || compressed_size == 0 || NULL == decompressed || NULL == decompressed_size) {
cli_errmsg("cli_egg_deflate_decompress: Invalid args!\n");
status = CL_EARG;
goto done;
}
*decompressed = NULL;
*decompressed_size = 0;
if (!(decoded = (uint8_t*)cli_calloc(BUFSIZ, sizeof(uint8_t)))) {
cli_errmsg("cli_egg_deflate_decompress: cannot allocate memory for decompressed output\n");
status = CL_EMEM;
goto done;
}
capacity = BUFSIZ;
memset(&stream, 0, sizeof(stream));
stream.next_in = (Bytef*)compressed;
stream.avail_in = compressed_size;
stream.next_out = (Bytef*)decoded;
stream.avail_out = BUFSIZ;
zstat = inflateInit2(&stream, -15);
if (zstat != Z_OK) {
cli_warnmsg("cli_egg_deflate_decompress: inflateInit failed\n");
status = CL_EMEM;
goto done;
}
stream_initialized = 1;
/* initial inflate */
zstat = inflate(&stream, Z_NO_FLUSH);
/* check if nothing written whatsoever */
if ((zstat != Z_OK) && (stream.avail_out == BUFSIZ)) {
/* Inflation failed */
cli_errmsg("cli_egg_deflate_decompress: failed to decompress data\n");
status = CL_EPARSE;
goto done;
}
while (zstat == Z_OK && stream.avail_in) {
/* extend output capacity if needed,*/
if (stream.avail_out == 0) {
if (!(decoded_tmp = cli_realloc(decoded, capacity + BUFSIZ))) {
cli_errmsg("cli_egg_deflate_decompress: cannot reallocate memory for decompressed output\n");
status = CL_EMEM;
goto done;
}
decoded = decoded_tmp;
stream.next_out = decoded + capacity;
stream.avail_out = BUFSIZ;
declen += BUFSIZ;
capacity += BUFSIZ;
}
/* continue inflation */
zstat = inflate(&stream, Z_NO_FLUSH);
}
/* add end fragment to decoded length */
declen += (BUFSIZ - stream.avail_out);
/* error handling */
switch (zstat) {
case Z_OK:
cli_dbgmsg("cli_egg_deflate_decompress: Z_OK on stream decompression\n");
/* intentional fall-through */
case Z_STREAM_END:
cli_dbgmsg("cli_egg_deflate_decompress: decompressed %lu bytes from %lu total bytes (%lu bytes remaining)\n",
(unsigned long)declen, (unsigned long)(compressed_size), (unsigned long)(stream.avail_in));
break;
/* potentially fatal - *mostly* ignored as per older version */
case Z_STREAM_ERROR:
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR:
default:
if (stream.msg)
cli_dbgmsg("cli_egg_deflate_decompress: after decompressing %lu bytes, got error \"%s\"\n",
(unsigned long)declen, stream.msg);
else
cli_dbgmsg("cli_egg_deflate_decompress: after decompressing %lu bytes, got error %d\n",
(unsigned long)declen, zstat);
if (declen == 0) {
cli_dbgmsg("cli_egg_deflate_decompress: no bytes were decompressed.\n");
status = CL_EPARSE;
}
break;
}
*decompressed = (char*)decoded;
*decompressed_size = declen;
status = CL_SUCCESS;
done:
if (stream_initialized) {
(void)inflateEnd(&stream);
}
if (CL_SUCCESS != status) {
free(decoded);
}
return status;
}
#ifdef HAVE_BZLIB_H
cl_error_t cli_egg_bzip2_decompress(char* compressed, size_t compressed_size, char** decompressed, size_t* decompressed_size)
{
cl_error_t status = CL_EPARSE;
char* decoded_tmp;
char* decoded = NULL;
uint32_t declen = 0, capacity = 0;
bz_stream stream;
int bzstat;
if (NULL == compressed || compressed_size == 0 || NULL == decompressed || NULL == decompressed_size) {
cli_errmsg("cli_egg_bzip2_decompress: Invalid args!\n");
status = CL_EARG;
goto done;
}
*decompressed = NULL;
*decompressed_size = 0;
if (!(decoded = (char*)cli_calloc(BUFSIZ, sizeof(Bytef)))) {
cli_errmsg("cli_egg_bzip2_decompress: cannot allocate memory for decompressed output\n");
status = CL_EMEM;
goto done;
}
capacity = BUFSIZ;
memset(&stream, 0, sizeof(stream));
stream.next_in = compressed;
stream.avail_in = compressed_size;
stream.next_out = decoded;
stream.avail_out = BUFSIZ;
if (BZ_OK != (bzstat = BZ2_bzDecompressInit(&stream, 0, 0))) {
cli_warnmsg("cli_egg_bzip2_decompress: bzinit failed\n");
status = CL_EMEM;
goto done;
}
/* initial inflate */
bzstat = BZ2_bzDecompress(&stream);
/* check if nothing written whatsoever */
if ((bzstat != BZ_OK) && (stream.avail_out == BUFSIZ)) {
/* Inflation failed */
cli_errmsg("cli_egg_bzip2_decompress: failed to decompress data\n");
status = CL_EPARSE;
goto done;
}
while (bzstat == BZ_OK && stream.avail_in) {
/* extend output capacity if needed,*/
if (stream.avail_out == 0) {
if (!(decoded_tmp = cli_realloc(decoded, capacity + BUFSIZ))) {
cli_errmsg("cli_egg_bzip2_decompress: cannot reallocate memory for decompressed output\n");
status = CL_EMEM;
goto done;
}
decoded = decoded_tmp;
stream.next_out = decoded + capacity;
stream.avail_out = BUFSIZ;
declen += BUFSIZ;
capacity += BUFSIZ;
}
/* continue inflation */
bzstat = BZ2_bzDecompress(&stream);
}
/* add end fragment to decoded length */
declen += (BUFSIZ - stream.avail_out);
/* error handling */
switch (bzstat) {
case BZ_OK:
cli_dbgmsg("cli_egg_bzip2_decompress: BZ_OK on stream decompression\n");
/* intentional fall-through */
case BZ_STREAM_END:
cli_dbgmsg("cli_egg_bzip2_decompress: decompressed %lu bytes from %lu total bytes (%lu bytes remaining)\n",
(unsigned long)declen, (unsigned long)(compressed_size), (unsigned long)(stream.avail_in));
break;
/* potentially fatal */
case BZ_DATA_ERROR:
case BZ_MEM_ERROR:
default:
cli_dbgmsg("cli_egg_bzip2_decompress: after decompressing %lu bytes, got error %d\n",
(unsigned long)declen, bzstat);
if (declen == 0) {
cli_dbgmsg("cli_egg_bzip2_decompress: no bytes were decompressed.\n");
status = CL_EPARSE;
}
break;
}
*decompressed = (char*)decoded;
*decompressed_size = declen;
status = CL_SUCCESS;
done:
(void)BZ2_bzDecompressEnd(&stream);
if (CL_SUCCESS != status) {
free(decoded);
}
return status;
}
#endif
cl_error_t cli_egg_lzma_decompress(char* compressed, size_t compressed_size, char** decompressed, size_t* decompressed_size)
{
cl_error_t status = CL_EPARSE;
uint8_t* decoded_tmp;
uint8_t* decoded = NULL;
uint32_t declen = 0, capacity = 0;
struct CLI_LZMA stream;
int stream_initialized = 0;
int lzmastat;
if (NULL == compressed || compressed_size == 0 || NULL == decompressed || NULL == decompressed_size) {
cli_errmsg("cli_egg_lzma_decompress: Invalid args!\n");
status = CL_EARG;
goto done;
}
*decompressed = NULL;
*decompressed_size = 0;
if (!(decoded = (uint8_t*)cli_calloc(BUFSIZ, sizeof(char)))) {
cli_errmsg("cli_egg_lzma_decompress: cannot allocate memory for decompressed output\n");
status = CL_EMEM;
goto done;
}
capacity = BUFSIZ;
memset(&stream, 0, sizeof(stream));
stream.next_in = (Bytef*)compressed;
stream.avail_in = compressed_size;
stream.next_out = (Bytef*)decoded;
stream.avail_out = BUFSIZ;
lzmastat = cli_LzmaInit(&stream, 0);
if (lzmastat != LZMA_RESULT_OK) {
cli_warnmsg("cli_egg_lzma_decompress: inflateInit failed\n");
status = CL_EMEM;
goto done;
}
stream_initialized = 1;
/* initial inflate */
lzmastat = cli_LzmaDecode(&stream);
/* check if nothing written whatsoever */
if ((lzmastat != LZMA_RESULT_OK) && (stream.avail_out == BUFSIZ)) {
/* Inflation failed */
cli_errmsg("cli_egg_lzma_decompress: failed to decompress data\n");
status = CL_EPARSE;
goto done;
}
while (lzmastat == LZMA_RESULT_OK && stream.avail_in) {
/* extend output capacity if needed,*/
if (stream.avail_out == 0) {
if (!(decoded_tmp = cli_realloc(decoded, capacity + BUFSIZ))) {
cli_errmsg("cli_egg_lzma_decompress: cannot reallocate memory for decompressed output\n");
status = CL_EMEM;
goto done;
}
decoded = decoded_tmp;
stream.next_out = decoded + capacity;
stream.avail_out = BUFSIZ;
declen += BUFSIZ;
capacity += BUFSIZ;
}
/* continue inflation */
lzmastat = cli_LzmaDecode(&stream);
}
/* add end fragment to decoded length */
declen += (BUFSIZ - stream.avail_out);
/* error handling */
switch (lzmastat) {
case LZMA_RESULT_OK:
cli_dbgmsg("cli_egg_lzma_decompress: Z_OK on stream decompression\n");
/* intentional fall-through */
case LZMA_STREAM_END:
cli_dbgmsg("cli_egg_lzma_decompress: decompressed %lu bytes from %lu total bytes (%lu bytes remaining)\n",
(unsigned long)declen, (unsigned long)(compressed_size), (unsigned long)(stream.avail_in));
break;
/* potentially fatal */
case LZMA_RESULT_DATA_ERROR:
default:
cli_dbgmsg("cli_egg_lzma_decompress: after decompressing %lu bytes, got error %d\n",
(unsigned long)declen, lzmastat);
if (declen == 0) {
cli_dbgmsg("cli_egg_lzma_decompress: no bytes were decompressed.\n");
status = CL_EPARSE;
}
break;
}
*decompressed = (char*)decoded;
*decompressed_size = declen;
status = CL_SUCCESS;
done:
if (stream_initialized) {
(void)cli_LzmaShutdown(&stream);
}
if (CL_SUCCESS != status) {
free(decoded);
}
return status;
}
cl_error_t cli_egg_extract_file(void* hArchive, const char** filename, const char** output_buffer, size_t* output_buffer_length)
{
cl_error_t status = CL_EPARSE;
egg_handle* handle = NULL;
egg_file* currFile = NULL;
char* decompressed = NULL;
uint64_t decompressed_size = 0;
uint64_t i = 0;
if (!hArchive || !filename || !output_buffer || !output_buffer_length) {
cli_errmsg("cli_egg_extract_file: Invalid args!\n");
status = CL_EARG;
goto done;
}
*output_buffer = NULL;
*output_buffer_length = 0;
handle = (egg_handle*)hArchive;
if (CL_SUCCESS != EGG_VALIDATE_HANDLE(handle)) {
cli_errmsg("cli_egg_extract_file: Invalid handle values!\n");
status = CL_EARG;
goto done;
}
if (handle->fileExtractionIndex >= handle->nFiles) {
cli_errmsg("cli_egg_extract_file: File index exceeds number of files in archive!\n");
goto done;
}
currFile = handle->files[handle->fileExtractionIndex];
if (NULL == currFile) {
cli_errmsg("cli_egg_extract_file: invalid egg_file pointer!\n");
goto done;
}
if (NULL == currFile->file) {
cli_errmsg("cli_egg_extract_file: egg_file is missing file header!\n");
goto done;
}
if (NULL == currFile->filename.name_utf8) {
cli_errmsg("cli_egg_extract_file: egg_file is missing filename!\n");
goto done;
}
if (handle->bSolid) {
/*
* TODO: Add support for extracting files from solid archives.
*
* For solid archives, the blocks are shared between all of the files.
* To unpack them, we'd have to identify which block(s) each file would
* be associated with.
*
* Then in theory a single file could be extracted without decompressing
* all of the blocks at the same time.
*
* To be efficient about it, a block could have some sort of ref count
* or list of associated files. Then during extraction, the decompressed
* data for each block that is shared between files is not freed until
* all of the files associated with that block have been extracted.
*/
} else {
if (currFile->nBlocks == 0 || currFile->blocks == NULL) {
cli_dbgmsg("cli_egg_extract_file: Empty file!\n");
}
for (i = 0; i < currFile->nBlocks; i++) {
char* decompressed_tmp;
egg_block* currBlock = currFile->blocks[i];
cl_error_t retval = CL_EPARSE;
if (NULL == currBlock->blockHeader) {
cli_errmsg("cli_egg_extract_file: current egg_block missing header!\n");
break;
}
switch (currBlock->blockHeader->compress_algorithm) {
case BLOCK_HEADER_COMPRESS_ALGORITHM_STORE: {
/*
* No compression. Woohoo!
*/
if (currBlock->blockHeader->compress_size == 0) {
cli_warnmsg("cli_egg_extract_file: blockHeader compress_size is 0!\n");
break;
} else if (currBlock->blockHeader->compress_size != currBlock->blockHeader->uncompress_size) {
cli_warnmsg("cli_egg_extract_file: blockHeader compress_size != uncompress_size!\n");
break;
}
decompressed_tmp = cli_realloc(decompressed, (size_t)decompressed_size + currBlock->blockHeader->compress_size);
if (NULL == decompressed_tmp) {
cli_errmsg("cli_egg_extract_file: Failed to allocate %" PRIu64 " bytes for decompressed file!\n",
decompressed_size);
status = CL_EMEM;
goto done;
}
decompressed = decompressed_tmp;
memcpy(decompressed + decompressed_size, currBlock->compressedData, currBlock->blockHeader->compress_size);
decompressed_size += currBlock->blockHeader->compress_size;
retval = CL_SUCCESS;
break;
}
case BLOCK_HEADER_COMPRESS_ALGORITHM_DEFLATE: {
char* decompressed_block = NULL;
size_t decompressed_block_size = 0;
if (CL_SUCCESS != cli_egg_deflate_decompress(currBlock->compressedData,
currBlock->blockHeader->compress_size,
&decompressed_block,
&decompressed_block_size)) {
/* Failed to decompress block */
cli_warnmsg("Failed to decompress RFC 1951 deflate compressed block\n");
goto done;
}
/* Decompressed block. Add it to the file data */
decompressed_tmp = cli_realloc(decompressed, (size_t)decompressed_size + decompressed_block_size);
if (NULL == decompressed_tmp) {
cli_errmsg("cli_egg_extract_file: Failed to allocate %" PRIu64 " bytes for decompressed file!\n",
decompressed_size);
free(decompressed_block);
status = CL_EMEM;
goto done;
}
decompressed = decompressed_tmp;
memcpy(decompressed + decompressed_size, decompressed_block, decompressed_block_size);
decompressed_size += decompressed_block_size;
free(decompressed_block);
retval = CL_SUCCESS;
break;
}
case BLOCK_HEADER_COMPRESS_ALGORITHM_BZIP2: {
#if HAVE_BZLIB_H
char* decompressed_block = NULL;
size_t decompressed_block_size = 0;
if (CL_SUCCESS != cli_egg_bzip2_decompress(currBlock->compressedData,
currBlock->blockHeader->compress_size,
&decompressed_block,
&decompressed_block_size)) {
/* Failed to decompress block */
cli_warnmsg("Failed to decompress BZIP2 compressed block\n");
goto done;
}
/* Decompressed block. Add it to the file data */
decompressed_tmp = cli_realloc(decompressed, (size_t)decompressed_size + decompressed_block_size);
if (NULL == decompressed_tmp) {
cli_errmsg("cli_egg_extract_file: Failed to allocate %" PRIu64 " bytes for decompressed file!\n",
decompressed_size);
free(decompressed_block);
status = CL_EMEM;
goto done;
}
decompressed = decompressed_tmp;
memcpy(decompressed + decompressed_size, decompressed_block, decompressed_block_size);
decompressed_size += decompressed_block_size;
free(decompressed_block);
retval = CL_SUCCESS;
break;
#else
cli_warnmsg("cli_egg_extract_file: BZIP2 decompression support not available.\n");
goto done;
#endif
}
case BLOCK_HEADER_COMPRESS_ALGORITHM_AZO: {
cli_warnmsg("cli_egg_extract_file: AZO decompression not yet supported.\n");
goto done;
2023-01-14 18:28:39 +08:00
// break;
2022-10-22 18:41:00 +08:00
}
case BLOCK_HEADER_COMPRESS_ALGORITHM_LZMA: {
cli_warnmsg("cli_egg_extract_file: LZMA decompression not yet supported.\n");
goto done;
// char* decompressed_block = NULL;
// size_t decompressed_block_size = 0;
// if (CL_SUCCESS != cli_egg_lzma_decompress(currBlock->compressedData,
// currBlock->blockHeader->compress_size,
// &decompressed_block,
// &decompressed_block_size)) {
// /* Failed to decompress block */
// cli_warnmsg("Failed to decompress LZMA compressed block\n");
// goto done;
// }
// /* Decompressed block. Add it to the file data */
// decompressed_tmp = cli_realloc(decompressed, (size_t)decompressed_size + decompressed_block_size);
// if (NULL == decompressed_tmp) {
// cli_errmsg("cli_egg_extract_file: Failed to allocate %" PRIu64 " bytes for decompressed file!\n",
// decompressed_size);
// free(decompressed_block);
// status = CL_EMEM;
// goto done;
// }
// decompressed = decompressed_tmp;
// memcpy(decompressed + decompressed_size, decompressed_block, decompressed_block_size);
// decompressed_size += decompressed_block_size;
// free(decompressed_block);
// retval = CL_SUCCESS;
// break;
}
default: {
cli_errmsg("cli_egg_extract_file: unknown compression algorithm: %d!\n",
currBlock->blockHeader->compress_algorithm);
goto done;
}
}
if (CL_SUCCESS != retval) {
cli_warnmsg("cli_egg_extract_file: Unable to decompress file: %s\n",
currFile->filename.name_utf8);
}
if ((i == currFile->nBlocks - 1) && // last block ?
(decompressed_size != currFile->file->file_length)) { // right amount of data ?
cli_warnmsg("cli_egg_extract_file: alleged filesize (%" PRIu64 ") != actual filesize (%" PRIu64 ")!\n",
currFile->file->file_length,
decompressed_size);
}
}
}
cli_dbgmsg("cli_egg_extract_file: File extracted: %s\n", currFile->filename.name_utf8);
*filename = strdup(currFile->filename.name_utf8);
*output_buffer = decompressed;
*output_buffer_length = decompressed_size;
status = CL_SUCCESS;
done:
if (NULL != handle) {
handle->fileExtractionIndex += 1;
}
if (CL_SUCCESS != status) {
/* Free buffer */
if (NULL != decompressed) {
free(decompressed);
}
}
return status;
}
cl_error_t cli_egg_skip_file(void* hArchive)
{
cl_error_t status = CL_EPARSE;
egg_handle* handle = NULL;
if (!hArchive) {
cli_errmsg("cli_egg_skip_file: Invalid args!\n");
return CL_EARG;
}
handle = (egg_handle*)hArchive;
if (CL_SUCCESS != EGG_VALIDATE_HANDLE(handle)) {
cli_errmsg("cli_egg_skip_file: Invalid handle values!\n");
status = CL_EARG;
goto done;
}
if (handle->fileExtractionIndex >= handle->nFiles) {
cli_warnmsg("cli_egg_skip_file: File index exceeds number of files in archive!\n");
status = CL_BREAK;
goto done;
}
handle->fileExtractionIndex += 1;
if (handle->fileExtractionIndex >= handle->nFiles) {
status = CL_BREAK;
}
cli_dbgmsg("cli_egg_skip_file: File skipped.\n");
status = CL_SUCCESS;
done:
return status;
}
void cli_egg_close(void* hArchive)
{
egg_handle* handle = NULL;
if (!hArchive) {
cli_errmsg("cli_egg_close: Invalid args.\n");
return;
}
handle = (egg_handle*)hArchive;
egg_free_egg_handle(handle);
return;
}