
/***************************************************************************
 *   Copyright (C) 1997 to 2004 by Jonathan Duddington                     *
 *   email: jonsd@users.sourceforge.net                                    *
 *                                                                         *
 *   This program 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 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   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 see:                           *
 *               <http://www.gnu.org/licenses/>.                           *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include "kernel.h"
#include "wimp.h"
#include "werr.h"
#include "dbox.h"
#include "os.h"
#include "flex.h"
#include "alarm.h"
#include "narc.h"
#include "hdrs.h"

#ifdef MemCheck
#include "MemCheck:Flex.h"
#endif



extern CARDFILE_REC cardfile[N_CARDFILES];
extern FOLDREC list_fr[N_LISTS];
extern FOLDREC src_fr;
extern OPTIONS options;
extern int transport_opts;
extern char *sort_card_file;
extern int lock_lists;
extern int regex_err;
extern int pluto_slave;

extern BOX box_table[N_BOXES];
extern char box_selected[N_BOXES];

extern NEWSG **ng_index;
extern int fetch_list_count;
extern int cardfile_changed[];

extern wimp_menustr *wmenu_boxes_names;
extern FILE *f_leave_mail;
extern char base64_decode[];


#define MIN_SQUASH_LENGTH 40  /* don't squash if less than this */
#define SQUASH_BUF_SIZE 10000

TEXTR text_rec_temp;

char *strip_mailing_list = "";  /* set by setup module */
int  strip_mailing_len = 0;
int  strip_mailing_posn;

char *atext_base = NULL;         /* article data in memory */
int atext_max = 0;               /* max. length of article data in memory */
static int  squash_workspace_size=0;
static char *squash_workspace=NULL;

extern int dont_save_cardfile;


static char fname_import[128];
dbox dbox_import=NULL;
static int import_box;
static int import_type;
static int import_flags;

unsigned int *ixlist_article = NULL;     /* card sorted by article address */

FILE *f_article_delete = NULL;
static int fileno_article_delete;

static FILE *file_append_article=NULL;
static int file_append_cf=0;
int fileno_append;              /* article file currently appending */
int import_filters=0;
int article_total_length=0;
int error_param;
int n_filtered;
int n_binned;
int n_undelivered;

char appended_art_file[N_ART_FILES_TOT];   /* note which article files have received news */
char compact_attempted[N_ART_FILES_TOT];
char errmess_article_read[80];

extern char addr_colr_trans[16];

int article_filetypes[] = {
	0xfff,    /* text */
	0xfaf,    /* HTML */
};


MNEM_TAB article_keys[] = {
	"from", 1,
	"subject", 2,
	"date", 3,
	"newsgroups", 4,
	"x-newsreader", 5,
	"x-mailer", 6,
	"to", 7,
	"references", 8,
	"message-id", 9,
	"bcc", 10,
	"followup-to", 11,
	"reply-to", 12,
	"envelope-to",13,
	"cc", 14,
	"return-receipt-to",15,
	"source",16,            /* internal only, not internet */
	"x-pluto-source",16,
	"x-categories",17,      /* internal only, not internet */
	"path",18,
	"content-transfer-encoding",19,
	"content-type",20,
	"content-disposition",21,
	"x-confirm-reading-to",22,
	"keywords", 23,
	"sender", 24,
	"x-mailing-list-name", 25,
	"delivered-to", 26,
	"x-sender", 27,
	"x-envelope-to", 28,
	"x-receiver", 28,       /* same as x-envelope-to */
	"original-recipient", 28, /* same as x-envelope-to */
	"x-rcpt-to", 28,        /* same as x-envelope-to */
	"in-reply-to", 29,
	"x-status", 30,
	"status", 31,
	"list-post", 32,
	"mail-followup-to", 33,
	"content-id", 34,
	"supersedes", 35,
	"return-path", 36,
	NULL, 0
};

/* to select specified lines in an internet header so they
   can be displayed separately */

#define N_KEY_DISPLAY  4
static int key_display_start[N_KEY_DISPLAY];
static int key_display_end[N_KEY_DISPLAY];
static char key_display_name[N_KEY_DISPLAY][24];
static int n_key_display = 1;

#define N_CROSSPOST   8
static int n_crosspost;
static char crosspost_source[N_CROSSPOST];
static char crosspost_box[N_CROSSPOST];

#define MB_FROM		0
#define MB_TO		1
#define MB_ENVTO	2
#define MB_ENVTO2	3
#define MB_ENVTO3       4
#define MB_CC		5
#define MB_MLIST        6
#define MB_RCPTTO       7

#define N_MB   8
static int mailbox[N_MB];
static int mailsource[N_MB];
static int mailuser[N_MB];
static NEWSG *ng_found;

char *string_repair = ". Please try running 'Articles->Repair' from Pluto's icon bar menu.";

ART_FILE_HEADER art_file_header[N_ART_FILES_TOT];  /* plus external boxes */



static CARD_EXPANDED card_exp;



int lookup_mnem(MNEM_TAB *mnemtab, char *string)
/***********************************************/
{
	while(mnemtab->mnem != NULL)
	{
		if(strcmp(mnemtab->mnem,string) == 0)
		{
			return(mnemtab->value);
		}
		mnemtab++;
	}
	return(0);
}   /* end of lookup_mnem */







/*************************************************************************************
Load articles   text, email, news
Dropped onto window or iconbar icon
*************************************************************************************/




int fwriteaccess(char *fname)
/***************************/
/* Does the file have write access ? */
{
	os_regset regs;
	os_error  *error;

	regs.r[0] = 5;   /* OS_File 5 - read catalogie info */
	regs.r[1] = (int)fname;
	error = os_swix(8,&regs);
	if(error == NULL)
	{
		if((regs.r[5] & 2)==0)
			return(0);
	}
	return(1);
}   /* end of fwriteaccess */




void article_file_close(void)
/*************************/
/* To match article_open_shortest */
{
	if(file_append_article != NULL)
	{
		fclose(file_append_article);
		file_append_article = NULL;
	}
	article_total_length = 0;
}   /* end of article_file_close */






void article_file_ensure_closed(int fileno)
/*****************************************/
{
	if(fileno == fileno_append)
	{
		article_file_close();
	}
}   /* end of article_file_ensure_closed */






FILE *article_fopen(int cf, int fileno, char *access)
/***************************************************/
/* cf determines whether this is an external article file.
   cf may be 0, indicating an internal article file */
{
	FILE *f=NULL;
	char *p;
	os_error *error;
	char fname[128];


	if(cf > 0)
	{
		fileno = fileno & 0x1f;    /* 32 article files in an external box */
	}
	else
	{
		if(fileno >= N_ART_FILES)
		{
			werr(0,"Bad article file number %d%s",fileno,string_repair);
			return(NULL);
		}
		fileno = fileno & 0x3f;    /* 64 files in main articles directory */
	}

	if(cardfile[cf].path[0]==0)
	{
		werr(0,"External box %s is no longer loaded",cardfile[cf].name);
		return(NULL);
	}

	/* close this file if it's currently open for appending */
	article_file_ensure_closed(cf_filenum(cf,fileno));

	sprintf(fname,"%s.articles%d",cardfile[cf].path,fileno);

	if(strcmp(access,"r") != 0)
	{
		/* not a simple read access, check that write access is allowed */
		if(fwriteaccess(fname)==0)
		{
			/* no write access */
			werr(0,"No write access to article file: %s",fname);
			return(NULL);
		}
	}

	f = fopen(fname,access);

	if(f==NULL)
	{
		p = "";
		error = os_swi3(0xd+os_X, 0x48, (int)fname, 0);
		if(error)
		{
			p = error->errmess;
		}

		if(pluto_slave==0)
		{
			/* don't report the errors if it's a slave copy */
			werr(0,"Can't '%s' access article file %d '%s'. Error: '%s''",access,fileno,fname,p);
		}
	}

	return(f);
}   /* end of article_fopen */





FILE *article_open_shortest(int box, int length, int omit)
/*******************************************************/
/* Open the shortest article file for append */
/* box = -1  not specified
       = 0-63  box number, check for external box
   length = 0 open new file, else only if size limit reached
   omit = fileno  don't try this fileno again */
{
	int  min;
	int  min_file;
	int  fileno;
	int  cf;
	int  n_art_files;

#define APPEND_MAX 1000000    /* max before changing to another file */

	/* check for external box and use its own articles file */
	if((box >= BOX_EXTERN) && (box < BOX_BIN))
	{
		cf = box_to_cf(box);
		n_art_files = N_ART_FILES_X;
	}
	else
	{
		/* including box = -1 */
		cf = 0;
		n_art_files = N_ART_FILES;
	}

	article_total_length += length;

	if((length>0) && (cf == file_append_cf) && (file_append_article != NULL)
			&& (article_total_length < APPEND_MAX))
	{
		return(file_append_article);   /* continue using this file */
	}

	/* close this and open a new file */
	if(file_append_article != NULL)
		fclose(file_append_article);
	file_append_article = NULL;

	/* which file to use ? */
	min = 0x7fffffff;
	min_file = 0;
	for(fileno = 0; fileno < n_art_files; fileno++)
	{
		if(fileno == omit)
			continue;   /* don't consider this one */

		if(art_file_header[cf_filenum(cf,fileno)].size < min)
		{
			min = art_file_header[cf_filenum(cf,fileno)].size;
			min_file = fileno;
		}
	}
	fileno = min_file;

	file_append_article = article_fopen(cf,fileno,"a");
	fileno_append = cf_filenum(cf,fileno);
	file_append_cf = cf;
	article_total_length = length;

	if(file_append_article == NULL)
	{
		werr(0,"Failed to open article file %d/%d to append",cf,fileno);
	}
	return(file_append_article);
}   /* end of article_open_shortest */




int loadfile_article(char *filename, int filetype)
/************************************************/
{
	int  i;
	int  c;
	int  j;
	int  length;
	int  file_length;
	int  uue_encoding = 0;
	FILE *f_input;
	int  len;
	char buf[128];


	f_input = fopen_werr(filename,"r",NULL);
	if(f_input == NULL)
	{
		return(-1);
	}

	length = file_length = get_filelength(filename);

	if((filetype != FILETYPE_HTML) && (filetype != 0xfff))
	{
		uue_encoding = 1;
		length = uuencode_length(length);
	}

	article_open_shortest(-1,length,-1);


	if((length + TEXT_EXTRA) > atext_max)
	{
		if(atext_max != 0)
			flex_free((flex_ptr)&atext_base);

		atext_max = length + TEXT_EXTRA*2;
		if(flex_alloc((flex_ptr)&atext_base,atext_max) == 0)
		{
			sprintf(buf,"for file: %s",filename);
			malloc_err_string(81,buf);
			fclose(f_input);
			atext_max = 0;
			return(-1);
		}
	}

	if(uue_encoding)
	{
		sprintf(buf,"\nbegin 644 %s\n",get_leaf_name(filename));

		strcpy(atext_base,buf);
		j = strlen(buf);

		length = uuencode_file(file_length, f_input, &atext_base, j);
		sprintf(buf,"RiscOS filetype : %.3X\n\n",filetype);
		len = strlen(buf);
		for(i=0; i<len; i++)
		{
			atext_base[length++] = buf[i];
		}

	}
	else
	{
		if(length == 0)
		{
			atext_base[0] = ' ';
			length = 1;
		}
		else
		{
			j = 0;
			for(i=0; i<length; i++)
			{
				c = fgetc(f_input);
				if(c != '\r')
					atext_base[j++] = c;
			}
			length = j;
		}
	}

	fclose(f_input);

	if(strcmp_lc(filename,"<wimp$scrap>") == 0)
	{
		/* delete input file if it was <Wimp$Scrap> */
		remove(filename);
	}

	return(length);
}   /* end of loadfile_article */



/**********************************************************************************/
/*  Interpret Articles   */
/**********************************************************************************/



static int interpret_newsgroups(char **text, int argument, int arg_end, CARD_EXPANDED *cardex, char **name_out, int *box, int followup_source)
/************************************************************************************************/
/* Examine the newsgroups line from an article header.  Find a newsgroup
   which has been declared as a source, and return this source number */
{
	int  source= -1;
	int  ix;
	char *p;
	char *p2;
	int  length;
	int  c;
	int  end_flag=0;
	NEWSG *ng;
	NEWSG *active_ng = NULL;
	NEWSG *full_body_ng = NULL;
	NEWSG *followup_ng = NULL;
	static char buf[128];

	/* examine each newsgroup in the list until we find one to match */
	ix = argument;

	while(!end_flag)
	{
		p = *text;
		length = 0;
		while(((c = p[ix]) != ',') && (ix < arg_end))
		{
			if((length < sizeof(buf)) && !isspace(c))
				buf[length++] = c;
			ix++;
		}
		buf[length]=0;

		if(ix >= arg_end)
			end_flag = 1;   /* end of argument */

		ix++;

		ng = newsg_lookup(buf);
		if(ng != NULL)
		{
			if(n_crosspost < (N_CROSSPOST-1))
			{
				if(ng->active)
				{
					crosspost_box[n_crosspost] = ng->box;
					crosspost_source[n_crosspost++] = ng->source;
				}
				if(n_crosspost > 1)
				{
					cardex->flags |= STATUS_BIT_CROSSPOST;
				}
			}

			if(ng->active)
			{
				/* use a newsgroup which is active, in preference to one only in the sources list */
				if(active_ng == NULL)
				{
					active_ng = ng;
				}

				if((ng->ng_flags & NG_HEADFETCH) == 0)
				{
					if(ng->source == followup_source)
					{
						/* this is the newsgroup to which followups are set, use this one */
						followup_ng = ng;
					}

					if(full_body_ng == 0)
						full_body_ng = ng;
				}
			}
			else
			{
				if(source < 0)
					source = ng->source;
			}
		}

		if(source <= 0)
		{
			source = category_lookup(&src_fr,buf,0);
		}
	}

	if(followup_ng != NULL)
	{
		active_ng = followup_ng;
	}
	else if(full_body_ng != NULL)
	{
		active_ng = full_body_ng;  /* use the first full body newsgroup, if there is one */
	}
	if(active_ng != NULL)
	{
		*box = active_ng->box;
		*name_out = NULL;

		ng_found = active_ng;
		return(active_ng->source);
	}

	*box = options.news_box;

	if(name_out != NULL)
	{
		p = buf;
		p2 = (*text) + argument;
		while(((c = *p2++) > ' ') && (c != ','))
		{
			*p++ = c;
		}
		*p = 0;
		*name_out = buf;
	}
	return(source);  /* -1 indicates not found */
}   /* end of interpret_newsgroups */



