#include "fm.h"
#include "istream.h"
#include <signal.h>
#ifdef USE_SSL
#include <x509v3.h>
#endif

#define	uchar		unsigned char

#define STREAM_BUF_SIZE (PIPE_BUF)
#define SSL_BUF_SIZE	1536

#define MUST_BE_UPDATED(bs) ((bs)->stream.cur==(bs)->stream.next)

#define POP_CHAR(bs) ((bs)->iseos?'\0':(bs)->stream.buf[(bs)->stream.cur++])

static void basic_close(union input_handle);
static int basic_read(union input_handle, char *buf, int len);

static void file_close(union input_handle handle);
static int file_read(union input_handle handle, char *buf, int len);

static void delimited_close(union input_handle handle);
static int delimited_read(union input_handle handle, char *buf, int len);

static int str_read(union input_handle handle, char *buf, int len);

#ifdef USE_SSL
static void ssl_close(union input_handle handle);
static int ssl_read(union input_handle handle, char *buf, int len);
#endif

static int ens_read(union input_handle handle, char *buf, int len);
static void ens_close(union input_handle handle);

static int limited_read(union input_handle handle, char *buf, int len);
static void limited_close(union input_handle handle);

static int chunked_read(union input_handle handle, char *buf, int len);
static void chunked_close(union input_handle handle);

static int tee_read(union input_handle hanlde, char *buf, int len);
static void tee_close(union input_handle handle);

static void
do_update(InputStream base)
{
  int len;

  base->stream.cur = base->stream.next = 0;

  if ((len = base->read(base->handle, base->stream.buf, base->stream.size)) <= 0)
    base->iseos = TRUE;
  else
    base->stream.next += len;
}

static int
buffer_read(StreamBuffer sb, char *obuf, int count)
{
  int len;

  if ((len = sb->next - sb->cur) > 0) {
    if (len > count)
      len = count;

    bcopy((const void *)&sb->buf[sb->cur], obuf, len);
    sb->cur += len;
  }

  return len;
}

static void
init_buffer(InputStream base, char *buf, int bufsize)
{
  StreamBuffer sb;

  sb = &base->stream;
  sb->size = bufsize;
  sb->cur = 0;

  if (buf) {
    sb->buf = (uchar *)buf;
    sb->next = bufsize;
  }
  else {
    sb->buf = NewAtom_N(uchar, bufsize);
    sb->next = 0;
  }

  base->iseos = FALSE;
}

static void
init_base_stream(InputStream base, int bufsize)
{
  init_buffer(base, NULL, bufsize);
}

static void
init_str_stream(InputStream base, Str s)
{
  init_buffer(base, s->ptr, s->length);
}

InputStream
newInputStream(int des)
{
  InputStream stream;

  if (des < 0)
    return NULL;

  stream = New(struct input_stream);
  init_base_stream(stream, STREAM_BUF_SIZE);
  stream->type = IST_BASIC;
  stream->handle.fd = des;
  stream->read = basic_read;
  stream->close = basic_close;
  return stream;
}

InputStream
newFileStream(FILE *f, void (*closep)())
{
  InputStream stream;

  if (!f)
    return NULL;

  stream = New(struct input_stream);
  init_base_stream(stream, STREAM_BUF_SIZE);
  stream->type = IST_FILE;
  stream->handle.file = New(struct file_handle);
  stream->handle.file->f = f;

  if (closep)
    stream->handle.file->close = closep;
  else
    stream->handle.file->close = (void (*)())fclose;

  stream->read = file_read;
  stream->close = file_close;
  return stream;
}

InputStream
newDelimitedStream(InputStream is, Str buf, int (*delim_p)(const char *, void *), void *delim_arg)
{
  InputStream stream;

  if (!is)
    return NULL;

  stream = New(struct input_stream);
  init_base_stream(stream, STREAM_BUF_SIZE);
  stream->type = IST_DELIMITED;
  stream->handle.delimited = New(struct delimited_handle);
  stream->handle.delimited->is = is;
  stream->handle.delimited->buf = buf ? buf : Strnew_size(STREAM_BUF_SIZE);
  stream->handle.delimited->cur = 0;
  stream->handle.delimited->delim_p = delim_p;
  stream->handle.delimited->delim_arg = delim_arg;
  stream->read = delimited_read;
  stream->close = delimited_close;
  return stream;
}

InputStream
newStrStream(Str s)
{
  InputStream stream;

  if (!s)
    return NULL;

  stream = New(struct input_stream);
  init_str_stream(stream, s);
  stream->type = IST_STR;
  stream->handle.str = s;
  stream->read = str_read;
  stream->close = NULL;
  return stream;
}

