/*
 * buffer.c
 */

#include <ctype.h>
#include <strings.h>
#include "pgpdump.h"

typedef char * cast_t;

private int line_not_blank(byte *);
private int read_binary(byte *, unsigned int);
private int read_radix64(byte *, unsigned int);
private int decode_radix64(byte *, unsigned int);

#ifdef HAVE_LIBZ
#include <zlib.h>
private int inflate_gzip(byte *, unsigned int);
private z_stream z;
#endif  /* HAVE_LIBZ */

#ifdef HAVE_LIBBZ2
#include <bzlib.h>
private int inflate_bzip2(byte *, unsigned int);
private bz_stream bz;
#endif  /* HAVE_LIBBZ2 */

#define NUL '\0'
#define CR  '\r'
#define LF  '\n'

#define OOB -1
#define EOP -2
#define ELF -3
#define ECR -4
#define SPC -5

private unsigned int MAGIC_COUNT = 0;
private unsigned int AVAIL_COUNT = 0;
private byte *NEXT_IN = NULL;

private int (*d_func1)(byte *, unsigned int);
private int (*d_func2)(byte *, unsigned int);
private int (*d_func3)(byte *, unsigned int);

private byte tmpbuf[BUFSIZ];
private byte d_buf1[BUFSIZ];
private byte d_buf2[BUFSIZ];
private byte d_buf3[BUFSIZ];

private signed char
base256[] = {
	OOB,OOB,OOB,OOB, OOB,OOB,OOB,OOB, OOB,SPC,ELF,OOB, OOB,ECR,OOB,OOB,

	OOB,OOB,OOB,OOB, OOB,OOB,OOB,OOB, OOB,OOB,OOB,OOB, OOB,OOB,OOB,OOB,
      /*                                                -                / */
	SPC,OOB,OOB,OOB, OOB,OOB,OOB,OOB, OOB,OOB,OOB, 62, OOB,OOB,OOB, 63,
      /*  0   1   2   3    4   5   6   7    8   9                =        */
	 52, 53, 54, 55,  56, 57, 58, 59,  60, 61,OOB,OOB, OOB,EOP,OOB,OOB,
      /*      A   B   C    D   E   F   G    H   I   J   K    L   M   N   O*/
	OOB,  0,  1,  2,   3,  4,  5,  6,   7,  8,  9, 10,  11, 12, 13, 14,
      /*  P   Q   R   S    T   U   V   W    X   Y   Z                     */
	 15, 16, 17, 18,  19, 20, 21, 22,  23, 24, 25,OOB, OOB,OOB,OOB,OOB,
      /*      a   b   c    d   e   f   g    h   i   j   k    l   m   n   o*/
	OOB, 26, 27, 28,  29, 30, 31, 32,  33, 34, 35, 36,  37, 38, 39, 40,
      /*  p   q   r   s    t   u   v   w    x   y   z                     */
	 41, 42, 43, 44,  45, 46, 47, 48,  49, 50, 51,OOB, OOB,OOB,OOB,OOB,
};

private int
line_not_blank(byte *s)
{
	while (isspace(*s)) {
		if (*s == CR || *s == LF)
			return NO;
		s++;
	}
	return YES;
}

private int
read_binary(byte *p, unsigned int max)
{
	if (feof(stdin)) {
		exit(EXIT_SUCCESS);
	}

	size_t ret = fread(p, sizeof(byte), max, stdin);

	if (ferror(stdin)) {
		warn_exit("error in read_binary");
	}

	return ret;
}

private int
read_radix64(byte *p, unsigned int max)
{
	static int done = NO, found = NO;
	int c, d, out = 0, lf = 0, cr = 0;
	byte *lim = p + max;
	
	if (done == YES) return 0;

	if (found == NO) {

	again:
		do {
			if (fgets((cast_t)tmpbuf, BUFSIZ, stdin) == NULL)
				warn_exit("can't find PGP armor boundary.");
		} while (strncmp("-----BEGIN PGP", (cast_t)tmpbuf, 14) != 0);

		if (strncmp("-----BEGIN PGP SIGNED", (cast_t)tmpbuf, 21) == 0)
			goto again;

		do {
			if (fgets((cast_t)tmpbuf, BUFSIZ, stdin) == NULL)
				warn_exit("can't find PGP armor.");
		} while (line_not_blank(tmpbuf) == YES);
		found = YES;
	}

	while (p < lim) {
		c = getchar();
		if (c == EOF) {
			done = YES;
			return out;
		}
		if (c >= 128) {
		  continue;
		}
		d = base256[c];
		switch (d) {
		case OOB:
			warning("illegal radix64 character.");
			goto skiptail;
		case EOP:
			/* radix64 surely matches this */
			goto skiptail;
		case ELF:
			if (++lf >= 2) goto skiptail;
			continue;
		case ECR:
			if (++cr >= 2) goto skiptail;
			continue;
		case SPC:
			continue;
		}
		lf = cr = 0;
		*p++ = d;
		out++;
	}
	return out;
 skiptail:
	while (getchar() != EOF);
	done = YES;
	return out;
}

