/*
** Copyright (c) 1990,1991,1992,1993 Keld Simonsen
** All rights reserved.
**
** Written by Keld Simonsen, RAP, Sct. Joergens Alle 8,
**	      DK-1615 Copenhagen V, Denmark
**
** Redistribution and use of this routine in source and binary forms are
** permitted provided that: (1) source distributions retain this entire
** copyright notice.  (2) The character and character set codes and names
** may not be changed. (3) The programming code must remain backwards
** compatible.
**
** The restriction on altering of names is done so that all versons
** of the code have a chance of being compatible. If you want alterations
** or additions, please mail me (preferably email to  keld@dkuug.dk)
** and I will consider it for future releases.
** 
** If the material is included in commercial products, donations
** will be most appreciated.
** 
** Keld Simonsen
**
** Code revised for better sendmail operation by Paul Pomes, University of
** Illinois.  
*/

/* configuration parameters */
#define debug  if (0) printf
#ifndef CHARFILE
#define CHARFILE "charsets.cpl"
/* #define CHARFILE "/usr/lib/charsets.cpl" */
#endif
char	*charfile	= CHARFILE;

#include "iso646.h"
#include <ctype.h>
#ifdef SENDMAIL
#include "sendmail.h"
#endif
#ifndef SENDMAIL
#include "charset.h"
#include <stdio.h>
#endif

# ifndef lint
static char RcsId[] = "@(#)$Header: /usr/local/src/mail/sendmail/ida/charset/RCS/strncnv.c,v 2.1 1991/04/05 17:11:49 paul Exp $";
# endif /* !lint */

extern char *charfile;

static FILE  *f = NULL;
static IN_CH *chset = NULL;
static CHARSET *charsets = NULL;
static CHARTAB *chartabs = NULL;
struct chdbhdr   hdr;
static INT16S ot1 ARRAY(C256), ot2 ARRAY(C256);

FILE *dfopen();
char *xalloc(),*newstr();

# ifdef __STDC__
static INT16S * getinch(CHARTAB *);
static CHAR8U * getoutch(CHARTAB *);
static CHARTAB * findchs(char *);
#  ifdef CC_WONT_PROMOTE
static char upper(char);
#  else /* !CC_WONT_PROMOTE */
static char upper(int);
#  endif /* CC_WONT_PROMOTE */
# else /* !__STDC__ */
static INT16S * getinch();
static CHAR8U * getoutch();
static CHARTAB * findchs();
static char upper();
# endif /* __STDC__ */
static int getchbas();
static INT32S ind;
#define		r_out	r_chset->cs->out
#define		s_in	s_chset->cs->in
#define		r_esca	r_chset->esc
#define		r_esc	r_chset->esc1
#define		s_esc	s_chset->esc1
#define		r_esc2	r_chset->esc2
#define		s_esc2	s_chset->esc2

/*
**  strncnv -- convert a string from one character set to another.
**
**	Parameters:
**		r_chset -- the character set to convert to.
**		s_chset -- the character set to convert from.
**		result -- the converted character string.
**		source -- the character string to convert.
**		n -- number of characters to convert
**
**	Returns:
**		pointer to result
**
**	Side Effects:
**		none.
*/

CHAR8U *
strncnv(r_chset, s_chset, result, source, n)
	register CHARSET *r_chset, *s_chset;
	CHAR8U	 *result, *source;
	int n;
