/* ------------------------------------------------------------------------ */
/* 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       */
/* ------------------------------------------------------------------------ */
#include "lha.h"

#if !STRCHR_8BIT_CLEAN
/* should use 8 bit clean version */
#undef strchr
#undef strrchr
#define strchr  xstrchr
#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))

int optional_archive_kanji_code = NONE;
int optional_system_kanji_code = NONE;
char *optional_archive_delim = NULL;
char *optional_system_delim = NULL;
int optional_filename_case = NONE;

#ifdef MULTIBYTE_FILENAME
int default_system_kanji_code = MULTIBYTE_FILENAME;
#else
int default_system_kanji_code = NONE;
#endif

/* ------------------------------------------------------------------------ */
int
calc_sum(p, len)
	register char  *p;
	register int    len;
{
	register int    sum;

	for (sum = 0; len; len--)
		sum += *p++;

	return sum & 0xff;
}

/* ------------------------------------------------------------------------ */
static unsigned short
get_word()
{
	int             b0, b1;

	b0 = get_byte();
	b1 = get_byte();
	return (b1 << 8) + b0;
}

/* ------------------------------------------------------------------------ */
static void
put_word(v)
	unsigned int    v;
{
	put_byte(v);
	put_byte(v >> 8);
}

/* ------------------------------------------------------------------------ */
static long
get_longword()
{
	long            b0, b1, b2, b3;

	b0 = get_byte();
	b1 = get_byte();
	b2 = get_byte();
	b3 = get_byte();
	return (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
}

/* ------------------------------------------------------------------------ */
static void
put_longword(v)
	long            v;
{
	put_byte(v);
	put_byte(v >> 8);
	put_byte(v >> 16);
	put_byte(v >> 24);
}

static int
get_bytes(buf, len, size)
    char *buf;
    int len, size;
{
    int i;
    for (i = 0; i < len && i < size; i++)
        buf[i] = get_ptr[i];
    get_ptr += len;
    return i;
}

static void
put_bytes(buf, len)
    char *buf;
    int len;
{
    int i;
    for (i = 0; i < len; i++)
        put_byte(buf[i]);
}

/* added by Koji Arai */
void
convert_filename(name, len, size,
                 from_code, to_code,
                 from_delim, to_delim,
                 case_to)
    char *name;
    int len;
    int size;
    int from_code, to_code, case_to;
    char *from_delim, *to_delim;

{
    int i;
#ifdef MULTIBYTE_FILENAME
    char tmp[FILENAME_LENGTH];

    if (from_code == CODE_SJIS && to_code == CODE_UTF8) {
        for (i = 0; i < len; i++)
            /* FIXME: provisionally fix for the Mac OS CoreFoundation */
            if ((unsigned char)name[i] == LHA_PATHSEP)  name[i] = '/';
        sjis_to_utf8(tmp, name, sizeof(tmp));
        strncpy(name, tmp, size);
        name[size-1] = 0;
        len = strlen(name);
        for (i = 0; i < len; i++)
            if (name[i] == '/')  name[i] = LHA_PATHSEP;
        from_code = CODE_UTF8;
    }
    else if (from_code == CODE_UTF8 && to_code == CODE_SJIS) {
        for (i = 0; i < len; i++)
            /* FIXME: provisionally fix for the Mac OS CoreFoundation */
            if ((unsigned char)name[i] == LHA_PATHSEP)  name[i] = '/';
        utf8_to_sjis(tmp, name, sizeof(tmp));
        strncpy(name, tmp, size);
        name[size-1] = 0;
        len = strlen(name);
        for (i = 0; i < len; i++)
            if (name[i] == '/')  name[i] = LHA_PATHSEP;
        from_code = CODE_SJIS;
    }
#endif

    for (i = 0; i < len; i ++) {
#ifdef MULTIBYTE_FILENAME
        if (from_code == CODE_EUC &&
            (unsigned char)name[i] == 0x8e) {
            if (to_code != CODE_SJIS) {
                i++;
                continue;
            }

            /* X0201 KANA */
            memmove(name + i, name + i + 1, len - i);
            len--;
            continue;
        }
        if (from_code == CODE_SJIS && X0201_KANA_P(name[i])) {
            if (to_code != CODE_EUC) {
                continue;
            }

            if (len == size - 1) /* check overflow */
                len--;
            memmove(name+i+1, name+i, len-i);
            name[i] = 0x8e;
            i++;
            len++;
            continue;
        }
        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];
            c2 = (unsigned char)name[i+1];
            euc2sjis(&c1, &c2);
            name[i] = c1;
            name[i+1] = c2;
            i++;
            continue;
        }
        if (from_code == CODE_SJIS &&
            SJC_FIRST_P(name[i]) &&
            SJC_SECOND_P(name[i+1])) {
            int c1, c2;

            if (to_code != CODE_EUC) {
                i++;
                continue;
            }

            c1 = (unsigned char)name[i];
            c2 = (unsigned char)name[i+1];
            sjis2euc(&c1, &c2);
            name[i] = c1;
            name[i+1] = c2;
            i++;
            continue;
        }
#endif /* MULTIBYTE_FILENAME */
        {
            char *ptr;

            /* transpose from_delim to to_delim */

            if ((ptr = strchr(from_delim, name[i])) != NULL) {
                name[i] = to_delim[ptr - from_delim];
                continue;
            }
        }

        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]);
            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 ->|						*/
