/*:ts=8*/
/*****************************************************************************
 * FIDOGATE --- Gateway UNIX Mail/News <-> FIDO NetMail/EchoMail
 *
 * $Id: message.c,v 3.9.2.0 1995/06/12 17:14:03 mj Exp $
 *
 * Reading and processing FTN text body
 *
 *****************************************************************************
 * Copyright (C) 1990-1995
 *  _____ _____
 * |     |___  |   Martin Junius             FIDO:      2:2452/110.1
 * | | | |   | |   Republikplatz 3           Internet:  mj@sungate.fido.de
 * |_|_|_|@home|   D-52072 Aachen, Germany   Phone:     ++49-241-86931 (voice)
 *
 * This file is part of FIDOGATE.
 *
 * FIDOGATE is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * FIDOGATE 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 FIDOGATE; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************/

#include "fidogate.h"



/*
 * Debug output of line
 */
static void debug_line(out, line)
    FILE *out;
    char *line;
{
    int c;
    
    while( (c = *line++) )
	if( !(c & 0x60) )
	    fprintf(out, "^%c", '@' + c);
	else
	    putc(c, out);
    putc('\n', out);
}



/*
 * Read one "line" from FTN text body. A line comprises arbitrary
 * characters terminated with a CR '\r'. None, one, or many LFs '\n'
 * may follow:
 *
 *    x x x x x x x x x \r [\n...]
 *
 * This function checks for orphan 0-bytes in the message body and
 * recognizes grunged messages produced by SQUISH 1.01.
 *
 * Return values:
 *     -1  ERROR     an error occured
 *      1            next line follows
 *      0  MSG_END   end of message, end of packet
 *      2  MSG_TYPE  end of message, next message header follows
 */
int pkt_get_line(fp, buf, size)
    FILE *fp;
    char *buf;
    int size;
{
    char *p;
    int c, c1, c2;
    int read_lf=FALSE;
    
    p = buf;

    while (size > 3)			/* Room for current + 2 extra chars */
    {
	c = getc(fp);
	
	if(read_lf && c!='\n')		/* No more LFs, this is end of line */
	{
	    ungetc(c, fp);
	    *p = 0;
	    return 1;
	}
	   
	switch(c)
	{
	case EOF:			/* premature EOF */
	    return ERROR;
	    break;

	case 0:				/* end of message or orphan */
	    c1 = getc(fp);
	    c2 = getc(fp);
	    if(c1==EOF || c2==EOF)
		return ERROR;
	    if(c1==2 && c2==0)		/* end of message */
	    {
		*p = 0;
		return MSG_TYPE;
	    }
	    if(c1==0 && c2==0)		/* end of packet */
	    {
		*p = 0;
		return MSG_END;
	    }
	    /* orphan 0-byte, skip */
	    log("pkt_get_line(): orphan 0-char");
	    if(c1)
	    {
		size--;
		*p++ = c1;
	    }
	    if(c2)
	    {
		size--;
		*p++ = c2;
	    }
	    continue;
	    break;
	    
#if 1 /***** Work around for a SQUISH bug **********************************/
	case 2:	    			/* May be grunged packet: start of */
	    c1 = getc(fp);		/* new message                     */
	    if(c1 == EOF)
		return ERROR;
	    if(c1 == 0)			/* Looks like it is ... */
	    {
		*p = 0;
		log("pkt_get_line(): grunged packet");
		return MSG_TYPE;
	    }
	    *p++ = c;
	    *p++ = c1;
	    size--;
	    size--;
	    break;
	    
#endif /********************************************************************/

	case '\r':			/* End of line */
	    read_lf = TRUE;
	    /**fall thru**/
		
	default:
	    *p++ = c;
	    size--;
	    break;
	}
    }

    /* buf too small */
    *p = 0;
    return 1;
}



/*
 * Read text body from packet into Textlist
 *
 * Return values:
 *     -1  ERROR     an error occured
 *      0  MSG_END   end of packet
 *      2  MSG_TYPE  next message header follows
 */
int pkt_get_body(fp, tl)
    FILE *fp;
    Textlist *tl;
{
    int type;

    tl_clear(tl);

    /* Read lines and put into textlist */
    while( (type=pkt_get_line(fp, buffer, sizeof(buffer))) == 1 )
	tl_append(tl, buffer);
    /* Put incomplete last line into textlist, if any */
    if( (type==MSG_END || type==MSG_TYPE) && buffer[0] )
    {
	/* Make sure that this line is terminated by \r\n */
	strncat0(buffer, "\r\n", sizeof(buffer));

	tl_append(tl, buffer);
    }
    
    return type;
}