void decode_iso8859_B(char *string, int length)
/*********************************************/
{
	int  c;
	int  acc=0;
	int  value;
	int  n_bits=0;
	int  n_chars;
	int  ix;
	int  outix = 0;

	n_chars = 0;
	for(ix=0; ix<length; ix++)
	{
		c = string[ix];
		if(c == '=') break;  /* ??? */

		if((c >= '+') && (c <= 'z'))
		{
			value = base64_decode[c - '+'];

			acc = (acc << 6) | value;
			n_bits += 6;

			if(n_bits >= 8)
			{
				n_bits -= 8;
				string[outix++] = acc >> n_bits;
				n_chars++;

				if(c == '=')
					break;
			}
		}
	}
	string[outix] = 0;
}   /* end of decode_iso8859_B */




int decode_iso8859(char *string, int control)
/*******************************************/
/* for use in author and title fields of received messages.
   Returns the charset
   control: bit 0   enclose in " if string contains comma */
{
	char *p;
	int  c;
	char *out;
	char *start;
	char *p2;
	int  charset = 0;
	int  quoted = 0;
	int  spaces_count = 0;
	int  between_quoted = 0;  /* skip space between 2 quoted strings */
	int  comma = 0;
	char *base64_start=NULL;

	out = start = string;

	while((c = *string++) != 0)
	{
		if(isspace(c))
		{
			if(between_quoted)
				spaces_count++;
		}
		else
		{
			between_quoted = 0;
		}


		if((quoted==1) && (c == ','))
			comma=1;
		else if((quoted==1) && (c == '_'))
			c = ' ';
		else if(c == '=')
		{
			if((quoted == 0) && (string[0]=='?'))
			{
				p = NULL;
				if((memcmp_lc(string,"?iso-8859-",10)==0) && (isdigit(string[10])))
				{
					charset = string[10] - '0';
					p = string + 12;

					if(isdigit(string[11]))
					{
						charset = charset*10 + (string[11] - '0');
						p++;
					}
				}
				else if(memcmp_lc(string,"?us-ascii?",10)==0)
					p = string + 10;
				else if(memcmp_lc(string,"?koi8-r?",8)==0)
				{
					p = string + 8;
					charset = 101;
				}
				else
				{
					charset = 100;   /* unknown */
					p2 = strchr(&string[1],'?');
					if((p2 != NULL) && ((p2 - string) < 14))
					{
						p = p2 + 1;
					}
				}

				if((p != NULL) && (p[1] == '?'))
				{
					if(spaces_count > 0)
					{
						out--;  /* delete space between quoted strings */
						spaces_count = 0;
					}

					if(tolower(p[0]) == 'q')
						quoted = 1;   /* quoted-printable encoding */
					else if(tolower(p[0]) == 'b')
					{
						quoted = 2;   /* base64 encoding */
						base64_start = out;
					}

					string = p+2;

					continue;
				}
			}
			else if(quoted == 1)
			{
				if((isxdigit(string[0]) && isxdigit(string[1])))
				{
					c = (hexdig(string[0]) * 16) + hexdig(string[1]);
					if(c == ',')
						comma = 1;
					string+=2;
				}
			}
		}
		else if(quoted && (c == '?') && (string[0] == '='))
		{
			if((quoted == 2) && (base64_start != NULL))
			{
				decode_iso8859_B(base64_start,out-base64_start);
				out = base64_start + strlen(base64_start);
			}
			quoted = 0;
			between_quoted = 1;
			spaces_count = 0;
			base64_start = NULL;
			string++;
			continue;
		}
		*out++ = c;

		if(!isspace(c))
			spaces_count = 0;
	}
	*out++ = 0;

	if((control & 1) && (comma) && (start[0] != '"'))
	{
		/* put the string in quotes, if it doesn't already start with a double-quote */
		c = strlen(start);
		memmove(&start[1],&start[0],c);
		start[0] = '"';
		start[c+1] = '"';
		start[c+2] = 0;
	}

	return(charset);
}   /* decode_iso8859 */




void interpret_sender(char **text, int argument, int length, char *out, int max_length, int *daemon)
/**************************************************************************************************/
/* Convert mail or news sender to a standard form.  Expected input:
   From: Richard Goodwin <mabel@argonet.co.uk>
   From: wibble@argonet.co.uk (Alastair Duncan)
*/
{
	char *p;
	int  c;
	char *atsign=NULL;
	char *angle=NULL;
	char *bracket=NULL;
	char *space=NULL;
	char *comma=NULL;
	int  bracket_count;
	int  quote_level;
	char *out_max;
	char unfold_buf[256];

	copy_unfold(unfold_buf,sizeof(unfold_buf),*text+argument,length);

	out_max = &out[max_length-1];
	p = unfold_buf;

	while((c = *p) > '\n')
	{
		if(c == '@')  atsign = p;
		if(c == '<')  angle = p;
		if(c == ' ')  space = p;
		if(c == ',')  comma = p;
		if((c == '(') && (bracket == NULL)) bracket = p;  /* first bracket */
		p++;
	}

	if((atsign!=NULL) && (daemon != NULL) &&
			((memcmp_lc(&atsign[-13],"mailer-daemon",13)==0) || (memcmp_lc(&atsign[-10],"postmaster",10)==0)
			 || ((memcmp_lc(&atsign[-4],"root",4)==0) && ((unfold_buf == (atsign-4)) || (atsign[-5]=='<'))) ))
	{
		/* message from a mailer-daemon */
		*daemon = 1;
	}

	if(angle == NULL)
	{
		/* starts with email address */
		if(bracket != NULL)
		{
			/* write name */
			p = bracket+1;   /* 1st char of name */
			bracket_count = 0;
			while(((c = *p++) > '\n') && (out < (out_max - 1)))
			{
				if(c == '(')
					bracket_count++;
				else if(c == ')')
				{
					bracket_count--;
					if(bracket_count < 0)
						break;
				}
				if(c != '"') *out++ = c;
			}
			*out++ = ' ';
		}

		/* followed by email address */
		p = unfold_buf;

		if(atsign)
		{
			*out++ = '<';
			while(((c = *p++) > ' ') && (out < (out_max - 1)))
			{
				if(c == ',') break;
				*out++ = c;
			}
			*out++ = '>';
		}
		else
		{
			/* plain text name */
			while(((c = *p++) != '\n') && (out < out_max))
				*out++ = c;
		}
	}
	else
	{
		/* name followed by email address */
		p = unfold_buf;
		if(strlen(p) >= (max_length-1))
			p = angle;   /* discard comment part of address if it's too long */

		quote_level = 0;
		while(((c = *p++) > '\n') && (out < out_max))
		{
			if(c=='"')
				quote_level ^= 1;

			if((c == ',') && (quote_level==0)) break;
			if((c == '"') && (comma == NULL)) continue;
			*out++ = c;
		}
	}
	*out = 0;   /* terminator */
	*out_max = 0;

}   /* end of interpret_sender */




static int interpret_subject(CARD_EXPANDED *cardex, char **text, int argument, int end)
/*************************************************************************************/
/* Convert subject line to a standard form, remove Re:
   Return 1 if Re: has been removed */
{
	char *title;
	int  i;
	int  result=0;
	char *out;
	char buf[140];

	copy_unfold(buf,sizeof(buf),(*text + argument),end-argument);

	/* there's no point in setting cardex->charset_title unless we also
	   store it in the CARD record (in pack_card).  Otherwise it will have
	   been lost by the time we display the article */
	cardex->charset_title = decode_iso8859(buf,0);
	title = buf;

	while((memcmp_lc(title,"re:",3)==0) || (memcmp_lc(title,"aw:",3)==0))
	{
		title += 3;
		result = 1;

		while(*title == ' ')
			title++;
	}
	while(*title == ' ')
		title++;    /* skip leading spaces */

	/* copy to output */
	out = cardex->title;

	for(i=0; i<(sizeof(cardex->title)-1); i++)
	{
		if((out[i] = title[i]) == 0)
			break;
	}
	out[i] = 0;

	/* strip trailing spaces */
	while((--i > 0) && (out[i] <= ' '))
	{
		out[i] = 0;
	}
	return(result);
}   /* end of interpret_subject */




int interpret_article_key(char **text, int *startptr, int end, int *arg, int *arg_end)
/************************************************************************************/
{
	char *p;
	char *p_end;
	char *colon;
	char *nextline;
	int  found;
	int  len;
	int  i;
	int  c;
	MNEM_TAB *key;
	char keyword[40];

	p = *text + *startptr;
	p_end = *text + end;

	colon = NULL;

	if(p >= p_end)
		return(-2);

	/* scan line until NL (not followed by TAB) */
	if(p[0] != '\n')
	{
		while(p < p_end)
		{
			if(*p == '\n')
			{
				if((p[1] != ' ') && (p[1] != '\t'))
					break;
			}

			if((colon == NULL) && (p[0] == ':'))
			{
				colon = p;   /* the first colon in the header line */
			}
			p++;
		}
	}

	/* find next line with keyword, or \n\n to indicate end of header */

	found = 0;
	nextline = p+1;
	while((p[1] != '\n') && (p < p_end))
	{
		if((p[1]==' ') && (p[2]=='\n'))
		{
			p[1]='\n';
			p[2]= ' ';
			break;
		}

		p++;
		while((p < p_end) && (*p != '\n'))
		{
			if(p[0] == ':')
			{
				found=1;
				break;
			}
			p++;
		}
		if(found)
			break;
		nextline = p+1;
	}
	*arg_end = (nextline-1) - *text;

	p = *text + *startptr;

	if(colon == NULL)
	{
		if(p >= p_end)
			return(-2);   /* header-only message */
		else
			return(-1);
	}

	len = (colon-p);
	if(len >= sizeof(keyword))
		len = sizeof(keyword)-1;


	/* get keyword at start of line */

	for(i=0; i<len; i++)
	{
		keyword[i] = tolower(p[i]);
	}
	keyword[len] = 0;


	/* lookup keyword in table of keywords to display */
	for(i=0; i<n_key_display; i++)
	{
		if(strcmp(keyword,key_display_name[i])==0)
		{
			key_display_start[i] = *startptr;
			key_display_end[i] = *arg_end;
			break;
		}
	}

	*startptr = nextline - *text;

	/* lookup keyword in table */
	key = article_keys;

	while(key->mnem != NULL)
	{
		i=0;
		while((c = key->mnem[i])==keyword[i])
		{
			if(c == 0)
			{
				/* match found */
				colon++;
				while((*colon == ' ') || (*colon == '\t') || (*colon == '\240'))
					colon++;
				*arg = colon - *text;    /* skip the space or tabs after the colon also */
				return(key->value);
			}
			i++;
		}
		key++;
	}
	return(0);


}   /* end of interpret_article_key */




static int interpret_get_param(char **text, int start, int end)
/******************************************************/
{
	int  i;
	char *p;
	int  quote=0;

	p = *text;

	for(i=start; i<end; i++)
	{
		if(!isspace(p[i]))
			break;   /* skip initial spaces */
	}

	for(; i<end; i++)
	{
		if(p[i]=='"')
			quote ^= 1;

		if(((p[i] == ',') && (quote==0)) || ((p[i] == '\n') && !isspace(p[i+1])))
			break;
	}
	return(i);
}   /* end of interpret_get_param */



int get_user_src(OPTIONS_MAILBOX *m)
/**********************************/
{
	int  source;
	char buf2[128];

	sprintf(buf2,"mail.%s",m->email_addr);
	if((source = category_lookup(&src_fr,buf2,0)) != 0)
		return(source);

	sprintf(buf2,"mail.%s",get_user_id(m));
	return(category_lookup(&src_fr,buf2,0));
}   /* end of get_user_src */



void interpret_mailing_list(char **text, int argument2, int arg_end, int mbtype,
							char *mail_unknown, CARD_EXPANDED *cardex)
/******************************************************************************/
{
	int  docbox;
	char *p;
	int  i;
	int  start;
	int  param_end;
	int  c;
	NEWSG *ng;
	char buf2[128];

	param_end = interpret_get_param(text,argument2,arg_end);
	p = *text;

	for(i=argument2; i<param_end; i++)
	{
		if(p[i]=='"')
		{
			/* skip to closing " */
			for(i=i+1; i<param_end; i++)
			{
				if(p[i]=='"')
					break;
			}
		}
		if(p[i] == '@')
			break;
	}
	if(i==param_end) i--;

	for(start=i-1; start>=argument2; start--)
	{
		if(((c = p[start]) <= ' ') || (c == '<') || (c == '"'))
			break;
	}
	start++;    /* start of email address in message */

	/* skip mailto: at start of address */
	if(memcmp_lc(&p[start],"mailto:",7)==0)
		start += 7;

	/* is it a mailing list ? */
	strcpy(buf2,"mail.");
	memcpy(&buf2[5],&p[start],sizeof(buf2)-6);
	buf2[sizeof(buf2)-1]=0;

	ng = newsg_lookup2(buf2);
	if(ng != NULL)
	{
		mailsource[mbtype] = ng->source;
		docbox = ng->box;
		mailbox[mbtype] = docbox;
		cardex->status_other2 |= STATUS_BIT_MAILLIST;
		ng_found = ng;
	}
}   /* end interpret_mailing_list */




void interpret_mail_addr(char **text, int argument2, int arg_end, int mbtype,
						 char *mail_unknown, CARD_EXPANDED *cardex)
