#include "args.h"
/*
 * Copyright (c) 1986, 2014 by The Trustees of Columbia University in
 * the City of New York.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  + Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  + Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 *  + Neither the name of Columbia University nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 */

#ifndef lint
static const char *rcsid = "$Header: /usr/local/src/mm/mm-0.94/mm/RCS/babyl.c,v 1.2 2005/05/28 22:27:14 beebe Exp $";
#endif

/* babyl.c: routines to handle babyl format mail files */
/*
 * cache stuff may break if there are two open files.
 * when opening second file, maybe rewind "cf", and free the cache.
 */

#include "mm.h"				/* good stuff! */
#include "rd.h"
#include "babyl.h"			/* babylish stuff */

static char *babyl_banner ARGS((msgvec *mail));
static int babyl_flags ARGS((msgvec *mail, char *str, message *msg));
static u_long flaglookup ARGS((char *str, struct flagword *table));

#define key "BABYL OPTIONS:"		/* key to babyl format */
#define keysize (sizeof(key)/sizeof(char))-1 /* don't count the null */
#define MSGEND '\037'
#define MSGBEG '\014'
#define EOOH  "*** EOOH ***"
#define FROM  "From:"
#define BABYLHEADER "BABYL OPTIONS:\n\
Version: 5\n\
Note:   This is the header of an rmail file.\n\
Note:   If you are seeing it in rmail,\n\
Note:    it means the file has no messages in it.\n\
Labels:"


static char *cache = 0;

static struct flagword flagnames[] = {
    { "deleted", M_DELETED },
    { "answered", M_ANSWERED },
    { "filed", M_FILED },
    { "forwarded", M_FORWARDED },
    { "unseen", M_SEEN },
    { "edited", M_EDITED },
    { NULL, 0 },
};


static struct flagword keynames[] = {
    { "flagged", M_FLAGGED },
    { NULL, 0 },
};

/*
 * babyl_open:
 * see rd.h comments about open
 */
int
#if HAVE_STDC
babyl_open(msgvec *mail, int flags)
#else /* K&R style */
babyl_open(mail,flags)
msgvec *mail;
int flags;
#endif /* HAVE_STDC */
{
    int error;
    struct stat sbuf;
    keylist k;
    struct flagword *fl;

    if (!(flags & OP_APP))		/* don't worry if just an append */
	mail->keywords = nil;
    error = local_contig_open (mail,flags);
    if (error != 0 || !(flags & OP_APP))
	return(error);
    if (mail->filep == NULL)		/* reuse cf, file lock downgraded */
	return(0);
    if (fstat(fileno(mail->filep), &sbuf) != 0) {
	perror("fstat");
	return(errno);
    }
    if (sbuf.st_size != 0)
	return(0);
    fprintf(mail->filep,"%s", BABYLHEADER);
    for(fl = keynames; fl && fl->name; fl++)
	fprintf(mail->filep, "%s%s", fl->name, (fl+1)->name ? "," : "");
    if (mail->keywords && keynames[0].name)
        fputc (',', mail->filep);
    if (mail->keywords)
	for(k = mail->keywords; *k != NULL; k++)
	    fprintf(mail->filep, "%s%s", *k, *(k+1) ? "," : "\n");
    else
        fputc ('\n', mail->filep);
    fputc (MSGEND, mail->filep);
    return(0);
}

/*
 * see rd.h comments about close
 */
void
#if HAVE_STDC
babyl_close (FILE *fp)
#else /* K&R style */
babyl_close (fp)
FILE *fp;
#endif /* HAVE_STDC */
{
    local_close (fp);
    if (cache) {
	free(cache);
	cache = NULL;
    }
}

/*
 * babyl_rdmsg:
 * see rd.h comments about rd_msg
 */
