/* @(#)msgs.c	(c) copyright 10/18/86 (Dan Heller) */

#include "mush.h"

void
display_msg(n, flg)
register int n;
u_long flg;
{
    char buf[32], *pager = NULL;
    int intro = TRUE;

    if (ison(msg[n].m_flags, DELETE) && !do_set(set_options, "show_deleted")) {
	print("Message %d deleted; ", n+1);
#ifdef SUNTOOL
	if (istool)
	    wprint("Select UNDELETE to read.\n");
	else
#endif /* SUNTOOL */
	if (iscurses)
	    print_more("Type 'u' to undelete.");
	else
	    wprint("Type 'undelete %d' to undelete\n", n+1);
	return;
    }
    set_isread(n);
    if (ison(flg, M_TOP)) {
	turnon(flg, NO_HEADER);
	print("Top of "), turnon(glob_flags, CONT_PRNT);
    }

#ifdef MSG_SEPARATOR
    turnon(flg, NO_SEPARATOR);
#endif /* MMDF */
    if (ison(msg[n].m_flags, METAMAIL) && isoff(flg, NO_PAGE) &&
	    (pager = do_set(set_options, "metamail"))) {
	intro = FALSE;
	turnoff(flg, NO_HEADER);
	turnoff(flg, M_TOP);
	turnon(flg, NO_IGNORE);
    } else if (!istool && isoff(flg, NO_PAGE) &&
	    crt < msg[n].m_lines && isoff(flg, M_TOP)) {
	if (!(pager = do_set(set_options, "pager")))
	    pager = DEF_PAGER;
	if (!*pager || !strcmp(pager, "internal"))
	    pager = NULL; /* default to internal pager if pager set to "" */
    }
    (void) do_pager(pager, intro? 1 : -1); /* start pager */
    if (intro)
	(void) do_pager(sprintf(buf, "Message #%d (%d lines)\n",
				 n+1, msg[n].m_lines), FALSE);
    (void) copy_msg(n, NULL_FILE, flg, NULL);
    (void) do_pager(NULL, FALSE); /* end pager */
}

/*
 * copy message 'n' to file "fp" according to various flag arguments
 * return number of lines copied or -1 if system error on fputs.
 * If "fp" is null, send to internal pager.  This can only happen from
 * display_msg above.
 */