begin
	CHAR8U	c, c1, *r_end=result+n-4 , *save = result;
	/* 4 chars for ending: esc two-char nul */
	int	o;		/* intermediate binary value */
	INT16S	mnem;
	int	oc; 		/* 2-byte output value */
	int 	bytes;
	int	gotesc2;

	bytes = r_chset->cs->outbytes; gotesc2= 0;
	while ((c = *source++) diff NULL && (result < r_end)) begin
		debug("%c%c %.2x %.2x ",c,*source,c,*source);	
		if (c == s_esc and (not s_esc2 or s_esc2 == *source)) begin
			if (s_esc2) source++;
			/* Two esc in a row -> one escape
			 * If esc followed by defined mnemonic,
			 * next char is mnemonic.  */
			if (*source == s_esc and (not s_esc2 or s_esc2 == *(source+1))) begin
				if (s_esc2) gotesc2++;
				o = *(s_in + s_esc);
				/* handle 2 escs */
				source++;
				if (s_esc2) source++;
				debug("in-esc ");
			end else begin /* mnemonic */
				c = *source++;
				if (c == '_') begin /* long ?? */ end
				else begin
					c1 = *source++;
					o = *(ot1+*(s_in+c)) + *(ot2+*(s_in+c1));
					debug("in-mnem %d %d %d ",*(s_in+c),*(s_in+c1),o);
				end
			end
		end else o = *(s_in + c);

		debug("out: o = %d %c%c  ",o,*(r_out+ (hdr.basechrs + o%hdr.basechrs)*bytes),*(r_out+ (hdr.basechrs + o/hdr.basechrs)*bytes));
		if (o > hdr.outsize) /* special handling */ begin
			if (o < hdr.begcombtabs ) /* long mnem */ begin
			end else begin /* combtabs */
				c1 = *source++;
				debug("combtab %d %x %d ",o,c1,o-hdr.begcombtabs+1);
				o=*(s_in + (o-hdr.begcombtabs+1)*C256 +c1);
				debug("new-o %d ",o);
			end
		end
		if (not o) o = 141; /* if not defined: underline */

		if (bytes ==2) oc= *((INT16S *) r_out + o);
		else oc= *(r_out + o);
		if (gotesc2) begin
			gotesc2= *(s_in+s_esc2);
			if (not gotesc2) gotesc2= 141;
			debug("gotesc2 %d ",gotesc2); 
			if (bytes ==2) gotesc2= *((INT16S *) r_out + gotesc2);
			else gotesc2= *(r_out + gotesc2);
			oc = oc *C256 + gotesc2;
			gotesc2=0;
		end
		debug("o= %d %d  oc=%x  ",o,r_esca,oc); 
		if (oc == r_esca) begin /* write 2 escs */
			*result++ = r_esc;
			if (r_esc2) *result++ = r_esc2;
			*result++ = r_esc;
			if (r_esc2) *result++ = r_esc2;
			debug("esc %x\n",r_esca);
		end else if (oc) begin
			if (oc/C256) *result++ = oc>>8;
			*result++ = oc%C256;
			debug("normal ");
		end else begin
			*result++ = r_esc;
			if (r_esc2) *result++ = r_esc2;
			if (bytes == 2) begin
			*result++ = *((INT16S *)r_out + hdr.basechrs + o%hdr.basechrs);
			*result++ = *((INT16S *)r_out + hdr.basechrs + o/hdr.basechrs);
			end else begin
			*result++ = *(r_out + hdr.basechrs + o%hdr.basechrs);
			*result++ = *(r_out + hdr.basechrs + o/hdr.basechrs);
			end
			debug("mnem %d ",o);
		end
		debug("\n");
	end
	*result++ = '\0';
	return (save);
end
/*
**  getchset -- fetch the named character set.
**
**	First loop through the list of already loaded character sets.
**	If found, return a pointer to it.  Otherwise read the set into
**	memory and append it to the CHARSET chain.
**
**	Parameters:
**		s -- the name of the character set to get.
**		esc -- the value for the out-of-band escape character.
**
**	Returns:
**		pointer to character set or NULL on error.
**
**	Side Effects:
**		mallocs memory for character set name and for the
**		CHARSET structure.  Gradually increases size of
**		running image if many sets are requested.
*/

CHARSET *
getchset(s, esc)
	char *s;
	INT16S esc;