private int
decode_radix64(byte *p, unsigned int max)
{
	static int done = NO;
	static unsigned int avail = 0;
	static byte *q;
	unsigned int i, size, out = 0;
	byte c1, c2, c3, c4, *r, *lim = p + max;

	if (done == YES) return 0;

	while (p + 3 < lim) {
		if (avail < 4) {
			r = q;
			q = d_buf1;
			for (i = 0; i < avail; i++)
				*q++ = *r++;
			size = (*d_func1)(q, sizeof(d_buf1) - avail);
			q = d_buf1;
			avail += size;
			if (size == 0) {
				done = YES;
				switch (avail) {
				case 0:
					return out;
				case 1:
					warning("illegal radix64 length.");
					return out; /* anyway */
				case 2:
					c1 = *q++;
					c2 = *q++;
					*p++ = (c1 << 2) | ((c2 & 0x30) >> 4);
					return out + 1;
				case 3:
					c1 = *q++;
					c2 = *q++;
					c3 = *q++;
					*p++ = (c1 << 2) | ((c2 & 0x30) >> 4);
					*p++ = ((c2 & 0x0f) << 4) |
						((c3 & 0x3c) >> 2);
					return out + 2;
				}
			}
		}

		if (avail >= 4) {
			c1 = *q++;
			c2 = *q++;
			c3 = *q++;
			c4 = *q++;
			*p++ = (c1 << 2) | ((c2 & 0x30) >> 4);
			*p++ = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2);
			*p++ = ((c3 & 0x03) << 6) | c4;
			avail -= 4;
			out += 3;
		}
	}
	return out;
}

#ifdef HAVE_LIBZ
private int
inflate_gzip(byte *p, unsigned int max)
{
	static int done = NO;
	int err, size, inflated = 0, old;

	if (done == YES) return 0;

	z.next_out = p;
	z.avail_out = max;

	while (z.avail_out != 0) {
		if (z.avail_in == 0) {
			size = (*d_func2)(d_buf2, sizeof(d_buf2));
			z.next_in  = d_buf2;
			z.avail_in = size;
		}

		old = z.avail_out;
		err = inflate(&z, Z_SYNC_FLUSH);

		if (err != Z_OK && err != Z_STREAM_END)
			warn_exit("zlib inflate error (%d).", err);

		inflated = max - z.avail_out;		

		if (old == z.avail_out && z.avail_in != 0)
			break;

		if (err == Z_STREAM_END) {
			done = YES;
			/* 8 bytes (crc and isize) are left. */
			if (inflateEnd(&z) != Z_OK)
				warn_exit("zlib inflateEnd error.");
			break;
		}
	}

	return inflated;
}
#endif /* HAVE_LIBZ */

#ifdef HAVE_LIBBZ2
private int
inflate_bzip2(byte *p, unsigned int max)
{
	static int done = NO;
	int err, size, inflated = 0, old;

	if (done == YES) return 0;

	bz.next_out = (cast_t)p;
	bz.avail_out = max;

	while (bz.avail_out != 0) {
		if (bz.avail_in == 0) {
			size = (*d_func2)(d_buf2, sizeof(d_buf2));
			if (size == 0)
				warn_exit("bzip2 no data for BZ2_bzDecompress");
			bz.next_in  = (cast_t)d_buf2;
			bz.avail_in = size;
		}

		old = bz.avail_out;
		err = BZ2_bzDecompress(&bz);

		if (err != BZ_OK && err != BZ_STREAM_END)
			warn_exit("bzip2 BZ2_bzDecompress error (%d).", err);

		inflated = max - bz.avail_out;		

		if (old == bz.avail_out && bz.avail_in != 0)
			break;

		if (err == BZ_STREAM_END) {
			done = YES;
			/* 8 bytes (crc and isize) are left. */
			if (BZ2_bzDecompressEnd(&bz) != BZ_OK)
				warn_exit("bzip2 BZ2_bzDecompressEnd error.");
			break;
		}
	}

	return inflated;
}
#endif /* HAVE_LIBBZ2 */

