/* 
 * 勝田さんの日本語slrn用の同名sourceを
 * 菊谷がmutt用に修正しています。
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include "khenkan.h"
#include "mutt.h"

/* 他のモジュールで日本語コードを変換する時に一時的に使う領域 */
/* このモジュールでは使ってはいけない */
unsigned char jtmp_str[JTMP_LEN];

/* X0201 / X0208 conversion tables */

/* X0201 kana conversion table */
/* 90-9F A0-DF */
static unsigned int cv[]= {
0x21,0x21,0x21,0x23,0x21,0x56,0x21,0x57,
0x21,0x22,0x21,0x26,0x25,0x72,0x25,0x21,
0x25,0x23,0x25,0x25,0x25,0x27,0x25,0x29,
0x25,0x63,0x25,0x65,0x25,0x67,0x25,0x43,
0x21,0x3c,0x25,0x22,0x25,0x24,0x25,0x26,
0x25,0x28,0x25,0x2a,0x25,0x2b,0x25,0x2d,
0x25,0x2f,0x25,0x31,0x25,0x33,0x25,0x35,
0x25,0x37,0x25,0x39,0x25,0x3b,0x25,0x3d,
0x25,0x3f,0x25,0x41,0x25,0x44,0x25,0x46,
0x25,0x48,0x25,0x4a,0x25,0x4b,0x25,0x4c,
0x25,0x4d,0x25,0x4e,0x25,0x4f,0x25,0x52,
0x25,0x55,0x25,0x58,0x25,0x5b,0x25,0x5e,
0x25,0x5f,0x25,0x60,0x25,0x61,0x25,0x62,
0x25,0x64,0x25,0x66,0x25,0x68,0x25,0x69,
0x25,0x6a,0x25,0x6b,0x25,0x6c,0x25,0x6d,
0x25,0x6f,0x25,0x73,0x21,0x2b,0x21,0x2c};

static int Zenkaku1; /* 変換した全角文字の上位 1 バイト */
static int Zenkaku2; /* 変換した全角文字の下位 1 バイト */

/* 半角カタカナ(1 or 2 文字) -> 全角カタカナ(1 文字)
 *  s   : 入力する文字列へのポインタ(EUC の場合は SS2 から始まる)
 *  return: 全角に変換した半角カタカナの文字数(EUC の場合は SS2 も含む)
 *    0: 半角カタカナではなかった
 *    1: 入力を一文字だけ全角カタカナに変換した(EUC の場合は 2)
 *    2: 濁点、半濁点も含めて、入力を二文字全角に変換した(EUC の場合は 4)
 */
static int mutt_han2zen(unsigned char *s)
{
  int ch, d1, d2, result, euc = 0;

  if ((*s | 0x80) == SS2) {
    s++;
    euc = 1;
  }
  ch = ((*s++ & 0x7f) - 0x20) << 1;
  if (ch < 0 || ch >= 0x80) {
    return (0);
  }
  d1 = cv[ch];
  d2 = cv[ch + 1];
  result = 1;

  if (d1 == 0x25
      && ((d2 >= 0x2b && d2 <= 0x48) || (d2 >= 0x4f && d2 <= 0x5b))) {
    ch = *s++;
    if (euc) {
      if ((ch | 0x80) == SS2) {
	ch = *s++;
      } else {
	ch = 0;
      }
    }
    ch &= 0x7f;
    if (ch == 0x5e) { /* 濁点 */
      d2++;
      result = 2;
    } else if (ch == 0x5f && d2 >= 0x4f) { /* 半濁点 */
      d2 += 2;
      result = 2;
    }
  }
  Zenkaku1 = d1;
  Zenkaku2 = d2;
  return (result << euc);
}

/*
 * EUC -> JIS  
 *     入力がJISだったらそのまま通すこと(なってる？)
 *     漢字の途中で切れてもそのまま(ASCII に戻さない)
 *     半角カタカナは全角に直す。
 *  return 0: success
 *         1: 途中で切れた
 */
#define TO_ASCII(p) (*(p)++ = ESC, *(p)++ = '(', *(p)++ = 'B')
#define TO_JIS208(p) (*(p)++ = ESC, *(p)++ = '$', *(p)++ = 'B')
#define TO_JIS201(p) (*(p)++ = ESC, *(p)++ = '(', *(p)++ = 'I')

int euc2jis(unsigned char *d, unsigned char *s, int dlen)
{
  int k = 0, ch;

  while(dlen > 0) { /* ASCII character or JIS encoding */
    ch = *s++;
    if (ch & 0x80) {
      if (k == 0) {
	dlen -= 3;
	if (dlen < 0) break;
	*d++ = ESC;
	*d++ = '$';
	*d++ = 'B';
	k = 1;
      }
      if (ch != SS2) {
	dlen--;
	*d++ = ch & 0x7f;
	ch = *s++;
      } else {  /* 半角カタカナ。全角に変換 */
	s--;
	s += mutt_han2zen(s);
	dlen--;
	*d++ = Zenkaku1;
	ch = Zenkaku2;
      }
      if (dlen <= 0) break;
    } else if (k == 1) {
      dlen -= 3;
      if (dlen < 0) break;
      *d++ = ESC;
      *d++ = '(';
      *d++ = 'B';
      k = 0;
    }
    if (ch == '\0') break;
    dlen--;
    *d++ = ch & 0x7f;
  }
  *d = '\0';
  if (dlen < 0) {
    return 1;
  } else {
    return 0;
  }
}

