From: Michihiro NAKAJIMA Date: Mon, 20 Dec 2010 08:59:25 +0000 (-0500) Subject: Add support for lha/lzh archive format. X-Git-Tag: v3.0.0a~815 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=344711d5afb202ddd767939edcfe60bbf532fc92;p=thirdparty%2Flibarchive.git Add support for lha/lzh archive format. SVN-Revision: 2831 --- diff --git a/Makefile.am b/Makefile.am index 64e9cfd2c..dd3271b3f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -137,6 +137,7 @@ libarchive_la_SOURCES= \ libarchive/archive_read_support_format_cpio.c \ libarchive/archive_read_support_format_empty.c \ libarchive/archive_read_support_format_iso9660.c \ + libarchive/archive_read_support_format_lha.c \ libarchive/archive_read_support_format_mtree.c \ libarchive/archive_read_support_format_raw.c \ libarchive/archive_read_support_format_tar.c \ @@ -303,6 +304,7 @@ libarchive_test_SOURCES= \ libarchive/test/test_read_format_isorr_new_bz2.c \ libarchive/test/test_read_format_isorr_rr_moved.c \ libarchive/test/test_read_format_isozisofs_bz2.c \ + libarchive/test/test_read_format_lha.c \ libarchive/test/test_read_format_mtree.c \ libarchive/test/test_read_format_pax_bz2.c \ libarchive/test/test_read_format_raw.c \ @@ -414,6 +416,13 @@ libarchive_test_EXTRA_DIST=\ libarchive/test/test_read_format_iso_rockridge_new.iso.Z.uu \ libarchive/test/test_read_format_iso_rockridge_rr_moved.iso.Z.uu\ libarchive/test/test_read_format_iso_zisofs.iso.Z.uu \ + libarchive/test/test_read_format_lha_header0.lzh.uu \ + libarchive/test/test_read_format_lha_header1.lzh.uu \ + libarchive/test/test_read_format_lha_header2.lzh.uu \ + libarchive/test/test_read_format_lha_lh0.lzh.uu \ + libarchive/test/test_read_format_lha_lh6.lzh.uu \ + libarchive/test/test_read_format_lha_lh7.lzh.uu \ + libarchive/test/test_read_format_lha_withjunk.lzh.uu \ libarchive/test/test_read_format_mtree.mtree.uu \ libarchive/test/test_read_format_raw.data.Z.uu \ libarchive/test/test_read_format_raw.data.uu \ diff --git a/libarchive/CMakeLists.txt b/libarchive/CMakeLists.txt index c50ac9b01..de903bc42 100644 --- a/libarchive/CMakeLists.txt +++ b/libarchive/CMakeLists.txt @@ -57,6 +57,7 @@ SET(libarchive_SOURCES archive_read_support_format_cpio.c archive_read_support_format_empty.c archive_read_support_format_iso9660.c + archive_read_support_format_lha.c archive_read_support_format_mtree.c archive_read_support_format_raw.c archive_read_support_format_tar.c diff --git a/libarchive/archive.h b/libarchive/archive.h index 34702c46c..089456eb0 100644 --- a/libarchive/archive.h +++ b/libarchive/archive.h @@ -304,6 +304,7 @@ typedef int archive_close_callback(struct archive *, void *_client_data); #define ARCHIVE_FORMAT_MTREE 0x80000 #define ARCHIVE_FORMAT_RAW 0x90000 #define ARCHIVE_FORMAT_XAR 0xA0000 +#define ARCHIVE_FORMAT_LHA 0xB0000 /*- * Basic outline for reading an archive: @@ -352,6 +353,7 @@ __LA_DECL int archive_read_support_format_cpio(struct archive *); __LA_DECL int archive_read_support_format_empty(struct archive *); __LA_DECL int archive_read_support_format_gnutar(struct archive *); __LA_DECL int archive_read_support_format_iso9660(struct archive *); +__LA_DECL int archive_read_support_format_lha(struct archive *); __LA_DECL int archive_read_support_format_mtree(struct archive *); __LA_DECL int archive_read_support_format_raw(struct archive *); __LA_DECL int archive_read_support_format_tar(struct archive *); diff --git a/libarchive/archive_read_private.h b/libarchive/archive_read_private.h index cf25004a4..070505238 100644 --- a/libarchive/archive_read_private.h +++ b/libarchive/archive_read_private.h @@ -169,7 +169,7 @@ struct archive_read { int (*read_data)(struct archive_read *, const void **, size_t *, int64_t *); int (*read_data_skip)(struct archive_read *); int (*cleanup)(struct archive_read *); - } formats[9]; + } formats[10]; struct archive_format_descriptor *format; /* Active format. */ /* diff --git a/libarchive/archive_read_support_format_all.c b/libarchive/archive_read_support_format_all.c index 967783885..43be4605c 100644 --- a/libarchive/archive_read_support_format_all.c +++ b/libarchive/archive_read_support_format_all.c @@ -35,6 +35,7 @@ archive_read_support_format_all(struct archive *a) archive_read_support_format_cpio(a); archive_read_support_format_empty(a); archive_read_support_format_iso9660(a); + archive_read_support_format_lha(a); archive_read_support_format_mtree(a); archive_read_support_format_tar(a); archive_read_support_format_xar(a); diff --git a/libarchive/archive_read_support_format_lha.c b/libarchive/archive_read_support_format_lha.c new file mode 100644 index 000000000..b5b4c19e9 --- /dev/null +++ b/libarchive/archive_read_support_format_lha.c @@ -0,0 +1,2340 @@ +/*- + * Copyright (c) 2008-2010 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" + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_endian.h" + + +#define MAXMATCH 256 /* Maximum match length. */ +#define MINMATCH 3 /* Minimum match length. */ +/* + * Literal table format: + * +0 +256 +510 + * +---------------+-------------------------+ + * | literal code | match length | + * | 0 ... 255 | MINMATCH ... MAXMATCH | + * +---------------+-------------------------+ + * <--- LT_BITLEN_SIZE ---> + */ +/* Literal table size. */ +#define LT_BITLEN_SIZE (UCHAR_MAX + 1 + MAXMATCH - MINMATCH + 1) +/* Position table size. + * Note: this used for both position table and pre literal table.*/ +#define PT_BITLEN_SIZE (3 + 16) + +struct lzh_dec { + /* Decoding status. */ + int state; + + /* + * Window to see last 8Ki(lh5),32Ki(lh6),64Ki(lh7) bytes of decoded + * data. + */ + int w_size; + int w_mask; + /* Window buffer, which is a loop buffer. */ + unsigned char *w_buff; + /* The insert position to the window. */ + int w_pos; + /* The position where we can copy decoded code from the window. */ + int copy_pos; + /* The length how many bytes we can copy decoded code from + * the window. */ + int copy_len; + + /* + * Bit stream reader. + */ + struct { +#define CACHE_TYPE uint64_t +#define CACHE_BITS (8 * sizeof(CACHE_TYPE)) + /* Cache buffer. */ + CACHE_TYPE cache_buffer; + /* Indicates how many bits avail in cache_buffer. */ + int cache_avail; + } br; + + /* + * Huffman coding. + */ + struct huffman { + int len_size; + int len_avail; + int len_bits; + int freq[17]; + unsigned char *bitlen; + short *skip; + + /* + * Use a index table. It's faster than traversing huffman + * coding tree, which is a binary tree. + * TODO: Reduce the memory used for the index table. + */ + int max_bits; + int tbl_bits; + uint16_t *tbl; + } lt, pt; + + int blocks_avail; + int pos_pt_len_size; + int pos_pt_len_bits; + int literal_pt_len_size; + int literal_pt_len_bits; + int reading_position; + int loop; + int error; +}; + +struct lzh_stream { + const unsigned char *next_in; + int64_t avail_in; + int64_t total_in; + unsigned char *next_out; + int64_t avail_out; + int64_t total_out; + struct lzh_dec *ds; +}; + +struct lha { + /* entry_bytes_remaining is the number of bytes we expect. */ + int64_t entry_offset; + int64_t entry_bytes_remaining; + int64_t entry_unconsumed; + uint16_t entry_crc_calculated; + + size_t header_size; /* header size */ + unsigned char level; /* header level */ + char method[3]; /* compress type */ + int64_t compsize; /* compressed data size */ + int64_t origsize; /* original file size */ + int setflag; +#define BIRTHTIME_IS_SET 1 +#define ATIME_IS_SET 2 +#define UNIX_MODE_IS_SET 4 + time_t birthtime; + long birthtime_tv_nsec; + time_t mtime; + long mtime_tv_nsec; + time_t atime; + long atime_tv_nsec; + mode_t mode; + int64_t uid; + int64_t gid; + struct archive_string uname; + struct archive_string gname; + uint16_t header_crc; + uint16_t crc; + + struct archive_string dirname; + struct archive_string filename; + struct archive_wstring ws; + struct archive_string mbs; + + unsigned char dos_attr; + + /* Flag to mark progress that an archive was read ther first header.*/ + char found_first_header; + /* Flag to mark that indicates an empty directory. */ + char directory; + + /* Flags to mark progress of decompression. */ + char decompress_init; + char end_of_entry; + char end_of_entry_cleanup; + char entry_is_compressed; + + unsigned char *uncompressed_buffer; + size_t uncompressed_buffer_size; + + char format_name[64]; + + struct lzh_stream strm; +}; + +/* + * LHA header common member offset. + */ +#define H_METHOD_OFFSET 2 /* Compress type. */ +#define H_ATTR_OFFSET 19 /* DOS attribute. */ +#define H_LEVEL_OFFSET 20 /* Header Level. */ +#define H_SIZE 22 /* Minimum header size. */ + +static const uint16_t crc16tbl[256] = { + 0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241, + 0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440, + 0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40, + 0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841, + 0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40, + 0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41, + 0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641, + 0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040, + 0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240, + 0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441, + 0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41, + 0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840, + 0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41, + 0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40, + 0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640, + 0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041, + 0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240, + 0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441, + 0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41, + 0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840, + 0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41, + 0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40, + 0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640, + 0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041, + 0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241, + 0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440, + 0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40, + 0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841, + 0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40, + 0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41, + 0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641, + 0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040 +}; + +static int archive_read_format_lha_bid(struct archive_read *); +static int archive_read_format_lha_read_header(struct archive_read *, + struct archive_entry *); +static int archive_read_format_lha_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_lha_read_data_skip(struct archive_read *); +static int archive_read_format_lha_cleanup(struct archive_read *); + +static int lha_read_file_header_0(struct archive_read *, struct lha *); +static int lha_read_file_header_1(struct archive_read *, struct lha *); +static int lha_read_file_header_2(struct archive_read *, struct lha *); +static int lha_read_file_header_3(struct archive_read *, struct lha *); +static int lha_read_file_extended_header(struct archive_read *, + struct lha *, uint16_t *, int, size_t, size_t *); +static size_t lha_check_header_format(const void *); +static int lha_skip_sfx(struct archive_read *); +static time_t lha_dos_time(const unsigned char *); +static time_t lha_win_time(uint64_t, long *); +static unsigned char lha_calcsum(unsigned char, const void *, + int, int); +static int lha_parse_linkname(struct archive_string *, + struct archive_string *); +static int lha_read_data_none(struct archive_read *, const void **, + size_t *, int64_t *); +static int lha_read_data_lzh(struct archive_read *, const void **, + size_t *, int64_t *); +static uint16_t lha_crc16(uint16_t, const void *, size_t); +static int lzh_decode_init(struct lzh_stream *, const char *); +static void lzh_decode_free(struct lzh_stream *); +static int lzh_decode(struct lzh_stream *, int); +static int lzh_br_fillup(struct lzh_stream *); +static int lzh_huffman_init(struct huffman *, size_t, int, int); +static void lzh_huffman_free(struct huffman *); +static int lzh_read_pt_bitlen(struct lzh_stream *, int start, int end); +static int lzh_make_fake_table(struct huffman *, uint16_t); +static int lzh_make_huffman_table(struct huffman *); + + +int +archive_read_support_format_lha(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct lha *lha; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_lha"); + + lha = (struct lha *)calloc(1, sizeof(*lha)); + if (lha == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate lha data"); + return (ARCHIVE_FATAL); + } + archive_string_init(&lha->ws); + archive_string_init(&lha->mbs); + + r = __archive_read_register_format(a, + lha, + "lha", + archive_read_format_lha_bid, + NULL, + archive_read_format_lha_read_header, + archive_read_format_lha_read_data, + archive_read_format_lha_read_data_skip, + archive_read_format_lha_cleanup); + + if (r != ARCHIVE_OK) + free(lha); + return (ARCHIVE_OK); +} + +static size_t +lha_check_header_format(const void *h) +{ + const unsigned char *p = h; + size_t next_skip_bytes; + + switch (p[H_METHOD_OFFSET+3]) { + /* + * "-lh0-" ... "-lh7-" "-lhd-" + * "-lzs-" "-lz5-" + */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case 'd': + case 's': + next_skip_bytes = 4; + + /* b0 == 0 means the end of an LHa archive file. */ + if (p[0] == 0) + break; + if (p[H_METHOD_OFFSET] != '-' || p[H_METHOD_OFFSET+1] != 'l' + || p[H_METHOD_OFFSET+4] != '-') + break; + + if (p[H_METHOD_OFFSET+2] == 'h') { + /* "-lh?-" */ + if (p[H_METHOD_OFFSET+3] == 's') + break; + if (p[H_LEVEL_OFFSET] == 0) + return (0); + if (p[H_LEVEL_OFFSET] <= 3 && p[H_ATTR_OFFSET] == 0x20) + return (0); + } + if (p[H_METHOD_OFFSET+2] == 'z') { + /* LArc extensions: -lzs-,-lz4- and -lz5- */ + if (p[H_LEVEL_OFFSET] != 0) + break; + if (p[H_METHOD_OFFSET+3] == 's' + || p[H_METHOD_OFFSET+3] == '4' + || p[H_METHOD_OFFSET+3] == '5') + return (0); + } + break; + case 'h': next_skip_bytes = 1; break; + case 'z': next_skip_bytes = 1; break; + case 'l': next_skip_bytes = 2; break; + case '-': next_skip_bytes = 3; break; + default : next_skip_bytes = 4; break; + } + + return (next_skip_bytes); +} + +static int +archive_read_format_lha_bid(struct archive_read *a) +{ + const char *p; + const void *buff; + ssize_t bytes_avail, offset, window; + size_t next; + + if ((p = __archive_read_ahead(a, H_SIZE, NULL)) == NULL) + return (-1); + + if (lha_check_header_format(p) == 0) + return (30); + + if (p[0] == 'M' && p[1] == 'Z') { + /* PE file */ + offset = 0; + window = 4096; + while (offset < (1024 * 20)) { + buff = __archive_read_ahead(a, offset + window, + &bytes_avail); + if (buff == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < (H_SIZE + 3)) + return (0); + continue; + } + p = (const char *)buff + offset; + while (p + H_SIZE < (const char *)buff + bytes_avail) { + if ((next = lha_check_header_format(p)) == 0) + return (30); + p += next; + } + offset = p - (const char *)buff; + } + } + return (0); +} + +static int +lha_skip_sfx(struct archive_read *a) +{ + const void *h; + const char *p, *q; + size_t next, skip; + ssize_t bytes, window; + + window = 4096; + for (;;) { + h = __archive_read_ahead(a, window, &bytes); + if (h == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < (H_SIZE + 3)) + goto fatal; + continue; + } + if (bytes < H_SIZE) + goto fatal; + p = h; + q = p + bytes; + + /* + * Scan ahead until we find something that looks + * like the lha header. + */ + while (p + H_SIZE < q) { + if ((next = lha_check_header_format(p)) == 0) { + skip = p - (const char *)h; + __archive_read_consume(a, skip); + return (ARCHIVE_OK); + } + p += next; + } + skip = p - (const char *)h; + __archive_read_consume(a, skip); + } +fatal: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Couldn't find out LHa header"); + return (ARCHIVE_FATAL); +} + +static int +truncated_error(struct archive_read *a) +{ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated LHa header"); + return (ARCHIVE_FATAL); +} + +static int +archive_read_format_lha_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct archive_string linkname; + struct archive_string pathname; + struct lha *lha; + const unsigned char *p; + const char *signature; + int err; + + a->archive.archive_format = ARCHIVE_FORMAT_LHA; + if (a->archive.archive_format_name == NULL) + a->archive.archive_format_name = "lha"; + + lha = (struct lha *)(a->format->data); + lha->decompress_init = 0; + lha->end_of_entry = 0; + lha->end_of_entry_cleanup = 0; + lha->entry_unconsumed = 0; + + if ((p = __archive_read_ahead(a, sizeof(*p), NULL)) == NULL) { + /* + * LHa archiver added 0 to the tail of its archive file as + * the mark of the end of the archive. + */ + signature = __archive_read_ahead(a, sizeof(signature[0]), NULL); + if (signature == NULL || signature[0] == 0) + return (ARCHIVE_EOF); + return (truncated_error(a)); + } + + signature = (const char *)p; + if (lha->found_first_header == 0 && + signature[0] == 'M' && signature[1] == 'Z') { + /* This is an executable? Must be self-extracting... */ + err = lha_skip_sfx(a); + if (err < ARCHIVE_WARN) + return (err); + + if ((p = __archive_read_ahead(a, sizeof(*p), NULL)) == NULL) + return (truncated_error(a)); + signature = (const char *)p; + } + /* signature[0] == 0 means the end of an LHa archive file. */ + if (signature[0] == 0) + return (ARCHIVE_EOF); + + /* + * Check the header format and method type. + */ + if (lha_check_header_format(p) != 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Bad LHa file"); + return (ARCHIVE_FATAL); + } + + /* We've found the first header. */ + lha->found_first_header = 1; + /* Set a default value and common data */ + lha->header_size = 0; + lha->level = p[H_LEVEL_OFFSET]; + lha->method[0] = p[H_METHOD_OFFSET+1]; + lha->method[1] = p[H_METHOD_OFFSET+2]; + lha->method[2] = p[H_METHOD_OFFSET+3]; + if (memcmp(lha->method, "lhd", 3) == 0) + lha->directory = 1; + else + lha->directory = 0; + if (memcmp(lha->method, "lh0", 3) == 0 || + memcmp(lha->method, "lz4", 3) == 0) + lha->entry_is_compressed = 0; + else + lha->entry_is_compressed = 1; + + lha->compsize = 0; + lha->origsize = 0; + lha->setflag = 0; + lha->birthtime = 0; + lha->birthtime_tv_nsec = 0; + lha->mtime = 0; + lha->mtime_tv_nsec = 0; + lha->atime = 0; + lha->atime_tv_nsec = 0; + lha->mode = (lha->directory)? 0777 : 0666; + lha->uid = 0; + lha->gid = 0; + archive_string_empty(&lha->dirname); + archive_string_empty(&lha->filename); + lha->dos_attr = 0; + + switch (p[H_LEVEL_OFFSET]) { + case 0: + err = lha_read_file_header_0(a, lha); + break; + case 1: + err = lha_read_file_header_1(a, lha); + break; + case 2: + err = lha_read_file_header_2(a, lha); + break; + case 3: + err = lha_read_file_header_3(a, lha); + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unsupported LHa header level %d", p[H_LEVEL_OFFSET]); + err = ARCHIVE_FATAL; + break; + } + if (err < ARCHIVE_WARN) + return (err); + + + if (!lha->directory && archive_strlen(&lha->filename) == 0) + /* The filename has not been set */ + return (truncated_error(a)); + + /* + * Make a pathname from a dirname and a filename. + */ + archive_string_init(&pathname); + archive_string_copy(&pathname, &lha->dirname); + archive_string_concat(&pathname, &lha->filename); + + if ((lha->mode & AE_IFMT) == AE_IFLNK) { + /* + * Extract the symlink-name if it's included in the pathname. + */ + archive_string_init(&linkname); + if (!lha_parse_linkname(&linkname, &pathname)) { + /* We couldn't get the symlink-name. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Unknown symlink-name"); + archive_string_free(&pathname); + archive_string_free(&linkname); + return (ARCHIVE_FAILED); + } + archive_entry_set_symlink(entry, linkname.s); + archive_string_free(&linkname); + } else { + archive_entry_set_symlink(entry, NULL); + /* + * Make sure a filetype is set. + * The mode has been overridden if it is in the extended data. + */ + lha->mode = (lha->mode & ~AE_IFMT) | + ((lha->directory)? AE_IFDIR: AE_IFREG); + } + if ((lha->setflag & UNIX_MODE_IS_SET) == 0 && + (lha->dos_attr & 1) != 0) + lha->mode &= ~(0222);/* read only. */ + + /* + * Set basic file parameters. + */ + archive_entry_set_pathname(entry, pathname.s); + archive_string_free(&pathname); + archive_entry_set_mode(entry, lha->mode); + archive_entry_set_uid(entry, lha->uid); + archive_entry_set_gid(entry, lha->gid); + if (archive_strlen(&lha->uname) > 0) + archive_entry_set_uname(entry, lha->uname.s); + if (archive_strlen(&lha->gname) > 0) + archive_entry_set_gname(entry, lha->gname.s); + if (lha->setflag & BIRTHTIME_IS_SET) + archive_entry_set_birthtime(entry, lha->birthtime, + lha->birthtime_tv_nsec); + else + archive_entry_unset_birthtime(entry); + archive_entry_set_mtime(entry, lha->mtime, lha->mtime_tv_nsec); + archive_entry_unset_ctime(entry); + if (lha->setflag & ATIME_IS_SET) + archive_entry_set_atime(entry, lha->atime, + lha->atime_tv_nsec); + else + archive_entry_unset_atime(entry); + if (lha->directory || archive_entry_symlink(entry) != NULL) + archive_entry_unset_size(entry); + else + archive_entry_set_size(entry, lha->origsize); + + /* + * Prepare variables used to read a file content. + */ + lha->entry_bytes_remaining = lha->compsize; + lha->entry_offset = 0; + lha->entry_crc_calculated = 0; + + /* + * This file does not have a content. + */ + if (lha->directory || lha->compsize == 0) + lha->end_of_entry = 1; + + sprintf(lha->format_name, "lha -%c%c%c-", + lha->method[0], lha->method[1], lha->method[2]); + a->archive.archive_format_name = lha->format_name; + + return (err); +} + +/* + * Replace a DOS path separator '\' by a character '/'. + * Some multi-byte character set have a character '\' in its second byte. + * Try to save a filename from breaking down as much as possible. + * Ideally, libarchive would have the ability to convert a character + * set for a filename in an archive. + */ +static void +lha_replace_path_separator(struct lha *lha, struct archive_string *fn) +{ + size_t i; + + for (i = 0; i < archive_strlen(fn); i++) { + if (fn->s[i] == '\\') + fn->s[i] = '/'; + else if (fn->s[i] & 0x80) + /* Are there any multibyte characters in fn ? */ + break; + } + if (i == archive_strlen(fn)) + return; + + /* + * Try to replace a character in wide character. + */ + + /* If converting to wide character failed, force a replacement. */ + if (!archive_wstrcpy_mbs(&(lha->ws), fn)) { + for (i = 0; i < archive_strlen(fn); i++) { + if (fn->s[i] == '\\') + fn->s[i] = '/'; + } + return; + } + + for (i = 0; i < archive_strlen(&(lha->ws)); i++) { + if (lha->ws.s[i] == L'\\') + lha->ws.s[i] = L'/'; + } + + /* + * Sanity check that we surely did not break a filename. + */ + archive_string_empty(&(lha->mbs)); + archive_strappend_w_mbs(&(lha->mbs), lha->ws.s); + /* If mbs length is differenct to fn, we broked the + * filename and we shouldn't use it. */ + if (archive_strlen(&(lha->mbs)) == archive_strlen(fn)) + archive_string_copy(fn, &(lha->mbs)); +} + +/* + * Header 0 format + * + * +0 +1 +2 +7 +11 + * +---------------+----------+----------------+-------------------+ + * |header size(*1)|header sum|compression type|compressed size(*2)| + * +---------------+----------+----------------+-------------------+ + * <---------------------(*1)----------* + * + * +11 +15 +17 +19 +20 +21 + * +-----------------+---------+---------+--------------+----------------+ + * |uncompressed size|time(DOS)|date(DOS)|attribute(DOS)|header level(=0)| + * +-----------------+---------+---------+--------------+----------------+ + * *--------------------------------(*1)---------------------------------* + * + * +21 +22 +22+(*3) +22+(*3)+2 +22+(*3)+2+(*4) + * +---------------+---------+----------+----------------+------------------+ + * |name length(*3)|file name|file CRC16|extra header(*4)| compressed data | + * +---------------+---------+----------+----------------+------------------+ + * <--(*3)-> <------(*2)------> + * *----------------------(*1)--------------------------> + * + */ +#define H0_HEADER_SIZE_OFFSET 0 +#define H0_HEADER_SUM_OFFSET 1 +#define H0_COMP_SIZE_OFFSET 7 +#define H0_ORIG_SIZE_OFFSET 11 +#define H0_DOS_TIME_OFFSET 15 +#define H0_NAME_LEN_OFFSET 21 +#define H0_FILE_NAME_OFFSET 22 +#define H0_FIXED_SIZE 24 +static int +lha_read_file_header_0(struct archive_read *a, struct lha *lha) +{ + const unsigned char *p; + int extdsize, namelen; + unsigned char headersum, sum_calculated; + + if ((p = __archive_read_ahead(a, H0_FIXED_SIZE, NULL)) == NULL) + return (truncated_error(a)); + lha->header_size = p[H0_HEADER_SIZE_OFFSET] + 2; + headersum = p[H0_HEADER_SUM_OFFSET]; + lha->compsize = archive_le32dec(p + H0_COMP_SIZE_OFFSET); + lha->origsize = archive_le32dec(p + H0_ORIG_SIZE_OFFSET); + lha->mtime = lha_dos_time(p + H0_DOS_TIME_OFFSET); + namelen = p[H0_NAME_LEN_OFFSET]; + extdsize = (int)lha->header_size - H0_FIXED_SIZE - namelen; + if (namelen > 221 || extdsize < 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid LHa header"); + return (ARCHIVE_FATAL); + } + if ((p = __archive_read_ahead(a, lha->header_size, NULL)) == NULL) + return (truncated_error(a)); + + archive_strncpy(&lha->filename, p + H0_FILE_NAME_OFFSET, namelen); + lha_replace_path_separator(lha, &lha->filename); + lha->crc = archive_le16dec(p + H0_FILE_NAME_OFFSET + namelen); + sum_calculated = lha_calcsum(0, p, 2, lha->header_size - 2); + + /* Read an extended header */ + if (extdsize > 0) { + /* This extended data is set by 'LHa for UNIX' only. + * Maybe fixed size. + */ + p += H0_FILE_NAME_OFFSET + namelen + 2; + if (p[0] == 'U' && extdsize == 12) { + /* p[1] is a minor version. */ + lha->mtime = archive_le32dec(&p[2]); + lha->mode = archive_le16dec(&p[6]); + lha->uid = archive_le16dec(&p[8]); + lha->gid = archive_le16dec(&p[10]); + lha->setflag |= UNIX_MODE_IS_SET; + } + } + __archive_read_consume(a, lha->header_size); + + if (sum_calculated != headersum) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "LHa header sum error"); + return (ARCHIVE_FATAL); + } + + return (ARCHIVE_OK); +} + +/* + * Header 1 format + * + * +0 +1 +2 +7 +11 + * +---------------+----------+----------------+-------------+ + * |header size(*1)|header sum|compression type|skip size(*2)| + * +---------------+----------+----------------+-------------+ + * <---------------(*1)----------* + * + * +11 +15 +17 +19 +20 +21 + * +-----------------+---------+---------+--------------+----------------+ + * |uncompressed size|time(DOS)|date(DOS)|attribute(DOS)|header level(=1)| + * +-----------------+---------+---------+--------------+----------------+ + * *-------------------------------(*1)----------------------------------* + * + * +21 +22 +22+(*3) +22+(*3)+2 +22+(*3)+3 +22+(*3)+3+(*4) + * +---------------+---------+----------+-----------+-----------+ + * |name length(*3)|file name|file CRC16| creator |padding(*4)| + * +---------------+---------+----------+-----------+-----------+ + * <--(*3)-> + * *----------------------------(*1)----------------------------* + * + * +22+(*3)+3+(*4) +22+(*3)+3+(*4)+2 +22+(*3)+3+(*4)+2+(*5) + * +----------------+---------------------+------------------------+ + * |next header size| extended header(*5) | compressed data | + * +----------------+---------------------+------------------------+ + * *------(*1)-----> <--------------------(*2)--------------------> + */ +#define H1_HEADER_SIZE_OFFSET 0 +#define H1_HEADER_SUM_OFFSET 1 +#define H1_COMP_SIZE_OFFSET 7 +#define H1_ORIG_SIZE_OFFSET 11 +#define H1_DOS_TIME_OFFSET 15 +#define H1_NAME_LEN_OFFSET 21 +#define H1_FILE_NAME_OFFSET 22 +#define H1_FIXED_SIZE 27 +static int +lha_read_file_header_1(struct archive_read *a, struct lha *lha) +{ + const unsigned char *p; + size_t extdsize; + int i, err, err2; + int namelen, padding; + unsigned char headersum, sum_calculated; + + err = ARCHIVE_OK; + + if ((p = __archive_read_ahead(a, H1_FIXED_SIZE, NULL)) == NULL) + return (truncated_error(a)); + + lha->header_size = p[H1_HEADER_SIZE_OFFSET] + 2; + headersum = p[H1_HEADER_SUM_OFFSET]; + /* Note: An extended header size is included in a compsize. */ + lha->compsize = archive_le32dec(p + H1_COMP_SIZE_OFFSET); + lha->origsize = archive_le32dec(p + H1_ORIG_SIZE_OFFSET); + lha->mtime = lha_dos_time(p + H1_DOS_TIME_OFFSET); + namelen = p[H1_NAME_LEN_OFFSET]; + /* Calculate a padding size. The result will be nomally 0 only(?) */ + padding = ((int)lha->header_size) - H1_FIXED_SIZE - namelen; + + if (namelen > 230 || padding < 0) + goto invalid; + + if ((p = __archive_read_ahead(a, lha->header_size, NULL)) == NULL) + return (truncated_error(a)); + + for (i = 0; i < namelen; i++) { + if (p[i + H1_FILE_NAME_OFFSET] == 0xff) + goto invalid;/* Invalid filename. */ + } + archive_strncpy(&lha->filename, p + H1_FILE_NAME_OFFSET, namelen); + lha->crc = archive_le16dec(p + H1_FILE_NAME_OFFSET + namelen); + + sum_calculated = lha_calcsum(0, p, 2, lha->header_size - 2); + /* Consume used bytes but not include `next header size' data + * since it will be consumed in lha_read_file_extended_header(). */ + __archive_read_consume(a, lha->header_size - 2); + + /* Read extended headers */ + err2 = lha_read_file_extended_header(a, lha, NULL, 2, + lha->compsize + 2, &extdsize); + if (err2 < ARCHIVE_WARN) + return (err2); + if (err2 < err) + err = err2; + /* Get a real compressed file size. */ + lha->compsize -= extdsize - 2; + + if (sum_calculated != headersum) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "LHa header sum error"); + return (ARCHIVE_FATAL); + } + return (err); +invalid: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid LHa header"); + return (ARCHIVE_FATAL); +} + +/* + * Header 2 format + * + * +0 +2 +7 +11 +15 + * +---------------+----------------+-------------------+-----------------+ + * |header size(*1)|compression type|compressed size(*2)|uncompressed size| + * +---------------+----------------+-------------------+-----------------+ + * <--------------------------------(*1)---------------------------------* + * + * +15 +19 +20 +21 +23 +24 + * +-----------------+------------+----------------+----------+-----------+ + * |data/time(time_t)| 0x20 fixed |header level(=2)|file CRC16| creator | + * +-----------------+------------+----------------+----------+-----------+ + * *---------------------------------(*1)---------------------------------* + * + * +24 +26 +26+(*3) +26+(*3)+(*4) + * +----------------+-------------------+-------------+-------------------+ + * |next header size|extended header(*3)| padding(*4) | compressed data | + * +----------------+-------------------+-------------+-------------------+ + * *--------------------------(*1)-------------------> <------(*2)-------> + * + */ +#define H2_HEADER_SIZE_OFFSET 0 +#define H2_COMP_SIZE_OFFSET 7 +#define H2_ORIG_SIZE_OFFSET 11 +#define H2_TIME_OFFSET 15 +#define H2_CRC_OFFSET 21 +#define H2_FIXED_SIZE 24 +static int +lha_read_file_header_2(struct archive_read *a, struct lha *lha) +{ + const unsigned char *p; + size_t extdsize; + int err, padding; + uint16_t header_crc; + + if ((p = __archive_read_ahead(a, H2_FIXED_SIZE, NULL)) == NULL) + return (truncated_error(a)); + + lha->header_size =archive_le16dec(p + H2_HEADER_SIZE_OFFSET); + lha->compsize = archive_le32dec(p + H2_COMP_SIZE_OFFSET); + lha->origsize = archive_le32dec(p + H2_ORIG_SIZE_OFFSET); + lha->mtime = archive_le32dec(p + H2_TIME_OFFSET); + lha->crc = archive_le16dec(p + H2_CRC_OFFSET); + + if (lha->header_size < H2_FIXED_SIZE) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid LHa header size"); + return (ARCHIVE_FATAL); + } + + header_crc = lha_crc16(0, p, H2_FIXED_SIZE); + __archive_read_consume(a, H2_FIXED_SIZE); + + /* Read extended headers */ + err = lha_read_file_extended_header(a, lha, &header_crc, 2, + lha->header_size - H2_FIXED_SIZE, &extdsize); + if (err < ARCHIVE_WARN) + return (err); + + /* Calculate a padding size. The result will be nomally 0 or 1. */ + padding = (int)lha->header_size - (int)(H2_FIXED_SIZE + extdsize); + if (padding > 0) { + if ((p = __archive_read_ahead(a, padding, NULL)) == NULL) + return (truncated_error(a)); + header_crc = lha_crc16(header_crc, p, padding); + __archive_read_consume(a, padding); + } + + if (header_crc != lha->header_crc) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "LHa header CRC error"); + return (ARCHIVE_FATAL); + } + return (err); +} + +/* + * Header 3 format + * + * +0 +2 +7 +11 +15 + * +------------+----------------+-------------------+-----------------+ + * | 0x04 fixed |compression type|compressed size(*2)|uncompressed size| + * +------------+----------------+-------------------+-----------------+ + * <-------------------------------(*1)-------------------------------* + * + * +15 +19 +20 +21 +23 +24 + * +-----------------+------------+----------------+----------+-----------+ + * |date/time(time_t)| 0x20 fixed |header level(=3)|file CRC16| creator | + * +-----------------+------------+----------------+----------+-----------+ + * *--------------------------------(*1)----------------------------------* + * + * +24 +28 +32 +32+(*3) + * +---------------+----------------+-------------------+-----------------+ + * |header size(*1)|next header size|extended header(*3)| compressed data | + * +---------------+----------------+-------------------+-----------------+ + * *------------------------(*1)-----------------------> <------(*2)-----> + * + */ +#define H3_FIELD_LEN_OFFSET 0 +#define H3_COMP_SIZE_OFFSET 7 +#define H3_ORIG_SIZE_OFFSET 11 +#define H3_TIME_OFFSET 15 +#define H3_CRC_OFFSET 21 +#define H3_HEADER_SIZE_OFFSET 24 +#define H3_FIXED_SIZE 28 +static int +lha_read_file_header_3(struct archive_read *a, struct lha *lha) +{ + const unsigned char *p; + size_t extdsize; + int err; + uint16_t header_crc; + + if ((p = __archive_read_ahead(a, H3_FIXED_SIZE, NULL)) == NULL) + return (truncated_error(a)); + + if (archive_le16dec(p + H3_FIELD_LEN_OFFSET) != 4) + goto invalid; + lha->header_size =archive_le32dec(p + H3_HEADER_SIZE_OFFSET); + lha->compsize = archive_le32dec(p + H3_COMP_SIZE_OFFSET); + lha->origsize = archive_le32dec(p + H3_ORIG_SIZE_OFFSET); + lha->mtime = archive_le32dec(p + H3_TIME_OFFSET); + lha->crc = archive_le16dec(p + H3_CRC_OFFSET); + + if (lha->header_size < H3_FIXED_SIZE + 4) + goto invalid; + header_crc = lha_crc16(0, p, H3_FIXED_SIZE); + __archive_read_consume(a, H3_FIXED_SIZE); + + /* Read extended headers */ + err = lha_read_file_extended_header(a, lha, &header_crc, 4, + lha->header_size - H3_FIXED_SIZE, &extdsize); + if (err < ARCHIVE_WARN) + return (err); + + if (header_crc != lha->header_crc) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "LHa header CRC error"); + return (ARCHIVE_FATAL); + } + return (err); +invalid: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid LHa header"); + return (ARCHIVE_FATAL); +} + +/* + * Extended header format + * + * +0 +2 +3 -- used in header 1 and 2 + * +0 +4 +5 -- used in header 3 + * +--------------+---------+-------------------+--------------+-- + * |ex-header size|header id| data |ex-header size| ....... + * +--------------+---------+-------------------+--------------+-- + * <-------------( ex-header size)------------> <-- next extended header --* + * + * If the ex-header size is zero, it is the make of the end of extended + * headers. + * + */ +static int +lha_read_file_extended_header(struct archive_read *a, struct lha *lha, + uint16_t *crc, int sizefield_length, size_t limitsize, size_t *total_size) +{ + const void *h; + const unsigned char *extdheader; + size_t extdsize; + size_t datasize; + unsigned int i; + unsigned char extdtype; + +#define EXT_HEADER_CRC 0x00 /* Header crc and information*/ +#define EXT_FILENAME 0x01 /* Filename */ +#define EXT_DIRECTORY 0x02 /* Directory name */ +#define EXT_DOS_ATTR 0x40 /* MS-DOS attribute */ +#define EXT_TIMESTAMP 0x41 /* Windows time stamp */ +#define EXT_FILESIZE 0x42 /* Large file size */ +#define EXT_TIMEZONE 0x43 /* Time zone */ +#define EXT_UTF16_FILENAME 0x44 /* UTF-16 filename */ +#define EXT_UTF16_DIRECTORY 0x45 /* UTF-16 directory name */ +#define EXT_CODEPAGE 0x46 /* Codepage */ +#define EXT_UNIX_MODE 0x50 /* File permission */ +#define EXT_UNIX_GID_UID 0x51 /* gid,uid */ +#define EXT_UNIX_GNAME 0x52 /* Group name */ +#define EXT_UNIX_UNAME 0x53 /* User name */ +#define EXT_UNIX_MTIME 0x54 /* Modified time */ +#define EXT_OS2_NEW_ATTR 0x7f /* new attribute(OS/2 only) */ +#define EXT_NEW_ATTR 0xff /* new attribute */ + + extdsize = 0; + *total_size = sizefield_length; + + for (;;) { + /* Read an extended header size. */ + if ((h = + __archive_read_ahead(a, sizefield_length, NULL)) == NULL) + return (truncated_error(a)); + /* Check if the size is the zero indicates the end of the + * extended header. */ + if (sizefield_length == sizeof(uint16_t)) + extdsize = archive_le16dec(h); + else + extdsize = archive_le32dec(h); + if (extdsize == 0) { + /* End of extended header */ + if (crc != NULL) + *crc = lha_crc16(*crc, h, sizefield_length); + __archive_read_consume(a, sizefield_length); + return (ARCHIVE_OK); + } + + /* Sanity check to the extended header size. */ + if (((uint64_t)*total_size + extdsize) > + (uint64_t)limitsize || + extdsize <= (size_t)sizefield_length) + goto invalid; + + /* Read the extended header. */ + if ((h = __archive_read_ahead(a, extdsize, NULL)) == NULL) + return (truncated_error(a)); + *total_size += extdsize; + + extdheader = (const unsigned char *)h; + /* Get the extended header type. */ + extdtype = extdheader[sizefield_length]; + /* Calculate an extended data size. */ + datasize = extdsize - (1 + sizefield_length); + /* Skip an extended header size field and type field. */ + extdheader += sizefield_length + 1; + + if (crc != NULL && extdtype != EXT_HEADER_CRC) + *crc = lha_crc16(*crc, h, extdsize); + switch (extdtype) { + case EXT_HEADER_CRC: + /* We only use a header CRC. Following data will not + * be used. */ + if (datasize >= 2) { + lha->header_crc = archive_le16dec(extdheader); + if (crc != NULL) { + static char zeros[2] = {0, 0}; + *crc = lha_crc16(*crc, h, + extdsize - datasize); + /* CRC value itself as zero */ + *crc = lha_crc16(*crc, zeros, 2); + *crc = lha_crc16(*crc, + extdheader+2, datasize - 2); + } + } + break; + case EXT_FILENAME: + if (datasize == 0) { + /* maybe directory header */ + archive_string_empty(&lha->filename); + break; + } + archive_strncpy(&lha->filename, + (const char *)extdheader, datasize); + break; + case EXT_DIRECTORY: + if (datasize == 0) + /* no directory name data. exit this case. */ + break; + + archive_strncpy(&lha->dirname, + (const char *)extdheader, datasize); + /* + * Convert directory delimiter from 0xFF + * to '/' for local system. + */ + for (i = 0; i < lha->dirname.length; i++) { + if ((unsigned char)lha->dirname.s[i] == 0xFF) + lha->dirname.s[i] = '/'; + } + /* Is last character directory separator? */ + if (lha->dirname.s[lha->dirname.length-1] != '/') + /* invalid directory data */ + goto invalid; + break; + case EXT_DOS_ATTR: + if (datasize == 2) + lha->dos_attr = (unsigned char) + (archive_le16dec(extdheader) & 0xff); + break; + case EXT_TIMESTAMP: + if (datasize == (sizeof(uint64_t) * 3)) { + lha->birthtime = lha_win_time( + archive_le64dec(extdheader), + &lha->birthtime_tv_nsec); + extdheader += sizeof(uint64_t); + lha->mtime = lha_win_time( + archive_le64dec(extdheader), + &lha->mtime_tv_nsec); + extdheader += sizeof(uint64_t); + lha->atime = lha_win_time( + archive_le64dec(extdheader), + &lha->atime_tv_nsec); + lha->setflag |= BIRTHTIME_IS_SET | + ATIME_IS_SET; + } + break; + case EXT_FILESIZE: + if (datasize == sizeof(uint64_t) * 2) { + lha->compsize = archive_le64dec(extdheader); + extdheader += sizeof(uint64_t); + lha->origsize = archive_le64dec(extdheader); + } + break; + case EXT_UNIX_MODE: + if (datasize == sizeof(uint16_t)) { + lha->mode = archive_le16dec(extdheader); + lha->setflag |= UNIX_MODE_IS_SET; + } + break; + case EXT_UNIX_GID_UID: + if (datasize == (sizeof(uint16_t) * 2)) { + lha->gid = archive_le16dec(extdheader); + lha->uid = archive_le16dec(extdheader+2); + } + break; + case EXT_UNIX_GNAME: + if (datasize > 0) + archive_strncpy(&lha->gname, + (const char *)extdheader, datasize); + break; + case EXT_UNIX_UNAME: + if (datasize > 0) + archive_strncpy(&lha->uname, + (const char *)extdheader, datasize); + break; + case EXT_UNIX_MTIME: + if (datasize == sizeof(uint32_t)) + lha->mtime = archive_le32dec(extdheader); + break; + case EXT_OS2_NEW_ATTR: + /* This extended header is OS/2 depend. */ + if (datasize == 16) { + lha->dos_attr = (unsigned char) + (archive_le16dec(extdheader) & 0xff); + lha->mode = archive_le16dec(extdheader+2); + lha->gid = archive_le16dec(extdheader+4); + lha->uid = archive_le16dec(extdheader+6); + lha->birthtime = archive_le32dec(extdheader+8); + lha->atime = archive_le32dec(extdheader+12); + lha->setflag |= UNIX_MODE_IS_SET + | BIRTHTIME_IS_SET | ATIME_IS_SET; + } + break; + case EXT_NEW_ATTR: + if (datasize == 20) { + lha->mode = (mode_t)archive_le32dec(extdheader); + lha->gid = archive_le32dec(extdheader+4); + lha->uid = archive_le32dec(extdheader+8); + lha->birthtime = archive_le32dec(extdheader+12); + lha->atime = archive_le32dec(extdheader+16); + lha->setflag |= UNIX_MODE_IS_SET + | BIRTHTIME_IS_SET | ATIME_IS_SET; + } + break; + case EXT_TIMEZONE: /* Not supported */ + case EXT_UTF16_FILENAME: /* Not supported */ + case EXT_UTF16_DIRECTORY: /* Not supported */ + case EXT_CODEPAGE: /* Not supported */ + default: + break; + } + + __archive_read_consume(a, extdsize); + } +invalid: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid extended LHa header"); + return (ARCHIVE_FATAL); +} + +static int +archive_read_format_lha_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + struct lha *lha = (struct lha *)(a->format->data); + int r; + + if (lha->entry_unconsumed) { + /* Consume as much as the decompressor actually used. */ + __archive_read_consume(a, lha->entry_unconsumed); + lha->entry_unconsumed = 0; + } + if (lha->end_of_entry) { + if (!lha->end_of_entry_cleanup) { + if (lha->level >= 2 && + lha->crc != lha->entry_crc_calculated) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "LHa data CRC error"); + return (ARCHIVE_WARN); + } + + /* End-of-entry cleanup done. */ + lha->end_of_entry_cleanup = 1; + } + *offset = lha->entry_offset; + *size = 0; + *buff = NULL; + return (ARCHIVE_EOF); + } + + if (lha->entry_is_compressed) + r = lha_read_data_lzh(a, buff, size, offset); + else + /* No compression. */ + r = lha_read_data_none(a, buff, size, offset); + return (r); +} + +/* + * Read a file content in no compression. + * + * Returns ARCHIVE_OK if successful, ARCHIVE_FATAL otherwise, sets + * lha->end_of_entry if it consumes all of the data. + */ +static int +lha_read_data_none(struct archive_read *a, const void **buff, + size_t *size, int64_t *offset) +{ + struct lha *lha = (struct lha *)(a->format->data); + ssize_t bytes_avail; + + if (lha->entry_bytes_remaining == 0) { + *buff = NULL; + *size = 0; + *offset = lha->entry_offset; + lha->end_of_entry = 1; + return (ARCHIVE_OK); + } + /* + * 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 LHa file data"); + return (ARCHIVE_FATAL); + } + if (bytes_avail > lha->entry_bytes_remaining) + bytes_avail = lha->entry_bytes_remaining; + lha->entry_crc_calculated = + lha_crc16(lha->entry_crc_calculated, *buff, bytes_avail); + *size = bytes_avail; + *offset = lha->entry_offset; + lha->entry_offset += bytes_avail; + lha->entry_bytes_remaining -= bytes_avail; + if (lha->entry_bytes_remaining == 0) + lha->end_of_entry = 1; + lha->entry_unconsumed = bytes_avail; + return (ARCHIVE_OK); +} + +/* + * Read a file content in LZHUFF encoding. + * + * Returns ARCHIVE_OK if successful, returns ARCHIVE_WARN if compression is + * unsupported, ARCHIVE_FATAL otherwise, sets lha->end_of_entry if it consumes + * all of the data. + */ +static int +lha_read_data_lzh(struct archive_read *a, const void **buff, + size_t *size, int64_t *offset) +{ + struct lha *lha = (struct lha *)(a->format->data); + ssize_t bytes_avail; + int r; + + /* If the buffer hasn't been allocated, allocate it now. */ + if (lha->uncompressed_buffer == NULL) { + lha->uncompressed_buffer_size = 64 * 1024; + lha->uncompressed_buffer + = (unsigned char *)malloc(lha->uncompressed_buffer_size); + if (lha->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for lzh decompression"); + return (ARCHIVE_FATAL); + } + } + + /* If we haven't yet read any data, initialize the decompressor. */ + if (!lha->decompress_init) { + r = lzh_decode_init(&(lha->strm), lha->method); + switch (r) { + case ARCHIVE_OK: + break; + case ARCHIVE_FAILED: + /* Unsupported compression. */ + *buff = NULL; + *size = 0; + *offset = 0; + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Unsupported lzh compression method -%c%c%c-", + lha->method[0], lha->method[1], lha->method[2]); + /* We know compressed size; just skip it. */ + archive_read_format_lha_read_data_skip(a); + return (ARCHIVE_WARN); + default: + archive_set_error(&a->archive, ENOMEM, + "Couldn't allocate memory " + "for lzh decompression"); + return (ARCHIVE_FATAL); + } + /* We've initialized decompression for this stream. */ + lha->decompress_init = 1; + lha->strm.avail_out = 0; + lha->strm.total_out = 0; + } + + /* + * 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. + */ + lha->strm.next_in = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated LHa file body"); + return (ARCHIVE_FATAL); + } + if (bytes_avail > lha->entry_bytes_remaining) + bytes_avail = lha->entry_bytes_remaining; + + lha->strm.avail_in = bytes_avail; + lha->strm.total_in = 0; + if (lha->strm.avail_out == 0) { + lha->strm.next_out = lha->uncompressed_buffer; + lha->strm.avail_out = lha->uncompressed_buffer_size; + } + + r = lzh_decode(&(lha->strm), bytes_avail == lha->entry_bytes_remaining); + switch (r) { + case ARCHIVE_OK: + break; + case ARCHIVE_EOF: + lha->end_of_entry = 1; + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Bad lzh data"); + return (ARCHIVE_FAILED); + } + lha->entry_unconsumed = lha->strm.total_in; + lha->entry_bytes_remaining -= lha->strm.total_in; + + if (lha->strm.avail_out == 0 || lha->end_of_entry) { + *offset = lha->entry_offset; + *size = lha->strm.next_out - lha->uncompressed_buffer; + *buff = lha->uncompressed_buffer; + lha->entry_crc_calculated = lha_crc16( + lha->entry_crc_calculated, lha->uncompressed_buffer, *size); + lha->entry_offset += *size; + } else { + *offset = lha->entry_offset; + *size = 0; + *buff = NULL; + } + return (ARCHIVE_OK); +} + +/* + * Skip a file content. + */ +static int +archive_read_format_lha_read_data_skip(struct archive_read *a) +{ + struct lha *lha; + off_t bytes_skipped; + + lha = (struct lha *)(a->format->data); + + if (lha->entry_unconsumed) { + /* Consume as much as the decompressor actually used. */ + __archive_read_consume(a, lha->entry_unconsumed); + lha->entry_unconsumed = 0; + } + + /* if we've already read to end of data, we're done. */ + if (lha->end_of_entry_cleanup) + return (ARCHIVE_OK); + + /* + * If the length is at the beginning, we can skip the + * compressed data much more quickly. + */ + bytes_skipped = __archive_read_consume(a, lha->entry_bytes_remaining); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + + /* This entry is finished and done. */ + lha->end_of_entry_cleanup = lha->end_of_entry = 1; + return (ARCHIVE_OK); +} + +static int +archive_read_format_lha_cleanup(struct archive_read *a) +{ + struct lha *lha = (struct lha *)(a->format->data); + + lzh_decode_free(&(lha->strm)); + free(lha->uncompressed_buffer); + archive_string_free(&(lha->dirname)); + archive_string_free(&(lha->filename)); + archive_string_free(&(lha->uname)); + archive_string_free(&(lha->gname)); + archive_wstring_free(&(lha->ws)); + archive_string_free(&(lha->mbs)); + free(lha); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +/* + * 'LHa for UNIX' utility has archived a symbolic-link name after + * a pathname with '|' character. + * This function extracts the symbolic-link name from the pathname. + * + * example. + * 1. a symbolic-name is 'aaa/bb/cc' + * 2. a filename is 'xxx/bbb' + * then a archived pathname is 'xxx/bbb|aaa/bb/cc' + */ +static int +lha_parse_linkname(struct archive_string *linkname, + struct archive_string *pathname) +{ + char * linkptr; + int symlen; + + linkptr = strchr(pathname->s, '|'); + if (linkptr != NULL) { + symlen = strlen(linkptr + 1); + archive_strncpy(linkname, linkptr+1, symlen); + + *linkptr = 0; + pathname->length = strlen(pathname->s); + + return (1); + } + return (0); +} + +/* Convert an MSDOS-style date/time into Unix-style time. */ +static time_t +lha_dos_time(const unsigned char *p) +{ + int msTime, msDate; + struct tm ts; + + msTime = archive_le16dec(p); + msDate = archive_le16dec(p+2); + + memset(&ts, 0, sizeof(ts)); + ts.tm_year = ((msDate >> 9) & 0x7f) + 80; /* Years since 1900. */ + ts.tm_mon = ((msDate >> 5) & 0x0f) - 1; /* Month number. */ + ts.tm_mday = msDate & 0x1f; /* Day of month. */ + ts.tm_hour = (msTime >> 11) & 0x1f; + ts.tm_min = (msTime >> 5) & 0x3f; + ts.tm_sec = (msTime << 1) & 0x3e; + ts.tm_isdst = -1; + return (mktime(&ts)); +} + +/* Convert an MS-Windows-style date/time into Unix-style time. */ +static time_t +lha_win_time(uint64_t wintime, long *ns) +{ +#define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) + + if (wintime >= EPOC_TIME) { + wintime -= EPOC_TIME; /* 1970-01-01 00:00:00 (UTC) */ + if (ns != NULL) + *ns = (long)(wintime % 10000000) * 100; + return (wintime / 10000000); + } else { + if (ns != NULL) + *ns = 0; + return (0); + } +} + +static unsigned char +lha_calcsum(unsigned char sum, const void *pp, int offset, int size) +{ + unsigned char const *p = (unsigned char const *)pp; + + p += offset; + while (--size >= 0) + sum += *p++; + return (sum); +} + +static uint16_t +lha_crc16(uint16_t crc, const void *pp, size_t len) +{ + const unsigned char *buff = (const unsigned char *)pp; + + while (len-- > 0) + crc = crc16tbl[(crc ^ (*buff++)) & 0xFF] ^ (crc >> 8); + return (crc); +} + + +/* + * Initialize LZHUF decoder. + * + * Returns ARCHIVE_OK if initialization was successful. + * Returns ARCHIVE_FAILED if method is unsupported. + * Returns ARCHIVE_FATAL if initialization failed; memory allocation + * error occurred. + */ +static int +lzh_decode_init(struct lzh_stream *strm, const char *method) +{ + struct lzh_dec *ds; + int w_bits, w_size; + + if (strm->ds == NULL) { + strm->ds = calloc(1, sizeof(*strm->ds)); + if (strm->ds == NULL) + return (ARCHIVE_FATAL); + } + ds = strm->ds; + ds->error = ARCHIVE_FAILED; + if (method == NULL || method[0] != 'l' || method[1] != 'h') + return (ARCHIVE_FAILED); + switch (method[2]) { + case '5': + w_bits = 13;/* 8KiB for window */ + break; + case '6': + w_bits = 15;/* 32KiB for window */ + break; + case '7': + w_bits = 16;/* 64KiB for window */ + break; + default: + return (ARCHIVE_FAILED);/* Not supported. */ + } + ds->error = ARCHIVE_FATAL; + w_size = ds->w_size; + ds->w_size = 1U << w_bits; + ds->w_mask = ds->w_size -1; + if (ds->w_buff == NULL || w_size != ds->w_size) { + free(ds->w_buff); + ds->w_buff = malloc(ds->w_size); + if (ds->w_buff == NULL) + return (ARCHIVE_FATAL); + } + memset(ds->w_buff, 0x20, ds->w_size); + ds->w_pos = 0; + ds->state = 0; + ds->pos_pt_len_size = w_bits + 1; + ds->pos_pt_len_bits = (w_bits == 15 || w_bits == 16)? 5: 4; + ds->literal_pt_len_size = PT_BITLEN_SIZE; + ds->literal_pt_len_bits = 5; + ds->br.cache_buffer = 0; + ds->br.cache_avail = 0; + + if (lzh_huffman_init(&(ds->lt), LT_BITLEN_SIZE, 16, 0) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + ds->lt.len_bits = 9; + if (lzh_huffman_init(&(ds->pt), PT_BITLEN_SIZE, 12, 1) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + ds->error = 0; + + return (ARCHIVE_OK); +} + +/* + * Relase LZHUF decoder. + */ +static void +lzh_decode_free(struct lzh_stream *strm) +{ + + if (strm->ds == NULL) + return; + free(strm->ds->w_buff); + lzh_huffman_free(&(strm->ds->lt)); + lzh_huffman_free(&(strm->ds->pt)); + free(strm->ds); + strm->ds = NULL; +} + +/* + * Bit stream reader. + */ +/* Check that the cache buffer has enough bits. */ +#define lzh_br_has(strm, n) ((strm)->ds->br.cache_avail >= n) +/* Get compressed data by bit. */ +#define lzh_br_bits(strm, n) \ + (((uint16_t)((strm)->ds->br.cache_buffer >> \ + ((strm)->ds->br.cache_avail - (n)))) & cache_masks[n]) +#define lzh_br_bits_forced(strm, n) \ + (((uint16_t)((strm)->ds->br.cache_buffer << \ + ((n) - (strm)->ds->br.cache_avail))) & cache_masks[n]) +/* Read ahead to make sure the cache buffer has enough compressed data we + * will use. + * True : completed, there is enough data in the cache buffer. + * False : we met that strm->next_in is empty, we have to get following + * bytes. */ +#define lzh_br_read_ahead(strm, n) \ + (lzh_br_has((strm), (n)) || lzh_br_fillup(strm)) +/* Notify how man bits we consumed. */ +#define lzh_br_consume(strm, n) ((strm)->ds->br.cache_avail -= (n)) + +static uint16_t cache_masks[] = { + 0x0000, 0x0001, 0x0003, 0x0007, + 0x000F, 0x001F, 0x003F, 0x007F, + 0x00FF, 0x01FF, 0x03FF, 0x07FF, + 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF +}; + +/* + * Shift away used bits in the cache data and fill it up with following bits. + * Call this when cache buffer does not have enough bits you need. + * + * Returns 1 if the cache buffer is full. + * Returns 0 if the cache buffer is not full; input buffer is empty. + */ +static int +lzh_br_fillup(struct lzh_stream *strm) +{ + struct lzh_dec *ds = strm->ds; + int n = CACHE_BITS - ds->br.cache_avail; + + while (n >= 8) { + if (strm->avail_in == 0) { + /* There is not enough compressed data to fill up the + * cache buffer. */ + return (0); + } else if (strm->avail_in >= 8) { + int bytes; + switch (bytes = (n >> 3)) { + case 8: + ds->br.cache_buffer = + archive_be64dec(strm->next_in); + break; + case 7: + ds->br.cache_buffer = + (ds->br.cache_buffer << 56) | + archive_be64dec(strm->next_in) >> 8; + break; + case 6: + ds->br.cache_buffer = + (ds->br.cache_buffer << 48) | + (((uint64_t)archive_be32dec( + strm->next_in)) << 16) | + archive_be16dec(&strm->next_in[4]); + break; + default: + bytes = 0; + break; + } + if (bytes) { + strm->next_in += bytes; + strm->avail_in -= bytes; + strm->total_in += bytes; + ds->br.cache_avail += bytes * 8; + return (1); + } + } + ds->br.cache_buffer = + (ds->br.cache_buffer << 8) | *strm->next_in++; + strm->avail_in--; + strm->total_in++; + ds->br.cache_avail += 8; + n -= 8; + } + return (1); +} + +/* + * Decode LZHUF. + * + * 1. Returns ARCHIVE_OK if output buffer or input buffer are empty. + * Please set available buffer and call this function again. + * 2. Returns ARCHIVE_EOF if decompression has been completed. + * 3. Returns ARCHIVE_FAILED if an error occurred; compressed data + * is broken or you do not set 'last' flag properly. + * 4. 'last' flag is very important, you must set 1 to the flag if there + * is no input data. The lha compressed data format does not provide how + * to know the compressed data is really finished. + * Note: lha command utility check if the total size of output bytes is + * reached the uncompressed size recorded in its header. it does not mind + * that the decoding process is properly finished. + * GNU ZIP can decompress another compressed file made by SCO LZH compress. + * it handles EOF as null to fill read buffer with zero until the decoding + * process meet 2 bytes of zeros at reading a size of a next chunk, so the + * zeros are treated as the mark of the end of the data although the zeros + * is dummy, not the file data. + */ +static int +lzh_decode(struct lzh_stream *strm, int last) +{ + struct lzh_dec *ds = strm->ds; + int c, i; +#define ST_RD_BLOCK 0 +#define ST_RD_PT_1 1 +#define ST_RD_PT_2 2 +#define ST_RD_PT_3 3 +#define ST_RD_PT_4 4 +#define ST_RD_LITERAL_1 5 +#define ST_RD_LITERAL_2 6 +#define ST_RD_LITERAL_3 7 +#define ST_RD_POS_DATA_1 8 +#define ST_GET_LITERAL 9 +#define ST_GET_POS_1 10 +#define ST_GET_POS_2 11 +#define ST_COPY_DATA 12 + if (ds->error) + return (ds->error); + + for (;;) { + switch (ds->state) { + case ST_RD_BLOCK: + /* + * Read a block number indicates how many blocks + * we will handle. the block is composed of a + * literal and a match, sometimes a literal only + * in particular, there are no reference data at + * the beginning of the decompression. + */ + if (!lzh_br_read_ahead(strm, 16)) { + if (!last) + /* We need following data. */ + return (ARCHIVE_OK); + if (lzh_br_has(strm, 8)) { + /* + * It seems there are extra bits. + * 1. Compressed data is broken. + * 2. `last' flag does not properly + * set. + */ + goto failed; + } + /* End of compressed data; we have completely + * handled all compressed data. */ + return (ARCHIVE_EOF); + } + ds->blocks_avail = lzh_br_bits(strm, 16); + if (ds->blocks_avail == 0) + goto failed; + lzh_br_consume(strm, 16); + /* + * Read a literal table compressed in huffman + * coding. + */ + ds->pt.len_size = ds->literal_pt_len_size; + ds->pt.len_bits = ds->literal_pt_len_bits; + ds->reading_position = 0; + /* FALL THROUGH */ + case ST_RD_PT_1: + /* Note: ST_RD_PT_1, ST_RD_PT_2 and ST_RD_PT_4 are + * used in reading both a literal table and a + * position table. */ + if (!lzh_br_read_ahead(strm, ds->pt.len_bits)) { + if (last) + goto failed;/* Truncated data. */ + ds->state = ST_RD_PT_1; + return (ARCHIVE_OK); + } + ds->pt.len_avail = lzh_br_bits(strm, ds->pt.len_bits); + lzh_br_consume(strm, ds->pt.len_bits); + /* FALL THROUGH */ + case ST_RD_PT_2: + if (ds->pt.len_avail == 0) { + /* There is no bitlen. */ + if (!lzh_br_read_ahead(strm, ds->pt.len_bits)) { + if (last) + goto failed;/* Truncated data.*/ + ds->state = ST_RD_PT_2; + return (ARCHIVE_OK); + } + if (!lzh_make_fake_table(&(ds->pt), + lzh_br_bits(strm, ds->pt.len_bits))) + goto failed;/* Invalid data. */ + lzh_br_consume(strm, ds->pt.len_bits); + if (ds->reading_position) + ds->state = ST_GET_LITERAL; + else + ds->state = ST_RD_LITERAL_1; + break; + } else if (ds->pt.len_avail > ds->pt.len_size) + goto failed;/* Invalid data. */ + ds->loop = 0; + memset(ds->pt.freq, 0, sizeof(ds->pt.freq)); + if (ds->pt.len_avail < 3 || + ds->pt.len_size == ds->pos_pt_len_size) { + ds->state = ST_RD_PT_4; + break; + } + /* FALL THROUGH */ + case ST_RD_PT_3: + ds->loop = lzh_read_pt_bitlen(strm, ds->loop, 3); + if (ds->loop < 3) { + if (ds->loop < 0 || last) + goto failed;/* Invalid data. */ + /* Not completed, get following data. */ + ds->state = ST_RD_PT_3; + return (ARCHIVE_OK); + } + /* There are some null in bitlen of the literal. */ + if (!lzh_br_read_ahead(strm, 2)) { + if (last) + goto failed;/* Truncated data. */ + ds->state = ST_RD_PT_3; + return (ARCHIVE_OK); + } + c = lzh_br_bits(strm, 2); + lzh_br_consume(strm, 2); + if (c > ds->pt.len_avail - 3) + goto failed;/* Invalid data. */ + for (i = 3; c-- > 0 ;) + ds->pt.bitlen[i++] = 0; + ds->loop = i; + /* FALL THROUGH */ + case ST_RD_PT_4: + ds->loop = lzh_read_pt_bitlen(strm, ds->loop, + ds->pt.len_avail); + if (ds->loop < ds->pt.len_avail) { + if (ds->loop < 0 || last) + goto failed;/* Invalid data. */ + /* Not completed, get following data. */ + ds->state = ST_RD_PT_4; + return (ARCHIVE_OK); + } + if (!lzh_make_huffman_table(&(ds->pt))) + goto failed;/* Invalid data */ + if (ds->reading_position) { + ds->state = ST_GET_LITERAL; + break; + } + /* FALL THROUGH */ + case ST_RD_LITERAL_1: + if (!lzh_br_read_ahead(strm, ds->lt.len_bits)) { + if (last) + goto failed;/* Truncated data. */ + ds->state = ST_RD_LITERAL_1; + return (ARCHIVE_OK); + } + ds->lt.len_avail = lzh_br_bits(strm, ds->lt.len_bits); + lzh_br_consume(strm, ds->lt.len_bits); + /* FALL THROUGH */ + case ST_RD_LITERAL_2: + if (ds->lt.len_avail == 0) { + /* There is no bitlen. */ + if (!lzh_br_read_ahead(strm, ds->lt.len_bits)) { + if (last) + goto failed;/* Truncated data.*/ + ds->state = ST_RD_LITERAL_2; + return (ARCHIVE_OK); + } + if (!lzh_make_fake_table(&(ds->lt), + lzh_br_bits(strm, ds->lt.len_bits))) + goto failed;/* Invalid data */ + lzh_br_consume(strm, ds->lt.len_bits); + ds->state = ST_RD_POS_DATA_1; + break; + } else if (ds->lt.len_avail > ds->lt.len_size) + goto failed;/* Invalid data */ + ds->loop = 0; + memset(ds->lt.freq, 0, sizeof(ds->lt.freq)); + /* FALL THROUGH */ + case ST_RD_LITERAL_3: + i = ds->loop; + while (i < ds->lt.len_avail) { + if (!lzh_br_read_ahead(strm, ds->pt.max_bits)) { + if (last) + goto failed;/* Truncated data.*/ + ds->loop = i; + ds->state = ST_RD_LITERAL_3; + return (ARCHIVE_OK); + } + c = ds->pt.tbl[lzh_br_bits(strm, + ds->pt.max_bits)]; + if (c > 2) { + /* Note: 'c' will never be more than + * eighteen since it's limited by + * PT_BITLEN_SIZE, which is being set + * to ds->pt.len_size through + * ds->literal_pt_len_size. */ + lzh_br_consume(strm, ds->pt.bitlen[c]); + c -= 2; + ds->lt.freq[c]++; + ds->lt.bitlen[i++] = c; + } else if (c == 0) { + lzh_br_consume(strm, ds->pt.bitlen[c]); + ds->lt.skip[i] = 1; + ds->lt.bitlen[i++] = 0; + } else { + /* c == 1 or c == 2 */ + int n = (c == 1)?4:9; + if (!lzh_br_read_ahead(strm, + ds->pt.bitlen[c] + n)) { + if (last) /* Truncated data. */ + goto failed; + ds->loop = i; + ds->state = ST_RD_LITERAL_3; + return (ARCHIVE_OK); + } + lzh_br_consume(strm, ds->pt.bitlen[c]); + c = lzh_br_bits(strm, n); + lzh_br_consume(strm, n); + c += (n == 4)?3:20; + ds->lt.skip[i] = c; + ds->lt.bitlen[i] = 0; + i += c; + } + } + if (i > ds->lt.len_avail || + !lzh_make_huffman_table(&(ds->lt))) + goto failed;/* Invalid data */ + /* FALL THROUGH */ + case ST_RD_POS_DATA_1: + /* + * Read a position table compressed in huffman + * coding. + */ + ds->pt.len_size = ds->pos_pt_len_size; + ds->pt.len_bits = ds->pos_pt_len_bits; + ds->reading_position = 1; + ds->state = ST_RD_PT_1; + break; + case ST_GET_LITERAL: + for (;;) { + if (ds->blocks_avail == 0) { + /* We have decoded all blocks. + * Let's handle next blocks. */ + ds->state = ST_RD_BLOCK; + break; + } + if (strm->avail_out <= 0) + /* Output buffer is empty. */ + return (ARCHIVE_OK); + + /* lzh_br_read_ahead() always try to fill the + * cache buffer up. In specific situation we + * are close to the end of the data, the cache + * buffer will not be full and thus we have to + * determain if the cache buffer has some bits + * as much as we need after lzh_br_read_ahead() + * failed. */ + if (!lzh_br_read_ahead(strm, + ds->lt.max_bits) && + !lzh_br_has(strm, ds->lt.max_bits)) { + if (!last) + return (ARCHIVE_OK); + /* Remaining bits are less than + * maximum bits(lt.max_bits) but maybe + * it still remains as much as we need, + * so we should try to use it with + * dummy bits. */ + c = ds->lt.tbl[lzh_br_bits_forced( + strm, ds->lt.max_bits)]; + lzh_br_consume(strm, ds->lt.bitlen[c]); + if (!lzh_br_has(strm, 0)) + goto failed;/* Over read. */ + } else { + c = ds->lt.tbl[lzh_br_bits(strm, + ds->lt.max_bits)]; + lzh_br_consume(strm, ds->lt.bitlen[c]); + } + ds->blocks_avail--; + if (c > UCHAR_MAX) + /* Current block is a match data. */ + break; + /* + * 'c' is exactly literal code. + */ + /* Save a decoded code to reference it + * afterward. */ + strm->ds->w_buff[ds->w_pos] = c; + strm->ds->w_pos = (ds->w_pos + 1) + & strm->ds->w_mask; + /* Store the decoded code to output buffer. */ + *strm->next_out++ = c; + strm->avail_out--; + strm->total_out++; + } + if (ds->state == ST_RD_BLOCK) + break; + /* 'c' is the length of a match pattern we have + * already extracted, which has be stored in + * window(ds->w_buff). */ + ds->copy_len = c - (UCHAR_MAX + 1) + MINMATCH; + /* FALL THROUGH */ + case ST_GET_POS_1: + /* + * Get a reference position. + */ + if (!lzh_br_read_ahead(strm, ds->pt.max_bits) && + !lzh_br_has(strm, ds->pt.max_bits)) { + if (!last) { + ds->state = ST_GET_POS_1; + return (ARCHIVE_OK); + } + c = ds->pt.tbl[lzh_br_bits_forced(strm, + ds->pt.max_bits)]; + lzh_br_consume(strm, ds->pt.bitlen[c]); + if (!lzh_br_has(strm, 0)) + goto failed;/* Over read. */ + } else { + c = ds->pt.tbl[lzh_br_bits(strm, + ds->pt.max_bits)]; + lzh_br_consume(strm, ds->pt.bitlen[c]); + } + ds->copy_pos = c; + /* FALL THROUGH */ + case ST_GET_POS_2: + if (ds->copy_pos > 1) { + /* We need an additional adjustment number to + * the position. */ + int p = ds->copy_pos - 1; + if (!lzh_br_read_ahead(strm, p) && + !lzh_br_has(strm, p)) { + if (last) + goto failed;/* Truncated data.*/ + ds->state = ST_GET_POS_2; + return (ARCHIVE_OK); + } + ds->copy_pos = (1 << p) + lzh_br_bits(strm, p); + lzh_br_consume(strm, p); + } + /* The position is actually a distance from the last + * code we had extracted and thus we have to convert + * it to a position of the window. */ + ds->copy_pos = + (ds->w_pos - ds->copy_pos - 1) & ds->w_mask; + /* FALL THROUGH */ + case ST_COPY_DATA: + /* + * Copy several bytes as extracted data from the window + * into the output buffer. + */ + do { + const unsigned char *s; + unsigned char *d; + int l,ll; + if (strm->avail_out <= 0) { + /* Output buffer is empty. */ + ds->state = ST_COPY_DATA; + return (ARCHIVE_OK); + } + l = ds->copy_len; + if (l > ds->w_size - ds->copy_pos) + l = ds->w_size - ds->copy_pos; + if (l > ds->w_size - ds->w_pos) + l = ds->w_size - ds->w_pos; + if (l > strm->avail_out) + l = (int)strm->avail_out; + ll = l; + s = &(ds->w_buff[ds->copy_pos]); + d = &(ds->w_buff[ds->w_pos]); + while (--l >= 0) { + *strm->next_out++ = *s; + *d++ = *s++; + } + strm->avail_out -= ll; + strm->total_out += ll; + ds->copy_pos = + (ds->copy_pos + ll) & ds->w_mask; + ds->w_pos = (ds->w_pos + ll) & ds->w_mask; + ds->copy_len -= ll; + } while (ds->copy_len > 0); + ds->state = ST_GET_LITERAL; + break; + } + } +failed: + return (ds->error = ARCHIVE_FAILED); +} + +static int +lzh_huffman_init(struct huffman *hf, size_t len_size, int tbl_bits, + int init_skip) +{ + unsigned i; + + if (hf->bitlen == NULL) { + hf->bitlen = malloc(len_size * sizeof(hf->bitlen[0])); + if (hf->bitlen == NULL) + return (ARCHIVE_FATAL); + } + if (hf->skip == NULL) { + hf->skip = malloc(len_size * sizeof(hf->skip[0])); + if (hf->skip == NULL) + return (ARCHIVE_FATAL); + } + if (init_skip) { + for (i = 0; i < len_size; i++) + hf->skip[i] = 1; + } + if (hf->tbl == NULL) { + hf->tbl = malloc((1 << tbl_bits) * sizeof(hf->tbl[0])); + if (hf->tbl == NULL) + return (ARCHIVE_FATAL); + } + hf->len_size = len_size; + hf->tbl_bits = tbl_bits; + return (ARCHIVE_OK); +} + +static void +lzh_huffman_free(struct huffman *hf) +{ + free(hf->bitlen); + free(hf->skip); + free(hf->tbl); +} + +static int +lzh_read_pt_bitlen(struct lzh_stream *strm, int start, int end) +{ + struct lzh_dec *ds = strm->ds; + int c, i; + + for (i = start; i < end;) { + /* + * bit pattern the number we need + * 000 -> 0 + * 001 -> 1 + * 010 -> 2 + * ... + * 110 -> 6 + * 1110 -> 7 + * 11110 -> 8 + * ... + * 1111111111110 -> 16 + */ + if (!lzh_br_read_ahead(strm, 3)) + return (i); + if ((c = lzh_br_bits(strm, 3)) == 7) { + int d; + if (!lzh_br_read_ahead(strm, 13)) + return (i); + d = lzh_br_bits(strm, 13); + while (d & 0x200) { + c++; + d <<= 1; + } + if (c > 16) + return (-1);/* Invalid data. */ + lzh_br_consume(strm, c - 3); + } else + lzh_br_consume(strm, 3); + ds->pt.bitlen[i++] = c; + ds->pt.freq[c]++; + } + return (i); +} + +static int +lzh_make_fake_table(struct huffman *hf, uint16_t c) +{ + if (c >= hf->len_size) + return (0); + hf->tbl[0] = c; + hf->max_bits = 0; + hf->bitlen[hf->tbl[0]] = 0; + return (1); +} + +/* + * Make a huffman coding table. + */ +static int +lzh_make_huffman_table(struct huffman *hf) +{ + int bitptn[17], weight[17]; + int i, maxbits = 0, ptn, tbl_size, w; + + /* + * Initialize bit patterns. + */ + ptn = 0; + for (i = 1, w = 1 << 15; i <= 16; i++, w >>= 1) { + bitptn[i] = ptn; + weight[i] = w; + if (hf->freq[i]) { + ptn += hf->freq[i] * w; + maxbits = i; + } + } + if ((ptn & 0xffff) != 0 || maxbits > hf->tbl_bits) + return (0);/* Invalid */ + + hf->max_bits = maxbits; + tbl_size = 1 << maxbits; + + /* + * Cut out extra bits which we won't house in the table. + * This preparation reduces the same calculation in the for-loop + * making the table. + */ + if (maxbits < 16) { + int ebits = 16 - maxbits; + for (i = 1; i <= maxbits; i++) { + bitptn[i] >>= ebits; + weight[i] >>= ebits; + } + } + + /* + * Make the table. + */ + for (i = 0; i < hf->len_avail; ) { + uint16_t *p; + int len, cnt; + + if ((len = hf->bitlen[i]) == 0) { + i += hf->skip[i]; + continue; + } + /* Get a bit pattern */ + ptn = bitptn[len]; + cnt = weight[len]; + /* Calculate next bit pattern */ + if ((bitptn[len] += cnt) > tbl_size) + return (0);/* Invalid */ + /* Update the table */ + p = &(hf->tbl[ptn]); + while (--cnt >= 0) + *p++ = (uint16_t)i; + i++; + } + return (1); +} + diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt index e0b9d1985..614ac1c14 100644 --- a/libarchive/test/CMakeLists.txt +++ b/libarchive/test/CMakeLists.txt @@ -74,6 +74,7 @@ IF(ENABLE_TEST) test_read_format_isorr_new_bz2.c test_read_format_isorr_rr_moved.c test_read_format_isozisofs_bz2.c + test_read_format_lha.c test_read_format_mtree.c test_read_format_pax_bz2.c test_read_format_raw.c diff --git a/libarchive/test/test_fuzz.c b/libarchive/test/test_fuzz.c index bb38fcf4e..79d732c73 100644 --- a/libarchive/test/test_fuzz.c +++ b/libarchive/test/test_fuzz.c @@ -52,6 +52,7 @@ static struct { } files[] = { {0, "test_fuzz_1.iso.Z"}, /* Exercise compress decompressor. */ {1, "test_fuzz_1.iso.Z"}, + {0, "test_fuzz.lzh"}, {0, "test_compat_bzip2_1.tbz"}, /* Exercise bzip2 decompressor. */ {1, "test_compat_bzip2_1.tbz"}, {0, "test_compat_gtar_1.tar"}, diff --git a/libarchive/test/test_fuzz.lzh.uu b/libarchive/test/test_fuzz.lzh.uu new file mode 100644 index 000000000..781ebbbb6 --- /dev/null +++ b/libarchive/test/test_fuzz.lzh.uu @@ -0,0 +1,152 @@ +begin 644 test_fuzz.lzh +M)L`M;&AD+0```````````$@B[!``!&1ID#Z0,GO2UL:#4M(@```#P`````2"+L(``%9FEL93&D +MYU4`@5$!`*2!Z0/I`P`80FYIQ>/Z`=-:'>9%#"P%J!\CH0"/GE$,.W6FMSD% +M*_4G02UL:#4M(@```$X`````2"+L(``%9FEL93+5%54`@5$!`+:!Z0/I`P`8 +M0FYIQV/Z`=.:'.9%#"P%J-\+H0"/'E$,.W6FMSD%*_T9?RUL:&0M&@`````` +M````2"+L(`$```!5!P`"9&ER_P4`4.A!!P!1Z0/I`P<`5(%1`0```!F!+6QH +M9"T;``````````!((NP@`0```%4(``)D:7(R_P4`4.U!!P!1Z0/I`P<`5(%1 +M`0```!YQ+6QH9"TG``````````%((^P@`05F:6QE,0``510``F1I6UL:6YK,GPN+O\%`%#MH0<`4>D# +MZ0,'`%0"HP(````>!BUL:#4M-0```#P`````2"+L(`$%9FEL93&DYU4%`%"D +M@0<`4>D#Z0,'`%2!40$`````&$)N:<7C^@'36AWF10PL!:@?(Z$`CYY1##MU +MIK40P[=::W.04K +M_34`+6QH9"T``````````(%1`0`@`@``504```7>`P`!!P`"9&ER_P4`4.A! +M!P!1Z0/I`P``-@`M;&AD+0``````````@5$!`"`"``!5!0``")D#``$(``)D +M:7(R_P4`4.U!!P!1Z0/I`P``1P`M;&AD+0```````````J,"`"`"``!5!0`` +MM*D(``%F:6QE,10``F1I6UL:6YK,GPN+O\%`%#MH0<`4>D#Z0,``#,`+6QH-2TB````/````(%1`0`@ +M`J3G504``/T!"``!9FEL93$%`%"D@0<`4>D#Z0,````80FYIQ>/Z`=-:'>9% +M#"P%J!\CH0"/GE$,.W6FMSD%*_4S`"UL:#4M(@```$X```"!40$`(`+5%54% +M```OQ@@``69I;&4R!0!0MH$'`%'I`^D#````&$)N:<=C^@'3FASF10PL!:C? +M"Z$`CQY1##MUIK@_0`9;+`0@`````+J\' +M``````0`+6QH9"T``````````(%1`0`@`P``37T````)````1J0#```%```` +M`0H````"9&ER,O\'````0!``&0```/_M00`````````````A&OY,1!K^3!T` +M``!!2H%NNP&6RP&`UD``J+*=`0!Z#]`!ELL!"`````!]%P<`````!``M;&@U +M+20````\````@5$!`"`#I.=-<0````D```!&I`,```H````!9FEL93$9```` +M_Z2!`````````````,<4_DPC&OY,'0```$$T0J**_I7+`8#60`"HLIT!_G*D +MO`&6RP$(``````KQ!P``````&4)MD:BT=H!Z:T.IZ9#S:`ZH%CJ$`A]OC1DU +M4VION(3=>H`$`"UL:#4M)````$X```"!40$`(`/5%4UQ````"0```$:D`P`` +M"@````%F:6QE,AD```#_I($`````````````(1K^3",:_DP=````0?Y%<[L! +MELL!@-9``*BRG0%8U::\`9;+`0@`````0=X'```````90FV1J.QV@'IS0XGI +MD/-H#JC&&H0"'F^-&353:F^XA-U^@#4`+6QH9"T``````````(%1`0`@`@`` +M504```7>`P`!!P`"9&ER_P4`4.A!!P!1Z0/I`P``-@`M;&AD+0`````````` +M@5$!`"`"``!5!0``")D#``$(``)D:7(R_P4`4.U!!P!1Z0/I`P``1P`M;&AD +M+0```````````J,"`"`"``!5!0``M*D(``%F:6QE,10``F1I6UL:6YK,GPN+O\%`%#MH0<`4>D#Z0,` +M`#,`+6QH,"T\````/````(%1`0`@`J3G504``+DI"``!9FEL93$%`%"D@0<` +M4>D#Z0,``"`@("`@("`@("`@("`@("`@("`@("`@("`@9FEL92`Q(&-O;G1E +M;G1S"FAE;&QO"FAE;&QO"FAE;&QO"C,`+6QH,"U.````3@```(%1`0`@`M45 +M504``+T9"``!9FEL93(%`%"V@0<`4>D#Z0,``"`@("`@("`@("`@("`@("`@ +M("`@("`@("`@9FEL92`R(&-O;G1E;G1S"FAE;&QO"FAE;&QO"FAE;&QO"FAE +M;&QO"FAE;&QO"FAE;&QO"C4`+6QH-2UW`@``Z00``&-,V4L@`H6/504``#-5 +M"@`!24Y35$%,3`4`4*2!!P!1Z0/I`P```FUK=XVV['"Z\`>$(6[`2-EZ5P&P +M^!J*0MPV*IM]&^L37/)[SD(6GCE`4$+,RA +M<%QVYXC%'H1Q`I@_B\H/L@(+W3`0@5CY6X%Q%WW+@7<`8(F7=77U]%5YG?`; +M3N&I:_6YWA$,3F@?:)>FZ!:WRP0:TH8%&:/A#VMNUUD +M[D?+:#XK%3NMR(PSF4;V"^\X0/E3JMRQ46+7\I]S@7ZJNTX[ROSD+=\7\.O# +MI:W._S>P-B(W4`=N*!<4AM0Y$,P.0&OJER*:CF[S6/=2*T4J +M8HB@[:HC'_0*&[=@,,G+98$J+TE>OL/*B^SJ]0B]?)C"QW6ESMD62-#L*]0YO*`ZHY784:YX:>BM^:'>,:IX.=Y*@R(+B)*BX24P/1>F- +M3ODP9D/YS5^9M>8OQ5I-[<^F!_.C?H\HT:!I/ZJ3)U)Z,OC8+[-;X +MMPT`HQXE(XTV!J`/FOL'T?3:#:L@[2#Y$F`?BFKE-DN]]H>'CIJ-:^/JCO\T +M.4`Z,BR&AN^@;&H.P/W[;;[KCY1@;[MRE6MWEA0[SS@BW-=BZ;/992/*144" +ME)9-=H3.#8,!KW4;K#^:HP[15TP>C!ZQL--2)IGK);'D<;J"O7.2A_4X'"IW +MB(_W\=@I^.U+$T\Q/F2:*5OSDVD#Z0,X?"UL:&0M```````````!2"/L$``69&ER,EQS +M>6UL:6YK,7PN+EQF:6QE,0``50`"HP(`[:'I`^D#.'XM;&AD+0`````````` +M`4@C[!``%F1I] +M+6QH-2TB````/`````!((NP@``5F:6QE,:3G50"!40$`I('I`^D#`!A";FG% +MX_H!TUH=YD4,+`6H'R.A`(^>40P[=::W.04K]2=!+6QH-2TB````3@````!( +M(NP@``5F:6QE,M4550"!40$`MH'I`^D#`!A";FG'8_H!TYH40P[=::W.04K_0`9?RUL:&0M&@``````````2"+L(`$```!5!P`"9&ER +M_P4`4.A!!P!1Z0/I`P<`5(%1`0```!F!+6QH9"T;``````````!((NP@`0`` +M`%4(``)D:7(R_P4`4.U!!P!1Z0/I`P<`5(%1`0```!YQ+6QH9"TG```````` +M``%((^P@`05F:6QE,0``510``F1I6UL:6YK,GPN+O\%`%#MH0<`4>D#Z0,'`%0"HP(````>!BUL:#4M +M-0```#P`````2"+L(`$%9FEL93&DYU4%`%"D@0<`4>D#Z0,'`%2!40$````` +M&$)N:<7C^@'36AWF10PL!:@?(Z$`CYY1##MUIK40P[=::W.04K_0`U`"UL:&0M``````````"! +M40$`(`(``%4%```%W@,``0<``F1ID#Z0,``#8`+6QH9"T` +M`````````(%1`0`@`@``504```B9`P`!"``"9&ER,O\%`%#M00<`4>D#Z0,` +M`$<`+6QH9"T```````````*C`@`@`@``504``+2I"``!9FEL93$4``)D:7(R +M_W-Y;6QI;FLQ?"XN_P4`4.VA!P!1Z0/I`P``1P`M;&AD+0```````````J," +M`"`"``!5!0``A^T(``%F:6QE,A0``F1I40P[=::W.04K_0`$ +M`"UL:&0M``````````"!40$`(`,``$U\````"0```$:D`P``!0````$)```` +M`F1IG-#B>F0\V@.J,8:A`(>;XT9-5-J +M;[B$W7Z``#4`+6QH9"T``````````(%1`0`@`@``504```7>`P`!!P`"9&ER +M_P4`4.A!!P!1Z0/I`P``-@`M;&AD+0``````````@5$!`"`"``!5!0``")D# +M``$(``)D:7(R_P4`4.U!!P!1Z0/I`P``1P`M;&AD+0```````````J,"`"`" +M``!5!0``M*D(``%F:6QE,10``F1I6UL:6YK,GPN+O\%`%#MH0<`4>D#Z0,``#,`+6QH,"T\````/``` +M`(%1`0`@`J3G504``+DI"``!9FEL93$%`%"D@0<`4>D#Z0,``"`@("`@("`@ +M("`@("`@("`@("`@("`@("`@9FEL92`Q(&-O;G1E;G1S"FAE;&QO"FAE;&QO +M"FAE;&QO"C,`+6QH,"U.````3@```(%1`0`@`M45504``+T9"``!9FEL93(% +M`%"V@0<`4>D#Z0,``"`@("`@("`@("`@("`@("`@("`@("`@("`@9FEL92`R +M(&-O;G1E;G1S"FAE;&QO"FAE;&QO"FAE;&QO"FAE;&QO"FAE;&QO"FAE;&QO +M"@`U`"UL:&0M``````````"!40$`(`(``%4%```%W@,``0<``F1ID#Z0,``#8`+6QH9"T``````````(%1`0`@`@``504```B9`P`!"``" +M9&ER,O\%`%#M00<`4>D#Z0,``$<`+6QH9"T```````````*C`@`@`@``504` +M`+2I"``!9FEL93$4``)D:7(R_W-Y;6QI;FLQ?"XN_P4`4.VA!P!1Z0/I`P`` +M1P`M;&AD+0```````````J,"`"`"``!5!0``A^T(``%F:6QE,A0``F1ID#Z0,````80FYIQV/Z`=.:'.9%#"P% +MJ-\+D(!'CRB&';K36YR"E?Z``#4`+6QH9"T``````````(%1`0`@`@``504` +M``7>`P`!!P`"9&ER_P4`4.A!!P!1Z0/I`P``-@`M;&AD+0``````````@5$! +M`"`"``!5!0``")D#``$(``)D:7(R_P4`4.U!!P!1Z0/I`P``1P`M;&AD+0`` +M`````````J,"`"`"``!5!0``M*D(``%F:6QE,10``F1I6UL:6YK,GPN+O\%`%#MH0<`4>D#Z0,``#,` +M+6QH-RTC````/````(%1`0`@`J3G504``!R)"``!9FEL93$%`%"D@0<`4>D# +MZ0,````80FYIQ>/Z`=-:'>9%#"P%J!\CD(!'SRB&';K36YR"E?J`,P`M;&@W +M+2,```!.````@5$!`"`"U155!0``SDX(``%F:6QE,@4`4+:!!P!1Z0/I`P`` +M`!A";FG'8_H!TYH/*(8=NM-;G(*5_H``-0`M;&@U+7<" +M``#I!```8TS92R`"A8]5!0``,U4*``%)3E-404Q,!0!0I($'`%'I`^D#```" +M;6MWC;;L<+KP!X0A;L!(V7I7`;#X&HI"W#8JFWT;ZQ-<\ESDLQ#>-O+FY91= +MO@?+_S`"5M)[O.0A:>.4!00LS*%P7';GB,4>A'$"F#^+R@^R`@O=,!"!6/E; +M@7$7?$0Q.:!]HEZ;H%P$)\YH.,]H< +MSS">!YK?+!!K2A@49H^$/:V[763N1\MH/BL5.ZW(C#.91O8+[SA`^5.JW+%1 +M8M?RGW.!?JJ[3CO*_.0MWQ?PZ\.EK<[_-[`V(C=0!VYS0\2_0M7UO5XH%Q2& +MU#D0S`Y`:^J7(IJ.;O-8]U(K12IBB*#MJB,?]`H;MV`PR5#!U11D+Z"_/*YZ,+'=:7.V19(T.PKU#F\H#JCE=A1KGAI +MZ*WYH=XQJG@YWDJ!R&E3>IWFDBLSH&=H+AD]3FI98==M/;;@-O#MU2?_ZT'9 +MV>Z&KV9B?6C-AC$X[O^PPZ739;23V`H2@29(T9[(@N(DJ+A)3`]%Z8U.^3!F0_G-7YFUYB_%6DWMSZ8'\Z-^CRC1 +MH&D_JI,G4GHR^-@OLUOARV,U\QZW#0"C'B4CC38&H`^:^P?1]-H-JR#M(/D2 +M8!^*:N4V2[WVAX>.FHUKX^J._S0Y0#HR+(:&[Z!L:@[`_?MMONN/E&!ONW*5 +M:W>6%#O/."+LEL>1QNH*](C_?QV"GX[4L33S$^9)HI6_.39R>_YC5` +!```` +` +end diff --git a/libarchive/test/test_read_format_lha.c b/libarchive/test/test_read_format_lha.c new file mode 100644 index 000000000..b31e245cb --- /dev/null +++ b/libarchive/test/test_read_format_lha.c @@ -0,0 +1,264 @@ +/*- + * Copyright (c) 2008, 2010 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 "test.h" +__FBSDID("$FreeBSD"); + +/* +Execute the following command to rebuild the data for this program: + tail -n +32 test_read_format_lha.c | /bin/sh + +#/bin/sh +# +# How to make test data. +# +# Temporary directory. +base=/tmp/lha +# Owner id +owner=1001 +# Group id +group=1001 +# +# Make contents of a lha archive. +# +rm -rf ${base} +mkdir ${base} +mkdir ${base}/dir +cat > ${base}/file1 << END + file 1 contents +hello +hello +hello +END +cat > ${base}/file2 << END + file 2 contents +hello +hello +hello +hello +hello +hello +END +mkdir ${base}/dir2 +# +# Set up a file mode, owner and group. +# +(cd ${base}/dir2; ln -s ../file1 symlink1) +(cd ${base}/dir2; ln -s ../file2 symlink2) +(cd ${base}; chown ${owner}:${group} dir file1 file2) +(cd ${base}; chown -h ${owner}:${group} dir2 dir2/symlink1 dir2/symlink2) +(cd ${base}; chmod 0750 dir) +(cd ${base}; chmod 0755 dir2) +(cd ${base}; chmod 0755 dir2/symlink1 dir2/symlink2) +(cd ${base}; chmod 0644 file1) +(cd ${base}; chmod 0666 file2) +TZ=utc touch -afhm -t 197001030000.02 ${base}/dir2/symlink1 ${base}/dir2/symlink2 +TZ=utc touch -afhm -t 197001020000.01 ${base}/dir ${base}/dir2 +TZ=utc touch -afhm -t 197001020000.01 ${base}/file1 ${base}/file2 +# +# Make several lha archives. +# +# Make a lha archive with header level 0 +lha0=test_read_format_lha_header0.lzh +(cd ${base}; lha c0q ${lha0} dir file1 file2 dir2) +# Make a lha archive with header level 1 +lha1=test_read_format_lha_header1.lzh +(cd ${base}; lha c1q ${lha1} dir file1 file2 dir2) +# Make a lha archive with header level 2 +lha2=test_read_format_lha_header2.lzh +(cd ${base}; lha c2q ${lha2} dir file1 file2 dir2) +# Make a lha archive with -lh6- compression mode +lha3=test_read_format_lha_lh6.lzh +(cd ${base}; lha co6q ${lha3} dir file1 file2 dir2) +# Make a lha archive with -lh7- compression mode +lha4=test_read_format_lha_lh7.lzh +(cd ${base}; lha co7q ${lha4} dir file1 file2 dir2) +# Make a lha archive with -lh0- no compression +lha5=test_read_format_lha_lh0.lzh +(cd ${base}; lha czq ${lha5} dir file1 file2 dir2) +# make a lha archive with junk data +lha6=test_read_format_lha_withjunk.lzh +(cd ${base}; cp ${lha2} ${lha6}; echo "junk data!!!!" >> ${lha6}) +# +uuencode ${base}/${lha0} ${lha0} > ${lha0}.uu +uuencode ${base}/${lha1} ${lha1} > ${lha1}.uu +uuencode ${base}/${lha2} ${lha2} > ${lha2}.uu +uuencode ${base}/${lha3} ${lha3} > ${lha3}.uu +uuencode ${base}/${lha4} ${lha4} > ${lha4}.uu +uuencode ${base}/${lha5} ${lha5} > ${lha5}.uu +uuencode ${base}/${lha6} ${lha5} > ${lha5}.uu +uuencode ${base}/${lha6} ${lha6} > ${lha6}.uu +# +# Finish making test data. +exit 1 +*/ + +static const char file1[] = { +" file 1 contents\n" +"hello\n" +"hello\n" +"hello\n" +}; +#define file1_size (sizeof(file1)-1) +static const char file2[] = { +" file 2 contents\n" +"hello\n" +"hello\n" +"hello\n" +"hello\n" +"hello\n" +"hello\n" +}; +#define file2_size (sizeof(file2)-1) + +static void +verify(const char *refname, int posix) +{ + struct archive_entry *ae; + struct archive *a; + char buff[128]; + const void *pv; + size_t s; + int64_t o; + int uid, gid; + + if (posix) + uid = gid = 1001; + else + uid = gid = 0; + + extract_reference_file(refname); + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_compression_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_open_filename(a, refname, 10240)); + + /* Verify directory1. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + if (posix) + assertEqualInt((AE_IFDIR | 0750), archive_entry_mode(ae)); + else + assertEqualInt((AE_IFDIR | 0755), archive_entry_mode(ae)); + assertEqualString("dir/", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualInt(uid, archive_entry_uid(ae)); + assertEqualInt(gid, archive_entry_gid(ae)); + assertEqualInt(0, archive_entry_size(ae)); + assertEqualIntA(a, ARCHIVE_EOF, + archive_read_data_block(a, &pv, &s, &o)); + assertEqualInt(s, 0); + + /* Verify directory2. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFDIR | 0755), archive_entry_mode(ae)); + assertEqualString("dir2/", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualInt(uid, archive_entry_uid(ae)); + assertEqualInt(gid, archive_entry_gid(ae)); + assertEqualInt(0, archive_entry_size(ae)); + assertEqualIntA(a, ARCHIVE_EOF, + archive_read_data_block(a, &pv, &s, &o)); + assertEqualInt(s, 0); + + if (posix) { + /* Verify symbolic link file1. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFLNK | 0755), archive_entry_mode(ae)); + assertEqualString("dir2/symlink1", archive_entry_pathname(ae)); + assertEqualString("../file1", archive_entry_symlink(ae)); + assertEqualInt(172802, archive_entry_mtime(ae)); + assertEqualInt(uid, archive_entry_uid(ae)); + assertEqualInt(gid, archive_entry_gid(ae)); + assertEqualInt(0, archive_entry_size(ae)); + + /* Verify symbolic link file2. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFLNK | 0755), archive_entry_mode(ae)); + assertEqualString("dir2/symlink2", archive_entry_pathname(ae)); + assertEqualString("../file2", archive_entry_symlink(ae)); + assertEqualInt(172802, archive_entry_mtime(ae)); + assertEqualInt(uid, archive_entry_uid(ae)); + assertEqualInt(gid, archive_entry_gid(ae)); + assertEqualInt(0, archive_entry_size(ae)); + } + + /* Verify regular file1. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); + assertEqualString("file1", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualInt(uid, archive_entry_uid(ae)); + assertEqualInt(gid, archive_entry_gid(ae)); + assertEqualInt(file1_size, archive_entry_size(ae)); + assertEqualInt(file1_size, archive_read_data(a, buff, file1_size)); + assertEqualMem(buff, file1, file1_size); + + /* Verify regular file2. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + if (posix) + assertEqualInt((AE_IFREG | 0666), archive_entry_mode(ae)); + else + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); + assertEqualString("file2", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualInt(uid, archive_entry_uid(ae)); + assertEqualInt(gid, archive_entry_gid(ae)); + assertEqualInt(file2_size, archive_entry_size(ae)); + assertEqualInt(file2_size, archive_read_data(a, buff, file2_size)); + assertEqualMem(buff, file2, file2_size); + + /* End of archive. */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + /* Verify archive format. */ + assertEqualIntA(a, ARCHIVE_COMPRESSION_NONE, archive_compression(a)); + assertEqualIntA(a, ARCHIVE_FORMAT_LHA, archive_format(a)); + + /* Close the archive. */ + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} + +DEFINE_TEST(test_read_format_lha) +{ + /* Verify Header level 0 */ + verify("test_read_format_lha_header0.lzh", 1); + /* Verify Header level 1 */ + verify("test_read_format_lha_header1.lzh", 1); + /* Verify Header level 2 */ + verify("test_read_format_lha_header2.lzh", 1); + /* Verify Header level 3 + * This test data can be made in Windows only. */ + verify("test_read_format_lha_header3.lzh", 0); + /* Verify compression mode -lh6- */ + verify("test_read_format_lha_lh6.lzh", 1); + /* Verify compression mode -lh7- */ + verify("test_read_format_lha_lh7.lzh", 1); + /* Verify no compression -lh0- */ + verify("test_read_format_lha_lh0.lzh", 1); + /* Verify an lha file with junk data. */ + verify("test_read_format_lha_withjunk.lzh", 1); +} + diff --git a/libarchive/test/test_read_format_lha_header0.lzh.uu b/libarchive/test/test_read_format_lha_header0.lzh.uu new file mode 100644 index 000000000..8fa8b9248 --- /dev/null +++ b/libarchive/test/test_read_format_lha_header0.lzh.uu @@ -0,0 +1,11 @@ +begin 644 test_read_format_lha_header0.lzh +M)L`M;&AD+0```````````$@B[!``!&1ID#Z0,GO2UL:#4M(@```#P`````2"+L(``%9FEL93&D +MYU4`@5$!`*2!Z0/I`P`80FYIQ>/Z`=-:'>9%#"P%J!\CH0"/GE$,.W6FMSD% +M*_4G02UL:#4M(@```$X`````2"+L(``%9FEL93+5%54`@5$!`+:!Z0/I`P`8 +A0FYIQV/Z`=.:'.9%#"P%J-\+H0"/'E$,.W6FMSD%*_T` +` +end diff --git a/libarchive/test/test_read_format_lha_header1.lzh.uu b/libarchive/test/test_read_format_lha_header1.lzh.uu new file mode 100644 index 000000000..303c9bbb2 --- /dev/null +++ b/libarchive/test/test_read_format_lha_header1.lzh.uu @@ -0,0 +1,13 @@ +begin 644 test_read_format_lha_header1.lzh +M&7\M;&AD+1H``````````$@B["`!````50<``F1ID#Z0,' +M`%2!40$````9@2UL:&0M&P``````````2"+L(`$```!5"``"9&ER,O\%`%#M +M00<`4>D#Z0,'`%2!40$````><2UL:&0M)P`````````!2"/L(`$%9FEL93$` +M`%44``)D:7(R_W-Y;6QI;FLQ?"XN_P4`4.VA!P!1Z0/I`P<`5`*C`@```!YR +M+6QH9"TG``````````%((^P@`05F:6QE,@``510``F1I40P[=::W.04K]1YX+6QH-2TU````3@````!((NP@`05F:6QE +M,M45504`4+:!!P!1Z0/I`P<`5(%1`0`````80FYIQV/Z`=.:'.9%#"P%J-\+ +/H0"/'E$,.W6FMSD%*_T` +` +end diff --git a/libarchive/test/test_read_format_lha_header2.lzh.uu b/libarchive/test/test_read_format_lha_header2.lzh.uu new file mode 100644 index 000000000..d5d7591f1 --- /dev/null +++ b/libarchive/test/test_read_format_lha_header2.lzh.uu @@ -0,0 +1,13 @@ +begin 644 test_read_format_lha_header2.lzh +M-0`M;&AD+0``````````@5$!`"`"``!5!0``!=X#``$'``)D:7+_!0!0Z$$' +M`%'I`^D#```V`"UL:&0M``````````"!40$`(`(``%4%```(F0,``0@``F1I +M6UL:6YK,7PN+O\%`%#MH0<`4>D#Z0,``$<` +M+6QH9"T```````````*C`@`@`@``504``(?M"``!9FEL93(4``)D:7(R_W-Y +M;6QI;FLR?"XN_P4`4.VA!P!1Z0/I`P``,P`M;&@U+2(````\````@5$!`"`" +MI.=5!0``_0$(``%F:6QE,04`4*2!!P!1Z0/I`P```!A";FG%X_H!TUH=YD4, +M+`6H'R.A`(^>40P[=::W.04K]3,`+6QH-2TB````3@```(%1`0`@`M45504` +M`"_&"``!9FEL93(%`%"V@0<`4>D#Z0,````80FYIQV/Z`=.:'.9%#"P%J-\+ +/H0"/'E$,.W6FMSD%*_T` +` +end diff --git a/libarchive/test/test_read_format_lha_header3.lzh.uu b/libarchive/test/test_read_format_lha_header3.lzh.uu new file mode 100644 index 000000000..e6128264a --- /dev/null +++ b/libarchive/test/test_read_format_lha_header3.lzh.uu @@ -0,0 +1,16 @@ +begin 644 test_read_format_lha_header3.lzh +M!``M;&AD+0``````````@5$!`"`#``!-?`````D```!&I`,```4````!"0`` +M``)D:7+_!P```$`0`!D```#_[4$`````````````#!7^3$0:_DP=````0@_0`9;+`0@`````+J\'``````0`+6QH9"T````` +M`````(%1`0`@`P``37T````)````1J0#```%`````0H````"9&ER,O\'```` +M0!``&0```/_M00`````````````A&OY,1!K^3!T```!!2H%NNP&6RP&`UD`` +MJ+*=`0!Z#]`!ELL!"`````!]%P<`````!``M;&@U+20````\````@5$!`"`# +MI.=-<0````D```!&I`,```H````!9FEL93$9````_Z2!`````````````,<4 +M_DPC&OY,'0```$$T0J**_I7+`8#60`"HLIT!_G*DO`&6RP$(``````KQ!P`` +M````&4)MD:BT=H!Z:T.IZ9#S:`ZH%CJ$`A]OC1DU4VION(3=>H`$`"UL:#4M +M)````$X```"!40$`(`/5%4UQ````"0```$:D`P``"@````%F:6QE,AD```#_ +MI($`````````````(1K^3",:_DP=````0?Y%<[L!ELL!@-9``*BRG0%8U::\ +M`9;+`0@`````0=X'```````90FV1J.QV@'IS0XGID/-H#JC&&H0"'F^-&353 +(:F^XA-U^@``` +` +end diff --git a/libarchive/test/test_read_format_lha_lh0.lzh.uu b/libarchive/test/test_read_format_lha_lh0.lzh.uu new file mode 100644 index 000000000..fff88c7ad --- /dev/null +++ b/libarchive/test/test_read_format_lha_lh0.lzh.uu @@ -0,0 +1,13 @@ +begin 644 test_read_format_lha_lh0.lzh +M-0`M;&AD+0``````````@5$!`"`"``!5!0``!=X#``$'``)D:7+_!0!0Z$$' +M`%'I`^D#```V`"UL:&0M``````````"!40$`(`(``%4%```(F0,``0@``F1I +M6UL:6YK,7PN+O\%`%#MH0<`4>D#Z0,``$<` +M+6QH9"T```````````*C`@`@`@``504``(?M"``!9FEL93(4``)D:7(R_W-Y +M;6QI;FLR?"XN_P4`4.VA!P!1Z0/I`P``,P`M;&@U+2(````\````@5$!`"`" +MI.=5!0``_0$(``%F:6QE,04`4*2!!P!1Z0/I`P```!A";FG%X_H!TUH=YD4, +M+`6H'R.A`(^>40P[=::W.04K]3,`+6QH-2TB````3@```(%1`0`@`M45504` +M`"_&"``!9FEL93(%`%"V@0<`4>D#Z0,````80FYIQV/Z`=.:'.9%#"P%J-\+ +=H0"/'E$,.W6FMSD%*_T`:G5N:R!D871A(2$A(0H` +` +end diff --git a/libarchive/test/test_read_format_lha_lh6.lzh.uu b/libarchive/test/test_read_format_lha_lh6.lzh.uu new file mode 100644 index 000000000..22ad4ac70 --- /dev/null +++ b/libarchive/test/test_read_format_lha_lh6.lzh.uu @@ -0,0 +1,13 @@ +begin 644 test_read_format_lha_lh6.lzh +M-0`M;&AD+0``````````@5$!`"`"``!5!0``!=X#``$'``)D:7+_!0!0Z$$' +M`%'I`^D#```V`"UL:&0M``````````"!40$`(`(``%4%```(F0,``0@``F1I +M6UL:6YK,7PN+O\%`%#MH0<`4>D#Z0,``$<` +M+6QH9"T```````````*C`@`@`@``504``(?M"``!9FEL93(4``)D:7(R_W-Y +M;6QI;FLR?"XN_P4`4.VA!P!1Z0/I`P``,P`M;&@V+2,````\````@5$!`"`" +MI.=5!0``C24(``%F:6QE,04`4*2!!P!1Z0/I`P```!A";FG%X_H!TUH=YD4, +M+`6H'R.0@$?/*(8=NM-;G(*5^H`S`"UL:#8M(P```$X```"!40$`(`+5%54% +M``!?X@@``69I;&4R!0!0MH$'`%'I`^D#````&$)N:<=C^@'3FASF10PL!:C? +1"Y"`1X\HAAVZTUN<@I7^@``` +` +end diff --git a/libarchive/test/test_read_format_lha_lh7.lzh.uu b/libarchive/test/test_read_format_lha_lh7.lzh.uu new file mode 100644 index 000000000..1eaf55cb1 --- /dev/null +++ b/libarchive/test/test_read_format_lha_lh7.lzh.uu @@ -0,0 +1,13 @@ +begin 644 test_read_format_lha_lh7.lzh +M-0`M;&AD+0``````````@5$!`"`"``!5!0``!=X#``$'``)D:7+_!0!0Z$$' +M`%'I`^D#```V`"UL:&0M``````````"!40$`(`(``%4%```(F0,``0@``F1I +M6UL:6YK,7PN+O\%`%#MH0<`4>D#Z0,``$<` +M+6QH9"T```````````*C`@`@`@``504``(?M"``!9FEL93(4``)D:7(R_W-Y +M;6QI;FLR?"XN_P4`4.VA!P!1Z0/I`P``,P`M;&@W+2,````\````@5$!`"`" +MI.=5!0``'(D(``%F:6QE,04`4*2!!P!1Z0/I`P```!A";FG%X_H!TUH=YD4, +M+`6H'R.0@$?/*(8=NM-;G(*5^H`S`"UL:#6UL:6YK,7PN+O\%`%#MH0<`4>D#Z0,``$<` +M+6QH9"T```````````*C`@`@`@``504``(?M"``!9FEL93(4``)D:7(R_W-Y +M;6QI;FLR?"XN_P4`4.VA!P!1Z0/I`P``,P`M;&@U+2(````\````@5$!`"`" +MI.=5!0``_0$(``%F:6QE,04`4*2!!P!1Z0/I`P```!A";FG%X_H!TUH=YD4, +M+`6H'R.A`(^>40P[=::W.04K]3,`+6QH-2TB````3@```(%1`0`@`M45504` +M`"_&"``!9FEL93(%`%"V@0<`4>D#Z0,````80FYIQV/Z`=.:'.9%#"P%J-\+ +=H0"/'E$,.W6FMSD%*_T`:G5N:R!D871A(2$A(0H` +` +end