public int
Getc1(void)
{
	byte c;

	if (AVAIL_COUNT == 0) {
		AVAIL_COUNT = (*d_func3)(d_buf3, sizeof(d_buf3));
		if (AVAIL_COUNT == 0)
			return EOF;
		NEXT_IN = d_buf3;
	}

	AVAIL_COUNT--;
	MAGIC_COUNT++;
	c = *NEXT_IN;
	NEXT_IN++;
	return c;
}

public int
Getc(void)
{
	int c = Getc1();
	if (c == EOF)
		warn_exit("unexpected end of file.");
	return c;
}

public int
Getc_getlen(void)
{
	return MAGIC_COUNT;
}

public void
Getc_resetlen(void)
{
	MAGIC_COUNT = 0;
}

public void
set_armor(void)
{
	d_func1 = read_radix64;
	d_func2 = NULL;
	d_func3 = decode_radix64;
}

public void
set_binary(void)
{
	d_func1 = NULL;
	d_func2 = NULL;
	d_func3 = read_binary;
}

/*
 * Assuming Compressed_Data_Packet ends at the end of file
 */

public void
Compressed_Data_Packet(int len)
{
#if defined(HAVE_LIBZ) || defined(HAVE_LIBBZ2)
	unsigned int alg = Getc();
	int err;
	private int (*func)(byte *, unsigned int);
	
	comp_algs(alg);

#ifdef HAVE_LIBZ
	z.zalloc = (alloc_func)0;
	z.zfree = (free_func)0;
	z.opaque = (voidpf)0;
#endif /* HAVE_LIBZ */
#ifdef HAVE_LIBBZ2
	bz.bzalloc = (void *)0;
	bz.bzfree = (void *)0;
	bz.opaque = (void *)0;
#endif /* HAVE_LIBBZ2 */

	/*
	 * 0 uncompressed
	 * 1 ZIP without zlib header (RFC 1951)
	 *	inflateInit2 (strm, -13)
	 * 2 ZLIB with zlib header (RFC 1950)
	 *	inflateInit  (strm)
	 * 3 BZIP2 (http://sources.redhat.com/bzip2/)
	 */

	switch (alg) {
	case 0:
		return;
#ifdef HAVE_LIBZ
	case 1:
		err = inflateInit2(&z, -13);
		if (err != Z_OK) warn_exit("zlib inflateInit error.");
		func = inflate_gzip;
		break;
	case 2:
		err = inflateInit(&z);
		if (err != Z_OK) warn_exit("zlib inflateInit error.");
		func = inflate_gzip;
		break;
#endif /* HAVE_LIBZ */
#ifdef HAVE_LIBBZ2
	case 3:
		err = BZ2_bzDecompressInit(&bz, 0, 0);
		if (err != BZ_OK) warn_exit("bzip2 BZ2_bzDecompressInit error.");
		func = inflate_bzip2;
		break;
#endif /* HAVE_LIBBZ2 */
	default:
		warn_exit("unknown compress algorithm.");
	}

#ifdef HAVE_LIBZ
	z.next_in  = d_buf2;
	z.avail_in = AVAIL_COUNT;
	z.next_out = 0;
	z.avail_out = sizeof(d_buf2);
#endif /* HAVE_LIBZ */
#ifdef HAVE_LIBBZ2
	bz.next_in  = (cast_t)d_buf2;
	bz.avail_in = AVAIL_COUNT;
	bz.next_out = 0;
	bz.avail_out = sizeof(d_buf2);
#endif /* HAVE_LIBBZ2 */

	memcpy(d_buf2, NEXT_IN, AVAIL_COUNT);
	AVAIL_COUNT = 0;

	if (d_func1 == NULL) {
		d_func1 = NULL;
		d_func2 = read_binary;
		d_func3 = func;
	} else {
		d_func1 = read_radix64;
		d_func2 = decode_radix64;
		d_func3 = func;
	}
#else /* defined(HAVE_LIBZ) || defined(HAVE_LIBBZ2) */
	comp_algs(Getc());
	warn_exit("can't uncompress without zlib/bzip2.");
#endif /* defined(HAVE_LIBZ) || defined(HAVE_LIBBZ2) */
}

/*
 * Copyright (C) 1998 Kazuhiko Yamamoto
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