#ifdef USE_SSL
InputStream
newSSLStream(SSL *ssl, int sock)
{
  InputStream stream;

  if (sock < 0)
    return NULL;

  stream = New(struct input_stream);
  init_base_stream(stream, SSL_BUF_SIZE);
  stream->type = IST_SSL;
  stream->handle.ssl = New(struct ssl_handle);
  stream->handle.ssl->ssl = ssl;
  stream->handle.ssl->sock = sock;
  stream->read = ssl_read;
  stream->close = ssl_close;
  return stream;
}
#endif

InputStream
newEncodedStream(InputStream is, char encoding)
{
  InputStream stream;

  if (is)
    switch (encoding) {
    case ENC_QUOTE:
    case ENC_BASE64:
    case ENC_UUENCODE:
      stream = New(struct input_stream);
      init_base_stream(stream, STREAM_BUF_SIZE);
      stream->type = IST_ENCODED;
      stream->handle.ens = New(struct ens_handle);
      stream->handle.ens->is = is;
      stream->handle.ens->pos = 0;
      stream->handle.ens->encoding = encoding;
      stream->handle.ens->s = NULL;
      stream->read = ens_read;
      stream->close = ens_close;
      return stream;
    default:
      break;
    }

  return is;
}

InputStream
newLimitedStream(InputStream orig, clen_t length)
{
  InputStream new;

  if (!orig || length < 0)
    return orig;

  new = New(struct input_stream);
  init_base_stream(new, STREAM_BUF_SIZE);
  new->type = IST_LIMITED;
  new->handle.limited = New(struct limited_handle);
  new->handle.limited->is = orig;
  new->handle.limited->rest = length;
  new->read = limited_read;
  new->close = limited_close;
  return new;
}

InputStream
newChunkedStream(InputStream orig)
{
  InputStream new;

  if (!orig)
    return orig;

  new = New(struct input_stream);
  init_base_stream(new, STREAM_BUF_SIZE);
  new->type = IST_CHUNKED;
  new->handle.chunked = New(struct chunked_handle);
  new->handle.chunked->is = orig;
  new->handle.chunked->rest = 0;
  new->handle.chunked->buf = Strnew();
  new->read = chunked_read;
  new->close = chunked_close;
  return new;
}

#if LANG == JA || defined(HAVE_MOE)
#ifndef CONV_BUF_MIN
#define CONV_BUF_MIN (STREAM_BUF_SIZE)
#endif
#ifndef CONV_BUF_MAX
#define CONV_BUF_MAX (CONV_BUF_MIN * 4)
#endif
#endif

static int
tee_read_is(struct tee_handle *th, char *buf, int len)
{
  InputStream is;
  int n;

  is = th->is;

  if (!(n = buffer_read(&is->stream, buf, len)) && !is->iseos) {
    int m;

    if ((m = is->read(is->handle, &buf[n], len - n)) > 0)
      n += m;
    else if (!m)
      is->iseos = TRUE;
  }

  if (n > 0) {
    if (th->save) {
      fwrite(buf, 1, n, th->save);
      fflush(th->save);
    }

    th->total += n;
  }

  return n;
}

#ifdef HAVE_MOE
static size_t
tee_moe_read(char *buf, size_t len, void *arg)
{
  return tee_read_is(arg, buf, len);
}
#endif

InputStream
newTeeStream(InputStream is, FILE *save
#if LANG == JA || defined(HAVE_MOE)
	     , int noconv, const char *cs, int len_min, int len_max
#endif
	     )
{
  InputStream stream;
  struct tee_handle *th;
#ifdef HAVE_MOE
  mb_setup_t mbsetup;
#endif

  if (is == NULL)
    return is;

  stream = New(struct input_stream);
  init_base_stream(stream, STREAM_BUF_SIZE);
  stream->type = IST_TEE;
  stream->read = tee_read;
  stream->close = tee_close;
  stream->handle.tee = th = New(struct tee_handle);
  th->is = is;
  th->save = save;
  th->total = 0;
#if LANG == JA || defined(HAVE_MOE)
  th->noconv = noconv;

  if (len_min <= 0)
    len_min = CONV_BUF_MIN;

  if (len_max < 0)
    len_max = CONV_BUF_MAX;

#ifdef HAVE_MOE
  mbsetup = conv_mb_setup_r;
  mbsetup.cs = NULL;
  memset(&th->cd, 0, sizeof(th->cd));
  mb_init_r(&th->cd, th, tee_moe_read, &mbsetup, "@", noconv ? "x-moe-internal" : cs);
  th->cd.size = len_min;
  th->cd.buf = NewAtom_N(char, th->cd.size);
  th->cd.b = th->cd.i = th->cd.e = 0;
  th->st = NULL;

  if (th->cd.flag & MB_FLAG_UNKNOWNCS) {
    if (conv_cs_detector) {
      int i;

      th->st = mb_alloc_cs_detector(&th->cd, len_min, len_max);
      th->st->flag |= MB_CS_DETECT_FLAG_MKUNBOUND;

      for (th->st->nstats = i = conv_cs_detector->nstats ; i > 0 ;) {
	--i;
	th->st->stat[i].ces = conv_cs_detector->stat[i].ces;
      }
    }
    else
      mb_ces_by_name("iso-8859-1", &th->cd);
  }
#else
  th->buf = th->from = NewAtom_N(char, len_min);
  th->size = th->len_min = len_min;
  th->len_max = len_max;
  th->from_left = 0;
  if (!cs) cs = "";
  JA_CES_stat_init(&th->st, *cs);

  if (*cs && *cs != CODE_UNKNOWN && getConvToEJRoutine(*cs, &th->conv, &th->cd))
    th->st.found = *cs;
#endif
#endif

  return stream;
}