/***************************************************************************/
{
	char *p;
	int  i;
	int  ix;
	int  c;
	int  start;
	int  param_end;
	char *p_end;
	int  length;
	int  src;
	int  docbox;
	int  mailing_list_source = 0;
	int  mailing_list_docbox;
	int  count = 0;
	char *p_plus;
	char *p_at;
	NEWSG *ng;
	OPTIONS_MAILBOX *m=NULL;
	char buf2[128];
	char addr1[128];
	char addr2[128];

	docbox = mailbox[mbtype];

	while(argument2 < arg_end)
	{
		param_end = interpret_get_param(text,argument2,arg_end);
		p = *text;

		for(i=argument2; i<param_end; i++)
		{
			if(p[i]=='"')
			{
				/* skip to closing " */
				for(i=i+1; i<param_end; i++)
				{
					if(p[i]=='"')
						break;
				}
			}
			if(p[i] == '@')
				break;
		}
		if(i==param_end) i--;

		for(start=i-1; start>=argument2; start--)
		{
			if(((c = p[start]) <= ' ') || (strchr("<\";",c) != NULL))
				break;
		}
		start++;    /* start of email address in message */
		p_end = strpbrk(&p[start],">\n\t, ");
		length = p_end - &p[start];

		/* get email address */
		if(length >= sizeof(addr1))
			length = sizeof(addr1)-1;

		memcpy(addr1,&p[start],length);
		addr1[length] = 0;

		/* get alternative address, omitting any +xxx part */
		addr2[0] = 0;
		if((p_plus = strchr(addr1,'+')) != NULL)
		{
			if((p_at = strchr(p_plus,'@')) != NULL)
			{
				i = p_plus - addr1;
				memcpy(addr2,addr1,i);
				strcpy(&addr2[i],p_at);
			}
		}

		count++;

		for(ix=0; ix<N_MAIL_BOX; ix++)
		{
			m = &options.mailbox[ix];
			if(m->email_addr[0] == 0)
				continue;

			if(strcmp_lc(addr1,m->email_addr) == 0)
				break;    /* found an exact match */


			/* try matching address without the +xxx */
			if((addr2[0] != 0) && (strcmp_lc(addr2,m->email_addr) == 0))
				break;
		}

		if(ix == N_MAIL_BOX)
		{
			/* no match found, try maching any wildcard entries */

			p_at = strchr(addr1,'@');
			if(p_at != NULL)
			{
				sprintf(addr2,"*%s",p_at);

				for(ix=0; ix<N_MAIL_BOX; ix++)
				{
					m = &options.mailbox[ix];
					if(strcmp_lc(addr2,m->email_addr)==0)
						break;
				}
			}
		}

		if(ix < N_MAIL_BOX)
		{
			/* found a matching email address */
			if(mbtype == MB_ENVTO)
			{
				/* 2nd or 3rd address in Envelope-To: line */
				if(mailuser[MB_ENVTO] >= 0)
				{
					mbtype = MB_ENVTO2;
					if(mailuser[MB_ENVTO2] >= 0)
						mbtype = MB_ENVTO3;
				}
			}

			if(mbtype != MB_FROM)
			{
				docbox = m->box;
				mailbox[mbtype] = docbox;
				mailuser[mbtype] = ix;
			}

			src = get_user_src(m);
			if(src > 0)
			{
				mailsource[mbtype] = src;
			}

			if(m->mail_list_server)
			{
				cardex->status_other2 |= STATUS_BIT_MAILLIST;

				sprintf(buf2,"mail.%s",m->email_addr);
				src = category_lookup(&src_fr,buf2,0);
				if(src > 0)
				{
					mailsource[mbtype] = src;
				}
			}
		}

		/*      if((docbox < 0) && (mailsource[mbtype] <= 0)) */
		{
			/* is it a mailing list ? */
			strcpy(buf2,"mail.");
			memcpy(&buf2[5],&p[start],sizeof(buf2)-6);
			buf2[sizeof(buf2)-1]=0;

			ng = newsg_lookup2(buf2);
			if(ng != NULL)
			{
				mbtype = MB_MLIST;

				mailing_list_source = ng->source;
				mailing_list_docbox = ng->box;
				cardex->status_other2 |= STATUS_BIT_MAILLIST;
				ng_found = ng;
			}

			p = strpbrk(buf2,"@\n");
			if(p != NULL)
			{
				*p = 0;
			}
			if(mail_unknown != NULL)
			{
				strncpy(mail_unknown,buf2,128);   /* sizeof mail_unknown */
				mail_unknown[127]=0;
			}
		}
		argument2 = param_end+1;
	}

	if(count > 1)
		cardex->status_other2 |= STATUS_BIT_CC;

	if(mailing_list_source > 0)
	{
		mailsource[mbtype] = mailing_list_source;
		docbox = mailing_list_docbox;
	}
	mailbox[mbtype] = docbox;
}   /* end of interpret_mail_addr */




char *get_reference(char **text, int start, int end, int which)
/*************************************************************/
/* Extract a message-id
   which=1  last, =2 next to last etc.
*/
{
	char *p;
	char *p_start;
	char *p_close;
	int  len;
	int  quoting;
	static char buf[131];

	if(start <= 0)
		return(NULL);


	p_start = *text + start;
	p = *text + end;

	while(which > 0)
	{
		quoting = 0;

		if(p <= p_start)
			return(NULL);

		while(p >= p_start)
		{
			if(*--p == '>')
				break;
		}
		p_close = p;

		while(--p >= p_start)
		{
			if(*p == '"')
				quoting ^= 1;

			if((quoting == 0) && (*p == '<'))
				break;
		}
		len = p_close-p+1;
		which--;
	}

	if((which > 0) || (len == 0))
		return(NULL);

	if((*p == '<') && (len < (sizeof(buf)-1)))
	{
		memcpy(buf,p,len);
		buf[len] = 0;
		return(buf);
	}
	return(NULL);
}   /* end of get_reference */






void interpret_references(CARD_EXPANDED *cardex, char **text, ARTICLE_IN *art_in)
/****************************************************************************/
{
	int  ix;
	int  n_refs;
	unsigned int  parent;

	cardex->parent = 0;
	cardex->n_refs = 0;

	/* get hash code for the last reference or in-reply-to */
	if(art_in->in_reply_to != -1)
	{
		/* use in-reply-to in preference to references */
		cardex->parent = gethash32(get_reference(text,art_in->in_reply_to,art_in->in_reply_to_end,1));
	}

	if(art_in->references < 0)
		return;   /* no references */

	ix = 1;
	if(cardex->parent == 0)
	{
		cardex->parent = gethash32(get_reference(text,art_in->references,art_in->references_end,ix++));
	}

	if(cardex->parent != 0)
	{
		for(n_refs=0; n_refs<CD_N_REFS; n_refs++)
		{
			if(ix > options.keep_references)
				break;

			parent = gethash32(get_reference(text,art_in->references,art_in->references_end,ix++));
			if(parent == 0)
				break;
			cardex->references[n_refs] = parent;
		}
		cardex->n_refs = n_refs;
	}
}  /* end of interpret_references */







static void interpret_pluto_status(int x_status, char **text, int arg, int arg_end, CARD_EXPANDED *cardex)
/********************************************************************************************************/
{
	char *p;
	int  i;
	int  status = -1;

	for(i=arg; i<arg_end; i++)
	{
		p = *text + i;
		switch(*p)
		{
		case 'A':
			cardex->replied = 1;
			break;

		case 'F':
			status = STATUS_MARKED;
			break;

		case 'L':
			status = STATUS_LOCKED;
			break;

		case 'G':   /* outgoing */
			cardex->status_other |= STATUS_BIT_OG;
			break;

		case 'O':   /* opened, set by mail server */
			if(x_status || (options.input_status & 2))
			{
				if(status < STATUS_UNREAD)
					status = STATUS_UNREAD;
			}
			break;

		case 'R':
			if(x_status || (options.input_status & 1))
				status = STATUS_READ;
			break;

		case 'U':
			status = STATUS_NEW;
			break;
		}
	}

	if(status >= 0)
		cardex->status = status;
}   /* end of interpret_pluto_status */



void title_strip_re(CARD_EXPANDED *cardex, char *p)
/*************************************************/
{
	int  ix;

	while(*p == ' ') p++;

	if((memcmp_lc(p,"re: ",4)==0) || (memcmp_lc(p,"re. ",4)==0) || (memcmp_lc(p,"aw: ",4)==0))
	{
		p+=4;
		cardex->reply = 1;
	}
	else if(memcmp_lc(p,"re ",3)==0)
	{
		p+=3;
		cardex->reply = 1;
	}

	ix = 0;
	while((cardex->title[ix] = p[ix]) != 0) ix++;
}   /* end of title_strip_re */




void undelivered_write(char *p, int length)
/*****************************************/
{
	int  i;
	int  c;

	for(i=0; i<length; i++)
	{
		c = p[i];
		if((c == '\n') && (transport_opts & TOPS_MAIL_CRLF))
			fputc('\r',f_leave_mail);   /* precede each NL by CR */
		fputc(c,f_leave_mail);
	}
	if(transport_opts & TOPS_MAIL_DOT)
	{
		fprintf(f_leave_mail,".\r\n");
	}
}   /* end of undelivered_write */





static int interpret_text(char **text,int *startptr, int *start_body, int *startptr2,
						  int end, CARD_EXPANDED *cardex, int action, ARTICLE_IN *art_in)
