/*------------------------------------------------------------------------
 Module:        /extra/development/xamime/xamime_working/ripmime/uuencode.c
 Author:        Paul L Daniels
 Project:       Xamime:ripMIME
 State:         Stable
 Creation Date:
 Description:   uuencode is a collection of functions to aid the decoding / encoding of UUENCODED data files.
                This module is primarily intended to be used with ripMIME rather than for 'stand alone' use.
                The biggest issue is that the interfaces to the decoding functions are too specific at this point for a more generic use.
------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>

#include "pldstr.h"
#include "ffget.h"
#include "filename-filters.h"

#include "uuencode.h"



#define _SL 1

#define UUENCODE_DEBUG_PEDANTIC 10
#define UUENCODE_DEBUG_NORMAL 1

#define UUENCODE_STRLEN_MAX 1024

// Debug precodes
#define UUENCODE_DPEDANTIC ((glb.debug >= UUENCODE_DEBUG_PEDANTIC))
#define UUENCODE_DNORMAL   ((glb.debug >= UUENCODE_DEBUG_NORMAL  ))

#define UUENCODE_WRITE_BUFFER_SIZE 4096
#define UUENCODE_WRITE_BUFFER_LIMIT 4000


static unsigned char uudec[256]={
	32,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,\
  48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,   62,   63,\
   0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,\
  16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,\
  32,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,\
  48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,   62,   63,\
   0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,\
  16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,\
  32,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,\
  48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,   62,   63,\
   0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,\
  16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,\
  32,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,\
  48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61,   62,   63,\
   0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,\
  16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31 \
};

struct UUENCODE_globals {
	int debug;
	int verbose;
	int nodecode;
	int syslogging;
	int stderrlogging;
};

static struct UUENCODE_globals glb;


int UUENCODE_init( void )
{
	glb.debug = 0;
	glb.verbose = 0;
	glb.nodecode = 0;
	glb.syslogging = 0;
	glb.stderrlogging = 0;

	return 0;
}


int UUENCODE_set_debug( int level )
{
	glb.debug = level;
	return glb.debug;
}

int UUENCODE_set_verbose( int level )
{
	glb.verbose = level;
	return glb.verbose;
}


int UUENCODE_set_nodecode( int level )
{
	glb.nodecode = level;

	return glb.nodecode;
}

/*------------------------------------------------------------------------
Procedure:     UUENCODE_is_uuencode_header ID:1
Purpose:       Tries to determine if the line handed to it is a UUencode header.
Input:
Output:        0 = No
1 = Yes
Errors:
------------------------------------------------------------------------*/
int UUENCODE_is_uuencode_header( char *line )
{
	struct PLD_strtok tx;
	char buf[UUENCODE_STRLEN_MAX];
	char *bp,*fp;
	int result = 0;

	// If we're not supposed to be decoding UUencoded files, then return 0
	if (glb.nodecode) return 0;

	snprintf( buf, sizeof(buf), "%s", line );

	bp = buf;

	// If you're wondering why we dont check for "begin ",it's because we dont know
	// if begin is followed by a \t or ' ' or some other white space

	if ((bp)&&(strncasecmp(bp,"begin",5)==0))
	{
		fp = NULL;
		bp = PLD_strtok(&tx, buf, " \n\r\t"); // Get the begin

		//		if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_is_uuencode_header: BEGIN = %s\n",bp);

		if (bp) fp = PLD_strtok(&tx, NULL, " \n\r\t"); // Get the file-permissions

		if (fp)
		{
			if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_is_uuencode_header: PERMISSIONS = %s\n",fp);

			if ((atoi(fp) == 0)||(atoi(fp) > 777))   // Maximum is 777, because R+W+X = 7
			{
				result = 0;
			}
			else result = 1;

		}
		else if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_is_uuencode_header: Cannot read permissions\n");
	}

	//	if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_is_uuencode_header: RESULT = %d\n",result);

	return result;
}






/*------------------------------------------------------------------------
Procedure:     UUENCODE_is_file_uuenc ID:1
Purpose:       Tries to determine if a given file is UUEncoded, or at
least contains a UUENCODED file to it.
This should only be run -after- we've checked with
is_file_mime() because if the file is MIME, then it'll be able
to detect UUencoding within the normal decoding routines.
Input:         filename to test
Output:        0 = not uuencoded
1 = _probably_ uuencoded.
Errors:
------------------------------------------------------------------------*/
int UUENCODE_is_file_uuencoded( char *fname )
{
	int result = 0;
	int linecount = 0;
	int limit=20;
	char line[ UUENCODE_STRLEN_MAX ];
	FILE *f;

	f = fopen(fname,"r");
	if (!f)
	{
		syslog(1,"UUENCODE_is_file_mime: Error, cannot open file '%s' for reading (%s)",fname,strerror(errno));
		return -1;
	}

	while ((linecount < limit)&&(fgets(line,1024,f)))
	{
		if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_is_file_uuenc: Testing line '%s'\n",line);
		if (UUENCODE_is_uuencode_header( line ))
		{
			result = 1;
			break;
		}
		linecount++;
	}

	fclose(f);

	return result;
}


