#if	!defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: attach.c,v 4.7 1993/08/09 22:09:34 mikes Exp $";
#endif
/*
 * Program:	Routines to support attachments in the Pine composer 
 *
 *
 * Michael Seibel
 * Networks and Distributed Computing
 * Computing and Communications
 * University of Washington
 * Administration Builiding, AG-44
 * Seattle, Washington, 98195, USA
 * Internet: mikes@cac.washington.edu
 *
 * Please address all bugs and comments to "pine-bugs@cac.washington.edu"
 *
 * Copyright 1991-1993  University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee to the University of
 * Washington is hereby granted, provided that the above copyright notice
 * appears in all copies and that both the above copyright notice and this
 * permission notice appear in supporting documentation, and that the name
 * of the University of Washington not be used in advertising or publicity
 * pertaining to distribution of the software without specific, written
 * prior permission.  This software is made available "as is", and
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
 * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
 * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Pine and Pico are trademarks of the University of Washington.
 * No commercial use of these trademarks may be made without prior
 * written permission of the University of Washington.
 *
 *
 * NOTES:
 *
 *
 */
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include "osdep.h"
#include "pico.h"
#include "estruct.h"
#include "edef.h"
#include "efunc.h"

#ifdef	ATTACHMENTS


#ifdef	ANSI
    int    ParseAttach(struct hdr_line **,int *,char *,char *,char *,int *);
    PATMT *NewAttach(char *, long, char *);
    void   ZotAttach(struct pico_atmt *);
    int    sinserts(char *, int, char *, int);
#else
    int    ParseAttach();
    PATMT *NewAttach();
    void   ZotAttach();
    int    sinserts();
#endif


/* 
 * max number of attachments
 */
#define	MAXATCH	64


/*
 * AskAttach - ask for attachment fields and build resulting structure
 *             return pointer to that struct if OK, NULL otherwise
 */
AskAttach(fn, sz, cmnt)
char *fn, *sz, *cmnt;
{
    int	   i, status;
    long   l = 0;
    char   bfn[NFILEN];

    i = 2;
    fn[0] = '\0';
    sz[0] = '\0';
    cmnt[0] = '\0';

    while(i){

	if(i == 2){
	    wkeyhelp("GC00000T0000","Get Help,Cancel,To Files");
	    status = mlreply("File to attach: ", fn, NLINE, QFFILE);
	}
	else{
	    wkeyhelp("GC0000000000","Get Help,Cancel");
	    status = mlreply("Attachment comment: ", cmnt, NLINE, QNORML);
	}

	switch(status){
	  case HELPCH:
	    if(i == 2)
	      emlwrite("No Attachment file help yet!", NULL);
	    else
	      emlwrite("No Attachment comment help yet!", NULL);

/* remove break and sleep when help text done to force redraw */     
	    sleep(3);
	    break;

	  case (CTRL|'T'):
	    if(i != 2){
		(*term.t_beep)();
		break;
	    }

	    *bfn = '\0';
	    if(*fn == '\0' || !isdir(fn, NULL))
	      strcpy(fn, gethomedir(NULL));

	    if(FileBrowse(fn, bfn, sz) == 1){
		strcat(fn, S_FILESEP);
		strcat(fn, bfn);
		i--;
	    }
	    else
	      *fn = '\0';

	    refresh(FALSE, 1);
	    update();
	    break;

	  case (CTRL|'L'):
	    refresh(FALSE, 1);
	    update();
	    continue;

	  case ABORT:
	    emlwrite("\007Cancelled", NULL);
	    return(0);
	  case TRUE:				/* some comment */
	  case FALSE:				/* No comment */
	    if(i-- == 2){
		fixpath(fn, NLINE);		/* names relative to ~ */
		if((gmode&MDSCUR) && homeless(fn)){
		    emlwrite("\007Restricted mode allows attachments from home directory only", NULL);
		    return(0);
		}

		if((status=fexist(fn, "r", &l)) != FIOSUC){ /* does file exist? */
		    fioperr(status, fn);
		    return(0);
		}
		strcpy(sz, prettysz(l));
	    }
	    else{
		mlerase();
		return(1);			/* mission accomplished! */
	    }

	    break;
	  default:
	    break;
	}
    }
}