/*
 * JIS(, EUC) -> EUC
 *  d: 変換後の文字列
 *  s: 変換前の文字列
 *  dlen: 文字列の長さ(最後の '\0' は含まない)
 *
 *  return
 *  0: ASCII
 *  1: JIS
 *  2: EUC
 *   以下は変換結果不定
 *  3: SJIS かも知れない
 *  4: 7bit で 2 バイト文字の途中で切れた、または JIS の終了シーケンスが無い
 *  5: 8bit で SJIS でも EUC でもない
 */
typedef enum {_ASCII, JISX0208, JISX0201, OTHER} Kanji_Type;

int jis2euc(unsigned char *d, unsigned char *s, int dlen)
{
  unsigned char c1, c2 = 0, *seq;
  Kanji_Type kanji_mode = _ASCII;
  int ctrl = 0, hibit = 0;

  while((c1 = *s++) && dlen > 0) {
    if (c1 & 0x80) { /* 8bit code ??? */ /* まさかEUC？ */
      /* JIS-X0208 へのシーケンスが存在すれば 8bit 文字は SJIS
	 と見なす */
      seq = s;
      while(*seq++ != '\0' && ctrl == 0) {
	if (*seq == ESC && *(seq + 1) == '('
	    && (*(seq + 2) == '@' || *(seq + 2) == 'B')) ctrl++;
      }
      if (ctrl == 0) {
	hibit++;
	c2 = *s;
	if (c1 == 0x92 && IS_EUC1(c2) && IS_EUC2(*(s+1))) {
	  /* Mule の内部コード */
	  c1 = c2;
	  s++;
	  c2 = *s;
	}
	if (IS_EUC1(c1) && IS_EUC2(c2)) { /* EUC らしい */
	  kanji_mode = _ASCII;
	  dlen--;
	  *d++ = c1;
	  c1 = c2;
	  s++;
	} else if (c1 == SS2 && IS_SJISH(c2)) { /* EUC 半角カタカナ */
	  s--;
	  s += mutt_han2zen(s);
	  dlen -= 2;
	  *d++ = Zenkaku1 | 0x80;
	  *d++ = Zenkaku2 | 0x80;
	  continue;
	} else if (IS_SJISH(c1) || (IS_SJIS1(c1) && IS_SJIS2(c2))) {
	  /* なんてこった、SJISらしい */
	  *d = 0; /* 一応 */
	  return (3);
	} else { /* 日本語ではない。取りあえず残りの文字を複写する */
	  strcpy((char *)d, (char *)s);
	  return (5);
	}
      } else {  /* SJIS とみなして変換する */
	unsigned char str[3];

	str[0] = c1;
	if (*s == '\0' || *s == ESC) {
	  str[1] = '\0';
	} else {
	  str[1] = *s++;
	  str[2] = 0;
	}
	if (sjis2euc(d, str, dlen) == 0) {
	  while(*d != '\0') d++;
	}
	continue;
      }
    } else {
      switch(c1) {
      case ESC:
	ctrl++;
	c1 = *s++;
	if (c1 == '$') {
	  c1 = *s++;
	  if (c1 == '(') c1 = *s++;
	  if (c1 == '@' || c1 == 'B') {
	    kanji_mode = JISX0208;
	  } else { /* その他の文字セット。'?'に置き換える */
	    kanji_mode = OTHER;
	  }
	} else if (c1 == '(') {
	  c1 = *s++;
	  if (c1 == 'B' || c1 == 'H' || c1 == 'J' ) {
	    kanji_mode = _ASCII;
	  } else if (c1 == 'I') { /* X0201 */
	    kanji_mode = JISX0201;
	  } else { /* その他の文字セット。'?'に置き換える */
	    kanji_mode = OTHER;
	  }
	}
	continue;
	/* not break */
      case SO:
	ctrl++;
	seq = s;
	while((c1 = *seq++) != '\0' && c1 != SI);
	if (c1 == '\0') { /* SI がなければシングルシフトと見なす */
	  s--;
	  s += mutt_han2zen(s);
	  dlen -= 2;
	  *d++ = Zenkaku1 | 0x80;
	  *d++ = Zenkaku2 | 0x80;
	} else {
	  kanji_mode = JISX0201;
	}
	continue;
	/* not break */
      case SI:
	ctrl++;
	kanji_mode = _ASCII;
	continue;
	/* not break */
      }
    }
    switch(kanji_mode) {
    case OTHER:
      c1 = '?';
      /*FALLTHROUGH*/
    case _ASCII: /* 非漢字または EUC */
      *d++ = c1;
      dlen--;
      continue;
    case JISX0201:
      s--;
      c1 = mutt_han2zen(s); /* X0208に変換(半角カナ→全角カナ) */
      if (c1 == 0) {  /* 半角カタカナでない。無視する。 */
	s++;
	continue;
      } else {
	s += c1;
      }
      c1 = Zenkaku1;
      c2 = Zenkaku2;
      break;
    default:
      c2 = *s++;
      if (c2 == 0x01) { /* 最近の Mozilla 4.04 [en] (Win95; I) のバグと */
	c1++;           /* おぼしき文字化けに対する補正 */
	c2 += 0x21;
      }
    }
    dlen -= 2;
    if (dlen < 0 || c2 == 0 || c2 == ESC) break;
    *d++ = c1 | 0x80;
    *d++ = c2 | 0x80;
  }
  *d = 0;
  if ((kanji_mode != _ASCII && c2 == 0) || kanji_mode == JISX0208) {
    return (4);
  } else if (ctrl) {
    return (1);
  } else if (hibit) {
    return (2);
  }
  return (0);
}