void
ISclose(InputStream stream)
{
  MySignalHandler(*prevtrap) ();

  if (!stream || !stream->close)
    return;

  if (!stream->iseos) {
    char *buf;

    switch (stream->type) {
    case IST_LIMITED:
      if (kept_sock >= 0 && kept_sock == ISfileno(stream->handle.limited->is))
	goto skip;
    default:
      break;
    case IST_CHUNKED:
      if (kept_sock < 0 || kept_sock != ISfileno(stream->handle.chunked->is))
	break;
    skip:
      buf = NewAtom_N(char, PIPE_BUF);

      while (ISreadonce(stream, buf, PIPE_BUF))
	;
    }
  }

  prevtrap = signal(SIGINT, SIG_IGN);
  stream->close(stream->handle);
  signal(SIGINT, prevtrap);
}

int
ISgetc(InputStream stream)
{
  if (!stream)
    return '\0';

  if (!stream->iseos && MUST_BE_UPDATED(stream))
    do_update(stream);

  return POP_CHAR(stream);
}

int
ISundogetc(InputStream stream)
{
  StreamBuffer sb;

  if (!stream)
    return -1;

  sb = &stream->stream;

  if (sb->cur > 0) {
    sb->cur--;
    return 0;
  }

  return -1;
}

#define MARGIN_STR_SIZE 10
Str
StrISgets(InputStream base, Str s)
{
  StreamBuffer sb;
  uchar *p;
  int len;

  if (!base)
    return NULL;

  sb = &base->stream;

  while (!base->iseos) {
    if (MUST_BE_UPDATED(base))
      do_update(base);
    else {
      if ((p = memchr(&sb->buf[sb->cur], '\n', sb->next - sb->cur))) {
	len = p - &sb->buf[sb->cur] + 1;

	if (!s)
	  s = Strnew_size(len);

	Strcat_charp_n(s, (char *)&sb->buf[sb->cur], len);
	sb->cur += len;
	return s;
      }
      else {
	if (!s)
	  s = Strnew_size(sb->next - sb->cur + MARGIN_STR_SIZE);

	Strcat_charp_n(s, (char *)&sb->buf[sb->cur], sb->next - sb->cur);
	sb->cur = sb->next;
      }
    }
  }

  if (!s)
    return Strnew();

  return s;
}

Str
StrmyISgets(InputStream base, Str s)
{
  StreamBuffer sb;
  int i, len;

  if (!base)
    return NULL;

  sb = &base->stream;

  while (!base->iseos) {
    if (MUST_BE_UPDATED(base))
      do_update(base);
    else {
      if (s && Strlastchar(s) == '\r') {
	if (sb->buf[sb->cur] == '\n')
	  Strcat_char(s, (char )sb->buf[sb->cur++]);

	return s;
      }

      for (i = sb->cur;
	   i < sb->next && sb->buf[i] != '\n' && sb->buf[i] != '\r'; 
	   i++);
      if (i < sb->next) {
	len = i - sb->cur + 1;

	if (!s)
	  s = Strnew_size(len + MARGIN_STR_SIZE);

	Strcat_charp_n(s, (char *)&sb->buf[sb->cur], len);
	sb->cur = i + 1;

	if (sb->buf[i] == '\n')
	  return s;
      }
      else {
	if (!s)
	  s = Strnew_size(sb->next - sb->cur + MARGIN_STR_SIZE);

	Strcat_charp_n(s, (char *)&sb->buf[sb->cur], sb->next - sb->cur);
	sb->cur = sb->next;
      }
    }
  }

  if (!s)
    return Strnew();

  return s;
}

