]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add support for lha/lzh archive format.
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Mon, 20 Dec 2010 08:59:25 +0000 (03:59 -0500)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Mon, 20 Dec 2010 08:59:25 +0000 (03:59 -0500)
SVN-Revision: 2831

18 files changed:
Makefile.am
libarchive/CMakeLists.txt
libarchive/archive.h
libarchive/archive_read_private.h
libarchive/archive_read_support_format_all.c
libarchive/archive_read_support_format_lha.c [new file with mode: 0644]
libarchive/test/CMakeLists.txt
libarchive/test/test_fuzz.c
libarchive/test/test_fuzz.lzh.uu [new file with mode: 0644]
libarchive/test/test_read_format_lha.c [new file with mode: 0644]
libarchive/test/test_read_format_lha_header0.lzh.uu [new file with mode: 0644]
libarchive/test/test_read_format_lha_header1.lzh.uu [new file with mode: 0644]
libarchive/test/test_read_format_lha_header2.lzh.uu [new file with mode: 0644]
libarchive/test/test_read_format_lha_header3.lzh.uu [new file with mode: 0644]
libarchive/test/test_read_format_lha_lh0.lzh.uu [new file with mode: 0644]
libarchive/test/test_read_format_lha_lh6.lzh.uu [new file with mode: 0644]
libarchive/test/test_read_format_lha_lh7.lzh.uu [new file with mode: 0644]
libarchive/test/test_read_format_lha_withjunk.lzh.uu [new file with mode: 0644]

