/*
 * uujoin [ -r ] [ {-amiga | -mac | -pc} ] file(s)...
 * join and decode Usenet encoded binary files.
 *
 * Author:  Mark R. Rinfret
 * Date:    08-15-90
 * 
 * Public Domain - Use as you wish.
 *
 * Notes:   The uudecode portion was adapted from uudecode.c, packaged
 *          with Matt Dillon's UUCP implementation.
 *
 *          The Macintosh mode simply filters and joins the files. It
 *          doesn't decode the BinHex format (got a de-binhexer I can
 *          have?).
 *
 *          The delimiter strings used to extract the encoded segments
 *          may change over time. Just edit the assignments to the
 *          string variables "start" and "stop", as appropriate.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <FileScan.h>

extern int unlink(const char *fileName);

extern int errno;

/* single character decode */
#define DEC(c)	(((c) - ' ') & 077)

int decode(FILE *in, FILE *out);
int outdec(char *p, FILE *f, int n);
int fr(FILE *fd, char *buf, int cnt);

#define AMI     0
#define MAC     1
#define PC      2

int     fileCount;                          /* set by FileScan() */
char    **fileNames;                        /* allocated by FileScan() */
int     fileNumber;                         /* index into fileNames */
char    line[256];                          /* allows for long Usenet lines */
int     machine;                            /* machine type */
int     removeFiles = 0;                    /* 1 => remove originals */
char    tempName[L_tmpnam];                 /* temporary file name */

int
DecodeMergedFile(void)
{
	char    dest[128];

	FILE    *in = NULL, *out = NULL;
    int     mode;                               /* file protection */

    in = fopen(tempName, "r");
    if (! in ) {
        fprintf(stderr,"Could not reopen merged file: '%s'\n", tempName);
        exit(1);
    }

	/* Search for header line */
	for (;;) {
		if (fgets(line, sizeof line, in) == NULL) {
			fprintf(stderr, "No begin line\n");
			exit(1);
		}
		if (strncmp(line, "begin ", 6) == 0)
			break;
	}

	sscanf(line, "begin %o %s", &mode, dest);

	/* create output file */
	out = fopen(dest, "w");
	if (out == NULL) {
		perror(dest);
		exit(1);
	}

	decode(in, out);

	if (fgets(line, sizeof line, in) == NULL || strcmp(line, "end\n")) {
		fprintf(stderr, "No end line\n");
		exit(5);
	}
    fclose(in);

    return 0;
}

int
JoinFiles(void)
{
    char    *fName;                         /* current file name */
    FILE    *inFile = NULL;
    char    *p;                             /* adjusted line pointer */
    char    *start, *stop;                  /* pattern matching strings */
    int     startLeng, stopLeng;            /* pattern lengths */
    int     status = 0;
    FILE    *tempFile = NULL;

    tmpnam(tempName);
    tempFile = fopen(tempName, "w");
    if (!tempFile) {
        perror(tempName);
        status = errno;
        goto done;
    }

    /* Set up pattern strings according to machine type. Patterns are
     * matched from beginning of line through number of chars in pattern. 
     */

    if (machine == MAC) {
        start = "---";
        stop = "--- end";
    }        
    else if (machine == PC) {
        start = "BEGIN";
        stop = "END--";
    }
    else {                              /* AMIGA */
        start = "sed";
        stop = "SHAR_EOF";
    }
    
    /* Compute the lengths of the match strings. */

    startLeng = strlen(start);
    stopLeng = strlen(stop);

    /* The Amiga binaries are packaged as shar files and therefore have a
     * leading 'X' which must be tossed. Other formats (so far) are not
     * packed as shar files and thus, the whole line is used.
     */
    p = (machine == AMI ? (line + 1) : line);

    while (fileNumber < fileCount) {
        fName = fileNames[fileNumber];  /* For programming convenience. */
        inFile = fopen(fName, "r");
        if (! inFile) {
            perror(fName);
            status = errno;
            goto done;
        }

        /*  Attempt to find the start of this file. */

        if (startLeng) {
            while (1) {
                fgets(line, sizeof(line), inFile);
                if ( feof(inFile) ) {
eof_err:
                    fprintf(stderr,"uujoin: unexpected EOF in file '%s'!\n", fName);
                    status = EOF;
                    goto done;
                }
                if (strncmp(start, line, startLeng) == 0)
                    break;                      /* We found the beginning. */
            }
        }

        while (1) {
            fgets(line, sizeof(line), inFile);
            if ( feof(inFile) ) {
                if (stopLeng == 0) 
                    break;
                else
                    goto eof_err;
            }

            if ( (*line != '\n') && (strncmp(stop, line, stopLeng) == 0) )
                break;                      /* We found the end. */
        
            if ( fputs(p, tempFile) ) {     /* Write the _adjusted_ line. */
                status = EOF;
                fprintf(stderr,"uujoin: error writing to temp file '%s'\n", 
                        tempName);
                exit(1);
            }
        }
        fclose(inFile);
        ++fileNumber;
    }

done:
    if (tempFile) fclose(tempFile);
    if (inFile) fclose(inFile);
    return status;
}