int
ISread(InputStream base, Str buf, int count)
{
  int rest, len;

  if (!base || base->iseos)
    return 0;

  len = buffer_read(&base->stream, buf->ptr, count);
  rest = count - len;

  if (MUST_BE_UPDATED(base)) {
    len = base->read(base->handle, &buf->ptr[len], rest);

    if (len <= 0) {
      base->iseos = TRUE;
      len = 0;
    }

    rest -= len;
  }

  Strtruncate(buf, count - rest);
  return buf->length > 0;
}

int
ISunread(InputStream base, Str buf)
{
  if (!base || base->type == IST_STR)
    return FALSE;

  NEW_OBJV1(&base->stream.size,
	    buf->length + (base->stream.next - base->stream.cur),
	    &base->stream.buf, 1, TRUE);

  if (base->stream.next - base->stream.cur > 0 &&
      base->stream.cur != buf->length)
    memmove(&base->stream.buf[buf->length], &base->stream.buf[base->stream.cur],
	    base->stream.next - base->stream.cur);

  memcpy(base->stream.buf, buf->ptr, buf->length);
  base->stream.next -= base->stream.cur - buf->length;
  base->stream.cur = 0;
  return TRUE;
}

int
ISreadonce(InputStream base, char *buf, int count)
{
  int len;

  if (base == NULL || base->iseos)
    return 0;

  if (!(len = buffer_read(&base->stream, buf, count)) &&
      !(len = base->read(base->handle, buf, count)))
    base->iseos = TRUE;

  return len;
}

int
ISfileno(InputStream stream)
{
  if (!stream)
    return -1;

  switch (IStype(stream)) {
  case IST_BASIC:
    return stream->handle.fd;
  case IST_FILE:
    return fileno(stream->handle.file->f);
  case IST_ENCODED:
    return ISfileno(stream->handle.ens->is);
  case IST_LIMITED:
    return ISfileno(stream->handle.limited->is);
  case IST_CHUNKED:
    return ISfileno(stream->handle.chunked->is);
  case IST_TEE:
    return ISfileno(stream->handle.tee->is);
  default:
    return -1;
  }
}

InputStream
ISoftype(InputStream is, int type)
{
  if (!is)
    return NULL;

  for (;;) {
    if (is->type == type)
      return is;

    switch (IStype(is)) {
    case IST_ENCODED:
      is = is->handle.ens->is;
      break;
    case IST_LIMITED:
      is = is->handle.limited->is;
      break;
    case IST_CHUNKED:
      is = is->handle.chunked->is;
      break;
    case IST_TEE:
      is = is->handle.tee->is;
      break;
    default:
      return NULL;
    }
  }
}

int
ISeos(InputStream base)
{
  if (!base->iseos && MUST_BE_UPDATED(base))
    do_update(base);

  return base->iseos;
}

#ifdef USE_SSL
static Str accept_this_site;

void
ssl_accept_this_site(char *hostname)
{
  if (hostname)
    accept_this_site = Strnew_charp(hostname);
  else
    accept_this_site = NULL;
}


static int
ssl_match_cert_ident(char *ident, int ilen, char *hostname)
{
  /* RFC2818 3.1.  Server Identity
   * Names may contain the wildcard
   * character * which is considered to match any single domain name
   * component or component fragment. E.g., *.a.com matches foo.a.com but
   * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
   */
  char *b, *e, *wc;

  for (b = ident, e = ident + ilen ; (wc = memchr(b, '*', e - b)) ;)
    if (wc + 1 < e) {
      if (*(wc + 1) == '.') {
	if ((wc > b && strncasecmp(hostname, b, wc - b)) ||
	    !(hostname = strchr(hostname, '.')))
	  return FALSE;

	++hostname;
	b = wc + 2;
      }
      else
	b = wc + 1;
    }
    else {
      if (wc > b && strncasecmp(hostname, b, wc - b))
	return FALSE;

      return TRUE;
    }

  if (strlen(hostname) != e - b ||
      (e > b && strcasecmp(hostname, b)))
    return FALSE;
  else
    return TRUE;
}