/*
 * SJIS -> EUC 
 * 世の中にはSJISで投げてくる人もいるのでしかたがない
 *  return 0: success
 *         1: 途中で切れた
 *         2: SJIS ではない (未着手)
 */
int sjis2euc(unsigned char *d, unsigned char *s, int dlen)
{
  unsigned char upper,lower;
  int jisx0201_mode = 0;

  while((upper = *s++) != 0 && dlen > 0) {
    if ((upper & 0x80) == 0) { /* ASCII */
      /* なぜか SJIS の中に JISX0201 へのシーケンスが存在することがある */
      if (upper != ESC) {
	*d++ = upper;
	dlen--;
	continue;
      } else if (*s == '(') {
	s++;
	if (*s == 'I') {
	  jisx0201_mode = 1;
	} else {
	  jisx0201_mode = 0;
	}
	upper = *(s + 1);
	s += 2;
      }
    }
    if ((upper >= 0xa0 && upper < 0xe0) || jisx0201_mode) {
      s--;  /* いわゆる半角カナ */
      s += mutt_han2zen(s);
      upper = Zenkaku1 | 0x80;
      lower = Zenkaku2 | 0x80;
    } else { /* 漢字 */
      lower = *s++;
      if (!IS_SJIS2(lower) || upper >= 0xf0) { /* 途中で切れた！ */
	if (lower == '\0') { /* たまに行の折り返し不正などで 2byte 文字の */
	  break;             /* 間の byte で切れている記事がある。*/
	} else {
	  return (1);
	}
      }
      if (upper >= 0xe0) {
	upper -= 0x70;
      } else {
	upper -= 0x30;
      }
      upper <<= 1;
      if (lower >= 0x9f) {
	lower += 2;
      } else {
	upper--;
	if (lower & 0x80) {
	  lower += 0x60;
	} else {
	  lower += 0x61;
	}
      }
    }
    dlen -= 2;
    if (dlen < 0) break;
    *d++ = upper;
    *d++ = lower;
  }
  *d = '\0';

  return 0;
}

/*
 * JIS -> display code
 */
int jis2disp(unsigned char *d, unsigned char *s, int dlen)
{
  int ret;
    /* 常に EUC に変換する */
    /* SJISくさかったらSJIS->EUCする */
    if ((ret = jis2euc(d, s, dlen)) == 3) return sjis2euc(d, s, dlen);
    return ret;
}

/*
 * EUC -> SJIS 
 * エラーチェックはしていない
 *  return 0: success
 */
int euc2sjis(unsigned char *d, unsigned char *s, int dlen)
{
  unsigned char ch, ch2;

  while((ch = *s++) != 0 && dlen > 0) {
    if ((ch & 0x80) == 0) { /* ASCII */
      *d++ = ch;
      dlen--;
      continue;
    }
    if (ch == SS2) {
      ch2 = mutt_han2zen(s - 1);
      if (ch2 != 0) {
	s += ch2 - 1;
	ch = Zenkaku1 | 0x80;
	ch2 = Zenkaku2 | 0x80;
      }
    } else {
      ch2 = *s++;
    }
    if (ch & 1) {
      ch2 -= 0x60;
      if ((ch2 & 0x80) == 0) {
	ch2--;
      }
    } else {
      ch2 -= 2;
    }
    ch++;
    ch >>= 1;
    ch += 0x30;
    if (ch > 0x9f) {
      ch += 0x40;
    }
    dlen -= 2;
    *d++ = ch;
    *d++ = ch2;
  }
  *d = '\0';
  return (0);
}

/*
 * JIS -> internal code(EUC)
 * return (入力コードが)
 * 0: ASCII
 * 1: JIS
 * 2: EUC (他の 8bit 系コードかも知れない)
 * 3: SJIS
 * 4: SJIS と思われるがそうでないかも知れない
 * 5: 8bit で SJIS でも EUC でもない
 */