begin
	register CHARTAB *t, *t1;
	register CHARSET *c, *c1;
	register char *p;

	/* Up case the character set name. */
	for (p = s; *p; p++) *p = upper(*p);

	/* Load the character base file into chset. */
	if (getchbas() == 0) return (NULL);

	/* Loop through the chain of character set names. */
	debug("in-core charsets: ");
	for (t = chartabs; t && strcmp(t->name, s); t = t->next)
		begin debug("%s ",t->name); t1 = t; end
	debug("\n");
	if (not t)
	begin
	/*
	 * The requested character set wasn't found on the in-memory chain.
	 * Load it up from the disk file.
	 */
		if((t = findchs(s)) == NULL)
                   return(NULL);
		debug("bits=%d  g0= %x  comb=%d intro=%d\n",t->bits,t->g0esc,t->combtabs,esc);
		t->next = NULL;
		t->name = newstr(s);
		t->in = getinch(t);
		if (not t->in)
		begin
			free(t);
			return (NULL);
		end
		t->out = getoutch(t);
		if (chartabs) t1->next = t;
		else chartabs = t;
	end

	/* loop thru chain of charsets */
	for (c = charsets; c && not (t == c->cs && esc == c->esc);
		c = c->next) c1 = c;
	if (not c) begin
		c = (CHARSET *) xalloc(sizeof (CHARSET));
		c->next = NULL;
		c->cs = t;
		c->esc = esc;
		if (esc/C256) begin
			c->esc1 = esc/C256;
			c->esc2 = esc%C256;
		end else begin
			c->esc1 = esc;
			c->esc2 = 0;
		end
		if (charsets) c1->next = c;
		else charsets = c;
	end
	return (c);
end
/*
**  getchbas -- load character set base definition file
**
**	Load the character set base definition file and assign the
**	global var chset to the beginning of it.
**
**	Parameters:
**		none.
**
**	Returns:
**		1 if file load successful or previously done, NULL
**		on error.
**
**	Side Effects:
**		mallocs storage that global chset points at.
*/

static int
getchbas()
begin
	if (not hdr.offend or not f or (fseek(f,hdr.offend,0) diff 0))
	begin
		if ((f = dfopen(charfile,"rb")) == NULL)
		begin
			syserr("getchbas: can't open charset def'n file %s\n", charfile);
# ifdef LOG
			syslog(LOG_ALERT, "getchbas: can't open charset def'n file %s: %m", charfile);
# endif /* LOG */
			return (NULL);
		end
	end

	if (not hdr.offend) 
	begin
		/* Read header of the CHARFILE file with offsets etc. */
		if (fread(&hdr, sizeof (hdr), 1, f) diff 1)
		begin
			syserr("getchbas: error reading header of charset def'n file %s\n", charfile);
# ifdef LOG
			syslog(LOG_ALERT, "getchbas: error reading header of charset def'n file %s: %m", charfile);
# endif /* LOG */
			return (NULL);
		end
		fread(ot1, C256, 2, f);
		fread(ot2, C256, 2, f);
	end
	return (1);
end
/*
**  findchs -- find a character set description
**
**	Parameters:
**		charset -- the character set to load from.
**
**	Returns:
**		pointer to result or NULL if error
**
**	Side Effects:
**		allocates space for data read from files.
*/

static CHARTAB *
findchs(s)
	char *s;
begin
	register CHARTAB *t;
	register char *p;
	char *buf;
	int i;

	/* Up case the character set name. */
	for (p = s; *p; p++) *p = upper(*p);

	/* Loop through the chain of character set names. */
	for (t = chartabs; t && strcmp(t->name, s); t = t->next);

	if (not t) begin
		debug("findchs: not incore %s\n",s);
		buf = (char *) xalloc(hdr.sizdir);
		if (fseek(f,hdr.offdir,0))
			syserr("findchs: fseek error of charset name table %d",hdr.offdir);
		if (fread(buf, hdr.sizdir , 1, f) diff 1)
		begin
			syserr("findchs: read error of charset name table %d %d",hdr.sizdir,hdr.offdir);
# ifdef LOG
			syslog(LOG_ALERT, "findchs: read error of charset name table: %m");
