/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1998-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * uuencode/uudecode methods
 */

static char id[] = "\n@(#)$Id: uulib (AT&T Research) 2003-01-07 $\0\n";

static const char lib[] = "libuu:uu";

#include "uulib.h"

#include <ctype.h>
#include <error.h>
#include <ls.h>
#include <modex.h>
#include <sfdisc.h>

#define UU_BEGIN	"begin"
#define UU_BEGIN_LEN	(sizeof(UU_BEGIN)-1)

#define UUIN		3
#define UUOUT		4
#define UUCHUNK		15
#define UULINE		76

#define UU_END	(UCHAR_MAX)
#define UU_IGN	(UCHAR_MAX-1)
#define UU_PAD	(UCHAR_MAX-2)

typedef struct
{
	const char*	name;
	unsigned long	flag;
} Option_t;

static const Option_t	options[] =
{
	"text",		UU_TEXT,
};

/*
 * initialize the uu map from dp
 */

static unsigned char*
uu_map(register Uudata_t* dp, char* map)
{
	register int		c;
	register char*		p;
	register unsigned char*	m;
	int			x;

	x = (dp->flags & UU_LENGTH) ? 0 : UU_IGN;
	p = map;
	memset(p, x, UCHAR_MAX + 2);
	if (x)
	{
		p++;
		p[dp->pad] = UU_PAD;
		p[EOF] = UU_END;
	}
	for (m = (unsigned char*)dp->map; c = *m; m++)
		p[c] =  m - (unsigned char*)dp->map;
	return (unsigned char*)p;
}

/*
 * grab uu header from input
 */

static int
uu_header(register Uu_t* uu)
{
	register char*	s;
	register int	c;
	int		n;
	int		k;
	Uumeth_t*	meth;
	char*		t;

	c = *UU_BEGIN;
	for (;;)
	{
		if (!(s = sfgetr(uu->ip, '\n', 1)))
		{
			if (uu->disc->errorf)
				(*uu->disc->errorf)(uu, uu->disc, 2, "unknown encoding");
			break;
		}
		if (*s == c && !strncasecmp(s, UU_BEGIN, UU_BEGIN_LEN) && (meth = uumeth(s)))
		{
			s += UU_BEGIN_LEN + strlen(meth->id);
			while (*s == '-')
			{
				for (t = ++s; isalnum(*s); s++);
				k = s - t;
				for (n = 0; n < elementsof(options); n++)
					if (strlen(options[n].name) == k && !strncasecmp(options[n].name, t, k))
					{
						uu->flags |= options[n].flag;
						break;
					}
			}
			if (*s++ == ' ')
			{
				uu->mode = strperm(s, &t, 0) & ~(S_IXUSR|S_IXGRP|S_IXOTH);
				if (*t++ == ' ')
				{
					if (!uu->path)
					{
						uu->path = strdup(t);
						uu->flags |= UU_FREEPATH;
					}
					if (meth->name != uu->meth.name)
					{
						if (!(((Uudata_t*)uu->meth.data)->flags & UU_DEFAULT) && !streq(meth->id, uu->meth.id) && uu->disc->errorf)
							(*uu->disc->errorf)(uu, uu->disc, 1, "switching to %s encoding", meth->name);
						uu->meth = *meth;
					}
					return 0;
				}
			}
		}
	}
	return -1;
}

/*
 * uu encode input to output
 */

static int
uu_encode(register Uu_t* uu)
{
	register char*		e;
	register char*		p;
	register unsigned char*	m;
	register int		c;
	register int		c1;
	register int		c2;
	register int		c3;
	register unsigned long	b;
	int			length;
	int			nl;
	int			pad;
	int			text;
	Uudata_t*		dp;
	struct stat		st;
	char			buf[UUOUT * (UUCHUNK + 1)];

	dp = (Uudata_t*)uu->meth.data;
	m = (unsigned char*)dp->map;
	length = !!(dp->flags & UU_LENGTH);
	text = !!(uu->flags & UU_TEXT);
	pad = dp->pad;
	if (uu->flags & UU_HEADER)
	{
		if (fstat(sffileno(uu->ip), &st) || !(c = modex(st.st_mode) & 0777))
			c = 0644;
		sfprintf(uu->op, "%s%s%s %03o %s\n", UU_BEGIN, uu->meth.id, text ? "-text" : "", c, uu->path ? uu->path : "-");
	}
	if (length)
		*buf = m[UUIN * UUCHUNK];
	p = buf + length;
	e = p + UUOUT * UUCHUNK;
	nl = 0;
	for (;;)
	{
		do {
			if (nl)
			{
				nl = 0;
				c1 = '\n';
				goto get_2;
			}
			if ((c1 = sfgetc(uu->ip)) == EOF)
			{
				if (length)
					*buf = m[((p - buf - length) / UUOUT) * UUIN];
				goto eof;
			}
			if (text && c1 == '\n')
			{
				c1 = '\r';
				c2 = '\n';
				goto get_3;
			}
 get_2:
			if ((c2 = sfgetc(uu->ip)) == EOF)
			{
				if (length)
					*buf = m[((p - buf - length) / UUOUT) * UUIN + 1];
				c2 = dp->fill;
				c3 = dp->fill;
				b = (c1 << 16) | (c2 << 8) | c3;
				*p++ = m[b >> 18];
				*p++ = m[(b >> 12) & 077];
				*p++ = pad ? pad : m[(b >> 6) & 077];
				*p++ = pad ? pad : m[b & 077];
				goto eof;
			}
			if (text && c2 == '\n')
			{
				c2 = '\r';
				c3 = '\n';
				goto put_123;
			}
 get_3:
			if ((c3 = sfgetc(uu->ip)) == EOF)
			{
				if (length)
					*buf = m[((p - buf - length) / UUOUT) * UUIN + 2];
				c3 = dp->fill;
				b = (c1 << 16) | (c2 << 8) | c3;
				*p++ = m[b >> 18];
				*p++ = m[(b >> 12) & 077];
				*p++ = m[(b >> 6) & 077];
				*p++ = pad ? pad : m[b & 077];
				goto eof;
			}
			if (text && c3 == '\n')
			{
				nl = 1;
				c3 = '\r';
			}
 put_123:
			b = (c1 << 16) | (c2 << 8) | c3;
			*p++ = m[b >> 18];
			*p++ = m[(b >> 12) & 077];
			*p++ = m[(b >> 6) & 077];
			*p++ = m[b & 077];
		} while (p < e);
		*p++ = '\n';
		sfwrite(uu->op, buf, p - buf);
		p = buf + length;
	}
 eof:
	if (p > buf + length)
	{
		*p++ = '\n';
		sfwrite(uu->op, buf, p - buf);
	}
	if (length)
		sfprintf(uu->op, "%c\n", m[0]);
	if (uu->flags & UU_HEADER)
		sfputr(uu->op, dp->end, '\n');
	return 0;
}

