/* $Id: uucode.cpp,v 1.5 2003/06/20 16:13:19 fesnel Exp $ */

/* This file is based on uu[en/de]code program from
 * The Regents of the University of California.
 */

/*-
 * Copyright (c) 1983, 1993
 *      The Regents of the University of California.  All rights reserved.
 */

#include <fmail.h>
#include <umail.h>

#include "uucode.h"

#define ENC(c) ((c) ? ((c) & 077) + ' ': '`')
#define DEC(c) (((c) - ' ') & 077)  /* single character decode */
#define IS_DEC(c) ( (((c) - ' ') >= 0) &&  (((c) - ' ') <= 077 + 1) )


UUEncode::UUEncode()
	: isOpen(false), fp(NULL) { }

UUEncode::UUEncode(const char *outfile)
	: isOpen(false), fp(NULL)
{

	open(outfile);
}

UUEncode::~UUEncode(void)
{

	close();
}

bool
UUEncode::open(const char *outfile)
{

	if (isOpen || outfile == NULL)
		return false;

	snprintf(tempfile, sizeof(tempfile), "%s", get_temp_file("uue"));
	fp = fopen(tempfile, "w");
	if (fp == NULL)
		return false;

	isOpen = true;
	snprintf(file, sizeof(file), "%s", outfile);
	return true;
}

void
UUEncode::close(void)
{
	FILE *tfile;
	char buf[1024];

	if (isOpen) {
		/*
		 * We need to append the current file onto the encoded
		 * part.
		 */

		if ((tfile = fopen(file, "r")) != NULL) {

			while (fgets(buf, sizeof(buf), tfile) != NULL)
				fputs(buf, fp);

			fclose(tfile);
			fclose(fp);
			fp = NULL;

/* Under OS/2 the file will not be deleted during rename() */
#ifdef __EMX__
			if(access(file, 0) == 0)
				unlink(file);
#endif
			if (rename(tempfile, file) == -1)
				unlink(tempfile);
		} else {
			fclose(fp);
			fp = NULL;
		}
	}
	isOpen = false;
}

bool
UUEncode::addFile(const char *infile)
{
	struct stat sb;
	FILE *fin;
	char buf[255], ch, *p;
	int n;

	if (!isOpen || infile == NULL)
		return false;

	if ((fin = fopen(infile, "r")) == NULL)
		return false;

	if (fstat(fileno(fin), &sb) == -1) {
		fclose(fin);
		return false;
	}

	fprintf(fp, "\nbegin %o %s\n", sb.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO),
	    name_path(infile));

	while ((n = fread(buf, sizeof(char), 45, fin)) != 0) {
		ch = ENC(n);
		if(fputc(ch, fp) == EOF)
			break;
		for(p = buf; n > 0; n -= 3, p += 3) {
			ch = *p >> 2;
			ch = ENC(ch);
			if(fputc(ch, fp) == EOF)
				break;
			ch = ((*p << 4) & 060) | ((p[1] >> 4) & 017);
			ch = ENC(ch);
			if(fputc(ch, fp) == EOF)
				break;
			ch = ((p[1] << 2) & 074) | ((p[2] >> 6) & 03);
			ch = ENC(ch);
			if(fputc(ch, fp) == EOF)
				break;
			ch = p[2] & 077;
			ch = ENC(ch);
			if(fputc(ch, fp) == EOF)
				break;
		}
		if(fputc('\n', fp) == EOF)
			break;
	}
	if (ferror(fin))
		return false;

	ch = ENC('\0');
	fprintf(fp, "%c\n", ch);
	fprintf(fp, "end\n");

	fclose(fin);

	return true;
}


UUDecode::UUDecode(void)
	: isOpen(false), mode(-1), fp(NULL)
{

	tempfile[0] = '\0';
}

UUDecode::UUDecode(const char *infile)
	: isOpen(false), mode(-1), fp(NULL)
{

	tempfile[0] = '\0';
	open(infile);
}

UUDecode::UUDecode(struct _mail_msg *msg)
	: isOpen(false), mode(-1), fp(NULL)
{

	tempfile[0] = '\0';
	open(msg);
}

UUDecode::~UUDecode(void)
{

	close();
}

bool
UUDecode::open(const char *file)
{

	if (isOpen || file == NULL)
		return false;

	if ((fp = fopen(file, "r")) == NULL)
		return false;

	isOpen = true;
	return true;
}