/*
 * SyncAttach - given a pointer to a linked list of attachment structures,
 *              return with that structure sync'd with what's displayed.
 *              delete any attachments in list of structs that's not on 
 *              the display, and add any that aren't in list but on display.
 */
SyncAttach()
{
    int offset = 0,				/* the offset to begin       */
        rv = 0,
        ki = 0,					/* number of known attmnts   */
        bi = 0,					/* build array index         */
        na,					/* old number of attachmnt   */
        status, i, j, n;
    char file[NLINE],				/* buffers to hold it all    */
         size[32],
         comment[1024];
    struct hdr_line *lp;			/* current line in header    */
    PATMT *tp, *knwn[MAXATCH], *bld[MAXATCH];
    extern struct headerentry headents[];

    if(Pmaster == NULL)
      return(-1);

    for(i=0;i<MAXATCH;i++)			/* bug - ever pop this? */
	knwn[i] = bld[i] = NULL;		/* zero out table */
    
    tp = Pmaster->attachments;
    while(tp != NULL){				/* fill table of     */
	knwn[ki++] = tp;			/* known attachments */
	tp = tp->next;
    }

    n = 0;
    lp = headents[ATTCHDR].hd_text;
    while(lp != NULL){
	na = ++n;

	if(status = ParseAttach(&lp, &offset, file, size, comment, &na))
	    rv = (rv < 0) ? rv : status ;	/* remember worst case */

	if(*file == '\0'){
	    if(n != na && na > 0 && na <= ki && (knwn[na-1]->flags&A_FLIT)){
		bld[bi++] = knwn[na-1];
		knwn[na-1] = NULL;
	    }
	    continue;
	}

	if((gmode&MDSCUR) && homeless(file))
	  /* no attachments outsize ~ in secure mode! */
	  continue;

	tp = NULL;
	for(i=0;i < ki; i++){			/* already know about it? */
	    /*
	     * this is kind of gruesome. what we want to do is keep track
	     * of literal attachment entries because they may not be 
	     * actual files we can access or that the user readily 
	     * access
	     */
	    if(knwn[i] && 
	       ((!(knwn[i]->flags&A_FLIT) && !strcmp(file, knwn[i]->filename))
	       || ((knwn[i]->flags&A_FLIT) && i+1 == na))){
		tp = knwn[i];
		knwn[i] = NULL;			/* forget we know about it */

		if(status == -1)		/* ignore garbage! */
		  break;

		if((tp->flags&A_FLIT) && strcmp(file, tp->filename)){
		    rv = 1;
		    if((j=strlen(file)) > strlen(tp->filename)){
			if((tp->filename = (char *)realloc(tp->filename,
					        sizeof(char)*(j+1))) == NULL){
			    emlwrite("\007Can't realloc filename space",NULL);
			    return(-1);
			}
		    }

		    strcpy(tp->filename, file);
		}
		else if(tp->size && strcmp(tp->size, size)){
		    rv = 1;
		    if((j=strlen(size)) > strlen(tp->size)){
			if((tp->size=(char *)realloc(tp->size,
					        sizeof(char)*(j+1))) == NULL){
			    emlwrite("\007Can't realloc space for size", NULL);
			    return(-1);
			}
		    }

		    strcpy(tp->size, size);
		}

		if(strcmp(tp->description, comment)){	/* new comment */
		    rv = 1;
		    if((j=strlen(comment)) > strlen(tp->description)){
			if((tp->description=(char *)realloc(tp->description,
						sizeof(char)*(j+1))) == NULL){
			    emlwrite("\007Can't realloc description", NULL);
			    return(-1);
			}
		    }
		      
		    strcpy(tp->description, comment);
		}
		break;
	    }
	}

	if(tp){
	    bld[bi++] = tp;
	}
	else{
	    if(file[0] != '['){
		if((tp = NewAttach(file, atol(size), comment)) == NULL)
		  return(-1);
		bld[bi++] = tp;
	    }
	}
    }

    for(i=0; i < bi; i++)		/* link together newly built list */
	bld[i]->next = bld[i+1];

    Pmaster->attachments = bld[0];

    for(i=0; i < ki; i++){		/* kill old/unused references */

	if(knwn[i]){
	    ZotAttach(knwn[i]);
	    free((char *) knwn[i]);
	}
    }

    return(rv);
}