/*
 * uu decode input to output
 */

static int
uu_decode(register Uu_t* uu)
{
	register Uudata_t*	dp;
	register char*		s;
	register char*		e;
	register char*		p;
	register unsigned char*	m;
	register int		c;
	register unsigned long	n;
	int			text;
	int			tl;
	int			x;
	char*			t;
	char			buf[UUIN * UUCHUNK + 1];
	char			map[UCHAR_MAX + 2];

	dp = (Uudata_t*)uu->meth.data;
	if (uu->path && (uu->flags & UU_CLOSEOUT) && (dp->flags & uu->flags & UU_HEADER) && chmod(uu->path, uu->mode) && uu->disc->errorf)
		(*uu->disc->errorf)(uu, uu->disc, ERROR_SYSTEM|2, "%s: cannot change mode to %s", uu->path, fmtperm(uu->mode));
	text = !!(uu->flags & UU_TEXT);
	m = uu_map(dp, map);
	if (dp->flags & UU_LENGTH)
	{
		t = (char*)dp->end;
		tl = strlen(t) + 1;
		while (((s = sfgetr(uu->ip, '\n', 0)) || (s = sfgetr(uu->ip, '\n', -1))) && ((n = sfvalue(uu->ip)) != tl || !strneq(s, t, tl - 1)))
			if (c = m[*((unsigned char*)s++)])
			{
				if (c > sizeof(buf))
				{
					if (uu->disc->errorf)
						(*uu->disc->errorf)(uu, uu->disc, 2, "input is not %s encoded", uu->meth.name);
					return -1;
				}
				p = buf;
				e = s + (c + UUIN - 1) / UUIN * UUOUT;
				while (s < e)
				{
					n = m[*((unsigned char*)s++)];
					n = (n << 6) | ((s < e) ? m[*((unsigned char*)s++)] : 0);
					n = (n << 6) | ((s < e) ? m[*((unsigned char*)s++)] : 0);
					n = (n << 6) | ((s < e) ? m[*((unsigned char*)s++)] : 0);
					if (text)
					{
						if ((x = (n >> 16) & 0xFF) == '\r')
							c--;
						else
							*p++ = x;
						if ((x = (n >> 8) & 0xFF) == '\r')
							c--;
						else
							*p++ = x;
						if ((x = n & 0xFF) == '\r')
							c--;
						else
							*p++ = x;
					}
					else
					{
						*p++ = (n >> 16);
						*p++ = (n >> 8);
						*p++ = n;
					}
				}
				sfwrite(uu->op, buf, c);
			}
		if (!s && (uu->flags & UU_HEADER) && uu->disc->errorf)
			(*uu->disc->errorf)(uu, uu->disc, 1, "end sequence `%s' omitted", t);
	}
	else
	{
		for (;;)
		{
			while ((c = m[sfgetc(uu->ip)]) >= 64)
				if (c != UU_IGN)
					goto pad;
			n = c;
			while ((c = m[sfgetc(uu->ip)]) >= 64)
				if (c != UU_IGN)
				{
					if (uu->disc->errorf)
						(*uu->disc->errorf)(uu, uu->disc, 1, "%c: extra input character ignored", c);
					goto pad;
				}
			n = (n << 6) | c;
			while ((c = m[sfgetc(uu->ip)]) >= 64)
				if (c != UU_IGN)
				{
					if (text)
					{
						if ((x = (n >> 4) & 0xFF) != '\r')
							sfputc(uu->op, x);
					}
					else sfputc(uu->op, n >> 4);
					goto pad;
				}
			n = (n << 6) | c;
			while ((c = m[sfgetc(uu->ip)]) >= 64)
				if (c != UU_IGN)
				{
					if (text)
					{
						if ((x = (n >> 10) & 0xFF) != '\r')
							sfputc(uu->op, x);
						if ((x = (n >> 2) & 0xFF) != '\r')
							sfputc(uu->op, x);
					}
					else
					{
						sfputc(uu->op, n >> 10);
						sfputc(uu->op, n >> 2);
					}
					goto pad;
				}
			n = (n << 6) | c;
			if (text)
			{
				if ((x = (n >> 16) & 0xFF) != '\r')
					sfputc(uu->op, x);
				if ((x = (n >> 8) & 0xFF) != '\r')
					sfputc(uu->op, x);
				if ((x = n & 0xFF) != '\r')
					sfputc(uu->op, x);
			}
			else
			{
				sfputc(uu->op, (n >> 16));
				sfputc(uu->op, (n >> 8));
				sfputc(uu->op, (n));
			}
		}
	pad:
		n = c == UU_PAD;
		while ((c = sfgetc(uu->ip)) != EOF)
			if (c == dp->pad && ++n >= 4)
				break;
		if (n < 4 && (uu->flags & UU_HEADER) && uu->disc->errorf)
			(*uu->disc->errorf)(uu, uu->disc, 1, "input end sequence `%s' omitted", dp->end);
	}
	return 0;
}