bool
UUDecode::open(struct _mail_msg *msg)
{
	struct _mime_msg *mime;

	if (isOpen || msg == NULL || msg->header == NULL)
		return false;
	
	if (msg->mime == NULL)
		mime_scan(msg);
	if (msg->mime == NULL)	/* Couldn't get it */
		return false;

	if ((mime = get_text_part(msg)) == NULL)
		return false;

	snprintf(tempfile, sizeof(tempfile), "%s", get_temp_file("uud"));

	if (save_part(msg, mime, tempfile, 0) == -1) {
		unlink(tempfile);
		tempfile[0] = '\0';
		return false;
	}

	if ((fp = fopen(tempfile, "r")) == NULL) {
		unlink(tempfile);
		tempfile[0] = '\0';
		return false;
	}

	isOpen = true;
	return true;
}

void
UUDecode::close(void)
{

	if (isOpen) {
		fclose(fp);
		fp = NULL;
		if (strlen(tempfile) > 0)
			unlink(tempfile);
		tempfile[0] = '\0';
		mode = -1;
	}
	isOpen = false;
}

const char *
UUDecode::getNextFileName(void)
{
	char buf[1024];
	static char path[1024]; /* XXX should be MAXPATHLEN */

	if (!isOpen)
		return NULL;

	/* Keep looking until we hit EOF or find a valid file name. */
	for (;;) {
		do {
			if (fgets(buf, sizeof(buf), fp) == NULL)
				return NULL;
		} while (strncmp(buf, "begin ", 6) != 0);

		sscanf(buf, "begin %o %1023s", &mode, path);
		path[sizeof(buf) - 1] = '\0';

		if (mode == -1 || strlen(path) == 0) {
			mode = -1;
			continue;
		}

		return path;
	}
}

bool
UUDecode::getNextFile(const char *outfile)
{
	FILE *fout;
	char buf[1024], ch, *p;
	int n;

	if (!isOpen || outfile == NULL)
		return false;

	/*
	 * If there is not a saved mode we need to seek to the beginning
	 * of the next file part and read it. getNextFileName() does this
	 * job, so use it, ignoring return value.
	 */
	if (mode == -1)
		getNextFileName();

	/* If mode is still invalid, we can't find the next file */
	if (mode == -1)
		return false;

	if ((fout = fopen(outfile, "w")) == NULL)
		return false;

#ifndef __EMX__
	/* XXX ignore failure */
	fchmod(fileno(fout), mode & 0666);
#endif

	/* for each input line */
	for(;;) {
		if(fgets(buf, sizeof(buf), fp) == NULL) {
			fclose(fout);
			mode = -1;
			return false;
		}

		p = buf;

		/*
		 * `n' is used to avoid writing out all the characters
		 * at the end of the file.
		 */
		if((n = DEC(*p)) <= 0)
			break;

		if(n / 3 * 4 >= strlen(p)) {
			fclose(fout);
			mode = -1;
			return false;
		}

		for(++p; n > 0; p += 4, n -= 3)
			if(n >= 3) {
				if(!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
				       IS_DEC(*(p + 2)) && IS_DEC(*(p + 3)))) {
					fclose(fout);
					mode = -1;
					return false;
				}

				ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
				fputc(ch, fout);
				ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
				fputc(ch, fout);
				ch = DEC(p[2]) << 6 | DEC(p[3]);
				fputc(ch, fout);
			} else {
				if(n >= 1) {
					if(!(IS_DEC(*p) && IS_DEC(*(p + 1)))) {
						fclose(fout);
						mode = -1;
						return false;
					}

					ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
					fputc(ch, fout);
				}

				if(n >= 2) {
					if(!(IS_DEC(*(p + 1)) &&
					       IS_DEC(*(p + 2)))) {
						fclose(fout);
						mode = -1;
						return false;
					}

					ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
					fputc(ch, fout);
				}

				if(n >= 3) {
					if(!(IS_DEC(*(p + 2)) &&
					       IS_DEC(*(p + 3)))) {
						fclose(fout);
						mode = -1;
						return false;
					}

					ch = DEC(p[2]) << 6 | DEC(p[3]);
					fputc(ch, fout);
				}
			}
	}

	fclose(fout);
	mode = -1;

	/* Ignores whether the trailing 'end' is missing */
	return true;
}