int
#if HAVE_STDC
babyl_rdmsg(msgvec *mail, int num)
#else /* K&R style */
babyl_rdmsg(mail,num)
msgvec *mail;
int num;
#endif /* HAVE_STDC */
{
    char *header, *cp, *text;
    int buflen, space, eof, line, seen_eooh = false, which_headers = 0;
    message *m;
    u_long size;

    if (num > mail->count)		/* much more than we have */
       return (RD_FAIL);		/* not there */

    if (mail->last_read > num) { 	/* gotta go back to it */
        rewind (mail->filep);		/* have to start from beginning */
	if (cache) {
	    free(cache);
	    cache = NULL;
	}
	mail->last_read = 0;		/* haven't read msg 1 */
	header = babyl_banner(mail);
	if (header == NULL)
	    return(RD_FAIL);		/* no banner, bad file */
    }
    else {				/* no need to rewind */
	if (cache) {
	    header = cache;
	    cache = NULL;
	}
	else
	    header = fgetline(mail->filep);
	if (header == NULL)
	    return (RD_EOF);		/* this is the end... */
	if (strcmp(header, key) == 0) {	/* looking at babyl header */
	    rewind(mail->filep);
	    mail->last_read = 0;
	    header = babyl_banner(mail); /* get past banner */
	    if (header == NULL) {
		fprintf(stderr,
		       "Babyl banner has bad format, ignoring rest of file\n");
		return(RD_FAIL);	/* bad banner, bad file */
	    }
	}
	if (header[0] != MSGEND) {
	    fail_msg (mail->last_read);
	    fseek(mail->filep, -strlen(header), SEEK_CUR);
	    return(RD_FAIL);		/* bad previous message */
	}
    }

    while (++mail->last_read < num) {	/* skip up to the num'th message */
	if (header[1] != MSGBEG) {
	    fseek(mail->filep, -strlen(header), SEEK_CUR);
	    free(header);
	    fail_msg (mail->last_read--); /* couldn't read it */
	    return (RD_FAIL);		/* earlier message missing */
	}
	while (true) {			/* skip one message */
	    free(header);
	    header = fgetline(mail->filep);
	    if (header == NULL) {
		fail_msg (mail->last_read--); /* couldn't read it */
		return (RD_FAIL);	/* bad file -- ended w/out MSGEND */
	    }
	    if (header[0] == MSGEND)
		break;
	}
    }
    /* should be looking at our msg now */
    if (header[1] != MSGBEG) {		/* no next message.... */
	fseek(mail->filep, -strlen(header), SEEK_CUR);
	free(header);
	if (header[1] != '\0') {	/* not just end of file */
	    fail_msg (mail->last_read--); /* couldn't read it */
	    return (RD_FAIL);
	}
	return(RD_EOF);			/* just missed it */
    }
    free(header);			/* free "MSGEND MSGBEG \n" */
    header = fgetline(mail->filep);	/* get line with flags and keywords */
					/* handle babyl flags/keywords */
    if (header[0] == '0')		/* no headers before EOOH */
	which_headers = 2;
    else if (header[0] == '1')
	which_headers = 1;
    else {
	free (header);
	fail_msg (mail->last_read--);
	return (RD_FAIL);		/* bad format */
    }
    mail->msgs[num].keywords = nil;
    if (!babyl_flags(mail,header,&mail->msgs[num])) {
	free(header);
	fail_msg (mail->last_read--);
	return(RD_FAIL);
    }
    free(header);
					/* now get the rest of the message */
    buflen = 0;
    text = cp = (char*)malloc (buflen += 100);	/* get some space for body of msg */
    space = buflen;			/* all empty so far */
    eof = false;			/* not the end yet */

    do {
	line = cp - text;		/* starting a line */

	if (space < 10) {
	    text = (char *) realloc (text, buflen += 100);
	    space += 100;		/* used the rest (but for '\0') */
	    cp = &text[buflen-space];	/* room to complete line */
	}
	if (fgets (cp, space, mail->filep) == NULL) { /* try to get a line */
	    eof = true;			/* end of file, that's it */
	    continue;			/* go down to the "while" */
	}
	while (index (cp, '\n') == NULL) {
	    text = (char *) realloc (text, buflen +=100);
	    space = 101;		/* used the rest (but for '\0') */
	    cp = &text[buflen-space];	/* room to complete line */
	    if (fgets (cp, space, mail->filep) == NULL) {
	        eof = true;		/* end of file */
		break;			/* out of while */
	    }
	}
					/* end of first header? */
	if (eof)
	    continue;
	if (text[line] == MSGEND)
	    continue;

	if (!seen_eooh && (ustrncmp (&text[line], EOOH, (int)strlen(EOOH)) == 0)) {
	    seen_eooh = true;
	    if (which_headers == 1) {
		text[line] = '\0';		/* forget this line */
		space = space + (cp - &text[line]);	/* reclaim that */
		cp = &text[line];		/* back to beginning */
		while(true) {
		    header = fgetline(mail->filep);
		    if (strlen(header) == 0) {
			free(header);
			break;
		    }
		    free(header);
		}
	    }
	    else {
		text[line] = '\0';		/* forget this line */
		space = space + (cp - &text[line]);	/* reclaim that */
		cp = &text[line];		/* back to beginning */
/* 		free(header); */
		continue;
	    }
	}
	else if (!seen_eooh && which_headers == 2) {
	    text[line] = '\0';		/* forget this line */
	    space = space + (cp - &text[line]);	/* reclaim that */
	    cp = &text[line];		/* back to beginning */
/* 	    free(header); */
	    continue;
	}
	else {
	    while (*cp)			/* get to null */
	        cp++;			/* keep going */
	    space = buflen - (cp - text); /* what we haven't used */
	}
    } while (!eof && (text[line] != MSGEND));

    if (!eof) {
	cache = (char *)safe_strcpy(&text[line]);
	if (text[line] == MSGEND)
	    text[line] = '\0';		/* throw it out */
	text = (char *) realloc (text, (size = line) +1); /* snug to fit */
    }
    else {
	if (text[line] == MSGEND)
	    text[line] = '\0';		/* throw it out */
        text = (char *) realloc (text, (size = (strlen (text))) +1);
    }


    m = &mail->msgs[num];
    cp = htext("date", text);
    if (cp) {
	m->date = babyl_date(cp);
	free(cp);
    }
    else
	m->date = 0;
    if (m->date == 0)
        fprintf (stderr,"Message %d has bad date string (continuing)\n", num);

    m->from = NULL;
    m->size = size;
    m->text = text;
    set_msg_keywords(m);
    return (RD_OK);			/* got the message! */
}