static Str
ssl_check_cert_ident(X509 * x, char *hostname)
{
  int i;
  Str ret = NULL;
  int match_ident = FALSE;

  /*
   * All we need to do here is check that the CN matches.
   *
   * From RFC2818 3.1 Server Identity:
   * If a subjectAltName extension of type dNSName is present, that MUST
   * be used as the identity. Otherwise, the (most specific) Common Name
   * field in the Subject field of the certificate MUST be used. Although
   * the use of the Common Name is existing practice, it is deprecated and
   * Certification Authorities are encouraged to use the dNSName instead.
   */

  i = X509_get_ext_by_NID(x, NID_subject_alt_name, -1);

  if (i >= 0) {
    X509_EXTENSION *ex;
    STACK_OF(GENERAL_NAME) * alt;

    ex = X509_get_ext(x, i);

    if ((alt = X509V3_EXT_d2i(ex))) {
      int n;
      GENERAL_NAME *gn;
      X509V3_EXT_METHOD *method;
      Str seen_dnsname = NULL;

      n = sk_GENERAL_NAME_num(alt);

      for (i = 0; i < n; i++) {
	gn = sk_GENERAL_NAME_value(alt, i);

	if (gn->type == GEN_DNS) {
	  char *sn = ASN1_STRING_data(gn->d.ia5);
	  int sl = ASN1_STRING_length(gn->d.ia5);

	  if (!seen_dnsname)
	    seen_dnsname = Strnew();

	  Strcat_m_charp(seen_dnsname, sn, " ", NULL);

	  if (ssl_match_cert_ident(sn, sl, hostname))
	    break;
	}
      }

      method = X509V3_EXT_get(ex);
      method->ext_free(alt);

      if (i < n)		/* Found a match */
	match_ident = TRUE;
      else if (seen_dnsname)
	ret = Sprintf("Bad cert ident from %s: dNSName=%s", hostname,
		      seen_dnsname->ptr);
    }
  }

  if (match_ident == FALSE && ret == NULL) {
    X509_NAME *xn;
    char buf[2048];

    xn = X509_get_subject_name(x);

    if (X509_NAME_get_text_by_NID(xn, NID_commonName,
				  buf, sizeof(buf)) == -1)
      ret = Strnew_charp("Unable to get common name from peer cert");
    else if (!ssl_match_cert_ident(buf, strlen(buf), hostname))
      ret = Sprintf("Bad cert ident %s from %s", buf, hostname);
    else
      match_ident = TRUE;
  }

  return ret;
}

Str
ssl_get_certificate(SSL *ssl, char *hostname)
{
  BIO *bp;
  X509 *x;
  X509_NAME *xn;
  char *p;
  int len;
  Str s;
  char buf[2048];
  Str amsg = NULL;
  Str emsg;
  char *ans;

  if (!ssl)
    return NULL;

  if (!(x = SSL_get_peer_certificate(ssl))) {
    if (accept_this_site
	&& !strcasecmp(accept_this_site->ptr, hostname))
      ans = "y";
    else {
      emsg = Strnew_charp("No SSL peer certificate: accept? (y/n)");
      ans = inputAnswer(emsg->ptr);
    }

    if (ans && tolower(*ans) == 'y')
      amsg = Strnew_charp("Accept SSL session without any peer certificate");
    else {
      char *e = "This SSL session was rejected to prevent security violation: no peer certificate";

      disp_err_message(e, FALSE);
      free_ssl_ctx();
      return NULL;
    }

    if (amsg)
      disp_err_message(amsg->ptr, FALSE);

    ssl_accept_this_site(hostname);
    s = amsg ? amsg : Strnew_charp("valid certificate");
    return s;
  }

#ifdef USE_SSL_VERIFY
  /* check the cert chain.
   * The chain length is automatically checked by OpenSSL when we
   * set the verify depth in the ctx.
   */
  if (ssl_verify_server) {
    long verr;

    if ((verr = SSL_get_verify_result(ssl)) != X509_V_OK) {
      const char *em = X509_verify_cert_error_string(verr);

      if (accept_this_site
	  && strcasecmp(accept_this_site->ptr, hostname) == 0)
	ans = "y";
      else {
	emsg = Sprintf("%s: accept? (y/n)", em);
	ans = inputAnswer(emsg->ptr);
      }

      if (ans && tolower(*ans) == 'y')
	amsg = Sprintf("Accept unsecure SSL session: unverified: %s", em);
      else {
	char *e = Sprintf("This SSL session was rejected: %s", em)->ptr;

	disp_err_message(e, FALSE);
	free_ssl_ctx();
	return NULL;
      }
    }
  }
#endif

  if ((emsg = ssl_check_cert_ident(x, hostname))) {
    if (accept_this_site
	&& strcasecmp(accept_this_site->ptr, hostname) == 0)
      ans = "y";
    else {
      Str ep = Strdup(emsg);

      if (ep->length > COLS - 16)
	Strshrink(ep, ep->length - (COLS - 16));

      Strcat_charp(ep, ": accept? (y/n)");
      ans = inputAnswer(ep->ptr);
    }

    if (ans && tolower(*ans) == 'y') {
      amsg = Strnew_charp("Accept unsecure SSL session:");
      Strcat(amsg, emsg);
    }
    else {
      char *e = "This SSL session was rejected to prevent security violation";

      disp_err_message(e, FALSE);
      free_ssl_ctx();
      return NULL;
    }
  }

  if (amsg)
    disp_err_message(amsg->ptr, FALSE);

  ssl_accept_this_site(hostname);
  s = amsg ? amsg : Strnew_charp("valid certificate");
  Strcat_charp(s, "\n");
  xn = X509_get_subject_name(x);

  if (X509_NAME_get_text_by_NID(xn, NID_commonName, buf, sizeof(buf)) == -1)
    Strcat_charp(s, " subject=<unknown>");
  else
    Strcat_m_charp(s, " subject=", buf, NULL);

  xn = X509_get_issuer_name(x);

  if (X509_NAME_get_text_by_NID(xn, NID_commonName, buf, sizeof(buf)) == -1)
    Strcat_charp(s, ": issuer=<unnown>");
  else
    Strcat_m_charp(s, ": issuer=", buf, NULL);

  Strcat_charp(s, "\n\n");
  bp = BIO_new(BIO_s_mem());
  X509_print(bp, x);
  len = (int)BIO_ctrl(bp, BIO_CTRL_INFO, 0, (char *)&p);
  Strcat_charp_n(s, p, len);
  BIO_free_all(bp);
  X509_free(x);
  return s;
}
#endif