copy_msg(n, fp, flags, pattern)
register int n;
register FILE *fp;
u_long flags;
char *pattern;
{
    register int  ignoring = 0, lines = 0;
    register char *indent_str, *p, *end_pat = NULL;
    int		  on_hdr = 1, top, squeeze = 0;
    long	  still_more = 0;
    int		  pat_len, pat_seek;
    char 	  line[BUFSIZ], *show_hdrs = NULL;

    if (ison(flags, M_TOP)) {
	p = do_set(set_options, "toplines");
	top = (p)? atoi(p) : crt;
    }
    /* When updating to a folder, always write all headers! */
    if (ison(flags, UPDATE_STATUS))
	turnon(flags, NO_IGNORE);
    else if (ison(flags, NO_IGNORE) &&
	    (p = do_set(set_options, "alwaysignore")) && !*p)
	turnoff(flags, NO_IGNORE);	/* preserve historic behavior */
    if (isoff(flags, NO_IGNORE)) {
	if (do_set(set_options, "squeeze"))
	    squeeze = 1;
	show_hdrs = do_set(set_options, "show_hdrs");
    }
    if (pattern && *pattern == '/' && (end_pat = index(pattern+1, '/'))) {
	if (end_pat[1] == ',') {
	    pattern++;
	    *end_pat++ = 0;
	} else
	    end_pat = NULL;
    }
    pat_len = pattern? strlen(pattern) : 0;
    pat_seek = !!pat_len;

#ifdef SUNTOOL
    xfree(more_prompt), more_prompt = NULL;
#endif /* SUNTOOL */

    if (ison(flags, INDENT)) {
	if ((indent_str = do_set(set_options, "pre_indent_str"))) {
	    fputs(format_hdr(n, indent_str, FALSE) + 9, fp); /* magic 9 !! */
	    fputc('\n', fp);
	}
	if (!(indent_str = do_set(set_options, "indent_str")))
	    indent_str = DEF_INDENT_STR;
	indent_str = format_hdr(n, indent_str, FALSE) + 9; /* magic 9 !! */
    }
    /* "line" used as dummy here, since 0 bytes read */
    if (!msg_get(n, line, 0)) {
	error("Unable to find msg %d", n+1);
	return -1;
    }
    while (still_more < msg[n].m_size && fgets(line, sizeof (line), tmpf)) {
	still_more += strlen(line);
#ifdef MSG_SEPARATOR
	if (ison(flags, NO_SEPARATOR)) {
#ifdef MMDF
	    if (!strncmp(line, MSG_SEPARATOR, 4))
#else /* !MMDF */
	    if (!strncmp(line, MSG_SEPARATOR, strlen(MSG_SEPARATOR)))
#endif /* MMDF */
		continue;
	}
#endif /* MMDF */
	/*
	 * If squeeze is one, all blanks lines squeeze down to one blank line.
	 * If squeeze is two, squeezing is in progress so wait for the next \n.
	 */
	if (*line == '\n') {
	    if (on_hdr) {  /* blank line -- end of header */
		on_hdr = 0;
		if (ison(flags, NO_HEADER))
		    continue;
	    }
	    if (squeeze > 1 || pat_len && pat_seek)
		continue;
	    else if (squeeze)
		squeeze = 2;
	} else {
	    if (squeeze > 1)
		squeeze = 1;
	    if (pat_len && (!on_hdr || isoff(flags, NO_HEADER))) {
		/* If we're looking for a pattern for mush-pipe, then
		 * continue if this line doesn't match the pattern.
		 */
		if (pat_len == 0)
		    continue;
		Debug("Seeking (%s) in (%s)", pattern, line);
		if (strncmp(line, pattern, pat_len)) {
		    if (pat_seek)
			continue;
		} else if (end_pat && *end_pat++ == ',') {
		    pattern = end_pat;
		    if (*pattern == '/') {
			pattern++;
			if (end_pat = index(pattern, '/'))
			    *end_pat++ = 0;
		    }
		    pat_len = pattern? strlen(pattern) : 0;
		    pat_seek = !pat_seek;
		} else {
		    pat_len = 0;
		    pat_seek = !pat_seek;
		}
	    }
	}

	if (ison(flags, UPDATE_STATUS))
	    if (!strncmp(line, "Status:", 7) || !strncmp(line, "Priority:", 9))
		continue; /* ignore "Status" and "Priority" lines */
	    else if (!on_hdr) {
		int i, write_priority = 0;
		p = line;
		p += Strcpy(p, "Priority:");
		for (i = 0; i < MAX_PRIORITY; i++)
		    if (ison(msg[n].m_flags, M_PRIORITY(i + 1))) {
			write_priority = 1;
			*p++ = ' ';
			*p++ = i + 'A';
		    }
		if (write_priority) {
		    *p++ = '\n', *p = 0;
		    (void) fputs(line, fp);
		}
		/* PRESERVE here avoids changing new message status */
		if (isoff(flags, PRESERVE) || /* NOT msg[n].m_flags */
			ison(msg[n].m_flags, OLD) ||
			isoff(msg[n].m_flags, UNREAD)) {
		    p = line;
		    p += Strcpy(p, "Status: O");
		    if (isoff(msg[n].m_flags, UNREAD))
			*p++ = 'R';
		    if (ison(msg[n].m_flags, SAVED))
			*p++ = 'S';
		    if (ison(msg[n].m_flags, REPLIED))
			*p++ = 'r';
		    if (ison(msg[n].m_flags, PRINTED))
			*p++ = 'p';
		    if (ison(msg[n].m_flags, FORWARD))
			*p++ = 'f';
		    *p++ = '\n', *p = 0;
		    (void) fputs(line, fp);
		}
		turnoff(flags, UPDATE_STATUS);
		line[0] = '\n', line[1] = '\0';
	    }
	if (on_hdr && (isoff(flags, NO_IGNORE) || ison(flags, FORWARD))) {
	    p = any(line, " \t:");
	    if (!p)
		ignoring = 0, on_hdr = 0;
	    else if (ignoring)
		if (*p != ':') {
		    Debug("Ignoring: %s", line);
		    continue;
		} else
		    ignoring = 0;
	    if (p && *p == ':') {
		*p = 0;
		ignoring = 0;
		if (ison(flags, FORWARD)) {
		    if (chk_two_lists(line, IGNORE_ON_FWD, ":, \t"))
			ignoring = 1;
		} else if (show_hdrs) {
		    if (!chk_two_lists(line, show_hdrs, ":, \t"))
			ignoring = 1;
		} else {
		    register struct options *opts;
		    for (opts = ignore_hdr; opts; opts = opts->next)
			if (!lcase_strncmp(opts->option, line, -1)) {
			    ignoring = 1;
			    break;
			}
		}
		*p = ':';
		if (ignoring) {
		    Debug("Ignoring: %s", line);
		    continue;
		}
	    }
	}
	if (!on_hdr && ison(flags, M_TOP) && !--top)
	    break;
	if (!on_hdr && (still_more < msg[n].m_size || line[0] != '\n') ||
		isoff(flags, NO_HEADER)) {
	    /* note that function returns the number of lines */
	    lines++;
	    if (ison(flags, INDENT))
		(void) fputs(indent_str, fp);
	    if (!fp) {
		if (do_pager(line, FALSE) == EOF)
		    return -1;
	    } else if (fputs(line, fp) == EOF)
		/* Pipe broken, out of file space, etc */
		return -1;
	}
	if (pat_seek && !pat_len)
	    break; /* Skip the rest */
    }
    if (ison(flags, INDENT) &&
	(indent_str = do_set(set_options, "post_indent_str")) && *indent_str) {
	(void) fprintf(fp, "%s\n", format_hdr(n, indent_str, FALSE)+9);
    }
    if (fp && fflush(fp) == EOF)
	return -1;	/* Write failure? */
    return lines;
}

