diff options
Diffstat (limited to 'src/libs/3rdparty/libarchive/archive_read_support_format_7zip.c')
-rw-r--r-- | src/libs/3rdparty/libarchive/archive_read_support_format_7zip.c | 4074 |
1 files changed, 4074 insertions, 0 deletions
diff --git a/src/libs/3rdparty/libarchive/archive_read_support_format_7zip.c b/src/libs/3rdparty/libarchive/archive_read_support_format_7zip.c new file mode 100644 index 000000000..b171bea01 --- /dev/null +++ b/src/libs/3rdparty/libarchive/archive_read_support_format_7zip.c @@ -0,0 +1,4074 @@ +/*- + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_BZLIB_H +#include <bzlib.h> +#endif +#ifdef HAVE_LZMA_H +#include <lzma.h> +#endif +#ifdef HAVE_ZLIB_H +#include <zlib.h> +#endif +#ifdef HAVE_ZSTD_H +#include <zstd.h> +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_ppmd7_private.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_endian.h" + +#ifndef HAVE_ZLIB_H +#include "archive_crc32.h" +#endif + +#define _7ZIP_SIGNATURE "7z\xBC\xAF\x27\x1C" +#define SFX_MIN_ADDR 0x27000 +#define SFX_MAX_ADDR 0x60000 + + +/* + * Codec ID + */ +#define _7Z_COPY 0 +#define _7Z_LZMA 0x030101 +#define _7Z_LZMA2 0x21 +#define _7Z_DEFLATE 0x040108 +#define _7Z_BZ2 0x040202 +#define _7Z_PPMD 0x030401 +#define _7Z_DELTA 0x03 +#define _7Z_CRYPTO_MAIN_ZIP 0x06F10101 /* Main Zip crypto algo */ +#define _7Z_CRYPTO_RAR_29 0x06F10303 /* Rar29 AES-128 + (modified SHA-1) */ +#define _7Z_CRYPTO_AES_256_SHA_256 0x06F10701 /* AES-256 + SHA-256 */ + + +#define _7Z_X86 0x03030103 +#define _7Z_X86_BCJ2 0x0303011B +#define _7Z_POWERPC 0x03030205 +#define _7Z_IA64 0x03030401 +#define _7Z_ARM 0x03030501 +#define _7Z_ARMTHUMB 0x03030701 +#define _7Z_ARM64 0xa +#define _7Z_SPARC 0x03030805 + +#define _7Z_ZSTD 0x4F71101 /* Copied from https://github.com/mcmilk/7-Zip-zstd.git */ + +/* + * 7-Zip header property IDs. + */ +#define kEnd 0x00 +#define kHeader 0x01 +#define kArchiveProperties 0x02 +#define kAdditionalStreamsInfo 0x03 +#define kMainStreamsInfo 0x04 +#define kFilesInfo 0x05 +#define kPackInfo 0x06 +#define kUnPackInfo 0x07 +#define kSubStreamsInfo 0x08 +#define kSize 0x09 +#define kCRC 0x0A +#define kFolder 0x0B +#define kCodersUnPackSize 0x0C +#define kNumUnPackStream 0x0D +#define kEmptyStream 0x0E +#define kEmptyFile 0x0F +#define kAnti 0x10 +#define kName 0x11 +#define kCTime 0x12 +#define kATime 0x13 +#define kMTime 0x14 +#define kAttributes 0x15 +#define kEncodedHeader 0x17 +#define kDummy 0x19 + +struct _7z_digests { + unsigned char *defineds; + uint32_t *digests; +}; + + +struct _7z_folder { + uint64_t numCoders; + struct _7z_coder { + unsigned long codec; + uint64_t numInStreams; + uint64_t numOutStreams; + uint64_t propertiesSize; + unsigned char *properties; + } *coders; + uint64_t numBindPairs; + struct { + uint64_t inIndex; + uint64_t outIndex; + } *bindPairs; + uint64_t numPackedStreams; + uint64_t *packedStreams; + uint64_t numInStreams; + uint64_t numOutStreams; + uint64_t *unPackSize; + unsigned char digest_defined; + uint32_t digest; + uint64_t numUnpackStreams; + uint32_t packIndex; + /* Unoperated bytes. */ + uint64_t skipped_bytes; +}; + +struct _7z_coders_info { + uint64_t numFolders; + struct _7z_folder *folders; + uint64_t dataStreamIndex; +}; + +struct _7z_pack_info { + uint64_t pos; + uint64_t numPackStreams; + uint64_t *sizes; + struct _7z_digests digest; + /* Calculated from pos and numPackStreams. */ + uint64_t *positions; +}; + +struct _7z_substream_info { + size_t unpack_streams; + uint64_t *unpackSizes; + unsigned char *digestsDefined; + uint32_t *digests; +}; + +struct _7z_stream_info { + struct _7z_pack_info pi; + struct _7z_coders_info ci; + struct _7z_substream_info ss; +}; + +struct _7z_header_info { + uint64_t dataIndex; + + unsigned char *emptyStreamBools; + unsigned char *emptyFileBools; + unsigned char *antiBools; + unsigned char *attrBools; +}; + +struct _7zip_entry { + size_t name_len; + unsigned char *utf16name; +#if defined(_WIN32) && !defined(__CYGWIN__) && defined(_DEBUG) + const wchar_t *wname; +#endif + uint32_t folderIndex; + uint32_t ssIndex; + unsigned flg; +#define MTIME_IS_SET (1<<0) +#define ATIME_IS_SET (1<<1) +#define CTIME_IS_SET (1<<2) +#define CRC32_IS_SET (1<<3) +#define HAS_STREAM (1<<4) + + time_t mtime; + time_t atime; + time_t ctime; + long mtime_ns; + long atime_ns; + long ctime_ns; + uint32_t mode; + uint32_t attr; +}; + +struct _7zip { + /* Structural information about the archive. */ + struct _7z_stream_info si; + + int header_is_being_read; + int header_is_encoded; + uint64_t header_bytes_remaining; + unsigned long header_crc32; + /* Header offset to check that reading points of the file contents + * will not exceed the header. */ + uint64_t header_offset; + /* Base offset of the archive file for a seek in case reading SFX. */ + uint64_t seek_base; + + /* List of entries */ + size_t entries_remaining; + uint64_t numFiles; + struct _7zip_entry *entries; + struct _7zip_entry *entry; + unsigned char *entry_names; + + /* entry_bytes_remaining is the number of bytes we expect. */ + int64_t entry_offset; + uint64_t entry_bytes_remaining; + + /* Running CRC32 of the decompressed data */ + unsigned long entry_crc32; + + /* Flags to mark progress of decompression. */ + char end_of_entry; + + /* Uncompressed buffer control. */ +#define UBUFF_SIZE (64 * 1024) + unsigned char *uncompressed_buffer; + unsigned char *uncompressed_buffer_pointer; + size_t uncompressed_buffer_size; + size_t uncompressed_buffer_bytes_remaining; + + /* Offset of the compressed data. */ + int64_t stream_offset; + + /* + * Decompressing control data. + */ + unsigned folder_index; + uint64_t folder_outbytes_remaining; + unsigned pack_stream_index; + unsigned pack_stream_remaining; + uint64_t pack_stream_inbytes_remaining; + size_t pack_stream_bytes_unconsumed; + + /* The codec information of a folder. */ + unsigned long codec; + unsigned long codec2; + + /* + * Decompressor controllers. + */ + /* Decoding LZMA1 and LZMA2 data. */ +#ifdef HAVE_LZMA_H + lzma_stream lzstream; + int lzstream_valid; +#endif + /* Decoding bzip2 data. */ +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + bz_stream bzstream; + int bzstream_valid; +#endif + /* Decoding deflate data. */ +#ifdef HAVE_ZLIB_H + z_stream stream; + int stream_valid; +#endif + /* Decoding Zstandard data. */ +#if HAVE_ZSTD_H + ZSTD_DStream *zstd_dstream; + int zstdstream_valid; +#endif + /* Decoding PPMd data. */ + int ppmd7_stat; + CPpmd7 ppmd7_context; + CPpmd7z_RangeDec range_dec; + IByteIn bytein; + struct { + const unsigned char *next_in; + int64_t avail_in; + int64_t total_in; + int64_t stream_in; + unsigned char *next_out; + int64_t avail_out; + int64_t total_out; + int overconsumed; + } ppstream; + int ppmd7_valid; + + /* Decoding BCJ and BCJ2 data. */ + uint32_t bcj_state; + size_t odd_bcj_size; + unsigned char odd_bcj[4]; + /* Decoding BCJ data. */ + size_t bcj_prevPosT; + uint32_t bcj_prevMask; + uint32_t bcj_ip; + + /* Decoding BCJ2 data. */ + size_t main_stream_bytes_remaining; + unsigned char *sub_stream_buff[3]; + size_t sub_stream_size[3]; + size_t sub_stream_bytes_remaining[3]; + unsigned char *tmp_stream_buff; + size_t tmp_stream_buff_size; + size_t tmp_stream_bytes_avail; + size_t tmp_stream_bytes_remaining; +#ifdef _LZMA_PROB32 +#define CProb uint32_t +#else +#define CProb uint16_t +#endif + CProb bcj2_p[256 + 2]; + uint8_t bcj2_prevByte; + uint32_t bcj2_range; + uint32_t bcj2_code; + uint64_t bcj2_outPos; + + /* Filename character-set conversion data. */ + struct archive_string_conv *sconv; + + char format_name[64]; + + /* Custom value that is non-zero if this archive contains encrypted entries. */ + int has_encrypted_entries; +}; + +/* Maximum entry size. This limitation prevents reading intentional + * corrupted 7-zip files on assuming there are not so many entries in + * the files. */ +#define UMAX_ENTRY ARCHIVE_LITERAL_ULL(100000000) + +static int archive_read_format_7zip_has_encrypted_entries(struct archive_read *); +static int archive_read_support_format_7zip_capabilities(struct archive_read *a); +static int archive_read_format_7zip_bid(struct archive_read *, int); +static int archive_read_format_7zip_cleanup(struct archive_read *); +static int archive_read_format_7zip_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_7zip_read_data_skip(struct archive_read *); +static int archive_read_format_7zip_read_header(struct archive_read *, + struct archive_entry *); +static int check_7zip_header_in_sfx(const char *); +static unsigned long decode_codec_id(const unsigned char *, size_t); +static int decode_encoded_header_info(struct archive_read *, + struct _7z_stream_info *); +static int decompress(struct archive_read *, struct _7zip *, + void *, size_t *, const void *, size_t *); +static ssize_t extract_pack_stream(struct archive_read *, size_t); +static void fileTimeToUtc(uint64_t, time_t *, long *); +static uint64_t folder_uncompressed_size(struct _7z_folder *); +static void free_CodersInfo(struct _7z_coders_info *); +static void free_Digest(struct _7z_digests *); +static void free_Folder(struct _7z_folder *); +static void free_Header(struct _7z_header_info *); +static void free_PackInfo(struct _7z_pack_info *); +static void free_StreamsInfo(struct _7z_stream_info *); +static void free_SubStreamsInfo(struct _7z_substream_info *); +static int free_decompression(struct archive_read *, struct _7zip *); +static ssize_t get_uncompressed_data(struct archive_read *, const void **, + size_t, size_t); +static const unsigned char * header_bytes(struct archive_read *, size_t); +static int init_decompression(struct archive_read *, struct _7zip *, + const struct _7z_coder *, const struct _7z_coder *); +static int parse_7zip_uint64(struct archive_read *, uint64_t *); +static int read_Bools(struct archive_read *, unsigned char *, size_t); +static int read_CodersInfo(struct archive_read *, + struct _7z_coders_info *); +static int read_Digests(struct archive_read *, struct _7z_digests *, + size_t); +static int read_Folder(struct archive_read *, struct _7z_folder *); +static int read_Header(struct archive_read *, struct _7z_header_info *, + int); +static int read_PackInfo(struct archive_read *, struct _7z_pack_info *); +static int read_StreamsInfo(struct archive_read *, + struct _7z_stream_info *); +static int read_SubStreamsInfo(struct archive_read *, + struct _7z_substream_info *, struct _7z_folder *, size_t); +static int read_Times(struct archive_read *, struct _7z_header_info *, + int); +static void read_consume(struct archive_read *); +static ssize_t read_stream(struct archive_read *, const void **, size_t, + size_t); +static int seek_pack(struct archive_read *); +static int64_t skip_stream(struct archive_read *, size_t); +static int skip_sfx(struct archive_read *, ssize_t); +static int slurp_central_directory(struct archive_read *, struct _7zip *, + struct _7z_header_info *); +static int setup_decode_folder(struct archive_read *, struct _7z_folder *, + int); +static void x86_Init(struct _7zip *); +static size_t x86_Convert(struct _7zip *, uint8_t *, size_t); +static void arm_Init(struct _7zip *); +static size_t arm_Convert(struct _7zip *, uint8_t *, size_t); +static size_t arm64_Convert(struct _7zip *, uint8_t *, size_t); +static ssize_t Bcj2_Decode(struct _7zip *, uint8_t *, size_t); + + +int +archive_read_support_format_7zip(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct _7zip *zip; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_7zip"); + + zip = calloc(1, sizeof(*zip)); + if (zip == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate 7zip data"); + return (ARCHIVE_FATAL); + } + + /* + * Until enough data has been read, we cannot tell about + * any encrypted entries yet. + */ + zip->has_encrypted_entries = ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW; + + + r = __archive_read_register_format(a, + zip, + "7zip", + archive_read_format_7zip_bid, + NULL, + archive_read_format_7zip_read_header, + archive_read_format_7zip_read_data, + archive_read_format_7zip_read_data_skip, + NULL, + archive_read_format_7zip_cleanup, + archive_read_support_format_7zip_capabilities, + archive_read_format_7zip_has_encrypted_entries); + + if (r != ARCHIVE_OK) + free(zip); + return (ARCHIVE_OK); +} + +static int +archive_read_support_format_7zip_capabilities(struct archive_read * a) +{ + (void)a; /* UNUSED */ + return (ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_DATA | + ARCHIVE_READ_FORMAT_CAPS_ENCRYPT_METADATA); +} + + +static int +archive_read_format_7zip_has_encrypted_entries(struct archive_read *_a) +{ + if (_a && _a->format) { + struct _7zip * zip = (struct _7zip *)_a->format->data; + if (zip) { + return zip->has_encrypted_entries; + } + } + return ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW; +} + +static int +archive_read_format_7zip_bid(struct archive_read *a, int best_bid) +{ + const char *p; + + /* If someone has already bid more than 32, then avoid + trashing the look-ahead buffers with a seek. */ + if (best_bid > 32) + return (-1); + + if ((p = __archive_read_ahead(a, 6, NULL)) == NULL) + return (0); + + /* If first six bytes are the 7-Zip signature, + * return the bid right now. */ + if (memcmp(p, _7ZIP_SIGNATURE, 6) == 0) + return (48); + + /* + * It may a 7-Zip SFX archive file. If first two bytes are + * 'M' and 'Z' available on Windows or first four bytes are + * "\x7F\x45LF" available on posix like system, seek the 7-Zip + * signature. Although we will perform a seek when reading + * a header, what we do not use __archive_read_seek() here is + * due to a bidding performance. + */ + if ((p[0] == 'M' && p[1] == 'Z') || memcmp(p, "\x7F\x45LF", 4) == 0) { + ssize_t offset = SFX_MIN_ADDR; + ssize_t window = 4096; + ssize_t bytes_avail; + while (offset + window <= (SFX_MAX_ADDR)) { + const char *buff = __archive_read_ahead(a, + offset + window, &bytes_avail); + if (buff == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < 0x40) + return (0); + continue; + } + p = buff + offset; + while (p + 32 < buff + bytes_avail) { + int step = check_7zip_header_in_sfx(p); + if (step == 0) + return (48); + p += step; + } + offset = p - buff; + } + } + return (0); +} + +static int +check_7zip_header_in_sfx(const char *p) +{ + switch ((unsigned char)p[5]) { + case 0x1C: + if (memcmp(p, _7ZIP_SIGNATURE, 6) != 0) + return (6); + /* + * Test the CRC because its extraction code has 7-Zip + * Magic Code, so we should do this in order not to + * make a mis-detection. + */ + if (crc32(0, (const unsigned char *)p + 12, 20) + != archive_le32dec(p + 8)) + return (6); + /* Hit the header! */ + return (0); + case 0x37: return (5); + case 0x7A: return (4); + case 0xBC: return (3); + case 0xAF: return (2); + case 0x27: return (1); + default: return (6); + } +} + +static int +skip_sfx(struct archive_read *a, ssize_t bytes_avail) +{ + const void *h; + const char *p, *q; + size_t skip, offset; + ssize_t bytes, window; + + /* + * If bytes_avail > SFX_MIN_ADDR we do not have to call + * __archive_read_seek() at this time since we have + * already had enough data. + */ + if (bytes_avail > SFX_MIN_ADDR) + __archive_read_consume(a, SFX_MIN_ADDR); + else if (__archive_read_seek(a, SFX_MIN_ADDR, SEEK_SET) < 0) + return (ARCHIVE_FATAL); + + offset = 0; + window = 1; + while (offset + window <= SFX_MAX_ADDR - SFX_MIN_ADDR) { + h = __archive_read_ahead(a, window, &bytes); + if (h == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < 0x40) + goto fatal; + continue; + } + if (bytes < 6) { + /* This case might happen when window == 1. */ + window = 4096; + continue; + } + p = (const char *)h; + q = p + bytes; + + /* + * Scan ahead until we find something that looks + * like the 7-Zip header. + */ + while (p + 32 < q) { + int step = check_7zip_header_in_sfx(p); + if (step == 0) { + struct _7zip *zip = + (struct _7zip *)a->format->data; + skip = p - (const char *)h; + __archive_read_consume(a, skip); + zip->seek_base = SFX_MIN_ADDR + offset + skip; + return (ARCHIVE_OK); + } + p += step; + } + skip = p - (const char *)h; + __archive_read_consume(a, skip); + offset += skip; + if (window == 1) + window = 4096; + } +fatal: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Couldn't find out 7-Zip header"); + return (ARCHIVE_FATAL); +} + +static int +archive_read_format_7zip_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + struct _7zip_entry *zip_entry; + int r, ret = ARCHIVE_OK; + struct _7z_folder *folder = 0; + uint64_t fidx = 0; + + /* + * It should be sufficient to call archive_read_next_header() for + * a reader to determine if an entry is encrypted or not. If the + * encryption of an entry is only detectable when calling + * archive_read_data(), so be it. We'll do the same check there + * as well. + */ + if (zip->has_encrypted_entries == ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW) { + zip->has_encrypted_entries = 0; + } + + a->archive.archive_format = ARCHIVE_FORMAT_7ZIP; + if (a->archive.archive_format_name == NULL) + a->archive.archive_format_name = "7-Zip"; + + if (zip->entries == NULL) { + struct _7z_header_info header; + + memset(&header, 0, sizeof(header)); + r = slurp_central_directory(a, zip, &header); + free_Header(&header); + if (r != ARCHIVE_OK) + return (r); + zip->entries_remaining = (size_t)zip->numFiles; + zip->entry = zip->entries; + } else { + ++zip->entry; + } + zip_entry = zip->entry; + + if (zip->entries_remaining <= 0 || zip_entry == NULL) + return ARCHIVE_EOF; + --zip->entries_remaining; + + zip->entry_offset = 0; + zip->end_of_entry = 0; + zip->entry_crc32 = crc32(0, NULL, 0); + + /* Setup a string conversion for a filename. */ + if (zip->sconv == NULL) { + zip->sconv = archive_string_conversion_from_charset( + &a->archive, "UTF-16LE", 1); + if (zip->sconv == NULL) + return (ARCHIVE_FATAL); + } + + /* Figure out if the entry is encrypted by looking at the folder + that is associated to the current 7zip entry. If the folder + has a coder with a _7Z_CRYPTO codec then the folder is encrypted. + Hence the entry must also be encrypted. */ + if (zip_entry && zip_entry->folderIndex < zip->si.ci.numFolders) { + folder = &(zip->si.ci.folders[zip_entry->folderIndex]); + for (fidx=0; folder && fidx<folder->numCoders; fidx++) { + switch(folder->coders[fidx].codec) { + case _7Z_CRYPTO_MAIN_ZIP: + case _7Z_CRYPTO_RAR_29: + case _7Z_CRYPTO_AES_256_SHA_256: { + archive_entry_set_is_data_encrypted(entry, 1); + zip->has_encrypted_entries = 1; + break; + } + } + } + } + + /* Now that we've checked for encryption, if there were still no + * encrypted entries found we can say for sure that there are none. + */ + if (zip->has_encrypted_entries == ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW) { + zip->has_encrypted_entries = 0; + } + + if (archive_entry_copy_pathname_l(entry, + (const char *)zip_entry->utf16name, + zip_entry->name_len, zip->sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname cannot be converted " + "from %s to current locale.", + archive_string_conversion_charset_name(zip->sconv)); + ret = ARCHIVE_WARN; + } + + /* Populate some additional entry fields: */ + archive_entry_set_mode(entry, zip_entry->mode); + if (zip_entry->flg & MTIME_IS_SET) + archive_entry_set_mtime(entry, zip_entry->mtime, + zip_entry->mtime_ns); + if (zip_entry->flg & CTIME_IS_SET) + archive_entry_set_ctime(entry, zip_entry->ctime, + zip_entry->ctime_ns); + if (zip_entry->flg & ATIME_IS_SET) + archive_entry_set_atime(entry, zip_entry->atime, + zip_entry->atime_ns); + if (zip_entry->ssIndex != (uint32_t)-1) { + zip->entry_bytes_remaining = + zip->si.ss.unpackSizes[zip_entry->ssIndex]; + archive_entry_set_size(entry, zip->entry_bytes_remaining); + } else { + zip->entry_bytes_remaining = 0; + archive_entry_set_size(entry, 0); + } + + /* If there's no body, force read_data() to return EOF immediately. */ + if (zip->entry_bytes_remaining < 1) + zip->end_of_entry = 1; + + if ((zip_entry->mode & AE_IFMT) == AE_IFLNK) { + unsigned char *symname = NULL; + size_t symsize = 0; + + /* + * Symbolic-name is recorded as its contents. We have to + * read the contents at this time. + */ + while (zip->entry_bytes_remaining > 0) { + const void *buff; + unsigned char *mem; + size_t size; + int64_t offset; + + r = archive_read_format_7zip_read_data(a, &buff, + &size, &offset); + if (r < ARCHIVE_WARN) { + free(symname); + return (r); + } + mem = realloc(symname, symsize + size + 1); + if (mem == NULL) { + free(symname); + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Symname"); + return (ARCHIVE_FATAL); + } + symname = mem; + memcpy(symname+symsize, buff, size); + symsize += size; + } + if (symsize == 0) { + /* If there is no symname, handle it as a regular + * file. */ + zip_entry->mode &= ~AE_IFMT; + zip_entry->mode |= AE_IFREG; + archive_entry_set_mode(entry, zip_entry->mode); + } else { + symname[symsize] = '\0'; + archive_entry_copy_symlink(entry, + (const char *)symname); + } + free(symname); + archive_entry_set_size(entry, 0); + } + + /* Set up a more descriptive format name. */ + snprintf(zip->format_name, sizeof(zip->format_name), "7-Zip"); + a->archive.archive_format_name = zip->format_name; + + return (ret); +} + +static int +archive_read_format_7zip_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + struct _7zip *zip; + ssize_t bytes; + int ret = ARCHIVE_OK; + + zip = (struct _7zip *)(a->format->data); + + if (zip->has_encrypted_entries == ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW) { + zip->has_encrypted_entries = 0; + } + + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + + *offset = zip->entry_offset; + *size = 0; + *buff = NULL; + /* + * If we hit end-of-entry last time, clean up and return + * ARCHIVE_EOF this time. + */ + if (zip->end_of_entry) + return (ARCHIVE_EOF); + + const uint64_t max_read_size = 16 * 1024 * 1024; // Don't try to read more than 16 MB at a time + size_t bytes_to_read = max_read_size; + if ((uint64_t)bytes_to_read > zip->entry_bytes_remaining) { + bytes_to_read = zip->entry_bytes_remaining; + } + bytes = read_stream(a, buff, bytes_to_read, 0); + if (bytes < 0) + return ((int)bytes); + if (bytes == 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + zip->entry_bytes_remaining -= bytes; + if (zip->entry_bytes_remaining == 0) + zip->end_of_entry = 1; + + /* Update checksum */ + if ((zip->entry->flg & CRC32_IS_SET) && bytes) + zip->entry_crc32 = crc32(zip->entry_crc32, *buff, + (unsigned)bytes); + + /* If we hit the end, swallow any end-of-data marker. */ + if (zip->end_of_entry) { + /* Check computed CRC against file contents. */ + if ((zip->entry->flg & CRC32_IS_SET) && + zip->si.ss.digests[zip->entry->ssIndex] != + zip->entry_crc32) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "7-Zip bad CRC: 0x%lx should be 0x%lx", + (unsigned long)zip->entry_crc32, + (unsigned long)zip->si.ss.digests[ + zip->entry->ssIndex]); + ret = ARCHIVE_WARN; + } + } + + *size = bytes; + *offset = zip->entry_offset; + zip->entry_offset += bytes; + + return (ret); +} + +static int +archive_read_format_7zip_read_data_skip(struct archive_read *a) +{ + struct _7zip *zip; + int64_t bytes_skipped; + + zip = (struct _7zip *)(a->format->data); + + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + + /* If we've already read to end of data, we're done. */ + if (zip->end_of_entry) + return (ARCHIVE_OK); + + /* + * If the length is at the beginning, we can skip the + * compressed data much more quickly. + */ + bytes_skipped = skip_stream(a, (size_t)zip->entry_bytes_remaining); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + zip->entry_bytes_remaining = 0; + + /* This entry is finished and done. */ + zip->end_of_entry = 1; + return (ARCHIVE_OK); +} + +static int +archive_read_format_7zip_cleanup(struct archive_read *a) +{ + struct _7zip *zip; + + zip = (struct _7zip *)(a->format->data); + free_StreamsInfo(&(zip->si)); + free(zip->entries); + free(zip->entry_names); + free_decompression(a, zip); + free(zip->uncompressed_buffer); + free(zip->sub_stream_buff[0]); + free(zip->sub_stream_buff[1]); + free(zip->sub_stream_buff[2]); + free(zip->tmp_stream_buff); + free(zip); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +static void +read_consume(struct archive_read *a) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + + if (zip->pack_stream_bytes_unconsumed) { + __archive_read_consume(a, zip->pack_stream_bytes_unconsumed); + zip->stream_offset += zip->pack_stream_bytes_unconsumed; + zip->pack_stream_bytes_unconsumed = 0; + } +} + +#ifdef HAVE_LZMA_H + +/* + * Set an error code and choose an error message for liblzma. + */ +static void +set_error(struct archive_read *a, int ret) +{ + + switch (ret) { + case LZMA_STREAM_END: /* Found end of stream. */ + case LZMA_OK: /* Decompressor made some progress. */ + break; + case LZMA_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Lzma library error: Cannot allocate memory"); + break; + case LZMA_MEMLIMIT_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Lzma library error: Out of memory"); + break; + case LZMA_FORMAT_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: format not recognized"); + break; + case LZMA_OPTIONS_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: Invalid options"); + break; + case LZMA_DATA_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: Corrupted input data"); + break; + case LZMA_BUF_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: No progress is possible"); + break; + default: + /* Return an error. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma decompression failed: Unknown error"); + break; + } +} + +#endif + +static unsigned long +decode_codec_id(const unsigned char *codecId, size_t id_size) +{ + unsigned i; + unsigned long id = 0; + + for (i = 0; i < id_size; i++) { + id <<= 8; + id += codecId[i]; + } + return (id); +} + +static Byte +ppmd_read(void *p) +{ + struct archive_read *a = ((IByteIn*)p)->a; + struct _7zip *zip = (struct _7zip *)(a->format->data); + Byte b; + + if (zip->ppstream.avail_in <= 0) { + /* + * Ppmd7_DecodeSymbol might require reading multiple bytes + * and we are on boundary; + * last resort to read using __archive_read_ahead. + */ + ssize_t bytes_avail = 0; + const uint8_t* data = __archive_read_ahead(a, + zip->ppstream.stream_in+1, &bytes_avail); + if(bytes_avail < zip->ppstream.stream_in+1) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7z file data"); + zip->ppstream.overconsumed = 1; + return (0); + } + zip->ppstream.next_in++; + b = data[zip->ppstream.stream_in]; + } else { + b = *zip->ppstream.next_in++; + } + zip->ppstream.avail_in--; + zip->ppstream.total_in++; + zip->ppstream.stream_in++; + return (b); +} + +static int +init_decompression(struct archive_read *a, struct _7zip *zip, + const struct _7z_coder *coder1, const struct _7z_coder *coder2) +{ + int r; + + zip->codec = coder1->codec; + zip->codec2 = -1; + + switch (zip->codec) { + case _7Z_COPY: + case _7Z_BZ2: + case _7Z_DEFLATE: + case _7Z_ZSTD: + case _7Z_PPMD: + if (coder2 != NULL) { + if (coder2->codec != _7Z_X86 && + coder2->codec != _7Z_X86_BCJ2 && + coder2->codec != _7Z_ARM && + coder2->codec != _7Z_ARM64) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Unsupported filter %lx for %lx", + coder2->codec, coder1->codec); + return (ARCHIVE_FAILED); + } + zip->codec2 = coder2->codec; + zip->bcj_state = 0; + if (coder2->codec == _7Z_X86) + x86_Init(zip); + else if (coder2->codec == _7Z_ARM) + arm_Init(zip); + } + break; + default: + break; + } + + switch (zip->codec) { + case _7Z_COPY: + break; + + case _7Z_LZMA: case _7Z_LZMA2: +#ifdef HAVE_LZMA_H +#if LZMA_VERSION_MAJOR >= 5 +/* Effectively disable the limiter. */ +#define LZMA_MEMLIMIT UINT64_MAX +#else +/* NOTE: This needs to check memory size which running system has. */ +#define LZMA_MEMLIMIT (1U << 30) +#endif + { + lzma_options_delta delta_opt; + lzma_filter filters[LZMA_FILTERS_MAX], *ff; + int fi = 0; + + if (zip->lzstream_valid) { + lzma_end(&(zip->lzstream)); + zip->lzstream_valid = 0; + } + + /* + * NOTE: liblzma incompletely handle the BCJ+LZMA compressed + * data made by 7-Zip because 7-Zip does not add End-Of- + * Payload Marker(EOPM) at the end of LZMA compressed data, + * and so liblzma cannot know the end of the compressed data + * without EOPM. So consequently liblzma will not return last + * three or four bytes of uncompressed data because + * LZMA_FILTER_X86 filter does not handle input data if its + * data size is less than five bytes. If liblzma detect EOPM + * or know the uncompressed data size, liblzma will flush out + * the remaining that three or four bytes of uncompressed + * data. That is why we have to use our converting program + * for BCJ+LZMA. If we were able to tell the uncompressed + * size to liblzma when using lzma_raw_decoder() liblzma + * could correctly deal with BCJ+LZMA. But unfortunately + * there is no way to do that. + * Discussion about this can be found at XZ Utils forum. + */ + if (coder2 != NULL) { + zip->codec2 = coder2->codec; + + filters[fi].options = NULL; + switch (zip->codec2) { + case _7Z_X86: + if (zip->codec == _7Z_LZMA2) { + filters[fi].id = LZMA_FILTER_X86; + fi++; + } else + /* Use our filter. */ + x86_Init(zip); + break; + case _7Z_X86_BCJ2: + /* Use our filter. */ + zip->bcj_state = 0; + break; + case _7Z_DELTA: + if (coder2->propertiesSize != 1) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Invalid Delta parameter"); + return (ARCHIVE_FAILED); + } + filters[fi].id = LZMA_FILTER_DELTA; + memset(&delta_opt, 0, sizeof(delta_opt)); + delta_opt.type = LZMA_DELTA_TYPE_BYTE; + delta_opt.dist = + (uint32_t)coder2->properties[0] + 1; + filters[fi].options = &delta_opt; + fi++; + break; + /* Following filters have not been tested yet. */ + case _7Z_POWERPC: + filters[fi].id = LZMA_FILTER_POWERPC; + fi++; + break; + case _7Z_IA64: + filters[fi].id = LZMA_FILTER_IA64; + fi++; + break; + case _7Z_ARM: + filters[fi].id = LZMA_FILTER_ARM; + fi++; + break; + case _7Z_ARMTHUMB: + filters[fi].id = LZMA_FILTER_ARMTHUMB; + fi++; + break; +#ifdef LZMA_FILTER_ARM64 + case _7Z_ARM64: + filters[fi].id = LZMA_FILTER_ARM64; + fi++; + break; +#endif + case _7Z_SPARC: + filters[fi].id = LZMA_FILTER_SPARC; + fi++; + break; + default: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Unexpected codec ID: %lX", zip->codec2); + return (ARCHIVE_FAILED); + } + } + + if (zip->codec == _7Z_LZMA2) + filters[fi].id = LZMA_FILTER_LZMA2; + else + filters[fi].id = LZMA_FILTER_LZMA1; + filters[fi].options = NULL; + ff = &filters[fi]; + r = lzma_properties_decode(&filters[fi], NULL, + coder1->properties, (size_t)coder1->propertiesSize); + if (r != LZMA_OK) { + set_error(a, r); + return (ARCHIVE_FAILED); + } + fi++; + + filters[fi].id = LZMA_VLI_UNKNOWN; + filters[fi].options = NULL; + r = lzma_raw_decoder(&(zip->lzstream), filters); + free(ff->options); + if (r != LZMA_OK) { + set_error(a, r); + return (ARCHIVE_FAILED); + } + zip->lzstream_valid = 1; + zip->lzstream.total_in = 0; + zip->lzstream.total_out = 0; + break; + } +#else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "LZMA codec is unsupported"); + return (ARCHIVE_FAILED); +#endif + case _7Z_BZ2: +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + if (zip->bzstream_valid) { + BZ2_bzDecompressEnd(&(zip->bzstream)); + zip->bzstream_valid = 0; + } + r = BZ2_bzDecompressInit(&(zip->bzstream), 0, 0); + if (r == BZ_MEM_ERROR) + r = BZ2_bzDecompressInit(&(zip->bzstream), 0, 1); + if (r != BZ_OK) { + int err = ARCHIVE_ERRNO_MISC; + const char *detail = NULL; + switch (r) { + case BZ_PARAM_ERROR: + detail = "invalid setup parameter"; + break; + case BZ_MEM_ERROR: + err = ENOMEM; + detail = "out of memory"; + break; + case BZ_CONFIG_ERROR: + detail = "mis-compiled library"; + break; + } + archive_set_error(&a->archive, err, + "Internal error initializing decompressor: %s", + detail != NULL ? detail : "??"); + zip->bzstream_valid = 0; + return (ARCHIVE_FAILED); + } + zip->bzstream_valid = 1; + zip->bzstream.total_in_lo32 = 0; + zip->bzstream.total_in_hi32 = 0; + zip->bzstream.total_out_lo32 = 0; + zip->bzstream.total_out_hi32 = 0; + break; +#else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "BZ2 codec is unsupported"); + return (ARCHIVE_FAILED); +#endif + case _7Z_ZSTD: + { +#if defined(HAVE_ZSTD_H) + if (zip->zstdstream_valid) { + ZSTD_freeDStream(zip->zstd_dstream); + zip->zstdstream_valid = 0; + } + zip->zstd_dstream = ZSTD_createDStream(); + zip->zstdstream_valid = 1; + break; +#else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZSTD codec is unsupported"); + return (ARCHIVE_FAILED); +#endif + } + case _7Z_DEFLATE: +#ifdef HAVE_ZLIB_H + if (zip->stream_valid) + r = inflateReset(&(zip->stream)); + else + r = inflateInit2(&(zip->stream), + -15 /* Don't check for zlib header */); + if (r != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Couldn't initialize zlib stream."); + return (ARCHIVE_FAILED); + } + zip->stream_valid = 1; + zip->stream.total_in = 0; + zip->stream.total_out = 0; + break; +#else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "DEFLATE codec is unsupported"); + return (ARCHIVE_FAILED); +#endif + case _7Z_PPMD: + { + unsigned order; + uint32_t msize; + + if (zip->ppmd7_valid) { + __archive_ppmd7_functions.Ppmd7_Free( + &zip->ppmd7_context); + zip->ppmd7_valid = 0; + } + + if (coder1->propertiesSize < 5) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed PPMd parameter"); + return (ARCHIVE_FAILED); + } + order = coder1->properties[0]; + msize = archive_le32dec(&(coder1->properties[1])); + if (order < PPMD7_MIN_ORDER || order > PPMD7_MAX_ORDER || + msize < PPMD7_MIN_MEM_SIZE || msize > PPMD7_MAX_MEM_SIZE) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed PPMd parameter"); + return (ARCHIVE_FAILED); + } + __archive_ppmd7_functions.Ppmd7_Construct(&zip->ppmd7_context); + r = __archive_ppmd7_functions.Ppmd7_Alloc( + &zip->ppmd7_context, msize); + if (r == 0) { + archive_set_error(&a->archive, ENOMEM, + "Coludn't allocate memory for PPMd"); + return (ARCHIVE_FATAL); + } + __archive_ppmd7_functions.Ppmd7_Init( + &zip->ppmd7_context, order); + __archive_ppmd7_functions.Ppmd7z_RangeDec_CreateVTable( + &zip->range_dec); + zip->ppmd7_valid = 1; + zip->ppmd7_stat = 0; + zip->ppstream.overconsumed = 0; + zip->ppstream.total_in = 0; + zip->ppstream.total_out = 0; + break; + } + case _7Z_X86: + case _7Z_X86_BCJ2: + case _7Z_POWERPC: + case _7Z_IA64: + case _7Z_ARM: + case _7Z_ARMTHUMB: + case _7Z_SPARC: + case _7Z_DELTA: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unexpected codec ID: %lX", zip->codec); + return (ARCHIVE_FAILED); + case _7Z_CRYPTO_MAIN_ZIP: + case _7Z_CRYPTO_RAR_29: + case _7Z_CRYPTO_AES_256_SHA_256: + if (a->entry) { + archive_entry_set_is_metadata_encrypted(a->entry, 1); + archive_entry_set_is_data_encrypted(a->entry, 1); + zip->has_encrypted_entries = 1; + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Crypto codec not supported yet (ID: 0x%lX)", zip->codec); + return (ARCHIVE_FAILED); + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unknown codec ID: %lX", zip->codec); + return (ARCHIVE_FAILED); + } + + return (ARCHIVE_OK); +} + +static int +decompress(struct archive_read *a, struct _7zip *zip, + void *buff, size_t *outbytes, const void *b, size_t *used) +{ + const uint8_t *t_next_in; + uint8_t *t_next_out; + size_t o_avail_in, o_avail_out; + size_t t_avail_in, t_avail_out; + uint8_t *bcj2_next_out; + size_t bcj2_avail_out; + int r, ret = ARCHIVE_OK; + + t_avail_in = o_avail_in = *used; + t_avail_out = o_avail_out = *outbytes; + t_next_in = b; + t_next_out = buff; + + if (zip->codec != _7Z_LZMA2 && zip->codec2 == _7Z_X86) { + int i; + + /* Do not copy out the BCJ remaining bytes when the output + * buffer size is less than five bytes. */ + if (o_avail_in != 0 && t_avail_out < 5 && zip->odd_bcj_size) { + *used = 0; + *outbytes = 0; + return (ret); + } + for (i = 0; zip->odd_bcj_size > 0 && t_avail_out; i++) { + *t_next_out++ = zip->odd_bcj[i]; + t_avail_out--; + zip->odd_bcj_size--; + } + if (o_avail_in == 0 || t_avail_out == 0) { + *used = o_avail_in - t_avail_in; + *outbytes = o_avail_out - t_avail_out; + if (o_avail_in == 0) + ret = ARCHIVE_EOF; + return (ret); + } + } + + bcj2_next_out = t_next_out; + bcj2_avail_out = t_avail_out; + if (zip->codec2 == _7Z_X86_BCJ2) { + /* + * Decord a remaining decompressed main stream for BCJ2. + */ + if (zip->tmp_stream_bytes_remaining) { + ssize_t bytes; + size_t remaining = zip->tmp_stream_bytes_remaining; + bytes = Bcj2_Decode(zip, t_next_out, t_avail_out); + if (bytes < 0) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "BCJ2 conversion Failed"); + return (ARCHIVE_FAILED); + } + zip->main_stream_bytes_remaining -= + remaining - zip->tmp_stream_bytes_remaining; + t_avail_out -= bytes; + if (o_avail_in == 0 || t_avail_out == 0) { + *used = 0; + *outbytes = o_avail_out - t_avail_out; + if (o_avail_in == 0 && + zip->tmp_stream_bytes_remaining) + ret = ARCHIVE_EOF; + return (ret); + } + t_next_out += bytes; + bcj2_next_out = t_next_out; + bcj2_avail_out = t_avail_out; + } + t_next_out = zip->tmp_stream_buff; + t_avail_out = zip->tmp_stream_buff_size; + } + + switch (zip->codec) { + case _7Z_COPY: + { + size_t bytes = + (t_avail_in > t_avail_out)?t_avail_out:t_avail_in; + + memcpy(t_next_out, t_next_in, bytes); + t_avail_in -= bytes; + t_avail_out -= bytes; + if (o_avail_in == 0) + ret = ARCHIVE_EOF; + break; + } +#ifdef HAVE_LZMA_H + case _7Z_LZMA: case _7Z_LZMA2: + zip->lzstream.next_in = t_next_in; + zip->lzstream.avail_in = t_avail_in; + zip->lzstream.next_out = t_next_out; + zip->lzstream.avail_out = t_avail_out; + + r = lzma_code(&(zip->lzstream), LZMA_RUN); + switch (r) { + case LZMA_STREAM_END: /* Found end of stream. */ + lzma_end(&(zip->lzstream)); + zip->lzstream_valid = 0; + ret = ARCHIVE_EOF; + break; + case LZMA_OK: /* Decompressor made some progress. */ + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Decompression failed(%d)", + r); + return (ARCHIVE_FAILED); + } + t_avail_in = zip->lzstream.avail_in; + t_avail_out = zip->lzstream.avail_out; + break; +#endif +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + case _7Z_BZ2: + zip->bzstream.next_in = (char *)(uintptr_t)t_next_in; + zip->bzstream.avail_in = (uint32_t)t_avail_in; + zip->bzstream.next_out = (char *)(uintptr_t)t_next_out; + zip->bzstream.avail_out = (uint32_t)t_avail_out; + r = BZ2_bzDecompress(&(zip->bzstream)); + switch (r) { + case BZ_STREAM_END: /* Found end of stream. */ + switch (BZ2_bzDecompressEnd(&(zip->bzstream))) { + case BZ_OK: + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Failed to clean up decompressor"); + return (ARCHIVE_FAILED); + } + zip->bzstream_valid = 0; + ret = ARCHIVE_EOF; + break; + case BZ_OK: /* Decompressor made some progress. */ + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "bzip decompression failed"); + return (ARCHIVE_FAILED); + } + t_avail_in = zip->bzstream.avail_in; + t_avail_out = zip->bzstream.avail_out; + break; +#endif +#ifdef HAVE_ZLIB_H + case _7Z_DEFLATE: + zip->stream.next_in = (Bytef *)(uintptr_t)t_next_in; + zip->stream.avail_in = (uInt)t_avail_in; + zip->stream.next_out = t_next_out; + zip->stream.avail_out = (uInt)t_avail_out; + r = inflate(&(zip->stream), 0); + switch (r) { + case Z_STREAM_END: /* Found end of stream. */ + ret = ARCHIVE_EOF; + break; + case Z_OK: /* Decompressor made some progress.*/ + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "File decompression failed (%d)", r); + return (ARCHIVE_FAILED); + } + t_avail_in = zip->stream.avail_in; + t_avail_out = zip->stream.avail_out; + break; +#endif +#ifdef HAVE_ZSTD_H + case _7Z_ZSTD: + { + ZSTD_inBuffer input = { t_next_in, t_avail_in, 0 }; // src, size, pos + ZSTD_outBuffer output = { t_next_out, t_avail_out, 0 }; // dst, size, pos + + size_t const zret = ZSTD_decompressStream(zip->zstd_dstream, &output, &input); + if (ZSTD_isError(zret)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Zstd decompression failed: %s", ZSTD_getErrorName(zret)); + return ARCHIVE_FAILED; + } + t_avail_in -= input.pos; + t_avail_out -= output.pos; + break; + } +#endif + case _7Z_PPMD: + { + uint64_t flush_bytes; + + if (!zip->ppmd7_valid || zip->ppmd7_stat < 0 || + t_avail_out <= 0) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Decompression internal error"); + return (ARCHIVE_FAILED); + } + zip->ppstream.next_in = t_next_in; + zip->ppstream.avail_in = t_avail_in; + zip->ppstream.stream_in = 0; + zip->ppstream.next_out = t_next_out; + zip->ppstream.avail_out = t_avail_out; + if (zip->ppmd7_stat == 0) { + zip->bytein.a = a; + zip->bytein.Read = &ppmd_read; + zip->range_dec.Stream = &zip->bytein; + r = __archive_ppmd7_functions.Ppmd7z_RangeDec_Init( + &(zip->range_dec)); + if (r == 0) { + zip->ppmd7_stat = -1; + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to initialize PPMd range decoder"); + return (ARCHIVE_FAILED); + } + if (zip->ppstream.overconsumed) { + zip->ppmd7_stat = -1; + return (ARCHIVE_FAILED); + } + zip->ppmd7_stat = 1; + } + + if (t_avail_in == 0) + /* XXX Flush out remaining decoded data XXX */ + flush_bytes = zip->folder_outbytes_remaining; + else + flush_bytes = 0; + + do { + int sym; + + sym = __archive_ppmd7_functions.Ppmd7_DecodeSymbol( + &(zip->ppmd7_context), &(zip->range_dec.p)); + if (sym < 0) { + zip->ppmd7_stat = -1; + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Failed to decode PPMd"); + return (ARCHIVE_FAILED); + } + if (zip->ppstream.overconsumed) { + zip->ppmd7_stat = -1; + return (ARCHIVE_FAILED); + } + *zip->ppstream.next_out++ = (unsigned char)sym; + zip->ppstream.avail_out--; + zip->ppstream.total_out++; + if (flush_bytes) + flush_bytes--; + } while (zip->ppstream.avail_out && + (zip->ppstream.avail_in || flush_bytes)); + + t_avail_in = (size_t)zip->ppstream.avail_in; + t_avail_out = (size_t)zip->ppstream.avail_out; + break; + } + default: + archive_set_error(&(a->archive), ARCHIVE_ERRNO_MISC, + "Decompression internal error"); + return (ARCHIVE_FAILED); + } + if (ret != ARCHIVE_OK && ret != ARCHIVE_EOF) + return (ret); + + *used = o_avail_in - t_avail_in; + *outbytes = o_avail_out - t_avail_out; + + /* + * Decord BCJ. + */ + if (zip->codec != _7Z_LZMA2) { + if (zip->codec2 == _7Z_X86) { + size_t l = x86_Convert(zip, buff, *outbytes); + + zip->odd_bcj_size = *outbytes - l; + if (zip->odd_bcj_size > 0 && zip->odd_bcj_size <= 4 && + o_avail_in && ret != ARCHIVE_EOF) { + memcpy(zip->odd_bcj, ((unsigned char *)buff) + l, + zip->odd_bcj_size); + *outbytes = l; + } else + zip->odd_bcj_size = 0; + } else if (zip->codec2 == _7Z_ARM) { + *outbytes = arm_Convert(zip, buff, *outbytes); + } else if (zip->codec2 == _7Z_ARM64) { + *outbytes = arm64_Convert(zip, buff, *outbytes); + } + } + + /* + * Decord BCJ2 with a decompressed main stream. + */ + if (zip->codec2 == _7Z_X86_BCJ2) { + ssize_t bytes; + + zip->tmp_stream_bytes_avail = + zip->tmp_stream_buff_size - t_avail_out; + if (zip->tmp_stream_bytes_avail > + zip->main_stream_bytes_remaining) + zip->tmp_stream_bytes_avail = + zip->main_stream_bytes_remaining; + zip->tmp_stream_bytes_remaining = zip->tmp_stream_bytes_avail; + bytes = Bcj2_Decode(zip, bcj2_next_out, bcj2_avail_out); + if (bytes < 0) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "BCJ2 conversion Failed"); + return (ARCHIVE_FAILED); + } + zip->main_stream_bytes_remaining -= + zip->tmp_stream_bytes_avail + - zip->tmp_stream_bytes_remaining; + bcj2_avail_out -= bytes; + *outbytes = o_avail_out - bcj2_avail_out; + } + + return (ret); +} + +static int +free_decompression(struct archive_read *a, struct _7zip *zip) +{ + int r = ARCHIVE_OK; + +#if !defined(HAVE_ZLIB_H) &&\ + !(defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR)) + (void)a;/* UNUSED */ +#endif +#ifdef HAVE_LZMA_H + if (zip->lzstream_valid) + lzma_end(&(zip->lzstream)); +#endif +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + if (zip->bzstream_valid) { + if (BZ2_bzDecompressEnd(&(zip->bzstream)) != BZ_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to clean up bzip2 decompressor"); + r = ARCHIVE_FATAL; + } + zip->bzstream_valid = 0; + } +#endif +#ifdef HAVE_ZLIB_H + if (zip->stream_valid) { + if (inflateEnd(&(zip->stream)) != Z_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to clean up zlib decompressor"); + r = ARCHIVE_FATAL; + } + zip->stream_valid = 0; + } +#endif + if (zip->ppmd7_valid) { + __archive_ppmd7_functions.Ppmd7_Free( + &zip->ppmd7_context); + zip->ppmd7_valid = 0; + } + return (r); +} + +static int +parse_7zip_uint64(struct archive_read *a, uint64_t *val) +{ + const unsigned char *p; + unsigned char avail, mask; + int i; + + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + avail = *p; + mask = 0x80; + *val = 0; + for (i = 0; i < 8; i++) { + if (avail & mask) { + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + *val |= ((uint64_t)*p) << (8 * i); + mask >>= 1; + continue; + } + *val += ((uint64_t)(avail & (mask -1))) << (8 * i); + break; + } + return (0); +} + +static int +read_Bools(struct archive_read *a, unsigned char *data, size_t num) +{ + const unsigned char *p; + unsigned i, mask = 0, avail = 0; + + for (i = 0; i < num; i++) { + if (mask == 0) { + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + avail = *p; + mask = 0x80; + } + data[i] = (avail & mask)?1:0; + mask >>= 1; + } + return (0); +} + +static void +free_Digest(struct _7z_digests *d) +{ + free(d->defineds); + free(d->digests); +} + +static int +read_Digests(struct archive_read *a, struct _7z_digests *d, size_t num) +{ + const unsigned char *p; + unsigned i; + + if (num == 0) + return (-1); + memset(d, 0, sizeof(*d)); + + d->defineds = malloc(num); + if (d->defineds == NULL) + return (-1); + /* + * Read Bools. + */ + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + if (*p == 0) { + if (read_Bools(a, d->defineds, num) < 0) + return (-1); + } else + /* All are defined */ + memset(d->defineds, 1, num); + + d->digests = calloc(num, sizeof(*d->digests)); + if (d->digests == NULL) + return (-1); + for (i = 0; i < num; i++) { + if (d->defineds[i]) { + if ((p = header_bytes(a, 4)) == NULL) + return (-1); + d->digests[i] = archive_le32dec(p); + } + } + + return (0); +} + +static void +free_PackInfo(struct _7z_pack_info *pi) +{ + free(pi->sizes); + free(pi->positions); + free_Digest(&(pi->digest)); +} + +static int +read_PackInfo(struct archive_read *a, struct _7z_pack_info *pi) +{ + const unsigned char *p; + unsigned i; + + memset(pi, 0, sizeof(*pi)); + + /* + * Read PackPos. + */ + if (parse_7zip_uint64(a, &(pi->pos)) < 0) + return (-1); + + /* + * Read NumPackStreams. + */ + if (parse_7zip_uint64(a, &(pi->numPackStreams)) < 0) + return (-1); + if (pi->numPackStreams == 0) + return (-1); + if (UMAX_ENTRY < pi->numPackStreams) + return (-1); + + /* + * Read PackSizes[num] + */ + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + if (*p == kEnd) + /* PackSizes[num] are not present. */ + return (0); + if (*p != kSize) + return (-1); + pi->sizes = calloc((size_t)pi->numPackStreams, sizeof(uint64_t)); + pi->positions = calloc((size_t)pi->numPackStreams, sizeof(uint64_t)); + if (pi->sizes == NULL || pi->positions == NULL) + return (-1); + + for (i = 0; i < pi->numPackStreams; i++) { + if (parse_7zip_uint64(a, &(pi->sizes[i])) < 0) + return (-1); + } + + /* + * Read PackStreamDigests[num] + */ + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + if (*p == kEnd) { + /* PackStreamDigests[num] are not present. */ + pi->digest.defineds = + calloc((size_t)pi->numPackStreams, sizeof(*pi->digest.defineds)); + pi->digest.digests = + calloc((size_t)pi->numPackStreams, sizeof(*pi->digest.digests)); + if (pi->digest.defineds == NULL || pi->digest.digests == NULL) + return (-1); + return (0); + } + + if (*p != kCRC) + return (-1); + + if (read_Digests(a, &(pi->digest), (size_t)pi->numPackStreams) < 0) + return (-1); + + /* + * Must be marked by kEnd. + */ + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + if (*p != kEnd) + return (-1); + return (0); +} + +static void +free_Folder(struct _7z_folder *f) +{ + unsigned i; + + if (f->coders) { + for (i = 0; i< f->numCoders; i++) { + free(f->coders[i].properties); + } + free(f->coders); + } + free(f->bindPairs); + free(f->packedStreams); + free(f->unPackSize); +} + +static int +read_Folder(struct archive_read *a, struct _7z_folder *f) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + const unsigned char *p; + uint64_t numInStreamsTotal = 0; + uint64_t numOutStreamsTotal = 0; + unsigned i; + + memset(f, 0, sizeof(*f)); + + /* + * Read NumCoders. + */ + if (parse_7zip_uint64(a, &(f->numCoders)) < 0) + return (-1); + if (f->numCoders > 4) + /* Too many coders. */ + return (-1); + + f->coders = calloc((size_t)f->numCoders, sizeof(*f->coders)); + if (f->coders == NULL) + return (-1); + for (i = 0; i< f->numCoders; i++) { + size_t codec_size; + int simple, attr; + + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + /* + * 0:3 CodecIdSize + * 4: 0 - IsSimple + * 1 - Is not Simple + * 5: 0 - No Attributes + * 1 - There are Attributes; + * 7: Must be zero. + */ + codec_size = *p & 0xf; + simple = (*p & 0x10)?0:1; + attr = *p & 0x20; + if (*p & 0x80) + return (-1);/* Not supported. */ + + /* + * Read Decompression Method IDs. + */ + if ((p = header_bytes(a, codec_size)) == NULL) + return (-1); + + f->coders[i].codec = decode_codec_id(p, codec_size); + + if (simple) { + f->coders[i].numInStreams = 1; + f->coders[i].numOutStreams = 1; + } else { + if (parse_7zip_uint64( + a, &(f->coders[i].numInStreams)) < 0) + return (-1); + if (UMAX_ENTRY < f->coders[i].numInStreams) + return (-1); + if (parse_7zip_uint64( + a, &(f->coders[i].numOutStreams)) < 0) + return (-1); + if (UMAX_ENTRY < f->coders[i].numOutStreams) + return (-1); + } + + if (attr) { + if (parse_7zip_uint64( + a, &(f->coders[i].propertiesSize)) < 0) + return (-1); + if ((p = header_bytes( + a, (size_t)f->coders[i].propertiesSize)) == NULL) + return (-1); + f->coders[i].properties = + malloc((size_t)f->coders[i].propertiesSize); + if (f->coders[i].properties == NULL) + return (-1); + memcpy(f->coders[i].properties, p, + (size_t)f->coders[i].propertiesSize); + } + + numInStreamsTotal += f->coders[i].numInStreams; + numOutStreamsTotal += f->coders[i].numOutStreams; + } + + if (numOutStreamsTotal == 0 || + numInStreamsTotal < numOutStreamsTotal-1) + return (-1); + + f->numBindPairs = numOutStreamsTotal - 1; + if (zip->header_bytes_remaining < f->numBindPairs) + return (-1); + if (f->numBindPairs > 0) { + f->bindPairs = + calloc((size_t)f->numBindPairs, sizeof(*f->bindPairs)); + if (f->bindPairs == NULL) + return (-1); + } else + f->bindPairs = NULL; + for (i = 0; i < f->numBindPairs; i++) { + if (parse_7zip_uint64(a, &(f->bindPairs[i].inIndex)) < 0) + return (-1); + if (UMAX_ENTRY < f->bindPairs[i].inIndex) + return (-1); + if (parse_7zip_uint64(a, &(f->bindPairs[i].outIndex)) < 0) + return (-1); + if (UMAX_ENTRY < f->bindPairs[i].outIndex) + return (-1); + } + + f->numPackedStreams = numInStreamsTotal - f->numBindPairs; + f->packedStreams = + calloc((size_t)f->numPackedStreams, sizeof(*f->packedStreams)); + if (f->packedStreams == NULL) + return (-1); + if (f->numPackedStreams == 1) { + for (i = 0; i < numInStreamsTotal; i++) { + unsigned j; + for (j = 0; j < f->numBindPairs; j++) { + if (f->bindPairs[j].inIndex == i) + break; + } + if (j == f->numBindPairs) + break; + } + if (i == numInStreamsTotal) + return (-1); + f->packedStreams[0] = i; + } else { + for (i = 0; i < f->numPackedStreams; i++) { + if (parse_7zip_uint64(a, &(f->packedStreams[i])) < 0) + return (-1); + if (UMAX_ENTRY < f->packedStreams[i]) + return (-1); + } + } + f->numInStreams = numInStreamsTotal; + f->numOutStreams = numOutStreamsTotal; + + return (0); +} + +static void +free_CodersInfo(struct _7z_coders_info *ci) +{ + unsigned i; + + if (ci->folders) { + for (i = 0; i < ci->numFolders; i++) + free_Folder(&(ci->folders[i])); + free(ci->folders); + } +} + +static int +read_CodersInfo(struct archive_read *a, struct _7z_coders_info *ci) +{ + const unsigned char *p; + struct _7z_digests digest; + unsigned i; + + memset(ci, 0, sizeof(*ci)); + memset(&digest, 0, sizeof(digest)); + + if ((p = header_bytes(a, 1)) == NULL) + goto failed; + if (*p != kFolder) + goto failed; + + /* + * Read NumFolders. + */ + if (parse_7zip_uint64(a, &(ci->numFolders)) < 0) + goto failed; + if (UMAX_ENTRY < ci->numFolders) + return (-1); + + /* + * Read External. + */ + if ((p = header_bytes(a, 1)) == NULL) + goto failed; + switch (*p) { + case 0: + ci->folders = + calloc((size_t)ci->numFolders, sizeof(*ci->folders)); + if (ci->folders == NULL) + return (-1); + for (i = 0; i < ci->numFolders; i++) { + if (read_Folder(a, &(ci->folders[i])) < 0) + goto failed; + } + break; + case 1: + if (parse_7zip_uint64(a, &(ci->dataStreamIndex)) < 0) + return (-1); + if (UMAX_ENTRY < ci->dataStreamIndex) + return (-1); + if (ci->numFolders > 0) { + archive_set_error(&a->archive, -1, + "Malformed 7-Zip archive"); + goto failed; + } + break; + default: + archive_set_error(&a->archive, -1, + "Malformed 7-Zip archive"); + goto failed; + } + + if ((p = header_bytes(a, 1)) == NULL) + goto failed; + if (*p != kCodersUnPackSize) + goto failed; + + for (i = 0; i < ci->numFolders; i++) { + struct _7z_folder *folder = &(ci->folders[i]); + unsigned j; + + folder->unPackSize = + calloc((size_t)folder->numOutStreams, sizeof(*folder->unPackSize)); + if (folder->unPackSize == NULL) + goto failed; + for (j = 0; j < folder->numOutStreams; j++) { + if (parse_7zip_uint64(a, &(folder->unPackSize[j])) < 0) + goto failed; + } + } + + /* + * Read CRCs. + */ + if ((p = header_bytes(a, 1)) == NULL) + goto failed; + if (*p == kEnd) + return (0); + if (*p != kCRC) + goto failed; + if (read_Digests(a, &digest, (size_t)ci->numFolders) < 0) + goto failed; + for (i = 0; i < ci->numFolders; i++) { + ci->folders[i].digest_defined = digest.defineds[i]; + ci->folders[i].digest = digest.digests[i]; + } + + /* + * Must be kEnd. + */ + if ((p = header_bytes(a, 1)) == NULL) + goto failed; + if (*p != kEnd) + goto failed; + free_Digest(&digest); + return (0); +failed: + free_Digest(&digest); + return (-1); +} + +static uint64_t +folder_uncompressed_size(struct _7z_folder *f) +{ + int n = (int)f->numOutStreams; + unsigned pairs = (unsigned)f->numBindPairs; + + while (--n >= 0) { + unsigned i; + for (i = 0; i < pairs; i++) { + if (f->bindPairs[i].outIndex == (uint64_t)n) + break; + } + if (i >= pairs) + return (f->unPackSize[n]); + } + return (0); +} + +static void +free_SubStreamsInfo(struct _7z_substream_info *ss) +{ + free(ss->unpackSizes); + free(ss->digestsDefined); + free(ss->digests); +} + +static int +read_SubStreamsInfo(struct archive_read *a, struct _7z_substream_info *ss, + struct _7z_folder *f, size_t numFolders) +{ + const unsigned char *p; + uint64_t *usizes; + size_t unpack_streams; + int type; + unsigned i; + uint32_t numDigests; + + memset(ss, 0, sizeof(*ss)); + + for (i = 0; i < numFolders; i++) + f[i].numUnpackStreams = 1; + + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + type = *p; + + if (type == kNumUnPackStream) { + unpack_streams = 0; + for (i = 0; i < numFolders; i++) { + if (parse_7zip_uint64(a, &(f[i].numUnpackStreams)) < 0) + return (-1); + if (UMAX_ENTRY < f[i].numUnpackStreams) + return (-1); + if (unpack_streams > SIZE_MAX - UMAX_ENTRY) { + return (-1); + } + unpack_streams += (size_t)f[i].numUnpackStreams; + } + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + type = *p; + } else + unpack_streams = numFolders; + + ss->unpack_streams = unpack_streams; + if (unpack_streams) { + ss->unpackSizes = calloc(unpack_streams, + sizeof(*ss->unpackSizes)); + ss->digestsDefined = calloc(unpack_streams, + sizeof(*ss->digestsDefined)); + ss->digests = calloc(unpack_streams, + sizeof(*ss->digests)); + if (ss->unpackSizes == NULL || ss->digestsDefined == NULL || + ss->digests == NULL) + return (-1); + } + + usizes = ss->unpackSizes; + for (i = 0; i < numFolders; i++) { + unsigned pack; + uint64_t sum; + + if (f[i].numUnpackStreams == 0) + continue; + + sum = 0; + if (type == kSize) { + for (pack = 1; pack < f[i].numUnpackStreams; pack++) { + if (parse_7zip_uint64(a, usizes) < 0) + return (-1); + sum += *usizes++; + } + } + *usizes++ = folder_uncompressed_size(&f[i]) - sum; + } + + if (type == kSize) { + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + type = *p; + } + + for (i = 0; i < unpack_streams; i++) { + ss->digestsDefined[i] = 0; + ss->digests[i] = 0; + } + + numDigests = 0; + for (i = 0; i < numFolders; i++) { + if (f[i].numUnpackStreams != 1 || !f[i].digest_defined) + numDigests += (uint32_t)f[i].numUnpackStreams; + } + + if (type == kCRC) { + struct _7z_digests tmpDigests; + unsigned char *digestsDefined = ss->digestsDefined; + uint32_t * digests = ss->digests; + int di = 0; + + memset(&tmpDigests, 0, sizeof(tmpDigests)); + if (read_Digests(a, &(tmpDigests), numDigests) < 0) { + free_Digest(&tmpDigests); + return (-1); + } + for (i = 0; i < numFolders; i++) { + if (f[i].numUnpackStreams == 1 && f[i].digest_defined) { + *digestsDefined++ = 1; + *digests++ = f[i].digest; + } else { + unsigned j; + + for (j = 0; j < f[i].numUnpackStreams; + j++, di++) { + *digestsDefined++ = + tmpDigests.defineds[di]; + *digests++ = + tmpDigests.digests[di]; + } + } + } + free_Digest(&tmpDigests); + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + type = *p; + } + + /* + * Must be kEnd. + */ + if (type != kEnd) + return (-1); + return (0); +} + +static void +free_StreamsInfo(struct _7z_stream_info *si) +{ + free_PackInfo(&(si->pi)); + free_CodersInfo(&(si->ci)); + free_SubStreamsInfo(&(si->ss)); +} + +static int +read_StreamsInfo(struct archive_read *a, struct _7z_stream_info *si) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + const unsigned char *p; + unsigned i; + + memset(si, 0, sizeof(*si)); + + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + if (*p == kPackInfo) { + uint64_t packPos; + + if (read_PackInfo(a, &(si->pi)) < 0) + return (-1); + + if (si->pi.positions == NULL || si->pi.sizes == NULL) + return (-1); + /* + * Calculate packed stream positions. + */ + packPos = si->pi.pos; + for (i = 0; i < si->pi.numPackStreams; i++) { + si->pi.positions[i] = packPos; + packPos += si->pi.sizes[i]; + if (packPos > zip->header_offset) + return (-1); + } + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + } + if (*p == kUnPackInfo) { + uint32_t packIndex; + struct _7z_folder *f; + + if (read_CodersInfo(a, &(si->ci)) < 0) + return (-1); + + /* + * Calculate packed stream indexes. + */ + packIndex = 0; + f = si->ci.folders; + for (i = 0; i < si->ci.numFolders; i++) { + f[i].packIndex = packIndex; + packIndex += (uint32_t)f[i].numPackedStreams; + if (packIndex > si->pi.numPackStreams) + return (-1); + } + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + } + + if (*p == kSubStreamsInfo) { + if (read_SubStreamsInfo(a, &(si->ss), + si->ci.folders, (size_t)si->ci.numFolders) < 0) + return (-1); + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + } + + /* + * Must be kEnd. + */ + if (*p != kEnd) + return (-1); + return (0); +} + +static void +free_Header(struct _7z_header_info *h) +{ + free(h->emptyStreamBools); + free(h->emptyFileBools); + free(h->antiBools); + free(h->attrBools); +} + +static int +read_Header(struct archive_read *a, struct _7z_header_info *h, + int check_header_id) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + const unsigned char *p; + struct _7z_folder *folders; + struct _7z_stream_info *si = &(zip->si); + struct _7zip_entry *entries; + uint32_t folderIndex, indexInFolder; + unsigned i; + int eindex, empty_streams, sindex; + + if (check_header_id) { + /* + * Read Header. + */ + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + if (*p != kHeader) + return (-1); + } + + /* + * Read ArchiveProperties. + */ + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + if (*p == kArchiveProperties) { + for (;;) { + uint64_t size; + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + if (*p == 0) + break; + if (parse_7zip_uint64(a, &size) < 0) + return (-1); + } + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + } + + /* + * Read MainStreamsInfo. + */ + if (*p == kMainStreamsInfo) { + if (read_StreamsInfo(a, &(zip->si)) < 0) + return (-1); + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + } + if (*p == kEnd) + return (0); + + /* + * Read FilesInfo. + */ + if (*p != kFilesInfo) + return (-1); + + if (parse_7zip_uint64(a, &(zip->numFiles)) < 0) + return (-1); + if (UMAX_ENTRY < zip->numFiles) + return (-1); + + zip->entries = calloc((size_t)zip->numFiles, sizeof(*zip->entries)); + if (zip->entries == NULL) + return (-1); + entries = zip->entries; + + empty_streams = 0; + for (;;) { + int type; + uint64_t size; + size_t ll; + + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + type = *p; + if (type == kEnd) + break; + + if (parse_7zip_uint64(a, &size) < 0) + return (-1); + if (zip->header_bytes_remaining < size) + return (-1); + ll = (size_t)size; + + switch (type) { + case kEmptyStream: + if (h->emptyStreamBools != NULL) + return (-1); + h->emptyStreamBools = calloc((size_t)zip->numFiles, + sizeof(*h->emptyStreamBools)); + if (h->emptyStreamBools == NULL) + return (-1); + if (read_Bools( + a, h->emptyStreamBools, (size_t)zip->numFiles) < 0) + return (-1); + empty_streams = 0; + for (i = 0; i < zip->numFiles; i++) { + if (h->emptyStreamBools[i]) + empty_streams++; + } + break; + case kEmptyFile: + if (empty_streams <= 0) { + /* Unexcepted sequence. Skip this. */ + if (header_bytes(a, ll) == NULL) + return (-1); + break; + } + if (h->emptyFileBools != NULL) + return (-1); + h->emptyFileBools = calloc(empty_streams, + sizeof(*h->emptyFileBools)); + if (h->emptyFileBools == NULL) + return (-1); + if (read_Bools(a, h->emptyFileBools, empty_streams) < 0) + return (-1); + break; + case kAnti: + if (empty_streams <= 0) { + /* Unexcepted sequence. Skip this. */ + if (header_bytes(a, ll) == NULL) + return (-1); + break; + } + if (h->antiBools != NULL) + return (-1); + h->antiBools = calloc(empty_streams, + sizeof(*h->antiBools)); + if (h->antiBools == NULL) + return (-1); + if (read_Bools(a, h->antiBools, empty_streams) < 0) + return (-1); + break; + case kCTime: + case kATime: + case kMTime: + if (read_Times(a, h, type) < 0) + return (-1); + break; + case kName: + { + unsigned char *np; + size_t nl, nb; + + /* Skip one byte. */ + if ((p = header_bytes(a, 1)) == NULL) + return (-1); + ll--; + + if ((ll & 1) || ll < zip->numFiles * 4) + return (-1); + + if (zip->entry_names != NULL) + return (-1); + zip->entry_names = malloc(ll); + if (zip->entry_names == NULL) + return (-1); + np = zip->entry_names; + nb = ll; + /* + * Copy whole file names. + * NOTE: This loop prevents from expanding + * the uncompressed buffer in order not to + * use extra memory resource. + */ + while (nb) { + size_t b; + if (nb > UBUFF_SIZE) + b = UBUFF_SIZE; + else + b = nb; + if ((p = header_bytes(a, b)) == NULL) + return (-1); + memcpy(np, p, b); + np += b; + nb -= b; + } + np = zip->entry_names; + nl = ll; + + for (i = 0; i < zip->numFiles; i++) { + entries[i].utf16name = np; +#if defined(_WIN32) && !defined(__CYGWIN__) && defined(_DEBUG) + entries[i].wname = (wchar_t *)np; +#endif + + /* Find a terminator. */ + while (nl >= 2 && (np[0] || np[1])) { + np += 2; + nl -= 2; + } + if (nl < 2) + return (-1);/* Terminator not found */ + entries[i].name_len = np - entries[i].utf16name; + np += 2; + nl -= 2; + } + break; + } + case kAttributes: + { + int allAreDefined; + + if ((p = header_bytes(a, 2)) == NULL) + return (-1); + allAreDefined = *p; + if (h->attrBools != NULL) + return (-1); + h->attrBools = calloc((size_t)zip->numFiles, + sizeof(*h->attrBools)); + if (h->attrBools == NULL) + return (-1); + if (allAreDefined) + memset(h->attrBools, 1, (size_t)zip->numFiles); + else { + if (read_Bools(a, h->attrBools, + (size_t)zip->numFiles) < 0) + return (-1); + } + for (i = 0; i < zip->numFiles; i++) { + if (h->attrBools[i]) { + if ((p = header_bytes(a, 4)) == NULL) + return (-1); + entries[i].attr = archive_le32dec(p); + } + } + break; + } + case kDummy: + if (ll == 0) + break; + __LA_FALLTHROUGH; + default: + if (header_bytes(a, ll) == NULL) + return (-1); + break; + } + } + + /* + * Set up entry's attributes. + */ + folders = si->ci.folders; + eindex = sindex = 0; + folderIndex = indexInFolder = 0; + for (i = 0; i < zip->numFiles; i++) { + if (h->emptyStreamBools == NULL || h->emptyStreamBools[i] == 0) + entries[i].flg |= HAS_STREAM; + /* The high 16 bits of attributes is a posix file mode. */ + entries[i].mode = entries[i].attr >> 16; + if (entries[i].flg & HAS_STREAM) { + if ((size_t)sindex >= si->ss.unpack_streams) + return (-1); + if (entries[i].mode == 0) + entries[i].mode = AE_IFREG | 0666; + if (si->ss.digestsDefined[sindex]) + entries[i].flg |= CRC32_IS_SET; + entries[i].ssIndex = sindex; + sindex++; + } else { + int dir; + if (h->emptyFileBools == NULL) + dir = 1; + else { + if (h->emptyFileBools[eindex]) + dir = 0; + else + dir = 1; + eindex++; + } + if (entries[i].mode == 0) { + if (dir) + entries[i].mode = AE_IFDIR | 0777; + else + entries[i].mode = AE_IFREG | 0666; + } else if (dir && + (entries[i].mode & AE_IFMT) != AE_IFDIR) { + entries[i].mode &= ~AE_IFMT; + entries[i].mode |= AE_IFDIR; + } + if ((entries[i].mode & AE_IFMT) == AE_IFDIR && + entries[i].name_len >= 2 && + (entries[i].utf16name[entries[i].name_len-2] != '/' || + entries[i].utf16name[entries[i].name_len-1] != 0)) { + entries[i].utf16name[entries[i].name_len] = '/'; + entries[i].utf16name[entries[i].name_len+1] = 0; + entries[i].name_len += 2; + } + entries[i].ssIndex = -1; + } + if (entries[i].attr & 0x01) + entries[i].mode &= ~0222;/* Read only. */ + + if ((entries[i].flg & HAS_STREAM) == 0 && indexInFolder == 0) { + /* + * The entry is an empty file or a directory file, + * those both have no contents. + */ + entries[i].folderIndex = -1; + continue; + } + if (indexInFolder == 0) { + for (;;) { + if (folderIndex >= si->ci.numFolders) + return (-1); + if (folders[folderIndex].numUnpackStreams) + break; + folderIndex++; + } + } + entries[i].folderIndex = folderIndex; + if ((entries[i].flg & HAS_STREAM) == 0) + continue; + indexInFolder++; + if (indexInFolder >= folders[folderIndex].numUnpackStreams) { + folderIndex++; + indexInFolder = 0; + } + } + + return (0); +} + +#define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) +static void +fileTimeToUtc(uint64_t fileTime, time_t *timep, long *ns) +{ + + if (fileTime >= EPOC_TIME) { + fileTime -= EPOC_TIME; + /* milli seconds base */ + *timep = (time_t)(fileTime / 10000000); + /* nano seconds base */ + *ns = (long)(fileTime % 10000000) * 100; + } else { + *timep = 0; + *ns = 0; + } +} + +static int +read_Times(struct archive_read *a, struct _7z_header_info *h, int type) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + const unsigned char *p; + struct _7zip_entry *entries = zip->entries; + unsigned char *timeBools; + int allAreDefined; + unsigned i; + + timeBools = calloc((size_t)zip->numFiles, sizeof(*timeBools)); + if (timeBools == NULL) + return (-1); + + /* Read allAreDefined. */ + if ((p = header_bytes(a, 1)) == NULL) + goto failed; + allAreDefined = *p; + if (allAreDefined) + memset(timeBools, 1, (size_t)zip->numFiles); + else { + if (read_Bools(a, timeBools, (size_t)zip->numFiles) < 0) + goto failed; + } + + /* Read external. */ + if ((p = header_bytes(a, 1)) == NULL) + goto failed; + if (*p) { + if (parse_7zip_uint64(a, &(h->dataIndex)) < 0) + goto failed; + if (UMAX_ENTRY < h->dataIndex) + goto failed; + } + + for (i = 0; i < zip->numFiles; i++) { + if (!timeBools[i]) + continue; + if ((p = header_bytes(a, 8)) == NULL) + goto failed; + switch (type) { + case kCTime: + fileTimeToUtc(archive_le64dec(p), + &(entries[i].ctime), + &(entries[i].ctime_ns)); + entries[i].flg |= CTIME_IS_SET; + break; + case kATime: + fileTimeToUtc(archive_le64dec(p), + &(entries[i].atime), + &(entries[i].atime_ns)); + entries[i].flg |= ATIME_IS_SET; + break; + case kMTime: + fileTimeToUtc(archive_le64dec(p), + &(entries[i].mtime), + &(entries[i].mtime_ns)); + entries[i].flg |= MTIME_IS_SET; + break; + } + } + + free(timeBools); + return (0); +failed: + free(timeBools); + return (-1); +} + +static int +decode_encoded_header_info(struct archive_read *a, struct _7z_stream_info *si) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + + errno = 0; + if (read_StreamsInfo(a, si) < 0) { + if (errno == ENOMEM) + archive_set_error(&a->archive, -1, + "Couldn't allocate memory"); + else + archive_set_error(&a->archive, -1, + "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); + } + + if (si->pi.numPackStreams == 0 || si->ci.numFolders == 0) { + archive_set_error(&a->archive, -1, "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); + } + + if (zip->header_offset < si->pi.pos + si->pi.sizes[0] || + (int64_t)(si->pi.pos + si->pi.sizes[0]) < 0 || + si->pi.sizes[0] == 0 || (int64_t)si->pi.pos < 0) { + archive_set_error(&a->archive, -1, "Malformed Header offset"); + return (ARCHIVE_FATAL); + } + + return (ARCHIVE_OK); +} + +static const unsigned char * +header_bytes(struct archive_read *a, size_t rbytes) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + const unsigned char *p; + + if (zip->header_bytes_remaining < rbytes) + return (NULL); + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + + if (zip->header_is_encoded == 0) { + p = __archive_read_ahead(a, rbytes, NULL); + if (p == NULL) + return (NULL); + zip->header_bytes_remaining -= rbytes; + zip->pack_stream_bytes_unconsumed = rbytes; + } else { + const void *buff; + ssize_t bytes; + + bytes = read_stream(a, &buff, rbytes, rbytes); + if (bytes <= 0) + return (NULL); + zip->header_bytes_remaining -= bytes; + p = buff; + } + + /* Update checksum */ + zip->header_crc32 = crc32(zip->header_crc32, p, (unsigned)rbytes); + return (p); +} + +static int +slurp_central_directory(struct archive_read *a, struct _7zip *zip, + struct _7z_header_info *header) +{ + const unsigned char *p; + uint64_t next_header_offset; + uint64_t next_header_size; + uint32_t next_header_crc; + ssize_t bytes_avail; + int check_header_crc, r; + + if ((p = __archive_read_ahead(a, 32, &bytes_avail)) == NULL) + return (ARCHIVE_FATAL); + + if ((p[0] == 'M' && p[1] == 'Z') || memcmp(p, "\x7F\x45LF", 4) == 0) { + /* This is an executable ? Must be self-extracting... */ + r = skip_sfx(a, bytes_avail); + if (r < ARCHIVE_WARN) + return (r); + if ((p = __archive_read_ahead(a, 32, &bytes_avail)) == NULL) + return (ARCHIVE_FATAL); + } + zip->seek_base += 32; + + if (memcmp(p, _7ZIP_SIGNATURE, 6) != 0) { + archive_set_error(&a->archive, -1, "Not 7-Zip archive file"); + return (ARCHIVE_FATAL); + } + + /* CRC check. */ + if (crc32(0, (const unsigned char *)p + 12, 20) + != archive_le32dec(p + 8)) { +#ifdef DONT_FAIL_ON_CRC_ERROR + archive_set_error(&a->archive, -1, "Header CRC error"); + return (ARCHIVE_FATAL); +#endif + } + + next_header_offset = archive_le64dec(p + 12); + next_header_size = archive_le64dec(p + 20); + next_header_crc = archive_le32dec(p + 28); + + if (next_header_size == 0) + /* There is no entry in an archive file. */ + return (ARCHIVE_EOF); + + if (((int64_t)next_header_offset) < 0) { + archive_set_error(&a->archive, -1, "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); + } + __archive_read_consume(a, 32); + if (next_header_offset != 0) { + if (bytes_avail >= (ssize_t)next_header_offset) + __archive_read_consume(a, next_header_offset); + else if (__archive_read_seek(a, + next_header_offset + zip->seek_base, SEEK_SET) < 0) + return (ARCHIVE_FATAL); + } + zip->stream_offset = next_header_offset; + zip->header_offset = next_header_offset; + zip->header_bytes_remaining = next_header_size; + zip->header_crc32 = 0; + zip->header_is_encoded = 0; + zip->header_is_being_read = 1; + zip->has_encrypted_entries = 0; + check_header_crc = 1; + + if ((p = header_bytes(a, 1)) == NULL) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + /* Parse ArchiveProperties. */ + switch (p[0]) { + case kEncodedHeader: + /* + * The archive has an encoded header and we have to decode it + * in order to parse the header correctly. + */ + r = decode_encoded_header_info(a, &(zip->si)); + + /* Check the EncodedHeader CRC.*/ + if (r == 0 && zip->header_crc32 != next_header_crc) { + archive_set_error(&a->archive, -1, +#ifndef DONT_FAIL_ON_CRC_ERROR + "Damaged 7-Zip archive"); + r = -1; +#endif + } + if (r == 0) { + if (zip->si.ci.folders[0].digest_defined) + next_header_crc = zip->si.ci.folders[0].digest; + else + check_header_crc = 0; + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + r = setup_decode_folder(a, zip->si.ci.folders, 1); + if (r == 0) { + zip->header_bytes_remaining = + zip->folder_outbytes_remaining; + r = seek_pack(a); + } + } + /* Clean up StreamsInfo. */ + free_StreamsInfo(&(zip->si)); + memset(&(zip->si), 0, sizeof(zip->si)); + if (r < 0) + return (ARCHIVE_FATAL); + zip->header_is_encoded = 1; + zip->header_crc32 = 0; + /* FALL THROUGH */ + case kHeader: + /* + * Parse the header. + */ + errno = 0; + r = read_Header(a, header, zip->header_is_encoded); + if (r < 0) { + if (errno == ENOMEM) + archive_set_error(&a->archive, -1, + "Couldn't allocate memory"); + else + archive_set_error(&a->archive, -1, + "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } + + /* + * Must be kEnd. + */ + if ((p = header_bytes(a, 1)) == NULL ||*p != kEnd) { + archive_set_error(&a->archive, -1, + "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); + } + + /* Check the Header CRC.*/ + if (check_header_crc && zip->header_crc32 != next_header_crc) { +#ifndef DONT_FAIL_ON_CRC_ERROR + archive_set_error(&a->archive, -1, + "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); +#endif + } + break; + default: + archive_set_error(&a->archive, -1, + "Unexpected Property ID = %X", p[0]); + return (ARCHIVE_FATAL); + } + + /* Clean up variables be used for decoding the archive header */ + zip->pack_stream_remaining = 0; + zip->pack_stream_index = 0; + zip->folder_outbytes_remaining = 0; + zip->uncompressed_buffer_bytes_remaining = 0; + zip->pack_stream_bytes_unconsumed = 0; + zip->header_is_being_read = 0; + + return (ARCHIVE_OK); +} + +static ssize_t +get_uncompressed_data(struct archive_read *a, const void **buff, size_t size, + size_t minimum) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + ssize_t bytes_avail; + + if (zip->codec == _7Z_COPY && zip->codec2 == (unsigned long)-1) { + /* Copy mode. */ + + *buff = __archive_read_ahead(a, minimum, &bytes_avail); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file data"); + return (ARCHIVE_FATAL); + } + if ((size_t)bytes_avail > + zip->uncompressed_buffer_bytes_remaining) + bytes_avail = (ssize_t) + zip->uncompressed_buffer_bytes_remaining; + if ((size_t)bytes_avail > size) + bytes_avail = (ssize_t)size; + + zip->pack_stream_bytes_unconsumed = bytes_avail; + } else if (zip->uncompressed_buffer_pointer == NULL) { + /* Decompression has failed. */ + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } else { + /* Packed mode. */ + if (minimum > zip->uncompressed_buffer_bytes_remaining) { + /* + * If remaining uncompressed data size is less than + * the minimum size, fill the buffer up to the + * minimum size. + */ + if (extract_pack_stream(a, minimum) < 0) + return (ARCHIVE_FATAL); + } + if (size > zip->uncompressed_buffer_bytes_remaining) + bytes_avail = (ssize_t) + zip->uncompressed_buffer_bytes_remaining; + else + bytes_avail = (ssize_t)size; + *buff = zip->uncompressed_buffer_pointer; + zip->uncompressed_buffer_pointer += bytes_avail; + } + zip->uncompressed_buffer_bytes_remaining -= bytes_avail; + return (bytes_avail); +} + +static ssize_t +extract_pack_stream(struct archive_read *a, size_t minimum) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + ssize_t bytes_avail; + int r; + + if (zip->codec == _7Z_COPY && zip->codec2 == (unsigned long)-1) { + if (minimum == 0) + minimum = 1; + if (__archive_read_ahead(a, minimum, &bytes_avail) == NULL + || bytes_avail <= 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + if ((uint64_t)bytes_avail > zip->pack_stream_inbytes_remaining) + bytes_avail = (ssize_t)zip->pack_stream_inbytes_remaining; + zip->pack_stream_inbytes_remaining -= bytes_avail; + if ((uint64_t)bytes_avail > zip->folder_outbytes_remaining) + bytes_avail = (ssize_t)zip->folder_outbytes_remaining; + zip->folder_outbytes_remaining -= bytes_avail; + zip->uncompressed_buffer_bytes_remaining = bytes_avail; + return (ARCHIVE_OK); + } + + /* If the buffer hasn't been allocated, allocate it now. */ + if (zip->uncompressed_buffer == NULL) { + zip->uncompressed_buffer_size = UBUFF_SIZE; + if (zip->uncompressed_buffer_size < minimum) { + zip->uncompressed_buffer_size = minimum + 1023; + zip->uncompressed_buffer_size &= ~0x3ff; + } + zip->uncompressed_buffer = + malloc(zip->uncompressed_buffer_size); + if (zip->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for 7-Zip decompression"); + return (ARCHIVE_FATAL); + } + zip->uncompressed_buffer_bytes_remaining = 0; + } else if (zip->uncompressed_buffer_size < minimum || + zip->uncompressed_buffer_bytes_remaining < minimum) { + /* + * Make sure the uncompressed buffer can have bytes + * at least `minimum' bytes. + * NOTE: This case happen when reading the header. + */ + size_t used; + if (zip->uncompressed_buffer_pointer != 0) + used = zip->uncompressed_buffer_pointer - + zip->uncompressed_buffer; + else + used = 0; + if (zip->uncompressed_buffer_size < minimum) { + /* + * Expand the uncompressed buffer up to + * the minimum size. + */ + void *p; + size_t new_size; + + new_size = minimum + 1023; + new_size &= ~0x3ff; + p = realloc(zip->uncompressed_buffer, new_size); + if (p == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for 7-Zip decompression"); + return (ARCHIVE_FATAL); + } + zip->uncompressed_buffer = (unsigned char *)p; + zip->uncompressed_buffer_size = new_size; + } + /* + * Move unconsumed bytes to the head. + */ + if (used) { + memmove(zip->uncompressed_buffer, + zip->uncompressed_buffer + used, + zip->uncompressed_buffer_bytes_remaining); + } + } else + zip->uncompressed_buffer_bytes_remaining = 0; + zip->uncompressed_buffer_pointer = NULL; + for (;;) { + size_t bytes_in, bytes_out; + const void *buff_in; + unsigned char *buff_out; + int end_of_data; + + /* + * Note: '1' here is a performance optimization. + * Recall that the decompression layer returns a count of + * available bytes; asking for more than that forces the + * decompressor to combine reads by copying data. + */ + buff_in = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + + buff_out = zip->uncompressed_buffer + + zip->uncompressed_buffer_bytes_remaining; + bytes_out = zip->uncompressed_buffer_size + - zip->uncompressed_buffer_bytes_remaining; + bytes_in = bytes_avail; + if (bytes_in > zip->pack_stream_inbytes_remaining) + bytes_in = (size_t)zip->pack_stream_inbytes_remaining; + /* Drive decompression. */ + r = decompress(a, zip, buff_out, &bytes_out, + buff_in, &bytes_in); + switch (r) { + case ARCHIVE_OK: + end_of_data = 0; + break; + case ARCHIVE_EOF: + end_of_data = 1; + break; + default: + return (ARCHIVE_FATAL); + } + zip->pack_stream_inbytes_remaining -= bytes_in; + if (bytes_out > zip->folder_outbytes_remaining) + bytes_out = (size_t)zip->folder_outbytes_remaining; + zip->folder_outbytes_remaining -= bytes_out; + zip->uncompressed_buffer_bytes_remaining += bytes_out; + zip->pack_stream_bytes_unconsumed = bytes_in; + + /* + * Continue decompression until uncompressed_buffer is full. + */ + if (zip->uncompressed_buffer_bytes_remaining == + zip->uncompressed_buffer_size) + break; + if (zip->codec2 == _7Z_X86 && zip->odd_bcj_size && + zip->uncompressed_buffer_bytes_remaining + 5 > + zip->uncompressed_buffer_size) + break; + if (zip->pack_stream_inbytes_remaining == 0 && + zip->folder_outbytes_remaining == 0) + break; + if (end_of_data || (bytes_in == 0 && bytes_out == 0)) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } + read_consume(a); + } + if (zip->uncompressed_buffer_bytes_remaining < minimum) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } + zip->uncompressed_buffer_pointer = zip->uncompressed_buffer; + return (ARCHIVE_OK); +} + +static int +seek_pack(struct archive_read *a) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + int64_t pack_offset; + + if (zip->pack_stream_remaining <= 0) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } + zip->pack_stream_inbytes_remaining = + zip->si.pi.sizes[zip->pack_stream_index]; + pack_offset = zip->si.pi.positions[zip->pack_stream_index]; + if (zip->stream_offset != pack_offset) { + if (0 > __archive_read_seek(a, pack_offset + zip->seek_base, + SEEK_SET)) + return (ARCHIVE_FATAL); + zip->stream_offset = pack_offset; + } + zip->pack_stream_index++; + zip->pack_stream_remaining--; + return (ARCHIVE_OK); +} + +static ssize_t +read_stream(struct archive_read *a, const void **buff, size_t size, + size_t minimum) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + uint64_t skip_bytes = 0; + ssize_t r; + + if (zip->uncompressed_buffer_bytes_remaining == 0) { + if (zip->pack_stream_inbytes_remaining > 0) { + r = extract_pack_stream(a, 0); + if (r < 0) + return (r); + return (get_uncompressed_data(a, buff, size, minimum)); + } else if (zip->folder_outbytes_remaining > 0) { + /* Extract a remaining pack stream. */ + r = extract_pack_stream(a, 0); + if (r < 0) + return (r); + return (get_uncompressed_data(a, buff, size, minimum)); + } + } else + return (get_uncompressed_data(a, buff, size, minimum)); + + /* + * Current pack stream has been consumed. + */ + if (zip->pack_stream_remaining == 0) { + if (zip->header_is_being_read) { + /* Invalid sequence. This might happen when + * reading a malformed archive. */ + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); + } + + /* + * All current folder's pack streams have been + * consumed. Switch to next folder. + */ + if (zip->folder_index == 0 && + (zip->si.ci.folders[zip->entry->folderIndex].skipped_bytes + || zip->folder_index != zip->entry->folderIndex)) { + zip->folder_index = zip->entry->folderIndex; + skip_bytes = + zip->si.ci.folders[zip->folder_index].skipped_bytes; + } + + if (zip->folder_index >= zip->si.ci.numFolders) { + /* + * We have consumed all folders and its pack streams. + */ + *buff = NULL; + return (0); + } + r = setup_decode_folder(a, + &(zip->si.ci.folders[zip->folder_index]), 0); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + zip->folder_index++; + } + + /* + * Switch to next pack stream. + */ + r = seek_pack(a); + if (r < 0) + return (r); + + /* Extract a new pack stream. */ + r = extract_pack_stream(a, 0); + if (r < 0) + return (r); + + /* + * Skip the bytes we already has skipped in skip_stream(). + */ + while (skip_bytes) { + ssize_t skipped; + + if (zip->uncompressed_buffer_bytes_remaining == 0) { + if (zip->pack_stream_inbytes_remaining > 0) { + r = extract_pack_stream(a, 0); + if (r < 0) + return (r); + } else if (zip->folder_outbytes_remaining > 0) { + /* Extract a remaining pack stream. */ + r = extract_pack_stream(a, 0); + if (r < 0) + return (r); + } else { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + } + skipped = get_uncompressed_data( + a, buff, (size_t)skip_bytes, 0); + if (skipped < 0) + return (skipped); + skip_bytes -= skipped; + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + } + + return (get_uncompressed_data(a, buff, size, minimum)); +} + +static int +setup_decode_folder(struct archive_read *a, struct _7z_folder *folder, + int header) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + const struct _7z_coder *coder1, *coder2; + const char *cname = (header)?"archive header":"file content"; + unsigned i; + int r, found_bcj2 = 0; + + /* + * Release the memory which the previous folder used for BCJ2. + */ + for (i = 0; i < 3; i++) { + free(zip->sub_stream_buff[i]); + zip->sub_stream_buff[i] = NULL; + } + + /* + * Initialize a stream reader. + */ + zip->pack_stream_remaining = (unsigned)folder->numPackedStreams; + zip->pack_stream_index = (unsigned)folder->packIndex; + zip->folder_outbytes_remaining = folder_uncompressed_size(folder); + zip->uncompressed_buffer_bytes_remaining = 0; + + /* + * Check coder types. + */ + for (i = 0; i < folder->numCoders; i++) { + switch(folder->coders[i].codec) { + case _7Z_CRYPTO_MAIN_ZIP: + case _7Z_CRYPTO_RAR_29: + case _7Z_CRYPTO_AES_256_SHA_256: { + /* For entry that is associated with this folder, mark + it as encrypted (data+metadata). */ + zip->has_encrypted_entries = 1; + if (a->entry) { + archive_entry_set_is_data_encrypted(a->entry, 1); + archive_entry_set_is_metadata_encrypted(a->entry, 1); + } + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "The %s is encrypted, " + "but currently not supported", cname); + return (ARCHIVE_FATAL); + } + case _7Z_X86_BCJ2: { + found_bcj2++; + break; + } + } + } + /* Now that we've checked for encryption, if there were still no + * encrypted entries found we can say for sure that there are none. + */ + if (zip->has_encrypted_entries == ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW) { + zip->has_encrypted_entries = 0; + } + + if ((folder->numCoders > 2 && !found_bcj2) || found_bcj2 > 1) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "The %s is encoded with many filters, " + "but currently not supported", cname); + return (ARCHIVE_FATAL); + } + coder1 = &(folder->coders[0]); + if (folder->numCoders == 2) + coder2 = &(folder->coders[1]); + else + coder2 = NULL; + + if (found_bcj2) { + /* + * Preparation to decode BCJ2. + * Decoding BCJ2 requires four sources. Those are at least, + * as far as I know, two types of the storage form. + */ + const struct _7z_coder *fc = folder->coders; + static const struct _7z_coder coder_copy = {0, 1, 1, 0, NULL}; + const struct _7z_coder *scoder[3] = + {&coder_copy, &coder_copy, &coder_copy}; + const void *buff; + ssize_t bytes; + unsigned char *b[3] = {NULL, NULL, NULL}; + uint64_t sunpack[3] ={-1, -1, -1}; + size_t s[3] = {0, 0, 0}; + int idx[3] = {0, 1, 2}; + + if (folder->numCoders == 4 && fc[3].codec == _7Z_X86_BCJ2 && + folder->numInStreams == 7 && folder->numOutStreams == 4 && + zip->pack_stream_remaining == 4) { + /* Source type 1 made by 7zr or 7z with -m options. */ + if (folder->bindPairs[0].inIndex == 5) { + /* The form made by 7zr */ + idx[0] = 1; idx[1] = 2; idx[2] = 0; + scoder[1] = &(fc[1]); + scoder[2] = &(fc[0]); + sunpack[1] = folder->unPackSize[1]; + sunpack[2] = folder->unPackSize[0]; + coder1 = &(fc[2]); + } else { + /* + * NOTE: Some patterns do not work. + * work: + * 7z a -m0=BCJ2 -m1=COPY -m2=COPY + * -m3=(any) + * 7z a -m0=BCJ2 -m1=COPY -m2=(any) + * -m3=COPY + * 7z a -m0=BCJ2 -m1=(any) -m2=COPY + * -m3=COPY + * not work: + * other patterns. + * + * We have to handle this like `pipe' or + * our libarchive7s filter frame work, + * decoding the BCJ2 main stream sequentially, + * m3 -> m2 -> m1 -> BCJ2. + * + */ + if (fc[0].codec == _7Z_COPY && + fc[1].codec == _7Z_COPY) + coder1 = &(folder->coders[2]); + else if (fc[0].codec == _7Z_COPY && + fc[2].codec == _7Z_COPY) + coder1 = &(folder->coders[1]); + else if (fc[1].codec == _7Z_COPY && + fc[2].codec == _7Z_COPY) + coder1 = &(folder->coders[0]); + else { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Unsupported form of " + "BCJ2 streams"); + return (ARCHIVE_FATAL); + } + } + coder2 = &(fc[3]); + zip->main_stream_bytes_remaining = + (size_t)folder->unPackSize[2]; + } else if (coder2 != NULL && coder2->codec == _7Z_X86_BCJ2 && + zip->pack_stream_remaining == 4 && + folder->numInStreams == 5 && folder->numOutStreams == 2) { + /* Source type 0 made by 7z */ + zip->main_stream_bytes_remaining = + (size_t)folder->unPackSize[0]; + } else { + /* We got an unexpected form. */ + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Unsupported form of BCJ2 streams"); + return (ARCHIVE_FATAL); + } + + /* Skip the main stream at this time. */ + if ((r = seek_pack(a)) < 0) + return (r); + zip->pack_stream_bytes_unconsumed = + (size_t)zip->pack_stream_inbytes_remaining; + read_consume(a); + + /* Read following three sub streams. */ + for (i = 0; i < 3; i++) { + const struct _7z_coder *coder = scoder[i]; + + if ((r = seek_pack(a)) < 0) { + free(b[0]); free(b[1]); free(b[2]); + return (r); + } + + if (sunpack[i] == (uint64_t)-1) + zip->folder_outbytes_remaining = + zip->pack_stream_inbytes_remaining; + else + zip->folder_outbytes_remaining = sunpack[i]; + + r = init_decompression(a, zip, coder, NULL); + if (r != ARCHIVE_OK) { + free(b[0]); free(b[1]); free(b[2]); + return (ARCHIVE_FATAL); + } + + /* Allocate memory for the decoded data of a sub + * stream. */ + b[i] = malloc((size_t)zip->folder_outbytes_remaining); + if (b[i] == NULL) { + free(b[0]); free(b[1]); free(b[2]); + archive_set_error(&a->archive, ENOMEM, + "No memory for 7-Zip decompression"); + return (ARCHIVE_FATAL); + } + + /* Extract a sub stream. */ + while (zip->pack_stream_inbytes_remaining > 0) { + r = (int)extract_pack_stream(a, 0); + if (r < 0) { + free(b[0]); free(b[1]); free(b[2]); + return (r); + } + bytes = get_uncompressed_data(a, &buff, + zip->uncompressed_buffer_bytes_remaining, + 0); + if (bytes < 0) { + free(b[0]); free(b[1]); free(b[2]); + return ((int)bytes); + } + memcpy(b[i]+s[i], buff, bytes); + s[i] += bytes; + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + } + } + + /* Set the sub streams to the right place. */ + for (i = 0; i < 3; i++) { + zip->sub_stream_buff[i] = b[idx[i]]; + zip->sub_stream_size[i] = s[idx[i]]; + zip->sub_stream_bytes_remaining[i] = s[idx[i]]; + } + + /* Allocate memory used for decoded main stream bytes. */ + if (zip->tmp_stream_buff == NULL) { + zip->tmp_stream_buff_size = 32 * 1024; + zip->tmp_stream_buff = + malloc(zip->tmp_stream_buff_size); + if (zip->tmp_stream_buff == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for 7-Zip decompression"); + return (ARCHIVE_FATAL); + } + } + zip->tmp_stream_bytes_avail = 0; + zip->tmp_stream_bytes_remaining = 0; + zip->odd_bcj_size = 0; + zip->bcj2_outPos = 0; + + /* + * Reset a stream reader in order to read the main stream + * of BCJ2. + */ + zip->pack_stream_remaining = 1; + zip->pack_stream_index = (unsigned)folder->packIndex; + zip->folder_outbytes_remaining = + folder_uncompressed_size(folder); + zip->uncompressed_buffer_bytes_remaining = 0; + } + + /* + * Initialize the decompressor for the new folder's pack streams. + */ + r = init_decompression(a, zip, coder1, coder2); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + return (ARCHIVE_OK); +} + +static int64_t +skip_stream(struct archive_read *a, size_t skip_bytes) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + const void *p; + int64_t skipped_bytes; + size_t bytes = skip_bytes; + + if (zip->folder_index == 0) { + /* + * Optimization for a list mode. + * Avoid unnecessary decoding operations. + */ + zip->si.ci.folders[zip->entry->folderIndex].skipped_bytes + += skip_bytes; + return (skip_bytes); + } + + while (bytes) { + skipped_bytes = read_stream(a, &p, bytes, 0); + if (skipped_bytes < 0) + return (skipped_bytes); + if (skipped_bytes == 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + bytes -= (size_t)skipped_bytes; + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + } + return (skip_bytes); +} + +/* + * Brought from LZMA SDK. + * + * Bra86.c -- Converter for x86 code (BCJ) + * 2008-10-04 : Igor Pavlov : Public domain + * + */ + +#define Test86MSByte(b) ((b) == 0 || (b) == 0xFF) + +static void +x86_Init(struct _7zip *zip) +{ + zip->bcj_state = 0; + zip->bcj_prevPosT = (size_t)0 - 1; + zip->bcj_prevMask = 0; + zip->bcj_ip = 5; +} + +static size_t +x86_Convert(struct _7zip *zip, uint8_t *data, size_t size) +{ + static const uint8_t kMaskToAllowedStatus[8] = {1, 1, 1, 0, 1, 0, 0, 0}; + static const uint8_t kMaskToBitNumber[8] = {0, 1, 2, 2, 3, 3, 3, 3}; + size_t bufferPos, prevPosT; + uint32_t ip, prevMask; + + if (size < 5) + return 0; + + bufferPos = 0; + prevPosT = zip->bcj_prevPosT; + prevMask = zip->bcj_prevMask; + ip = zip->bcj_ip; + + for (;;) { + uint8_t *p = data + bufferPos; + uint8_t *limit = data + size - 4; + + for (; p < limit; p++) + if ((*p & 0xFE) == 0xE8) + break; + bufferPos = (size_t)(p - data); + if (p >= limit) + break; + prevPosT = bufferPos - prevPosT; + if (prevPosT > 3) + prevMask = 0; + else { + prevMask = (prevMask << ((int)prevPosT - 1)) & 0x7; + if (prevMask != 0) { + unsigned char b = + p[4 - kMaskToBitNumber[prevMask]]; + if (!kMaskToAllowedStatus[prevMask] || + Test86MSByte(b)) { + prevPosT = bufferPos; + prevMask = ((prevMask << 1) & 0x7) | 1; + bufferPos++; + continue; + } + } + } + prevPosT = bufferPos; + + if (Test86MSByte(p[4])) { + uint32_t src = ((uint32_t)p[4] << 24) | + ((uint32_t)p[3] << 16) | ((uint32_t)p[2] << 8) | + ((uint32_t)p[1]); + uint32_t dest; + for (;;) { + uint8_t b; + int b_index; + + dest = src - (ip + (uint32_t)bufferPos); + if (prevMask == 0) + break; + b_index = kMaskToBitNumber[prevMask] * 8; + b = (uint8_t)(dest >> (24 - b_index)); + if (!Test86MSByte(b)) + break; + src = dest ^ ((1 << (32 - b_index)) - 1); + } + p[4] = (uint8_t)(~(((dest >> 24) & 1) - 1)); + p[3] = (uint8_t)(dest >> 16); + p[2] = (uint8_t)(dest >> 8); + p[1] = (uint8_t)dest; + bufferPos += 5; + } else { + prevMask = ((prevMask << 1) & 0x7) | 1; + bufferPos++; + } + } + zip->bcj_prevPosT = prevPosT; + zip->bcj_prevMask = prevMask; + zip->bcj_ip += (uint32_t)bufferPos; + return (bufferPos); +} + +static void +arm_Init(struct _7zip *zip) +{ + zip->bcj_ip = 8; +} + +static size_t +arm_Convert(struct _7zip *zip, uint8_t *buf, size_t size) +{ + // This function was adapted from + // static size_t bcj_arm(struct xz_dec_bcj *s, uint8_t *buf, size_t size) + // in https://git.tukaani.org/xz-embedded.git + + /* + * Branch/Call/Jump (BCJ) filter decoders + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <https://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + + size_t i; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 4) { + if (buf[i + 3] == 0xEB) { + // Calculate the transformed addr. + addr = (uint32_t)buf[i] | ((uint32_t)buf[i + 1] << 8) + | ((uint32_t)buf[i + 2] << 16); + addr <<= 2; + addr -= zip->bcj_ip + (uint32_t)i; + addr >>= 2; + + // Store the transformed addr in buf. + buf[i] = (uint8_t)addr; + buf[i + 1] = (uint8_t)(addr >> 8); + buf[i + 2] = (uint8_t)(addr >> 16); + } + } + + zip->bcj_ip += (uint32_t)i; + + return i; +} + +static size_t +arm64_Convert(struct _7zip *zip, uint8_t *buf, size_t size) +{ + // This function was adapted from + // static size_t bcj_arm64(struct xz_dec_bcj *s, uint8_t *buf, size_t size) + // in https://git.tukaani.org/xz-embedded.git + + /* + * Branch/Call/Jump (BCJ) filter decoders + * + * Authors: Lasse Collin <lasse.collin@tukaani.org> + * Igor Pavlov <https://7-zip.org/> + * + * This file has been put into the public domain. + * You can do whatever you want with this file. + */ + + size_t i; + uint32_t instr; + uint32_t addr; + + for (i = 0; i + 4 <= size; i += 4) { + instr = (uint32_t)buf[i] + | ((uint32_t)buf[i+1] << 8) + | ((uint32_t)buf[i+2] << 16) + | ((uint32_t)buf[i+3] << 24); + + if ((instr >> 26) == 0x25) { + /* BL instruction */ + addr = instr - ((zip->bcj_ip + (uint32_t)i) >> 2); + instr = 0x94000000 | (addr & 0x03FFFFFF); + + buf[i] = (uint8_t)instr; + buf[i+1] = (uint8_t)(instr >> 8); + buf[i+2] = (uint8_t)(instr >> 16); + buf[i+3] = (uint8_t)(instr >> 24); + } else if ((instr & 0x9F000000) == 0x90000000) { + /* ADRP instruction */ + addr = ((instr >> 29) & 3) | ((instr >> 3) & 0x1FFFFC); + + /* Only convert values in the range +/-512 MiB. */ + if ((addr + 0x020000) & 0x1C0000) + continue; + + addr -= (zip->bcj_ip + (uint32_t)i) >> 12; + + instr &= 0x9000001F; + instr |= (addr & 3) << 29; + instr |= (addr & 0x03FFFC) << 3; + instr |= (0U - (addr & 0x020000)) & 0xE00000; + + buf[i] = (uint8_t)instr; + buf[i+1] = (uint8_t)(instr >> 8); + buf[i+2] = (uint8_t)(instr >> 16); + buf[i+3] = (uint8_t)(instr >> 24); + } + } + + zip->bcj_ip += (uint32_t)i; + + return i; +} + +/* + * Brought from LZMA SDK. + * + * Bcj2.c -- Converter for x86 code (BCJ2) + * 2008-10-04 : Igor Pavlov : Public domain + * + */ + +#define SZ_ERROR_DATA ARCHIVE_FAILED + +#define IsJcc(b0, b1) ((b0) == 0x0F && ((b1) & 0xF0) == 0x80) +#define IsJ(b0, b1) ((b1 & 0xFE) == 0xE8 || IsJcc(b0, b1)) + +#define kNumTopBits 24 +#define kTopValue ((uint32_t)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_READ_BYTE (*buffer++) +#define RC_TEST { if (buffer == bufferLim) return SZ_ERROR_DATA; } +#define RC_INIT2 zip->bcj2_code = 0; zip->bcj2_range = 0xFFFFFFFF; \ + { int ii; for (ii = 0; ii < 5; ii++) { RC_TEST; zip->bcj2_code = (zip->bcj2_code << 8) | RC_READ_BYTE; }} + +#define NORMALIZE if (zip->bcj2_range < kTopValue) { RC_TEST; zip->bcj2_range <<= 8; zip->bcj2_code = (zip->bcj2_code << 8) | RC_READ_BYTE; } + +#define IF_BIT_0(p) ttt = *(p); bound = (zip->bcj2_range >> kNumBitModelTotalBits) * ttt; if (zip->bcj2_code < bound) +#define UPDATE_0(p) zip->bcj2_range = bound; *(p) = (CProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); NORMALIZE; +#define UPDATE_1(p) zip->bcj2_range -= bound; zip->bcj2_code -= bound; *(p) = (CProb)(ttt - (ttt >> kNumMoveBits)); NORMALIZE; + +static ssize_t +Bcj2_Decode(struct _7zip *zip, uint8_t *outBuf, size_t outSize) +{ + size_t inPos = 0, outPos = 0; + const uint8_t *buf0, *buf1, *buf2, *buf3; + size_t size0, size1, size2, size3; + const uint8_t *buffer, *bufferLim; + unsigned int i, j; + + size0 = zip->tmp_stream_bytes_remaining; + buf0 = zip->tmp_stream_buff + zip->tmp_stream_bytes_avail - size0; + size1 = zip->sub_stream_bytes_remaining[0]; + buf1 = zip->sub_stream_buff[0] + zip->sub_stream_size[0] - size1; + size2 = zip->sub_stream_bytes_remaining[1]; + buf2 = zip->sub_stream_buff[1] + zip->sub_stream_size[1] - size2; + size3 = zip->sub_stream_bytes_remaining[2]; + buf3 = zip->sub_stream_buff[2] + zip->sub_stream_size[2] - size3; + + buffer = buf3; + bufferLim = buffer + size3; + + if (zip->bcj_state == 0) { + /* + * Initialize. + */ + zip->bcj2_prevByte = 0; + for (i = 0; + i < sizeof(zip->bcj2_p) / sizeof(zip->bcj2_p[0]); i++) + zip->bcj2_p[i] = kBitModelTotal >> 1; + RC_INIT2; + zip->bcj_state = 1; + } + + /* + * Gather the odd bytes of a previous call. + */ + for (i = 0; zip->odd_bcj_size > 0 && outPos < outSize; i++) { + outBuf[outPos++] = zip->odd_bcj[i]; + zip->odd_bcj_size--; + } + + if (outSize == 0) { + zip->bcj2_outPos += outPos; + return (outPos); + } + + for (;;) { + uint8_t b; + CProb *prob; + uint32_t bound; + uint32_t ttt; + + size_t limit = size0 - inPos; + if (outSize - outPos < limit) + limit = outSize - outPos; + + if (zip->bcj_state == 1) { + while (limit != 0) { + uint8_t bb = buf0[inPos]; + outBuf[outPos++] = bb; + if (IsJ(zip->bcj2_prevByte, bb)) { + zip->bcj_state = 2; + break; + } + inPos++; + zip->bcj2_prevByte = bb; + limit--; + } + } + + if (limit == 0 || outPos == outSize) + break; + zip->bcj_state = 1; + + b = buf0[inPos++]; + + if (b == 0xE8) + prob = zip->bcj2_p + zip->bcj2_prevByte; + else if (b == 0xE9) + prob = zip->bcj2_p + 256; + else + prob = zip->bcj2_p + 257; + + IF_BIT_0(prob) { + UPDATE_0(prob) + zip->bcj2_prevByte = b; + } else { + uint32_t dest; + const uint8_t *v; + uint8_t out[4]; + + UPDATE_1(prob) + if (b == 0xE8) { + v = buf1; + if (size1 < 4) + return SZ_ERROR_DATA; + buf1 += 4; + size1 -= 4; + } else { + v = buf2; + if (size2 < 4) + return SZ_ERROR_DATA; + buf2 += 4; + size2 -= 4; + } + dest = (((uint32_t)v[0] << 24) | + ((uint32_t)v[1] << 16) | + ((uint32_t)v[2] << 8) | + ((uint32_t)v[3])) - + ((uint32_t)zip->bcj2_outPos + (uint32_t)outPos + 4); + out[0] = (uint8_t)dest; + out[1] = (uint8_t)(dest >> 8); + out[2] = (uint8_t)(dest >> 16); + out[3] = zip->bcj2_prevByte = (uint8_t)(dest >> 24); + + for (i = 0; i < 4 && outPos < outSize; i++) + outBuf[outPos++] = out[i]; + if (i < 4) { + /* + * Save odd bytes which we could not add into + * the output buffer because of out of space. + */ + zip->odd_bcj_size = 4 -i; + for (; i < 4; i++) { + j = i - 4 + (unsigned)zip->odd_bcj_size; + zip->odd_bcj[j] = out[i]; + } + break; + } + } + } + zip->tmp_stream_bytes_remaining -= inPos; + zip->sub_stream_bytes_remaining[0] = size1; + zip->sub_stream_bytes_remaining[1] = size2; + zip->sub_stream_bytes_remaining[2] = bufferLim - buffer; + zip->bcj2_outPos += outPos; + + return ((ssize_t)outPos); +} + |