clen_t
IStotal(InputStream is)
{
  for (;;) {
    switch (is->type) {
    case IST_DELIMITED:
      is = is->handle.delimited->is;
      break;
    default:
      return is->type == IST_TEE ? is->handle.tee->total : -1;
    }
  }
}

#if LANG == JA || defined(HAVE_MOE)
void
ISsetces(InputStream is, const char *ces_name)
{
  if (!ces_name || !*ces_name)
    return;

  while (is)
    switch (is->type) {
    case IST_DELIMITED:
      is = is->handle.delimited->is;
      break;
    default:
      if (is->type == IST_TEE) {
	struct tee_handle *th;

	th = is->handle.tee;

#ifdef HAVE_MOE
	if (th->cd.flag & MB_FLAG_UNKNOWNCS) {
	  mb_G_t G, Gsave;

	  if (th->cd.flag & MB_FLAG_CS_DETECTING) {
	    mb_cs_detector_t *p;

	    p = th->cd.io_arg;
	    G = p->copy.G;
	    Gsave = p->copy.Gsave;
	  }
	  else {
	    G = th->cd.G;
	    Gsave = th->cd.Gsave;
	  }

	  mb_ces_by_name(ces_name, &th->cd);
	  mb_restore_G(&th->cd, &G, &Gsave);
	}
#else
	if (th->st.found == CODE_UNKNOWN &&
	    getConvToEJRoutine(*ces_name, &th->conv, &th->cd))
	  th->st.found = *ces_name;
#endif
      }
  
      return;
    }
}

#ifdef HAVE_MOE
#define GETCES_FAILURE NULL
#else
#define GETCES_FAILURE ""
#endif

const char *
ISgetces(InputStream is)
{
  while (is)
    switch (is->type) {
    case IST_DELIMITED:
      is = is->handle.delimited->is;
      break;
    default:
      if (is->type == IST_TEE) {
	struct tee_handle *th;

	th = is->handle.tee;
#ifdef HAVE_MOE
	return th->cd.flag & MB_FLAG_UNKNOWNCS ? NULL : th->cd.ces->namev[0];
#else
	return th->st.found == CODE_UNKNOWN ? "" : (const char *)&th->st.found;
#endif
      }

      return GETCES_FAILURE;
    }

  return GETCES_FAILURE;
}
#endif

/* Raw level input stream functions */

static void
basic_close(union input_handle handle)
{
  close(handle.fd);
}

static int
basic_read(union input_handle handle, char *buf, int len)
{
  return read(handle.fd, buf, len);
}

static void
file_close(union input_handle handle)
{
  handle.file->close(handle.file->f);
}

static int
file_read(union input_handle handle, char *buf, int len)
{
  return fread(buf, 1, len, handle.file->f);
}

static int
delimited_read(union input_handle handle, char *buf, int len)
{
  if (handle.delimited->cur >= handle.delimited->buf->length) {
    Strclear(handle.delimited->buf);
    handle.delimited->cur = 0;

    if (StrISgets(handle.delimited->is, handle.delimited->buf)->length &&
	handle.delimited->buf->ptr[handle.delimited->buf->length - 1] == '\n') {
      handle.delimited->buf->ptr[handle.delimited->buf->length - 1] = '\0';

      if (handle.delimited->delim_p(handle.delimited->buf->ptr, handle.delimited->delim_arg))
	Strclear(handle.delimited->buf);
      else
	handle.delimited->buf->ptr[handle.delimited->buf->length - 1] = '\n';
    }
  }

  if (len > handle.delimited->buf->length - handle.delimited->cur)
    len = handle.delimited->buf->length - handle.delimited->cur;

  memcpy(buf, &handle.delimited->buf->ptr[handle.delimited->cur], len);
  handle.delimited->cur += len;
  return len;
}