/*
 * copy tempfile back to folder.
 * Return 1 on success, 0 on failure.
 */
copyback(prompt, final)
char *prompt;
int final;	/* Are we exiting or updating? */
{
    register int	i = 0, held = 0, saved = 0;
    register u_long	flg = 0;
    register FILE	*mbox = NULL_FILE, *mail_fp = NULL_FILE;
#ifdef SYSV
    FILE 		*save_mail_fp = NULL_FILE;
#endif /* SYSV */
    char		*mbox_file, action = 0;
    int 		hold = 0, delete_it = 0, dont_unlink = !final;
    int			isspool, keepsave, write_err = FALSE;
    static int		first = 1;

    /*
     * if there is new mail in this folder, the user is notified and
     * prompted if he really wants to update the folder.  He's either
     * quitting or changing folders, so let him read the new mail first.
     */
    if (!first && mail_size()) {
lost_lock:
	if ((ison(glob_flags, CORRUPTED) || get_new_mail(TRUE)) &&
		prompt && isoff(glob_flags, REDIRECT) && show_new_mail()) {
	    char buf[80];
	    if (iscurses)
		putchar('\n'), turnon(glob_flags, CNTD_CMD);
	    if (!istool)
		print("%s [n] ", prompt);
	    buf[0] = 0;
#ifdef SUNTOOL
	    if (istool) {
		(void) sprintf(buf, "%s -- %s",
			ison(glob_flags, CORRUPTED) ? "Error" : "New mail",
			prompt);
		if (ask(buf) != TRUE)
		    return 0;
	    } else
#endif /* SUNTOOL */
		if (!Getstr(buf, sizeof (buf), 0) || lower(*buf) != 'y')
		    return 0;
	    turnoff(glob_flags, CORRUPTED); /* User says go ahead */
	}
    }
    first = 0;

    /* If the user hasn't changed anything, just return true */
    if (isoff(glob_flags, DO_UPDATE) || ison(glob_flags, CORRUPTED))
	return 1;
    if (ison(glob_flags, READ_ONLY)) {
	print("Unable to update %s: read only\n", mailfile);
	return 0; /* user should use "exit" instead of "quit". */
    }
    if (!msg_cnt) /* prevent unnecessary overwrite */
	return 1;

#ifdef SUNTOOL
    if (istool) {
	(void) notify_set_itimer_func(tool, do_check,
	    ITIMER_REAL, (struct itimerval *) 0, (struct itimerval *) 0);
    }
#endif /* SUNTOOL */

    /* We can't lock a file unless we have an fd, but "w+" will zero
     * the file.  If the lock later failed for any reason (possible
     * race condition with an MTA), we would lose all current mail.
     * So, open read/write (if possible) and truncate later.
     */
    if (!(mail_fp = lock_fopen(mailfile, "r+"))) {
	error("WARNING: unable to lock %s -- update aborted", mailfile);
#ifdef SUNTOOL
	if (istool) {
	    write_err = 1;	/* forces return 0; below */
	    goto resume_timer;	/* blecch */
	}
#endif /* SUNTOOL */
	return 0;
    }
    /* Make sure no mail arrived between the last check and when we
     * got the lock.  If it did, release the lock and try again.
     */
    if (mail_size()) {
	(void) close_lock(mailfile, mail_fp);
	goto lost_lock;
    }

    /* open mbox if: "autodelete" AND "hold" are NOT set. */
    if (!strcmp(mailfile, spoolfile)
	    && !(delete_it = !!do_set(set_options, "autodelete"))
	    && !(hold = !!do_set(set_options, "hold"))) {
	register char *p;
	int x = 1; /* tell getpath to ignore "ENOENT" if file not found */

	if (!(p = do_set(set_options, "mbox")))
	    p = DEF_MBOX;
	mbox_file = getpath(p, &x); /* static data -- copy? */
	if (x) {
	    if (x > 0)
		print("%s is a directory.\n", mbox_file);
	    else
		print("Unable to open %s: %s\n", p, mbox_file);
	    mbox = NULL_FILE;
	} else {
	    if (Access(mbox_file, F_OK) == -1) /* does it exist? */
		mbox = lock_fopen(mbox_file, "w");
	    else
		mbox = lock_fopen(mbox_file, "a");
	    if (!mbox)
		error("Unable to write to %s", mbox_file);
	}
    }

    /* ignore signals before truncating */
    turnon(glob_flags, IGN_SIGS);
#ifdef SYSV
    /* SysV can't truncate a file in the middle, so we can't just
     * write to mail_fp and close.  Instead, we save the mail_fp
     * and reopen for writing, ignoring our own lock.  After updating,
     * we can safely fclose both file pointers.
     */
    save_mail_fp = mail_fp;
    /* This could fail if we run out of file descriptors */
    if (!(mail_fp = fopen(mailfile, "w"))) {
	error("WARNING: unable to reopen %s for update", mailfile);
	if (save_mail_fp)
	    (void) close_lock(mailfile, save_mail_fp);
	if (mbox)
	    (void) close_lock(mbox_file, mbox);
	turnoff(glob_flags, IGN_SIGS);
	return 0;
    }
#endif /* SYSV */

    print("Updating \"%s\"", mailfile);

    turnon(flg, UPDATE_STATUS);
    /* Don't set OLD for new messages on update. */
    if (!final)
	turnon(flg, PRESERVE);

    keepsave = !!do_set(set_options, "keepsave");
    isspool = !strcmp(mailfile, spoolfile);

    for (i = 0; i < msg_cnt; i++) {
	/* Maintain the current message across update; if this turns out
	 * to be unnecessary (changing folders), folder() will reset it.
	 */
	if (current_msg == i)
	    current_msg = held;
	/* Check to see if message is marked for deletion or, if read and not
	 * preserved, delete it if autodelete is set.  Otherwise, if hold is
	 * set save the message in the spool file.  If all fails, save in mbox.
	 */
	if (ison(msg[i].m_flags, DELETE)
	||  ison(msg[i].m_flags, SAVED) && !keepsave &&
	    isoff(msg[i].m_flags, PRESERVE) && isspool
	||  isoff(msg[i].m_flags, UNREAD) && isoff(msg[i].m_flags, PRESERVE) 
		&& delete_it) {
	    Debug("%s %d",
		(action!='d')? "\ndeleting message:" : "", i+1), action = 'd';
	    continue;
	} else if (isoff(msg[i].m_flags, DO_UPDATE) || hold || !mbox ||
		ison(msg[i].m_flags, UNREAD) ||
		ison(msg[i].m_flags, PRESERVE)) {
	    Debug("%s %d",
		(action!='s')? "\nsaving in spool:" : "", i+1), action = 's';
	    if (copy_msg(i, mail_fp, flg, NULL) == -1) {
		error("WARNING: unable to write back to spool");
		print_more("ALL mail left in %s\n", tempfile);
		print_more("Spool mailbox may be corrupted.\n");
		dont_unlink = TRUE;
		write_err = TRUE;
		break;
	    }
	    held++;
	} else if (isspool) {   /* copy back to mbox */
	    if (copy_msg(i, mbox, flg, NULL) == -1) {
		error("WARNING: unable to write to mbox");
		print_more("Unresolved mail left in %s\n", tempfile);
		dont_unlink = TRUE;
		write_err = TRUE;
		break;
	    }
	    saved++;
	    Debug("%s %d",
		(action!='m')? "\nsaving in mbox:" : "", i+1), action = 'm';
	}
    }
    if (write_err)
	current_msg = 0;
    else if (current_msg == held)
	current_msg--;	/* Don't point to a message that got deleted */
    Debug("\n%s", mailfile);

#ifdef SYSV
    /* Close the write file pointer first */
    (void) fclose(mail_fp);
    mail_fp = save_mail_fp;
#else /* !SYSV */
    /* Truncate the file at the end of what we just wrote.
     * If you aren't SYSV and you still can't ftruncate(),
     * you're out of luck?
     */
    (void) ftruncate(fileno(mail_fp), ftell(mail_fp));
#endif /* SYSV */

    /* some users like to have zero length folders for frequent usage */
    if (mbox && close_lock(mbox_file, mbox) == EOF) {
	error("WARNING: unable to close mbox");
	print_more("Unresolved mail left in %s\n", tempfile);
	dont_unlink = TRUE;
	write_err = TRUE;
    }
    if (held) {
	print_more(": saved %d message%s\n", held, (held==1)? NO_STRING: "s");
    } else
#ifdef HOMEMAIL
    if (!dont_unlink && !do_set(set_options, "save_empty"))
#else /* HOMEMAIL */
    if (strcmp(mailfile, spoolfile) && !dont_unlink &&
	!do_set(set_options, "save_empty"))
#endif /* HOMEMAIL */
	if (unlink(mailfile))
	    turnon(glob_flags, CONT_PRNT), error(": cannot remove");
	else {
	    print_more(": removed\n");
	    held = -1;
	}
    else
	print_more(": empty\n");
    if (saved)
	print("saved %d message%s in %s\n",
			    saved,(saved==1)? NO_STRING:"s", mbox_file);

    if (held > 0) {
	/* Reset the access time of the spool file to prevent
	 * bogus "new mail" messages from the shell.
	 */
#ifdef POSIX_UTIME
	struct utimbuf times[1];
	(void) fflush(mail_fp); /* just in case */
	times[0].modtime = time(&times[0].actime) - 2;
	times[0].ausec = times[0].modusec = 0;
#else /* !POSIX_UTIME */
	long times[2];
	(void) fflush(mail_fp); /* just in case */
	times[1] = time(&times[0]) - (long)2;
#endif /* POSIX_UTIME */
	if (!strcmp(mailfile, spoolfile) && utime(mailfile, times))
	    error("utime");
    }

    if (close_lock(mailfile, mail_fp) == EOF) {
	error("WARNING: unable to close spool");
	print_more("ALL mail left in %s\n", tempfile);
	print_more("Spool mailbox may be corrupted.\n");
	write_err = TRUE;
    }

#ifdef SUNTOOL
    if (istool) {
resume_timer:
	mail_timer.it_value.tv_sec = time_out;
	mail_timer.it_interval.tv_sec = time_out;
	(void) notify_set_itimer_func(tool, do_check,
	    ITIMER_REAL, &mail_timer, (struct itimerval *) 0);
    }
#endif /* SUNTOOL */

    turnoff(glob_flags, IGN_SIGS);

    /* Return nonzero for success, -1 if file removed */
    if (write_err)
	return 0;
    else if (held < 0)
	return -1;
    else
	return 1;
}

