| /*- |
| * Copyright (c) 2003-2007 Tim Kientzle |
| * Copyright (c) 2012 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_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #ifdef HAVE_LZO_LZOCONF_H |
| #include <lzo/lzoconf.h> |
| #endif |
| #ifdef HAVE_LZO_LZO1X_H |
| #include <lzo/lzo1x.h> |
| #endif |
| #ifdef HAVE_ZLIB_H |
| #include <zlib.h> /* for crc32 and adler32 */ |
| #endif |
| |
| #include "archive.h" |
| #if !defined(HAVE_ZLIB_H) &&\ |
| defined(HAVE_LZO_LZOCONF_H) && defined(HAVE_LZO_LZO1X_H) |
| #include "archive_crc32.h" |
| #endif |
| #include "archive_endian.h" |
| #include "archive_private.h" |
| #include "archive_read_private.h" |
| |
| #ifndef HAVE_ZLIB_H |
| #define adler32 lzo_adler32 |
| #endif |
| |
| #define LZOP_HEADER_MAGIC "\x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a" |
| #define LZOP_HEADER_MAGIC_LEN 9 |
| |
| #if defined(HAVE_LZO_LZOCONF_H) && defined(HAVE_LZO_LZO1X_H) |
| struct read_lzop { |
| unsigned char *out_block; |
| size_t out_block_size; |
| int64_t total_out; |
| int flags; |
| uint32_t compressed_cksum; |
| uint32_t uncompressed_cksum; |
| size_t compressed_size; |
| size_t uncompressed_size; |
| size_t unconsumed_bytes; |
| char in_stream; |
| char eof; /* True = found end of compressed data. */ |
| }; |
| |
| #define FILTER 0x0800 |
| #define CRC32_HEADER 0x1000 |
| #define EXTRA_FIELD 0x0040 |
| #define ADLER32_UNCOMPRESSED 0x0001 |
| #define ADLER32_COMPRESSED 0x0002 |
| #define CRC32_UNCOMPRESSED 0x0100 |
| #define CRC32_COMPRESSED 0x0200 |
| #define MAX_BLOCK_SIZE (64 * 1024 * 1024) |
| |
| static ssize_t lzop_filter_read(struct archive_read_filter *, const void **); |
| static int lzop_filter_close(struct archive_read_filter *); |
| #endif |
| |
| static int lzop_bidder_bid(struct archive_read_filter_bidder *, |
| struct archive_read_filter *); |
| static int lzop_bidder_init(struct archive_read_filter *); |
| |
| int |
| archive_read_support_filter_lzop(struct archive *_a) |
| { |
| struct archive_read *a = (struct archive_read *)_a; |
| struct archive_read_filter_bidder *reader; |
| |
| archive_check_magic(_a, ARCHIVE_READ_MAGIC, |
| ARCHIVE_STATE_NEW, "archive_read_support_filter_lzop"); |
| |
| if (__archive_read_get_bidder(a, &reader) != ARCHIVE_OK) |
| return (ARCHIVE_FATAL); |
| |
| reader->data = NULL; |
| reader->bid = lzop_bidder_bid; |
| reader->init = lzop_bidder_init; |
| reader->options = NULL; |
| reader->free = NULL; |
| /* Signal the extent of lzop support with the return value here. */ |
| #if defined(HAVE_LZO_LZOCONF_H) && defined(HAVE_LZO_LZO1X_H) |
| return (ARCHIVE_OK); |
| #else |
| /* Return ARCHIVE_WARN since this always uses an external program. */ |
| archive_set_error(_a, ARCHIVE_ERRNO_MISC, |
| "Using external lzop program for lzop decompression"); |
| return (ARCHIVE_WARN); |
| #endif |
| } |
| |
| /* |
| * Bidder just verifies the header and returns the number of verified bits. |
| */ |
| static int |
| lzop_bidder_bid(struct archive_read_filter_bidder *self, |
| struct archive_read_filter *filter) |
| { |
| const unsigned char *p; |
| ssize_t avail; |
| |
| (void)self; /* UNUSED */ |
| |
| p = __archive_read_filter_ahead(filter, LZOP_HEADER_MAGIC_LEN, &avail); |
| if (p == NULL || avail == 0) |
| return (0); |
| |
| if (memcmp(p, LZOP_HEADER_MAGIC, LZOP_HEADER_MAGIC_LEN)) |
| return (0); |
| |
| return (LZOP_HEADER_MAGIC_LEN * 8); |
| } |
| |
| #if !defined(HAVE_LZO_LZOCONF_H) || !defined(HAVE_LZO_LZO1X_H) |
| /* |
| * If we don't have the library on this system, we can't do the |
| * decompression directly. We can, however, try to run "lzop -d" |
| * in case that's available. |
| */ |
| static int |
| lzop_bidder_init(struct archive_read_filter *self) |
| { |
| int r; |
| |
| r = __archive_read_program(self, "lzop -d"); |
| /* Note: We set the format here even if __archive_read_program() |
| * above fails. We do, after all, know what the format is |
| * even if we weren't able to read it. */ |
| self->code = ARCHIVE_FILTER_LZOP; |
| self->name = "lzop"; |
| return (r); |
| } |
| #else |
| /* |
| * Initialize the filter object. |
| */ |
| static int |
| lzop_bidder_init(struct archive_read_filter *self) |
| { |
| struct read_lzop *state; |
| |
| self->code = ARCHIVE_FILTER_LZOP; |
| self->name = "lzop"; |
| |
| state = (struct read_lzop *)calloc(sizeof(*state), 1); |
| if (state == NULL) { |
| archive_set_error(&self->archive->archive, ENOMEM, |
| "Can't allocate data for lzop decompression"); |
| return (ARCHIVE_FATAL); |
| } |
| |
| self->data = state; |
| self->read = lzop_filter_read; |
| self->skip = NULL; /* not supported */ |
| self->close = lzop_filter_close; |
| |
| return (ARCHIVE_OK); |
| } |
| |
| static int |
| consume_header(struct archive_read_filter *self) |
| { |
| struct read_lzop *state = (struct read_lzop *)self->data; |
| const unsigned char *p, *_p; |
| unsigned checksum, flags, len, method, version; |
| |
| /* |
| * Check LZOP magic code. |
| */ |
| p = __archive_read_filter_ahead(self->upstream, |
| LZOP_HEADER_MAGIC_LEN, NULL); |
| if (p == NULL) |
| return (ARCHIVE_EOF); |
| |
| if (memcmp(p, LZOP_HEADER_MAGIC, LZOP_HEADER_MAGIC_LEN)) |
| return (ARCHIVE_EOF); |
| __archive_read_filter_consume(self->upstream, |
| LZOP_HEADER_MAGIC_LEN); |
| |
| p = __archive_read_filter_ahead(self->upstream, 29, NULL); |
| if (p == NULL) |
| goto truncated; |
| _p = p; |
| version = archive_be16dec(p); |
| p += 4;/* version(2 bytes) + library version(2 bytes) */ |
| |
| if (version >= 0x940) { |
| unsigned reqversion = archive_be16dec(p); p += 2; |
| if (reqversion < 0x900) { |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_MISC, "Invalid required version"); |
| return (ARCHIVE_FAILED); |
| } |
| } |
| |
| method = *p++; |
| if (method < 1 || method > 3) { |
| archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, |
| "Unsupported method"); |
| return (ARCHIVE_FAILED); |
| } |
| |
| if (version >= 0x940) { |
| unsigned level = *p++; |
| #if 0 |
| unsigned default_level[] = {0, 3, 1, 9}; |
| #endif |
| if (level == 0) |
| /* Method is 1..3 here due to check above. */ |
| #if 0 /* Avoid an error Clang Static Analyzer claims |
| "Value stored to 'level' is never read". */ |
| level = default_level[method]; |
| #else |
| ;/* NOP */ |
| #endif |
| else if (level > 9) { |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_MISC, "Invalid level"); |
| return (ARCHIVE_FAILED); |
| } |
| } |
| |
| flags = archive_be32dec(p); p += 4; |
| |
| if (flags & FILTER) |
| p += 4; /* Skip filter */ |
| p += 4; /* Skip mode */ |
| if (version >= 0x940) |
| p += 8; /* Skip mtime */ |
| else |
| p += 4; /* Skip mtime */ |
| len = *p++; /* Read filename length */ |
| len += p - _p; |
| /* Make sure we have all bytes we need to calculate checksum. */ |
| p = __archive_read_filter_ahead(self->upstream, len + 4, NULL); |
| if (p == NULL) |
| goto truncated; |
| if (flags & CRC32_HEADER) |
| checksum = crc32(crc32(0, NULL, 0), p, len); |
| else |
| checksum = adler32(adler32(0, NULL, 0), p, len); |
| if (archive_be32dec(p + len) != checksum) |
| goto corrupted; |
| __archive_read_filter_consume(self->upstream, len + 4); |
| if (flags & EXTRA_FIELD) { |
| /* Skip extra field */ |
| p = __archive_read_filter_ahead(self->upstream, 4, NULL); |
| if (p == NULL) |
| goto truncated; |
| len = archive_be32dec(p); |
| __archive_read_filter_consume(self->upstream, len + 4 + 4); |
| } |
| state->flags = flags; |
| state->in_stream = 1; |
| return (ARCHIVE_OK); |
| truncated: |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_FILE_FORMAT, "Truncated lzop data"); |
| return (ARCHIVE_FAILED); |
| corrupted: |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_FILE_FORMAT, "Corrupted lzop header"); |
| return (ARCHIVE_FAILED); |
| } |
| |
| static int |
| consume_block_info(struct archive_read_filter *self) |
| { |
| struct read_lzop *state = (struct read_lzop *)self->data; |
| const unsigned char *p; |
| unsigned flags = state->flags; |
| |
| p = __archive_read_filter_ahead(self->upstream, 4, NULL); |
| if (p == NULL) |
| goto truncated; |
| state->uncompressed_size = archive_be32dec(p); |
| __archive_read_filter_consume(self->upstream, 4); |
| if (state->uncompressed_size == 0) |
| return (ARCHIVE_EOF); |
| if (state->uncompressed_size > MAX_BLOCK_SIZE) |
| goto corrupted; |
| |
| p = __archive_read_filter_ahead(self->upstream, 4, NULL); |
| if (p == NULL) |
| goto truncated; |
| state->compressed_size = archive_be32dec(p); |
| __archive_read_filter_consume(self->upstream, 4); |
| if (state->compressed_size > state->uncompressed_size) |
| goto corrupted; |
| |
| if (flags & (CRC32_UNCOMPRESSED | ADLER32_UNCOMPRESSED)) { |
| p = __archive_read_filter_ahead(self->upstream, 4, NULL); |
| if (p == NULL) |
| goto truncated; |
| state->compressed_cksum = state->uncompressed_cksum = |
| archive_be32dec(p); |
| __archive_read_filter_consume(self->upstream, 4); |
| } |
| if ((flags & (CRC32_COMPRESSED | ADLER32_COMPRESSED)) && |
| state->compressed_size < state->uncompressed_size) { |
| p = __archive_read_filter_ahead(self->upstream, 4, NULL); |
| if (p == NULL) |
| goto truncated; |
| state->compressed_cksum = archive_be32dec(p); |
| __archive_read_filter_consume(self->upstream, 4); |
| } |
| return (ARCHIVE_OK); |
| truncated: |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_FILE_FORMAT, "Truncated lzop data"); |
| return (ARCHIVE_FAILED); |
| corrupted: |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_FILE_FORMAT, "Corrupted lzop header"); |
| return (ARCHIVE_FAILED); |
| } |
| |
| static ssize_t |
| lzop_filter_read(struct archive_read_filter *self, const void **p) |
| { |
| struct read_lzop *state = (struct read_lzop *)self->data; |
| const void *b; |
| lzo_uint out_size; |
| uint32_t cksum; |
| int ret, r; |
| |
| if (state->unconsumed_bytes) { |
| __archive_read_filter_consume(self->upstream, |
| state->unconsumed_bytes); |
| state->unconsumed_bytes = 0; |
| } |
| if (state->eof) |
| return (0); |
| |
| for (;;) { |
| if (!state->in_stream) { |
| ret = consume_header(self); |
| if (ret < ARCHIVE_OK) |
| return (ret); |
| if (ret == ARCHIVE_EOF) { |
| state->eof = 1; |
| return (0); |
| } |
| } |
| ret = consume_block_info(self); |
| if (ret < ARCHIVE_OK) |
| return (ret); |
| if (ret == ARCHIVE_EOF) |
| state->in_stream = 0; |
| else |
| break; |
| } |
| |
| if (state->out_block == NULL || |
| state->out_block_size < state->uncompressed_size) { |
| void *new_block; |
| |
| new_block = realloc(state->out_block, state->uncompressed_size); |
| if (new_block == NULL) { |
| archive_set_error(&self->archive->archive, ENOMEM, |
| "Can't allocate data for lzop decompression"); |
| return (ARCHIVE_FATAL); |
| } |
| state->out_block = new_block; |
| state->out_block_size = state->uncompressed_size; |
| } |
| |
| b = __archive_read_filter_ahead(self->upstream, |
| state->compressed_size, NULL); |
| if (b == NULL) { |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_FILE_FORMAT, "Truncated lzop data"); |
| return (ARCHIVE_FATAL); |
| } |
| if (state->flags & CRC32_COMPRESSED) |
| cksum = crc32(crc32(0, NULL, 0), b, state->compressed_size); |
| else if (state->flags & ADLER32_COMPRESSED) |
| cksum = adler32(adler32(0, NULL, 0), b, state->compressed_size); |
| else |
| cksum = state->compressed_cksum; |
| if (cksum != state->compressed_cksum) { |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_MISC, "Corrupted data"); |
| return (ARCHIVE_FATAL); |
| } |
| |
| /* |
| * If the both uncompressed size and compressed size are the same, |
| * we do not decompress this block. |
| */ |
| if (state->uncompressed_size == state->compressed_size) { |
| *p = b; |
| state->total_out += state->compressed_size; |
| state->unconsumed_bytes = state->compressed_size; |
| return ((ssize_t)state->uncompressed_size); |
| } |
| |
| /* |
| * Drive lzo uncompression. |
| */ |
| out_size = (lzo_uint)state->uncompressed_size; |
| r = lzo1x_decompress_safe(b, (lzo_uint)state->compressed_size, |
| state->out_block, &out_size, NULL); |
| switch (r) { |
| case LZO_E_OK: |
| if (out_size == state->uncompressed_size) |
| break; |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_MISC, "Corrupted data"); |
| return (ARCHIVE_FATAL); |
| case LZO_E_OUT_OF_MEMORY: |
| archive_set_error(&self->archive->archive, ENOMEM, |
| "lzop decompression failed: out of memory"); |
| return (ARCHIVE_FATAL); |
| default: |
| archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, |
| "lzop decompression failed: %d", r); |
| return (ARCHIVE_FATAL); |
| } |
| |
| if (state->flags & CRC32_UNCOMPRESSED) |
| cksum = crc32(crc32(0, NULL, 0), state->out_block, |
| state->uncompressed_size); |
| else if (state->flags & ADLER32_UNCOMPRESSED) |
| cksum = adler32(adler32(0, NULL, 0), state->out_block, |
| state->uncompressed_size); |
| else |
| cksum = state->uncompressed_cksum; |
| if (cksum != state->uncompressed_cksum) { |
| archive_set_error(&self->archive->archive, |
| ARCHIVE_ERRNO_MISC, "Corrupted data"); |
| return (ARCHIVE_FATAL); |
| } |
| |
| __archive_read_filter_consume(self->upstream, state->compressed_size); |
| *p = state->out_block; |
| state->total_out += out_size; |
| return ((ssize_t)out_size); |
| } |
| |
| /* |
| * Clean up the decompressor. |
| */ |
| static int |
| lzop_filter_close(struct archive_read_filter *self) |
| { |
| struct read_lzop *state = (struct read_lzop *)self->data; |
| |
| free(state->out_block); |
| free(state); |
| return (ARCHIVE_OK); |
| } |
| |
| #endif |