static void
delimited_close(union input_handle handle)
{
  ISclose(handle.delimited->is);
}

static int
str_read(union input_handle handle, char *buf, int len)
{
  return 0;
}

#ifdef USE_SSL
static void
ssl_close(union input_handle handle)
{
  close(handle.ssl->sock);

  if (handle.ssl->ssl)
    SSL_free(handle.ssl->ssl);
}

static int
ssl_read(union input_handle handle, char *buf, int len)
{
  int status;

  if (handle.ssl->ssl) {
#ifdef USE_SSL_VERIFY
    for (;;) {
      if ((status = SSL_read(handle.ssl->ssl, buf, len)) > 0)
	break;

      switch (SSL_get_error(handle.ssl->ssl, status)) {
      case SSL_ERROR_WANT_READ:
      case SSL_ERROR_WANT_WRITE: /* reads can trigger write errors; see SSL_get_error(3) */
	continue;
      default:
	break;
      }

      break;
    }
#else				/* if !defined(USE_SSL_VERIFY) */
    status = SSL_read(handle.ssl->ssl, buf, len);
#endif				/* !defined(USE_SSL_VERIFY) */
  }
  else
    status = read(handle.ssl->sock, buf, len);

  return status;
}
#endif				/* USE_SSL */

static void
ens_close(union input_handle handle)
{
  ISclose(handle.ens->is);
}

static int
ens_read(union input_handle handle, char *buf, int len)
{
  if (handle.ens->s == NULL || handle.ens->pos == handle.ens->s->length) {
    char *p;

    handle.ens->s = StrmyISgets(handle.ens->is, NULL);

    if (handle.ens->s->length == 0)
      return 0;

    cleanup_line(handle.ens->s, PAGER_MODE);

    if (handle.ens->encoding == ENC_BASE64)
      Strchop(handle.ens->s);
    else if (handle.ens->encoding == ENC_UUENCODE) {
      if (!strncmp(handle.ens->s->ptr, "begin", 5))
	handle.ens->s = StrmyISgets(handle.ens->is, NULL);

      Strchop(handle.ens->s);
    }

    p = handle.ens->s->ptr;

    if (handle.ens->encoding == ENC_QUOTE)
      handle.ens->s = decodeQP(&p);
    else if (handle.ens->encoding == ENC_BASE64)
      handle.ens->s = decodeB(&p);
    else if (handle.ens->encoding == ENC_UUENCODE)
      handle.ens->s = decodeU(&p);

    handle.ens->pos = 0;
  }

  if (len > handle.ens->s->length - handle.ens->pos)
    len = handle.ens->s->length - handle.ens->pos;

  bcopy(&handle.ens->s->ptr[handle.ens->pos], buf, len);
  handle.ens->pos += len;
  return len;
}

static void
limited_close(union input_handle handle)
{
  if (ISfileno(handle.chunked->is) != kept_sock)
    ISclose(handle.limited->is);
}

static int
limited_read(union input_handle handle, char *buf, int len)
{
  struct limited_handle *lh;

  lh = handle.limited;

  if (len > lh->rest)
    len = lh->rest;

  if (len > 0 &&
      (len = ISreadonce(lh->is, buf, len)) > 0)
    lh->rest -= len;

  return len;
}

static void
chunked_close(union input_handle handle)
{
  if (ISfileno(handle.chunked->is) != kept_sock)
    ISclose(handle.chunked->is);
}

static int
chunked_read(union input_handle handle, char *buf, int len)
{
  struct chunked_handle *ch;

  ch = handle.chunked;

  if (!ch->rest) {
    Strclear(ch->buf);

    if (!StrISgets(ch->is, ch->buf)->length)
      return 0;
    else if (!(ch->rest = strtol(ch->buf->ptr, NULL, 16))) {
      while (Strclear(ch->buf), StrISgets(ch->is, ch->buf)->length) {
	Strchop(ch->buf);

	if (!ch->buf->length)
	  break;
      }

      return 0;
    }
  }

  if (len > ch->rest)
    len = ch->rest;

  if (len > 0 &&
      (len = ISreadonce(ch->is, buf, len)) > 0) {
    if (!(ch->rest -= len))
      StrISgets(ch->is, ch->buf);
  }

  return len;
}

static void
tee_close(union input_handle handle)
{
  ISclose(handle.tee->is);
}