/*
 * check the sizes of the current folder (file) and the spool file.
 * spool_size is the size in bytes of the user's main mailbox.
 * last_size is the size of the _current_ folder the last time we checked.
 * return true if the current folder has new mail.  check_new_mail() checks
 * for new mail in the system mailbox since it checks against last_spool_size.
 */
mail_size()
{
    struct stat buf;

    if (!stat(spoolfile, &buf))
	spool_size = buf.st_size;
    else if (!strcmp(mailfile, spoolfile))
	return 0;
    if (!is_shell || ison(glob_flags, IS_SENDING))
	return 0;
    if (strcmp(mailfile, spoolfile) && stat(mailfile, &buf)) {
	if (errno != ENOENT)
	    error("Unable to stat %s", mailfile);
	return 0;
    }
    if (buf.st_size != last_size) {
	last_size = buf.st_size;
	return 1;
    }
    return 0;
}

static
struct mailstat {
    int new, unread, deleted;
} mail_stat;

void
mail_status(as_prompt)
{
    char buf[MAXPATHLEN];
    register int cnt;

    mail_stat.new = mail_stat.unread = mail_stat.deleted = 0;

    for (cnt = 0; cnt < msg_cnt; cnt++) {
	if (ison(msg[cnt].m_flags, UNREAD))
	    mail_stat.unread++;
	if (ison(msg[cnt].m_flags, DELETE))
	    mail_stat.deleted++;
	if (isoff(msg[cnt].m_flags, OLD))
	    mail_stat.new++;
    }
    if (as_prompt) {
	/* use %s in case prompt has any %'s in it */
	print("%s", format_prompt(current_msg, prompt));
	return;
    }
    (void) sprintf(buf,"\"%s\"%s: %d message%s, %d new, %d unread",
	trim_filename(mailfile),
	ison(glob_flags, READ_ONLY)? " [read only]" : "",
	msg_cnt, (msg_cnt != 1)? "s": NO_STRING,
	mail_stat.new, mail_stat.unread);
    if (istool || iscurses)
	(void) sprintf(buf+strlen(buf), ", %d deleted", mail_stat.deleted);
#ifdef SUNTOOL
    if (istool) {
	static char ic_text[4];
	char *lbl;
	Icon icon;
	extern struct pixrect mail_icon_image1, mail_icon_image2;
	(void) sprintf(ic_text, "%3d", msg_cnt);
	if (!(lbl = (char *)window_get(tool, FRAME_LABEL)) || strcmp(lbl, buf))
	    (void) window_set(tool, FRAME_LABEL, buf, NULL);
	icon = (Icon) window_get(tool, FRAME_ICON);
	(void) icon_set(icon,
	    ICON_IMAGE, ison(glob_flags, NEW_MAIL)?
			    &mail_icon_image2 : &mail_icon_image1,
	    NULL);
	if (!chk_option("quiet", "iconlabel"))
	    (void) icon_set(icon, ICON_LABEL, ic_text, NULL);
	else
	    (void) icon_set(icon, ICON_LABEL, NO_STRING, NULL);
	(void) window_set(tool, FRAME_ICON, icon, NULL);
    } else
#endif /* SUNTOOL */

#ifdef CURSES
	if (iscurses) {
	    move (0, 0);
	    printw("%-3d %-.*s",
		((msg_cnt)? current_msg+1 : 0), COLS-5, buf), clrtoeol();
	} else
#endif /* CURSES */
	    puts(buf);
    return;
}

