/* $Id: uudecode.c,v 4.4.3.1 1992/02/01 03:09:32 $
 * 
 * Decode one or more uuencoded articles back to binary form.
 * Trn version created by Wayne Davison.
 * Formerly the nn version by Kim Storm.
 * From the Berkeley original, modified by MSD, RDR, JPHD & WLS.
 */
/*
 * This software is Copyright 1991 by Stan Barber. 
 *
 * Permission is hereby granted to copy, reproduce, redistribute or otherwise
 * use this software as long as: there is no monetary profit gained
 * specifically from the use or reproduction of this software, it is not
 * sold, rented, traded or otherwise marketed, and this copyright notice is
 * included prominently in any copy made. 
 *
 * The authors make no claims as to the fitness or correctness of this software
 * for any use whatsoever, and it is provided as is. Any use of this software
 * is at the user's own risk. 
 */

#include "EXTERN.h"
#include "common.h"
#include "respond.h"
#include "decode.h"

#define MAXCHAR 256
#define NORMLEN 64	/* allows for 84 encoded chars per line */

#define SEQMAX 'z'
#define SEQMIN 'a'

static char seqc;
static int first, secnd, check, numl;

static char blank;
static int chtbl[MAXCHAR], cdlen[NORMLEN + 3];
static int state;
static bool Xflag;
static int expecting_part;

static int decode_line _((char *));
static void inittbls _((void));
static void gettable _((FILE *));

#define	NO_ADVANCE		0x10

#define	FIND_BEGIN		0x01
#define	AFTER_ERROR_FIND_BEGIN	0x02
#define	DECODE_TEXT		0x03
#define	SKIP_TRAILING	       (0x04 | NO_ADVANCE)
#define	SKIP_LEADING		0x05
#define	FOUND_END	       (0x06 | NO_ADVANCE)
#define DECODE_ERROR	       (0x07 | NO_ADVANCE)
#define OTHER_ERROR	       (0x08 | NO_ADVANCE)
#define NEW_BEGIN	       (0x09 | NO_ADVANCE)

void
uud_start()
{
    Xflag = FALSE;
    expecting_part = 0;
    seqc = SEQMAX;
    check = 1;
    first = 1;
    secnd = 0;
    state = FIND_BEGIN;
}

