/*
 *  Copyright (C) 2007-2008 Sourcefire, Inc.
 *
 *  Authors: Nigel Horne
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 *
 * $Log: text.c,v $
 * Revision 1.25  2007/02/12 20:46:09  njh
 * Various tidy
 *
 * Revision 1.24  2006/09/13 20:53:50  njh
 * Added debug
 *
 * Revision 1.23  2006/07/14 12:13:08  njh
 * Typo
 *
 * Revision 1.22  2006/07/01 21:03:36  njh
 * Better use of destroy mode
 *
 * Revision 1.21  2006/07/01 16:17:35  njh
 * Added destroy flag
 *
 * Revision 1.20  2006/07/01 03:47:50  njh
 * Don't loop if binhex runs out of memory
 *
 * Revision 1.19  2006/05/19 11:02:12  njh
 * Just include mbox.h
 *
 * Revision 1.18  2006/05/04 10:37:03  nigelhorne
 * Speed up scanning of clean files
 *
 * Revision 1.17  2006/05/03 09:36:40  nigelhorne
 * Pass full ctx into the mbox code
 *
 * Revision 1.16  2006/04/09 19:59:28  kojm
 * update GPL headers with new address for FSF
 *
 * Revision 1.15  2005/03/10 08:50:49  nigelhorne
 * Tidy
 *
 * Revision 1.14  2005/01/19 05:31:55  nigelhorne
 * Added textIterate
 *
 * Revision 1.13  2004/12/08 19:03:41  nigelhorne
 * Fix compilation error on Solaris
 *
 * Revision 1.12  2004/12/04 16:03:55  nigelhorne
 * Text/plain now handled as no encoding
 *
 * Revision 1.11  2004/11/27 21:54:26  nigelhorne
 * Tidy
 *
 * Revision 1.10  2004/08/22 10:34:24  nigelhorne
 * Use fileblob
 *
 * Revision 1.9  2004/08/21 11:57:57  nigelhorne
 * Use line.[ch]
 *
 * Revision 1.8  2004/07/20 14:35:29  nigelhorne
 * Some MYDOOM.I were getting through
 *
 * Revision 1.7  2004/06/22 04:08:02  nigelhorne
 * Optimise empty lines
 *
 * Revision 1.6  2004/05/05 09:37:52  nigelhorne
 * Removed textClean - not needed in clamAV
 *
 * Revision 1.5  2004/03/25 22:40:46  nigelhorne
 * Removed even more calls to realloc and some duplicated code
 *
 * Revision 1.4  2004/02/26 13:26:34  nigelhorne
 * Handle spaces at the end of uuencoded lines
 *
 */

static	char	const	rcsid[] = "$Id: text.c,v 1.25 2007/02/12 20:46:09 njh Exp $";

#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

#include <stdlib.h>
#ifdef	C_DARWIN
#include <sys/types.h>
#include <sys/malloc.h>
#else
#ifdef HAVE_MALLOC_H /* tk: FreeBSD-CURRENT doesn't support malloc.h */
#ifndef	C_BSD	/* BSD now uses stdlib.h */
#include <malloc.h>
#endif
#endif
#endif
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <stdio.h>

#include "others.h"

#include "mbox.h"

static	text	*textCopy(const text *t_head);
static	text	*textAdd(text *t_head, const text *t);
static	void	addToFileblob(const line_t *line, void *arg);
static	void	getLength(const line_t *line, void *arg);
static	void	addToBlob(const line_t *line, void *arg);
static	void	*textIterate(text *t_text, void (*cb)(const line_t *line, void *arg), void *arg, int destroy);

void
textDestroy(text *t_head)
{
	while(t_head) {
		text *t_next = t_head->t_next;
		if(t_head->t_line)
			(void)lineUnlink(t_head->t_line);
		free(t_head);
		t_head = t_next;
	}
}

/* Clone the current object */
static text *
textCopy(const text *t_head)
{
	text *first = NULL, *last = NULL;

	while(t_head) {
		if(first == NULL)
			last = first = (text *)cli_malloc(sizeof(text));
		else {
			last->t_next = (text *)cli_malloc(sizeof(text));
			last = last->t_next;
		}

		if(last == NULL) {
			if(first)
				textDestroy(first);
			return NULL;
		}

		if(t_head->t_line)
			last->t_line = lineLink(t_head->t_line);
		else
			last->t_line = NULL;

		t_head = t_head->t_next;
	}

	if(first)
		last->t_next = NULL;

	return first;
}

/* Add a copy of a text to the end of the current object */
static text *
textAdd(text *t_head, const text *t)
{
	text *ret;
	int count;

	if(t_head == NULL) {
		if(t == NULL) {
			cli_errmsg("textAdd fails sanity check\n");
			return NULL;
		}
		return textCopy(t);
	}

	if(t == NULL)
		return t_head;

	ret = t_head;

	count = 0;
	while(t_head->t_next) {
		count++;
		t_head = t_head->t_next;
	}

	cli_dbgmsg("textAdd: count = %d\n", count);

	while(t) {
		t_head->t_next = (text *)cli_malloc(sizeof(text));
		t_head = t_head->t_next;

		assert(t_head != NULL);

		if(t->t_line)
			t_head->t_line = lineLink(t->t_line);
		else
			t_head->t_line = NULL;

		t = t->t_next;
	}

	t_head->t_next = NULL;

	return ret;
}

/*
 * Add a message's content to the end of the current object
 */