int jis2int(unsigned char *d, unsigned char *s, int dlen)
{
  unsigned char ch, *p = s;
  int hibit = 0;

  /* 常に EUC に変換する */
  switch (jis2euc(d, s, dlen)) {
  case 0: /* ASCII */
    return (0);
  case 1: /* JIS */
  case 4: /* JIS の途中で切れた。*/
    return (1);
  case 2: /* EUC */
    return (2);
  case 3: /* SJIS? */
    /* SJISくさかったらSJIS->EUCする */
    if (sjis2euc(d, s, dlen) != 0) return (5);
    /* 8bit 文字に比べて ASCII が非常に多ければ日本語でないとみなす */
    /* 8bit 文字が 1 つおきに 3 つ連続していれば SJIS とみなす */
    /* 0x80 - 0x9f にあれば ISO-8859-X ではない(かといって SJIS であるとも
       言えないが SJIS でということにする) */
    /* もうちょっと正確に判定が出来るように、改良が必要!! */
    while((ch = *p++) != 0) {
      if (ch & 0x80) {
	hibit++;
	if (ch < 0xa0 || (ch != *p && ch != *(p + 1)
	    && (*(p + 1) & 0x80) && (*(p + 3) & 0x80))) return (3);
      }
    }
    if (hibit * 9 > strlen((char *)s)) return (3);
    return (4);
  }
  return (5);
}

/*
 * universal code covert routine
 */
int convcode(unsigned char *d, unsigned char *s, int dlen, int code)
{
  static unsigned char jstr[JTMP_LEN];
  int ret;

  ret = jis2euc(jstr, s, dlen);
  switch(code) {
  case JIS:
    if (ret <= 1 || ret >= 4) {
      strcpy((char *)d, (char *)s);
      return (0);
    } else if (ret == 3) {
      (void)sjis2euc(jstr, s, dlen);
    }
    return (euc2jis(d, jstr, dlen));
  case EUC:
    if (ret == 0 || ret == 2 || ret >= 4) {
      strcpy((char *)d, (char *)s);
      return (0);
    } else if (ret == 1) {
      strcpy((char *)d, (char *)jstr);
      return (0);
    } else {
      return (sjis2euc(d, s, dlen));
    }
  case SJIS:
    if (ret == 0 || ret >= 3) {
      strcpy((char *)d, (char *)s);
      return (0);
    } else {
      return (euc2sjis(d, jstr, dlen));
    }
  }
  return (0);
}

void mutt_mktemp (char *);
FILE *safe_fopen (const char *, const char *);
/* ファイルを code で指定されたコードに変換する */
int mutt_conv_file(char *file, int code)
{
  static unsigned char jstr[JTMP_LEN];
  static char line[1024], tmpfile[256 + 2 * _POSIX_PATH_MAX];
  FILE *origfp, *tmpfp;
  int ch;
  unsigned int len;

  origfp = fopen(file, "r");
  mutt_mktemp (tmpfile);
  tmpfp = safe_fopen (tmpfile, "w");
  if (origfp == NULL || tmpfp == NULL) {
    mutt_error("File Open Error.");
    return (-1);
  }
  while (EOF != (ch = getc(origfp))) {
    if (EOF == putc(ch, tmpfp)) {
      mutt_error("Write Error.");
      return (-1);
    }
  }
  fclose (tmpfp);
  fclose (origfp);
     
  origfp = fopen(file, "w");
  tmpfp = fopen(tmpfile, "r");
  while (fgets (line, sizeof(line), tmpfp) != NULL) {
    len = strlen (line);       
    if (len == 0) continue;
    len--;

    if (line [len] == '\n') line [len] = '\0';
    (void)convcode(jstr, (unsigned char *)line, JTMP_LEN, code);
    fputs((char *)jstr, origfp);
    putc('\n', origfp);
  }
  fclose (tmpfp);
  fclose (origfp);

  unlink(tmpfile);
  return (0);
}

/* 文字列を code で指定されたコードに変換する */
void mutt_conv_string(char *line, int code)
{
  static unsigned char jstr[JTMP_LEN];

  (void)convcode(jstr, (unsigned char *)line, JTMP_LEN, code);
  (void)strcpy(line, (char *)jstr);
}

#define match_2byte(c1, c2) ((((c1) > '\040' && (c1) < '\050') \
			      || ((c1) > '\057' && (c1) < '\165')) \
			     && (c2) > '\040' && (c2) < '\177')

#define match_kin(c1, c2) ((c1) == '$' \
			   && ((c2) == '@' || (c2) == 'B'))

#define match_kout(c1, c2) ((c1) == '(' \
			    && ((c2) == 'J' || (c2) == 'B'))