int
uudecode(in)
FILE *in;
{
    int mode, onedone, lens;
    char buff[LBUFLEN];

    numl = onedone = 0;

    if (state == FIND_BEGIN)
	inittbls();

    /*
     * search for header or translation table line.
     */

    while ((state & NO_ADVANCE) || fgets(buff, sizeof buff, in) != Nullch) {
	numl++;

	switch (state) {
	 case NEW_BEGIN:
	    if (decode_fp != Nullfp) {
		if (expecting_part) {
		    register int got_part = 0;

		    if (strnEQ(buff + 6, "part ", 5)) {
			register char *bp;

			for (bp = buff + 11; islower(*bp); bp++)
			    got_part = got_part * 26 + *bp - 'a';
		    }
		    if (expecting_part == got_part) {
			state = DECODE_TEXT;
			break;
		    }
		    printf("Expecting part %d; got part %d.\n",
			 expecting_part + 1, got_part + 1);
		    if (got_part) {
			state = SKIP_LEADING;
			return -1;
		    }
		}
		decode_end();
		sleep(2);
		Xflag = FALSE;
		expecting_part = 0;
	    }
	    state = FIND_BEGIN;
	    /* fall thru */

	 case FIND_BEGIN:
	 case AFTER_ERROR_FIND_BEGIN:
	    if (strnEQ(buff, "table", 5)) {
		gettable(in);
		continue;
	    }

	    if (strnEQ(buff, "begin ", 6)
	     || strnEQ(buff, "Xbegin ", 7)) {
		lens = strlen(buff)-1;
		if (buff[lens] == '\n')
		    buff[lens] = '\0';

		if(sscanf(buff+6,"%o%s", &mode, decode_fname) != 2) {
		    register char *bp = buff + 6;

		    if (*bp == ' ')
			bp++;
		    if (strnEQ(bp, "part ", 5)) {
			register int got_part = 0;

			for (bp = bp + 5; islower(*bp); bp++)
			    got_part = got_part * 26 + *bp - 'a';
			printf("Expecting part 1; got part %d.\n",
				got_part + 1);
			return -1;
		    }
		    continue;
		}

		Xflag = (*buff == 'X');

		sprintf(decode_dest, "%s/%s", extractdest, decode_fname);

		if ((decode_fp = fopen(decode_dest, FOPEN_WB)) == Nullfp) {
		    printf("Cannot create file: %s\n", decode_dest);
		    goto err;
		}
		chmod(decode_dest, mode);
		printf("Decoding: %s\n", decode_fname);
		state = DECODE_TEXT;
	    }
	    continue;

	 case SKIP_LEADING:
	    state = decode_line(buff);
	    continue;

	 case DECODE_TEXT:
	    state = decode_line(buff);
	    onedone = 1;
	    continue;

	 case FOUND_END:
	    fclose(decode_fp);
	    decode_fp = Nullfp;
	    Xflag = FALSE;
	    expecting_part = 0;
	    state = FIND_BEGIN;
	    printf("Done.\n");
	    continue;

	 case SKIP_TRAILING:
	    printf("(Continued)\n");
	    state = SKIP_LEADING;
	    return 0;

	 case DECODE_ERROR:
	    state = SKIP_TRAILING;
	    continue;

	 case OTHER_ERROR:
	    fclose(decode_fp);
	    decode_fp = Nullfp;
	    Xflag = FALSE;
	    expecting_part = 0;
	    state = AFTER_ERROR_FIND_BEGIN;
	    goto err;
	}
    }

    if (onedone) {
	if (state == DECODE_TEXT) {
	    printf("(Continued)\n");
	    state = SKIP_LEADING;
	}
	return 0;
    }

    if (state == AFTER_ERROR_FIND_BEGIN)
	return -1;
    printf("Couldn't find anything to decode.\n");

 err:
    sleep(2);
    return -1;
}

/*
 * decode one line and write it out using decode_fp
 */