/*
 * babyl_wrmsg:
 * see rd.h comments about wr_msg
 * Each babyl format message starts with a special MSGBEG character.
 * The next line has a "0" to signify that what comes next is
 * unprocessed (as far as true babyl is concerned) headers, with any
 * keywords and flags (as text strings) on the same line as the "0".
 *
 * Next is a "Summary-line:" header if that was in the text, then the
 * special EOOH (End Of, Oh... Headers) string, then the entire text of
 * the message (except for the "Summary-line:").
 *
 * Last comes the special MSGEND character.
 *
 */
int
#if HAVE_STDC
babyl_wrmsg(msgvec *mail, message *msg, int num, int flags)
#else /* K&R style */
babyl_wrmsg(mail,msg,num,flags)
msgvec *mail;
message *msg;
int num;
int flags;
#endif /* HAVE_STDC */
{
  char *text;
  struct flagword *fl;
  char **k;
  char *sl, *sl_end;

  if (!(flags & (WR_SAVE | WR_COPY))) {	/* not a save or copy */
      mail->flags |= MF_DIRTY;		/* mark it to be saved */
      return (false);			/* no error */
  }
  if ((msg->flags & M_DELETED) && (flags & WR_EXP)) {
      if (num == mail->count)
	  local_get_size(mail);
      return (false);			/* don't save deleted messages */
  }

  fprintf(mail->filep, "%c\n", MSGBEG);
  fprintf(mail->filep,"0,");

  msg->flags ^= M_SEEN;			/* this one works backwards */
  for(fl = flagnames; fl->name != NULL; fl++) {
      if (msg->flags & fl->flag)
#ifdef BROKEN_BABYL
	  fprintf(mail->filep, "%s,", fl->name);
#else
	  fprintf(mail->filep, " %s,", fl->name); /* space before keyword */
#endif /* BROKEN_BABYL */
  }
  msg->flags ^= M_SEEN;			/* this one works backwards */

  fputc(',', mail->filep);
  for(fl = keynames; fl->name != NULL; fl++) {
      if (msg->flags & fl->flag)
#ifdef BROKEN_BABYL
	  fprintf(mail->filep, "%s,", fl->name);
#else
	  fprintf(mail->filep, " %s,", fl->name); /* space */
#endif /* BROKEN_BABYL */
  }
  for(k = msg->keywords; k && *k; k++)
#ifdef BROKEN_BABYL
      fprintf(mail->filep,"%s,",*k);
#else
      fprintf(mail->filep," %s,",*k);	/* space */
#endif /* BROKEN_BABYL */
  fputc('\n', mail->filep);
  text = msg->text;
  if ((sl = (char *) hfind("summary-line",text)) != NULL) {
      sl_end = index(sl,'\n');
      if (sl_end) *sl_end = '\0';
      fprintf(mail->filep,"%s\n", sl);
      if (sl_end) *sl_end = '\n';
  }
  fprintf(mail->filep,"%s\n",EOOH);
  while(true) {
      char *cp;
      if (text == sl)			/* don't print summary-line */
	  text = sl_end+1;
      cp = index(text,'\n');
      if (cp) {
	  *cp = '\0';
	  fprintf(mail->filep,"%s\n",text);
	  if (strlen(text) == 0) {
	      *cp = '\n';
	      text = cp+1;
	      break;
	  }
	  *cp = '\n';
	  text = cp+1;
      }
      else {
	  fprintf(mail->filep,"%s",text);
	  break;
      }
  }

  fwrite(text, sizeof(char), strlen(text), mail->filep);
  fputc(MSGEND, mail->filep);
  if ((fflush (mail->filep) == EOF) ||	/* make sure we get it out */
      (ferror(mail->filep) != 0))
      return (true);
  if (flags & WR_SAVE) {		/* we saved it */
      msg->flags &= ~M_MODIFIED;	/* it's not modified anymore  */
      if (num == mail->count)
	  local_get_size(mail);
  }
  return (false);			/* no error */
}

