/*
 * ratMessage.c --
 *
 *	This file contains code which implements the message entities.
 *
 * TkRat software and its included text is Copyright 1996-2002 by
 * Martin Forssén
 *
 * The full text of the legal notice is contained in the file called
 * COPYRIGHT, included with this distribution.
 */

#include <signal.h>
#include <unistd.h>
#include "ratStdFolder.h"
#include "ratPGP.h"
#include "osdep.h"

/*
 * An array of commands. It contains one entry for each internal message
 * type (as defined by RatMessageType).
 */
static MessageProcInfo *messageProcInfo = NULL;

/*
 * The number of replies created. This is used to create new unique
 * message handlers.
 */
static int numReplies = 0;

/*
 * The number of message entities created. This is used to create new
 * unique command names.
 */
static int numBodies = 0;

static void RatBodyDelete(Tcl_Interp *interp, BodyInfo *bodyInfoPtr);
static BodyInfo *RatFindFirstText(BodyInfo *bodyInfoPtr);
static CONST84 char *RatGetCitation(Tcl_Interp *interp, MessageInfo *msgPtr);
static void RatCiteMessage(Tcl_Interp *interp, Tcl_Obj *dstObjPtr, 
			   CONST84 char *src, CONST84 char *myCitation);

extern long unix_create (MAILSTREAM *stream,char *mailbox);


/*
 *----------------------------------------------------------------------
 *
 * RatInitMessages --
 *
 *      Initialize the message data structures.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The messageProcInfo array is allocated and initialized.
 *
 *
 *----------------------------------------------------------------------
 */

void
RatInitMessages()
{
    messageProcInfo = (MessageProcInfo*)ckalloc(3*sizeof(MessageProcInfo));
    RatStdMessagesInit(&messageProcInfo[RAT_CCLIENT_MESSAGE]);
    RatDbMessagesInit(&messageProcInfo[RAT_DBASE_MESSAGE]);
    RatFrMessagesInit(&messageProcInfo[RAT_FREE_MESSAGE]);
}


