| /*- |
| * 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 |
| #include <stdio.h> |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #include <time.h> |
| #ifdef HAVE_BZLIB_H |
| #include <bzlib.h> |
| #endif |
| #ifdef HAVE_LZMA_H |
| #include <lzma.h> |
| #endif |
| #ifdef HAVE_ZLIB_H |
| #include <zlib.h> |
| #endif |
| |
| #include "archive.h" |
| #include "archive_entry.h" |
| #include "archive_entry_locale.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 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 |
| |
| struct _7z_digests { |
| unsigned char *defineds; |
| uint32_t *digests; |
| }; |
| |
| |
| struct _7z_folder { |
| uint64_t numCoders; |
| struct { |
| size_t codecIdSize; |
| unsigned char *codecId; |
| 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; |
| uint64_t *positions; |
| }; |
| |
| struct _7z_substream_info { |
| 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; |
| }; |
| |
| /* |
| * 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 0x06F10701 |
| #define _7Z_X86 0x03030103 |
| #define _7Z_POWERPC 0x03030205 |
| #define _7Z_IA64 0x03030401 |
| #define _7Z_ARM 0x03030501 |
| #define _7Z_ARMTHUMB 0x03030701 |
| #define _7Z_SPARC 0x03030805 |
| |
| struct _7zip_entry { |
| size_t name_len; |
| const 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; |
| uint64_t header_offset; |
| 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; |
| size_t entry_bytes_unconsumed; |
| uint64_t entry_bytes_remaining; |
| |
| /* * */ |
| int64_t offset; |
| unsigned folder_index; |
| unsigned pack_stream_index; |
| uint64_t pack_stream_remaining; |
| uint64_t pack_stream_bytes_remaining; |
| uint64_t unpack_stream_bytes_remaining; |
| uint64_t uncompressed_buffer_bytes_remaining; |
| unsigned char *uncompressed_buff_pointer; |
| |
| /* These count the number of bytes actually read for the entry. */ |
| int64_t entry_uncompressed_bytes_read; |
| |
| /* Running CRC32 of the decompressed data */ |
| unsigned long entry_crc32; |
| |
| /* Flags to mark progress of decompression. */ |
| char end_of_entry; |
| |
| unsigned char *uncompressed_buffer; |
| size_t uncompressed_buffer_size; |
| unsigned long codec; |
| #ifdef HAVE_LZMA_H |
| lzma_stream lzstream; |
| int lzstream_valid; |
| #endif |
| #if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) |
| bz_stream bzstream; |
| int bzstream_valid; |
| #endif |
| #ifdef HAVE_ZLIB_H |
| z_stream stream; |
| int stream_valid; |
| #endif |
| |
| struct archive_string_conv *sconv; |
| char format_name[64]; |
| }; |
| |
| 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 unsigned long decode_codec_id(const unsigned char *, size_t); |
| static int decompress(struct archive_read *, struct _7zip *, |
| void *, size_t *, const void *, size_t *); |
| static ssize_t extract_pack_stream(struct archive_read *); |
| 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); |
| static int init_decompression(struct archive_read *, struct _7zip *, |
| struct _7z_folder *); |
| static int parse_7zip_uint64(const unsigned char *, size_t, uint64_t *); |
| static int read_Bools(unsigned char *, size_t, const unsigned char *, |
| size_t); |
| static int read_CodersInfo(struct _7z_coders_info *, |
| const unsigned char *, size_t); |
| static int read_Digests(struct _7z_digests *, size_t, |
| const unsigned char *, size_t); |
| static int read_Folder(struct _7z_folder *, const unsigned char *, |
| size_t); |
| static int read_Header(struct _7zip *, struct _7z_header_info *, |
| const unsigned char *, size_t); |
| static int read_PackInfo(struct _7z_pack_info *, const unsigned char *, |
| size_t); |
| static int read_StreamsInfo(struct _7zip *, struct _7z_stream_info *, |
| const unsigned char *, size_t); |
| static int read_SubStreamsInfo(struct _7z_substream_info *, |
| struct _7z_folder *, size_t, const unsigned char *, |
| size_t); |
| static int read_Times(struct _7zip *, struct _7z_header_info *, int, |
| const unsigned char *, size_t); |
| static ssize_t read_stream(struct archive_read *, const void **, size_t); |
| 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 *); |
| |
| |
| 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); |
| } |
| |
| 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, |
| archive_read_format_7zip_cleanup); |
| |
| if (r != ARCHIVE_OK) |
| free(zip); |
| return (ARCHIVE_OK); |
| } |
| |
| 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', 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') { |
| ssize_t offset = 0x27000; |
| ssize_t window = 4096; |
| ssize_t bytes_avail; |
| while (offset + window <= (0x30000)) { |
| 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 + 6 < buff + bytes_avail) { |
| if (memcmp(p, _7ZIP_SIGNATURE, 6) == 0) |
| return (48); |
| p += 0x100; |
| } |
| offset = p - buff; |
| } |
| } |
| return (0); |
| } |
| |
| 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 > 0x27000 we do not have to call |
| * __archive_read_seek() at this time since we have |
| * alredy had enough data. |
| */ |
| if (bytes_avail > 0x27000) |
| __archive_read_consume(a, 0x27000); |
| else if (__archive_read_seek(a, 0x27000, SEEK_SET) < 0) |
| return (ARCHIVE_FATAL); |
| |
| offset = 0; |
| window = 1; |
| while (offset + window <= 0x30000 - 0x27000) { |
| 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 + 6 < q) { |
| if (memcmp(p, _7ZIP_SIGNATURE, 6) == 0) { |
| struct _7zip *zip = |
| (struct _7zip *)a->format->data; |
| skip = p - (const char *)h; |
| __archive_read_consume(a, skip); |
| zip->seek_base = 0x27000 + offset + skip; |
| return (ARCHIVE_OK); |
| } |
| p += 0x100; |
| } |
| 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; |
| |
| 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 = zip->numFiles; |
| zip->entry = zip->entries; |
| } else { |
| ++zip->entry; |
| } |
| zip_entry = zip->entry; |
| |
| if (zip->entries_remaining <= 0) |
| return ARCHIVE_EOF; |
| --zip->entries_remaining; |
| |
| zip->entry_offset = 0; |
| zip->end_of_entry = 0; |
| zip->entry_uncompressed_bytes_read = 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); |
| } |
| |
| 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 != -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; |
| |
| /* Set up a more descriptive format name. */ |
| sprintf(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; |
| |
| zip = (struct _7zip *)(a->format->data); |
| |
| if (zip->entry_bytes_unconsumed) { |
| __archive_read_consume(a, zip->entry_bytes_unconsumed); |
| zip->offset += zip->entry_bytes_unconsumed; |
| zip->entry_bytes_unconsumed = 0; |
| } |
| |
| /* |
| * If we hit end-of-entry last time, clean up and return |
| * ARCHIVE_EOF this time. |
| */ |
| if (zip->end_of_entry) { |
| *offset = zip->entry_uncompressed_bytes_read; |
| *size = 0; |
| *buff = NULL; |
| return (ARCHIVE_EOF); |
| } |
| |
| bytes = read_stream(a, buff, zip->entry_bytes_remaining); |
| 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, 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]); |
| return (ARCHIVE_WARN); |
| } |
| } |
| |
| *size = bytes; |
| *offset = zip->entry_offset; |
| zip->entry_offset += bytes; |
| zip->entry_uncompressed_bytes_read += bytes; |
| |
| return (ARCHIVE_OK); |
| } |
| |
| 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->entry_bytes_unconsumed) { |
| __archive_read_consume(a, zip->entry_bytes_unconsumed); |
| zip->offset += zip->entry_bytes_unconsumed; |
| zip->entry_bytes_unconsumed = 0; |
| } |
| |
| /* 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, 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); |
| (a->format->data) = NULL; |
| return (ARCHIVE_OK); |
| } |
| |
| #ifdef HAVE_LZMA_H |
| |
| /* |
| * Set an error code and choose an error message |
| */ |
| 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 int |
| init_decompression(struct archive_read *a, struct _7zip *zip, |
| struct _7z_folder *folder) |
| { |
| int r; |
| unsigned long codec; |
| |
| codec = decode_codec_id(folder->coders[0].codecId, |
| folder->coders[0].codecIdSize); |
| |
| switch (zip->codec = codec) { |
| case _7Z_COPY: |
| return (ARCHIVE_OK); |
| |
| 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]; |
| #if LZMA_VERSION < 50000030 |
| lzma_filter *ff; |
| #endif |
| int fi = 0; |
| |
| if (zip->lzstream_valid) { |
| lzma_end(&(zip->lzstream)); |
| zip->lzstream_valid = 0; |
| } |
| |
| /* |
| * NOTE: BCJ+LZMA2 is OK, but it seems BCJ+LZMA does not |
| * correctly work. With BCJ, liblzma did not return all |
| * bytes I expected; it was four or three bytes lost. |
| */ |
| if (folder->numCoders >= 2) { |
| codec = decode_codec_id(folder->coders[1].codecId, |
| folder->coders[1].codecIdSize); |
| |
| filters[fi].options = NULL; |
| switch (codec) { |
| case _7Z_X86: |
| #if 1 |
| if (zip->codec == _7Z_LZMA) { |
| archive_set_error(&a->archive, |
| ARCHIVE_ERRNO_MISC, |
| "LZMA + BCJ is unsupported"); |
| return (ARCHIVE_FAILED); |
| } |
| #endif |
| filters[fi].id = LZMA_FILTER_X86; |
| break; |
| case _7Z_DELTA: |
| filters[fi].id = LZMA_FILTER_DELTA; |
| memset(&delta_opt, 0, sizeof(delta_opt)); |
| delta_opt.type = LZMA_DELTA_TYPE_BYTE; |
| delta_opt.dist = 1; |
| filters[fi].options = &delta_opt; |
| break; |
| /* Following filters have not been tested yet. */ |
| case _7Z_POWERPC: |
| filters[fi].id = LZMA_FILTER_POWERPC; |
| break; |
| case _7Z_IA64: |
| filters[fi].id = LZMA_FILTER_IA64; |
| break; |
| case _7Z_ARM: |
| filters[fi].id = LZMA_FILTER_ARM; |
| break; |
| case _7Z_ARMTHUMB: |
| filters[fi].id = LZMA_FILTER_ARMTHUMB; |
| break; |
| case _7Z_SPARC: |
| filters[fi].id = LZMA_FILTER_SPARC; |
| break; |
| default: |
| archive_set_error(&a->archive, |
| ARCHIVE_ERRNO_MISC, |
| "Unexpected codec ID: %lX", codec); |
| return (ARCHIVE_FAILED); |
| } |
| fi++; |
| } |
| |
| if (zip->codec == _7Z_LZMA2) |
| filters[fi].id = LZMA_FILTER_LZMA2; |
| else |
| filters[fi].id = LZMA_FILTER_LZMA1; |
| filters[fi].options = NULL; |
| #if LZMA_VERSION < 50000030 |
| ff = &filters[fi]; |
| #endif |
| r = lzma_properties_decode(&filters[fi], NULL, |
| folder->coders[0].properties, |
| folder->coders[0].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); |
| #if LZMA_VERSION < 50000030 |
| free(ff->options); |
| #endif |
| 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 (folder->numCoders >= 2) { |
| archive_set_error(&a->archive, |
| ARCHIVE_ERRNO_MISC, |
| "BZ2 + FILTER such as BCJ is unsupported"); |
| return (ARCHIVE_FAILED); |
| } |
| 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_DEFLATE: |
| #ifdef HAVE_ZLIB_H |
| if (folder->numCoders >= 2) { |
| archive_set_error(&a->archive, |
| ARCHIVE_ERRNO_MISC, |
| "DEFLATE + FILTER such as BCJ is unsupported"); |
| return (ARCHIVE_FAILED); |
| } |
| 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: |
| /* TODO: Can we use archive_ppmd7.c ? */ |
| archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, |
| "PPMD codec is unsupported"); |
| return (ARCHIVE_FAILED); |
| case _7Z_X86: |
| 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", codec); |
| return (ARCHIVE_FAILED); |
| default: |
| archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, |
| "Unknown codec ID: %lX", 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) |
| { |
| size_t avail_in, avail_out; |
| int r, ret = ARCHIVE_OK; |
| |
| avail_in = *used; |
| avail_out = *outbytes; |
| |
| switch (zip->codec) { |
| #ifdef HAVE_LZMA_H |
| case _7Z_LZMA: case _7Z_LZMA2: |
| zip->lzstream.next_in = b; |
| zip->lzstream.avail_in = avail_in; |
| zip->lzstream.next_out = buff; |
| zip->lzstream.avail_out = 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); |
| } |
| *used = avail_in - zip->lzstream.avail_in; |
| *outbytes = 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)b; |
| zip->bzstream.avail_in = avail_in; |
| zip->bzstream.next_out = buff; |
| zip->bzstream.avail_out = 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); |
| } |
| *used = avail_in - zip->bzstream.avail_in; |
| *outbytes = avail_out - zip->bzstream.avail_out; |
| break; |
| #endif |
| #ifdef HAVE_ZLIB_H |
| case _7Z_DEFLATE: |
| zip->stream.next_in = (Bytef *)(uintptr_t)b; |
| zip->stream.avail_in = avail_in; |
| zip->stream.next_out = buff; |
| zip->stream.avail_out = 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); |
| } |
| *used = avail_in - zip->stream.avail_in; |
| *outbytes = avail_out - zip->stream.avail_out; |
| break; |
| #endif |
| default: |
| archive_set_error(&(a->archive), |
| ARCHIVE_ERRNO_MISC, |
| "Decompression internal error"); |
| return (ARCHIVE_FAILED); |
| } |
| return (ret); |
| } |
| |
| static int |
| free_decompression(struct archive_read *a, struct _7zip *zip) |
| { |
| int r = ARCHIVE_OK; |
| |
| #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; |
| } |
| } |
| #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; |
| } |
| } |
| #endif |
| return (r); |
| } |
| |
| static int |
| parse_7zip_uint64(const unsigned char *p, size_t len, uint64_t *val) |
| { |
| const unsigned char *_p = p; |
| unsigned char avail, mask; |
| int i; |
| |
| if (len-- == 0) |
| return (-1); |
| avail = *p++; |
| mask = 0x80; |
| *val = 0; |
| for (i = 0; i < 8; i++) { |
| if (avail & mask) { |
| if (len-- == 0) |
| return (-1); |
| *val |= ((uint64_t)*p++) << (8 * i); |
| mask >>= 1; |
| continue; |
| } |
| *val += (avail & (mask -1)) << (8 * i); |
| break; |
| } |
| return (p - _p); |
| } |
| |
| static int |
| read_Bools(unsigned char *data, size_t num, const unsigned char *p, size_t len) |
| { |
| const unsigned char *_p = p; |
| unsigned i, mask = 0, avail; |
| |
| for (i = 0; i < num; i++) { |
| if (mask == 0) { |
| if (len == 0) |
| return (-1); |
| avail = *p++; |
| len--; |
| mask = 0x80; |
| } |
| data[i] = (avail & mask)?1:0; |
| mask >>= 1; |
| } |
| return (p - _p); |
| } |
| |
| static void |
| free_Digest(struct _7z_digests *d) |
| { |
| free(d->defineds); |
| free(d->digests); |
| } |
| |
| static int |
| read_Digests(struct _7z_digests *d, size_t num, const unsigned char *p, |
| size_t len) |
| { |
| const unsigned char *_p = p; |
| unsigned i; |
| |
| memset(d, 0, sizeof(*d)); |
| |
| if (len == 0) |
| return (-1); |
| |
| d->defineds = malloc(num); |
| if (d->defineds == NULL) |
| return (-1); |
| /* |
| * Read Bools. |
| */ |
| len--; |
| if (*p++ == 0) { |
| int r = read_Bools(d->defineds, num, p, len); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| } 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 (len < 4) |
| return (-1); |
| d->digests[i] = archive_le32dec(p); |
| p += 4; |
| len -= 4; |
| } |
| } |
| |
| return (p - _p); |
| } |
| |
| static void |
| free_PackInfo(struct _7z_pack_info *pi) |
| { |
| free(pi->sizes); |
| free(pi->positions); |
| free_Digest(&(pi->digest)); |
| } |
| |
| static int |
| read_PackInfo(struct _7z_pack_info *pi, const unsigned char *p, size_t len) |
| { |
| const unsigned char *_p = p; |
| unsigned i; |
| int r; |
| |
| memset(pi, 0, sizeof(*pi)); |
| |
| if (len < 3 || *p++ != kPackInfo) |
| return (-1); |
| --len; |
| |
| /* |
| * Read PackPos. |
| */ |
| r = parse_7zip_uint64(p, len, &(pi->pos)); |
| if (r < 0) |
| return (r); |
| p += r; |
| len -= r; |
| |
| /* |
| * Read NumPackStreams. |
| */ |
| r = parse_7zip_uint64(p, len, &(pi->numPackStreams)); |
| if (r < 0 || pi->numPackStreams == 0) |
| return (r); |
| p += r; |
| len -= r; |
| |
| /* |
| * Read PackSizes[num] |
| */ |
| if (len >= 1 && *p == kEnd) |
| /* PackSizes[num] are not present. */ |
| return (p - _p + 1); |
| if (len < 1 + pi->numPackStreams || *p++ != kSize) |
| return (-1); |
| --len; |
| pi->sizes = calloc(pi->numPackStreams, sizeof(uint64_t)); |
| pi->positions = calloc(pi->numPackStreams, sizeof(uint64_t)); |
| if (pi->sizes == NULL || pi->positions == NULL) |
| return (-1); |
| |
| for (i = 0; i < pi->numPackStreams; i++) { |
| r = parse_7zip_uint64(p, len, &(pi->sizes[i])); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| } |
| |
| /* |
| * Read PackStreamDigests[num] |
| */ |
| if (len >= 1 && *p == kEnd) { |
| /* PackStreamDigests[num] are not present. */ |
| pi->digest.defineds = |
| calloc(pi->numPackStreams, sizeof(*pi->digest.defineds)); |
| pi->digest.digests = |
| calloc(pi->numPackStreams, sizeof(*pi->digest.digests)); |
| if (pi->digest.defineds == NULL || pi->digest.digests == NULL) |
| return (-1); |
| return (p - _p + 1); |
| } |
| |
| if (len < 1 + pi->numPackStreams || *p++ != kSize) |
| return (-1); |
| --len; |
| |
| r = read_Digests(&(pi->digest), pi->numPackStreams, p, len); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| |
| /* |
| * Must be marked by kEnd. |
| */ |
| if (len == 0 || *p++ != kEnd) |
| return (-1); |
| return (p - _p); |
| } |
| |
| static void |
| free_Folder(struct _7z_folder *f) |
| { |
| unsigned i; |
| |
| if (f->coders) { |
| for (i = 0; i< f->numCoders; i++) { |
| free(f->coders[i].codecId); |
| free(f->coders[i].properties); |
| } |
| free(f->coders); |
| } |
| free(f->bindPairs); |
| free(f->packedStreams); |
| free(f->unPackSize); |
| } |
| |
| static int |
| read_Folder(struct _7z_folder *f, const unsigned char *p, size_t len) |
| { |
| const unsigned char *_p = p; |
| uint64_t numInStreamsTotal = 0; |
| uint64_t numOutStreamsTotal = 0; |
| int r; |
| unsigned i; |
| |
| memset(f, 0, sizeof(*f)); |
| |
| /* |
| * Read NumCoders. |
| */ |
| r = parse_7zip_uint64(p, len, &(f->numCoders)); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| |
| f->coders = calloc(f->numCoders, sizeof(*f->coders)); |
| if (f->coders == NULL) |
| return (-1); |
| for (i = 0; i< f->numCoders; i++) { |
| int simple, attr; |
| |
| if (len == 0) |
| return (-1); |
| /* |
| * 0:3 CodecIdSize |
| * 4: 0 - IsSimple |
| * 1 - Is not Simple |
| * 5: 0 - No Attributes |
| * 1 - There are Attributes; |
| * 7: Must be zero. |
| */ |
| f->coders[i].codecIdSize = *p & 0xf; |
| simple = (*p & 0x10)?0:1; |
| attr = *p & 0x20; |
| if (*p & 0x80) |
| return (-1);/* Not supported. */ |
| p++; |
| len--; |
| |
| /* |
| * Read Decompression Method IDs. |
| */ |
| if (len < f->coders[i].codecIdSize) |
| return (-1); |
| f->coders[i].codecId = |
| calloc(f->coders[i].codecIdSize, |
| sizeof(*f->coders->codecId)); |
| if (f->coders[i].codecId == NULL) |
| return (-1); |
| memcpy(f->coders[i].codecId, p, |
| f->coders[i].codecIdSize); |
| p += f->coders[i].codecIdSize; |
| len -= f->coders[i].codecIdSize; |
| |
| if (simple) { |
| f->coders[i].numInStreams = 1; |
| f->coders[i].numOutStreams = 1; |
| } else { |
| r = parse_7zip_uint64(p, len, |
| &(f->coders[i].numInStreams)); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| r = parse_7zip_uint64(p, len, |
| &(f->coders[i].numOutStreams)); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| } |
| |
| if (attr) { |
| r = parse_7zip_uint64(p, len, |
| &(f->coders[i].propertiesSize)); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| |
| if (len < f->coders[i].propertiesSize) |
| return (-1); |
| f->coders[i].properties = |
| malloc(f->coders[i].propertiesSize); |
| if (f->coders[i].properties == NULL) |
| return (-1); |
| memcpy(f->coders[i].properties, p, |
| f->coders[i].propertiesSize); |
| p += f->coders[i].propertiesSize; |
| len -= 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; |
| f->bindPairs = calloc(f->numBindPairs, sizeof(*f->bindPairs)); |
| if (f->bindPairs == NULL) |
| return (-1); |
| for (i = 0; i < f->numBindPairs; i++) { |
| r = parse_7zip_uint64(p, len, &(f->bindPairs[i].inIndex)); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| r = parse_7zip_uint64(p, len, &(f->bindPairs[i].outIndex)); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| } |
| |
| f->numPackedStreams = numInStreamsTotal - f->numBindPairs; |
| f->packedStreams = |
| calloc(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++) { |
| r = parse_7zip_uint64(p, len, &(f->packedStreams[i])); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| } |
| } |
| f->numInStreams = numInStreamsTotal; |
| f->numOutStreams = numOutStreamsTotal; |
| |
| return (p - _p); |
| } |
| |
| 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 _7z_coders_info *ci, const unsigned char *p, size_t len) |
| { |
| const unsigned char *_p = p; |
| struct _7z_digests digest; |
| unsigned i, external; |
| int r; |
| |
| memset(ci, 0, sizeof(*ci)); |
| memset(&digest, 0, sizeof(digest)); |
| |
| if (len < 3 || *p++ != kUnPackInfo) |
| goto failed; |
| --len; |
| |
| if (len < 3 || *p++ != kFolder) |
| goto failed; |
| --len; |
| |
| /* |
| * Read NumFolders. |
| */ |
| r = parse_7zip_uint64(p, len, &(ci->numFolders)); |
| if (r < 0) |
| goto failed; |
| p += r; |
| len -= r; |
| |
| /* |
| * Read External. |
| */ |
| if (len == 0) |
| goto failed; |
| external = *p++; |
| len --; |
| switch (external) { |
| case 0: |
| ci->folders = calloc(ci->numFolders, sizeof(*ci->folders)); |
| if (ci->folders == NULL) |
| return (-1); |
| for (i = 0; i < ci->numFolders; i++) { |
| r = read_Folder(&(ci->folders[i]), p, len); |
| if (r < 0) |
| goto failed; |
| p += r; |
| len -= r; |
| } |
| break; |
| case 1: |
| r = parse_7zip_uint64(p, len, &(ci->dataStreamIndex)); |
| if (r < 0) |
| return (r); |
| p += r; |
| len -= r; |
| break; |
| } |
| |
| if (len < 1 + ci->numFolders || *p++ != kCodersUnPackSize) |
| goto failed; |
| --len; |
| |
| for (i = 0; i < ci->numFolders; i++) { |
| unsigned j; |
| |
| ci->folders[i].unPackSize = |
| calloc(ci->folders[i].numOutStreams, |
| sizeof(*ci->folders[i].unPackSize)); |
| if (ci->folders[i].unPackSize == NULL) |
| goto failed; |
| for (j = 0; j < ci->folders[i].numOutStreams; j++) { |
| r = parse_7zip_uint64(p, len, |
| &(ci->folders[i].unPackSize[j])); |
| if (r < 0) |
| goto failed; |
| p += r; |
| len -= r; |
| } |
| } |
| |
| /* |
| * Read CRCs. |
| */ |
| if (len == 0) |
| goto failed; |
| if (*p == kEnd) |
| return (p - _p + 1); |
| if (len < 1 + ci->numFolders || *p++ != kCRC) |
| goto failed; |
| --len; |
| r = read_Digests(&digest, ci->numFolders, p, len); |
| if (r < 0) |
| goto failed; |
| p += r; |
| len -= r; |
| 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 (len == 0 || *p++ != kEnd) |
| goto failed; |
| free_Digest(&digest); |
| return (p - _p); |
| failed: |
| free_Digest(&digest); |
| return (-1); |
| } |
| |
| static uint64_t |
| folder_uncompressed_size(struct _7z_folder *f) |
| { |
| int n = f->numOutStreams; |
| unsigned pairs = f->numBindPairs; |
| |
| while (--n >= 0) { |
| unsigned i; |
| for (i = 0; i < pairs; i++) { |
| if (f->bindPairs[i].outIndex == 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 _7z_substream_info *ss, struct _7z_folder *f, |
| size_t numFolders, const unsigned char *p, size_t len) |
| { |
| const unsigned char *_p = p; |
| uint64_t *usizes; |
| size_t unpack_streams; |
| int r, type; |
| unsigned i; |
| uint32_t numDigests; |
| |
| memset(ss, 0, sizeof(*ss)); |
| |
| if (len < 2 || *p++ != kSubStreamsInfo) |
| return (-1); |
| --len; |
| |
| for (i = 0; i < numFolders; i++) |
| f[i].numUnpackStreams = 1; |
| |
| if (len < 1) |
| return (-1); |
| type = *p++; |
| --len; |
| |
| if (type == kNumUnPackStream) { |
| unpack_streams = 0; |
| for (i = 0; i < numFolders; i++) { |
| r = parse_7zip_uint64(p, len, |
| &(f[i].numUnpackStreams)); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| unpack_streams += f[i].numUnpackStreams; |
| } |
| if (len < 1) |
| return (-1); |
| type = *p++; |
| --len; |
| } else |
| unpack_streams = numFolders; |
| |
| 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++) { |
| r = parse_7zip_uint64(p, len, usizes); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| sum += *usizes++; |
| } |
| } |
| *usizes++ = folder_uncompressed_size(&f[i]) - sum; |
| } |
| |
| if (type == kSize) { |
| if (len < 1) |
| return (-1); |
| type = *p++; |
| --len; |
| } |
| |
| 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 += 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)); |
| r = read_Digests(&(tmpDigests), numDigests, p, len); |
| if (r < 0) { |
| free_Digest(&tmpDigests); |
| return (-1); |
| } |
| p += r; |
| len -= r; |
| 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 (len < 1) |
| return (-1); |
| type = *p++; |
| --len; |
| } |
| |
| /* |
| * Must be kEnd. |
| */ |
| if (type != kEnd) |
| return (-1); |
| return (p - _p); |
| } |
| |
| 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 _7zip *zip, struct _7z_stream_info *si, |
| const unsigned char *p, size_t len) |
| { |
| const unsigned char *_p = p; |
| unsigned i; |
| int r; |
| |
| memset(si, 0, sizeof(*si)); |
| |
| if (len > 0 && *p == kPackInfo) { |
| uint64_t packPos; |
| |
| r = read_PackInfo(&(si->pi), p, len); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| |
| /* |
| * Calculate packed stream positions. |
| */ |
| packPos = 0; |
| 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 (len > 0 && *p == kUnPackInfo) { |
| uint32_t packIndex; |
| struct _7z_folder *f; |
| |
| r = read_CodersInfo(&(si->ci), p, len); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| |
| /* |
| * Calculate packed stream indexes. |
| */ |
| packIndex = 0; |
| f = si->ci.folders; |
| for (i = 0; i < si->ci.numFolders; i++) { |
| f[i].packIndex = packIndex; |
| packIndex += f[i].numPackedStreams; |
| if (packIndex > si->pi.numPackStreams) |
| return (-1); |
| } |
| } |
| if (len > 0 && *p == kSubStreamsInfo) { |
| r = read_SubStreamsInfo(&(si->ss), |
| si->ci.folders, si->ci.numFolders, p, len); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| } |
| |
| /* |
| * Must be kEnd. |
| */ |
| if (len == 0 || *p++ != kEnd) |
| return (-1); |
| return (p - _p); |
| } |
| |
| 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 _7zip *zip, struct _7z_header_info *h, |
| const unsigned char *p, size_t len) |
| { |
| const unsigned char *_p = 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, r, sindex; |
| |
| if (len < 2 || *p++ != kHeader) |
| return (-1); |
| len--; |
| |
| /* |
| * Read ArchiveProperties. |
| */ |
| if (*p == kArchiveProperties) { |
| p++; |
| len--; |
| |
| for (;;) { |
| uint64_t size; |
| int atype = *p++; |
| len--; |
| if (atype == 0) |
| break; |
| r = parse_7zip_uint64(p, len, &size); |
| if (r < 0 || len < r + size) |
| return (-1); |
| p += r + size; |
| len -= r + size; |
| } |
| } |
| |
| /* |
| * Read MainStreamsInfo. |
| */ |
| if (*p == kMainStreamsInfo) { |
| p++; |
| len--; |
| r = read_StreamsInfo(zip, &(zip->si), p, len); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| } |
| if (len == 0) |
| return (-1); |
| if (*p == kEnd) |
| return (p - _p + 1); |
| |
| /* |
| * Read FilesInfo. |
| */ |
| if (len < 2 || *p++ != kFilesInfo) |
| return (-1); |
| len--; |
| |
| r = parse_7zip_uint64(p, len, &(zip->numFiles)); |
| if (r < 0) |
| return (-1); |
| p += r; |
| len -= r; |
| |
| zip->entries = calloc(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 (len < 1) |
| return (-1); |
| type = *p++; |
| len--; |
| if (type == kEnd) |
| break; |
| |
| r = parse_7zip_uint64(p, len, &size); |
| if (r < 0 || len < size) |
| return (-1); |
| p += r; |
| len -= r; |
| ll = (size_t)size; |
| len -= ll; |
| |
| switch (type) { |
| case kEmptyStream: |
| h->emptyStreamBools = calloc(zip->numFiles, |
| sizeof(*h->emptyStreamBools)); |
| if (h->emptyStreamBools == NULL) |
| return (-1); |
| r = read_Bools(h->emptyStreamBools, zip->numFiles, |
| p, ll); |
| if (r < 0) |
| return (-1); |
| p += r; |
| ll -= r; |
| empty_streams = 0; |
| for (i = 0; i < zip->numFiles; i++) { |
| if (h->emptyStreamBools[i]) |
| empty_streams++; |
| } |
| break; |
| case kEmptyFile: |
| h->emptyFileBools = calloc(empty_streams, |
| sizeof(*h->emptyFileBools)); |
| if (h->emptyFileBools == NULL) |
| return (-1); |
| r = read_Bools(h->emptyFileBools, empty_streams, |
| p, len); |
| if (r < 0) |
| return (-1); |
| p += r; |
| ll -= r; |
| break; |
| case kAnti: |
| h->antiBools = calloc(empty_streams, |
| sizeof(*h->antiBools)); |
| if (h->antiBools == NULL) |
| return (-1); |
| r = read_Bools(h->antiBools, empty_streams, p, len); |
| if (r < 0) |
| return (-1); |
| p += r; |
| ll -= r; |
| break; |
| case kCTime: |
| case kATime: |
| case kMTime: |
| r = read_Times(zip, h, type, p, ll); |
| if (r < 0) |
| return (-1); |
| p += r; |
| ll -= r; |
| break; |
| case kName: |
| { |
| unsigned char *np; |
| size_t nl; |
| |
| if (ll < 1) |
| return (-1); |
| p++; ll--;/* Skip one byte. */ |
| if ((ll & 1) || ll < zip->numFiles * 4) |
| return (-1); |
| |
| zip->entry_names = malloc(ll); |
| if (zip->entry_names == NULL) |
| return (-1); |
| memcpy(zip->entry_names, p, ll); |
| 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 (ll < 2) |
| return (-1); |
| allAreDefined = *p++; |
| --ll; |
| p++; --ll;/* Skip one byte. */ |
| h->attrBools = calloc(zip->numFiles, |
| sizeof(*h->attrBools)); |
| if (h->attrBools == NULL) |
| return (-1); |
| if (allAreDefined) |
| memset(h->attrBools, 1, zip->numFiles); |
| else { |
| r = read_Bools(h->attrBools, |
| zip->numFiles, p, ll); |
| if (r < 0) |
| return (-1); |
| p += r; |
| ll -= r; |
| } |
| for (i = 0; i < zip->numFiles; i++) { |
| if (h->attrBools[i]) { |
| if (ll < 4) |
| return (-1); |
| entries[i].attr = archive_le32dec(p); |
| p += 4; |
| ll -= 4; |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| /* Skip remaining data. */ |
| p += ll; |
| } |
| |
| /* |
| * 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; |
| if (entries[i].flg & HAS_STREAM) { |
| entries[i].mode = AE_IFREG | 0777; |
| if (si->ss.digestsDefined[sindex]) |
| entries[i].flg |= CRC32_IS_SET; |
| entries[i].ssIndex = sindex; |
| sindex++; |
| } else { |
| if (h->emptyFileBools == NULL) |
| entries[i].mode = AE_IFDIR | 0777; |
| else { |
| if (h->emptyFileBools[eindex]) |
| entries[i].mode = AE_IFREG | 0777; |
| else |
| entries[i].mode = AE_IFDIR | 0777; |
| eindex++; |
| } |
| 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 (p - _p); |
| } |
| |
| #define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) |
| static void |
| fileTimeToUtc(uint64_t fileTime, time_t *time, long *ns) |
| { |
| |
| if (fileTime >= EPOC_TIME) { |
| fileTime -= EPOC_TIME; |
| /* milli seconds base */ |
| *time = (time_t)(fileTime / 10000000); |
| /* nano seconds base */ |
| *ns = (long)(fileTime % 10000000) * 100; |
| } else { |
| *time = 0; |
| *ns = 0; |
| } |
| } |
| |
| static int |
| read_Times(struct _7zip *zip, struct _7z_header_info *h, int type, |
| const unsigned char *p, size_t len) |
| { |
| const unsigned char *_p = p; |
| struct _7zip_entry *entries = zip->entries; |
| unsigned char *timeBools; |
| int r; |
| int allAreDefined, external; |
| unsigned i; |
| |
| timeBools = calloc(zip->numFiles, sizeof(*timeBools)); |
| if (timeBools == NULL) |
| return (-1); |
| |
| if (len < 1) |
| goto failed; |
| allAreDefined = *p++; |
| len--; |
| if (allAreDefined) |
| memset(timeBools, 1, zip->numFiles); |
| else { |
| r = read_Bools(timeBools, zip->numFiles, p, len); |
| if (r < 0) |
| goto failed; |
| p += r; |
| len -= r; |
| } |
| |
| if (len < 1) |
| goto failed; |
| external = *p++; |
| len--; |
| if (external) { |
| r = parse_7zip_uint64(p, len, &(h->dataIndex)); |
| if (r < 0) |
| goto failed; |
| p += r; |
| len -= r; |
| } |
| |
| for (i = 0; i < zip->numFiles; i++) { |
| if (len < 8) |
| goto failed; |
| if (timeBools[i]) { |
| 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; |
| } |
| } |
| p += 8; |
| len -= 8; |
| } |
| |
| free(timeBools); |
| return (p - _p); |
| failed: |
| free(timeBools); |
| return (-1); |
| } |
| |
| static int |
| slurp_central_directory(struct archive_read *a, struct _7zip *zip, |
| struct _7z_header_info *header) |
| { |
| const unsigned char *p; |
| unsigned char *v; |
| struct _7z_stream_info *si; |
| uint64_t len; |
| uint64_t next_header_offset; |
| uint64_t next_header_size; |
| uint32_t next_header_crc; |
| size_t vsize, remaining; |
| ssize_t bytes_avail; |
| int r; |
| |
| if ((p = __archive_read_ahead(a, 32, &bytes_avail)) == NULL) |
| return (ARCHIVE_FATAL); |
| |
| if (p[0] == 'M' && p[1] == 'Z') { |
| /* 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, NULL)) == 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, (unsigned char *)p + 12, 20) != archive_le32dec(p + 8)) { |
| archive_set_error(&a->archive, -1, "Header CRC error"); |
| return (ARCHIVE_FATAL); |
| } |
| |
| 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); |
| } |
| if (__archive_read_seek(a, next_header_offset + zip->seek_base, |
| SEEK_SET) < 0) |
| return (ARCHIVE_FATAL); |
| zip->header_offset = next_header_offset; |
| |
| if ((p = __archive_read_ahead(a, next_header_size, NULL)) == NULL) |
| return (ARCHIVE_FATAL); |
| |
| if (crc32(0, p, next_header_size) != next_header_crc) { |
| archive_set_error(&a->archive, -1, "Damaged 7-Zip archive"); |
| return (ARCHIVE_FATAL); |
| } |
| |
| len = next_header_size; |
| /* Parse ArchiveProperties. */ |
| switch (p[0]) { |
| case kHeader: |
| errno = 0; |
| r = read_Header(zip, header, p, len); |
| 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); |
| } |
| if (len - r == 0 || p[r] != kEnd) { |
| archive_set_error(&a->archive, -1, |
| "Damaged 7-Zip archive"); |
| return (ARCHIVE_FATAL); |
| } |
| break; |
| case kEncodedHeader: |
| p++; |
| len--; |
| errno = 0; |
| |
| si = &(zip->si); |
| r = read_StreamsInfo(zip, si, p, len); |
| if (r < 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 (next_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); |
| } |
| |
| if (__archive_read_seek(a, si->pi.pos + zip->seek_base, |
| SEEK_SET) < 0) |
| return (ARCHIVE_FATAL); |
| zip->header_offset = si->pi.pos; |
| |
| p = __archive_read_ahead(a, si->pi.sizes[0], NULL); |
| if (p == NULL) |
| return (ARCHIVE_FATAL); |
| |
| r = init_decompression(a, zip, si->ci.folders); |
| if (r != ARCHIVE_OK) |
| return (ARCHIVE_FATAL); |
| |
| /* Get an unpacked header size. */ |
| vsize = (size_t)folder_uncompressed_size(si->ci.folders); |
| if ((v = malloc(vsize)) == NULL) { |
| archive_set_error(&a->archive, -1, |
| "Couldn't allocate memory"); |
| return (ARCHIVE_FATAL); |
| } |
| |
| remaining = si->pi.sizes[0]; |
| if (zip->codec == _7Z_COPY) { |
| /* |
| * The header is not compressed. |
| * This case won't happen ? |
| */ |
| if (vsize > remaining) { |
| free(v); |
| archive_set_error(&a->archive, -1, |
| "Invalid Header data"); |
| return (ARCHIVE_FATAL); |
| } |
| memcpy(v, p, vsize); |
| } else { |
| /* |
| * Decompress the header. |
| */ |
| r = decompress(a, zip, v, &vsize, p, &remaining); |
| if (r != ARCHIVE_OK) { |
| free(v); |
| return (ARCHIVE_FATAL); |
| } |
| } |
| if (si->ci.folders[0].digest_defined){ |
| uint32_t c = crc32(0, v, vsize); |
| if (c != si->ci.folders[0].digest) { |
| free(v); |
| archive_set_error(&a->archive, -1, |
| "Header CRC error"); |
| return (ARCHIVE_FATAL); |
| } |
| } |
| free_StreamsInfo(si); |
| memset(si, 0, sizeof(*si)); |
| |
| errno = 0; |
| r = read_Header(zip, header, v, vsize); |
| /* Must be kEnd. */ |
| if (r < 0 || vsize - r == 0 || v[r] != kEnd) { |
| free(v); |
| 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); |
| } |
| free(v); |
| break; |
| default: |
| archive_set_error(&a->archive, -1, |
| "Unexpected Property ID = %X", p[0]); |
| return (ARCHIVE_FATAL); |
| } |
| zip->offset = -1; |
| |
| return (ARCHIVE_OK); |
| } |
| |
| static ssize_t |
| get_uncompressed_data(struct archive_read *a, const void **buff, size_t size) |
| { |
| struct _7zip *zip = (struct _7zip *)a->format->data; |
| ssize_t bytes_avail; |
| |
| if (zip->codec == _7Z_COPY) { |
| /* Copy mode. */ |
| |
| /* |
| * 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 = __archive_read_ahead(a, 1, &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->entry_bytes_unconsumed = bytes_avail; |
| } else { |
| /* Packed mode. */ |
| 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_buff_pointer; |
| zip->uncompressed_buff_pointer += bytes_avail; |
| } |
| zip->uncompressed_buffer_bytes_remaining -= bytes_avail; |
| return (bytes_avail); |
| } |
| |
| static ssize_t |
| extract_pack_stream(struct archive_read *a) |
| { |
| struct _7zip *zip = (struct _7zip *)a->format->data; |
| ssize_t bytes_avail; |
| int r; |
| |
| if (zip->codec == _7Z_COPY) { |
| zip->uncompressed_buffer_bytes_remaining = |
| zip->pack_stream_bytes_remaining; |
| zip->pack_stream_bytes_remaining = 0; |
| zip->unpack_stream_bytes_remaining = 0; |
| } else { |
| size_t bytes_in, bytes_out; |
| const void *compressed_buff; |
| |
| /* If the buffer hasn't been allocated, allocate it now. */ |
| if (zip->uncompressed_buffer == NULL) { |
| zip->uncompressed_buffer_size = 64 * 1024; |
| 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); |
| } |
| } |
| /* |
| * 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. |
| */ |
| compressed_buff = __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); |
| } |
| |
| bytes_out = zip->uncompressed_buffer_size; |
| bytes_in = bytes_avail; |
| if (bytes_in > zip->pack_stream_bytes_remaining) |
| bytes_in = zip->pack_stream_bytes_remaining; |
| r = decompress(a, zip, zip->uncompressed_buffer, |
| &bytes_out, compressed_buff, &bytes_in); |
| switch (r) { |
| case ARCHIVE_OK: |
| case ARCHIVE_EOF: |
| break; |
| default: |
| return (ARCHIVE_FATAL); |
| } |
| zip->pack_stream_bytes_remaining -= bytes_in; |
| if (bytes_out > zip->unpack_stream_bytes_remaining) |
| bytes_out = zip->unpack_stream_bytes_remaining; |
| zip->unpack_stream_bytes_remaining -= bytes_out; |
| zip->uncompressed_buffer_bytes_remaining = bytes_out; |
| zip->uncompressed_buff_pointer = zip->uncompressed_buffer; |
| zip->entry_bytes_unconsumed = bytes_in; |
| } |
| return (ARCHIVE_OK); |
| } |
| |
| static ssize_t |
| read_stream(struct archive_read *a, const void **buff, size_t size) |
| { |
| struct _7zip *zip = (struct _7zip *)a->format->data; |
| uint64_t skip_bytes = 0; |
| int r; |
| |
| if (zip->uncompressed_buffer_bytes_remaining == 0) { |
| if (zip->pack_stream_bytes_remaining > 0) { |
| r = extract_pack_stream(a); |
| if (r < 0) |
| return (r); |
| return (get_uncompressed_data(a, buff, size)); |
| } else if (zip->unpack_stream_bytes_remaining > 0) { |
| /* Extract a new pack stream. */ |
| r = extract_pack_stream(a); |
| if (r < 0) |
| return (r); |
| return (get_uncompressed_data(a, buff, size)); |
| } |
| } else |
| return (get_uncompressed_data(a, buff, size)); |
| |
| /* |
| * Current pack stream has been consumed. |
| */ |
| if (zip->pack_stream_remaining == 0) { |
| /* |
| * 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); |
| } |
| zip->pack_stream_remaining = |
| zip->si.ci.folders[zip->folder_index].numPackedStreams; |
| zip->pack_stream_index = |
| zip->si.ci.folders[zip->folder_index].packIndex; |
| zip->unpack_stream_bytes_remaining = |
| zip->si.ci.folders[zip->folder_index].unPackSize[0]; |
| zip->uncompressed_buffer_bytes_remaining = 0; |
| /* |
| * We have to initialize the decompressor for |
| * the new folder's pack streams. |
| */ |
| r = init_decompression(a, zip, |
| &(zip->si.ci.folders[zip->folder_index])); |
| if (r != ARCHIVE_OK) |
| return (ARCHIVE_FATAL); |
| |
| zip->folder_index++; |
| } |
| |
| /* |
| * Switch to next pack stream. |
| */ |
| zip->pack_stream_bytes_remaining = |
| zip->si.pi.sizes[zip->pack_stream_index]; |
| if (zip->offset != zip->si.pi.positions[zip->pack_stream_index]) { |
| if (0 > __archive_read_seek(a, |
| zip->si.pi.positions[zip->pack_stream_index] |
| + zip->seek_base, SEEK_SET)) |
| return (ARCHIVE_FATAL); |
| zip->offset = zip->si.pi.positions[zip->pack_stream_index]; |
| } |
| zip->pack_stream_index++; |
| zip->pack_stream_remaining--; |
| |
| /* Extract a new pack stream. */ |
| r = extract_pack_stream(a); |
| if (r < 0) |
| return (r); |
| |
| /* |
| * Skip the bytes we alrady has skipped in skip_stream(). |
| */ |
| while (skip_bytes) { |
| ssize_t skipped; |
| |
| skipped = get_uncompressed_data(a, buff, skip_bytes); |
| if (skipped < 0) |
| return (skipped); |
| skip_bytes -= skipped; |
| if (zip->entry_bytes_unconsumed) { |
| __archive_read_consume(a, zip->entry_bytes_unconsumed); |
| zip->offset += zip->entry_bytes_unconsumed; |
| zip->entry_bytes_unconsumed = 0; |
| } |
| } |
| |
| return (get_uncompressed_data(a, buff, size)); |
| } |
| |
| 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 unncecessary 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); |
| 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 -= skipped_bytes; |
| if (zip->entry_bytes_unconsumed) { |
| __archive_read_consume(a, zip->entry_bytes_unconsumed); |
| zip->offset += zip->entry_bytes_unconsumed; |
| zip->entry_bytes_unconsumed = 0; |
| } |
| } |
| return (skip_bytes); |
| } |
| |