/***************************************************************************************************/
/* Looks for header to article, extracts header information, and advances
   'text' to point to start of text after the header */
{
	char *p;
	char *p_end;
	int  start;
	int  start_header;
	int  start_prev;
	int  start_content_type = -1;
	int  box;
	char *argument;
	int argument2;
	int  arg_end;
	int  arg_length;
	char *name_out;
	int  keyword;
	int  newsbox = -1;
	int  news_source = 0;
	int  mail_flag = 0;
	char *news_unknown=NULL;
	int  message_id_length=0;
	int  user = -1;
	int  mailer_daemon=0;
	int  followup_source=0;
	char *p1;
	char *p2;
	char *p_at;
	int  ix;
	int  c;
	int  any_keywords=0;
	CARD *cptr_log;
	int  status;
	OPTIONS_MAILBOX *m;
	char mail_unknown[128];
	char buf[132];

	n_crosspost = 0;
	memset(key_display_end,0,sizeof(key_display_end));

	/* give it a synthetic message-id in case the header doesn't have one */
	sprintf(cardex->message_id,"pluto%s",get_message_id(0,0));
	cardex->msgid = gethash32(cardex->message_id);
	message_id_length = strlen(cardex->message_id);


	for(ix=0; ix<N_MB; ix++)
	{
		mailbox[ix] = -1;
		mailuser[ix] = -1;
		mailsource[ix] = 0;
	}

	mail_flag = action & 0x0080;

	/* Look at initial lines starting with <word>: up to blank line */
	start = *startptr;
	p = *text+start;
	p_end = *text+end;

	mail_unknown[0] = 0;

	p1 = p;
	p2 = p1 + 256;    /* just search the first part of the file */
	if(p2 > (p_end-6))
		p2 = p_end-6;


	if(memcmp(p1,"\001\n",2)==0)
	{
		/* starts with CTRL-A NL */
		p1 += 2;
		p = p1;
	}

	if(action & 0x4000)
	{
		/* skip the first line */
		while(p1 < p2)
		{
			if(*p1 == '\n')
			{
				p = p1+1;
				break;
			}
			p1++;
		}
	}
	else if(memcmp(p1,"MAIL FROM:",10)==0)
	{
		while(p1 < (p_end-6))
		{
			if(memcmp(p1,"\nRCPT TO:<",10)==0)
			{
				/* lookup the user name and compare it with our user names */
				p1+=10;
				p_at = strchr(p1,'@');
				if(p_at != NULL)
				{
					for(ix=0; ix<N_MAIL_BOX; ix++)
					{
						m = &options.mailbox[ix];
						if(memcmp_lc2(m->email_addr,p1,p_at-p1)==0)
						{
							mailuser[MB_RCPTTO] = ix;
							mailsource[MB_RCPTTO] = get_user_src(m);
							mailbox[MB_RCPTTO] = m->box;
							break;
						}
					}
				}
			}
			if(memcmp(p1,"\nDATA\n",6)==0)
			{
				p = p1+6;
				break;
			}
			p1++;
		}
	}
	else if(memcmp(p1,"POST +00000128",14)==0)
	{
		p = p1 + 15;
		if(*p == '\n') p++;
	}
	else
	{
		while(p1 < p2)
		{
			if((p1[0] == '#') &&
					((memcmp(p1,"#! rnews",8)==0) || (memcmp(p1,"#! rmail",8)==0)))
			{
				p1 += 8;

				/* skip to first line with a colon - this skips the garbage that Voyager
				   newsfetcher sometimes put at the start of an article */
				while((p1 < p2) && ((*p1++ != ':') || (!isspace(*p1))));

				while((p1 > p) && (*(--p1) != '\n'));

				p = p1+1;
				break;
			}
			p1++;
		}
	}

	start = p - *text;
	start_header = start;    /* start of header, after the !# rnews line */
	cardex->docbox = -1;
	art_in->header_end = 0;

	for(;;)
	{
		start_prev = start;
		if((keyword = interpret_article_key(text,&start,end,&argument2,&arg_end)) < 0)
			break;

		arg_length = arg_end - argument2;
		art_in->header_end = arg_end;
		if(keyword > 0)
			any_keywords++;

		argument = *text + argument2;

		switch(keyword)
		{
		case 1:    /* From */
			if((action & 0x10) == 0)
			{
				interpret_sender(text,argument2,arg_end-argument2,cardex->author,
								 sizeof(cardex->author),&mailer_daemon);
				cardex->charset_author = decode_iso8859(cardex->author,1);

				if((action & 0x40) == 0)
				{
					/* not a news message */
					interpret_mailing_list(text,argument2,arg_end,MB_MLIST,mail_unknown,cardex);
				}
			}
			else
			{
				/* outgoing message which is being logged */
				interpret_mail_addr(text,argument2,arg_end,MB_FROM,NULL,cardex);
			}
			break;

		case 2:    /* Subject */
			cardex->reply = interpret_subject(cardex,text,argument2,arg_end);
			break;

		case 3:    /* Date */
			memcpy(buf,argument,36);
			cardex->date = interpret_date(buf);
			break;

		case 4:    /* Newsgroups */
			art_in->newsgroups = argument2;
			art_in->newsgroups_end = arg_end;

			if(action & 0x10)
			{
				/* outgoing message which is being logged */
				cardex->author[0] = 0;

				for(ix=0; ix<(CD_STRLEN-1); ix++)
				{
					c = argument[ix];
					if((c == 0) || (c == '\n'))
						break;
					cardex->author[ix] = c;
				}
				cardex->author[ix] = 0;
			}
			break;

		case 5:   /* X-Newsreader */
			break;

		case 6:   /* X-Mailer */
			break;

		case 7:   /* To:  Terminate string at '@' */
			mail_flag = 1;

			if(action & 0x10)
			{
				/* outgoing message which is being logged */
				interpret_sender(text,argument2,arg_end-argument2,cardex->author,
								 sizeof(cardex->author),NULL);
				cardex->charset_author = decode_iso8859(cardex->author,1);
			}
			if((action & 0x40) == 0)
			{
				/* not a news message */
				art_in->to = argument2;
				art_in->to_len = arg_end;
				interpret_mail_addr(text,argument2,arg_end,MB_TO,mail_unknown,cardex);
			}
			break;

		case 13:   /* Envelope-to */
		case 28:   /* X-Envelope-To, x-reveiver */
			interpret_mail_addr(text,argument2,arg_end,MB_ENVTO,NULL,cardex);
			art_in->envelope_to = argument2;
			art_in->envelope_to_len = arg_end;
			break;

		case 14:  /* Cc */
			/* look for our own email address */
			if((action & 0x40) == 0)
			{
				/* not a news message */
				art_in->cc = argument2;
				art_in->cc_len = arg_end;

				interpret_mail_addr(text,argument2,arg_end,MB_CC,NULL,cardex);
				cardex->status_other2 |= STATUS_BIT_CC;
			}
			break;


		case 29:  /* in-reply-to */
			cardex->reply = 1;
			if(art_in->references == -1)
			{
				art_in->references = argument2;
				art_in->references_end = arg_end;
			}
			art_in->in_reply_to = argument2;
			art_in->in_reply_to_end = arg_end;
			break;

		case 8:   /* references */
			cardex->reply = 1;
			art_in->references = argument2;
			art_in->references_end = arg_end;
			break;

		case 9:   /* message id */
			p = get_reference(text,argument2,arg_end,1);
			if(p != NULL)
			{
				strncpy0(cardex->message_id,p,sizeof(cardex->message_id));
				cardex->msgid = gethash32(p);
				message_id_length = arg_length;
			}
			break;

		case 35:   /* supersedes */
			p = get_reference(text,argument2,arg_end,1);
			if(p != NULL)
			{
				art_in->supersedes = gethash32(p);
			}
			break;

		case 10:  /* Bcc */
			break;

		case 15:  /* Return-Receipt-To */
		case 22:  /* X-Confirm-Reading-To */
			if(action & 0x20)
			{
				art_in->ack_receipt = argument2;
				art_in->ack_receipt_len = arg_length;
			}
			break;

		case 18:   /* Path */
			art_in->path = argument2;
			art_in->path_end = arg_end;
			break;

		case 19:   /* Content-Transfer-Encoding */
			if(memcmp_lc(argument,"quoted-printable",16)==0)
			{
				art_in->transfer_encoding = ENCODE_QUOTED;
			}
			else if(memcmp_lc(argument,"base64",6)==0)
			{
				art_in->transfer_encoding = ENCODE_BASE64_SINGLE;
				memcpy(argument, "8bit  ",6);  // change to 8bit for export
			}
			break;

		case 20:   /* Content-Type */
			art_in->content_type = argument2;
			art_in->content_type_len = arg_length;

			if(memcmp_lc(argument,"multipart",9)==0)
			{
				start_content_type = start_prev-1;   /* start of content-type line */

				if(memcmp_lc(&argument[10],"digest",6) == 0)
				{
					art_in->multipart_digest = 1;
				}
				else
				{
					cardex->status_other2 |= STATUS_BIT_ATTACH;
				}
			}
			else if(memcmp_lc(argument,"text/html",9)==0)
			{
				cardex->ftype = 1;   /* html */
				start_content_type = start_prev-1;
				cardex->status_other2 |= STATUS_BIT_ATTACH;
			}
			break;

		case 17:
			copy_unfold(cardex->cats,sizeof(cardex->cats),argument,arg_length);
			break;

		case 23:
			copy_unfold(cardex->keywords,sizeof(cardex->keywords),argument,arg_length);
			break;

		case 26:  /* delivered-to, use to identify a user */
			interpret_mail_addr(text,argument2,arg_end,MB_ENVTO,NULL,cardex);
//         art_in->envelope_to = argument2;
//         art_in->envelope_to_len = arg_end;

			/* drop through to the next case */
		case 32:   /* List-Post <mailto:address@domain> */
		case 24:   /* Sender:  check for mailing list address */
		case 27:   /* X-Sender */
		case 25:  /* X-Mailing-List-Name */
		case 12:  /* Reply-to */
		case 33:  /* mail-followup-to */
			if((action & 0x40) == 0)
			{
				/* not a news message */
				interpret_mailing_list(text,argument2,arg_end,MB_MLIST,mail_unknown,cardex);
			}
			break;

		case 11:    /* followup-to: */
		case 16:    /* source */
			ix = arg_end - argument2;
			if(ix >= sizeof(buf))  ix = sizeof(buf)-1;
			memcpy(buf,argument,ix);
			buf[ix] = 0;

			if((p = strchr(buf,','))!=NULL)
				*p = 0;   /* only look at the first NG in the list */

			if(keyword == 11)
				followup_source = category_lookup(&src_fr,buf,0);
			else
				news_source = category_lookup(&src_fr,buf,0);
			break;

		case 30:    /* X-Status */
			interpret_pluto_status(1,text,argument2,arg_end,cardex);
			break;

		case 31:    /* Status */
			interpret_pluto_status(0,text,argument2,arg_end,cardex);
			break;

		default:    /* ignore this keyword */
			break;
		}
	}

	/* get hash codes for in-reply-to and references */
	interpret_references(cardex,text,art_in);


	if((art_in->newsgroups > 0) && ((action & 0x40) || (mail_flag == 0)))
	{
		/* interpret the Newsgroups: line */
		if((news_source = interpret_newsgroups(text,art_in->newsgroups,art_in->newsgroups_end,cardex,&name_out,&box,followup_source)) <= 0)
		{
			news_unknown = name_out;

			if(options.leave_undelivered_news==2)
			{
				return(-1);
			}
		}

		newsbox = box;
	}

	if(ng_found != NULL)
	{
		cardex->ng_found = ng_found;  /* pass to parts of a digest */

		if(ng_found->maillist && ng_found->strip_n_chars)
		{
			/* remove the first N characters from the Subject */
			p = &cardex->title[ng_found->strip_n_chars];
			title_strip_re(cardex,p);

		}

		if(ng_found->ng_flags & NG_SUBJECT_TAGS)
		{
			/* remove any [xxx] tag at the start of the Subject */
			if((cardex->title[0]=='[') && ((p = strchr(cardex->title,']')) != NULL))
			{
				p++;
				title_strip_re(cardex,p);
			}
		}

		/* if option is set for this box, look
		   for a matching log copy and delete it */
		cptr_log = msgid_lookup(cardex->msgid,NULL,0x102);
		if(cptr_log != NULL)
		{
			status = cptr_log->status & STATUS_MASK;
			if(((cptr_log->date_box >> 26) == ng_found->box) &&
					(box_table[cptr_log->date_box >> 26].flags & BOX_DELETE_LOG_COPY) &&
					((cptr_log->date_box & DATE_MASK) == cardex->date) &&
					(cptr_log->status & STATUS_BIT_OG) &&
					(status != STATUS_LOCKED) &&
					(status != STATUS_MARKED))
			{
// article_file_ensure_closed((cptr_log->addr >> 24) & 0xff);
				/* now done in article_fopen */
				article_delete(NULL,cptr_log,1,6);   /* and -> BIN */
			}
		}
	}

	if(any_keywords == 0)
	{
		start = start_header;
	}

	if(keyword == -2)
	{
		/* header-only message */
		cardex->status_other |= STATUS_BIT_HDR_ONLY;
		if(message_id_length > 0)
		{
			strncpy0(cardex->comment,cardex->message_id,sizeof(cardex->comment));
		}
	}

	if(action & 0x10)
	{
		/* outgoing message */
		if(action & 0x400)
			cardex->status_other2 &= ~STATUS_BIT_CC;  /* CC bit is inverted in OG messages */
		else
			cardex->status_other2 |= STATUS_BIT_CC;
	}
	else if((mailuser[MB_TO] < 0) && (mail_flag>0))
	{
		/* not directly addressed to one of our users */
		if((cardex->status_other2 & STATUS_BIT_MAILLIST)==0)
			cardex->status_other2 |= STATUS_BIT_CC;
	}

	/* invert CC bit for maillist messages */
	if(cardex->status_other2 & STATUS_BIT_MAILLIST)
		cardex->status_other2 ^= STATUS_BIT_CC;


	/* get source, from news and mail entries in the header */
	if(cardex->docbox == -1)
	{
		if((mailsource[MB_FROM] > 0) && (action & 0x10) && ((action & 0x40)==0))
		{
			/* log of outgoing message, but not news */
			cardex->source = mailsource[MB_FROM];
		}
		else if(mailbox[MB_TO] >= 0)
		{
			cardex->docbox = mailbox[MB_TO];
			cardex->source = mailsource[MB_TO];
			user = mailuser[MB_TO];
		}
		else if(mailbox[MB_CC] >= 0)
		{
			cardex->docbox = mailbox[MB_CC];
			cardex->source = mailsource[MB_CC];
			user = mailuser[MB_CC];
		}
		else if(mailbox[MB_ENVTO] >= 0)
		{
			cardex->docbox = mailbox[MB_ENVTO];
			cardex->source = mailsource[MB_ENVTO];
			user = mailuser[MB_ENVTO];
		}
		else if(mailbox[MB_RCPTTO] >= 0)
		{
			cardex->docbox = mailbox[MB_RCPTTO];
			cardex->source = mailsource[MB_RCPTTO];
			user = mailuser[MB_RCPTTO];
		}
		else if(newsbox >= 0)
			cardex->docbox = newsbox;
		else if((mail_flag) && (mailbox[MB_MLIST] < 0))
		{

			if((options.leave_undelivered_mail == 1) && f_leave_mail)
			{
				/* return undelivered mail to the input queue */
				p = *text + *startptr;
				undelivered_write(p,end-*startptr);
				n_undelivered++;
				return(-1);
			}
			if(options.leave_undelivered_mail == 2)
			{
				n_undelivered++;
				return(-2);   /* discard undelivered mail */
			}
			cardex->docbox = options.postmaster_box;
		}
		else
			cardex->docbox = options.text_box;


		if(mailbox[MB_MLIST] >= 0)
		{
			cardex->docbox = mailbox[MB_MLIST];
			cardex->source = mailsource[MB_MLIST];
		}
	}

	cardex->user = user+1;

	if(user >= 0)
	{
		cardex->copy_box = options.mailbox[user].copy_box;
		cardex->text_anchor = text;
		cardex->text_start = start_header;
		cardex->text_length = end-start_header;

		if((options.mailbox[user].mail_list_server) && ((action & 0x10) == 0))
		{
			if(mailer_daemon == 0)
			{
				mail_list_server(cardex,user);
			}
			else
			{
				/* from mailer-daemon or postmaster, don't broadcast to mailing list */
				cardex->docbox = options.returned_box;
			}
		}
	}


	if(cardex->source == 0)
	{
		if(news_source > 0)
			cardex->source = news_source;
		else if(news_unknown != NULL)
		{
			cardex->comment[0] = 0;
			strncat(cardex->comment,news_unknown,CD_STRLEN-2);
			cardex->source = category_lookup(&src_fr,"news",0);
		}
		else if(mail_unknown[0] != 0)
		{
			cardex->comment[0] = 0;
			strncat(cardex->comment,mail_unknown,CD_STRLEN-2);
			cardex->source = category_lookup(&src_fr,"mail",0);
		}
		else
		{
			if(mail_flag)
				cardex->source = category_lookup(&src_fr,"mail",0);
		}
	}


	/* skip over internet header if required */
	*start_body = start;
	*startptr = start_header;
	*startptr2 = start_content_type;

	return(0);
}   /* end of interpret_text */




void interpret_html(char **text, int *start, int end, CARD_EXPANDED *cardex)
/**************************************************************************/
/* Find <title> ... </title>   and  <base href= ...
   in an html file.  Don't strip header */
{
	int  ix;
	char *p;
	int  i;
	int  c;
	int  len;

	p = *text + *start;
	len = end - *start;

	for(ix=0; (ix < 512) && (ix < len); ix++)
	{
		if(memcmp_lc(&p[ix],"<title>",7) == 0)
		{
			p += (ix + 7);
			i = 0;
			while(*p <= ' ') p++;

			while((memcmp_lc(&p[i],"</title>",8) != 0) && (i < 79))
			{
				cardex->title[i] = p[i];
				i++;
			}
			cardex->title[i] = 0;
			break;
		}
	}

	p = *text + *start;
	while((++ix < 512) && (ix < len))
	{
		if(memcmp_lc(&p[ix],"<base href=\"",12) == 0)
		{
			p += (ix + 12);
			i = 0;
			while(((c = p[i]) != '"') && (c != 0) && (i < 79))
			{
				cardex->author[i++] = c;
			}
			cardex->author[i] = 0;
			break;
		}
	}

	cardex->source = category_lookup(&src_fr,"http",0);
}   /* end of interpret_html */




int time_stamp_compare(char *time1, char *time2)
/**********************************************/
{
	int  i;
	int  x;

	for(i=4; i>=0; i--)
	{
		if((x = time1[i] - time2[i]) != 0)
			return(x);
	}
	return(0);
}   /* end of time_stamp_compare */




void get_time_stamp2(char *fname, char *time_stamp)
/*************************************************/
/* get 5 byte time stamp for a file, least sig byte 1st */
{
	os_regset regs;

	regs.r[0] = 23;
	regs.r[1] = (int)fname;
	os_swix(0x08,&regs);    /* OS_File 23   Read catalogue info */

	time_stamp[4] = regs.r[2];
	memcpy(&time_stamp[0],&regs.r[3],4);
}   /* end of get_time_stamp2 */




int get_time_stamp(char *fname)
/*****************************/
{
	int date_enc;
	os_regset regs;
	char date_buf[5];    /* least significant byte 1st */
	char date_string[32];

	if(strcmp_lc(fname,"<wimp$scrap>")==0)
	{
		get_current_date_string(&date_enc);
		return(date_enc);
	}

	/* take the time/date stamp of the file */

	get_time_stamp2(fname,date_buf);

	date_string[0] = 0;
	regs.r[0] = (int)date_buf;
	regs.r[1] = (int)date_string;
	regs.r[2] = sizeof(date_string);
	regs.r[3] = (int)"%DY %M3 %YR %24:%MI:%SE";
	os_swix(0xc1,&regs);   /* OS_ConvertDateAndTime */
	return(interpret_date(date_string));
} /* end of get_time_stamp */









int decode_quoted_printable(TEXTR *t, int start, int end)
/**********************************************************/
/* Returns the new value of "end" once the text has shrunk */
{
	int  i;
	int  j;
	int  c;
	char *p;

	p = t->text_base;
	j=start;

	for(i=start; i<end; i++)
	{
		if((c = p[i]) == '=')
		{
			if(isxdigit(p[i+1]) && isxdigit(p[i+2]))
			{
				c = (hexdig(p[i+1]) * 16) + hexdig(p[i+2]);
				i += 2;

				p[j++] = c;
			}
			else if(p[i+1] == '\n')
			{
				i += 1;
			}
			else
			{
				p[j++] = c;
			}
		}
		else
		{
			if(i == t->sig_start)
				t->sig_start = j;

			p[j++] = c;
		}
	}
	return(j);
}   /* end of decode_quoted_printable */



