/************************************************************************/
/*									*/
/*  Simple io streams translating from/to the MIME (RFC 2045)		*/
/*  'Quoted Printable' content transfer encoding.			*/
/*									*/
/************************************************************************/

#   include	"appUtilConfig.h"

#   include	<stdlib.h>

#   include	"sioQuotedPrintable.h"
#   include	<appDebugon.h>

/************************************************************************/
/*									*/
/*  For Hexadecimal decoding.						*/
/*									*/
/************************************************************************/

static const unsigned char	SioHexDigits[]= "0123456789ABCDEF";
static unsigned char		SioHexIndices[256];

/************************************************************************/
/*									*/
/*  Input.								*/
/*									*/
/*  1)  Contrary to the rfc 2045 standard, decoding stops at a single	*/
/*	'-' character at the beginning of a line. It is up to the	*/
/*	caller to read a series of chunks of input that are corrupted	*/
/*	with '-' signs to restart decoding after every '-'.		*/
/*									*/
/************************************************************************/

typedef struct QuotedInputStream
    {
    SimpleInputStream *		qisSisQuoted;
    int				qisExhausted;
    int				qisColumn;
    } QuotedInputStream;

static int sioInQuotedReadBytes(	void *		voidqis,
					unsigned char *	buffer,
					unsigned int	count )
    {
    QuotedInputStream *		qis= (QuotedInputStream *)voidqis;
    int				done= 0;

    if  ( qis->qisExhausted )
	{ return -1;	}

    while( done < count )
	{
	int			c;

	c= sioInGetByte( qis->qisSisQuoted );
	if  ( c == EOF )
	    { qis->qisExhausted= 1; break;	}

	if  ( c == '-' && qis->qisColumn == 0 )
	    {
	    sioInUngetLastRead( qis->qisSisQuoted );
	    qis->qisExhausted= 1; break;
	    }

	if  ( c == '\r' )
	    {
	    c= sioInGetByte( qis->qisSisQuoted );
	    if  ( c != '\n' )
		{ *(buffer++)= '\r'; done++;	}

	    sioInUngetLastRead( qis->qisSisQuoted );

	    continue;
	    }
	if  ( c == '\n' )
	    { *(buffer++)= c; done++; qis->qisColumn= 0; continue; }

	if  ( c == '=' )
	    {
	    int		x1;
	    int		x2;

	    x1= sioInGetByte( qis->qisSisQuoted );

	    switch( x1 )
		{
		case '\n':
		    continue;

		case '\r':
		    x2= sioInGetByte( qis->qisSisQuoted );
		    if  ( x2 != '\n' )
			{
			CDEB(x2);
			if  ( x2 != EOF )
			    { sioInUngetLastRead( qis->qisSisQuoted ); }
			}
		    continue;

		case EOF:
		    XDEB(x1);
		    qis->qisExhausted= 1;
		    return -1;

		default:
		    XDEB(x1);
		    sioInUngetLastRead( qis->qisSisQuoted );
		    *(buffer++)= c; done++;
		    break;
		}
	    }

	*(buffer++)= c; done++; qis->qisColumn++; continue;
	}

    return done;
    }

/************************************************************************/
/*									*/
/*  Close a 'Quoted Printable' input stream.				*/
/*									*/
/*  1)  Drain the underlaying stream upto a '-' at the beginning of.	*/
/*	a line. This is a violation of rfc 2045. See the comment above.	*/
/*									*/
/************************************************************************/

static int sioInQuotedClose(	void *	voidqis )
    {
    int				rval= 0;
    QuotedInputStream *		qis= (QuotedInputStream *)voidqis;

    while( ! qis->qisExhausted )
	{
	int		res;
	unsigned char	scratch[SIOsizBUF];

	res= sioInQuotedReadBytes( voidqis, scratch, SIOsizBUF );
	if  ( res < 0 )
	    { LDEB(res); rval= -1; break;	}
	}

    free( qis );

    return rval;
    }

SimpleInputStream * sioInQuotedPrintableOpen(
					SimpleInputStream *	sisQuoted )
    {
    SimpleInputStream *	sis;
    QuotedInputStream *	qis;

    if  ( SioHexIndices[0] == 0 )
	{
	unsigned int	i;

	for ( i= 0; i < sizeof(SioHexIndices); i++ )
	    { SioHexIndices[i]= 0xff;	}

	i= 0;
	while( SioHexDigits[i] )
	    { SioHexIndices[SioHexDigits[i]]= i; i++;	}
	}

    qis= (QuotedInputStream *)malloc( sizeof(QuotedInputStream) );
    if  ( ! qis )
	{ XDEB(qis); return (SimpleInputStream *)0;	}

    qis->qisSisQuoted= sisQuoted;
    qis->qisExhausted= 0;
    qis->qisColumn= 0;

    sis= sioInOpen( (void *)qis, sioInQuotedReadBytes, sioInQuotedClose );

    if  ( ! sis )
	{ XDEB(sis); free( qis ); return (SimpleInputStream *)0; }

    return sis;
    }

/************************************************************************/
/*									*/
/*  Output.								*/
/*									*/
/************************************************************************/