/*
 * Construct a prompt for the given message number using the given format
 */
char *
format_prompt(num, fmt)
int num;
char *fmt;
{
    static char buf[MAXPATHLEN];
    register char *p, *b = buf, *mf;

    if (is_shell)
	mf = mailfile;
    else
	mf = "[no folder]";

    for (p = fmt; *p; p++)
	if (*p == '\\')
	    switch (*++p) {
		case 'n': case 'r': *b++ = '\n';
		when 't': *b++ = '\t';
		otherwise: *b++ = *p;
	    }
	else if (*p == '%')
	    switch (*++p) {
		case 'm':
		    b += strlen(sprintf(b,"%d",(msg_cnt)? num + 1 : 0));
		when 't':
		    b += strlen(sprintf(b, "%d", msg_cnt));
		when 'd':
		    b += strlen(sprintf(b, "%d", mail_stat.deleted));
		when 'u':
		    b += strlen(sprintf(b, "%d", mail_stat.unread));
		when 'n':
		    b += strlen(sprintf(b, "%d", mail_stat.new));
		when 'f':
		{
		    char *tail = rindex(mf, '/'); 
		    if (tail && tail[1])
			b += Strcpy(b, tail+1);
		    else {
			/* Fall through */
		case 'F':
			b += Strcpy(b, mf);
		    }
		    if (ison(glob_flags, READ_ONLY))
			b += Strcpy(b, " [read-only]");
		}
		when 'T': case 'D': case 'Y': case 'y':
		case 'M': case 'N': case 'W':
		    b += Strcpy(b, Time(p, (long)0));
		when '$':
		{
		    struct expand var;
		    var.orig = p;
		    if (varexp(&var)) {
			b += Strcpy(b, var.exp);
			xfree(var.exp);
			p = var.rest - 1;
		    }
		}
		otherwise: *b++ = *p;
	    }
	else if (*p == '!')
	    b += strlen(sprintf(b, "%d", hist_no+1));
	else
	    *b++ = *p;
    *b = 0;
    return buf;
}