int interpret_digest(char *fname, char **text, int start, int length, int ctype_start, CARD_EXPANDED *parent, ARTICLE_IN *art_in)
/**********************************************************************************************/
{
	char *p, *p1;
	int  i;

	char *p2;
	int  ix;
	int  j;
	int  c;

	int  len;
	int  msg_start;
	int  msg_end;
	int  count=0;
	CARD *cptr;

	int  boundary_string_length=0;
	char boundary_string[80];

	/* find boundary marker */
	if(ctype_start >= 0)
	{
		p = *text + ctype_start;

		for(i=0; i<72; i++)
		{
			if(memcmp_lc(p,"boundary=",9)==0)
			{
				p += 9;
				if(*p=='"') p++;
				p1 = strpbrk(p,"\";\n");
				len = p1-p;
				if(len > sizeof(boundary_string))
					len = sizeof(boundary_string);
				memcpy(boundary_string,p,len);
				boundary_string_length=len;
				break;
			}
			p++;
		}
	}
	if(boundary_string_length==0)
	{
		strcpy(boundary_string,"---------------------------");
		boundary_string_length = strlen(boundary_string);
	}

	/* look for boundaries in the message */
	msg_start = -1;
	for(i=start; i<(length-boundary_string_length); i++)
	{
		p = *text + i;
		if((p[0] == '\n') && (p[1]=='-') && (p[2]=='-') && (memcmp(p+3,boundary_string,boundary_string_length)==0))
		{
			/* start of a digest part */
			msg_end = i;

			if(msg_start > 0)
			{
				if(memcmp_lc((*text + msg_start),"content-type: message/rfc822",28)==0)
					msg_start+=28;

				/* look for blank line to mark end of attachment header */
				while((msg_start < (length-2)) &&
						(((*text + msg_start)[0] != '\n') || ((*text + msg_start)[1] != '\n')))
				{
					msg_start++;
				}
				msg_start += 2;

				if(art_in->transfer_encoding == ENCODE_QUOTED)
				{
					/* the whole digest is quoted printable. decode the individual message */
					p2 = *text;
					j = msg_start;
					for(ix=msg_start; ix<msg_end; ix++)
					{
						if((c = p2[ix]) == '=')
						{
							if(isxdigit(p2[ix+1]) && isxdigit(p2[ix+2]))
							{
								c = (hexdig(p2[ix+1]) * 16) + hexdig(p2[ix+2]);
								ix += 2;

								p2[j++] = c;
							}
							else if(p2[ix+1] == '\n')
							{
								ix += 1;
							}
							else
							{
								p2[j++] = c;
							}
						}
						else
						{
							p2[j++] = c;
						}
					}
					msg_end = j;
				}

				cptr = interpret_file(fname,0xfff,0x80,parent->docbox,NULL,msg_start,msg_end,parent,0);
				if(cptr == NULL)
					break;
				else
					parent->card_ptr = cptr;  /* so that the parent interpret_file returns a non-error result */
				count++;
			}

			msg_start = i+4+boundary_string_length;
		}
	}

	return(count);
}   /* end of interpret_digest */




void supersede_article(CARD_EXPANDED *cardex, int message_id)
/***********************************************************/
{
#ifdef not_yet_implemented

	int  ix;
	CARD *cptr;
	int  status;

	for(ix=0; ix<list_fr[0].n_entries; ix++)
	{
		cptr = (CARD *)((list_fr[0].ixlist[ix] & IXLIST_MASK) << 2);

		if((cptr->msgid == message_id) && ((cptr->date_box >> 26)==cardex->docbox))
		{
			status = cptr->status & STATUS_MASK;
			if((cptr->status & STATUS_BIT_NEWS) && (status != STATUS_MARKED) && (status != STATUS_LOCKED))
			{
				/* News article and not Locked. Now check for match on author */
				if(strcmp(cardex->author,&cptr->data[cptr->d_author])==0)
				{
					cardfile_remove3(&list_fr[0],cptr);
				}
			}
		}
	}
#endif
}   /* end of supersede_article */




CARD *interpret_file(char *fname, int filetype, int action, int box, char *comment, int start1,
					 int text_length, CARD_EXPANDED *parent, int set_user)
/********************************************************************************/
/* Process the article in text_base */
/* Action
     bit 4   outgoing message
     bit 5   send acks to "Request-Receipt-To"
     bit 6   news message (rather than mail)
     bit 7   mail message
     bit 8   debatching: apply killfile and crossposts
     bit 9   mark as locked
     bit 10  force CC bit to be set
     bit 12  0x1000  force #! rmail/rnews delimeters
     bit 13  0x2000  remove CR's
     bit 14  0x4000  first line is From ***
     bit 16  0x10000 status= STATUS_DRAFT
     bit 17  0x20000  set "header only" bit (use with STATUS_DRAFT)
*/
{
	int  i;
	char *p;
	char *p2;
	int  start;
	int  start_body;
	int  start_content_type = -1;
	int  offset;
	int  docbox;
	int  source;
	int  user;
	int text_end;
	int n_chars;
	char *text_out;
	ADDR_BOOK *a;
	CARD_EXPANDED *cardex;
	ARTICLE_IN article_in;
	CARD_EXPANDED card_exp2;
	CARD *cardfile_add_error = (CARD *)1;
	char *p3;
	int not_uue;
	char buf[200];

	cardex = &card_exp2;

	init_cardex(cardex);
	ng_found = NULL;
	cardex->status = STATUS_NEW;   /* new */

	if(parent != NULL)
	{
		cardex->source = parent->source;
		cardex->date = parent->date;
		strcpy(cardex->title,parent->title);
		ng_found = parent->ng_found;
	}

	article_in.references = -1;
	article_in.in_reply_to = -1;
	article_in.path = -1;
	article_in.newsgroups = -1;
	article_in.envelope_to = -1;
	article_in.to = -1;
	article_in.cc = -1;
	article_in.content_type = -1;
	article_in.ack_receipt = -1;
	article_in.multipart_digest = 0;
	article_in.transfer_encoding = 0;
	article_in.supersedes = 0;

	n_crosspost = 0;
	start = start1;

	switch(filetype)
	{
	case 0xfff:   /* text */
	case 0xfe4:   /* DOS */
		if(interpret_text(&atext_base,&start,&start_body,&start_content_type,text_length,
						  cardex,action,&article_in) != 0)
		{
			return((CARD *)1);
		}

		if((ng_found != NULL) && (ng_found->ng_flags & NG_DIGESTS))
		{
			if((article_in.multipart_digest) || (strstr(cardex->title,"igest") != NULL))
			{
				if(box >= 0)
					cardex->docbox = box;
				if(interpret_digest(fname, &atext_base,start_body,text_length,start_content_type,cardex,&article_in) > 0)
					return(cardex->card_ptr);
			}
		}


		if(article_in.transfer_encoding == ENCODE_BASE64_SINGLE)
		{
			if(memcmp_lc(&atext_base[article_in.content_type],"text",4)==0)
			{
				/* decode it (decoded text will be shorter than the base64 text) */
				i = 1;
				offset = start_body+i;
				text_end = start_body + text_length;
				while(offset < text_length)
				{
					text_out = uudecode_line(&atext_base,&offset,&n_chars,ENCODE_BASE64);
					if(text_out == NULL)
						break;
					memcpy(&atext_base[start_body+i], text_out, n_chars);
					i += n_chars;
				}
				text_length = start_body+i;
			}
			else
			{
				cardex->status_other2 |= STATUS_BIT_ATTACH;   // not text, make an attachment
			}
		}

		strip_mailing_posn = 0;
		if((cardex->status_other2 & STATUS_BIT_ATTACH) == 0)
		{
			/* no MIME or BASE64 attachment, look for UUE or YENC attachment */
			p = atext_base;
			for(i=start_body; i<(text_length-8); i++)
			{
				if(p[i]=='\n')
				{
					if(memcmp(&p[i+1],"=ybegin ",8)==0)
					{
						cardex->status_other2 |= STATUS_BIT_ATTACH;
					}
					else if((memcmp(&p[i+1],"begin ",6)==0) && isdigit(p[i+7]) && isdigit(p[i+8]))
					{
						/* further checks for UUE attachment */
						not_uue = 0;
						p3 = &p[i+8];

						/* skip to end of line */
						while((*p3 != '\n') && (*p3 >= ' ')) p3++;

						/* check next line doesn't contain lower case */
						while(isspace(*p3)) p3++;
						while((*p3 != '\n') && (*p3 >= ' '))
						{
							if((islower(*p3++)) && (*p3 != '\n'))
							{
								not_uue = 1;
								break;
							}
						}

						if(not_uue == 0)
						{
							cardex->status_other2 |= STATUS_BIT_ATTACH;
							break;
						}
					}

					if(p[i+1]==strip_mailing_list[0])
					{
						if(memcmp(&p[i+2],&strip_mailing_list[1],strip_mailing_len)==0)
						{
							strip_mailing_posn = i+1;
						}
					}
				}
			}
		}

		if(((cardex->status_other2 & STATUS_BIT_ATTACH)==0) &&
				(strip_mailing_posn>0) && (strip_mailing_posn > (start_body+2)))
		{
			/* found match for strip mailing list junk, truncate article */
			text_length = strip_mailing_posn;
		}

		if((cardex->title[0]==0) && (strcmp_lc(fname,"<wimp$scrap>")!=0))
			strcpy(cardex->title,get_leaf_name(fname));
		break;

	case 0xfaf:   /* html */
		interpret_html(&atext_base,&start,text_length, cardex);
		start_body = start;
		cardex->ftype = 1;
		break;

	default:
		/* open new article and add this file as an attachment */
		/* loadfile_article() has encoed it as uue */
		cardex->status_other2 |= STATUS_BIT_ATTACH;
		start_body = start;
		strcpy(cardex->title,get_leaf_name(fname));
		cardex->docbox = options.text_box;
		break;
	}

	if(cardex->title[0] == 0)
		strcpy(cardex->title,"Untitled");
	if(cardex->author[0] == 0)
		strcpy(cardex->author,"Anon");

	if(set_user > 0)
		cardex->user = set_user;

	if((action & 0x40) &&
			((cardex->status_other & STATUS_BIT_HDR_ONLY)==0) &&
			(fetch_list_count > 0))
	{
		/* News message, does it match a stored headr-only article? */
		if((i = check_fetch_list(cardex->message_id)) >= 0)
		{
			cardex->status = 0;   /* STATUS_NEW + FETCHED */
			if(i < BOX_BIN)
			{
				/* put it into same box as original, header-only message */
				cardex->docbox = i;
			}
		}
	}

	if(box >= 0)
	{
		cardex->docbox = box;
	}


	if(start_content_type >= 0)
	{
		/* we have a CONTENT_TYPE: header line.  We don't want to
		   delete this when we strip internet headers */
		start_body = start_content_type;
		cardex->flags |= INET_HDR_MASK;
	}
	/* stripping internet header is now done in cardfile_add */


#ifdef deleted
	if((box_table[cardex->docbox].flags & BOX_STRIP_HEADERS) == 0)
	{
		if(start_body > start)
		{
			cardex->flags |= INET_HDR_MASK;
		}
	}
	else
	{
		if(start_content_type >= 0)
		{
			start = start_content_type;
			cardex->flags |= INET_HDR_MASK;
		}
		else
			start = start_body;
	}
#endif

	/* ensure text ends with a Newline */
	if(atext_base[text_length-1] != '\n')
	{
		atext_base[text_length] = '\n';
		text_length++;
	}

	cardex->text_anchor = &atext_base;
	cardex->text_start = start;
	cardex->text_start_body = start_body;
	cardex->text_length = text_length - start;
	cardex->expiry = 2;   /* default */


	if((action & 3)== 3)
	{
		if(cardex->status <= STATUS_UNREAD)
			cardex->status = STATUS_READ;  /* mark as read */
	}
	if(action & 0x200)
		cardex->status = STATUS_LOCKED;
	if(action & 0x10000)
	{
		cardex->status = STATUS_DRAFT;
		cardex->replied = 1;
	}
	if(action & 0x20000)
	{
		cardex->status_other |= STATUS_BIT_HDR_ONLY;
	}

	if(action & 0x10)
		cardex->status_other |= STATUS_BIT_OG;   /* mark as outgoing message */

	if(action & 0x40)
		cardex->status_other |= STATUS_BIT_NEWS;   /* mark as news (not mail) */

	cardex->filetype = filetype;

	if(cardex->date == 0)
	{
		cardex->date = get_time_stamp(fname);
	}

	if(comment != NULL)
	{
		strcpy(cardex->comment,comment);
	}


	cardex->colour = 255;

	/* lookup sender's email address in address book */
	article_in.addr_book_entry = NULL;
	p = strchr(cardex->author,'<');
	if(p != NULL)
	{
		p++;
		if((p2 = strchr(p,'>')) != NULL)
		{
			memcpy(buf,p,p2-p);
			buf[p2-p]=0;
			article_in.addr_book_entry = addr_lookup_url(buf);
		}
	}


	/*   if(action & 0x100) */
	{
		article_in.text_length = text_length;
		if(article_killfile(cardex,&atext_base,action,&article_in) > 0)   /* set box number from contents */
		{
			n_crosspost = 0;
			if(cardex->docbox == BOX_BIN)
				n_binned++;
			else
				n_filtered++;
		}
	}



	/* lookup sender in the address book */
	if((filetype == 0xfff) && ((options.addrlist_translate) != 99))
	{
		a = article_in.addr_book_entry;

		if((a != NULL) && (a->data[0] != 0))
		{
			if(a->flags & 8)
			{
				/* replace with name */
				strcpy(cardex->author,a->data);
			}
			else if(p == &cardex->author[1])
			{
				/* no name in message, only an address, add the name */
				sprintf(cardex->author,"%s <%s>",a->data,&a->data[a->offset]);
			}

			if((cardex->colour == 255) && (a->flags & 4))
				cardex->colour = addr_colr_trans[a->colr & 0xf];
		}
	}

	if(cardex->colour == 255)
	{
		/* not set by filter or address book */
		cardex->colour = 0;
	}


	if(cardex->docbox < BOX_BIN)
	{
		cardfile_add_error = cardfile_add(cardex);
	}

	docbox = cardex->docbox;
	source = cardex->source;
	user = cardex->user;

	if(((action & 0x10) == 0) && (cardfile_add_error != NULL) && (cardfile_add_error != (CARD *)1))
	{
		/* make copies for copy-box and cross postings.
		   don't do this for outgoing messages */

		if((mailuser[MB_ENVTO2] >= 0) && (mailuser[MB_ENVTO2] != mailuser[MB_ENVTO]))
		{
			if((mailuser[MB_ENVTO2]+1) != user)
			{
				cardex->docbox = mailbox[MB_ENVTO2];
				cardex->source = mailsource[MB_ENVTO2];
				cardex->user = mailuser[MB_ENVTO2] + 1;
				cardfile_add(cardex);
			}
		}
		if((mailuser[MB_ENVTO3] >= 0) &&
				(mailuser[MB_ENVTO3] != mailuser[MB_ENVTO]) &&
				(mailuser[MB_ENVTO3] != mailuser[MB_ENVTO2]))
		{
			if((mailuser[MB_ENVTO3]+1) != user)
			{
				cardex->docbox = mailbox[MB_ENVTO3];
				cardex->source = mailsource[MB_ENVTO3];
				cardex->user = mailuser[MB_ENVTO3] + 1;
				cardfile_add(cardex);
			}
		}

		if(cardex->copy_box > 0)
		{
			/* a copy if required */
			cardex->docbox = cardex->copy_box-1;
			cardfile_add(cardex);
		}

		/* make copies for cross postings */
		if((action & 0x100) && (options.crossposts > 0))
		{
			/* only when debatching, not when importing */
			for(i=0; i<n_crosspost; i++)
			{
				if(crosspost_source[i] != source)
				{
					cardex->docbox = crosspost_box[i];
					cardex->source = crosspost_source[i];
					cardfile_add(cardex);
				}
			}
		}
	}


	cardex->source = source;
	cardex->docbox = docbox;
	cardex->user = user;

	if((cardex->docbox != BOX_BIN) && (article_in.ack_receipt>=0) && (user > 0))
	{
		copy_unfold(buf,sizeof(buf),(*(cardex->text_anchor) + article_in.ack_receipt),article_in.ack_receipt_len);
		reply_receipt_ack(cardex,buf,user-1,0);
	}

	if(article_in.supersedes != NULL)
		supersede_article(cardex,article_in.supersedes);

	memcpy(&card_exp,&card_exp2,sizeof(card_exp));
	return(cardfile_add_error);
}   /* end of interpret_file */