static const char	hex[] = "0123456789ABCDEFabcdef";

/*
 * quoted-printable encode input to output
 */

static int
qp_encode(register Uu_t* uu)
{
	register unsigned char*	s;
	register unsigned char*	e;
	register char*		b;
	register char*		x;
	register int		c;
	char			buf[UULINE + 1];

	b = buf;
	x = b + UULINE - 4;
	while ((s = (unsigned char*)sfgetr(uu->ip, '\n', 0)) || (s = (unsigned char*)sfgetr(uu->ip, '\n', -1)))
	{
		e = s + sfvalue(uu->ip);
		switch (*s)
		{
		case 'F':
			if ((e - s) >= 5 && strneq((char*)s, "From ", 5))
			{
				c = *s++;
				goto quote;
			}
			break;
		case '.':
			if ((e - s) == 2)
			{
				c = *s++;
				goto quote;
			}
			break;
		}
		while (s < e)
		{
			if ((c = *s++) == '\n')
			{
				*b++ = c;
				sfwrite(uu->op, buf, b - buf);
				b = buf;
				break;
			}
			if (b >= x)
			{
				*b++ = '=';
				*b++ = '\n';
				sfwrite(uu->op, buf, b - buf);
				b = buf;
			}
			if (c == ' ' || c == '\t')
			{
				if (s < e && *s != '\n')
				{
					*b++ = c;
					continue;
				}
			}
			else if (isprint(c) && !iscntrl(c) && c != '=')
			{
				*b++ = c;
				continue;
			}
		quote:
			*b++ = '=';
			*b++ = hex[(c >> 4) & 0xF];
			*b++ = hex[c & 0xF];
		}
	}
	if (b > buf)
	{
		*b++ = '=';
		*b++ = '\n';
		sfwrite(uu->op, buf, b - buf);
	}
	return 0;
}

/*
 * quoted-printable decode input to output
 */

static int
qp_decode(register Uu_t* uu)
{
	register unsigned char*	s;
	register unsigned char*	b;
	register unsigned char*	x;
	register int		c;
	register int		d;

	short			xeh[UCHAR_MAX + 1];

	for (c = 0; c < elementsof(xeh); c++)
		xeh[c] = -1;
	for (c = 0; c < elementsof(hex) - 1; c++)
		xeh[hex[c]] = c >= 16 ? (c - 6) : c;
	while (s = (unsigned char*)sfgetr(uu->ip, '\n', 1))
	{
		if (((b = s + sfvalue(uu->ip)) > s) && !*--b)
		{
			while (b > s && ((c = *(b - 1)) == ' ' || c == '\t'))
				b--;
			*b = 0;
		}
		x = b = s;
		for (;;)
		{
			switch (c = *s++)
			{
			case 0:
				*b++ = '\n';
				break;
			case '=':
				if ((c = xeh[*s++]) < 0 || (d = xeh[*s++]) < 0)
					break;
				*b++ = (c << 4) | d;
				continue;
			default:
				*b++ = c;
				continue;
			}
			break;
		}
		sfwrite(uu->op, x, b - x);
	}
	return 0;
}

/*
 * binhex based on
 *
 *	xbin Version 2.3 09/30/85
 *	Dave Johnson, Brown University Computer Science
 */

#define BX_REPEAT	0x90

#define BX_OLD		(UU_METHOD<<0)

typedef struct
{
	int		col;
	int		eof;
	int		last;
	int		repeat;
	off_t		size;
	unsigned long	crc;
	unsigned char*	qp;
	unsigned char*	qe;
	unsigned char	qbuf[3];
	char		map[UCHAR_MAX + 2];
} Bx_t;

/*
 * add c to the binhex Q format crc
 */