/* ESC の落ちた JIS encoding を EUC に変換する。変換した場合は */
/* 1 を返す。*/
int recover_jis(unsigned char *to, unsigned char *from, int max)
{
  static unsigned char jtmp[JTMP_LEN];
  unsigned char *orig_ptr = from, *buf = jtmp, c1, c2;
  int len, eflag = 0;

  while(*from != '\0' && from - orig_ptr < JTMP_LEN) {
    if (match_kin(*from, *(from + 1))){
      if (from > orig_ptr && *(from - 1) == '\033') {
	eflag = 1;
      }
      len = 2;
      while(*(from + len) != '\0' && from - orig_ptr < max - len) {
	c1 = *(from + len);
	c2 = *(from + len + 1);
	if (match_2byte(c1, c2)) { /* 2byte charcter */
	  len += 2;
	} else if (c1 == '\040') {  /* space */
	  len++;
	} else if (c1 == '\033') {
	  if (match_kin(c2, *(from + len + 2)) && eflag == 0) {
	    break;  /* real kin sequence */
	  }
	  len++; /* only a escape character */
	} else {  /* not a mutibyte character sequence */
	  if (match_kout(c1, c2)  /* kout sequence */
	      && (eflag == 0 || *(from + len - 1) == '\033')) {
	    if (eflag == 0) {
	      *buf++ = '\033';
	    }
	    while(len > 0) {
	      *buf++ = *from++;
	      len--;
	    }
	    if (*(from - 1) != '\033') {
	      *buf++ = '\033';
	    }
	  }
	  break;
	}
      }
      eflag = 0;
    }
    *buf = *from;
    from++;
    buf++;
  }
  *buf = '\0';
  return (jis2int(to, jtmp, max));
}

/* koi8-r を ISO-8859-5 に変換する */
static unsigned char koi8_table[] = {
  32, 32, 32, 0xf1, 32, 32, 32, 32,
  32, 32, 32, 32, 32, 32, 32, 32,
  32, 32, 32, 0xa1, 32, 32, 32, 32,
  32, 32, 32, 32, 32, 32, 32, 32,
  0xee, 0xd0, 0xd1, 0xe6, 0xd4, 0xd5, 0xe4, 0xd3,
  0xe5, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
  0xdf, 0xef, 0xe0, 0xe1, 0xe2, 0xe3, 0xd6, 0xd2,
  0xec, 0xeb, 0xd7, 0xe8, 0xed, 0xe9, 0xe7, 0xea,
  0xce, 0xb0, 0xb1, 0xc6, 0xb4, 0xb5, 0xc4, 0xb3,
  0xc5, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe,
  0xbf, 0xcf, 0xc0, 0xc1, 0xc2, 0xc3, 0xb6, 0xb2,
  0xcc, 0xcb, 0xb7, 0xc8, 0xcd, 0xc9, 0xc7, 0xca
};

void mutt_convert_koi8_r2iso8859_5(unsigned char *s)
{
  while(*s != '\0') {
    if (*s >= 0xa0) {
      *s = koi8_table[*s - 0xa0];
    }
    s++;
  }
}

/* utf8 を JISX0201、JISX0208(ひらがな、かたかなのみ)または ISO-8859-1
   に変換する(それ以外の漢字の変換はできない) */
static unsigned char u3000[] = {  /* 3000 - 3015 */
  0x21, 0x21, 0x21, 0x22, 0x21, 0x23, 0x21, 0x37,
  0x00, 0x00, 0x21, 0x39, 0x21, 0x3A, 0x21, 0x3B,
  0x21, 0x52, 0x21, 0x53, 0x21, 0x54, 0x21, 0x55,
  0x21, 0x56, 0x21, 0x57, 0x21, 0x58, 0x21, 0x59,
  0x21, 0x5A, 0x21, 0x5B, 0x22, 0x29, 0x22, 0x2E,
  0x21, 0x4C, 0x21, 0x4D
};

static unsigned char u309b[] = { /* 309B - 309E */
  0x21, 0x2B, 0x21, 0x2C, 0x21, 0x35, 0x21, 0x36
};

static unsigned char u30fb[] = { /* 30FB - 30FE */
  0x21, 0x26, 0x21, 0x3C, 0x21, 0x33, 0x21, 0x34
};

static unsigned char uff01[] = { /* FF01 - FF0F */
  0x21, 0x2A, 0x00, 0x00, 0x21, 0x74, 0x21, 0x70,
  0x21, 0x73, 0x21, 0x75, 0x00, 0x00, 0x21, 0x4A,
  0x21, 0x4B, 0x21, 0x76, 0x21, 0x5C, 0x21, 0x24,
  0x21, 0x5D, 0x21, 0x25, 0x21, 0x3F
};

static unsigned char uff1a[] = { /* FF1A - FF20 */
  0x21, 0x27, 0x21, 0x28, 0x21, 0x63, 0x21, 0x61,
  0x21, 0x64, 0x21, 0x29, 0x21, 0x77
};

static unsigned char uff3b[] = { /* FF3B - FF40 */
  0x21, 0x4E, 0x21, 0x40, 0x21, 0x4F, 0x21, 0x30,
  0x21, 0x32, 0x21, 0x2E
};

static unsigned char uff5b[] = { /* FF5B - FF5D */
  0x21, 0x50, 0x21, 0x43, 0x21, 0x51
};


/* return:
 * 0: ASCII のみ
 * 1: ISO-8859-1 のみ
 * 2: 2 byte 文字があった。
 */