/*
 * ParseAttach - given a header line and an offset into it, return with 
 *		 the three given fields filled in.  Assumes the size of 
 *		 the buffers passed is large enough to hold what's there.
 *		 Always updates header fields that have changed or are 
 *		 fixed.  An error advances offset to next attachment.
 *
 *		returns: 1 if a field changed
 *                       0 nothing changed
 *                      -1 on error
 */
ParseAttach(lp, off, fn, sz, cmnt, no)
struct hdr_line **lp;				/* current header line      */
int  *off;					/* offset into that line    */
char *fn, *sz, *cmnt;				/* places to return fields  */
int  *no;					/* attachment number        */
{
    int  j, status,				/* various return codes     */
         rv = 0,				/* return value             */
         lbln  = 0;				/* label'd attachment	    */
    long l;					/* attachment length        */
    char c,
        *p = s,
        *lblsz = NULL,				/* label'd attchmnt's size  */
         number[8];
    register struct hdr_line  *lprev;
    enum {					/* parse levels             */
	LWS,					/* leading white space      */
	NUMB,					/* attachment number        */
	WSN,					/* white space after number */
	TAG,					/* attachments tag (fname)  */
	WST,					/* white space after tag    */
	SIZE,					/* attachments size         */
	SWS,					/* white space after size   */
	COMMENT,				/* attachment comment       */
	TG} level;				/* trailing garbage         */

    *fn = *sz = *cmnt = '\0';			/* initialize return strings */

    level = LWS;				/* start at beginning */
    while(*lp != NULL){

	if((c=(*lp)->text[*off]) == '\0'){	/* end of display line */
	    lprev = *lp;
	    if((*lp = (*lp)->next) != NULL)
	      c = (*lp)->text[*off = 0];	/* reset offset */
	}

	switch(level){
	  case LWS:				/* skip leading white space */
	    if(isspace(c) || c == '\0'){
		break;
	    }
	    else if(c < '0' || c > '9'){	/* add a number */
		sprintf(number, "%d. ", *no);
		*no = 0;			/* no previous number! */
		sinserts((*lp == NULL) ? &lprev->text[*off]
			               : &(*lp)->text[*off],
			     0, number, j=strlen(number));
		*off += j - 1;
		rv = 1;
		if(c == ',')			/* special case */
		  (*off)++;
		else
		  level = TAG;			/* interpret the name */
		break;
	    }
	    level = NUMB;
	  case NUMB:				/* attachment number */
	    if(c == '.'){			/* finished grabbing size */
		/*
		 * replace number if it's not right
		 */
		*p = '\0';
		sprintf(number, "%d", *no);	/* record the current...  */
		*no = atoi(s);			/* and the old place in list */
		if(strcmp(number, s)){
		    if(p-s > *off){		/* where to begin replacemnt */
			j = (p-s) - *off;
			sinserts((*lp)->text, *off, "", 0);
			sinserts(&lprev->text[strlen(lprev->text)-j], j, 
				 number, strlen(number));
			*off = 0;
		    }
		    else{
			j = (*off) - (p-s);
			sinserts((*lp == NULL) ? &lprev->text[j] 
				               : &(*lp)->text[j], 
				 p-s , number, strlen(number));
			*off += strlen(number) - (p-s);
		    }
		    rv = 1;
		}
		p = s;
		level = WSN;			/* what's next... */
	    }
	    else if(c < '0' || c > '9'){
		*p = '\0';
		emlwrite("\007Attchmnt: Number field missing '.': \"%s\"", s);
		rv = -1;
	    }
	    else
	      *p++ = c;

	    break;

	  case WSN:				/* blast whitespace */
	    if(isspace(c) || c == '\0'){
		break;
	    }
	    else if(c == '['){			/* labeled attachment */
		lbln++;
	    }
	    else if(!fallowc(c)){
		emlwrite("\007Attchmnt: '%c' not allowed in file name", 
			  (void *)(int)c);
		rv = -1;
		level = TG;			/* eat rest of garbage */
		break;
	    }
	    level = TAG;
	  case TAG:				/* get and check filename */
						/* or labeled attachment  */
						/* enclosed in []         */
	    if(c == '\0' || (!lbln && (isspace(c) || strchr(",(\"", c)))
	       || (lbln && c == ']')){
		if(p != s){
		    *p = '\0';			/* got something */

		    strcpy(fn, s);		/* store file name */
		    if(!lbln){			/* normal file attachment */
			fixpath(fn, NLINE);
			if((status=fexist(fn, "r", &l)) != FIOSUC){
			    fioperr(status, fn);
			    rv = -1;
			    level = TG;		/* munch rest of garbage */
			    break;
			}


			if((gmode&MDSCUR) && homeless(fn)){
			    emlwrite("\007Restricted mode allows attachments from home directory only", NULL);
			    rv = -1;
			    level = TG;
			    break;
			}

			if(strcmp(fn, s)){	/* fn changed: display it */
			    if(*off >=  p - s){	/* room for it? */
				sinserts((*lp == NULL)? 
					 &lprev->text[(*off)-(p-s)] :
					 &(*lp)->text[(*off)-(p-s)],
					 p-s, fn, j=strlen(fn));
				*off += j - (p - s);	/* advance offset */
				rv = 1;
			    }
			    else{
				emlwrite("\007Attchmnt: Problem displaying real file path", NULL);
			    }
			}
		    }
		    else{			/* labelled attachment! */
			/*
			 * should explain about labelled attachments:
			 * these are attachments that came into the composer
			 * with meaningless file names (up to caller of 
			 * composer to decide), for example, attachments
			 * being forwarded from another message.  here, we
			 * just make sure the size stays what was passed
			 * to us.  The user is SOL if they change the label
			 * since, as it is now, after changed, it will
			 * just get dropped from the list of what gets 
			 * passed back to the caller.
			 */
			PATMT *tp;

			if(c != ']'){		/* legit label? */
			    emlwrite("\007Attchmnt: Expected ']' after \"%s\"",
				     fn);
			    rv = -1;
			    level = TG;
			    break;
			}
			strcat(fn, "]");

			/*
			 * This is kind of cheating since otherwise
			 * ParseAttach doesn't know about the attachment
			 * struct.  OK if filename's not found as it will
			 * get taken care of later...
			 */
			tp = Pmaster->attachments; /* caller check Pmaster! */
			j = 0;
			while(tp != NULL){
			    if(++j == *no){
				lblsz = tp->size;
				break;
			    }
			    tp = tp->next;
			}

			if(tp == NULL){
			    emlwrite("\007Attchmnt: Unknown reference: %s",fn);
			    lblsz =  "XXX";
			}
		    }
		    p = s;			/* reset p in s */
		    level = WST;
		}

		if(!lbln && c == '(')		/* no space 'tween file, size*/
		  level = SIZE;
		else if(c == '\0' || (!lbln && (c == ',' || c == '\"'))){
		    strcpy(sz, (lblsz) ? lblsz : prettysz(l));
		    sprintf(s, " (%s) %s", sz, (c == '\"') ? "" : "\"\"");
		    sinserts((*lp == NULL) ? &lprev->text[*off] 
			                   : &(*lp)->text[*off],
			     0, s, j = strlen(s));
		    *off += j;
		    rv = 1;
		    level = (c == '\"') ? COMMENT : TG;/* cmnt or eat trash */
		}
	    }
	    else if(!lbln && (!fallowc(c) || c == '[' || c == ']')){
		emlwrite("\007Attchmnt: '%c' not allowed in file name",
			  (void *)(int)c);
		rv = -1;			/* bad char in file name */
		level = TG;			/* gobble garbage */
	    }
	    else
	      *p++ = c;				/* add char to name */

	    break;

	  case WST:				/* skip white space */
	    if(!isspace(c)){
		/*
		 * whole attachment, comment or done! 
		 */
		if(c == ',' || c == '\0' || c == '\"'){
		    strcpy(sz, (lblsz) ? lblsz : prettysz(l));
		    sprintf(s, " (%s) %s", sz, 
				           (c == '\"') ? "" : "\"\"");
		    sinserts((*lp == NULL) ? &lprev->text[*off]
				           : &(*lp)->text[*off],
			     0, s, j = strlen(s));
		    *off += j;
		    rv = 1;
		    level = (c == '\"') ? COMMENT : TG;
		    lbln = 0;			/* reset flag */
		}
		else if(c == '('){		/* get the size */
		    level = SIZE;
		}
		else{
		    emlwrite("\007Attchmnt: Expected '(' or '\"' after %s",fn);
		    rv = -1;			/* bag it all */
		    level = TG;
		}
	    }
	    break;

	  case SIZE:				/* check size */
	    if(c == ')'){			/* finished grabbing size */
		*p = '\0';
		/*
		 * replace sizes if they don't match!
		 */
		strcpy(sz, s);
		if(strcmp(sz, (lblsz) ? lblsz : prettysz(l))){
		    strcpy(sz, (lblsz) ? lblsz : prettysz(l));
		    if(p-s > *off){		/* where to begin replacemnt */
			j = (p-s) - *off;
			sinserts((*lp)->text, *off, "", 0);
			sinserts(&lprev->text[strlen(lprev->text)-j], j, 
				 sz, strlen(sz));
			*off = 0;
		    }
		    else{
			j = (*off) - (p-s);
			sinserts((*lp == NULL) ? &lprev->text[j] 
				               : &(*lp)->text[j], 
				 p-s , sz, strlen(sz));
			*off += strlen(sz) - (p-s);
		    }
		    rv = 1;
		}
		p = s;
		level = SWS;			/* what's next... */
	    }
	    else if(c == '\0' || c == ','){
		*p = '\0';
		emlwrite("\007Attchmnt: Size field missing ')': \"%s\"", s);
		rv = -1;
		level = TG;
	    }
	    else
	      *p++ = c;

	    break;

	  case SWS:				/* skip white space */
	    if(!isspace(c)){
		if(c == ','){			/* no description */
		    level = TG;			/* munch rest of garbage */
		    lbln = 0;			/* reset flag */
		}
		else if(c != '\"' && c != '\0'){
		    emlwrite("\007Attchmnt: Malformed comment, quotes required", NULL);
		    rv = -1;
		    level = TG;
		}
		else
		  level = COMMENT;
	    }
	    break;

	  case COMMENT:				/* slurp up comment */
	    if(c == '\"' || c == '\0'){		/* got comment */
		*p = '\0';			/* cap it off */
		p = s;				/* reset p */
		strcpy(cmnt,s);			/* copy the comment  */
		if(c == '\0'){
		    emlwrite("\007Attchmnt: Closing quote required at end of comment", NULL);
		    rv = -1;
		}
		level = TG;			/* prepare for next one */
		lbln = 0;			/* reset flag */
	    }
	    else
	      *p++ = c;

	    break;

	  case TG:				/* get comma or final EOL */
	    if(!isspace(c)){
		if(c != ',' && c != '\0'){
		    if(rv != -1)
		      emlwrite("\007Attchmnt: Comma must separate attachments", NULL);
		    rv = -1;
		}
	    }
	    break;

	  default:				/* something's very wrong */
	    emlwrite("\007Attchmnt: Weirdness in ParseAttach", NULL);
	    return(-1);				/* just give up */
	}

	if(c == '\0')				/* we're done */
	  break;

	(*off)++;

	/*
	 * not in comment or label name? done. 
	 */
	if(c == ',' && (level != COMMENT && !lbln))
	  break;				/* put offset past ',' */
    }

    return(rv);
}