/*------------------------------------------------------------------------
Procedure:     UUENCODE_decode_uu ID:1
Purpose:       Decodes a UUencoded stream
Input:
Output:
Errors:
s------------------------------------------------------------------------*/
int UUENCODE_decode_uu( FFGET_FILE *f, char *unpackdir, char *input_filename, char *out_filename, int out_filename_size, int decode_whole_file, int keep )
{
	int filename_found = 0;
	char buf[ UUENCODE_STRLEN_MAX ];
	char *bp = buf, *fn, *fp;
	int n, i, expected;
	char fullpath[ UUENCODE_STRLEN_MAX ]="";
	struct PLD_strtok tx;
	unsigned char *writebuffer = NULL;
	unsigned char *wbpos;
	int wbcount = 0;
	int loop = 0;
	int buflen = 0;
	int filecount = 0;
	FFGET_FILE ffinf;	// Local static FFGET struct used if *f is  NULL

	FFGET_FILE *finf;	// Points to either *f or &ffinf

	FILE *outf;
	int outfo =0; 			// set if outfile was opened.

	FILE *inf = NULL;

	// generate the filename, and open it up...

	if (UUENCODE_DNORMAL) fprintf(stdout, "UUENCODE_decode_uu: Starting.(%s)\n", input_filename );

	// If no FFGET_FILE param is passed to us directly, then we must create out own.

	if (!f)
	{
		if (UUENCODE_DNORMAL) fprintf(stdout, "UUENCODE_decode_uu: NULL FFGET stream given to us, create our own.\n");

		snprintf(fullpath,sizeof(fullpath),"%s/%s",unpackdir, input_filename );

		inf = fopen(fullpath,"r");
		if (!inf)
		{
			if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: Cannot open file '%s' (%s)",fullpath, strerror(errno));
			if (glb.syslogging > 0) syslog(_SL,"UUENCODE_decode_uu: Cannot open file '%s' (%s)",fullpath,strerror(errno));
			if (glb.stderrlogging > 0) fprintf(stderr,"UUENCODE_decode_uu: Cannot open file '%s' (%s)",fullpath,strerror(errno));
			return -1;
		}

		FFGET_setstream(&ffinf, inf);
		finf = &ffinf;

		if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: Creation done. [FFGET-FILE=%p, FILE=%p]\n", finf, inf);
	}
	else finf = f;

	writebuffer = malloc( UUENCODE_WRITE_BUFFER_SIZE *sizeof(unsigned char));
	if (!writebuffer)
	{
		if (glb.syslogging > 0) syslog(_SL,"UUENCODE_decode_64: Error: cannot allocate 100K of memory for the write buffer");
		if (glb.stderrlogging > 0) fprintf(stderr,"UUENCODE_decode_64: Error: cannot allocate 100K of memory for the write buffer");
		return -1;
	}
	else {
		wbpos = writebuffer;
		wbcount = 0;
	}


	if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: Beginning.(%s)\n",fullpath);


	while (!FFGET_feof(finf))
	{
		filename_found = 0;

		// First lets locate the BEGIN line of this UUDECODE file

		while (FFGET_fgets(buf, sizeof(buf), finf))
		{

			if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: Reading: %s\n",buf);


			if (strncasecmp(buf,"begin",5)==0)
			{
				if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: Located BEGIN\n");
				// Okay, so the line contains begin at the start, now, lets get the decode details
				fp = fn = NULL;

				bp = PLD_strtok(&tx, buf, " \n\r\t"); // Get the begin

				if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: BEGIN = '%s'\n",bp);
				if (bp) fp = PLD_strtok(&tx, NULL, " \n\r\t"); // Get the file-permissions

				if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: Permissions/Name = '%s'\n",fp);
				if (fp) fn = PLD_strtok(&tx, NULL, "\n\r"); // Get the file-name

				if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: Name = '%s'\n",fn);

				if (!fn)
				{
					bp = fp;
				}
				else bp = fn;

				if ((!bp)&&(!f))
				{
					fprintf(stderr,"UUENCODE_decode_uu: WARNING - unable to obtain filename from UUencoded text file header");
					if (writebuffer) free(writebuffer);
					fclose(inf);
					return -1;
				}

				if (UUENCODE_DNORMAL) fprintf(stdout,"%s:%d:UUENCODE_decode_uu: Full path = (%s)\n",__FILE__,__LINE__,bp);

				filename_found = 1;
				break;
			} // If line starts with BEGIN


		} // While more lines in the INPUT file.


		if ((filename_found)&&(bp))
		{
			if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: Located filename (%s), now decoding.\n",bp);

			// Clean up the file name
			FNFILTER_filter( bp, out_filename_size );
			snprintf( out_filename, out_filename_size, "%s", bp ); // FIXME - replace 128 with real value.


			// Create the new output full path

			snprintf(fullpath, sizeof(fullpath), "%s/%s", unpackdir, bp );
			if (UUENCODE_DNORMAL) fprintf(stdout,"%s:%d:UUENCODE_decode_uu: Filename = (%s)\n",__FILE__,__LINE__,fullpath);

			outf = fopen(fullpath, "wb");
			if (!outf)
			{
				if (glb.syslogging > 0) syslog(_SL,"UUENCODE_decode_uu: Cannot open file '%s' (%s)",fullpath,strerror(errno));
				if (glb.stderrlogging > 0) fprintf(stderr,"UUENCODE_decode_uu: Cannot open file '%s' (%s)",fullpath,strerror(errno));
				if (writebuffer) free(writebuffer);
				return -1;
			}
			else outfo = 1;

			// Allocate the write buffer.  By using the write buffer we gain an additional 10% in performance
	// due to the lack of function call (fwrite) overheads

			// Okay, now we have the UUDECODE data to decode...

			wbcount = 0;
			wbpos = writebuffer;

			while (outf)
			{
				// for each input line
				if (FFGET_fgets(buf, sizeof(buf), finf) == NULL)
				{
					fprintf(stderr,"UUENCODE_decode_uu: Short file\n");
					return -1;
				}

				// If we've reached the end of the UUencoding

				if (strncasecmp(buf,"end",3)==0)
				{
					if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: End of UUencoding detected\n");
					break;
				}

				if ( !strpbrk(buf,"\r\n") )
				{
					fprintf(stdout,"UUENCODE_decode_uu: WARNING - Excessive length line\n");
				}

				// The first char of the line indicates how many bytes are to be expected

				n = uudec[(int)*buf];


				// If the line is a -blank- then break out.

				if ((n <= 0) || (*buf == '\n')) break;

				// Calculate expected # of chars and pad if necessary

				expected = ((n+2)/3)<<2;
				buflen = strlen(buf) -1;
				for (i = buflen; i <= expected; i++) buf[i] = ' ';
				bp = &buf[1];

				// Decode input buffer to output file.

				while (n > 0)
				{
					// In order to reduce function call overheads, we've bought the UUDecoding
					// bit shifting routines into the UUDecode main decoding routines. This should
					// save us about 250,000 function calls per Mb.
					// UUENCODE_outdec(bp, outf, n);

					char c[3];
					int m = n;

					c[0] = uudec[(int)*bp] << 2 | uudec[(int)*(bp+1)] >> 4;
					c[1] = uudec[(int)*(bp+1)] << 4 | uudec[(int)*(bp+2)] >> 2;
					c[2] = uudec[(int)*(bp+2)] << 6 | uudec[(int)*(bp+3)];

					if (m > 3) m = 3;

					if ( wbcount >= UUENCODE_WRITE_BUFFER_LIMIT )
					{
						fwrite(writebuffer, 1, wbcount, outf);
						wbpos = writebuffer;
						wbcount = 0;
					}

					// Transfer the decoded data to the write buffer.
					// The reason why we use a loop, rather than just a set of if
					// statements is just for code-viewing simplicity.  It's a lot
					// easier to read than some nested chain of if's

					for (loop = 0; loop < m; loop++)
					{
						*wbpos = c[loop];
						wbpos++;
						wbcount++;
					}

					bp += 4;
					n -= 3;

				} // while (n > 0)

			} // While (1)

			if ((outfo)&&(wbcount > 0))
			{
				fwrite(writebuffer, 1, wbcount, outf);
			}


			if (outfo) fclose(outf);
			if (glb.verbose) fprintf(stdout,"Decoded %s\n",fullpath);

			filecount++;

		} // If valid filename was found for UUdecode

		else
		{
			out_filename[0] = '\0';
			if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: No FILENAME was found in data...\n");
		}

		if (UUENCODE_DNORMAL) fprintf(stdout,"UUENCODE_decode_uu: Segment completed\n");

		// If this file was a result of the x-uuencode content encoding, then we need to exit out
		// as we're reading in the -stream-, and we dont want to carry on reading because we'll
		// end up just absorbing email data which we weren't supposed to.

		if ((f)&&( !decode_whole_file )) break;

	} // While !feof(inf)



	if (writebuffer) free(writebuffer);

	// if (UUENCODE_DNORMAL) if (FFGET_feof(finf)) fprintf(stdout,"UUENCODE_decode_uu: End of input file hit\n");

	if (UUENCODE_DNORMAL) fprintf(stdout, "UUENCODE_decode_uu: Completed\n");

	if (inf) fclose(inf);


	return filecount;
}






