/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 2003-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>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                                                                      *
***********************************************************************/
#pragma prototyped

/*
 * quoted printable coder
 */

#include <codex.h>
#include <ctype.h>

#define LINE		76
#define BUFFER		SF_BUFSIZE

typedef struct State_s
{
	Codex_t*	codex;

	unsigned char*	bp;
	unsigned char*	be;

	unsigned char*	pp;
	unsigned char	prv[5];

	short		xxx;
	short		xeh[UCHAR_MAX+1];

	int		col;

	unsigned char	buf[LINE + BUFFER + 1];
} State_t;

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

#define GETCHAR(p)	((p)->bp < (p)->be ? (int)*(p)->bp++ : fill(p))
#define PUTCHAR(p,c)	((p)->bp < (p)->be ? (int)(*(p)->bp++=(c)) : flush(p,c))

static int
fill(State_t* state)
{
	ssize_t	r;

	state->bp = state->buf + LINE;
	if ((r = sfrd(state->codex->sp, state->bp, BUFFER, &state->codex->sfdisc)) <= 0)
	{
		state->be = state->bp;
		return EOF;
	}
	state->be = state->bp + r;
	return *state->bp++;
}

static int
flush(register State_t* state, int c)
{
	size_t	n;

	if (c < 0 && state->col)
	{
		state->col = 0;
		PUTCHAR(state, '=');
		PUTCHAR(state, '\n');
	}
	if (state->bp && (n = state->bp - state->buf) && sfwr(state->codex->sp, state->buf, n, &state->codex->sfdisc) != n)
		return EOF;
	state->be = (state->bp = state->buf) + sizeof(state->buf);
	if (c >= 0)
		*state->bp++ = c;
	return 0;
}

static int
qp_open(Codex_t* p, char* const args[], Codexnum_t flags)
{
	register State_t*	state;
	register int		i;

	if (!(state = newof(0, State_t, 1, 0)))
	{
		if (p->disc->errorf)
			(*p->disc->errorf)(NiL, p->disc, 2, "out of space");
		return -1;
	}
	if (flags & CODEX_DECODE)
	{
		for (i = -1; i < elementsof(state->xeh); i++)
			state->xeh[i] = -1;
		for (i = 0; i < elementsof(hex) - 1; i++)
			state->xeh[hex[i]] = i >= 16 ? (i - 6) : i;
	}
	p->data = state;
	state->codex = p;
	return 0;
}

static ssize_t
qp_read(Sfio_t* sp, void* buf, size_t n, Sfdisc_t* disc)
{
	register State_t*	state = (State_t*)CODEX(disc)->data;
	register char*		s = (char*)buf;
	register char*		e = s + n;
	register char*		x;
	register int		c;
	register int		d;

	x = 0;
	while (s < e)
	{
		switch (c = GETCHAR(state))
		{
		case '=':
			if ((c = GETCHAR(state)) == '\n')
				continue;
			if ((d = state->xeh[c]) != EOF && (c = state->xeh[GETCHAR(state)]) != EOF)
			{
				c |= (d << 4);
				x = 0;
				break;
			}
			/*FALLTHROUGH*/
		case EOF:
			return s - (char*)buf;
		case '\n':
			if (x)
			{
				s = x;
				x = 0;
			}
			break;
		case ' ':
		case '\t':
		case '\r':
			if (!x)
				x = s;
			break;
		default:
			x = 0;
			break;
		}
		*s++ = c;
	}
	if (x)
		while (s > x && state->bp > state->buf)
			*--state->bp = *--s;
	return s - (char*)buf;
}

static ssize_t
qp_write(Sfio_t* sp, const void* buf, size_t n, Sfdisc_t* disc)
{
	register State_t*	state = (State_t*)CODEX(disc)->data;
	register unsigned char*	s;
	register unsigned char*	e;
	register int		c;
	register int		col;

 again:
	if (state->pp)
	{
		s = state->prv;
		e = state->pp;
		state->col = 0;
	}
	else
	{
		s = (unsigned char*)buf;
		e = s + n;
		col = state->col;
	}
	for (;;)
	{
		if (s >= e)
		{
			if (state->pp)
			{
				state->pp = 0;
				state->col = col;
				goto again;
			}
			break;
		}
		c = *s++;
		if (!col++)
		{
			if (c == 'F')
			{
				if ((e - s) < 4)
				{
					s--;
					col--;
					state->pp = state->prv;
					for (c = 0; c < (e - s); ++c)
						*state->pp++ = s[c];
					break;
				}
				else if (s[0] == 'r' && s[1] == 'o' && s[2] == 'm' && s[3] == ' ')
					goto quote;
			}
			else if (c == '.')
			{
				if ((e - s) < 1)
				{
					s--;
					col--;
					state->pp = state->prv;
					*state->pp++ = c;
					break;
				}
				else if (s[0] == '\r' || s[0] == '\n')
					goto quote;
			}
		}
		if (c == '\n')
		{
			col = 0;
			PUTCHAR(state, c);
			continue;
		}
		else if (col >= (LINE - 4))
		{
			col = 0;
			PUTCHAR(state, '=');
			PUTCHAR(state, '\n');
		}
		if (c == ' ' || c == '\t')
		{
			if ((e - s) < 1)
			{
				s--;
				col--;
				state->pp = state->prv;
				*state->pp++ = c;
				break;
			}
			else if (s[0] == '\r' || s[0] == '\n')
				goto quote;
			else
			{
				if (c == '\t')
					col |= 7;
				PUTCHAR(state, c);
				continue;
			}
		}
		else if (isprint(c) && !iscntrl(c) && c != '=')
		{
			PUTCHAR(state, c);
			continue;
		}
	quote:
		col += 2;
		PUTCHAR(state, '=');
		PUTCHAR(state, hex[(c >> 4) & 0xF]);
		PUTCHAR(state, hex[c & 0xF]);
	}
	state->col = col;
	return n;
}

static int
qp_sync(Codex_t* p)
{
	return flush((State_t*)p->data, -1);
}

Codexmeth_t	codex_qp =
{
	"qp",
	"quoted printable encoding.",
	"[+(version)?codex-qp (AT&T Labs Research) 1998-11-11]"
	"[+(author)?Glenn Fowler <gsf@research.att.com>]",
	CODEX_DECODE|CODEX_ENCODE|CODEX_UU,
	0,
	0,
	qp_open,
	0,
	0,
	0,
	qp_read,
	qp_write,
	qp_sync,
	0,
	0,
	0,
	0,
	CODEXNEXT(codex_qp_next)
};

CODEXLIB(&codex_qp)