static int utf82euc(unsigned char *s, unsigned char *d, int dlen)
{
  int ch, ch2, ch3, hi, lo, ret = 0;
  unsigned char *tbl;

  while(*s != '\0' && dlen > 0) {
    ch = *s++;
    if (ch & 0x80) {
      ch2 = *s++;
      if (ch2 == '\0') {
	break;
      }
      if (ch & 0x20) {
	ch3 = *s++;
	if (ch3 == '\0') {
	  break;
	}
	hi = ((ch & 0x0f) << 4) | ((ch2 & 0x3c) >> 2);
	lo = ((ch2 & 0x03) << 6) | (ch3 & 0x3f);
      } else {
	hi = (ch & 0x1c) >> 2;
	lo = ((ch & 0x03) << 6) | (ch2 & 0x3f);
      }
      ret = 2;
    } else { /* ASCII */
      hi = 0;
      lo = ch;
    }
    tbl = NULL;
    switch (hi) {
    case 0x00:
      ch2 = 1;  /* 0:cannot convert 1:1 byte character 2:2 byte character */
      if (lo & 0x80) {
	if (ret == 2) {   /* cannot use ISO-8859-1 */
	  lo = '?';
	} else {
	  ret = 1;
	}
      }
      break;
    case 0x30:
      ch2 = 2;
      if (lo <= 0x15) {
	ch = lo;
	tbl = u3000;
      } else if (lo >= 0x41 && lo <= 0x93) {  /* hiragana */
	hi = 0x24;
	lo -= 0x20;
      } else if (lo >= 0x9b && lo <= 0x9e) {
	ch = lo - 0x9b;
	tbl = u309b;
      } else if (lo >= 0xa1 && lo <= 0xf6) { /* katakana */
	hi = 0x25;
      } else if (lo >= 0xfb && lo <= 0xfe) {
	ch = lo - 0xfb;
	tbl = u30fb;
      } else {
	ch2 = 0;
      }
      break;
    case 0xff:
      ch2 = 2;
      if (lo <= 0x0f) {
	ch = lo - 0x01;
	tbl = uff01;
      } else if (lo <= 0x19) {  /* full width digit */
	hi = 0x23;
	lo += 0x21;
      } else if (lo <= 0x20) {
	ch = lo - 0x1a;
	tbl = uff1a;
      } else if ((lo <= 0x3a) || (lo >= 0x41 && lo <= 0x5a)) {
	hi = 0x23;
	lo += 0x20;
      } else if (lo <= 0x40) {
	ch = lo - 0x3b;
	tbl = uff3b;
      } else if (lo <= 0x5d) {
	ch = lo - 0x5b;
	tbl = uff5b;
      } else if (lo >= 0x61 && lo <= 0x9f) {
	hi = 0x8e;    /* half width katakana */
	lo += 0x40;
      } else {
	ch2 = 0;
      }
      break;
    default:
      ch2 = 0;
    }
    if (ch2 == 1) {
      *d++ = lo;
      dlen--;
      continue;
    }
    if (tbl != NULL) {
      ch <<= 1;
      hi = tbl[ch];
      lo = tbl[ch + 1];
    }
    if (ch2 == 0 || (hi == 0 && lo == 0)) {
      hi = '?';
      lo = '?';
    } else {
      hi |= 0x80;
      lo |= 0x80;
    }
    if (dlen < 2) {
      break;
    }
    *d++ = (unsigned char)hi;
    *d++ = (unsigned char)lo;
    dlen -= 2;
  }
  *d = 0;
  return (ret);
}

void mutt_convert_utf82int(unsigned char *s)
{
  /* int ret;
  ret = */ (void)utf82euc(s, s, strlen((char *)s));
  (void)jis2euc(s, s, strlen((char *)s)); /* 念のため */
  /* return (ret); */
}

#if 1 /* def MUTT_JED_SLANG  Now it's in JED's SLANG */
/* EUC で、2byte 文字の位置を返す。
 * 0: ASCII
 * 1: 1 byte 目
 * 2: 2 byte 目
 */
int kanji_pos(unsigned char *beg, unsigned char *end)
{
  int ret = 0;
  while(*beg) {
    if (*beg & 0x80) {
      if (ret == 1) {
	ret = 2;
      } else {
	ret = 1;
      }
    } else {
      ret = 0;
    }
    if (beg == end) break;
    beg++;
  }
  return ret;
}
#endif