typedef struct QuotedOutputStream
    {
    SimpleOutputStream *	qosSosQuoted;
    int				qosColumn;
    int				qosAfterWhiteSpace;
    } QuotedOutputStream;

/************************************************************************/
/*									*/
/*  Close a 'Quoted Printable' output stream.				*/
/*									*/
/*  1)  If necessary finish the current line of output.			*/
/*									*/
/************************************************************************/

static int sioOutQuotedClose(	void *	voidqos )
    {
    int				rval= 0;
    QuotedOutputStream *	qos= (QuotedOutputStream *)voidqos;

    /*  1  */
    if  ( qos->qosColumn > 0 )
	{
	if  ( sioOutPutByte( '=',  qos->qosSosQuoted ) < 0 )
	    { rval= -1;	}
	if  ( sioOutPutByte( '\r', qos->qosSosQuoted ) < 0 )
	    { rval= -1;	}
	if  ( sioOutPutByte( '\n', qos->qosSosQuoted ) < 0 )
	    { rval= -1;	}

	qos->qosColumn= 0;
	}

    free( qos );
    return rval;
    }

/************************************************************************/
/*									*/
/*  Write bytes in Quoted Printable format.				*/
/*									*/
/************************************************************************/

static int sioOutQuotedBreakLine(	QuotedOutputStream *	qos )
    {
    if  ( sioOutPutByte( '=',  qos->qosSosQuoted ) < 0 )
	{ return -1;	}
    if  ( sioOutPutByte( '\r', qos->qosSosQuoted ) < 0 )
	{ return -1;	}
    if  ( sioOutPutByte( '\n', qos->qosSosQuoted ) < 0 )
	{ return -1;	}

    qos->qosColumn= 0;
    qos->qosAfterWhiteSpace= 0;

    return 0;
    }

static int sioOutQoutedEmitHex(	QuotedOutputStream *	qos,
					int			c )
    {
    if  ( sioOutPutByte( '=', qos->qosSosQuoted ) < 0 )
	{ return -1;	}
    if  ( sioOutPutByte( SioHexDigits[( c >> 4 ) & 0x0f], qos->qosSosQuoted ) < 0 )
	{ return -1;	}
    if  ( sioOutPutByte( SioHexDigits[( c >> 0 ) & 0x0f], qos->qosSosQuoted ) < 0 )
	{ return -1;	}

    qos->qosColumn += 3;
    qos->qosAfterWhiteSpace= 0;

    return 0;
    }

static int sioOutQuotedWriteBytes(	void *			voidqos,
					const unsigned char *	buffer,
					int			count )
    {
    QuotedOutputStream *	qos= (QuotedOutputStream *)voidqos;
    int				done= 0;

    while( done < count )
	{
	if  ( *buffer == '\r' && qos->qosAfterWhiteSpace )
	    {
	    if  ( qos->qosColumn > 72 )
		{ sioOutQuotedBreakLine( qos );	}

	    sioOutQoutedEmitHex( qos, *buffer );

	    buffer++; done++; continue;
	    }

	if  ( *buffer == '\n' && qos->qosAfterWhiteSpace )
	    {
	    if  ( qos->qosColumn > 72 )
		{ sioOutQuotedBreakLine( qos );	}

	    sioOutQoutedEmitHex( qos, *buffer );
	    sioOutQuotedBreakLine( qos );

	    buffer++; done++; continue;
	    }

	if  ( *buffer == '='				||
	      *buffer < 32				||	/* ' '	*/
	      *buffer > 126				||	/* '~'	*/
	      ( *buffer == '-' && qos->qosColumn == 0 )	)
	    {
	    if  ( qos->qosColumn > 72 )
		{ sioOutQuotedBreakLine( qos );	}

	    sioOutQoutedEmitHex( qos, *buffer );

	    buffer++; done++; continue;
	    }

	if  ( qos->qosColumn > 75 )
	    { sioOutQuotedBreakLine( qos );	}

	if  ( *buffer == ' ' || *buffer == '\t' )
	    { qos->qosAfterWhiteSpace= 1;	}
	else{ qos->qosAfterWhiteSpace= 0;	}

	if  ( sioOutPutByte( *buffer, qos->qosSosQuoted ) < 0 )
	    { LDEB(1); return -1;	}

	qos->qosColumn += 1;
	buffer++; done++; continue;
	}

    return count;
    }

SimpleOutputStream * sioOutQuotedPrintableOpen(
				    SimpleOutputStream *	sosQuoted )
    {
    SimpleOutputStream *	sos;
    QuotedOutputStream *	qos;

    qos= (QuotedOutputStream *)malloc( sizeof(QuotedOutputStream) );
    if  ( ! qos )
	{ XDEB(qos); return (SimpleOutputStream *)0;	}

    qos->qosSosQuoted= sosQuoted;
    qos->qosColumn= 0;
    qos->qosAfterWhiteSpace= 0;

    sos= sioOutOpen( (void *)qos, sioOutQuotedWriteBytes, sioOutQuotedClose );

    if  ( ! sos )
	{ XDEB(sos); free( qos ); return (SimpleOutputStream *)0; }

    return sos;
    }