/*
 *  For uucp mailers that use >From lines with "remote from <path>":
 * (where "path" is a hostname or pathnames)
 *
 *  a. Set the return_path to the empty string.
 *  b. For each From_ or >From_ line:
 *  c. Save the username (second token).
 *  d. Save the date (3-7 tokens).
 *  e. If it has a "remote from" then append the remote host
 *	(last token) followed by a "!" to the return_path.
 *  f. If the saved username has a '@' but no '!' then convert it
 *	to UUCP path form.
 *  g. Append the saved username to return_path.
 */
parse_from(fp, path)
FILE *fp;
char path[];
{
    char user[256], buf[256]; /* max size for each line in a mail file */
    register char *p;
    long save_offset = ftell(fp);

    path[0] = '\0';
    while (fgets(buf, sizeof buf, fp)) {
	if (strncmp(buf, ">From ", 6))
	    break;
	p = buf + 6;

	(void) sscanf(p, "%s", user);

	while (p = index(p+1, 'r')) {
	    if (!strncmp(p, "remote from ", 12)) {
		char *p2 = path+strlen(path);
		skipspaces(12);
		(void) sscanf(p, "%s", p2); /* add the new machine to current path */
		(void) strcat(p2, "!");
		break;
	    }
	}

	if (p)
	    (void) bang_form(path + strlen(path), user);
	save_offset = ftell(fp);
    }
    (void) fseek(fp, save_offset, L_SET);
}

/*
 * Scan a file and select messages from it and append them to the current folder
 *
 * If "append" is 1, start where we left off (held in msg[cnt].m_offset)
 * and scan for messages.  Append all messages found until EOF.
 *
 * If "append" is 2, we're merging in a new file, so start at the end of
 * the present folder and append all messages found until EOF.
 *
 * If "append" is 0, then the message separator must exist once and
 * only once.  All extra occurrences of the separator is preceded by a '>'.
 * The list argument will be the message number to replace in the current
 * folder with the message read in from other filename.
 */