static int
bx_q_crc(register Bx_t* bx, int c)
{
	register int		i = 8;
	register unsigned int	k = c;
	register unsigned long	crc = bx->crc;

	while (i--)
	{
		k <<= 1;
		if ((crc <<= 1) & 0x10000)
			crc = (crc & 0xFFFF) ^ 0x1021;
		crc ^= k >> 8;
		k &= 0xFF;
	}
	bx->crc = crc;
	return c;
}

/*
 * return next binhex Q format char
 */

static int
bx_q_getc(register Uu_t* uu, register Bx_t* bx)
{
	register int		c;
	register unsigned char*	ip;
	register unsigned char*	ie;
	register unsigned char*	m;
	int			x;
	unsigned long		crc;
	unsigned char		ibuf[4];

	if (bx->repeat > 0)
	{
		bx->repeat--;
		return bx_q_crc(bx, bx->last);
	}
	if (bx->qp >= bx->qe)
	{
		if (bx->eof)
			return EOF;
		bx->qp = bx->qbuf;
		m = (unsigned char*)bx->map + 1;
		ie = (ip = ibuf) + sizeof(ibuf);
		while (ip < ie)
		{
			while ((c = m[sfgetc(uu->ip)]) >= 64)
				if (c != UU_IGN)
				{
					bx->eof = 1;
					bx->qe = bx->qbuf;
					if ((c = (ip - ibuf) - 1) <= 0)
						return EOF;
					bx->qe += c;
					break;
				}
			*ip++ = c;
		}
		ip = ibuf;
		ie = bx->qp;
		ie[0] = (ip[0] << 2) | (ip[1] >> 4);
		ie[1] = (ip[1] << 4) | (ip[2] >> 2);
		ie[2] = (ip[2] << 6) | (ip[3]     );
	}
	if ((c = *bx->qp++) == BX_REPEAT && !bx->repeat)
	{
		c = bx->last;
		crc = bx->crc;
		bx->repeat = -1;
		x = bx_q_getc(uu, bx);
		bx->crc = crc;
		switch (x)
		{
		case EOF:
			return EOF;
		case 0:
			bx->repeat = 0;
			c = BX_REPEAT;
			break;
		case 1:
			bx->repeat = 0;
			break;
		default:
			bx->repeat = x - 2;
			break;
		}
	}
	return bx->last = bx_q_crc(bx, c);
}

/*
 * return binhex Q format n byte int
 */

static long
bx_q_getn(register Uu_t* uu, register Bx_t* bx, register int n)
{
	register long	v = 0;

	while (n--)
		v = (v << 8) | bx_q_getc(uu, bx);
	return v;
}

/*
 * return binhex Q format buffer of size n
 */

static ssize_t
bx_q_gets(register Uu_t* uu, register Bx_t* bx, register char* s, size_t n)
{
	register int	c;
	register char*	e;

	e = s + n;
	while (s < e)
	{
		if ((c = bx_q_getc(uu, bx)) == EOF)
			return -1;
		*s++ = c;
	}
	return n;
}

/*
 * low level for bx_q_putc()
 */

static int
bx_q_put(register Uu_t* uu, register Bx_t* bx, register int c)
{
	register unsigned char*	p;
	register unsigned char*	m;

	*bx->qp++ = c;
	if (bx->qp >= bx->qe)
	{
		m = (unsigned char*)((Uudata_t*)uu->meth.data)->map;
		p = bx->qp = bx->qbuf;
		c = (p[0] << 16) | (p[1] << 8) | p[2];
		sfputc(uu->op, m[(c >> 18) & 0x3f]);
		sfputc(uu->op, m[(c >> 12) & 0x3f]);
		sfputc(uu->op, m[(c >>  6) & 0x3f]);
		sfputc(uu->op, m[(c      ) & 0x3f]);
		if ((bx->col += 4) >= 63)
		{
			bx->col = 0;
			sfputc(uu->op, '\n');
		}
	}
	return 0;
}

/*
 * output binhex Q format char
 */

static int
bx_q_putc(register Uu_t* uu, register Bx_t* bx, register int c)
{
	if (c == bx->last)
	{
		if (!bx->repeat++)
		{
			bx_q_put(uu, bx, c);
			bx_q_put(uu, bx, BX_REPEAT);
		}
		bx_q_crc(bx, c);
		if (bx->repeat >= 0xFF)
		{
			bx_q_put(uu, bx, bx->repeat);
			bx->repeat = 0;
			bx->last = -1;
		}
	}
	else
	{
		if (bx->repeat)
		{
			bx_q_put(uu, bx, bx->repeat);
			bx->repeat = 0;
			bx->last = -1;
		}
		if (c == BX_REPEAT)
		{
			bx_q_put(uu, bx, c);
			bx_q_put(uu, bx, 0);
			bx->last = -1;
		}
		else if (c == '\n' && (uu->flags & UU_TEXT))
		{
			bx_q_put(uu, bx, '\r');
			bx_q_crc(bx, '\r');
			bx_q_put(uu, bx, '\n');
			bx->last = -1;
		}
		else
		{
			bx_q_put(uu, bx, c);
			bx->last = c;
		}
		bx_q_crc(bx, c);
	}
	return 0;
}

/*
 * output binhex Q format n byte int
 */