/*
 * NewAttach - given a filename (assumed to accessible) and comment, creat
 */
PATMT *NewAttach(f, l, c)
char *f;
long l;
char *c;
{
    PATMT  *tp;

    if((tp=(PATMT *)malloc(sizeof(PATMT))) == NULL){
	emlwrite("No memory to add attachment", NULL);
	return(NULL);
    }
    else{
	tp->filename = tp->description = NULL;
	tp->size = tp->id = NULL;
	tp->next = NULL;
    }

    /* file and size malloc */
    if((tp->filename = (char *)malloc(strlen(f)+1)) == NULL){
	emlwrite("Can't malloc name for attachment", NULL);
	free((char *) tp);
	return(NULL);
    }
    strcpy(tp->filename, f);

    if(l > -1){
	strcpy(s, prettysz(l));
	if((tp->size = (char *)malloc(sizeof(char)*(strlen(s)+1))) == NULL){
	    emlwrite("Can't malloc size for attachment", NULL);
	    free((char *) tp->filename);
	    free((char *) tp);
	    return(NULL);
	}
	else
	  strcpy(tp->size, s);
    }

    /* description malloc */
    if((tp->description = (char *)malloc(strlen(c)+1)) == NULL){
	emlwrite("Can't malloc description for attachment", NULL);
	free((char *) tp->size);
	free((char *) tp->filename);
	free((char *) tp);
	return(NULL);
    }
    strcpy(tp->description, c);

    return(tp);
}