# endif /* LOG */
			return (NULL);
		end

		/* find charset name in chain, index = i */
		i= 0;
		for (p= buf; p < buf+hdr.sizdir and strcmp(s,p+1); p += *p+1)
			begin debug("%d,%s\n",*p,p+1); i++; end
		if (not (p < buf+hdr.sizdir)) begin
			syserr("findchs: charset name not found %s\n",s);
# ifdef LOG
			syslog(LOG_ALERT, "findchs: charset name not found %s: %m",s);
# endif /* LOG */
			return (NULL);
		end
		debug("findchs: found %*.*s\n",*p,*p,p+1);

		/* get charset header */
		fseek(f,i*sizeof(ind)+hdr.offdat,0);
		fread(&ind,sizeof(ind),1,f);
		fseek(f,ind,0);
		t = (CHARTAB *) xalloc(sizeof(CHARTAB));
		fread(t,sizeof(CHARTAB),1,f);
		free(buf);
	end
	return t;
end
/*
**  getinch -- load a character set input translation table.
**
**	Parameters:
**		charset -- the character set to load from.
**
**	Returns:
**		pointer to result or NULL if error
**
**	Side Effects:
**		allocates space for data read from files.
*/

static INT16S *
getinch(t)
	CHARTAB	*t;
begin
	INT32S	sz,adr;
	IN_CH	*in;

	sz = C256 * 2 * (1+t->combtabs);
	if (t->bits == 16) sz = 2 *C256*C256;
	if (t->bits == 14) sz = 2 *128*128;
	in = (IN_CH *) xalloc(sz);
	adr = t->fileoffset+sizeof(CHARTAB)
		+hdr.outsize*t->outbytes*(1-t->createout);
	debug("in=%o insz= %o adr=%o off=%o\n",in,sz, adr,t->fileoffset);
 	debug("getinch: in seek: %s %d\n",t->name,(fseek(f,adr,0))); 

	sz = fread(in, sz, 1, f);
	debug("feof %o ferror %o sz %d \n",feof(f),ferror(f),sz);
	if (sz diff 1)
	begin
		syserr("getinch: read of charset input translation table: %s %d\n", t->name,sz);
# ifdef LOG
		syslog(LOG_ALERT, "getinch: read of charset input translation table %s: %m", t->name);
# endif /* LOG */
		return (NULL);
	end
	/* *in = '\0'; */
	/* debug(" %c %d \n",c,*in);  */
	return (in);
end
/*
**  getoutch -- load a character set output translation table.
**
**	Parameters:
**		charset -- the character set to load from.
**
**	Returns:
**		pointer to result or NULL if error
**
**	Side Effects:
**		allocates space for data read from files.
*/

static CHAR8U *
getoutch(t)
	CHARTAB	*t;
begin
	unsigned sz1;
	register int i;
	register OUT_CH	*out;
	register IN_CH *in= t->in;

	sz1 = hdr.outsize*t->outbytes;
	(void) fseek(f, t->fileoffset + sizeof (CHARTAB), 0);
	out = (OUT_CH *) xalloc(sz1 * sizeof (OUT_CH));
	debug("out=%o osz= %o createout=%d\n",out,sz1,t->createout);
	if (t->createout) begin
		if (not in) return NULL;
		for (i=0; i<sz1; i++) *(out+i) = 0;
		for (i=0; i<C256; i++) if ( *(in+i) < sz1)
			*(out+ *(in+i)) = i;
	end else if (fread(out, sizeof (OUT_CH), sz1, f) diff sz1) begin
		syserr("getoutch: read of charset output translation table %s\n", t->name);
# ifdef LOG
		syslog(LOG_ALERT, "getoutch: read of charset output translation table %s: %m", t->name);
# endif /* LOG */
		return (NULL);
	end
	*out = '\0';
	return (out);
end
/*
**  UPPER -- turn letter into upper case.
**
**	Parameters:
**		c -- character to turn into upper case.
**
**	Returns:
**		c, in upper case.
**
**	Side Effects:
**		none.
*/

static char
upper(c)
	register char c;
begin
	return (islower(c) ? toupper(c) : c);
end