index 64e9cfd2c8ad480f528f18e23c722d16c8e81de4..dd3271b3fc42982022b2d14027704c4afd964d69 100644 (file)
@@ -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                    \
index c50ac9b0118320a4730ee651f6c9cb3f53e313b5..de903bc42fb3e13e735701204fbc5c94fbc91b2c 100644 (file)
@@ -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
index 34702c46c71c1272bfea33ddbe55e886c9f88059..089456eb0db2cc012751a6903dc58f8b07ac988f 100644 (file)
@@ -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 *);
index cf25004a49b228bab9382f9dc80e0a98317f8840..070505238bfda42f522b857e1d115e1ca59199be 100644 (file)
@@ -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. */
 
        /*
index 96778388594b395ee0c6212dc31830d99e10f98b..43be4605cb53a9e4bcf8025918802f69326d0eb3 100644 (file)
@@ -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 (file)
index 0000000..b5b4c19
--- /dev/null
@@ -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 <sys/types.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#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);
+}
+
index e0b9d1985dd733dfaa4ce5a3a7a27d1bee5acf37..614ac1c14cf686a17a2b6f082adffb4c409afea3 100644 (file)
@@ -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
index bb38fcf4e782d772bc3c25372830bfbb0f114538..79d732c73351f640ff34c24b4c09d0fd1b796b1c 100644 (file)
@@ -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 (file)
index 0000000..781ebbb
--- /dev/null
@@ -0,0 +1,152 @@
+begin 644 test_fuzz.lzh
+M)L`M;&AD+0```````````$@B[!``!&1I<EP``%4`@5$!`.A!Z0/I`R?X+6QH
+M9"T```````````!((NP0``5D:7(R7```50"!40$`[4'I`^D#.'PM;&AD+0``
+M`````````4@C[!``%F1I<C)<<WEM;&EN:S%\+BY<9FEL93$``%4``J,"`.VA
+MZ0/I`SA^+6QH9"T```````````%((^P0`!9D:7(R7'-Y;6QI;FLR?"XN7&9I
+M;&4R``!5``*C`@#MH>D#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``F1I<C+_<WEM
+M;&EN:S%\+B[_!0!0[:$'`%'I`^D#!P!4`J,"````'G(M;&AD+2<`````````
+M`4@C["`!!69I;&4R``!5%``"9&ER,O]S>6UL: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
+MIK<Y!2OU'G@M;&@U+34```!.`````$@B["`!!69I;&4RU155!0!0MH$'`%'I
+M`^D#!P!4@5$!`````!A";FG'8_H!TYH<YD4,+`6HWPNA`(\>40P[=::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``F1I<C+_<WEM;&EN:S%\+B[_!0!0[:$'`%'I`^D#``!'
+M`"UL:&0M```````````"HP(`(`(``%4%``"'[0@``69I;&4R%``"9&ER,O]S
+M>6UL: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<Y!2O]!``M;&AD+0``````````@5$!`"`#``!-?`````D`
+M``!&I`,```4````!"0````)D:7+_!P```$`0`!D```#_[4$`````````````
+M#!7^3$0:_DP=````0<S?4;3^E<L!@-9``*BRG0$`>@_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``F1I<C+_<WEM;&EN
+M:S%\+B[_!0!0[:$'`%'I`^D#``!'`"UL:&0M```````````"HP(`(`(``%4%
+M``"'[0@``69I;&4R%``"9&ER,O]S>6UL: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/)<Y+,0WC;RYN647;X'R_\P`E;2>[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!<!"?.:#C/:',\PG@>: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<T/$OT+5];U>*!<4AM0Y$,P.0&OJER*:CF[S6/=2*T4J
+M8HB@[:HC'_0*&[=@,,G+98$J+TE>OL/*B^SJ]0B]?)</$8'E0P=449"^@OSR
+MN>C"QW6ESMD62-#L*]0YO*`ZHY784:YX:>BM^:'>,:IX.=Y*@<AI4WJ=YI(K
+M,Z!G:"X9/4YJ66'7;3VVX#;P[=4G_^M!V=GNAJ]F8GUHS88Q..[_L,.ETV6T
+MD]@*$H$G,/AURJWR/%VN2S0M1ROK^-$D4`#*A2GF2-&>R(+B)*BX24P/1>F-
+M3ODP9D/YS5^9M>8OQ5I-[<^F!_.C?H\HT:!I/ZJ3)U)Z,OC8+[-;X<MC-?,>
+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:*5OSDV<GO^8U0``FP"UL:&0M````````````2"+L
+M$``$9&ER7```50"!40$`Z$'I`^D#)_@M;&AD+0```````````$@B[!``!61I
+M<C)<``!5`(%1`0#M0>D#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<C)<<WEM;&EN:S)\+BY<9FEL93(``%4``J,"`.VAZ0/I`R>]
+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!TYH<YD4,+`6HWPNA
+M`(\>40P[=::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``F1I<C+_<WEM;&EN:S%\+B[_!0!0[:$'`%'I
+M`^D#!P!4`J,"````'G(M;&AD+2<``````````4@C["`!!69I;&4R``!5%``"
+M9&ER,O]S>6UL: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##MUIK<Y!2OU'G@M;&@U+34```!.
+M`````$@B["`!!69I;&4RU155!0!0MH$'`%'I`^D#!P!4@5$!`````!A";FG'
+M8_H!TYH<YD4,+`6HWPNA`(\>40P[=::W.04K_0`U`"UL:&0M``````````"!
+M40$`(`(``%4%```%W@,``0<``F1I<O\%`%#H00<`4>D#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``F1I<C+_<WEM;&EN:S)\+B[_!0!0[:$'
+M`%'I`^D#```S`"UL:#4M(@```#P```"!40$`(`*DYU4%``#]`0@``69I;&4Q
+M!0!0I($'`%'I`^D#````&$)N:<7C^@'36AWF10PL!:@?(Z$`CYY1##MUIK<Y
+M!2OU,P`M;&@U+2(```!.````@5$!`"`"U155!0``+\8(``%F:6QE,@4`4+:!
+M!P!1Z0/I`P```!A";FG'8_H!TYH<YD4,+`6HWPNA`(\>40P[=::W.04K_0`$
+M`"UL:&0M``````````"!40$`(`,``$U\````"0```$:D`P``!0````$)````
+M`F1I<O\'````0!``&0```/_M00`````````````,%?Y,1!K^3!T```!!S-]1
+MM/Z5RP&`UD``J+*=`0!Z#]`!ELL!"``````NKP<`````!``M;&AD+0``````
+M````@5$!`"`#``!-?0````D```!&I`,```4````!"@````)D:7(R_P<```!`
+M$``9````_^U!`````````````"$:_DQ$&OY,'0```$%*@6Z[`9;+`8#60`"H
+MLIT!`'H/T`&6RP$(`````'T7!P`````$`"UL:#4M)````#P```"!40$`(`.D
+MYTUQ````"0```$:D`P``"@````%F:6QE,1D```#_I($`````````````QQ3^
+M3",:_DP=````031"HHK^E<L!@-9``*BRG0'^<J2\`9;+`0@`````"O$'````
+M```90FV1J+1V@'IK0ZGID/-H#J@6.H0"'V^-&353:F^XA-UZ@`0`+6QH-2TD
+M````3@```(%1`0`@`]4537$````)````1J0#```*`````69I;&4R&0```/^D
+M@0`````````````A&OY,(QK^3!T```!!_D5SNP&6RP&`UD``J+*=`5C5IKP!
+MELL!"`````!!W@<``````!E";9&H[':`>G-#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``F1I<C+_<WEM;&EN:S%\+B[_!0!0[:$'`%'I
+M`^D#``!'`"UL:&0M```````````"HP(`(`(``%4%``"'[0@``69I;&4R%``"
+M9&ER,O]S>6UL: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<``F1I<O\%`%#H
+M00<`4>D#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``F1I<C+_
+M<WEM;&EN:S)\+B[_!0!0[:$'`%'I`^D#```S`"UL:#8M(P```#P```"!40$`
+M(`*DYU4%``"-)0@``69I;&4Q!0!0I($'`%'I`^D#````&$)N:<7C^@'36AWF
+M10PL!:@?(Y"`1\\HAAVZTUN<@I7Z@#,`+6QH-BTC````3@```(%1`0`@`M45
+M504``%_B"``!9FEL93(%`%"V@0<`4>D#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``F1I<C+_<WEM;&EN:S%\
+M+B[_!0!0[:$'`%'I`^D#``!'`"UL:&0M```````````"HP(`(`(``%4%``"'
+M[0@``69I;&4R%``"9&ER,O]S>6UL: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<YD4,+`6HWPN0@$>/*(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?<N!=P!@B9=U=?7T57F=\!M.X:EK];G>$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`PR<ME@2HO25Z^P\J+
+M[.KU"+U\EP\1@>5#!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@2<P^'7*K?(\7:Y+-"U'*^OXT210
+M`,J%*>9(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/."+<UV+IL]EE(\I%10*4EDUVA,X-@P&O=1NL/YJC#M%73!Z,'K&P
+MTU(FF>LEL>1QNH*]<Y*']3@<*G>(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 (file)
index 0000000..b31e245
--- /dev/null
@@ -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 (file)
index 0000000..8fa8b92
--- /dev/null
@@ -0,0 +1,11 @@
+begin 644 test_read_format_lha_header0.lzh
+M)L`M;&AD+0```````````$@B[!``!&1I<EP``%4`@5$!`.A!Z0/I`R?X+6QH
+M9"T```````````!((NP0``5D:7(R7```50"!40$`[4'I`^D#.'PM;&AD+0``
+M`````````4@C[!``%F1I<C)<<WEM;&EN:S%\+BY<9FEL93$``%4``J,"`.VA
+MZ0/I`SA^+6QH9"T```````````%((^P0`!9D:7(R7'-Y;6QI;FLR?"XN7&9I
+M;&4R``!5``*C`@#MH>D#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 (file)
index 0000000..303c9bb
--- /dev/null
@@ -0,0 +1,13 @@
+begin 644 test_read_format_lha_header1.lzh
+M&7\M;&AD+1H``````````$@B["`!````50<``F1I<O\%`%#H00<`4>D#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``F1I<C+_<WEM;&EN:S)\
+M+B[_!0!0[:$'`%'I`^D#!P!4`J,"````'@8M;&@U+34````\`````$@B["`!
+M!69I;&4QI.=5!0!0I($'`%'I`^D#!P!4@5$!`````!A";FG%X_H!TUH=YD4,
+M+`6H'R.A`(^>40P[=::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 (file)
index 0000000..d5d7591
--- /dev/null
@@ -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
+M<C+_!0!0[4$'`%'I`^D#``!'`"UL:&0M```````````"HP(`(`(``%4%``"T
+MJ0@``69I;&4Q%``"9&ER,O]S>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`
+`
+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 (file)
index 0000000..e612826
--- /dev/null
@@ -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<S?
+M4;3^E<L!@-9``*BRG0$`>@_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 (file)
index 0000000..fff88c7
--- /dev/null
@@ -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
+M<C+_!0!0[4$'`%'I`^D#``!'`"UL:&0M```````````"HP(`(`(``%4%``"T
+MJ0@``69I;&4Q%``"9&ER,O]S>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
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 (file)
index 0000000..22ad4ac
--- /dev/null
@@ -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
+M<C+_!0!0[4$'`%'I`^D#``!'`"UL:&0M```````````"HP(`(`(``%4%``"T
+MJ0@``69I;&4Q%``"9&ER,O]S>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;&@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 (file)
index 0000000..1eaf55c
--- /dev/null
@@ -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
+M<C+_!0!0[4$'`%'I`^D#``!'`"UL:&0M```````````"HP(`(`(``%4%``"T
+MJ0@``69I;&4Q%``"9&ER,O]S>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;&@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:#<M(P```$X```"!40$`(`+5%54%
+M``#.3@@``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_withjunk.lzh.uu b/libarchive/test/test_read_format_lha_withjunk.lzh.uu
new file mode 100644 (file)
index 0000000..5e7c617
--- /dev/null
@@ -0,0 +1,13 @@
+begin 644 test_read_format_lha_withjunk.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
+M<C+_!0!0[4$'`%'I`^D#``!'`"UL:&0M```````````"HP(`(`(``%4%``"T
+MJ0@``69I;&4Q%``"9&ER,O]S>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