OSDN Git Service

Fix a bug: Could not extract 2G over files.
[lha/lha.git] / src / header.c
index 4b12710..dd251c0 100644 (file)
@@ -1,18 +1,23 @@
 /* ------------------------------------------------------------------------ */
-/* LHa for UNIX                                                                                                                        */
-/*                             header.c -- header manipulate functions                                         */
-/*                                                                                                                                                     */
-/*             Modified                        Nobutaka Watazaki                                                       */
-/*                                                                                                                                                     */
-/*     Original                                                                                                Y.Tagawa                */
-/*     modified                                                                        1991.12.16      M.Oki                   */
-/*     Ver. 1.10  Symbolic Link added                          1993.10.01      N.Watazaki              */
-/*     Ver. 1.13b Symbolic Link Bug Fix                        1994.08.22      N.Watazaki              */
-/*     Ver. 1.14  Source All chagned                           1995.01.14      N.Watazaki              */
-/*  Ver. 1.14i bug fixed                                               2000.10.06  t.okamoto       */
+/* LHa for UNIX                                                             */
+/*              header.c -- header manipulate functions                     */
+/*                                                                          */
+/*      Modified                Nobutaka Watazaki                           */
+/*                                                                          */
+/*  Original                                                Y.Tagawa        */
+/*  modified                                    1991.12.16  M.Oki           */
+/*  Ver. 1.10  Symbolic Link added              1993.10.01  N.Watazaki      */
+/*  Ver. 1.13b Symbolic Link Bug Fix            1994.08.22  N.Watazaki      */
+/*  Ver. 1.14  Source All chagned               1995.01.14  N.Watazaki      */
+/*  Ver. 1.14i bug fixed                        2000.10.06  t.okamoto       */
+/*  Ver. 1.14i Contributed UTF-8 convertion for Mac OS X                    */
+/*                                              2002.06.29  Hiroto Sakai    */
+/*  Ver. 1.14i autoconfiscated & rewritten      2003.02.23  Koji Arai       */
 /* ------------------------------------------------------------------------ */
 #include "lha.h"
 
+#define DUMP_HEADER 1           /* for debugging */
+
 #if !STRCHR_8BIT_CLEAN
 /* should use 8 bit clean version */
 #undef strchr
 #define strrchr  xstrrchr
 #endif
 
-/* ------------------------------------------------------------------------ */
 static char    *get_ptr;
-#define setup_get(PTR) (get_ptr = (PTR))
-#define get_byte()             (*get_ptr++ & 0xff)
-#define put_ptr                        get_ptr
-#define setup_put(PTR) (put_ptr = (PTR))
-#define put_byte(c)            (*put_ptr++ = (char)(c))
+#define GET_BYTE()      (*get_ptr++ & 0xff)
+
+#if DUMP_HEADER
+static char    *start_ptr;
+#define setup_get(PTR)  (start_ptr = get_ptr = (PTR))
+#define get_byte()      dump_get_byte()
+#define skip_bytes(len) dump_skip_bytes(len)
+#else
+#define setup_get(PTR)  (get_ptr = (PTR))
+#define get_byte()      GET_BYTE()
+#define skip_bytes(len) (get_ptr += (len))
+#endif
+#define put_ptr         get_ptr
+#define setup_put(PTR)  (put_ptr = (PTR))
+#define put_byte(c)     (*put_ptr++ = (char)(c))
 
 int optional_archive_kanji_code = NONE;
 int optional_system_kanji_code = NONE;
@@ -41,63 +55,155 @@ int default_system_kanji_code = MULTIBYTE_FILENAME;
 int default_system_kanji_code = NONE;
 #endif
 
-/* ------------------------------------------------------------------------ */
 int
 calc_sum(p, len)
-       register char  *p;
-       register int    len;
+    void *p;
+    int len;
 {
-       register int    sum;
+    int sum = 0;
 
-       for (sum = 0; len; len--)
-               sum += *p++;
+    while (len--) sum += *(unsigned char*)p++;
 
-       return sum & 0xff;
+    return sum & 0xff;
 }
 
-/* ------------------------------------------------------------------------ */
-static unsigned short
+#if DUMP_HEADER
+static int
+dump_get_byte()
+{
+    int c;
+
+    if (verbose_listing && verbose > 1)
+        printf("%02d %2d: ", get_ptr - start_ptr, 1);
+    c = GET_BYTE();
+    if (verbose_listing && verbose > 1) {
+        if (isprint(c))
+            printf("%d(0x%02x) '%c'\n", c, c, c);
+        else
+            printf("%d(0x%02x)\n", c, c);
+    }
+    return c;
+}
+
+static void
+dump_skip_bytes(len)
+    int len;
+{
+    if (len == 0) return;
+    if (verbose_listing && verbose > 1) {
+        printf("%02d %2d: ", get_ptr - start_ptr, len);
+        while (len--)
+            printf("0x%02x ", GET_BYTE());
+        printf("... ignored\n");
+    }
+    else
+        get_ptr += len;
+}
+#endif
+
+static int
 get_word()
 {
-       int             b0, b1;
+    int b0, b1;
+    int w;
 
-       b0 = get_byte();
-       b1 = get_byte();
-       return (b1 << 8) + b0;
+#if DUMP_HEADER
+    if (verbose_listing && verbose > 1)
+        printf("%02d %2d: ", get_ptr - start_ptr, 2);
+#endif
+    b0 = GET_BYTE();
+    b1 = GET_BYTE();
+    w = (b1 << 8) + b0;
+#if DUMP_HEADER
+    if (verbose_listing && verbose > 1)
+        printf("%d(0x%04x)\n", w, w);
+#endif
+    return w;
 }
 
-/* ------------------------------------------------------------------------ */
 static void
 put_word(v)
-       unsigned int    v;
+    unsigned int    v;
 {
-       put_byte(v);
-       put_byte(v >> 8);
+    put_byte(v);
+    put_byte(v >> 8);
 }
 