main(int argc, char **argv)
{

    if (argc < 2) {
usage:
        puts("Program: uujoin - join and decode Usenet encoded binary files.");
        puts("Usage:   uujoin [options] file1 [... filen]");
        puts("Where [options] may be:");
        puts("\t-amiga  -> uuencoded files wrapped with shar");
        puts("\t-mac    -> Macintosh binhex files (not decoded in this release)");
        puts("\t-pc     -> uuencoded files enclosed in BEGIN/END pairs");
        puts("\t-r      -> remove original files when done");
        puts("\n\tUnix-style wildcards may be used in file specifications.");
        puts("\n\tExample: uujoin -amiga -r program.??");
        puts("\n\tMark R. Rinfret, August 1990.");
        puts("\tContributed to the public domain.");
        exit(1);
    }
    
    --argc;                                 /* Skip over program name. */
    ++argv;

    while (**argv == '-') {
        if (strcmp(*argv,"-mac") == 0) {
            machine = MAC;
bump:
            --argc;                         /* Skip this arg. */
            ++argv;
        }
        else if (strcmp(*argv, "-pc") == 0) {
            machine = PC;
            goto bump;
        }
        else if (strcmp(*argv,"-amiga") == 0) {
            machine = AMI;
            goto bump;
        }
        else if (strcmp(*argv,"-r") == 0) {
            removeFiles = 1;
            goto bump;
        }
        else
            goto usage;
    }

    fileNames = FileScan(argv, argc, &fileCount, 1);
    if (! fileNames) {
        printf("Filename expansion failed - abort!\n");
        exit(1);
    }

    if (JoinFiles()) exit(1);                   /* Attempt to join files. */

    if (DecodeMergedFile()) exit(1);

    unlink(tempName);
    if (removeFiles) {
        while (--fileCount >= 0) {
            unlink(fileNames[fileCount]);
        }
    }
	exit(0);
}

/*
 * copy from in to out, decoding as you go along.
 */
decode(in, out)
FILE *in;
FILE *out;
{
	char buf[80];
	char *bp;
	int n;

	for (;;) {
		/* for each input line */
		if (fgets(buf, sizeof buf, in) == NULL) {
			printf("Short file\n");
			exit(10);
		}
		n = DEC(buf[0]);
		if (n <= 0)
			break;

		bp = &buf[1];
		while (n > 0) {
			outdec(bp, out, n);
			bp += 4;
			n -= 3;
		}
	}
}

/*
 * 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.  n is used to tell us not to
 * output all of them at the end of the file.
 */
outdec(p, f, n)
char *p;
FILE *f;
{
	int c1, c2, c3;

	c1 = DEC(*p) << 2 | DEC(p[1]) >> 4;
	c2 = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
	c3 = DEC(p[2]) << 6 | DEC(p[3]);
	if (n >= 1)
		fputc(c1, f);
	if (n >= 2)
		fputc(c2, f);
	if (n >= 3)
		fputc(c3, f);
}


/* fr: like read but stdio */
int
fr(fd, buf, cnt)
FILE *fd;
char *buf;
int cnt;
{
	int c, i;

	for (i=0; i<cnt; i++) {
		c = fgetc(fd);
		if (c == EOF)
			return(i);
		buf[i] = c;
	}
	return (cnt);
}