void new_file()
/*************/
/* Open a new article viewer window */
{
	int  date;
	TEXTR *t;
	CARD_EXPANDED *cardex;
	static char buf[8] = "\n";
	char *buf1;

	if(check_lock_lists(0xffff) != 0)
		return;

	cardex = &card_exp;
	init_cardex(cardex);
	get_current_date_string(&date);

	buf1 = buf;
	cardex->text_anchor = &buf1;
	cardex->text_start = 0;
	cardex->text_length = strlen(buf);
	cardex->status = STATUS_READ;
	cardex->date = date;
	cardex->docbox = options.text_box;

	article_open_shortest(-1,0,-1);
	cardfile_add(cardex);
	article_file_close();
	set_boxlist_extent(6);

	t = text_data_init(TEXT_EXTRA,X_VIEW2);

	if(card_edit(t,&list_fr[0],card_exp.card_ptr,0x103,0) != 0)
	{
		beep();
		text_data_free(t);
	}
	t->new_file = 1;
	redraw_list_lines(NULL,0);
}   /* end of new_file */



CARD *load_article2(char *fname, int filetype, int action, int box, char *comment, int user)
/****************************************************************************************/
/* Read an article, extract header information based on the filetype
   into cardex
   Action=1  present edit box  2=no edit box
          bit 4  an outgoing message, get "From" from recipient
*/
{
	/* discard short files of 5 or fewer characters,
	   Voyager newsfetcher sometimes has empty article files of 5 characters */

	int  length;
	CARD *cptr;

	length = loadfile_article(fname,filetype);

	if(length <= 4)
		return(NULL);

	cptr = interpret_file(fname,filetype,action,box,comment,0,length,NULL,user);
	if(cptr == (CARD *)1)
		cptr = NULL;
	return(cptr);
}  /* end of load_article2 */





void load_hierarchy(char *path, int action, int box)
/**************************************************/
/* Load all the files in a directory hierarchy,
   Called recursively
*/
{
	int  offset=0;
	os_regset regs;
	os_error *error;
	char path2[256];

	struct {
		int l_addr;
		int x_addr;
		int length;
		int attributes;
		int type;
		int file_type;
		char name[16];
	} buf;

	regex_err = 0;

	while(offset >= 0)
	{
		call_event_process(0);

		regs.r[0] = 12;
		regs.r[1] = (int)path;
		regs.r[2] = (int)&buf;
		regs.r[3] = 1;
		regs.r[4] = offset;
		regs.r[5] = sizeof(buf);
		regs.r[6] = NULL;
		error = os_swix(0x0c,&regs);

		if(error != NULL)
			break;

		offset = regs.r[4];
		if(regs.r[3] == 0)
			break;    /* no items read */

		sprintf(path2,"%s.%s",path,buf.name);
		if(buf.type == 1)
		{
			/* file */
			if(buf.file_type == 0xfff)
			{
				load_article2(path2,buf.file_type,action,box,NULL,0);
			}
		}
		else if(buf.type == 2)
		{
			/* directory, recurse */
			load_hierarchy(path2,action,box);
		}

	}
}   /* end of load hierarchy */



void import_box_selected(int *hits)
/*********************************/
{
	import_box = box_lookup_number(hits[0]);
	dbox_setfield(dbox_import,2,get_box_name(import_box));
	dbox_showstatic(dbox_import);
}   /* end of import_box_selected */



BOOL dbox_import_raw_handler(dbox d, void *event, void *handle)
/**************************************************************/
{
	wimp_mousestr *mouse;
	int  icon;
	wimp_eventstr *e = event;


	switch(e->e)
	{
	case wimp_EBUT:
		/* click on icon.  get icon number. */
		mouse = &e->data.but.m;
		icon = mouse->i;

		if(icon == 2)
		{
			make_boxes_menu(0x800,NULL);  /* include external boxes */
			dbox_menu(wmenu_boxes_names,import_box_selected,mouse);
			return(TRUE);
		}
	}
	return(FALSE);
}   /* end of dbox_import_raw_handler */




void import_articles2(char *fname, int type, int box, int flags)
/**************************************************************/
{
	char buf[256];

	if(type == 0x1000)
	{
		if(check_lock_lists(0xffff) != 0)
			return;   /* check for any multi-threaded use of index */

		visdelay2_begin();
		set_lock_lists(2,NULL);

		strcpy(buf,fname);

		article_open_shortest(box,0,-1);

		load_hierarchy(buf,flags | 0x40, box);   /* 0x40 mark as news */
		article_file_close();
		if(atext_max > 0)
		{
			flex_free((flex_ptr)&atext_base);
			atext_max = 0;
		}

		clear_lock_lists(2);
		visdelay2_end();
		redraw_list_lines(NULL,0);
	}
	else
	{
		if(check_lock_lists(0xffff) == 0)
		{
			set_lock_lists(2,NULL);
			visdelay2_begin();
			debatch_file(fname,type,box,flags,NULL);   /* raw mail */
			visdelay2_end();
			clear_lock_lists(2);
		}
	}

}   /* end of import_articles2 */




void dbox_import_handler(dbox d, void *handle)
/*********************************************/
{
	if(dbox_getnumeric(d,3))
		import_flags |= 0x12;   /* outgoing message */

	if(dbox_getnumeric(d,4))
		import_flags |= 3;     /* mark as read */

	if(dbox_get(d) == 1)
	{
		import_filters = 0x100;  /* no filters */

		if(dbox_getnumeric(d,7))
			import_filters = 8;   /* iconbar filters */
		else if(dbox_getnumeric(d,8))
			import_filters = 2;   /* mail */
		else if(dbox_getnumeric(d,9))
			import_filters = 1;   /* news */

		dbox_hide(dbox_import);

		dbox_dispose(&dbox_import);
		dbox_import = NULL;

		import_articles2(fname_import,import_type,import_box,import_flags);
		import_filters = 0;
	}
	else
	{
		dbox_dispose(&dbox_import);
		dbox_import = NULL;
	}

	set_boxlist_extent(0x43);   /* open boxlist window */

}   /* end of dbox_import_handler */




void import_articles(char *fname, int type, int flags)
/****************************************************/
{

	import_box = -1;   /* deduce box from article */
	import_type = type;
	import_flags = flags;
	strcpy(fname_import,fname);

	if(dbox_import != NULL)
	{
		beep();
		return;
	}

	dbox_import = dbox_new("Import");
	dbox_raw_eventhandler(dbox_import,dbox_import_raw_handler,NULL);
	dbox_eventhandler(dbox_import, dbox_import_handler, NULL);

	dbox_showstatic(dbox_import);
}   /* end of import_articles */



CARD *load_article(char *fname, int filetype, int action, int box, char *comment, int user)
/****************************************************************************************/
/* Read an article, extract header information based on the filetype
   into cardex
   Action=1 display text afterwards, 2=mark as unread, 3=mark as read
     bit 4   outgoing message
*/
{
	int  i;
	char buf[256];
	int  done=0;
	FILE *f;
	CARD *cptr = NULL;


	if(box == BOX_BIN)
		return(NULL);

	if(check_lock_lists(0xffff) != 0)
		return(NULL);

	regex_err = 0;

	if(filetype == 0x1000)
	{
		import_articles(fname,0x1000,0);
		return(NULL);

	}
	else
	{
		/* load a single file */
		if((action & 0x10) == 0)
		{
			f = fopen_werr(fname,"r",NULL);
			if(f != NULL)
			{
				fgets(buf,sizeof(buf),f);
				fclose(f);
				if(memcmp(buf,"#! rmail",8)==0)
				{
					import_articles(fname,X_MAIL,0x3002);  /* bit 13 - remove CR's */
					return(NULL);
				}
				if(memcmp(buf,"#! rnews",8)==0)
				{
					import_articles(fname,X_NEWS,0x3042);
					return(NULL);
				}
				if(memcmp(buf,"From ",5)==0)
				{
					import_articles(fname,X_MAIL,0x6002);
					return(NULL);
				}
				if(memcmp(buf,"MAIL FROM:",10)==0)
				{
					import_articles(fname,X_MAIL,0x0002);
					return(NULL);
				}

				i = strlen(fname);
				if((options.news_transport==X_ANT) &&
						((memcmp(&fname[i-10],".nn",3)==0) || ((memcmp(&fname[i-8],".ErrNews",8)==0))))
				{
					/* looks like an ANT raw news file */
					import_articles(fname,X_NEWS,0x0002);
					return(NULL);
				}
			}
		}

		if(done == 0)
		{
			article_open_shortest(box,0,-1);
			cptr = load_article2(fname, filetype, action, box, comment, user);
			if((cptr == NULL) || (cptr == (CARD *)1))
				action = 0;
			article_file_close();
		}
	}

	if(((action & 3)== 1) && (card_exp.card_ptr != NULL))
	{
		/* now display the article */
		card_edit(NULL,&list_fr[0],card_exp.card_ptr,3,0);
	}

	if(atext_max > 0)
	{
		flex_free((flex_ptr)&atext_base);
		atext_max = 0;
	}
	set_boxlist_extent(6);   /* open boxlist window */
	redraw_list_lines(NULL,0);

	return(cptr);
}  /* end of load_article */








/**************************************************************************/
/*  Article File management  */
/**************************************************************************/




unsigned int *article_make_index(FOLDREC *fr, unsigned int **ixptr)
/*****************************************************************/
/* Make an index to the card file, sorted by article address */
{
	int  size;

	size = fr->n_entries * sizeof(int);

	if(flex_alloc((flex_ptr)ixptr,size)==0)
	{
		malloc_err(80);
		return(NULL);
	}

	/* dont_budge around the sort */
	/*   _kernel_register_slotextend(flex_dont_budge); */

	memcpy(*ixptr,fr->ixlist,size);

	sort_card_file = 0;
	qsortG(*ixptr,fr->n_entries,sizeof(int *),article_sorter);

	/*   _kernel_register_slotextend(flex_budge); */
	return(*ixptr);
}   /* end of article_make_index */






void article_save_file_headers(int cf)
/*********************************/
{
	FILE *f;
	int  start;
	int  num;

	char fname[256];

	sprintf(fname,"%s.headers",cardfile[cf].path);

	if(cf==0)
		f = fopen_werr(fname,"w",NULL);
	else
		f = fopen(fname,"w");    /* no error message on open, may be readonly */

	if(f == NULL)
		return;

	if(cf==0)
	{
		start = 0;
		num = N_ART_FILES;
	}
	else
	{
		start = N_ART_FILES + (N_ART_FILES_X * (cf-1));
		num = N_ART_FILES_X;
	}

	fwrite(&art_file_header[start],1,num * sizeof(ART_FILE_HEADER),f);
	fclose(f);
}   /* article_save_file_headers */





void article_read_file_headers(int cf)
/************************************/
{
	FILE *f;
	int  start;
	int  num;

	char fname[256];


	sprintf(fname,"%s.headers",cardfile[cf].path);
	f = fopen(fname,"r");
	if(f == NULL)
		return;

	if(cf==0)
	{
		start = 0;
		num = N_ART_FILES;
	}
	else
	{
		start = N_ART_FILES + (N_ART_FILES_X * (cf-1));
		num = N_ART_FILES_X;
	}

	fread(&art_file_header[start],1,num * sizeof(ART_FILE_HEADER),f);
	fclose(f);
}   /* end of article_read_file_headers */





int box_to_cf(int box)
/********************/
{
	if((box >= BOX_EXTERN) && (box < BOX_BIN))
		return(box - BOX_EXTERN + 1);

	return(0);   /* box = -1 also returns 0 */
}   /* end of box_to_cf */



int cptr_to_cf(CARD *cptr)
/************************/
{
	return(box_to_cf(cptr->date_box >> 26));
}   /* end of cptr_to_cf */



int cf_to_box(int cf)
/******************/
{
	return(cf + BOX_EXTERN - 1);
}   /* end of cf_to_box */



int cf_filenum(int cf,int filenum)
/*****************************/
/* Modify article file number to include external boxes.
   Must explicitly replace the cf part of filenum because extermal
   article files can appear in any external box slot */
{
	if(cf == 0)
		return(filenum & 0x3f);

	return(N_ART_FILES + (cf-1)*32 + (filenum & 0x1f));
}   /* end of cf_filenum */