/*
 *----------------------------------------------------------------------
 *
 * RatMessageCmd --
 *
 *      Main std mail entity procedure. This routine implements the mail
 *	commands mentioned in ../INTERFACE.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	many.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatMessageCmd(ClientData clientData,Tcl_Interp *interp, int objc,
	Tcl_Obj *CONST objv[])
{
    MessageInfo *msgPtr = (MessageInfo*) clientData;
    Tcl_Obj *rPtr;

    if (objc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		Tcl_GetString(objv[0]), " option ?arg?\"", (char *) NULL);
	return TCL_ERROR;
    }
    if (!strcmp(Tcl_GetString(objv[1]), "headers")) {
	if (objc != 2) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    Tcl_GetString(objv[0]), " headers\"", (char *) NULL);
	    return TCL_ERROR;
	}
	return RatMessageGetHeader(interp,
		(*messageProcInfo[msgPtr->type].getHeadersProc)(interp,
								msgPtr));

    } else if (!strcmp(Tcl_GetString(objv[1]), "body")) {
	if (!msgPtr->bodyInfoPtr) {
	    msgPtr->bodyInfoPtr =
		    (*messageProcInfo[msgPtr->type].createBodyProc)(interp,
		    msgPtr);
	    RatPGPBodyCheck(interp, messageProcInfo, &msgPtr->bodyInfoPtr);
	    Tcl_CreateObjCommand(interp, msgPtr->bodyInfoPtr->cmdName,
		    RatBodyCmd, (ClientData) msgPtr->bodyInfoPtr, NULL);
	}
	Tcl_SetResult(interp, msgPtr->bodyInfoPtr->cmdName, TCL_STATIC);
	return TCL_OK;
    
    } else if (!strcmp(Tcl_GetString(objv[1]), "rawText")) {
	rPtr = Tcl_NewObj();
	Tcl_AppendToObj(rPtr,
		(*messageProcInfo[msgPtr->type].getHeadersProc)(interp,msgPtr),
		-1);
	Tcl_AppendToObj(rPtr, "\r\n", 2);
	Tcl_AppendToObj(rPtr,
		(*messageProcInfo[msgPtr->type].fetchTextProc)(interp, msgPtr),
		-1);
	Tcl_SetObjResult(interp, rPtr);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "get")) {
	ENVELOPE *env = (*messageProcInfo[msgPtr->type].envelopeProc)(msgPtr);
	int i;
	if (objc < 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    Tcl_GetString(objv[0]), " get fields\"", (char *) NULL);
	    return TCL_ERROR;
	}
	for (i=2; i<objc; i++) {
	    if (!strcasecmp(Tcl_GetString(objv[i]), "return_path")) {
		RatInitAddresses(interp, env->return_path);
	    } else if (!strcasecmp(Tcl_GetString(objv[i]), "from")) {
		RatInitAddresses(interp, env->from);
	    } else if (!strcasecmp(Tcl_GetString(objv[i]), "sender")) {
		RatInitAddresses(interp, env->sender);
	    } else if (!strcasecmp(Tcl_GetString(objv[i]), "reply_to")) {
		RatInitAddresses(interp, env->reply_to);
	    } else if (!strcasecmp(Tcl_GetString(objv[i]), "to")) {
		RatInitAddresses(interp, env->to);
	    } else if (!strcasecmp(Tcl_GetString(objv[i]), "cc")) {
		RatInitAddresses(interp, env->cc);
	    } else if (!strcasecmp(Tcl_GetString(objv[i]), "bcc")) {
		RatInitAddresses(interp, env->bcc);
	    } else {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "bad field \"",Tcl_GetString(objv[i]),
			"\": must be one of return_path, from, sender, ",
			"reply_to, to, cc or bcc", (char*)NULL);
		return TCL_ERROR;
	    }
	}
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "reply")) {
	/*
	 * Construct a reply to a message. We should really handle
	 * different character sets here. /MaF
	 */
	ENVELOPE *env = (*messageProcInfo[msgPtr->type].envelopeProc)(msgPtr);
	char handler[32], buf[1024], *cPtr;
	BodyInfo *bodyInfoPtr;
	unsigned long bodylength;
	ADDRESS *adrPtr;
	char *dataPtr;
	Tcl_DString ds;
	Tcl_Obj *oPtr, *vPtr;

	Tcl_DStringInit(&ds);

	if (objc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    Tcl_GetString(objv[0]), " reply to\"", (char *) NULL);
	    return TCL_ERROR;
	}
	sprintf(handler, "reply%d", numReplies++);
	if (!strcasecmp(Tcl_GetString(objv[2]), "sender")) {
	    /*
	     * We should construct a reply which should go only to one person.
	     * We look for the address in the following fields:
	     *  Reply-To:, From:. Sender:, From
	     * As sson as an address is found the search stops.
	     */
	    if (env->reply_to) {
		adrPtr = env->reply_to;
	    } else if (env->from) {
		adrPtr = env->from;
	    } else if (env->sender) {
		adrPtr = env->sender;
	    } else {
		adrPtr = env->return_path;
	    }
	    for (;adrPtr; adrPtr = adrPtr->next) {
		RatAddressTranslate(interp, adrPtr);
		if (adrPtr->mailbox) {
		    if (Tcl_DStringLength(&ds)) {
			Tcl_DStringAppend(&ds, ", ", -1);
		    }
		    Tcl_DStringAppend(&ds, RatAddressMail(adrPtr), -1);
		}
	    }
	    Tcl_SetVar2(interp, handler, "to", Tcl_DStringValue(&ds),
		    TCL_GLOBAL_ONLY);
	} else {
	    /*
	     * We should construct a reply which goes to everybody who has
	     * recieved this message. This is done by first collecting all
	     * addresses found in: Reply-To:, From:, Sender:, To: and Cc:.
	     * Then go though this list and eliminate myself and any
	     * duplicates. Now we use the first element of the list as To:
	     * and the rest as Cc:.
	     */
	    ADDRESS **recipientPtrPtr =(ADDRESS**)ckalloc(16*sizeof(ADDRESS*));
	    int numAllocated = 16;
	    int numRecipients = 0;
	    int inList = 0;
	    ADDRESS *to = NULL;
	    int i, j;

#define SCANLIST(x) for (adrPtr = (x); adrPtr; adrPtr = adrPtr->next) { \
			if (numRecipients == numAllocated) { \
			    numAllocated += 16; \
			    recipientPtrPtr = (ADDRESS**)ckrealloc( \
				    recipientPtrPtr, \
				    numAllocated*sizeof(ADDRESS*)); \
			} \
			recipientPtrPtr[numRecipients++] = adrPtr; \
	    	    }

	    SCANLIST(env->reply_to);
	    if (NULL == env->reply_to) {
		SCANLIST(env->from);
	    }
	    SCANLIST(env->to);
	    SCANLIST(env->cc);

	    for (i=0; i<numRecipients; i++) {
		adrPtr = recipientPtrPtr[i];
		if (!adrPtr->host) {
		    inList = (inList)? 0 : 1;
		    continue;
		}
		if (RatAddressIsMe(interp, adrPtr, 1)) {
		    continue;
		}
		RatAddressTranslate(interp, adrPtr);
		for (j=0; j<i; j++) {
		    if (!RatAddressCompare(adrPtr, recipientPtrPtr[j])) {
			break;
		    }
		}
		if (j < i) {
		    continue;
		}
		if (!to) {
		    to = adrPtr;
		} else {
		    if (Tcl_DStringLength(&ds)) {
			Tcl_DStringAppend(&ds, ", ", 2);
		    }
		    Tcl_DStringAppend(&ds, RatAddressMail(adrPtr), -1);
		}
	    }
	    if (Tcl_DStringLength(&ds)) {
		Tcl_SetVar2(interp, handler, "cc", Tcl_DStringValue(&ds),
			TCL_GLOBAL_ONLY);
	    }
	    if (!to && numRecipients) {
		to = recipientPtrPtr[0];
	    }
	    if (to) {
		snprintf(buf, sizeof(buf), "%s@%s", to->mailbox, to->host);
		Tcl_SetVar2(interp, handler, "to", buf, TCL_GLOBAL_ONLY);
	    }
	    ckfree(recipientPtrPtr);
	}
	if (env->subject) {
	    int match;

	    cPtr = RatDecodeHeader(interp, env->subject, 0);
	    Tcl_DStringSetLength(&ds, 0);
	    Tcl_DStringAppendElement(&ds, "regexp");
	    Tcl_DStringAppendElement(&ds, "-nocase");
	    Tcl_DStringAppendElement(&ds,
		    Tcl_GetVar2(interp, "option","re_regexp",TCL_GLOBAL_ONLY));
	    Tcl_DStringAppendElement(&ds, cPtr);
	    Tcl_DStringAppendElement(&ds, "reply_match");
	    if (TCL_OK == Tcl_EvalEx(interp, Tcl_DStringValue(&ds), -1,
				     TCL_EVAL_DIRECT)
		&& NULL != (oPtr = Tcl_GetObjResult(interp))
		&& TCL_OK == Tcl_GetBooleanFromObj(interp, oPtr, &match)
		&& match) {
		CONST84 char *s = Tcl_GetVar(interp, "reply_match", 0);

		if (!strncmp(s, cPtr, strlen(s))) {
		    cPtr += strlen(s);
		    while (isspace(*cPtr)) cPtr++;
		}
	    }
	    oPtr = Tcl_NewStringObj("Re: ", 4);
	    Tcl_AppendToObj(oPtr, cPtr, -1);
	    Tcl_SetVar2Ex(interp, handler,"subject", oPtr,TCL_GLOBAL_ONLY);
	} else {
	    Tcl_SetVar2(interp, handler, "subject",
		    Tcl_GetVar2(interp, "option","no_subject",TCL_GLOBAL_ONLY),
		    TCL_GLOBAL_ONLY);
	}
	if (env->message_id) {
	    Tcl_SetVar2(interp, handler, "in_reply_to", env->message_id,
		    TCL_GLOBAL_ONLY);
	}
	bodyInfoPtr = RatFindFirstText(msgPtr->bodyInfoPtr);
	if (bodyInfoPtr && (NULL != (dataPtr =
		(*messageProcInfo[bodyInfoPtr->msgPtr->type].fetchBodyProc)
		(bodyInfoPtr, &bodylength)))) {
	    CONST84 char *alias, *attrFormat, *charset = "us-ascii", *citation;
	    ListExpression *exprPtr;
	    PARAMETER *parameter;
	    Tcl_DString *decBPtr;
	    BODY *bodyPtr;
	    int wrap;

	    bodyPtr = bodyInfoPtr->bodyPtr;

	    for (parameter = bodyPtr->parameter; parameter;
		    parameter = parameter->next) {
		if ( 0 == strcasecmp("charset", parameter->attribute)) {
		    charset = parameter->value;
		}
	    }
	    if ((alias = Tcl_GetVar2(interp, "charsetAlias",
				     (char*)charset, TCL_GLOBAL_ONLY))) {
		charset = alias;
	    }
	    decBPtr = RatDecode(interp, bodyPtr->encoding, dataPtr, bodylength,
		    charset);
	    attrFormat = Tcl_GetVar2(interp, "option", "attribution",
		    TCL_GLOBAL_ONLY);
	    if (attrFormat && strlen(attrFormat)
		    && (exprPtr = RatParseList(attrFormat))) {
		oPtr = RatDoList(interp, exprPtr,
			messageProcInfo[msgPtr->type].getInfoProc,
			(ClientData)msgPtr, 0);
		RatFreeListExpression(exprPtr);
		Tcl_AppendToObj(oPtr, "\n", 1);
	    } else {
		oPtr = Tcl_NewObj();
	    }

	    citation = RatGetCitation(interp, msgPtr);
	    RatCiteMessage(interp, oPtr, Tcl_DStringValue(decBPtr), citation);
	    Tcl_DStringFree(decBPtr);
	    vPtr = Tcl_GetVar2Ex(interp,"option","wrap_cited",TCL_GLOBAL_ONLY);
	    Tcl_GetBooleanFromObj(interp, vPtr, &wrap);
	    if (wrap) {
		Tcl_Obj *nPtr;
		nPtr = RatWrapMessage(interp, oPtr);
		Tcl_DecrRefCount(oPtr);
		oPtr = nPtr;
	    }
	    Tcl_SetVar2Ex(interp, handler, "data", oPtr, TCL_GLOBAL_ONLY);
	    Tcl_SetVar2(interp, handler, "data_tags", "Cited noWrap no_spell",
			TCL_GLOBAL_ONLY);
	}
	Tcl_SetResult(interp, handler, TCL_VOLATILE);
	Tcl_DStringFree(&ds);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "copy")) {
	char flags[128], date[128], *spec, *name;
	Tcl_Obj *defPtr, **dobjv, **eobjv, *oPtr;
	MAILSTREAM *stream = NULL;
	struct stat sbuf;
	Tcl_DString ds, specBuf;
	STRING string;
	int dobjc, eobjc, result, i, freeListObjv = 0, specBufUse = 0;
	RatFolderInfo *infoPtr;

	if (objc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    Tcl_GetString(objv[0])," copy vfolder_def\"", NULL);
	    return TCL_ERROR;
	}

	defPtr = objv[2];
        infoPtr = RatGetOpenFolder(interp, defPtr);
	if (infoPtr) {
	    name = msgPtr->name;
	    result = RatFolderInsert(interp, infoPtr, 1, &name);
	    RatFolderClose(interp, infoPtr, 0);
	    return result;
	} 

	Tcl_ListObjGetElements(interp, defPtr, &dobjc, &dobjv);

	/*
	 * If the destination is dbase then we call RatInsert
	 */
	if (!strcmp("dbase", Tcl_GetString(dobjv[1]))) {
	    Tcl_ListObjGetElements(interp, dobjv[5], &eobjc, &eobjv);
	    for (i=0; i<eobjc-1; i++) {
		if (!strcmp("keywords", Tcl_GetString(eobjv[i]))) {
		    break;
		}
	    }
	    oPtr = Tcl_NewListObj(eobjc-i-1, &eobjv[i+1]);
	    result = RatInsertMsg(interp, msgPtr, Tcl_GetString(oPtr),
		    Tcl_GetString(dobjv[4]), Tcl_GetString(dobjv[3]));
	    Tcl_DecrRefCount(oPtr);
	    return result;
	}

	/*
	 * If the destination is a dynamic folder then we have to do some
	 * magic.
	 */
	if (!strcmp("dynamic", Tcl_GetString(dobjv[1]))) {
	    char *name = NULL;
	    Tcl_Obj *oPtr;

	    oPtr = (*messageProcInfo[msgPtr->type].getInfoProc)(interp,
		    (ClientData)msgPtr, RAT_FOLDER_MAIL_REAL, 0);
	    if (oPtr) {
		name = Tcl_GetString(oPtr);
	    }
	    if (!name) {
		struct passwd *passwdPtr = getpwuid(getuid());
		name = passwdPtr->pw_name;
	    }
	    defPtr = Tcl_NewObj();
	    Tcl_ListObjAppendElement(interp, defPtr, dobjv[0]);
	    Tcl_IncrRefCount(dobjv[0]);
	    Tcl_ListObjAppendElement(interp, defPtr,
				     Tcl_NewStringObj("file", 4));
	    Tcl_ListObjAppendElement(interp, defPtr, dobjv[2]);
	    Tcl_IncrRefCount(dobjv[2]);
	    oPtr = Tcl_DuplicateObj(dobjv[3]);
	    Tcl_AppendToObj(oPtr, "/", 1);
	    for (i=0; name[i] && name[i] != '@'; i++);
	    Tcl_AppendToObj(oPtr, name, i);
	    Tcl_ListObjAppendElement(interp, defPtr, oPtr);
	    freeListObjv = 1;
	    Tcl_ListObjGetElements(interp, defPtr, &dobjc, &dobjv);
	}

	/*
	 * Try to create nonexisting files
	 */
	if (!strcmp("file", Tcl_GetString(dobjv[1]))) {
	    name = RatGetFolderSpec(interp, defPtr);
	    if (!strcmp(name, "INBOX")) {
		name = sysinbox();
	    }
	    if (0 != stat(name, &sbuf)) {
		unix_create(NIL, name);
	    }
	}

	/*
	 * Try the case where both source and destination are c-client
	 * messages of sufficiently same type.
	 */
	if (RAT_CCLIENT_MESSAGE == msgPtr->type
	    && RatStdEasyCopyingOK(interp, msgPtr, defPtr)) {
	    result = RatStdMessageCopy(interp, msgPtr,
				       RatGetFolderSpec(interp, defPtr));
	    goto end_copy;
	}

	/*
	 * Open a folder and get the stream
	 */
	spec = RatGetFolderSpec(interp, defPtr);
	stream = OpenStdFolder(interp, spec, NULL);
	if (stream) {
	    Tcl_DStringInit(&ds);
	    RatMessageGet(interp, msgPtr, &ds, flags, sizeof(flags),
		    date, sizeof(date));
	    INIT(&string, mail_string, Tcl_DStringValue(&ds),
		    Tcl_DStringLength(&ds));

	    RatPurgeFlags(flags, 1);
	    if (!mail_append_full(stream, spec, flags, date,
		    &string)){
		CloseStdFolder(interp, stream);
		Tcl_SetResult(interp, "mail_append failed", TCL_STATIC);
		result = TCL_ERROR;
		Tcl_DStringFree(&ds);
		goto end_copy;
	    }
	    if (infoPtr) {
		RatFolderClose(interp, infoPtr, 0);
	    } else {
		CloseStdFolder(interp, stream);
	    }
	    Tcl_DStringFree(&ds);
	    result = TCL_OK;
	} else {
	    result = TCL_ERROR;
	}