/*																			*/
/* ------------------------------------------------------------------------ */

/*
 * NOTE : If you don't have `gettimeofday(2)', or your gettimeofday(2)
 * returns bogus timezone information, try FTIME, MKTIME, TIMELOCAL or TZSET.
 */

/* choose one */
#if defined(HAVE_MKTIME)
#ifdef HAVE_TIMELOCAL
#undef HAVE_TIMELOCAL
#endif
#endif				/* defined(HAVE_MKTIME) */

#if defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL)
#ifdef HAVE_TZSET
#undef HAVE_TZSET
#endif
#endif				/* defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) */

#if defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) || defined(HAVE_TZSET)
#ifdef HAVE_FTIME
#undef HAVE_FTIME
#endif
#endif

#if defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) || defined(HAVE_TZSET) || defined(HAVE_FTIME)
#ifdef HAVE_GETTIMEOFDAY
#undef HAVE_GETTIMEOFDAY
#endif
#else
#ifndef HAVE_GETTIMEOFDAY
#define HAVE_GETTIMEOFDAY		/* use gettimeofday() */
#endif
#endif

#ifdef HAVE_FTIME
#include <sys/timeb.h>
#endif

/*
 * You may define as : #define TIMEZONE_HOOK		\ extern long
 * timezone ;	\ extern void tzset();
 */
#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 */
#endif

/* ------------------------------------------------------------------------ */
#if defined(HAVE_FTIME) || defined(HAVE_GETTIMEOFDAY) || defined(HAVE_TZSET)
static long
gettz()
#ifdef HAVE_TZSET
{
	tzset();
	return timezone;
}
#endif

/* ------------------------------------------------------------------------ */
#if !defined(HAVE_TZSET) && defined(HAVE_FTIME)
{
	struct timeb    buf;

	ftime(&buf);
	return buf.timezone * 60L;
}
#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 */
}
#endif
#endif				/* defined(HAVE_FTIME) || defined(HAVE_GETTIMEOFDAY) ||
                     * defined(HAVE_TZSET) */

/* ------------------------------------------------------------------------ */
static          time_t
generic_to_unix_stamp(t)
	long            t;
#if defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL)
{
	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
}

#else				/* defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) */
{
	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;
}
#endif				/* defined(HAVE_MKTIME) || defined(HAVE_TIMELOCAL) */

/* ------------------------------------------------------------------------ */
static long
unix_to_generic_stamp(t)
	time_t          t;
{
	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)));
}

/* ------------------------------------------------------------------------ */
/* build header functions													*/
/* ------------------------------------------------------------------------ */

/*
 * extended header
 *
 *             size  field name
 *  --------------------------------
 *  base header:         :
 *                2  next-header size  [*1]
 *  --------------------------------------
 *  ext header:   1  ext-type            ^
 *                ?  contents            | [*1] next-header size
 *                2  next-header size    v
 *  --------------------------------------
 *
 */