load_folder(file, append, list)
char *file, *list;
int append;
{
    char	buf[BUFSIZ];
    int		lines = 0, msg_found = 0, had_error = 1;
    int		get_status = 1, cnt;
    long	bytes, ftell();
    struct msg  old;
    char	*p, date[64];
    FILE	*fp;
    int         warn = ison(glob_flags, WARNING);
#ifdef MMDF
    int		begin_sep = 0; /* track beginning vs ending separators */
#endif /* MMDF */

    if (!(fp = lock_fopen(file, "r"))) {
	error("Unable to open %s", file);
	return -1;
    }

    if (append) {
	cnt = msg_cnt;
	(void) fseek(fp, append == 1 ? msg[cnt].m_offset : 0L, L_SET);
    } else {
	cnt = (int)list;
	old = msg[cnt];
    }

    if (isoff(glob_flags, READ_ONLY)) {
	if (tmpf)
	    (void) fclose(tmpf);
	if (!(tmpf = mask_fopen(tempfile, "a"))) {
	    error("Unable to open %s for appending", tempfile);
	    close_lock(file, fp);
	    return -1;
	}
	(void) fseek(tmpf, 0L, 2); /* assure we're at the end of the file */
    } else if (append == 2) {
	/* you can't merge in a folder to a read-only folder */
	close_lock(file, fp);
	return -1;
    }

#ifdef MMDF
    if (!append) {
	(void) strcpy(buf, MSG_SEPARATOR);
	goto do_headers;
    }
#endif /* MMDF */
    buf[0] = 0;
    while (fgets(buf, sizeof (buf), fp)) {
#ifndef MSG_SEPARATOR
	turnoff(glob_flags, WARNING);
	if (!strncmp(buf, "From ", 5)) {
	    /* skip the address to find the date */
	    p = buf + 5;	/* skip "From " */
	    skipspaces(0);
	    if ((p = any(p, " \t")) && (p = parse_date(p + 1)) ||
		    /* Try once more the hard way */
		    (p = get_name_n_addr(buf + 5, NULL, NULL)) &&
		    (p = parse_date(p + 1)))
		(void) strcpy(date, p);
	} else
	    p = NULL;
	if (p)
#else /* MSG_SEPARATOR */
	if (!strncmp(buf, MSG_SEPARATOR, strlen(MSG_SEPARATOR)))
#endif /* MSG_SEPARATOR */
	{
#ifdef MMDF
	    if (!append)
		(void) fputc('>', tmpf);
	    else if (begin_sep = !begin_sep)
do_headers:
#else /* MMDF */
	    if (!append && msg_found)
		(void) fputc('>', tmpf);
	    else
#endif /* MMDF */
	    {
		msg_found++;
		had_error = 0;
#ifndef MSGS_DYNAMIC
		if (append && cnt == MAXMSGS-append) {
		    wprint("WARNING: exceeded %d messages.\n", MAXMSGS-append);
		    wprint("Not all messages have been loaded.\n");
		    msg_cnt--;
		    had_error++;
		    break;

		}
#else
		if (append && cnt == msg_max-append) {
		    /* 
		     * XXX: should really ignore signals here but instead
		     * are careful with the order we do things
		     * so if we get interrupted, we only lose memory
		     */
		    int nmsg_max = msg_max + MSGS_INCR;
		    struct msg* m;
		    char* b, *l;
		    extern char* last_output;

		    m = (struct msg *) realloc(msg, sizeof(struct msg) * 
					       nmsg_max);
		    if (m == (struct msg *) 0) {
bad:
			wprint("Out of memory for %d messages.\n", nmsg_max);
			wprint("Not all messages have been loaded.\n");
			msg_cnt--;
			had_error++;
			break;
		    }

		    b = (char *) realloc(msg_list, MSGS_BITS(nmsg_max));
		    if (b == NULL)
			goto bad;

		    l = (char *) realloc(last_output, sizeof(char) * nmsg_max);
		    if (l == NULL)
			goto bad;

		    free(msg_list);
		    free(msg);
		    free(last_output);
		    msg = m;
		    bzero(&m[msg_max], (nmsg_max - msg_max) * sizeof(struct msg));
		    msg_list = b;
		    last_output = l;
		    msg_max = nmsg_max;
		}
#endif
		if (ison(glob_flags, READ_ONLY))
		    bytes = ftell(fp) - strlen(buf);
		else {
		    char path[256];
		    parse_from(fp, path);
		    if (path[0])
			(void)sprintf(buf,"From %s %s", path,
					    date_to_ctime(date));
		    bytes = ftell(tmpf);
		}
		/* finish up message structure from previous message.
		 * if this is incorporating new mail, check "lines" to
		 * see if previous message has already been set!
		 */
		if (cnt && lines) {
		    msg[cnt-1].m_size = bytes - msg[cnt-1].m_offset;
		    msg[cnt-1].m_lines = lines;
		}
		if (isoff(glob_flags, READ_ONLY) && fputs(buf, tmpf) == -1) {
		    error(tempfile);
		    had_error++;
		    break;
		}
		msg[cnt].m_offset = bytes;
		msg[cnt].m_flags = 0L;
#ifdef MSG_SEPARATOR
		lines = 0;
#else /* MSG_SEPARATOR */
		lines = 1; /* count the From_ line */
		if (warn)
		    turnon(glob_flags, WARNING);
		strdup(msg[cnt].m_date_recv, date);
#endif /* MSG_SEPARATOR */
		turnon(msg[cnt].m_flags, UNREAD); /* initialize */
		/* we've read the "From " line(s), now read the rest of
		 * the message headers till we get to a blank line.
		 */
		while (fgets(buf, sizeof (buf), fp) && (*buf != '\n')) {
		    p = buf;
#ifdef MMDF
		    /* MMDF might keep the From_ line, so check for it */
		    if (!msg[cnt].m_date_recv && !strncmp(buf, "From ", 5)) {
			p = buf + 5;	/* skip "From " */
			skipspaces(0);
			if ((p = any(p, " \t")) && (p = parse_date(p + 1)) ||
				/* Try once more the hard way */
				(p = get_name_n_addr(buf + 5, NULL, NULL)) &&
				(p = parse_date(p + 1)))
			    strdup(msg[cnt].m_date_recv, p);
		    } else
#endif /* MMDF */
		    if (!strncmp(buf, "Date:", 5))
			strdup(msg[cnt].m_date_sent, parse_date(p+5));
		    else if (!msg[cnt].m_date_sent &&
			    !strncmp(buf, "Resent-Date:", 12))
			msg[cnt].m_date_sent = savestr(parse_date(p+12));
		    else if (!strncmp(buf, "Content-Type:", 13))
			turnon(msg[cnt].m_flags, METAMAIL);
		    else if (!strncmp(buf, "Priority:", 9)) {
			for (p += 9 ; *p != '\n'; p++) {
			    if (!isalpha(*p) || upper(*p) > 'A' + MAX_PRIORITY)
				continue;
			    turnon(msg[cnt].m_flags,
				M_PRIORITY(upper(*p) - 'A' + 1));
			}
		    } else if (get_status &&
			    !(get_status = strncmp(p, "Status:", 7))) {
			/* new mail should not have a Status: field! */
			turnon(msg[cnt].m_flags, OLD);
			for (p += 7 ; *p != '\n'; p++) {
			    if (isspace(*p))
				continue;
			    switch(*p) {
				case 'R': turnoff(msg[cnt].m_flags, UNREAD);
				when 'P': turnon(msg[cnt].m_flags, UNREAD);
				when 'N': turnon(msg[cnt].m_flags, UNREAD);
					  turnoff(msg[cnt].m_flags, OLD);
				when 'S': turnon(msg[cnt].m_flags, SAVED);
				when 'r': turnon(msg[cnt].m_flags, REPLIED);
				when 'O': ; /* do nothing */
				when 'f': turnon(msg[cnt].m_flags, FORWARD);
				when 'p': turnon(msg[cnt].m_flags, PRINTED);
				otherwise :
				    if (ison(glob_flags, WARNING))
					print("unknown msg status flag: %c\n",
						*p);
			    }
			}
		    }
		    if (isoff(glob_flags,READ_ONLY) && fputs(buf, tmpf) == -1) {
			error(tempfile);
			had_error++;
			break;
		    }
		    lines++;
		}
		if (!msg[cnt].m_date_sent || !*msg[cnt].m_date_sent)
		    if (!msg[cnt].m_date_recv || !*msg[cnt].m_date_recv) {
			wprint("Message %d has *no* date!?\n", cnt+1);
			msg[cnt].m_date_sent = msg[cnt].m_date_recv =
			    "0000000000XXX";
		    } else
			strdup(msg[cnt].m_date_sent, msg[cnt].m_date_recv);
		else if (!msg[cnt].m_date_recv || !*msg[cnt].m_date_recv)
		    strdup(msg[cnt].m_date_recv, msg[cnt].m_date_sent);
		if (had_error)
		    break;
		if (append && list)
		    set_msg_bit(list, cnt);
		if (append)
		    cnt = ++msg_cnt;
		get_status = 1;
	    }
	} else if (!msg_found && buf[0] != '\n') {
	    /* Allow leading blank lines, but anything else is wrong */
	    lines++;
	    had_error++;
	    break;
	}
	if (msg_found) {
	    lines++;
	    if (isoff(glob_flags, READ_ONLY) && fputs(buf, tmpf) == -1) {
		error(tempfile);
		had_error++;
		break;
	    }
	}
    }
#ifndef MSG_SEPARATOR
    if (warn)
	turnon(glob_flags, WARNING);
#endif /* !MSG_SEPARATOR */
    if (msg_found && append != 1)
	turnon(glob_flags, DO_UPDATE);
#ifdef MMDF
    if (!append)
	(void) fputs(END_MSG_SEP, tmpf);
#endif /* MMDF */
    if (had_error) {
	if (!append)
	    msg[cnt] = old;
	if (!msg_found) {
	    if (!append)
		print("File not left in correct message format.\n");
	    else if (cnt == 0) {
		if (buf[0]) 
		    print("\"%s\" does not seem to be a folder\n", file);
		else
		    had_error = 0;	/* empty files are OK */
	    }
	}
    } else {
	if (append)
	    cnt--;
	if (isoff(glob_flags, READ_ONLY))
	    msg[cnt].m_size = ftell(tmpf) - msg[cnt].m_offset;
	else
	    msg[cnt].m_size = ftell(fp) - msg[cnt].m_offset;
	msg[cnt].m_lines = lines;
	/* remember where we were to seek to for when we append new mail */ 
	if (append)
	    cnt++;
    }
    if (append == 1) { /* merge_folders takes care of this for append == 2 */
	(void) fseek(fp, 0L, 2); /* Position at end of file */
	msg[msg_cnt].m_offset = ftell(fp);
    }
    close_lock(file, fp);
    if (isoff(glob_flags, READ_ONLY)) {
#ifndef MSGS_DYNAMIC
	if (had_error && msg_found && append == 1 && cnt == MAXMSGS-append) {
#else
	if (had_error && msg_found && append == 1 && cnt == msg_max-append) {
#endif
	    wprint("Using read-only mode.\n");
	    turnon(glob_flags, READ_ONLY);
	    had_error = 0;	/* return successfully anyway */
	}
	(void) fclose(tmpf);
	if (!(tmpf = fopen(tempfile, "r"))) {
	    error("Unable to open %s for reading", tempfile);
	    return -1;
	}
    }
    return !had_error;
}