text *
textAddMessage(text *aText, message *aMessage)
{
	assert(aMessage != NULL);

	if(messageGetEncoding(aMessage) == NOENCODING)
		return textAdd(aText, messageGetBody(aMessage));
	else {
		text *anotherText = messageToText(aMessage);

		if(aText)
			return textMove(aText, anotherText);
		return anotherText;
	}
}

/*
 * Put the contents of the given text at the end of the current object.
 * The given text emptied; it can be used again if needed, though be warned that
 * it will have an empty line at the start.
 */
text *
textMove(text *t_head, text *t)
{
	text *ret;

	if(t_head == NULL) {
		if(t == NULL) {
			cli_errmsg("textMove fails sanity check\n");
			return NULL;
		}
		t_head = (text *)cli_malloc(sizeof(text));
		if(t_head == NULL)
			return NULL;
		t_head->t_line = t->t_line;
		t_head->t_next = t->t_next;
		t->t_line = NULL;
		t->t_next = NULL;
		return t_head;
	}

	if(t == NULL)
		return t_head;

	ret = t_head;

	while(t_head->t_next)
		t_head = t_head->t_next;

	/*
	 * Move the first line manually so that the caller is left clean but
	 * empty, the rest is moved by a simple pointer reassignment
	 */
	t_head->t_next = (text *)cli_malloc(sizeof(text));
	if(t_head->t_next == NULL)
		return NULL;
	t_head = t_head->t_next;

	assert(t_head != NULL);

	if(t->t_line) {
		t_head->t_line = t->t_line;
		t->t_line = NULL;
	} else
		t_head->t_line = NULL;

	t_head->t_next = t->t_next;
	t->t_next = NULL;

	return ret;
}

/*
 * Transfer the contents of the text into a blob
 * The caller must free the returned blob if b is NULL
 */
blob *
textToBlob(text *t, blob *b, int destroy)
{
	size_t s;
	blob *bin;

	if(t == NULL)
		return NULL;

	s = 0;

	(void)textIterate(t, getLength, &s, 0);

	if(s == 0)
		return b;

	/*
	 * copy b. If b is NULL and an error occurs we know we need to free
	 *	before returning
	 */
	bin = b;
	if(b == NULL) {
		b = blobCreate();

		if(b == NULL)
			return NULL;
	}

	if(blobGrow(b, s) != CL_SUCCESS) {
		cli_warnmsg("Couldn't grow the blob: we may be low on memory\n");
#if	0
		if(!destroy) {
			if(bin == NULL)
				blobDestroy(b);
			return NULL;
		}
		/*
		 * We may be able to recover enough memory as we destroy to
		 * create the blob
		 */
#else
		if(bin == NULL)
			blobDestroy(b);
		return NULL;
#endif
	}

	(void)textIterate(t, addToBlob, b, destroy);

	if(destroy && t->t_next) {
		textDestroy(t->t_next);
		t->t_next = NULL;
	}

	blobClose(b);

	return b;
}

fileblob *
textToFileblob(text *t, fileblob *fb, int destroy)
{
	assert(fb != NULL);
	assert(t != NULL);

	if(fb == NULL) {
		cli_dbgmsg("textToFileBlob, destroy = %d\n", destroy);
		fb = fileblobCreate();

		if(fb == NULL)
			return NULL;
	} else {
		cli_dbgmsg("textToFileBlob to %s, destroy = %d\n",
			fileblobGetFilename(fb), destroy);

		fb->ctx = NULL;	/* no need to scan */
	}

	fb = textIterate(t, addToFileblob, fb, destroy);
	if(destroy && t->t_next) {
		textDestroy(t->t_next);
		t->t_next = NULL;
	}
	return fb;
}

static void
getLength(const line_t *line, void *arg)
{
	size_t *length = (size_t *)arg;

	if(line)
		*length += strlen(lineGetData(line)) + 1;
	else
		(*length)++;
}

static void
addToBlob(const line_t *line, void *arg)
{
	blob *b = (blob *)arg;

	if(line) {
		const char *l = lineGetData(line);

		blobAddData(b, (const unsigned char *)l, strlen(l));
	}
	blobAddData(b, (const unsigned char *)"\n", 1);
}

static void
addToFileblob(const line_t *line, void *arg)
{
	fileblob *fb = (fileblob *)arg;

	if(line) {
		const char *l = lineGetData(line);

		fileblobAddData(fb, (const unsigned char *)l, strlen(l));
	}
	fileblobAddData(fb, (const unsigned char *)"\n", 1);
}

static void *
textIterate(text *t_text, void (*cb)(const line_t *item, void *arg), void *arg, int destroy)
{
	/*
	 * Have two loops rather than one, so that we're not checking the
	 * value of "destroy" lots and lots of times
	 */
#if	0
	while(t_text) {
		(*cb)(t_text->t_line, arg);

		if(destroy && t_text->t_line) {
			lineUnlink(t_text->t_line);
			t_text->t_line = NULL;
		}

		t_text = t_text->t_next;
	}
#else
	if(destroy)
		while(t_text) {
			(*cb)(t_text->t_line, arg);

			if(t_text->t_line) {
				lineUnlink(t_text->t_line);
				t_text->t_line = NULL;
			}

			t_text = t_text->t_next;
		}
	else
		while(t_text) {
			(*cb)(t_text->t_line, arg);

			t_text = t_text->t_next;
		}
#endif
	return arg;
}