end_copy:
	if (specBufUse) {
	    Tcl_DStringFree(&specBuf);
	}
	if (freeListObjv) {
	    Tcl_DecrRefCount(defPtr);
	}
	return result;

    } else if (!strcmp(Tcl_GetString(objv[1]), "list")) {
	ListExpression *exprPtr;
	Tcl_Obj *oPtr;

	if (objc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    Tcl_GetString(objv[0]), " list format\"", (char *) NULL);
	    return TCL_ERROR;
	}
	if (NULL == (exprPtr = RatParseList(Tcl_GetString(objv[2])))) {
	    Tcl_SetResult(interp, "Illegal list format", TCL_STATIC);
	    return TCL_ERROR;
	}
	oPtr = RatDoList(interp, exprPtr,
		messageProcInfo[msgPtr->type].getInfoProc,
		(ClientData)msgPtr, 0);
	Tcl_SetObjResult(interp, oPtr);
	RatFreeListExpression(exprPtr);
	return TCL_OK;

    } else {
	Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[1]),
		"\": must be one of header, body, rawText reply, or get",
		(char*)NULL);
	return TCL_ERROR;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RatMessageGetHeader --
 *
 *      Gets the header of a message
 *
 * Results:
 *	The header is returned as a list in the result area.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */
int
RatMessageGetHeader(Tcl_Interp *interp, char *srcHeader)
{
    char *header, *listArgv[2];
    char *dstPtr, *srcPtr = srcHeader, *cPtr, *tPtr;
    Tcl_Obj *oPtr = Tcl_NewObj(), *fPtr[2];
    int adr;

    if (!srcHeader) {
	RatLog(interp, RAT_FATAL, Tcl_GetStringResult(interp), RATLOG_TIME);
	exit(1);
    }
    header = (char*) ckalloc (strlen(srcHeader)+1);
    if (!strncmp("From ", srcPtr, 5)) {
	while ('\n' != *srcPtr) {
	    srcPtr++;
	}
	if ('\r' == *(++srcPtr)) {
	    srcPtr++;
	}
    }
    while (*srcPtr) {
	dstPtr = header;
	listArgv[0] = dstPtr = header;
	while (*srcPtr && ':' != *srcPtr && ' ' != *srcPtr) {
	    *dstPtr++ = *srcPtr++;
	}
	*dstPtr = '\0';
	fPtr[0] = Tcl_NewStringObj(header, -1);
	cPtr = ++dstPtr;
	do {
	    srcPtr++;
	} while (' ' == *srcPtr || '\t' == *srcPtr);
	do {
	    for (; *srcPtr && '\n' != *srcPtr; srcPtr++) {
		if ('\r' != *srcPtr) {
		    *dstPtr++ = *srcPtr;
		}
	    }
	    while ('\n' == *srcPtr || '\r' == *srcPtr) {
		srcPtr++;
	    }
	} while (*srcPtr && (' ' == *srcPtr || '\t' == *srcPtr));
	*dstPtr = '\0';
	tPtr = cPtr;
	if (0 == strncasecmp("resent-", tPtr, 7)) {
	    tPtr += 7;
	} 
	if (!strcasecmp(tPtr, "to")
		|| !strcasecmp(tPtr, "cc")
		|| !strcasecmp(tPtr, "bcc")
		|| !strcasecmp(tPtr, "from")
		|| !strcasecmp(tPtr, "sender")
		|| !strcasecmp(tPtr, "reply-to")) {
	    adr = 1;
	} else {
	    adr = 0;
	}
	fPtr[1] = Tcl_NewStringObj(RatDecodeHeader(interp, cPtr, adr), -1);
	Tcl_ListObjAppendElement(interp, oPtr, Tcl_NewListObj(2, fPtr));
    }
    ckfree(header);
    Tcl_SetObjResult(interp, oPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RatMessageDelete --
 *
 *      Deletes the given message.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The message and all its bodyparts are deleted from the interpreter
 *	and all the structures are freed.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatMessageDelete(Tcl_Interp *interp, char *msgName)
{
    Tcl_CmdInfo cmdInfo;
    MessageInfo *msgPtr;
    char buf[256];
    int i;

    if (0 == Tcl_GetCommandInfo(interp, msgName, &cmdInfo)) {
	Tcl_AppendResult(interp, "No such message: ", msgName, NULL);
	return TCL_ERROR;
    }
    msgPtr = (MessageInfo*)cmdInfo.objClientData;

    (*messageProcInfo[msgPtr->type].msgDeleteProc)(msgPtr);
    if (msgPtr->bodyInfoPtr) {
	if (msgPtr->bodyInfoPtr->altPtr) {
	    RatBodyDelete(interp, msgPtr->bodyInfoPtr->altPtr);
	}
	if (msgPtr->bodyInfoPtr->decodedTextPtr) {
	    Tcl_DStringFree(msgPtr->bodyInfoPtr->decodedTextPtr);
	    ckfree(msgPtr->bodyInfoPtr->decodedTextPtr);
	}
	if (msgPtr->bodyInfoPtr->secPtr) {
	    RatBodyDelete(interp, msgPtr->bodyInfoPtr->secPtr);
	} else {
	    RatBodyDelete(interp, msgPtr->bodyInfoPtr);
	}
    }
    snprintf(buf, sizeof(buf), "msgInfo_%s", msgPtr->name);
    Tcl_UnsetVar(interp, buf, TCL_GLOBAL_ONLY);
    Tcl_DeleteCommand(interp, msgName);
    for (i=0; i<sizeof(msgPtr->info)/sizeof(*msgPtr->info); i++) {
	if (msgPtr->info[i]) {
	    Tcl_DecrRefCount(msgPtr->info[i]);
	}
    }
    ckfree(msgPtr);

    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * CreateBodyInfo --
 *
 *      Create and somewhat initialize a BodyInfo structure.
 *
 * Results:
 *	A pointer to a BodyInfo structure.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */
BodyInfo*
CreateBodyInfo(MessageInfo *msgPtr)
{
    BodyInfo *bodyInfoPtr;
    int pad = sizeof(char*) - sizeof(BodyInfo)%sizeof(char*);

    if (sizeof(char*) == pad) {
	pad = 0;
    }

    bodyInfoPtr = (BodyInfo*)ckalloc(sizeof(BodyInfo)+pad+16);

    bodyInfoPtr->cmdName = (char*)bodyInfoPtr + pad + sizeof(BodyInfo);

    sprintf(bodyInfoPtr->cmdName, "RatBody%d", numBodies++);
    bodyInfoPtr->firstbornPtr = NULL;
    bodyInfoPtr->nextPtr = NULL;
    bodyInfoPtr->containedEntity = NULL;
    bodyInfoPtr->type = msgPtr->type;
    bodyInfoPtr->msgPtr = msgPtr;
    bodyInfoPtr->secPtr = NULL;
    bodyInfoPtr->altPtr = NULL;
    bodyInfoPtr->decodedTextPtr = NULL;
    bodyInfoPtr->encoded = 0;
    bodyInfoPtr->sigStatus = RAT_UNSIGNED;
    bodyInfoPtr->pgpOutput = NULL;
    return bodyInfoPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * RatBodyCmd --
 *
 *      Main bodypart entity procedure. This routine implements the
 *	bodypart commands mentioned in ../INTERFACE.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	many.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatBodyCmd(ClientData clientData, Tcl_Interp *interp, int objc,
	   Tcl_Obj *const objv[])
{
    BodyInfo *bodyInfoPtr = (BodyInfo*) clientData;
    BODY *bodyPtr = bodyInfoPtr->bodyPtr;
    unsigned long length;
    Tcl_Obj *ov[2], *oPtr, *rPtr;

    if (objc < 2) {
	goto usage;
    }
    if (!strcmp(Tcl_GetString(objv[1]), "children")) {
	BodyInfo **partInfoPtrPtr, *partInfoPtr;

	if (TYPEMULTIPART != bodyPtr->type) {
	    return TCL_OK;
	}

	if (!bodyInfoPtr->firstbornPtr) {
	    (*messageProcInfo[bodyInfoPtr->type].makeChildrenProc)
		    (interp, bodyInfoPtr);
	    for (partInfoPtrPtr = &bodyInfoPtr->firstbornPtr; *partInfoPtrPtr;
		 partInfoPtrPtr = &(*partInfoPtrPtr)->nextPtr) {
		RatPGPBodyCheck(interp, messageProcInfo, partInfoPtrPtr);
		Tcl_CreateObjCommand(interp, (*partInfoPtrPtr)->cmdName,
				     RatBodyCmd, (ClientData)(*partInfoPtrPtr),
				     NULL);
	    }
	}
	rPtr = Tcl_NewObj();
	for (partInfoPtr = bodyInfoPtr->firstbornPtr; partInfoPtr;
		partInfoPtr = partInfoPtr->nextPtr) {
	    Tcl_ListObjAppendElement(interp, rPtr,
				     Tcl_NewStringObj(partInfoPtr->cmdName,
						      -1));
	}
	Tcl_SetObjResult(interp, rPtr);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "message")) {
	char *body;

	if (!bodyInfoPtr->containedEntity) {
	    if (TYPEMESSAGE != bodyPtr->type &&
		    !strcasecmp(bodyPtr->subtype, "rfc822")) {
		Tcl_SetResult(interp, "Not an message/rfc822 bodypart",
			TCL_STATIC);
		return TCL_ERROR;
	    }
	    body = (*messageProcInfo[bodyInfoPtr->type].fetchBodyProc)
		    (bodyInfoPtr, &length);
	    if (body && *body) {
		bodyInfoPtr->containedEntity =
			RatFrMessageCreate(interp, body, length, NULL);
		Tcl_SetResult(interp, bodyInfoPtr->containedEntity,TCL_STATIC);
	    } else {
		Tcl_SetResult(interp,"Failed to fetch mail body. "
			      "The message is damaged", TCL_STATIC);
		return TCL_ERROR;
	    }
	} else {
	    Tcl_SetResult(interp, bodyInfoPtr->containedEntity, TCL_STATIC);
	}
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "type")) {
	Tcl_SetObjResult(interp, RatBodyType(bodyInfoPtr));
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "params")) {
	PARAMETER *parameter;

	rPtr = Tcl_NewObj();
	for (parameter = bodyPtr->parameter; parameter;
		parameter = parameter->next) {
	    ov[0] = Tcl_NewStringObj(parameter->attribute, -1);
	    ov[1] = Tcl_NewStringObj(parameter->value, -1);
	    Tcl_ListObjAppendElement(interp, rPtr, Tcl_NewListObj(2, ov));
	}
	Tcl_SetObjResult(interp, rPtr);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "parameter")) {
	PARAMETER *parameter;

	if (objc != 3) goto usage;
	for (parameter = bodyPtr->parameter; parameter;
		parameter = parameter->next) {
	    if (0 == strcasecmp(Tcl_GetString(objv[2]),parameter->attribute)) {
		Tcl_SetResult(interp, parameter->value, TCL_VOLATILE);
		break;
	    }
	}
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "disp_type")) {
	Tcl_SetResult(interp, bodyPtr->disposition.type, TCL_VOLATILE);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "disp_parm")) {
	PARAMETER *parameter;

	rPtr = Tcl_NewObj();
	for (parameter = bodyPtr->parameter; parameter;
		parameter = parameter->next) {
	    ov[0] = Tcl_NewStringObj(parameter->attribute, -1);
	    ov[1] = Tcl_NewStringObj(parameter->value, -1);
	    Tcl_ListObjAppendElement(interp, rPtr, Tcl_NewListObj(2, ov));
	}
	Tcl_SetObjResult(interp, rPtr);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "id")) {
	if (bodyPtr->id) {
	    Tcl_SetResult(interp, bodyPtr->id, TCL_VOLATILE);
	}
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "description")) {
	if (bodyPtr->description) {
	    char *desc = RatDecodeHeader(interp, bodyPtr->description, 0);
	    Tcl_SetResult(interp, desc, TCL_VOLATILE);
	}
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "size")) {
	Tcl_SetObjResult(interp, Tcl_NewIntObj(bodyPtr->size.bytes));
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "lines")) {
	Tcl_SetObjResult(interp, Tcl_NewIntObj(bodyPtr->size.lines));
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "encoding")) {
	char *enc;

	switch(bodyPtr->encoding) {
	case ENC7BIT:		enc = "7bit"; break;
	case ENC8BIT:		enc = "8bit"; break;
	case ENCBINARY:		enc = "binary"; break;
	case ENCBASE64:		enc = "base64"; break;
	case ENCQUOTEDPRINTABLE:enc = "quoted-printable"; break;
	default:		enc = "unkown"; break;
	}
	Tcl_SetResult(interp, enc, TCL_STATIC);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "isGoodCharset")) {
	PARAMETER *parameter;
	CONST84 char *charset = "us-ascii", *alias;
	int b;

	for (parameter = bodyPtr->parameter; parameter;
		parameter = parameter->next) {
	    if ( 0 == strcasecmp("charset", parameter->attribute)) {
		charset = parameter->value;
		break;
	    }
	}
	if ((alias = Tcl_GetVar2(interp, "charsetAlias", charset,
		TCL_GLOBAL_ONLY))) {
	    charset = alias;
	}
	b = RatGetEncoding(interp, charset) ? 1 : 0;
	Tcl_SetObjResult(interp, Tcl_NewBooleanObj(b));
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "data")) {
	char *isCharset;
	int encoded;

	if (3 != objc && 4 != objc) goto usage;
	if (TCL_OK != Tcl_GetBooleanFromObj(interp, objv[2], &encoded)) {
	    return TCL_ERROR;
	}
	if (objc == 4) {
	    isCharset = Tcl_GetString(objv[3]);
	} else {
	    isCharset = NULL;
	}
	Tcl_SetObjResult(interp,
		RatBodyData(interp, bodyInfoPtr, encoded, isCharset));
	return TCL_OK;


    } else if (!strcmp(Tcl_GetString(objv[1]), "saveData")) {
	int encoded, convertNL;
	Tcl_Channel channel;

	if (5 != objc) goto usage;
	if (NULL==(channel=Tcl_GetChannel(interp,Tcl_GetString(objv[2]),NULL))
	    || TCL_OK != Tcl_GetBooleanFromObj(interp, objv[3], &encoded)
	    || TCL_OK != Tcl_GetBooleanFromObj(interp, objv[4], &convertNL)) {
	    goto usage;
	}
	return RatBodySave(interp, channel, bodyInfoPtr, encoded, convertNL);

    } else if (!strcmp(Tcl_GetString(objv[1]), "dsn")) {
	char *body;
	int r;

	if (TYPEMESSAGE != bodyPtr->type &&
		!strcasecmp(bodyPtr->subtype, "delivery-status")) {
	    Tcl_SetResult(interp, "Not an message/delivery-status bodypart",
		    TCL_STATIC);
	    return TCL_ERROR;
	}
	body = (*messageProcInfo[bodyInfoPtr->type].fetchBodyProc)
		(bodyInfoPtr, &length);
	if (*body) {
	    oPtr = Tcl_NewStringObj(body, length);
	    r = RatDSNExtract(interp, oPtr);
	    Tcl_DecrRefCount(oPtr);
	    return r;
	} else {
	    Tcl_SetResult(interp, "No body", TCL_STATIC);
	    return TCL_ERROR;
	}

    } else if (!strcmp(Tcl_GetString(objv[1]), "getShowCharset")) {
	CONST84 char *c_charset = "us-ascii", *alias;
	PARAMETER *parmPtr;
	char *charset;

	if (TYPETEXT != bodyPtr->type) {
	    Tcl_AppendElement(interp, "good");
	    Tcl_AppendElement(interp, "us-ascii");
	    return TCL_OK;
	}
	for (parmPtr = bodyPtr->parameter; parmPtr; parmPtr = parmPtr->next) {
	    if (!strcasecmp(parmPtr->attribute, "charset")) {
		c_charset = parmPtr->value;
		break;
	    }
	}

	charset = cpystr(c_charset);
	lcase(charset);

	/*
	 * - See if this charset is an alias and resolve that if so.
	 * - Check if this is a charset we do have a font for
	 *   return good and the charset in that case.
	 * - Check if this is a character set we know about and convert.
	 *   return lose if that is the case.
	 * - return none.
	 */
	if (NULL != ( alias = Tcl_GetVar2(interp, "charsetAlias", charset,
					  TCL_GLOBAL_ONLY))) {
	    ckfree(charset);
	    charset = cpystr(alias);
	}
	if (Tcl_GetVar2(interp, "fontEncoding", charset, TCL_GLOBAL_ONLY)) {
	    ov[0] = Tcl_NewStringObj("good", 4);
	    ov[1] = Tcl_NewStringObj("charset", -1);
	    Tcl_SetObjResult(interp, Tcl_NewListObj(2, ov));
	    ckfree(charset);
	    return TCL_OK;
	}
	/*
	 * This converting part is not implemented yet
	 */
	ov[0] = Tcl_NewStringObj("none", 4);
	ov[1] = Tcl_NewStringObj("", 0);
	Tcl_SetObjResult(interp, Tcl_NewListObj(2, ov));
	ckfree(charset);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "findShowCommand")) {
	return RatMcapFindCmd(interp, bodyInfoPtr);

    } else if (!strcmp(Tcl_GetString(objv[1]), "filename")) {
	PARAMETER *parmPtr;
	char *filename = NULL, *delim, *d;

	for (parmPtr = bodyPtr->disposition.parameter; parmPtr;
		parmPtr = parmPtr->next) {
	    if (!strcasecmp(parmPtr->attribute, "filename")
		    || !strcasecmp(parmPtr->attribute, "name")) {
		filename = parmPtr->value;
		break;
	    }
	}
	for (parmPtr = bodyPtr->parameter; !filename && parmPtr;
		parmPtr = parmPtr->next) {
	    if (!strcasecmp(parmPtr->attribute, "filename")
		    || !strcasecmp(parmPtr->attribute, "name")) {
		filename = parmPtr->value;
		break;
	    }
	}
	if (!filename && bodyPtr->description
		&& !strchr(bodyPtr->description, ' ')) {
	    filename = bodyPtr->description;
	}
	if (filename) {
	    d = RatDecodeHeader(interp, filename, 0);
	    delim = strrchr(d, '/');
	    if (delim) {
		Tcl_SetResult(interp, delim+1, TCL_VOLATILE);
	    } else {
		Tcl_SetResult(interp, d, TCL_VOLATILE);
	    }
	}
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "encoded")) {
	Tcl_SetObjResult(interp, Tcl_NewIntObj(bodyInfoPtr->encoded));
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "sigstatus")) {
	char *status = NULL;

	switch (bodyInfoPtr->sigStatus) {
	case RAT_UNSIGNED:	status = "pgp_none"; break;
	case RAT_UNCHECKED:	status = "pgp_unchecked"; break;
	case RAT_SIG_GOOD:	status = "pgp_good"; break;
	case RAT_SIG_BAD:	status = "pgp_bad"; break;
	}
	Tcl_SetResult(interp, status, TCL_STATIC);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "checksig")) {
	RatPGPChecksig(interp, messageProcInfo, bodyInfoPtr);
	return TCL_OK;

    } else if (!strcmp(Tcl_GetString(objv[1]), "getPGPOutput")) {
	if (bodyInfoPtr->pgpOutput) {
	    Tcl_SetResult(interp, Tcl_DStringValue(bodyInfoPtr->pgpOutput),
		    TCL_VOLATILE);
	} else {
	    Tcl_ResetResult(interp);
	}
	return TCL_OK;

    }

 usage:
    Tcl_AppendResult(interp, "Illegal argument string", NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * RatBodyDelete --
 *
 *      Deletes the given body.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The bodypart and all its siblings are deleted from the interpreter
 *	and all the structures are freed.
 *
 *
 *----------------------------------------------------------------------
 */

static void
RatBodyDelete(Tcl_Interp *interp, BodyInfo *bodyInfoPtr)
{
    BodyInfo *siblingInfoPtr, *nextSiblingInfoPtr;
    Tcl_DeleteCommand(interp, bodyInfoPtr->cmdName);
    siblingInfoPtr = bodyInfoPtr->firstbornPtr;
    (*messageProcInfo[bodyInfoPtr->type].bodyDeleteProc)(bodyInfoPtr);
    while (siblingInfoPtr) {
	nextSiblingInfoPtr = siblingInfoPtr->nextPtr;
	RatBodyDelete(interp, siblingInfoPtr);
	siblingInfoPtr = nextSiblingInfoPtr;
    }
    if (bodyInfoPtr->containedEntity) {
	RatMessageDelete(interp, bodyInfoPtr->containedEntity);
    }
    if (bodyInfoPtr->pgpOutput) {
	Tcl_DStringFree(bodyInfoPtr->pgpOutput);
	ckfree(bodyInfoPtr->pgpOutput);
    }
    ckfree(bodyInfoPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * RatMessageGet --
 *
 *      Retrieves a message in textual form. The text is placed in the
 *	supplied Tcl_DString.
 *
 * Results:
 *	No result.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

void
RatMessageGet(Tcl_Interp *interp, MessageInfo *msgPtr, Tcl_DString *ds,
	      char *flags, size_t flaglen, char *date, size_t datelen)
{
    char *data;
    int seen;
    Tcl_Obj *oPtr;

    data = (*messageProcInfo[msgPtr->type].getHeadersProc)(interp, msgPtr);
    Tcl_DStringAppend(ds, data, strlen(data));
    Tcl_DStringAppend(ds, "\r\n", 2);
    if (msgPtr->folderInfoPtr) {
	seen  = (*msgPtr->folderInfoPtr->getFlagProc)(msgPtr->folderInfoPtr,
		interp, msgPtr->msgNo, RAT_SEEN);
    } else {
	seen = 1;
    }
    data = (*messageProcInfo[msgPtr->type].fetchTextProc)(interp, msgPtr);
    Tcl_DStringAppend(ds, data, strlen(data));
    if (!seen) {
        (*msgPtr->folderInfoPtr->setFlagProc)(msgPtr->folderInfoPtr,
	    interp, msgPtr->msgNo, RAT_SEEN, 0);
    }
    if (flags) {
	oPtr = (*messageProcInfo[msgPtr->type].getInfoProc)(interp,
		(ClientData)msgPtr, RAT_FOLDER_FLAGS, 0);
	strlcpy(flags, Tcl_GetString(oPtr), flaglen);
	oPtr = (*messageProcInfo[msgPtr->type].getInfoProc)(interp,
		(ClientData)msgPtr, RAT_FOLDER_DATE_IMAP4, 0);
	strlcpy(date, Tcl_GetString(oPtr), datelen);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RatInsertCmd --
 *
 *      Inserts the given message into the database
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatInsertCmd(ClientData clientData, Tcl_Interp *interp, int objc,
	     Tcl_Obj *const objv[])
{
    Tcl_CmdInfo cmdInfo;
    MessageInfo *msgPtr;

    if (objc != 5) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
			 Tcl_GetString(objv[0]),
			 " msgId keywords exDate exType\"", (char *) NULL);
	return TCL_ERROR;
    }
    if (0 == Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo)) {
	Tcl_AppendResult(interp, "No such message: ", Tcl_GetString(objv[1]),
			 NULL);
	return TCL_ERROR;
    }
    msgPtr = (MessageInfo*)cmdInfo.objClientData;
    return RatInsertMsg(interp, msgPtr, Tcl_GetString(objv[2]),
			Tcl_GetString(objv[3]), Tcl_GetString(objv[4]));
}


/*
 *----------------------------------------------------------------------
 *
 * RatInsertMsg --
 *
 *      Inserts the given message into the database
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatInsertMsg (Tcl_Interp *interp, MessageInfo *msgPtr, char *keywords,
	char *exDate, char *exType)
{
    char *to, *from, *cc, *subject, *msgid, *ref, *flags, *key, *value;
    MESSAGECACHE elt;
    int listObjc, elemObjc;
    char *eFrom, *header, *body, *s, *e, *d;
    Tcl_DString dString;
    int result, i;
    struct tm tm;
    time_t date = 0, exTime;
    Tcl_Obj *oPtr, **listObjv, **elemObjv;

    to = from = cc = subject = msgid = ref = flags = NULL;
    if (TCL_OK != RatMessageGetHeader(interp,
	    (*messageProcInfo[msgPtr->type].getHeadersProc)(interp, msgPtr))) {
	return TCL_ERROR;
    }
    oPtr = Tcl_GetObjResult(interp);
    Tcl_ListObjGetElements(interp, oPtr, &listObjc, &listObjv);
    for (i=0; i<listObjc; i++) {
	Tcl_ListObjGetElements(interp, listObjv[i], &elemObjc, &elemObjv);
	key = Tcl_GetString(elemObjv[0]);
	value = Tcl_GetString(elemObjv[1]);
	if (!strcasecmp(key, "to")) {
	    to = cpystr(value);
	} else if (!strcasecmp(key, "from")) {
	    from = cpystr(value);
	} else if (!strcasecmp(key, "cc")) {
	    cc = cpystr(value);
	} else if (!strcasecmp(key, "subject")) {
	    subject = cpystr(value);
	} else if (!strcasecmp(key, "message-id")) {
	    msgid = cpystr(value);
	} else if (!strcasecmp(key, "references")
		&& !ref
		&& (s = strchr(value, '<'))
		&& (e = strchr(s, '>'))) {
	    ref = (char*)ckalloc(e-s+1);
	    strlcpy(ref, s, e-s+1);
	} else if (!strcasecmp(key, "in-reply-to")
		&& (s = strchr(value, '<'))
		&& (e = strchr(s, '>'))) {
	    ckfree(ref);
	    ref = (char*)ckalloc(e-s+1);
	    strlcpy(ref, s, e-s+1);
	    ref = cpystr(value);
	} else if (!strcasecmp(key, "status") ||
		   !strcasecmp(key, "x-status")) {
	    if (flags) {
		flags = (char*)ckrealloc(flags,
			strlen(flags)+strlen(value)+1);
		strcpy(&flags[strlen(flags)], value);
	    } else {
		flags = cpystr(value);
	    }
	} else if (!strcasecmp(key, "date")) {
	    if (T == mail_parse_date(&elt, value)) {
		tm.tm_sec = elt.seconds;
		tm.tm_min = elt.minutes;
		tm.tm_hour = elt.hours;
		tm.tm_mday = elt.day;
		tm.tm_mon = elt.month - 1;
		tm.tm_year = elt.year+70;
		tm.tm_wday = 0;
		tm.tm_yday = 0;
		tm.tm_isdst = -1;
		date = (int)mktime(&tm);
	    } else {
		date = 0;
	    }
	}
    }
    if (flags) {
	for (s = d = flags; *s; s++) {
	    if ('D' != *s && 'F' != *s) {
		*d++ = *s;
	    }
	}
	*d = '\0';
    } else {
	oPtr = (*messageProcInfo[msgPtr->type].getInfoProc)(interp,
		(ClientData)msgPtr, RAT_FOLDER_UNIXFLAGS, 0);
	flags = cpystr(Tcl_GetString(oPtr));
    }
    if (0 == date) {
	long myLong = 0;
	oPtr = (*messageProcInfo[msgPtr->type].getInfoProc)(interp,
		(ClientData)msgPtr, RAT_FOLDER_DATE_N, 0);
	Tcl_GetLongFromObj(interp, oPtr, &myLong);
	date = (time_t) myLong;
    }
    Tcl_DStringInit(&dString);
    eFrom = (*messageProcInfo[msgPtr->type].getEnvelopeProc)(interp, msgPtr);
    header = (*messageProcInfo[msgPtr->type].getHeadersProc)(interp, msgPtr);
    Tcl_DStringAppend(&dString, header, strlen(header));
    Tcl_DStringAppend(&dString, "\r\n", 2);
    body = (*messageProcInfo[msgPtr->type].fetchTextProc)(interp, msgPtr);
    Tcl_DStringAppend(&dString, body, strlen(body));
    Tcl_ResetResult(interp);
    exTime = atol(exDate);
    if (!strcmp("none", exType)) {
	exTime = 0;
    }
    result = RatDbInsert(interp, to, from, cc, msgid, ref, subject, date,
	    flags, keywords, exTime, exType, eFrom, Tcl_DStringValue(&dString),
	    Tcl_DStringLength(&dString));
    Tcl_DStringFree(&dString);
    ckfree(to);
    ckfree(from);
    ckfree(cc);
    ckfree(msgid);
    ckfree(ref);
    ckfree(subject);
    ckfree(flags);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * RatParseList --
 *
 *      Parse a list expression (almost like a printf format string)
 *
 * Results:
 *	A structure representing the parsed expression, or null if
 *	there is a syntax error in the format string.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

ListExpression*
RatParseList(const char *format)
{
    ListExpression *expPtr;
    int i, w, expIndex, bufLen, num;
    char buf[1024];

    for(i=num=0; '\0' != format[i]; i++) {
	if ('%' == format[i] && format[i+1] && '%' != format[i+1]) {
	    while (format[++i] && ('-' == format[i]
		    || isdigit((unsigned char)format[i])));
	    if (!strchr("snmrRbBdDSitM", format[i])) {
		return NULL;
	    }
	    num++;
	}
    }
    expPtr = (ListExpression*)ckalloc(sizeof(ListExpression));
    expPtr->size = num;
    expPtr->preString = (char**)ckalloc(num*sizeof(char*));
    expPtr->typeList =
	(RatFolderInfoType*)ckalloc(num*sizeof(RatFolderInfoType));
    expPtr->fieldWidth = (int*)ckalloc(num*sizeof(int));
    expPtr->leftJust = (int*)ckalloc(num*sizeof(int));
    for (i = expIndex = bufLen = 0; format[i]; i++) {
	if ('%' == format[i]) {
	    if ('%' == format[++i]) {
		buf[bufLen++] = format[i];
		continue;
	    }
	    buf[bufLen] = '\0';
	    expPtr->preString[expIndex] = cpystr(buf);
	    if ('-' == format[i]) {
		expPtr->leftJust[expIndex] = 1;
		i++;
	    } else {
		expPtr->leftJust[expIndex] = 0;
	    }
	    w=0;
	    while (isdigit((unsigned char)format[i])) {
		w = w*10+format[i++]-'0';
	    }
	    expPtr->fieldWidth[expIndex] = w;
	    switch(format[i]) {
	    case 's': expPtr->typeList[expIndex++] = RAT_FOLDER_SUBJECT; break;
	    case 'n': expPtr->typeList[expIndex++] = RAT_FOLDER_NAME; break;
	    case 'm': expPtr->typeList[expIndex++] = RAT_FOLDER_MAIL; break;
	    case 'r': expPtr->typeList[expIndex++] = RAT_FOLDER_NAME_RECIPIENT;
		    break;
	    case 'R': expPtr->typeList[expIndex++] = RAT_FOLDER_MAIL_RECIPIENT;
		    break;
	    case 'b': expPtr->typeList[expIndex++] = RAT_FOLDER_SIZE; break;
	    case 'B': expPtr->typeList[expIndex++] = RAT_FOLDER_SIZE_F; break;
	    case 'd': expPtr->typeList[expIndex++] = RAT_FOLDER_DATE_F; break;
	    case 'D': expPtr->typeList[expIndex++] = RAT_FOLDER_DATE_N; break;
	    case 'S': expPtr->typeList[expIndex++] = RAT_FOLDER_STATUS; break;
	    case 'i': expPtr->typeList[expIndex++] = RAT_FOLDER_INDEX; break;
	    case 't': expPtr->typeList[expIndex++] =RAT_FOLDER_THREADING;break;
	    case 'M': expPtr->typeList[expIndex++] = RAT_FOLDER_MSGID; break;
	    }
	    bufLen = 0;
	} else {
	    buf[bufLen++] = format[i];
	}
    }
    if (bufLen) {
	buf[bufLen] = '\0';
	expPtr->postString = cpystr(buf);
    } else {
	expPtr->postString = NULL;
    }
    return expPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * RatFreeListExpression --
 *
 *      Frees all memory associated with a list expression.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Some memory is freed.
 *
 *
 *----------------------------------------------------------------------
 */

void
RatFreeListExpression(ListExpression *exPtr)
{
    int i;

    for (i=0; i<exPtr->size; i++) {
	ckfree(exPtr->preString[i]);
    }
    ckfree(exPtr->preString);
    ckfree(exPtr->typeList);
    ckfree(exPtr->fieldWidth);
    ckfree(exPtr->leftJust);
    ckfree(exPtr->postString);
    ckfree(exPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * RatDoList --
 *
 *      Print the list information about a message.
 *
 * Results:
 *	A tcl object
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

Tcl_Obj*
RatDoList(Tcl_Interp *interp, ListExpression *exprPtr, RatInfoProc *infoProc,
	ClientData clientData, int index)
{
    Tcl_Obj *oPtr = Tcl_NewObj(), *iPtr;
    char *str;
    int i, j, slen, length;

    for (i=0; i<exprPtr->size; i++) {
	if (exprPtr->preString[i]) {
	    Tcl_AppendToObj(oPtr, exprPtr->preString[i], -1);
	}
	iPtr = (*infoProc)(interp, clientData, exprPtr->typeList[i], index);
	if (!iPtr) {
	    for (j=0; j<exprPtr->fieldWidth[i]; j++) {
		Tcl_AppendToObj(oPtr, " ", 1);
	    }
	    continue;
	}
	if (exprPtr->fieldWidth[i]) {
	    str = Tcl_GetStringFromObj(iPtr, &slen);
	    length = Tcl_NumUtfChars(str, slen);
	    if (length > exprPtr->fieldWidth[i]) {
		j = Tcl_UtfAtIndex(str, exprPtr->fieldWidth[i]) - str;
		Tcl_AppendToObj(oPtr, str, j);
	    } else {
		if (exprPtr->leftJust[i]) {
		    Tcl_AppendObjToObj(oPtr, iPtr);
		    for (j=length; j<exprPtr->fieldWidth[i]; j++) {
			Tcl_AppendToObj(oPtr, " ", 1);
		    }
		} else {
		    for (j=length; j<exprPtr->fieldWidth[i]; j++) {
			Tcl_AppendToObj(oPtr, " ", 1);
		    }
		    Tcl_AppendObjToObj(oPtr, iPtr);
		}
	    }
	} else {
	    Tcl_AppendObjToObj(oPtr, iPtr);
	}
    }
    if (exprPtr->postString) {
	Tcl_AppendToObj(oPtr, exprPtr->postString, -1);
    }
    return oPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * RatMsgInfo --
 *
 *      get information about a message
 *
 * Results:
 *	A tcl object
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

Tcl_Obj*
RatMsgInfo(Tcl_Interp *interp, MessageInfo *msgPtr, RatFolderInfoType type)
{
    return (*messageProcInfo[msgPtr->type].getInfoProc)(interp,
	    (ClientData)msgPtr, type, 0);
}


/*
 *----------------------------------------------------------------------
 *
 * RatBodySave --
 *
 *      Save a bodypart to an open channel.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatBodySave(Tcl_Interp *interp,Tcl_Channel channel, BodyInfo *bodyInfoPtr,
	    int encoded, int convertNL)
{
    BODY *bodyPtr = bodyInfoPtr->bodyPtr;
    char *body;
    int result = 0, i;
    unsigned long length;
    Tcl_DString *dsPtr = NULL;

    if (NULL == (body = (*messageProcInfo[bodyInfoPtr->type].fetchBodyProc)
	    (bodyInfoPtr, &length))) {
	Tcl_SetResult(interp, "[Body not available]\n", TCL_STATIC);
	return TCL_OK;
    }
    if (!encoded) {
	dsPtr = RatDecode(interp, bodyPtr->encoding, body, length, NULL);
	body =Tcl_DStringValue(dsPtr);
	length = Tcl_DStringLength(dsPtr);
    }
    if (convertNL) {
	/* 
	 * This isn't really elegant but since the channel is buffered
	 * we shouldn't suffer too badly.
	 */
	for (i=0; i<length && -1 != result; i++) {
	    if ('\r' == body[i] && '\n' == body[i+1]) {
		i++;
	    }
	    result = Tcl_Write(channel, &body[i], 1);
	}
    } else {
	result = Tcl_Write(channel, body, length);
    }
    if (!encoded) {
	Tcl_DStringFree(dsPtr);
	ckfree(dsPtr);
    }
    if (-1 == result) {
	Tcl_AppendResult(interp, "error writing : ",
		Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * RatFindFirstText --
 *
 *      Finds the first text part of a message
 *
 * Results:
 *	A pointer to the BodyInfo of the first text part, or NULL if none
 *	are found.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

static BodyInfo*
RatFindFirstText(BodyInfo *bodyInfoPtr)
{
    BodyInfo *b2Ptr;

    for (; NULL != bodyInfoPtr; bodyInfoPtr = bodyInfoPtr->nextPtr) {
	if (TYPETEXT == bodyInfoPtr->bodyPtr->type) {
	    return bodyInfoPtr;
	}
	if (bodyInfoPtr->firstbornPtr && (NULL !=
		(b2Ptr = RatFindFirstText(bodyInfoPtr->firstbornPtr)))) {
	    return b2Ptr;
	}
    }
    return NULL;
}


/*
 *----------------------------------------------------------------------
 *
 * RatGetCitation --
 *
 *      Get the citation to use
 *
 * Results:
 *	A pointer to a citation string to use. This pointer will
 *	remain valid until the next call to this function.
 *	are found.
 *
 * Side effects:
 *	May call userproc.
 *
 *
 *----------------------------------------------------------------------
 */

static CONST84 char*
RatGetCitation(Tcl_Interp *interp, MessageInfo *msgPtr)
{
    Tcl_CmdInfo cmdInfo;
    static char citation[80];

    if (0 != Tcl_GetCommandInfo(interp, "RatUP_Citation", &cmdInfo)) {
	if (TCL_OK != Tcl_VarEval(interp,"RatUP_Citation ",msgPtr->name,NULL)){
	    RatLog(interp, RAT_ERROR, Tcl_GetStringResult(interp),
		    RATLOG_EXPLICIT);
	    return "";
	}
	if (79 < strlen(Tcl_GetStringResult(interp))) {
	    RatLog(interp, RAT_ERROR, "Too long citation", RATLOG_EXPLICIT);
	    return "";
	}
	strlcpy(citation, Tcl_GetStringResult(interp), sizeof(citation));
	return citation;
    }
    return Tcl_GetVar2(interp, "option", "reply_lead", TCL_GLOBAL_ONLY);
}

/*
 *----------------------------------------------------------------------
 *
 * RatCiteMessage --
 *
 *      Copy a message and add citation.
 *
 * Results:
 *	The cited text is appended to dstObjPtr
 *
 * Side effects:
 *	Modifies *dstObjPtr
 *
 *----------------------------------------------------------------------
 */

static void
RatCiteMessage(Tcl_Interp *interp, Tcl_Obj *dstObjPtr, CONST84 char *src,
	       CONST84 char *myCitation)
{
    int i, skipSig, addCitBlank, myCitLength;
    Tcl_Obj *oPtr;
    CONST84 char *srcPtr;

    /*
     * Initialize and find out desired behaviour
     */
    myCitLength = strlen(myCitation);
    if (' ' == myCitation[myCitLength-1]) {
	addCitBlank = 1;
	myCitLength--;
    } else {
	addCitBlank = 0;
    }
    oPtr = Tcl_GetVar2Ex(interp, "option", "skip_sig", TCL_GLOBAL_ONLY);
    Tcl_GetBooleanFromObj(interp, oPtr, &skipSig);

    /*
     * Go over the text and add citation
     */
    for (srcPtr = src; *srcPtr;) {
	/*
	 * Stop when encoutering signature (if that option is true)
	 */
	if (skipSig && '-'== srcPtr[0] && '-' == srcPtr[1] && ' ' == srcPtr[2]
		&& '\n' == srcPtr[3]) {
	    break;
	}

	Tcl_AppendToObj(dstObjPtr, myCitation, myCitLength);
	if ('>' != *srcPtr && addCitBlank) {
	    Tcl_AppendToObj(dstObjPtr, " ", 1);
	}
	for (i=0; '\n' != srcPtr[i] && srcPtr[i]; i++);
	Tcl_AppendToObj(dstObjPtr, srcPtr, i);
	srcPtr += i;
	if ('\n' == *srcPtr) {
	    Tcl_AppendToObj(dstObjPtr, "\n", 1);
	    srcPtr++;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RatWrapMessage --
 *
 *      Wraps the text of a message
 *
 * Results:
 *	An opject containing the wrapped text is returned.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

Tcl_Obj*
RatWrapMessage(Tcl_Interp *interp, Tcl_Obj *textPtr)
{
    int wrapLength, l, citLength, citLength2, overflow, i, add, mark, broken;
    CONST84 char *s, *e, *cPtr, *lineStartPtr, *startPtr, *citPtr = NULL;
    Tcl_RegExp citexp, bullexp;
    Tcl_Obj *nPtr = Tcl_NewObj(), *oPtr;

    oPtr = Tcl_GetVar2Ex(interp, "option", "wrap_length", TCL_GLOBAL_ONLY);
    Tcl_GetIntFromObj(interp, oPtr, &wrapLength);
    s = Tcl_GetVar2(interp, "option", "citexp", TCL_GLOBAL_ONLY);
    citexp = Tcl_RegExpCompile(interp, s);
    if (NULL == citexp) {
	RatLogF(interp, RAT_ERROR, "illegal_regexp", RATLOG_EXPLICIT,
		Tcl_GetStringResult(interp));
    }
    s = Tcl_GetVar2(interp, "option", "bullexp", TCL_GLOBAL_ONLY);
    bullexp = Tcl_RegExpCompile(interp, s);
    if (NULL == citexp) {
	RatLogF(interp, RAT_ERROR, "illegal_regexp", RATLOG_EXPLICIT,
		Tcl_GetStringResult(interp));
    }

    for (cPtr = Tcl_GetString(textPtr); *cPtr;) {
	/*
	 * Check if this line needs to be wrapped
	 */
	startPtr = cPtr;
	for (l=0; l < wrapLength && '\n' != *cPtr && *cPtr;
		l++, cPtr = Tcl_UtfNext(cPtr));
	if (l < wrapLength) {
	    Tcl_AppendToObj(nPtr, startPtr, cPtr-startPtr);
	    if ('\n' == *cPtr) {
		Tcl_AppendToObj(nPtr, "\n", 1);
		cPtr++;
	    }
	    continue;
	}

	/*
	 * If it contains no letters after the wrap-point we keep it unwrapped
	 */
	for (s=cPtr; *s && '\n' != *s && !isalpha(*s); s = Tcl_UtfNext(s));
	if (!*s || '\n' == *s) {
	    Tcl_AppendToObj(nPtr, startPtr, s-startPtr);
	    cPtr = s;
	    if ('\n' == *cPtr) {
		Tcl_AppendToObj(nPtr, "\n", 1);
		cPtr++;
	    }
	    continue;
	}

	/*
	 * It should be wrapped, find citation
	 */
	if (citexp
		&& Tcl_RegExpExec(interp, citexp, startPtr, startPtr)
		&& (Tcl_RegExpRange(citexp, 0, &s, &e), s == startPtr)) {
	    citLength = e-s;
	    citPtr = startPtr;
	} else {
	    citLength = 0;
	}

	/*
	 * Find point to break
	 *  First walk backwards until first LWSP.
	 *  Then check that we actually have some text left.
	 *  If not then do not bother wrapping this line
	 */
	for (; !isspace(*cPtr) && cPtr > startPtr+citLength; cPtr--);
	for (s = startPtr+citLength; s < cPtr && isspace(*s); s++);
	if (s == cPtr) {
	    for (; !isspace(*cPtr) && *cPtr; cPtr++);
	    Tcl_AppendToObj(nPtr, startPtr, cPtr-startPtr);
	    continue;
	}

	/*
	 * Add first part of line and linebreak
	 */
	Tcl_AppendToObj(nPtr, startPtr, cPtr-startPtr);
	Tcl_AppendToObj(nPtr, "\n", 1);
	mark = nPtr->length;
	Tcl_AppendToObj(nPtr, citPtr, citLength);

	/*
	 * Continue adding the following lines.
	 * Keep doing that until we find either:
	 *  An empty line (citation does not count)
	 *  A line whose citation differs in any non LWSP-character
	 *  A line whose indention is longer than the curent one, and
	 *    where the difference does not match a bullet expression
	 */
	lineStartPtr = startPtr = ++cPtr;
	l = citLength;
	broken = 1;
	while (*cPtr) {
	    /* Found end of line? */
	    if ('\n' == *cPtr) {
		/* Skip trailing LWSP */
		for (e = cPtr; isspace(*e) && e > startPtr; e--);
		if (e >= startPtr) e++;
		Tcl_AppendToObj(nPtr, startPtr, e-startPtr);
		cPtr++;
		/* Find length of citation */
		if (citexp
			&& Tcl_RegExpExec(interp, citexp, cPtr, cPtr)
			&& (Tcl_RegExpRange(citexp, 0, &s, &e), s == cPtr)) {
		    citLength2 = e-s;
		} else {
		    citLength2 = 0;
		}
		add = 0;
		/* Check for empty line */
		for (s=cPtr+citLength2; isspace(*s) && '\n' != *s && *s;s++);
		if (*s != '\n' &&
		    (isalnum(*s) || '\'' == *s || '"' == *s || '(' == *s)) {
		    /* Is citation identical? */
		    if (citLength == citLength2
			&& !strncmp(cPtr, citPtr, citLength)) {
			add = 1;
		    }
		    if (citLength > citLength2) {
			/* Citation is short (by LWSP only) */
			for (i=citLength2;
			     i < citLength && isspace(citPtr[i]);
			     i++);
			if (i == citLength) {
			    /*
			     * We have found a line with shorter citation.
			     * Change data already inserted into dest as well
			     * as remembered citation
			     */
			    /* Data already there */
			    oPtr = Tcl_NewStringObj(
				nPtr->bytes+mark+citLength,
				nPtr->length-mark-citLength);
			    Tcl_SetObjLength(nPtr, mark+citLength2);
			    Tcl_AppendObjToObj(nPtr, oPtr);
			    Tcl_DecrRefCount(oPtr);
			    l -= citLength-citLength2;
			    citLength = citLength2;
			    add = 1;
			}
		    } else if (citLength < citLength2
			       && Tcl_RegExpExec(interp, bullexp,
						 citPtr+citLength,
						 citPtr+citLength)
			       && (Tcl_RegExpRange(bullexp, 0, &s, &e), 1)
			       && citLength + e - s == citLength2) {
			/* Citation is longer and bullet exp matches */
			/* Data already there */
			oPtr = Tcl_NewStringObj(
			    nPtr->bytes+mark+citLength,
			    nPtr->length-mark-citLength);
			Tcl_SetObjLength(nPtr, mark);
			Tcl_AppendToObj(nPtr, cPtr, citLength2);
			Tcl_AppendObjToObj(nPtr, oPtr);
			Tcl_DecrRefCount(oPtr);
			l += citLength2-citLength;
			add = 1;
			citPtr = cPtr;
			citLength = citLength2;
		    }
		}
		if (add && broken) {
		    Tcl_AppendToObj(nPtr, " ", 1);
		    l++;
		    cPtr += citLength;
		    startPtr = cPtr;
		    broken = 0;
		    continue;
		} else {
		    Tcl_AppendToObj(nPtr, "\n", 1);
		    l = 0;
		    break;
		}
	    } else if (l >= wrapLength) {
		broken = 1;
		for (; !isspace(*cPtr) && cPtr > startPtr; cPtr--);
		l = overflow = 0;
		if (cPtr == startPtr && startPtr == lineStartPtr) {
		    while (!isspace(*cPtr)) cPtr++;
		    overflow = 1;
		}
		Tcl_AppendToObj(nPtr, startPtr, cPtr-startPtr);
		Tcl_AppendToObj(nPtr, "\n", 1);
		if (startPtr != cPtr) {
		    cPtr++;
		}
		lineStartPtr = startPtr = cPtr;
		if (overflow) break;
		mark = nPtr->length;
		Tcl_AppendToObj(nPtr, citPtr, citLength);
		l += citLength;
	    } else {
		l++;
		cPtr = Tcl_UtfNext(cPtr);
	    }
	}
    }

    return nPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * RatBodyType --
 *
 *      Gets the types of a bodypart
 *
 * Results:
 *	A list object containing two strings, the first is the major
 *	type and the second is the subtype.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
Tcl_Obj*
RatBodyType(BodyInfo *bodyInfoPtr)
{
    BODY *bodyPtr = bodyInfoPtr->bodyPtr;
    Tcl_Obj *oPtr[2];

    oPtr[0] = Tcl_NewStringObj(body_types[bodyPtr->type], -1);
    if (bodyPtr->subtype) {
	oPtr[1] = Tcl_NewStringObj(bodyPtr->subtype, -1);
    } else {
	oPtr[1] = Tcl_NewStringObj("", 0);
    }
    return Tcl_NewListObj(2, oPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * RatBodyData --
 *
 *      Gets the content of a bodypart
 *
 * Results:
 *	An object containing the data.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
Tcl_Obj*
RatBodyData(Tcl_Interp *interp, BodyInfo *bodyInfoPtr, int encoded,
	char *charset)
{
    BODY *bodyPtr = bodyInfoPtr->bodyPtr;
    Tcl_Obj *oPtr;
    char *body;
    CONST84 char *isCharset = NULL, *alias;
    PARAMETER *parameter;
    unsigned long length;

    if (charset) {
	isCharset = charset;
    } else if (TYPETEXT == bodyPtr->type){
	isCharset = "us-ascii";
	for (parameter = bodyPtr->parameter; parameter;
		parameter = parameter->next) {
	    if ( 0 == strcasecmp("charset", parameter->attribute)) {
		isCharset = parameter->value;
	    }
	}
	if ((alias = Tcl_GetVar2(interp, "charsetAlias", isCharset,
		TCL_GLOBAL_ONLY))) {
	    isCharset = alias;
	}
    }

    body = (*messageProcInfo[bodyInfoPtr->type].fetchBodyProc)
	    (bodyInfoPtr, &length);
    if (body) {
	if (encoded) {
	    Tcl_Encoding enc;
	    Tcl_DString ds;

	    Tcl_DStringInit(&ds);
	    if (ENC8BIT == bodyPtr->encoding) {
		enc = RatGetEncoding(interp, isCharset);
		Tcl_ExternalToUtfDString(enc, body, length, &ds);
	    } else {
		 Tcl_DStringAppend(&ds, body, length);
	    }
	    oPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds),
				    Tcl_DStringLength(&ds));
	    Tcl_DStringFree(&ds);
	} else {
	    Tcl_DString *dsPtr = RatDecode(interp, bodyPtr->encoding,
		    body, length, isCharset);
	    oPtr = Tcl_NewStringObj(Tcl_DStringValue(dsPtr),
				    Tcl_DStringLength(dsPtr));
	    Tcl_DStringFree(dsPtr);
	    ckfree(dsPtr);
	}
    } else {
	oPtr = Tcl_NewStringObj("[Body not available]\n", -1);
    }
    return oPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * RatMessageInternalDate --
 *
 *      Gets the internal date of a message
 *
 * Results:
 *	A pointer to a MESSAGECACHE entry where only the date-fields may
 *	be used. May return NULL on errors.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
MESSAGECACHE*
RatMessageInternalDate(Tcl_Interp *interp, MessageInfo *msgPtr)
{
    return (*messageProcInfo[msgPtr->type].getInternalDateProc)(interp,msgPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * RatPurgeFlags --
 *
 *      Purge Flagged, Deleted and Recent flags
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May modify the buffer passed as argument. However the result is
 *	never bigger than the argument.
 *
 *----------------------------------------------------------------------
 */

char*
RatPurgeFlags(char *flags, int level)
{
    char *cPtr, *toPurge[4];
    int i, l;

    i = 0;
    if (1 == level) {
	toPurge[i++] = flag_name[RAT_FLAGGED].imap_name;
	toPurge[i++] = flag_name[RAT_DELETED].imap_name;
	toPurge[i++] = flag_name[RAT_RECENT].imap_name;
    } else {
	toPurge[i++] = flag_name[RAT_RECENT].imap_name;
    }
    toPurge[i] = NULL;

    for (i=0; '\0' != toPurge[i]; i++) {
	if (NULL != (cPtr = strstr(flags, toPurge[i]))) {
	    l = strlen(toPurge[i]);
	    if (flags == cPtr) {
		if (' ' == cPtr[l]) {
		    l++;
		}
	    } else {
		cPtr--;
		l++;
	    }
	    strcpy(cPtr, cPtr+l);
	}
    }
    return flags;
}

#ifdef MEM_DEBUG
void ratMessageCleanup()
{
    ckfree(messageProcInfo);
}
#endif /* MEM_DEBUG */


syntax highlighted by Code2HTML, v. 0.9.1