/*
 * Initialize MsgBody
 */
void msg_body_init(body)
    MsgBody *body;
{
    body->area = NULL;
    tl_init(&body->kludge);
    tl_init(&body->rfc);
    tl_init(&body->body);
    body->tear = NULL;
    body->origin = NULL;
    tl_init(&body->seenby);
    tl_init(&body->path);
    tl_init(&body->via);
}



/*
 * Clear MsgBody
 */
void msg_body_clear(body)
    MsgBody *body;
{
    xfree(body->area);
    body->area = NULL;
    tl_clear(&body->kludge);
    tl_clear(&body->rfc);
    tl_clear(&body->body);
    xfree(body->tear);
    body->tear = NULL;
    xfree(body->origin);
    body->origin = NULL;
    tl_clear(&body->seenby);
    tl_clear(&body->path);
    tl_clear(&body->via);
}



/*
 * Convert message body from Textlist to MsgBody struct
 *
 * Return: -1  error during parsing, but still valid control info
 *         -2  fatal error, can't process message
 */

static char *rfc_headers[] =
{
    FTN_RFC_HEADERS, NULL
};


static int msg_body_parse_echomail(body)
    MsgBody *body;
{
    Textline *p, *pn;

    /*
     * Work our way backwards from the end of the body to the tear line
     */
    /* Search for last ^APath or SEEN-BY line */
    for(p=body->body.last;
	p && strncmp(p->line, "\001PATH", 5) && strncmp(p->line, "SEEN-BY", 7);
	p=p->prev) ;
    if(p == NULL)
	return -2;
    /* ^APATH */
    for(; p && !strncmp(p->line, "\001PATH", 5); p=p->prev) ;
    /* SEEN-BY */
    for(; p && !strncmp(p->line, "SEEN-BY", 7); p=p->prev) ;
    /* Some systems generate empty line[s] between Origin and SEEN-BY :-( */
    for(; p && *p->line=='\r'; p=p->prev );
    /*  * Origin: */
    if(p && !strncmp(p->line, " * Origin:", 10))
	p = p->prev;
    /* --- Tear line */
    if(p && !strncmp(p->line, "---", 3))
	p = p->prev;
    /* Move back */
    p = p ? p->next : body->body.first;
    
    /*
     * Copy to MsgBody
     */
    /* --- Tear line */
    if(p && !strncmp(p->line, "---", 3))
    {
	pn = p->next;
	body->tear = strsave(p->line);
	tl_delete(&body->body, p);
	p = pn;
    }
    /*  * Origin: */
    if(p && !strncmp(p->line, " * Origin:", 10))
    {
	pn = p->next;
	body->origin = strsave(p->line);
	tl_delete(&body->body, p);
	p = pn;
    }
    /* There may be empty lines after Origin ... more FIDO brain damage */
    while(p && *p->line=='\r')
    {
	pn = p->next;
	tl_delete(&body->body, p);
	p = pn;
    }
    /* SEEN-BY */
    while(p)
    {
	pn = p->next;
	if(!strncmp(p->line, "SEEN-BY", 7))
	{
	    tl_remove(&body->body, p);
	    tl_add(&body->seenby, p);
	    p = pn;
	}
	else
	    break;
    }
    /* ^APATH */
    while(p)
    {
	pn = p->next;
	if(!strncmp(p->line, "\001PATH", 5))
	{
	    tl_remove(&body->body, p);
	    tl_add(&body->path, p);
	    p = pn;
	}
	else
	    break;
    }
    /* Delete any following lines */
    while(p)
    {
	pn = p->next;
	tl_delete(&body->body, p);
	p = pn;
    }

    if(body->seenby.n==0 /*|| body->path.n==0*/)
	return -2;
    if(body->tear==NULL || body->origin==NULL || body->path.n==0)
	return -1;
    
    return OK;
}