static int
bx_q_putn(register Uu_t* uu, register Bx_t* bx, register unsigned long v, int n)
{
	switch (n)
	{
	case 4:	bx_q_putc(uu, bx, (v >> 24) & 0xFF);
	case 3:	bx_q_putc(uu, bx, (v >> 16) & 0xFF);
	case 2:	bx_q_putc(uu, bx, (v >>  8) & 0xFF);
	case 1:	bx_q_putc(uu, bx, (v >>  0) & 0xFF);
	}
	return 0;
}

/*
 * grab binhex header from input
 */

static int
bx_header(register Uu_t* uu)
{
	register Bx_t*	bx = (Bx_t*)(uu + 1);
	Uudata_t*	dp = (Uudata_t*)uu->meth.data;
	register int	c;
	register int	bol;
	register char*	s;
	register char*	m;
	unsigned long	crc;
	unsigned long	crx;
	char		buf[UCHAR_MAX + 2];

	if (uu->flags & UU_HEADER)
		do
		{
			if (!(s = sfgetr(uu->ip, '\n', 0)))
			{
				if (uu->disc->errorf)
					(*uu->disc->errorf)(uu, uu->disc, 2, "unknown encoding");
				return -1;
			}
		} while (*s != '(' || strncmp(s, "(This file", 10));
	bol = 1;
	for (;;)
	{
		switch (c = sfgetc(uu->ip))
		{
		case EOF:
			return -1;
		case '\n':
		case '\r':
			bol = 1;
			break;
		case ':':
			if (bol)
			{
				/*
				 * Q format
				 *
				 *	1 n name length
				 *	n s name
				 *	4 s type
				 *	4 s author
				 *	2 n flags
				 *	4 n data size
				 *	4 n resource size
				 *	2 n header checksum
				 */

				memset(s = bx->map, UU_END, sizeof(bx->map));
				for (s++, m = (char*)dp->map; c = *m; m++)
					s[c] =  m - (char*)dp->map;
				s['\n'] = UU_IGN;
				s['\r'] = UU_IGN;
				bx->qp = bx->qe = bx->qbuf + sizeof(bx->qbuf);
				if ((c = bx_q_getc(uu, bx)) == EOF)
					return -1;
				if (bx_q_gets(uu, bx, buf, c + 1) < 0)
					return -1;
				if (!uu->path)
				{
					uu->path = strdup(buf);
					uu->flags |= UU_FREEPATH;
				}
				if (bx_q_gets(uu, bx, buf, 4) < 0)
					return -1;
				if (bx_q_gets(uu, bx, buf, 4) < 0)
					return -1;
				bx_q_getn(uu, bx, 2);
				bx->size = bx_q_getn(uu, bx, 4);
				bx_q_getn(uu, bx, 4);
				bx_q_crc(bx, 0);
				bx_q_crc(bx, 0);
				crc = bx->crc;
				crx = bx_q_getn(uu, bx, 2);
				if (crc != crx)
				{
					if (uu->disc->errorf)
						(*uu->disc->errorf)(uu, uu->disc, 2, "%s format header checksum mismatch", uu->meth.name);
					return -1;
				}
				return 0;
			}
			break;
		case '#':
			if (bol)
			{
				/*
				 * old format
				 */

				sfungetc(uu->ip, c);
				uu->flags |= BX_OLD;

				/*
				 * #<TYPE><AUTH>$<flag>
				 */

				if (!sfgetr(uu->ip, '\n', 0))
					return -1;
				return 0;
			}
			break;
		default:
			bol = 0;
			break;
		}
	}
	return -1;
}

/*
 * old binhex line decode
 */

static int
bx_o_decode(register Uu_t* uu, Bx_t* bx, char* buf, register size_t n)
{
	register int		c;
	register int		d;
	register unsigned long	crc = bx->crc;
	register unsigned char*	m = (unsigned char*)bx->map;
	register char*		t = (char*)hex;
	register unsigned char*	s = (unsigned char*)buf;

	memset(m, UU_END, sizeof(bx->map));
	for (c = 0; c < elementsof(hex); c++)
		m[t[c]] = c;
	n <= 2;
	while (n--)
	{
		if ((c = m[*s++]) == UU_END || (d = m[*s++]) == UU_END)
			return -1;
		crc += c = (c << 4) | d;
		sfputc(uu->op, c);
	}
	bx->crc = crc;
	return 0;
}

/*
 * old binhex compressed line decode
 */

#define BX_O_MASK(c)	(((c)-0x20)&0x3F)
#define BX_O_CRC(s,c)	((s=(s+c)&0xFF),(s=((s<<3)&0xFF)|(s>>13)))