void ZotAttach(p)
PATMT *p;
{
    if(!p)
      return;
    if(p->description)
      free((char *)p->description);
    if(p->filename)
      free((char *)p->filename);
    if(p->size)
      free((char *)p->size);
    if(p->id)
      free((char *)p->id);
    p->next = NULL;
}
#endif	/* ATTACHMENTS */


/*
 * intag - return TRUE if i is in a column that makes up an
 *         attachment line number
 */
intag(s, i)
char *s;
int   i;
{
    char *p = s;
    int n = 0;

    while(*p != '\0' && (p-s) < 5){		/* is there a tag? it */
	if(n && *p == '.')			/* can't be more than 4 */
	  return(i <= p-s);				/* chars long! */

	if(*p < '0' || *p > '9')
	  break;
	else
	  n = (n * 10) + (*p - '0');

	p++;
    }

    return(FALSE);
}


/*
 * prettysz - return pointer to string containing nice
 */
char *prettysz(l)
long l;
{
    static char b[32];

    if(l < 1000)
      sprintf(b, "%d  B", l);			/* xxx B */
    else if(l < 10000)
      sprintf(b, "%1.1f KB", (float)l/1000);    /* x.x KB */
    else if(l < 1000000)
      sprintf(b, "%d KB", l/1000);		/* xxx KB */
    else if(l < 10000000)
      sprintf(b, "%1.1f MB", (float)l/1000000); /* x.x MB */
    else
      sprintf(b, "%d MB", l/1000000);		/* xxx MB */
    return(b);
}


/*
 * sinserts - s insert into another string
 */
sinserts(ds, dl, ss, sl)
char *ds, *ss;					/* dest. and source strings */
int  dl, sl;					/* their lengths */
{
    char *dp, *edp;				/* pointers into dest. */
    int  j;					/* jump difference */

    if(sl >= dl){				/* source bigger than dest. */
	dp = ds + dl;				/* shift dest. to make room */
	if((edp = strchr(dp, '\0')) != NULL){
	    j = sl - dl;

	    for( ;edp >= dp; edp--)
	      edp[j] = *edp;

	    while(sl--)
	       *ds++ = *ss++;
	}
	else
	  emlwrite("\007No end of line???", NULL);	/* can this happen? */
    }
    else{					/* dest is longer, shrink it */
	j = dl - sl;				/* difference in lengths */

	while(sl--)				/* copy ss onto ds */
	  *ds++ = *ss++;

	if(strlen(ds) > j){			/* shuffle the rest left */
	    do
	      *ds = ds[j];
	    while(*ds++ != '\0');
	}
	else
	  *ds = '\0';
    }
}