static int msg_body_parse_netmail(body)
    MsgBody *body;
{
    Textline *p, *pn;

    /*
     * Work our way backwards from the end of the body to the tear line
     */
    /* ^AVia, there may be empty lines within the ^AVia lines */
    for(p=body->body.last;
	p && ( !strncmp(p->line, "\001Via", 4) || *p->line=='\r' );
	p=p->prev) ;
    /*  * Origin: */
    if(p && !strncmp(p->line, " * Origin:", 10))
	p = p->prev;
    /* --- Tear line */
    if(p && !strncmp(p->line, "---", 3))
	p = p->prev;
    /* Move back */
    p = p ? p->next : body->body.first;
    
    /*
     * Copy to MsgBody
     */
    /* --- Tear line */
    if(p && !strncmp(p->line, "---", 3))
    {
	pn = p->next;
	body->tear = strsave(p->line);
	tl_delete(&body->body, p);
	p = pn;
    }
    /*  * Origin: */
    if(p && !strncmp(p->line, " * Origin:", 10))
    {
	pn = p->next;
	body->origin = strsave(p->line);
	tl_delete(&body->body, p);
	p = pn;
    }
    /* ^AVia */
    while(p)
    {
	pn = p->next;
	if(!strncmp(p->line, "\001Via", 4))
	{
	    tl_remove(&body->body, p);
	    tl_add(&body->via, p);
	    p = pn;
	}
	else if(*p->line == '\r')
	{
	    tl_remove(&body->body, p);
	    p = pn;
	}
	else
	    break;
    }
    /* Delete any following lines */
    while(p)
    {
	pn = p->next;
	tl_delete(&body->body, p);
	p = pn;
    }

    return OK;
}


static int is_blank_line(s)
    char *s;
{
    if(!s)
	return TRUE;
    while(*s)
    {
	if(!is_space(*s))
	    return FALSE;
	s++;
    }
    return TRUE;
}


int msg_body_parse(text, body)
    Textlist *text;
    MsgBody *body;
{
    Textline *p, *pn;
    int i;
    int look_for_AREA;
    
    msg_body_clear(body);
    
    p = text->first;
    
    /*
     * 1st, look for ^A kludges and AREA: line
     */
    look_for_AREA = TRUE;
    while(p)
    {
	pn = p->next;
	
	if(p->line[0]=='\001'             && 	/* ^A kludge,    */
	   strncmp(p->line, "\001Via", 4)   )	/* but not ^AVia */
	{
	    tl_remove(text, p);
	    tl_add(&body->kludge, p);
	    p = pn;
	    continue;
	}
	if(look_for_AREA                  &&	/* Only 1st AREA line */
	   !strncmp(p->line, "AREA:", 5)    )	/* AREA:XXX */
	{
	    look_for_AREA = FALSE;
	    body->area = strsave(p->line);
	    tl_delete(text, p);
	    p = pn;
	    continue;
	}

	break;
    }

    /*
     * Next, look for supported RFC header lines. Up to 3 blank
     * lines before the RFC headers are allowed
     */
    if(p && is_blank_line(p->line))
	p = p->next;
    if(p && is_blank_line(p->line))
	p = p->next;
    if(p && is_blank_line(p->line))
	p = p->next;

    while(p)
    {
	int found;
	pn = p->next;

	for(found=FALSE, i=0; rfc_headers[i]; i++)
	    if(!strnicmp(p->line, rfc_headers[i], strlen(rfc_headers[i])))
	    {
		tl_remove(text, p);
		tl_add(&body->rfc, p);
		p = pn;
		found = TRUE;
		break;
	    }

	if(!found)
	    break;
    }

    /*
     * Now, text contains just the message body after kludges
     * and RFC headers, copy to body->body.
     */
    body->body = *text;
    tl_init(text);

    /*
     * Call function for EchoMail and NetMail, respectively, parsing
     * control info at end of text body.
     */
    return body->area ? msg_body_parse_echomail(body)
	              : msg_body_parse_netmail (body) ;
}



/*
 * Debug output of message body
 */
void msg_body_debug(out, body)
    FILE *out;
    MsgBody *body;
{
    Textline *p;
    
    fprintf(out, "----------------------------------------"
	         "--------------------------------------\n");
    if(body->area)
	debug_line(out, body->area);
    for(p=body->kludge.first; p; p=p->next)
	debug_line(out, p->line);
    fprintf(out, "----------------------------------------"
	            "--------------------------------------\n");
    if(body->rfc.first)
    {
	for(p=body->rfc.first; p; p=p->next)
	    debug_line(out, p->line);
	fprintf(out, "----------------------------------------"
		     "--------------------------------------\n");
    }
    for(p=body->body.first; p; p=p->next)
	debug_line(out, p->line);
    fprintf(out, "----------------------------------------"
	         "--------------------------------------\n");
    if(body->tear)
	debug_line(out, body->tear);
    if(body->origin)
	debug_line(out, body->origin);
    for(p=body->seenby.first; p; p=p->next)
	debug_line(out, p->line);
    for(p=body->path.first; p; p=p->next)
	debug_line(out, p->line);
    for(p=body->via.first; p; p=p->next)
	debug_line(out, p->line);
    fprintf(out, "========================================"
	         "======================================\n");
}