int jp_snprintf(char *str, int n, char *format, char *arg)
{
  char *p, min_pre[16], fld_pre[16], *d;
  int minimum, field, i, ladjust = 0;

  /* format should be "%[-]NNN.MMMs" */
  if (*format != '%') return snprintf(str, n, format, arg);
  p = format + 1;
  if (*p == 's' || (*p != '-' && *p != '.' && !isdigit(*p))) 
    return snprintf(str, n, format, arg);
  
  if (*p == '-') {
    ladjust = 1;
    ++p;
  }
  if (*p == '.') {
    min_pre[0] = '\0';
    ++p;
  }
  else { 
    for (d = min_pre; isdigit(*p); ++p)
      *d++ = *p;
    if (*p == '\0') return snprintf(str, n, format, arg);
    *d = '\0';
  }

  if (*p == 's') {
    fld_pre[0] = '\0';
  }
  else if (*p == '.') {
    for (++p, d = fld_pre; isdigit(*p); ++p)
      *d++ = *p;
    if (*p != 's') return snprintf(str, n, format, arg);
    *d = '\0';
  }

  minimum = atoi(min_pre);
  field = atoi(fld_pre);
  if (field == 0)
    field = 9999; /* big integer... */

  d = str;
  p = arg;
  i = 0;
  if (ladjust) {
    for (; i < field && *p != '\0'; ++i, p++) {
      if (i >= n) return n;
      *d++ = *p;
    }
    if (kanji_pos((unsigned char *)str, (unsigned char *)(d-1)) == 1) {
      *(d-1) = ' ';
    }
    if (i < minimum)
      for (; i < minimum; ++i) {
        if (i >= n) return n;
        *d++ = ' ';
      }
    *d = '\0';
    return i;
  }
  else {
    int len = strlen(arg);
    if (len < minimum) {
      for (; i < minimum-len; ++i) {
        if (i >= n) return n;
        *d++ = ' ';
      }
    }
    for (; i < field && *p != '\0'; ++i, p++) {
      if (i >= n) return n;
      *d++ = *p;
    }
    if (kanji_pos((unsigned char *)str, (unsigned char *)(d-1)) == 1) {
      *(d-1) = ' ';
    }
    *d = '\0';
    return i;
  }
}

/* ---------------------- MIME ----------------------
 * muttのmimeエンコーダは使いものにならない。
 * 以下はslrnの勝田さんのものを拝借。
 */
#define IS_RFC850_SPECIAL(c) \
 (((c) == '(') || ((c) == ')') || ((c) == '<') || ((c) == '>') || ((c) == '"'))

static char basis_64[] =
   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/* src must less than srcmax */
static unsigned char *encode_base64(unsigned char *dest,
				    unsigned char *src, unsigned char *srcmax)
{
  int c1, c2, c3, pad = 0;

  while (src < srcmax) {
    c1 = *src++;
    *dest++ = basis_64[c1>>2];
    if (src < srcmax) {
      c2 = *src++;
    } else {
      c2 = 0;
      pad = 1;
    }
    *dest++ = basis_64[((c1 & 0x3)<< 4) | ((c2 & 0xf0) >> 4)];
    if (src < srcmax) {
      c3 = *src++;
    } else {
      c3 = 0;
      pad++;
    }
    if (pad < 2) {
      *dest++ = basis_64[((c2 & 0xf) << 2) | ((c3 & 0xc0) >>6)];
    } else {
      *dest++ = '=';
    }
    if (pad == 0) {
      *dest++ = basis_64[c3 & 0x3f];
    } else {
      *dest++ = '=';
    }
  }
  return (dest);
}

/* 
 * 上のoutput64chunkとto64は佐藤@電総研さんのmimekit1.1のコードを
 * 参考にさせていただきましたが、下のエンコーダー本体は、スペースを
 * 考慮していないきわめていいかげんなものです。
 *
 * RFC2047 に厳密に対応するには、
 * 1. "あa" の場合、"あ" のみを encode すると decode した時 "あ a" となって
 *    しまう。-> "a" も含めてすべて encode するしかない。
 *
 * 2. "あ い" の場合、"あ" と "い" を別々に encode すると、decode した時
 *    "あい" となってしまう。-> space も含めて一緒に encode するか、"あ"
 *     か "い" のどちらかに space を含める。
 *
 * 3. encoded word の前後は space でなければならない。つまり、日本語と
 * くっついている半角文字もすべてひとつの encoded word にしなければな
 * らない。ただ、これでは encode した文字列の可読性が落ちるので、日本
 * 語だけ encode する実装も存在するが、RFC2047 に厳密であるとは言えない。
 *
 * 注: 2.で space も含めて一緒に encode すると単語の区切りがなくなって
 * しまうので、RFC に厳密であるとは言えない。しかし、どうせ長くなると 
 * folding してしまうし、分けて encode すると全体が非常に長くなってし
 * まうので、ここでは一緒に encode することにする。
 *
 */
/* アルゴリズム
 * 1. まず、日本語の始まる ESC sequence を探す。
   2. 空白まで元に戻る。
   3. 日本語の終わる ESC sequence を探す。
   4. 次が空白で、その次が日本語を含むならば、その日本語の終わる ESC sequence
      を探す。
   5. 4 を繰り返して、その次の文字列が日本語を含まない空白を探す。
   6. 2 の位置から現在地まで base64 エンコードする。

   さらに 75 文字以下に folding するのと、structured field に対応しなけ
   ればならない。
 */
/* only for ISO-2022-JP */
int Slrn_Mime_Strict_Rfc = 0; /* RFC2047 に厳密な encode を行う(その代わり
				 ASCII 文字も encode してしまう)。 */
#define IS_TO_KANJI(p) (*(p) == ESC && *((p) + 1) == '$' \
			&& (*((p) + 2) == 'B' || *((p) + 2) == '@'))
#define IS_TO_ASCII(p) (*(p) == ESC && *((p) + 1) == '(' \
			&& (*((p) + 2) == 'B' || *((p) + 2) == 'J'))