-/* ------------------------------------------------------------------------ */
 static long
 get_longword()
 {
-       long            b0, b1, b2, b3;
+    long b0, b1, b2, b3;
+    long l;
 
-       b0 = get_byte();
-       b1 = get_byte();
-       b2 = get_byte();
-       b3 = get_byte();
-       return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
+#if DUMP_HEADER
+    if (verbose_listing && verbose > 1)
+        printf("%02d %2d: ", get_ptr - start_ptr, 4);
+#endif
+    b0 = GET_BYTE();
+    b1 = GET_BYTE();
+    b2 = GET_BYTE();
+    b3 = GET_BYTE();
+    l = (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
+#if DUMP_HEADER
+    if (verbose_listing && verbose > 1)
+        printf("%ld(0x%08lx)\n", l, l);
+#endif
+    return l;
 }
 
-/* ------------------------------------------------------------------------ */
 static void
 put_longword(v)
-       long            v;
+    long v;
+{
+    put_byte(v);
+    put_byte(v >> 8);
+    put_byte(v >> 16);
+    put_byte(v >> 24);
+}
+
+#ifdef HAVE_UINT64_T
+static uint64_t
+get_longlongword()
+{
+    uint64_t b0, b1, b2, b3, b4, b5, b6, b7;
+    uint64_t l;
+
+#if DUMP_HEADER
+    if (verbose_listing && verbose > 1)
+        printf("%02d %2d: ", get_ptr - start_ptr, 4);
+#endif
+    b0 = GET_BYTE();
+    b1 = GET_BYTE();
+    b2 = GET_BYTE();
+    b3 = GET_BYTE();
+    b4 = GET_BYTE();
+    b5 = GET_BYTE();
+    b6 = GET_BYTE();
+    b7 = GET_BYTE();
+
+    l = (b7 << 24) + (b6 << 16) + (b5 << 8) + b4;
+    l <<= 32;
+    l |= (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
+#if DUMP_HEADER
+    if (verbose_listing && verbose > 1)
+        printf("%lld(%#016llx)\n", l, l);
+#endif
+    return l;
+}
+
+static void
+put_longlongword(uint64_t v)
 {
-       put_byte(v);
-       put_byte(v >> 8);
-       put_byte(v >> 16);
-       put_byte(v >> 24);
+    put_byte(v);
+    put_byte(v >> 8);
+    put_byte(v >> 16);
+    put_byte(v >> 24);
+    put_byte(v >> 32);
+    put_byte(v >> 40);
+    put_byte(v >> 48);
+    put_byte(v >> 56);
 }
+#endif
 
 static int
 get_bytes(buf, len, size)
@@ -105,8 +211,29 @@ get_bytes(buf, len, size)
     int len, size;
 {
     int i;
+
+#if DUMP_HEADER
+    if (verbose_listing && verbose > 1)
+        printf("%02d %2d: \"", get_ptr - start_ptr, len);
+
+    for (i = 0; i < len; i++) {
+        if (i < size) buf[i] = get_ptr[i];
+
+        if (verbose_listing && verbose > 1) {
+            if (isprint(buf[i]))
+                printf("%c", buf[i]);
+            else
+                printf("\\x%02x", (unsigned char)buf[i]);
+        }
+    }
+
+    if (verbose_listing && verbose > 1)
+        printf("\"\n");
+#else
     for (i = 0; i < len && i < size; i++)
         buf[i] = get_ptr[i];
+#endif
+
     get_ptr += len;
     return i;
 }
@@ -120,110 +247,6 @@ put_bytes(buf, len)
     for (i = 0; i < len; i++)
         put_byte(buf[i]);
 }
-#if 0   /* no use */
-/* ------------------------------------------------------------------------ */
-static void
-msdos_to_unix_filename(name, len)
-       register char  *name;
-       register int    len;
-{
-       register int    i;
-
-#ifdef MULTIBYTE_FILENAME
-       for (i = 0; i < len; i++) {
-               if (MULTIBYTE_FIRST_P(name[i]) &&
-                   MULTIBYTE_SECOND_P(name[i + 1]))
-                       i++;
-               else if (name[i] == '\\')
-                       name[i] = '/';
-               else if (!noconvertcase && isupper(name[i]))
-                       name[i] = tolower(name[i]);
-       }
-#else
-       for (i = 0; i < len; i++) {
-               if (name[i] == '\\')
-                       name[i] = '/';
-               else if (!noconvertcase && isupper(name[i]))
-                       name[i] = tolower(name[i]);
-       }
-#endif
-}
-
-/* ------------------------------------------------------------------------ */
-static void
-generic_to_unix_filename(name, len)
-       register char  *name;
-       register int    len;
-{
-       register int    i;
-       boolean         lower_case_used = FALSE;
-
-#ifdef MULTIBYTE_FILENAME
-       for (i = 0; i < len; i++) {
-               if (MULTIBYTE_FIRST_P(name[i]) &&
-                   MULTIBYTE_SECOND_P(name[i + 1]))
-                       i++;
-               else if (islower(name[i])) {
-                       lower_case_used = TRUE;
-                       break;
-               }
-       }
-       for (i = 0; i < len; i++) {
-               if (MULTIBYTE_FIRST_P(name[i]) &&
-                   MULTIBYTE_SECOND_P(name[i + 1]))
-                       i++;
-               else if (name[i] == '\\')
-                       name[i] = '/';
-               else if (!noconvertcase && !lower_case_used && isupper(name[i]))
-                       name[i] = tolower(name[i]);
-       }
-#else
-       for (i = 0; i < len; i++)
-               if (islower(name[i])) {
-                       lower_case_used = TRUE;
-                       break;
-               }
-       for (i = 0; i < len; i++) {
-               if (name[i] == '\\')
-                       name[i] = '/';
-               else if (!noconvertcase && !lower_case_used && isupper(name[i]))
-                       name[i] = tolower(name[i]);
-       }
-#endif
-}
-
-/* ------------------------------------------------------------------------ */
-static void
-macos_to_unix_filename(name, len)
-       register char  *name;
-       register int    len;
-{
-       register int    i;
-
-       for (i = 0; i < len; i++) {
-               if (name[i] == ':')
-                       name[i] = '/';
-               else if (name[i] == '/')
-                       name[i] = ':';
-       }
-}
-
-/* ------------------------------------------------------------------------ */
-static void
-unix_to_generic_filename(name, len)
-       register char  *name;
-       register int    len;
-{
-       register int    i;
-
-       for (i = 0; i < len; i++) {
-               if (name[i] == '/')
-                       name[i] = '\\';
-               else if (islower(name[i]))
-                       name[i] = toupper(name[i]);
-       }
-}
-#endif /* 0 */
 
 /* added by Koji Arai */
 void
@@ -231,16 +254,30 @@ convert_filename(name, len, size,
                  from_code, to_code,
                  from_delim, to_delim,
                  case_to)
-       register char  *name;
-       register int    len;
-       register int    size;
+    char *name;
+    int len;                    /* length of name */
+    int size;                   /* size of name buffer */
     int from_code, to_code, case_to;
     char *from_delim, *to_delim;
 
 {
-       register int    i;
+    int i;
 #ifdef MULTIBYTE_FILENAME
     char tmp[FILENAME_LENGTH];
+    int to_code_save = NONE;
+
+    if (from_code == CODE_CAP) {
+        len = cap_to_sjis(tmp, name, sizeof(tmp));
+        strncpy(name, tmp, size);
+        name[size-1] = 0;
+        len = strlen(name);
+        from_code = CODE_SJIS;
+    }
+
+    if (to_code == CODE_CAP) {
+        to_code_save = CODE_CAP;
+        to_code = CODE_SJIS;
+    }
 
     if (from_code == CODE_SJIS && to_code == CODE_UTF8) {
         for (i = 0; i < len; i++)
@@ -268,7 +305,22 @@ convert_filename(name, len, size,
     }
 #endif
 
-       for (i = 0; i < len; i ++) {
+    /* special case: if `name' has small lettter, not convert case. */
+    if (from_code == CODE_SJIS && case_to == TO_LOWER) {
+        for (i = 0; i < len; i++) {
+#ifdef MULTIBYTE_FILENAME
+            if (SJIS_FIRST_P(name[i]) && SJIS_SECOND_P(name[i+1]))
+                i++;
+            else
+#endif
+            if (islower(name[i])) {
+                case_to = NONE;
+                break;
+            }
+        }
+    }
+
+    for (i = 0; i < len; i ++) {
 #ifdef MULTIBYTE_FILENAME
         if (from_code == CODE_EUC &&
             (unsigned char)name[i] == 0x8e) {
@@ -295,37 +347,37 @@ convert_filename(name, len, size,
             len++;
             continue;
         }
-               if (from_code == CODE_EUC && (name[i] & 0x80) && (name[i+1] & 0x80)) {
-                       int c1, c2;
+        if (from_code == CODE_EUC && (name[i] & 0x80) && (name[i+1] & 0x80)) {
+            int c1, c2;
             if (to_code != CODE_SJIS) {
                 i++;
                 continue;
             }
 
-                       c1 = (unsigned char)name[i];
+            c1 = (unsigned char)name[i];
             c2 = (unsigned char)name[i+1];
-                       euc2sjis(&c1, &c2);
-                       name[i] = c1;
+            euc2sjis(&c1, &c2);
+            name[i] = c1;
             name[i+1] = c2;
-                       i++;
+            i++;
             continue;
-               }
+        }
         if (from_code == CODE_SJIS &&
-            SJC_FIRST_P(name[i]) &&
-            SJC_SECOND_P(name[i+1])) {
-                       int c1, c2;
+            SJIS_FIRST_P(name[i]) &&
+            SJIS_SECOND_P(name[i+1])) {
+            int c1, c2;
 
             if (to_code != CODE_EUC) {
                 i++;
                 continue;
             }
 
-                       c1 = (unsigned char)name[i];
+            c1 = (unsigned char)name[i];
             c2 = (unsigned char)name[i+1];
-                       sjis2euc(&c1, &c2);
-                       name[i] = c1;
+            sjis2euc(&c1, &c2);
+            name[i] = c1;
             name[i+1] = c2;
-                       i++;
+            i++;
             continue;
         }
 #endif /* MULTIBYTE_FILENAME */
@@ -340,250 +392,738 @@ convert_filename(name, len, size,
             }
         }
 
-               if (case_to == TO_UPPER && islower(name[i])) {
-                       name[i] = toupper(name[i]);
+        if (case_to == TO_UPPER && islower(name[i])) {
+            name[i] = toupper(name[i]);
             continue;
         }
         if (case_to == TO_LOWER && isupper(name[i])) {
-                       name[i] = tolower(name[i]);
+            name[i] = tolower(name[i]);
             continue;
         }
-       }
-}
+    }
 
-/* ------------------------------------------------------------------------ */
-/*                                                                                                                                                     */
-/* Generic stamp format:                                                                                                       */
-/*                                                                                                                                                     */
-/* 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16                                                     */
-/* |<-------- year ------->|<- month ->|<-- day -->|                                           */
-/*                                                                                                                                                     */
-/* 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0                                                     */
-/* |<--- hour --->|<---- minute --->|<- second*2 ->|                                           */
-/*                                                                                                                                                     */
-/* ------------------------------------------------------------------------ */
+#ifdef MULTIBYTE_FILENAME
+    if (to_code_save == CODE_CAP) {
+        len = sjis_to_cap(tmp, name, sizeof(tmp));
+        strncpy(name, tmp, size);
+        name[size-1] = 0;
+        len = strlen(name);
+    }
+#endif /* MULTIBYTE_FILENAME */
+}
 
 /*
- * NOTE : If you don't have `gettimeofday(2)', or your gettimeofday(2)
- * returns bogus timezone information, try FTIME, MKTIME, TIMELOCAL or TZSET.
+ * Generic (MS-DOS style) time stamp format (localtime):
+ *
+ *  31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
+ * |<---- year-1980 --->|<- month ->|<--- day ---->|
+ *
+ *  15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
+ * |<--- hour --->|<---- minute --->|<- second/2 ->|
+ *
  */
 
-/* choose one */
-#if defined(HAVE_MKTIME)
-#ifdef HAVE_TIMELOCAL
-#undef HAVE_TIMELOCAL
-#endif
-#endif                         /* defined(HAVE_MKTIME) */
+static time_t
+generic_to_unix_stamp(t)
+    long t;
+{
+    struct tm tm;
 
-#if defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL)
-#ifdef HAVE_TZSET
-#undef HAVE_TZSET
-#endif
-#endif                         /* defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) */
+#define subbits(n, off, len) (((n) >> (off)) & ((1 << (len))-1))
 
-#if defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) || defined(HAVE_TZSET)
-#ifdef HAVE_FTIME
-#undef HAVE_FTIME
-#endif
-#endif
+    tm.tm_sec  = subbits(t,  0, 5) * 2;
+    tm.tm_min  = subbits(t,  5, 6);
+    tm.tm_hour = subbits(t, 11, 5);
+    tm.tm_mday = subbits(t, 16, 5);
+    tm.tm_mon  = subbits(t, 21, 4) - 1;
+    tm.tm_year = subbits(t, 25, 7) + 80;
+    tm.tm_isdst = -1;
 
-#if defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) || defined(HAVE_TZSET) || defined(HAVE_FTIME)
-#ifdef HAVE_GETTIMEOFDAY
-#undef HAVE_GETTIMEOFDAY
-#endif
+#if HAVE_MKTIME
+    return mktime(&tm);
 #else
-#ifndef HAVE_GETTIMEOFDAY
-#define HAVE_GETTIMEOFDAY              /* use gettimeofday() */
-#endif
+    return timelocal(&tm);
 #endif
+}
+
+static long
+unix_to_generic_stamp(t)
+    time_t t;
+{
+    struct tm *tm = localtime(&t);
+
+    tm->tm_year -= 80;
+    tm->tm_mon += 1;
+
+    return ((long)(tm->tm_year << 25) +
+            (tm->tm_mon  << 21) +
+            (tm->tm_mday << 16) +
+            (tm->tm_hour << 11) +
+            (tm->tm_min  << 5) +
+            (tm->tm_sec / 2));
+}
+
+static unsigned long
+wintime_to_unix_stamp()
+{
+#if HAVE_UINT64_T
+    uint64_t t;
+    uint64_t epoch = ((uint64_t)0x019db1de << 32) + 0xd53e8000;
+                     /* 0x019db1ded53e8000ULL: 1970-01-01 00:00:00 (UTC) */
+
+    t = (unsigned long)get_longword();
+    t |= (uint64_t)(unsigned long)get_longword() << 32;
+    t = (t - epoch) / 10000000;
+    return t;
+#else
+    int i, borrow;
+    unsigned long t, q, x;
+    unsigned long wintime[8];
+    unsigned long epoch[8] = {0x01,0x9d,0xb1,0xde, 0xd5,0x3e,0x80,0x00};
+                                /* 1970-01-01 00:00:00 (UTC) */
+    /* wintime -= epoch */
+    borrow = 0;
+    for (i = 7; i >= 0; i--) {
+        wintime[i] = (unsigned)get_byte() - epoch[i] - borrow;
+        borrow = (wintime[i] > 0xff) ? 1 : 0;
+        wintime[i] &= 0xff;
+    }
 
-#ifdef HAVE_FTIME
-#include <sys/timeb.h>
+    /* q = wintime / 10000000 */
+    t = q = 0;
+    x = 10000000;               /* x: 24bit */
+    for (i = 0; i < 8; i++) {
+        t = (t << 8) + wintime[i]; /* 24bit + 8bit. t must be 32bit variable */
+        q <<= 8;                   /* q must be 32bit (time_t) */
+        q += t / x;
+        t %= x;     /* 24bit */
+    }
+    return q;
 #endif
+}
 
 /*
- * You may define as : #define TIMEZONE_HOOK           \ extern long
- * timezone ;  \ extern void tzset();
+ * extended header
+ *
+ *             size  field name
+ *  --------------------------------
+ *  base header:         :
+ *           2 or 4  next-header size  [*1]
+ *  --------------------------------------
+ *  ext header:   1  ext-type            ^
+ *                ?  contents            | [*1] next-header size
+ *           2 or 4  next-header size    v
+ *  --------------------------------------
+ *
+ *  on level 1, 2 header:
+ *    size field is 2 bytes
+ *  on level 3 header:
+ *    size field is 4 bytes
  */
-#ifdef TIMEZONE_HOOK
-TIMEZONE_HOOK
-/* Which do you like better, `TIMEZONE_HOOK' or `TIMEZONE_HOOK;' ? */
-#endif
 
-#if defined(HAVE_TZSET) && defined(_MINIX)
-extern long     timezone;              /* not defined in time.h */
+static ssize_t
+get_extended_header(fp, hdr, header_size, hcrc)
+    FILE *fp;
+    LzHeader *hdr;
+    size_t header_size;
+    unsigned int *hcrc;
+{
+    char data[LZHEADER_STORAGE];
+    int name_length;
+    char dirname[FILENAME_LENGTH];
+    int dir_length = 0;
+    int i;
+    ssize_t whole_size = header_size;
+    int ext_type;
+    int n = 1 + hdr->size_field_length; /* `ext-type' + `next-header size' */
+
+    if (hdr->header_level == 0)
+        return 0;
+
+    name_length = strlen(hdr->name);
+
+    while (header_size) {
+#if DUMP_HEADER
+        if (verbose_listing && verbose > 1)
+            printf("---\n");
 #endif
+        setup_get(data);
+        if (sizeof(data) < header_size) {
+            error("header size (%ld) too large.", header_size);
+            exit(1);
+        }
 
-/* ------------------------------------------------------------------------ */
-#if defined(HAVE_FTIME) || defined(HAVE_GETTIMEOFDAY) || defined(HAVE_TZSET)
-static long
-gettz()
-#ifdef HAVE_TZSET
-{
-       tzset();
-       return timezone;
-}
+        if (fread(data, header_size, 1, fp) == 0) {
+            error("Invalid header (LHa file ?)");
+            return -1;
+        }
+
+        ext_type = get_byte();
+        switch (ext_type) {
+        case 0:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < header crc >\n");
+#endif
+            /* header crc (CRC-16) */
+            hdr->header_crc = get_word();
+            /* clear buffer for CRC calculation. */
+            data[1] = data[2] = 0;
+            skip_bytes(header_size - n - 2);
+            break;
+        case 1:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < filename >\n");
+#endif
+            /* filename */
+            name_length =
+                get_bytes(hdr->name, header_size-n, sizeof(hdr->name)-1);
+            hdr->name[name_length] = 0;
+            break;
+        case 2:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < directory >\n");
 #endif
+            /* directory */
+            dir_length = get_bytes(dirname, header_size-n, sizeof(dirname)-1);
+            dirname[dir_length] = 0;
+            break;
+        case 0x40:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < MS-DOS attribute >\n");
+#endif
+            /* MS-DOS attribute */
+            hdr->attribute = get_word();
+            break;
+        case 0x41:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < Windows time stamp (FILETIME) >\n");
+#endif
+            /* Windows time stamp (FILETIME structure) */
+            /* it is time in 100 nano seconds since 1601-01-01 00:00:00 */
 
-/* ------------------------------------------------------------------------ */
-#if !defined(HAVE_TZSET) && defined(HAVE_FTIME)
-{
-       struct timeb    buf;
+            skip_bytes(8); /* create time is ignored */
 
-       ftime(&buf);
-       return buf.timezone * 60L;
-}
+            /* set last modified time */
+            if (hdr->header_level >= 2)
+                skip_bytes(8);  /* time_t has been already set */
+            else
+                hdr->unix_last_modified_stamp = wintime_to_unix_stamp();
+
+            skip_bytes(8); /* last access time is ignored */
+
+            break;
+        case 0x42:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < 64bits file size header >\n");
+#endif
+#ifdef HAVE_UINT64_T
+            /* 64bits file size header (UNLHA32 extension) */
+            hdr->packed_size = get_longlongword();
+            hdr->original_size = get_longlongword();
+#else
+            skip_bytes(8);
+            skip_bytes(8);
 #endif
 
-/* ------------------------------------------------------------------------ */
-#if !defined(HAVE_TZSET) && !defined(HAVE_FTIME)       /* maybe defined(HAVE_GETTIMEOFDAY) */
-{
-#ifdef HAVE_STRUCT_TM_TM_GMTOFF
-       time_t tt;
-
-       time(&tt);
-       return -localtime(&tt)->tm_gmtoff;
-#else /* HAVE_STRUCT_TM_TM_GMTOFF */
-       struct timeval  tp;
-       struct timezone tzp;
-       gettimeofday(&tp, &tzp);/* specific to 4.3BSD */
-       /*
-        * return (tzp.tz_minuteswest * 60L + (tzp.tz_dsttime != 0 ? 60L *
-        * 60L : 0));
-        */
-       return (tzp.tz_minuteswest * 60L);
-#endif /* HAVE_STRUCT_TM_TM_GMTOFF */
-}
+            break;
+        case 0x50:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX permission >\n");
+#endif
+            /* UNIX permission */
+            hdr->unix_mode = get_word();
+            break;
+        case 0x51:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX gid and uid >\n");
+#endif
+            /* UNIX gid and uid */
+            hdr->unix_gid = get_word();
+            hdr->unix_uid = get_word();
+            break;
+        case 0x52:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX group name >\n");
 #endif
-#endif                         /* defined(HAVE_FTIME) || defined(HAVE_GETTIMEOFDAY) ||
-                     * defined(HAVE_TZSET) */
+            /* UNIX group name */
+            i = get_bytes(hdr->group, header_size-n, sizeof(hdr->group)-1);
+            hdr->group[i] = '\0';
+            break;
+        case 0x53:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX user name >\n");
+#endif
+            /* UNIX user name */
+            i = get_bytes(hdr->user, header_size-n, sizeof(hdr->user)-1);
+            hdr->user[i] = '\0';
+            break;
+        case 0x54:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX last modifed time (time_t) >\n");
+#endif
+            /* UNIX last modified time */
+            hdr->unix_last_modified_stamp = (time_t) get_longword();
+            break;
+        default:
+            /* other headers */
+            /* 0x39: multi-disk header
+               0x3f: uncompressed comment
+               0x42: 64bit large file size
+               0x48-0x4f(?): reserved for authenticity verification
+               0x7d: encapsulation
+               0x7e: extended attribute - platform information
+               0x7f: extended attribute - permission, owner-id and timestamp
+                     (level 3 on OS/2)
+               0xc4: compressed comment (dict size: 4096)
+               0xc5: compressed comment (dict size: 8192)
+               0xc6: compressed comment (dict size: 16384)
+               0xc7: compressed comment (dict size: 32768)
+               0xc8: compressed comment (dict size: 65536)
+               0xd0-0xdf(?): operating systemm specific information
+               0xfc: encapsulation (another opinion)
+               0xfe: extended attribute - platform information(another opinion)
+               0xff: extended attribute - permission, owner-id and timestamp
+                     (level 3 on UNLHA32) */
+            if (verbose)
+                warning("unknown extended header 0x%02x", ext_type);
+            skip_bytes(header_size - n);
+            break;
+        }
 
-/* ------------------------------------------------------------------------ */
-#ifdef NOT_USED
-static struct tm *
-msdos_to_unix_stamp_tm(a)
-       long            a;
+        if (hcrc)
+            *hcrc = calccrc(*hcrc, data, header_size);
+
+        if (hdr->size_field_length == 2)
+            whole_size += header_size = get_word();
+        else
+            whole_size += header_size = get_longword();
+    }
+
+    /* concatenate dirname and filename */
+    if (dir_length) {
+        if (name_length + dir_length >= sizeof(hdr->name)) {
+            warning("the length of pathname \"%s%s\" is too long.",
+                    dirname, hdr->name);
+            name_length = sizeof(hdr->name) - dir_length - 1;
+            hdr->name[name_length] = 0;
+        }
+        strcat(dirname, hdr->name); /* ok */
+        strcpy(hdr->name, dirname); /* ok */
+        name_length += dir_length;
+    }
+
+    return whole_size;
+}
+
+#define I_HEADER_SIZE           0               /* level 0,1,2   */
+#define I_HEADER_CHECKSUM       1               /* level 0,1     */
+#define I_METHOD                2               /* level 0,1,2,3 */
+#define I_PACKED_SIZE           7               /* level 0,1,2,3 */
+#define I_ATTRIBUTE             19              /* level 0,1,2,3 */
+#define I_HEADER_LEVEL          20              /* level 0,1,2,3 */
+
+#define COMMON_HEADER_SIZE      21      /* size of common part */
+
+#define I_GENERIC_HEADER_SIZE 24 /* + name_length */
+#define I_LEVEL0_HEADER_SIZE  36 /* + name_length (unix extended) */
+#define I_LEVEL1_HEADER_SIZE  27 /* + name_length */
+#define I_LEVEL2_HEADER_SIZE  26 /* + padding */
+#define I_LEVEL3_HEADER_SIZE  32
+
+/*
+ * level 0 header
+ *
+ *
+ * offset  size  field name
+ * ----------------------------------
+ *     0      1  header size    [*1]
+ *     1      1  header sum
+ *            ---------------------------------------
+ *     2      5  method ID                         ^
+ *     7      4  packed size    [*2]               |
+ *    11      4  original size                     |
+ *    15      2  time                              |
+ *    17      2  date                              |
+ *    19      1  attribute                         | [*1] header size (X+Y+22)
+ *    20      1  level (0x00 fixed)                |
+ *    21      1  name length                       |
+ *    22      X  pathname                          |
+ * X +22      2  file crc (CRC-16)                 |
+ * X +24      Y  ext-header(old style)             v
+ * -------------------------------------------------
+ * X+Y+24        data                              ^
+ *                 :                               | [*2] packed size
+ *                 :                               v
+ * -------------------------------------------------
+ *
+ * ext-header(old style)
+ *     0      1  ext-type ('U')
+ *     1      1  minor version
+ *     2      4  UNIX time
+ *     6      2  mode
+ *     8      2  uid
+ *    10      2  gid
+ *
+ * attribute (MS-DOS)
+ *    bit1  read only
+ *    bit2  hidden
+ *    bit3  system
+ *    bit4  volume label
+ *    bit5  directory
+ *    bit6  archive bit (need to backup)
+ *
+ */
+static int
+get_header_level0(fp, hdr, data)
+    FILE *fp;
+    LzHeader *hdr;
+    char *data;
 {
-       static struct tm t;
-
-       t.tm_sec = (a & 0x1f) * 2;
-       t.tm_min = (a >> 5) & 0x3f;
-       t.tm_hour = (a >> 11) & 0x1f;
-       t.tm_mday = (a >> 16) & 0x1f;
-       t.tm_mon = ((a >> 16 + 5) & 0x0f) - 1;
-       t.tm_year = ((a >> 16 + 9) & 0x7f) + 80;
-       return &t;
+    size_t header_size;
+    ssize_t extend_size;
+    int checksum;
+    int name_length;
+    int i;
+
+    hdr->size_field_length = 2; /* in bytes */
+    hdr->header_size = header_size = get_byte();
+    checksum = get_byte();
+
+    if (fread(data + COMMON_HEADER_SIZE,
+              header_size + 2 - COMMON_HEADER_SIZE, 1, fp) == 0) {
+        error("Invalid header (LHarc file ?)");
+        return FALSE;   /* finish */
+    }
+
+    if (calc_sum(data + I_METHOD, header_size) != checksum) {
+        error("Checksum error (LHarc file?)");
+        return FALSE;
+    }
+
+    get_bytes(hdr->method, 5, sizeof(hdr->method));
+    hdr->packed_size = (unsigned long)get_longword();
+    hdr->original_size = (unsigned long)get_longword();
+    hdr->unix_last_modified_stamp = generic_to_unix_stamp(get_longword());
+    hdr->attribute = get_byte(); /* MS-DOS attribute */
+    hdr->header_level = get_byte();
+    name_length = get_byte();
+    i = get_bytes(hdr->name, name_length, sizeof(hdr->name)-1);
+    hdr->name[i] = '\0';
+
+    /* defaults for other type */
+    hdr->unix_mode = UNIX_FILE_REGULAR | UNIX_RW_RW_RW;
+    hdr->unix_gid = 0;
+    hdr->unix_uid = 0;
+
+    extend_size = header_size+2 - name_length - 24;
+
+    if (extend_size < 0) {
+        if (extend_size == -2) {
+            /* CRC field is not given */
+            hdr->extend_type = EXTEND_GENERIC;
+            hdr->has_crc = FALSE;
+
+            return TRUE;
+        }
+
+        error("Unkonwn header (lha file?)");
+        exit(1);
+    }
+
+    hdr->has_crc = TRUE;
+    hdr->crc = get_word();
+
+    if (extend_size == 0)
+        return TRUE;
+
+    hdr->extend_type = get_byte();
+    extend_size--;
+
+    if (hdr->extend_type == EXTEND_UNIX) {
+        if (extend_size >= 11) {
+            hdr->minor_version = get_byte();
+            hdr->unix_last_modified_stamp = (time_t) get_longword();
+            hdr->unix_mode = get_word();
+            hdr->unix_uid = get_word();
+            hdr->unix_gid = get_word();
+            extend_size -= 11;
+        } else {
+            hdr->extend_type = EXTEND_GENERIC;
+        }
+    }
+    if (extend_size > 0)
+        skip_bytes(extend_size);
+
+    hdr->header_size += 2;
+    return TRUE;
 }
-#endif
 
-/* ------------------------------------------------------------------------ */
-static          time_t
-generic_to_unix_stamp(t)
-       long            t;
-#if defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL)
+/*
+ * level 1 header
+ *
+ *
+ * offset   size  field name
+ * -----------------------------------
+ *     0       1  header size   [*1]
+ *     1       1  header sum
+ *             -------------------------------------
+ *     2       5  method ID                        ^
+ *     7       4  skip size     [*2]               |
+ *    11       4  original size                    |
+ *    15       2  time                             |
+ *    17       2  date                             |
+ *    19       1  attribute (0x20 fixed)           | [*1] header size (X+Y+25)
+ *    20       1  level (0x01 fixed)               |
+ *    21       1  name length                      |
+ *    22       X  filename                         |
+ * X+ 22       2  file crc (CRC-16)                |
+ * X+ 24       1  OS ID                            |
+ * X +25       Y  ???                              |
+ * X+Y+25      2  next-header size                 v
+ * -------------------------------------------------
+ * X+Y+27      Z  ext-header                       ^
+ *                 :                               |
+ * -----------------------------------             | [*2] skip size
+ * X+Y+Z+27       data                             |
+ *                 :                               v
+ * -------------------------------------------------
+ *
+ */
+static int
+get_header_level1(fp, hdr, data)
+    FILE *fp;
+    LzHeader *hdr;
+    char *data;
 {
-       struct tm       dostm;
-
-       /*
-        * special case:  if MSDOS format date and time were zero, then we
-        * set time to be zero here too.
-        */
-       if (t == 0)
-               return (time_t) 0;
-
-       dostm.tm_sec = (t & 0x1f) * 2;
-       dostm.tm_min = t >> 5 & 0x3f;
-       dostm.tm_hour = t >> 11 & 0x1f;
-       dostm.tm_mday = t >> 16 & 0x1f;
-       dostm.tm_mon = (t >> 16 + 5 & 0x0f) - 1;        /* 0..11 */
-       dostm.tm_year = (t >> 16 + 9 & 0x7f) + 80;
-#if 0
-       dostm.tm_isdst = 0;     /* correct? */
-#endif
-       dostm.tm_isdst = -1;    /* correct? */
-#ifdef HAVE_MKTIME
-       return (time_t) mktime(&dostm);
-#else                          /* maybe defined(HAVE_TIMELOCAL) */
-       return (time_t) timelocal(&dostm);
-#endif
+    size_t header_size;
+    ssize_t extend_size;
+    int checksum;
+    int name_length;
+    int i, dummy;
+
+    hdr->size_field_length = 2; /* in bytes */
+    hdr->header_size = header_size = get_byte();
+    checksum = get_byte();
+
+    if (fread(data + COMMON_HEADER_SIZE,
+              header_size + 2 - COMMON_HEADER_SIZE, 1, fp) == 0) {
+        error("Invalid header (LHarc file ?)");
+        return FALSE;   /* finish */
+    }
+
+    if (calc_sum(data + I_METHOD, header_size) != checksum) {
+        error("Checksum error (LHarc file?)");
+        return FALSE;
+    }
+
+    get_bytes(hdr->method, 5, sizeof(hdr->method));
+    hdr->packed_size = (unsigned long)get_longword(); /* skip size */
+    hdr->original_size = (unsigned long)get_longword();
+    hdr->unix_last_modified_stamp = generic_to_unix_stamp(get_longword());
+    hdr->attribute = get_byte(); /* 0x20 fixed */
+    hdr->header_level = get_byte();
+
+    name_length = get_byte();
+    i = get_bytes(hdr->name, name_length, sizeof(hdr->name)-1);
+    hdr->name[i] = '\0';
+
+    /* defaults for other type */
+    hdr->unix_mode = UNIX_FILE_REGULAR | UNIX_RW_RW_RW;
+    hdr->unix_gid = 0;
+    hdr->unix_uid = 0;
+
+    hdr->has_crc = TRUE;
+    hdr->crc = get_word();
+    hdr->extend_type = get_byte();
+
+    dummy = header_size+2 - name_length - I_LEVEL1_HEADER_SIZE;
+    if (dummy > 0)
+        skip_bytes(dummy); /* skip old style extend header */
+
+    extend_size = get_word();
+    extend_size = get_extended_header(fp, hdr, extend_size, 0);
+    if (extend_size == -1)
+        return FALSE;
+
+    /* On level 1 header, size fields should be adjusted. */
+    /* the `packed_size' field contains the extended header size. */
+    /* the `header_size' field does not. */
+    hdr->packed_size -= extend_size;
+    hdr->header_size += extend_size + 2;
+
+    return TRUE;
 }
 
-#else                          /* defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) */
+/*
+ * level 2 header
+ *
+ *
+ * offset   size  field name
+ * --------------------------------------------------
+ *     0       2  total header size [*1]           ^
+ *             -----------------------             |
+ *     2       5  method ID                        |
+ *     7       4  packed size       [*2]           |
+ *    11       4  original size                    |
+ *    15       4  time                             |
+ *    19       1  RESERVED (0x20 fixed)            | [*1] total header size
+ *    20       1  level (0x02 fixed)               |      (X+26+(1))
+ *    21       2  file crc (CRC-16)                |
+ *    23       1  OS ID                            |
+ *    24       2  next-header size                 |
+ * -----------------------------------             |
+ *    26       X  ext-header                       |
+ *                 :                               |
+ * -----------------------------------             |
+ * X +26      (1) padding                          v
+ * -------------------------------------------------
+ * X +26+(1)      data                             ^
+ *                 :                               | [*2] packed size
+ *                 :                               v
+ * -------------------------------------------------
+ *
+ */
+static int
+get_header_level2(fp, hdr, data)
+    FILE *fp;
+    LzHeader *hdr;
+    char *data;
 {
-       int             year, month, day, hour, min, sec;
-       long            longtime;
-       static unsigned int dsboy[12] = {0, 31, 59, 90, 120, 151,
-       181, 212, 243, 273, 304, 334};
-       unsigned int    days;
-
-       /*
-        * special case:  if MSDOS format date and time were zero, then we
-        * set time to be zero here too.
-        */
-       if (t == 0)
-               return (time_t) 0;
-
-       year = ((int) (t >> 16 + 9) & 0x7f) + 1980;
-       month = (int) (t >> 16 + 5) & 0x0f;     /* 1..12 means Jan..Dec */
-       day = (int) (t >> 16) & 0x1f;   /* 1..31 means 1st,...31st */
-
-       hour = ((int) t >> 11) & 0x1f;
-       min = ((int) t >> 5) & 0x3f;
-       sec = ((int) t & 0x1f) * 2;
-
-       /* Calculate days since 1970.01.01 */
-       days = (365 * (year - 1970) +   /* days due to whole years */
-               (year - 1970 + 1) / 4 + /* days due to leap years */
-               dsboy[month - 1] +      /* days since beginning of this year */
-               day - 1);       /* days since beginning of month */
-
-       if ((year % 4 == 0) &&
-               (year % 100 != 0 || year % 400 == 0) &&         /* 1999.5.24 t.oka */
-           (month >= 3))       /* if this is a leap year and month */
-               days++;         /* is March or later, add a day */
-
-       /* Knowing the days, we can find seconds */
-       longtime = (((days * 24) + hour) * 60 + min) * 60 + sec;
-       longtime += gettz();    /* adjust for timezone */
-
-       /* LONGTIME is now the time in seconds, since 1970/01/01 00:00:00.  */
-       return (time_t) longtime;
+    size_t header_size;
+    ssize_t extend_size;
+    int padding;
+    unsigned int hcrc;
+
+    hdr->size_field_length = 2; /* in bytes */
+    hdr->header_size = header_size = get_word();
+
+    if (fread(data + COMMON_HEADER_SIZE,
+              I_LEVEL2_HEADER_SIZE - COMMON_HEADER_SIZE, 1, fp) == 0) {
+        error("Invalid header (LHarc file ?)");
+        return FALSE;   /* finish */
+    }
+
+    get_bytes(hdr->method, 5, sizeof(hdr->method));
+    hdr->packed_size = (unsigned long)get_longword();
+    hdr->original_size = (unsigned long)get_longword();
+    hdr->unix_last_modified_stamp = get_longword();
+    hdr->attribute = get_byte(); /* reserved */
+    hdr->header_level = get_byte();
+
+    /* defaults for other type */
+    hdr->unix_mode = UNIX_FILE_REGULAR | UNIX_RW_RW_RW;
+    hdr->unix_gid = 0;
+    hdr->unix_uid = 0;
+
+    hdr->has_crc = TRUE;
+    hdr->crc = get_word();
+    hdr->extend_type = get_byte();
+    extend_size = get_word();
+
+    INITIALIZE_CRC(hcrc);
+    hcrc = calccrc(hcrc, data, get_ptr - data);
+
+    extend_size = get_extended_header(fp, hdr, extend_size, &hcrc);
+    if (extend_size == -1)
+        return FALSE;
+
+    padding = header_size - I_LEVEL2_HEADER_SIZE - extend_size;
+    while (padding--)           /* padding should be 0 or 1 */
+        hcrc = UPDATE_CRC(hcrc, fgetc(fp));
+
+    if (hdr->header_crc != hcrc)
+        error("header CRC error");
+
+    return TRUE;
 }
-#endif                         /* defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) */
 
-/* ------------------------------------------------------------------------ */
-static long
-unix_to_generic_stamp(t)
-       time_t          t;
+/*
+ * level 3 header
+ *
+ *
+ * offset   size  field name
+ * --------------------------------------------------
+ *     0       2  size field length (4 fixed)      ^
+ *     2       5  method ID                        |
+ *     7       4  packed size       [*2]           |
+ *    11       4  original size                    |
+ *    15       4  time                             |
+ *    19       1  RESERVED (0x20 fixed)            | [*1] total header size
+ *    20       1  level (0x03 fixed)               |      (X+32)
+ *    21       2  file crc (CRC-16)                |
+ *    23       1  OS ID                            |
+ *    24       4  total header size [*1]           |
+ *    28       4  next-header size                 |
+ * -----------------------------------             |
+ *    32       X  ext-header                       |
+ *                 :                               v
+ * -------------------------------------------------
+ * X +32          data                             ^
+ *                 :                               | [*2] packed size
+ *                 :                               v
+ * -------------------------------------------------
+ *
+ */
+static int
+get_header_level3(fp, hdr, data)
+    FILE *fp;
+    LzHeader *hdr;
+    char *data;
 {
-       struct tm      *tm = localtime(&t);
-
-       return ((((long) (tm->tm_year - 80)) << 25) +
-               (((long) (tm->tm_mon + 1)) << 21) +
-               (((long) tm->tm_mday) << 16) +
-               (long) ((tm->tm_hour << 11) +
-                       (tm->tm_min << 5) +
-                       (tm->tm_sec / 2)));
+    size_t header_size;
+    ssize_t extend_size;
+    int padding;
+    unsigned int hcrc;
+
+    hdr->size_field_length = get_word();
+
+    if (fread(data + COMMON_HEADER_SIZE,
+              I_LEVEL3_HEADER_SIZE - COMMON_HEADER_SIZE, 1, fp) == 0) {
+        error("Invalid header (LHarc file ?)");
+        return FALSE;   /* finish */
+    }
+
+    get_bytes(hdr->method, 5, sizeof(hdr->method));
+    hdr->packed_size = (unsigned long)get_longword();
+    hdr->original_size = (unsigned long)get_longword();
+    hdr->unix_last_modified_stamp = get_longword();
+    hdr->attribute = get_byte(); /* reserved */
+    hdr->header_level = get_byte();
+
+    /* defaults for other type */
+    hdr->unix_mode = UNIX_FILE_REGULAR | UNIX_RW_RW_RW;
+    hdr->unix_gid = 0;
+    hdr->unix_uid = 0;
+
+    hdr->has_crc = TRUE;
+    hdr->crc = get_word();
+    hdr->extend_type = get_byte();
+    hdr->header_size = header_size = get_longword();
+    extend_size = get_longword();
+
+    INITIALIZE_CRC(hcrc);
+    hcrc = calccrc(hcrc, data, get_ptr - data);
+
+    extend_size = get_extended_header(fp, hdr, extend_size, &hcrc);
+    if (extend_size == -1)
+        return FALSE;
+
+    padding = header_size - I_LEVEL3_HEADER_SIZE - extend_size;
+    while (padding--)           /* padding should be 0 */
+        hcrc = UPDATE_CRC(hcrc, fgetc(fp));
+
+    if (hdr->header_crc != hcrc)
+        error("header CRC error");
+
+    return TRUE;
 }
 
-/* ------------------------------------------------------------------------ */
-/* build header functions                                                                                                      */
-/* ------------------------------------------------------------------------ */
 boolean
 get_header(fp, hdr)
-       FILE           *fp;
-       register LzHeader *hdr;
+    FILE *fp;
+    LzHeader *hdr;
 {
-       int             header_size;
-       int             name_length;
-       char            data[LZHEADER_STRAGE];
-       char            dirname[FILENAME_LENGTH];
-       int             dir_length = 0;
-       int             checksum;
-       int             i;
-       char           *ptr;
-       int                             extend_size;
-       int                             dmy;
+    char data[LZHEADER_STORAGE];
 
     int archive_kanji_code = CODE_SJIS;
     int system_kanji_code = default_system_kanji_code;
@@ -591,256 +1131,69 @@ get_header(fp, hdr)
                                        broken archive. */
     char *system_delim = "//";
     int filename_case = NONE;
+    int end_mark;
 
-       memset(hdr, 0, sizeof(LzHeader));
-
-       if (((header_size = getc(fp)) == EOF) || (header_size == 0)) {
-               return FALSE;   /* finish */
-       }
-
-       if (fread(data + 1, I_NAME_LENGTH - 1, 1, fp) == 0) {
-               fatal_error("Invalid header (LHarc file ?)");
-               return FALSE;   /* finish */
-       }
-       setup_get(data + I_HEADER_LEVEL);
-       hdr->header_level = get_byte();
-
-       if (hdr->header_level >= 3) {
-               error("Unknown level header (level %d)", hdr->header_level);
-               return FALSE;
-       }
-
-       setup_get(data + I_HEADER_CHECKSUM);
-       checksum = get_byte();
-
-       if (hdr->header_level == 2) {
-               hdr->header_size = header_size += checksum*256;
-       } else {
-               hdr->header_size = header_size;
-       }
-
-    if (fread(data + I_NAME_LENGTH, header_size - I_NAME_LENGTH, 1, fp) == 0) {
-        fatal_error("Invalid header (LHarc file ?)");
-        return FALSE;  /* finish */
-    }
-
-       if (hdr->header_level != 2 &&
-           fread(data + header_size, sizeof(char), 2, fp) < 2) {
-               fatal_error("Invalid header (LHarc file ?)");
-               return FALSE;   /* finish */
-       }
-
-       memcpy(hdr->method, data + I_METHOD, METHOD_TYPE_STRAGE);
-       setup_get(data + I_PACKED_SIZE);
-       hdr->packed_size = get_longword();
-       hdr->original_size = get_longword();
-       hdr->last_modified_stamp = get_longword();
-       hdr->attribute = get_byte();
-
-       if ((hdr->header_level = get_byte()) != 2) {
-               if (calc_sum(data + I_METHOD, header_size) != checksum)
-                       warning("Checksum error (LHarc file?)");
-               name_length = get_byte();
-        i = get_bytes(hdr->name, name_length, sizeof(hdr->name)-1);
-               hdr->name[i] = '\0';
-       }
-       else {
-               hdr->unix_last_modified_stamp = hdr->last_modified_stamp;
-               name_length = 0;
-       }
-
-       /* defaults for other type */
-       hdr->unix_mode = UNIX_FILE_REGULAR | UNIX_RW_RW_RW;
-       hdr->unix_gid = 0;
-       hdr->unix_uid = 0;
-
-       if (hdr->header_level == 0) {
-               extend_size = header_size - name_length -22;
-               if (extend_size < 0) {
-                       if (extend_size == -2) {
-                               hdr->extend_type = EXTEND_GENERIC;
-                               hdr->has_crc = FALSE;
-                       } else {
-                               error("Unkonwn header (lha file?)");
-                exit(1);
-                       }
-               } else {
-                       hdr->has_crc = TRUE;
-                       hdr->crc = get_word();
-               }
-
-               if (extend_size >= 1) {
-                       hdr->extend_type = get_byte();
-                       extend_size--;
-               }
-               if (hdr->extend_type == EXTEND_UNIX) {
-                       if (extend_size >= 11) {
-                               hdr->minor_version = get_byte();
-                               hdr->unix_last_modified_stamp = (time_t) get_longword();
-                               hdr->unix_mode = get_word();
-                               hdr->unix_uid = get_word();
-                               hdr->unix_gid = get_word();
-                               extend_size -= 11;
-                       } else {
-                               hdr->extend_type = EXTEND_GENERIC;
-                       }
-               }
-               while (extend_size-- > 0)
-                       dmy = get_byte();
-       } else if (hdr->header_level == 1) {
-               hdr->has_crc = TRUE;
-               extend_size = header_size - name_length-25;
-               hdr->crc = get_word();
-               hdr->extend_type = get_byte();
-               while (extend_size-- > 0)
-                       dmy = get_byte();
-       } else { /* level 2 */
-               hdr->has_crc = TRUE;
-               hdr->crc = get_word();
-               hdr->extend_type = get_byte();
-       }               
-
-       if (hdr->header_level > 0) {
-               /* Extend Header */
-               if (hdr->header_level != 2)
-                       setup_get(data + hdr->header_size);
-               ptr = get_ptr;
-               while ((header_size = get_word()) != 0) {
-                       if (hdr->header_level != 2 &&
-                       ((data + LZHEADER_STRAGE - get_ptr < header_size) ||
-                        fread(get_ptr, sizeof(char), header_size, fp) < header_size)) {
-                               error("Invalid header (LHa file ?)");
-                               return FALSE;
-                       }
-                       switch (get_byte()) {
-                       case 0:
-                               /*
-                                * header crc
-                                */
-                               setup_get(get_ptr + header_size - 3);
-                               break;
-                       case 1:
-                               /*
-                                * filename
-                                */
-                name_length =
-                    get_bytes(hdr->name, header_size-3, sizeof(hdr->name)-1);
-                hdr->name[name_length] = 0;
-                               break;
-                       case 2:
-                               /*
-                                * directory
-                                */
-                dir_length =
-                    get_bytes(dirname, header_size-3, sizeof(dirname)-1);
-                dirname[dir_length] = 0;
-                               break;
-                       case 0x40:
-                               /*
-                                * MS-DOS attribute
-                                */
-                               if (hdr->extend_type == EXTEND_MSDOS ||
-                                   hdr->extend_type == EXTEND_HUMAN ||
-                                   hdr->extend_type == EXTEND_GENERIC)
-                                       hdr->attribute = get_word();
-                               break;
-                       case 0x50:
-                               /*
-                                * UNIX permission
-                                */
-                               if (hdr->extend_type == EXTEND_UNIX)
-                                       hdr->unix_mode = get_word();
-                               break;
-                       case 0x51:
-                               /*
-                                * UNIX gid and uid
-                                */
-                               if (hdr->extend_type == EXTEND_UNIX) {
-                                       hdr->unix_gid = get_word();
-                                       hdr->unix_uid = get_word();
-                               }
-                               break;
-                       case 0x52:
-                               /*
-                                * UNIX group name
-                                */
-                i = get_bytes(hdr->group, header_size-3, sizeof(hdr->group)-1);
-                hdr->group[i] = '\0';
-                               break;
-                       case 0x53:
-                               /*
-                                * UNIX user name
-                                */
-                i = get_bytes(hdr->user, header_size-3, sizeof(hdr->user)-1);
-                hdr->user[i] = '\0';
-                               break;
-                       case 0x54:
-                               /*
-                                * UNIX last modified time
-                                */
-                               if (hdr->extend_type == EXTEND_UNIX)
-                                       hdr->unix_last_modified_stamp = (time_t) get_longword();
-                               break;
-                       default:
-                               /*
-                                * other headers
-                                */
-                               setup_get(get_ptr + header_size - 3);
-                               break;
-                       }
-               }
-               if (hdr->header_level != 2 && get_ptr - ptr != 2) {
-                       hdr->packed_size -= get_ptr - ptr - 2;
-                       hdr->header_size += get_ptr - ptr - 2;
-               }
-       }
-
-       switch (hdr->extend_type) {
-       case EXTEND_MSDOS:
-        filename_case = noconvertcase ? NONE : TO_LOWER;
-
-        /* fall through */
-       case EXTEND_HUMAN:
-               if (hdr->header_level == 2)
-                       hdr->unix_last_modified_stamp = hdr->last_modified_stamp;
-               else
-                       hdr->unix_last_modified_stamp =
-                               generic_to_unix_stamp(hdr->last_modified_stamp);
-               break;
-
-#ifdef OSK
-       case EXTEND_OS68K:
-       case EXTEND_XOSK:
-#endif
-       case EXTEND_UNIX:
-        filename_case = NONE;
+    memset(hdr, 0, sizeof(LzHeader));
+
+    setup_get(data);
+
+    if ((end_mark = getc(fp)) == EOF || end_mark == 0) {
+        return FALSE;           /* finish */
+    }
+    data[0] = end_mark;
 
-               break;
+    if (fread(data + 1, COMMON_HEADER_SIZE - 1, 1, fp) == 0) {
+        error("Invalid header (LHarc file ?)");
+        return FALSE;           /* finish */
+    }
+
+    switch (data[I_HEADER_LEVEL]) {
+    case 0:
+        if (get_header_level0(fp, hdr, data) == FALSE)
+            return FALSE;
+        break;
+    case 1:
+        if (get_header_level1(fp, hdr, data) == FALSE)
+            return FALSE;
+        break;
+    case 2:
+        if (get_header_level2(fp, hdr, data) == FALSE)
+            return FALSE;
+        break;
+    case 3:
+        if (get_header_level3(fp, hdr, data) == FALSE)
+            return FALSE;
+        break;
+    default:
+        error("Unknown level header (level %d)", data[I_HEADER_LEVEL]);
+        return FALSE;
+    }
 
-       case EXTEND_MACOS:
+    /* filename conversion */
+    switch (hdr->extend_type) {
+    case EXTEND_MSDOS:
+        filename_case = convertcase ? TO_LOWER : NONE;
+        break;
+    case EXTEND_HUMAN:
+    case EXTEND_OS68K:
+    case EXTEND_XOSK:
+    case EXTEND_UNIX:
+    case EXTEND_JAVA:
+        filename_case = NONE;
+        break;
+
+    case EXTEND_MACOS:
         archive_delim = "\377/:\\";
                           /* `\' is for level 0 header and broken archive. */
         system_delim = "/://";
         filename_case = NONE;
+        break;
 
-               hdr->unix_last_modified_stamp =
-                       generic_to_unix_stamp(hdr->last_modified_stamp, sizeof(hdr->name));
-               break;
-
-       default:
-        filename_case = noconvertcase ? NONE : TO_LOWER;
-        /* FIXME: if small letter is included in filename,
-           the generic_to_unix_filename() do not case conversion,
-           but this code does not consider it. */
-
-               if (hdr->header_level == 2)
-                       hdr->unix_last_modified_stamp = hdr->last_modified_stamp;
-               else
-                       hdr->unix_last_modified_stamp =
-                               generic_to_unix_stamp(hdr->last_modified_stamp);
-       }
+    default:
+        filename_case = convertcase ? TO_LOWER : NONE;
+        break;
+    }
 
-    /* filename kanji code and delimiter conversion */
     if (optional_archive_kanji_code)
         archive_kanji_code = optional_archive_kanji_code;
     if (optional_system_kanji_code)
@@ -852,63 +1205,226 @@ get_header(fp, hdr)
     if (optional_filename_case)
         filename_case = optional_filename_case;
 
-       if (dir_length) {
-        if (name_length + dir_length >= sizeof(hdr->name)) {
-            warning("the length of pathname \"%s%s\" is too long.",
-                    dirname, hdr->name);
-            name_length = sizeof(hdr->name) - dir_length - 1;
-            hdr->name[name_length] = 0;
-        }
-               strcat(dirname, hdr->name);
-               strcpy(hdr->name, dirname);
-               name_length += dir_length;
-       }
-
-    convert_filename(hdr->name, name_length, sizeof(hdr->name),
+    /* kanji code and delimiter conversion */
+    convert_filename(hdr->name, strlen(hdr->name), sizeof(hdr->name),
                      archive_kanji_code,
                      system_kanji_code,
                      archive_delim, system_delim, filename_case);
 
-       return TRUE;
+    if ((hdr->unix_mode & UNIX_FILE_SYMLINK) == UNIX_FILE_SYMLINK) {
+        char *p;
+        /* split symbolic link */
+        p = strchr(hdr->name, '|');
+        if (p) {
+            /* hdr->name is symbolic link name */
+            /* hdr->realname is real name */
+            *p = 0;
+            strcpy(hdr->realname, p+1); /* ok */
+        }
+        else
+            error("unknown symlink name \"%s\"", hdr->name);
+    }
+
+    return TRUE;
+}
+
+/* skip SFX header */
+int
+seek_lha_header(fp)
+    FILE *fp;
+{
+    unsigned char   buffer[64 * 1024]; /* max seek size */
+    unsigned char  *p;
+    int             n;
+
+    n = fread(buffer, 1, sizeof(buffer), fp);
+
+    for (p = buffer; p < buffer + n; p++) {
+        if (! (p[I_METHOD]=='-' && p[I_METHOD+1]=='l' && p[I_METHOD+4]=='-'))
+            continue;
+        /* found "-l??-" keyword (as METHOD type string) */
+
+        /* level 0 or 1 header */
+        if ((p[I_HEADER_LEVEL] == 0 || p[I_HEADER_LEVEL] == 1)
+            && p[I_HEADER_SIZE] > 20
+            && p[I_HEADER_CHECKSUM] == calc_sum(p+2, p[I_HEADER_SIZE])) {
+            if (fseeko(fp, (p - buffer) - n, SEEK_CUR) == -1)
+                fatal_error("cannot seek header");
+            return 0;
+        }
+
+        /* level 2 header */
+        if (p[I_HEADER_LEVEL] == 2
+            && p[I_HEADER_SIZE] >= 24
+            && p[I_ATTRIBUTE] == 0x20) {
+            if (fseeko(fp, (p - buffer) - n, SEEK_CUR) == -1)
+                fatal_error("cannot seek header");
+            return 0;
+        }
+    }
+
+    if (fseeko(fp, -n, SEEK_CUR) == -1)
+        fatal_error("cannot seek header");
+    return -1;
+}
+
+
+/* remove leading `xxxx/..' */
+static char *
+remove_leading_dots(char *path)
+{
+    char *first = path;
+    char *ptr = 0;
+
+    if (strcmp(first, "..") == 0) {
+        warning("Removing leading `..' from member name.");
+        return first+1;         /* change to "." */
+    }
+
+    if (strstr(first, "..") == 0)
+        return first;
+
+    while (path && *path) {
+
+        if (strcmp(path, "..") == 0)
+            ptr = path = path+2;
+        else if (strncmp(path, "../", 3) == 0)
+            ptr = path = path+3;
+        else
+            path = strchr(path, '/');
+
+        if (path && *path == '/') {
+            path++;
+        }
+    }
+
+    if (ptr) {
+        warning("Removing leading `%.*s' from member name.", ptr-first, first);
+        return ptr;
+    }
+
+    return first;
+}
+
+static int
+copy_path_element(char *dst, const char *src, int size)
+{
+    int i;
+
+    if (size < 1) return 0;
+
+    for (i = 0; i < size; i++) {
+        dst[i] = src[i];
+       if (dst[i] == '\0')
+           return i;
+        if (dst[i] == '/') {
+            dst[++i] = 0;
+            return i;
+        }
+    }
+
+    dst[--i] = 0;
+
+    return i;
+}
+
+/* remove leading "xxx/../" and "./" */
+static int
+remove_dots(char *newpath, char *path, size_t size)
+{
+    int len;
+    char *p = newpath;
+
+    path = remove_leading_dots(path);
+
+    while (*path) {
+        if (path[0] == '.' && path[1] == '/')
+            path += 2;
+        else {
+            int len;
+            len = copy_path_element(newpath, path, size);
+
+            path += len;
+            newpath += len;
+            size -= len;
+            if (size <= 1)
+                break;
+        }
+    }
+
+    /* When newpath is empty, set "." */
+    if (newpath == p) {
+        strcpy(newpath, ".");
+        newpath++;
+    }
+
+    return newpath - p;         /* string length */
 }
 
-/* ------------------------------------------------------------------------ */
 void
 init_header(name, v_stat, hdr)
-       char           *name;
-       struct stat    *v_stat;
-       LzHeader       *hdr;
+    char           *name;
+    struct stat    *v_stat;
+    LzHeader       *hdr;
 {
-       int             len;
+    int             len;
 
     memset(hdr, 0, sizeof(LzHeader));
 
-       if (compress_method == LZHUFF5_METHOD_NUM)  /* Changed N.Watazaki */
-               memcpy(hdr->method, LZHUFF5_METHOD, METHOD_TYPE_STRAGE);
-       else if (compress_method)
-               memcpy(hdr->method, LZHUFF1_METHOD, METHOD_TYPE_STRAGE);
-       else
-               memcpy(hdr->method, LZHUFF0_METHOD, METHOD_TYPE_STRAGE);
-
-       hdr->packed_size = 0;
-       hdr->original_size = v_stat->st_size;
-       hdr->last_modified_stamp = unix_to_generic_stamp(v_stat->st_mtime);
-       hdr->attribute = GENERIC_ATTRIBUTE;
-       hdr->header_level = header_level;
-       strcpy(hdr->name, name);
-       len = strlen(name);
-       hdr->crc = 0x0000;
-       hdr->extend_type = EXTEND_UNIX;
-       hdr->unix_last_modified_stamp = v_stat->st_mtime;
-       /* since 00:00:00 JAN.1.1970 */
+    /* the `method' member is rewrote by the encoding function.
+       but need set for empty files */
+    memcpy(hdr->method, LZHUFF0_METHOD, METHOD_TYPE_STORAGE);
+
+    hdr->packed_size = 0;
+    hdr->original_size = v_stat->st_size;
+    hdr->attribute = GENERIC_ATTRIBUTE;
+    hdr->header_level = header_level;
+
+    len = remove_dots(hdr->name, name, sizeof(hdr->name));
+
+    hdr->crc = 0x0000;
+    hdr->extend_type = EXTEND_UNIX;
+    hdr->unix_last_modified_stamp = v_stat->st_mtime;
+    /* since 00:00:00 JAN.1.1970 */
 #ifdef NOT_COMPATIBLE_MODE
-       /* Please need your modification in this space. */
+    /* Please need your modification in this space. */
+#ifdef __DJGPP__
+    hdr->unix_mode = 0;
+    if (S_ISREG(v_stat->st_mode))
+           hdr->unix_mode = hdr->unix_mode | UNIX_FILE_REGULAR;
+    if (S_ISDIR(v_stat->st_mode))
+           hdr->unix_mode = hdr->unix_mode | UNIX_FILE_DIRECTORY;
+    if (S_ISLNK(v_stat->st_mode))
+           hdr->unix_mode = hdr->unix_mode | UNIX_FILE_SYMLINK;
+    if (v_stat->st_mode & S_IRUSR) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_OWNER_READ_PERM;
+    if (v_stat->st_mode & S_IRGRP) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_GROUP_READ_PERM;
+    if (v_stat->st_mode & S_IROTH) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_OTHER_READ_PERM;
+    if (v_stat->st_mode & S_IWUSR) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_OWNER_WRITE_PERM;
+    if (v_stat->st_mode & S_IWGRP) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_GROUP_WRITE_PERM;
+    if (v_stat->st_mode & S_IWOTH) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_OTHER_WRITE_PERM;
+    if (v_stat->st_mode & S_IXUSR) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_OWNER_EXEC_PERM;
+    if (v_stat->st_mode & S_IXGRP) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_GROUP_EXEC_PERM;
+    if (v_stat->st_mode & S_IXOTH) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_OTHER_EXEC_PERM;
+    if (v_stat->st_mode & S_ISUID) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_SETUID;
+    if (v_stat->st_mode & S_ISGID) 
+           hdr->unix_mode = hdr->unix_mode | UNIX_SETGID;
+#endif /* __DJGPP__ */
 #else
-       hdr->unix_mode = v_stat->st_mode;
+    hdr->unix_mode = v_stat->st_mode;
 #endif
 
-       hdr->unix_uid = v_stat->st_uid;
-       hdr->unix_gid = v_stat->st_gid;
+    hdr->unix_uid = v_stat->st_uid;
+    hdr->unix_gid = v_stat->st_gid;
 
 #if INCLUDE_OWNER_NAME_IN_HEADER
 #if HAVE_GETPWUID
@@ -934,251 +1450,378 @@ init_header(name, v_stat, hdr)
     }
 #endif
 #endif /* INCLUDE_OWNER_NAME_IN_HEADER */
-       if (is_directory(v_stat)) {
-               memcpy(hdr->method, LZHDIRS_METHOD, METHOD_TYPE_STRAGE);
-               hdr->attribute = GENERIC_DIRECTORY_ATTRIBUTE;
-               hdr->original_size = 0;
-               if (len > 0 && hdr->name[len - 1] != '/')
-                       strcpy(&hdr->name[len++], "/");
-       }
-
-#ifdef S_IFLNK 
-       if (is_symlink(v_stat)) {
-               char    lkname[FILENAME_LENGTH];
-               int             len;    
-               memcpy(hdr->method, LZHDIRS_METHOD, METHOD_TYPE_STRAGE);
-               hdr->attribute = GENERIC_DIRECTORY_ATTRIBUTE;
-               hdr->original_size = 0;
-               len = readlink(name, lkname, sizeof(lkname));
-               if (xsnprintf(hdr->name, sizeof(hdr->name),
-                      "%s|%.*s", hdr->name, len, lkname) == -1)
-            error("file name is too long (%s -> %.*s)", hdr->name, len, lkname);
-       }
+    if (is_directory(v_stat)) {
+        memcpy(hdr->method, LZHDIRS_METHOD, METHOD_TYPE_STORAGE);
+        hdr->attribute = GENERIC_DIRECTORY_ATTRIBUTE;
+        hdr->original_size = 0;
+        if (len > 0 && hdr->name[len - 1] != '/') {
+            if (len < sizeof(hdr->name)-1)
+                strcpy(&hdr->name[len++], "/"); /* ok */
+            else
+                warning("the length of dirname \"%s\" is too long.",
+                        hdr->name);
+        }
+    }
+
+#ifdef S_IFLNK
+    if (is_symlink(v_stat)) {
+        memcpy(hdr->method, LZHDIRS_METHOD, METHOD_TYPE_STORAGE);
+        hdr->attribute = GENERIC_DIRECTORY_ATTRIBUTE;
+        hdr->original_size = 0;
+        readlink(name, hdr->realname, sizeof(hdr->realname));
+    }
 #endif
 }
 
-/* ------------------------------------------------------------------------ */
-/* Write unix extended header or generic header. */
+static void
+write_unix_info(hdr)
+    LzHeader *hdr;
+{
+    /* UNIX specific informations */
+
+    put_word(5);            /* size */
+    put_byte(0x50);         /* permission */
+    put_word(hdr->unix_mode);
+
+    put_word(7);            /* size */
+    put_byte(0x51);         /* gid and uid */
+    put_word(hdr->unix_gid);
+    put_word(hdr->unix_uid);
+
+    if (hdr->group[0]) {
+        int len = strlen(hdr->group);
+        put_word(len + 3);  /* size */
+        put_byte(0x52);     /* group name */
+        put_bytes(hdr->group, len);
+    }
+
+    if (hdr->user[0]) {
+        int len = strlen(hdr->user);
+        put_word(len + 3);  /* size */
+        put_byte(0x53);     /* user name */
+        put_bytes(hdr->user, len);
+    }
+
+    if (hdr->header_level == 1) {
+        put_word(7);        /* size */
+        put_byte(0x54);     /* time stamp */
+        put_longword(hdr->unix_last_modified_stamp);
+    }
+}
+
+static size_t
+write_header_level0(data, hdr, pathname)
+    LzHeader *hdr;
+    char *data, *pathname;
+{
+    int limit;
+    int name_length;
+    size_t header_size;
+
+    setup_put(data);
+    memset(data, 0, LZHEADER_STORAGE);
+
+    put_byte(0x00);             /* header size */
+    put_byte(0x00);             /* check sum */
+    put_bytes(hdr->method, 5);
+    put_longword(hdr->packed_size);
+    put_longword(hdr->original_size);
+    put_longword(unix_to_generic_stamp(hdr->unix_last_modified_stamp));
+    put_byte(hdr->attribute);
+    put_byte(hdr->header_level); /* level 0 */
+
+    /* write pathname (level 0 header contains the directory part) */
+    name_length = strlen(pathname);
+    if (generic_format)
+        limit = 255 - I_GENERIC_HEADER_SIZE + 2;
+    else
+        limit = 255 - I_LEVEL0_HEADER_SIZE + 2;
+
+    if (name_length > limit) {
+        warning("the length of pathname \"%s\" is too long.", pathname);
+        name_length = limit;
+    }
+    put_byte(name_length);
+    put_bytes(pathname, name_length);
+    put_word(hdr->crc);
+
+    if (generic_format) {
+        header_size = I_GENERIC_HEADER_SIZE + name_length - 2;
+        data[I_HEADER_SIZE] = header_size;
+        data[I_HEADER_CHECKSUM] = calc_sum(data + I_METHOD, header_size);
+    } else {
+        /* write old-style extend header */
+        put_byte(EXTEND_UNIX);
+        put_byte(CURRENT_UNIX_MINOR_VERSION);
+        put_longword(hdr->unix_last_modified_stamp);
+        put_word(hdr->unix_mode);
+        put_word(hdr->unix_uid);
+        put_word(hdr->unix_gid);
+
+        /* size of extended header is 12 */
+        header_size = I_LEVEL0_HEADER_SIZE + name_length - 2;
+        data[I_HEADER_SIZE] = header_size;
+        data[I_HEADER_CHECKSUM] = calc_sum(data + I_METHOD, header_size);
+    }
+
+    return header_size + 2;
+}
+
+static size_t
+write_header_level1(data, hdr, pathname)
+    LzHeader *hdr;
+    char *data, *pathname;
+{
+    int name_length, dir_length, limit;
+    char *basename, *dirname;
+    size_t header_size;
+    char *extend_header_top;
+    size_t extend_header_size;
+
+    basename = strrchr(pathname, LHA_PATHSEP);
+    if (basename) {
+        basename++;
+        name_length = strlen(basename);
+        dirname = pathname;
+        dir_length = basename - dirname;
+    }
+    else {
+        basename = pathname;
+        name_length = strlen(basename);
+        dirname = "";
+        dir_length = 0;
+    }
+
+    setup_put(data);
+    memset(data, 0, LZHEADER_STORAGE);
+
+    put_byte(0x00);             /* header size */
+    put_byte(0x00);             /* check sum */
+    put_bytes(hdr->method, 5);
+    put_longword(hdr->packed_size);
+    put_longword(hdr->original_size);
+    put_longword(unix_to_generic_stamp(hdr->unix_last_modified_stamp));
+    put_byte(0x20);
+    put_byte(hdr->header_level); /* level 1 */
+
+    /* level 1 header: write filename (basename only) */
+    limit = 255 - I_LEVEL1_HEADER_SIZE + 2;
+    if (name_length > limit) {
+        put_byte(0);            /* name length */
+    }
+    else {
+        put_byte(name_length);
+        put_bytes(basename, name_length);
+    }
+
+    put_word(hdr->crc);
+
+    if (generic_format)
+        put_byte(0x00);
+    else
+        put_byte(EXTEND_UNIX);
+
+    /* write extend header from here. */
+
+    extend_header_top = put_ptr+2; /* +2 for the field `next header size' */
+    header_size = extend_header_top - data - 2;
+
+    /* write filename and dirname */
+
+    if (name_length > limit) {
+        put_word(name_length + 3); /* size */
+        put_byte(0x01);         /* filename */
+        put_bytes(basename, name_length);
+    }
+
+    if (dir_length > 0) {
+        put_word(dir_length + 3); /* size */
+        put_byte(0x02);         /* dirname */
+        put_bytes(dirname, dir_length);
+    }
+
+    if (!generic_format)
+        write_unix_info(hdr);
+
+    put_word(0x0000);           /* next header size */
+
+    extend_header_size = put_ptr - extend_header_top;
+    /* On level 1 header, the packed size field is contains the ext-header */
+    hdr->packed_size += put_ptr - extend_header_top;
+
+    /* put `skip size' */
+    setup_put(data + I_PACKED_SIZE);
+    put_longword(hdr->packed_size);
+
+    data[I_HEADER_SIZE] = header_size;
+    data[I_HEADER_CHECKSUM] = calc_sum(data + I_METHOD, header_size);
+
+    return header_size + extend_header_size + 2;
+}
+
+static size_t
+write_header_level2(data, hdr, pathname)
+    LzHeader *hdr;
+    char *data, *pathname;
+{
+    int name_length, dir_length;
+    char *basename, *dirname;
+    size_t header_size;
+    char *extend_header_top;
+    char *headercrc_ptr;
+    unsigned int hcrc;
+
+    basename = strrchr(pathname, LHA_PATHSEP);
+    if (basename) {
+        basename++;
+        name_length = strlen(basename);
+        dirname = pathname;
+        dir_length = basename - dirname;
+    }
+    else {
+        basename = pathname;
+        name_length = strlen(basename);
+        dirname = "";
+        dir_length = 0;
+    }
+
+    setup_put(data);
+    memset(data, 0, LZHEADER_STORAGE);
+
+    put_word(0x0000);           /* header size */
+    put_bytes(hdr->method, 5);
+    put_longword(hdr->packed_size);
+    put_longword(hdr->original_size);
+    put_longword(hdr->unix_last_modified_stamp);
+    put_byte(0x20);
+    put_byte(hdr->header_level); /* level 2 */
+
+    put_word(hdr->crc);
+
+    if (generic_format)
+        put_byte(0x00);
+    else
+        put_byte(EXTEND_UNIX);
+
+    /* write extend header from here. */
+
+    extend_header_top = put_ptr+2; /* +2 for the field `next header size' */
+
+    /* write common header */
+    put_word(5);
+    put_byte(0x00);
+    headercrc_ptr = put_ptr;
+    put_word(0x0000);           /* header CRC */
+
+    /* write filename and dirname */
+    /* must have this header, even if the name_length is 0. */
+    put_word(name_length + 3);  /* size */
+    put_byte(0x01);             /* filename */
+    put_bytes(basename, name_length);
+
+    if (dir_length > 0) {
+        put_word(dir_length + 3); /* size */
+        put_byte(0x02);         /* dirname */
+        put_bytes(dirname, dir_length);
+    }
+
+    if (!generic_format)
+        write_unix_info(hdr);
+
+    put_word(0x0000);           /* next header size */
+
+    header_size = put_ptr - data;
+    if ((header_size & 0xff) == 0) {
+        /* cannot put zero at the first byte on level 2 header. */
+        /* adjust header size. */
+        put_byte(0);            /* padding */
+        header_size++;
+    }
+
+    /* put header size */
+    setup_put(data + I_HEADER_SIZE);
+    put_word(header_size);
+
+    /* put header CRC in extended header */
+    INITIALIZE_CRC(hcrc);
+    hcrc = calccrc(hcrc, data, (unsigned int) header_size);
+    setup_put(headercrc_ptr);
+    put_word(hcrc);
+
+    return header_size;
+}
+
 void
-write_header(nafp, hdr)
-       FILE           *nafp;
-       LzHeader       *hdr;
+write_header(fp, hdr)
+    FILE           *fp;
+    LzHeader       *hdr;
 {
-       int             header_size;
-       int             name_length;
-       char            data[LZHEADER_STRAGE];
-       char           *p;
-       char           *headercrc_ptr;
+    size_t header_size;
+    char data[LZHEADER_STORAGE];
 
     int archive_kanji_code = CODE_SJIS;
     int system_kanji_code = default_system_kanji_code;
     char *archive_delim = "\377";
     char *system_delim = "/";
     int filename_case = NONE;
-       char lzname[FILENAME_LENGTH];
+    char pathname[FILENAME_LENGTH];
 
     if (optional_archive_kanji_code)
         archive_kanji_code = optional_archive_kanji_code;
     if (optional_system_kanji_code)
         system_kanji_code = optional_system_kanji_code;
 
-       memset(data, 0, LZHEADER_STRAGE);
-       memcpy(data + I_METHOD, hdr->method, METHOD_TYPE_STRAGE);
-       setup_put(data + I_PACKED_SIZE);
-       put_longword(hdr->packed_size);
-       put_longword(hdr->original_size);
-
-       if (hdr->header_level == HEADER_LEVEL2)
-               put_longword((long) hdr->unix_last_modified_stamp);
-       else
-               put_longword(hdr->last_modified_stamp);
-
-       switch (hdr->header_level) {
-       case HEADER_LEVEL0:
-               put_byte(hdr->attribute);
-               break;
-       case HEADER_LEVEL1:
-       case HEADER_LEVEL2:
-               put_byte(0x20);
-               break;
-       }
-
-       put_byte(hdr->header_level);
-
-    if (generic_format)
+    if (generic_format && convertcase)
         filename_case = TO_UPPER;
 
-       if (hdr->header_level == HEADER_LEVEL0) {
+    if (hdr->header_level == 0) {
         archive_delim = "\\";
     }
 
-    strncpy(lzname, hdr->name, sizeof(lzname));
-    convert_filename(lzname, strlen(lzname), sizeof(lzname),
+    if ((hdr->unix_mode & UNIX_FILE_SYMLINK) == UNIX_FILE_SYMLINK) {
+        char *p;
+        p = strchr(hdr->name, '|');
+        if (p) {
+            error("symlink name \"%s\" contains '|' char. change it into '_'",
+                  hdr->name);
+            *p = '_';
+        }
+        if (xsnprintf(pathname, sizeof(pathname),
+                      "%s|%s", hdr->name, hdr->realname) == -1)
+            error("file name is too long (%s -> %s)", hdr->name, hdr->realname);
+    }
+    else {
+        strncpy(pathname, hdr->name, sizeof(pathname));
+        pathname[sizeof(pathname)-1] = 0;
+    }
+
+    convert_filename(pathname, strlen(pathname), sizeof(pathname),
                      system_kanji_code,
                      archive_kanji_code,
                      system_delim, archive_delim, filename_case);
 
-       if (hdr->header_level != HEADER_LEVEL2) {
-        int limit;
-        /* level 0 header: write pathname (contain the directory part) */
-        /* level 1 header: write filename (basename only) */
-        if (hdr->header_level == HEADER_LEVEL0 ||
-            (p = strrchr(lzname, LHA_PATHSEP)) == 0)
-            p = lzname;
-        else
-            ++p;
-        name_length = strlen(p);
-
-        limit = 255 - I_UNIX_EXTEND_BOTTOM + 2;
-        if (header_level == 0) {
-            if (generic_format)
-                limit = 255 - I_GENERIC_HEADER_BOTTOM + 2;
-
-            if (name_length > limit) {
-                warning("the length of pathname \"%s\" is too long.", p);
-                name_length = limit;
-            }
-        }
-
-        if (header_level == 1) {
-            put_byte(0);
-        }
-        else {
-            put_byte(name_length);
-            memcpy(data + I_NAME, p, name_length);
-            setup_put(data + I_NAME + name_length);
-        }
-       }
-
-       put_word(hdr->crc);
-       if (header_level == HEADER_LEVEL0) {
-               if (generic_format) {
-                       header_size = I_GENERIC_HEADER_BOTTOM - 2 + name_length;
-                       data[I_HEADER_SIZE] = header_size;
-                       data[I_HEADER_CHECKSUM] = calc_sum(data + I_METHOD, header_size);
-               } else {
-                       /* write old-style extend header */
-                       put_byte(EXTEND_UNIX);
-                       put_byte(CURRENT_UNIX_MINOR_VERSION);
-                       put_longword((long) hdr->unix_last_modified_stamp);
-                       put_word(hdr->unix_mode);
-                       put_word(hdr->unix_uid);
-                       put_word(hdr->unix_gid);
-                       header_size = I_UNIX_EXTEND_BOTTOM - 2 + name_length;
-                       data[I_HEADER_SIZE] = header_size;
-                       data[I_HEADER_CHECKSUM] = calc_sum(data + I_METHOD, header_size);
-               }
-       } else {
-               /* write extend header. */
-               char           *ptr;
-
-               if (generic_format)
-                       put_byte(0x00);
-               else
-                       put_byte(EXTEND_UNIX);
-
-               ptr = put_ptr;
-               if (hdr->header_level == HEADER_LEVEL2) {
-                       /* write common header */
-                       put_word(5);
-                       put_byte(0x00);
-                       headercrc_ptr = put_ptr;
-                       put_word(0x0000);
-               }
-
-               if (generic_format) {
-                       header_size = put_ptr - data;   /* +2 for last 0x0000 */
-               } else {
-                       put_word(5);
-                       if (hdr->header_level == HEADER_LEVEL1)
-                               header_size = put_ptr - data - 2;
-                       put_byte(0x50); /* permission */
-                       put_word(hdr->unix_mode);
-                       put_word(7);
-                       put_byte(0x51); /* gid and uid */
-                       put_word(hdr->unix_gid);
-                       put_word(hdr->unix_uid);
-
-            {
-                int len = strlen(hdr->group);
-                if (len > 0) {
-                    put_word(len + 3);
-                    put_byte(0x52);    /* group name */
-                    put_bytes(hdr->group, len);
-                }
-
-                len = strlen(hdr->user);
-                if (len > 0) {
-                    put_word(len + 3);
-                    put_byte(0x53);    /* user name */
-                    put_bytes(hdr->user, len);
-                }
-            }
-
-            if (hdr->header_level == 1) {
-                if (p = strrchr(lzname, LHA_PATHSEP))
-                    name_length = strlen(++p);
-                else {
-                    p = lzname;
-                    name_length = strlen(lzname);
-                }
-                put_word(name_length + 3);
-                put_byte(1);   /* filename */
-                put_bytes(p, name_length);
-            }
+    switch (hdr->header_level) {
+    case 0:
+        header_size = write_header_level0(data, hdr, pathname);
+        break;
+    case 1:
+        header_size = write_header_level1(data, hdr, pathname);
+        break;
+    case 2:
+        header_size = write_header_level2(data, hdr, pathname);
+        break;
+    default:
+        error("Unknown level header (level %d)", hdr->header_level);
+        exit(1);
+    }
 
-                       if (p = strrchr(lzname, LHA_PATHSEP)) {
-                               name_length = p - lzname + 1;
-                               put_word(name_length + 3);
-                               put_byte(2);    /* dirname */
-                put_bytes(lzname, name_length);
-                       }
-               }               /* if generic .. */
-
-               if (header_level != HEADER_LEVEL2) {
-                       if (!generic_format) {
-                               put_word(7);
-                               put_byte(0x54); /* time stamp */
-                               put_longword(hdr->unix_last_modified_stamp);
-                       }
-                       hdr->packed_size += put_ptr - ptr;
-                       ptr = put_ptr;
-                       setup_put(data + I_PACKED_SIZE);
-                       put_longword(hdr->packed_size);
-                       put_ptr = ptr;
-                       data[I_HEADER_SIZE] = header_size;
-                       data[I_HEADER_CHECKSUM] = calc_sum(data + I_METHOD, header_size);
-               } else {                /* header level 2 */
-                       if (p = strrchr(lzname, LHA_PATHSEP))
-                               name_length = strlen(++p);
-                       else {
-                               p = lzname;
-                               name_length = strlen(lzname);
-                       }
-                       put_word(name_length + 3);
-                       put_byte(1);    /* filename */
-            put_bytes(p, name_length);
-               }               /* if he.. != HEAD_LV2 */
-               header_size = put_ptr - data;
-       }
-
-       if (header_level == HEADER_LEVEL2) {
-               unsigned short  hcrc;
-               setup_put(data + I_HEADER_SIZE);
-               put_word(header_size + 2);
-               /* common header */
-               hcrc = calc_header_crc(data, (unsigned int) header_size + 2);
-               setup_put(headercrc_ptr);
-               put_word(hcrc);
-       }
-
-       if (fwrite(data, header_size + 2, 1, nafp) == 0)
-               fatal_error("Cannot write to temporary file");
+    if (fwrite(data, header_size, 1, fp) == 0)
+        fatal_error("Cannot write to temporary file");
 }
 
 #if MULTIBYTE_FILENAME
 
-#if defined(__APPLE__)
+#if defined(__APPLE__)  /* Added by Hiroto Sakai */
 
 #include <CoreFoundation/CFString.h>
 #include <CoreFoundation/CFStringEncodingExt.h>
@@ -1294,12 +1937,12 @@ ConvertEncodingByIconv(const char *src, char *dst, int dstsize,
 
     ic = iconv_open(dstEnc, srcEnc);
     if (ic == (iconv_t)-1) {
-        error("iconv_open() failure");
+        error("iconv_open() failure: %s", strerror(errno));
         return -1;
     }
 
     if (iconv(ic, &src_p, &sLen, &dst_p, &iLen) == (size_t)-1) {
-        error("iconv() failure");
+        error("iconv() failure: %s", strerror(errno));
         iconv_close(ic);
         return -1;
     }
@@ -1328,7 +1971,6 @@ sjis_to_utf8(char *dst, const char *src, size_t dstsize)
   error("not support utf-8 conversion");
 #endif
 
-  /* not supported */
   if (dstsize < 1) return dst;
   dst[dstsize-1] = 0;
   return strncpy(dst, src, dstsize-1);
@@ -1353,7 +1995,6 @@ utf8_to_sjis(char *dst, const char *src, size_t dstsize)
   error("not support utf-8 conversion");
 #endif
 
-  /* not supported */
   if (dstsize < 1) return dst;
   dst[dstsize-1] = 0;
   return strncpy(dst, src, dstsize-1);
@@ -1361,8 +2002,8 @@ utf8_to_sjis(char *dst, const char *src, size_t dstsize)
 
 /*
  * SJIS <-> EUC ÊÑ´¹´Ø¿ô
- * ¡ÖÆüËܸì¾ðÊó½èÍý¡×  ¥½¥Õ¥È¥Ð¥ó¥¯(³ô)
- *     ¤è¤êÈ´¿è(by Koji Arai)
+ * ¡ÖÆüËܸì¾ðÊó½èÍý¡×   ¥½¥Õ¥È¥Ð¥ó¥¯(³ô)
+ *  ¤è¤êÈ´¿è(by Koji Arai)
  */
 void
 euc2sjis(int *p1, int *p2)
@@ -1389,11 +2030,106 @@ sjis2euc(int *p1, int *p2)
     *p1 |= 0x80;
     *p2 |= 0x80;
 }
-#endif /* MULTIBYTE_FILENAME */
 
-/* Local Variables: */
-/* mode:c */
-/* tab-width:4 */
-/* compile-command:"gcc -c header.c" */
-/* End: */
-/* vi: set tabstop=4: */
+static int
+hex2int(int c)
+{
+    switch (c) {
+    case '0': case '1': case '2': case '3': case '4':
+    case '5': case '6': case '7': case '8': case '9':
+        return c - '0';
+
+    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+        return c - 'a' + 10;
+
+    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+        return c - 'A' + 10;
+    default:
+        return -1;
+    }
+}
+
+static int
+int2hex(int c)
+{
+    switch (c) {
+    case 0: case 1: case 2: case 3: case 4:
+    case 5: case 6: case 7: case 8: case 9:
+        return c + '0';
+
+    case 10: case 11: case 12: case 13: case 14: case 15:
+        return c + 'a' - 10;
+
+    default:
+        return -1;
+    }
+}
+
+int
+cap_to_sjis(char *dst, const char *src, size_t dstsize)
+{
+    int i, j;
+    size_t len = strlen(src);
+    int a, b;
+
+    for (i = j = 0; i < len && i < dstsize; i++) {
+        if (src[i] != ':') {
+            dst[j++] = src[i];
+            continue;
+        }
+
+        i++;
+        a = hex2int((unsigned char)src[i]);
+        b = hex2int((unsigned char)src[i+1]);
+
+        if (a == -1 || b == -1) {
+            /* leave as it */
+            dst[j++] = ':';
+            strncpy(dst+j, src+i, dstsize-j);
+            dst[dstsize-1] = 0;
+            return strlen(dst);
+        }
+
+        i++;
+
+        dst[j++] = a * 16 + b;
+    }
+    dst[j] = 0;
+    return j;
+}
+
+int
+sjis_to_cap(char *dst, const char *src, size_t dstsize)
+{
+    int i, j;
+    size_t len = strlen(src);
+    int a, b;
+
+    for (i = j = 0; i < len && i < dstsize; i++) {
+        if (src[i] == ':') {
+            strncpy(dst+j, ":3a", dstsize-j);
+            dst[dstsize-1] = 0;
+            j = strlen(dst);
+            continue;
+        }
+        if (isprint(src[i])) {
+            dst[j++] = src[i];
+            continue;
+        }
+
+        if (j + 3 >= dstsize) {
+            dst[j] = 0;
+            return j;
+        }
+
+        a = int2hex((unsigned char)src[i] / 16);
+        b = int2hex((unsigned char)src[i] % 16);
+
+        dst[j++] = ':';
+        dst[j++] = a;
+        dst[j++] = b;
+    }
+    dst[j] = 0;
+    return j;
+}
+#endif /* MULTIBYTE_FILENAME */