int article_read(TEXTR *t, long int addr, int box, int offset, int len, int keep_buf, int upd_read, int quiet)
/******************************************************************************************************/
/* Returns 0  OK,  -1 failed to read article,  >0 read but an error was detected */
{

	FILE *f;
	int  fileno;
	int  i;
	char *p;
	int  unused;
	int  unsquash_size;
	int  buf_length;
	int  length;
	int  get_size;
	int *ip;
	int  error=0;
	char *access;
	char *subject;
	int  cf;
	os_error *oserror;
	os_regset regs;
	CARD card;
	char buf[200];
	char squash_buf2[SQUASH_BUF_SIZE];

	static char *err_suspicious = "Suspicious decompression ratio %d -> %d";

	strcpy(errmess_article_read,"Failed to allocate memory");

	if(len <= 0)
		return(-1);

	cf = box_to_cf(box);
	fileno = (int)((addr >> 24) & 0xff);
	addr = addr & 0xffffff;

	if(upd_read)
	{
		/* check for write access */
		sprintf(buf,"%s.articles%d",cardfile[cf].path,fileno);

		if(fwriteaccess(buf)==0)
		{
			upd_read = 0;    /* no write access, can't update article status */
		}
	}

	if(upd_read)
		access = "r+";
	else
		access = "r";

	f = article_fopen(cf,fileno,access);
	if(f==NULL)
	{
		if((f = article_fopen(cf,fileno,"r")) == NULL)
		{
			sprintf(errmess_article_read,"Failed to read text from article file %d",fileno);
		}
		else
		{
			sprintf(errmess_article_read,"Article file %d doesn't allow write access",fileno);
			fclose(f);
		}
		if(!quiet)
			werr(0,errmess_article_read);
		return(-1);
	}

	/* update status to 'read' */
	/* Read all the fixed length info from the card */
	fseek(f,addr+sizeof(ART_HEAD),SEEK_SET);
	if(fread(&card,24,1,f) != 1)
	{
		sprintf(errmess_article_read,"Failed to read article %d/%lx",fileno,addr);
		if(!quiet)
			werr(0,errmess_article_read);
		fclose(f);
		return(-1);
	}

	/* ignore the offset and length parameters, calculate them from the card data in the
	   article file */
	i = card_size(&card);
	if(offset != i)
	{
		if((card.addr == 0) && (card.alength == 0))
			sprintf(errmess_article_read,"Article file %d may be corrupt at &%lx.  Overwritten with zeros?",fileno,addr);
		else
			sprintf(errmess_article_read,"Index may be corrupt at article %d/&%lx",fileno,addr);

		if(!quiet)
			werr(0,"%s%s",errmess_article_read,string_repair);

		fclose(f);
		return(-1);
	}
	offset = i;
	length = (card.alength & ART_LENGTH_MASK) - offset;

	if(length < 0)
	{
		fclose(f);
		sprintf(errmess_article_read,"Bad card.length value at article %d/&%lx",fileno,addr);
		if(!quiet)
			werr(0,errmess_article_read);
		return(-1);
	}

	if((upd_read > 0) && ((card.status & STATUS_MASK) <= STATUS_UNREAD))
	{
		/* update the status to 'been read' */
		card.status = (card.status & ~STATUS_MASK) + STATUS_READ;
		fseek(f,addr+sizeof(ART_HEAD),SEEK_SET);
		fwrite(&card,1,16,f);
	}

	fseek(f,addr+sizeof(ART_HEAD),SEEK_SET);
	fread(&card,offset,1,f);   /* read full card */
	subject = &card.data[card.d_title];

	t->text_start = 0;

	if((card.user & SQUASHED_MASK) == 0)
	{
		/* text data is not squashed, read directly into the text area */

		if((length > t->text_buf_size) ||
				((keep_buf == 0) && ((length+TEXT_EXTRA*2) < t->text_buf_size)))
		{
			if(t->text_base != NULL)
			{
				flex_free((flex_ptr)&t->text_base);
				t->text_base = NULL;
				t->text_buf_size = 0;
			}

			if(flex_alloc((flex_ptr)&t->text_base,length+TEXT_EXTRA) == 0)
			{
				if(!quiet)
				{
					sprintf(buf,"to read article: \"%s\"",subject);
					malloc_err_string(82,buf);
				}
				if(t->text_type == X_VIEW)
					text_close_window(t);

				fclose(f);
				return(-1);
			}
			t->text_buf_size = length+TEXT_EXTRA;
		}

		fseek(f,addr+offset+sizeof(ART_HEAD),SEEK_SET);
		/* _kernel_register_slotextend(flex_dont_budge); */
		fread(t->text_base,1,length,f);
		/* _kernel_register_slotextend(flex_budge); */
		fclose(f);

		t->text_length = length;
	}
	else
	{
		/* text data is squashed.  Unsquash it */
		regs.r[0] = 0x8;          /* find workspace size */
		regs.r[1] = length-4;
		oserror = os_swix(0x42701,&regs);    /* Squash_Decompress, find workspace size */

		if(oserror != NULL)
		{
			strcpy(errmess_article_read,oserror->errmess);
			if(!quiet)
				werr(0,errmess_article_read);
			return(-1);
		}


		if(regs.r[0] > squash_workspace_size)
		{
			squash_workspace_size = regs.r[0];
			squash_workspace = realloc(squash_workspace, squash_workspace_size);
			if(squash_workspace == NULL)
			{
				if(!quiet)
				{
					malloc_err(88);
				}
				squash_workspace_size = 0;
				fclose(f);
				return(-1);
			}
		}


		buf_length = length;
		if(buf_length > SQUASH_BUF_SIZE)
			buf_length = SQUASH_BUF_SIZE;

		fseek(f,addr+offset+sizeof(ART_HEAD),SEEK_SET);
		fread(squash_buf2,1,buf_length,f);

		ip = (int *)squash_buf2;
		unsquash_size = *ip;    /* to get round possible compiler bug */

		if((unsquash_size < (length-4)) || (unsquash_size < (MIN_SQUASH_LENGTH-4)) ||
				(unsquash_size > (length*14)))
		{
			sprintf(errmess_article_read,err_suspicious,length,unsquash_size);

			if(quiet)
			{
				fclose(f);
				return(-1);
			}
			else
			{
				werr(0,"%s%s",errmess_article_read,string_repair);

				if(query2("Skip this article","Skip","Use"))
				{
					fclose(f);
					return(-1);
				}
			}
		}

		get_size = unsquash_size + TEXT_EXTRA;
		if((get_size > t->text_buf_size) ||
				((keep_buf == 0) && ((get_size+TEXT_EXTRA*2) < t->text_buf_size)))  /* ???? */
		{
			if(t->text_base != NULL)
			{
				flex_free((flex_ptr)&t->text_base);
				t->text_base = NULL;
				t->text_buf_size = 0;
			}

			if(flex_alloc((flex_ptr)&t->text_base,get_size+TEXT_EXTRA+16) == 0)
			{
				if(!quiet)
				{
					sprintf(buf,"to read article: \"%s\"",subject);
					malloc_err_string(84,buf);
				}

				if(t->text_type == X_VIEW)
					text_close_window(t);

				fclose(f);
				return(-1);
			}
			t->text_buf_size = get_size+TEXT_EXTRA;
		}

		t->text_base[unsquash_size-1]=0;  // TEST write to last byte of the output area.

		if(length == buf_length)
		{
			regs.r[0] = 0x4;          /* fast algorithm */
			regs.r[1] = (int)squash_workspace;
			regs.r[2] = (int)(squash_buf2 + 4);
			regs.r[3] = length-4;
			regs.r[4] = (int)t->text_base;
			regs.r[5] = unsquash_size;
			oserror = os_swix(0x42701,&regs);    /* Squash_Decompress */
		}
		else
		{
			regs.r[0] = 0x2;
			regs.r[1] = (int)squash_workspace;
			regs.r[2] = (int)(squash_buf2 + 4);
			regs.r[3] = buf_length-4;
			regs.r[4] = (int)t->text_base;
			regs.r[5] = unsquash_size;
			oserror = os_swix(0x42701,&regs);    /* Squash_Decompress */


			while((oserror == NULL) && ((length -= buf_length) > 0))
			{
				unused = regs.r[3];
				p = (char *)regs.r[2];

				if(unused >= SQUASH_BUF_SIZE)
				{
					/* not making any progress, stuck in a loop, bad article */
					sprintf(errmess_article_read,"Can't unsquash article: '%s'",subject);
					if(!quiet)
						werr(0,errmess_article_read);
					fclose(f);
					return(-1);
				}


				if(unused > 0)
					memcpy(squash_buf2,p,unused);

				buf_length = length;
				if(buf_length > (SQUASH_BUF_SIZE-unused))
				{
					buf_length = SQUASH_BUF_SIZE - unused;
					regs.r[0] = 3;   /* continue, and more to follow */
				}
				else
				{
					regs.r[0] = 1;   /* continue, last */
				}


				fread(squash_buf2+unused,1,buf_length,f);

				regs.r[2] = (int)squash_buf2;
				regs.r[3] = buf_length+unused;
				oserror = os_swix(0x42701,&regs);     /* Squash_Decompress */
			}
		}
		fclose(f);

		if(oserror != NULL)
		{
			strcpy(errmess_article_read,oserror->errmess);
			if(!quiet)
				werr(0,errmess_article_read);
			return(-1);
		}

		if(regs.r[5] != 0)
		{
			error = 1;
			sprintf(errmess_article_read,"Article unsquashes to bigger than its declared size, by %d bytes",-regs.r[5]);
			if((!quiet) && (regs.r[5] < -TEXT_EXTRA))
				werr(0,errmess_article_read);
		}
		else if(regs.r[3]!=0)
		{
			error = 2;
			sprintf(errmess_article_read,"Overrun on reading squashed data, by %d bytes",-regs.r[3]);
		}

		t->text_length = unsquash_size;
	}
	return(error);
}   /* end of article_read */






static int article_squash(char **text_anchor, int text_start, int textsize, FILE *f)
/**********************************************************************************/
/* Squashes data and puts it into 'squash_buf'.
   First 4bytes of squash_buf gives unsquashed length

   Returns squashed length (including 4bytes giving unsquashed length
*/
{
	int  squash_size;
	int  n_bytes;
	int continuing;
	os_regset regs;
	char squash_buf[SQUASH_BUF_SIZE];

	if((options.dont_compress) || (textsize < MIN_SQUASH_LENGTH))
		return(0);    /* don't squash */

	/* squash text data */
	squash_size = 0;

	regs.r[0] = 0x8;
	regs.r[1] = textsize;
	os_swi(0x42700,&regs);         /* Squash_Compress, find output size */
	squash_size = regs.r[1];
	if(squash_size < 0)
	{
		squash_size = textsize;
	}

	if(regs.r[0] > squash_workspace_size)
	{
		squash_workspace_size = regs.r[0];
		if((squash_workspace = realloc(squash_workspace, squash_workspace_size)) == NULL)
		{
			squash_workspace_size = 0;
			return(0);
		}
	}


	if(squash_size <= SQUASH_BUF_SIZE)
		continuing = 0;   /* fits in one buffer */
	else
		continuing=2;   /* start, more follows */
	continuing = 0;   /* all the input is present */

	squash_size = 0;
	regs.r[2] = (int)(*text_anchor + text_start);
	regs.r[3] = textsize;

	while(regs.r[3] > 0)
	{
		regs.r[0] = continuing;
		regs.r[1] = (int)squash_workspace;
		regs.r[4] = (int)squash_buf;
		regs.r[5] = SQUASH_BUF_SIZE;
		os_swi(0x42700,&regs);      /* Squash_Compress */

		n_bytes = ((char *)regs.r[4] - squash_buf);
		squash_size += n_bytes;

		if(f != NULL)
		{
			if((continuing & 1) == 0)
			{
				/* write out unsquashed size */
				fwrite(&textsize,4,1,f);
			}
			/* write out squashed data */
			/* _kernel_register_slotextend(flex_dont_budge); */
			fwrite_werr(squash_buf,1,n_bytes,f);
			/* _kernel_register_slotextend(flex_budge); */

		}
		continuing |= 1;
	}

	squash_size += 4;
	if(squash_size >= textsize)
	{
		/* use unsquashed text */
		squash_size = 0;
	}

	return(squash_size);
}   /* end of article_squash */





int article_add(CARD *cptr, char **text_anchor, int text_start, int textsize)
/****************************************************************************/
{
	int  cardsize;
	int  length;
	int  status;
	FILE *f;
	int  displ;
	int  squash_size;
	int  orig_textsize;
	ART_HEAD art_head;
	int  box;

	if((textsize & 0xff000000) != 0)
	{
		/* was previously 0xff800000, to catch possible interal error */
		werr(0,"Article too long: %d bytes (limit is %d)",textsize,0xfffff);
		return(5);
	}

	box = cptr->date_box >> 26;

	f = file_append_article;
	if((f==NULL) || (file_append_cf != box_to_cf(box)))
	{
		article_open_shortest(box,0,-1);
		if((f = file_append_article) == NULL)
		{
//         werr(0,"Article file not open");
			return(1);
		}
	}

	cardfile_autosave_set(box_to_cf(box));

	displ = (int)ftell(f);
	if(displ < 0)
	{
		fseek(f,0,SEEK_END);
		displ = (int)ftell(f);
	}

	if(displ > ART_ADDR_MASK)
	{
		/* current article file is full, open another */
		article_open_shortest(box,0,-1);
		f = file_append_article;
		if(f==NULL)
		{
			werr(0,"Article file not open");
			return(2);
		}
	}

	displ = (int)ftell(f);
	if(displ < 0x20)
	{
		werr(0,"Error, attempt to write to bad article file address %d/%.6x",fileno_append,displ);
		article_open_shortest(box,0,fileno_append);    /* don't open the same one again */

		f = file_append_article;
		if(f==NULL)
		{
			werr(0,"Article file not open");
			return(3);
		}

		displ = (int)ftell(f);
		if(displ < 0x20)
		{
			werr(0,"Serious Error! Article file %d may be empty or corrupt %.6x",fileno_append,displ);
			return(4);
		}
	}

	cardsize = card_size(cptr);

	/* find the squashed size of the text */
	orig_textsize = textsize;
	squash_size = article_squash(text_anchor, text_start, textsize, NULL);

	if(squash_size > 0)
	{
		textsize = squash_size;
		cptr->user |= SQUASHED_MASK;        /* squashed indicator */
	}
	else
	{
		cptr->user &= ~SQUASHED_MASK;
	}

	length = (cardsize + textsize + 3) & ~0x3;  /* pad to word boundary */
	art_head.magic = MAGIC;
	art_head.length = length;

	if((length & 0xff800000) != 0)
	{
		werr(0,"Article suspiciously long, header %d  text %d",cardsize,textsize);
	}

	cptr->addr = (fileno_append << 24) + displ;
	fwrite(&art_head,1,sizeof(art_head),f);

	cptr->alength = (cptr->alength & ~ART_LENGTH_MASK) | (cardsize + textsize); /* card plus squahsed text */
	status = cptr->status;
	if((status & STATUS_MASK) <= STATUS_NEW)
	{
		cptr->status = status + (STATUS_UNREAD-STATUS_NEW); /* changed 'new' to 'unread' in the article file */
	}
	fwrite(cptr,1,cardsize,f);
	cptr->status = status;        /* restore previous value */


	if(squash_size > 0)
	{
		article_squash(text_anchor, text_start, orig_textsize, f);
	}
	else
	{
		fwrite_werr(*text_anchor+text_start,1,textsize,f);
	}

	length -= (cardsize + textsize);  /* padding */
	while(length-- > 0)
		fputc(0,f);

	displ = (int)ftell(f);
	if(displ > art_file_header[fileno_append].size)
		art_file_header[fileno_append].size = displ;

	art_file_header[fileno_append].n_articles++;

	/* leave file_append_article open */
	return(0);
}   /* end of article_add */