int encode_base64_header(unsigned char *d, unsigned char *dmax,
				unsigned char *s, char *charset,
				unsigned int c_len, int ignore_specials)
{
  char *p = (char *)s, ch, *beg, *end=NULL, *to_ascii=NULL, *to_kanji=NULL;
  int len = 0, count, beg_kanji, end_kanji=0;

  /* check if buffer 'd' has a enough length */
  if (dmax - d < strlen((char *)s) * 4 / 3) return (-1);

  while(*p != 0 && d < dmax) {
    beg = p;
    while (((ch = *p) != 0) && d < dmax && !IS_TO_KANJI(p)
	   && len < 76) {
      *d++ = ch;
      p++;
      len++;
      if (ch == ' ' || (ignore_specials == 0 && IS_RFC850_SPECIAL(ch))) {
	beg = p;   /* remember the beginning of the current word */
      }
    }
    if (ch == 0) break;
    if (d == dmax) return (-1);

    if (len > 75) { /* line break(in text) */
      p = beg;  /* search previous word's end */
      while(beg > (char *)s && *beg != ' ') beg--;
      while(beg > (char *)s && *beg == ' ') beg--;
      ++beg;
      d -= p - beg + 1;
      *d++ = '\n';
      *d++ = ' ';
      len = 0;
      continue;
    }

    /* found to_kanji sequence and the beginning of encoding */
    to_kanji = p;
    if (Slrn_Mime_Strict_Rfc) {
      d -= p - beg;
      len -= p - beg;
      p = beg;
    } else {
      beg = p;
    }

    /* search to_ascii sequence and the end of encoding */
    while((ch = *p) != 0) {
      if (IS_TO_ASCII(p)) {            /* skip to space or end of line */
	to_ascii = p;
	p += 3;
	if (!Slrn_Mime_Strict_Rfc) {
	  end = p;
	  break;
	}
	while((ch = *p) != 0 && ch != ' ' && ch != '\n'
	      && (ignore_specials || !IS_RFC850_SPECIAL(ch))) p++;
	end = p;
	if (ch == 0) break;
	SKIPWS(p);
	while((ch = *p) != 0 && ch != ' ' && ch != '\n' && !IS_TO_KANJI(p)
	      && (ignore_specials || !IS_RFC850_SPECIAL(ch))) {
	  p++;
	}
	if (!IS_TO_KANJI(p)) break;
      }
      p++;
    }

    /* encode between beg and end */
    p = beg;   /* p must less than end */
    beg_kanji = 0;  /* flag whether last string ended in kanji or ascii*/
    while (p < end && d < dmax) {
      unsigned char *new_d;

      if (d + c_len >= dmax) return (-1);

      if (len + c_len + 14 > 75) { /* 14 = to_kanji(4) + one char(4) */
	*d++ = '\n';		   /*	   + to_ascii(4) + "?="(2)   */
	*d++ = ' ';
	len = 0;
      } else if (!Slrn_Mime_Strict_Rfc
		 && p != (char *)s && *(p - 1) != ' '
		 && (ignore_specials || !IS_RFC850_SPECIAL(*(p - 1)))) {
	*d++ = ' ';
	len++;
      }
      strcpy((char *)d, charset);
      d += c_len;
      len += c_len + 2; /* 2 = "?=" */

      /* count length of encoded word */
      count = beg_kanji * 4;                /* 8 = one char(4) + to_ascii(4) */
      while(p < end && ((ch = *p) != 0) && len + count + 8 < 76) {
	if (IS_TO_KANJI(p)) {
	  if (Slrn_Mime_Strict_Rfc && len  + count > 70) {
	    len = 76;
	    break;
	  } else {   /* flag whether current string ended in kanji or ascii*/
	    end_kanji = 1;
	  }
	  p += 3;
	} else if (IS_TO_ASCII(p)) {
	  end_kanji = 0;
	  p += 3;
	} else if (*p > ' ' && end_kanji) { /* 2 byte character */
	  p += 2;
	} else { /* 1 byte character */
	  p++;
	}
	count = (20 - (beg + 60 - p) / 3) * 4;
      }

      if (len + count + 8 < 76) {
	p = end;
      }
      if (beg_kanji) {
	strncpy((char *)jtmp_str, to_kanji, 3);
	count = 3;
      } else {
	count = 0;
      }

      strncpy((char *)jtmp_str + count, beg, p - beg);
      count += p - beg;
      if (end_kanji) {
	strncpy((char *)jtmp_str + count, to_ascii, 3);
	count += 3;
      }
      /* add trailing space */
      if (!Slrn_Mime_Strict_Rfc && *p == ' ') {
	jtmp_str[count] = ' ';
	count++;
	p++;
      }

      new_d = encode_base64(d, jtmp_str, jtmp_str + count);
      len += new_d - d;
      d = new_d;

      beg_kanji = end_kanji;
      beg = p;

      if (d + 1 >= dmax) return (-1);
      d[0] = '?';
      d[1] = '=';
      d += 2;
      if (!Slrn_Mime_Strict_Rfc && *p != ' ') {
	*d++ = ' ';
	len++;
      }
    }
  }

  *d = 0;
  return (0);
}