static int
bx_c_decode(register Uu_t* uu, Bx_t* bx, register char* s, size_t n)
{
	register int		c;
	register int		oc;
	register unsigned long	crc;
	char*			e;
	int			ic;
	char			buf[SF_BUFSIZE];

	crc = bx->crc;
	oc = (BX_O_MASK(s[0]) << 2) | (BX_O_MASK(s[1]) >> 4);
	ic = ((oc / 3) + 1) * 4;
	if (ic > SF_BUFSIZE)
		ic = SF_BUFSIZE;
	if (n > SF_BUFSIZE)
		n = SF_BUFSIZE;
	memcpy(buf, s, n);
	s = buf + n;
	e = buf + ic;
	while (s < e)
		*s++ = ' ';
	s = buf;
	while ((oc -= 3) >= 0)
	{
		c = (BX_O_MASK(s[0]) << 2) | (BX_O_MASK(s[1]) >> 4);
		BX_O_CRC(crc, c);
		sfputc(uu->op, c);
		c = (BX_O_MASK(s[1]) << 4) | (BX_O_MASK(s[2]) >> 2);
		BX_O_CRC(crc, c);
		sfputc(uu->op, c);
		c = (BX_O_MASK(s[2]) << 6) | (BX_O_MASK(s[3])     );
		BX_O_CRC(crc, c);
		sfputc(uu->op, c);
		s += 4;
	}
	bx->crc = crc;
	return 0;
}

/*
 * binhex decode input to output
 */

static int
bx_decode(register Uu_t* uu)
{
	register Bx_t*	bx = (Bx_t*)(uu + 1);
	register off_t	n;
	register int	c;
	register char*	s;
	unsigned long	crc;
	int		(*decode)(Uu_t*, Bx_t*, char*, size_t);

	crc = 0x10000;
	bx->crc = 0;
	if (uu->flags & BX_OLD)
	{
		decode = bx_o_decode;
		c = 0;
		while (s = sfgetr(uu->ip, '\n', 0))
			if (*s++ == '*' && *s++ == '*' && *s++ == '*')
			{
				switch (*s)
				{
				case 'C':
					switch (s[1])
					{
					case 'O':
						if (c || strncmp(s, "COMPRESSED", 10))
							continue;
						decode = bx_c_decode;
						continue;
					case 'H':
						if (decode == bx_c_decode || strncmp(s, "CHECKSUM:", 9))
							continue;
						crc = bx->crc & 0xFF;
						bx->crc = strtoul(s + 9, NiL, 16) & 0xFF;
						break;
					case 'R':
						if (decode == bx_o_decode || strncmp(s, "CRC:", 4))
							continue;
						crc = bx->crc & 0xFFFF;
						bx->crc = strtoul(s + 4, NiL, 16) & 0xFFFF;
						break;
					default:
						continue;
					}
					break;
				case 'D':
					if (strncmp(s, "DATA", 4))
						continue;
					while (s = sfgetr(uu->ip, '\n', 0))
					{
						if (strneq(s, "***END", 6))
							break;
						if ((*decode)(uu, bx, s, sfvalue(uu->ip) - 1) < 0)
							return -1;
					}
					break;
				case 'R':
					if (c || strncmp(s, "RESOURCE", 8))
						continue;
					c = 1;
					continue;
				default:
					continue;
				}
				break;
			}
	}
	else
	{
		if ((n = bx->size) > 0)
			while (n--)
			{
				if ((c = bx_q_getc(uu, bx)) == EOF)
					return -1;
				sfputc(uu->op, c);
			}

		/*
		 * check the header crc
		 */

		bx_q_crc(bx, 0);
		bx_q_crc(bx, 0);
		crc = bx->crc;
		bx->crc = bx_q_getn(uu, bx, 2);
	}
	if (crc != bx->crc)
	{
		if (uu->disc->errorf)
		{
			if (crc == 0x10000)
				(*uu->disc->errorf)(uu, uu->disc, 2, "%s format checksum missing", uu->meth.name);
			else
				(*uu->disc->errorf)(uu, uu->disc, 2, "%s format data checksum mismatch", uu->meth.name);
		}
		return -1;
	}
	return 0;
}

/*
 * binhex encode input to output
 */

static int
bx_encode(register Uu_t* uu)
{
	register Bx_t*		bx = (Bx_t*)(uu + 1);
	register unsigned char*	m;
	register int		c;
	register int		i;
	struct stat		st;

	bx->last = -1;
	bx->qp = bx->qbuf;
	bx->qe = bx->qbuf + sizeof(bx->qbuf);
	if (fstat(sffileno(uu->ip), &st))
		st.st_size = 0;
	sfprintf(uu->op, "(This file must be converted with BinHex 4.0)\n:");
	if (!(m = (unsigned char*)uu->path))
		m = (unsigned char*)"-";
	if ((c = strlen((char*)m)) > 63)
		c = 63;
	bx_q_putc(uu, bx, c);
	for (i = 0; i < c; i++)
		bx_q_putc(uu, bx, m[i]);
	bx_q_putc(uu, bx, 0);
	bx_q_putn(uu, bx, 0, 4);
	bx_q_putn(uu, bx, 0, 4);
	bx_q_putn(uu, bx, 0xF800, 2);
	bx_q_putn(uu, bx, st.st_size, 4);
	bx_q_putn(uu, bx, 0, 4);
	bx_q_crc(bx, 0);
	bx_q_crc(bx, 0);
	bx_q_putn(uu, bx, bx->crc, 2);
	bx->crc = 0;
	while ((c = sfgetc(uu->ip)) != EOF)
		bx_q_putc(uu, bx, c);
	bx_q_crc(bx, 0);
	bx_q_crc(bx, 0);
	bx_q_putn(uu, bx, bx->crc, 2);
	bx->crc = 0;
	bx_q_crc(bx, 0);
	bx_q_crc(bx, 0);
	bx_q_putn(uu, bx, bx->crc, 2);
	while (bx->qp != bx->qbuf)
		bx_q_putc(uu, bx, 0);
	sfputc(uu->op, ':');
	sfputc(uu->op, '\n');
	return 0;
}