static int
get_extended_header(fp, hdr, header_size)
    FILE *fp;
    LzHeader *hdr;
    int header_size;
{
    char data[LZHEADER_STRAGE];
    int name_length;
    char dirname[FILENAME_LENGTH];
    int dir_length = 0;
    int i;
    int whole_size = header_size;

    if (hdr->header_level == 0)
        return 0;

    name_length = strlen(hdr->name);

    for (; header_size != 0; whole_size += header_size = get_word()) {
        setup_get(data);
        if (sizeof(data) < header_size)
            fatal_error("header size too large.");

        if (fread(data, header_size, 1, fp) == 0) {
            error("Invalid header (LHa file ?)");
            return -1;
        }
        switch (get_byte()) {
        case 0:
            /* header crc */
            setup_get(get_ptr + header_size - 3); /* FIXME: ignored? */
            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 */
            if (hdr->extend_type == EXTEND_UNIX) {
                i = get_bytes(hdr->group, header_size-3, sizeof(hdr->group)-1);
                hdr->group[i] = '\0';
            }
            break;
        case 0x53:
            /* UNIX user name */
            if (hdr->extend_type == EXTEND_UNIX) {
                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 (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;
    }

    return whole_size;
}

/*
 * 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                          |
 * X +24      Y  ext-header(old style)             v
 * -------------------------------------------------
 * X+Y+24   [*2] data
 *            :
 *
 * 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
 *
 */
static int
get_header_level0(fp, hdr, data)
    FILE *fp;
    LzHeader *hdr;
    char *data;
{
    int header_size;
    int checksum;
    int name_length;
    int i;
    int extend_size;

    hdr->header_size = header_size = get_byte();
    checksum = get_byte();

    if (fread(data+I_NAME_LENGTH, header_size+2-I_NAME_LENGTH, 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 = get_longword();
    hdr->original_size = 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 does 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)
        setup_get(get_ptr + extend_size);

    return TRUE;
}

/*
 * 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                         |
 * 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;
{
    int header_size, extend_size;
    int checksum;
    int name_length;
    int i, dummy;

    hdr->header_size = header_size = get_byte();
    checksum = get_byte();

    if (fread(data+I_NAME_LENGTH, header_size+2-I_NAME_LENGTH, 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 = get_longword(); /* skip size */
    hdr->original_size = 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 - 27;
    if (dummy > 0)
        setup_get(get_ptr + dummy); /* skip old style extend header */

    extend_size = get_word();
    extend_size = get_extended_header(fp, hdr, extend_size);
    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;

    return TRUE;
}

/*
 * 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                         | [*1] total header size
 *    20       1  level (0x02 fixed)               |      (X+26+(1))
 *    21       2  file crc                         |
 *    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 header_size, extend_size;
    int padding;

    hdr->header_size = header_size = get_word();

    if (fread(data + I_NAME_LENGTH, 26 - I_NAME_LENGTH, 1, fp) == 0) {
        error("Invalid header (LHarc file ?)");
        return FALSE;   /* finish */
    }

    get_bytes(hdr->method, 5, sizeof(hdr->method));
    hdr->packed_size = get_longword();
    hdr->original_size = 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();

    extend_size = get_extended_header(fp, hdr, extend_size);
    if (extend_size == -1)
        return FALSE;

    padding = header_size - 26 - extend_size;
    while (padding--)           /* padding should be 0 or 1 */
        fgetc(fp);

    /* FIXME: no collate the header CRC */

    return TRUE;
}

boolean
get_header(fp, hdr)
    FILE *fp;
    LzHeader *hdr;
{
    char data[LZHEADER_STRAGE];

    int archive_kanji_code = CODE_SJIS;
    int system_kanji_code = default_system_kanji_code;
    char *archive_delim = "\377\\"; /* `\' is for level 0 header and
                                       broken archive. */
    char *system_delim = "//";
    int filename_case = NONE;

    memset(hdr, 0, sizeof(LzHeader));

    setup_get(data);

    switch (fread(data, 1, I_NAME_LENGTH, fp)) {
    case I_NAME_LENGTH:
        break;
    case 0:
        return FALSE;           /* end of file */
    case 1:
        if (data[0] == '\0')    /* end mark */
            return FALSE;
        /* fall through */
    default:
        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;
    default:
        error("Unknown level header (level %d)", data[I_HEADER_LEVEL]);
        return FALSE;
    }

    /* filename conversion */
    switch (hdr->extend_type) {
    case EXTEND_MSDOS:
        filename_case = noconvertcase ? NONE : TO_LOWER;
        break;
    case EXTEND_HUMAN:
    case EXTEND_OS68K:
    case EXTEND_XOSK:
    case EXTEND_UNIX:
        filename_case = NONE;
        break;

    case EXTEND_MACOS:
        archive_delim = "\377/:\\";
                          /* `\' is for level 0 header and broken archive. */
        system_delim = "/://";
        filename_case = NONE;
        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. */
        break;
    }

    if (optional_archive_kanji_code)
        archive_kanji_code = optional_archive_kanji_code;
    if (optional_system_kanji_code)
        system_kanji_code = optional_system_kanji_code;
    if (optional_archive_delim)
        archive_delim = optional_archive_delim;
    if (optional_system_delim)
        system_delim = optional_system_delim;
    if (optional_filename_case)
        filename_case = optional_filename_case;

    /* 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;
}

/* ------------------------------------------------------------------------ */
void
init_header(name, v_stat, hdr)
	char           *name;
	struct stat    *v_stat;
	LzHeader       *hdr;
{
	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->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 */
#ifdef NOT_COMPATIBLE_MODE
	/* Please need your modification in this space. */
#else
	hdr->unix_mode = v_stat->st_mode;
#endif

	hdr->unix_uid = v_stat->st_uid;
	hdr->unix_gid = v_stat->st_gid;

#if INCLUDE_OWNER_NAME_IN_HEADER
#if HAVE_GETPWUID
    {
        struct passwd *ent = getpwuid(hdr->unix_uid);

        if (ent) {
            strncpy(hdr->user, ent->pw_name, sizeof(hdr->user));
            if (hdr->user[sizeof(hdr->user)-1])
                hdr->user[sizeof(hdr->user)-1] = 0;
        }
    }
#endif
#if HAVE_GETGRGID
    {
        struct group *ent = getgrgid(hdr->unix_gid);

        if (ent) {
            strncpy(hdr->group, ent->gr_name, sizeof(hdr->group));
            if (hdr->group[sizeof(hdr->group)-1])
                hdr->group[sizeof(hdr->group)-1] = 0;
        }
    }
#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);
	}
#endif
}

/* ------------------------------------------------------------------------ */
/* Write unix extended header or generic header. */

static int
write_header_level0(data, hdr, lzname)
    LzHeader *hdr;
    char *data, *lzname;
{
    int limit;
    int name_length;
    int header_size;

    setup_put(data);
    memset(data, 0, LZHEADER_STRAGE);

    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 */

    /* level 0 header: write pathname (contain the directory part) */
    name_length = strlen(lzname);
    if (generic_format)
        limit = 255 - I_GENERIC_HEADER_BOTTOM + 2;
    else
        limit = 255 - I_UNIX_EXTEND_BOTTOM + 2;

    if (name_length > limit) {
        warning("the length of pathname \"%s\" is too long.", lzname);
        name_length = limit;
    }
    put_byte(name_length);
    put_bytes(lzname, name_length);
    put_word(hdr->crc);

    if (generic_format) {
        header_size = I_GENERIC_HEADER_BOTTOM + 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_UNIX_EXTEND_BOTTOM + 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 int
write_header_level1(data, hdr, lzname)
    LzHeader *hdr;
    char *data, *lzname;
{
    int name_length, dir_length, limit;
    char *basename, *dirname;
    int header_size;
    char *extend_header_top;
    int extend_header_size;

    basename = strrchr(lzname, LHA_PATHSEP);
    if (basename) {
        basename++;
        name_length = strlen(basename);
        dirname = lzname;
        dir_length = basename - dirname;
    }
    else {
        basename = lzname;
        name_length = strlen(basename);
        dirname = "";
        dir_length = 0;
    }

    setup_put(data);
    memset(data, 0, LZHEADER_STRAGE);

    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 - 27 + 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) {
        /* 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);
        }
    }       /* if generic .. */

    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 int
write_header_level2(data, hdr, lzname)
    LzHeader *hdr;
    char *data, *lzname;
{
    int name_length, dir_length;
    char *basename, *dirname;
    int header_size;
    char *extend_header_top;
    char *headercrc_ptr;
    unsigned short  hcrc;

    basename = strrchr(lzname, LHA_PATHSEP);
    if (basename) {
        basename++;
        name_length = strlen(basename);
        dirname = lzname;
        dir_length = basename - dirname;
    }
    else {
        basename = lzname;
        name_length = strlen(basename);
        dirname = "";
        dir_length = 0;
    }

    setup_put(data);
    memset(data, 0, LZHEADER_STRAGE);

    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) {
        /* 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);
        }
    }       /* if generic .. */

    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 hader size */
    setup_put(data + I_HEADER_SIZE);
    put_word(header_size);

    /* put hader CRC in extended header */
    hcrc = calc_header_crc(data, (unsigned int) header_size);
    setup_put(headercrc_ptr);
    put_word(hcrc);

    return header_size;
}

void
write_header(fp, hdr)
    FILE           *fp;
    LzHeader       *hdr;
{
    int header_size;
    char data[LZHEADER_STRAGE];

    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 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;

    if (generic_format)
        filename_case = TO_UPPER;

    if (hdr->header_level == HEADER_LEVEL0) {
        archive_delim = "\\";
    }

    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);

    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:
        fatal_error("Unknown level header (level %d)", hdr->header_level);
    }

    if (fwrite(data, header_size, 1, fp) == 0)
        fatal_error("Cannot write to temporary file");
}

#if MULTIBYTE_FILENAME

#if defined(__APPLE__)

#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFStringEncodingExt.h>

/* this is not need for Mac OS X v 10.2 later */
enum {
  kCFStringEncodingAllowLossyConversion = 1,
  kCFStringEncodingBasicDirectionLeftToRight = (1 << 1),
  kCFStringEncodingBasicDirectionRightToLeft = (1 << 2),
  kCFStringEncodingSubstituteCombinings = (1 << 3),
  kCFStringEncodingComposeCombinings = (1 << 4),
  kCFStringEncodingIgnoreCombinings = (1 << 5),
  kCFStringEncodingUseCanonical = (1 << 6),
  kCFStringEncodingUseHFSPlusCanonical = (1 << 7),
  kCFStringEncodingPrependBOM = (1 << 8),
  kCFStringEncodingDisableCorporateArea = (1 << 9),
  kCFStringEncodingASCIICompatibleConversion = (1 << 10),
};

static int
ConvertEncodingToUTF8(const char* inCStr,
                      char* outUTF8Buffer,
                      int outUTF8BufferLength,
                      unsigned long scriptEncoding,
                      unsigned long flags)
{
    unsigned long unicodeChars;
    unsigned long srcCharsUsed;
    unsigned long usedByteLen = 0;
    UniChar uniStr[512];
    unsigned long cfResult;

    cfResult = CFStringEncodingBytesToUnicode(scriptEncoding,
                                              flags,
                                              (char *)inCStr,
                                              strlen(inCStr),
                                              &srcCharsUsed,
                                              uniStr,
                                              512,
                                              &unicodeChars);
    if (cfResult == 0) {
        cfResult = CFStringEncodingUnicodeToBytes(kCFStringEncodingUTF8,
                                                  flags,
                                                  uniStr,
                                                  unicodeChars,
                                                  &srcCharsUsed,
                                                  (char*)outUTF8Buffer,
                                                  outUTF8BufferLength - 1,
                                                  &usedByteLen);
        outUTF8Buffer[usedByteLen] = '\0';
    }

    return cfResult;
}

static int
ConvertUTF8ToEncoding(const char* inUTF8Buf,
                      int inUTF8BufLength,
                      char* outCStrBuffer,
                      int outCStrBufferLength,
                      unsigned long scriptEncoding,
                      unsigned long flags)
{
    unsigned long unicodeChars;
    unsigned long srcCharsUsed;
    unsigned long usedByteLen = 0;
    UniChar uniStr[256];
    unsigned long cfResult;

    cfResult = CFStringEncodingBytesToUnicode(kCFStringEncodingUTF8,
                                              flags,
                                              (char*)inUTF8Buf,
                                              inUTF8BufLength,
                                              &srcCharsUsed,
                                              uniStr,
                                              255,
                                              &unicodeChars);
    if (cfResult == 0) {
        cfResult = CFStringEncodingUnicodeToBytes(scriptEncoding,
                                                  flags,
                                                  uniStr,
                                                  unicodeChars,
                                                  &srcCharsUsed,
                                                  (char*)outCStrBuffer,
                                                  outCStrBufferLength - 1,
                                                  &usedByteLen);
        outCStrBuffer[usedByteLen] = '\0';
    }

    return cfResult;
}

#elif HAVE_ICONV
#include <iconv.h>

static int
ConvertEncodingByIconv(const char *src, char *dst, int dstsize,
                       const char *srcEnc, const char *dstEnc)
{
    iconv_t ic;
    static char szTmpBuf[2048];
    char *src_p;
    char *dst_p;
    size_t sLen;
    size_t iLen;

    dst_p = &szTmpBuf[0];
    iLen = (size_t)sizeof(szTmpBuf)-1;
    src_p = (char *)src;
    sLen = (size_t)strlen(src);
    memset(szTmpBuf, 0, sizeof(szTmpBuf));
    memset(dst, 0, dstsize);

    ic = iconv_open(dstEnc, srcEnc);
    if (ic == (iconv_t)-1) {
        error("iconv_open() failure: %s", strerror(errno));
        return -1;
    }

    if (iconv(ic, &src_p, &sLen, &dst_p, &iLen) == (size_t)-1) {
        error("iconv() failure: %s", strerror(errno));
        iconv_close(ic);
        return -1;
    }

    strncpy(dst, szTmpBuf, dstsize);

    iconv_close(ic);

    return 0;
}
#endif /* defined(__APPLE__) */

char *
sjis_to_utf8(char *dst, const char *src, size_t dstsize)
{
#if defined(__APPLE__)
  dst[0] = '\0';
  if (ConvertEncodingToUTF8(src, dst, dstsize,
                            kCFStringEncodingDOSJapanese,
                            kCFStringEncodingUseHFSPlusCanonical) == 0)
      return dst;
#elif HAVE_ICONV
  if (ConvertEncodingByIconv(src, dst, dstsize, "SJIS", "UTF-8") != -1)
      return dst;
#else
  error("not support utf-8 conversion");
#endif

  if (dstsize < 1) return dst;
  dst[dstsize-1] = 0;
  return strncpy(dst, src, dstsize-1);
}

char *
utf8_to_sjis(char *dst, const char *src, size_t dstsize)
{
#if defined(__APPLE__)
  int srclen;

  dst[0] = '\0';
  srclen = strlen(src);
  if (ConvertUTF8ToEncoding(src, srclen, dst, dstsize,
                            kCFStringEncodingDOSJapanese,
                            kCFStringEncodingUseHFSPlusCanonical) == 0)
      return dst;
#elif HAVE_ICONV
  if (ConvertEncodingByIconv(src, dst, dstsize, "UTF-8", "SJIS") != -1)
      return dst;
#else
  error("not support utf-8 conversion");
#endif

  if (dstsize < 1) return dst;
  dst[dstsize-1] = 0;
  return strncpy(dst, src, dstsize-1);
}

/*
 * SJIS <-> EUC 変換関数
 * 「日本語情報処理」	ソフトバンク(株)
 *	より抜粋(by Koji Arai)
 */
void
euc2sjis(int *p1, int *p2)
{
    unsigned char c1 = *p1 & 0x7f;
    unsigned char c2 = *p2 & 0x7f;
    int rowoff = c1 < 0x5f ? 0x70 : 0xb0;
    int celoff = c1 % 2 ? (c2 > 0x5f ? 0x20 : 0x1f) : 0x7e;
    *p1 = ((c1 + 1) >> 1) + rowoff;
    *p2 += celoff - 0x80;
}

void
sjis2euc(int *p1, int *p2)
{
    unsigned char c1 = *p1;
    unsigned char c2 = *p2;
    int adjust = c2 < 0x9f;
    int rowoff = c1 < 0xa0 ? 0x70 : 0xb0;
    int celoff = adjust ? (c2 > 0x7f ? 0x20 : 0x1f) : 0x7e;
    *p1 = ((c1 - rowoff) << 1) - adjust;
    *p2 -= celoff;

    *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: */