void article_file_truncate(int cf, int fileno, int size)
/******************************************************/
{
	int  handle;
	os_regset regs;
	char fname[128];

	if(cf > 0)
	{
		fileno = fileno - N_ART_FILES;
	}
	sprintf(fname,"%s.articles%d",cardfile[cf].path,fileno);

	regs.r[0] = 0xc0;
	regs.r[1] = (int)fname;
	os_swix(0x0d,&regs);    /* OS_Find  open for RW */

	if((handle = regs.r[0]) != 0)
	{
		regs.r[0] = 3;         /* set extent */
		regs.r[1] = handle;
		regs.r[2] = size;
		os_swix(0x09,&regs);

		regs.r[0] = 0;
		regs.r[1] = handle;
		os_swix(0x0d,&regs);   /* close file */

		art_file_header[fileno].size = size;
	}
}   /* end of article_file_truncate */




int article_delete(FOLDREC *fr, CARD *cptr, int close, int resurrect)
/*******************************************************************/
/* Ressurect:  bit0 = bring back from BIN
               bit1 = dont remove crossposts
               bit2 = set box to BIN */
{
	int fileno;
	int addr;
	int  cf;
	int  file_size;
	ART_HEAD art_head;

	if(cptr == NULL)
	{
		if(f_article_delete != NULL)
			fclose(f_article_delete);
		f_article_delete = NULL;
		return(0);
	}



	cf = box_to_cf(cptr->date_box>>26);
	cardfile_autosave_set(cf);
	cardfile_autosave_set(0);


	if(((resurrect&3)==0) && (cptr->user & STATUS_BIT_CROSSPOST) && ((cptr->status & STATUS_BIT_OG)==0))
	{
		/* delete, or mark-read, cross posts of this article */

		if(f_article_delete != NULL)
		{
			/* close this file in case the cross-post is also in it and needs to access it */
			fclose(f_article_delete);
			f_article_delete = NULL;
		}
		crosspost_remove(fr,cptr,options.crossposts);
	}

	fileno = (cptr->addr >> 24) & 0xff;
	fileno = cf_filenum(cf,fileno);
	addr = cptr->addr & ART_ADDR_MASK;

	if(f_article_delete != NULL)
	{
		if(fileno_article_delete != fileno)
		{
			fclose(f_article_delete);
			f_article_delete = article_fopen(cf,fileno,"r+");
		}
	}
	else
	{
		f_article_delete = article_fopen(cf,fileno,"r+");
	}
	if(f_article_delete == NULL)
	{
		werr(0,"Failed to delete article from article file %d",fileno);
		return(-1);
	}

	fileno_article_delete = fileno;

	fseek(f_article_delete,addr,SEEK_SET);
	fread(&art_head,sizeof(art_head),1,f_article_delete);

	if(art_head.magic != MAGIC)
	{
		werr(0,"Card file does not match article file %d%s",fileno,string_repair);
		fclose(f_article_delete);
		f_article_delete = NULL;
		return(-1);
	}

	/* free_space  should really be using "(card.alength + 3) & ~3"  here */
	if(resurrect==1)
	{
		if(art_head.length < 0)
		{
			art_head.length &= 0x7fffffff;  /* clear top bit to indicate 'in use' */

			/* keep track of amount of free space in file */
			art_file_header[fileno].free_space -= (art_head.length & ART_LENGTH_MASK);
			art_file_header[fileno].n_articles++;

			fseek(f_article_delete,addr,SEEK_SET);
			fwrite(&art_head,1,sizeof(art_head),f_article_delete);
		}
	}
	else
	{
		if(art_head.length >= 0)
		{
			art_file_header[fileno].n_articles--;

			fseek(f_article_delete,0,SEEK_END);
			file_size = (int)ftell(f_article_delete);

			if(((cptr->status & STATUS_MASK2) == STATUS_HIDDEN) &&
					((addr + 8 + (art_head.length & ART_LENGTH_MASK)) >= file_size))
			{
				/* we are deleting the last article in the file.
				   Truncate the file */
				fclose(f_article_delete);
				f_article_delete = NULL;
				close = 0;
				article_file_truncate(cf,fileno,addr);

				cptr->date_box = (cptr->date_box & DATE_MASK) | ((unsigned)BOX_DELETED << 26);   /* don't save this record in cardfile_save */
			}
			else
			{
				art_head.length |= 0x80000000;  /* set top bit to indicate 'free' */

				/* keep track of amount of free space in file */
				art_file_header[fileno].free_space += (art_head.length & ART_LENGTH_MASK);

				fseek(f_article_delete,addr,SEEK_SET);
				fwrite(&art_head,1,sizeof(art_head),f_article_delete);
			}
		}
	}

	if(close)
	{
		fclose(f_article_delete);
		f_article_delete = NULL;
	}

	/* set BOX to BIN */
	if((resurrect & 4) && ((cptr->date_box >> 26) != BOX_DELETED))
		cptr->date_box = (cptr->date_box & ~BOX_MASK) + ((unsigned)BOX_BIN << 26);

	return(0);
}   /* end of article_delete */





int article_update(FOLDREC *fr,CARD *cptr,char **text_anchor, int text_start, int text_length)
/**********************************************************************************/
/* Open an article.  Re-allocate space in the article file if the new
   size is too big for the current space.

   text_anchor==NULL indicates no change to text
*/
{
	FILE *f;
	int  i;
	int filenum;
	int addr;
	int cardsize;
	int totalsize;
	int old_cardsize;
	int  displ;
	int  new_displ;
	int  displ_top;
	int  displ_text;
	int  squashed_size;
	int  old_length;
	char *copy_buf;
	int  orig_textsize;
	int  cf;
	ART_HEAD art_head;
	CARD card;

	/* find the article from the address in the card */
	cf = box_to_cf(cptr->date_box>>26);
	filenum = (cptr->addr >> 24) & 0xff;
	filenum = cf_filenum(cf,filenum);

	addr = cptr->addr & ART_ADDR_MASK;

	if(addr < 0x20)
	{
		werr(0,"Bad article address for update %d/%.6x",filenum,addr);
		return(-1);
	}

	f = article_fopen(cf,filenum,"r+");
	if(f == NULL)
	{
		werr(0,"Failed to update article file %d",filenum);
		return(-1);
	}

	fseek(f,addr,SEEK_SET);
	fread(&art_head,sizeof(art_head),1,f);

	if(art_head.magic != MAGIC)
	{
		werr(0,"Card file does not match article file %d%s",filenum,string_repair);
		fclose(f);
		return(-1);
	}

	cardfile_autosave_set(cf);

	text_show_status2(cptr);

	displ = (int)ftell(f);
	fread(&card,sizeof(CARD),1,f);
	old_cardsize = card_size(&card);
	old_length = ((card.alength & ART_LENGTH_MASK) + 3) & ~3;
	displ_text = displ + old_cardsize;

	if(text_anchor == NULL)
	{
		/* no change to the text */
		text_length = (card.alength & ART_LENGTH_MASK) - old_cardsize;
	}
	else
	{
		orig_textsize = text_length;
		squashed_size = article_squash(text_anchor,text_start,text_length,NULL);
		if(squashed_size > 0)
		{
			text_length = squashed_size;
			cptr->user |= SQUASHED_MASK;        /* squashed indicator */
		}
		else
		{
			cptr->user &= ~SQUASHED_MASK;        /* squashed indicator */
		}
	}

	cardsize = card_size(cptr);
	totalsize = (cardsize + text_length + 3) & ~3;   /* round up to word boundary */
	cptr->alength = (cptr->alength & ~ART_LENGTH_MASK) | (cardsize + text_length);

	/* ???? */
	if((totalsize > art_head.length) &&
			((addr + art_head.length + sizeof(art_head)) == art_file_header[filenum].size))
	{
		/* last article in the file, we can re-use the space */
		art_head.length = totalsize;
		fseek(f,addr,SEEK_SET);
		fwrite(&art_head,1,sizeof(art_head),f);
	}


	if(totalsize > art_head.length)
	{
		/* need more space */

		/* mark previous article block as empty */
		fseek(f,addr,SEEK_SET);
		art_head.length |= 0x80000000;
		fwrite(&art_head,1,sizeof(art_head),f);

		/* keep track of amount of free space in file */
		art_file_header[filenum].free_space += old_length;


		/* move to end of file */
		fseek(f,0,SEEK_END);
		new_displ = (int)ftell(f);
		cptr->addr = (cptr->addr & ~ART_ADDR_MASK) + new_displ;

		art_head.magic = MAGIC;
		art_head.length = totalsize;
		fwrite(&art_head,1,sizeof(art_head),f);

		/* write out card */
		fwrite(cptr,1,cardsize,f);


		if(text_anchor == NULL)
		{
			/* copy text down from previous location in article file */
			displ = (int)ftell(f);

			if(flex_alloc((flex_ptr)&copy_buf,text_length) == 0)
			{
				malloc_err(86);
			}
			else
			{
				fseek(f,displ_text,SEEK_SET);
				fread(copy_buf,1,text_length,f);
				fseek(f,displ,SEEK_SET);
				fwrite_werr(copy_buf,1,text_length,f);

				flex_free((flex_ptr)&copy_buf);
			}
		}
		else if(squashed_size > 0)
		{
			article_squash(text_anchor,text_start,orig_textsize,f);
		}
		else
		{
			fwrite_werr(*text_anchor+text_start,1,text_length,f);
		}

		i = totalsize - (cardsize + text_length);
		while(i-- > 0)
			fputc(0,f);   /* padding to word boundary */

		displ_top = (int)ftell(f);

	}
	else
	{
		/* we can use the same space in the article file */

		displ = displ + cardsize;

		if(text_anchor == NULL)
		{
			/* use the text in the article file */
			if(old_cardsize != cardsize)
			{
				/* size of card has changed, move the text */

				if(flex_alloc((flex_ptr)&copy_buf,text_length) == 0)
				{
					malloc_err(87);
				}
				else
				{
					fseek(f,displ_text,SEEK_SET);
					fread(copy_buf,1,text_length,f);
					fseek(f,displ,SEEK_SET);
					fwrite_werr(copy_buf,1,text_length,f);

					flex_free((flex_ptr)&copy_buf);
				}
			}
		}
		else if(squashed_size > 0)
		{
			/* write out the squashed data */
			fseek(f,displ,SEEK_SET);
			article_squash(text_anchor,text_start,orig_textsize,f);
		}
		else
		{
			/* write out unsquashed data */
			fseek(f,displ,SEEK_SET);
			fwrite_werr(*text_anchor+text_start,1,text_length,f);
		}

		displ_top = (int)ftell(f);
		while((displ_top & 3) != 0)
		{
			fputc(0,f);
			displ_top++;   /* pad to word boundary */
		}

		/* write out the card */
		displ = addr + sizeof(art_head);
		fseek(f,displ,SEEK_SET);
		fwrite(cptr,1,cardsize,f);

		art_file_header[filenum].free_space += (old_length - totalsize);
	}

	if(displ_top > art_file_header[filenum].size)
		art_file_header[filenum].size = displ_top;

	fclose(f);
	return(0);
}   /* end of article_update */



int article_delete_internet_header(FOLDREC *fr, CARD *cptr, int quiet)
/*************************************************************/
{
	int  start;
	char *p;
	CARD_EXPANDED *cardex;
	TEXTR *t;
	int  delete_header=0;

	CARD_EXPANDED card_expanded;

	cardfile_autosave_set(box_to_cf(cptr->date_box >> 26));

	cardex = &card_expanded;
	t = &text_rec_temp;

	if(cptr->user & INET_HDR_MASK)
	{
		unpack_card(cptr, cardex);
		if(article_read(t,cardex->addr,cardex->docbox,cardex->text_offset,cardex->text_length,0,0,0) < 0)
			return(-1);

		p = t->text_base;
		for(start=0; start<t->text_length; start++)
		{
			if(p[start]=='\n')
			{
				if(p[start+1]=='\n')
				{
					/* double NL indicates end of internet header */
					cptr->user &= ~INET_HDR_MASK;   /* delete internet header indicator */
					start += 2;
					break;
				}

				if(memcmp_lc(&p[start+1],"content-type: multipart",23)==0)
				{
					/* we need to keep multipart boundary string */
					break;
				}
			}
		}

		if(start < t->text_length)
		{
			delete_header = 1;
		}
	}

	if(delete_header)
	{
		article_update(fr,cptr,&t->text_base,start,t->text_length-start);
	}
	else
	{
		article_update(fr,cptr,NULL,0,0);
	}
	return(0);
}   /* end of article_delete_internet_header */




void init_article()
/*****************/
{
	memset(&text_rec_temp,0,sizeof(text_rec_temp));
	text_rec_temp.fr = &list_fr[0];

	memset(cardfile,0,sizeof(cardfile));
	sprintf(cardfile[0].path,"%s",pluto_articles);
	strcpy(cardfile[0].name,"");

	memset(appended_art_file,0,sizeof(appended_art_file));
	article_check_integrity(0);    /* in lists.c */

	strcpy(key_display_name[0],"subject");

	auto_debatch(0,NULL);

	strip_mailing_len = strlen(strip_mailing_list)-1;
}   /* end of init_article */