static Uudata_t	uu_posix =
{
	"end",
	0,
	0,
	UU_HEADER|UU_LENGTH,
	0,
	" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
};

static const Uudata_t	uu_ucb =
{
	"end",
	0,
	0156,
	UU_HEADER|UU_LENGTH,
	0,
	"`!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
};

static const Uudata_t	uu_base64 =
{
	"====",
	'=',
	0,
	0,
	0,
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
};

static const Uudata_t	uu_bx =
{
	0,
	0,
	0,
	UU_HEADER|UU_HEADERMUST,
	sizeof(Bx_t),
	"!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr"
};

static const Uumeth_t	methods[] =
{

{
	"posix",			"uuencode",	"",	  
	uu_header,	uu_encode,	uu_decode,	(void*)&uu_posix
},
{
	"ucb",				"bsd",		"",	  
	uu_header,	uu_encode,	uu_decode,	(void*)&uu_ucb
},
{
	"mime",				"base64",	"-base64",
	0,		uu_encode,	uu_decode,	(void*)&uu_base64
},
{
	"quoted-printable",		"qp",		"",	  
	0,		qp_encode,	qp_decode,	0
},
{
	"binhex",			"mac-binhex",	"",	  
	bx_header,	bx_encode,	bx_decode,	(void*)&uu_bx
},

{ 0 }

};

/*
 * list the method names/alternates on fp
 */

int
uulist(Sfio_t* fp)
{
	register const Uumeth_t*	mp;

	sfprintf(fp, "ENCODING          ALIAS\n");
	for (mp = methods; mp->name; mp++)
		sfprintf(fp, "%-17s %s\n", mp->name, mp->alias);
	return 0;
}

/*
 * return method pointer given name
 */

Uumeth_t*
uumeth(const char* name)
{
	register const Uumeth_t*	mp;
	register int			c;
	register const char*		v;
	register int			vl;
	const char*			np;

	/*
	 * first entry is the default
	 */

	if (!name || !*name)
	{
		((Uudata_t*)methods->data)->flags |= UU_DEFAULT;
		return (Uumeth_t*)methods;
	}
	if ((*name == 'x' || *name == 'X') && *(name + 1) == '-')
		name += 2;
	if (*name == 'b' && !strncasecmp(name, UU_BEGIN, UU_BEGIN_LEN))
	{
		/*
		 * id prefix match
		 */

		if ((c = *(name += UU_BEGIN_LEN)) == ' ')
			return (Uumeth_t*)methods;
		for (mp = methods; mp->name; mp++)
			if (*mp->id == c && !strncasecmp(name, mp->id, strlen(mp->id)))
				return (Uumeth_t*)mp;
		if (c == '-')
			return (Uumeth_t*)methods;
	}
	else
	{
		c = *name;
		for (v = name + strlen(name); v > name && (isdigit(*(v - 1)) || *(v - 1) == '.'); v--);
		vl = *v ? (v - name) : 0;

		/*
		 * exact name or alias match
		 */
	
		for (;;)
		{
			for (mp = methods; mp->name; mp++)
				if (*mp->name == c && (!strcasecmp(name, mp->name) || vl && !strncasecmp(name, mp->name, vl)) ||
			    	mp->alias && *mp->alias == c && (!strcasecmp(name, mp->alias) || vl && !strncasecmp(name, mp->alias, vl)))
					return (Uumeth_t*)mp;
			np = name;
			if (!(name = strchr(name, '/')))
				break;
			if (((c = *++name) == 'x' || *name == 'X') && *(name + 1) == '-')
				c = *(name += 2);
			if (vl)
				vl -= (name - np);
		}
	
		/*
		 * first char name match
		 */
	
		for (mp = methods; mp->name; mp++)
			if (*mp->name == c)
				return (Uumeth_t*)mp;
	}
	return 0;
}

/*
 * open an encode/decode handle
 */

Uu_t*
uuopen(Uudisc_t* disc, Uumeth_t* meth)
{
	register Uu_t*		uu;
	register Uudata_t*	data;
	int			extra;

	if (data = (Uudata_t*)meth->data)
		extra = data->size;
	else
		extra = 0;
	if (!(uu = newof(0, Uu_t, 1, extra)))
		return 0;
	uu->id = lib;
	uu->disc = disc;
	uu->meth = *meth;
	return uu;
}

/*
 * close an encode/decode handle
 */

int
uuclose(Uu_t* uu)
{
	if (!uu)
		return -1;
	if ((uu->flags & UU_FREEPATH) && uu->path)
		free(uu->path);
	free(uu);
	return 0;
}

/*
 * common encode/decode tail
 */