static int
tee_read(union input_handle handle, char *buf, int len)
{
#if LANG == JA
  if (handle.tee->noconv)
#endif
#ifndef HAVE_MOE
    return tee_read_is(handle.tee, buf, len);
#endif
#ifdef HAVE_MOE
  {
    int n = 0;

    if (len > 0) {
      void *to;

      to = buf;

      if (handle.tee->noconv)
	mb_encode(&handle.tee->cd, MB_ENCODE_SKIP_INVALID | MB_ENCODE_SKIP_SHORT,
		  &to, buf + len);
      else
	mb_cs_detect_encode(&handle.tee->cd, MB_ENCODE_SKIP_INVALID | MB_ENCODE_SKIP_SHORT,
			    &to, buf + len);

      n = (char *)to - buf;
    }

    return n;
  }
#elif LANG == JA
  {
    struct tee_handle *th;
    int res, n;
    char *to;
    unsigned char prev_iso;

    th = handle.tee;

    if (th->st.found == CODE_UNKNOWN) {
      for (;;) {
	if (!th->from_left) {
	  if (th->is->iseos)
	    goto detection_end;

	  if ((n = th->size - (th->from - th->buf)) <= 0) {
	    if (!th->len_max || th->size < th->len_max) {
	      th->size = (th->size / 2 + 1) * 3;

	      if (th->len_max && th->size > th->len_max)
		th->size = th->len_max;

	      n = th->from - th->buf;
	      th->buf = GC_REALLOC(th->buf, th->size);
	      th->from = th->buf + n;
	    }
	    else
	      goto detection_end;

	    n = th->size - (th->from - th->buf);
	  }

	  if ((n = tee_read_is(th, th->from, n)) <= 0)
	    goto detection_end;

	  th->from_left = n;
	}

	res = 0;

	do {
	  if (th->st.skip) {
	    th->st.skip = FALSE;
	    continue;
	  }

	  prev_iso = th->st.iso;
	  JA_CES_find1(&th->from[res], &th->st);

	  if (th->st.iso == JA_CODE_ERROR &&
	      res && th->from == th->buf && prev_iso == JA_ISO_NOSTATE) {
	    if ((th->st.si || th->st.so) &&
		getConvToEJRoutine(JA_CES_examin_siso(&th->st), &th->conv, &th->cd)) {
	      n = res;
	      res = th->conv(&th->cd, (const char **)&th->from, &n, &buf, &len);

	      if ((th->from_left -= th->from - th->buf) > 0)
		memmove(th->buf, th->from, th->from_left);

	      th->from = th->buf;

	      if (!res)
		continue;
	    }
	    else {
	      if (res > len)
		res = len;

	      memcpy(buf, th->from, res);

	      if ((th->from_left -= res) > 0)
		memmove(th->from, &th->from[res], th->from_left);
	    }

	    th->st.skip = TRUE;
	    goto end;
	  }
	} while (++res < th->from_left);

	if (th->st.iso == JA_ISO_NOSTATE && !(th->st.si || th->st.so)) {
	  if (res > len)
	    res = len;

	  memcpy(buf, th->from, res);

	  if ((th->from_left -= res) > 0)
	    memmove(th->from, &th->from[res], th->from_left);

	  goto end;
	}
	else if (th->st.iso == JA_ISO_NOSTATE &&
		 th->st.euc == JA_EUC_NOSTATE &&
		 th->st.sjis == JA_SJIS_NOSTATE &&
		 (th->st.found = JA_CES_examine_stat(&th->st)) != CODE_UNKNOWN)
	  goto found;

	th->from += th->from_left;
	th->from_left = 0;
      }
    detection_end:
      if ((th->st.found = JA_CES_examine_stat(&th->st)) != CODE_UNKNOWN)
	goto found;
    force_euc:
      th->st.found = CODE_EUC;
    found:
      if (!getConvToEJRoutine(th->st.found, &th->conv, &th->cd))
	goto force_euc;

      th->from_left += th->from - th->buf;
      th->from = th->buf;
    }

    if (!th->from_left) {
      th->from = th->buf;

      if (th->is->iseos)
	return 0;

      if ((n = tee_read_is(th, th->from, th->size)) <= 0)
	return n;

      th->from_left = n;
    }

    to = buf;

    while (!(res = th->conv(&th->cd, (const char **)&th->from, &th->from_left, &to, &len))
	   && len > 0 && !th->is->iseos) {
      if (th->from_left && th->buf != th->from) {
	memmove(th->buf, th->from, th->from_left);
	th->from = th->buf;
      }

      if ((n = tee_read_is(th, th->from + th->from_left, th->size - (th->from + th->from_left - th->buf))) <= 0)
	return n;

      th->from_left += n;
    }
  end:
    return res;
  }
#endif
}