/*
 * Write single line to packet file, checking for NULL
 */
int msg_put_line(fp, line)
    FILE *fp;
    char *line;
{
    if(line)
	fputs(line, fp);
    return ferror(fp);
}



/*
 * Write MsgBody to packet file
 */
int msg_put_msgbody(fp, body, term)
    FILE *fp;
    MsgBody *body;
    int term;
{
    msg_put_line(fp,  body->area  );
    tl_fput     (fp, &body->kludge);
    tl_fput     (fp, &body->rfc   );
    tl_fput     (fp, &body->body  );
    msg_put_line(fp,  body->tear  );
    msg_put_line(fp,  body->origin);
    tl_fput     (fp, &body->seenby);
    tl_fput     (fp, &body->path  );
    tl_fput     (fp, &body->via   );

    if(term)
	putc(0, fp);			/* Terminating 0-byte */
    
    return ferror(fp);
}



/*
 * Convert text line read from FTN message body, using charset_xlate()
 */
char *msg_xlate_line(buf, n, line)
    char *buf;				/* Buffer for converted text */
    int n;				/* Size of buffer */
    char *line;				/* Input */
{
    char *s, *p, *xl;
    int c;

    n--;				/* Room for \0 char */

    for(s=line, p=buf; *s; s++)
    {
	c = *s & 0xff;
	
	/*
	 * Special chars require special treatment ...
	 */
	if(c=='\n' || c==0x8d)		/* Ignore \n and soft \r */
	    continue;
	if(c == '\r')
	    c = '\n';
	else if(c < ' ') {
	    /*
	     * Substitute control chars with '^X'
	     */
	    if(c!='\t' && c!='\f')
	    {
		if(!n--)
		    break;
		*p++ = '^';
		c = c + '@';
	    }
	}
	else if(c & 0x80)
	{
	    /*
	     * Translate special characters according to character set
	     */
	    xl = charset_xlate(c);
	    if(!xl || !*xl)
		continue;
	    while(*xl)
	    {
		if(!n--)
		    break;
		*p++ = *xl++;
	    }
	    continue;
	}

	/*
	 * Put normal char into buf
	 */
	if(!n--)
	    break;
	*p++ = c;
    }

    *p = 0;
    
    return OK;
}



/*
 * Format buffer line and put it into Textlist. Returns number of
 * lines.
 */
int msg_format_buffer(buffer, tlist)
    char *buffer;
    Textlist *tlist;
{
    /*
     * Remark: MAX_LINELEN+16 and MAX_LINELEN+8, the numbers +16 and +8
     *         are just arbitrary values to be safe.
     */
    char *p, *np;
    char localbuffer[MAX_LINELEN + 16];
    int i;
    int lines;
    
    if(strlen(buffer) <= MAX_LINELEN)		/* Nothing to do */
    {
	tl_append(tlist, buffer);
	return 1;
    }
    else
    {
	/*
	 * Break line with word wrap
	 */
	lines = 0;
	p     = buffer;

	while(TRUE)
	{
	    /*
	     * Search backward for a whitespace to break line. If no proper
	     * point is found, the line will not be splitted.
	     */
	    for(i=MAX_LINELEN-1; i>=0; i--)
		if(is_blank(p[i]))	/* Found a white space */
		    break;
	    if(i < MAX_LINELEN/2)	/* Not proper space to split found, */
	    {				/* put line as is                   */
		tl_append(tlist, p);
		lines++;
		return lines;
	    }
	    for(; i>=0 && is_blank(p[i]); i--);	/* Skip more white space */
	    i++;				/* Return to last white sp. */

	    /*
	     * Cut here and put into textlist
	     */
	    np = p + i;
	    *np++ = 0;
	    strncpy0(localbuffer, p, MAX_LINELEN+8);
	    strcat(localbuffer, "\n");
	    tl_append(tlist, localbuffer);
	    lines++;
	    
	    /*
	     * Advance buffer pointer and test length of remaining line
	     */
	    p = np;
	    for(; *p && is_blank(*p); p++);	/* Skip white space */
	    if(*p == 0)				/* The end */
		return lines;
	    if(strlen(p) <= MAX_LINELEN)	/* No more wrappin' */
	    {
		tl_append(tlist, p);
		lines++;
		return lines;
	    }

	    /* Play it again, Sam! */
	}
    }
    /**NOT REACHED**/
    return 0;
}