static ssize_t
uuop(register Uu_t* uu, Uu_f fun)
{
	int		n;
	ssize_t		r;
	Sfoff_t		p;

	if (uu->count != SF_UNBOUND && ((p = sfseek(uu->ip, (Sfoff_t)0, SEEK_CUR)) < 0 || !(uu->ip = sfdcsubstream(NiL, uu->lp = uu->ip, p, uu->count))))
	{
		if (uu->disc->errorf)
			(*uu->disc->errorf)(uu, uu->disc, 2, "cannot initialize substream at %I*d for %I*d bytes", sizeof(p), p, sizeof(uu->count), uu->count);
		return -1;
	}
	p = sfseek(uu->op, (Sfoff_t)0, SEEK_CUR);
	n = (*fun)(uu);
	if (uu->lp)
	{
		sfclose(uu->ip);
		uu->ip = uu->lp;
		uu->lp = 0;
	}
	if (n < 0)
		r = -1;
	else if (sfsync(uu->op) || sferror(uu->op))
	{
		r = -1;
		if (uu->disc->errorf)
			(*uu->disc->errorf)(uu, uu->disc, 2, "write error");
	}
	else if (sferror(uu->ip))
	{
		r = -1;
		if (uu->disc->errorf)
			(*uu->disc->errorf)(uu, uu->disc, 2, "read error");
	}
	else
		r = sfseek(uu->op, (Sfoff_t)0, SEEK_CUR) - p;
	if (uu->flags & UU_CLOSEOUT)
	{
		uu->flags &= ~UU_CLOSEOUT;
		sfclose(uu->op);
	}
	return r;
}

/*
 * encode n bytes (or all if SF_UNBOUND) from ip to op
 */

ssize_t
uuencode(register Uu_t* uu, Sfio_t* ip, Sfio_t* op, size_t n, const char* path)
{
	if (!uu->meth.encodef)
	{
		if (uu->disc->errorf)
			(*uu->disc->errorf)(uu, uu->disc, 2, "%s format encoding not supported", uu->meth.name);
		return -1;
	}
	if (!(uu->ip = ip) || !(uu->op = op))
		return -1;
	uu->count = n;
	if ((uu->flags & UU_FREEPATH) && uu->path)
		free(uu->path);
	uu->path = (char*)path;
	uu->flags = uu->disc->flags;
	return uuop(uu, uu->meth.encodef);
}

/*
 * decode n bytes (or all if SF_UNBOUND) from ip to op
 */

ssize_t
uudecode(register Uu_t* uu, Sfio_t* ip, Sfio_t* op, size_t n, const char* path)
{
	register char*	s;
	unsigned char*	m;
	int		c;
	int		headerpath;
	Uudata_t*	data;
	const Uumeth_t*	mp;
	char		map[UCHAR_MAX + 2];

	if (!uu->meth.decodef)
	{
		if (uu->disc->errorf)
			(*uu->disc->errorf)(uu, uu->disc, 2, "%s format decoding not supported", uu->meth.name);
		return -1;
	}
	if (!(uu->ip = ip))
		return -1;
	uu->op = op;
	uu->count = n;
	if ((uu->flags & UU_FREEPATH) && uu->path)
		free(uu->path);
	uu->path = (char*)path;
	uu->flags = uu->disc->flags;
	data = (Uudata_t*)uu->meth.data;
	if (((uu->flags & UU_HEADER) || data && (data->flags & UU_HEADERMUST)) && uu->meth.headerf)
	{
		if ((*uu->meth.headerf)(uu))
			return -1;
		headerpath = 1;
	}
	else
	{
		headerpath = 0;
		if (data && (data->flags & UU_DEFAULT) && (c = sfgetc(uu->ip)) != EOF)
		{
			sfungetc(uu->ip, c);
			for (mp = methods; ((Uudata_t*)mp->data)->flags & UU_LENGTH; mp++)
			{
				m = uu_map((Uudata_t*)mp->data, map);
				if (m[c] > 0 && m[c] < (UUIN * UUCHUNK + 1))
					break;
			}
			if (mp > methods)
			{
				uu->meth = *mp;
				if (uu->disc->errorf)
					(*uu->disc->errorf)(uu, uu->disc, 1, "assuming %s encoding", mp->name);
			}
		}
	}
	if (!uu->op)
	{
		if (!uu->path && headerpath)
		{
			if (uu->disc->errorf)
				(*uu->disc->errorf)(uu, uu->disc, 2, "%s format header has no output file name", uu->meth.name);
			return -1;
		}
		if (!uu->path || !*uu->path || streq(uu->path, "-") || streq(uu->path, "/dev/stdout"))
			uu->op = sfstdout;
		else
		{
			if (headerpath && (uu->flags & UU_LOCAL))
			{
				for (s = uu->path; *s; s++)
					if (isspace(*s) || iscntrl(*s) || !isprint(*s) || *s == '/' || *s == '\\')
						*s = '_';
				s = uu->path;
				if (isalpha(s[0]) && s[1] == ':')
					s[1] = '_';
			}
			if (!(uu->op = sfopen(NiL, uu->path, "w")))
			{
				if (uu->disc->errorf)
					(*uu->disc->errorf)(uu, uu->disc, 2, "%s: cannot write", uu->path);
				return -1;
			}
			uu->flags |= UU_CLOSEOUT;
		}
	}
	return uuop(uu, uu->meth.decodef);
}