static int
decode_line(buff)
char *buff;
{
    char outl[LBUFLEN];
    register char *bp, *ut;
    register int *trtbl = chtbl;
    register int n;
    register int blen;		/* binary length (from decoded file) */
    register int rlen;		/* calculated input line length */
    register int len;		/* actual input line length */
    register int dash;		/* number of '-'s encountered on a line */
				/* If it's too high, we reject the line */

#   define REJECT(buf,rlen,len) \
	((*buf == 'M' && len > rlen + 5) \
	 || (*buf != 'M' && len != rlen && len != rlen+1) \
	 || (strnEQ(buf, "BEGIN", 5)) \
	 || (strnEQ(buf, "END", 3)))

    if (Xflag) {
	if (*buff == 'X')
	    buff++;
	else
	    *buff = 'x';	/* force a mis-parse of a non-x'ed line */
    }
    len = strlen(buff);
    if (--len <= 0)
	return state;

    buff[len] = '\0';

    /*
     * Get the binary line length.
     */
    if ((blen = trtbl[buff[0]]) < 0) {
	if (state == SKIP_LEADING) {
	    if (strnEQ(buff, "begin ", 6))
		return NEW_BEGIN;

	    return SKIP_LEADING;
	}
	/*
	 * end of uuencoded file ?
	 */
	if (strnEQ(buff, "end", 3))
	    return FOUND_END;

	/*
	 * end of current file ? : get next one.
	 */
	if (strnEQ(buff, "include ", 8)) {
	    for (bp = buff + 8; *bp; bp++) {
		if (bp[0] == '.' && bp[1] == 'u') {
		    expecting_part = (bp[2] - 'a') * 26 + bp[3] - 'a';
		    break;
		}
	    }
	}

	/*
	 * trailing garbage
	 */
	return SKIP_TRAILING;
    }

    rlen = cdlen[blen];
    if (state == SKIP_LEADING && REJECT(buff,rlen,len))
	return SKIP_LEADING;

    /*
     * Is it the empty line before the end line ?
     */
    if (blen == 0)
	return state;

    if (REJECT(buff,rlen,len))
	return SKIP_TRAILING;

    /*
     * Pad with blanks.
     */
    for (bp = buff + len, n = rlen - len; --n >= 0; )
	*bp++ = blank;

    /*
     * Verify
     */
    for (n = rlen, bp = buff, dash = 0; --n >= 0; bp++) {
	if (trtbl[*bp] < 0) {
	    if (state == SKIP_LEADING)
		return SKIP_LEADING;
	    return DECODE_ERROR;
	}
	if (*bp == '-')
	    dash++;
    }
    if (dash * 100 / rlen > 33)		/* more than 1/3 dashes? */
	if (state == SKIP_LEADING)
	    return SKIP_LEADING;	/* -> reject */
	else
	    return SKIP_TRAILING;

    /*
     * Check for uuencodes that append a 'z' to each line....
     */
    if (check)
	if (secnd) {
	    secnd = 0;
	    if (buff[rlen] == SEQMAX)
		check = 0;
	} else if (first) {
	    first = 0;
	    secnd = 1;
	    if (buff[rlen] != SEQMAX)
		check = 0;
	}

    /*
     * There we check.
     */
    if (check) {
	if (buff[rlen] != seqc) {
	    if (state == SKIP_LEADING)
		return SKIP_LEADING;
	    return DECODE_ERROR;
	}

	if (--seqc < SEQMIN)
	    seqc = SEQMAX;
    }

    /*
     * output a group of 3 bytes (4 input characters).
     * the input chars are pointed to by p, they are to
     * be output to file f. blen is used to tell us not to
     * output all of them at the end of the file.
     */
    ut = outl;
    n = blen;
    bp = &buff[1];
    while (--n >= 0) {
	*(ut++) = trtbl[*bp] << 2 | trtbl[bp[1]] >> 4;
	if (n > 0) {
	    *(ut++) = (trtbl[bp[1]] << 4) | (trtbl[bp[2]] >> 2);
	    n--;
	}
	if (n > 0) {
	    *(ut++) = trtbl[bp[2]] << 6 | trtbl[bp[3]];
	    n--;
	}
	bp += 4;
    }
    if ((int)fwrite(outl, 1, blen, decode_fp) <= 0) {
	printf("Error on writing decoded file\n");
	return OTHER_ERROR;
    }

    return DECODE_TEXT;
}



/*
 * Install the table in memory for later use.
 */
static void
inittbls()
{
    register int i, j;

    /*
     * Set up the default translation table.
     */
    for (i = 0; i < ' '; i++)
	chtbl[i] = -1;
    for (i = ' ', j = 0; i < ' ' + 64; i++, j++)
	chtbl[i] = j;
    for (i = ' ' + 64; i < MAXCHAR; i++)
	chtbl[i] = -1;
    chtbl['`'] = chtbl[' '];	/* common mutation */
    chtbl['~'] = chtbl['^'];	/* another common mutation */
    blank = ' ';
    /*
     * set up the line length table, to avoid computing lotsa * and / ...
     */
    cdlen[0] = 1;
    for (i = 1, j = 5; i <= NORMLEN; i += 3, j += 4)
	cdlen[i] = (cdlen[i + 1] = (cdlen[i + 2] = j));
}

static void
gettable(in)
FILE *in;
{
    char buff[LBUFLEN];
    register int c, n = 0;
    register char *cpt;

    for (c = 0; c < MAXCHAR; c++)
	chtbl[c] = -1;

    for (;;) {
	if (fgets(buff, sizeof buff, in) == Nullch) {
	    printf("EOF while in translation table.\n");
	    return;
	}
	numl++;
	if (strnEQ(buff, "begin", 5)) {
	    printf("Incomplete translation table.\n");
	    return;
	}
	cpt = buff + strlen(buff) - 1;
	*cpt = ' ';
	while (*cpt == ' ') {
	    *cpt = 0;
	    cpt--;
	}
	cpt = buff;
	while (c = *cpt) {
	    if (chtbl[c] != -1) {
		printf("Duplicate char in translation table.\n");
		return;
	    }
	    if (n == 0)
		blank = c;
	    chtbl[c] = n++;
	    if (n >= 64)
		return;
	    cpt++;
	}
    }
}