/*
 * probe file to see if it's in babyl format
 * babyl format files are distinguished by starting with "BABYL OPTIONS"
 */
int
#if HAVE_STDC
babyl_probe (const char *file)
#else /* K&R style */
babyl_probe (file)
const char *file;
#endif /* HAVE_STDC */
{
    FILE *fp;
    char p[keysize +1];		/* enough to probe it, with null */
    int i;
    int ret;

    if (cf && same_file(cf->filename, file)) {
	if (cf->type == TYPE_BABYL)
	    return (PR_OK);
	else
	    return (PR_NOTOK);
    }
    if ((ret = local_contig_proben (file, &fp)) != PR_OK)
	return (ret);			/* couldn't open it */
    i = fread (p, sizeof (char), keysize, fp);
    fclose (fp);			/* okay, we're done looking */
    if (i == 0)
	return (PR_EMPTY);		/* nothing in it */
    if (i < keysize)
        return (PR_NOTOK);		/* not enough characters */
    p[i] = '\0';			/* finish the string */
    if (strcmp (p, key) != 0)
        return (PR_NOTOK);		/* doesn't look right */
    return (PR_OK);			/* passed! */
}

static int
#if HAVE_STDC
babyl_flags(msgvec *mail, char *str, message *msg)
#else /* K&R style */
babyl_flags(mail,str, msg)
char *str;
message *msg;
msgvec *mail;
#endif /* HAVE_STDC */
{
    int num;
    u_long f;
    char *ptr;
    keylist kw;
    u_long flags;

    flags=0;
    kw = NULL;
    if (!(ptr = index(str, ',')))
	return(false);
    *ptr = '\0';
    num = atoi(str);
    if (num == 0 && str[0] != '0')
	return(false);
    *ptr = ',';
    str = ptr + 1;

    while((ptr = index(str,','), ptr)) {
	if (*str != ',') {
	    *ptr = '\0';
	    if (*str == ' ')		/* ignore leading space */
	        str++;			/*   see BROKEN_BABYL stuff */
	    f = flaglookup(str,flagnames);
	    if (f == 0)
		f = flaglookup(str,keynames);
	    if (f == 0) {
		kw = add_keyword(str,kw);
		mail->keywords = add_keyword(str,mail->keywords);
	    }
	    else
		flags |= f;
	    *ptr = ',';
	}
	str = ptr + 1;
    }
    msg->flags = flags ^ M_SEEN;	/* this one's backwards */
    msg->keybits = 0;			/* no keybits in babyl format */
    msg->keywords = kw;
    return(true);
}

static u_long
#if HAVE_STDC
flaglookup(char *str, struct flagword *table)
#else /* K&R style */
flaglookup(str,table)
char *str;
struct flagword *table;
#endif /* HAVE_STDC */
{
    while(table->name) {
	if (ustrcmp(table->name, str) == 0)
	    return(table->flag);
	table++;
    }
    return(0);
}

/*
 * babyl_banner:
 * skip past the banner, and return a pointer to the MSGEND MSGBEG line
 * before the first message
 * return NULL if something goes wrong...
 */
static char *
#if HAVE_STDC
babyl_banner(msgvec *mail)
#else /* K&R style */
babyl_banner(mail)
msgvec *mail;
#endif /* HAVE_STDC */
{
    char *h;
    rewind(mail->filep);
    h = fgetline(mail->filep);
    if (strcmp(h,key) != 0) {
	free(h);
	return(NULL);
    }
    while(true) {
	free(h);
	h = fgetline(mail->filep);
	if (h == NULL)
	    return(NULL);
	if (h[0] == MSGEND)
	    return(h);
    }
}
